From 9ffed7bbc8812b125637fe69c466c956750cde1c Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Wed, 29 Sep 2021 02:30:59 -0700 Subject: [PATCH 01/18] move constant declarations outside the function calls, no need to re-run them for each call. --- sources/inverter-mqtt/mqtt-push.sh | 32 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/sources/inverter-mqtt/mqtt-push.sh b/sources/inverter-mqtt/mqtt-push.sh index 5b1002c..a143594 100755 --- a/sources/inverter-mqtt/mqtt-push.sh +++ b/sources/inverter-mqtt/mqtt-push.sh @@ -1,14 +1,24 @@ #!/bin/bash + +MQTT_SERVER=`cat /etc/inverter/mqtt.json | jq '.server' -r` +MQTT_PORT=`cat /etc/inverter/mqtt.json | jq '.port' -r` +MQTT_TOPIC=`cat /etc/inverter/mqtt.json | jq '.topic' -r` +MQTT_DEVICENAME=`cat /etc/inverter/mqtt.json | jq '.devicename' -r` +MQTT_USERNAME=`cat /etc/inverter/mqtt.json | jq '.username' -r` +MQTT_PASSWORD=`cat /etc/inverter/mqtt.json | jq '.password' -r` + INFLUX_ENABLED=`cat /etc/inverter/mqtt.json | jq '.influx.enabled' -r` +if [[ $INFLUX_ENABLED == "true" ]] ; then + INFLUX_HOST=`cat /etc/inverter/mqtt.json | jq '.influx.host' -r` + INFLUX_USERNAME=`cat /etc/inverter/mqtt.json | jq '.influx.username' -r` + INFLUX_PASSWORD=`cat /etc/inverter/mqtt.json | jq '.influx.password' -r` + INFLUX_DEVICE=`cat /etc/inverter/mqtt.json | jq '.influx.device' -r` + INFLUX_PREFIX=`cat /etc/inverter/mqtt.json | jq '.influx.prefix' -r` + INFLUX_DATABASE=`cat /etc/inverter/mqtt.json | jq '.influx.database' -r` + INFLUX_MEASUREMENT_NAME=`cat /etc/inverter/mqtt.json | jq '.influx.namingMap.'$1'' -r` +fi pushMQTTData () { - MQTT_SERVER=`cat /etc/inverter/mqtt.json | jq '.server' -r` - MQTT_PORT=`cat /etc/inverter/mqtt.json | jq '.port' -r` - MQTT_TOPIC=`cat /etc/inverter/mqtt.json | jq '.topic' -r` - MQTT_DEVICENAME=`cat /etc/inverter/mqtt.json | jq '.devicename' -r` - MQTT_USERNAME=`cat /etc/inverter/mqtt.json | jq '.username' -r` - MQTT_PASSWORD=`cat /etc/inverter/mqtt.json | jq '.password' -r` - mosquitto_pub \ -h $MQTT_SERVER \ -p $MQTT_PORT \ @@ -23,14 +33,6 @@ pushMQTTData () { } pushInfluxData () { - INFLUX_HOST=`cat /etc/inverter/mqtt.json | jq '.influx.host' -r` - INFLUX_USERNAME=`cat /etc/inverter/mqtt.json | jq '.influx.username' -r` - INFLUX_PASSWORD=`cat /etc/inverter/mqtt.json | jq '.influx.password' -r` - INFLUX_DEVICE=`cat /etc/inverter/mqtt.json | jq '.influx.device' -r` - INFLUX_PREFIX=`cat /etc/inverter/mqtt.json | jq '.influx.prefix' -r` - INFLUX_DATABASE=`cat /etc/inverter/mqtt.json | jq '.influx.database' -r` - INFLUX_MEASUREMENT_NAME=`cat /etc/inverter/mqtt.json | jq '.influx.namingMap.'$1'' -r` - curl -i -XPOST "$INFLUX_HOST/write?db=$INFLUX_DATABASE&precision=s" -u "$INFLUX_USERNAME:$INFLUX_PASSWORD" --data-binary "$INFLUX_PREFIX,device=$INFLUX_DEVICE $INFLUX_MEASUREMENT_NAME=$2" } From 73aa98678cf7d33dc008a2d6e54d079ce67e389c Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Sat, 2 Oct 2021 02:08:39 -0700 Subject: [PATCH 02/18] - loop the subscriber process to handle mqtt server restarts - reduce unnecessary wrapper processes --- sources/inverter-mqtt/entrypoint.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sources/inverter-mqtt/entrypoint.sh b/sources/inverter-mqtt/entrypoint.sh index a459cd3..85ef711 100755 --- a/sources/inverter-mqtt/entrypoint.sh +++ b/sources/inverter-mqtt/entrypoint.sh @@ -6,10 +6,11 @@ export TERM=xterm # Init the mqtt server for the first time, then every 5 minutes # This will re-create the auto-created topics in the MQTT server if HA is restarted... -watch -n 300 /opt/inverter-mqtt/mqtt-init.sh > /dev/null 2>&1 & +watch -xn 300 /opt/inverter-mqtt/mqtt-init.sh & # Run the MQTT Subscriber process in the background (so that way we can change the configuration on the inverter from home assistant) -/opt/inverter-mqtt/mqtt-subscriber.sh & +# This normally doesn't exit, but the watch is needed to handle mqtt server restarts. +watch -xn 1 /opt/inverter-mqtt/mqtt-subscriber.sh & # execute exactly every 30 seconds... -watch -n 30 /opt/inverter-mqtt/mqtt-push.sh > /dev/null 2>&1 +watch -xn 30 /opt/inverter-mqtt/mqtt-push.sh From 7dd92830164ca242c7cba5a459ad44923af2094a Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Sat, 2 Oct 2021 02:10:22 -0700 Subject: [PATCH 03/18] change temperature icon --- sources/inverter-mqtt/mqtt-init.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/inverter-mqtt/mqtt-init.sh b/sources/inverter-mqtt/mqtt-init.sh index e9e6197..e03b9b2 100755 --- a/sources/inverter-mqtt/mqtt-init.sh +++ b/sources/inverter-mqtt/mqtt-init.sh @@ -52,7 +52,7 @@ registerTopic "Load_watt" "W" "chart-bell-curve" registerTopic "Load_watthour" "Wh" "chart-bell-curve" registerTopic "Load_va" "VA" "chart-bell-curve" registerTopic "Bus_voltage" "V" "details" -registerTopic "Heatsink_temperature" "" "details" +registerTopic "Heatsink_temperature" "C" "thermometer" registerTopic "Battery_capacity" "%" "battery-outline" registerTopic "Battery_voltage" "V" "battery-outline" registerTopic "Battery_charge_current" "A" "current-dc" From be2d786cb30bbd7c3ad6a17cf4a0f892d60432da Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Sat, 2 Oct 2021 02:10:41 -0700 Subject: [PATCH 04/18] update mqtt immediately after running a command --- sources/inverter-mqtt/mqtt-subscriber.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/sources/inverter-mqtt/mqtt-subscriber.sh b/sources/inverter-mqtt/mqtt-subscriber.sh index f30ce27..0e28ef0 100755 --- a/sources/inverter-mqtt/mqtt-subscriber.sh +++ b/sources/inverter-mqtt/mqtt-subscriber.sh @@ -12,5 +12,6 @@ do echo "Incoming request send: [$rawcmd] to inverter." /opt/inverter-cli/bin/inverter_poller -r $rawcmd; + /opt/inverter-mqtt/mqtt-push.sh done < <(mosquitto_sub -h $MQTT_SERVER -p $MQTT_PORT -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" -t "$MQTT_TOPIC/sensor/$MQTT_DEVICENAME" -q 1) From dbea4695dae633bbec67f61ac4d732fb8e2f3667 Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Sat, 2 Oct 2021 05:10:06 -0700 Subject: [PATCH 05/18] - @nrm21 inspired changes--allows sending commands longer than 5 bytes and removes the need to provide qpiri/qpiws/qmod/qpigs reply sizes in the config. - also fixed some compiler warnings. --- sources/inverter-cli/inverter.cpp | 61 +++++++++++++++++++++---------- sources/inverter-cli/inverter.h | 4 +- sources/inverter-cli/main.cpp | 11 +----- 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/sources/inverter-cli/inverter.cpp b/sources/inverter-cli/inverter.cpp index 45711f4..cd910f1 100644 --- a/sources/inverter-cli/inverter.cpp +++ b/sources/inverter-cli/inverter.cpp @@ -9,16 +9,12 @@ #include #include -cInverter::cInverter(std::string devicename, int qpiri, int qpiws, int qmod, int qpigs) { +cInverter::cInverter(std::string devicename) { device = devicename; status1[0] = 0; status2[0] = 0; warnings[0] = 0; mode = 0; - qpiri = qpiri; - qpiws = qpiws; - qmod = qmod; - qpigs = qpigs; } string *cInverter::GetQpigsStatus() { @@ -68,7 +64,7 @@ int cInverter::GetMode() { return result; } -bool cInverter::query(const char *cmd, int replysize) { +bool cInverter::query(const char *cmd) { time_t started; int fd; int i=0, n; @@ -109,14 +105,35 @@ bool cInverter::query(const char *cmd, int replysize) { buf[n++] = crc >> 8; buf[n++] = crc & 0xff; - buf[n++] = 0x0d; + buf[n++] = 0x0d; // '\r' + buf[n+1] = '\0'; // see workaround below + + // send a command + int chunk_size = 8; + for (int offset = 0; offset < n; usleep(50000)) { + int left = n - offset; + int towrite = left > chunk_size ? chunk_size : left; + // WORKAROUND: For some reason, writing 1 byte causes it to error. + // However, since we padded with '\0' above, we can give it 2 instead. + // I don't know of any 6 (+ 2*CRC + '\r') byte commnads to test it on + // but this at least gets it to return NAK. + if (towrite == 1) towrite = 2; + //lprintf("DEBUG: offset %d, writing %d", offset, towrite); + ssize_t written = write(fd, &buf[offset], towrite); + if (written > 0) + offset += written; + else { + lprintf("INVERTER: command write failed (written=%d, errno=%d: %s)", written, errno, strerror(errno)); + break; + } + //lprintf("DEBUG: %d bytes to write, %d bytes written", n, offset); + } - //send a command - write(fd, &buf, n); + char *startbuf = 0; + char *endbuf = 0; time(&started); - do { - n = read(fd, (void*)buf+i, replysize-i); + n = read(fd, &buf[i], 120-i); if (n < 0) { if (time(NULL) - started > 2) { lprintf("INVERTER: %s read timeout", cmd); @@ -128,10 +145,14 @@ bool cInverter::query(const char *cmd, int replysize) { } i += n; - } while (i (%d bytes)", cmd, i); return false; } } @@ -163,7 +184,7 @@ void cInverter::poll() { // Reading mode if (!ups_qmod_changed) { - if (query("QMOD", qmod)) { + if (query("QMOD")) { SetMode(buf[1]); ups_qmod_changed = true; } @@ -171,7 +192,7 @@ void cInverter::poll() { // reading status (QPIGS) if (!ups_qpigs_changed) { - if (query("QPIGS", qpigs)) { + if (query("QPIGS")) { m.lock(); strcpy(status1, (const char*)buf+1); m.unlock(); @@ -181,7 +202,7 @@ void cInverter::poll() { // Reading QPIRI status if (!ups_qpiri_changed) { - if (query("QPIRI", qpiri)) { + if (query("QPIRI")) { m.lock(); strcpy(status2, (const char*)buf+1); m.unlock(); @@ -191,7 +212,7 @@ void cInverter::poll() { // Get any device warnings... if (!ups_qpiws_changed) { - if (query("QPIWS", qpiws)) { + if (query("QPIWS")) { m.lock(); strcpy(warnings, (const char*)buf+1); m.unlock(); @@ -205,7 +226,7 @@ void cInverter::poll() { void cInverter::ExecuteCmd(const string cmd) { // Sending any command raw - if (query(cmd.data(), 7)) { + if (query(cmd.data())) { m.lock(); strcpy(status2, (const char*)buf+1); m.unlock(); diff --git a/sources/inverter-cli/inverter.h b/sources/inverter-cli/inverter.h index 3501b6b..f406599 100644 --- a/sources/inverter-cli/inverter.h +++ b/sources/inverter-cli/inverter.h @@ -19,11 +19,11 @@ class cInverter { void SetMode(char newmode); bool CheckCRC(unsigned char *buff, int len); - bool query(const char *cmd, int replysize); + bool query(const char *cmd); uint16_t cal_crc_half(uint8_t *pin, uint8_t len); public: - cInverter(std::string devicename, int qpiri, int qpiws, int qmod, int qpigs); + cInverter(std::string devicename); void poll(); void runMultiThread() { std::thread t1(&cInverter::poll, this); diff --git a/sources/inverter-cli/main.cpp b/sources/inverter-cli/main.cpp index 7650fab..8802e87 100644 --- a/sources/inverter-cli/main.cpp +++ b/sources/inverter-cli/main.cpp @@ -43,7 +43,6 @@ string devicename; int runinterval; float ampfactor; float wattfactor; -int qpiri, qpiws, qmod, qpigs; // --------------------------------------- @@ -91,14 +90,6 @@ void getSettingsFile(string filename) { attemptAddSetting(&wattfactor, linepart2); else if(linepart1 == "watt_factor") attemptAddSetting(&wattfactor, linepart2); - else if(linepart1 == "qpiri") - attemptAddSetting(&qpiri, linepart2); - else if(linepart1 == "qpiws") - attemptAddSetting(&qpiws, linepart2); - else if(linepart1 == "qmod") - attemptAddSetting(&qmod, linepart2); - else if(linepart1 == "qpigs") - attemptAddSetting(&qpigs, linepart2); else continue; } @@ -180,7 +171,7 @@ int main(int argc, char* argv[]) { } bool ups_status_changed(false); - ups = new cInverter(devicename,qpiri,qpiws,qmod,qpigs); + ups = new cInverter(devicename); // Logic to send 'raw commands' to the inverter.. if (!rawcmd.empty()) { From 5cb57c59aa713758eb73e0935cbc6d61ee5ca024 Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Sat, 2 Oct 2021 05:10:06 -0700 Subject: [PATCH 06/18] - @nrm21 inspired changes--allows sending commands longer than 5 bytes and removes the need to provide qpiri/qpiws/qmod/qpigs reply sizes in the config. - also fixed some compiler warnings. --- sources/inverter-cli/inverter.cpp | 61 +++++++++++++++++++++---------- sources/inverter-cli/inverter.h | 4 +- sources/inverter-cli/main.cpp | 11 +----- 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/sources/inverter-cli/inverter.cpp b/sources/inverter-cli/inverter.cpp index 45711f4..cd910f1 100644 --- a/sources/inverter-cli/inverter.cpp +++ b/sources/inverter-cli/inverter.cpp @@ -9,16 +9,12 @@ #include #include -cInverter::cInverter(std::string devicename, int qpiri, int qpiws, int qmod, int qpigs) { +cInverter::cInverter(std::string devicename) { device = devicename; status1[0] = 0; status2[0] = 0; warnings[0] = 0; mode = 0; - qpiri = qpiri; - qpiws = qpiws; - qmod = qmod; - qpigs = qpigs; } string *cInverter::GetQpigsStatus() { @@ -68,7 +64,7 @@ int cInverter::GetMode() { return result; } -bool cInverter::query(const char *cmd, int replysize) { +bool cInverter::query(const char *cmd) { time_t started; int fd; int i=0, n; @@ -109,14 +105,35 @@ bool cInverter::query(const char *cmd, int replysize) { buf[n++] = crc >> 8; buf[n++] = crc & 0xff; - buf[n++] = 0x0d; + buf[n++] = 0x0d; // '\r' + buf[n+1] = '\0'; // see workaround below + + // send a command + int chunk_size = 8; + for (int offset = 0; offset < n; usleep(50000)) { + int left = n - offset; + int towrite = left > chunk_size ? chunk_size : left; + // WORKAROUND: For some reason, writing 1 byte causes it to error. + // However, since we padded with '\0' above, we can give it 2 instead. + // I don't know of any 6 (+ 2*CRC + '\r') byte commnads to test it on + // but this at least gets it to return NAK. + if (towrite == 1) towrite = 2; + //lprintf("DEBUG: offset %d, writing %d", offset, towrite); + ssize_t written = write(fd, &buf[offset], towrite); + if (written > 0) + offset += written; + else { + lprintf("INVERTER: command write failed (written=%d, errno=%d: %s)", written, errno, strerror(errno)); + break; + } + //lprintf("DEBUG: %d bytes to write, %d bytes written", n, offset); + } - //send a command - write(fd, &buf, n); + char *startbuf = 0; + char *endbuf = 0; time(&started); - do { - n = read(fd, (void*)buf+i, replysize-i); + n = read(fd, &buf[i], 120-i); if (n < 0) { if (time(NULL) - started > 2) { lprintf("INVERTER: %s read timeout", cmd); @@ -128,10 +145,14 @@ bool cInverter::query(const char *cmd, int replysize) { } i += n; - } while (i (%d bytes)", cmd, i); return false; } } @@ -163,7 +184,7 @@ void cInverter::poll() { // Reading mode if (!ups_qmod_changed) { - if (query("QMOD", qmod)) { + if (query("QMOD")) { SetMode(buf[1]); ups_qmod_changed = true; } @@ -171,7 +192,7 @@ void cInverter::poll() { // reading status (QPIGS) if (!ups_qpigs_changed) { - if (query("QPIGS", qpigs)) { + if (query("QPIGS")) { m.lock(); strcpy(status1, (const char*)buf+1); m.unlock(); @@ -181,7 +202,7 @@ void cInverter::poll() { // Reading QPIRI status if (!ups_qpiri_changed) { - if (query("QPIRI", qpiri)) { + if (query("QPIRI")) { m.lock(); strcpy(status2, (const char*)buf+1); m.unlock(); @@ -191,7 +212,7 @@ void cInverter::poll() { // Get any device warnings... if (!ups_qpiws_changed) { - if (query("QPIWS", qpiws)) { + if (query("QPIWS")) { m.lock(); strcpy(warnings, (const char*)buf+1); m.unlock(); @@ -205,7 +226,7 @@ void cInverter::poll() { void cInverter::ExecuteCmd(const string cmd) { // Sending any command raw - if (query(cmd.data(), 7)) { + if (query(cmd.data())) { m.lock(); strcpy(status2, (const char*)buf+1); m.unlock(); diff --git a/sources/inverter-cli/inverter.h b/sources/inverter-cli/inverter.h index 3501b6b..f406599 100644 --- a/sources/inverter-cli/inverter.h +++ b/sources/inverter-cli/inverter.h @@ -19,11 +19,11 @@ class cInverter { void SetMode(char newmode); bool CheckCRC(unsigned char *buff, int len); - bool query(const char *cmd, int replysize); + bool query(const char *cmd); uint16_t cal_crc_half(uint8_t *pin, uint8_t len); public: - cInverter(std::string devicename, int qpiri, int qpiws, int qmod, int qpigs); + cInverter(std::string devicename); void poll(); void runMultiThread() { std::thread t1(&cInverter::poll, this); diff --git a/sources/inverter-cli/main.cpp b/sources/inverter-cli/main.cpp index 7650fab..8802e87 100644 --- a/sources/inverter-cli/main.cpp +++ b/sources/inverter-cli/main.cpp @@ -43,7 +43,6 @@ string devicename; int runinterval; float ampfactor; float wattfactor; -int qpiri, qpiws, qmod, qpigs; // --------------------------------------- @@ -91,14 +90,6 @@ void getSettingsFile(string filename) { attemptAddSetting(&wattfactor, linepart2); else if(linepart1 == "watt_factor") attemptAddSetting(&wattfactor, linepart2); - else if(linepart1 == "qpiri") - attemptAddSetting(&qpiri, linepart2); - else if(linepart1 == "qpiws") - attemptAddSetting(&qpiws, linepart2); - else if(linepart1 == "qmod") - attemptAddSetting(&qmod, linepart2); - else if(linepart1 == "qpigs") - attemptAddSetting(&qpigs, linepart2); else continue; } @@ -180,7 +171,7 @@ int main(int argc, char* argv[]) { } bool ups_status_changed(false); - ups = new cInverter(devicename,qpiri,qpiws,qmod,qpigs); + ups = new cInverter(devicename); // Logic to send 'raw commands' to the inverter.. if (!rawcmd.empty()) { From 8c00a511e4d80024fdaf1be8f7a950d9528ac3f6 Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Sat, 2 Oct 2021 05:32:37 -0700 Subject: [PATCH 07/18] fix typo --- sources/inverter-cli/inverter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/inverter-cli/inverter.cpp b/sources/inverter-cli/inverter.cpp index cd910f1..ef0218b 100644 --- a/sources/inverter-cli/inverter.cpp +++ b/sources/inverter-cli/inverter.cpp @@ -115,7 +115,7 @@ bool cInverter::query(const char *cmd) { int towrite = left > chunk_size ? chunk_size : left; // WORKAROUND: For some reason, writing 1 byte causes it to error. // However, since we padded with '\0' above, we can give it 2 instead. - // I don't know of any 6 (+ 2*CRC + '\r') byte commnads to test it on + // I don't know of any 6 (+ 2*CRC + '\r') byte commands to test it on // but this at least gets it to return NAK. if (towrite == 1) towrite = 2; //lprintf("DEBUG: offset %d, writing %d", offset, towrite); From 8b79ebb245dbd8915cb1c0c5e21c74690cec6232 Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Sat, 2 Oct 2021 06:20:40 -0700 Subject: [PATCH 08/18] switch to while loops so that the output can be read in docker logs. --- sources/inverter-mqtt/entrypoint.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/inverter-mqtt/entrypoint.sh b/sources/inverter-mqtt/entrypoint.sh index 85ef711..65b71ab 100755 --- a/sources/inverter-mqtt/entrypoint.sh +++ b/sources/inverter-mqtt/entrypoint.sh @@ -6,11 +6,11 @@ export TERM=xterm # Init the mqtt server for the first time, then every 5 minutes # This will re-create the auto-created topics in the MQTT server if HA is restarted... -watch -xn 300 /opt/inverter-mqtt/mqtt-init.sh & +(while :; do /opt/inverter-mqtt/mqtt-init.sh; sleep 300; done) & # Run the MQTT Subscriber process in the background (so that way we can change the configuration on the inverter from home assistant) # This normally doesn't exit, but the watch is needed to handle mqtt server restarts. -watch -xn 1 /opt/inverter-mqtt/mqtt-subscriber.sh & +(while :; do /opt/inverter-mqtt/mqtt-subscriber.sh; sleep 1; done) & # execute exactly every 30 seconds... -watch -xn 30 /opt/inverter-mqtt/mqtt-push.sh +while :; do /opt/inverter-mqtt/mqtt-push.sh; sleep 30; done From 6d7e64953dc986048e273475e4d9fc853bf7d648 Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Sat, 2 Oct 2021 05:10:06 -0700 Subject: [PATCH 09/18] - @nrm21 inspired changes--allows sending commands longer than 5 bytes and removes the need to provide qpiri/qpiws/qmod/qpigs reply sizes in the config. - also fixed some compiler warnings. --- sources/inverter-cli/inverter.cpp | 61 +++++++++++++++++++++---------- sources/inverter-cli/inverter.h | 4 +- sources/inverter-cli/main.cpp | 11 +----- 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/sources/inverter-cli/inverter.cpp b/sources/inverter-cli/inverter.cpp index 45711f4..ef0218b 100644 --- a/sources/inverter-cli/inverter.cpp +++ b/sources/inverter-cli/inverter.cpp @@ -9,16 +9,12 @@ #include #include -cInverter::cInverter(std::string devicename, int qpiri, int qpiws, int qmod, int qpigs) { +cInverter::cInverter(std::string devicename) { device = devicename; status1[0] = 0; status2[0] = 0; warnings[0] = 0; mode = 0; - qpiri = qpiri; - qpiws = qpiws; - qmod = qmod; - qpigs = qpigs; } string *cInverter::GetQpigsStatus() { @@ -68,7 +64,7 @@ int cInverter::GetMode() { return result; } -bool cInverter::query(const char *cmd, int replysize) { +bool cInverter::query(const char *cmd) { time_t started; int fd; int i=0, n; @@ -109,14 +105,35 @@ bool cInverter::query(const char *cmd, int replysize) { buf[n++] = crc >> 8; buf[n++] = crc & 0xff; - buf[n++] = 0x0d; + buf[n++] = 0x0d; // '\r' + buf[n+1] = '\0'; // see workaround below + + // send a command + int chunk_size = 8; + for (int offset = 0; offset < n; usleep(50000)) { + int left = n - offset; + int towrite = left > chunk_size ? chunk_size : left; + // WORKAROUND: For some reason, writing 1 byte causes it to error. + // However, since we padded with '\0' above, we can give it 2 instead. + // I don't know of any 6 (+ 2*CRC + '\r') byte commands to test it on + // but this at least gets it to return NAK. + if (towrite == 1) towrite = 2; + //lprintf("DEBUG: offset %d, writing %d", offset, towrite); + ssize_t written = write(fd, &buf[offset], towrite); + if (written > 0) + offset += written; + else { + lprintf("INVERTER: command write failed (written=%d, errno=%d: %s)", written, errno, strerror(errno)); + break; + } + //lprintf("DEBUG: %d bytes to write, %d bytes written", n, offset); + } - //send a command - write(fd, &buf, n); + char *startbuf = 0; + char *endbuf = 0; time(&started); - do { - n = read(fd, (void*)buf+i, replysize-i); + n = read(fd, &buf[i], 120-i); if (n < 0) { if (time(NULL) - started > 2) { lprintf("INVERTER: %s read timeout", cmd); @@ -128,10 +145,14 @@ bool cInverter::query(const char *cmd, int replysize) { } i += n; - } while (i (%d bytes)", cmd, i); return false; } } @@ -163,7 +184,7 @@ void cInverter::poll() { // Reading mode if (!ups_qmod_changed) { - if (query("QMOD", qmod)) { + if (query("QMOD")) { SetMode(buf[1]); ups_qmod_changed = true; } @@ -171,7 +192,7 @@ void cInverter::poll() { // reading status (QPIGS) if (!ups_qpigs_changed) { - if (query("QPIGS", qpigs)) { + if (query("QPIGS")) { m.lock(); strcpy(status1, (const char*)buf+1); m.unlock(); @@ -181,7 +202,7 @@ void cInverter::poll() { // Reading QPIRI status if (!ups_qpiri_changed) { - if (query("QPIRI", qpiri)) { + if (query("QPIRI")) { m.lock(); strcpy(status2, (const char*)buf+1); m.unlock(); @@ -191,7 +212,7 @@ void cInverter::poll() { // Get any device warnings... if (!ups_qpiws_changed) { - if (query("QPIWS", qpiws)) { + if (query("QPIWS")) { m.lock(); strcpy(warnings, (const char*)buf+1); m.unlock(); @@ -205,7 +226,7 @@ void cInverter::poll() { void cInverter::ExecuteCmd(const string cmd) { // Sending any command raw - if (query(cmd.data(), 7)) { + if (query(cmd.data())) { m.lock(); strcpy(status2, (const char*)buf+1); m.unlock(); diff --git a/sources/inverter-cli/inverter.h b/sources/inverter-cli/inverter.h index 3501b6b..f406599 100644 --- a/sources/inverter-cli/inverter.h +++ b/sources/inverter-cli/inverter.h @@ -19,11 +19,11 @@ class cInverter { void SetMode(char newmode); bool CheckCRC(unsigned char *buff, int len); - bool query(const char *cmd, int replysize); + bool query(const char *cmd); uint16_t cal_crc_half(uint8_t *pin, uint8_t len); public: - cInverter(std::string devicename, int qpiri, int qpiws, int qmod, int qpigs); + cInverter(std::string devicename); void poll(); void runMultiThread() { std::thread t1(&cInverter::poll, this); diff --git a/sources/inverter-cli/main.cpp b/sources/inverter-cli/main.cpp index 7650fab..8802e87 100644 --- a/sources/inverter-cli/main.cpp +++ b/sources/inverter-cli/main.cpp @@ -43,7 +43,6 @@ string devicename; int runinterval; float ampfactor; float wattfactor; -int qpiri, qpiws, qmod, qpigs; // --------------------------------------- @@ -91,14 +90,6 @@ void getSettingsFile(string filename) { attemptAddSetting(&wattfactor, linepart2); else if(linepart1 == "watt_factor") attemptAddSetting(&wattfactor, linepart2); - else if(linepart1 == "qpiri") - attemptAddSetting(&qpiri, linepart2); - else if(linepart1 == "qpiws") - attemptAddSetting(&qpiws, linepart2); - else if(linepart1 == "qmod") - attemptAddSetting(&qmod, linepart2); - else if(linepart1 == "qpigs") - attemptAddSetting(&qpigs, linepart2); else continue; } @@ -180,7 +171,7 @@ int main(int argc, char* argv[]) { } bool ups_status_changed(false); - ups = new cInverter(devicename,qpiri,qpiws,qmod,qpigs); + ups = new cInverter(devicename); // Logic to send 'raw commands' to the inverter.. if (!rawcmd.empty()) { From 6ffef39a2a9de58e684d532eae1373ef8b7fe4b6 Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Mon, 4 Oct 2021 03:23:18 -0700 Subject: [PATCH 10/18] reduce the number of jq invocations drastically convert json output from the poller directly into a bash hash, so that it can be iterated upon. --- sources/inverter-mqtt/mqtt-push.sh | 130 +++++------------------------ 1 file changed, 19 insertions(+), 111 deletions(-) diff --git a/sources/inverter-mqtt/mqtt-push.sh b/sources/inverter-mqtt/mqtt-push.sh index a143594..a83acf0 100755 --- a/sources/inverter-mqtt/mqtt-push.sh +++ b/sources/inverter-mqtt/mqtt-push.sh @@ -19,16 +19,18 @@ if [[ $INFLUX_ENABLED == "true" ]] ; then fi pushMQTTData () { - mosquitto_pub \ - -h $MQTT_SERVER \ - -p $MQTT_PORT \ - -u "$MQTT_USERNAME" \ - -P "$MQTT_PASSWORD" \ - -t "$MQTT_TOPIC/sensor/"$MQTT_DEVICENAME"_$1" \ - -m "$2" + if [ -n "$2" ]; then + mosquitto_pub \ + -h $MQTT_SERVER \ + -p $MQTT_PORT \ + -u "$MQTT_USERNAME" \ + -P "$MQTT_PASSWORD" \ + -t "$MQTT_TOPIC/sensor/"$MQTT_DEVICENAME"_$1" \ + -m "$2" - if [[ $INFLUX_ENABLED == "true" ]] ; then - pushInfluxData $1 $2 + if [[ $INFLUX_ENABLED == "true" ]] ; then + pushInfluxData "$1" "$2" + fi fi } @@ -36,109 +38,15 @@ pushInfluxData () { curl -i -XPOST "$INFLUX_HOST/write?db=$INFLUX_DATABASE&precision=s" -u "$INFLUX_USERNAME:$INFLUX_PASSWORD" --data-binary "$INFLUX_PREFIX,device=$INFLUX_DEVICE $INFLUX_MEASUREMENT_NAME=$2" } -INVERTER_DATA=`timeout 10 /opt/inverter-cli/bin/inverter_poller -1` +############################################################################### -##################################################################################### +# Inverter modes: 1 = Power_On, 2 = Standby, 3 = Line, 4 = Battery, 5 = Fault, 6 = Power_Saving, 7 = Unknown -Inverter_mode=`echo $INVERTER_DATA | jq '.Inverter_mode' -r` +POLLER_JSON=$(timeout 10 /opt/inverter-cli/bin/inverter_poller -1) +BASH_HASH=$(echo $POLLER_JSON | jq -r '. | to_entries | .[] | @sh "[\(.key)]=\(.value)"') +eval "declare -A INVERTER_DATA=($BASH_HASH)" - # 1 = Power_On, 2 = Standby, 3 = Line, 4 = Battery, 5 = Fault, 6 = Power_Saving, 7 = Unknown - -[ ! -z "$Inverter_mode" ] && pushMQTTData "Inverter_mode" "$Inverter_mode" - -AC_grid_voltage=`echo $INVERTER_DATA | jq '.AC_grid_voltage' -r` -[ ! -z "$AC_grid_voltage" ] && pushMQTTData "AC_grid_voltage" "$AC_grid_voltage" - -AC_grid_frequency=`echo $INVERTER_DATA | jq '.AC_grid_frequency' -r` -[ ! -z "$AC_grid_frequency" ] && pushMQTTData "AC_grid_frequency" "$AC_grid_frequency" - -AC_out_voltage=`echo $INVERTER_DATA | jq '.AC_out_voltage' -r` -[ ! -z "$AC_out_voltage" ] && pushMQTTData "AC_out_voltage" "$AC_out_voltage" - -AC_out_frequency=`echo $INVERTER_DATA | jq '.AC_out_frequency' -r` -[ ! -z "$AC_out_frequency" ] && pushMQTTData "AC_out_frequency" "$AC_out_frequency" - -PV_in_voltage=`echo $INVERTER_DATA | jq '.PV_in_voltage' -r` -[ ! -z "$PV_in_voltage" ] && pushMQTTData "PV_in_voltage" "$PV_in_voltage" - -PV_in_current=`echo $INVERTER_DATA | jq '.PV_in_current' -r` -[ ! -z "$PV_in_current" ] && pushMQTTData "PV_in_current" "$PV_in_current" - -PV_in_watts=`echo $INVERTER_DATA | jq '.PV_in_watts' -r` -[ ! -z "$PV_in_watts" ] && pushMQTTData "PV_in_watts" "$PV_in_watts" - -PV_in_watthour=`echo $INVERTER_DATA | jq '.PV_in_watthour' -r` -[ ! -z "$PV_in_watthour" ] && pushMQTTData "PV_in_watthour" "$PV_in_watthour" - -SCC_voltage=`echo $INVERTER_DATA | jq '.SCC_voltage' -r` -[ ! -z "$SCC_voltage" ] && pushMQTTData "SCC_voltage" "$SCC_voltage" - -Load_pct=`echo $INVERTER_DATA | jq '.Load_pct' -r` -[ ! -z "$Load_pct" ] && pushMQTTData "Load_pct" "$Load_pct" - -Load_watt=`echo $INVERTER_DATA | jq '.Load_watt' -r` -[ ! -z "$Load_watt" ] && pushMQTTData "Load_watt" "$Load_watt" - -Load_watthour=`echo $INVERTER_DATA | jq '.Load_watthour' -r` -[ ! -z "$Load_watthour" ] && pushMQTTData "Load_watthour" "$Load_watthour" - -Load_va=`echo $INVERTER_DATA | jq '.Load_va' -r` -[ ! -z "$Load_va" ] && pushMQTTData "Load_va" "$Load_va" - -Bus_voltage=`echo $INVERTER_DATA | jq '.Bus_voltage' -r` -[ ! -z "$Bus_voltage" ] && pushMQTTData "Bus_voltage" "$Bus_voltage" - -Heatsink_temperature=`echo $INVERTER_DATA | jq '.Heatsink_temperature' -r` -[ ! -z "$Heatsink_temperature" ] && pushMQTTData "Heatsink_temperature" "$Heatsink_temperature" - -Battery_capacity=`echo $INVERTER_DATA | jq '.Battery_capacity' -r` -[ ! -z "$Battery_capacity" ] && pushMQTTData "Battery_capacity" "$Battery_capacity" - -Battery_voltage=`echo $INVERTER_DATA | jq '.Battery_voltage' -r` -[ ! -z "$Battery_voltage" ] && pushMQTTData "Battery_voltage" "$Battery_voltage" - -Battery_charge_current=`echo $INVERTER_DATA | jq '.Battery_charge_current' -r` -[ ! -z "$Battery_charge_current" ] && pushMQTTData "Battery_charge_current" "$Battery_charge_current" - -Battery_discharge_current=`echo $INVERTER_DATA | jq '.Battery_discharge_current' -r` -[ ! -z "$Battery_discharge_current" ] && pushMQTTData "Battery_discharge_current" "$Battery_discharge_current" - -Load_status_on=`echo $INVERTER_DATA | jq '.Load_status_on' -r` -[ ! -z "$Load_status_on" ] && pushMQTTData "Load_status_on" "$Load_status_on" - -SCC_charge_on=`echo $INVERTER_DATA | jq '.SCC_charge_on' -r` -[ ! -z "$SCC_charge_on" ] && pushMQTTData "SCC_charge_on" "$SCC_charge_on" - -AC_charge_on=`echo $INVERTER_DATA | jq '.AC_charge_on' -r` -[ ! -z "$AC_charge_on" ] && pushMQTTData "AC_charge_on" "$AC_charge_on" - -Battery_recharge_voltage=`echo $INVERTER_DATA | jq '.Battery_recharge_voltage' -r` -[ ! -z "$Battery_recharge_voltage" ] && pushMQTTData "Battery_recharge_voltage" "$Battery_recharge_voltage" - -Battery_under_voltage=`echo $INVERTER_DATA | jq '.Battery_under_voltage' -r` -[ ! -z "$Battery_under_voltage" ] && pushMQTTData "Battery_under_voltage" "$Battery_under_voltage" - -Battery_bulk_voltage=`echo $INVERTER_DATA | jq '.Battery_bulk_voltage' -r` -[ ! -z "$Battery_bulk_voltage" ] && pushMQTTData "Battery_bulk_voltage" "$Battery_bulk_voltage" - -Battery_float_voltage=`echo $INVERTER_DATA | jq '.Battery_float_voltage' -r` -[ ! -z "$Battery_float_voltage" ] && pushMQTTData "Battery_float_voltage" "$Battery_float_voltage" - -Max_grid_charge_current=`echo $INVERTER_DATA | jq '.Max_grid_charge_current' -r` -[ ! -z "$Max_grid_charge_current" ] && pushMQTTData "Max_grid_charge_current" "$Max_grid_charge_current" - -Max_charge_current=`echo $INVERTER_DATA | jq '.Max_charge_current' -r` -[ ! -z "$Max_charge_current" ] && pushMQTTData "Max_charge_current" "$Max_charge_current" - -Out_source_priority=`echo $INVERTER_DATA | jq '.Out_source_priority' -r` -[ ! -z "$Out_source_priority" ] && pushMQTTData "Out_source_priority" "$Out_source_priority" - -Charger_source_priority=`echo $INVERTER_DATA | jq '.Charger_source_priority' -r` -[ ! -z "$Charger_source_priority" ] && pushMQTTData "Charger_source_priority" "$Charger_source_priority" - -Battery_redischarge_voltage=`echo $INVERTER_DATA | jq '.Battery_redischarge_voltage' -r` -[ ! -z "$Battery_redischarge_voltage" ] && pushMQTTData "Battery_redischarge_voltage" "$Battery_redischarge_voltage" - -Warnings=`echo $INVERTER_DATA | jq '.Warnings' -r` -[ ! -z "$Warnings" ] && pushMQTTData "Warnings" "$Warnings" +for key in "${!INVERTER_DATA[@]}"; do + pushMQTTData "$key" "${INVERTER_DATA[$key]}" +done From 043d07e7482083a8e4bf545d2f9520ca36db98ef Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Mon, 4 Oct 2021 03:30:09 -0700 Subject: [PATCH 11/18] remove the push after running a command, didn't like the extra delay. added publishing the poller's reply into mqtt. --- sources/inverter-mqtt/mqtt-subscriber.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/inverter-mqtt/mqtt-subscriber.sh b/sources/inverter-mqtt/mqtt-subscriber.sh index 0e28ef0..b8ccdc4 100755 --- a/sources/inverter-mqtt/mqtt-subscriber.sh +++ b/sources/inverter-mqtt/mqtt-subscriber.sh @@ -11,7 +11,7 @@ while read rawcmd; do echo "Incoming request send: [$rawcmd] to inverter." - /opt/inverter-cli/bin/inverter_poller -r $rawcmd; - /opt/inverter-mqtt/mqtt-push.sh + REPLY=$(/opt/inverter-cli/bin/inverter_poller -r $rawcmd) + mosquitto_pub -h $MQTT_SERVER -p $MQTT_PORT -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" -t "$MQTT_TOPIC/sensor/${MQTT_DEVICENAME}/reply" -q 1 -m "$rawcmd - $REPLY" done < <(mosquitto_sub -h $MQTT_SERVER -p $MQTT_PORT -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" -t "$MQTT_TOPIC/sensor/$MQTT_DEVICENAME" -q 1) From 25d9aaf7ea6f6a6b2bcab4b675593ff90a5e654a Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Mon, 4 Oct 2021 03:31:45 -0700 Subject: [PATCH 12/18] don't need the fixed response sizes anymore. --- config/inverter.conf | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/config/inverter.conf b/config/inverter.conf index 21e6a2f..e2646d2 100644 --- a/config/inverter.conf +++ b/config/inverter.conf @@ -5,7 +5,7 @@ # /dev/ttyUSB0 if a USB<>Serial, # /dev/hidraw0 if you're connecting via the USB port on the inverter. -device=/dev/ttyUSB0 +device=/dev/hidraw0 # How many times per hour is the program going to run... # This is used to calculate the PV & Load Watt Hours between runs... @@ -20,23 +20,6 @@ amperage_factor=1.0 # This allows you to modify the wattage in case the inverter is giving an incorrect # reading compared to measurement tools. Normally this will remain '1' -watt_factor=1.01 +#watt_factor=1.01 +watt_factor=1.0 - -# The following settings allow you to modify runtime buffers. -# N.B. These values may not be applicable to all inverter types, as such you will -# need to run docker exec -it voltronic-mqtt bash -c '/opt/inverter-cli/bin/inverter_poller -d -1' -# or simply inverter_poller -d -1 to check for warnings or errors -# mentioned in https://github.com/ned-kelly/docker-voltronic-homeassistant/issues/5 - -# This allows you to modify the buffersize for the qpiri command -qpiri=98 - -# This allows you to modify the buffersize for the qpiws command -qpiws=36 - -# This allows you to modify the buffersize for the qmod command -qmod=5 - -# This allows you to modify the buffersize for the qpigs command -qpigs=110 From 60186ea66e895707db02774d4d79d7fc430a42f7 Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Tue, 5 Oct 2021 01:58:51 -0700 Subject: [PATCH 13/18] set retain flag on publishes, don't need to loop the init script anymore with default HA install of Mosquitto broker using persistence. --- sources/inverter-mqtt/entrypoint.sh | 23 ++++++++++++++--------- sources/inverter-mqtt/mqtt-init.sh | 2 ++ sources/inverter-mqtt/mqtt-push.sh | 1 + 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/sources/inverter-mqtt/entrypoint.sh b/sources/inverter-mqtt/entrypoint.sh index 65b71ab..39fdd64 100755 --- a/sources/inverter-mqtt/entrypoint.sh +++ b/sources/inverter-mqtt/entrypoint.sh @@ -1,16 +1,21 @@ #!/bin/bash -export TERM=xterm + +UNBUFFER='stdbuf -i0 -oL -eL' # stty -F /dev/ttyUSB0 2400 raw -# Init the mqtt server for the first time, then every 5 minutes -# This will re-create the auto-created topics in the MQTT server if HA is restarted... +# Init the mqtt server. This creates the config topics in the MQTT server +# that the MQTT integration uses to create entities in HA. + +# broker using persistence (default HA config) +$UNBUFFER /opt/inverter-mqtt/mqtt-init.sh -(while :; do /opt/inverter-mqtt/mqtt-init.sh; sleep 300; done) & +# broker not using persistence +#(while :; do $UNBUFFER /opt/inverter-mqtt/mqtt-init.sh; sleep 300; done) & -# Run the MQTT Subscriber process in the background (so that way we can change the configuration on the inverter from home assistant) -# This normally doesn't exit, but the watch is needed to handle mqtt server restarts. -(while :; do /opt/inverter-mqtt/mqtt-subscriber.sh; sleep 1; done) & +# Run the MQTT subscriber process in the background (so that way we can change +# the configuration on the inverter from home assistant). +$UNBUFFER /opt/inverter-mqtt/mqtt-subscriber.sh & -# execute exactly every 30 seconds... -while :; do /opt/inverter-mqtt/mqtt-push.sh; sleep 30; done +# Push poller updates every 30 seconds. +while :; do $UNBUFFER /opt/inverter-mqtt/mqtt-push.sh; sleep 30; done diff --git a/sources/inverter-mqtt/mqtt-init.sh b/sources/inverter-mqtt/mqtt-init.sh index e03b9b2..f1dcb5a 100755 --- a/sources/inverter-mqtt/mqtt-init.sh +++ b/sources/inverter-mqtt/mqtt-init.sh @@ -15,6 +15,7 @@ registerTopic () { -p $MQTT_PORT \ -u "$MQTT_USERNAME" \ -P "$MQTT_PASSWORD" \ + -r \ -t "$MQTT_TOPIC/sensor/"$MQTT_DEVICENAME"_$1/config" \ -m "{ \"name\": \""$MQTT_DEVICENAME"_$1\", @@ -30,6 +31,7 @@ registerInverterRawCMD () { -p $MQTT_PORT \ -u "$MQTT_USERNAME" \ -P "$MQTT_PASSWORD" \ + -r \ -t "$MQTT_TOPIC/sensor/$MQTT_DEVICENAME/config" \ -m "{ \"name\": \""$MQTT_DEVICENAME"\", diff --git a/sources/inverter-mqtt/mqtt-push.sh b/sources/inverter-mqtt/mqtt-push.sh index a83acf0..ac9b103 100755 --- a/sources/inverter-mqtt/mqtt-push.sh +++ b/sources/inverter-mqtt/mqtt-push.sh @@ -25,6 +25,7 @@ pushMQTTData () { -p $MQTT_PORT \ -u "$MQTT_USERNAME" \ -P "$MQTT_PASSWORD" \ + -r \ -t "$MQTT_TOPIC/sensor/"$MQTT_DEVICENAME"_$1" \ -m "$2" From 4eef447c24db59ae8ec8adaa54fd1e0e320eb3d8 Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Tue, 5 Oct 2021 02:02:45 -0700 Subject: [PATCH 14/18] add timestamps to stdout output (for docker logs) retry up to 3 times total if the reply is anything under than "ACK" --- sources/inverter-mqtt/mqtt-subscriber.sh | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/sources/inverter-mqtt/mqtt-subscriber.sh b/sources/inverter-mqtt/mqtt-subscriber.sh index b8ccdc4..0f39e9b 100755 --- a/sources/inverter-mqtt/mqtt-subscriber.sh +++ b/sources/inverter-mqtt/mqtt-subscriber.sh @@ -7,11 +7,21 @@ MQTT_DEVICENAME=`cat /etc/inverter/mqtt.json | jq '.devicename' -r` MQTT_USERNAME=`cat /etc/inverter/mqtt.json | jq '.username' -r` MQTT_PASSWORD=`cat /etc/inverter/mqtt.json | jq '.password' -r` -while read rawcmd; -do +function subscribe () { + mosquitto_sub -h $MQTT_SERVER -p $MQTT_PORT -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" -t "$MQTT_TOPIC/sensor/$MQTT_DEVICENAME" -q 1 +} - echo "Incoming request send: [$rawcmd] to inverter." - REPLY=$(/opt/inverter-cli/bin/inverter_poller -r $rawcmd) - mosquitto_pub -h $MQTT_SERVER -p $MQTT_PORT -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" -t "$MQTT_TOPIC/sensor/${MQTT_DEVICENAME}/reply" -q 1 -m "$rawcmd - $REPLY" +function reply () { + mosquitto_pub -h $MQTT_SERVER -p $MQTT_PORT -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" -t "$MQTT_TOPIC/sensor/${MQTT_DEVICENAME}/reply" -q 1 -m "$*" +} -done < <(mosquitto_sub -h $MQTT_SERVER -p $MQTT_PORT -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" -t "$MQTT_TOPIC/sensor/$MQTT_DEVICENAME" -q 1) +subscribe | while read rawcmd; do + echo "[$(date +%F+%T)] Incoming request send: [$rawcmd] to inverter." + for attempt in $(seq 3); do + REPLY=$(/opt/inverter-cli/bin/inverter_poller -r $rawcmd) + echo "[$(date +%F+%T)] $REPLY" + reply "[$rawcmd] [Attempt $attempt] [$REPLY]" + [ "$REPLY" = "Reply: ACK" ] && break + [ "$attempt" != "3" ] && sleep 1 + done +done From 711370b20c799d41c13d81b602569bfd5dcf60ab Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Tue, 5 Oct 2021 02:21:12 -0700 Subject: [PATCH 15/18] updated for watch removal. --- sources/healthcheck | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/healthcheck b/sources/healthcheck index 476093f..b79ab57 100755 --- a/sources/healthcheck +++ b/sources/healthcheck @@ -1,9 +1,9 @@ #!/bin/bash -PROC=`ps cax | grep -E "mqtt-subscriber|mosquitto_sub|watch" | awk '{print $5}' | sort -u | wc -l` +PROC=`ps cax | grep -E "mqtt-subscriber|mosquitto_sub" | awk '{print $5}' | sort -u | wc -l` -if [ "$PROC" -eq "3" ] ; then +if [ "$PROC" -eq "2" ] ; then exit 0 else exit 99 -fi \ No newline at end of file +fi From c8b560d4778716c0d5a5569bb0d2ceec7f4774a9 Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Wed, 6 Oct 2021 23:42:58 -0700 Subject: [PATCH 16/18] fixed a bug with response handling giving corrupt values. don't accept "NAK" results for QMOD, QPIGS, QPIRI, QPIWS queries on polling. clean up debug output a little. --- sources/inverter-cli/inverter.cpp | 88 +++++++++++++++++++++---------- sources/inverter-cli/inverter.h | 4 +- sources/inverter-cli/main.cpp | 12 ++--- sources/inverter-cli/tools.cpp | 4 +- 4 files changed, 72 insertions(+), 36 deletions(-) diff --git a/sources/inverter-cli/inverter.cpp b/sources/inverter-cli/inverter.cpp index ef0218b..d47faa3 100644 --- a/sources/inverter-cli/inverter.cpp +++ b/sources/inverter-cli/inverter.cpp @@ -64,19 +64,39 @@ int cInverter::GetMode() { return result; } +#define HEX(x) (x < 10 ? ('0' + x) : ('a' + x - 10)) +char *cInverter::escape_strn(unsigned char *str, int n) { + int j=0; + + for (int i=0; i> 4; + unsigned char x2 = str[i] & 0x0f; + escaped_buf[j++] = '\\'; + escaped_buf[j++] = 'x'; + escaped_buf[j++] = HEX(x1); + escaped_buf[j++] = HEX(x2); + } + } + + escaped_buf[j] = '\0'; + return escaped_buf; +} + bool cInverter::query(const char *cmd) { time_t started; int fd; - int i=0, n; + int i=0, n, write_failed=0; fd = open(this->device.data(), O_RDWR | O_NONBLOCK); if (fd == -1) { - lprintf("INVERTER: Unable to open device file (errno=%d %s)", errno, strerror(errno)); + lprintf("Unable to open device file (errno=%d %s)", errno, strerror(errno)); sleep(5); return false; } - // Once connected, set the baud rate and other serial config (Don't rely on this being correct on the system by default...) speed_t baud = B2400; @@ -101,7 +121,7 @@ bool cInverter::query(const char *cmd) { uint16_t crc = cal_crc_half((uint8_t*)cmd, strlen(cmd)); n = strlen(cmd); memcpy(&buf, cmd, n); - lprintf("INVERTER: Current CRC: %X %X", crc >> 8, crc & 0xff); + lprintf("%s: command CRC: %X %X", cmd, crc >> 8, crc & 0xff); buf[n++] = crc >> 8; buf[n++] = crc & 0xff; @@ -118,25 +138,26 @@ bool cInverter::query(const char *cmd) { // I don't know of any 6 (+ 2*CRC + '\r') byte commands to test it on // but this at least gets it to return NAK. if (towrite == 1) towrite = 2; - //lprintf("DEBUG: offset %d, writing %d", offset, towrite); + lprintf("%s: write offset %d, writing %d", cmd, offset, towrite); ssize_t written = write(fd, &buf[offset], towrite); if (written > 0) offset += written; else { - lprintf("INVERTER: command write failed (written=%d, errno=%d: %s)", written, errno, strerror(errno)); + lprintf("%s: write failed (written=%d, errno=%d: %s)", cmd, written, errno, strerror(errno)); + write_failed=1; break; } - //lprintf("DEBUG: %d bytes to write, %d bytes written", n, offset); + lprintf("%s: %d bytes to write, %d bytes written", cmd, n, offset); } - char *startbuf = 0; + char *startbuf = (char *)&buf[0]; char *endbuf = 0; time(&started); do { n = read(fd, &buf[i], 120-i); if (n < 0) { if (time(NULL) - started > 2) { - lprintf("INVERTER: %s read timeout", cmd); + lprintf("%s: read timeout", cmd); break; } else { usleep(10); @@ -144,36 +165,45 @@ bool cInverter::query(const char *cmd) { } } + endbuf = (char *)memrchr((void *)&buf[i], 0x0d, n); i += n; - startbuf = (char *)&buf[0]; - endbuf = strchr(startbuf, '\r'); } while (endbuf == NULL); close(fd); buf[i] = '\0'; - if (endbuf != NULL) { - int replysize = endbuf - startbuf + 1; + lprintf("%s: %d bytes in reply: %s", cmd, i, escape_strn(buf, i)); + + if (write_failed) { + return false; + } + else if (i < 3) { + lprintf("%s: reply too short (%d bytes)", cmd, i); + return false; + } + else if (endbuf == NULL) { + lprintf("%s: couldn't find reply : %s", cmd, buf); + return false; + } - lprintf("INVERTER: %s reply size (%d bytes)", cmd, i); + int replysize = endbuf - startbuf + 1; - if (buf[0]!='(' || buf[replysize-1]!=0x0d) { - lprintf("INVERTER: %s: incorrect start/stop bytes. Buffer: %s", cmd, buf); - return false; - } + // proper response, check CRC + if (buf[0]=='(' && buf[replysize-1]==0x0d) { if (!(CheckCRC(buf, replysize))) { - lprintf("INVERTER: %s: CRC Failed! Reply size: %d Buffer: %s", cmd, replysize, buf); + lprintf("%s: CRC failed!", cmd); return false; } - - buf[replysize-3] = '\0'; // null terminating on first CRC byte - lprintf("INVERTER: %s: %d bytes read: %s", cmd, i, buf); - - lprintf("INVERTER: %s query finished", cmd); - return true; - } else { - lprintf("INVERTER: %s couldn't find reply (%d bytes)", cmd, i); + replysize -= 3; + buf[replysize] = '\0'; // null terminating on first CRC byte + } + else { + lprintf("%s: incorrect start/stop bytes", cmd); return false; } + + lprintf("%s: %d bytes in payload", cmd, replysize); + lprintf("%s: query finished", cmd); + return true; } void cInverter::poll() { @@ -185,6 +215,7 @@ void cInverter::poll() { // Reading mode if (!ups_qmod_changed) { if (query("QMOD")) { + if (strcmp((char *)&buf[1], "NAK") == 0) break; SetMode(buf[1]); ups_qmod_changed = true; } @@ -193,6 +224,7 @@ void cInverter::poll() { // reading status (QPIGS) if (!ups_qpigs_changed) { if (query("QPIGS")) { + if (strcmp((char *)&buf[1], "NAK") == 0) break; m.lock(); strcpy(status1, (const char*)buf+1); m.unlock(); @@ -203,6 +235,7 @@ void cInverter::poll() { // Reading QPIRI status if (!ups_qpiri_changed) { if (query("QPIRI")) { + if (strcmp((char *)&buf[1], "NAK") == 0) break; m.lock(); strcpy(status2, (const char*)buf+1); m.unlock(); @@ -213,6 +246,7 @@ void cInverter::poll() { // Get any device warnings... if (!ups_qpiws_changed) { if (query("QPIWS")) { + if (strcmp((char *)&buf[1], "NAK") == 0) break; m.lock(); strcpy(warnings, (const char*)buf+1); m.unlock(); diff --git a/sources/inverter-cli/inverter.h b/sources/inverter-cli/inverter.h index f406599..89e1322 100644 --- a/sources/inverter-cli/inverter.h +++ b/sources/inverter-cli/inverter.h @@ -7,7 +7,8 @@ using namespace std; class cInverter { - unsigned char buf[1024]; //internal work buffer + unsigned char buf[1024]; // internal work buffer + char escaped_buf[4096]; // screen-printable version of above char warnings[1024]; char status1[1024]; @@ -21,6 +22,7 @@ class cInverter { bool CheckCRC(unsigned char *buff, int len); bool query(const char *cmd); uint16_t cal_crc_half(uint8_t *pin, uint8_t len); + char *escape_strn(unsigned char *str, int n); public: cInverter(std::string devicename); diff --git a/sources/inverter-cli/main.cpp b/sources/inverter-cli/main.cpp index 8802e87..9a8e23a 100644 --- a/sources/inverter-cli/main.cpp +++ b/sources/inverter-cli/main.cpp @@ -161,7 +161,7 @@ int main(int argc, char* argv[]) { if(cmdArgs.cmdOptionExists("-1") || cmdArgs.cmdOptionExists("--run-once")) { runOnce = true; } - lprintf("INVERTER: Debug set"); + lprintf("Debug set"); // Get the rest of the settings from the conf file if( access( "./inverter.conf", F_OK ) != -1 ) { // file exists @@ -188,7 +188,7 @@ int main(int argc, char* argv[]) { int mode = ups->GetMode(); if (mode) - lprintf("INVERTER: Mode Currently set to: %d", mode); + lprintf("Mode Currently set to: %d", mode); ups_status_changed = false; } @@ -207,15 +207,15 @@ int main(int argc, char* argv[]) { if (reply1 && reply2 && warnings) { // Parse and display values - sscanf(reply1->c_str(), "%f %f %f %f %d %d %d %d %f %d %d %d %f %f %f %d %s", &voltage_grid, &freq_grid, &voltage_out, &freq_out, &load_va, &load_watt, &load_percent, &voltage_bus, &voltage_batt, &batt_charge_current, &batt_capacity, &temp_heatsink, &pv_input_current, &pv_input_voltage, &scc_voltage, &batt_discharge_current, &device_status); + sscanf(reply1->c_str(), "%f %f %f %f %d %d %d %d %f %d %d %d %f %f %f %d %s", &voltage_grid, &freq_grid, &voltage_out, &freq_out, &load_va, &load_watt, &load_percent, &voltage_bus, &voltage_batt, &batt_charge_current, &batt_capacity, &temp_heatsink, &pv_input_current, &pv_input_voltage, &scc_voltage, &batt_discharge_current, (char *)&device_status); sscanf(reply2->c_str(), "%f %f %f %f %f %d %d %f %f %f %f %f %d %d %d %d %d %d - %d %d %d %f", &grid_voltage_rating, &grid_current_rating, &out_voltage_rating, &out_freq_rating, &out_current_rating, &out_va_rating, &out_watt_rating, &batt_rating, &batt_recharge_voltage, &batt_under_voltage, &batt_bulk_voltage, &batt_float_voltage, &batt_type, &max_grid_charge_current, &max_charge_current, &in_voltage_range, &out_source_priority, &charger_source_priority, &machine_type, &topology, &out_mode, &batt_redischarge_voltage); // There appears to be a discrepancy in actual DMM measured current vs what the meter is // telling me it's getting, so lets add a variable we can multiply/divide by to adjust if // needed. This should be set in the config so it can be changed without program recompile. if (debugFlag) { - printf("INVERTER: ampfactor from config is %.2f\n", ampfactor); - printf("INVERTER: wattfactor from config is %.2f\n", wattfactor); + printf("ampfactor from config is %.2f\n", ampfactor); + printf("wattfactor from config is %.2f\n", wattfactor); } pv_input_current = pv_input_current * ampfactor; @@ -274,7 +274,7 @@ int main(int argc, char* argv[]) { if(runOnce) { // Do once and exit instead of loop endlessly - lprintf("INVERTER: All queries complete, exiting loop."); + lprintf("All queries complete, exiting loop."); exit(0); } } diff --git a/sources/inverter-cli/tools.cpp b/sources/inverter-cli/tools.cpp index 95311a0..8b3244c 100644 --- a/sources/inverter-cli/tools.cpp +++ b/sources/inverter-cli/tools.cpp @@ -28,7 +28,7 @@ void lprintf(const char *format, ...) { buf[strlen(buf)-1] = 0; //connect with args - snprintf(fmt, sizeof(fmt), "%s %s\n", buf, format); + snprintf(fmt, sizeof(fmt), "%s INVERTER: %s\n", buf, format); //put on screen: va_start(ap, format); @@ -71,4 +71,4 @@ int print_help() { printf(" PEx / PDx (Enable/disable backlight)\n\n"); return 1; -} \ No newline at end of file +} From 8296b07ff4c39522efc70380d87bcf6b0fc54ce4 Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Thu, 7 Oct 2021 00:17:12 -0700 Subject: [PATCH 17/18] use a later Debian image for a newer mosquitto-clients package. --- Dockerfile.dev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index aecf7ae..5704253 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM debian:stretch +FROM debian:bullseye RUN apt update && apt install -y \ curl \ From 9a06aa9ff737ec09000abb7821a356b68d33488f Mon Sep 17 00:00:00 2001 From: Keith Chiem Date: Thu, 7 Oct 2021 02:13:19 -0700 Subject: [PATCH 18/18] oops, can't break out of if-blocks. --- sources/inverter-cli/inverter.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/sources/inverter-cli/inverter.cpp b/sources/inverter-cli/inverter.cpp index d47faa3..1957c26 100644 --- a/sources/inverter-cli/inverter.cpp +++ b/sources/inverter-cli/inverter.cpp @@ -150,6 +150,7 @@ bool cInverter::query(const char *cmd) { lprintf("%s: %d bytes to write, %d bytes written", cmd, n, offset); } + // reads tend to be in multiple of 8 chars with NULL padding as necessary char *startbuf = (char *)&buf[0]; char *endbuf = 0; time(&started); @@ -214,8 +215,8 @@ void cInverter::poll() { // Reading mode if (!ups_qmod_changed) { - if (query("QMOD")) { - if (strcmp((char *)&buf[1], "NAK") == 0) break; + if (query("QMOD") && + strcmp((char *)&buf[1], "NAK") != 0) { SetMode(buf[1]); ups_qmod_changed = true; } @@ -223,8 +224,8 @@ void cInverter::poll() { // reading status (QPIGS) if (!ups_qpigs_changed) { - if (query("QPIGS")) { - if (strcmp((char *)&buf[1], "NAK") == 0) break; + if (query("QPIGS") && + strcmp((char *)&buf[1], "NAK") != 0) { m.lock(); strcpy(status1, (const char*)buf+1); m.unlock(); @@ -234,8 +235,8 @@ void cInverter::poll() { // Reading QPIRI status if (!ups_qpiri_changed) { - if (query("QPIRI")) { - if (strcmp((char *)&buf[1], "NAK") == 0) break; + if (query("QPIRI") && + strcmp((char *)&buf[1], "NAK") != 0) { m.lock(); strcpy(status2, (const char*)buf+1); m.unlock(); @@ -245,8 +246,8 @@ void cInverter::poll() { // Get any device warnings... if (!ups_qpiws_changed) { - if (query("QPIWS")) { - if (strcmp((char *)&buf[1], "NAK") == 0) break; + if (query("QPIWS") && + strcmp((char *)&buf[1], "NAK") != 0) { m.lock(); strcpy(warnings, (const char*)buf+1); m.unlock();