Add support of JSY-MK-163T

This commit is contained in:
Benoît Leforestier 2024-04-28 09:55:35 +02:00 committed by GitHub
parent d0981934b0
commit 387ff53369
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 1721 additions and 174 deletions

61
.vscode/settings.json vendored
View File

@ -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"
}
} }

View File

@ -4,7 +4,11 @@
[![cpplint](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml) [![cpplint](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/cpplint.yml)
[![Yarn Linting](https://github.com/tbnobody/OpenDTU/actions/workflows/yarnlint.yml/badge.svg)](https://github.com/tbnobody/OpenDTU/actions/workflows/yarnlint.yml) [![Yarn Linting](https://github.com/tbnobody/OpenDTU/actions/workflows/yarnlint.yml/badge.svg)](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! 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!

View File

@ -30,6 +30,8 @@
#define DEV_MAX_MAPPING_NAME_STRLEN 63 #define DEV_MAX_MAPPING_NAME_STRLEN 63
#define PWRMTR_MAX_CHAN_COUNT 2
struct CHANNEL_CONFIG_T { struct CHANNEL_CONFIG_T {
uint16_t MaxChannelPower; uint16_t MaxChannelPower;
char Name[CHAN_MAX_NAME_STRLEN]; char Name[CHAN_MAX_NAME_STRLEN];
@ -152,6 +154,18 @@ struct CONFIG_T {
uint8_t Brightness; uint8_t Brightness;
} Led_Single[PINMAPPING_LED_COUNT]; } 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]; INVERTER_CONFIG_T Inverter[INV_MAX_COUNT];
char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1]; char Dev_PinMapping[DEV_MAX_MAPPING_NAME_STRLEN + 1];
}; };

63
include/JsyMk.h Normal file
View 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;

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "MqttHandlePowerMeter.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <Hoymiles.h> #include <Hoymiles.h>
#include <TaskSchedulerDeclarations.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 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 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 createInverterInfo(JsonDocument& doc, std::shared_ptr<InverterAbstract> inv);
static void createDtuInfo(JsonDocument& doc); static void createDtuInfo(JsonDocument& doc);

View 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;

View File

@ -39,6 +39,9 @@ struct PinMapping_t {
uint8_t display_cs; uint8_t display_cs;
uint8_t display_reset; uint8_t display_reset;
int8_t led[PINMAPPING_LED_COUNT]; int8_t led[PINMAPPING_LED_COUNT];
int8_t serial_modbus_tx;
int8_t serial_modbus_rx;
}; };
class PinMappingClass { class PinMappingClass {
@ -50,6 +53,7 @@ public:
bool isValidNrf24Config() const; bool isValidNrf24Config() const;
bool isValidCmt2300Config() const; bool isValidCmt2300Config() const;
bool isValidEthConfig() const; bool isValidEthConfig() const;
bool isValidSerialModbusConfig() const;
private: private:
PinMapping_t _pinMapping; PinMapping_t _pinMapping;

View File

@ -16,6 +16,7 @@ private:
static void generateInverterCommonJsonResponse(JsonObject& root, std::shared_ptr<InverterAbstract> inv); static void generateInverterCommonJsonResponse(JsonObject& root, std::shared_ptr<InverterAbstract> inv);
static void generateInverterChannelJsonResponse(JsonObject& root, std::shared_ptr<InverterAbstract> inv); static void generateInverterChannelJsonResponse(JsonObject& root, std::shared_ptr<InverterAbstract> inv);
static void generateCommonJsonResponse(JsonVariant& root); 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 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); static void addTotalField(JsonObject& root, const String& name, const float value, const String& unit, const uint8_t digits);

View File

@ -108,3 +108,9 @@
#define LED_BRIGHTNESS 100U #define LED_BRIGHTNESS 100U
#define MAX_INVERTER_LIMIT 2250 #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
View 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
View 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
View 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 {};
};

View 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"
]
}

View 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);
}

View 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;
};

View File

@ -37,11 +37,11 @@ build_unflags =
-std=gnu++11 -std=gnu++11
lib_deps = lib_deps =
mathieucarbou/ESP Async WebServer @ 2.9.0 mathieucarbou/ESP Async WebServer @ 2.9.3
bblanchon/ArduinoJson @ ^7.0.4 bblanchon/ArduinoJson @ ^7.0.4
https://github.com/bertmelis/espMqttClient.git#v1.6.0 https://github.com/bertmelis/espMqttClient.git#v1.6.0
nrf24/RF24 @ ^1.4.8 nrf24/RF24 @ ^1.4.8
olikraus/U8g2 @ ^2.35.15 olikraus/U8g2 @ ^2.35.17
buelowp/sunset @ ^1.1.7 buelowp/sunset @ ^1.1.7
https://github.com/arkhipenko/TaskScheduler#testing https://github.com/arkhipenko/TaskScheduler#testing
@ -123,6 +123,15 @@ build_flags = ${env.build_flags}
-DHOYMILES_PIN_CE=4 -DHOYMILES_PIN_CE=4
-DHOYMILES_PIN_CS=5 -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] [env:olimex_esp32_poe]
; https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware ; https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware

View File

@ -32,3 +32,15 @@
; -DHOYMILES_PIN_CS=6 ; -DHOYMILES_PIN_CS=6
;monitor_port = /dev/ttyACM0 ;monitor_port = /dev/ttyACM0
;upload_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

View File

@ -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__)) { if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
return false; 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(); f.close();
return true; return true;
} }

View File

