|
// SPDX-License-Identifier: MIT |
|
/* |
|
* Copyright (C) 2023-2024 Mathieu Carbou and others |
|
*/ |
|
#include "MycilaJSY.h" |
|
#include "MycilaLogger.h" |
|
|
|
#include <algorithm> |
|
|
|
#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() { |
|
if (_enabled) |
|
return; |
|
|
|
if (!JSYConfig.isEnabled()) { |
|
Logger.warn(TAG, "Disable JSY"); |
|
return; |
|
} |
|
|
|
int32_t valRX = JSYConfig.getRXPin(); |
|
if (GPIO_IS_VALID_GPIO(valRX)) { |
|
_pinRX = (gpio_num_t)valRX; |
|
} else { |
|
Logger.error(TAG, "Disable JSY: Invalid JSY RX pin: %u", _pinRX); |
|
_pinRX = GPIO_NUM_NC; |
|
return; |
|
} |
|
|
|
int32_t valTX = JSYConfig.getTXPin(); |
|
if (GPIO_IS_VALID_OUTPUT_GPIO(valTX)) { |
|
_pinTX = (gpio_num_t)valTX; |
|
} else { |
|
Logger.error(TAG, "Disable JSY: Invalid JSY TX pin: %u", _pinTX); |
|
_pinTX = GPIO_NUM_NC; |
|
return; |
|
} |
|
|
|
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"); |
|
|
|
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, "- JSY Speed Detected: %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(_task, "Mycila-JSY", 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; |
|
return _read(); |
|
} |
|
|
|
void Mycila::JSYClass::toJson(const JsonObject& root) { |
|
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::_task(void* pvParameters) { |
|
// Serial.println("JSY async task started"); |
|
// Serial.println(xPortGetCoreID()); |
|
JSYClass* jsy = reinterpret_cast<JSYClass*>(pvParameters); |
|
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<uint32_t>(MYCILA_JSY_ASYNC_READ_PAUSE_INTERVAL_MS))); |
|
} |
|
vTaskDelete(NULL); |
|
} |
|
|
|
namespace Mycila { |
|
JSYClass JSY; |
|
JSYConfigClass JSYConfig; |
|
} // namespace Mycila |