Add support of JSY-MK-163T
This commit is contained in:
parent
d0981934b0
commit
387ff53369
61
.vscode/settings.json
vendored
61
.vscode/settings.json
vendored
@ -1,3 +1,62 @@
|
||||
{
|
||||
"C_Cpp.clang_format_style": "WebKit"
|
||||
"C_Cpp.clang_format_style": "WebKit",
|
||||
"files.associations": {
|
||||
"*.tcc": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"fstream": "cpp",
|
||||
"istream": "cpp",
|
||||
"ostream": "cpp",
|
||||
"sstream": "cpp",
|
||||
"array": "cpp",
|
||||
"deque": "cpp",
|
||||
"list": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"vector": "cpp",
|
||||
"string_view": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"regex": "cpp",
|
||||
"chrono": "cpp",
|
||||
"functional": "cpp",
|
||||
"atomic": "cpp",
|
||||
"bitset": "cpp",
|
||||
"cctype": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"iterator": "cpp",
|
||||
"map": "cpp",
|
||||
"memory": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"random": "cpp",
|
||||
"ratio": "cpp",
|
||||
"system_error": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"limits": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"thread": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"typeinfo": "cpp"
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,11 @@
|
||||
[](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml)
|
||||
[](https://github.com/tbnobody/OpenDTU/actions/workflows/yarnlint.yml)
|
||||
|
||||
## !! IMPORTANT UPGRADE NOTES !!
|
||||
# Fork
|
||||
|
||||
This fork add support of [JSY-MK power meter modules](https://www.jsypowermeter.com/).
|
||||
|
||||
## !! IMPORTANT UPGRADE NOTES
|
||||
|
||||
If you are upgrading from a version before 15.03.2023 you have to upgrade the partition table of the ESP32. Please follow the [this](docs/UpgradePartition.md) documentation!
|
||||
|
||||
|
||||
@ -30,6 +30,8 @@
|
||||
|
||||
#define DEV_MAX_MAPPING_NAME_STRLEN 63
|
||||
|
||||
#define PWRMTR_MAX_CHAN_COUNT 2
|
||||
|
||||
struct CHANNEL_CONFIG_T {
|
||||
uint16_t MaxChannelPower;
|
||||
char Name[CHAN_MAX_NAME_STRLEN];
|
||||
@ -152,6 +154,18 @@ struct CONFIG_T {
|
||||
uint8_t Brightness;
|
||||
} Led_Single[PINMAPPING_LED_COUNT];
|
||||
|
||||
struct {
|
||||
uint32_t BaudRate;
|
||||
uint32_t PollInterval;
|
||||
} SerialModbus;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
bool InvertDirection;
|
||||
bool NegativePower;
|
||||
} channel[PWRMTR_MAX_CHAN_COUNT];
|
||||
} PowerMeter;
|
||||
|
||||
INVERTER_CONFIG_T Inverter[INV_MAX_COUNT];
|
||||
char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1];
|
||||
};
|
||||
|
||||
63
include/JsyMk.h
Normal file
63
include/JsyMk.h
Normal file
@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <JSY_MK.h>
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
|
||||
class JsyMkClass {
|
||||
public:
|
||||
enum class Field_t : size_t {
|
||||
// Device informations
|
||||
ADDRESS,
|
||||
MANUFACTURER,
|
||||
MODEL,
|
||||
VERSION,
|
||||
VOLTAGE_RANGE,
|
||||
CURRENT_RANGE,
|
||||
// Measures
|
||||
VOLTAGE,
|
||||
CURRENT,
|
||||
POWER,
|
||||
POWER_FACTOR,
|
||||
FREQUENCY,
|
||||
NEGATIVE,
|
||||
TOTAL_POSITIVE_ENERGY,
|
||||
TOTAL_NEGATIVE_ENERGY,
|
||||
TODAY_POSITIVE_ENERGY,
|
||||
TODAY_NEGATIVE_ENERGY
|
||||
};
|
||||
|
||||
JsyMkClass();
|
||||
void init(Scheduler& scheduler);
|
||||
|
||||
uint32_t getLastUpdate() const;
|
||||
bool isInitialised() const;
|
||||
|
||||
uint32_t getPollInterval() const;
|
||||
void setPollInterval(const uint32_t interval);
|
||||
|
||||
size_t getChannelNumber() const;
|
||||
|
||||
String getFieldName(size_t channel, Field_t fieldId) const;
|
||||
String getFieldString(size_t channel, Field_t fieldId) const;
|
||||
float getFieldValue(size_t channel, Field_t fieldId) const;
|
||||
|
||||
size_t getFieldDigits(Field_t fieldId) const;
|
||||
String getFieldUnit(Field_t fieldId) const;
|
||||
String getFieldDeviceClass(Field_t fieldId) const;
|
||||
String getFieldStatusClass(Field_t fieldId) const;
|
||||
|
||||
void reset();
|
||||
|
||||
private:
|
||||
void loop();
|
||||
|
||||
JSY_MK _jsymk;
|
||||
Task _loopTask;
|
||||
bool _initialised = false;
|
||||
uint32_t _lastUpdate = 0;
|
||||
float _todayPositiveRef = 0;
|
||||
float _todayNegativeRef = 0;
|
||||
};
|
||||
|
||||
extern JsyMkClass JsyMk;
|
||||
@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "MqttHandlePowerMeter.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <Hoymiles.h>
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
@ -66,6 +67,9 @@ private:
|
||||
void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min = 1, const int16_t max = 100);
|
||||
void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off);
|
||||
|
||||
void publishPowerMeterField(size_t channel, JsyMkClass::Field_t fieldId, const bool clear = false);
|
||||
|
||||
static void createPowerMeterInfo(JsonDocument& doc, size_t channel, JsyMkClass::Field_t fieldId);
|
||||
static void createInverterInfo(JsonDocument& doc, std::shared_ptr<InverterAbstract> inv);
|
||||
static void createDtuInfo(JsonDocument& doc);
|
||||
|
||||
|
||||
27
include/MqttHandlePowerMeter.h
Normal file
27
include/MqttHandlePowerMeter.h
Normal file
@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "Configuration.h"
|
||||
#include "JsyMk.h"
|
||||
#include <espMqttClient.h>
|
||||
|
||||
class MqttHandlePowerMeterClass {
|
||||
public:
|
||||
using Field_t = JsyMkClass::Field_t;
|
||||
|
||||
MqttHandlePowerMeterClass();
|
||||
void init(Scheduler& scheduler);
|
||||
|
||||
static String getTopic(size_t channel, Field_t fieldId);
|
||||
|
||||
private:
|
||||
void
|
||||
loop();
|
||||
void publishField(size_t channel, const Field_t fieldId);
|
||||
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const size_t total);
|
||||
|
||||
Task _loopTask;
|
||||
uint32_t _lastUpdate = {};
|
||||
};
|
||||
|
||||
extern MqttHandlePowerMeterClass MqttHandlePowerMeter;
|
||||
@ -39,6 +39,9 @@ struct PinMapping_t {
|
||||
uint8_t display_cs;
|
||||
uint8_t display_reset;
|
||||
int8_t led[PINMAPPING_LED_COUNT];
|
||||
|
||||
int8_t serial_modbus_tx;
|
||||
int8_t serial_modbus_rx;
|
||||
};
|
||||
|
||||
class PinMappingClass {
|
||||
@ -50,6 +53,7 @@ public:
|
||||
bool isValidNrf24Config() const;
|
||||
bool isValidCmt2300Config() const;
|
||||
bool isValidEthConfig() const;
|
||||
bool isValidSerialModbusConfig() const;
|
||||
|
||||
private:
|
||||
PinMapping_t _pinMapping;
|
||||
|
||||
@ -16,6 +16,7 @@ private:
|
||||
static void generateInverterCommonJsonResponse(JsonObject& root, std::shared_ptr<InverterAbstract> inv);
|
||||
static void generateInverterChannelJsonResponse(JsonObject& root, std::shared_ptr<InverterAbstract> inv);
|
||||
static void generateCommonJsonResponse(JsonVariant& root);
|
||||
static void generatePowerMeterJsonResponse(JsonObject& root);
|
||||
|
||||
static void addField(JsonObject& root, std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId, String topic = "");
|
||||
static void addTotalField(JsonObject& root, const String& name, const float value, const String& unit, const uint8_t digits);
|
||||
|
||||
@ -108,3 +108,9 @@
|
||||
#define LED_BRIGHTNESS 100U
|
||||
|
||||
#define MAX_INVERTER_LIMIT 2250
|
||||
|
||||
#define SERIAL_MODBUS_POLL_INTERVAL 1U
|
||||
|
||||
#ifndef SERIAL_MODBUS_BAUDRATE
|
||||
#define SERIAL_MODBUS_BAUDRATE 9600U
|
||||
#endif
|
||||
13
lib/JSY_MK/library.json
Normal file
13
lib/JSY_MK/library.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "JSY_MK",
|
||||
"keywords": "modbus, RTU, energy",
|
||||
"description": "JSY_MK",
|
||||
"authors": {
|
||||
"name": "Benoit Leforestier"
|
||||
},
|
||||
"version": "0.1.0",
|
||||
"frameworks": "arduino",
|
||||
"platforms": [
|
||||
"espressif32"
|
||||
]
|
||||
}
|
||||
258
lib/JSY_MK/src/JSY_MK.cpp
Normal file
258
lib/JSY_MK/src/JSY_MK.cpp
Normal file
@ -0,0 +1,258 @@
|
||||
#include "JSY_MK.h"
|
||||
#include <byteswap.h>
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t DEVICE_ADDRESS = 1;
|
||||
constexpr std::chrono::seconds DEFAULT_UPDATE_PERIOD(1);
|
||||
|
||||
struct parameterRaw {
|
||||
uint8_t len;
|
||||
uint16_t model;
|
||||
uint16_t version;
|
||||
uint16_t voltageRange;
|
||||
uint16_t CurrentRange;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct measurementRaw {
|
||||
uint8_t len;
|
||||
uint16_t voltage;
|
||||
uint16_t current;
|
||||
uint16_t power;
|
||||
uint32_t positiveEnergy;
|
||||
uint16_t powerFactor;
|
||||
uint32_t negativeEnergy;
|
||||
uint16_t powerDirect;
|
||||
uint16_t frequency;
|
||||
} __attribute__((__packed__));
|
||||
} // namespace
|
||||
|
||||
JSY_MK::JSY_MK(uint8_t uart_nr)
|
||||
: m_serialRTU(uart_nr)
|
||||
, m_updatePeriod(DEFAULT_UPDATE_PERIOD)
|
||||
, m_lastUpdateTime(0)
|
||||
{
|
||||
}
|
||||
|
||||
void JSY_MK::begin(uint32_t baud, uint32_t config, int8_t rxPin, int8_t txPin)
|
||||
{
|
||||
m_serialRTU.setRequestTimeout(std::chrono::milliseconds(500));
|
||||
m_serialRTU.setResponseCallback(std::bind(
|
||||
&JSY_MK::responseHandler,
|
||||
this,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2,
|
||||
std::placeholders::_3,
|
||||
std::placeholders::_4,
|
||||
std::placeholders::_5));
|
||||
|
||||
m_serialRTU.begin(baud, config, rxPin, txPin);
|
||||
m_serialRTU.readHoldingRegisters(DEVICE_ADDRESS, 0x0000, 4);
|
||||
}
|
||||
|
||||
void JSY_MK::end()
|
||||
{
|
||||
m_serialRTU.end();
|
||||
m_model = 0;
|
||||
}
|
||||
|
||||
void JSY_MK::loop()
|
||||
{
|
||||
m_serialRTU.loop();
|
||||
|
||||
if (m_updatePeriod > std::chrono::milliseconds::zero() && !m_serialRTU.isDeviceBusy(DEVICE_ADDRESS) && m_model != 0 && (m_lastUpdateTime == 0 || (millis() - m_lastUpdateTime) > m_updatePeriod.count())) {
|
||||
m_serialRTU.readHoldingRegisters(DEVICE_ADDRESS, 0x0048, 10);
|
||||
m_lastUpdateTime = millis();
|
||||
}
|
||||
}
|
||||
|
||||
bool JSY_MK::isBusy() const
|
||||
{
|
||||
return m_serialRTU.isDeviceBusy(DEVICE_ADDRESS);
|
||||
}
|
||||
|
||||
void JSY_MK::setUpdatedCallback(updatedCb cb)
|
||||
{
|
||||
m_updateCallback = cb;
|
||||
}
|
||||
|
||||
void JSY_MK::setErrorCallback(errorCb cb)
|
||||
{
|
||||
m_errorCallback = cb;
|
||||
}
|
||||
|
||||
void JSY_MK::responseHandler(
|
||||
SerialModbusRTU::ec exceptionCode,
|
||||
uint8_t deviceAddress,
|
||||
uint8_t functionCode,
|
||||
const void* data,
|
||||
size_t len)
|
||||
{
|
||||
if (exceptionCode != SerialModbusRTU::ec::noError) {
|
||||
if (m_model == 0) {
|
||||
// Retry init
|
||||
m_serialRTU.readHoldingRegisters(DEVICE_ADDRESS, 0x0000, 4);
|
||||
}
|
||||
|
||||
if (m_errorCallback)
|
||||
m_errorCallback(exceptionCode);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (deviceAddress != DEVICE_ADDRESS)
|
||||
return; // Ignore
|
||||
|
||||
if (data == nullptr)
|
||||
return; // Error
|
||||
|
||||
if (len == sizeof(parameterRaw)) {
|
||||
const auto* buffer = static_cast<const parameterRaw*>(data);
|
||||
if (buffer->len != sizeof(parameterRaw) - 1)
|
||||
return; // Error
|
||||
|
||||
m_model = __bswap_16(buffer->model);
|
||||
m_version = __bswap_16(buffer->version);
|
||||
m_voltageRange = __bswap_16(buffer->voltageRange);
|
||||
m_CurrentRange = __bswap_16(buffer->CurrentRange) / 10;
|
||||
|
||||
if (m_version == 0)
|
||||
m_version = 0x0100;
|
||||
} else if (len == sizeof(measurementRaw)) {
|
||||
const auto* buffer = static_cast<const measurementRaw*>(data);
|
||||
if (buffer->len != sizeof(measurementRaw) - 1)
|
||||
return; // Error
|
||||
|
||||
m_voltage = static_cast<float>(__bswap_16(buffer->voltage)) / 100.F;
|
||||
m_current = static_cast<float>(__bswap_16(buffer->current)) / 100.F;
|
||||
m_power = static_cast<float>(__bswap_16(buffer->power));
|
||||
m_powerFactor = static_cast<float>(__bswap_16(buffer->powerFactor)) / 1000.F;
|
||||
m_frequency = static_cast<float>(__bswap_16(buffer->frequency)) / 100.F;
|
||||
|
||||
m_isNegative = (buffer->powerDirect != 0);
|
||||
m_positiveEnergy = static_cast<float>(__bswap_32(buffer->positiveEnergy)) / 3200.F;
|
||||
m_negativeEnergy = static_cast<float>(__bswap_32(buffer->negativeEnergy)) / 3200.F;
|
||||
|
||||
if (m_updateCallback)
|
||||
m_updateCallback();
|
||||
} else if (len == 2) {
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::milliseconds JSY_MK::getUpdatePeriod() const
|
||||
{
|
||||
return m_updatePeriod;
|
||||
}
|
||||
|
||||
void JSY_MK::setUpdatePeriod(std::chrono::milliseconds period)
|
||||
{
|
||||
m_updatePeriod = period;
|
||||
}
|
||||
|
||||
void JSY_MK::setBaudrate(baurate_t baudrate)
|
||||
{
|
||||
if (m_serialRTU.isDeviceBusy(DEVICE_ADDRESS))
|
||||
return;
|
||||
|
||||
std::array<uint8_t, 2> buffer = { DEVICE_ADDRESS, static_cast<uint8_t>(baudrate) };
|
||||
m_serialRTU.writeMultipleRegisters(DEVICE_ADDRESS, 0x0004, 1, buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
uint8_t JSY_MK::getAddress() const
|
||||
{
|
||||
return DEVICE_ADDRESS;
|
||||
}
|
||||
|
||||
std::string JSY_MK::getManufacturer() const
|
||||
{
|
||||
return "Shenzhen JianSiYan Technologies";
|
||||
}
|
||||
|
||||
size_t JSY_MK::getChannelNumber() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t JSY_MK::getModel() const
|
||||
{
|
||||
return m_model;
|
||||
}
|
||||
|
||||
std::string JSY_MK::getModelAsString() const
|
||||
{
|
||||
std::array<char, 20> buffer = {};
|
||||
int len = std::snprintf(buffer.data(), buffer.size(), "JSY-MK-%XT", m_model);
|
||||
return len > 0 ? std::string(buffer.data(), len) : std::string();
|
||||
}
|
||||
|
||||
size_t JSY_MK::getVersion() const
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
|
||||
std::string JSY_MK::getVersionAsString() const
|
||||
{
|
||||
std::array<char, 10> buffer = {};
|
||||
int len = std::snprintf(buffer.data(), buffer.size(), "%X.%X", (m_version >> 8) & 0x0F, (m_version >> 4) & 0x0F);
|
||||
return len > 0 ? std::string(buffer.data(), len) : std::string();
|
||||
}
|
||||
|
||||
size_t JSY_MK::getVoltageRange() const
|
||||
{
|
||||
return m_voltageRange;
|
||||
}
|
||||
|
||||
size_t JSY_MK::getCurrentRange() const
|
||||
{
|
||||
return m_CurrentRange;
|
||||
}
|
||||
|
||||
bool JSY_MK::isNegative() const
|
||||
{
|
||||
return m_isNegative;
|
||||
}
|
||||
|
||||
float JSY_MK::getVoltage() const
|
||||
{
|
||||
return m_voltage;
|
||||
}
|
||||
|
||||
float JSY_MK::getCurrent() const
|
||||
{
|
||||
return m_current;
|
||||
}
|
||||
|
||||
float JSY_MK::getPower() const
|
||||
{
|
||||
return m_power;
|
||||
}
|
||||
|
||||
float JSY_MK::getPowerFactor() const
|
||||
{
|
||||
return m_powerFactor;
|
||||
}
|
||||
|
||||
float JSY_MK::getFrequency() const
|
||||
{
|
||||
return m_frequency;
|
||||
}
|
||||
|
||||
float JSY_MK::getPositiveEnergy() const
|
||||
{
|
||||
return m_positiveEnergy;
|
||||
}
|
||||
|
||||
float JSY_MK::getNegativeEnergy() const
|
||||
{
|
||||
return m_negativeEnergy;
|
||||
}
|
||||
|
||||
void JSY_MK::resetEnergy()
|
||||
{
|
||||
if (m_serialRTU.isDeviceBusy(DEVICE_ADDRESS))
|
||||
return;
|
||||
|
||||
// Registers 0x000C, 0x000D
|
||||
// Data 00000000
|
||||
std::array<uint8_t, 4> buffer = {};
|
||||
m_serialRTU.writeMultipleRegisters(DEVICE_ADDRESS, 0x000C, 2, buffer.data(), buffer.size());
|
||||
}
|
||||
91
lib/JSY_MK/src/JSY_MK.h
Normal file
91
lib/JSY_MK/src/JSY_MK.h
Normal file
@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
#include "SerialModbusRTU.h"
|
||||
|
||||
// Jian Si Yan Metering module
|
||||
// www.jsypowermeter.com
|
||||
|
||||
class JSY_MK {
|
||||
public:
|
||||
enum class baurate_t : uint8_t {
|
||||
_1200 = 3, // 1200bps
|
||||
_2400 = 4, // 2400bps
|
||||
_4800 = 5, // 4800bps
|
||||
_9600 = 6, // 9600bps
|
||||
_19200 = 7, // 19200bps
|
||||
_38400 = 8 // 38400bps
|
||||
};
|
||||
|
||||
typedef std::function<void()> updatedCb;
|
||||
typedef std::function<void(SerialModbusRTU::ec)> errorCb;
|
||||
|
||||
JSY_MK(uint8_t uart_nr);
|
||||
~JSY_MK() = default;
|
||||
|
||||
void begin(uint32_t baud, uint32_t config = SERIAL_8N1, int8_t rxPin = -1, int8_t txPin = -1);
|
||||
void end();
|
||||
void loop();
|
||||
|
||||
bool isBusy() const;
|
||||
|
||||
void setUpdatedCallback(updatedCb cb);
|
||||
void setErrorCallback(errorCb cb);
|
||||
|
||||
std::chrono::milliseconds getUpdatePeriod() const;
|
||||
void setUpdatePeriod(std::chrono::milliseconds period);
|
||||
|
||||
void setBaudrate(baurate_t baudrate);
|
||||
|
||||
uint8_t getAddress() const;
|
||||
size_t getChannelNumber() const;
|
||||
|
||||
size_t getModel() const;
|
||||
std::string getModelAsString() const;
|
||||
std::string getManufacturer() const;
|
||||
|
||||
size_t getVersion() const;
|
||||
std::string getVersionAsString() const;
|
||||
|
||||
size_t getVoltageRange() const;
|
||||
size_t getCurrentRange() const;
|
||||
|
||||
bool isNegative() const;
|
||||
float getVoltage() const;
|
||||
float getCurrent() const;
|
||||
float getPower() const;
|
||||
float getPowerFactor() const;
|
||||
float getFrequency() const;
|
||||
|
||||
float getPositiveEnergy() const;
|
||||
float getNegativeEnergy() const;
|
||||
|
||||
void resetEnergy();
|
||||
|
||||
private:
|
||||
void responseHandler(
|
||||
SerialModbusRTU::ec exceptionCode,
|
||||
uint8_t deviceAddress,
|
||||
uint8_t functionCode,
|
||||
const void* data,
|
||||
size_t len);
|
||||
|
||||
private:
|
||||
SerialModbusRTU m_serialRTU;
|
||||
std::chrono::milliseconds m_updatePeriod;
|
||||
time_t m_lastUpdateTime;
|
||||
updatedCb m_updateCallback;
|
||||
errorCb m_errorCallback;
|
||||
|
||||
uint16_t m_model {};
|
||||
uint16_t m_version {};
|
||||
size_t m_voltageRange {};
|
||||
size_t m_CurrentRange {};
|
||||
|
||||
bool m_isNegative {};
|
||||
float m_voltage {};
|
||||
float m_current {};
|
||||
float m_power {};
|
||||
float m_powerFactor {};
|
||||
float m_frequency {};
|
||||
float m_positiveEnergy {};
|
||||
float m_negativeEnergy {};
|
||||
};
|
||||
13
lib/SerialModbusRTU/library.json
Normal file
13
lib/SerialModbusRTU/library.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "SerialModbusRTU",
|
||||
"keywords": "serial, modbus, RTU",
|
||||
"description": "Serial Modus RTU protocol",
|
||||
"authors": {
|
||||
"name": "Benoit Leforestier"
|
||||
},
|
||||
"version": "0.1.0",
|
||||
"frameworks": "arduino",
|
||||
"platforms": [
|
||||
"espressif32"
|
||||
]
|
||||
}
|
||||
248
lib/SerialModbusRTU/src/SerialModbusRTU.cpp
Normal file
248
lib/SerialModbusRTU/src/SerialModbusRTU.cpp
Normal file
@ -0,0 +1,248 @@
|
||||
#include "SerialModbusRTU.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
constexpr size_t MODBUS_ADU = 256;
|
||||
|
||||
// Function codes
|
||||
constexpr uint8_t FC_READ_COILS = 0x01; // Read Coils (Output) Status
|
||||
constexpr uint8_t FC_READ_INPUT_STAT = 0x02; // Not implemented. Read Input Status (Discrete Inputs)
|
||||
constexpr uint8_t FC_READ_REGS = 0x03; // Read Holding Registers
|
||||
constexpr uint8_t FC_READ_INPUT_REGS = 0x04; // Not implemented. Read Input Registers
|
||||
constexpr uint8_t FC_WRITE_COIL = 0x05; // Write Single Coil (Output)
|
||||
constexpr uint8_t FC_WRITE_REG = 0x06; // Not implemented. Preset Single Register
|
||||
constexpr uint8_t FC_DIAGNOSTICS = 0x08; // Not implemented. Diagnostics (Serial Line only)
|
||||
constexpr uint8_t FC_WRITE_COILS = 0x0F; // Not implemented. Write Multiple Coils (Outputs)
|
||||
constexpr uint8_t FC_WRITE_REGS = 0x10; // Write block of contiguous registers
|
||||
constexpr uint8_t FC_READ_FILE_REC = 0x14; // Not implemented. Read File Record
|
||||
constexpr uint8_t FC_WRITE_FILE_REC = 0x15; // Not implemented. Write File Record
|
||||
constexpr uint8_t FC_MASKWRITE_REG = 0x16; // Not implemented. Mask Write Register
|
||||
constexpr uint8_t FC_READWRITE_REGS = 0x17; // Not implemented. Read/Write Multiple registers
|
||||
|
||||
// Code from https://github.com/LacobusVentura/MODBUS-CRC16
|
||||
// Copyright (c) 2019 Tiago Ventura
|
||||
uint16_t computeModbusCRC(const uint8_t* buf, size_t len)
|
||||
{
|
||||
constexpr uint16_t table[256] = {
|
||||
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500,
|
||||
0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1,
|
||||
0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81,
|
||||
0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540,
|
||||
0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001,
|
||||
0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0,
|
||||
0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80,
|
||||
0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
|
||||
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700,
|
||||
0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0,
|
||||
0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480,
|
||||
0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41,
|
||||
0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01,
|
||||
0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1,
|
||||
0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181,
|
||||
0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
|
||||
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901,
|
||||
0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1,
|
||||
0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680,
|
||||
0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
|
||||
};
|
||||
|
||||
uint8_t xor8 = 0;
|
||||
uint16_t crc16 = 0xFFFF;
|
||||
|
||||
while (len--) {
|
||||
xor8 = (*buf++) ^ crc16;
|
||||
crc16 >>= 8;
|
||||
crc16 ^= table[xor8];
|
||||
}
|
||||
|
||||
return crc16;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> getTxBuffer(uint8_t address, uint8_t functionCode, size_t len)
|
||||
{
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.reserve(len + 2);
|
||||
|
||||
buffer.push_back(address);
|
||||
buffer.push_back(functionCode);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SerialModbusRTU::SerialModbusRTU(uint8_t uart_nr)
|
||||
: m_serial(uart_nr)
|
||||
, m_requestTimeout(100)
|
||||
, m_requestStartTime(0)
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t SerialModbusRTU::baudRate() const
|
||||
{
|
||||
return m_serial.baudRate();
|
||||
}
|
||||
|
||||
void SerialModbusRTU::begin(uint32_t baud, uint32_t config, int8_t rxPin, int8_t txPin)
|
||||
{
|
||||
m_serial.onReceive(std::bind(&SerialModbusRTU::onReceiveCb, this), true);
|
||||
m_serial.setRxTimeout(3);
|
||||
m_serial.setRxBufferSize(MODBUS_ADU);
|
||||
m_serial.setTxBufferSize(MODBUS_ADU);
|
||||
m_serial.begin(baud, config, rxPin, txPin);
|
||||
}
|
||||
|
||||
void SerialModbusRTU::end()
|
||||
{
|
||||
m_rxReady = false;
|
||||
m_requestStartTime = 0;
|
||||
m_serial.end();
|
||||
}
|
||||
|
||||
void SerialModbusRTU::setResponseCallback(cbResponse cb)
|
||||
{
|
||||
m_callback = cb;
|
||||
}
|
||||
|
||||
void SerialModbusRTU::setRequestTimeout(std::chrono::milliseconds timeout)
|
||||
{
|
||||
m_requestTimeout = timeout;
|
||||
}
|
||||
|
||||
bool SerialModbusRTU::setPins(int8_t rxPin, int8_t txPin, int8_t ctsPin, int8_t rtsPin)
|
||||
{
|
||||
return m_serial.setPins(rxPin, txPin, ctsPin, rtsPin);
|
||||
}
|
||||
|
||||
bool SerialModbusRTU::isDeviceBusy(uint8_t /*address*/) const
|
||||
{
|
||||
return (m_requestStartTime > 0);
|
||||
}
|
||||
|
||||
void SerialModbusRTU::readCoils(uint8_t address, uint16_t startCoil, uint16_t number)
|
||||
{
|
||||
std::vector<uint8_t> buffer = getTxBuffer(address, FC_READ_COILS, 4);
|
||||
|
||||
buffer.push_back(startCoil >> 8);
|
||||
buffer.push_back(startCoil & 0xFF);
|
||||
buffer.push_back(number >> 8);
|
||||
buffer.push_back(number & 0xFF);
|
||||
|
||||
sendQuery(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
void SerialModbusRTU::writeSingleCoil(uint8_t address, uint16_t coil, bool enabled)
|
||||
{
|
||||
std::vector<uint8_t> buffer = getTxBuffer(address, FC_WRITE_COIL, 4);
|
||||
|
||||
buffer.push_back(coil >> 8);
|
||||
buffer.push_back(coil & 0xFF);
|
||||
buffer.push_back(enabled ? 0xFF : 0x00);
|
||||
buffer.push_back(0x00);
|
||||
|
||||
sendQuery(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
void SerialModbusRTU::readHoldingRegisters(uint8_t address, uint16_t startReg, uint16_t number)
|
||||
{
|
||||
std::vector<uint8_t> buffer = getTxBuffer(address, FC_READ_REGS, 4);
|
||||
|
||||
buffer.push_back(startReg >> 8);
|
||||
buffer.push_back(startReg & 0xFF);
|
||||
buffer.push_back(number >> 8);
|
||||
buffer.push_back(number & 0xFF);
|
||||
|
||||
sendQuery(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
void SerialModbusRTU::writeMultipleRegisters(
|
||||
uint8_t address,
|
||||
uint16_t startReg,
|
||||
uint16_t number,
|
||||
const void* data,
|
||||
uint8_t len)
|
||||
{
|
||||
std::vector<uint8_t> buffer = getTxBuffer(address, FC_WRITE_REGS, len + 5);
|
||||
|
||||
buffer.push_back(startReg >> 8);
|
||||
buffer.push_back(startReg & 0xFF);
|
||||
buffer.push_back(number >> 8);
|
||||
buffer.push_back(number & 0xFF);
|
||||
buffer.push_back(len);
|
||||
buffer.insert(buffer.end(), reinterpret_cast<const uint8_t*>(data), reinterpret_cast<const uint8_t*>(data) + len);
|
||||
|
||||
sendQuery(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
void SerialModbusRTU::onReceiveCb()
|
||||
{
|
||||
m_rxReady = true;
|
||||
}
|
||||
|
||||
void SerialModbusRTU::sendQuery(const uint8_t* data, size_t len)
|
||||
{
|
||||
if (m_requestStartTime > 0) {
|
||||
// Device busy
|
||||
if (m_callback)
|
||||
m_callback(ec::deviceBusy, data[0], data[1], nullptr, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t crc16 = computeModbusCRC(data, len);
|
||||
|
||||
m_serial.write(data, len);
|
||||
m_serial.write(reinterpret_cast<const uint8_t*>(&crc16), sizeof(crc16));
|
||||
|
||||
m_requestStartTime = millis();
|
||||
}
|
||||
|
||||
void SerialModbusRTU::loop()
|
||||
{
|
||||
if (!m_rxReady.exchange(false)) {
|
||||
if (m_requestStartTime > 0 && (millis() - m_requestStartTime) > m_requestTimeout.count()) {
|
||||
m_requestStartTime = 0;
|
||||
if (m_callback)
|
||||
m_callback(ec::requestTimeOut, 0, 0, nullptr, 0);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_requestStartTime = 0;
|
||||
|
||||
// Read RX buffer
|
||||
int rxLen = m_serial.available();
|
||||
|
||||
std::vector<uint8_t> rxBuffer(rxLen);
|
||||
size_t readLen = m_serial.read(rxBuffer.data(), rxBuffer.size());
|
||||
|
||||
if (readLen != rxBuffer.size() || readLen < 5) {
|
||||
if (m_callback)
|
||||
m_callback(ec::invalidFrameSize, 0, 0, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify CRC
|
||||
uint16_t crc16 = rxBuffer[rxBuffer.size() - 1] << 8;
|
||||
crc16 |= rxBuffer[rxBuffer.size() - 2];
|
||||
|
||||
if (crc16 != computeModbusCRC(rxBuffer.data(), rxBuffer.size() - 2)) {
|
||||
if (m_callback)
|
||||
m_callback(ec::invalidCRC, 0, 0, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t address = rxBuffer[0];
|
||||
uint8_t functionCode = rxBuffer[1] & 0x7F;
|
||||
|
||||
// Check error response
|
||||
if ((rxBuffer[1] & 0x80) == 0x80) {
|
||||
if (m_callback)
|
||||
m_callback(static_cast<ec>(rxBuffer[2]), address, functionCode, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_callback)
|
||||
m_callback(ec::noError, address, functionCode, rxBuffer.data() + 2, rxBuffer.size() - 4);
|
||||
}
|
||||
69
lib/SerialModbusRTU/src/SerialModbusRTU.h
Normal file
69
lib/SerialModbusRTU/src/SerialModbusRTU.h
Normal file
@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
|
||||
#include <HardwareSerial.h>
|
||||
|
||||
class SerialModbusRTU
|
||||
{
|
||||
public:
|
||||
enum class ec : uint8_t
|
||||
{
|
||||
noError = 0x00,
|
||||
// MODBUS errors
|
||||
illegalFunction = 0x01,
|
||||
illegalDataAddress = 0x02,
|
||||
illegalDataValue = 0x03,
|
||||
serverDeviceFailure = 0x04,
|
||||
acknowledge = 0x05,
|
||||
serverDeviceBusy = 0x06,
|
||||
memoryParityError = 0x08,
|
||||
gatewayPathUnavailable = 0x0A,
|
||||
gatewayTargetDeviceFailed = 0x0B,
|
||||
// Class errors
|
||||
invalidFrameSize = 0x20,
|
||||
invalidCRC = 0x21,
|
||||
requestTimeOut = 0x22,
|
||||
deviceBusy = 0x23
|
||||
};
|
||||
|
||||
typedef std::function<
|
||||
void(ec exceptionCode, uint8_t deviceAddress, uint8_t functionCode, const void* data, size_t len)>
|
||||
cbResponse;
|
||||
|
||||
SerialModbusRTU(uint8_t uart_nr);
|
||||
~SerialModbusRTU() = default;
|
||||
|
||||
uint32_t baudRate() const;
|
||||
|
||||
void begin(uint32_t baud, uint32_t config = SERIAL_8N1, int8_t rxPin = -1, int8_t txPin = -1);
|
||||
void end();
|
||||
void loop();
|
||||
|
||||
// setResponseCallback and setRequestTimeout shall be called before begin()
|
||||
void setResponseCallback(cbResponse cb);
|
||||
void setRequestTimeout(std::chrono::milliseconds timeout);
|
||||
|
||||
// SetPins shall be called after begin()
|
||||
bool setPins(int8_t rxPin, int8_t txPin, int8_t ctsPin = -1, int8_t rtsPin = -1);
|
||||
|
||||
[[nodiscard]] bool isDeviceBusy(uint8_t address) const;
|
||||
|
||||
void readCoils(uint8_t address, uint16_t startCoil, uint16_t number);
|
||||
void writeSingleCoil(uint8_t address, uint16_t coil, bool enabled);
|
||||
|
||||
void readHoldingRegisters(uint8_t address, uint16_t startReg, uint16_t number);
|
||||
void writeMultipleRegisters(uint8_t address, uint16_t startReg, uint16_t number, const void* data, uint8_t len);
|
||||
|
||||
private:
|
||||
void onReceiveCb();
|
||||
void sendQuery(const uint8_t* data, size_t len);
|
||||
|
||||
private:
|
||||
mutable HardwareSerial m_serial;
|
||||
std::atomic<bool> m_rxReady;
|
||||
cbResponse m_callback;
|
||||
std::chrono::milliseconds m_requestTimeout;
|
||||
time_t m_requestStartTime;
|
||||
};
|
||||
@ -37,11 +37,11 @@ build_unflags =
|
||||
-std=gnu++11
|
||||
|
||||
lib_deps =
|
||||
mathieucarbou/ESP Async WebServer @ 2.9.0
|
||||
mathieucarbou/ESP Async WebServer @ 2.9.3
|
||||
bblanchon/ArduinoJson @ ^7.0.4
|
||||
https://github.com/bertmelis/espMqttClient.git#v1.6.0
|
||||
nrf24/RF24 @ ^1.4.8
|
||||
olikraus/U8g2 @ ^2.35.15
|
||||
olikraus/U8g2 @ ^2.35.17
|
||||
buelowp/sunset @ ^1.1.7
|
||||
https://github.com/arkhipenko/TaskScheduler#testing
|
||||
|
||||
@ -123,6 +123,15 @@ build_flags = ${env.build_flags}
|
||||
-DHOYMILES_PIN_CE=4
|
||||
-DHOYMILES_PIN_CS=5
|
||||
|
||||
[env:generic_cmt]
|
||||
board = esp32dev
|
||||
build_flags = ${env.build_flags}
|
||||
-DCMT_CLK=18
|
||||
-DCMT_CS=4
|
||||
-DCMT_FCS=5
|
||||
-DCMT_SDIO=23
|
||||
-DCMT_GPIO2=19
|
||||
-DCMT_GPIO3=14
|
||||
|
||||
[env:olimex_esp32_poe]
|
||||
; https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware
|
||||
|
||||
@ -32,3 +32,15 @@
|
||||
; -DHOYMILES_PIN_CS=6
|
||||
;monitor_port = /dev/ttyACM0
|
||||
;upload_port = /dev/ttyACM0
|
||||
|
||||
[env:esp32_cmt_modbus]
|
||||
board = esp32dev
|
||||
build_flags = ${env.build_flags}
|
||||
-DCMT_CLK=18
|
||||
-DCMT_CS=4
|
||||
-DCMT_FCS=5
|
||||
-DCMT_SDIO=23
|
||||
-DCMT_GPIO2=19
|
||||
-DCMT_GPIO3=14
|
||||
-DSERIAL_MODBUS_TX=17
|
||||
-DSERIAL_MODBUS_RX=16
|
||||
|
||||
@ -139,6 +139,18 @@ bool ConfigurationClass::write()
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject serialModbus = doc["serial_modbus"].to<JsonObject>();
|
||||
serialModbus["baudrate"] = config.SerialModbus.BaudRate;
|
||||
serialModbus["poll_interval"] = config.SerialModbus.PollInterval;
|
||||
|
||||
JsonObject powerMeter = doc["power_meter"].to<JsonObject>();
|
||||
JsonArray channel = powerMeter["channel"].to<JsonArray>();
|
||||
for (uint8_t c = 0; c < PWRMTR_MAX_CHAN_COUNT; c++) {
|
||||
JsonObject chanData = channel.add<JsonObject>();
|
||||
chanData["invert_direction"] = config.PowerMeter.channel[c].InvertDirection;
|
||||
chanData["negative_power"] = config.PowerMeter.channel[c].NegativePower;
|
||||
}
|
||||
|
||||
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
|
||||
return false;
|
||||
}
|
||||
@ -312,6 +324,17 @@ bool ConfigurationClass::read()
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject serialModbus = doc["serial_modbus"];
|
||||
config.SerialModbus.BaudRate = serialModbus["baudrate"] | SERIAL_MODBUS_BAUDRATE;
|
||||
config.SerialModbus.PollInterval = serialModbus["poll_interval"] | SERIAL_MODBUS_POLL_INTERVAL;
|
||||
|
||||
JsonObject powerMeter = doc["power_meter"];
|
||||
JsonArray channel = powerMeter["channel"];
|
||||
for (uint8_t c = 0; c < PWRMTR_MAX_CHAN_COUNT; c++) {
|
||||
config.PowerMeter.channel[c].InvertDirection = channel[c]["invert_direction"] | false;
|
||||
config.PowerMeter.channel[c].NegativePower = channel[c]["negative_power"] | false;
|
||||
}
|
||||
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
*/
|
||||
#include "Display_Graphic.h"
|
||||
#include "Datastore.h"
|
||||
#include "JsyMk.h"
|
||||
#include <NetworkSettings.h>
|
||||
#include <map>
|
||||
#include <time.h>
|
||||
@ -41,6 +42,15 @@ static const char* const i18n_yield_total_mwh[] = { "total: %.0f kWh", "Ges.: %.
|
||||
|
||||
static const char* const i18n_date_format[] = { "%m/%d/%Y %H:%M", "%d.%m.%Y %H:%M", "%d/%m/%Y %H:%M" };
|
||||
|
||||
static const char* const i18n_powermeter_power_w[] = { "%c %.0f W", "%c %.0f W", "%c %.0f W" };
|
||||
static const char* const i18n_powermeter_power_kw[] = { "%c %.1f kW", "%c %.1f kW", "%c %.1f kW" };
|
||||
|
||||
static const char* const i18n_pm_positive_today_wh[] = { "In: %4.0f Wh", "In: %4.0f Wh", "In: %4.0f Wh" };
|
||||
static const char* const i18n_pm_positive_today_kwh[] = { "In: %.1f kWh", "In: %.1f kWh", "In: %.1f kWh" };
|
||||
|
||||
static const char* const i18n_pm_negative_today_wh[] = { "Out: %4.0f Wh", "Out: %4.0f Wh", "Out: %4.0f Wh" };
|
||||
static const char* const i18n_pm_negative_today_kwh[] = { "Out: %.1f kWh", "Out: %.1f kWh", "Out: %.1f kWh" };
|
||||
|
||||
DisplayGraphicClass::DisplayGraphicClass()
|
||||
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&DisplayGraphicClass::loop, this))
|
||||
{
|
||||
@ -202,8 +212,21 @@ void DisplayGraphicClass::loop()
|
||||
bool displayPowerSave = false;
|
||||
bool showText = true;
|
||||
|
||||
bool displayPowerMeter = _mExtra % (10 * 2) < 10 && JsyMk.isInitialised(); // Every 10 seconds, swap screen
|
||||
|
||||
if (displayPowerMeter) {
|
||||
const float watts = std::fabs(JsyMk.getFieldValue(0, JsyMkClass::Field_t::POWER));
|
||||
const char direction = (JsyMk.getFieldValue(0, JsyMkClass::Field_t::NEGATIVE) > 0) ? 'O' : 'I';
|
||||
|
||||
if (watts > 999) {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_powermeter_power_kw[_display_language], direction, watts / 1000);
|
||||
} else {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_powermeter_power_w[_display_language], direction, watts);
|
||||
}
|
||||
printText(_fmtText, 0);
|
||||
}
|
||||
//=====> Actual Production ==========
|
||||
if (Datastore.getIsAtLeastOneReachable()) {
|
||||
else if (Datastore.getIsAtLeastOneReachable()) {
|
||||
displayPowerSave = false;
|
||||
if (_isLarge) {
|
||||
uint8_t screenSaverOffsetX = enableScreensaver ? (_mExtra % 7) : 0;
|
||||
@ -246,20 +269,40 @@ void DisplayGraphicClass::loop()
|
||||
//<=======================
|
||||
|
||||
if (showText) {
|
||||
// Daily production
|
||||
float wattsToday = Datastore.getTotalAcYieldDayEnabled();
|
||||
if (wattsToday >= 10000) {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_kwh[_display_language], wattsToday / 1000);
|
||||
} else {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], wattsToday);
|
||||
}
|
||||
printText(_fmtText, 1);
|
||||
if (displayPowerMeter) {
|
||||
// Daily Input
|
||||
float wattsInput = JsyMk.getFieldValue(0, JsyMkClass::Field_t::TODAY_POSITIVE_ENERGY);
|
||||
if (wattsInput > 999) {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_pm_positive_today_kwh[_display_language], wattsInput / 1000);
|
||||
} else {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_pm_positive_today_wh[_display_language], wattsInput);
|
||||
}
|
||||
printText(_fmtText, 1);
|
||||
|
||||
// Total production
|
||||
const float wattsTotal = Datastore.getTotalAcYieldTotalEnabled();
|
||||
auto const format = (wattsTotal >= 1000) ? i18n_yield_total_mwh : i18n_yield_total_kwh;
|
||||
snprintf(_fmtText, sizeof(_fmtText), format[_display_language], wattsTotal);
|
||||
printText(_fmtText, 2);
|
||||
// Daily Output
|
||||
float wattsOutput = JsyMk.getFieldValue(0, JsyMkClass::Field_t::TODAY_POSITIVE_ENERGY);
|
||||
if (wattsOutput > 999) {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_pm_negative_today_kwh[_display_language], wattsOutput / 1000);
|
||||
} else {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_pm_negative_today_wh[_display_language], wattsOutput);
|
||||
}
|
||||
printText(_fmtText, 2);
|
||||
} else {
|
||||
// Daily production
|
||||
float wattsToday = Datastore.getTotalAcYieldDayEnabled();
|
||||
if (wattsToday >= 10000) {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_kwh[_display_language], wattsToday / 1000);
|
||||
} else {
|
||||
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], wattsToday);
|
||||
}
|
||||
printText(_fmtText, 1);
|
||||
|
||||
// Total production
|
||||
const float wattsTotal = Datastore.getTotalAcYieldTotalEnabled();
|
||||
auto const format = (wattsTotal >= 1000) ? i18n_yield_total_mwh : i18n_yield_total_kwh;
|
||||
snprintf(_fmtText, sizeof(_fmtText), format[_display_language], wattsTotal);
|
||||
printText(_fmtText, 2);
|
||||
}
|
||||
|
||||
//=====> IP or Date-Time ========
|
||||
// Change every 3 seconds
|
||||
|
||||
247
src/JsyMk.cpp
Normal file
247
src/JsyMk.cpp
Normal file
@ -0,0 +1,247 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "JsyMk.h"
|
||||
#include "Configuration.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "PinMapping.h"
|
||||
|
||||
JsyMkClass JsyMk;
|
||||
|
||||
namespace {
|
||||
// HA status classes
|
||||
constexpr std::string_view scMeasurement("measurement");
|
||||
constexpr std::string_view scTotalIncreasing("total_increasing");
|
||||
|
||||
// Name, unit, digits, HA device class, HA status class
|
||||
constexpr std::array<std::tuple<std::string_view, std::string_view, size_t, std::string_view, std::string_view>, 16> fieldInfos = { //
|
||||
{ { "Address", {}, {}, {}, {} },
|
||||
{ "Manufacturer", {}, {}, {}, {} },
|
||||
{ "Model", {}, {}, {}, {} },
|
||||
{ "Version", {}, {}, {}, {} },
|
||||
{ "Voltage Range", "V", 0, "voltage", scMeasurement },
|
||||
{ "Current Range", "A", 0, "current", scMeasurement },
|
||||
{ "Voltage", "V", 2, "voltage", scMeasurement },
|
||||
{ "Current", "A", 2, "current", scMeasurement },
|
||||
{ "Power", "W", 0, "power", scMeasurement },
|
||||
{ "Power Factor", "%", 2, "power_factor", scMeasurement },
|
||||
{ "Frequency", "Hz", 2, "frequency", scMeasurement },
|
||||
{ "Negative", {}, {}, {}, scMeasurement },
|
||||
{ "Positive Energy", "kWh", 2, "energy", scTotalIncreasing },
|
||||
{ "Negative Energy", "kWh", 2, "energy", scTotalIncreasing },
|
||||
{ "Today Positive Energy", "kWh", 2, "energy", scTotalIncreasing },
|
||||
{ "Today Negative Energy", "kWh", 2, "energy", scTotalIncreasing } }
|
||||
};
|
||||
}
|
||||
JsyMkClass::JsyMkClass()
|
||||
: _jsymk(1)
|
||||
, _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&JsyMkClass::loop, this))
|
||||
{
|
||||
}
|
||||
|
||||
void JsyMkClass::init(Scheduler& scheduler)
|
||||
{
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
const PinMapping_t& pin = PinMapping.get();
|
||||
|
||||
if (PinMapping.isValidSerialModbusConfig()) {
|
||||
// Initialize inverter communication
|
||||
_jsymk.setUpdatedCallback(
|
||||
[&] {
|
||||
_lastUpdate = millis();
|
||||
});
|
||||
_jsymk.setErrorCallback([&](SerialModbusRTU::ec error_code) {
|
||||
MessageOutput.printf("JSY-MK error %d\n", static_cast<uint8_t>(error_code));
|
||||
});
|
||||
|
||||
_jsymk.setUpdatePeriod(std::chrono::seconds(config.SerialModbus.PollInterval));
|
||||
_jsymk.begin(config.SerialModbus.BaudRate, SERIAL_8N1, pin.serial_modbus_rx, pin.serial_modbus_tx);
|
||||
|
||||
scheduler.addTask(_loopTask);
|
||||
_loopTask.enable();
|
||||
}
|
||||
}
|
||||
|
||||
void JsyMkClass::loop()
|
||||
{
|
||||
_jsymk.loop();
|
||||
|
||||
if (!_initialised && _jsymk.getModel() != 0) {
|
||||
_initialised = true;
|
||||
|
||||
MessageOutput.println("JSY-MK-xxxT Initialized");
|
||||
MessageOutput.printf("Model: %s %s\n", _jsymk.getModelAsString().c_str(), _jsymk.getVersionAsString().c_str());
|
||||
MessageOutput.printf("Ranges: %dV %dA\n", _jsymk.getVoltageRange(), _jsymk.getCurrentRange());
|
||||
} else {
|
||||
// Check current time
|
||||
time_t now = time(nullptr);
|
||||
const auto* lt = localtime(&now);
|
||||
|
||||
if (lt->tm_hour == 0 && lt->tm_min == 0 && lt->tm_sec <= Configuration.get().SerialModbus.PollInterval) {
|
||||
_todayPositiveRef = 0;
|
||||
_todayNegativeRef = 0;
|
||||
}
|
||||
|
||||
if (_todayPositiveRef == 0) {
|
||||
_todayPositiveRef = _jsymk.getPositiveEnergy();
|
||||
}
|
||||
if (_todayNegativeRef == 0) {
|
||||
_todayNegativeRef = _jsymk.getNegativeEnergy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool JsyMkClass::isInitialised() const
|
||||
{
|
||||
return _initialised;
|
||||
}
|
||||
|
||||
uint32_t JsyMkClass::getLastUpdate() const
|
||||
{
|
||||
return _lastUpdate;
|
||||
}
|
||||
|
||||
uint32_t JsyMkClass::getPollInterval() const
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(_jsymk.getUpdatePeriod()).count();
|
||||
}
|
||||
|
||||
void JsyMkClass::setPollInterval(const uint32_t interval)
|
||||
{
|
||||
_jsymk.setUpdatePeriod(std::chrono::seconds(interval));
|
||||
}
|
||||
|
||||
size_t JsyMkClass::getChannelNumber() const
|
||||
{
|
||||
return _jsymk.getChannelNumber();
|
||||
}
|
||||
|
||||
String JsyMkClass::getFieldName(size_t /*channel*/, Field_t fieldId) const
|
||||
{
|
||||
if (static_cast<size_t>(fieldId) >= fieldInfos.size())
|
||||
return {};
|
||||
|
||||
const auto& fieldName = std::get<0>(fieldInfos[static_cast<size_t>(fieldId)]);
|
||||
return String(fieldName.data(), fieldName.size());
|
||||
}
|
||||
|
||||
String JsyMkClass::getFieldString(size_t channel, Field_t fieldId) const
|
||||
{
|
||||
if (static_cast<size_t>(fieldId) >= fieldInfos.size())
|
||||
return {};
|
||||
|
||||
switch (fieldId) {
|
||||
case Field_t::ADDRESS:
|
||||
return String(_jsymk.getAddress());
|
||||
|
||||
case Field_t::MANUFACTURER:
|
||||
return String(_jsymk.getManufacturer().c_str());
|
||||
|
||||
case Field_t::MODEL:
|
||||
return String(_jsymk.getModelAsString().c_str());
|
||||
|
||||
case Field_t::VERSION:
|
||||
return String(_jsymk.getVersionAsString().c_str());
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return String(getFieldValue(channel, fieldId), getFieldDigits(fieldId));
|
||||
}
|
||||
|
||||
float JsyMkClass::getFieldValue(size_t channel, Field_t fieldId) const
|
||||
{
|
||||
if (static_cast<size_t>(fieldId) >= fieldInfos.size())
|
||||
return 0;
|
||||
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
auto isLogicalNegative = [&]() -> bool {
|
||||
return config.PowerMeter.channel[channel].InvertDirection ? !_jsymk.isNegative() : _jsymk.isNegative();
|
||||
};
|
||||
|
||||
switch (fieldId) {
|
||||
case Field_t::VOLTAGE_RANGE:
|
||||
return static_cast<float>(_jsymk.getVoltageRange());
|
||||
|
||||
case Field_t::CURRENT_RANGE:
|
||||
return static_cast<float>(_jsymk.getCurrentRange());
|
||||
|
||||
case Field_t::VOLTAGE:
|
||||
return _jsymk.getVoltage();
|
||||
|
||||
case Field_t::CURRENT:
|
||||
return _jsymk.getCurrent();
|
||||
|
||||
case Field_t::POWER:
|
||||
if (config.PowerMeter.channel[channel].NegativePower) {
|
||||
return (isLogicalNegative() ? -_jsymk.getPower() : _jsymk.getPower());
|
||||
}
|
||||
return _jsymk.getPower();
|
||||
|
||||
case Field_t::POWER_FACTOR:
|
||||
return _jsymk.getPowerFactor();
|
||||
|
||||
case Field_t::FREQUENCY:
|
||||
return _jsymk.getFrequency();
|
||||
|
||||
case Field_t::NEGATIVE:
|
||||
return isLogicalNegative() ? 1 : 0;
|
||||
|
||||
case Field_t::TOTAL_POSITIVE_ENERGY:
|
||||
return (config.PowerMeter.channel[channel].InvertDirection ? _jsymk.getNegativeEnergy() : _jsymk.getPositiveEnergy());
|
||||
|
||||
case Field_t::TOTAL_NEGATIVE_ENERGY:
|
||||
return (config.PowerMeter.channel[channel].InvertDirection ? _jsymk.getPositiveEnergy() : _jsymk.getNegativeEnergy());
|
||||
|
||||
case Field_t::TODAY_POSITIVE_ENERGY:
|
||||
return (config.PowerMeter.channel[channel].InvertDirection ? _jsymk.getNegativeEnergy() - _todayNegativeRef : _jsymk.getPositiveEnergy() - _todayPositiveRef);
|
||||
|
||||
case Field_t::TODAY_NEGATIVE_ENERGY:
|
||||
return (config.PowerMeter.channel[channel].InvertDirection ? _jsymk.getPositiveEnergy() - _todayPositiveRef : _jsymk.getNegativeEnergy() - _todayNegativeRef);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
String JsyMkClass::getFieldUnit(Field_t fieldId) const
|
||||
{
|
||||
if (static_cast<size_t>(fieldId) >= fieldInfos.size())
|
||||
return {};
|
||||
|
||||
const auto& fielUnit = std::get<1>(fieldInfos[static_cast<size_t>(fieldId)]);
|
||||
return String(fielUnit.data(), fielUnit.size());
|
||||
}
|
||||
|
||||
size_t JsyMkClass::getFieldDigits(Field_t fieldId) const
|
||||
{
|
||||
if (static_cast<size_t>(fieldId) >= fieldInfos.size())
|
||||
return {};
|
||||
|
||||
return std::get<2>(fieldInfos[static_cast<size_t>(fieldId)]);
|
||||
}
|
||||
|
||||
String JsyMkClass::getFieldDeviceClass(Field_t fieldId) const
|
||||
{
|
||||
if (static_cast<size_t>(fieldId) >= fieldInfos.size())
|
||||
return {};
|
||||
|
||||
const auto& fieldDeviceClass = std::get<3>(fieldInfos[static_cast<size_t>(fieldId)]);
|
||||
return String(fieldDeviceClass.data(), fieldDeviceClass.size());
|
||||
}
|
||||
|
||||
String JsyMkClass::getFieldStatusClass(Field_t fieldId) const
|
||||
{
|
||||
if (static_cast<size_t>(fieldId) >= fieldInfos.size())
|
||||
return {};
|
||||
|
||||
const auto& fieldStatusClass = std::get<4>(fieldInfos[static_cast<size_t>(fieldId)]);
|
||||
return String(fieldStatusClass.data(), fieldStatusClass.size());
|
||||
}
|
||||
|
||||
void JsyMkClass::reset()
|
||||
{
|
||||
_jsymk.resetEnergy();
|
||||
}
|
||||
@ -96,6 +96,27 @@ void MqttHandleHassClass::publishConfig()
|
||||
|
||||
yield();
|
||||
}
|
||||
|
||||
if (!JsyMk.isInitialised())
|
||||
return;
|
||||
|
||||
// Loop all power meter channels
|
||||
// publishDtuButton("Reset energy", "", "config", "reset", "cmd/power_meter_reset", "1");
|
||||
for (size_t i = 0; i < JsyMk.getChannelNumber(); ++i) {
|
||||
for (auto field : {
|
||||
JsyMkClass::Field_t::VOLTAGE,
|
||||
JsyMkClass::Field_t::CURRENT,
|
||||
JsyMkClass::Field_t::POWER,
|
||||
JsyMkClass::Field_t::POWER_FACTOR,
|
||||
JsyMkClass::Field_t::FREQUENCY,
|
||||
JsyMkClass::Field_t::NEGATIVE,
|
||||
JsyMkClass::Field_t::TOTAL_POSITIVE_ENERGY,
|
||||
JsyMkClass::Field_t::TOTAL_NEGATIVE_ENERGY }) {
|
||||
publishPowerMeterField(i, field);
|
||||
}
|
||||
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
void MqttHandleHassClass::publishInverterField(std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear)
|
||||
@ -371,6 +392,75 @@ void MqttHandleHassClass::publishDtuBinarySensor(const char* name, const char* d
|
||||
publish(configTopic, buffer);
|
||||
}
|
||||
|
||||
void MqttHandleHassClass::publishPowerMeterField(size_t channel, JsyMkClass::Field_t fieldId, const bool clear)
|
||||
{
|
||||
String modelUID = JsyMk.getFieldString(channel, JsyMkClass::Field_t::MODEL) + "-" + String(channel);
|
||||
String name = JsyMk.getFieldName(channel, fieldId);
|
||||
|
||||
String sensorId = name;
|
||||
sensorId.replace(" ", "_");
|
||||
sensorId.toLowerCase();
|
||||
|
||||
String configTopic = "sensor/dtu_" + modelUID
|
||||
+ "/" + sensorId
|
||||
+ "/config";
|
||||
|
||||
if (!clear) {
|
||||
JsonDocument root;
|
||||
createPowerMeterInfo(root, channel, fieldId);
|
||||
|
||||
root["name"] = name;
|
||||
root["stat_t"] = MqttSettings.getPrefix() + MqttHandlePowerMeterClass::getTopic(channel, fieldId);
|
||||
root["uniq_id"] = modelUID + "_" + sensorId;
|
||||
|
||||
String unit_of_measure = JsyMk.getFieldUnit(fieldId);
|
||||
if (!unit_of_measure.isEmpty()) {
|
||||
root["unit_of_meas"] = unit_of_measure;
|
||||
}
|
||||
|
||||
if (Configuration.get().Mqtt.Hass.Expire) {
|
||||
root["exp_aft"] = std::max<uint32_t>(JsyMk.getPollInterval(), Configuration.get().Mqtt.PublishInterval) * 2;
|
||||
}
|
||||
|
||||
String deviceClass = JsyMk.getFieldDeviceClass(fieldId);
|
||||
if (!deviceClass.isEmpty()) {
|
||||
root["dev_cla"] = deviceClass;
|
||||
}
|
||||
|
||||
String statusClass = JsyMk.getFieldStatusClass(fieldId);
|
||||
if (!statusClass.isEmpty()) {
|
||||
root["stat_cla"] = statusClass;
|
||||
}
|
||||
|
||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String buffer;
|
||||
serializeJson(root, buffer);
|
||||
publish(configTopic, buffer);
|
||||
} else {
|
||||
publish(configTopic, {});
|
||||
}
|
||||
}
|
||||
|
||||
void MqttHandleHassClass::createPowerMeterInfo(JsonDocument& root, size_t channel, JsyMkClass::Field_t fieldId)
|
||||
{
|
||||
auto object = root["dev"].to<JsonObject>();
|
||||
|
||||
String swVersion = JsyMk.getFieldString(channel, JsyMkClass::Field_t::VOLTAGE_RANGE) + JsyMk.getFieldUnit(JsyMkClass::Field_t::VOLTAGE_RANGE)
|
||||
+ " " + JsyMk.getFieldString(channel, JsyMkClass::Field_t::CURRENT_RANGE) + JsyMk.getFieldUnit(JsyMkClass::Field_t::CURRENT_RANGE);
|
||||
|
||||
object["name"] = JsyMk.getFieldString(channel, JsyMkClass::Field_t::MODEL) + " Channel " + String(channel);
|
||||
object["identifiers"] = JsyMk.getFieldString(channel, JsyMkClass::Field_t::MODEL) + "-" + String(channel);
|
||||
object["configuration_url"] = getDtuUrl();
|
||||
object["manufacturer"] = JsyMk.getFieldString(channel, JsyMkClass::Field_t::MANUFACTURER);
|
||||
object["model"] = JsyMk.getFieldString(channel, JsyMkClass::Field_t::MODEL);
|
||||
object["sw_version"] = swVersion;
|
||||
object["hw_version"] = JsyMk.getFieldString(channel, JsyMkClass::Field_t::VERSION);
|
||||
object["via_device"] = getDtuUniqueId();
|
||||
}
|
||||
|
||||
void MqttHandleHassClass::createInverterInfo(JsonDocument& root, std::shared_ptr<InverterAbstract> inv)
|
||||
{
|
||||
createDeviceInfo(
|
||||
|
||||
121
src/MqttHandlePowerMeter.cpp
Normal file
121
src/MqttHandlePowerMeter.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "MqttHandlePowerMeter.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "MqttSettings.h"
|
||||
|
||||
#define TOPIC_SUB_RESET "power_meter_reset"
|
||||
|
||||
MqttHandlePowerMeterClass MqttHandlePowerMeter;
|
||||
|
||||
namespace {
|
||||
}
|
||||
|
||||
MqttHandlePowerMeterClass::MqttHandlePowerMeterClass()
|
||||
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&MqttHandlePowerMeterClass::loop, this))
|
||||
{
|
||||
}
|
||||
|
||||
void MqttHandlePowerMeterClass::init(Scheduler& scheduler)
|
||||
{
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
using std::placeholders::_3;
|
||||
using std::placeholders::_4;
|
||||
using std::placeholders::_5;
|
||||
using std::placeholders::_6;
|
||||
|
||||
String topic = MqttSettings.getPrefix();
|
||||
topic.concat("+/cmd/" TOPIC_SUB_RESET);
|
||||
MqttSettings.subscribe(topic, 0, std::bind(&MqttHandlePowerMeterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
|
||||
scheduler.addTask(_loopTask);
|
||||
_loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND);
|
||||
_loopTask.enable();
|
||||
}
|
||||
|
||||
void MqttHandlePowerMeterClass::loop()
|
||||
{
|
||||
_loopTask.setInterval(Configuration.get().Mqtt.PublishInterval * TASK_SECOND);
|
||||
|
||||
if (!MqttSettings.getConnected() || !JsyMk.isInitialised() || JsyMk.getLastUpdate() == _lastUpdate) {
|
||||
_loopTask.forceNextIteration();
|
||||
return;
|
||||
}
|
||||
|
||||
_lastUpdate = JsyMk.getLastUpdate();
|
||||
|
||||
// Loop all channels
|
||||
for (size_t i = 0; i < JsyMk.getChannelNumber(); ++i) {
|
||||
publishField(i, Field_t::VOLTAGE);
|
||||
publishField(i, Field_t::CURRENT);
|
||||
publishField(i, Field_t::POWER);
|
||||
publishField(i, Field_t::POWER_FACTOR);
|
||||
publishField(i, Field_t::FREQUENCY);
|
||||
publishField(i, Field_t::NEGATIVE);
|
||||
publishField(i, Field_t::TOTAL_POSITIVE_ENERGY);
|
||||
publishField(i, Field_t::TOTAL_NEGATIVE_ENERGY);
|
||||
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
void MqttHandlePowerMeterClass::publishField(size_t channel, const Field_t fieldId)
|
||||
{
|
||||
const String topic = getTopic(channel, fieldId);
|
||||
if (topic.isEmpty())
|
||||
return;
|
||||
|
||||
MqttSettings.publish(topic, JsyMk.getFieldString(channel, fieldId));
|
||||
}
|
||||
|
||||
String MqttHandlePowerMeterClass::getTopic(size_t channel, const Field_t fieldId)
|
||||
{
|
||||
String model = JsyMk.getFieldString(channel, Field_t::MODEL);
|
||||
String name = JsyMk.getFieldName(channel, fieldId);
|
||||
|
||||
if (model.isEmpty() || name.isEmpty())
|
||||
return {};
|
||||
|
||||
String sensorId = name;
|
||||
sensorId.replace(" ", "_");
|
||||
sensorId.toLowerCase();
|
||||
|
||||
return model + "/" + String(channel) + "/" + sensorId;
|
||||
}
|
||||
|
||||
void MqttHandlePowerMeterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* /*payload*/, const size_t /*len*/, const size_t /*index*/, const size_t /*total*/)
|
||||
{
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
std::string_view tokenTopic = topic;
|
||||
tokenTopic.remove_prefix(strlen(config.Mqtt.Topic));
|
||||
|
||||
auto findNextToken = [&]() -> std::string_view {
|
||||
std::string_view result;
|
||||
|
||||
size_t pos = tokenTopic.find('/');
|
||||
if (pos == std::string_view::npos) {
|
||||
result = tokenTopic;
|
||||
tokenTopic = {};
|
||||
} else {
|
||||
result = tokenTopic.substr(pos);
|
||||
tokenTopic.remove_prefix(pos + 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
std::string_view model = findNextToken();
|
||||
std::string_view subtopic = findNextToken();
|
||||
std::string_view setting = findNextToken();
|
||||
|
||||
if (model.empty() || subtopic.empty() || setting.empty() || subtopic != "cmd") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (setting == TOPIC_SUB_RESET) {
|
||||
MessageOutput.println(TOPIC_SUB_RESET);
|
||||
if (JsyMk.isInitialised())
|
||||
JsyMk.reset();
|
||||
}
|
||||
}
|
||||
@ -84,6 +84,14 @@
|
||||
#define CMT_SDIO -1
|
||||
#endif
|
||||
|
||||
#ifndef SERIAL_MODBUS_TX
|
||||
#define SERIAL_MODBUS_TX -1
|
||||
#endif
|
||||
|
||||
#ifndef SERIAL_MODBUS_RX
|
||||
#define SERIAL_MODBUS_RX -1
|
||||
#endif
|
||||
|
||||
PinMappingClass PinMapping;
|
||||
|
||||
PinMappingClass::PinMappingClass()
|
||||
@ -124,6 +132,9 @@ PinMappingClass::PinMappingClass()
|
||||
|
||||
_pinMapping.led[0] = LED0;
|
||||
_pinMapping.led[1] = LED1;
|
||||
|
||||
_pinMapping.serial_modbus_tx = SERIAL_MODBUS_TX;
|
||||
_pinMapping.serial_modbus_rx = SERIAL_MODBUS_RX;
|
||||
}
|
||||
|
||||
PinMapping_t& PinMappingClass::get()
|
||||
@ -186,6 +197,9 @@ bool PinMappingClass::init(const String& deviceMapping)
|
||||
_pinMapping.led[0] = doc[i]["led"]["led0"] | LED0;
|
||||
_pinMapping.led[1] = doc[i]["led"]["led1"] | LED1;
|
||||
|
||||
_pinMapping.serial_modbus_tx = doc[i]["serial_modbus"]["tx"] | SERIAL_MODBUS_TX;
|
||||
_pinMapping.serial_modbus_rx = doc[i]["serial_modbus"]["rx"] | SERIAL_MODBUS_RX;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -215,3 +229,8 @@ bool PinMappingClass::isValidEthConfig() const
|
||||
{
|
||||
return _pinMapping.eth_enabled;
|
||||
}
|
||||
|
||||
bool PinMappingClass::isValidSerialModbusConfig() const
|
||||
{
|
||||
return _pinMapping.serial_modbus_tx >= 0 && _pinMapping.serial_modbus_rx >= 0;
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ void WebApiConfigClass::onConfigGet(AsyncWebServerRequest* request)
|
||||
requestFile = name;
|
||||
} else {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -86,6 +86,10 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
||||
led["brightness"] = config.Led_Single[i].Brightness;
|
||||
}
|
||||
|
||||
auto serialModbusPinObj = curPin["serial_modbus"].to<JsonObject>();
|
||||
serialModbusPinObj["tx"] = pin.serial_modbus_tx;
|
||||
serialModbusPinObj["rx"] = pin.serial_modbus_rx;
|
||||
|
||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
*/
|
||||
#include "WebApi_sysstatus.h"
|
||||
#include "Configuration.h"
|
||||
#include "JsyMk.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "PinMapping.h"
|
||||
#include "WebApi.h"
|
||||
@ -76,5 +77,11 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
|
||||
root["cmt_configured"] = PinMapping.isValidCmt2300Config();
|
||||
root["cmt_connected"] = Hoymiles.getRadioCmt()->isConnected();
|
||||
|
||||
root["modbus_configured"] = PinMapping.isValidSerialModbusConfig();
|
||||
root["jsy_connected"] = JsyMk.isInitialised();
|
||||
root["jsy_variant"] = JsyMk.getFieldString(0, JsyMkClass::Field_t::MODEL) + " " + JsyMk.getFieldString(0, JsyMkClass::Field_t::VERSION)
|
||||
+ JsyMk.getFieldString(0, JsyMkClass::Field_t::VOLTAGE_RANGE) + JsyMk.getFieldUnit(JsyMkClass::Field_t::VOLTAGE_RANGE)
|
||||
+ " " + JsyMk.getFieldString(0, JsyMkClass::Field_t::CURRENT_RANGE) + JsyMk.getFieldUnit(JsyMkClass::Field_t::CURRENT_RANGE);
|
||||
|
||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
*/
|
||||
#include "WebApi_ws_live.h"
|
||||
#include "Datastore.h"
|
||||
#include "JsyMk.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "Utils.h"
|
||||
#include "WebApi.h"
|
||||
@ -83,6 +84,9 @@ void WebApiWsLiveClass::sendDataTaskCb()
|
||||
generateInverterCommonJsonResponse(invObject, inv);
|
||||
generateInverterChannelJsonResponse(invObject, inv);
|
||||
|
||||
auto powerMeter = var["power_meter"].to<JsonObject>();
|
||||
generatePowerMeterJsonResponse(powerMeter);
|
||||
|
||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
||||
continue;
|
||||
}
|
||||
@ -247,6 +251,9 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
||||
}
|
||||
}
|
||||
|
||||
auto powerMeter = root["power_meter"].to<JsonObject>();
|
||||
generatePowerMeterJsonResponse(powerMeter);
|
||||
|
||||
generateCommonJsonResponse(root);
|
||||
|
||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||
@ -259,3 +266,55 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
||||
WebApi.sendTooManyRequests(request);
|
||||
}
|
||||
}
|
||||
|
||||
void WebApiWsLiveClass::generatePowerMeterJsonResponse(JsonObject& root)
|
||||
{
|
||||
if (!JsyMk.isInitialised())
|
||||
return;
|
||||
|
||||
root["data_age"] = (millis() - JsyMk.getLastUpdate()) / 1000;
|
||||
|
||||
for (auto field : {
|
||||
JsyMkClass::Field_t::ADDRESS,
|
||||
JsyMkClass::Field_t::MANUFACTURER,
|
||||
JsyMkClass::Field_t::MODEL,
|
||||
JsyMkClass::Field_t::VERSION }) {
|
||||
|
||||
String name = JsyMk.getFieldName(0, field);
|
||||
root[name]["v"] = JsyMk.getFieldString(0, field);
|
||||
root[name]["u"] = JsyMk.getFieldUnit(field);
|
||||
root[name]["d"] = JsyMk.getFieldDigits(field);
|
||||
}
|
||||
|
||||
for (auto field : {
|
||||
JsyMkClass::Field_t::VOLTAGE_RANGE,
|
||||
JsyMkClass::Field_t::CURRENT_RANGE }) {
|
||||
|
||||
String name = JsyMk.getFieldName(0, field);
|
||||
root[name]["v"] = JsyMk.getFieldValue(0, field);
|
||||
root[name]["u"] = JsyMk.getFieldUnit(field);
|
||||
root[name]["d"] = JsyMk.getFieldDigits(field);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < JsyMk.getChannelNumber(); ++i) {
|
||||
for (auto field : {
|
||||
JsyMkClass::Field_t::VOLTAGE,
|
||||
JsyMkClass::Field_t::CURRENT,
|
||||
JsyMkClass::Field_t::POWER,
|
||||
JsyMkClass::Field_t::POWER_FACTOR,
|
||||
JsyMkClass::Field_t::FREQUENCY,
|
||||
JsyMkClass::Field_t::NEGATIVE,
|
||||
JsyMkClass::Field_t::TODAY_POSITIVE_ENERGY,
|
||||
JsyMkClass::Field_t::TODAY_NEGATIVE_ENERGY,
|
||||
JsyMkClass::Field_t::TOTAL_POSITIVE_ENERGY,
|
||||
JsyMkClass::Field_t::TOTAL_NEGATIVE_ENERGY }) {
|
||||
|
||||
String channel(i);
|
||||
String name = JsyMk.getFieldName(i, field);
|
||||
|
||||
root[channel][name]["v"] = JsyMk.getFieldValue(i, field);
|
||||
root[channel][name]["u"] = JsyMk.getFieldUnit(field);
|
||||
root[channel][name]["d"] = JsyMk.getFieldDigits(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,12 +6,14 @@
|
||||
#include "Datastore.h"
|
||||
#include "Display_Graphic.h"
|
||||
#include "InverterSettings.h"
|
||||
#include "JsyMk.h"
|
||||
#include "Led_Single.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "MqttHandleDtu.h"
|
||||
#include "MqttHandleHass.h"
|
||||
#include "MqttHandleInverter.h"
|
||||
#include "MqttHandleInverterTotal.h"
|
||||
#include "MqttHandlePowerMeter.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "NtpSettings.h"
|
||||
@ -107,6 +109,7 @@ void setup()
|
||||
MqttHandleDtu.init(scheduler);
|
||||
MqttHandleInverter.init(scheduler);
|
||||
MqttHandleInverterTotal.init(scheduler);
|
||||
MqttHandlePowerMeter.init(scheduler);
|
||||
MqttHandleHass.init(scheduler);
|
||||
MessageOutput.println("done");
|
||||
|
||||
@ -138,6 +141,11 @@ void setup()
|
||||
LedSingle.init(scheduler);
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Initialize JSY-MK-xxxT
|
||||
MessageOutput.print("Initialize JSY-MK-xxxT... ");
|
||||
JsyMk.init(scheduler);
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Check for default DTU serial
|
||||
MessageOutput.print("Check for default DTU serial... ");
|
||||
if (config.Dtu.Serial == DTU_SERIAL) {
|
||||
|
||||
@ -18,9 +18,9 @@
|
||||
"mitt": "^3.0.1",
|
||||
"sortablejs": "^1.15.2",
|
||||
"spark-md5": "^3.0.2",
|
||||
"vue": "^3.4.21",
|
||||
"vue-i18n": "^9.12.0",
|
||||
"vue-router": "^4.3.0"
|
||||
"vue": "^3.4.25",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-router": "^4.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
@ -33,16 +33,16 @@
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/eslint-config-typescript": "^13.0.0",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-plugin-vue": "^9.24.1",
|
||||
"eslint": "^9.1.1",
|
||||
"eslint-plugin-vue": "^9.25.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"pulltorefreshjs": "^0.1.22",
|
||||
"sass": "^1.75.0",
|
||||
"terser": "^5.30.3",
|
||||
"terser": "^5.30.4",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.8",
|
||||
"vite": "^5.2.10",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-css-injected-by-js": "^3.5.0",
|
||||
"vue-tsc": "^2.0.13"
|
||||
"vue-tsc": "^2.0.14"
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,7 +180,7 @@
|
||||
"America/Santiago":"<-04>4<-03>,M9.1.6/24,M4.1.6/24",
|
||||
"America/Santo_Domingo":"AST4",
|
||||
"America/Sao_Paulo":"<-03>3",
|
||||
"America/Scoresbysund":"<-01>1<+00>,M3.5.0/0,M10.5.0/1",
|
||||
"America/Scoresbysund":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0",
|
||||
"America/Sitka":"AKST9AKDT,M3.2.0,M11.1.0",
|
||||
"America/St_Barthelemy":"AST4",
|
||||
"America/St_Johns":"NST3:30NDT,M3.2.0,M11.1.0",
|
||||
@ -200,7 +200,7 @@
|
||||
"America/Winnipeg":"CST6CDT,M3.2.0,M11.1.0",
|
||||
"America/Yakutat":"AKST9AKDT,M3.2.0,M11.1.0",
|
||||
"America/Yellowknife":"MST7MDT,M3.2.0,M11.1.0",
|
||||
"Antarctica/Casey":"<+11>-11",
|
||||
"Antarctica/Casey":"<+08>-8",
|
||||
"Antarctica/Davis":"<+07>-7",
|
||||
"Antarctica/DumontDUrville":"<+10>-10",
|
||||
"Antarctica/Macquarie":"AEST-10AEDT,M10.1.0,M4.1.0/3",
|
||||
@ -210,10 +210,10 @@
|
||||
"Antarctica/Rothera":"<-03>3",
|
||||
"Antarctica/Syowa":"<+03>-3",
|
||||
"Antarctica/Troll":"<+00>0<+02>-2,M3.5.0/1,M10.5.0/3",
|
||||
"Antarctica/Vostok":"<+06>-6",
|
||||
"Antarctica/Vostok":"<+05>-5",
|
||||
"Arctic/Longyearbyen":"CET-1CEST,M3.5.0,M10.5.0/3",
|
||||
"Asia/Aden":"<+03>-3",
|
||||
"Asia/Almaty":"<+06>-6",
|
||||
"Asia/Almaty":"<+05>-5",
|
||||
"Asia/Amman":"<+03>-3",
|
||||
"Asia/Anadyr":"<+12>-12",
|
||||
"Asia/Aqtau":"<+05>-5",
|
||||
|
||||
@ -65,7 +65,7 @@ export function login(username: string, password: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function handleResponse(response: Response, emitter: Emitter<Record<EventType, unknown>>, router: Router) {
|
||||
export function handleResponse(response: Response, emitter: Emitter<Record<EventType, unknown>>, router: Router, ignore_error: boolean = false) {
|
||||
return response.text().then(text => {
|
||||
const data = text && JSON.parse(text);
|
||||
if (!response.ok) {
|
||||
@ -78,7 +78,9 @@ export function handleResponse(response: Response, emitter: Emitter<Record<Event
|
||||
}
|
||||
|
||||
const error = { message: (data && data.message) || response.statusText, status: response.status || 0 };
|
||||
router.push({ name: "Error", params: error });
|
||||
if (!ignore_error) {
|
||||
router.push({ name: "Error", params: error });
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
|
||||
@ -219,7 +219,7 @@ export default defineComponent({
|
||||
getPinMappingList() {
|
||||
this.pinMappingLoading = true;
|
||||
fetch("/api/config/get?file=pin_mapping.json", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router, true))
|
||||
.then(
|
||||
(data) => {
|
||||
this.pinMappingList = data;
|
||||
@ -246,6 +246,9 @@ export default defineComponent({
|
||||
.then(
|
||||
(data) => {
|
||||
this.deviceConfigList = data;
|
||||
if (this.deviceConfigList.curPin.name === "") {
|
||||
this.deviceConfigList.curPin.name = "Default";
|
||||
}
|
||||
this.dataLoading = false;
|
||||
}
|
||||
)
|
||||
|
||||
307
webapp/yarn.lock
307
webapp/yarn.lock
@ -17,6 +17,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b"
|
||||
integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==
|
||||
|
||||
"@babel/parser@^7.24.4":
|
||||
version "7.24.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88"
|
||||
integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==
|
||||
|
||||
"@esbuild/aix-ppc64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
|
||||
@ -171,15 +176,15 @@
|
||||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@eslint/js@9.0.0":
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.0.0.tgz#1a9e4b4c96d8c7886e0110ed310a0135144a1691"
|
||||
integrity sha512-RThY/MnKrhubF6+s1JflwUjPEsnCEmYCWwqa/aRISKWNXGZ9epUwft4bUMM35SdKF9xvBrLydAM1RDHd1Z//ZQ==
|
||||
"@eslint/js@9.1.1":
|
||||
version "9.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.1.1.tgz#eb0f82461d12779bbafc1b5045cde3143d350a8a"
|
||||
integrity sha512-5WoDz3Y19Bg2BnErkZTp0en+c/i9PvgFS7MBe1+m60HjFr0hrphlAGp4yzI7pxpt4xShln4ZyYp4neJm8hmOkQ==
|
||||
|
||||
"@humanwhocodes/config-array@^0.12.3":
|
||||
version "0.12.3"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.12.3.tgz#a6216d90f81a30bedd1d4b5d799b47241f318072"
|
||||
integrity sha512-jsNnTBlMWuTpDkeE3on7+dWJi0D6fdDfeANj/w7MpS8ztROCoLvIO2nG0CcFj+E4k8j4QrSTh4Oryi3i2G669g==
|
||||
"@humanwhocodes/config-array@^0.13.0":
|
||||
version "0.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748"
|
||||
integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==
|
||||
dependencies:
|
||||
"@humanwhocodes/object-schema" "^2.0.3"
|
||||
debug "^4.3.1"
|
||||
@ -195,6 +200,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
|
||||
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
|
||||
|
||||
"@humanwhocodes/retry@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.2.3.tgz#c9aa036d1afa643f1250e83150f39efb3a15a631"
|
||||
integrity sha512-X38nUbachlb01YMlvPFojKoiXq+LzZvuSce70KPMPdeM1Rj03k4dR7lDslhbqXn3Ang4EU3+EAmwEAsbrjHW3g==
|
||||
|
||||
"@intlify/bundle-utils@^8.0.0":
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-8.0.0.tgz#4e05153ac031bfc7adef70baedc9b0744a93adfd"
|
||||
@ -210,20 +220,20 @@
|
||||
source-map-js "^1.0.1"
|
||||
yaml-eslint-parser "^1.2.2"
|
||||
|
||||
"@intlify/core-base@9.12.0":
|
||||
version "9.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.12.0.tgz#79f43faa8eb1f3b2bfe569a9fbae9bc50908d311"
|
||||
integrity sha512-6EnWQXHnCh2bMiXT5N/IWwkcYQXjmF8nnEZ3YhTm23h1ZfOylz83D7pJYhcU8CsTiEdgbGiNdqyZPKwrHw03Ng==
|
||||
"@intlify/core-base@9.13.1":
|
||||
version "9.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.13.1.tgz#bd1f38e665095993ef9b67aeeb794f3cabcb515d"
|
||||
integrity sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w==
|
||||
dependencies:
|
||||
"@intlify/message-compiler" "9.12.0"
|
||||
"@intlify/shared" "9.12.0"
|
||||
"@intlify/message-compiler" "9.13.1"
|
||||
"@intlify/shared" "9.13.1"
|
||||
|
||||
"@intlify/message-compiler@9.12.0":
|
||||
version "9.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.12.0.tgz#5e152344853c29369911bd5e541e061b09218333"
|
||||
integrity sha512-2c6VwhvVJ1nur+2cN2NjdrmrV6vXjvyxYVvtUYMXKsWSUwoNURHGds0xJVJmWxbF8qV9oGepcVV6xl9bvadEIg==
|
||||
"@intlify/message-compiler@9.13.1":
|
||||
version "9.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.13.1.tgz#ff8129badf77db3fb648b8d3cceee87c8033ed0a"
|
||||
integrity sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w==
|
||||
dependencies:
|
||||
"@intlify/shared" "9.12.0"
|
||||
"@intlify/shared" "9.13.1"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
"@intlify/message-compiler@^9.4.0":
|
||||
@ -234,10 +244,10 @@
|
||||
"@intlify/shared" "9.4.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
"@intlify/shared@9.12.0":
|
||||
version "9.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.12.0.tgz#993383b6a98c8e37a1fa184a677eb39635a14a1c"
|
||||
integrity sha512-uBcH55x5CfZynnerWHQxrXbT6yD6j6T7Nt+R2+dHAOAneoMd6BoGvfEzfYscE94rgmjoDqdr+PdGDBLk5I5EjA==
|
||||
"@intlify/shared@9.13.1":
|
||||
version "9.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.13.1.tgz#202741d11ece1a9c7480bfd3f27afcf9cb8f72e4"
|
||||
integrity sha512-u3b6BKGhE6j/JeRU6C/RL2FgyJfy6LakbtfeVF8fJXURpZZTzfh3e05J0bu0XPw447Q6/WUp3C4ajv4TMS4YsQ==
|
||||
|
||||
"@intlify/shared@9.4.0", "@intlify/shared@^9.4.0":
|
||||
version "9.4.0"
|
||||
@ -557,26 +567,26 @@
|
||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz#508d6a0f2440f86945835d903fcc0d95d1bb8a37"
|
||||
integrity sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==
|
||||
|
||||
"@volar/language-core@2.2.0-alpha.8":
|
||||
version "2.2.0-alpha.8"
|
||||
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.2.0-alpha.8.tgz#74120a27ff2498ad297e86d17be95a9c7e1b46f5"
|
||||
integrity sha512-Ew1Iw7/RIRNuDLn60fWJdOLApAlfTVPxbPiSLzc434PReC9kleYtaa//Wo2WlN1oiRqneW0pWQQV0CwYqaimLQ==
|
||||
"@volar/language-core@2.2.0-alpha.10":
|
||||
version "2.2.0-alpha.10"
|
||||
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.2.0-alpha.10.tgz#e77db9b2ef4826cc55cf929289933d018c48e56c"
|
||||
integrity sha512-njVJLtpu0zMvDaEk7K5q4BRpOgbyEUljU++un9TfJoJNhxG0z/hWwpwgTRImO42EKvwIxF3XUzeMk+qatAFy7Q==
|
||||
dependencies:
|
||||
"@volar/source-map" "2.2.0-alpha.8"
|
||||
"@volar/source-map" "2.2.0-alpha.10"
|
||||
|
||||
"@volar/source-map@2.2.0-alpha.8":
|
||||
version "2.2.0-alpha.8"
|
||||
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.2.0-alpha.8.tgz#ca090f828fbef7e09ea06a636c41a06aa2afe153"
|
||||
integrity sha512-E1ZVmXFJ5DU4fWDcWHzi8OLqqReqIDwhXvIMhVdk6+VipfMVv4SkryXu7/rs4GA/GsebcRyJdaSkKBB3OAkIcA==
|
||||
"@volar/source-map@2.2.0-alpha.10":
|
||||
version "2.2.0-alpha.10"
|
||||
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.2.0-alpha.10.tgz#d055232eb2a24fb4678db578b55ec095c9925dc3"
|
||||
integrity sha512-nrdWApVkP5cksAnDEyy1JD9rKdwOJsEq1B+seWO4vNXmZNcxQQCx4DULLBvKt7AzRUAQiAuw5aQkb9RBaSqdVA==
|
||||
dependencies:
|
||||
muggle-string "^0.4.0"
|
||||
|
||||
"@volar/typescript@2.2.0-alpha.8":
|
||||
version "2.2.0-alpha.8"
|
||||
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.2.0-alpha.8.tgz#83a056c52995b4142364be3dda41d955a96f7356"
|
||||
integrity sha512-RLbRDI+17CiayHZs9HhSzlH0FhLl/+XK6o2qoiw2o2GGKcyD1aDoY6AcMd44acYncTOrqoTNoY6LuCiRyiJiGg==
|
||||
"@volar/typescript@2.2.0-alpha.10":
|
||||
version "2.2.0-alpha.10"
|
||||
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.2.0-alpha.10.tgz#14c002a3549ff3adcf9306933f4bf81e80422eff"
|
||||
integrity sha512-GCa0vTVVdA9ULUsu2Rx7jwsIuyZQPvPVT9o3NrANTbYv+523Ao1gv3glC5vzNSDPM6bUl37r94HbCj7KINQr+g==
|
||||
dependencies:
|
||||
"@volar/language-core" "2.2.0-alpha.8"
|
||||
"@volar/language-core" "2.2.0-alpha.10"
|
||||
path-browserify "^1.0.1"
|
||||
|
||||
"@vue/compiler-core@3.2.47":
|
||||
@ -600,6 +610,17 @@
|
||||
estree-walker "^2.0.2"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
"@vue/compiler-core@3.4.25":
|
||||
version "3.4.25"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.25.tgz#691f59ee5014f6f2a2488fd4465f892e1e82f729"
|
||||
integrity sha512-Y2pLLopaElgWnMNolgG8w3C5nNUVev80L7hdQ5iIKPtMJvhVpG0zhnBG/g3UajJmZdvW0fktyZTotEHD1Srhbg==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.24.4"
|
||||
"@vue/shared" "3.4.25"
|
||||
entities "^4.5.0"
|
||||
estree-walker "^2.0.2"
|
||||
source-map-js "^1.2.0"
|
||||
|
||||
"@vue/compiler-dom@3.2.47":
|
||||
version "3.2.47"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305"
|
||||
@ -608,7 +629,15 @@
|
||||
"@vue/compiler-core" "3.2.47"
|
||||
"@vue/shared" "3.2.47"
|
||||
|
||||
"@vue/compiler-dom@3.4.21", "@vue/compiler-dom@^3.4.0":
|
||||
"@vue/compiler-dom@3.4.25":
|
||||
version "3.4.25"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.25.tgz#b367e0c84e11d9e9f70beabdd6f6b2277fde375f"
|
||||
integrity sha512-Ugz5DusW57+HjllAugLci19NsDK+VyjGvmbB2TXaTcSlQxwL++2PETHx/+Qv6qFwNLzSt7HKepPe4DcTE3pBWg==
|
||||
dependencies:
|
||||
"@vue/compiler-core" "3.4.25"
|
||||
"@vue/shared" "3.4.25"
|
||||
|
||||
"@vue/compiler-dom@^3.4.0":
|
||||
version "3.4.21"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz#0077c355e2008207283a5a87d510330d22546803"
|
||||
integrity sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==
|
||||
@ -616,20 +645,20 @@
|
||||
"@vue/compiler-core" "3.4.21"
|
||||
"@vue/shared" "3.4.21"
|
||||
|
||||
"@vue/compiler-sfc@3.4.21":
|
||||
version "3.4.21"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz#4af920dc31ab99e1ff5d152b5fe0ad12181145b2"
|
||||
integrity sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==
|
||||
"@vue/compiler-sfc@3.4.25":
|
||||
version "3.4.25"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.25.tgz#ceab148f81571c8b251e8a8b75a9972addf1db8b"
|
||||
integrity sha512-m7rryuqzIoQpOBZ18wKyq05IwL6qEpZxFZfRxlNYuIPDqywrXQxgUwLXIvoU72gs6cRdY6wHD0WVZIFE4OEaAQ==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.23.9"
|
||||
"@vue/compiler-core" "3.4.21"
|
||||
"@vue/compiler-dom" "3.4.21"
|
||||
"@vue/compiler-ssr" "3.4.21"
|
||||
"@vue/shared" "3.4.21"
|
||||
"@babel/parser" "^7.24.4"
|
||||
"@vue/compiler-core" "3.4.25"
|
||||
"@vue/compiler-dom" "3.4.25"
|
||||
"@vue/compiler-ssr" "3.4.25"
|
||||
"@vue/shared" "3.4.25"
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.30.7"
|
||||
postcss "^8.4.35"
|
||||
source-map-js "^1.0.2"
|
||||
magic-string "^0.30.10"
|
||||
postcss "^8.4.38"
|
||||
source-map-js "^1.2.0"
|
||||
|
||||
"@vue/compiler-sfc@^3.2.47":
|
||||
version "3.2.47"
|
||||
@ -655,13 +684,13 @@
|
||||
"@vue/compiler-dom" "3.2.47"
|
||||
"@vue/shared" "3.2.47"
|
||||
|
||||
"@vue/compiler-ssr@3.4.21":
|
||||
version "3.4.21"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz#b84ae64fb9c265df21fc67f7624587673d324fef"
|
||||
integrity sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==
|
||||
"@vue/compiler-ssr@3.4.25":
|
||||
version "3.4.25"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.25.tgz#7fdd540bfdf2d4a3d6cb107b7ba4c77228d36331"
|
||||
integrity sha512-H2ohvM/Pf6LelGxDBnfbbXFPyM4NE3hrw0e/EpwuSiYu8c819wx+SVGdJ65p/sFrYDd6OnSDxN1MB2mN07hRSQ==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.4.21"
|
||||
"@vue/shared" "3.4.21"
|
||||
"@vue/compiler-dom" "3.4.25"
|
||||
"@vue/shared" "3.4.25"
|
||||
|
||||
"@vue/devtools-api@^6.5.0":
|
||||
version "6.5.0"
|
||||
@ -682,12 +711,12 @@
|
||||
"@typescript-eslint/parser" "^7.1.1"
|
||||
vue-eslint-parser "^9.3.1"
|
||||
|
||||
"@vue/language-core@2.0.13":
|
||||
version "2.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.0.13.tgz#2d1638b882011187b4b57115425d52b0901acab5"
|
||||
integrity sha512-oQgM+BM66SU5GKtUMLQSQN0bxHFkFpLSSAiY87wVziPaiNQZuKVDt/3yA7GB9PiQw0y/bTNL0bOc0jM/siYjKg==
|
||||
"@vue/language-core@2.0.14":
|
||||
version "2.0.14"
|
||||
resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.0.14.tgz#99d1dcd7df8a859e12606e80863b3cb4cf045f9e"
|
||||
integrity sha512-3q8mHSNcGTR7sfp2X6jZdcb4yt8AjBXAfKk0qkZIh7GAJxOnoZ10h5HToZglw4ToFvAnq+xu/Z2FFbglh9Icag==
|
||||
dependencies:
|
||||
"@volar/language-core" "2.2.0-alpha.8"
|
||||
"@volar/language-core" "2.2.0-alpha.10"
|
||||
"@vue/compiler-dom" "^3.4.0"
|
||||
"@vue/shared" "^3.4.0"
|
||||
computeds "^0.0.1"
|
||||
@ -706,37 +735,37 @@
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.25.7"
|
||||
|
||||
"@vue/reactivity@3.4.21":
|
||||
version "3.4.21"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.21.tgz#affd3415115b8ebf4927c8d2a0d6a24bccfa9f02"
|
||||
integrity sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==
|
||||
"@vue/reactivity@3.4.25":
|
||||
version "3.4.25"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.25.tgz#74983b146e06ce3341d15382669350125375d36f"
|
||||
integrity sha512-mKbEtKr1iTxZkAG3vm3BtKHAOhuI4zzsVcN0epDldU/THsrvfXRKzq+lZnjczZGnTdh3ojd86/WrP+u9M51pWQ==
|
||||
dependencies:
|
||||
"@vue/shared" "3.4.21"
|
||||
"@vue/shared" "3.4.25"
|
||||
|
||||
"@vue/runtime-core@3.4.21":
|
||||
version "3.4.21"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.21.tgz#3749c3f024a64c4c27ecd75aea4ca35634db0062"
|
||||
integrity sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==
|
||||
"@vue/runtime-core@3.4.25":
|
||||
version "3.4.25"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.25.tgz#c5545d469ae0827dc471a1376f97c6ace41081ec"
|
||||
integrity sha512-3qhsTqbEh8BMH3pXf009epCI5E7bKu28fJLi9O6W+ZGt/6xgSfMuGPqa5HRbUxLoehTNp5uWvzCr60KuiRIL0Q==
|
||||
dependencies:
|
||||
"@vue/reactivity" "3.4.21"
|
||||
"@vue/shared" "3.4.21"
|
||||
"@vue/reactivity" "3.4.25"
|
||||
"@vue/shared" "3.4.25"
|
||||
|
||||
"@vue/runtime-dom@3.4.21":
|
||||
version "3.4.21"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz#91f867ef64eff232cac45095ab28ebc93ac74588"
|
||||
integrity sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==
|
||||
"@vue/runtime-dom@3.4.25":
|
||||
version "3.4.25"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.25.tgz#9bc195e4860edcd0db4303cbba5a160922b963fd"
|
||||
integrity sha512-ode0sj77kuwXwSc+2Yhk8JMHZh1sZp9F/51wdBiz3KGaWltbKtdihlJFhQG4H6AY+A06zzeMLkq6qu8uDSsaoA==
|
||||
dependencies:
|
||||
"@vue/runtime-core" "3.4.21"
|
||||
"@vue/shared" "3.4.21"
|
||||
"@vue/runtime-core" "3.4.25"
|
||||
"@vue/shared" "3.4.25"
|
||||
csstype "^3.1.3"
|
||||
|
||||
"@vue/server-renderer@3.4.21":
|
||||
version "3.4.21"
|
||||
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.21.tgz#150751579d26661ee3ed26a28604667fa4222a97"
|
||||
integrity sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==
|
||||
"@vue/server-renderer@3.4.25":
|
||||
version "3.4.25"
|
||||
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.25.tgz#6cfc96ee631104951d5d6c09a8f1e7cef3ef3972"
|
||||
integrity sha512-8VTwq0Zcu3K4dWV0jOwIVINESE/gha3ifYCOKEhxOj6MEl5K5y8J8clQncTcDhKF+9U765nRw4UdUEXvrGhyVQ==
|
||||
dependencies:
|
||||
"@vue/compiler-ssr" "3.4.21"
|
||||
"@vue/shared" "3.4.21"
|
||||
"@vue/compiler-ssr" "3.4.25"
|
||||
"@vue/shared" "3.4.25"
|
||||
|
||||
"@vue/shared@3.2.47":
|
||||
version "3.2.47"
|
||||
@ -748,6 +777,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.21.tgz#de526a9059d0a599f0b429af7037cd0c3ed7d5a1"
|
||||
integrity sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==
|
||||
|
||||
"@vue/shared@3.4.25":
|
||||
version "3.4.25"
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.25.tgz#243ba8543e7401751e0ca319f75a80f153edd273"
|
||||
integrity sha512-k0yappJ77g2+KNrIaF0FFnzwLvUBLUYr8VOwz+/6vLsmItFp51AcxLL7Ey3iPd7BIRyWPOcqUjMnm7OkahXllA==
|
||||
|
||||
"@vue/tsconfig@^0.5.1":
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.5.1.tgz#3124ec16cc0c7e04165b88dc091e6b97782fffa9"
|
||||
@ -1124,10 +1158,10 @@ escodegen@^2.1.0:
|
||||
optionalDependencies:
|
||||
source-map "~0.6.1"
|
||||
|
||||
eslint-plugin-vue@^9.24.1:
|
||||
version "9.24.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.24.1.tgz#0d90330c939f9dd2f4c759da5a2ad91dc1c8bac4"
|
||||
integrity sha512-wk3SuwmS1pZdcuJlokGYEi/buDOwD6KltvhIZyOnpJ/378dcQ4zchu9PAMbbLAaydCz1iYc5AozszcOOgZIIOg==
|
||||
eslint-plugin-vue@^9.25.0:
|
||||
version "9.25.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.25.0.tgz#615cb7bb6d0e2140d21840b9aa51dce69e803e7a"
|
||||
integrity sha512-tDWlx14bVe6Bs+Nnh3IGrD+hb11kf2nukfm6jLsmJIhmiRQ1SUaksvwY9U5MvPB0pcrg0QK0xapQkfITs3RKOA==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.4.0"
|
||||
globals "^13.24.0"
|
||||
@ -1174,17 +1208,18 @@ eslint-visitor-keys@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
|
||||
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
|
||||
|
||||
eslint@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.0.0.tgz#6270548758e390343f78c8afd030566d86927d40"
|
||||
integrity sha512-IMryZ5SudxzQvuod6rUdIUz29qFItWx281VhtFVc2Psy/ZhlCeD/5DT6lBIJ4H3G+iamGJoTln1v+QSuPw0p7Q==
|
||||
eslint@^9.1.1:
|
||||
version "9.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.1.1.tgz#39ec657ccd12813cb4a1dab2f9229dcc6e468271"
|
||||
integrity sha512-b4cRQ0BeZcSEzPpY2PjFY70VbO32K7BStTGtBsnIGdTSEEQzBi8hPBcGQmTG2zUvFr9uLe0TK42bw8YszuHEqg==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.2.0"
|
||||
"@eslint-community/regexpp" "^4.6.1"
|
||||
"@eslint/eslintrc" "^3.0.2"
|
||||
"@eslint/js" "9.0.0"
|
||||
"@humanwhocodes/config-array" "^0.12.3"
|
||||
"@eslint/js" "9.1.1"
|
||||
"@humanwhocodes/config-array" "^0.13.0"
|
||||
"@humanwhocodes/module-importer" "^1.0.1"
|
||||
"@humanwhocodes/retry" "^0.2.3"
|
||||
"@nodelib/fs.walk" "^1.2.8"
|
||||
ajv "^6.12.4"
|
||||
chalk "^4.0.0"
|
||||
@ -1200,7 +1235,6 @@ eslint@^9.0.0:
|
||||
file-entry-cache "^8.0.0"
|
||||
find-up "^5.0.0"
|
||||
glob-parent "^6.0.2"
|
||||
graphemer "^1.4.0"
|
||||
ignore "^5.2.0"
|
||||
imurmurhash "^0.1.4"
|
||||
is-glob "^4.0.0"
|
||||
@ -1786,10 +1820,10 @@ magic-string@^0.25.7:
|
||||
dependencies:
|
||||
sourcemap-codec "^1.4.8"
|
||||
|
||||
magic-string@^0.30.7:
|
||||
version "0.30.7"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.7.tgz#0cecd0527d473298679da95a2d7aeb8c64048505"
|
||||
integrity sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==
|
||||
magic-string@^0.30.10:
|
||||
version "0.30.10"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e"
|
||||
integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.4.15"
|
||||
|
||||
@ -2056,15 +2090,6 @@ postcss@^8.1.10:
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
postcss@^8.4.35:
|
||||
version "8.4.35"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.35.tgz#60997775689ce09011edf083a549cea44aabe2f7"
|
||||
integrity sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==
|
||||
dependencies:
|
||||
nanoid "^3.3.7"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
postcss@^8.4.38:
|
||||
version "8.4.38"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
|
||||
@ -2381,10 +2406,10 @@ supports-preserve-symlinks-flag@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
terser@^5.30.3:
|
||||
version "5.30.3"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.30.3.tgz#f1bb68ded42408c316b548e3ec2526d7dd03f4d2"
|
||||
integrity sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==
|
||||
terser@^5.30.4:
|
||||
version "5.30.4"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.30.4.tgz#62b4d16a819424e6317fd5ceffb4ee8dc769803a"
|
||||
integrity sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==
|
||||
dependencies:
|
||||
"@jridgewell/source-map" "^0.3.3"
|
||||
acorn "^8.8.2"
|
||||
@ -2494,10 +2519,10 @@ vite-plugin-css-injected-by-js@^3.5.0:
|
||||
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.0.tgz#784c0f42c2b42155eb4c726c6addfa24aba9f4fb"
|
||||
integrity sha512-d0QaHH9kS93J25SwRqJNEfE29PSuQS5jn51y9N9i2Yoq0FRO7rjuTeLvjM5zwklZlRrIn6SUdtOEDKyHokgJZg==
|
||||
|
||||
vite@^5.2.8:
|
||||
version "5.2.8"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.8.tgz#a99e09939f1a502992381395ce93efa40a2844aa"
|
||||
integrity sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==
|
||||
vite@^5.2.10:
|
||||
version "5.2.10"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.10.tgz#2ac927c91e99d51b376a5c73c0e4b059705f5bd7"
|
||||
integrity sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==
|
||||
dependencies:
|
||||
esbuild "^0.20.1"
|
||||
postcss "^8.4.38"
|
||||
@ -2531,19 +2556,19 @@ vue-eslint-parser@^9.4.2:
|
||||
lodash "^4.17.21"
|
||||
semver "^7.3.6"
|
||||
|
||||
vue-i18n@^9.12.0:
|
||||
version "9.12.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.12.0.tgz#8d073b3d7b92e822dcc3268946af4ecf14b778b3"
|
||||
integrity sha512-rUxCKTws8NH3XP98W71GA7btAQdAuO7j6BC5y5s1bTNQYo/CIgZQf+p7d1Zo5bo/3v8TIq9aSUMDjpfgKsC3Uw==
|
||||
vue-i18n@^9.13.1:
|
||||
version "9.13.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.13.1.tgz#a292c8021b7be604ebfca5609ae1f8fafe5c36d7"
|
||||
integrity sha512-mh0GIxx0wPtPlcB1q4k277y0iKgo25xmDPWioVVYanjPufDBpvu5ySTjP5wOrSvlYQ2m1xI+CFhGdauv/61uQg==
|
||||
dependencies:
|
||||
"@intlify/core-base" "9.12.0"
|
||||
"@intlify/shared" "9.12.0"
|
||||
"@intlify/core-base" "9.13.1"
|
||||
"@intlify/shared" "9.13.1"
|
||||
"@vue/devtools-api" "^6.5.0"
|
||||
|
||||
vue-router@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.3.0.tgz#d5913f27bf68a0a178ee798c3c88be471811a235"
|
||||
integrity sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==
|
||||
vue-router@^4.3.2:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.3.2.tgz#08096c7765dacc6832f58e35f7a081a8b34116a7"
|
||||
integrity sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==
|
||||
dependencies:
|
||||
"@vue/devtools-api" "^6.5.1"
|
||||
|
||||
@ -2555,25 +2580,25 @@ vue-template-compiler@^2.7.14:
|
||||
de-indent "^1.0.2"
|
||||
he "^1.2.0"
|
||||
|
||||
vue-tsc@^2.0.13:
|
||||
version "2.0.13"
|
||||
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-2.0.13.tgz#6ee557705456442e0f43ec0d1774ebf5ffec54f1"
|
||||
integrity sha512-a3nL3FvguCWVJUQW/jFrUxdeUtiEkbZoQjidqvMeBK//tuE2w6NWQAbdrEpY2+6nSa4kZoKZp8TZUMtHpjt4mQ==
|
||||
vue-tsc@^2.0.14:
|
||||
version "2.0.14"
|
||||
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-2.0.14.tgz#5a8a652bcba30fa6fd8f7ac6af5df8e387f25cd8"
|
||||
integrity sha512-DgAO3U1cnCHOUO7yB35LENbkapeRsBZ7Ugq5hGz/QOHny0+1VQN8eSwSBjYbjLVPfvfw6EY7sNPjbuHHUhckcg==
|
||||
dependencies:
|
||||
"@volar/typescript" "2.2.0-alpha.8"
|
||||
"@vue/language-core" "2.0.13"
|
||||
"@volar/typescript" "2.2.0-alpha.10"
|
||||
"@vue/language-core" "2.0.14"
|
||||
semver "^7.5.4"
|
||||
|
||||
vue@^3.4.21:
|
||||
version "3.4.21"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.21.tgz#69ec30e267d358ee3a0ce16612ba89e00aaeb731"
|
||||
integrity sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==
|
||||
vue@^3.4.25:
|
||||
version "3.4.25"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.25.tgz#e59d4ed36389647b52ff2fd7aa84bb6691f4205b"
|
||||
integrity sha512-HWyDqoBHMgav/OKiYA2ZQg+kjfMgLt/T0vg4cbIF7JbXAjDexRf5JRg+PWAfrAkSmTd2I8aPSXtooBFWHB98cg==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.4.21"
|
||||
"@vue/compiler-sfc" "3.4.21"
|
||||
"@vue/runtime-dom" "3.4.21"
|
||||
"@vue/server-renderer" "3.4.21"
|
||||
"@vue/shared" "3.4.21"
|
||||
"@vue/compiler-dom" "3.4.25"
|
||||
"@vue/compiler-sfc" "3.4.25"
|
||||
"@vue/runtime-dom" "3.4.25"
|
||||
"@vue/server-renderer" "3.4.25"
|
||||
"@vue/shared" "3.4.25"
|
||||
|
||||
webpack-sources@^3.2.3:
|
||||
version "3.2.3"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user