@ -4,6 +4,7 @@
*/ */
#include "Display_Graphic.h" #include "Display_Graphic.h"
#include "Datastore.h" #include "Datastore.h"
#include "JsyMk.h"
#include <NetworkSettings.h> #include <NetworkSettings.h>
#include <map> #include <map>
#include <time.h> #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_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() DisplayGraphicClass::DisplayGraphicClass()
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&DisplayGraphicClass::loop, this)) : _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&DisplayGraphicClass::loop, this))
{ {
@ -202,8 +212,21 @@ void DisplayGraphicClass::loop()
bool displayPowerSave = false; bool displayPowerSave = false;
bool showText = true; 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 ========== //=====> Actual Production ==========
if (Datastore.getIsAtLeastOneReachable()) { else if (Datastore.getIsAtLeastOneReachable()) {
displayPowerSave = false; displayPowerSave = false;
if (_isLarge) { if (_isLarge) {
uint8_t screenSaverOffsetX = enableScreensaver ? (_mExtra % 7) : 0; uint8_t screenSaverOffsetX = enableScreensaver ? (_mExtra % 7) : 0;
@ -246,6 +269,25 @@ void DisplayGraphicClass::loop()
//<======================= //<=======================
if (showText) { if (showText) {
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);
// 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 // Daily production
float wattsToday = Datastore.getTotalAcYieldDayEnabled(); float wattsToday = Datastore.getTotalAcYieldDayEnabled();
if (wattsToday >= 10000) { if (wattsToday >= 10000) {
@ -260,6 +302,7 @@ void DisplayGraphicClass::loop()
auto const format = (wattsTotal >= 1000) ? i18n_yield_total_mwh : i18n_yield_total_kwh; auto const format = (wattsTotal >= 1000) ? i18n_yield_total_mwh : i18n_yield_total_kwh;
snprintf(_fmtText, sizeof(_fmtText), format[_display_language], wattsTotal); snprintf(_fmtText, sizeof(_fmtText), format[_display_language], wattsTotal);
printText(_fmtText, 2); printText(_fmtText, 2);
}
//=====> IP or Date-Time ======== //=====> IP or Date-Time ========
// Change every 3 seconds // Change every 3 seconds

247
src/JsyMk.cpp Normal file
View 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();
}

View File

@ -96,6 +96,27 @@ void MqttHandleHassClass::publishConfig()
yield(); 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) 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); 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) void MqttHandleHassClass::createInverterInfo(JsonDocument& root, std::shared_ptr<InverterAbstract> inv)
{ {
createDeviceInfo( createDeviceInfo(

View 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();
}
}

View File

@ -84,6 +84,14 @@
#define CMT_SDIO -1 #define CMT_SDIO -1
#endif #endif
#ifndef SERIAL_MODBUS_TX
#define SERIAL_MODBUS_TX -1
#endif
#ifndef SERIAL_MODBUS_RX
#define SERIAL_MODBUS_RX -1
#endif
PinMappingClass PinMapping; PinMappingClass PinMapping;
PinMappingClass::PinMappingClass() PinMappingClass::PinMappingClass()
@ -124,6 +132,9 @@ PinMappingClass::PinMappingClass()
_pinMapping.led[0] = LED0; _pinMapping.led[0] = LED0;
_pinMapping.led[1] = LED1; _pinMapping.led[1] = LED1;
_pinMapping.serial_modbus_tx = SERIAL_MODBUS_TX;
_pinMapping.serial_modbus_rx = SERIAL_MODBUS_RX;
} }
PinMapping_t& PinMappingClass::get() PinMapping_t& PinMappingClass::get()
@ -186,6 +197,9 @@ bool PinMappingClass::init(const String& deviceMapping)
_pinMapping.led[0] = doc[i]["led"]["led0"] | LED0; _pinMapping.led[0] = doc[i]["led"]["led0"] | LED0;
_pinMapping.led[1] = doc[i]["led"]["led1"] | LED1; _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; return true;
} }
} }
@ -215,3 +229,8 @@ bool PinMappingClass::isValidEthConfig() const
{ {
return _pinMapping.eth_enabled; return _pinMapping.eth_enabled;
} }
bool PinMappingClass::isValidSerialModbusConfig() const
{
return _pinMapping.serial_modbus_tx >= 0 && _pinMapping.serial_modbus_rx >= 0;
}

View File

@ -40,6 +40,7 @@ void WebApiConfigClass::onConfigGet(AsyncWebServerRequest* request)
requestFile = name; requestFile = name;
} else { } else {
request->send(404); request->send(404);
return;
} }
} }

View File

@ -86,6 +86,10 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
led["brightness"] = config.Led_Single[i].Brightness; 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__); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
} }

View File

@ -4,6 +4,7 @@
*/ */
#include "WebApi_sysstatus.h" #include "WebApi_sysstatus.h"
#include "Configuration.h" #include "Configuration.h"
#include "JsyMk.h"
#include "NetworkSettings.h" #include "NetworkSettings.h"
#include "PinMapping.h" #include "PinMapping.h"
#include "WebApi.h" #include "WebApi.h"
@ -76,5 +77,11 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
root["cmt_configured"] = PinMapping.isValidCmt2300Config(); root["cmt_configured"] = PinMapping.isValidCmt2300Config();
root["cmt_connected"] = Hoymiles.getRadioCmt()->isConnected(); 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__); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
} }

View File

