From fe8b5353e62f760b061f372272aa65a0dff4e47d Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Mon, 22 Jan 2024 10:42:24 +0100 Subject: [PATCH] Initial commit --- .clang-format | 16 ++ .github/FUNDING.yml | 13 ++ .github/dependabot.yml | 20 ++ .github/workflows/ci.yml | 122 ++++++++++++ .github/workflows/dependabot.yml | 19 ++ .gitignore | 6 + .vscode/settings.json | 10 + COPYING | 11 + LICENSE | 21 ++ README.md | 35 ++++ examples/JSYRead/JSYRead.ino | 22 ++ examples/JSYReadAsync/JSYReadAsync.ino | 23 +++ library.json | 35 ++++ library.properties | 11 + platformio.ini | 22 ++ src/MycilaJSY.cpp | 266 +++++++++++++++++++++++++ src/MycilaJSY.h | 93 +++++++++ 17 files changed, 745 insertions(+) create mode 100644 .clang-format create mode 100644 .github/FUNDING.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/dependabot.yml create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 COPYING create mode 100644 LICENSE create mode 100644 README.md create mode 100644 examples/JSYRead/JSYRead.ino create mode 100644 examples/JSYReadAsync/JSYReadAsync.ino create mode 100644 library.json create mode 100644 library.properties create mode 100644 platformio.ini create mode 100644 src/MycilaJSY.cpp create mode 100644 src/MycilaJSY.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a0498d9 --- /dev/null +++ b/.clang-format @@ -0,0 +1,16 @@ +Language: Cpp +BasedOnStyle: LLVM + +AccessModifierOffset: -2 +AllowShortIfStatementsOnASingleLine: false +ColumnLimit: 0 +ContinuationIndentWidth: 2 +FixNamespaceComments: false +IndentAccessModifiers: true +IndentCaseLabels: true +IndentWidth: 2 +NamespaceIndentation: All +PointerAlignment: Left +ReferenceAlignment: Left +TabWidth: 2 +UseTab: Never \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..e411f9b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: mathieucarbou +# patreon: # Replace with a single Patreon username +# open_collective: # Replace with a single Open Collective username +# ko_fi: # Replace with a single Ko-fi username +# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +# liberapay: # Replace with a single Liberapay username +# issuehunt: # Replace with a single IssueHunt username +# otechie: # Replace with a single Otechie username +# lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: https://paypal.me/mathieucarboufr diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..bd654c2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gitsubmodule" + directory: "/" + schedule: + interval: "daily" + reviewers: + - "mathieucarbou" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + reviewers: + - "mathieucarbou" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..082a4a3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,122 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Continuous Integration + +on: + workflow_dispatch: + push: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + cpplint: + name: cpplint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Cache + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-cpplint + path: ~/.cache/pip + + - name: Pyhton + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: cpplint + run: | + python -m pip install --upgrade pip + pip install --upgrade cpplint + cpplint \ + --repository=. \ + --recursive \ + --filter=-whitespace/line_length,-whitespace/braces,-whitespace/comments,-runtime/indentation_namespace,-whitespace/indent,-readability/braces,-whitespace/newline,-readability/todo,-build/c++11 \ + src + + arduino: + name: Arduino + needs: cpplint + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - core: esp32:esp32 + board: esp32:esp32:esp32 + eeprom: true + softwareserial: false + index_url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install arduino-cli + run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh + + - name: Update core index + run: arduino-cli core update-index --additional-urls "${{ matrix.index_url }}" + + - name: Install core + run: arduino-cli core install --additional-urls "${{ matrix.index_url }}" ${{ matrix.core }} + + - name: Install ArduinoJson + run: arduino-cli lib install ArduinoJson@7.0.2 + + - name: Install MycilaLogger + run: arduino-cli lib install MycilaLogger@2.0.0 + + - name: Build JSYRead + run: arduino-cli compile --library . --warnings all -b ${{ matrix.board }} "examples/JSYRead/JSYRead.ino" + + - name: Build JSYReadAsync + run: arduino-cli compile --library . --warnings all -b ${{ matrix.board }} "examples/JSYReadAsync/JSYReadAsync.ino" + + platformio: + name: PlatformIO + needs: cpplint + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - platform: espressif32 + board: esp32dev + eeprom: true + softwareserial: false + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up cache + uses: actions/cache@v4 + with: + path: | + ~/.platformio + ~/.cache/pip + key: ${{ runner.os }}-platformio + + - name: Set up Python 3.x + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install PlatformIO + run: pip install platformio + + - name: Install platform "${{ matrix.platform }}" + run: platformio platform install ${{ matrix.platform }} + + - name: Build JSYRead + run: platformio ci "examples/JSYRead/JSYRead.ino" -l '.' -b ${{ matrix.board }} + + - name: Build JSYReadAsync + run: platformio ci "examples/JSYReadAsync/JSYReadAsync.ino" -l '.' -b ${{ matrix.board }} diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml new file mode 100644 index 0000000..da098fd --- /dev/null +++ b/.github/workflows/dependabot.yml @@ -0,0 +1,19 @@ +name: PlatformIO Dependabot + +on: + workflow_dispatch: + schedule: + # Runs every day at 01:00 + - cron: "0 1 * * *" + +jobs: + dependabot: + runs-on: ubuntu-latest + name: PlatformIO Dependabot + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: run PlatformIO Dependabot + uses: peterus/platformio_dependabot@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ac15a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +.lh +/.pio +/.vscode/* +!/.vscode/settings.json +/logs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..06877f5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "C_Cpp.clang_format_fallbackStyle": "{ BasedOnStyle: LLVM, UseTab: Never, IndentWidth: 2, TabWidth: 2, AllowShortIfStatementsOnASingleLine: false, IndentCaseLabels: true, ColumnLimit: 0, AccessModifierOffset: -2, NamespaceIndentation: All, FixNamespaceComments: false, IndentAccessModifiers: true, PointerAlignment: Left, ReferenceAlignment: Left, ContinuationIndentWidth: 2}", + "files.exclude": { + "**/.lh": true + }, + "cSpell.words": [ + "YASOLR" + ], + "cmake.configureOnOpen": false +} \ No newline at end of file diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..818c06c --- /dev/null +++ b/COPYING @@ -0,0 +1,11 @@ +MycilaJSY + +Copyright (C) 2023-2024 Mathieu Carbou + +MycilaJSY is provided under: + + SPDX-License-Identifier: MIT + +Being under the terms of the MIT License according with: + + LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1616585 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-2024 Mathieu Carbou + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..77d9390 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# MycilaJSY + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Continuous Integration](https://github.com/mathieucarbou/MycilaJSY/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/MycilaJSY/actions/workflows/ci.yml) +[![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/MycilaJSY.svg)](https://registry.platformio.org/libraries/mathieucarbou/MycilaJSY) + +Arduino / ESP32 library for the JSY-MK-194T single-phase two-way electric energy metering module + +- Sync mode and async mode +- Core, stack size and interval can be configured +- Energy reset +- Custom bauds rate +- Automatically detect and switch bauds rate +- Configurable Serial (Serial2 by default) +- Metrics: + +```c++ + volatile float current1 = 0; // A + volatile float current2 = 0; // A + volatile float energy1 = 0; // kWh + volatile float energy2 = 0; // kWh + volatile float energyReturned1 = 0; // kWh + volatile float energyReturned2 = 0; // kWh + volatile uint8_t frequency = 0; // Hz + volatile float power1 = 0; // W + volatile float power2 = 0; // W + volatile float powerFactor1 = 0; + volatile float powerFactor2 = 0; + volatile float voltage1 = 0; // V + volatile float voltage2 = 0; // V +``` + +## Usage + +See examples and API diff --git a/examples/JSYRead/JSYRead.ino b/examples/JSYRead/JSYRead.ino new file mode 100644 index 0000000..1ad3fbb --- /dev/null +++ b/examples/JSYRead/JSYRead.ino @@ -0,0 +1,22 @@ +#include + +#define KEY_JSY_RX_PIN + +void setup() { + Serial.begin(115200); + while (!Serial) + continue; + + Mycila::JSY.begin(17, 16); +} + +void loop() { + Mycila::JSY.read(); + + JsonDocument doc; + Mycila::JSY.toJson(doc.to()); + serializeJson(doc, Serial); + Serial.println(); + + delay(5000); +} diff --git a/examples/JSYReadAsync/JSYReadAsync.ino b/examples/JSYReadAsync/JSYReadAsync.ino new file mode 100644 index 0000000..e412812 --- /dev/null +++ b/examples/JSYReadAsync/JSYReadAsync.ino @@ -0,0 +1,23 @@ +#define MYCILA_JSY_ASYNC_READ_PAUSE_INTERVAL_MS 60 +#define MYCILA_JSY_ASYNC_CORE 0 + +#include + +#define KEY_JSY_RX_PIN + +void setup() { + Serial.begin(115200); + while (!Serial) + continue; + + Mycila::JSY.begin(17, 16, true); +} + +void loop() { + JsonDocument doc; + Mycila::JSY.toJson(doc.to()); + serializeJson(doc, Serial); + Serial.println(); + + delay(5000); +} diff --git a/library.json b/library.json new file mode 100644 index 0000000..b6a5e62 --- /dev/null +++ b/library.json @@ -0,0 +1,35 @@ +{ + "name": "MycilaJSY", + "version": "1.0.0", + "description": "Arduino / ESP32 library for the JSY-MK-194T single-phase two-way electric energy metering module", + "keywords": "JSY,JSY-MK-194,JSY-MK-194T", + "homepage": "https://github.com/mathieucarbou/MycilaJSY", + "repository": { + "type": "git", + "url": "https://github.com/mathieucarbou/MycilaJSY.git" + }, + "authors": { + "name": "Mathieu Carbou", + "url": "https://github.com/mathieucarbou" + }, + "license": "MIT", + "frameworks": "arduino", + "platforms": [ + "espressif32" + ], + "headers": "MycilaJSY.h", + "dependencies": [ + { + "owner": "bblanchon", + "name": "ArduinoJson", + "version": "^7.0.2", + "platforms": "espressif32" + }, + { + "owner": "mathieucarbou", + "name": "MycilaLogger", + "version": "^2.0.0", + "platforms": "espressif32" + } + ] +} \ No newline at end of file diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..17c525e --- /dev/null +++ b/library.properties @@ -0,0 +1,11 @@ +name=MycilaJSY +version=1.0.0 +author=Mathieu Carbou +maintainer=Mathieu Carbou +sentence=Arduino / ESP32 library for the JSY-MK-194T single-phase two-way electric energy metering module +paragraph= +category=Other +url=https://github.com/mathieucarbou/MycilaJSY +architectures=esp32 +license=MIT +depends=ArduinoJson,MycilaLogger diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..b3278f1 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,22 @@ +[env] +build_flags = -Wall -Wextra +lib_deps = + bblanchon/ArduinoJson @ 7.0.2 + mathieucarbou/MycilaLogger @ 2.0.0 + +upload_protocol = esptool +upload_port = /dev/cu.usbserial-0001 + +monitor_port = /dev/cu.usbserial-0001 +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder, log2file + +[platformio] +lib_dir = . +src_dir = examples/JSYRead +; src_dir = examples/JSYReadAsync + +[env:esp32] +platform = espressif32@6.5.0 +board = esp32dev +framework = arduino diff --git a/src/MycilaJSY.cpp b/src/MycilaJSY.cpp new file mode 100644 index 0000000..7217138 --- /dev/null +++ b/src/MycilaJSY.cpp @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#include "MycilaJSY.h" +#include "MycilaLogger.h" + +#include + +#define TAG "JSY" +#define FLUSH_BUFFER_SIZE 256 + +static const uint8_t JSY_READ_MSG[] = {0x01, 0x03, 0x00, 0x48, 0x00, 0x0E, 0x44, 0x18}; +static uint8_t BUFFER[FLUSH_BUFFER_SIZE]; + +void Mycila::JSYClass::begin(const uint8_t jsyRXPin, const uint8_t jsyTXPin, const bool async, const JSYBaudRate baudRate, HardwareSerial* serial) { + if (_enabled) + return; + + if (GPIO_IS_VALID_OUTPUT_GPIO(jsyRXPin)) { + _pinRX = (gpio_num_t)jsyRXPin; + } else { + Logger.error(TAG, "Disable JSY: Invalid JSY RX pin: %u", _pinRX); + _pinRX = GPIO_NUM_NC; + return; + } + + if (GPIO_IS_VALID_GPIO(jsyTXPin)) { + _pinTX = (gpio_num_t)jsyTXPin; + } else { + Logger.error(TAG, "Disable JSY: Invalid JSY TX pin: %u", _pinTX); + _pinTX = GPIO_NUM_NC; + return; + } + + _baudRate = baudRate; + _async = async; + _serial = serial; + + Logger.info(TAG, "Enable JSY..."); + Logger.debug(TAG, "- JSY RX Pin: %u", _pinRX); + Logger.debug(TAG, "- JSY TX Pin: %u", _pinTX); + Logger.debug(TAG, "- Async: %s", _async ? "true" : "false"); + Logger.debug(TAG, "- Baud Rate: %u bps", (uint32_t)_baudRate); + + const JSYBaudRate baudRates[] = {JSYBaudRate::BAUD_4800, JSYBaudRate::BAUD_9600, JSYBaudRate::BAUD_19200, JSYBaudRate::BAUD_38400}; + bool ok = false; + for (int i = 0; i < 4; i++) { + _serial->begin((uint32_t)baudRates[i], SERIAL_8N1, _pinTX, _pinRX); + while (!_serial) + yield(); + if (_read(4)) { + Logger.debug(TAG, "- Detected baud Rate: %u bps", (uint32_t)baudRates[i]); + if (baudRates[i] == _baudRate) { + ok = true; + break; + } + _setBaudRate(_baudRate); + _serial->end(); + _serial->begin((uint32_t)_baudRate, SERIAL_8N1, _pinTX, _pinRX); + while (!_serial) + yield(); + if (!_read(4)) { + Logger.error(TAG, "Unable to read JSY data after baud rate change"); + break; + } + ok = true; + break; + } else { + _serial->end(); + } + } + + if (!ok) { + Logger.error(TAG, "Unable to read JSY data with any baud rate"); + _serial->end(); + return; + } + + if (_async && xTaskCreateUniversal(_jsyTask, "jsyTask", MYCILA_JSY_ASYNC_STACK_SIZE, this, 1, nullptr, MYCILA_JSY_ASYNC_CORE) != pdPASS) { + Logger.error(TAG, "Unable to create JSY async task"); + return; + } + + _enabled = true; +} + +void Mycila::JSYClass::end() { + if (_disable()) { + _serial->end(); + } +} + +void Mycila::JSYClass::endAndResetEnergy() { + _disable(); + + Logger.warn(TAG, "Energy Reset..."); + + const uint8_t data[] = {0x01, 0x10, 0x00, 0x0C, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0xF3, 0xFA}; + _serial->write(data, 13); + _serial->flush(); + _readSerial(); + + // Note: do not end() _serial: ESP needs to restart right after sending the reset command + Logger.debug(TAG, "Energy Reset done"); +} + +bool Mycila::JSYClass::read() { + if (!_enabled) + return false; + if (_async) + return false; + return _read(); +} + +void Mycila::JSYClass::toJson(const JsonObject& root) const { + root["current1"] = current1; + root["current2"] = current2; + root["enabled"] = _enabled; + root["energy_returned1"] = energyReturned1; + root["energy_returned2"] = energyReturned2; + root["energy1"] = energy1; + root["energy2"] = energy2; + root["frequency"] = frequency; + root["power_factor1"] = powerFactor1; + root["power_factor2"] = powerFactor2; + root["power1"] = power1; + root["power2"] = power2; + root["voltage1"] = voltage1; + root["voltage2"] = voltage2; +} + +bool Mycila::JSYClass::_read(uint8_t maxCount) { + while (maxCount > 0 && !_read()) { + if (--maxCount == 0) + return false; + delay(60); + } + return true; +} + +bool __attribute__((hot)) Mycila::JSYClass::_read() { + if (_reading) + return false; + + _reading = true; + + _serial->write(JSY_READ_MSG, 8); + _serial->flush(); + size_t count = _readSerial(); + + if (count != 61 || BUFFER[0] != 0x01) { + _reading = false; + return false; + } + + voltage1 = ((BUFFER[3] << 24) + (BUFFER[4] << 16) + (BUFFER[5] << 8) + BUFFER[6]) * 0.0001; + current1 = ((BUFFER[7] << 24) + (BUFFER[8] << 16) + (BUFFER[9] << 8) + BUFFER[10]) * 0.0001; + power1 = ((BUFFER[11] << 24) + (BUFFER[12] << 16) + (BUFFER[13] << 8) + BUFFER[14]) * (BUFFER[27] == 1 ? -0.0001 : 0.0001); + energy1 = ((BUFFER[15] << 24) + (BUFFER[16] << 16) + (BUFFER[17] << 8) + BUFFER[18]) * 0.0001; + powerFactor1 = ((BUFFER[19] << 24) + (BUFFER[20] << 16) + (BUFFER[21] << 8) + BUFFER[22]) * 0.001; + 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 + frequency = round(((BUFFER[31] << 24) + (BUFFER[32] << 16) + (BUFFER[33] << 8) + BUFFER[34]) * 0.01); + voltage2 = ((BUFFER[35] << 24) + (BUFFER[36] << 16) + (BUFFER[37] << 8) + BUFFER[38]) * 0.0001; + current2 = ((BUFFER[39] << 24) + (BUFFER[40] << 16) + (BUFFER[41] << 8) + BUFFER[42]) * 0.0001; + power2 = ((BUFFER[43] << 24) + (BUFFER[44] << 16) + (BUFFER[45] << 8) + BUFFER[46]) * (BUFFER[28] == 1 ? -0.0001 : 0.0001); + energy2 = ((BUFFER[47] << 24) + (BUFFER[48] << 16) + (BUFFER[49] << 8) + BUFFER[50]) * 0.0001; + powerFactor2 = ((BUFFER[51] << 24) + (BUFFER[52] << 16) + (BUFFER[53] << 8) + BUFFER[54]) * 0.001; + energyReturned2 = ((BUFFER[55] << 24) + (BUFFER[56] << 16) + (BUFFER[57] << 8) + BUFFER[58]) * 0.0001; + + _reading = false; + return true; +} + +bool Mycila::JSYClass::_setBaudRate(JSYBaudRate baudRate) { + Logger.debug(TAG, "Set JSY baud rate to %u bps...", (uint32_t)baudRate); + + uint8_t data[] = {0x00, 0x10, 0x00, 0x04, 0x00, 0x01, 0x02, 0x01, 0x00, 0x00, 0x00}; + switch (baudRate) { + case JSYBaudRate::BAUD_4800: + data[8] = 0x05; + data[9] = 0x6B; + data[10] = 0xD7; + break; + case JSYBaudRate::BAUD_9600: + data[8] = 0x06; + data[9] = 0x2B; + data[10] = 0xD6; + break; + case JSYBaudRate::BAUD_19200: + data[8] = 0x07; + data[9] = 0xEA; + data[10] = 0x16; + break; + case JSYBaudRate::BAUD_38400: + data[8] = 0x08; + data[9] = 0xAA; + data[10] = 0x12; + break; + default: + assert(false); + break; + } + + _serial->write(data, 11); + _serial->flush(); + _readSerial(); + + Logger.debug(TAG, "JSY baud rate updated."); + + return true; +} + +size_t Mycila::JSYClass::_readSerial() { + size_t count = 0; + while (const size_t n = _serial->available()) { + const size_t pos = count % FLUSH_BUFFER_SIZE; + count += _serial->readBytes(BUFFER + pos, min(n, FLUSH_BUFFER_SIZE - pos)); + } + return count; +} + +bool Mycila::JSYClass::_disable() { + if (_enabled) { + Logger.info(TAG, "Disable JSY..."); + _enabled = false; + while (_reading) + delay(100); + current1 = 0; + current2 = 0; + energy1 = 0; + energy2 = 0; + energyReturned1 = 0; + energyReturned2 = 0; + frequency = 0; + power1 = 0; + power2 = 0; + powerFactor1 = 0; + powerFactor2 = 0; + voltage1 = 0; + voltage2 = 0; + return true; + } + return false; +} + +void Mycila::JSYClass::_jsyTask(void* params) { + // Serial.println("JSY async task started"); + // Serial.println(xPortGetCoreID()); + JSYClass* jsy = reinterpret_cast(params); + while (jsy->_enabled) { + jsy->_read(); + if (jsy->_enabled) + // https://esp32developer.com/programming-in-c-c/tasks/tasks-vs-co-routines + delay(max(portTICK_PERIOD_MS, static_cast(MYCILA_JSY_ASYNC_READ_PAUSE_INTERVAL_MS))); + } + vTaskDelete(NULL); +} + +namespace Mycila { + JSYClass JSY; +} // namespace Mycila diff --git a/src/MycilaJSY.h b/src/MycilaJSY.h new file mode 100644 index 0000000..37ade4a --- /dev/null +++ b/src/MycilaJSY.h @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2023-2024 Mathieu Carbou and others + */ +#pragma once + +#include +#include + +#define MYCILA_JSY_VERSION "1.0.0" +#define MYCILA_JSY_VERSION_MAJOR 1 +#define MYCILA_JSY_VERSION_MINOR 0 +#define MYCILA_JSY_VERSION_REVISION 0 + +#ifndef MYCILA_JSY_ASYNC_CORE +#define MYCILA_JSY_ASYNC_CORE 0 +#endif + +#ifndef MYCILA_JSY_ASYNC_STACK_SIZE +#define MYCILA_JSY_ASYNC_STACK_SIZE 256 * 5 +#endif + +#ifndef MYCILA_JSY_ASYNC_READ_PAUSE_INTERVAL_MS +#define MYCILA_JSY_ASYNC_READ_PAUSE_INTERVAL_MS 60 +#endif + +namespace Mycila { + enum class JSYBaudRate { + BAUD_4800 = 4800, + BAUD_9600 = 9600, + BAUD_19200 = 19200, + BAUD_38400 = 38400, + }; + + class JSYClass { + public: + // jsyRXPin: pin connected to the RX of the JSY, jsyTXPin: pin connected to the TX of the JSY + void begin(const uint8_t jsyRXPin, const uint8_t jsyTXPin, const bool async = false, const JSYBaudRate baudRate = JSYBaudRate::BAUD_38400, HardwareSerial* serial = &Serial2); + + void end(); + + // ends the JSY reading and resets + // note that after this call, I need to restart the ESP so that the reset can be achieved. + // TODO: check if we can instead do a voltage cycle (LOW - HIGH) on the pins and restart Serial2 + void endAndResetEnergy(); + + gpio_num_t getRXPin() const { return _pinRX; } + gpio_num_t getTXPin() const { return _pinTX; } + bool isEnabled() const { return _enabled; } + bool isAsync() const { return _async; } + JSYBaudRate getBaudRate() const { return _baudRate; } + HardwareSerial* getSerial() const { return _serial; } + + void toJson(const JsonObject& root) const; + + // IMPORTANT: DO NOT CALL read() in async mode: it will have no effect and will return false + bool read(); + + public: + volatile float current1 = 0; // A + volatile float current2 = 0; // A + volatile float energy1 = 0; // kWh + volatile float energy2 = 0; // kWh + volatile float energyReturned1 = 0; // kWh + volatile float energyReturned2 = 0; // kWh + volatile uint8_t frequency = 0; // Hz + volatile float power1 = 0; // W + volatile float power2 = 0; // W + volatile float powerFactor1 = 0; + volatile float powerFactor2 = 0; + volatile float voltage1 = 0; // V + volatile float voltage2 = 0; // V + + private: + gpio_num_t _pinRX = GPIO_NUM_NC; + gpio_num_t _pinTX = GPIO_NUM_NC; + HardwareSerial* _serial = &Serial2; + JSYBaudRate _baudRate = JSYBaudRate::BAUD_38400; + volatile bool _async = false; + volatile bool _enabled = false; + volatile bool _reading = false; + + private: + bool _disable(); + bool _read(); + bool _read(uint8_t maxCount); + bool _setBaudRate(JSYBaudRate baudRate); + size_t _readSerial(); + static void _jsyTask(void* pvParameters); + }; + + extern JSYClass JSY; +} // namespace Mycila