Skip to content

Commit

Permalink
Merge pull request #629 from IgorYbema/main
Browse files Browse the repository at this point in the history
release v3.9
  • Loading branch information
Egyras authored Jan 20, 2025
2 parents 5ab2a40 + 2cb132e commit 70ef308
Show file tree
Hide file tree
Showing 28 changed files with 1,207 additions and 601 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,21 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Update version.h
if: github.ref != 'refs/heads/main' #do not change version in main branch run
run: cd HeishaMon && echo "static const char *heishamon_version = \"Alpha-$(git rev-parse --short HEAD)\";" > version.h && cat version.h
shell: bash

- name: Setup Arduino CLI
uses: arduino/setup-arduino-cli@v1.1.1
uses: arduino/setup-arduino-cli@v2

- name: Install platform
run: |
arduino-cli core update-index
arduino-cli core install esp8266:esp8266
arduino-cli core install esp32:esp32
arduino-cli core install esp32:esp32@3.0.7
- name: Install dependencies
run: arduino-cli lib install ringbuffer pubsubclient arduinojson dallastemperature onewire "Adafruit NeoPixel"
Expand All @@ -48,10 +48,10 @@ jobs:

- name: Add MD5 checksum to ESP32 binary
run: cd HeishaMon && MD5=`md5sum HeishaMon.ino.bin | cut -d\ -f1` && mv HeishaMon.ino.bin HeishaMon_ESP32-alpha-$MD5.bin
shell: bash
shell: bash

- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: HeishaMon.ino.bin
path: HeishaMon/HeishaMon_*.bin
path: HeishaMon/HeishaMon_*.bin
2 changes: 1 addition & 1 deletion HeatPumpType.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Assuming that bytes from #129 to #138 are unique for each model of Aquarea heat
|26 | 42 D4 0B 83 71 42 D2 0C 46 55 | WH-ADC0309J3E5C | WH-UD07JE5 | KIT-ADC07JE5C | 7 | 1ph | HP - All-In-One Compact |
|27 | C2 D3 0C 34 65 B2 D3 0B 95 65 | Monoblock | WH-MDC07J3E5 | Monoblock | 7 | 1ph | HP (new version?) |
|28 | C2 D3 0C 33 65 B2 D3 0B 94 65 | Monoblock | WH-MDC05J3E5 | Monoblock | 5 | 1ph | HP (new version) |
|29 | E2 CF 0B 83 05 12 D0 0D 92 05 | WH-UQ12HE8 | WH-SQC12H9E8 | KIT-WQC12H9E8 | 12 | 3ph | T-CAP - Super Quiet |
|29 | E2 CF 0B 83 05 12 D0 0D 92 05 | WH-SQC12H9E8 | WH-UQ12HE8 | KIT-WQC12H9E8 | 12 | 3ph | T-CAP - Super Quiet |
|30 | E2 CF 0C 78 09 12 D0 0B 06 11 | WH-SXC12H6E5 | WH-UX12HE5 | KIT-WXC12H6E5 | 12 | 1ph | T-CAP |
|31 | C2 D3 0C 35 65 B2 D3 0B 96 65 | Monoblock | WH-MDC09J3E5 | Monoblock | 9 | 1ph | HP (new version?) |
|32 | 32 D4 0B 99 77 62 90 0B 01 78 | Monoblock | WH-MXC09J3E5 | Monoblock | 9 | 1ph | T-CAP
Expand Down
106 changes: 73 additions & 33 deletions HeishaMon/HeishaMon.ino
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ static uint8_t cmdnrel = 0;
WiFiClient mqtt_wifi_client;
PubSubClient mqtt_client;



bool firstConnectSinceBoot = true; //if this is true there is no first connection made yet