@ -4,6 +4,7 @@
*/ */
#include "WebApi_ws_live.h" #include "WebApi_ws_live.h"
#include "Datastore.h" #include "Datastore.h"
#include "JsyMk.h"
#include "MessageOutput.h" #include "MessageOutput.h"
#include "Utils.h" #include "Utils.h"
#include "WebApi.h" #include "WebApi.h"
@ -83,6 +84,9 @@ void WebApiWsLiveClass::sendDataTaskCb()
generateInverterCommonJsonResponse(invObject, inv); generateInverterCommonJsonResponse(invObject, inv);
generateInverterChannelJsonResponse(invObject, inv); generateInverterChannelJsonResponse(invObject, inv);
auto powerMeter = var["power_meter"].to<JsonObject>();
generatePowerMeterJsonResponse(powerMeter);
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) { if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
continue; continue;
} }
@ -247,6 +251,9 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
} }
} }
auto powerMeter = root["power_meter"].to<JsonObject>();
generatePowerMeterJsonResponse(powerMeter);
generateCommonJsonResponse(root); generateCommonJsonResponse(root);
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
@ -259,3 +266,55 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
WebApi.sendTooManyRequests(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);
}
}
}

View File

@ -6,12 +6,14 @@
#include "Datastore.h" #include "Datastore.h"
#include "Display_Graphic.h" #include "Display_Graphic.h"
#include "InverterSettings.h" #include "InverterSettings.h"
#include "JsyMk.h"
#include "Led_Single.h" #include "Led_Single.h"
#include "MessageOutput.h" #include "MessageOutput.h"
#include "MqttHandleDtu.h" #include "MqttHandleDtu.h"
#include "MqttHandleHass.h" #include "MqttHandleHass.h"
#include "MqttHandleInverter.h" #include "MqttHandleInverter.h"
#include "MqttHandleInverterTotal.h" #include "MqttHandleInverterTotal.h"
#include "MqttHandlePowerMeter.h"
#include "MqttSettings.h" #include "MqttSettings.h"
#include "NetworkSettings.h" #include "NetworkSettings.h"
#include "NtpSettings.h" #include "NtpSettings.h"
@ -107,6 +109,7 @@ void setup()
MqttHandleDtu.init(scheduler); MqttHandleDtu.init(scheduler);
MqttHandleInverter.init(scheduler); MqttHandleInverter.init(scheduler);
MqttHandleInverterTotal.init(scheduler); MqttHandleInverterTotal.init(scheduler);
MqttHandlePowerMeter.init(scheduler);
MqttHandleHass.init(scheduler); MqttHandleHass.init(scheduler);
MessageOutput.println("done"); MessageOutput.println("done");
@ -138,6 +141,11 @@ void setup()
LedSingle.init(scheduler); LedSingle.init(scheduler);
MessageOutput.println("done"); MessageOutput.println("done");
// Initialize JSY-MK-xxxT
MessageOutput.print("Initialize JSY-MK-xxxT... ");
JsyMk.init(scheduler);
MessageOutput.println("done");
// Check for default DTU serial // Check for default DTU serial
MessageOutput.print("Check for default DTU serial... "); MessageOutput.print("Check for default DTU serial... ");
if (config.Dtu.Serial == DTU_SERIAL) { if (config.Dtu.Serial == DTU_SERIAL) {

View File

@ -18,9 +18,9 @@
"mitt": "^3.0.1", "mitt": "^3.0.1",
"sortablejs": "^1.15.2", "sortablejs": "^1.15.2",
"spark-md5": "^3.0.2", "spark-md5": "^3.0.2",
"vue": "^3.4.21", "vue": "^3.4.25",
"vue-i18n": "^9.12.0", "vue-i18n": "^9.13.1",
"vue-router": "^4.3.0" "vue-router": "^4.3.2"
}, },
"devDependencies": { "devDependencies": {
"@intlify/unplugin-vue-i18n": "^4.0.0", "@intlify/unplugin-vue-i18n": "^4.0.0",
@ -33,16 +33,16 @@
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"@vue/eslint-config-typescript": "^13.0.0", "@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1", "@vue/tsconfig": "^0.5.1",
"eslint": "^9.0.0", "eslint": "^9.1.1",
"eslint-plugin-vue": "^9.24.1", "eslint-plugin-vue": "^9.25.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"pulltorefreshjs": "^0.1.22", "pulltorefreshjs": "^0.1.22",
"sass": "^1.75.0", "sass": "^1.75.0",
"terser": "^5.30.3", "terser": "^5.30.4",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"vite": "^5.2.8", "vite": "^5.2.10",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-css-injected-by-js": "^3.5.0", "vite-plugin-css-injected-by-js": "^3.5.0",
"vue-tsc": "^2.0.13" "vue-tsc": "^2.0.14"
} }
} }

View File

