mirror of
https://github.com/GrKoR/esphome_aux_ac_component.git
synced 2025-12-06 11:36:55 +03:00
unfinished
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
# You can modify this file to suit your needs.
|
||||
**/.vscode/
|
||||
**/.esphome/
|
||||
**/esphome/
|
||||
**/.pioenvs/
|
||||
**/.piolibdeps/
|
||||
**/lib/
|
||||
|
||||
148
components/aux_ac/aux_frame.h
Normal file
148
components/aux_ac/aux_frame.h
Normal file
@@ -0,0 +1,148 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h> // for memcpy and memset, move to .cpp later if needed
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_ac
|
||||
{
|
||||
|
||||
class AuxFrame
|
||||
{
|
||||
private:
|
||||
// CRC of the AUX frame
|
||||
// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_crc
|
||||
union frame_crc_t
|
||||
{
|
||||
uint16_t crc16;
|
||||
uint8_t crc[2];
|
||||
};
|
||||
|
||||
// frame header
|
||||
// https://github.com/GrKoR/AUX_HVAC_Protocol#packet_header
|
||||
struct frame_header_t
|
||||
{
|
||||
uint8_t startByte;
|
||||
uint8_t _unknown1;
|
||||
uint8_t frameType;
|
||||
uint8_t wifi;
|
||||
uint8_t pingAnswer;
|
||||
uint8_t _unknown2;
|
||||
uint8_t bodyLength;
|
||||
uint8_t _unknown3;
|
||||
};
|
||||
|
||||
uint8_t *_rawData = nullptr;
|
||||
frame_header_t *_header = nullptr;
|
||||
uint8_t *_body = nullptr;
|
||||
frame_crc_t *_crc = nullptr;
|
||||
|
||||
bool _isValid = false;
|
||||
|
||||
uint8_t const AC_PACKET_START_BYTE = 0xBB;
|
||||
uint8_t const AC_HEADER_SIZE = 8;
|
||||
uint8_t const AC_BODY_LENGTH_OFFSET = 6;
|
||||
|
||||
uint8_t const AC_BUFFER_SIZE = 35; // TODO: integrate it with aux_uart.h
|
||||
|
||||
uint16_t _CRC16(uint8_t *data, uint8_t len)
|
||||
{
|
||||
uint32_t crc = 0;
|
||||
|
||||
uint8_t _crcBuffer[AC_BUFFER_SIZE];
|
||||
memset(_crcBuffer, 0, AC_BUFFER_SIZE);
|
||||
memcpy(_crcBuffer, data, len);
|
||||
|
||||
if ((len % 2) == 1)
|
||||
len++;
|
||||
|
||||
uint32_t word = 0;
|
||||
for (uint8_t i = 0; i < len; i += 2)
|
||||
{
|
||||
word = (_crcBuffer[i] << 8) + _crcBuffer[i + 1];
|
||||
crc += word;
|
||||
}
|
||||
crc = (crc >> 16) + (crc & 0xFFFF);
|
||||
crc = ~crc;
|
||||
|
||||
return crc & 0xFFFF;
|
||||
}
|
||||
|
||||
bool _checkCRC()
|
||||
{
|
||||
frame_crc_t crc;
|
||||
crc.crc16 = _CRC16(this->_rawData, AC_HEADER_SIZE + this->_rawData[AC_BODY_LENGTH_OFFSET]);
|
||||
|
||||
return ((this->_crc->crc[0] == crc.crc[1]) && (this->_crc->crc[1] == crc.crc[0]));
|
||||
}
|
||||
|
||||
void _checkFrame()
|
||||
{
|
||||
this->_isValid = false;
|
||||
if (this->_rawData == nullptr)
|
||||
return;
|
||||
|
||||
if (this->_header->startByte != AC_PACKET_START_BYTE)
|
||||
return;
|
||||
|
||||
if (!this->_checkCRC())
|
||||
return;
|
||||
|
||||
this->_isValid = true;
|
||||
}
|
||||
|
||||
public:
|
||||
AuxFrame() = default;
|
||||
~AuxFrame() {};
|
||||
|
||||
void set_data(uint8_t *data)
|
||||
{
|
||||
clearData();
|
||||
if (data == nullptr)
|
||||
return;
|
||||
|
||||
this->_rawData = data;
|
||||
this->_header = (frame_header_t *)this->_rawData;
|
||||
this->_crc = (frame_crc_t *)(this->_rawData + AC_HEADER_SIZE + this->_header->bodyLength);
|
||||
if (this->_header->bodyLength > 0)
|
||||
this->_body = this->_rawData + AC_HEADER_SIZE;
|
||||
else
|
||||
this->_body = nullptr;
|
||||
this->_checkFrame();
|
||||
}
|
||||
|
||||
void clearData()
|
||||
{
|
||||
this->_rawData = nullptr;
|
||||
this->_header = nullptr;
|
||||
this->_crc = nullptr;
|
||||
this->_body = nullptr;
|
||||
this->_isValid = false;
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return this->_isValid;
|
||||
};
|
||||
|
||||
uint8_t frameSize() const
|
||||
{
|
||||
if (!this->isValid())
|
||||
return 0;
|
||||
|
||||
return AC_HEADER_SIZE + this->_header->bodyLength + 2;
|
||||
}
|
||||
|
||||
uint8_t bodyLength() const
|
||||
{
|
||||
if (!this->isValid())
|
||||
return 0;
|
||||
|
||||
return this->_header->bodyLength;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace aux_ac
|
||||
|
||||
} // namespace esphome
|
||||
100
components/aux_ac/aux_logger.cpp
Normal file
100
components/aux_ac/aux_logger.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include "aux_logger.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_ac
|
||||
{
|
||||
static const char *const TAG = "AirCon"; // TODO: verify if this tag is appropriate
|
||||
|
||||
/** вывод отладочной информации в лог
|
||||
*
|
||||
* dbgLevel - уровень сообщения, определен в ESPHome. За счет его использования можно из ESPHome управлять полнотой сведений в логе.
|
||||
* msg - сообщение, выводимое в лог
|
||||
* line - строка, на которой произошел вызов (удобно при отладке)
|
||||
*/
|
||||
void debugMsg(const /*String &*/ char *msg, uint8_t dbgLevel, unsigned int line, ...)
|
||||
{
|
||||
if (dbgLevel < ESPHOME_LOG_LEVEL_NONE)
|
||||
dbgLevel = ESPHOME_LOG_LEVEL_NONE;
|
||||
if (dbgLevel > ESPHOME_LOG_LEVEL_VERY_VERBOSE)
|
||||
dbgLevel = ESPHOME_LOG_LEVEL_VERY_VERBOSE;
|
||||
|
||||
if (line == 0)
|
||||
line = __LINE__; // если строка не передана, берем текущую строку
|
||||
|
||||
va_list vl;
|
||||
va_start(vl, line);
|
||||
esp_log_vprintf_(dbgLevel, TAG, line, msg, vl);
|
||||
va_end(vl);
|
||||
};
|
||||
|
||||
/** выводим данные пакета в лог для отладки
|
||||
*
|
||||
* dbgLevel - уровень сообщения, определен в ESPHome. За счет его использования можно из ESPHome управлять полнотой сведений в логе.
|
||||
* packet - указатель на пакет для вывода;
|
||||
* если указатель на crc равен nullptr или первый байт в буфере не AC_PACKET_START_BYTE, то считаем, что передан битый пакет
|
||||
* или не пакет вовсе. Для такого выводим только массив байт.
|
||||
* Для нормального пакета данные выводятся с форматированием.
|
||||
* line - строка, на которой произошел вызов (удобно при отладке)
|
||||
**/
|
||||
void debugPrintPacket(packet_t *packet, uint8_t dbgLevel, unsigned int line)
|
||||
{
|
||||
// определяем, полноценный ли пакет нам передан
|
||||
bool notAPacket = false;
|
||||
/*
|
||||
// указатель заголовка всегда установден на начало буфера
|
||||
notAPacket = notAPacket || (packet->crc == nullptr);
|
||||
notAPacket = notAPacket || (packet->data[0] != AC_PACKET_START_BYTE);
|
||||
|
||||
String st = "";
|
||||
char textBuf[11];
|
||||
|
||||
// заполняем время получения пакета
|
||||
memset(textBuf, 0, 11);
|
||||
sprintf(textBuf, "%010" PRIu32, packet->msec);
|
||||
st = st + textBuf + ": ";
|
||||
|
||||
// формируем преамбулы
|
||||
if (packet == &_inPacket)
|
||||
{
|
||||
st += "[<=] "; // преамбула входящего пакета
|
||||
}
|
||||
else if (packet == &_outPacket)
|
||||
{
|
||||
st += "[=>] "; // преамбула исходящего пакета
|
||||
}
|
||||
else
|
||||
{
|
||||
st += "[--] "; // преамбула для "непакета"
|
||||
}
|
||||
|
||||
// формируем данные
|
||||
for (int i = 0; i < packet->bytesLoaded; i++)
|
||||
{
|
||||
// для заголовков нормальных пакетов надо отработать скобки (если они есть)
|
||||
if ((!notAPacket) && (i == 0))
|
||||
st += HOLMES_HEADER_BRACKET_OPEN;
|
||||
// для CRC нормальных пакетов надо отработать скобки (если они есть)
|
||||
if ((!notAPacket) && (i == packet->header->body_length + AC_HEADER_SIZE))
|
||||
st += HOLMES_CRC_BRACKET_OPEN;
|
||||
|
||||
memset(textBuf, 0, 11);
|
||||
sprintf(textBuf, HOLMES_BYTE_FORMAT, packet->data[i]);
|
||||
st += textBuf;
|
||||
|
||||
// для заголовков нормальных пакетов надо отработать скобки (если они есть)
|
||||
if ((!notAPacket) && (i == AC_HEADER_SIZE - 1))
|
||||
st += HOLMES_HEADER_BRACKET_CLOSE;
|
||||
// для CRC нормальных пакетов надо отработать скобки (если они есть)
|
||||
if ((!notAPacket) && (i == packet->header->body_length + AC_HEADER_SIZE + 2 - 1))
|
||||
st += HOLMES_CRC_BRACKET_CLOSE;
|
||||
|
||||
st += HOLMES_DELIMITER;
|
||||
}
|
||||
|
||||
_debugMsg(st, dbgLevel, line);
|
||||
*/
|
||||
}
|
||||
|
||||
} // namespace aux_ac
|
||||
} // namespace esphome
|
||||
15
components/aux_ac/aux_logger.h
Normal file
15
components/aux_ac/aux_logger.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_ac
|
||||
{
|
||||
using packet_t = uint8_t; // TODO: Replace with actual packet_t definition
|
||||
|
||||
void debugMsg(const char *msg, uint8_t dbgLevel, unsigned int line = 0, ...);
|
||||
void debugPrintPacket(packet_t *packet, uint8_t dbgLevel = ESPHOME_LOG_LEVEL_DEBUG, unsigned int line = __LINE__);
|
||||
} // namespace aux_ac
|
||||
} // namespace esphome
|
||||
68
components/aux_ac/aux_uart.cpp
Normal file
68
components/aux_ac/aux_uart.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "aux_uart.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_ac
|
||||
{
|
||||
void AuxUart::send_frame(const std::vector<uint8_t> &frame)
|
||||
{
|
||||
this->write_array(frame);
|
||||
}
|
||||
|
||||
bool AuxUart::read_frame(std::vector<uint8_t> &frame)
|
||||
{
|
||||
// Check if enough data is available
|
||||
if (this->available() < 3)
|
||||
return false;
|
||||
|
||||
// Peek at the first 3 bytes to determine the frame length
|
||||
uint8_t header[3];
|
||||
if (!this->peek_array(header, 3))
|
||||
return false;
|
||||
|
||||
// Validate start byte
|
||||
if (header[0] != 0xAA)
|
||||
{
|
||||
// Invalid start byte, discard one byte and return false
|
||||
uint8_t discard;
|
||||
this->read_byte(&discard);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine frame length from the second byte
|
||||
size_t frame_length = header[1];
|
||||
if (frame_length < 3 || frame_length > AC_BUFFER_SIZE)
|
||||
{
|
||||
// Invalid length, discard one byte and return false
|
||||
uint8_t discard;
|
||||
this->read_byte(&discard);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the full frame is available
|
||||
if (this->available() < frame_length)
|
||||
return false;
|
||||
|
||||
// Read the full frame into the internal buffer
|
||||
if (!this->read_array(this->_data, frame_length))
|
||||
return false;
|
||||
|
||||
// Validate checksum
|
||||
uint8_t checksum = 0;
|
||||
for (size_t i = 0; i < frame_length - 1; i++)
|
||||
{
|
||||
checksum += this->_data[i];
|
||||
}
|
||||
if (checksum != this->_data[frame_length - 1])
|
||||
{
|
||||
// Invalid checksum, discard the frame and return false
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy the valid frame to the output vector
|
||||
frame.assign(this->_data, this->_data + frame_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aux_ac
|
||||
} // namespace esphome
|
||||
30
components/aux_ac/aux_uart.h
Normal file
30
components/aux_ac/aux_uart.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
#define AC_BUFFER_SIZE 35
|
||||
|
||||
using namespace esphome::uart;
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_ac
|
||||
{
|
||||
class AuxUart : public UARTDevice
|
||||
{
|
||||
public:
|
||||
AuxUart() = delete;
|
||||
explicit AuxUart(UARTComponent *parent) : UARTDevice(parent) {}
|
||||
~AuxUart() = default;
|
||||
|
||||
void send_frame(const std::vector<uint8_t> &frame);
|
||||
bool read_frame(std::vector<uint8_t> &frame);
|
||||
|
||||
protected:
|
||||
// Internal buffer for incoming data
|
||||
uint8_t _data[AC_BUFFER_SIZE];
|
||||
};
|
||||
|
||||
} // namespace aux_ac
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,122 @@
|
||||
import logging
|
||||
from esphome.core import CORE, Define
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import uart, climate
|
||||
|
||||
from esphome.const import (
|
||||
CONF_DATA,
|
||||
CONF_ID,
|
||||
CONF_UART_ID,
|
||||
)
|
||||
|
||||
AUX_AC_FIRMWARE_VERSION = '0.2.17'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CODEOWNERS = ["@GrKoR"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
aux_ac_ns = cg.esphome_ns.namespace("aux_ac")
|
||||
|
||||
AuxUart = aux_ac_ns.class_("AuxUart", uart.UARTDevice)
|
||||
|
||||
def output_info(config):
|
||||
_LOGGER.info("AUX_AC firmware version: %s", AUX_AC_FIRMWARE_VERSION)
|
||||
return config
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
uart.UART_DEVICE_SCHEMA
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AuxUart),
|
||||
}
|
||||
),
|
||||
output_info,
|
||||
)
|
||||
|
||||
async def to_code(config):
|
||||
CORE.add_define(
|
||||
Define("AUX_AC_FIRMWARE_VERSION", '"'+AUX_AC_FIRMWARE_VERSION+'"')
|
||||
)
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
# await cg.register_component(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_UART_ID])
|
||||
cg.add(var.initAC(parent))
|
||||
|
||||
if CONF_INDOOR_TEMPERATURE in config:
|
||||
conf = config[CONF_INDOOR_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_indoor_temperature_sensor(sens))
|
||||
|
||||
if CONF_OUTDOOR_TEMPERATURE in config:
|
||||
conf = config[CONF_OUTDOOR_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_outdoor_temperature_sensor(sens))
|
||||
|
||||
if CONF_OUTBOUND_TEMPERATURE in config:
|
||||
conf = config[CONF_OUTBOUND_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_outbound_temperature_sensor(sens))
|
||||
|
||||
if CONF_INBOUND_TEMPERATURE in config:
|
||||
conf = config[CONF_INBOUND_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_inbound_temperature_sensor(sens))
|
||||
|
||||
if CONF_COMPRESSOR_TEMPERATURE in config:
|
||||
conf = config[CONF_COMPRESSOR_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_compressor_temperature_sensor(sens))
|
||||
|
||||
if CONF_VLOUVER_STATE in config:
|
||||
conf = config[CONF_VLOUVER_STATE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_vlouver_state_sensor(sens))
|
||||
|
||||
if CONF_DISPLAY_STATE in config:
|
||||
conf = config[CONF_DISPLAY_STATE]
|
||||
sens = await binary_sensor.new_binary_sensor(conf)
|
||||
cg.add(var.set_display_sensor(sens))
|
||||
|
||||
if CONF_DEFROST_STATE in config:
|
||||
conf = config[CONF_DEFROST_STATE]
|
||||
sens = await binary_sensor.new_binary_sensor(conf)
|
||||
cg.add(var.set_defrost_state(sens))
|
||||
|
||||
if CONF_INVERTER_POWER in config:
|
||||
conf = config[CONF_INVERTER_POWER]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_inverter_power_sensor(sens))
|
||||
|
||||
if CONF_PRESET_REPORTER in config:
|
||||
conf = config[CONF_PRESET_REPORTER]
|
||||
sens = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(var.set_preset_reporter_sensor(sens))
|
||||
|
||||
if CONF_INVERTER_POWER_LIMIT_VALUE in config:
|
||||
conf = config[CONF_INVERTER_POWER_LIMIT_VALUE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_inverter_power_limit_value_sensor(sens))
|
||||
|
||||
if CONF_INVERTER_POWER_LIMIT_STATE in config:
|
||||
conf = config[CONF_INVERTER_POWER_LIMIT_STATE]
|
||||
sens = await binary_sensor.new_binary_sensor(conf)
|
||||
cg.add(var.set_inverter_power_limit_state_sensor(sens))
|
||||
|
||||
cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds))
|
||||
cg.add(var.set_show_action(config[CONF_SHOW_ACTION]))
|
||||
cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED]))
|
||||
cg.add(var.set_packet_timeout(config[CONF_TIMEOUT]))
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
if CONF_SUPPORTED_MODES in config:
|
||||
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
|
||||
if CONF_SUPPORTED_SWING_MODES in config:
|
||||
cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES]))
|
||||
if CONF_SUPPORTED_PRESETS in config:
|
||||
cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS]))
|
||||
if CONF_CUSTOM_PRESETS in config:
|
||||
cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS]))
|
||||
if CONF_CUSTOM_FAN_MODES in config:
|
||||
cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES]))
|
||||
|
||||
Reference in New Issue
Block a user