diff --git a/.gitignore b/.gitignore index 9afcb24..89bb977 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ **/__pycache__/** +**.DS_Store \ No newline at end of file diff --git a/components/panasonic_heatpump/__init__.py b/components/panasonic_heatpump/__init__.py new file mode 100644 index 0000000..ef71476 --- /dev/null +++ b/components/panasonic_heatpump/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID, CONF_ADDRESS + +CODEOWNERS = ['@elvit'] +MULTICONF = True +DEPENDENCIES = ['uart'] + +CONF_PANASONIC_HEATPUMP_ID = "panasonic_heatpump" +CONF_UART_HP = "uart_hp" +CONF_UART_WM = "uart_wm" + +panasonic_heatpump_ns = cg.esphome_ns.namespace('panasonic_heatpump') +PanasonicHeatpumpComponent = panasonic_heatpump_ns.class_('PanasonicHeatpumpComponent', cg.Component) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(PanasonicHeatpumpComponent), + + cv.Required(CONF_UART_HP): cv.use_id(uart.UARTComponent), + cv.Required(CONF_UART_WM): cv.use_id(uart.UARTComponent), + } + ).extend(cv.COMPONENT_SCHEMA) +) + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + uart_hp = await cg.get_variable(config[CONF_UART_HP]) + cg.add(var.set_uart_hp(uart_hp)) + + uart_wm = await cg.get_variable(config[CONF_UART_WM]) + cg.add(var.set_uart_wm(uart_wm)) diff --git a/components/panasonic_heatpump/binary_sensor.py b/components/panasonic_heatpump/binary_sensor.py new file mode 100755 index 0000000..ae28dfc --- /dev/null +++ b/components/panasonic_heatpump/binary_sensor.py @@ -0,0 +1,72 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + DEVICE_CLASS_HEAT, +) +from . import PanasonicHeatpumpComponent, CONF_PANASONIC_HEATPUMP_ID + +CONF_BIN_SENS_TOP0 = "top0" +CONF_BIN_SENS_TOP2 = "top2" +CONF_BIN_SENS_TOP3 = "top3" +CONF_BIN_SENS_TOP13 = "top13" +CONF_BIN_SENS_TOP26 = "top26" +CONF_BIN_SENS_TOP60 = "top60" +CONF_BIN_SENS_TOP61 = "top61" +CONF_BIN_SENS_TOP68 = "top68" +CONF_BIN_SENS_TOP69 = "top69" +CONF_BIN_SENS_TOP99 = "top99" +CONF_BIN_SENS_TOP100 = "top100" +CONF_BIN_SENS_TOP108 = "top108" +CONF_BIN_SENS_TOP109 = "top109" +CONF_BIN_SENS_TOP110 = "top110" + +TYPES = [ + CONF_BIN_SENS_TOP0, + CONF_BIN_SENS_TOP2, + CONF_BIN_SENS_TOP3, + CONF_BIN_SENS_TOP13, + CONF_BIN_SENS_TOP26, + CONF_BIN_SENS_TOP60, + CONF_BIN_SENS_TOP61, + CONF_BIN_SENS_TOP68, + CONF_BIN_SENS_TOP69, + CONF_BIN_SENS_TOP99, + CONF_BIN_SENS_TOP100, + CONF_BIN_SENS_TOP108, + CONF_BIN_SENS_TOP109, + CONF_BIN_SENS_TOP110, +] + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_PANASONIC_HEATPUMP_ID): cv.use_id(PanasonicHeatpumpComponent), + + cv.Optional(CONF_BIN_SENS_TOP0): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP2): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP3): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP13): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP26): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP60): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP61): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP68): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP69): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP99): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP100): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP108): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP109): binary_sensor.binary_sensor_schema(), + cv.Optional(CONF_BIN_SENS_TOP110): binary_sensor.binary_sensor_schema(), + } + ).extend(cv.COMPONENT_SCHEMA) +) + +async def setup_conf(config, key, hub): + if sensor_config := config.get(key): + var = await binary_sensor.new_binary_sensor(sensor_config) + cg.add(getattr(hub, f"set_{key}_binary_sensor")(var)) + +async def to_code(config): + hub = await cg.get_variable(config[CONF_PANASONIC_HEATPUMP_ID]) + for key in TYPES: + await setup_conf(config, key, hub) diff --git a/components/panasonic_heatpump/decode.cpp b/components/panasonic_heatpump/decode.cpp new file mode 100755 index 0000000..27cd208 --- /dev/null +++ b/components/panasonic_heatpump/decode.cpp @@ -0,0 +1,256 @@ +#include "decode.h" + + +constexpr const char* const PanasonicDecode::DisabledEnabled[]; +constexpr const char* const PanasonicDecode::BlockedFree[]; +constexpr const char* const PanasonicDecode::OffOn[]; +constexpr const char* const PanasonicDecode::InactiveActive[]; +constexpr const char* const PanasonicDecode::PumpFlowRateMode[]; +constexpr const char* const PanasonicDecode::HolidayState[]; +constexpr const char* const PanasonicDecode::OpModeDesc[]; +constexpr const char* const PanasonicDecode::Powerfulmode[]; +constexpr const char* const PanasonicDecode::Quietmode[]; +constexpr const char* const PanasonicDecode::Valve[]; +constexpr const char* const PanasonicDecode::MixingValve[]; +constexpr const char* const PanasonicDecode::ZonesState[]; +constexpr const char* const PanasonicDecode::HeatCoolModeDesc[]; +constexpr const char* const PanasonicDecode::SolarModeDesc[]; +constexpr const char* const PanasonicDecode::ZonesSensorType[]; +constexpr const char* const PanasonicDecode::LiquidType[]; +constexpr const char* const PanasonicDecode::ExtPadHeaterType[]; +constexpr const char* const PanasonicDecode::Bivalent[]; +constexpr const char* const PanasonicDecode::ModelNames[]; + + +int PanasonicDecode::getBit1(uint8_t input) +{ + return (input >> 7); +} + +int PanasonicDecode::getBit1and2(uint8_t input) +{ + return ((input >> 6) - 1); +} + +int PanasonicDecode::getBit3and4(uint8_t input) +{ + return (((input >> 4) & 0b11) - 1); +} + +int PanasonicDecode::getBit5and6(uint8_t input) +{ + return (((input >> 2) & 0b11) - 1); +} + +int PanasonicDecode::getBit7and8(uint8_t input) +{ + return ((input & 0b11) - 1); +} + +int PanasonicDecode::getBit3and4and5(uint8_t input) +{ + return (((input >> 3) & 0b111) - 1); +} + +int PanasonicDecode::getLeft5bits(uint8_t input) +{ + return ((input >> 3) - 1); +} + +int PanasonicDecode::getRight3bits(uint8_t input) +{ + return ((input & 0b111) - 1); +} + +int PanasonicDecode::getIntMinus1(uint8_t input) +{ + int value = (int)input - 1; + return value; +} + +int PanasonicDecode::getIntMinus128(uint8_t input) +{ + int value = (int)input - 128; + return value; +} + +float PanasonicDecode::getIntMinus1Div5(uint8_t input) +{ + return ((((float)input - 1) / 5), 1); +} + +float PanasonicDecode::getIntMinus1Div50(uint8_t input) +{ + return ((((float)input - 1) / 50), 2); +} + +int PanasonicDecode::getIntMinus1Times10(uint8_t input) +{ + int value = (int)input - 1; + return (value * 10); +} + +int PanasonicDecode::getIntMinus1Times50(uint8_t input) +{ + int value = (int)input - 1; + return (value * 50); +} + +int PanasonicDecode::getFirstByte(uint8_t input) +{ + return ((input >> 4) - 1); +} + +int PanasonicDecode::getSecondByte(uint8_t input) +{ + return ((input & 0b1111) - 1); +} + +int PanasonicDecode::getWord(uint8_t low, uint8_t hi) +{ + return ((hi << 8) + low) - 1; +} + +int PanasonicDecode::getUintt16(uint8_t input1, uint8_t input2) +{ + uint16_t value = static_cast((input2 << 8) | input1); + return (value - 1); +} + +// TOP15, TOP16, TOP38, TOP39, TOP40, TOP41 // +int PanasonicDecode::getPower(uint8_t input) +{ + int value = ((int)input - 1) * 200; + return value; +} + +// TOP4 // +int PanasonicDecode::getOpMode(uint8_t input) +{ + switch ((int)(input & 0b111111)) + { + case 18: + return 0; + case 19: + return 1; + case 25: + return 2; + case 33: + return 3; + case 34: + return 4; + case 35: + return 5; + case 41: + return 6; + case 26: + return 7; + case 42: + return 8; + default: + return -1; + } +} + +// TOP124 // +int PanasonicDecode::getBivalent(uint8_t input) +{ + switch ((int)input) + { + case 0x55: + return 0; + case 0x56: + return 1; + case 0x57: + return 2; + case 0x58: + return 3; + case 0x59: + return 4; + case 0x5A: + return 5; + default: + return -1; + } +} + +// TOP92 // +int PanasonicDecode::getModel(std::vector& data, uint8_t index) +{ + uint8_t model[10] = { data[index], data[index+1], data[index+2], data[index+3], data[index+4], data[index+5], data[index+6], data[index+7], data[index+8], data[index+9] }; + int result = -1; + int size = sizeof(KnownModels) / sizeof(KnownModels[0]); + + for (unsigned int i = 0 ; i < size; i++) + { + if (memcmp(model, KnownModels[i], 10) == 0) + { + result = i; + break; + } + } + + return result; +} + +// TOP1 // +// input1 = data[169] +// input2 = data[170] +float PanasonicDecode::getPumpFlow(uint8_t input1, uint8_t input2) +{ + int PumpFlow2 = (int)input2; + float PumpFlow1 = (((float)input1 - 1) / 256); + float PumpFlow = PumpFlow2 + PumpFlow1; + return PumpFlow; +} + +// TOP5, TOP6 // +float PanasonicDecode::getFractional(uint8_t input, uint8_t shift) +{ + float result; + int fractional = (int)((input >> shift) & 0b111) - 1; + result = fractional * 0.25; + return result; +} + +// TOP44 // +// errorType = data[113] +// errorNumber = data[114] +std::string PanasonicDecode::getErrorInfo(uint8_t errorType, uint8_t errorNumber) +{ + int error_type = (int)(errorType); + int error_number = ((int)(errorNumber)) - 17; + char error_string[10]; + + switch (error_type) + { + case 177: // B1=F type error + sprintf(error_string, "F%02X", error_number); + break; + case 161: // A1=H type error + sprintf(error_string, "H%02X", error_number); + break; + default: + sprintf(error_string, "No error"); + break; + } + + return error_string; +} + +bool PanasonicDecode::getBoolState(uint8_t input) +{ + return input != 0; +} + +std::string PanasonicDecode::getTextState(const char* const array[], int index) +{ + int size = atoi(array[0]); + + if ((index < 0) || (index >= size)) + { + return "UNKNOWN"; + } + + return array[index + 1]; +} diff --git a/components/panasonic_heatpump/decode.h b/components/panasonic_heatpump/decode.h new file mode 100755 index 0000000..7c59bbc --- /dev/null +++ b/components/panasonic_heatpump/decode.h @@ -0,0 +1,181 @@ +#pragma once +#include +#include +#include +#include +#include + +#define NUMBER_OF_MODELS 51 +#define NUMBER_OF_TOPICS 127 +#define NUMBER_OF_TOPICS_EXTRA 6 +#define NUMBER_OF_OPT_TOPICS 7 +#define MAX_TOPIC_LEN 42 + + +class PanasonicDecode +{ +public: + PanasonicDecode() = delete; + + static int getBit1(uint8_t input); + static int getBit1and2(uint8_t input); + static int getBit3and4(uint8_t input); + static int getBit5and6(uint8_t input); + static int getBit7and8(uint8_t input); + static int getBit3and4and5(uint8_t input); + static int getLeft5bits(uint8_t input); + static int getRight3bits(uint8_t input); + static int getIntMinus1(uint8_t input); + static int getIntMinus128(uint8_t input); + static float getIntMinus1Div5(uint8_t input); + static float getIntMinus1Div50(uint8_t input); + static int getIntMinus1Times10(uint8_t input); + static int getIntMinus1Times50(uint8_t input); + static int getFirstByte(uint8_t input); + static int getSecondByte(uint8_t input); + static int getWord(uint8_t hi, uint8_t low); + static int getUintt16(uint8_t input1, uint8_t input2); + + static int getPower(uint8_t input); + static int getOpMode(uint8_t input); + static int getModel(std::vector& data, uint8_t index); + static int getBivalent(uint8_t input); + static float getPumpFlow(uint8_t input1, uint8_t input2); + static float getFractional(uint8_t input, uint8_t shift); + static std::string getErrorInfo(uint8_t errorType, uint8_t errorNumber); + + static bool getBoolState(uint8_t input); + static std::string getTextState(const char* const array[], int index); + + + static const constexpr char* const DisabledEnabled[] = {"2", "Disabled", "Enabled"}; + static const constexpr char* const BlockedFree[] = {"2", "Blocked", "Free"}; + static const constexpr char* const OffOn[] = {"2", "Off", "On"}; + static const constexpr char* const InactiveActive[] = {"2", "Inactive", "Active"}; + static const constexpr char* const PumpFlowRateMode[] = {"2", "DeltaT", "Max flow"}; + static const constexpr char* const HolidayState[] = {"3", "Off", "Scheduled", "Active"}; + static const constexpr char* const OpModeDesc[] = {"9", "Heat", "Cool", "Auto(heat)", "DHW", "Heat+DHW", "Cool+DHW", "Auto(heat)+DHW", "Auto(cool)", "Auto(cool)+DHW"}; + static const constexpr char* const Powerfulmode[] = {"4", "Off", "30min", "60min", "90min"}; + static const constexpr char* const Quietmode[] = {"4", "Off", "Level 1", "Level 2", "Level 3"}; + static const constexpr char* const Valve[] = {"2", "Room", "DHW"}; + static const constexpr char* const MixingValve[] = {"4", "Off", "Increase","Nothing","Decrease"}; + static const constexpr char* const ZonesState[] = {"3", "Zone1 active", "Zone2 active", "Zone1 and zone2 active"}; + static const constexpr char* const HeatCoolModeDesc[] = {"2", "Comp. Curve", "Direct"}; + static const constexpr char* const SolarModeDesc[] = {"3", "Disabled", "Buffer", "DHW"}; + static const constexpr char* const ZonesSensorType[] = {"4", "Water Temperature", "External Thermostat", "Internal Thermostat", "Thermistor"}; + static const constexpr char* const LiquidType[] = {"2", "Water", "Glycol"}; + static const constexpr char* const ExtPadHeaterType[] = {"3", "Disabled", "Type-A","Type-B"}; + static const constexpr char* const Bivalent[] = {"6", "Off", "Alternativ", "A-Off", "Parallel", "P-Off", "Parallel Advanced"}; + + static const constexpr char* const ModelNames[] = + { + "51", //string representation of number of known models (last model number + 1) + "WH-MDC05H3E5", //0 + "WH-MDC07H3E5", //1 + "IDU:WH-SXC09H3E5, ODU:WH-UX09HE5", //2 + "IDU:WH-SDC09H3E8, ODU:WH-UD09HE8", //3 + "IDU:WH-SXC09H3E8, ODU:WH-UX09HE8", //4 + "IDU:WH-SXC12H9E8, ODU:WH-UX12HE8", //5 + "IDU:WH-SXC16H9E8, ODU:WH-UX16HE8", //6 + "IDU:WH-SDC05H3E5, ODU:WH-UD05HE5", //7 + "IDU:WH-SDC0709J3E5, ODU:WH-UD09JE5", //8 + "WH-MDC05J3E5", //9 + "WH-MDC09H3E5", //10 + "WH-MXC09H3E5", //11 + "IDU:WH-ADC0309J3E5, ODU:WH-UD09JE5", //12 + "IDU:WH-ADC0916H9E8, ODU:WH-UX12HE8", //13 + "IDU:WH-SQC09H3E8, ODU:WH-UQ09HE8", //14 + "IDU:WH-SDC09H3E5, ODU:WH-UD09HE5", //15 + "IDU:WH-ADC0309H3E5, ODU:WH-UD09HE5", //16 + "IDU:WH-ADC0309J3E5, ODU:WH-UD05JE5", //17 + "IDU:WH-SDC0709J3E5, ODU:WH-UD07JE5", //18 + "IDU:WH-SDC07H3E5-1, ODU:WH-UD07HE5-1", //19 + "WH-MDC07J3E5", //20 + "WH-MDC09J3E5", //21 + "IDU:WH-SDC0305J3E5, ODU:WH-UD05JE5", //22 + "WH-MXC09J3E8", //23 + "WH-MXC12J9E8", //24 + "IDU:WH-ADC1216H6E5, ODU:WH-UD12HE5", //25 + "IDU:WH-ADC0309J3E5C, ODU:WH-UD07JE5", //26 + "WH-MDC07J3E5", //27 + "WH-MDC05J3E5", //28 + "IDU:WH-UQ12HE8, ODU:WH-SQC12H9E8", //29 + "IDU:WH-SXC12H6E5, ODU:WH-UX12HE5", //30 + "WH-MDC09J3E5", //31 + "WH-MXC09J3E5", //32 + "IDU:WH-ADC1216H6E5C ODU:WH-UD12HE5", //33 + "IDU:WH-ADC0509L3E5 ODU:WH-WDG07LE5", //34 + "IDU:WH-SXC09H3E8 ODU:WH-UX09HE8", //35 + "IDU:WH-ADC0309K3E5AN ODU:WH-UDZ07KE5", //36 + "IDU:WH-SDC0309K3E5 ODU:WH-UDZ05KE5", //37 + "IDU:WH-SDC0509L3E5 ODU:WH-WDG09LE5", //38 + "IDU:WH-SDC12H9E8 ODU:WH-UD12HE8", //39 + "IDU:WH-SDC0309K3E5, ODU:WH-UDZ07KE5", //40 + "IDU:WH-ADC0916H9E8, ODU:WH-UX16HE8", //41 + "IDU:WH-ADC0912H9E8, ODU:WH-UX12HE8", //42 + "WH-MXC16J9E8", //43 + "WH-MXC12J6E5", //44 + "IDU:WH-SQC09H3E8, ODU:WH-UQ09HE8", //45 + "IDU:WH-ADC0309K3E5 ODU:WH-UDZ09KE5", //46 + "IDU:WH-ADC0916H9E8, ODU:WH-UX12HE8", //47 + "IDU:WH-SDC0509L3E5 ODU:WH-WDG07LE5", //48 + "IDU:WH-SXC09H3E5, ODU:WH-UX09HE5", //49 + "IDU:WH-SXC12H9E8, ODU:WH-UX12HE8", //50 + }; + + // stores the bytes #129 to #138 of known models in the same order as the ModelNames above + static constexpr uint8_t KnownModels[NUMBER_OF_MODELS][10] = + { + 0xE2, 0xCF, 0x0B, 0x13, 0x33, 0x32, 0xD1, 0x0C, 0x16, 0x33, //0 + 0xE2, 0xCF, 0x0B, 0x14, 0x33, 0x42, 0xD1, 0x0B, 0x17, 0x33, //1 + 0xE2, 0xCF, 0x0D, 0x77, 0x09, 0x12, 0xD0, 0x0B, 0x05, 0x11, //2 + 0xE2, 0xCF, 0x0C, 0x88, 0x05, 0x12, 0xD0, 0x0B, 0x97, 0x05, //3 + 0xE2, 0xCF, 0x0D, 0x85, 0x05, 0x12, 0xD0, 0x0C, 0x94, 0x05, //4 + 0xE2, 0xCF, 0x0D, 0x86, 0x05, 0x12, 0xD0, 0x0C, 0x95, 0x05, //5 + 0xE2, 0xCF, 0x0D, 0x87, 0x05, 0x12, 0xD0, 0x0C, 0x96, 0x05, //6 + 0xE2, 0xCE, 0x0D, 0x71, 0x81, 0x72, 0xCE, 0x0C, 0x92, 0x81, //7 + 0x62, 0xD2, 0x0B, 0x43, 0x54, 0x42, 0xD2, 0x0B, 0x72, 0x66, //8 + 0xC2, 0xD3, 0x0B, 0x33, 0x65, 0xB2, 0xD3, 0x0B, 0x94, 0x65, //9 + 0xE2, 0xCF, 0x0B, 0x15, 0x33, 0x42, 0xD1, 0x0B, 0x18, 0x33, //10 + 0xE2, 0xCF, 0x0B, 0x41, 0x34, 0x82, 0xD1, 0x0B, 0x31, 0x35, //11 + 0x62, 0xD2, 0x0B, 0x45, 0x54, 0x42, 0xD2, 0x0B, 0x47, 0x55, //12 + 0xE2, 0xCF, 0x0C, 0x74, 0x09, 0x12, 0xD0, 0x0D, 0x95, 0x05, //13 + 0xE2, 0xCF, 0x0B, 0x82, 0x05, 0x12, 0xD0, 0x0C, 0x91, 0x05, //14 + 0xE2, 0xCF, 0x0C, 0x55, 0x14, 0x12, 0xD0, 0x0B, 0x15, 0x08, //15 + 0xE2, 0xCF, 0x0C, 0x43, 0x00, 0x12, 0xD0, 0x0B, 0x15, 0x08, //16 + 0x62, 0xD2, 0x0B, 0x45, 0x54, 0x32, 0xD2, 0x0C, 0x45, 0x55, //17 + 0x62, 0xD2, 0x0B, 0x43, 0x54, 0x42, 0xD2, 0x0C, 0x46, 0x55, //18 + 0xE2, 0xCF, 0x0C, 0x54, 0x14, 0x12, 0xD0, 0x0B, 0x14, 0x08, //19 + 0xC2, 0xD3, 0x0B, 0x34, 0x65, 0xB2, 0xD3, 0x0B, 0x95, 0x65, //20 + 0xC2, 0xD3, 0x0B, 0x35, 0x65, 0xB2, 0xD3, 0x0B, 0x96, 0x65, //21 + 0x62, 0xD2, 0x0B, 0x41, 0x54, 0x32, 0xD2, 0x0C, 0x45, 0x55, //22 + 0x32, 0xD4, 0x0B, 0x87, 0x84, 0x73, 0x90, 0x0C, 0x84, 0x84, //23 + 0x32, 0xD4, 0x0B, 0x88, 0x84, 0x73, 0x90, 0x0C, 0x85, 0x84, //24 + 0xE2, 0xCF, 0x0B, 0x75, 0x09, 0x12, 0xD0, 0x0C, 0x06, 0x11, //25 + 0x42, 0xD4, 0x0B, 0x83, 0x71, 0x42, 0xD2, 0x0C, 0x46, 0x55, //26 + 0xC2, 0xD3, 0x0C, 0x34, 0x65, 0xB2, 0xD3, 0x0B, 0x95, 0x65, //27 + 0xC2, 0xD3, 0x0C, 0x33, 0x65, 0xB2, 0xD3, 0x0B, 0x94, 0x65, //28 + 0xE2, 0xCF, 0x0B, 0x83, 0x05, 0x12, 0xD0, 0x0D, 0x92, 0x05, //29 + 0xE2, 0xCF, 0x0C, 0x78, 0x09, 0x12, 0xD0, 0x0B, 0x06, 0x11, //30 + 0xC2, 0xD3, 0x0C, 0x35, 0x65, 0xB2, 0xD3, 0x0B, 0x96, 0x65, //31 + 0x32, 0xD4, 0x0B, 0x99, 0x77, 0x62, 0x90, 0x0B, 0x01, 0x78, //32 + 0x42, 0xD4, 0x0B, 0x15, 0x76, 0x12, 0xD0, 0x0B, 0x10, 0x11, //33 + 0xE2, 0xD5, 0x0C, 0x29, 0x99, 0x83, 0x92, 0x0C, 0x28, 0x98, //34 + 0xE2, 0xCF, 0x0D, 0x85, 0x05, 0x12, 0xD0, 0x0E, 0x94, 0x05, //35 + 0xE2, 0xD5, 0x0D, 0x36, 0x99, 0x02, 0xD6, 0x0F, 0x67, 0x95, //36 + 0xE2, 0xD5, 0x0B, 0x08, 0x95, 0x02, 0xD6, 0x0E, 0x66, 0x95, //37 + 0xE2, 0xD5, 0x0B, 0x34, 0x99, 0x83, 0x92, 0x0C, 0x29, 0x98, //38 + 0xE2, 0xCF, 0x0C, 0x89, 0x05, 0x12, 0xD0, 0x0C, 0x98, 0x05, //39 + 0xE2, 0xD5, 0x0B, 0x08, 0x95, 0x02, 0xD6, 0x0E, 0x67, 0x95, //40 + 0xE2, 0xCF, 0x0C, 0x74, 0x09, 0x12, 0xD0, 0x0C, 0x96, 0x05, //41 + 0xE2, 0xCF, 0x0C, 0x74, 0x09, 0x12, 0xD0, 0x0E, 0x95, 0x05, //42 + 0x32, 0xD4, 0x0B, 0x89, 0x84, 0x73, 0x90, 0x0C, 0x86, 0x84, //43 + 0x32, 0xD4, 0x0B, 0x00, 0x78, 0x62, 0x90, 0x0B, 0x02, 0x78, //44 + 0xE2, 0xCF, 0x0B, 0x82, 0x05, 0x12, 0xD0, 0x0D, 0x91, 0x05, //45 + 0xE2, 0xD5, 0x0D, 0x99, 0x94, 0x02, 0xD6, 0x0D, 0x68, 0x95, //46 + 0xE2, 0xCF, 0x0C, 0x74, 0x09, 0x12, 0xD0, 0x0C, 0x95, 0x05, //47 + 0xE2, 0xD5, 0x0B, 0x34, 0x99, 0x83, 0x92, 0x0C, 0x28, 0x98, //48 + 0xE2, 0xCF, 0x0D, 0x77, 0x09, 0x12, 0xD0, 0x0C, 0x05, 0x11, //49 + 0xE2, 0xCF, 0x0D, 0x86, 0x05, 0x12, 0xD0, 0x0E, 0x95, 0x05, //50 + }; +}; diff --git a/components/panasonic_heatpump/panasonic_heatpump.cpp b/components/panasonic_heatpump/panasonic_heatpump.cpp new file mode 100644 index 0000000..6ae00bc --- /dev/null +++ b/components/panasonic_heatpump/panasonic_heatpump.cpp @@ -0,0 +1,293 @@ +#include "panasonic_heatpump.h" +#include "esphome/core/log.h" + + +namespace esphome +{ + namespace panasonic_heatpump + { + static const char *const TAG = "panasonic_heatpump"; + + static const uint8_t RESPONSE_DATA_SIZE = 203; + static const uint8_t REQUEST_DATA_SIZE = 111; + + void PanasonicHeatpumpComponent::dump_config() + { + ESP_LOGCONFIG(TAG, "Panasonic Heatpump"); + } + + void PanasonicHeatpumpComponent::loop() + { + uint8_t byte_hp; + uint8_t byte_wm; + + while (this->uart_hp_->available()) + { + this->uart_hp_->read_byte(&byte_hp); + this->uart_wm_->write_byte(byte_hp); + + if (!this->response_receiving_) + { + // Message shall start with 0x71, if not skip this byte + if (byte_hp != 0x71) + continue; + this->response_receiving_ = true; + } + // Add current byte to message buffer + this->response_data_.push_back(byte_hp); + // 2. bytes contains the payload size + if (this->response_data_.size() == 2) + this->response_payload_length_ = byte_hp; + // Discard message if 3. and 4. byte are not as expected + if (this->response_data_.size() == 3 && byte_hp != 0x01 || + this->response_data_.size() == 4 && byte_hp != 0x10) + { + ESP_LOGW(TAG, "Invalid response message: %d. byte is 0x%02X but expexted is 0x%02X", + response_data_.size(), byte_hp, response_data_.size() == 3 ? "01" : "10"); + delay(10); + this->response_data_.clear(); + this->response_receiving_ = false; + continue; + } + // Decode message if message is complete + if (this->response_data_.size() > 2 && this->response_data_.size() == this->response_payload_length_ + 3) + { + this->log_uart_hex("<<<", this->response_data_, ','); + this->decode_response_(this->response_data_); + this->response_data_.clear(); + this->response_receiving_ = false; + } + } + + while (this->uart_wm_->available()) + { + this->uart_wm_->read_byte(&byte_wm); + this->uart_hp_->write_byte(byte_wm); + + if (!this->request_receiving_) + { + // Message shall start with 0x71, if not skip this byte + if (byte_wm != 0x71) + continue; + this->request_receiving_ = true; + } + // Add current byte to message buffer + this->request_data_.push_back(byte_wm); + // 2. bytes contains the payload size + if (this->request_data_.size() == 2) + this->request_payload_length_ = byte_wm; + // Discard message if 3. and 4. byte are not as expected + if (this->request_data_.size() == 3 && byte_wm != 0x01 || + this->request_data_.size() == 4 && byte_wm != 0x10) + { + ESP_LOGW(TAG, "Invalid request message: %d. byte is 0x%02X but expexted is 0x%02X", + request_data_.size(), byte_wm, request_data_.size() == 3 ? "01" : "10"); + delay(10); + this->request_data_.clear(); + this->request_receiving_ = false; + continue; + } + // Decode message if message is complete + if (this->request_data_.size() > 2 && this->request_data_.size() == this->request_payload_length_ + 3) + { + this->log_uart_hex(">>>", this->request_data_, ','); + this->request_data_.clear(); + this->request_receiving_ = false; + } + } + } + + void PanasonicHeatpumpComponent::decode_response_(std::vector bytes) + { + // Read response message: + // format: 0x71 [payload_length] 0x01 0x10 [[TOP0 - TOP114] ...] 0x00 [checksum] + // payload_length: payload_length + 3 = packet_length + // checksum: if (sum(all bytes) & 0xFF == 0) ==> valid packet + + if (bytes.size() != RESPONSE_DATA_SIZE) + { + ESP_LOGW(TAG, "Invalid response message length: recieved %d - expected %d", bytes.size(), RESPONSE_DATA_SIZE); + delay(10); + return; + } + + uint8_t checksum = 0; + for (int i = 0; i < bytes.size(); i++) + { + checksum += bytes[i]; + } + // checksum = checksum & 0xFF; + if (checksum != 0) + { + ESP_LOGW(TAG, "Invalid response message: checksum = 0x%02X, last_byte = 0x%02X", checksum, bytes[202]); + delay(10); + return; + } + +#ifdef USE_SENSOR + if (this->top1_sensor_) this->top1_sensor_->publish_state(PanasonicDecode::getPumpFlow(bytes[169], bytes[170])); + if (this->top5_sensor_) this->top5_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[143]) + PanasonicDecode::getFractional(bytes[118], 0)); + if (this->top6_sensor_) this->top6_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[144]) + PanasonicDecode::getFractional(bytes[118], 3)); + if (this->top7_sensor_) this->top7_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[153])); + if (this->top8_sensor_) this->top8_sensor_->publish_state(PanasonicDecode::getIntMinus1(bytes[166])); + if (this->top9_sensor_) this->top9_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[42])); + if (this->top10_sensor_) this->top10_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[141])); + if (this->top11_sensor_) this->top11_sensor_->publish_state(PanasonicDecode::getWord(bytes[182], bytes[183])); + if (this->top12_sensor_) this->top12_sensor_->publish_state(PanasonicDecode::getWord(bytes[179], bytes[180])); + if (this->top14_sensor_) this->top14_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[142])); + if (this->top15_sensor_) this->top15_sensor_->publish_state(PanasonicDecode::getPower(bytes[194])); + if (this->top16_sensor_) this->top16_sensor_->publish_state(PanasonicDecode::getPower(bytes[193])); + if (this->top21_sensor_) this->top21_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[158])); + if (this->top22_sensor_) this->top22_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[99])); + if (this->top23_sensor_) this->top23_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[84])); + if (this->top24_sensor_) this->top24_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[94])); + if (this->top25_sensor_) this->top25_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[44])); + if (this->top27_sensor_) this->top27_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[38])); + if (this->top28_sensor_) this->top28_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[39])); + if (this->top29_sensor_) this->top29_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[75])); + if (this->top30_sensor_) this->top30_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[76])); + if (this->top31_sensor_) this->top31_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[78])); + if (this->top32_sensor_) this->top32_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[77])); + if (this->top33_sensor_) this->top33_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[156])); + if (this->top34_sensor_) this->top34_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[40])); + if (this->top35_sensor_) this->top35_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[41])); + if (this->top36_sensor_) this->top36_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[145])); + if (this->top37_sensor_) this->top37_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[146])); + if (this->top38_sensor_) this->top38_sensor_->publish_state(PanasonicDecode::getPower(bytes[196])); + if (this->top39_sensor_) this->top39_sensor_->publish_state(PanasonicDecode::getPower(bytes[195])); + if (this->top40_sensor_) this->top40_sensor_->publish_state(PanasonicDecode::getPower(bytes[198])); + if (this->top41_sensor_) this->top41_sensor_->publish_state(PanasonicDecode::getPower(bytes[197])); + if (this->top42_sensor_) this->top42_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[147])); + if (this->top43_sensor_) this->top43_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[148])); + if (this->top45_sensor_) this->top45_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[43])); + if (this->top46_sensor_) this->top46_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[149])); + if (this->top47_sensor_) this->top47_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[150])); + if (this->top48_sensor_) this->top48_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[151])); + if (this->top49_sensor_) this->top49_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[154])); + if (this->top50_sensor_) this->top50_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[155])); + if (this->top51_sensor_) this->top51_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[157])); + if (this->top52_sensor_) this->top52_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[159])); + if (this->top53_sensor_) this->top53_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[160])); + if (this->top54_sensor_) this->top54_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[161])); + if (this->top55_sensor_) this->top55_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[162])); + if (this->top56_sensor_) this->top56_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[139])); + if (this->top57_sensor_) this->top57_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[140])); + if (this->top62_sensor_) this->top62_sensor_->publish_state(PanasonicDecode::getIntMinus1Times10(bytes[173])); + if (this->top63_sensor_) this->top63_sensor_->publish_state(PanasonicDecode::getIntMinus1Times10(bytes[174])); + if (this->top64_sensor_) this->top64_sensor_->publish_state(PanasonicDecode::getIntMinus1Div5(bytes[163])); + if (this->top65_sensor_) this->top65_sensor_->publish_state(PanasonicDecode::getIntMinus1Times50(bytes[171])); + if (this->top66_sensor_) this->top66_sensor_->publish_state(PanasonicDecode::getIntMinus1Times50(bytes[164])); + if (this->top67_sensor_) this->top67_sensor_->publish_state(PanasonicDecode::getIntMinus1Div5(bytes[165])); + if (this->top70_sensor_) this->top70_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[100])); + if (this->top71_sensor_) this->top71_sensor_->publish_state(PanasonicDecode::getIntMinus1(bytes[101])); + if (this->top72_sensor_) this->top72_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[86])); + if (this->top73_sensor_) this->top73_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[87])); + if (this->top74_sensor_) this->top74_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[89])); + if (this->top75_sensor_) this->top75_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[88])); + if (this->top77_sensor_) this->top77_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[83])); + if (this->top78_sensor_) this->top78_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[85])); + if (this->top79_sensor_) this->top79_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[95])); + if (this->top80_sensor_) this->top80_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[96])); + if (this->top82_sensor_) this->top82_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[79])); + if (this->top83_sensor_) this->top83_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[80])); + if (this->top84_sensor_) this->top84_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[82])); + if (this->top85_sensor_) this->top85_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[81])); + if (this->top86_sensor_) this->top86_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[90])); + if (this->top87_sensor_) this->top87_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[91])); + if (this->top88_sensor_) this->top88_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[93])); + if (this->top89_sensor_) this->top89_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[92])); + if (this->top90_sensor_) this->top90_sensor_->publish_state(PanasonicDecode::getWord(bytes[185], bytes[186])); + if (this->top91_sensor_) this->top91_sensor_->publish_state(PanasonicDecode::getWord(bytes[188], bytes[189])); + if (this->top93_sensor_) this->top93_sensor_->publish_state(PanasonicDecode::getIntMinus1(bytes[172])); + if (this->top95_sensor_) this->top95_sensor_->publish_state(PanasonicDecode::getIntMinus1(bytes[45])); + if (this->top96_sensor_) this->top96_sensor_->publish_state(PanasonicDecode::getIntMinus1(bytes[104])); + if (this->top97_sensor_) this->top97_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[105])); + if (this->top98_sensor_) this->top98_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[106])); + if (this->top102_sensor_) this->top102_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[61])); + if (this->top103_sensor_) this->top103_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[62])); + if (this->top104_sensor_) this->top104_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[63])); + if (this->top105_sensor_) this->top105_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[64])); + if (this->top113_sensor_) this->top113_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[59])); + if (this->top115_sensor_) this->top115_sensor_->publish_state(PanasonicDecode::getIntMinus1Div50(bytes[125])); + if (this->top116_sensor_) this->top116_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[126])); + if (this->top117_sensor_) this->top117_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[127])); + if (this->top118_sensor_) this->top118_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[128])); + if (this->top119_sensor_) this->top119_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[65])); + if (this->top120_sensor_) this->top120_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[66])); + if (this->top121_sensor_) this->top121_sensor_->publish_state(PanasonicDecode::getIntMinus128(bytes[68])); + if (this->top122_sensor_) this->top122_sensor_->publish_state(PanasonicDecode::getIntMinus1(bytes[67])); + if (this->top123_sensor_) this->top123_sensor_->publish_state(PanasonicDecode::getIntMinus1(bytes[69])); + if (this->top125_sensor_) this->top125_sensor_->publish_state(PanasonicDecode::getIntMinus1(bytes[177])); + if (this->top126_sensor_) this->top126_sensor_->publish_state(PanasonicDecode::getIntMinus1(bytes[178])); +#endif + +#ifdef USE_BINARY_SENSOR + if (this->top0_binary_sensor_) this->top0_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit7and8(bytes[4]))); + if (this->top2_binary_sensor_) this->top2_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit1and2(bytes[4]))); + if (this->top3_binary_sensor_) this->top3_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit1and2(bytes[7]))); + if (this->top13_binary_sensor_) this->top13_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit1and2(bytes[5]))); + if (this->top26_binary_sensor_) this->top26_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit5and6(bytes[111]))); + if (this->top60_binary_sensor_) this->top60_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit7and8(bytes[112]))); + if (this->top61_binary_sensor_) this->top61_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit5and6(bytes[112]))); + if (this->top68_binary_sensor_) this->top68_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit5and6(bytes[5]))); + if (this->top69_binary_sensor_) this->top69_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit5and6(bytes[117]))); + if (this->top99_binary_sensor_) this->top99_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit5and6(bytes[24]))); + if (this->top100_binary_sensor_) this->top100_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit7and8(bytes[24]))); + if (this->top108_binary_sensor_) this->top108_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit3and4(bytes[20]))); + if (this->top109_binary_sensor_) this->top109_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit5and6(bytes[20]))); + if (this->top110_binary_sensor_) this->top110_binary_sensor_->publish_state(PanasonicDecode::getBoolState(PanasonicDecode::getBit7and8(bytes[20]))); +#endif + +#ifdef USE_TEXT_SENSOR + if (this->top4_text_sensor_) this->top4_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::OpModeDesc, PanasonicDecode::getOpMode(bytes[6]))); + if (this->top17_text_sensor_) this->top17_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::Powerfulmode, PanasonicDecode::getRight3bits(bytes[7]))); + if (this->top18_text_sensor_) this->top18_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::Quietmode, PanasonicDecode::getBit3and4and5(bytes[7]))); + if (this->top19_text_sensor_) this->top19_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::HolidayState, PanasonicDecode::getBit3and4(bytes[5]))); + if (this->top20_text_sensor_) this->top20_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::Valve, PanasonicDecode::getBit7and8(bytes[111]))); + if (this->top44_text_sensor_) this->top44_text_sensor_->publish_state(PanasonicDecode::getErrorInfo(bytes[113], bytes[114])); + if (this->top58_text_sensor_) this->top58_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::BlockedFree, PanasonicDecode::getBit5and6(bytes[9]))); + if (this->top59_text_sensor_) this->top59_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::BlockedFree, PanasonicDecode::getBit7and8(bytes[9]))); + if (this->top76_text_sensor_) this->top76_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::HeatCoolModeDesc, PanasonicDecode::getBit7and8(bytes[28]))); + if (this->top81_text_sensor_) this->top81_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::HeatCoolModeDesc, PanasonicDecode::getBit5and6(bytes[28]))); + // if (this->top92_text_sensor_) this->top92_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::ModelNames, PanasonicDecode::getModel(bytes, 129))); + if (this->top94_text_sensor_) this->top94_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::ZonesState, PanasonicDecode::getBit1and2(bytes[6]))); + if (this->top101_text_sensor_) this->top101_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::SolarModeDesc, PanasonicDecode::getBit3and4(bytes[24]))); + if (this->top106_text_sensor_) this->top106_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::PumpFlowRateMode, PanasonicDecode::getBit3and4(bytes[29]))); + if (this->top107_text_sensor_) this->top107_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::LiquidType, PanasonicDecode::getBit1(bytes[20]))); + if (this->top111_text_sensor_) this->top111_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::ZonesSensorType, PanasonicDecode::getSecondByte(bytes[22]))); + if (this->top112_text_sensor_) this->top112_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::ZonesSensorType, PanasonicDecode::getFirstByte(bytes[22]))); + if (this->top114_text_sensor_) this->top114_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::ExtPadHeaterType, PanasonicDecode::getBit3and4(bytes[25]))); + if (this->top124_text_sensor_) this->top124_text_sensor_->publish_state(PanasonicDecode::getTextState(PanasonicDecode::Bivalent, PanasonicDecode::getBivalent(bytes[26]))); +#endif + } + + void PanasonicHeatpumpComponent::send_request_() + { + + } + + void PanasonicHeatpumpComponent::log_uart_hex(std::string prefix, std::vector bytes, uint8_t separator) + { + std::string logStr; + logStr = "[" + std::to_string(bytes.size()) + "]"; + ESP_LOGD(TAG, "%s %s", prefix.c_str(), logStr.c_str()); + delay(10); + + logStr = ""; + char buffer[5]; + for (size_t i = 0; i < bytes.size(); i++) + { + if (i > 0) logStr += separator; + sprintf(buffer, "%02X", bytes[i]); + logStr += buffer; + } + + size_t chunkSize = 90; + for (size_t i = 0; i < logStr.length(); i += chunkSize) + { + ESP_LOGD(TAG, "%s %s", prefix.c_str(), logStr.substr(i, chunkSize).c_str()); + delay(10); + } + } + } // namespace panasonic_heatpump +} // namespace esphome \ No newline at end of file diff --git a/components/panasonic_heatpump/panasonic_heatpump.h b/components/panasonic_heatpump/panasonic_heatpump.h new file mode 100755 index 0000000..186abc1 --- /dev/null +++ b/components/panasonic_heatpump/panasonic_heatpump.h @@ -0,0 +1,188 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/components/uart/uart.h" +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif +#include "decode.h" +#include +#include + + +namespace esphome +{ + namespace panasonic_heatpump + { + class PanasonicHeatpumpComponent : public Component + { + public: + PanasonicHeatpumpComponent() = default; + +#ifdef USE_SENSOR + SUB_SENSOR(top1); + SUB_SENSOR(top5); + SUB_SENSOR(top6); + SUB_SENSOR(top7); + SUB_SENSOR(top8); + SUB_SENSOR(top9); + SUB_SENSOR(top10); + SUB_SENSOR(top11); + SUB_SENSOR(top12); + SUB_SENSOR(top14); + SUB_SENSOR(top15); + SUB_SENSOR(top16); + SUB_SENSOR(top21); + SUB_SENSOR(top22); + SUB_SENSOR(top23); + SUB_SENSOR(top24); + SUB_SENSOR(top25); + SUB_SENSOR(top27); + SUB_SENSOR(top28); + SUB_SENSOR(top29); + SUB_SENSOR(top30); + SUB_SENSOR(top31); + SUB_SENSOR(top32); + SUB_SENSOR(top33); + SUB_SENSOR(top34); + SUB_SENSOR(top35); + SUB_SENSOR(top36); + SUB_SENSOR(top37); + SUB_SENSOR(top38); + SUB_SENSOR(top39); + SUB_SENSOR(top40); + SUB_SENSOR(top41); + SUB_SENSOR(top42); + SUB_SENSOR(top43); + SUB_SENSOR(top45); + SUB_SENSOR(top46); + SUB_SENSOR(top47); + SUB_SENSOR(top48); + SUB_SENSOR(top49); + SUB_SENSOR(top50); + SUB_SENSOR(top51); + SUB_SENSOR(top52); + SUB_SENSOR(top53); + SUB_SENSOR(top54); + SUB_SENSOR(top55); + SUB_SENSOR(top56); + SUB_SENSOR(top57); + SUB_SENSOR(top62); + SUB_SENSOR(top63); + SUB_SENSOR(top64); + SUB_SENSOR(top65); + SUB_SENSOR(top66); + SUB_SENSOR(top67); + SUB_SENSOR(top70); + SUB_SENSOR(top71); + SUB_SENSOR(top72); + SUB_SENSOR(top73); + SUB_SENSOR(top74); + SUB_SENSOR(top75); + SUB_SENSOR(top77); + SUB_SENSOR(top78); + SUB_SENSOR(top79); + SUB_SENSOR(top80); + SUB_SENSOR(top82); + SUB_SENSOR(top83); + SUB_SENSOR(top84); + SUB_SENSOR(top85); + SUB_SENSOR(top86); + SUB_SENSOR(top87); + SUB_SENSOR(top88); + SUB_SENSOR(top89); + SUB_SENSOR(top90); + SUB_SENSOR(top91); + SUB_SENSOR(top93); + SUB_SENSOR(top95); + SUB_SENSOR(top96); + SUB_SENSOR(top97); + SUB_SENSOR(top98); + SUB_SENSOR(top102); + SUB_SENSOR(top103); + SUB_SENSOR(top104); + SUB_SENSOR(top105); + SUB_SENSOR(top113); + SUB_SENSOR(top115); + SUB_SENSOR(top116); + SUB_SENSOR(top117); + SUB_SENSOR(top118); + SUB_SENSOR(top119); + SUB_SENSOR(top120); + SUB_SENSOR(top121); + SUB_SENSOR(top122); + SUB_SENSOR(top123); + SUB_SENSOR(top125); + SUB_SENSOR(top126); +#endif + +#ifdef USE_BINARY_SENSOR + SUB_BINARY_SENSOR(top0); + SUB_BINARY_SENSOR(top2); + SUB_BINARY_SENSOR(top3); + SUB_BINARY_SENSOR(top13); + SUB_BINARY_SENSOR(top26); + SUB_BINARY_SENSOR(top60); + SUB_BINARY_SENSOR(top61); + SUB_BINARY_SENSOR(top68); + SUB_BINARY_SENSOR(top69); + SUB_BINARY_SENSOR(top99); + SUB_BINARY_SENSOR(top100); + SUB_BINARY_SENSOR(top108); + SUB_BINARY_SENSOR(top109); + SUB_BINARY_SENSOR(top110); +#endif + +#ifdef USE_TEXT_SENSOR + SUB_TEXT_SENSOR(top4); + SUB_TEXT_SENSOR(top17); + SUB_TEXT_SENSOR(top18); + SUB_TEXT_SENSOR(top19); + SUB_TEXT_SENSOR(top20); + SUB_TEXT_SENSOR(top44); + SUB_TEXT_SENSOR(top58); + SUB_TEXT_SENSOR(top59); + SUB_TEXT_SENSOR(top76); + SUB_TEXT_SENSOR(top81); + // SUB_TEXT_SENSOR(top92); + SUB_TEXT_SENSOR(top94); + SUB_TEXT_SENSOR(top101); + SUB_TEXT_SENSOR(top106); + SUB_TEXT_SENSOR(top107); + SUB_TEXT_SENSOR(top111); + SUB_TEXT_SENSOR(top112); + SUB_TEXT_SENSOR(top114); + SUB_TEXT_SENSOR(top124); +#endif + + float get_setup_priority() const override { return setup_priority::LATE; } + void loop() override; + void dump_config() override; + + void set_uart_hp(uart::UARTComponent *uart) { this->uart_hp_ = uart; } + void set_uart_wm(uart::UARTComponent *uart) { this->uart_wm_ = uart; } + + protected: + uart::UARTComponent *uart_hp_; + uart::UARTComponent *uart_wm_; + + std::vector response_data_; + uint8_t response_payload_length_; + bool response_receiving_{false}; + std::vector request_data_; + uint8_t request_payload_length_; + bool request_receiving_{false}; + + void decode_response_(std::vector data); + void send_request_(); + void log_uart_hex(std::string prefix, std::vector bytes, uint8_t separator); + }; + } // namespace panasonic_heatpump +} // namespace esphome \ No newline at end of file diff --git a/components/panasonic_heatpump/sensor.py b/components/panasonic_heatpump/sensor.py new file mode 100755 index 0000000..d9c0696 --- /dev/null +++ b/components/panasonic_heatpump/sensor.py @@ -0,0 +1,327 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_HERTZ, + UNIT_HOUR, + UNIT_KELVIN, + UNIT_MINUTE, + UNIT_WATT, +) +from . import PanasonicHeatpumpComponent, CONF_PANASONIC_HEATPUMP_ID + +UNIT_LITRE_PER_MINUTE = "l/min" +UNIT_ROTATIONS_PER_MINUTE = "r/min" +UNIT_PRESSURE_KGFCM2 = "kgf/cm²" +UNIT_BAR = "bar" + +CONF_SENS_TOP1 = "top1" +CONF_SENS_TOP5 = "top5" +CONF_SENS_TOP6 = "top6" +CONF_SENS_TOP7 = "top7" +CONF_SENS_TOP8 = "top8" +CONF_SENS_TOP9 = "top9" +CONF_SENS_TOP10 = "top10" +CONF_SENS_TOP11 = "top11" +CONF_SENS_TOP12 = "top12" +CONF_SENS_TOP14 = "top14" +CONF_SENS_TOP15 = "top15" +CONF_SENS_TOP16 = "top16" +CONF_SENS_TOP21 = "top21" +CONF_SENS_TOP22 = "top22" +CONF_SENS_TOP23 = "top23" +CONF_SENS_TOP24 = "top24" +CONF_SENS_TOP25 = "top25" +CONF_SENS_TOP27 = "top27" +CONF_SENS_TOP28 = "top28" +CONF_SENS_TOP29 = "top29" +CONF_SENS_TOP30 = "top30" +CONF_SENS_TOP31 = "top31" +CONF_SENS_TOP32 = "top32" +CONF_SENS_TOP33 = "top33" +CONF_SENS_TOP34 = "top34" +CONF_SENS_TOP35 = "top35" +CONF_SENS_TOP36 = "top36" +CONF_SENS_TOP37 = "top37" +CONF_SENS_TOP38 = "top38" +CONF_SENS_TOP39 = "top39" +CONF_SENS_TOP40 = "top40" +CONF_SENS_TOP41 = "top41" +CONF_SENS_TOP42 = "top42" +CONF_SENS_TOP43 = "top43" +CONF_SENS_TOP45 = "top45" +CONF_SENS_TOP46 = "top46" +CONF_SENS_TOP47 = "top47" +CONF_SENS_TOP48 = "top48" +CONF_SENS_TOP49 = "top49" +CONF_SENS_TOP50 = "top50" +CONF_SENS_TOP51 = "top51" +CONF_SENS_TOP52 = "top52" +CONF_SENS_TOP53 = "top53" +CONF_SENS_TOP54 = "top54" +CONF_SENS_TOP55 = "top55" +CONF_SENS_TOP56 = "top56" +CONF_SENS_TOP57 = "top57" +CONF_SENS_TOP62 = "top62" +CONF_SENS_TOP63 = "top63" +CONF_SENS_TOP64 = "top64" +CONF_SENS_TOP65 = "top65" +CONF_SENS_TOP66 = "top66" +CONF_SENS_TOP67 = "top67" +CONF_SENS_TOP70 = "top70" +CONF_SENS_TOP71 = "top71" +CONF_SENS_TOP72 = "top72" +CONF_SENS_TOP73 = "top73" +CONF_SENS_TOP74 = "top74" +CONF_SENS_TOP75 = "top75" +CONF_SENS_TOP77 = "top77" +CONF_SENS_TOP78 = "top78" +CONF_SENS_TOP79 = "top79" +CONF_SENS_TOP80 = "top80" +CONF_SENS_TOP82 = "top82" +CONF_SENS_TOP83 = "top83" +CONF_SENS_TOP84 = "top84" +CONF_SENS_TOP85 = "top85" +CONF_SENS_TOP86 = "top86" +CONF_SENS_TOP87 = "top87" +CONF_SENS_TOP88 = "top88" +CONF_SENS_TOP89 = "top89" +CONF_SENS_TOP90 = "top90" +CONF_SENS_TOP91 = "top91" +CONF_SENS_TOP93 = "top93" +CONF_SENS_TOP95 = "top95" +CONF_SENS_TOP96 = "top96" +CONF_SENS_TOP97 = "top97" +CONF_SENS_TOP98 = "top98" +CONF_SENS_TOP102 = "top102" +CONF_SENS_TOP103 = "top103" +CONF_SENS_TOP104 = "top104" +CONF_SENS_TOP105 = "top105" +CONF_SENS_TOP113 = "top113" +CONF_SENS_TOP115 = "top115" +CONF_SENS_TOP116 = "top116" +CONF_SENS_TOP117 = "top117" +CONF_SENS_TOP118 = "top118" +CONF_SENS_TOP119 = "top119" +CONF_SENS_TOP120 = "top120" +CONF_SENS_TOP121 = "top121" +CONF_SENS_TOP122 = "top122" +CONF_SENS_TOP123 = "top123" +CONF_SENS_TOP125 = "top125" +CONF_SENS_TOP126 = "top126" + +TYPES = [ + CONF_SENS_TOP1, + CONF_SENS_TOP5, + CONF_SENS_TOP6, + CONF_SENS_TOP7, + CONF_SENS_TOP8, + CONF_SENS_TOP9, + CONF_SENS_TOP10, + CONF_SENS_TOP11, + CONF_SENS_TOP12, + CONF_SENS_TOP14, + CONF_SENS_TOP15, + CONF_SENS_TOP16, + CONF_SENS_TOP21, + CONF_SENS_TOP22, + CONF_SENS_TOP23, + CONF_SENS_TOP24, + CONF_SENS_TOP25, + CONF_SENS_TOP27, + CONF_SENS_TOP28, + CONF_SENS_TOP29, + CONF_SENS_TOP30, + CONF_SENS_TOP31, + CONF_SENS_TOP32, + CONF_SENS_TOP33, + CONF_SENS_TOP34, + CONF_SENS_TOP35, + CONF_SENS_TOP36, + CONF_SENS_TOP37, + CONF_SENS_TOP38, + CONF_SENS_TOP39, + CONF_SENS_TOP40, + CONF_SENS_TOP41, + CONF_SENS_TOP42, + CONF_SENS_TOP43, + CONF_SENS_TOP45, + CONF_SENS_TOP46, + CONF_SENS_TOP47, + CONF_SENS_TOP48, + CONF_SENS_TOP49, + CONF_SENS_TOP50, + CONF_SENS_TOP51, + CONF_SENS_TOP52, + CONF_SENS_TOP53, + CONF_SENS_TOP54, + CONF_SENS_TOP55, + CONF_SENS_TOP56, + CONF_SENS_TOP57, + CONF_SENS_TOP62, + CONF_SENS_TOP63, + CONF_SENS_TOP64, + CONF_SENS_TOP65, + CONF_SENS_TOP66, + CONF_SENS_TOP67, + CONF_SENS_TOP70, + CONF_SENS_TOP71, + CONF_SENS_TOP72, + CONF_SENS_TOP73, + CONF_SENS_TOP74, + CONF_SENS_TOP75, + CONF_SENS_TOP77, + CONF_SENS_TOP78, + CONF_SENS_TOP79, + CONF_SENS_TOP80, + CONF_SENS_TOP82, + CONF_SENS_TOP83, + CONF_SENS_TOP84, + CONF_SENS_TOP85, + CONF_SENS_TOP86, + CONF_SENS_TOP87, + CONF_SENS_TOP88, + CONF_SENS_TOP89, + CONF_SENS_TOP90, + CONF_SENS_TOP91, + CONF_SENS_TOP93, + CONF_SENS_TOP95, + CONF_SENS_TOP96, + CONF_SENS_TOP97, + CONF_SENS_TOP98, + CONF_SENS_TOP102, + CONF_SENS_TOP103, + CONF_SENS_TOP104, + CONF_SENS_TOP105, + CONF_SENS_TOP113, + CONF_SENS_TOP115, + CONF_SENS_TOP116, + CONF_SENS_TOP117, + CONF_SENS_TOP118, + CONF_SENS_TOP119, + CONF_SENS_TOP120, + CONF_SENS_TOP121, + CONF_SENS_TOP122, + CONF_SENS_TOP123, + CONF_SENS_TOP125, + CONF_SENS_TOP126, +] + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_PANASONIC_HEATPUMP_ID): cv.use_id(PanasonicHeatpumpComponent), + + cv.Optional(CONF_SENS_TOP1): sensor.sensor_schema( + accuracy_decimals = 1, + unit_of_measurement = UNIT_LITRE_PER_MINUTE), + cv.Optional(CONF_SENS_TOP5): sensor.sensor_schema( + accuracy_decimals = 1, + unit_of_measurement = UNIT_CELSIUS), + cv.Optional(CONF_SENS_TOP6): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP7): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP8): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP9): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP10): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP11): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP12): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP14): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP15): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP16): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP21): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP22): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP23): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP24): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP25): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP27): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP28): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP29): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP30): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP31): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP32): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP33): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP34): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP35): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP36): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP37): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP38): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP39): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP40): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP41): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP42): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP43): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP45): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP46): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP47): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP48): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP49): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP50): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP51): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP52): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP53): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP54): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP55): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP56): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP57): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP62): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP63): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP64): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP65): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP66): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP67): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP70): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP71): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP72): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP73): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP74): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP75): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP77): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP78): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP79): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP80): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP82): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP83): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP84): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP85): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP86): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP87): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP88): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP89): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP90): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP91): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP93): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP95): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP96): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP97): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP98): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP102): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP103): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP104): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP105): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP113): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP115): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP116): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP117): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP118): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP119): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP120): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP121): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP122): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP123): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP125): sensor.sensor_schema(), + cv.Optional(CONF_SENS_TOP126): sensor.sensor_schema(), + } + ).extend(cv.COMPONENT_SCHEMA) +) + +async def setup_conf(config, key, hub): + if sensor_config := config.get(key): + sens = await sensor.new_sensor(sensor_config) + cg.add(getattr(hub, f"set_{key}_sensor")(sens)) + +async def to_code(config): + hub = await cg.get_variable(config[CONF_PANASONIC_HEATPUMP_ID]) + for key in TYPES: + await setup_conf(config, key, hub) diff --git a/components/panasonic_heatpump/text_sensor.py b/components/panasonic_heatpump/text_sensor.py new file mode 100755 index 0000000..036f5c3 --- /dev/null +++ b/components/panasonic_heatpump/text_sensor.py @@ -0,0 +1,87 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import ( + DEVICE_CLASS_HEAT, +) +from . import PanasonicHeatpumpComponent, CONF_PANASONIC_HEATPUMP_ID + +CONF_TEXT_SENS_TOP4 = "top4" +CONF_TEXT_SENS_TOP17 = "top17" +CONF_TEXT_SENS_TOP18 = "top18" +CONF_TEXT_SENS_TOP19 = "top19" +CONF_TEXT_SENS_TOP20 = "top20" +CONF_TEXT_SENS_TOP44 = "top44" +CONF_TEXT_SENS_TOP58 = "top58" +CONF_TEXT_SENS_TOP59 = "top59" +CONF_TEXT_SENS_TOP76 = "top76" +CONF_TEXT_SENS_TOP81 = "top81" +CONF_TEXT_SENS_TOP92 = "top92" +CONF_TEXT_SENS_TOP94 = "top94" +CONF_TEXT_SENS_TOP101 = "top101" +CONF_TEXT_SENS_TOP106 = "top106" +CONF_TEXT_SENS_TOP107 = "top107" +CONF_TEXT_SENS_TOP111 = "top111" +CONF_TEXT_SENS_TOP112 = "top112" +CONF_TEXT_SENS_TOP114 = "top114" +CONF_TEXT_SENS_TOP124 = "top124" + +TYPES = [ + CONF_TEXT_SENS_TOP4, + CONF_TEXT_SENS_TOP17, + CONF_TEXT_SENS_TOP18, + CONF_TEXT_SENS_TOP19, + CONF_TEXT_SENS_TOP20, + CONF_TEXT_SENS_TOP44, + CONF_TEXT_SENS_TOP58, + CONF_TEXT_SENS_TOP59, + CONF_TEXT_SENS_TOP76, + CONF_TEXT_SENS_TOP81, + CONF_TEXT_SENS_TOP92, + CONF_TEXT_SENS_TOP94, + CONF_TEXT_SENS_TOP101, + CONF_TEXT_SENS_TOP106, + CONF_TEXT_SENS_TOP107, + CONF_TEXT_SENS_TOP111, + CONF_TEXT_SENS_TOP112, + CONF_TEXT_SENS_TOP114, + CONF_TEXT_SENS_TOP124, +] + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_PANASONIC_HEATPUMP_ID): cv.use_id(PanasonicHeatpumpComponent), + + cv.Optional(CONF_TEXT_SENS_TOP4): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP17): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP18): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP19): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP20): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP44): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP58): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP59): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP76): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP81): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP92): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP94): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP101): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP106): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP107): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP111): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP112): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP114): text_sensor.text_sensor_schema(), + cv.Optional(CONF_TEXT_SENS_TOP124): text_sensor.text_sensor_schema(), + } + ).extend(cv.COMPONENT_SCHEMA) +) + +async def setup_conf(config, key, hub): + if sensor_config := config.get(key): + sens = await text_sensor.new_text_sensor(sensor_config) + cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) + +async def to_code(config): + hub = await cg.get_variable(config[CONF_PANASONIC_HEATPUMP_ID]) + for key in TYPES: + await setup_conf(config, key, hub) diff --git a/example_panasonic_heatpump.yaml b/example_panasonic_heatpump.yaml new file mode 100755 index 0000000..74a3422 --- /dev/null +++ b/example_panasonic_heatpump.yaml @@ -0,0 +1,431 @@ +substitutions: + mdns_name: panasonic-heatpump + device_name: Panasonic Heatpump + + pin_rx_hp: GPIO4 # heatpump reads data (RX) on this pin + pin_tx_hp: GPIO3 # heatpump sends data (TX) on this pin + pin_tx_cz: GPIO1 # WiFi module sends data (TX) on this pin + pin_rx_cz: GPIO0 # WiFi module reads data (RX) on this pin + + # CN-CNT Pinout (from top to bottom) + # 1 - +5V (250mA) + # 2 - 0-5V TX (from heatpump) + # 3 - 0-5V RX (to heatpump) + # 4 - +12V (250mA) + # 5 - GND + +esp32: + board: esp32-c3-devkitm-1 + variant: ESP32C3 + framework: + type: arduino + +esphome: + name: $mdns_name + friendly_name: $device_name + +ota: + platform: esphome + password: !secret ota_password + id: ota_pass + +api: + encryption: + key: !secret ha_api_key + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + ap: + ssid: "${device_name} Fallback" + password: !secret fallback_password + +web_server: + port: 80 + auth: + username: !secret webserver_username + password: !secret webserver_password + +captive_portal: + +improv_serial: + +logger: + +uart: + - id: uart_heat + tx_pin: $pin_rx_hp + rx_pin: $pin_tx_hp + baud_rate: 9600 + data_bits: 8 + parity: EVEN + stop_bits: 1 + - id: uart_wifi + tx_pin: $pin_rx_cz + rx_pin: $pin_tx_cz + baud_rate: 9600 + data_bits: 8 + parity: EVEN + stop_bits: 1 + +external_components: + - source: + type: git + url: https://github.com/ElVit/esphome_components/ + components: [ panasonic_heatpump ] + +panasonic_heatpump: + uart_hp: uart_heat + uart_wm: uart_wifi + +sensor: + - platform: panasonic_heatpump + top1: + name: "Pump Flow" + unit_of_measurement: l/min" + top5: + name: "Main Inlet Temp" + unit_of_measurement: °C" + top6: + name: "Main Outlet Temp" + unit_of_measurement: °C" + top7: + name: "Main Target Temp" + unit_of_measurement: °C" + top8: + name: "Compressor Freq" + unit_of_measurement: Hz" + top9: + name: "DHW Target Temp" + unit_of_measurement: °C" + top10: + name: "DHW Temp" + unit_of_measurement: °C" + top11: + name: "Operations Hours" + unit_of_measurement: h" + top12: + name: "Operations Counter" + top14: + name: "Outside Temp" + unit_of_measurement: °C" + top15: + name: "Heat Power Production" + unit_of_measurement: W" + top16: + name: "Heat Power Consumption" + unit_of_measurement: W" + top21: + name: "Outside Pipe Temp" + unit_of_measurement: °C" + top22: + name: "DHW Heat Delta" + unit_of_measurement: K" + top23: + name: "Heat Delta" + unit_of_measurement: K" + top24: + name: "Cool Delta" + unit_of_measurement: K" + top25: + name: "DHW Holiday Shift Temp" + unit_of_measurement: K" + top27: + name: "Z1 Heat Request Temp" + unit_of_measurement: °C" + top28: + name: "Z1 Cool Request Temp" + unit_of_measurement: °C" + top29: + name: "Z1 Heat Curve Target High Temp" + unit_of_measurement: °C" + top30: + name: "Z1 Heat Curve Target Low Temp" + unit_of_measurement: °C" + top31: + name: "Z1 Heat Curve Outside High Temp" + unit_of_measurement: °C" + top32: + name: "Z1 Heat Curve Outside Low Temp" + unit_of_measurement: °C" + top33: + name: "Room Thermostat Temp" + unit_of_measurement: °C" + top34: + name: "Z2 Heat Request Temp" + unit_of_measurement: °C" + top35: + name: "Z2 Cool Request Temp" + unit_of_measurement: °C" + top36: + name: "Z1 Water Temp" + unit_of_measurement: °C" + top37: + name: "Z2 Water Temp" + unit_of_measurement: °C" + top38: + name: "Cool Power Production" + unit_of_measurement: W" + top39: + name: "Cool Power Consumption" + unit_of_measurement: W" + top40: + name: "DHW Power Production" + unit_of_measurement: W" + top41: + name: "DHW Power Consumption" + unit_of_measurement: W" + top42: + name: "Z1 Water Target Temp" + unit_of_measurement: °C" + top43: + name: "Z2 Water Target Temp" + unit_of_measurement: °C" + top45: + name: "Room Holiday Shift Temp" + unit_of_measurement: K" + top46: + name: "Buffer Temp" + unit_of_measurement: °C" + top47: + name: "Solar Temp" + unit_of_measurement: °C" + top48: + name: "Pool Temp" + unit_of_measurement: °C" + top49: + name: "Main Hex Outlet Temp" + unit_of_measurement: °C" + top50: + name: "Discharge Temp" + unit_of_measurement: °C" + top51: + name: "Inside Pipe Temp" + unit_of_measurement: °C" + top52: + name: "Defrost Temp" + unit_of_measurement: °C" + top53: + name: "Eva Outlet Temp" + unit_of_measurement: °C" + top54: + name: "Bypass Outlet Temp" + unit_of_measurement: °C" + top55: + name: "Ipm Temp" + unit_of_measurement: °C" + top56: + name: "Z1 Temp" + unit_of_measurement: °C" + top57: + name: "Z2 Temp" + unit_of_measurement: °C" + top62: + name: "Fan1 Motor Speed" + unit_of_measurement: r/min" + top63: + name: "Fan2 Motor Speed" + unit_of_measurement: r/min" + top64: + name: "High Pressure" + unit_of_measurement: kgf/cm²" + top65: + name: "Pump Speed" + unit_of_measurement: r/min" + top66: + name: "Low Pressure" + unit_of_measurement: kgf/cm²" + top67: + name: "Compressor Current" + unit_of_measurement: A" + top70: + name: "Sterilization Temp" + unit_of_measurement: °C" + top71: + name: "Sterilization Max Time" + unit_of_measurement: min" + top72: + name: "Z1 Cool Curve Target High Temp" + unit_of_measurement: °C" + top73: + name: "Z1 Cool Curve Target Low Temp" + unit_of_measurement: °C" + top74: + name: "Z1 Cool Curve Outside High Temp" + unit_of_measurement: °C" + top75: + name: "Z1 Cool Curve Outside Low Temp" + unit_of_measurement: °C" + top77: + name: "Heating Off Outdoor Temp" + unit_of_measurement: °C" + top78: + name: "Heater On Outdoor Temp" + unit_of_measurement: °C" + top79: + name: "Heat To Cool Temp" + unit_of_measurement: °C" + top80: + name: "Cool To Heat Temp" + unit_of_measurement: °C" + top82: + name: "Z2 Heat Curve Target High Temp" + unit_of_measurement: °C" + top83: + name: "Z2 Heat Curve Target Low Temp" + unit_of_measurement: °C" + top84: + name: "Z2 Heat Curve Outside High Temp" + unit_of_measurement: °C" + top85: + name: "Z2 Heat Curve Outside Low Temp" + unit_of_measurement: °C" + top86: + name: "Z2 Cool Curve Target High Temp" + unit_of_measurement: °C" + top87: + name: "Z2 Cool Curve Target Low Temp" + unit_of_measurement: °C" + top88: + name: "Z2 Cool Curve Outside High Temp" + unit_of_measurement: °C" + top89: + name: "Z2 Cool Curve Outside Low Temp" + unit_of_measurement: °C" + top90: + name: "Room Heater Operations Hours" + unit_of_measurement: h" + top91: + name: "DHW Heater Operations Hours" + unit_of_measurement: h" + top93: + name: "Pump Duty" + top95: + name: "Max Pump Duty" + top96: + name: "Heater Delay Time" + unit_of_measurement: min" + top97: + name: "Heater Start Delta" + unit_of_measurement: K" + top98: + name: "Heater Stop Delta" + unit_of_measurement: K" + top102: + name: "Solar On Delta" + unit_of_measurement: K" + top103: + name: "Solar Off Delta" + unit_of_measurement: K" + top104: + name: "Solar Frost Protection" + unit_of_measurement: °C" + top105: + name: "Solar High Limit" + unit_of_measurement: °C" + top113: + name: "Buffer Tank Delta" + unit_of_measurement: K" + top115: + name: "Water Pressure" + unit_of_measurement: bar" + top116: + name: "Second Inlet Temp" + unit_of_measurement: °C" + top117: + name: "Economizer Outlet Temp" + unit_of_measurement: °C" + top118: + name: "Second Room Thermostat Temp" + unit_of_measurement: °C" + top119: + name: "Bivalent Heating Start Temperature" + unit_of_measurement: °C" + top120: + name: "Bivalent Heating Parallel Adv Starttemp" + unit_of_measurement: °C" + top121: + name: "Bivalent Heating Parallel Adv Stoptemp" + unit_of_measurement: °C" + top122: + name: "Bivalent Heating Parallel Adv Start Delay" + unit_of_measurement: min" + top123: + name: "Bivalent Heating Parallel Adv Stop Delay" + unit_of_measurement: min" + top125: + name: "2 Zone mixing valve 1 opening" + top126: + name: "2 Zone mixing valve 2 opening" + +binary_sensor: + - platform: panasonic_heatpump + top0: + name: "Heatpump State" + top2: + name: "Force DHW State" + top3: + name: "Quiet Mode Schedule" + top13: + name: "Main Schedule State" + top26: + name: "Defrosting State" + top60: + name: "Internal Heater State" + top61: + name: "External Heater State" + top68: + name: "Force Heater State" + top69: + name: "Sterilization State" + top99: + name: "Buffer Installed" + top100: + name: "DHW Installed" + top108: + name: "Alt External Sensor" + top109: + name: "Anti Freeze Mode" + top110: + name: "Optional PCB" + +text_sensor: + - platform: panasonic_heatpump + top4: + name: "Operating Mode State" + top17: + name: "Powerful Mode Time" + top18: + name: "Quiet Mode Level" + top19: + name: "Holiday Mode State" + top20: + name: "ThreeWay Valve State" + top44: + name: "Error" + top58: + name: "DHW Heater State" + top59: + name: "Room Heater State" + top76: + name: "Heating Mode" + top81: + name: "Cooling Mode" + top92: + name: "Heat Pump Model" + top94: + name: "Zones State" + top101: + name: "Solar Mode" + top106: + name: "Pump Flowrate Mode" + top107: + name: "Liquid Type" + top111: + name: "Z1 Sensor Settings" + top112: + name: "Z2 Sensor Settings" + top114: + name: "External Pad Heater" + top124: + name: "Bivalent Heating Setting"