@ -180,7 +180,7 @@
"America/Santiago":"<-04>4<-03>,M9.1.6/24,M4.1.6/24", "America/Santiago":"<-04>4<-03>,M9.1.6/24,M4.1.6/24",
"America/Santo_Domingo":"AST4", "America/Santo_Domingo":"AST4",
"America/Sao_Paulo":"<-03>3", "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/Sitka":"AKST9AKDT,M3.2.0,M11.1.0",
"America/St_Barthelemy":"AST4", "America/St_Barthelemy":"AST4",
"America/St_Johns":"NST3:30NDT,M3.2.0,M11.1.0", "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/Winnipeg":"CST6CDT,M3.2.0,M11.1.0",
"America/Yakutat":"AKST9AKDT,M3.2.0,M11.1.0", "America/Yakutat":"AKST9AKDT,M3.2.0,M11.1.0",
"America/Yellowknife":"MST7MDT,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/Davis":"<+07>-7",
"Antarctica/DumontDUrville":"<+10>-10", "Antarctica/DumontDUrville":"<+10>-10",
"Antarctica/Macquarie":"AEST-10AEDT,M10.1.0,M4.1.0/3", "Antarctica/Macquarie":"AEST-10AEDT,M10.1.0,M4.1.0/3",
@ -210,10 +210,10 @@
"Antarctica/Rothera":"<-03>3", "Antarctica/Rothera":"<-03>3",
"Antarctica/Syowa":"<+03>-3", "Antarctica/Syowa":"<+03>-3",
"Antarctica/Troll":"<+00>0<+02>-2,M3.5.0/1,M10.5.0/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", "Arctic/Longyearbyen":"CET-1CEST,M3.5.0,M10.5.0/3",
"Asia/Aden":"<+03>-3", "Asia/Aden":"<+03>-3",
"Asia/Almaty":"<+06>-6", "Asia/Almaty":"<+05>-5",
"Asia/Amman":"<+03>-3", "Asia/Amman":"<+03>-3",
"Asia/Anadyr":"<+12>-12", "Asia/Anadyr":"<+12>-12",
"Asia/Aqtau":"<+05>-5", "Asia/Aqtau":"<+05>-5",

View File

