diff --git a/README.md b/README.md index a90cef4..e165623 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,16 @@ [![GitHub latest commit](https://badgen.net/github/last-commit/mathieucarbou/MycilaJSY)](https://GitHub.com/mathieucarbou/MycilaJSY/commit/) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/mathieucarbou/MycilaJSY) -Arduino / ESP32 library for the JSY-MK-194T, JSY-MK-194TG single-phase two-way electric energy meters +Arduino / ESP32 library for the JSY-MK-194T, JSY-MK-194TG single-phase two-way electric energy meters from [Shenzhen Jiansiyan Technologies Co, Ltd.](https://www.jsypowermeter.com) -- Sync mode and async mode (non-blocking) -- Core, stack size and interval can be configured - Automatically detect baud rate -- Energy reset live at runtime -- Switch bauds rate to any supported speed live at runtime - Configurable Serial (Serial2 by default) +- Core, stack size and interval can be configured +- Device address support (for multiple devices on the same bus) +- Energy reset live at runtime - Focus on speed and reactivity with a callback mechanism +- Switch bauds rate to any supported speed live at runtime +- Sync mode and async mode (non-blocking) - Metrics: ```c++ @@ -87,10 +88,10 @@ There is a getter for each metric. jsy.begin(Serial2, 16, 17); // equivalent as above -jsy.begin(Serial2, 16, 17, Mycila::JSYBaudRate::UNKNOWN); +jsy.begin(Serial2, 16, 17, Mycila::JSY::BaudRate::UNKNOWN); // Skips baud rate detection and use the given baud rate -jsy.begin(Serial2, 16, 17, Mycila::JSYBaudRate::BAUD_38400); +jsy.begin(Serial2, 16, 17, Mycila::JSY::BaudRate::BAUD_38400); ``` ### Blocking mode @@ -134,8 +135,8 @@ jsy.resetEnergy(); ### Update Baud rate (change speed) ```c++ -if (jsy.isEnabled() && jsy.getBaudRate() != Mycila::JSYBaudRate::BAUD_38400) { - if (jsy.setBaudRate(Mycila::JSYBaudRate::BAUD_38400)) { +if (jsy.isEnabled() && jsy.getBaudRate() != Mycila::JSY::BaudRate::BAUD_38400) { + if (jsy.setBaudRate(Mycila::JSY::BaudRate::BAUD_38400)) { // speed changed and switched to new speed } else { // speed changed failed, keeping current speed @@ -146,7 +147,7 @@ if (jsy.isEnabled() && jsy.getBaudRate() != Mycila::JSYBaudRate::BAUD_38400) { ### Callbacks -- `JSYCallback`: called when the JSY has read the data and when a change for any of the metric is detected by the JSY. +- `Callback`: called when the JSY has read the data and when a change for any of the metric is detected by the JSY. This is useful to be notified exactly when required. You must check the event type @@ -155,14 +156,14 @@ if (jsy.isEnabled() && jsy.getBaudRate() != Mycila::JSYBaudRate::BAUD_38400) { Reading a load for 2 second after it is turned on: ```c++ - jsy.setCallback([](const Mycila::JSYEventType eventType) { + jsy.setCallback([](const Mycila::JSY::EventType eventType) { int64_t now = esp_timer_get_time(); switch (eventType) { - case Mycila::JSYEventType::EVT_READ: + case Mycila::JSY::EventType::EVT_READ: Serial.printf(" - %" PRId64 " EVT_READ\n", now); break; - case Mycila::JSYEventType::EVT_CHANGE: - Serial.printf(" - %" PRId64 " EVT_CHANGE: %f W\n", now, jsy.getPower2()); + case Mycila::JSY::EventType::EVT_CHANGE: + Serial.printf(" - %" PRId64 " EVT_CHANGE: %f W\n", now, jsy.getActivePower2()); break; default: Serial.printf(" - %" PRId64 " ERR\n", now); diff --git a/docs/index.md b/docs/index.md index a90cef4..e165623 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,15 +10,16 @@ [![GitHub latest commit](https://badgen.net/github/last-commit/mathieucarbou/MycilaJSY)](https://GitHub.com/mathieucarbou/MycilaJSY/commit/) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/mathieucarbou/MycilaJSY) -Arduino / ESP32 library for the JSY-MK-194T, JSY-MK-194TG single-phase two-way electric energy meters +Arduino / ESP32 library for the JSY-MK-194T, JSY-MK-194TG single-phase two-way electric energy meters from [Shenzhen Jiansiyan Technologies Co, Ltd.](https://www.jsypowermeter.com) -- Sync mode and async mode (non-blocking) -- Core, stack size and interval can be configured - Automatically detect baud rate -- Energy reset live at runtime -- Switch bauds rate to any supported speed live at runtime - Configurable Serial (Serial2 by default) +- Core, stack size and interval can be configured +- Device address support (for multiple devices on the same bus) +- Energy reset live at runtime - Focus on speed and reactivity with a callback mechanism +- Switch bauds rate to any supported speed live at runtime +- Sync mode and async mode (non-blocking) - Metrics: ```c++ @@ -87,10 +88,10 @@ There is a getter for each metric. jsy.begin(Serial2, 16, 17); // equivalent as above -jsy.begin(Serial2, 16, 17, Mycila::JSYBaudRate::UNKNOWN); +jsy.begin(Serial2, 16, 17, Mycila::JSY::BaudRate::UNKNOWN); // Skips baud rate detection and use the given baud rate -jsy.begin(Serial2, 16, 17, Mycila::JSYBaudRate::BAUD_38400); +jsy.begin(Serial2, 16, 17, Mycila::JSY::BaudRate::BAUD_38400); ``` ### Blocking mode @@ -134,8 +135,8 @@ jsy.resetEnergy(); ### Update Baud rate (change speed) ```c++ -if (jsy.isEnabled() && jsy.getBaudRate() != Mycila::JSYBaudRate::BAUD_38400) { - if (jsy.setBaudRate(Mycila::JSYBaudRate::BAUD_38400)) { +if (jsy.isEnabled() && jsy.getBaudRate() != Mycila::JSY::BaudRate::BAUD_38400) { + if (jsy.setBaudRate(Mycila::JSY::BaudRate::BAUD_38400)) { // speed changed and switched to new speed } else { // speed changed failed, keeping current speed @@ -146,7 +147,7 @@ if (jsy.isEnabled() && jsy.getBaudRate() != Mycila::JSYBaudRate::BAUD_38400) { ### Callbacks -- `JSYCallback`: called when the JSY has read the data and when a change for any of the metric is detected by the JSY. +- `Callback`: called when the JSY has read the data and when a change for any of the metric is detected by the JSY. This is useful to be notified exactly when required. You must check the event type @@ -155,14 +156,14 @@ if (jsy.isEnabled() && jsy.getBaudRate() != Mycila::JSYBaudRate::BAUD_38400) { Reading a load for 2 second after it is turned on: ```c++ - jsy.setCallback([](const Mycila::JSYEventType eventType) { + jsy.setCallback([](const Mycila::JSY::EventType eventType) { int64_t now = esp_timer_get_time(); switch (eventType) { - case Mycila::JSYEventType::EVT_READ: + case Mycila::JSY::EventType::EVT_READ: Serial.printf(" - %" PRId64 " EVT_READ\n", now); break; - case Mycila::JSYEventType::EVT_CHANGE: - Serial.printf(" - %" PRId64 " EVT_CHANGE: %f W\n", now, jsy.getPower2()); + case Mycila::JSY::EventType::EVT_CHANGE: + Serial.printf(" - %" PRId64 " EVT_CHANGE: %f W\n", now, jsy.getActivePower2()); break; default: Serial.printf(" - %" PRId64 " ERR\n", now); diff --git a/examples/Callback/Callback.ino b/examples/Callback/Callback.ino index f070cef..ddce9d3 100644 --- a/examples/Callback/Callback.ino +++ b/examples/Callback/Callback.ino @@ -20,11 +20,11 @@ Mycila::JSY jsy; JsonDocument doc; -const Mycila::JSYBaudRate rates[] = { - Mycila::JSYBaudRate::BAUD_4800, - Mycila::JSYBaudRate::BAUD_9600, - Mycila::JSYBaudRate::BAUD_19200, - Mycila::JSYBaudRate::BAUD_38400, +const Mycila::JSY::BaudRate rates[] = { + Mycila::JSY::BaudRate::BAUD_4800, + Mycila::JSY::BaudRate::BAUD_9600, + Mycila::JSY::BaudRate::BAUD_19200, + Mycila::JSY::BaudRate::BAUD_38400, }; void setup() { @@ -32,14 +32,14 @@ void setup() { while (!Serial) continue; - jsy.setCallback([](const Mycila::JSYEventType eventType) { + jsy.setCallback([](const Mycila::JSY::EventType eventType) { int64_t now = esp_timer_get_time(); switch (eventType) { - case Mycila::JSYEventType::EVT_READ: + case Mycila::JSY::EventType::EVT_READ: Serial.printf(" - %" PRId64 " EVT_READ\n", now); break; - case Mycila::JSYEventType::EVT_CHANGE: - Serial.printf(" - %" PRId64 " EVT_CHANGE: %f W\n", now, jsy.getPower2()); + case Mycila::JSY::EventType::EVT_CHANGE: + Serial.printf(" - %" PRId64 " EVT_CHANGE: %f W\n", now, jsy.getActivePower2()); break; default: Serial.printf(" - %" PRId64 " ERR\n", now); diff --git a/examples/CallbackAsync/CallbackAsync.ino b/examples/CallbackAsync/CallbackAsync.ino index 0d0e3c6..887bec9 100644 --- a/examples/CallbackAsync/CallbackAsync.ino +++ b/examples/CallbackAsync/CallbackAsync.ino @@ -20,15 +20,15 @@ void setup() { while (!Serial) continue; - jsy.setCallback([](const Mycila::JSYEventType eventType) { - if (eventType == Mycila::JSYEventType::EVT_CHANGE) { - Serial.printf(" - %" PRIu32 " EVT_CHANGE: %f V, %f A, %f W\n", (uint32_t)millis(), jsy.getVoltage2(), jsy.getCurrent2(), jsy.getPower2()); + jsy.setCallback([](const Mycila::JSY::EventType eventType) { + if (eventType == Mycila::JSY::EventType::EVT_CHANGE) { + Serial.printf(" - %" PRIu32 " EVT_CHANGE: %f V, %f A, %f W\n", (uint32_t)millis(), jsy.getVoltage2(), jsy.getCurrent2(), jsy.getActivePower2()); } }); jsy.begin(Serial2, 16, 17); - if (jsy.getBaudRate() != Mycila::JSYBaudRate::BAUD_38400) { - jsy.setBaudRate(Mycila::JSYBaudRate::BAUD_38400); + if (jsy.getBaudRate() != Mycila::JSY::BaudRate::BAUD_38400) { + jsy.setBaudRate(Mycila::JSY::BaudRate::BAUD_38400); } jsy.end(); diff --git a/examples/PerfTest1/PerfTest1.ino b/examples/PerfTest1/PerfTest1.ino index 4034fde..174a189 100644 --- a/examples/PerfTest1/PerfTest1.ino +++ b/examples/PerfTest1/PerfTest1.ino @@ -20,11 +20,11 @@ Mycila::JSY jsy; JsonDocument doc; -const Mycila::JSYBaudRate rates[] = { - Mycila::JSYBaudRate::BAUD_4800, - Mycila::JSYBaudRate::BAUD_9600, - Mycila::JSYBaudRate::BAUD_19200, - Mycila::JSYBaudRate::BAUD_38400, +const Mycila::JSY::BaudRate rates[] = { + Mycila::JSY::BaudRate::BAUD_4800, + Mycila::JSY::BaudRate::BAUD_9600, + Mycila::JSY::BaudRate::BAUD_19200, + Mycila::JSY::BaudRate::BAUD_38400, }; void setup() { diff --git a/examples/PerfTest2/PerfTest2.ino b/examples/PerfTest2/PerfTest2.ino index b7adfca..8288e28 100644 --- a/examples/PerfTest2/PerfTest2.ino +++ b/examples/PerfTest2/PerfTest2.ino @@ -20,11 +20,11 @@ Mycila::JSY jsy; JsonDocument doc; -const Mycila::JSYBaudRate rates[] = { - Mycila::JSYBaudRate::BAUD_4800, - Mycila::JSYBaudRate::BAUD_9600, - Mycila::JSYBaudRate::BAUD_19200, - Mycila::JSYBaudRate::BAUD_38400, +const Mycila::JSY::BaudRate rates[] = { + Mycila::JSY::BaudRate::BAUD_4800, + Mycila::JSY::BaudRate::BAUD_9600, + Mycila::JSY::BaudRate::BAUD_19200, + Mycila::JSY::BaudRate::BAUD_38400, }; void setup() { @@ -60,7 +60,7 @@ void setup() { Serial.printf(" - ROUND: %d\n", rounds); digitalWrite(RELAY_PIN, LOW); - while (jsy.getPower2() > 0) { + while (jsy.getActivePower2() > 0) { jsy.read(); } @@ -80,7 +80,7 @@ void setup() { int64_t start = esp_timer_get_time(); while (true) { jsy.read(); - now = jsy.getPower2(); + now = jsy.getActivePower2(); if (reactivityTime == 0) { if (now > 0) { @@ -106,7 +106,7 @@ void setup() { start = esp_timer_get_time(); while (true) { jsy.read(); - now = jsy.getPower2(); + now = jsy.getActivePower2(); if (now == 0) { break; diff --git a/examples/Read/Read.ino b/examples/Read/Read.ino index 8c554a7..5aab051 100644 --- a/examples/Read/Read.ino +++ b/examples/Read/Read.ino @@ -24,13 +24,13 @@ void setup() { // read JSY on pins 17 (JSY RX / Serial TX) and 16 (JSY TX / Serial RX) // baud rate will be detected automatically jsy.begin(Serial2, 16, 17); - // jsy.begin(Serial2, 16, 17, Mycila::JSYBaudRate::UNKNOWN); + // jsy.begin(Serial2, 16, 17, Mycila::JSY::BaudRate::UNKNOWN); // if you know the bauds rate, you can set it manually - // jsy.begin(Serial2, 16, 17, Mycila::JSYBaudRate::BAUD_4800); - // jsy.begin(Serial2, 16, 17, Mycila::JSYBaudRate::BAUD_9600); - // jsy.begin(Serial2, 16, 17, Mycila::JSYBaudRate::BAUD_19200); - // jsy.begin(Serial2, 16, 17, Mycila::JSYBaudRate::BAUD_38400); + // jsy.begin(Serial2, 16, 17, Mycila::JSY::BaudRate::BAUD_4800); + // jsy.begin(Serial2, 16, 17, Mycila::JSY::BaudRate::BAUD_9600); + // jsy.begin(Serial2, 16, 17, Mycila::JSY::BaudRate::BAUD_19200); + // jsy.begin(Serial2, 16, 17, Mycila::JSY::BaudRate::BAUD_38400); } void loop() { diff --git a/examples/ReadAsync/ReadAsync.ino b/examples/ReadAsync/ReadAsync.ino index 8ec7fa2..82bf0a8 100644 --- a/examples/ReadAsync/ReadAsync.ino +++ b/examples/ReadAsync/ReadAsync.ino @@ -20,7 +20,7 @@ void setup() { jsy.begin(Serial2, 16, 17, true); } -Mycila::JSYBaudRate target = Mycila::JSYBaudRate::BAUD_38400; +Mycila::JSY::BaudRate target = Mycila::JSY::BaudRate::BAUD_38400; void loop() { if (!jsy.isEnabled()) { diff --git a/examples/RemoteUDP/Listener/Listener.ino b/examples/RemoteUDP/Listener/Listener.ino index 7b43320..c144a64 100644 --- a/examples/RemoteUDP/Listener/Listener.ino +++ b/examples/RemoteUDP/Listener/Listener.ino @@ -124,7 +124,7 @@ Chart power2History = Chart(&dashboard, BAR_CHART, "Channel 2 Active Power (W)") String hostname; String ssid; -volatile Mycila::JSYData jsyData; +volatile Mycila::JSY::Data jsyData; // circular buffer for msg rate Mycila::CircularBuffer messageRateBuffer; diff --git a/examples/RemoteUDP/Listener/platformio.ini b/examples/RemoteUDP/Listener/platformio.ini index a6e5cc1..1c32db5 100644 --- a/examples/RemoteUDP/Listener/platformio.ini +++ b/examples/RemoteUDP/Listener/platformio.ini @@ -23,7 +23,6 @@ lib_deps = mathieucarbou/AsyncTCP @ 3.2.10 mathieucarbou/ESPAsyncWebServer @ 3.3.17 mathieucarbou/MycilaESPConnect @ 6.0.3 - mathieucarbou/MycilaJSY @ 9.1.10 mathieucarbou/MycilaLogger @ 3.2.0 mathieucarbou/MycilaSystem @ 3.1.0 mathieucarbou/MycilaTaskManager @ 3.1.2 @@ -32,6 +31,7 @@ lib_deps = mathieucarbou/MycilaWebSerial @ 6.4.1 ayushsharma82/ElegantOTA @ 3.1.6 https://github.com/mathieucarbou/ayushsharma82-ESP-DASH#dev-1 + MycilaJSY=file://../../.. build_flags = -D ARDUINO_LOOP_STACK_SIZE=4096 -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000 diff --git a/examples/RemoteUDP/Sender/Sender.ino b/examples/RemoteUDP/Sender/Sender.ino index 24ff483..3100696 100644 --- a/examples/RemoteUDP/Sender/Sender.ino +++ b/examples/RemoteUDP/Sender/Sender.ino @@ -212,7 +212,7 @@ Mycila::Task dashboardTask("Dashboard", [](void* params) { networkWiFiSSID.set(espConnect.getWiFiSSID().c_str()); uptime.set(Mycila::Time::toDHHMMSS(Mycila::System::getUptime()).c_str()); - activePower1.update(jsy.getPower1()); + activePower1.update(jsy.getActivePower1()); apparentPower1.update(jsy.getApparentPower1()); current1.update(jsy.getCurrent1()); energy1.update(jsy.getEnergy1()); @@ -221,7 +221,7 @@ Mycila::Task dashboardTask("Dashboard", [](void* params) { voltage1.update(jsy.getVoltage1()); voltageDimmed1.update(jsy.getDimmedVoltage1()); - activePower2.update(jsy.getPower2()); + activePower2.update(jsy.getActivePower2()); apparentPower2.update(jsy.getApparentPower2()); current2.update(jsy.getCurrent2()); energy2.update(jsy.getEnergy2()); @@ -244,8 +244,8 @@ Mycila::Task dashboardTask("Dashboard", [](void* params) { } // set new value - power1HistoryY[MYCILA_GRAPH_POINTS - 1] = round(jsy.getPower1()); - power2HistoryY[MYCILA_GRAPH_POINTS - 1] = round(jsy.getPower2()); + power1HistoryY[MYCILA_GRAPH_POINTS - 1] = round(jsy.getActivePower1()); + power2HistoryY[MYCILA_GRAPH_POINTS - 1] = round(jsy.getActivePower2()); // update charts power1History.updateY(power1HistoryY, MYCILA_GRAPH_POINTS); @@ -359,14 +359,14 @@ void setup() { dashboardTask.forceRun(); // jsy - jsy.setCallback([](const Mycila::JSYEventType eventType) { + jsy.setCallback([](const Mycila::JSY::EventType eventType) { if (!udpSendEnabled) { messageRate = 0; dataRate = 0; return; } - if (eventType == Mycila::JSYEventType::EVT_CHANGE) { + if (eventType == Mycila::JSY::EventType::EVT_CHANGE) { Mycila::ESPConnect::Mode mode = espConnect.getMode(); @@ -380,8 +380,8 @@ void setup() { root["er1"] = jsy.getEnergyReturned1(); root["er2"] = jsy.getEnergyReturned2(); root["f"] = jsy.getFrequency(); - root["p1"] = jsy.getPower1(); - root["p2"] = jsy.getPower2(); + root["p1"] = jsy.getActivePower1(); + root["p2"] = jsy.getActivePower2(); root["pf1"] = jsy.getPowerFactor1(); root["pf2"] = jsy.getPowerFactor2(); root["v1"] = jsy.getVoltage1(); @@ -433,8 +433,8 @@ void setup() { } }); jsy.begin(MYCILA_JSY_SERIAL, MYCILA_JSY_RX, MYCILA_JSY_TX); - if (jsy.isEnabled() && jsy.getBaudRate() != Mycila::JSYBaudRate::BAUD_38400) - jsy.setBaudRate(Mycila::JSYBaudRate::BAUD_38400); + if (jsy.isEnabled() && jsy.getBaudRate() != Mycila::JSY::BaudRate::BAUD_38400) + jsy.setBaudRate(Mycila::JSY::BaudRate::BAUD_38400); // Network Manager espConnect.setAutoRestart(true); diff --git a/examples/RemoteUDP/Sender/platformio.ini b/examples/RemoteUDP/Sender/platformio.ini index a6ee5d7..236e646 100644 --- a/examples/RemoteUDP/Sender/platformio.ini +++ b/examples/RemoteUDP/Sender/platformio.ini @@ -33,7 +33,6 @@ lib_deps = mathieucarbou/AsyncTCP @ 3.2.10 mathieucarbou/ESPAsyncWebServer @ 3.3.17 mathieucarbou/MycilaESPConnect @ 6.0.3 - mathieucarbou/MycilaJSY @ 9.1.10 mathieucarbou/MycilaLogger @ 3.2.0 mathieucarbou/MycilaSystem @ 3.1.0 mathieucarbou/MycilaTaskManager @ 3.1.2 @@ -42,6 +41,7 @@ lib_deps = mathieucarbou/MycilaWebSerial @ 6.4.1 ayushsharma82/ElegantOTA @ 3.1.6 https://github.com/mathieucarbou/ayushsharma82-ESP-DASH#dev-1 + MycilaJSY=file://../../.. build_flags = -D ARDUINO_LOOP_STACK_SIZE=4096 -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000 diff --git a/examples/Repair/Repair.ino b/examples/Repair/Repair.ino index 8b3d457..fdcc821 100644 --- a/examples/Repair/Repair.ino +++ b/examples/Repair/Repair.ino @@ -22,11 +22,11 @@ void setup() { jsy.begin(Serial2, 16, 17); // By forcing a baud rate you know you have set to JSY - // jsy.begin(Serial2, 16, 17, Mycila::JSYBaudRate::BAUD_38400); + // jsy.begin(Serial2, 16, 17, Mycila::JSY::BaudRate::BAUD_38400); // Try change speed until success uint32_t start = millis(); - Mycila::JSYBaudRate baudRate = jsy.getBaudRate() == Mycila::JSYBaudRate::BAUD_38400 ? Mycila::JSYBaudRate::BAUD_1200 : Mycila::JSYBaudRate::BAUD_38400; + Mycila::JSY::BaudRate baudRate = jsy.getBaudRate() == Mycila::JSY::BaudRate::BAUD_38400 ? Mycila::JSY::BaudRate::BAUD_1200 : Mycila::JSY::BaudRate::BAUD_38400; while (!jsy.setBaudRate(baudRate) && millis() - start < 15000) { delay(500); } diff --git a/examples/SetSpeed/SetSpeed.ino b/examples/SetSpeed/SetSpeed.ino index 238b5c2..c14ecc1 100644 --- a/examples/SetSpeed/SetSpeed.ino +++ b/examples/SetSpeed/SetSpeed.ino @@ -9,7 +9,7 @@ #define Serial2 Serial1 #endif -Mycila::JSYBaudRate target = Mycila::JSYBaudRate::BAUD_38400; +Mycila::JSY::BaudRate target = Mycila::JSY::BaudRate::BAUD_38400; Mycila::JSY jsy; diff --git a/examples/SetSpeed2/SetSpeed2.ino b/examples/SetSpeed2/SetSpeed2.ino index a90fe1a..a9404b7 100644 --- a/examples/SetSpeed2/SetSpeed2.ino +++ b/examples/SetSpeed2/SetSpeed2.ino @@ -39,7 +39,7 @@ void loop() { } if (counts % 7 == 0) { - if (jsy.setBaudRate(jsy.getBaudRate() == Mycila::JSYBaudRate::BAUD_38400 ? Mycila::JSYBaudRate::BAUD_1200 : Mycila::JSYBaudRate::BAUD_38400)) { + if (jsy.setBaudRate(jsy.getBaudRate() == Mycila::JSY::BaudRate::BAUD_38400 ? Mycila::JSY::BaudRate::BAUD_1200 : Mycila::JSY::BaudRate::BAUD_38400)) { Serial.printf("JSY baud rate updated to %d\n", static_cast(jsy.getBaudRate())); } else { Serial.println("JSY baud rate update failed"); diff --git a/src/MycilaJSY.cpp b/src/MycilaJSY.cpp index cdf8e80..382307f 100644 --- a/src/MycilaJSY.cpp +++ b/src/MycilaJSY.cpp @@ -53,35 +53,35 @@ extern Mycila::Logger logger; #define JSY_REGISTER_CH2_PF 0x0054 #define JSY_REGISTER_CH2_ENERGY_RETURNED 0x0055 -#define JSY_ADDRESS_BROADCAST 0x00 -#define JSY_ADDRESS_DEFAULT 0x01 - #define JSY_CMD_READ 0x03 #define JSY_CMD_WRITE 0x10 -#define JSY_READ_REGISTERS_RESPONSE_SIZE 61 +#define JSY_READ_REGISTERS_RESPONSE_SIZE 61 +#define JSY_RESET_ENERGY_RESPONSE_SIZE 8 +#define JSY_CHANGE_SETTINGS_RESPONSE_SIZE 8 + +#define JSY_LOCK_TIMEOUT 2000 -// CRC with JSY_ADDRESS_DEFAULT: 0x44 0x18 -// CRC with JSY_ADDRESS_BROADCAST: 0x45 0xC9 +// CRC with MYCILA_JSY_ADDRESS_DEFAULT: 0x44 0x18 +// CRC with MYCILA_JSY_ADDRESS_BROADCAST: 0x45 0xC9 // Ref: https://crccalc.com (CRC-16/MODBUS) static constexpr uint8_t JSY_READ_REGISTERS_REQUEST[] PROGMEM = { - JSY_ADDRESS_BROADCAST, + MYCILA_JSY_ADDRESS_BROADCAST, JSY_CMD_READ, (uint8_t)(JSY_REGISTER_CH1_VOLTAGE >> 8), (uint8_t)(JSY_REGISTER_CH1_VOLTAGE & 0xFF), 0x00, // 14 registers (high byte) 0x0E, // 14 registers (low byte) - 0x45, // CRC - 0xC9 // CRC + 0x45, // CRC (low) + 0xC9 // CRC (high) }; +static constexpr size_t JSY_READ_REGISTERS_REQUEST_LEN = sizeof(JSY_READ_REGISTERS_REQUEST); -#define JSY_RESET_ENERGY_RESPONSE_SIZE 8 - -// CRC with JSY_ADDRESS_DEFAULT: 0xF3 0xFA -// CRC with JSY_ADDRESS_BROADCAST: 0xF7 0x06 +// CRC with MYCILA_JSY_ADDRESS_DEFAULT: 0xF3 0xFA +// CRC with MYCILA_JSY_ADDRESS_BROADCAST: 0xF7 0x06 // Ref: https://crccalc.com (CRC-16/MODBUS) static constexpr uint8_t JSY_RESET_ENERGY_REQUEST[] PROGMEM = { - JSY_ADDRESS_BROADCAST, + MYCILA_JSY_ADDRESS_BROADCAST, JSY_CMD_WRITE, 0x00, // start address high 0x0C, // start address low @@ -92,11 +92,34 @@ static constexpr uint8_t JSY_RESET_ENERGY_REQUEST[] PROGMEM = { 0x00, // data 0x00, // data 0x00, // data - 0xF7, // CRC - 0x06 // CRC + 0xF7, // CRC (low) + 0x06 // CRC (high) }; +static constexpr size_t JSY_RESET_ENERGY_REQUEST_LEN = sizeof(JSY_RESET_ENERGY_REQUEST); -#define JSY_CMD_SET_BAUDS_RESPONSE_SIZE 8 +static constexpr uint8_t JSY_CHANGE_SETTINGS_REQUEST[] PROGMEM = { + MYCILA_JSY_ADDRESS_BROADCAST, + JSY_CMD_WRITE, + (uint8_t)(JSY_REGISTER_ID_AND_BAUDS >> 8), + (uint8_t)(JSY_REGISTER_ID_AND_BAUDS & 0xFF), + 0x00, // number of registers to write (high byte) + 0x01, // number of registers to write (low byte) + 0x02, // number of bytes to follow + 0x00, // new device address + 0x00, // BAUDS ID + 0x00, // CRC (low) + 0x00 // CRC (high) +}; +static constexpr size_t JSY_CHANGE_SETTINGS_REQUEST_LEN = sizeof(JSY_CHANGE_SETTINGS_REQUEST); + +static constexpr Mycila::JSY::BaudRate BAUD_RATES[] = { + Mycila::JSY::BaudRate::BAUD_38400, + Mycila::JSY::BaudRate::BAUD_19200, + Mycila::JSY::BaudRate::BAUD_9600, + Mycila::JSY::BaudRate::BAUD_4800, + Mycila::JSY::BaudRate::BAUD_2400, + Mycila::JSY::BaudRate::BAUD_1200}; +static constexpr size_t BAUD_RATES_COUNT = 6; static constexpr uint16_t CRCTable[] PROGMEM = { 0x0000, @@ -359,7 +382,7 @@ static constexpr uint16_t CRCTable[] PROGMEM = { void Mycila::JSY::begin(HardwareSerial& serial, const int8_t rxPin, const int8_t txPin, - const JSYBaudRate baudRate, + const BaudRate baudRate, const bool async, const uint8_t core, const uint32_t stackSize, @@ -383,24 +406,47 @@ void Mycila::JSY::begin(HardwareSerial& serial, return; } - LOGI(TAG, "Enable JSY on Serial RX (JSY TX Pin): %" PRId8 " and Serial TX (JSY RX Pin): %" PRId8, rxPin, txPin); + LOGI(TAG, "Enable JSY @ 0x%02X on Serial RX (JSY TX Pin): %" PRId8 " and Serial TX (JSY RX Pin): %" PRId8, _destinationAddress, rxPin, txPin); _pause = pause; _serial = &serial; - if (baudRate == JSYBaudRate::UNKNOWN) { - _baudRate = _detectBauds(); - if (_baudRate == JSYBaudRate::UNKNOWN) { - LOGE(TAG, "Unable to read at any supported speed. Disabling."); + if (baudRate == BaudRate::UNKNOWN) { + _baudRate = _detectBauds(_destinationAddress); + + if (_lastAddress != MYCILA_JSY_ADDRESS_UNKNOWN) { + LOGI(TAG, "Detected JSY @ 0x%02X", _lastAddress); + } + + if (_baudRate == BaudRate::UNKNOWN) { + LOGE(TAG, "Unable to read any JSY @ 0x%02X at any supported speed.", _destinationAddress); _serial->end(); return; - } else { - LOGI(TAG, "Detected speed: %" PRIu32 " bauds", (uint32_t)_baudRate); } + + LOGI(TAG, "Detected speed: %" PRIu32 " bauds", (uint32_t)_baudRate); + } else { - _baudRate = baudRate; + LOGW(TAG, "JSY @ 0x%02X bauds detection skipped, forcing baud rate: %" PRIu32, _destinationAddress, (uint32_t)_baudRate); _openSerial(baudRate); - LOGW(TAG, "JSY bauds detection skipped, forcing baud rate: %" PRIu32, (uint32_t)_baudRate); + + _baudRate = BaudRate::UNKNOWN; + for (int j = 0; j < MYCILA_JSY_RETRY_COUNT; j++) { + if (_canRead(_destinationAddress, baudRate)) { + _baudRate = baudRate; + break; + } + } + + if (_lastAddress != MYCILA_JSY_ADDRESS_UNKNOWN) { + LOGI(TAG, "Detected JSY @ 0x%02X", _lastAddress); + } + + if (_baudRate == BaudRate::UNKNOWN) { + LOGE(TAG, "Unable to read JSY @ 0x%02X at speed: %" PRIu32, _destinationAddress, (uint32_t)baudRate); + _serial->end(); + return; + } } assert(!async || xTaskCreateUniversal(_jsyTask, "jsyTask", stackSize, this, MYCILA_JSY_ASYNC_PRIORITY, &_taskHandle, core) == pdPASS); @@ -410,13 +456,15 @@ void Mycila::JSY::begin(HardwareSerial& serial, void Mycila::JSY::end() { if (_enabled) { - LOGI(TAG, "Disable JSY..."); + LOGI(TAG, "Disable JSY @ 0x%02X", _destinationAddress); _enabled = false; - _baudRate = JSYBaudRate::UNKNOWN; while (_taskHandle != NULL) { // JSY takes at least 40-160 ms to finish a read delay(50); } + _baudRate = BaudRate::UNKNOWN; + _lastAddress = MYCILA_JSY_ADDRESS_UNKNOWN; + _address = MYCILA_JSY_ADDRESS_UNKNOWN; _current1 = 0; _current2 = 0; _frequency = 0; @@ -430,31 +478,22 @@ void Mycila::JSY::end() { } } -bool Mycila::JSY::read() { +bool Mycila::JSY::read(const uint8_t address) { if (!_enabled) return false; - if (!_mutex.try_lock_for(std::chrono::milliseconds(1000))) { - LOGW(TAG, "Cannot read: Serial is busy!"); + if (!_mutex.try_lock_for(std::chrono::milliseconds(JSY_LOCK_TIMEOUT))) { + LOGW(TAG, "Cannot read JSY @ 0x%02X: Serial is busy!", address); return false; } #ifdef MYCILA_JSY_DEBUG - Serial.println("[JSY] read()"); - Serial.printf("[JSY] Sent %d > ", sizeof(JSY_READ_REGISTERS_REQUEST)); - for (size_t i = 0; i < sizeof(JSY_READ_REGISTERS_REQUEST); i++) { - Serial.printf("0x%02X ", JSY_READ_REGISTERS_REQUEST[i]); - } - Serial.println(); + Serial.printf("[JSY] read( @ 0x%02X)\n", address); #endif - _serial->flush(false); - _serial->write(JSY_READ_REGISTERS_REQUEST, sizeof(JSY_READ_REGISTERS_REQUEST)); - - uint8_t buffer[JSY_READ_REGISTERS_RESPONSE_SIZE]; - ReadResult result = _timedRead(buffer, JSY_READ_REGISTERS_RESPONSE_SIZE, _baudRate); - - _mutex.unlock(); + memcpy(_buffer, JSY_READ_REGISTERS_REQUEST, JSY_READ_REGISTERS_REQUEST_LEN); + _send(address, JSY_READ_REGISTERS_REQUEST_LEN); + ReadResult result = _timedRead(address, JSY_READ_REGISTERS_RESPONSE_SIZE, _baudRate); if (result == ReadResult::READ_TIMEOUT) { // reset live values in case of read timeout @@ -467,9 +506,13 @@ bool Mycila::JSY::read() { _powerFactor2 = 0; _voltage1 = 0; _voltage2 = 0; + + _mutex.unlock(); + if (_callback) { - _callback(JSYEventType::EVT_READ_TIMEOUT); + _callback(EventType::EVT_READ_TIMEOUT); } + return false; } @@ -484,33 +527,49 @@ bool Mycila::JSY::read() { _powerFactor2 = 0; _voltage1 = 0; _voltage2 = 0; + + _mutex.unlock(); + if (_callback) { - _callback(JSYEventType::EVT_READ_ERROR); + _callback(EventType::EVT_READ_ERROR); } + + return false; + } + + if (result == ReadResult::READ_ERROR_ADDRESS) { + // we have set a destination address, but we read another device + _mutex.unlock(); + + if (_callback) { + _callback(EventType::EVT_READ_ERROR); + } + return false; } assert(result == ReadResult::READ_SUCCESS); - float voltage1 = ((buffer[3] << 24) + (buffer[4] << 16) + (buffer[5] << 8) + buffer[6]) * 0.0001; - float current1 = ((buffer[7] << 24) + (buffer[8] << 16) + (buffer[9] << 8) + buffer[10]) * 0.0001; - float power1 = ((buffer[11] << 24) + (buffer[12] << 16) + (buffer[13] << 8) + buffer[14]) * (buffer[27] == 1 ? -0.0001 : 0.0001); - float energy1 = ((buffer[15] << 24) + (buffer[16] << 16) + (buffer[17] << 8) + buffer[18]) * 0.0001; - float powerFactor1 = ((buffer[19] << 24) + (buffer[20] << 16) + (buffer[21] << 8) + buffer[22]) * 0.001; - float energyReturned1 = ((buffer[23] << 24) + (buffer[24] << 16) + (buffer[25] << 8) + buffer[26]) * 0.0001; - // buffer[27] is the sign of power1 - // buffer[28] is the sign of power2 - // buffer[29] unused - // buffer[30] unused - float frequency = ((buffer[31] << 24) + (buffer[32] << 16) + (buffer[33] << 8) + buffer[34]) * 0.01; - float voltage2 = ((buffer[35] << 24) + (buffer[36] << 16) + (buffer[37] << 8) + buffer[38]) * 0.0001; - float current2 = ((buffer[39] << 24) + (buffer[40] << 16) + (buffer[41] << 8) + buffer[42]) * 0.0001; - float power2 = ((buffer[43] << 24) + (buffer[44] << 16) + (buffer[45] << 8) + buffer[46]) * (buffer[28] == 1 ? -0.0001 : 0.0001); - float energy2 = ((buffer[47] << 24) + (buffer[48] << 16) + (buffer[49] << 8) + buffer[50]) * 0.0001; - float powerFactor2 = ((buffer[51] << 24) + (buffer[52] << 16) + (buffer[53] << 8) + buffer[54]) * 0.001; - float energyReturned2 = ((buffer[55] << 24) + (buffer[56] << 16) + (buffer[57] << 8) + buffer[58]) * 0.0001; - - bool changed = voltage1 != _voltage1 || + float voltage1 = ((_buffer[3] << 24) + (_buffer[4] << 16) + (_buffer[5] << 8) + _buffer[6]) * 0.0001; + float current1 = ((_buffer[7] << 24) + (_buffer[8] << 16) + (_buffer[9] << 8) + _buffer[10]) * 0.0001; + float power1 = ((_buffer[11] << 24) + (_buffer[12] << 16) + (_buffer[13] << 8) + _buffer[14]) * (_buffer[27] == 1 ? -0.0001 : 0.0001); + float energy1 = ((_buffer[15] << 24) + (_buffer[16] << 16) + (_buffer[17] << 8) + _buffer[18]) * 0.0001; + float powerFactor1 = ((_buffer[19] << 24) + (_buffer[20] << 16) + (_buffer[21] << 8) + _buffer[22]) * 0.001; + float energyReturned1 = ((_buffer[23] << 24) + (_buffer[24] << 16) + (_buffer[25] << 8) + _buffer[26]) * 0.0001; + // _buffer[27] is the sign of power1 + // _buffer[28] is the sign of power2 + // _buffer[29] unused + // _buffer[30] unused + float frequency = ((_buffer[31] << 24) + (_buffer[32] << 16) + (_buffer[33] << 8) + _buffer[34]) * 0.01; + float voltage2 = ((_buffer[35] << 24) + (_buffer[36] << 16) + (_buffer[37] << 8) + _buffer[38]) * 0.0001; + float current2 = ((_buffer[39] << 24) + (_buffer[40] << 16) + (_buffer[41] << 8) + _buffer[42]) * 0.0001; + float power2 = ((_buffer[43] << 24) + (_buffer[44] << 16) + (_buffer[45] << 8) + _buffer[46]) * (_buffer[28] == 1 ? -0.0001 : 0.0001); + float energy2 = ((_buffer[47] << 24) + (_buffer[48] << 16) + (_buffer[49] << 8) + _buffer[50]) * 0.0001; + float powerFactor2 = ((_buffer[51] << 24) + (_buffer[52] << 16) + (_buffer[53] << 8) + _buffer[54]) * 0.001; + float energyReturned2 = ((_buffer[55] << 24) + (_buffer[56] << 16) + (_buffer[57] << 8) + _buffer[58]) * 0.0001; + + bool changed = _buffer[0] != _address || + voltage1 != _voltage1 || current1 != _current1 || power1 != _power1 || energy1 != _energy1 || @@ -525,6 +584,7 @@ bool Mycila::JSY::read() { energyReturned2 != _energyReturned2; if (changed) { + _address = _buffer[0]; _voltage1 = voltage1; _current1 = current1; _power1 = power1; @@ -542,147 +602,145 @@ bool Mycila::JSY::read() { _lastReadSuccess = millis(); + _mutex.unlock(); + if (_callback) { - _callback(JSYEventType::EVT_READ); + _callback(EventType::EVT_READ); if (changed) { - _callback(JSYEventType::EVT_CHANGE); + _callback(EventType::EVT_CHANGE); } } return true; } -bool Mycila::JSY::resetEnergy() { +bool Mycila::JSY::resetEnergy(const uint8_t address) { if (!_enabled) return false; - LOGD(TAG, "Try reset energy data..."); + LOGD(TAG, "Try reset energy data of JSY @ 0x%02X", address); - if (!_mutex.try_lock_for(std::chrono::milliseconds(1000))) { - LOGW(TAG, "Cannot reset: Serial is busy!"); + if (!_mutex.try_lock_for(std::chrono::milliseconds(JSY_LOCK_TIMEOUT))) { + LOGW(TAG, "Cannot reset JSY @ 0x%02X: Serial is busy!", address); return false; } #ifdef MYCILA_JSY_DEBUG - Serial.println("[JSY] resetEnergy()"); - Serial.printf("[JSY] Sent %d > ", sizeof(JSY_RESET_ENERGY_REQUEST)); - for (size_t i = 0; i < sizeof(JSY_RESET_ENERGY_REQUEST); i++) { - Serial.printf("0x%02X ", JSY_RESET_ENERGY_REQUEST[i]); - } - Serial.println(); + Serial.printf("[JSY] resetEnergy(0x%02X)\n", address); #endif - _serial->flush(false); - _serial->write(JSY_RESET_ENERGY_REQUEST, sizeof(JSY_RESET_ENERGY_REQUEST)); - - uint8_t buffer[JSY_RESET_ENERGY_RESPONSE_SIZE]; - ReadResult result = _timedRead(buffer, JSY_RESET_ENERGY_RESPONSE_SIZE, _baudRate); + memcpy(_buffer, JSY_RESET_ENERGY_REQUEST, JSY_RESET_ENERGY_REQUEST_LEN); + _send(address, JSY_RESET_ENERGY_REQUEST_LEN); + ReadResult result = _timedRead(address, JSY_RESET_ENERGY_RESPONSE_SIZE, _baudRate); _mutex.unlock(); return result == ReadResult::READ_SUCCESS; } -bool Mycila::JSY::setBaudRate(const JSYBaudRate baudRate) { +bool Mycila::JSY::setBaudRate(const uint8_t address, const BaudRate baudRate) { + return _set(address, address ? address : (_lastAddress ? _lastAddress : MYCILA_JSY_ADDRESS_DEFAULT), baudRate); +} + +bool Mycila::JSY::setDeviceAddress(const uint8_t address, const uint8_t newAddress) { + return _set(address, newAddress, _baudRate); +} + +bool Mycila::JSY::_set(const uint8_t address, const uint8_t newAddress, const BaudRate newBaudRate) { if (!_enabled) return false; - if (baudRate == JSYBaudRate::UNKNOWN) + if (newBaudRate == BaudRate::UNKNOWN) + return false; + + if (newAddress == MYCILA_JSY_ADDRESS_UNKNOWN) + return false; + + LOGD(TAG, "Try update JSY @ 0x%02X with new address 0x%02X and baud rate %" PRIu32 "...", address, newAddress, static_cast(newBaudRate)); + + if (!_mutex.try_lock_for(std::chrono::milliseconds(JSY_LOCK_TIMEOUT))) { + LOGW(TAG, "Cannot set JSY @ 0x%02X: Serial is busy!", address); return false; + } + +#ifdef MYCILA_JSY_DEBUG + Serial.printf("[JSY] _set(0x%02X)\n", address); +#endif + + memcpy(_buffer, JSY_CHANGE_SETTINGS_REQUEST, JSY_CHANGE_SETTINGS_REQUEST_LEN); - if (_baudRate == baudRate) - return true; - - LOGD(TAG, "Try update baud rate to %" PRIu32 "...", (uint32_t)baudRate); - - uint8_t data[] = { - JSY_ADDRESS_BROADCAST, - JSY_CMD_WRITE, - (uint8_t)(JSY_REGISTER_ID_AND_BAUDS >> 8), - (uint8_t)(JSY_REGISTER_ID_AND_BAUDS & 0xFF), - 0x00, // number of registers to write (high byte) - 0x01, // number of registers to write (low byte) - 0x02, // number of bytes to follow - JSY_ADDRESS_DEFAULT, - 0x00, // BAUDS ID - 0x00, // CRC - 0x00 // CRC - }; - - // For CRC: https://crccalc.com - // Select CRC-16/MODBUS - switch (baudRate) { - case JSYBaudRate::BAUD_1200: - data[8] = 0x03; // BAUDS ID - data[9] = 0xEB; // CRC - data[10] = 0xD5; // CRC + // set address + _buffer[7] = newAddress; + + // set baud rate ID + switch (newBaudRate) { + case BaudRate::BAUD_1200: + _buffer[8] = 0x03; break; - case JSYBaudRate::BAUD_2400: - data[8] = 0x04; // BAUDS ID - data[9] = 0xAA; // CRC - data[10] = 0x17; // CRC + case BaudRate::BAUD_2400: + _buffer[8] = 0x04; break; - case JSYBaudRate::BAUD_4800: - data[8] = 0x05; // BAUDS ID - data[9] = 0x6B; // CRC - data[10] = 0xD7; // CRC + case BaudRate::BAUD_4800: + _buffer[8] = 0x05; break; - case JSYBaudRate::BAUD_9600: - data[8] = 0x06; // BAUDS ID - data[9] = 0x2B; // CRC - data[10] = 0xD6; // CRC + case BaudRate::BAUD_9600: + _buffer[8] = 0x06; break; - case JSYBaudRate::BAUD_19200: - data[8] = 0x07; // BAUDS ID - data[9] = 0xEA; // CRC - data[10] = 0x16; // CRC + case BaudRate::BAUD_19200: + _buffer[8] = 0x07; break; - case JSYBaudRate::BAUD_38400: - data[8] = 0x08; // BAUDS ID - data[9] = 0xAA; // CRC - data[10] = 0x12; // CRC + case BaudRate::BAUD_38400: + _buffer[8] = 0x08; break; default: assert(false); break; } - if (!_mutex.try_lock_for(std::chrono::milliseconds(1000))) { - LOGW(TAG, "Cannot set baud rate: Serial is busy!"); - return false; - } - -#ifdef MYCILA_JSY_DEBUG - Serial.println("[JSY] setBaudRate()"); - Serial.printf("[JSY] Sent %d > ", sizeof(data)); - for (size_t i = 0; i < sizeof(data); i++) { - Serial.printf("0x%02X ", data[i]); - } - Serial.println(); -#endif + // pre-calculate crc16 (with the default broadcast address) + uint16_t crc = _crc16(_buffer, JSY_CHANGE_SETTINGS_REQUEST_LEN - 2); + _buffer[JSY_CHANGE_SETTINGS_REQUEST_LEN - 2] = LOBYTE(crc); + _buffer[JSY_CHANGE_SETTINGS_REQUEST_LEN - 1] = HIBYTE(crc); - _serial->flush(false); - _serial->write(data, sizeof(data)); + _send(address, JSY_CHANGE_SETTINGS_REQUEST_LEN); + ReadResult result = _timedRead(address, JSY_CHANGE_SETTINGS_RESPONSE_SIZE, _baudRate); - uint8_t buffer[JSY_CMD_SET_BAUDS_RESPONSE_SIZE]; - ReadResult result = _timedRead(buffer, JSY_CMD_SET_BAUDS_RESPONSE_SIZE, _baudRate); + // unexpected error ? + if (result != ReadResult::READ_SUCCESS && result != ReadResult::READ_ERROR_ADDRESS) { + _mutex.unlock(); + return false; + } - if (result != ReadResult::READ_SUCCESS) { + // response from unexpected address ? + if (result == ReadResult::READ_ERROR_ADDRESS && _lastAddress != newAddress) { _mutex.unlock(); return false; } - _openSerial(baudRate); + _openSerial(newBaudRate); bool success = false; - for (int i = 0; i < MYCILA_JSY_RETRY_COUNT && !success; i++) { - success |= _canRead(baudRate); + for (int i = 0; i < MYCILA_JSY_RETRY_COUNT; i++) { + if (_canRead(address, newBaudRate)) { + success = true; + break; + } } if (success) { - _baudRate = baudRate; - } else if (_baudRate != JSYBaudRate::UNKNOWN) { - _openSerial(_baudRate); + // update baud rate + _baudRate = newBaudRate; + + // update destination address if needed + if (_destinationAddress != MYCILA_JSY_ADDRESS_BROADCAST && _destinationAddress == address) { + _destinationAddress = newAddress; + } + + } else { + LOGE(TAG, "Unable to read JSY @ 0x%02X at speed: %" PRIu32, address, (uint32_t)newBaudRate); + if (_baudRate != BaudRate::UNKNOWN) { + _openSerial(_baudRate); + } } _mutex.unlock(); @@ -693,6 +751,7 @@ bool Mycila::JSY::setBaudRate(const JSYBaudRate baudRate) { #ifdef MYCILA_JSON_SUPPORT void Mycila::JSY::toJson(const JsonObject& root) const { root["enabled"] = _enabled; + root["address"] = _address; root["speed"] = static_cast(_baudRate); root["time"] = _lastReadSuccess; root["current1"] = _current1; @@ -711,24 +770,20 @@ void Mycila::JSY::toJson(const JsonObject& root) const { } #endif -void Mycila::JSY::_openSerial(JSYBaudRate baudRate) { - LOGD(TAG, "Open serial at %" PRIu32 " bauds", (uint32_t)baudRate); - _serial->begin(static_cast(baudRate), SERIAL_8N1, _pinRX, _pinTX); - _serial->setTimeout(200); - while (!_serial) - yield(); - while (!_serial->availableForWrite()) - yield(); - _drop(); - _serial->flush(false); -} +bool Mycila::JSY::_canRead(const uint8_t address, BaudRate baudRate) { +#ifdef MYCILA_JSY_DEBUG + Serial.printf("[JSY] _canRead(0x%02X)\n", address); +#endif -Mycila::JSY::ReadResult Mycila::JSY::_timedRead(uint8_t* buffer, const size_t expectedLength, const JSYBaudRate baudRate) { - assert(expectedLength > 2); + memcpy(_buffer, JSY_READ_REGISTERS_REQUEST, JSY_READ_REGISTERS_REQUEST_LEN); + _send(address, JSY_READ_REGISTERS_REQUEST_LEN); + return _timedRead(address, JSY_READ_REGISTERS_RESPONSE_SIZE, baudRate) == ReadResult::READ_SUCCESS; +} +Mycila::JSY::ReadResult Mycila::JSY::_timedRead(const uint8_t expectedAddress, const size_t expectedLen, const BaudRate baudRate) { size_t count = 0; - while (count < expectedLength) { - size_t read = _serial->readBytes(buffer + count, expectedLength - count); + while (count < expectedLen) { + size_t read = _serial->readBytes(_buffer + count, expectedLen - count); if (read) { count += read; } else { @@ -737,42 +792,66 @@ Mycila::JSY::ReadResult Mycila::JSY::_timedRead(uint8_t* buffer, const size_t ex } #ifdef MYCILA_JSY_DEBUG - Serial.printf("[JSY] Read %d < ", count); + Serial.printf("[JSY] Read @ 0x%02X %d < ", expectedAddress, count); for (size_t i = 0; i < count; i++) { - Serial.printf("0x%02X ", buffer[i]); + Serial.printf("0x%02X ", _buffer[i]); } Serial.println(); #endif _drop(); + // timeout ? if (count == 0) { - LOGD(TAG, "Read timeout"); + LOGD(TAG, "Read @ 0x%02X timeout", expectedAddress); return ReadResult::READ_TIMEOUT; } - if (count != expectedLength) { - LOGD(TAG, "Read error: %d != %d", count, expectedLength); + // check length + if (count != expectedLen) { + LOGD(TAG, "Read @ 0x%02X error: %d != %d", expectedAddress, count, expectedLen); return ReadResult::READ_ERROR_COUNT; } // CRC check - uint16_t crc = 0xFFFF; - uint8_t* data = buffer; - size_t len = expectedLength - 2; - while (len--) { - uint8_t temp = *(data++) ^ LOBYTE(crc); - crc = (crc >> 8) ^ pgm_read_word_near(CRCTable + temp); + uint16_t crc = _crc16(_buffer, expectedLen - 2); + if (_buffer[expectedLen - 2] != LOBYTE(crc) || _buffer[expectedLen - 1] != HIBYTE(crc)) { + LOGD(TAG, "Read @ 0x%02X error: Bad CRC 0x%02X 0x%02X != 0x%02X 0x%02X", expectedAddress, _buffer[expectedLen - 2], _buffer[expectedLen - 1], LOBYTE(crc), HIBYTE(crc)); + return ReadResult::READ_ERROR_CRC; } - if (buffer[expectedLength - 2] != LOBYTE(crc) || buffer[expectedLength - 1] != HIBYTE(crc)) { - LOGD(TAG, "Read error: Bad CRC 0x%02X 0x%02X != 0x%02X 0x%02X", buffer[expectedLength - 2], buffer[expectedLength - 1], LOBYTE(crc), HIBYTE(crc)); - return ReadResult::READ_ERROR_CRC; + _lastAddress = _buffer[0]; + + // address check + if (expectedAddress != MYCILA_JSY_ADDRESS_BROADCAST && expectedAddress != _lastAddress) { + LOGD(TAG, "Read @ 0x%02X error: Wrong device address 0x%02X", expectedAddress, _buffer[0]); + return ReadResult::READ_ERROR_ADDRESS; } return ReadResult::READ_SUCCESS; } +void Mycila::JSY::_send(const uint8_t address, const size_t len) { + if (address != MYCILA_JSY_ADDRESS_BROADCAST) { + _buffer[0] = address; + // re-calculate crc16 + uint16_t crc = _crc16(_buffer, len - 2); + _buffer[len - 2] = LOBYTE(crc); + _buffer[len - 1] = HIBYTE(crc); + } + +#ifdef MYCILA_JSY_DEBUG + Serial.printf("[JSY] Send @ 0x%02X %d > ", address, len); + for (size_t i = 0; i < len; i++) { + Serial.printf("0x%02X ", _buffer[i]); + } + Serial.println(); +#endif + + _serial->flush(false); + _serial->write(_buffer, len); +} + size_t Mycila::JSY::_drop() { size_t count = 0; if (_serial->available()) { @@ -794,43 +873,41 @@ size_t Mycila::JSY::_drop() { return count; } -bool Mycila::JSY::_canRead(JSYBaudRate baudRate) { -#ifdef MYCILA_JSY_DEBUG - Serial.println("[JSY] _canRead()"); - Serial.printf("[JSY] Sent %d > ", sizeof(JSY_READ_REGISTERS_REQUEST)); - for (size_t i = 0; i < sizeof(JSY_READ_REGISTERS_REQUEST); i++) { - Serial.printf("0x%02X ", JSY_READ_REGISTERS_REQUEST[i]); - } - Serial.println(); -#endif - +void Mycila::JSY::_openSerial(BaudRate baudRate) { + LOGD(TAG, "Open serial at %" PRIu32 " bauds", static_cast(baudRate)); + _serial->begin(static_cast(baudRate), SERIAL_8N1, _pinRX, _pinTX); + _serial->setTimeout(200); + while (!_serial) + yield(); + while (!_serial->availableForWrite()) + yield(); + _drop(); _serial->flush(false); - _serial->write(JSY_READ_REGISTERS_REQUEST, sizeof(JSY_READ_REGISTERS_REQUEST)); - - uint8_t buffer[JSY_READ_REGISTERS_RESPONSE_SIZE]; - return _timedRead(buffer, JSY_READ_REGISTERS_RESPONSE_SIZE, baudRate) == ReadResult::READ_SUCCESS; } -Mycila::JSYBaudRate Mycila::JSY::_detectBauds() { - constexpr JSYBaudRate baudRates[] = { - JSYBaudRate::BAUD_38400, - JSYBaudRate::BAUD_19200, - JSYBaudRate::BAUD_9600, - JSYBaudRate::BAUD_4800, - JSYBaudRate::BAUD_2400, - JSYBaudRate::BAUD_1200}; - constexpr size_t baudsRateCount = 6; - for (int i = 0; i < baudsRateCount * 2; i++) { - JSYBaudRate baudRate = baudRates[i % baudsRateCount]; - LOGD(TAG, "Trying to read at %" PRIu32 " bauds...", (uint32_t)baudRate); +Mycila::JSY::BaudRate Mycila::JSY::_detectBauds(const uint8_t address) { + for (int i = 0; i < BAUD_RATES_COUNT * 2; i++) { + BaudRate baudRate = BAUD_RATES[i % BAUD_RATES_COUNT]; + LOGD(TAG, "Trying to read JSY @ 0x%02X at %" PRIu32 " bauds...", address, static_cast(baudRate)); _openSerial(baudRate); for (int j = 0; j < MYCILA_JSY_RETRY_COUNT; j++) { - if (_canRead(baudRate)) { + if (_canRead(address, baudRate)) { return baudRate; } } } - return JSYBaudRate::UNKNOWN; + return BaudRate::UNKNOWN; +} + +// For CRC: https://crccalc.com +// Select CRC-16/MODBUS +uint16_t Mycila::JSY::_crc16(const uint8_t* data, size_t len) { + uint16_t crc = 0xFFFF; + while (len--) { + uint8_t temp = *(data++) ^ LOBYTE(crc); + crc = (crc >> 8) ^ pgm_read_word_near(CRCTable + temp); + } + return crc; } void Mycila::JSY::_jsyTask(void* params) { diff --git a/src/MycilaJSY.h b/src/MycilaJSY.h index ff48b12..293391c 100644 --- a/src/MycilaJSY.h +++ b/src/MycilaJSY.h @@ -38,56 +38,81 @@ #define MYCILA_JSY_RETRY_COUNT 4 #endif -// #define MYCILA_JSY_DEBUG 1 +// broadcast address to send requests to all devices +#define MYCILA_JSY_ADDRESS_BROADCAST 0x00 -namespace Mycila { - enum class JSYBaudRate { - UNKNOWN = 0, - BAUD_1200 = 1200, - BAUD_2400 = 2400, - BAUD_4800 = 4800, - BAUD_9600 = 9600, - BAUD_19200 = 19200, - BAUD_38400 = 38400, - }; +// default factory JSY address +#define MYCILA_JSY_ADDRESS_DEFAULT 0x01 - enum class JSYEventType { - // JSY has successfully read the data - EVT_READ = 0, - // JSY has successfully read the data and the values have changed - EVT_CHANGE, - // wrong data received when reading values - EVT_READ_ERROR, - // timeout reached when reading values - EVT_READ_TIMEOUT - }; +// constant returned when device address is unknown +#define MYCILA_JSY_ADDRESS_UNKNOWN 0x00 - typedef struct { - float current1 = 0; // A - float current2 = 0; // A - float energy1 = 0; // kWh - float energy2 = 0; // kWh - float energyReturned1 = 0; // kWh - float energyReturned2 = 0; // kWh - float frequency = 0; // Hz - float power1 = 0; // W - float power2 = 0; // W - float powerFactor1 = 0; - float powerFactor2 = 0; - float voltage1 = 0; // V - float voltage2 = 0; // V - } JSYData; - - typedef std::function JSYCallback; +// #define MYCILA_JSY_DEBUG 1 +namespace Mycila { class JSY { public: + enum class BaudRate { + UNKNOWN = 0, + BAUD_1200 = 1200, + BAUD_2400 = 2400, + BAUD_4800 = 4800, + BAUD_9600 = 9600, + BAUD_19200 = 19200, + BAUD_38400 = 38400, + }; + + enum class EventType { + // JSY has successfully read the data + EVT_READ = 0, + // JSY has successfully read the data and the values have changed + EVT_CHANGE, + // wrong data received when reading values + EVT_READ_ERROR, + // timeout reached when reading values + EVT_READ_TIMEOUT, + // wrong JSY device read + EVT_READ_PEER + }; + + typedef struct { + uint8_t address = MYCILA_JSY_ADDRESS_UNKNOWN; // device address + float current1 = 0; // A + float current2 = 0; // A + float energy1 = 0; // kWh + float energy2 = 0; // kWh + float energyReturned1 = 0; // kWh + float energyReturned2 = 0; // kWh + float frequency = 0; // Hz + float power1 = 0; // W + float power2 = 0; // W + float powerFactor1 = 0; + float powerFactor2 = 0; + float voltage1 = 0; // V + float voltage2 = 0; // V + } Data; + + typedef std::function Callback; + ~JSY() { end(); } - // - rxPin: RX board pin connected to the TX of the JSY - // - txPin: TX board pin connected to the RX of the JSY - // - pause: time in milliseconds to wait between each read in async mode - // The baud rate is automatically detected + /** + * @brief Set the address used to send requests (1-255). + * Default value is MYCILA_JSY_ADDRESS_BROADCAST (0), which means every device will receive the request. + */ + void setDestinationAddress(const uint8_t address) { _destinationAddress = address; } + + /** + * @brief Initialize the JSY with the given RX and TX pins. + * @param serial The serial port to use + * @param rxPin RX board pin connected to the TX of the JSY + * @param txPin TX board pin connected to the RX of the JSY + * @param async If true, the JSY will be read in a separate task + * @param core The core to use for the async task + * @param stackSize The stack size of the async task + * @param pause Time in milliseconds to wait between each read in async mode + * @note The baud rate is automatically detected. + */ void begin(HardwareSerial& serial, // NOLINT const int8_t rxPin, const int8_t txPin, @@ -95,37 +120,100 @@ namespace Mycila { const uint8_t core = MYCILA_JSY_ASYNC_CORE, const uint32_t stackSize = MYCILA_JSY_ASYNC_STACK_SIZE, const uint32_t pause = MYCILA_JSY_ASYNC_READ_PAUSE_MS) { - begin(serial, rxPin, txPin, JSYBaudRate::UNKNOWN, async, core, stackSize, pause); + begin(serial, rxPin, txPin, BaudRate::UNKNOWN, async, core, stackSize, pause); } - // - rxPin: RX board pin connected to the TX of the JSY - // - txPin: TX board pin connected to the RX of the JSY - // - baudRate: the baud rate of the JSY. If set to JSYBaudRate::UNKNOWN, the baud rate is automatically detected - // - pause: time in milliseconds to wait between each read in async mode - // The baud rate is automatically detected + /** + * @brief Initialize the JSY with the given RX and TX pins. + * @param serial The serial port to use + * @param rxPin RX board pin connected to the TX of the JSY + * @param txPin TX board pin connected to the RX of the JSY + * @param baudRate The baud rate of the JSY. If set to BaudRate::UNKNOWN, the baud rate is automatically detected + * @param async If true, the JSY will be read in a separate task + * @param core The core to use for the async task + * @param stackSize The stack size of the async task + * @param pause Time in milliseconds to wait between each read in async mode + */ void begin(HardwareSerial& serial, // NOLINT const int8_t rxPin, const int8_t txPin, - const JSYBaudRate baudRate, + const BaudRate baudRate, const bool async = false, const uint8_t core = MYCILA_JSY_ASYNC_CORE, const uint32_t stackSize = MYCILA_JSY_ASYNC_STACK_SIZE, const uint32_t pause = MYCILA_JSY_ASYNC_READ_PAUSE_MS); + /** + * @brief Ends the JSY communication. + */ void end(); - // Read the JSY values. Returns true if the read was successful. - // This function is blocking until the data is read or the timeout is reached. - // No need to call read in async mode - bool read(); + /** + * @brief Set a new address for a device. + * @param newAddress The new address to set (1-255) + * @return true if the address was changed + * @note The destination address is updated by this function if it was set to the old address. + */ + bool setDeviceAddress(const uint8_t newAddress) { return setDeviceAddress(_destinationAddress, newAddress); } + + /** + * @brief Set a new address for a device. + * @param address The address of the device to change (1-255) or MYCILA_JSY_ADDRESS_BROADCAST for the broadcast address + * @param newAddress The new address to set (1-255) + * @return true if the address was changed + * @note The destination address is updated by this function if it was set to the old address. + */ + bool setDeviceAddress(const uint8_t address, const uint8_t newAddress); + + /** + * @brief Read the JSY values. + * @return true if the read was successful + * @note This function is blocking until the data is read or the timeout is reached. + */ + bool read() { return read(_destinationAddress); } - // Resets energy counters. Returns true if the reset was successful. - // This function is blocking until the reset is confirmed or the timeout is reached. - bool resetEnergy(); + /** + * @brief Read the JSY values. + * @param address The address of the device to read (1-255) or MYCILA_JSY_ADDRESS_BROADCAST for all devices + * @return true if the read was successful + * @note This function is blocking until the data is read or the timeout is reached. + */ + bool read(const uint8_t address); + + /** + * @brief Reset the energy counters of the JSY. + * @return true if the reset was successful + * @note This function is blocking until the reset is confirmed or the timeout is reached. + */ + bool resetEnergy() { return resetEnergy(_destinationAddress); } + + /** + * @brief Reset the energy counters of the JSY. + * @param address The address of the device to reset (1-255) or MYCILA_JSY_ADDRESS_BROADCAST for all devices + * @return true if the reset was successful + * @note This function is blocking until the reset is confirmed or the timeout is reached. + */ + bool resetEnergy(const uint8_t address); // Try to change the baud rate of the JSY. Returns true if the baud rate was changed. // This function is blocking until the change is confirmed or the timeout is reached. - bool setBaudRate(const JSYBaudRate baudRate); + + /** + * @brief Change the baud rate of the JSY. + * @param baudRate The new baud rate to set + * @return true if the baud rate was changed + * @note This function is blocking until the change is confirmed or the timeout is reached. + */ + bool setBaudRate(const BaudRate baudRate) { return setBaudRate(_destinationAddress, baudRate); } + + /** + * @brief Change the baud rate of the JSY. + * @param address The address of the device to change (1-255) or MYCILA_JSY_ADDRESS_BROADCAST for all devices + * @param baudRate The new baud rate to set + * @return true if the baud rate was changed + * @note This function is blocking until the change is confirmed or the timeout is reached. + */ + bool setBaudRate(const uint8_t address, const BaudRate baudRate); #ifdef MYCILA_JSON_SUPPORT void toJson(const JsonObject& root) const; @@ -133,9 +221,16 @@ namespace Mycila { gpio_num_t getRXPin() const { return _pinRX; } gpio_num_t getTXPin() const { return _pinTX; } - JSYBaudRate getBaudRate() const { return _baudRate; } + BaudRate getBaudRate() const { return _baudRate; } + // the address of the last device's response + uint8_t getLastAddress() const { return _lastAddress; } bool isEnabled() const { return _enabled; } + /** + * @brief Get device address from which the last data was read. + */ + uint8_t getAddress() const { return _address; } + float getCurrent1() const { return _current1; } float getCurrent2() const { return _current2; } float getEnergy1() const { return _energy1; } @@ -143,9 +238,8 @@ namespace Mycila { float getEnergyReturned1() const { return _energyReturned1; } float getEnergyReturned2() const { return _energyReturned2; } float getFrequency() const { return _frequency; } - // active power, not apparent power - float getPower1() const { return _power1; } - float getPower2() const { return _power2; } + float getActivePower1() const { return _power1; } + float getActivePower2() const { return _power2; } float getPowerFactor1() const { return _powerFactor1; } float getPowerFactor2() const { return _powerFactor2; } float getResistance1() const { return _current1 == 0 ? 0 : abs(_power1 / (_current1 * _current1)); } @@ -173,7 +267,8 @@ namespace Mycila { // get the uptime in milliseconds of the last successful read uint32_t getTime() const { return _lastReadSuccess; } - void getData(JSYData& data) const { // NOLINT + void getData(Data& data) const { // NOLINT + data.address = _address; data.current1 = _current1; data.current2 = _current2; data.energy1 = _energy1; @@ -192,9 +287,10 @@ namespace Mycila { // check if the device is connected to the grid, meaning if last read was successful bool isConnected() const { return _frequency > 0; } - void setCallback(JSYCallback callback) { _callback = callback; } + void setCallback(Callback callback) { _callback = callback; } private: + volatile uint8_t _address = MYCILA_JSY_ADDRESS_UNKNOWN; volatile float _current1 = 0; // A volatile float _current2 = 0; // A volatile float _energy1 = 0; // kWh @@ -217,22 +313,33 @@ namespace Mycila { uint32_t _lastReadSuccess = 0; TaskHandle_t _taskHandle; volatile bool _enabled = false; - volatile JSYBaudRate _baudRate = JSYBaudRate::UNKNOWN; + volatile BaudRate _baudRate = BaudRate::UNKNOWN; std::timed_mutex _mutex; - JSYCallback _callback = nullptr; + Callback _callback = nullptr; + uint8_t _destinationAddress = MYCILA_JSY_ADDRESS_BROADCAST; + uint8_t _lastAddress = MYCILA_JSY_ADDRESS_UNKNOWN; + uint8_t _buffer[64]; private: enum class ReadResult { READ_SUCCESS = 0, READ_TIMEOUT, READ_ERROR_COUNT, - READ_ERROR_CRC + READ_ERROR_CRC, + READ_ERROR_ADDRESS, }; - void _openSerial(JSYBaudRate baudRate); - ReadResult _timedRead(uint8_t* buffer, const size_t length, const JSYBaudRate baudRate); + + bool _set(const uint8_t address, const uint8_t newAddress, const BaudRate newBaudRate); + + bool _canRead(const uint8_t address, BaudRate baudRate); + ReadResult _timedRead(const uint8_t expectedAddress, const size_t expectedLen, const BaudRate baudRate); + void _send(const uint8_t address, const size_t len); + size_t _drop(); - bool _canRead(JSYBaudRate baudRate); - JSYBaudRate _detectBauds(); + void _openSerial(BaudRate baudRate); + BaudRate _detectBauds(const uint8_t address); + uint16_t _crc16(const uint8_t* buffer, size_t len); + static void _jsyTask(void* pvParameters); }; } // namespace Mycila