Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion HeishaMon/HeishaMon.ino
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,41 @@ bool loadTlsCaFromFS(WiFiClientSecure *client) {
#endif


void publishRuleExecution(const char *name, uint8_t id, int result, uint32_t duration, bool nested) {
if (!mqtt_client.connected()) return;
JsonDocument doc;
doc["rule"] = name;
doc["rule_id"] = id;
doc["result"] = result;
doc["uptime_ms"] = millis();
doc["nested"] = nested;
if (!nested) doc["duration_us"] = duration;
String payload;
serializeJson(doc, payload);
char topic[256];
snprintf(topic, sizeof(topic), "%s/rules/executed", heishamonSettings.mqtt_topic_base);
mqtt_client.publish(topic, payload.c_str(), false);
}

void publishRulesState(bool force) {
static int reported = -1;
static unsigned long lastPublish = 0;
const unsigned long now = millis();
const int state = nrrules > 0 ? 1 : 0;
const bool periodic = (unsigned long)(now - lastPublish) >= heishamonSettings.updateAllTime * 1000UL;
if (!mqtt_client.connected()) return;
if (force || periodic || reported != state) {
char topic[256];
snprintf(topic, sizeof(topic), "%s/rules/state", heishamonSettings.mqtt_topic_base);
if (mqtt_client.publish(topic, state ? "1" : "0", true)) {
reported = state;
lastPublish = now;
} else {
reported = -1; // Retry on the next loop.
}
}
}

void mqtt_reconnect()
{
unsigned long now = millis();
Expand Down Expand Up @@ -488,6 +523,8 @@ void mqtt_reconnect()
if (mqtt_client.connect(heishamonSettings.wifi_hostname, heishamonSettings.mqtt_username, heishamonSettings.mqtt_password, topic, 1, true, "Offline"))
{
mqttReconnects++;
publishRulesState(true);
publishRelayStates(mqtt_client, heishamonSettings.mqtt_topic_base, heishamonSettings.updateAllTime, true);
if (heishamonSettings.opentherm) {
sprintf(topic, "%s/%s/#", heishamonSettings.mqtt_topic_base, mqtt_topic_opentherm_read);
mqtt_client.subscribe(topic);
Expand Down Expand Up @@ -1027,7 +1064,7 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
mqttOTCallback(topic_otcommand, msg);
} else if (strncmp(topic_command, mqtt_topic_gpio, strlen(mqtt_topic_gpio)) == 0) {
char* topic_gpiocommand = topic_command + strlen(mqtt_topic_gpio) + 1; //strip the gpio subtopic from the topic
mqttGPIOCallback(topic_gpiocommand, msg);
mqttGPIOCallback(topic_gpiocommand, msg, mqtt_client, heishamonSettings.mqtt_topic_base);
} else if (strncmp(topic_command, mqtt_topic_1wire, strlen(mqtt_topic_1wire)) == 0) { //this is a 1wire address topic, restore its retained value at boot
char* topic_1wire_address = topic_command + strlen(mqtt_topic_1wire) + 1; //strip the "1wire/" from the topic to get the sensor address
if ((strchr(topic_1wire_address, '/') == NULL) && (length > 0)) { //only handle the address topic itself (not .../alias) and skip empty (cleared) retained messages
Expand Down Expand Up @@ -1201,6 +1238,22 @@ int8_t webserver_cb(struct webserver_t *client, void *dat) {
memset(&cpy, 0, args->len + 1);
snprintf((char *)&cpy, args->len + 1, "%.*s", args->len, args->value);

#ifdef ESP32
if (strcmp((char *)args->name, "SetRelayOne") == 0 || strcmp((char *)args->name, "SetRelayTwo") == 0) {
const bool firstRelay = strcmp((char *)args->name, "SetRelayOne") == 0;
mqttGPIOCallback((char *)(firstRelay ? "relay/one" : "relay/two"), cpy, mqtt_client, heishamonSettings.mqtt_topic_base);
snprintf(log_msg, sizeof(log_msg), "%s: %d", (char *)args->name, digitalRead(firstRelay ? relayOnePin : relayTwoPin) ? 1 : 0);
if ((client->userdata = realloc(client->userdata, strlen((char *)client->userdata) + strlen(log_msg) + 2)) == NULL) {
loggingSerial.printf(PSTR("Out of memory %s:#%d\n"), __FUNCTION__, __LINE__);
ESP.restart();
exit(-1);
}
strcat((char *)client->userdata, log_msg);
strcat((char *)client->userdata, "\n");
return 0;
}
#endif

for (uint8_t x = 0; x < sizeof(commands) / sizeof(commands[0]); x++) {
cmdStruct tmp;
memcpy_P(&tmp, &commands[x], sizeof(tmp));
Expand Down Expand Up @@ -2240,6 +2293,8 @@ void loop() {
}

timerqueue_update();
publishRulesState(false);
publishRelayStates(mqtt_client, heishamonSettings.mqtt_topic_base, heishamonSettings.updateAllTime);
#ifdef ESP32
delay(1); // to keep watchdog happy
#endif
Expand Down
48 changes: 48 additions & 0 deletions HeishaMon/commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,54 @@ unsigned int set_force_defrost(char *msg, unsigned char *cmd, char *log_msg) {
return sizeof(panasonicSendQuery);
}

// Validate the complete payload before touching the command buffer. The limits
// below describe the nonzero wire encoding; the heat pump may impose narrower
// model-specific installer-menu limits.
static unsigned int set_integer_field(char *msg, unsigned char *cmd, char *log_msg,
byte address, int minimum, int maximum,
int step, int divisor, int offset, int multiplier) {
const size_t length = msg == NULL ? 0 : strlen(msg);
bool valid = length > 0 && length <= 5;
size_t index = valid && msg[0] == '-' ? 1 : 0;
const bool negative = index == 1;
valid = valid && index < length;
int value = 0;
for (; valid && index < length; ++index) {
valid = msg[index] >= '0' && msg[index] <= '9';
if (valid) value = value * 10 + msg[index] - '0';
}
if (negative) value = -value;
if (!valid || value < minimum || value > maximum || value % step != 0) {
snprintf_P(log_msg, 256, PSTR("Invalid setting for byte %u: expected integer %d..%d, step %d"),
address, minimum, maximum, step);
return 0;
}
memcpy_P(cmd, panasonicSendQuery, sizeof(panasonicSendQuery));
cmd[address] = (value / divisor + offset) * multiplier;
snprintf_P(log_msg, 256, PSTR("set byte %u to value %d"), address, value);
return sizeof(panasonicSendQuery);
}

unsigned int set_heat_to_cool_temp(char *msg, unsigned char *cmd, char *log_msg) {
return set_integer_field(msg, cmd, log_msg, 95, -127, 127, 1, 1, 128, 1);
}

unsigned int set_cool_to_heat_temp(char *msg, unsigned char *cmd, char *log_msg) {
return set_integer_field(msg, cmd, log_msg, 96, -127, 127, 1, 1, 128, 1);
}

unsigned int set_room_operation_max_time(char *msg, unsigned char *cmd, char *log_msg) {
return set_integer_field(msg, cmd, log_msg, 97, 30, 7620, 30, 30, 1, 1);
}

unsigned int set_dhw_heatup_max_time(char *msg, unsigned char *cmd, char *log_msg) {
return set_integer_field(msg, cmd, log_msg, 98, 1, 254, 1, 1, 1, 1);
}

unsigned int set_quiet_mode_schedule(char *msg, unsigned char *cmd, char *log_msg) {
return set_integer_field(msg, cmd, log_msg, 7, 0, 1, 1, 1, 1, 64);
}

unsigned int set_sterilization_temp(char *msg, unsigned char *cmd, char *log_msg) {
if (msg == NULL || strlen(msg) != 2 || msg[0] < '0' || msg[0] > '9' || msg[1] < '0' || msg[1] > '9') {
snprintf_P(log_msg, 256, PSTR("Invalid sterilization temperature: expected integer 55-75 C"));
Expand Down
10 changes: 10 additions & 0 deletions HeishaMon/commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ unsigned int set_dhw_sensor_selection(char *msg, unsigned char *cmd, char *log_m
unsigned int set_dhw_heater_state(char *msg, unsigned char *cmd, char *log_msg);
unsigned int set_room_heater_state(char *msg, unsigned char *cmd, char *log_msg);
unsigned int set_heater_on_outdoor_temp(char *msg, unsigned char *cmd, char *log_msg);
unsigned int set_heat_to_cool_temp(char *msg, unsigned char *cmd, char *log_msg);
unsigned int set_cool_to_heat_temp(char *msg, unsigned char *cmd, char *log_msg);
unsigned int set_room_operation_max_time(char *msg, unsigned char *cmd, char *log_msg);
unsigned int set_dhw_heatup_max_time(char *msg, unsigned char *cmd, char *log_msg);
unsigned int set_quiet_mode_schedule(char *msg, unsigned char *cmd, char *log_msg);

//optional pcb commands
unsigned int set_heat_cool_mode(char *msg, char *log_msg);
Expand Down Expand Up @@ -178,6 +183,11 @@ const cmdStruct commands[] PROGMEM = {
{ "SetDHWHeaterState", set_dhw_heater_state },
{ "SetRoomHeaterState", set_room_heater_state },
{ "SetHeaterOnOutdoorTemp", set_heater_on_outdoor_temp },
{ "SetHeatToCoolTemp", set_heat_to_cool_temp },
{ "SetCoolToHeatTemp", set_cool_to_heat_temp },
{ "SetRoomOperationMaxTime", set_room_operation_max_time },
{ "SetDHWHeatupMaxTime", set_dhw_heatup_max_time },
{ "SetQuietModeSchedule", set_quiet_mode_schedule },
};

struct optCmdStruct{
Expand Down
21 changes: 21 additions & 0 deletions HeishaMon/decode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@ unsigned long lastalldatatime = 0;
unsigned long lastallextradatatime = 0;
unsigned long lastalloptdatatime = 0;

String getRawByte(byte input) {
return String(input);
}

String getHeaterCapacity(byte input) {
int encoded = (input >> 2) & 0b11;
return String(encoded == 0 ? -1 : encoded * 3);
}

String getIntMinus1Times30(byte input) {
return String(((int)input - 1) * 30);
}

String getZone2Active(byte input) {
return String((input >> 1) & 1);
}

String getZone1Active(byte input) {
return String(input & 1);
}

String getBit1(byte input) {
return String(input >> 7);
}
Expand Down
117 changes: 116 additions & 1 deletion HeishaMon/decode.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ void decode_heatpump_data(char* data, char* actData, PubSubClient &mqtt_client,
void decode_heatpump_data_extra(char* data, char* actDataExtra, PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, unsigned int updateAllTime);
void decode_optional_heatpump_data(char* data, char* actOptDat, PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, unsigned int updateAllTime);

String getRawByte(byte input);
String getHeaterCapacity(byte input);
String getIntMinus1Times30(byte input);
String getZone2Active(byte input);
String getZone1Active(byte input);
String unknown(byte input);
String getBit1(byte input);
String getBit1and2(byte input);
Expand Down Expand Up @@ -42,7 +47,7 @@ static const char _unknown[] PROGMEM = "unknown";



#define NUMBER_OF_TOPICS 144 //last topic number + 1
#define NUMBER_OF_TOPICS 170 //last topic number + 1
#define NUMBER_OF_TOPICS_EXTRA 6 //last topic number + 1
#define NUMBER_OF_OPT_TOPICS 7 //last topic number + 1
#define MAX_TOPIC_LEN 42 // max length + 1
Expand Down Expand Up @@ -220,6 +225,32 @@ static const char topics[][MAX_TOPIC_LEN] PROGMEM = {
"Quiet_Mode_Priority", //TOP141
"Expansion_Valve", //TOP142
"DHW_Sensor_Selection", //TOP143
"Dry_Concrete_State", //TOP144
"DHW_Capacity_Mode", //TOP145
"Zone_Configuration", //TOP146
"Internal_Heater_Capacity", //TOP147
"DHW_Heater_Type", //TOP148
"SG_Ready_Control", //TOP149
"Demand_Control", //TOP150
"DHW_Defrost", //TOP151
"Dry_Concrete_Target_Temp", //TOP152
"Pool_Delta", //TOP153
"SG_Ready_1_Heat_Capacity", //TOP154
"SG_Ready_1_DHW_Capacity", //TOP155
"SG_Ready_2_Heat_Capacity", //TOP156
"SG_Ready_2_DHW_Capacity", //TOP157
"Room_Operation_Max_Time", //TOP158
"DHW_Heatup_Max_Time", //TOP159
"Quiet_Mode_Actual_State", //TOP160
"Powerful_Mode_Actual_State", //TOP161
"Heat_Cool_Switch_State", //TOP162
"External_Switch_State", //TOP163
"Bivalent_Boiler_Contact_State", //TOP164
"Z2_Active", //TOP165
"Z1_Active", //TOP166
"Backup_Heater_Enable", //TOP167
"Cooling_Enable", //TOP168
"Heat_Request_Temp_With_Buffer", //TOP169
};

static const byte topicBytes[] PROGMEM = { //can store the index as byte (8-bit unsigned humber) as there aren't more then 255 bytes (actually only 203 bytes) to decode
Expand Down Expand Up @@ -367,6 +398,32 @@ static const byte topicBytes[] PROGMEM = { //can store the index as byte (8-bit
11, //TOP141
175, //TOP142
11, //TOP143
5, //TOP144
9, //TOP145
21, //TOP146
25, //TOP147
25, //TOP148
27, //TOP149
27, //TOP150
30, //TOP151
46, //TOP152
58, //TOP153
71, //TOP154
72, //TOP155
73, //TOP156
74, //TOP157
97, //TOP158
98, //TOP159
110, //TOP160
110, //TOP161
110, //TOP162
110, //TOP163
112, //TOP164
117, //TOP165
117, //TOP166
120, //TOP167
120, //TOP168
152, //TOP169
};


Expand Down Expand Up @@ -526,6 +583,32 @@ static const topicFP topicFunctions[] PROGMEM = {
getBit3and4, //TOP141
getIntMinus1, //TOP142
getBit7and8, //TOP143
getBit7and8, //TOP144
getBit3and4, //TOP145
getRawByte, //TOP146
getHeaterCapacity, //TOP147
getBit7and8, //TOP148
getBit5and6, //TOP149
getBit7and8, //TOP150
getBit7and8, //TOP151
getIntMinus128, //TOP152
getIntMinus128, //TOP153
getIntMinus1, //TOP154
getIntMinus1, //TOP155
getIntMinus1, //TOP156
getIntMinus1, //TOP157
getIntMinus1Times30, //TOP158
getIntMinus1, //TOP159
getBit1and2, //TOP160
getBit3and4, //TOP161
getBit5and6, //TOP162
getBit7and8, //TOP163
getBit1and2, //TOP164
getZone2Active, //TOP165
getZone1Active, //TOP166
getBit3and4, //TOP167
getBit5and6, //TOP168
getIntMinus128, //TOP169
};

static const char *DisabledEnabled[] PROGMEM = {"2", "Disabled", "Enabled"};
Expand Down Expand Up @@ -567,6 +650,12 @@ static const char *HeatingControl[] PROGMEM = {"2", "Comfort", "Efficiency"};
static const char *SmartDHW[] PROGMEM = {"2", "Variable", "Standard"};
static const char *QuietModePriority[] PROGMEM = {"2", "Sound", "Capacity"};
static const char *DHWSensorType[] PROGMEM = {"2", "Top", "Center"};
static const char *DHWCapacityMode[] PROGMEM = {"2", "Standard", "Variable"};
static const char *DHWHeaterType[] PROGMEM = {"2", "Internal", "External"};
static const char *Kilowatt[] PROGMEM = {"0", "kW"};
static const char *HeatCoolSwitch[] PROGMEM = {"2", "Heat", "Cool"};
static const char *ClosedOpen[] PROGMEM = {"2", "Closed", "Open"};
static const char *ZoneConfiguration[] PROGMEM = {"0", "Configuration code"};
static const char *Steps[] PROGMEM = {"0", "Steps"};

static const char **opttopicDescription[] PROGMEM = {
Expand Down Expand Up @@ -733,4 +822,30 @@ static const char **topicDescription[] PROGMEM = {
QuietModePriority, //TOP141
Steps, //TOP142
DHWSensorType, //TOP143
OffOn, //TOP144
DHWCapacityMode, //TOP145
ZoneConfiguration, //TOP146
Kilowatt, //TOP147
DHWHeaterType, //TOP148
DisabledEnabled, //TOP149
DisabledEnabled, //TOP150
DisabledEnabled, //TOP151
Celsius, //TOP152
Kelvin, //TOP153
Percent, //TOP154
Percent, //TOP155
Percent, //TOP156
Percent, //TOP157
Minutes, //TOP158
Minutes, //TOP159
OffOn, //TOP160
OffOn, //TOP161
HeatCoolSwitch, //TOP162
ClosedOpen, //TOP163
OffOn, //TOP164
InactiveActive, //TOP165
InactiveActive, //TOP166
InactiveActive, //TOP167
InactiveActive, //TOP168
Celsius, //TOP169
};
Loading
Loading