@ -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 => { return response.text().then(text => {
const data = text && JSON.parse(text); const data = text && JSON.parse(text);
if (!response.ok) { 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 }; const error = { message: (data && data.message) || response.statusText, status: response.status || 0 };
if (!ignore_error) {
router.push({ name: "Error", params: error }); router.push({ name: "Error", params: error });
}
return Promise.reject(error); return Promise.reject(error);
} }

View File

@ -219,7 +219,7 @@ export default defineComponent({
getPinMappingList() { getPinMappingList() {
this.pinMappingLoading = true; this.pinMappingLoading = true;
fetch("/api/config/get?file=pin_mapping.json", { headers: authHeader() }) 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( .then(
(data) => { (data) => {
this.pinMappingList = data; this.pinMappingList = data;
@ -246,6 +246,9 @@ export default defineComponent({
.then( .then(
(data) => { (data) => {
this.deviceConfigList = data; this.deviceConfigList = data;
if (this.deviceConfigList.curPin.name === "") {
this.deviceConfigList.curPin.name = "Default";
}
this.dataLoading = false; this.dataLoading = false;
} }
) )

View File

@ -17,6 +17,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b"
integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA== 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": "@esbuild/aix-ppc64@0.20.2":
version "0.20.2" version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
@ -171,15 +176,15 @@
minimatch "^3.1.2" minimatch "^3.1.2"
strip-json-comments "^3.1.1" strip-json-comments "^3.1.1"
"@eslint/js@9.0.0": "@eslint/js@9.1.1":
version "9.0.0" version "9.1.1"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.0.0.tgz#1a9e4b4c96d8c7886e0110ed310a0135144a1691" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.1.1.tgz#eb0f82461d12779bbafc1b5045cde3143d350a8a"
integrity sha512-RThY/MnKrhubF6+s1JflwUjPEsnCEmYCWwqa/aRISKWNXGZ9epUwft4bUMM35SdKF9xvBrLydAM1RDHd1Z//ZQ== integrity sha512-5WoDz3Y19Bg2BnErkZTp0en+c/i9PvgFS7MBe1+m60HjFr0hrphlAGp4yzI7pxpt4xShln4ZyYp4neJm8hmOkQ==
"@humanwhocodes/config-array@^0.12.3": "@humanwhocodes/config-array@^0.13.0":
version "0.12.3" version "0.13.0"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.12.3.tgz#a6216d90f81a30bedd1d4b5d799b47241f318072" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748"
integrity sha512-jsNnTBlMWuTpDkeE3on7+dWJi0D6fdDfeANj/w7MpS8ztROCoLvIO2nG0CcFj+E4k8j4QrSTh4Oryi3i2G669g== integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==
dependencies: dependencies:
"@humanwhocodes/object-schema" "^2.0.3" "@humanwhocodes/object-schema" "^2.0.3"
debug "^4.3.1" debug "^4.3.1"
@ -195,6 +200,11 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== 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": "@intlify/bundle-utils@^8.0.0":
version "8.0.0" version "8.0.0"
resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-8.0.0.tgz#4e05153ac031bfc7adef70baedc9b0744a93adfd" 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" source-map-js "^1.0.1"
yaml-eslint-parser "^1.2.2" yaml-eslint-parser "^1.2.2"
"@intlify/core-base@9.12.0": "@intlify/core-base@9.13.1":
version "9.12.0" version "9.13.1"
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.12.0.tgz#79f43faa8eb1f3b2bfe569a9fbae9bc50908d311" resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.13.1.tgz#bd1f38e665095993ef9b67aeeb794f3cabcb515d"
integrity sha512-6EnWQXHnCh2bMiXT5N/IWwkcYQXjmF8nnEZ3YhTm23h1ZfOylz83D7pJYhcU8CsTiEdgbGiNdqyZPKwrHw03Ng== integrity sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w==
dependencies: dependencies:
"@intlify/message-compiler" "9.12.0" "@intlify/message-compiler" "9.13.1"
"@intlify/shared" "9.12.0" "@intlify/shared" "9.13.1"
"@intlify/message-compiler@9.12.0": "@intlify/message-compiler@9.13.1":
version "9.12.0" version "9.13.1"
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.12.0.tgz#5e152344853c29369911bd5e541e061b09218333" resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.13.1.tgz#ff8129badf77db3fb648b8d3cceee87c8033ed0a"
integrity sha512-2c6VwhvVJ1nur+2cN2NjdrmrV6vXjvyxYVvtUYMXKsWSUwoNURHGds0xJVJmWxbF8qV9oGepcVV6xl9bvadEIg== integrity sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w==
dependencies: dependencies:
"@intlify/shared" "9.12.0" "@intlify/shared" "9.13.1"
source-map-js "^1.0.2" source-map-js "^1.0.2"
"@intlify/message-compiler@^9.4.0": "@intlify/message-compiler@^9.4.0":
@ -234,10 +244,10 @@
"@intlify/shared" "9.4.0" "@intlify/shared" "9.4.0"
source-map-js "^1.0.2" source-map-js "^1.0.2"
"@intlify/shared@9.12.0": "@intlify/shared@9.13.1":
version "9.12.0" version "9.13.1"
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.12.0.tgz#993383b6a98c8e37a1fa184a677eb39635a14a1c" resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.13.1.tgz#202741d11ece1a9c7480bfd3f27afcf9cb8f72e4"
integrity sha512-uBcH55x5CfZynnerWHQxrXbT6yD6j6T7Nt+R2+dHAOAneoMd6BoGvfEzfYscE94rgmjoDqdr+PdGDBLk5I5EjA== integrity sha512-u3b6BKGhE6j/JeRU6C/RL2FgyJfy6LakbtfeVF8fJXURpZZTzfh3e05J0bu0XPw447Q6/WUp3C4ajv4TMS4YsQ==
"@intlify/shared@9.4.0", "@intlify/shared@^9.4.0": "@intlify/shared@9.4.0", "@intlify/shared@^9.4.0":
version "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" resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz#508d6a0f2440f86945835d903fcc0d95d1bb8a37"
integrity sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ== integrity sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==
"@volar/language-core@2.2.0-alpha.8": "@volar/language-core@2.2.0-alpha.10":
version "2.2.0-alpha.8" version "2.2.0-alpha.10"
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.2.0-alpha.8.tgz#74120a27ff2498ad297e86d17be95a9c7e1b46f5" resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.2.0-alpha.10.tgz#e77db9b2ef4826cc55cf929289933d018c48e56c"
integrity sha512-Ew1Iw7/RIRNuDLn60fWJdOLApAlfTVPxbPiSLzc434PReC9kleYtaa//Wo2WlN1oiRqneW0pWQQV0CwYqaimLQ== integrity sha512-njVJLtpu0zMvDaEk7K5q4BRpOgbyEUljU++un9TfJoJNhxG0z/hWwpwgTRImO42EKvwIxF3XUzeMk+qatAFy7Q==
dependencies: 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": "@volar/source-map@2.2.0-alpha.10":
version "2.2.0-alpha.8" version "2.2.0-alpha.10"
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.2.0-alpha.8.tgz#ca090f828fbef7e09ea06a636c41a06aa2afe153" resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.2.0-alpha.10.tgz#d055232eb2a24fb4678db578b55ec095c9925dc3"
integrity sha512-E1ZVmXFJ5DU4fWDcWHzi8OLqqReqIDwhXvIMhVdk6+VipfMVv4SkryXu7/rs4GA/GsebcRyJdaSkKBB3OAkIcA== integrity sha512-nrdWApVkP5cksAnDEyy1JD9rKdwOJsEq1B+seWO4vNXmZNcxQQCx4DULLBvKt7AzRUAQiAuw5aQkb9RBaSqdVA==
dependencies: dependencies:
muggle-string "^0.4.0" muggle-string "^0.4.0"
"@volar/typescript@2.2.0-alpha.8": "@volar/typescript@2.2.0-alpha.10":
version "2.2.0-alpha.8" version "2.2.0-alpha.10"
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.2.0-alpha.8.tgz#83a056c52995b4142364be3dda41d955a96f7356" resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.2.0-alpha.10.tgz#14c002a3549ff3adcf9306933f4bf81e80422eff"
integrity sha512-RLbRDI+17CiayHZs9HhSzlH0FhLl/+XK6o2qoiw2o2GGKcyD1aDoY6AcMd44acYncTOrqoTNoY6LuCiRyiJiGg== integrity sha512-GCa0vTVVdA9ULUsu2Rx7jwsIuyZQPvPVT9o3NrANTbYv+523Ao1gv3glC5vzNSDPM6bUl37r94HbCj7KINQr+g==
dependencies: dependencies:
"@volar/language-core" "2.2.0-alpha.8" "@volar/language-core" "2.2.0-alpha.10"
path-browserify "^1.0.1" path-browserify "^1.0.1"
"@vue/compiler-core@3.2.47": "@vue/compiler-core@3.2.47":
@ -600,6 +610,17 @@
estree-walker "^2.0.2" estree-walker "^2.0.2"
source-map-js "^1.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": "@vue/compiler-dom@3.2.47":
version "3.2.47" version "3.2.47"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305" 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/compiler-core" "3.2.47"
"@vue/shared" "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" version "3.4.21"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz#0077c355e2008207283a5a87d510330d22546803" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz#0077c355e2008207283a5a87d510330d22546803"
integrity sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA== integrity sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==
@ -616,20 +645,20 @@
"@vue/compiler-core" "3.4.21" "@vue/compiler-core" "3.4.21"
"@vue/shared" "3.4.21" "@vue/shared" "3.4.21"
"@vue/compiler-sfc@3.4.21": "@vue/compiler-sfc@3.4.25":
version "3.4.21" version "3.4.25"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz#4af920dc31ab99e1ff5d152b5fe0ad12181145b2" resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.25.tgz#ceab148f81571c8b251e8a8b75a9972addf1db8b"
integrity sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ== integrity sha512-m7rryuqzIoQpOBZ18wKyq05IwL6qEpZxFZfRxlNYuIPDqywrXQxgUwLXIvoU72gs6cRdY6wHD0WVZIFE4OEaAQ==
dependencies: dependencies:
"@babel/parser" "^7.23.9" "@babel/parser" "^7.24.4"
"@vue/compiler-core" "3.4.21" "@vue/compiler-core" "3.4.25"
"@vue/compiler-dom" "3.4.21" "@vue/compiler-dom" "3.4.25"
"@vue/compiler-ssr" "3.4.21" "@vue/compiler-ssr" "3.4.25"
"@vue/shared" "3.4.21" "@vue/shared" "3.4.25"
estree-walker "^2.0.2" estree-walker "^2.0.2"
magic-string "^0.30.7" magic-string "^0.30.10"
postcss "^8.4.35" postcss "^8.4.38"
source-map-js "^1.0.2" source-map-js "^1.2.0"
"@vue/compiler-sfc@^3.2.47": "@vue/compiler-sfc@^3.2.47":
version "3.2.47" version "3.2.47"
@ -655,13 +684,13 @@
"@vue/compiler-dom" "3.2.47" "@vue/compiler-dom" "3.2.47"
"@vue/shared" "3.2.47" "@vue/shared" "3.2.47"
"@vue/compiler-ssr@3.4.21": "@vue/compiler-ssr@3.4.25":
version "3.4.21" version "3.4.25"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz#b84ae64fb9c265df21fc67f7624587673d324fef" resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.25.tgz#7fdd540bfdf2d4a3d6cb107b7ba4c77228d36331"
integrity sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q== integrity sha512-H2ohvM/Pf6LelGxDBnfbbXFPyM4NE3hrw0e/EpwuSiYu8c819wx+SVGdJ65p/sFrYDd6OnSDxN1MB2mN07hRSQ==
dependencies: dependencies:
"@vue/compiler-dom" "3.4.21" "@vue/compiler-dom" "3.4.25"
"@vue/shared" "3.4.21" "@vue/shared" "3.4.25"
"@vue/devtools-api@^6.5.0": "@vue/devtools-api@^6.5.0":
version "6.5.0" version "6.5.0"
@ -682,12 +711,12 @@
"@typescript-eslint/parser" "^7.1.1" "@typescript-eslint/parser" "^7.1.1"
vue-eslint-parser "^9.3.1" vue-eslint-parser "^9.3.1"
"@vue/language-core@2.0.13": "@vue/language-core@2.0.14":
version "2.0.13" version "2.0.14"
resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.0.13.tgz#2d1638b882011187b4b57115425d52b0901acab5" resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.0.14.tgz#99d1dcd7df8a859e12606e80863b3cb4cf045f9e"
integrity sha512-oQgM+BM66SU5GKtUMLQSQN0bxHFkFpLSSAiY87wVziPaiNQZuKVDt/3yA7GB9PiQw0y/bTNL0bOc0jM/siYjKg== integrity sha512-3q8mHSNcGTR7sfp2X6jZdcb4yt8AjBXAfKk0qkZIh7GAJxOnoZ10h5HToZglw4ToFvAnq+xu/Z2FFbglh9Icag==
dependencies: dependencies:
"@volar/language-core" "2.2.0-alpha.8" "@volar/language-core" "2.2.0-alpha.10"
"@vue/compiler-dom" "^3.4.0" "@vue/compiler-dom" "^3.4.0"
"@vue/shared" "^3.4.0" "@vue/shared" "^3.4.0"
computeds "^0.0.1" computeds "^0.0.1"
@ -706,37 +735,37 @@
estree-walker "^2.0.2" estree-walker "^2.0.2"
magic-string "^0.25.7" magic-string "^0.25.7"
"@vue/reactivity@3.4.21": "@vue/reactivity@3.4.25":
version "3.4.21" version "3.4.25"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.21.tgz#affd3415115b8ebf4927c8d2a0d6a24bccfa9f02" resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.25.tgz#74983b146e06ce3341d15382669350125375d36f"
integrity sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw== integrity sha512-mKbEtKr1iTxZkAG3vm3BtKHAOhuI4zzsVcN0epDldU/THsrvfXRKzq+lZnjczZGnTdh3ojd86/WrP+u9M51pWQ==
dependencies: dependencies:
"@vue/shared" "3.4.21" "@vue/shared" "3.4.25"
"@vue/runtime-core@3.4.21": "@vue/runtime-core@3.4.25":
version "3.4.21" version "3.4.25"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.21.tgz#3749c3f024a64c4c27ecd75aea4ca35634db0062" resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.25.tgz#c5545d469ae0827dc471a1376f97c6ace41081ec"
integrity sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA== integrity sha512-3qhsTqbEh8BMH3pXf009epCI5E7bKu28fJLi9O6W+ZGt/6xgSfMuGPqa5HRbUxLoehTNp5uWvzCr60KuiRIL0Q==
dependencies: dependencies:
"@vue/reactivity" "3.4.21" "@vue/reactivity" "3.4.25"
"@vue/shared" "3.4.21" "@vue/shared" "3.4.25"
"@vue/runtime-dom@3.4.21": "@vue/runtime-dom@3.4.25":
version "3.4.21" version "3.4.25"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz#91f867ef64eff232cac45095ab28ebc93ac74588" resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.25.tgz#9bc195e4860edcd0db4303cbba5a160922b963fd"
integrity sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw== integrity sha512-ode0sj77kuwXwSc+2Yhk8JMHZh1sZp9F/51wdBiz3KGaWltbKtdihlJFhQG4H6AY+A06zzeMLkq6qu8uDSsaoA==
dependencies: dependencies:
"@vue/runtime-core" "3.4.21" "@vue/runtime-core" "3.4.25"
"@vue/shared" "3.4.21" "@vue/shared" "3.4.25"
csstype "^3.1.3" csstype "^3.1.3"
"@vue/server-renderer@3.4.21": "@vue/server-renderer@3.4.25":
version "3.4.21" version "3.4.25"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.21.tgz#150751579d26661ee3ed26a28604667fa4222a97" resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.25.tgz#6cfc96ee631104951d5d6c09a8f1e7cef3ef3972"
integrity sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg== integrity sha512-8VTwq0Zcu3K4dWV0jOwIVINESE/gha3ifYCOKEhxOj6MEl5K5y8J8clQncTcDhKF+9U765nRw4UdUEXvrGhyVQ==
dependencies: dependencies:
"@vue/compiler-ssr" "3.4.21" "@vue/compiler-ssr" "3.4.25"
"@vue/shared" "3.4.21" "@vue/shared" "3.4.25"
"@vue/shared@3.2.47": "@vue/shared@3.2.47":
version "3.2.47" version "3.2.47"
@ -748,6 +777,11 @@
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.21.tgz#de526a9059d0a599f0b429af7037cd0c3ed7d5a1" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.21.tgz#de526a9059d0a599f0b429af7037cd0c3ed7d5a1"
integrity sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g== 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": "@vue/tsconfig@^0.5.1":
version "0.5.1" version "0.5.1"
resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.5.1.tgz#3124ec16cc0c7e04165b88dc091e6b97782fffa9" resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.5.1.tgz#3124ec16cc0c7e04165b88dc091e6b97782fffa9"
@ -1124,10 +1158,10 @@ escodegen@^2.1.0:
optionalDependencies: optionalDependencies:
source-map "~0.6.1" source-map "~0.6.1"
eslint-plugin-vue@^9.24.1: eslint-plugin-vue@^9.25.0:
version "9.24.1" version "9.25.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.24.1.tgz#0d90330c939f9dd2f4c759da5a2ad91dc1c8bac4" resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.25.0.tgz#615cb7bb6d0e2140d21840b9aa51dce69e803e7a"
integrity sha512-wk3SuwmS1pZdcuJlokGYEi/buDOwD6KltvhIZyOnpJ/378dcQ4zchu9PAMbbLAaydCz1iYc5AozszcOOgZIIOg== integrity sha512-tDWlx14bVe6Bs+Nnh3IGrD+hb11kf2nukfm6jLsmJIhmiRQ1SUaksvwY9U5MvPB0pcrg0QK0xapQkfITs3RKOA==
dependencies: dependencies:
"@eslint-community/eslint-utils" "^4.4.0" "@eslint-community/eslint-utils" "^4.4.0"
globals "^13.24.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" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw== integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
eslint@^9.0.0: eslint@^9.1.1:
version "9.0.0" version "9.1.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.0.0.tgz#6270548758e390343f78c8afd030566d86927d40" resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.1.1.tgz#39ec657ccd12813cb4a1dab2f9229dcc6e468271"
integrity sha512-IMryZ5SudxzQvuod6rUdIUz29qFItWx281VhtFVc2Psy/ZhlCeD/5DT6lBIJ4H3G+iamGJoTln1v+QSuPw0p7Q== integrity sha512-b4cRQ0BeZcSEzPpY2PjFY70VbO32K7BStTGtBsnIGdTSEEQzBi8hPBcGQmTG2zUvFr9uLe0TK42bw8YszuHEqg==
dependencies: dependencies:
"@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.6.1" "@eslint-community/regexpp" "^4.6.1"
"@eslint/eslintrc" "^3.0.2" "@eslint/eslintrc" "^3.0.2"
"@eslint/js" "9.0.0" "@eslint/js" "9.1.1"
"@humanwhocodes/config-array" "^0.12.3" "@humanwhocodes/config-array" "^0.13.0"
"@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/module-importer" "^1.0.1"
"@humanwhocodes/retry" "^0.2.3"
"@nodelib/fs.walk" "^1.2.8" "@nodelib/fs.walk" "^1.2.8"
ajv "^6.12.4" ajv "^6.12.4"
chalk "^4.0.0" chalk "^4.0.0"
@ -1200,7 +1235,6 @@ eslint@^9.0.0:
file-entry-cache "^8.0.0" file-entry-cache "^8.0.0"
find-up "^5.0.0" find-up "^5.0.0"
glob-parent "^6.0.2" glob-parent "^6.0.2"
graphemer "^1.4.0"
ignore "^5.2.0" ignore "^5.2.0"
imurmurhash "^0.1.4" imurmurhash "^0.1.4"
is-glob "^4.0.0" is-glob "^4.0.0"
@ -1786,10 +1820,10 @@ magic-string@^0.25.7:
dependencies: dependencies:
sourcemap-codec "^1.4.8" sourcemap-codec "^1.4.8"
magic-string@^0.30.7: magic-string@^0.30.10:
version "0.30.7" version "0.30.10"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.7.tgz#0cecd0527d473298679da95a2d7aeb8c64048505" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e"
integrity sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA== integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==
dependencies: dependencies:
"@jridgewell/sourcemap-codec" "^1.4.15" "@jridgewell/sourcemap-codec" "^1.4.15"
@ -2056,15 +2090,6 @@ postcss@^8.1.10:
picocolors "^1.0.0" picocolors "^1.0.0"
source-map-js "^1.0.2" 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: postcss@^8.4.38:
version "8.4.38" version "8.4.38"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" 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" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
terser@^5.30.3: terser@^5.30.4:
version "5.30.3" version "5.30.4"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.30.3.tgz#f1bb68ded42408c316b548e3ec2526d7dd03f4d2" resolved "https://registry.yarnpkg.com/terser/-/terser-5.30.4.tgz#62b4d16a819424e6317fd5ceffb4ee8dc769803a"
integrity sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA== integrity sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==
dependencies: dependencies:
"@jridgewell/source-map" "^0.3.3" "@jridgewell/source-map" "^0.3.3"
acorn "^8.8.2" 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" 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== integrity sha512-d0QaHH9kS93J25SwRqJNEfE29PSuQS5jn51y9N9i2Yoq0FRO7rjuTeLvjM5zwklZlRrIn6SUdtOEDKyHokgJZg==
vite@^5.2.8: vite@^5.2.10:
version "5.2.8" version "5.2.10"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.8.tgz#a99e09939f1a502992381395ce93efa40a2844aa" resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.10.tgz#2ac927c91e99d51b376a5c73c0e4b059705f5bd7"
integrity sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA== integrity sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==
dependencies: dependencies:
esbuild "^0.20.1" esbuild "^0.20.1"
postcss "^8.4.38" postcss "^8.4.38"
@ -2531,19 +2556,19 @@ vue-eslint-parser@^9.4.2:
lodash "^4.17.21" lodash "^4.17.21"
semver "^7.3.6" semver "^7.3.6"
vue-i18n@^9.12.0: vue-i18n@^9.13.1:
version "9.12.0" version "9.13.1"
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.12.0.tgz#8d073b3d7b92e822dcc3268946af4ecf14b778b3" resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.13.1.tgz#a292c8021b7be604ebfca5609ae1f8fafe5c36d7"
integrity sha512-rUxCKTws8NH3XP98W71GA7btAQdAuO7j6BC5y5s1bTNQYo/CIgZQf+p7d1Zo5bo/3v8TIq9aSUMDjpfgKsC3Uw== integrity sha512-mh0GIxx0wPtPlcB1q4k277y0iKgo25xmDPWioVVYanjPufDBpvu5ySTjP5wOrSvlYQ2m1xI+CFhGdauv/61uQg==
dependencies: dependencies:
"@intlify/core-base" "9.12.0" "@intlify/core-base" "9.13.1"
"@intlify/shared" "9.12.0" "@intlify/shared" "9.13.1"
"@vue/devtools-api" "^6.5.0" "@vue/devtools-api" "^6.5.0"
vue-router@^4.3.0: vue-router@^4.3.2:
version "4.3.0" version "4.3.2"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.3.0.tgz#d5913f27bf68a0a178ee798c3c88be471811a235" resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.3.2.tgz#08096c7765dacc6832f58e35f7a081a8b34116a7"
integrity sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ== integrity sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==
dependencies: dependencies:
"@vue/devtools-api" "^6.5.1" "@vue/devtools-api" "^6.5.1"
@ -2555,25 +2580,25 @@ vue-template-compiler@^2.7.14:
de-indent "^1.0.2" de-indent "^1.0.2"
he "^1.2.0" he "^1.2.0"
vue-tsc@^2.0.13: vue-tsc@^2.0.14:
version "2.0.13" version "2.0.14"
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-2.0.13.tgz#6ee557705456442e0f43ec0d1774ebf5ffec54f1" resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-2.0.14.tgz#5a8a652bcba30fa6fd8f7ac6af5df8e387f25cd8"
integrity sha512-a3nL3FvguCWVJUQW/jFrUxdeUtiEkbZoQjidqvMeBK//tuE2w6NWQAbdrEpY2+6nSa4kZoKZp8TZUMtHpjt4mQ== integrity sha512-DgAO3U1cnCHOUO7yB35LENbkapeRsBZ7Ugq5hGz/QOHny0+1VQN8eSwSBjYbjLVPfvfw6EY7sNPjbuHHUhckcg==
dependencies: dependencies:
"@volar/typescript" "2.2.0-alpha.8" "@volar/typescript" "2.2.0-alpha.10"
"@vue/language-core" "2.0.13" "@vue/language-core" "2.0.14"
semver "^7.5.4" semver "^7.5.4"
vue@^3.4.21: vue@^3.4.25:
version "3.4.21" version "3.4.25"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.21.tgz#69ec30e267d358ee3a0ce16612ba89e00aaeb731" resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.25.tgz#e59d4ed36389647b52ff2fd7aa84bb6691f4205b"
integrity sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA== integrity sha512-HWyDqoBHMgav/OKiYA2ZQg+kjfMgLt/T0vg4cbIF7JbXAjDexRf5JRg+PWAfrAkSmTd2I8aPSXtooBFWHB98cg==
dependencies: dependencies:
"@vue/compiler-dom" "3.4.21" "@vue/compiler-dom" "3.4.25"
"@vue/compiler-sfc" "3.4.21" "@vue/compiler-sfc" "3.4.25"
"@vue/runtime-dom" "3.4.21" "@vue/runtime-dom" "3.4.25"
"@vue/server-renderer" "3.4.21" "@vue/server-renderer" "3.4.25"
"@vue/shared" "3.4.21" "@vue/shared" "3.4.25"
webpack-sources@^3.2.3: webpack-sources@^3.2.3:
version "3.2.3" version "3.2.3"

Binary file not shown.

Binary file not shown.