struct timerqueue_t **timerqueue = NULL;
Expand Down Expand Up @@ -195,12 +197,15 @@ void check_wifi() {
*/
#ifdef ESP8266
if ((heishamonSettings.wifi_ssid[0] != '\0') && (wifistatus != WL_DISCONNECTED) && (WiFi.scanComplete() != -1) && (WiFi.softAPgetStationNum() > 0)) {
log_message(_F("WiFi lost, but softAP station connecting, so stop trying to connect to configured ssid..."));
WiFi.disconnect(true);
}
#else
if ((heishamonSettings.wifi_ssid[0] != '\0') && (wifistatus != WL_STOPPED) && (WiFi.scanComplete() != -1) && (WiFi.softAPgetStationNum() > 0)) {
#endif
log_message(_F("WiFi lost, but softAP station connecting, so stop trying to connect to configured ssid..."));
WiFi.disconnect(true);
WiFi.mode(WIFI_AP);
}
#endif

/* only start this routine if timeout on
reconnecting to AP and SSID is set
Expand All @@ -221,22 +226,19 @@ void check_wifi() {
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
WiFi.softAP(_F("HeishaMon-Setup"));
}
if ((wifistatus == WL_STOPPED) && (WiFi.softAPgetStationNum() == 0)) {
if ((wifistatus == WL_STOPPED ) && (WiFi.softAPgetStationNum() == 0)) { //make sure we start STA again if it was stopped
log_message(_F("Retrying configured WiFi, ..."));
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); //select best AP with same SSID
#endif
if (heishamonSettings.wifi_password[0] == '\0') {
WiFi.begin(heishamonSettings.wifi_ssid);
} else {
WiFi.begin(heishamonSettings.wifi_ssid, heishamonSettings.wifi_password);
}
#ifdef ESP8266
} else {
log_message(_F("Reconnecting to WiFi failed. Waiting a few seconds before trying again."));
#ifdef ESP8266
WiFi.disconnect(true);
#else
WiFi.mode(WIFI_MODE_APSTA);
WiFi.disconnect(true);
WiFi.mode(WIFI_MODE_AP);
#endif
}
}
Expand Down Expand Up @@ -354,11 +356,10 @@ void mqtt_reconnect()
resetlastalldatatime(); //resend all heatpump values to mqtt
}
//use this to receive valid heishamon raw data from other heishamon to debug this OT code
#define OTDEBUG
#ifdef OTDEBUG
if ( heishamonSettings.listenonly && heishamonSettings.listenmqtt ) {
sprintf(topic, "%s/raw/data", heishamonSettings.mqtt_topic_listen);
mqtt_client.subscribe(topic); //subscribe to raw heatpump data over MQTT
//#define RAWDEBUG
#ifdef RAWDEBUG
if ( heishamonSettings.listenonly) {
mqtt_client.subscribe((char*)"panasonic_heat_pump/raw/data"); //subscribe to raw heatpump data over MQTT
}
#endif
}
Expand Down Expand Up @@ -387,7 +388,7 @@ void log_message(char* string)
struct tm *timeinfo = localtime(&rawtime);
char timestring[32];
strftime(timestring, 32, "%c", timeinfo);
size_t len = strlen(string) + strlen(timestring) + 20; //+20 long enough to contain millis()
size_t len = strlen(string) + strlen(timestring) + 32; //+32 long enough to contain millis() and the json part later for websocket mesg
char* log_line = (char *) malloc(len);
snprintf(log_line, len, "%s (%lu): %s", timestring, millis(), string);

Expand All @@ -408,11 +409,10 @@ void log_message(char* string)
mqtt_client.disconnect();
}
}
char* websocketMsg = (char *) malloc(len+12);
snprintf(websocketMsg, len+12, "{\"logMsg\":\"%s\"}", log_line);
//send log message to websocket
snprintf(log_line, len+12, "{\"logMsg\":\"%s (%lu): %s\"}", timestring, millis(), string);
websocket_write_all(log_line, strlen(log_line));
free(log_line);
websocket_write_all(websocketMsg, strlen(websocketMsg));
free(websocketMsg);
#ifdef ESP32
if (!inSetup) blinkNeoPixel(false);
#endif
Expand Down Expand Up @@ -707,10 +707,9 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
{
char* topic_sendcommand = topic_command + strlen(mqtt_topic_commands) + 1; //strip the first 9 "commands/" from the topic to get what we need
send_heatpump_command(topic_sendcommand, msg, send_command, log_message, heishamonSettings.optionalPCB);
}
//use this to receive valid heishamon raw data from other heishamon to debug this OT code
#ifdef OTDEBUG
else if (strcmp((char*)"panasonic_heat_pump/data", topic) == 0) { // check for raw heatpump input
#ifdef RAWDEBUG
} else if (strcmp((char*)"panasonic_heat_pump/raw/data", topic) == 0) { // check for raw heatpump input
sprintf_P(log_msg, PSTR("Received raw heatpump data from MQTT"));
log_message(log_msg);
decode_heatpump_data(msg, actData, mqtt_client, log_message, heishamonSettings.mqtt_topic_base, heishamonSettings.updateAllTime);
Expand Down Expand Up @@ -750,7 +749,11 @@ void setupOTA() {
ArduinoOTA.begin();
}



int8_t webserver_cb(struct webserver_t *client, void *dat) {


switch (client->step) {
case WEBSERVER_CLIENT_REQUEST_METHOD: {
if (strcmp_P((char *)dat, PSTR("POST")) == 0) {
Expand Down Expand Up @@ -1172,8 +1175,6 @@ void factoryReset() {
yield();
}
#else
pixels.begin();
pixels.clear();
while (true) {
delay(100);
pixels.setPixelColor(0, 128, 0, 0);
Expand Down Expand Up @@ -1414,8 +1415,6 @@ void setup() {
yield();
}
#else
pixels.begin();
pixels.clear();
while (true) {
delay(50);
pixels.setPixelColor(0, 128, 0, 0);
Expand Down Expand Up @@ -1486,16 +1485,21 @@ void setup() {
}

loggingSerial.println(F("Enabling rules.."));
if (heishamonSettings.force_rules == false) {
#if defined(ESP8266)
rst_info *resetInfo = ESP.getResetInfoPtr();
loggingSerial.printf(PSTR("Reset reason: %d, exception cause: %d\n"), resetInfo->reason, resetInfo->exccause);
if (resetInfo->reason > 0 && resetInfo->reason < 4) {
if (resetInfo->reason > 0 && resetInfo->reason < 4) {
#elif defined(ESP32)
esp_reset_reason_t reset_reason = esp_reset_reason();
loggingSerial.printf(PSTR("Reset reason: %d\n"), reset_reason);
if (reset_reason > 3 && reset_reason < 12) { //is this correct for esp32?
esp_reset_reason_t reset_reason = esp_reset_reason();
loggingSerial.printf(PSTR("Reset reason: %d\n"), reset_reason);
if (reset_reason > 3 && reset_reason < 12) { //is this correct for esp32?
#endif
loggingSerial.println("Not loading rules due to crash reboot!");
loggingSerial.println("Not loading rules due to crash reboot!");
} else {
rules_parse((char *)"/rules.txt");
rules_boot();
}
} else {
rules_parse((char *)"/rules.txt");
rules_boot();
Expand Down Expand Up @@ -1532,8 +1536,8 @@ void send_panasonic_query() {
send_command(panasonicQuery, PANASONICQUERYSIZE);
panasonicQuery[3] = 0x10; //setting 4th back to 0x10 for normal data request next time
} else {
if ((actData[0] == 0x71) && (actData[1] == 0xc8) && (actData[2] == 0x01) && (actData[193] == 0) && (actData[195] == 0) && (actData[197] == 0) ) { //do we have valid data but 0 value in heat consumptiom power, then assume K or L series
//can be replaced with: if ((actData[0] == 0x71) && (actData[0xc7] >= 3) ) { //do we have valid header and byte 0xc7 is more or equal 3 then assume K&L series
//if ((actData[0] == 0x71) && (actData[1] == 0xc8) && (actData[2] == 0x01) && (actData[193] == 0) && (actData[195] == 0) && (actData[197] == 0) ) { //do we have valid data but 0 value in heat consumptiom power, then assume K or L series
if ((actData[0] == 0x71) && (actData[0xc7] >= 3) ) { //do we have valid header and byte 0xc7 is more or equal 3 then assume K&L and more series
log_message(_F("Assuming K or L heatpump type due to missing heat/cool/dhw power data"));
extraDataBlockAvailable = true; //request for extra data next run
}
Expand Down Expand Up @@ -1584,7 +1588,7 @@ void loop() {

// check wifi
check_wifi();
// Handle OTA first.
// Handle OTA first.s
ArduinoOTA.handle();

mqtt_client.loop();
Expand Down Expand Up @@ -1728,12 +1732,48 @@ void loop() {
stats += timeoutread;
stats += F(",\"version\":\"");
stats += heishamon_version;
stats += F("\",\"board\":\"");
#ifdef ESP8266
stats += F("ESP8266");
#else
stats += F("ESP32");
#endif
stats += F("\",\"rules active\":");
stats += nrrules;
stats += F("}");
sprintf_P(mqtt_topic, PSTR("%s/stats"), heishamonSettings.mqtt_topic_base);
mqtt_client.publish(mqtt_topic, stats.c_str(), MQTT_RETAIN_VALUES);

//websocket stats
#ifdef ESP32
String ethernetStat;
if (ETH.phyAddr() != 0) {
if (ETH.connected()) {
if (ETH.hasIP()) {
ethernetStat = F("connected - IP: ");
ethernetStat += ETH.localIP().toString();
ethernetStat += F(")");
} else {
ethernetStat = F("connected - no IP");
}
}
else {
ethernetStat = F("not connected");
}
} else {
ethernetStat = F("not installed");
}
char *getuptime = getUptime();
sprintf_P(log_msg, PSTR("{\"data\": {\"stats\": {\"wifi\": %d, \"ethernet\": \"%s\", \"memory\": %d, \"correct\": %.0f,\"mqtt\": %d,\"uptime\": \"%s\"}}}"), getWifiQuality(), ethernetStat.c_str(), getFreeMemory(), readpercentage, mqttReconnects, getuptime);
free(getuptime);
#else
char *getuptime = getUptime();
sprintf_P(log_msg, PSTR("{\"data\": {\"stats\": {\"wifi\": %d, \"memory\": %d, \"correct\": %.0f,\"mqtt\": %d,\"uptime\": \"%s\"}}}"), getWifiQuality(), getFreeMemory(), readpercentage, mqttReconnects, getuptime);
free(getuptime);
#endif

websocket_write_all(log_msg, strlen(log_msg));

//get new data
if (!heishamonSettings.listenonly) send_panasonic_query();

Expand Down
22 changes: 21 additions & 1 deletion HeishaMon/HeishaOT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,20 @@ void processOTRequest(unsigned long request, OpenThermResponseStatus status) {
if ((bool)CHEnable != getOTStructMember(_F("chEnable"))->value.b) { //only publish if changed
getOTStructMember(_F("chEnable"))->value.b = (bool)CHEnable;
CHEnable ? mqttPublish((char*)mqtt_topic_opentherm_write, _F("chEnable"), _F("true")) : mqttPublish((char*)mqtt_topic_opentherm_write, _F("chEnable"), _F("false")) ;
sprintf_P(log_msg, PSTR("{\"data\": {\"opentherm\": {\"name\": \"%s\", \"value\": %s}}}"), _F("chEnable"), CHEnable ? _F("true") : _F("false"));
websocket_write_all(log_msg, strlen(log_msg));
}
if ((bool)DHWEnable != getOTStructMember(_F("dhwEnable"))->value.b) { //only publish if changed
getOTStructMember(_F("dhwEnable"))->value.b = (bool)DHWEnable;
DHWEnable ? mqttPublish((char*)mqtt_topic_opentherm_write, _F("dhwEnable"), _F("true")) : mqttPublish((char*)mqtt_topic_opentherm_write, _F("dhwEnable"), _F("false")) ;
sprintf_P(log_msg, PSTR("{\"data\": {\"opentherm\": {\"name\": \"%s\", \"value\": %s}}}"), _F("dhwEnable"), DHWEnable ? _F("true") : _F("false"));
websocket_write_all(log_msg, strlen(log_msg));
}
if ((bool)Cooling != getOTStructMember(_F("coolingEnable"))->value.b) { //only publish if changed
getOTStructMember(_F("coolingEnable"))->value.b = (bool)Cooling;
Cooling ? mqttPublish((char*)mqtt_topic_opentherm_write, _F("coolingEnable"), _F("true")) : mqttPublish((char*)mqtt_topic_opentherm_write, _F("coolingEnable"), _F("false")) ;
sprintf_P(log_msg, PSTR("{\"data\": {\"opentherm\": {\"name\": \"%s\", \"value\": %s}}}"), _F("coolingEnable"), Cooling ? _F("true") : _F("false"));
websocket_write_all(log_msg, strlen(log_msg));
}

sprintf_P(log_msg, PSTR(
Expand Down Expand Up @@ -129,6 +135,8 @@ void processOTRequest(unsigned long request, OpenThermResponseStatus status) {
if (getOTStructMember(_F("chSetpoint"))->value.f != ot.getFloat(request)) { //only publish if changed
getOTStructMember(_F("chSetpoint"))->value.f = ot.getFloat(request);
mqttPublish((char*)mqtt_topic_opentherm_write, _F("chSetpoint"), str);
sprintf_P(log_msg, PSTR("{\"data\": {\"opentherm\": {\"name\": \"%s\", \"value\": %.2f}}}"), _F("chSetpoint"), getOTStructMember(_F("chSetpoint"))->value.f);
websocket_write_all(log_msg, strlen(log_msg));
}
otResponse = ot.buildResponse(OpenThermMessageType::WRITE_ACK, OpenThermMessageID::TSet, request & 0xffff);
rules_event_cb(_F("?"), _F("chsetpoint"));
Expand Down Expand Up @@ -170,6 +178,8 @@ void processOTRequest(unsigned long request, OpenThermResponseStatus status) {
getOTStructMember(_F("relativeModulation"))->value.f = getOTStructMember(_F("maxRelativeModulation"))->value.f;
}
mqttPublish((char*)mqtt_topic_opentherm_write, _F("maxRelativeModulation"), str);
sprintf_P(log_msg, PSTR("{\"data\": {\"opentherm\": {\"name\": \"%s\", \"value\": %.2f}}}"), _F("maxRelativeModulation"), getOTStructMember(_F("maxRelativeModulation"))->value.f);
websocket_write_all(log_msg, strlen(log_msg));
}
otResponse = ot.buildResponse(OpenThermMessageType::WRITE_ACK, OpenThermMessageID::MaxRelModLevelSetting, request & 0xffff); //ACK for mandatory fields
} break;
Expand Down Expand Up @@ -212,6 +222,8 @@ void processOTRequest(unsigned long request, OpenThermResponseStatus status) {
if (getOTStructMember(_F("coolingControl"))->value.f != ot.getFloat(request)) {
getOTStructMember(_F("coolingControl"))->value.f = ot.getFloat(request);
mqttPublish((char*)mqtt_topic_opentherm_write, _F("coolingControl"), str);
sprintf_P(log_msg, PSTR("{\"data\": {\"opentherm\": {\"name\": \"%s\", \"value\": %.2f}}}"), _F("coolingControl"), getOTStructMember(_F("coolingControl"))->value.f);
websocket_write_all(log_msg, strlen(log_msg));
}
otResponse = ot.buildResponse(OpenThermMessageType::WRITE_ACK, OpenThermMessageID::CoolingControl, request & 0xffff);
rules_event_cb(_F("?"), _F("coolingControl"));
Expand All @@ -238,6 +250,8 @@ void processOTRequest(unsigned long request, OpenThermResponseStatus status) {
if (getOTStructMember(_F("roomTemp"))->value.f != ot.getFloat(request)) {
mqttPublish((char*)mqtt_topic_opentherm_write, _F("roomTemp"), str);
getOTStructMember(_F("roomTemp"))->value.f = ot.getFloat(request);
sprintf_P(log_msg, PSTR("{\"data\": {\"opentherm\": {\"name\": \"%s\", \"value\": %.2f}}}"), _F("roomTemp"), getOTStructMember(_F("roomTemp"))->value.f);
websocket_write_all(log_msg, strlen(log_msg));
}
otResponse = ot.buildResponse(OpenThermMessageType::WRITE_ACK, OpenThermMessageID::Tr, request & 0xffff);
rules_event_cb(_F("?"), _F("roomtemp"));
Expand All @@ -250,6 +264,8 @@ void processOTRequest(unsigned long request, OpenThermResponseStatus status) {
if (getOTStructMember(_F("roomTempSet"))->value.f != ot.getFloat(request)) {
getOTStructMember(_F("roomTempSet"))->value.f = ot.getFloat(request);
mqttPublish((char*)mqtt_topic_opentherm_write, _F("roomTempSet"), str);
sprintf_P(log_msg, PSTR("{\"data\": {\"opentherm\": {\"name\": \"%s\", \"value\": %.2f}}}"), _F("roomTempSet"), getOTStructMember(_F("roomTempSet"))->value.f);
websocket_write_all(log_msg, strlen(log_msg));
}
otResponse = ot.buildResponse(OpenThermMessageType::WRITE_ACK, OpenThermMessageID::TrSet, request & 0xffff);
rules_event_cb(_F("?"), _F("roomtempset"));
Expand All @@ -262,7 +278,9 @@ void processOTRequest(unsigned long request, OpenThermResponseStatus status) {
log_message(log_msg);
if (getOTStructMember(_F("dhwSetpoint"))->value.f != ot.getFloat(request)) {
getOTStructMember(_F("dhwSetpoint"))->value.f = ot.getFloat(request);
mqttPublish((char*)mqtt_topic_opentherm_write, _F("dhwSetpoint"), str);
mqttPublish((char*)mqtt_topic_opentherm_write, _F("dhwSetpoint"), str);
sprintf_P(log_msg, PSTR("{\"data\": {\"opentherm\": {\"name\": \"%s\", \"value\": %.2f}}}"), _F("dhwSetpoint"), getOTStructMember(_F("dhwSetpoint"))->value.f);
websocket_write_all(log_msg, strlen(log_msg));
}
otResponse = ot.buildResponse(OpenThermMessageType::WRITE_ACK, OpenThermMessageID::TdhwSet, ot.temperatureToData(getOTStructMember(_F("dhwSetpoint"))->value.f));
} else { //READ_DATA
Expand All @@ -281,6 +299,8 @@ void processOTRequest(unsigned long request, OpenThermResponseStatus status) {
if (getOTStructMember(_F("maxTSet"))->value.f != ot.getFloat(request)) {
getOTStructMember(_F("maxTSet"))->value.f = ot.getFloat(request);
mqttPublish((char*)mqtt_topic_opentherm_write, _F("maxTSet"), str);
sprintf_P(log_msg, PSTR("{\"data\": {\"opentherm\": {\"name\": \"%s\", \"value\": %.2f}}}"), _F("maxTSet"), getOTStructMember(_F("maxTSet"))->value.f);
websocket_write_all(log_msg, strlen(log_msg));
}
otResponse = ot.buildResponse(OpenThermMessageType::WRITE_ACK, OpenThermMessageID::MaxTSet, ot.temperatureToData(getOTStructMember(_F("maxTSet"))->value.f));
} else { //READ_DATA
Expand Down
Loading

0 comments on commit 70ef308

Please sign in to comment.