Merge branch 'development'
This commit is contained in:
commit
7c66965ced
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@ -3,6 +3,7 @@
|
|||||||
// for the documentation about the extensions.json format
|
// for the documentation about the extensions.json format
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"DavidAnson.vscode-markdownlint",
|
"DavidAnson.vscode-markdownlint",
|
||||||
|
"EditorConfig.EditorConfig",
|
||||||
"Vue.volar",
|
"Vue.volar",
|
||||||
"Vue.vscode-typescript-vue-plugin",
|
"Vue.vscode-typescript-vue-plugin",
|
||||||
"platformio.platformio-ide"
|
"platformio.platformio-ide"
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#define CONFIG_FILENAME "/config.json"
|
#define CONFIG_FILENAME "/config.json"
|
||||||
#define CONFIG_VERSION 0x00011a00 // 0.1.26 // make sure to clean all after change
|
#define CONFIG_VERSION 0x00011b00 // 0.1.27 // make sure to clean all after change
|
||||||
|
|
||||||
#define WIFI_MAX_SSID_STRLEN 32
|
#define WIFI_MAX_SSID_STRLEN 32
|
||||||
#define WIFI_MAX_PASSWORD_STRLEN 64
|
#define WIFI_MAX_PASSWORD_STRLEN 64
|
||||||
@ -152,6 +152,7 @@ struct CONFIG_T {
|
|||||||
struct {
|
struct {
|
||||||
int8_t PaLevel;
|
int8_t PaLevel;
|
||||||
uint32_t Frequency;
|
uint32_t Frequency;
|
||||||
|
uint8_t CountryMode;
|
||||||
} Cmt;
|
} Cmt;
|
||||||
bool VerboseLogging;
|
bool VerboseLogging;
|
||||||
} Dtu;
|
} Dtu;
|
||||||
@ -167,7 +168,10 @@ struct CONFIG_T {
|
|||||||
uint8_t Rotation;
|
uint8_t Rotation;
|
||||||
uint8_t Contrast;
|
uint8_t Contrast;
|
||||||
uint8_t Language;
|
uint8_t Language;
|
||||||
uint32_t DiagramDuration;
|
struct {
|
||||||
|
uint32_t Duration;
|
||||||
|
uint8_t Mode;
|
||||||
|
} Diagram;
|
||||||
} Display;
|
} Display;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|||||||
@ -6,6 +6,14 @@
|
|||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
#include <U8g2lib.h>
|
#include <U8g2lib.h>
|
||||||
|
|
||||||
|
#define CHART_HEIGHT 20 // chart area hight in pixels
|
||||||
|
#define CHART_WIDTH 47 // chart area width in pixels
|
||||||
|
|
||||||
|
// Left-Upper position of diagram is drawn
|
||||||
|
// (text of Y-axis is display left of that pos)
|
||||||
|
#define CHART_POSX 80
|
||||||
|
#define CHART_POSY 0
|
||||||
|
|
||||||
enum DisplayType_t {
|
enum DisplayType_t {
|
||||||
None,
|
None,
|
||||||
PCD8544,
|
PCD8544,
|
||||||
@ -15,6 +23,13 @@ enum DisplayType_t {
|
|||||||
DisplayType_Max,
|
DisplayType_Max,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum DiagramMode_t {
|
||||||
|
Off,
|
||||||
|
Small,
|
||||||
|
Fullscreen,
|
||||||
|
DisplayMode_Max,
|
||||||
|
};
|
||||||
|
|
||||||
class DisplayGraphicClass {
|
class DisplayGraphicClass {
|
||||||
public:
|
public:
|
||||||
DisplayGraphicClass();
|
DisplayGraphicClass();
|
||||||
@ -25,6 +40,7 @@ public:
|
|||||||
void setStatus(const bool turnOn);
|
void setStatus(const bool turnOn);
|
||||||
void setOrientation(const uint8_t rotation = DISPLAY_ROTATION);
|
void setOrientation(const uint8_t rotation = DISPLAY_ROTATION);
|
||||||
void setLanguage(const uint8_t language);
|
void setLanguage(const uint8_t language);
|
||||||
|
void setDiagramMode(DiagramMode_t mode);
|
||||||
void setStartupDisplay();
|
void setStartupDisplay();
|
||||||
|
|
||||||
DisplayGraphicDiagramClass& Diagram();
|
DisplayGraphicDiagramClass& Diagram();
|
||||||
@ -47,10 +63,11 @@ private:
|
|||||||
bool _displayTurnedOn;
|
bool _displayTurnedOn;
|
||||||
|
|
||||||
DisplayType_t _display_type = DisplayType_t::None;
|
DisplayType_t _display_type = DisplayType_t::None;
|
||||||
|
DiagramMode_t _diagram_mode = DiagramMode_t::Off;
|
||||||
uint8_t _display_language = DISPLAY_LANGUAGE;
|
uint8_t _display_language = DISPLAY_LANGUAGE;
|
||||||
uint8_t _mExtra;
|
uint8_t _mExtra;
|
||||||
uint16_t _period = 1000;
|
const uint16_t _period = 1000;
|
||||||
uint16_t _interval = 60000; // interval at which to power save (milliseconds)
|
const uint16_t _interval = 60000; // interval at which to power save (milliseconds)
|
||||||
uint32_t _previousMillis = 0;
|
uint32_t _previousMillis = 0;
|
||||||
char _fmtText[32];
|
char _fmtText[32];
|
||||||
bool _isLarge = false;
|
bool _isLarge = false;
|
||||||
|
|||||||
@ -5,20 +5,14 @@
|
|||||||
#include <U8g2lib.h>
|
#include <U8g2lib.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#define CHART_HEIGHT 20 // chart area hight in pixels
|
#define MAX_DATAPOINTS 128
|
||||||
#define CHART_WIDTH 47 // chart area width in pixels
|
|
||||||
|
|
||||||
// Left-Upper position of diagram is drawn
|
|
||||||
// (text of Y-axis is display left of that pos)
|
|
||||||
#define DIAG_POSX 80
|
|
||||||
#define DIAG_POSY 0
|
|
||||||
|
|
||||||
class DisplayGraphicDiagramClass {
|
class DisplayGraphicDiagramClass {
|
||||||
public:
|
public:
|
||||||
DisplayGraphicDiagramClass();
|
DisplayGraphicDiagramClass();
|
||||||
|
|
||||||
void init(Scheduler& scheduler, U8G2* display);
|
void init(Scheduler& scheduler, U8G2* display);
|
||||||
void redraw(uint8_t screenSaverOffsetX);
|
void redraw(uint8_t screenSaverOffsetX, uint8_t xPos, uint8_t yPos, uint8_t width, uint8_t height, bool isFullscreen);
|
||||||
|
|
||||||
void updatePeriod();
|
void updatePeriod();
|
||||||
|
|
||||||
@ -26,15 +20,17 @@ private:
|
|||||||
void averageLoop();
|
void averageLoop();
|
||||||
void dataPointLoop();
|
void dataPointLoop();
|
||||||
|
|
||||||
static uint32_t getSecondsPerDot();
|
uint32_t getSecondsPerDot();
|
||||||
|
|
||||||
Task _averageTask;
|
Task _averageTask;
|
||||||
Task _dataPointTask;
|
Task _dataPointTask;
|
||||||
|
|
||||||
U8G2* _display = nullptr;
|
U8G2* _display = nullptr;
|
||||||
std::array<float, CHART_WIDTH> _graphValues = {};
|
std::array<float, MAX_DATAPOINTS> _graphValues = {};
|
||||||
uint8_t _graphValuesCount = 0;
|
uint8_t _graphValuesCount = 0;
|
||||||
|
|
||||||
|
uint8_t _chartWidth = MAX_DATAPOINTS;
|
||||||
|
|
||||||
float _iRunningAverage = 0;
|
float _iRunningAverage = 0;
|
||||||
uint16_t _iRunningAverageCnt = 0;
|
uint16_t _iRunningAverageCnt = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,17 +10,24 @@ public:
|
|||||||
void init();
|
void init();
|
||||||
bool updateValues();
|
bool updateValues();
|
||||||
float getPower(int8_t phase);
|
float getPower(int8_t phase);
|
||||||
bool httpRequest(const char* url, Auth authType, const char* username, const char* password, const char* httpHeader, const char* httpValue, uint32_t timeout,
|
char httpPowerMeterError[256];
|
||||||
char* response, size_t responseSize, char* error, size_t errorSize);
|
bool queryPhase(int phase, const String& url, Auth authType, const char* username, const char* password,
|
||||||
float getFloatValueByJsonPath(const char* jsonString, const char* jsonPath, float &value);
|
const char* httpHeader, const char* httpValue, uint32_t timeout, const char* jsonPath);
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void extractUrlComponents(const String& url, String& protocol, String& hostname, String& uri);
|
float power[POWERMETER_MAX_PHASES];
|
||||||
void prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue);
|
HTTPClient httpClient;
|
||||||
HTTPClient httpClient;
|
String httpResponse;
|
||||||
float power[POWERMETER_MAX_PHASES];
|
bool httpRequest(int phase, WiFiClient &wifiClient, const String& host, const String& uri, bool https, Auth authType, const char* username,
|
||||||
String sha256(const String& data);
|
const char* password, const char* httpHeader, const char* httpValue, uint32_t timeout, const char* jsonPath);
|
||||||
|
void extractUrlComponents(const String& url, String& protocol, String& host, String& uri);
|
||||||
|
String extractParam(String& authReq, const String& param, const char delimit);
|
||||||
|
String getcNonce(const int len);
|
||||||
|
String getDigestAuth(String& authReq, const String& username, const String& password, const String& method, const String& uri, unsigned int counter);
|
||||||
|
bool tryGetFloatValueForPhase(int phase, int httpCode, const char* jsonPath);
|
||||||
|
void prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue);
|
||||||
|
String sha256(const String& data);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern HttpPowerMeterClass HttpPowerMeter;
|
extern HttpPowerMeterClass HttpPowerMeter;
|
||||||
|
|||||||
@ -13,4 +13,5 @@ private:
|
|||||||
void onDtuAdminPost(AsyncWebServerRequest* request);
|
void onDtuAdminPost(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
AsyncWebServer* _server;
|
||||||
|
bool _performReload = false;
|
||||||
};
|
};
|
||||||
@ -15,6 +15,7 @@ enum WebApiError {
|
|||||||
DtuPollZero,
|
DtuPollZero,
|
||||||
DtuInvalidPowerLevel,
|
DtuInvalidPowerLevel,
|
||||||
DtuInvalidCmtFrequency,
|
DtuInvalidCmtFrequency,
|
||||||
|
DtuInvalidCmtCountry,
|
||||||
|
|
||||||
ConfigBase = 3000,
|
ConfigBase = 3000,
|
||||||
ConfigNotDeleted,
|
ConfigNotDeleted,
|
||||||
|
|||||||
@ -83,7 +83,8 @@
|
|||||||
#define DTU_POLL_INTERVAL 5U
|
#define DTU_POLL_INTERVAL 5U
|
||||||
#define DTU_NRF_PA_LEVEL 0U
|
#define DTU_NRF_PA_LEVEL 0U
|
||||||
#define DTU_CMT_PA_LEVEL 0
|
#define DTU_CMT_PA_LEVEL 0
|
||||||
#define DTU_CMT_FREQUENCY 865000U
|
#define DTU_CMT_FREQUENCY 865000000U
|
||||||
|
#define DTU_CMT_COUNTRY_MODE 0U
|
||||||
|
|
||||||
#define MQTT_HASS_ENABLED false
|
#define MQTT_HASS_ENABLED false
|
||||||
#define MQTT_HASS_EXPIRE true
|
#define MQTT_HASS_EXPIRE true
|
||||||
@ -99,9 +100,58 @@
|
|||||||
#define DISPLAY_CONTRAST 60U
|
#define DISPLAY_CONTRAST 60U
|
||||||
#define DISPLAY_LANGUAGE 0U
|
#define DISPLAY_LANGUAGE 0U
|
||||||
#define DISPLAY_DIAGRAM_DURATION (10UL * 60UL * 60UL)
|
#define DISPLAY_DIAGRAM_DURATION (10UL * 60UL * 60UL)
|
||||||
|
#define DISPLAY_DIAGRAM_MODE 1U
|
||||||
|
|
||||||
#define REACHABLE_THRESHOLD 2U
|
#define REACHABLE_THRESHOLD 2U
|
||||||
|
|
||||||
|
#define MAX_INVERTER_LIMIT 2250
|
||||||
|
#define VEDIRECT_ENABLED false
|
||||||
|
#define VEDIRECT_VERBOSE_LOGGING false
|
||||||
|
#define VEDIRECT_UPDATESONLY true
|
||||||
|
|
||||||
|
#define POWERMETER_ENABLED false
|
||||||
|
#define POWERMETER_INTERVAL 10
|
||||||
|
#define POWERMETER_SOURCE 2
|
||||||
|
#define POWERMETER_SDMBAUDRATE 9600
|
||||||
|
#define POWERMETER_SDMADDRESS 1
|
||||||
|
|
||||||
|
|
||||||
|
#define POWERLIMITER_ENABLED false
|
||||||
|
#define POWERLIMITER_SOLAR_PASSTHROUGH_ENABLED true
|
||||||
|
#define POWERLIMITER_SOLAR_PASSTHROUGH_LOSSES 3
|
||||||
|
#define POWERLIMITER_BATTERY_DRAIN_STRATEGY 0
|
||||||
|
#define POWERLIMITER_INTERVAL 10
|
||||||
|
#define POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER true
|
||||||
|
#define POWERLIMITER_INVERTER_ID 0
|
||||||
|
#define POWERLIMITER_INVERTER_CHANNEL_ID 0
|
||||||
|
#define POWERLIMITER_TARGET_POWER_CONSUMPTION 0
|
||||||
|
#define POWERLIMITER_TARGET_POWER_CONSUMPTION_HYSTERESIS 0
|
||||||
|
#define POWERLIMITER_LOWER_POWER_LIMIT 10
|
||||||
|
#define POWERLIMITER_UPPER_POWER_LIMIT 800
|
||||||
|
#define POWERLIMITER_BATTERY_SOC_START_THRESHOLD 80
|
||||||
|
#define POWERLIMITER_BATTERY_SOC_STOP_THRESHOLD 20
|
||||||
|
#define POWERLIMITER_VOLTAGE_START_THRESHOLD 50.0
|
||||||
|
#define POWERLIMITER_VOLTAGE_STOP_THRESHOLD 49.0
|
||||||
|
#define POWERLIMITER_VOLTAGE_LOAD_CORRECTION_FACTOR 0.001
|
||||||
|
#define POWERLIMITER_RESTART_HOUR -1
|
||||||
|
#define POWERLIMITER_FULL_SOLAR_PASSTHROUGH_SOC 100
|
||||||
|
#define POWERLIMITER_FULL_SOLAR_PASSTHROUGH_START_VOLTAGE 100.0
|
||||||
|
#define POWERLIMITER_FULL_SOLAR_PASSTHROUGH_STOP_VOLTAGE 100.0
|
||||||
|
|
||||||
|
#define BATTERY_ENABLED false
|
||||||
|
#define BATTERY_PROVIDER 0 // Pylontech CAN receiver
|
||||||
|
#define BATTERY_JKBMS_INTERFACE 0
|
||||||
|
#define BATTERY_JKBMS_POLLING_INTERVAL 5
|
||||||
|
|
||||||
|
#define HUAWEI_ENABLED false
|
||||||
|
#define HUAWEI_CAN_CONTROLLER_FREQUENCY 8000000UL
|
||||||
|
#define HUAWEI_AUTO_POWER_VOLTAGE_LIMIT 42.0
|
||||||
|
#define HUAWEI_AUTO_POWER_ENABLE_VOLTAGE_LIMIT 42.0
|
||||||
|
#define HUAWEI_AUTO_POWER_LOWER_POWER_LIMIT 150
|
||||||
|
#define HUAWEI_AUTO_POWER_UPPER_POWER_LIMIT 2000
|
||||||
|
|
||||||
|
#define VERBOSE_LOGGING true
|
||||||
|
|
||||||
#define LED_BRIGHTNESS 100U
|
#define LED_BRIGHTNESS 100U
|
||||||
|
|
||||||
#define MAX_INVERTER_LIMIT 2250
|
#define MAX_INVERTER_LIMIT 2250
|
||||||
|
|||||||
@ -628,7 +628,7 @@ int CMT2300A_GetRssiDBm(void)
|
|||||||
* for fast frequency hopping operation.
|
* for fast frequency hopping operation.
|
||||||
* @param nChann: the frequency channel
|
* @param nChann: the frequency channel
|
||||||
* *********************************************************/
|
* *********************************************************/
|
||||||
void CMT2300A_SetFrequencyChannel(uint8_t nChann)
|
void CMT2300A_SetFrequencyChannel(const uint8_t nChann)
|
||||||
{
|
{
|
||||||
CMT2300A_WriteReg(CMT2300A_CUS_FREQ_CHNL, nChann);
|
CMT2300A_WriteReg(CMT2300A_CUS_FREQ_CHNL, nChann);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,7 +75,7 @@ void CMT2300A_EnableTxDinInvert(bool bEnable);
|
|||||||
bool CMT2300A_IsExist(void);
|
bool CMT2300A_IsExist(void);
|
||||||
uint8_t CMT2300A_GetRssiCode(void);
|
uint8_t CMT2300A_GetRssiCode(void);
|
||||||
int CMT2300A_GetRssiDBm(void);
|
int CMT2300A_GetRssiDBm(void);
|
||||||
void CMT2300A_SetFrequencyChannel(uint8_t nChann);
|
void CMT2300A_SetFrequencyChannel(const uint8_t nChann);
|
||||||
void CMT2300A_SetFrequencyStep(uint8_t nOffset);
|
void CMT2300A_SetFrequencyStep(uint8_t nOffset);
|
||||||
void CMT2300A_SetPayloadLength(uint16_t nLength);
|
void CMT2300A_SetPayloadLength(uint16_t nLength);
|
||||||
void CMT2300A_EnableLfosc(bool bEnable);
|
void CMT2300A_EnableLfosc(bool bEnable);
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
* @name CMT2300A_InitSpi
|
* @name CMT2300A_InitSpi
|
||||||
* @desc Initializes the CMT2300A SPI interface.
|
* @desc Initializes the CMT2300A SPI interface.
|
||||||
* *********************************************************/
|
* *********************************************************/
|
||||||
void CMT2300A_InitSpi(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, uint32_t spi_speed)
|
void CMT2300A_InitSpi(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const uint32_t spi_speed)
|
||||||
{
|
{
|
||||||
cmt_spi3_init(pin_sdio, pin_clk, pin_cs, pin_fcs, spi_speed);
|
cmt_spi3_init(pin_sdio, pin_clk, pin_cs, pin_fcs, spi_speed);
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ void CMT2300A_InitSpi(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin
|
|||||||
* @param addr: register address
|
* @param addr: register address
|
||||||
* @return Register value
|
* @return Register value
|
||||||
* *********************************************************/
|
* *********************************************************/
|
||||||
uint8_t CMT2300A_ReadReg(uint8_t addr)
|
uint8_t CMT2300A_ReadReg(const uint8_t addr)
|
||||||
{
|
{
|
||||||
return cmt_spi3_read(addr);
|
return cmt_spi3_read(addr);
|
||||||
}
|
}
|
||||||
@ -48,7 +48,7 @@ uint8_t CMT2300A_ReadReg(uint8_t addr)
|
|||||||
* @param addr: register address
|
* @param addr: register address
|
||||||
* dat: register value
|
* dat: register value
|
||||||
* *********************************************************/
|
* *********************************************************/
|
||||||
void CMT2300A_WriteReg(uint8_t addr, uint8_t dat)
|
void CMT2300A_WriteReg(const uint8_t addr, const uint8_t dat)
|
||||||
{
|
{
|
||||||
cmt_spi3_write(addr, dat);
|
cmt_spi3_write(addr, dat);
|
||||||
}
|
}
|
||||||
@ -59,7 +59,7 @@ void CMT2300A_WriteReg(uint8_t addr, uint8_t dat)
|
|||||||
* @param buf: buffer where to copy the FIFO read data
|
* @param buf: buffer where to copy the FIFO read data
|
||||||
* len: number of bytes to be read from the FIFO
|
* len: number of bytes to be read from the FIFO
|
||||||
* *********************************************************/
|
* *********************************************************/
|
||||||
void CMT2300A_ReadFifo(uint8_t buf[], uint16_t len)
|
void CMT2300A_ReadFifo(uint8_t buf[], const uint16_t len)
|
||||||
{
|
{
|
||||||
cmt_spi3_read_fifo(buf, len);
|
cmt_spi3_read_fifo(buf, len);
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ void CMT2300A_ReadFifo(uint8_t buf[], uint16_t len)
|
|||||||
* @param buf: buffer containing data to be put on the FIFO
|
* @param buf: buffer containing data to be put on the FIFO
|
||||||
* len: number of bytes to be written to the FIFO
|
* len: number of bytes to be written to the FIFO
|
||||||
* *********************************************************/
|
* *********************************************************/
|
||||||
void CMT2300A_WriteFifo(const uint8_t buf[], uint16_t len)
|
void CMT2300A_WriteFifo(const uint8_t buf[], const uint16_t len)
|
||||||
{
|
{
|
||||||
cmt_spi3_write_fifo(buf, len);
|
cmt_spi3_write_fifo(buf, len);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,13 +36,13 @@ extern "C" {
|
|||||||
#define CMT2300A_GetTickCount() millis()
|
#define CMT2300A_GetTickCount() millis()
|
||||||
/* ************************************************************************ */
|
/* ************************************************************************ */
|
||||||
|
|
||||||
void CMT2300A_InitSpi(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, uint32_t spi_speed);
|
void CMT2300A_InitSpi(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const uint32_t spi_speed);
|
||||||
|
|
||||||
uint8_t CMT2300A_ReadReg(uint8_t addr);
|
uint8_t CMT2300A_ReadReg(const uint8_t addr);
|
||||||
void CMT2300A_WriteReg(uint8_t addr, uint8_t dat);
|
void CMT2300A_WriteReg(const uint8_t addr, const uint8_t dat);
|
||||||
|
|
||||||
void CMT2300A_ReadFifo(uint8_t buf[], uint16_t len);
|
void CMT2300A_ReadFifo(uint8_t buf[], const uint16_t len);
|
||||||
void CMT2300A_WriteFifo(const uint8_t buf[], uint16_t len);
|
void CMT2300A_WriteFifo(const uint8_t buf[], const uint16_t len);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,14 +85,14 @@
|
|||||||
; RSSI Offset = 0
|
; RSSI Offset = 0
|
||||||
; RSSI Offset Sign = 0
|
; RSSI Offset Sign = 0
|
||||||
*/
|
*/
|
||||||
#ifndef __CMT2300A_PARAMS_H
|
#ifndef __CMT2300A_PARAMS_860_H
|
||||||
#define __CMT2300A_PARAMS_H
|
#define __CMT2300A_PARAMS_860_H
|
||||||
|
|
||||||
#include "cmt2300a_defs.h"
|
#include "cmt2300a_defs.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/* [CMT Bank] with RSSI offset of +- 0 (and Tx power double bit not set) */
|
/* [CMT Bank] with RSSI offset of +- 0 (and Tx power double bit not set) */
|
||||||
static uint8_t g_cmt2300aCmtBank[CMT2300A_CMT_BANK_SIZE] = {
|
static uint8_t g_cmt2300aCmtBank_860[CMT2300A_CMT_BANK_SIZE] = {
|
||||||
0x00,
|
0x00,
|
||||||
0x66,
|
0x66,
|
||||||
0xEC,
|
0xEC,
|
||||||
@ -108,7 +108,7 @@ static uint8_t g_cmt2300aCmtBank[CMT2300A_CMT_BANK_SIZE] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* [System Bank] */
|
/* [System Bank] */
|
||||||
static uint8_t g_cmt2300aSystemBank[CMT2300A_SYSTEM_BANK_SIZE] = {
|
static uint8_t g_cmt2300aSystemBank_860[CMT2300A_SYSTEM_BANK_SIZE] = {
|
||||||
0xAE,
|
0xAE,
|
||||||
0xE0,
|
0xE0,
|
||||||
0x35,
|
0x35,
|
||||||
@ -124,7 +124,7 @@ static uint8_t g_cmt2300aSystemBank[CMT2300A_SYSTEM_BANK_SIZE] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* [Frequency Bank] 860 MHz */
|
/* [Frequency Bank] 860 MHz */
|
||||||
static uint8_t g_cmt2300aFrequencyBank[CMT2300A_FREQUENCY_BANK_SIZE] = {
|
static uint8_t g_cmt2300aFrequencyBank_860[CMT2300A_FREQUENCY_BANK_SIZE] = {
|
||||||
0x42,
|
0x42,
|
||||||
0x32,
|
0x32,
|
||||||
0xCF,
|
0xCF,
|
||||||
@ -136,7 +136,7 @@ static uint8_t g_cmt2300aFrequencyBank[CMT2300A_FREQUENCY_BANK_SIZE] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* [Data Rate Bank] */
|
/* [Data Rate Bank] */
|
||||||
static uint8_t g_cmt2300aDataRateBank[CMT2300A_DATA_RATE_BANK_SIZE] = {
|
static uint8_t g_cmt2300aDataRateBank_860[CMT2300A_DATA_RATE_BANK_SIZE] = {
|
||||||
0xA6,
|
0xA6,
|
||||||
0xC9,
|
0xC9,
|
||||||
0x20,
|
0x20,
|
||||||
@ -164,7 +164,7 @@ static uint8_t g_cmt2300aDataRateBank[CMT2300A_DATA_RATE_BANK_SIZE] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* [Baseband Bank] - EU */
|
/* [Baseband Bank] - EU */
|
||||||
static uint8_t g_cmt2300aBasebandBank[CMT2300A_BASEBAND_BANK_SIZE] = {
|
static uint8_t g_cmt2300aBasebandBank_860[CMT2300A_BASEBAND_BANK_SIZE] = {
|
||||||
0x12,
|
0x12,
|
||||||
0x1E,
|
0x1E,
|
||||||
0x00,
|
0x00,
|
||||||
@ -197,7 +197,7 @@ static uint8_t g_cmt2300aBasebandBank[CMT2300A_BASEBAND_BANK_SIZE] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* [Tx Bank] 13 dBm */
|
/* [Tx Bank] 13 dBm */
|
||||||
static uint8_t g_cmt2300aTxBank[CMT2300A_TX_BANK_SIZE] = {
|
static uint8_t g_cmt2300aTxBank_860[CMT2300A_TX_BANK_SIZE] = {
|
||||||
0x70,
|
0x70,
|
||||||
0x4D,
|
0x4D,
|
||||||
0x06,
|
0x06,
|
||||||
214
lib/CMT2300a/cmt2300a_params_900.h
Normal file
214
lib/CMT2300a/cmt2300a_params_900.h
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
;---------------------------------------
|
||||||
|
; CMT2300A Configuration File
|
||||||
|
; Generated by CMOSTEK RFPDK 1.46
|
||||||
|
; 2023.03.17 23:16
|
||||||
|
;---------------------------------------
|
||||||
|
; Mode = Advanced
|
||||||
|
; Part Number = CMT2300A
|
||||||
|
; Frequency = 900.000 MHz
|
||||||
|
; Xtal Frequency = 26.0000 MHz
|
||||||
|
; Demodulation = GFSK
|
||||||
|
; AGC = On
|
||||||
|
; Data Rate = 20.0 kbps
|
||||||
|
; Deviation = 20.0 kHz
|
||||||
|
; Tx Xtal Tol. = 20 ppm
|
||||||
|
; Rx Xtal Tol. = 20 ppm
|
||||||
|
; TRx Matching Network Type = 20 dBm
|
||||||
|
; Tx Power = +13 dBm
|
||||||
|
; Gaussian BT = 0.5
|
||||||
|
; Bandwidth = Auto-Select kHz
|
||||||
|
; CDR Type = Counting
|
||||||
|
; CDR DR Range = NA
|
||||||
|
; AFC = On
|
||||||
|
; AFC Method = Auto-Select
|
||||||
|
; Data Representation = 0:F-low 1:F-high
|
||||||
|
; Rx Duty-Cycle = Off
|
||||||
|
; Tx Duty-Cycle = Off
|
||||||
|
; Sleep Timer = Off
|
||||||
|
; Sleep Time = NA
|
||||||
|
; Rx Timer = Off
|
||||||
|
; Rx Time T1 = NA
|
||||||
|
; Rx Time T2 = NA
|
||||||
|
; Rx Exit State = STBY
|
||||||
|
; Tx Exit State = STBY
|
||||||
|
; SLP Mode = Disable
|
||||||
|
; RSSI Valid Source = PJD
|
||||||
|
; PJD Window = 8 Jumps
|
||||||
|
; LFOSC Calibration = On
|
||||||
|
; Xtal Stable Time = 155 us
|
||||||
|
; RSSI Compare TH = NA
|
||||||
|
; Data Mode = Packet
|
||||||
|
; Whitening = Disable
|
||||||
|
; Whiten Type = NA
|
||||||
|
; Whiten Seed Type = NA
|
||||||
|
; Whiten Seed = NA
|
||||||
|
; Manchester = Disable
|
||||||
|
; Manchester Type = NA
|
||||||
|
; FEC = Enable
|
||||||
|
; FEC Type = x^3+x^2+1
|
||||||
|
; Tx Prefix Type = 0
|
||||||
|
; Tx Packet Number = 1
|
||||||
|
; Tx Packet Gap = 32
|
||||||
|
; Packet Type = Variable Length
|
||||||
|
; Node-Length Position = First Node, then Length
|
||||||
|
; Payload Bit Order = Start from msb
|
||||||
|
; Preamble Rx Size = 2
|
||||||
|
; Preamble Tx Size = 30
|
||||||
|
; Preamble Value = 170
|
||||||
|
; Preamble Unit = 8-bit
|
||||||
|
; Sync Size = 4-byte
|
||||||
|
; Sync Value = 1296587336
|
||||||
|
; Sync Tolerance = None
|
||||||
|
; Sync Manchester = Disable
|
||||||
|
; Node ID Size = NA
|
||||||
|
; Node ID Value = NA
|
||||||
|
; Node ID Mode = None
|
||||||
|
; Node ID Err Mask = Disable
|
||||||
|
; Node ID Free = Disable
|
||||||
|
; Payload Length = 32
|
||||||
|
; CRC Options = IBM-16
|
||||||
|
; CRC Seed = 0 crc_seed
|
||||||
|
; CRC Range = Entire Payload
|
||||||
|
; CRC Swap = Start from MSB
|
||||||
|
; CRC Bit Invert = Normal
|
||||||
|
; CRC Bit Order = Start from bit 15
|
||||||
|
; Dout Mute = Off
|
||||||
|
; Dout Adjust Mode = Disable
|
||||||
|
; Dout Adjust Percentage = NA
|
||||||
|
; Collision Detect = Off
|
||||||
|
; Collision Detect Offset = NA
|
||||||
|
; RSSI Detect Mode = At PREAM_OK
|
||||||
|
; RSSI Filter Setting = 32-tap
|
||||||
|
; RF Performance = High
|
||||||
|
; LBD Threshold = 2.4 V
|
||||||
|
; RSSI Offset = 0
|
||||||
|
; RSSI Offset Sign = 0
|
||||||
|
*/
|
||||||
|
#ifndef __CMT2300A_PARAMS_900_H
|
||||||
|
#define __CMT2300A_PARAMS_900_H
|
||||||
|
|
||||||
|
#include "cmt2300a_defs.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* [CMT Bank] with RSSI offset of +- 0 (and Tx power double bit not set) */
|
||||||
|
static uint8_t g_cmt2300aCmtBank_900[CMT2300A_CMT_BANK_SIZE] = {
|
||||||
|
0x00,
|
||||||
|
0x66,
|
||||||
|
0xEC,
|
||||||
|
0x1C,
|
||||||
|
0x70,
|
||||||
|
0x80,
|
||||||
|
0x14,
|
||||||
|
0x08,
|
||||||
|
0x11,
|
||||||
|
0x02,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* [System Bank] */
|
||||||
|
static uint8_t g_cmt2300aSystemBank_900[CMT2300A_SYSTEM_BANK_SIZE] = {
|
||||||
|
0xAE,
|
||||||
|
0xE0,
|
||||||
|
0x35,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xF4,
|
||||||
|
0x10,
|
||||||
|
0xE2,
|
||||||
|
0x42,
|
||||||
|
0x20,
|
||||||
|
0x0C,
|
||||||
|
0x81,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* [Frequency Bank] 900 MHz */
|
||||||
|
static uint8_t g_cmt2300aFrequencyBank_900[CMT2300A_FREQUENCY_BANK_SIZE] = {
|
||||||
|
0x45,
|
||||||
|
0x46,
|
||||||
|
0x0A,
|
||||||
|
0x84,
|
||||||
|
0x45,
|
||||||
|
0x3B,
|
||||||
|
0xB1,
|
||||||
|
0x13,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* [Data Rate Bank] */
|
||||||
|
static uint8_t g_cmt2300aDataRateBank_900[CMT2300A_DATA_RATE_BANK_SIZE] = {
|
||||||
|
0xA6,
|
||||||
|
0xC9,
|
||||||
|
0x20,
|
||||||
|
0x20,
|
||||||
|
0xD2,
|
||||||
|
0x35,
|
||||||
|
0x0C,
|
||||||
|
0x0B,
|
||||||
|
0x9F,
|
||||||
|
0x4B,
|
||||||
|
0x29,
|
||||||
|
0x29,
|
||||||
|
0xC0,
|
||||||
|
0x14,
|
||||||
|
0x05,
|
||||||
|
0x53,
|
||||||
|
0x10,
|
||||||
|
0x00,
|
||||||
|
0xB4,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* [Baseband Bank] - EU */
|
||||||
|
static uint8_t g_cmt2300aBasebandBank_900[CMT2300A_BASEBAND_BANK_SIZE] = {
|
||||||
|
0x12,
|
||||||
|
0x1E,
|
||||||
|
0x00,
|
||||||
|
0xAA,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x48,
|
||||||
|
0x5A,
|
||||||
|
0x48,
|
||||||
|
0x4D,
|
||||||
|
0x01,
|
||||||
|
0x1F,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xC3,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x60,
|
||||||
|
0xFF,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x1F,
|
||||||
|
0x10,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* [Tx Bank] 13 dBm */
|
||||||
|
static uint8_t g_cmt2300aTxBank_900[CMT2300A_TX_BANK_SIZE] = {
|
||||||
|
0x70,
|
||||||
|
0x4D,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x50,
|
||||||
|
0x00,
|
||||||
|
0x53,
|
||||||
|
0x09,
|
||||||
|
0x3F,
|
||||||
|
0x7F,
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,12 +1,13 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Thomas Basler and others
|
* Copyright (C) 2023-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "cmt2300wrapper.h"
|
#include "cmt2300wrapper.h"
|
||||||
#include "cmt2300a.h"
|
#include "cmt2300a.h"
|
||||||
#include "cmt2300a_params.h"
|
#include "cmt2300a_params_860.h"
|
||||||
|
#include "cmt2300a_params_900.h"
|
||||||
|
|
||||||
CMT2300A::CMT2300A(uint8_t pin_sdio, uint8_t pin_clk, uint8_t pin_cs, uint8_t pin_fcs, uint32_t spi_speed)
|
CMT2300A::CMT2300A(const uint8_t pin_sdio, const uint8_t pin_clk, const uint8_t pin_cs, const uint8_t pin_fcs, const uint32_t spi_speed)
|
||||||
{
|
{
|
||||||
_pin_sdio = pin_sdio;
|
_pin_sdio = pin_sdio;
|
||||||
_pin_clk = pin_clk;
|
_pin_clk = pin_clk;
|
||||||
@ -57,7 +58,7 @@ bool CMT2300A::available(void)
|
|||||||
) & CMT2300A_ReadReg(CMT2300A_CUS_INT_FLAG);
|
) & CMT2300A_ReadReg(CMT2300A_CUS_INT_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CMT2300A::read(void* buf, uint8_t len)
|
void CMT2300A::read(void* buf, const uint8_t len)
|
||||||
{
|
{
|
||||||
// Fetch the payload
|
// Fetch the payload
|
||||||
CMT2300A_ReadFifo(static_cast<uint8_t*>(buf), len);
|
CMT2300A_ReadFifo(static_cast<uint8_t*>(buf), len);
|
||||||
@ -65,7 +66,7 @@ void CMT2300A::read(void* buf, uint8_t len)
|
|||||||
CMT2300A_ClearInterruptFlags();
|
CMT2300A_ClearInterruptFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CMT2300A::write(const uint8_t* buf, uint8_t len)
|
bool CMT2300A::write(const uint8_t* buf, const uint8_t len)
|
||||||
{
|
{
|
||||||
CMT2300A_GoStby();
|
CMT2300A_GoStby();
|
||||||
CMT2300A_ClearInterruptFlags();
|
CMT2300A_ClearInterruptFlags();
|
||||||
@ -100,7 +101,7 @@ bool CMT2300A::write(const uint8_t* buf, uint8_t len)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CMT2300A::setChannel(uint8_t channel)
|
void CMT2300A::setChannel(const uint8_t channel)
|
||||||
{
|
{
|
||||||
CMT2300A_SetFrequencyChannel(channel);
|
CMT2300A_SetFrequencyChannel(channel);
|
||||||
}
|
}
|
||||||
@ -122,7 +123,7 @@ int CMT2300A::getRssiDBm()
|
|||||||
return CMT2300A_GetRssiDBm();
|
return CMT2300A_GetRssiDBm();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CMT2300A::setPALevel(int8_t level)
|
bool CMT2300A::setPALevel(const int8_t level)
|
||||||
{
|
{
|
||||||
uint16_t Tx_dBm_word;
|
uint16_t Tx_dBm_word;
|
||||||
switch (level) {
|
switch (level) {
|
||||||
@ -242,6 +243,22 @@ bool CMT2300A::rxFifoAvailable()
|
|||||||
) & CMT2300A_ReadReg(CMT2300A_CUS_INT_FLAG);
|
) & CMT2300A_ReadReg(CMT2300A_CUS_INT_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t CMT2300A::getBaseFrequency() const
|
||||||
|
{
|
||||||
|
return getBaseFrequency(_frequencyBand);
|
||||||
|
}
|
||||||
|
|
||||||
|
FrequencyBand_t CMT2300A::getFrequencyBand() const
|
||||||
|
{
|
||||||
|
return _frequencyBand;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMT2300A::setFrequencyBand(const FrequencyBand_t mode)
|
||||||
|
{
|
||||||
|
_frequencyBand = mode;
|
||||||
|
_init_radio();
|
||||||
|
}
|
||||||
|
|
||||||
void CMT2300A::flush_rx(void)
|
void CMT2300A::flush_rx(void)
|
||||||
{
|
{
|
||||||
CMT2300A_ClearRxFifo();
|
CMT2300A_ClearRxFifo();
|
||||||
@ -261,12 +278,24 @@ bool CMT2300A::_init_radio()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* config registers */
|
/* config registers */
|
||||||
CMT2300A_ConfigRegBank(CMT2300A_CMT_BANK_ADDR, g_cmt2300aCmtBank, CMT2300A_CMT_BANK_SIZE);
|
switch (_frequencyBand) {
|
||||||
CMT2300A_ConfigRegBank(CMT2300A_SYSTEM_BANK_ADDR, g_cmt2300aSystemBank, CMT2300A_SYSTEM_BANK_SIZE);
|
case FrequencyBand_t::BAND_900:
|
||||||
CMT2300A_ConfigRegBank(CMT2300A_FREQUENCY_BANK_ADDR, g_cmt2300aFrequencyBank, CMT2300A_FREQUENCY_BANK_SIZE);
|
CMT2300A_ConfigRegBank(CMT2300A_CMT_BANK_ADDR, g_cmt2300aCmtBank_900, CMT2300A_CMT_BANK_SIZE);
|
||||||
CMT2300A_ConfigRegBank(CMT2300A_DATA_RATE_BANK_ADDR, g_cmt2300aDataRateBank, CMT2300A_DATA_RATE_BANK_SIZE);
|
CMT2300A_ConfigRegBank(CMT2300A_SYSTEM_BANK_ADDR, g_cmt2300aSystemBank_900, CMT2300A_SYSTEM_BANK_SIZE);
|
||||||
CMT2300A_ConfigRegBank(CMT2300A_BASEBAND_BANK_ADDR, g_cmt2300aBasebandBank, CMT2300A_BASEBAND_BANK_SIZE);
|
CMT2300A_ConfigRegBank(CMT2300A_FREQUENCY_BANK_ADDR, g_cmt2300aFrequencyBank_900, CMT2300A_FREQUENCY_BANK_SIZE);
|
||||||
CMT2300A_ConfigRegBank(CMT2300A_TX_BANK_ADDR, g_cmt2300aTxBank, CMT2300A_TX_BANK_SIZE);
|
CMT2300A_ConfigRegBank(CMT2300A_DATA_RATE_BANK_ADDR, g_cmt2300aDataRateBank_900, CMT2300A_DATA_RATE_BANK_SIZE);
|
||||||
|
CMT2300A_ConfigRegBank(CMT2300A_BASEBAND_BANK_ADDR, g_cmt2300aBasebandBank_900, CMT2300A_BASEBAND_BANK_SIZE);
|
||||||
|
CMT2300A_ConfigRegBank(CMT2300A_TX_BANK_ADDR, g_cmt2300aTxBank_900, CMT2300A_TX_BANK_SIZE);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
CMT2300A_ConfigRegBank(CMT2300A_CMT_BANK_ADDR, g_cmt2300aCmtBank_860, CMT2300A_CMT_BANK_SIZE);
|
||||||
|
CMT2300A_ConfigRegBank(CMT2300A_SYSTEM_BANK_ADDR, g_cmt2300aSystemBank_860, CMT2300A_SYSTEM_BANK_SIZE);
|
||||||
|
CMT2300A_ConfigRegBank(CMT2300A_FREQUENCY_BANK_ADDR, g_cmt2300aFrequencyBank_860, CMT2300A_FREQUENCY_BANK_SIZE);
|
||||||
|
CMT2300A_ConfigRegBank(CMT2300A_DATA_RATE_BANK_ADDR, g_cmt2300aDataRateBank_860, CMT2300A_DATA_RATE_BANK_SIZE);
|
||||||
|
CMT2300A_ConfigRegBank(CMT2300A_BASEBAND_BANK_ADDR, g_cmt2300aBasebandBank_860, CMT2300A_BASEBAND_BANK_SIZE);
|
||||||
|
CMT2300A_ConfigRegBank(CMT2300A_TX_BANK_ADDR, g_cmt2300aTxBank_860, CMT2300A_TX_BANK_SIZE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// xosc_aac_code[2:0] = 2
|
// xosc_aac_code[2:0] = 2
|
||||||
uint8_t tmp;
|
uint8_t tmp;
|
||||||
|
|||||||
@ -4,13 +4,21 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define CMT2300A_ONE_STEP_SIZE 2500 // frequency channel step size for fast frequency hopping operation: One step size is 2.5 kHz.
|
#define CMT2300A_ONE_STEP_SIZE 2500 // frequency channel step size for fast frequency hopping operation: One step size is 2.5 kHz.
|
||||||
#define CMT_BASE_FREQ 860000000 // from Frequency Bank in cmt2300a_params.h
|
|
||||||
#define FH_OFFSET 100 // value * CMT2300A_ONE_STEP_SIZE = channel frequency offset
|
#define FH_OFFSET 100 // value * CMT2300A_ONE_STEP_SIZE = channel frequency offset
|
||||||
#define CMT_SPI_SPEED 4000000 // 4 MHz
|
#define CMT_SPI_SPEED 4000000 // 4 MHz
|
||||||
|
|
||||||
|
#define CMT_BASE_FREQ_900 900000000
|
||||||
|
#define CMT_BASE_FREQ_860 860000000
|
||||||
|
|
||||||
|
enum FrequencyBand_t {
|
||||||
|
BAND_860,
|
||||||
|
BAND_900,
|
||||||
|
FrequencyBand_Max,
|
||||||
|
};
|
||||||
|
|
||||||
class CMT2300A {
|
class CMT2300A {
|
||||||
public:
|
public:
|
||||||
CMT2300A(uint8_t pin_sdio, uint8_t pin_clk, uint8_t pin_cs, uint8_t pin_fcs, uint32_t _spi_speed = CMT_SPI_SPEED);
|
CMT2300A(const uint8_t pin_sdio, const uint8_t pin_clk, const uint8_t pin_cs, const uint8_t pin_fcs, const uint32_t _spi_speed = CMT_SPI_SPEED);
|
||||||
|
|
||||||
bool begin(void);
|
bool begin(void);
|
||||||
|
|
||||||
@ -54,15 +62,15 @@ public:
|
|||||||
* in one call is 32 (for dynamic payload lengths) or whatever number was
|
* in one call is 32 (for dynamic payload lengths) or whatever number was
|
||||||
* previously passed to setPayloadSize() (for static payload lengths).
|
* previously passed to setPayloadSize() (for static payload lengths).
|
||||||
*/
|
*/
|
||||||
void read(void* buf, uint8_t len);
|
void read(void* buf, const uint8_t len);
|
||||||
|
|
||||||
bool write(const uint8_t* buf, uint8_t len);
|
bool write(const uint8_t* buf, const uint8_t len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set RF communication channel. The frequency used by a channel is
|
* Set RF communication channel. The frequency used by a channel is
|
||||||
* @param channel Which RF channel to communicate on, 0-254
|
* @param channel Which RF channel to communicate on, 0-254
|
||||||
*/
|
*/
|
||||||
void setChannel(uint8_t channel);
|
void setChannel(const uint8_t channel);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get RF communication channel
|
* Get RF communication channel
|
||||||
@ -82,10 +90,26 @@ public:
|
|||||||
|
|
||||||
int getRssiDBm();
|
int getRssiDBm();
|
||||||
|
|
||||||
bool setPALevel(int8_t level);
|
bool setPALevel(const int8_t level);
|
||||||
|
|
||||||
bool rxFifoAvailable();
|
bool rxFifoAvailable();
|
||||||
|
|
||||||
|
uint32_t getBaseFrequency() const;
|
||||||
|
static constexpr uint32_t getBaseFrequency(FrequencyBand_t band)
|
||||||
|
{
|
||||||
|
switch (band) {
|
||||||
|
case FrequencyBand_t::BAND_900:
|
||||||
|
return CMT_BASE_FREQ_900;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return CMT_BASE_FREQ_860;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FrequencyBand_t getFrequencyBand() const;
|
||||||
|
void setFrequencyBand(const FrequencyBand_t mode);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty the RX (receive) FIFO buffers.
|
* Empty the RX (receive) FIFO buffers.
|
||||||
*/
|
*/
|
||||||
@ -109,4 +133,6 @@ private:
|
|||||||
int8_t _pin_cs;
|
int8_t _pin_cs;
|
||||||
int8_t _pin_fcs;
|
int8_t _pin_fcs;
|
||||||
uint32_t _spi_speed;
|
uint32_t _spi_speed;
|
||||||
|
|
||||||
|
FrequencyBand_t _frequencyBand = FrequencyBand_t::BAND_860;
|
||||||
};
|
};
|
||||||
@ -16,7 +16,7 @@ SemaphoreHandle_t paramLock = NULL;
|
|||||||
|
|
||||||
spi_device_handle_t spi_reg, spi_fifo;
|
spi_device_handle_t spi_reg, spi_fifo;
|
||||||
|
|
||||||
void cmt_spi3_init(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, uint32_t spi_speed)
|
void cmt_spi3_init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const uint32_t spi_speed)
|
||||||
{
|
{
|
||||||
paramLock = xSemaphoreCreateMutex();
|
paramLock = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ void cmt_spi3_init(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fc
|
|||||||
delay(100);
|
delay(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cmt_spi3_write(uint8_t addr, uint8_t dat)
|
void cmt_spi3_write(const uint8_t addr, const uint8_t dat)
|
||||||
{
|
{
|
||||||
uint8_t tx_data;
|
uint8_t tx_data;
|
||||||
tx_data = ~dat;
|
tx_data = ~dat;
|
||||||
@ -84,7 +84,7 @@ void cmt_spi3_write(uint8_t addr, uint8_t dat)
|
|||||||
delayMicroseconds(100);
|
delayMicroseconds(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t cmt_spi3_read(uint8_t addr)
|
uint8_t cmt_spi3_read(const uint8_t addr)
|
||||||
{
|
{
|
||||||
uint8_t rx_data;
|
uint8_t rx_data;
|
||||||
spi_transaction_t t = {
|
spi_transaction_t t = {
|
||||||
@ -102,7 +102,7 @@ uint8_t cmt_spi3_read(uint8_t addr)
|
|||||||
return rx_data;
|
return rx_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cmt_spi3_write_fifo(const uint8_t* buf, uint16_t len)
|
void cmt_spi3_write_fifo(const uint8_t* buf, const uint16_t len)
|
||||||
{
|
{
|
||||||
uint8_t tx_data;
|
uint8_t tx_data;
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ void cmt_spi3_write_fifo(const uint8_t* buf, uint16_t len)
|
|||||||
SPI_PARAM_UNLOCK();
|
SPI_PARAM_UNLOCK();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cmt_spi3_read_fifo(uint8_t* buf, uint16_t len)
|
void cmt_spi3_read_fifo(uint8_t* buf, const uint16_t len)
|
||||||
{
|
{
|
||||||
uint8_t rx_data;
|
uint8_t rx_data;
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,12 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
void cmt_spi3_init(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8_t pin_fcs, uint32_t spi_speed);
|
void cmt_spi3_init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const uint32_t spi_speed);
|
||||||
|
|
||||||
void cmt_spi3_write(uint8_t addr, uint8_t dat);
|
void cmt_spi3_write(const uint8_t addr, const uint8_t dat);
|
||||||
uint8_t cmt_spi3_read(uint8_t addr);
|
uint8_t cmt_spi3_read(const uint8_t addr);
|
||||||
|
|
||||||
void cmt_spi3_write_fifo(const uint8_t* p_buf, uint16_t len);
|
void cmt_spi3_write_fifo(const uint8_t* p_buf, const uint16_t len);
|
||||||
void cmt_spi3_read_fifo(uint8_t* p_buf, uint16_t len);
|
void cmt_spi3_read_fifo(uint8_t* p_buf, const uint16_t len);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,49 +1,79 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Thomas Basler and others
|
* Copyright (C) 2023-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "HoymilesRadio_CMT.h"
|
#include "HoymilesRadio_CMT.h"
|
||||||
#include "Hoymiles.h"
|
#include "Hoymiles.h"
|
||||||
#include "crc.h"
|
#include "crc.h"
|
||||||
#include <FunctionalInterrupt.h>
|
#include <FunctionalInterrupt.h>
|
||||||
|
#include <frozen/map.h>
|
||||||
|
|
||||||
#define HOY_BOOT_FREQ 868000000 // Hoymiles boot/init frequency after power up inverter or connection lost for 15 min
|
constexpr CountryFrequencyDefinition_t make_value(FrequencyBand_t Band, uint32_t Freq_Legal_Min, uint32_t Freq_Legal_Max, uint32_t Freq_Default, uint32_t Freq_StartUp)
|
||||||
#define HOY_BASE_FREQ 860000000
|
|
||||||
// offset from initalized CMT base frequency to Hoy base frequency in channels
|
|
||||||
#define CMT_BASE_CH_OFFSET860 ((CMT_BASE_FREQ - HOY_BASE_FREQ) / CMT2300A_ONE_STEP_SIZE / FH_OFFSET)
|
|
||||||
|
|
||||||
// frequency can not be lower than actual initailized base freq
|
|
||||||
#define MIN_FREQ_KHZ ((HOY_BASE_FREQ + (CMT_BASE_CH_OFFSET860 >= 1 ? CMT_BASE_CH_OFFSET860 : 1) * CMT2300A_ONE_STEP_SIZE * FH_OFFSET) / 1000)
|
|
||||||
|
|
||||||
// =923500, 0xFF does not work
|
|
||||||
#define MAX_FREQ_KHZ ((HOY_BASE_FREQ + 0xFE * CMT2300A_ONE_STEP_SIZE * FH_OFFSET) / 1000)
|
|
||||||
|
|
||||||
float HoymilesRadio_CMT::getFrequencyFromChannel(const uint8_t channel)
|
|
||||||
{
|
{
|
||||||
return (CMT_BASE_FREQ + (CMT_BASE_CH_OFFSET860 + channel) * FH_OFFSET * CMT2300A_ONE_STEP_SIZE) / 1000000.0;
|
// frequency can not be lower than actual initailized base freq + 250000
|
||||||
|
uint32_t minFrequency = CMT2300A::getBaseFrequency(Band) + HoymilesRadio_CMT::getChannelWidth();
|
||||||
|
|
||||||
|
// =923500, 0xFF does not work
|
||||||
|
uint32_t maxFrequency = CMT2300A::getBaseFrequency(Band) + 0xFE * HoymilesRadio_CMT::getChannelWidth();
|
||||||
|
|
||||||
|
CountryFrequencyDefinition_t v = { Band, minFrequency, maxFrequency, Freq_Legal_Min, Freq_Legal_Max, Freq_Default, Freq_StartUp };
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t HoymilesRadio_CMT::getChannelFromFrequency(const uint32_t freq_kHz)
|
constexpr frozen::map<CountryModeId_t, CountryFrequencyDefinition_t, 3> countryDefinition = {
|
||||||
|
{ CountryModeId_t::MODE_EU, make_value(FrequencyBand_t::BAND_860, 863e6, 870e6, 865e6, 868e6) },
|
||||||
|
{ CountryModeId_t::MODE_US, make_value(FrequencyBand_t::BAND_900, 905e6, 925e6, 918e6, 915e6) },
|
||||||
|
{ CountryModeId_t::MODE_BR, make_value(FrequencyBand_t::BAND_900, 915e6, 928e6, 918e6, 915e6) },
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t HoymilesRadio_CMT::getFrequencyFromChannel(const uint8_t channel) const
|
||||||
{
|
{
|
||||||
if ((freq_kHz % 250) != 0) {
|
return (_radio->getBaseFrequency() + channel * getChannelWidth());
|
||||||
Hoymiles.getMessageOutput()->printf("%.3f MHz is not divisible by 250 kHz!\r\n", freq_kHz / 1000.0);
|
}
|
||||||
|
|
||||||
|
uint8_t HoymilesRadio_CMT::getChannelFromFrequency(const uint32_t frequency) const
|
||||||
|
{
|
||||||
|
if ((frequency % getChannelWidth()) != 0) {
|
||||||
|
Hoymiles.getMessageOutput()->printf("%.3f MHz is not divisible by %d kHz!\r\n", frequency / 1000000.0, getChannelWidth());
|
||||||
return 0xFF; // ERROR
|
return 0xFF; // ERROR
|
||||||
}
|
}
|
||||||
if (freq_kHz < MIN_FREQ_KHZ || freq_kHz > MAX_FREQ_KHZ) {
|
if (frequency < getMinFrequency() || frequency > getMaxFrequency()) {
|
||||||
Hoymiles.getMessageOutput()->printf("%.2f MHz is out of Hoymiles/CMT range! (%.2f MHz - %.2f MHz)\r\n",
|
Hoymiles.getMessageOutput()->printf("%.2f MHz is out of Hoymiles/CMT range! (%.2f MHz - %.2f MHz)\r\n",
|
||||||
freq_kHz / 1000.0, MIN_FREQ_KHZ / 1000.0, MAX_FREQ_KHZ / 1000.0);
|
frequency / 1000000.0, getMinFrequency() / 1000000.0, getMaxFrequency() / 1000000.0);
|
||||||
return 0xFF; // ERROR
|
return 0xFF; // ERROR
|
||||||
}
|
}
|
||||||
if (freq_kHz < 863000 || freq_kHz > 870000) {
|
if (frequency < countryDefinition.at(_countryMode).Freq_Legal_Min || frequency > countryDefinition.at(_countryMode).Freq_Legal_Max) {
|
||||||
Hoymiles.getMessageOutput()->printf("!!! caution: %.2f MHz is out of EU legal range! (863 - 870 MHz)\r\n",
|
Hoymiles.getMessageOutput()->printf("!!! caution: %.2f MHz is out of region legal range! (%d - %d MHz)\r\n",
|
||||||
freq_kHz / 1000.0);
|
frequency / 1000000.0,
|
||||||
|
static_cast<uint32_t>(countryDefinition.at(_countryMode).Freq_Legal_Min / 1e6),
|
||||||
|
static_cast<uint32_t>(countryDefinition.at(_countryMode).Freq_Legal_Max / 1e6));
|
||||||
}
|
}
|
||||||
return (freq_kHz * 1000 - CMT_BASE_FREQ) / CMT2300A_ONE_STEP_SIZE / FH_OFFSET - CMT_BASE_CH_OFFSET860; // frequency to channel
|
|
||||||
|
return (frequency - _radio->getBaseFrequency()) / getChannelWidth(); // frequency to channel
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HoymilesRadio_CMT::cmtSwitchDtuFreq(const uint32_t to_freq_kHz)
|
std::vector<CountryFrequencyList_t> HoymilesRadio_CMT::getCountryFrequencyList() const
|
||||||
{
|
{
|
||||||
const uint8_t toChannel = getChannelFromFrequency(to_freq_kHz);
|
std::vector<CountryFrequencyList_t> v;
|
||||||
|
for (const auto& [key, value] : countryDefinition) {
|
||||||
|
CountryFrequencyList_t s;
|
||||||
|
s.mode = key;
|
||||||
|
s.definition.Band = value.Band;
|
||||||
|
s.definition.Freq_Default = value.Freq_Default;
|
||||||
|
s.definition.Freq_StartUp = value.Freq_StartUp;
|
||||||
|
s.definition.Freq_Min = value.Freq_Min;
|
||||||
|
s.definition.Freq_Max = value.Freq_Max;
|
||||||
|
s.definition.Freq_Legal_Max = value.Freq_Legal_Max;
|
||||||
|
s.definition.Freq_Legal_Min = value.Freq_Legal_Min;
|
||||||
|
|
||||||
|
v.push_back(s);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HoymilesRadio_CMT::cmtSwitchDtuFreq(const uint32_t to_frequency)
|
||||||
|
{
|
||||||
|
const uint8_t toChannel = getChannelFromFrequency(to_frequency);
|
||||||
if (toChannel == 0xFF) {
|
if (toChannel == 0xFF) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -61,6 +91,7 @@ void HoymilesRadio_CMT::init(const int8_t pin_sdio, const int8_t pin_clk, const
|
|||||||
|
|
||||||
_radio->begin();
|
_radio->begin();
|
||||||
|
|
||||||
|
setCountryMode(CountryModeId_t::MODE_EU);
|
||||||
cmtSwitchDtuFreq(_inverterTargetFrequency); // start dtu at work freqency, for fast Rx if inverter is already on and frequency switched
|
cmtSwitchDtuFreq(_inverterTargetFrequency); // start dtu at work freqency, for fast Rx if inverter is already on and frequency switched
|
||||||
|
|
||||||
if (!_radio->isChipConnected()) {
|
if (!_radio->isChipConnected()) {
|
||||||
@ -134,7 +165,7 @@ void HoymilesRadio_CMT::loop()
|
|||||||
|
|
||||||
if (nullptr != inv) {
|
if (nullptr != inv) {
|
||||||
// Save packet in inverter rx buffer
|
// Save packet in inverter rx buffer
|
||||||
Hoymiles.getVerboseMessageOutput()->printf("RX %.2f MHz --> ", getFrequencyFromChannel(f.channel));
|
Hoymiles.getVerboseMessageOutput()->printf("RX %.2f MHz --> ", getFrequencyFromChannel(f.channel) / 1000000.0);
|
||||||
dumpBuf(f.fragment, f.len, false);
|
dumpBuf(f.fragment, f.len, false);
|
||||||
Hoymiles.getVerboseMessageOutput()->printf("| %d dBm\r\n", f.rssi);
|
Hoymiles.getVerboseMessageOutput()->printf("| %d dBm\r\n", f.rssi);
|
||||||
|
|
||||||
@ -191,14 +222,31 @@ bool HoymilesRadio_CMT::isConnected() const
|
|||||||
return _radio->isChipConnected();
|
return _radio->isChipConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t HoymilesRadio_CMT::getMinFrequency()
|
uint32_t HoymilesRadio_CMT::getMinFrequency() const
|
||||||
{
|
{
|
||||||
return MIN_FREQ_KHZ;
|
return countryDefinition.at(_countryMode).Freq_Min;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t HoymilesRadio_CMT::getMaxFrequency()
|
uint32_t HoymilesRadio_CMT::getMaxFrequency() const
|
||||||
{
|
{
|
||||||
return MAX_FREQ_KHZ;
|
return countryDefinition.at(_countryMode).Freq_Max;
|
||||||
|
}
|
||||||
|
|
||||||
|
CountryModeId_t HoymilesRadio_CMT::getCountryMode() const
|
||||||
|
{
|
||||||
|
return _countryMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoymilesRadio_CMT::setCountryMode(const CountryModeId_t mode)
|
||||||
|
{
|
||||||
|
_radio->setFrequencyBand(countryDefinition.at(mode).Band);
|
||||||
|
_countryMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t HoymilesRadio_CMT::getInvBootFrequency() const
|
||||||
|
{
|
||||||
|
// Hoymiles boot/init frequency after power up inverter or connection lost for 15 min
|
||||||
|
return countryDefinition.at(_countryMode).Freq_StartUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARDUINO_ISR_ATTR HoymilesRadio_CMT::handleInt1()
|
void ARDUINO_ISR_ATTR HoymilesRadio_CMT::handleInt1()
|
||||||
@ -220,11 +268,11 @@ void HoymilesRadio_CMT::sendEsbPacket(CommandAbstract& cmd)
|
|||||||
_radio->stopListening();
|
_radio->stopListening();
|
||||||
|
|
||||||
if (cmd.getDataPayload()[0] == 0x56) { // @todo(tbnobody) Bad hack to identify ChannelChange Command
|
if (cmd.getDataPayload()[0] == 0x56) { // @todo(tbnobody) Bad hack to identify ChannelChange Command
|
||||||
cmtSwitchDtuFreq(HOY_BOOT_FREQ / 1000);
|
cmtSwitchDtuFreq(getInvBootFrequency());
|
||||||
}
|
}
|
||||||
|
|
||||||
Hoymiles.getVerboseMessageOutput()->printf("TX %s %.2f MHz --> ",
|
Hoymiles.getVerboseMessageOutput()->printf("TX %s %.2f MHz --> ",
|
||||||
cmd.getCommandName().c_str(), getFrequencyFromChannel(_radio->getChannel()));
|
cmd.getCommandName().c_str(), getFrequencyFromChannel(_radio->getChannel()) / 1000000.0);
|
||||||
cmd.dumpDataPayload(Hoymiles.getVerboseMessageOutput());
|
cmd.dumpDataPayload(Hoymiles.getVerboseMessageOutput());
|
||||||
|
|
||||||
if (!_radio->write(cmd.getDataPayload(), cmd.getDataSize())) {
|
if (!_radio->write(cmd.getDataPayload(), cmd.getDataSize())) {
|
||||||
|
|||||||
@ -8,14 +8,37 @@
|
|||||||
#include <cmt2300wrapper.h>
|
#include <cmt2300wrapper.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
// number of fragments hold in buffer
|
// number of fragments hold in buffer
|
||||||
#define FRAGMENT_BUFFER_SIZE 30
|
#define FRAGMENT_BUFFER_SIZE 30
|
||||||
|
|
||||||
#ifndef HOYMILES_CMT_WORK_FREQ
|
#ifndef HOYMILES_CMT_WORK_FREQ
|
||||||
#define HOYMILES_CMT_WORK_FREQ 865000
|
#define HOYMILES_CMT_WORK_FREQ 865000000
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
enum CountryModeId_t {
|
||||||
|
MODE_EU,
|
||||||
|
MODE_US,
|
||||||
|
MODE_BR,
|
||||||
|
CountryModeId_Max
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CountryFrequencyDefinition_t {
|
||||||
|
FrequencyBand_t Band;
|
||||||
|
uint32_t Freq_Min;
|
||||||
|
uint32_t Freq_Max;
|
||||||
|
uint32_t Freq_Legal_Min;
|
||||||
|
uint32_t Freq_Legal_Max;
|
||||||
|
uint32_t Freq_Default;
|
||||||
|
uint32_t Freq_StartUp;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CountryFrequencyList_t {
|
||||||
|
CountryModeId_t mode;
|
||||||
|
CountryFrequencyDefinition_t definition;
|
||||||
|
};
|
||||||
|
|
||||||
class HoymilesRadio_CMT : public HoymilesRadio {
|
class HoymilesRadio_CMT : public HoymilesRadio {
|
||||||
public:
|
public:
|
||||||
void init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3);
|
void init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int8_t pin_gpio2, const int8_t pin_gpio3);
|
||||||
@ -26,11 +49,22 @@ public:
|
|||||||
|
|
||||||
bool isConnected() const;
|
bool isConnected() const;
|
||||||
|
|
||||||
static uint32_t getMinFrequency();
|
uint32_t getMinFrequency() const;
|
||||||
static uint32_t getMaxFrequency();
|
uint32_t getMaxFrequency() const;
|
||||||
|
static constexpr uint32_t getChannelWidth()
|
||||||
|
{
|
||||||
|
return FH_OFFSET * CMT2300A_ONE_STEP_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
static float getFrequencyFromChannel(const uint8_t channel);
|
CountryModeId_t getCountryMode() const;
|
||||||
static uint8_t getChannelFromFrequency(const uint32_t freq_kHz);
|
void setCountryMode(const CountryModeId_t mode);
|
||||||
|
|
||||||
|
uint32_t getInvBootFrequency() const;
|
||||||
|
|
||||||
|
uint32_t getFrequencyFromChannel(const uint8_t channel) const;
|
||||||
|
uint8_t getChannelFromFrequency(const uint32_t frequency) const;
|
||||||
|
|
||||||
|
std::vector<CountryFrequencyList_t> getCountryFrequencyList() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ARDUINO_ISR_ATTR handleInt1();
|
void ARDUINO_ISR_ATTR handleInt1();
|
||||||
@ -51,5 +85,7 @@ private:
|
|||||||
|
|
||||||
uint32_t _inverterTargetFrequency = HOYMILES_CMT_WORK_FREQ;
|
uint32_t _inverterTargetFrequency = HOYMILES_CMT_WORK_FREQ;
|
||||||
|
|
||||||
bool cmtSwitchDtuFreq(const uint32_t to_freq_kHz);
|
bool cmtSwitchDtuFreq(const uint32_t to_frequency);
|
||||||
|
|
||||||
|
CountryModeId_t _countryMode;
|
||||||
};
|
};
|
||||||
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Thomas Basler and others
|
* Copyright (C) 2023-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -12,7 +12,8 @@ Command structure:
|
|||||||
|
|
||||||
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
||||||
-----------------------------------------------------------------------------------------------------------------
|
-----------------------------------------------------------------------------------------------------------------
|
||||||
56 71 60 35 46 80 12 23 04 02 15 21 00 14 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
56 71 60 35 46 80 12 23 04 02 15 21 00 14 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- (860 MHz band)
|
||||||
|
56 71 60 35 46 80 12 23 04 03 17 3c 00 14 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- (900 MHz band)
|
||||||
^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^
|
^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^
|
||||||
ID Target Addr Source Addr ? ? ? CH ? CRC8
|
ID Target Addr Source Addr ? ? ? CH ? CRC8
|
||||||
*/
|
*/
|
||||||
@ -22,12 +23,10 @@ ChannelChangeCommand::ChannelChangeCommand(const uint64_t target_address, const
|
|||||||
: CommandAbstract(target_address, router_address)
|
: CommandAbstract(target_address, router_address)
|
||||||
{
|
{
|
||||||
_payload[0] = 0x56;
|
_payload[0] = 0x56;
|
||||||
_payload[9] = 0x02;
|
|
||||||
_payload[10] = 0x15;
|
|
||||||
_payload[11] = 0x21;
|
|
||||||
_payload[13] = 0x14;
|
_payload[13] = 0x14;
|
||||||
_payload_size = 14;
|
_payload_size = 14;
|
||||||
|
|
||||||
|
setCountryMode(CountryModeId_t::MODE_EU);
|
||||||
setChannel(channel);
|
setChannel(channel);
|
||||||
setTimeout(10);
|
setTimeout(10);
|
||||||
}
|
}
|
||||||
@ -47,6 +46,27 @@ uint8_t ChannelChangeCommand::getChannel() const
|
|||||||
return _payload[12];
|
return _payload[12];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChannelChangeCommand::setCountryMode(const CountryModeId_t mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case CountryModeId_t::MODE_US:
|
||||||
|
_payload[9] = 0x03;
|
||||||
|
_payload[10] = 0x17;
|
||||||
|
_payload[11] = 0x3c;
|
||||||
|
break;
|
||||||
|
case CountryModeId_t::MODE_BR:
|
||||||
|
_payload[9] = 0x03;
|
||||||
|
_payload[10] = 0x17;
|
||||||
|
_payload[11] = 0x3c;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_payload[9] = 0x02;
|
||||||
|
_payload[10] = 0x15;
|
||||||
|
_payload[11] = 0x21;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool ChannelChangeCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id)
|
bool ChannelChangeCommand::handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CommandAbstract.h"
|
#include "CommandAbstract.h"
|
||||||
|
#include "../HoymilesRadio_CMT.h"
|
||||||
|
|
||||||
class ChannelChangeCommand : public CommandAbstract {
|
class ChannelChangeCommand : public CommandAbstract {
|
||||||
public:
|
public:
|
||||||
@ -12,6 +13,8 @@ public:
|
|||||||
void setChannel(const uint8_t channel);
|
void setChannel(const uint8_t channel);
|
||||||
uint8_t getChannel() const;
|
uint8_t getChannel() const;
|
||||||
|
|
||||||
|
void setCountryMode(const CountryModeId_t mode);
|
||||||
|
|
||||||
virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id);
|
virtual bool handleResponse(InverterAbstract& inverter, const fragment_t fragment[], const uint8_t max_fragment_id);
|
||||||
|
|
||||||
virtual uint8_t getMaxResendCount();
|
virtual uint8_t getMaxResendCount();
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Thomas Basler and others
|
* Copyright (C) 2023-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "HMS_Abstract.h"
|
#include "HMS_Abstract.h"
|
||||||
#include "Hoymiles.h"
|
#include "Hoymiles.h"
|
||||||
@ -19,7 +19,8 @@ bool HMS_Abstract::sendChangeChannelRequest()
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto cmdChannel = _radio->prepareCommand<ChannelChangeCommand>();
|
auto cmdChannel = _radio->prepareCommand<ChannelChangeCommand>();
|
||||||
cmdChannel->setChannel(HoymilesRadio_CMT::getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency()));
|
cmdChannel->setCountryMode(Hoymiles.getRadioCmt()->getCountryMode());
|
||||||
|
cmdChannel->setChannel(Hoymiles.getRadioCmt()->getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency()));
|
||||||
cmdChannel->setTargetAddress(serial());
|
cmdChannel->setTargetAddress(serial());
|
||||||
_radio->enqueCommand(cmdChannel);
|
_radio->enqueCommand(cmdChannel);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Thomas Basler and others
|
* Copyright (C) 2023-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "HMT_Abstract.h"
|
#include "HMT_Abstract.h"
|
||||||
#include "Hoymiles.h"
|
#include "Hoymiles.h"
|
||||||
@ -21,7 +21,8 @@ bool HMT_Abstract::sendChangeChannelRequest()
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto cmdChannel = _radio->prepareCommand<ChannelChangeCommand>();
|
auto cmdChannel = _radio->prepareCommand<ChannelChangeCommand>();
|
||||||
cmdChannel->setChannel(HoymilesRadio_CMT::getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency()));
|
cmdChannel->setCountryMode(Hoymiles.getRadioCmt()->getCountryMode());
|
||||||
|
cmdChannel->setChannel(Hoymiles.getRadioCmt()->getChannelFromFrequency(Hoymiles.getRadioCmt()->getInverterTargetFrequency()));
|
||||||
cmdChannel->setTargetAddress(serial());
|
cmdChannel->setTargetAddress(serial());
|
||||||
_radio->enqueCommand(cmdChannel);
|
_radio->enqueCommand(cmdChannel);
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,10 @@ import re
|
|||||||
Import("env")
|
Import("env")
|
||||||
|
|
||||||
def getPatchPath(env):
|
def getPatchPath(env):
|
||||||
return os.path.join(env["PROJECT_DIR"], "patches", env.GetProjectOption('custom_patches'))
|
patchList = []
|
||||||
|
for patch in env.GetProjectOption('custom_patches').split(","):
|
||||||
|
patchList.append(os.path.join(env["PROJECT_DIR"], "patches", patch))
|
||||||
|
return patchList
|
||||||
|
|
||||||
def is_tool(name):
|
def is_tool(name):
|
||||||
"""Check whether `name` is on PATH and marked as executable."""
|
"""Check whether `name` is on PATH and marked as executable."""
|
||||||
@ -44,35 +47,36 @@ def main():
|
|||||||
print('Git not found. Will not apply custom patches!')
|
print('Git not found. Will not apply custom patches!')
|
||||||
return
|
return
|
||||||
|
|
||||||
directory = getPatchPath(env)
|
directories = getPatchPath(env)
|
||||||
if (not os.path.isdir(directory)):
|
for directory in directories:
|
||||||
print('Patch directory not found: ' + directory)
|
if (not os.path.isdir(directory)):
|
||||||
return
|
print('Patch directory not found: ' + directory)
|
||||||
|
return
|
||||||
|
|
||||||
for file in os.listdir(directory):
|
for file in os.listdir(directory):
|
||||||
if (not file.endswith('.patch')):
|
if (not file.endswith('.patch')):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
fullPath = os.path.join(directory, file)
|
fullPath = os.path.join(directory, file)
|
||||||
preparePath = fullPath + '.prepare'
|
preparePath = fullPath + '.prepare'
|
||||||
replaceInFile(fullPath, preparePath, '$$$env$$$', env['PIOENV'])
|
replaceInFile(fullPath, preparePath, '$$$env$$$', env['PIOENV'])
|
||||||
print('Working on patch: ' + fullPath + '... ', end='')
|
print('Working on patch: ' + fullPath + '... ', end='')
|
||||||
|
|
||||||
|
# Check if patch was already applied
|
||||||
|
process = subprocess.run(['git', 'apply', '--reverse', '--check', preparePath], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
if (process.returncode == 0):
|
||||||
|
print('already applied')
|
||||||
|
os.remove(preparePath)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Apply patch
|
||||||
|
process = subprocess.run(['git', 'apply', preparePath])
|
||||||
|
if (process.returncode == 0):
|
||||||
|
print('applied')
|
||||||
|
else:
|
||||||
|
print('failed')
|
||||||
|
|
||||||
# Check if patch was already applied
|
|
||||||
process = subprocess.run(['git', 'apply', '--reverse', '--check', preparePath], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
||||||
if (process.returncode == 0):
|
|
||||||
print('already applied')
|
|
||||||
os.remove(preparePath)
|
os.remove(preparePath)
|
||||||
continue
|
|
||||||
|
|
||||||
# Apply patch
|
|
||||||
process = subprocess.run(['git', 'apply', preparePath])
|
|
||||||
if (process.returncode == 0):
|
|
||||||
print('applied')
|
|
||||||
else:
|
|
||||||
print('failed')
|
|
||||||
|
|
||||||
os.remove(preparePath)
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
main()
|
||||||
@ -45,8 +45,8 @@ lib_deps =
|
|||||||
https://github.com/arkhipenko/TaskScheduler#testing
|
https://github.com/arkhipenko/TaskScheduler#testing
|
||||||
https://github.com/arkhipenko/TaskScheduler#testing
|
https://github.com/arkhipenko/TaskScheduler#testing
|
||||||
https://github.com/coryjfowler/MCP_CAN_lib
|
https://github.com/coryjfowler/MCP_CAN_lib
|
||||||
plerup/EspSoftwareSerial @ 8.0.1
|
plerup/EspSoftwareSerial @ ^8.0.1
|
||||||
mobizt/FirebaseJson @ ^3.0.6
|
https://github.com/dok-net/ghostl @ ^1.0.1
|
||||||
rweather/Crypto@^0.4.0
|
rweather/Crypto@^0.4.0
|
||||||
|
|
||||||
extra_scripts =
|
extra_scripts =
|
||||||
@ -64,6 +64,8 @@ board_build.embed_files =
|
|||||||
webapp_dist/js/app.js.gz
|
webapp_dist/js/app.js.gz
|
||||||
webapp_dist/site.webmanifest
|
webapp_dist/site.webmanifest
|
||||||
|
|
||||||
|
custom_patches =
|
||||||
|
|
||||||
monitor_filters = esp32_exception_decoder, time, log2file, colorize
|
monitor_filters = esp32_exception_decoder, time, log2file, colorize
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_protocol = esptool
|
upload_protocol = esptool
|
||||||
@ -80,13 +82,13 @@ build_flags = ${env.build_flags}
|
|||||||
|
|
||||||
[env:generic_esp32c3]
|
[env:generic_esp32c3]
|
||||||
board = esp32-c3-devkitc-02
|
board = esp32-c3-devkitc-02
|
||||||
custom_patches = esp32c3
|
custom_patches = ${env.custom_patches},esp32c3
|
||||||
build_flags = ${env.build_flags}
|
build_flags = ${env.build_flags}
|
||||||
|
|
||||||
|
|
||||||
[env:generic_esp32c3_usb]
|
[env:generic_esp32c3_usb]
|
||||||
board = esp32-c3-devkitc-02
|
board = esp32-c3-devkitc-02
|
||||||
custom_patches = esp32c3
|
custom_patches = ${env.custom_patches},esp32c3
|
||||||
build_flags = ${env.build_flags}
|
build_flags = ${env.build_flags}
|
||||||
-DARDUINO_USB_MODE=1
|
-DARDUINO_USB_MODE=1
|
||||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Thomas Basler and others
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
@ -97,6 +97,7 @@ bool ConfigurationClass::write()
|
|||||||
dtu["nrf_pa_level"] = config.Dtu.Nrf.PaLevel;
|
dtu["nrf_pa_level"] = config.Dtu.Nrf.PaLevel;
|
||||||
dtu["cmt_pa_level"] = config.Dtu.Cmt.PaLevel;
|
dtu["cmt_pa_level"] = config.Dtu.Cmt.PaLevel;
|
||||||
dtu["cmt_frequency"] = config.Dtu.Cmt.Frequency;
|
dtu["cmt_frequency"] = config.Dtu.Cmt.Frequency;
|
||||||
|
dtu["cmt_country_mode"] = config.Dtu.Cmt.CountryMode;
|
||||||
|
|
||||||
JsonObject security = doc.createNestedObject("security");
|
JsonObject security = doc.createNestedObject("security");
|
||||||
security["password"] = config.Security.Password;
|
security["password"] = config.Security.Password;
|
||||||
@ -111,7 +112,8 @@ bool ConfigurationClass::write()
|
|||||||
display["rotation"] = config.Display.Rotation;
|
display["rotation"] = config.Display.Rotation;
|
||||||
display["contrast"] = config.Display.Contrast;
|
display["contrast"] = config.Display.Contrast;
|
||||||
display["language"] = config.Display.Language;
|
display["language"] = config.Display.Language;
|
||||||
display["diagram_duration"] = config.Display.DiagramDuration;
|
display["diagram_duration"] = config.Display.Diagram.Duration;
|
||||||
|
display["diagram_mode"] = config.Display.Diagram.Mode;
|
||||||
|
|
||||||
JsonArray leds = device.createNestedArray("led");
|
JsonArray leds = device.createNestedArray("led");
|
||||||
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
||||||
@ -339,6 +341,7 @@ bool ConfigurationClass::read()
|
|||||||
config.Dtu.Nrf.PaLevel = dtu["nrf_pa_level"] | DTU_NRF_PA_LEVEL;
|
config.Dtu.Nrf.PaLevel = dtu["nrf_pa_level"] | DTU_NRF_PA_LEVEL;
|
||||||
config.Dtu.Cmt.PaLevel = dtu["cmt_pa_level"] | DTU_CMT_PA_LEVEL;
|
config.Dtu.Cmt.PaLevel = dtu["cmt_pa_level"] | DTU_CMT_PA_LEVEL;
|
||||||
config.Dtu.Cmt.Frequency = dtu["cmt_frequency"] | DTU_CMT_FREQUENCY;
|
config.Dtu.Cmt.Frequency = dtu["cmt_frequency"] | DTU_CMT_FREQUENCY;
|
||||||
|
config.Dtu.Cmt.CountryMode = dtu["cmt_country_mode"] | DTU_CMT_COUNTRY_MODE;
|
||||||
|
|
||||||
JsonObject security = doc["security"];
|
JsonObject security = doc["security"];
|
||||||
strlcpy(config.Security.Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security.Password));
|
strlcpy(config.Security.Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security.Password));
|
||||||
@ -353,7 +356,8 @@ bool ConfigurationClass::read()
|
|||||||
config.Display.Rotation = display["rotation"] | DISPLAY_ROTATION;
|
config.Display.Rotation = display["rotation"] | DISPLAY_ROTATION;
|
||||||
config.Display.Contrast = display["contrast"] | DISPLAY_CONTRAST;
|
config.Display.Contrast = display["contrast"] | DISPLAY_CONTRAST;
|
||||||
config.Display.Language = display["language"] | DISPLAY_LANGUAGE;
|
config.Display.Language = display["language"] | DISPLAY_LANGUAGE;
|
||||||
config.Display.DiagramDuration = display["diagram_duration"] | DISPLAY_DIAGRAM_DURATION;
|
config.Display.Diagram.Duration = display["diagram_duration"] | DISPLAY_DIAGRAM_DURATION;
|
||||||
|
config.Display.Diagram.Mode = display["diagram_mode"] | DISPLAY_DIAGRAM_MODE;
|
||||||
|
|
||||||
JsonArray leds = device["led"];
|
JsonArray leds = device["led"];
|
||||||
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
||||||
@ -513,6 +517,11 @@ void ConfigurationClass::migrate()
|
|||||||
nvs_flash_init();
|
nvs_flash_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.Cfg.Version < 0x00011b00) {
|
||||||
|
// Convert from kHz to Hz
|
||||||
|
config.Dtu.Cmt.Frequency *= 1000;
|
||||||
|
}
|
||||||
|
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
config.Cfg.Version = CONFIG_VERSION;
|
config.Cfg.Version = CONFIG_VERSION;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Thomas Basler and others
|
* Copyright (C) 2023-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "Display_Graphic.h"
|
#include "Display_Graphic.h"
|
||||||
#include "Datastore.h"
|
#include "Datastore.h"
|
||||||
@ -28,8 +28,8 @@ const uint8_t languages[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const char* const i18n_offline[] = { "Offline", "Offline", "Offline" };
|
static const char* const i18n_offline[] = { "Offline", "Offline", "Offline" };
|
||||||
static const char* const i18n_current_power_w[] = { "%3.0f W", "%3.0f W", "%3.0f W" };
|
static const char* const i18n_current_power_w[] = { "%.0f W", "%.0f W", "%.0f W" };
|
||||||
static const char* const i18n_current_power_kw[] = { "%2.1f kW", "%2.1f kW", "%2.1f kW" };
|
static const char* const i18n_current_power_kw[] = { "%.1f kW", "%.1f kW", "%.1f kW" };
|
||||||
static const char* const i18n_yield_today_wh[] = { "today: %4.0f Wh", "Heute: %4.0f Wh", "auj.: %4.0f Wh" };
|
static const char* const i18n_yield_today_wh[] = { "today: %4.0f Wh", "Heute: %4.0f Wh", "auj.: %4.0f Wh" };
|
||||||
static const char* const i18n_yield_total_kwh[] = { "total: %.1f kWh", "Ges.: %.1f kWh", "total: %.1f kWh" };
|
static const char* const i18n_yield_total_kwh[] = { "total: %.1f kWh", "Ges.: %.1f kWh", "total: %.1f kWh" };
|
||||||
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" };
|
||||||
@ -94,13 +94,31 @@ bool DisplayGraphicClass::isValidDisplay()
|
|||||||
|
|
||||||
void DisplayGraphicClass::printText(const char* text, const uint8_t line)
|
void DisplayGraphicClass::printText(const char* text, const uint8_t line)
|
||||||
{
|
{
|
||||||
|
setFont(line);
|
||||||
|
|
||||||
uint8_t dispX;
|
uint8_t dispX;
|
||||||
if (!_isLarge) {
|
if (!_isLarge) {
|
||||||
dispX = (line == 0) ? 5 : 0;
|
dispX = (line == 0) ? 5 : 0;
|
||||||
} else {
|
} else {
|
||||||
dispX = (line == 0) ? 10 : 5;
|
switch (line) {
|
||||||
|
case 0:
|
||||||
|
if (_diagram_mode == DiagramMode_t::Small) {
|
||||||
|
// Center between left border and diagram
|
||||||
|
dispX = (CHART_POSX - _display->getStrWidth(text)) / 2;
|
||||||
|
} else {
|
||||||
|
// Center on screen
|
||||||
|
dispX = (_display->getDisplayWidth() - _display->getStrWidth(text)) / 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// Center on screen
|
||||||
|
dispX = (_display->getDisplayWidth() - _display->getStrWidth(text)) / 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dispX = 5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setFont(line);
|
|
||||||
|
|
||||||
dispX += enableScreensaver ? (_mExtra % 7) : 0;
|
dispX += enableScreensaver ? (_mExtra % 7) : 0;
|
||||||
_display->drawStr(dispX, _lineOffsets[line], text);
|
_display->drawStr(dispX, _lineOffsets[line], text);
|
||||||
@ -136,6 +154,13 @@ void DisplayGraphicClass::setLanguage(const uint8_t language)
|
|||||||
_display_language = language < sizeof(languages) / sizeof(languages[0]) ? language : DISPLAY_LANGUAGE;
|
_display_language = language < sizeof(languages) / sizeof(languages[0]) ? language : DISPLAY_LANGUAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DisplayGraphicClass::setDiagramMode(DiagramMode_t mode)
|
||||||
|
{
|
||||||
|
if (mode < DiagramMode_t::DisplayMode_Max) {
|
||||||
|
_diagram_mode = mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DisplayGraphicClass::setStartupDisplay()
|
void DisplayGraphicClass::setStartupDisplay()
|
||||||
{
|
{
|
||||||
if (!isValidDisplay()) {
|
if (!isValidDisplay()) {
|
||||||
@ -158,21 +183,37 @@ void DisplayGraphicClass::loop()
|
|||||||
|
|
||||||
_display->clearBuffer();
|
_display->clearBuffer();
|
||||||
bool displayPowerSave = false;
|
bool displayPowerSave = false;
|
||||||
|
bool showText = true;
|
||||||
|
|
||||||
//=====> Actual Production ==========
|
//=====> Actual Production ==========
|
||||||
if (Datastore.getIsAtLeastOneReachable()) {
|
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;
|
||||||
_diagram.redraw(screenSaverOffsetX);
|
switch (_diagram_mode) {
|
||||||
|
case DiagramMode_t::Small:
|
||||||
|
_diagram.redraw(screenSaverOffsetX, CHART_POSX, CHART_POSY, CHART_WIDTH, CHART_HEIGHT, false);
|
||||||
|
break;
|
||||||
|
case DiagramMode_t::Fullscreen:
|
||||||
|
// Every 10 seconds
|
||||||
|
if (_mExtra % (10 * 2) < 10) {
|
||||||
|
_diagram.redraw(screenSaverOffsetX, 10, 0, _display->getDisplayWidth() - 12, _display->getDisplayHeight() - 3, true);
|
||||||
|
showText = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const float watts = Datastore.getTotalAcPowerEnabled();
|
if (showText) {
|
||||||
if (watts > 999) {
|
const float watts = Datastore.getTotalAcPowerEnabled();
|
||||||
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_kw[_display_language], watts / 1000);
|
if (watts > 999) {
|
||||||
} else {
|
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_kw[_display_language], watts / 1000);
|
||||||
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_w[_display_language], watts);
|
} else {
|
||||||
|
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_w[_display_language], watts);
|
||||||
|
}
|
||||||
|
printText(_fmtText, 0);
|
||||||
}
|
}
|
||||||
printText(_fmtText, 0);
|
|
||||||
_previousMillis = millis();
|
_previousMillis = millis();
|
||||||
}
|
}
|
||||||
//<=======================
|
//<=======================
|
||||||
@ -187,23 +228,27 @@ void DisplayGraphicClass::loop()
|
|||||||
}
|
}
|
||||||
//<=======================
|
//<=======================
|
||||||
|
|
||||||
//=====> Today & Total Production =======
|
if (showText) {
|
||||||
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], Datastore.getTotalAcYieldDayEnabled());
|
//=====> Today & Total Production =======
|
||||||
printText(_fmtText, 1);
|
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], Datastore.getTotalAcYieldDayEnabled());
|
||||||
|
printText(_fmtText, 1);
|
||||||
|
|
||||||
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_total_kwh[_display_language], Datastore.getTotalAcYieldTotalEnabled());
|
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_total_kwh[_display_language], Datastore.getTotalAcYieldTotalEnabled());
|
||||||
printText(_fmtText, 2);
|
printText(_fmtText, 2);
|
||||||
//<=======================
|
//<=======================
|
||||||
|
|
||||||
//=====> IP or Date-Time ========
|
//=====> IP or Date-Time ========
|
||||||
if (!(_mExtra % 10) && NetworkSettings.localIP()) {
|
// Change every 3 seconds
|
||||||
printText(NetworkSettings.localIP().toString().c_str(), 3);
|
if (!(_mExtra % (3 * 2) < 3) && NetworkSettings.localIP()) {
|
||||||
} else {
|
printText(NetworkSettings.localIP().toString().c_str(), 3);
|
||||||
// Get current time
|
} else {
|
||||||
time_t now = time(nullptr);
|
// Get current time
|
||||||
strftime(_fmtText, sizeof(_fmtText), i18n_date_format[_display_language], localtime(&now));
|
time_t now = time(nullptr);
|
||||||
printText(_fmtText, 3);
|
strftime(_fmtText, sizeof(_fmtText), i18n_date_format[_display_language], localtime(&now));
|
||||||
|
printText(_fmtText, 3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_display->sendBuffer();
|
_display->sendBuffer();
|
||||||
|
|
||||||
_mExtra++;
|
_mExtra++;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Thomas Basler and others
|
* Copyright (C) 2023-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "Display_Graphic_Diagram.h"
|
#include "Display_Graphic_Diagram.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
@ -52,38 +52,39 @@ void DisplayGraphicDiagramClass::dataPointLoop()
|
|||||||
|
|
||||||
uint32_t DisplayGraphicDiagramClass::getSecondsPerDot()
|
uint32_t DisplayGraphicDiagramClass::getSecondsPerDot()
|
||||||
{
|
{
|
||||||
return Configuration.get().Display.DiagramDuration / CHART_WIDTH;
|
return Configuration.get().Display.Diagram.Duration / _chartWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayGraphicDiagramClass::updatePeriod()
|
void DisplayGraphicDiagramClass::updatePeriod()
|
||||||
{
|
{
|
||||||
_dataPointTask.setInterval(getSecondsPerDot() * TASK_SECOND);
|
// Calculate seconds per datapoint
|
||||||
|
_dataPointTask.setInterval(Configuration.get().Display.Diagram.Duration * TASK_SECOND / MAX_DATAPOINTS );
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayGraphicDiagramClass::redraw(uint8_t screenSaverOffsetX)
|
void DisplayGraphicDiagramClass::redraw(uint8_t screenSaverOffsetX, uint8_t xPos, uint8_t yPos, uint8_t width, uint8_t height, bool isFullscreen)
|
||||||
{
|
{
|
||||||
// screenSaverOffsetX expected to be in range 0..6
|
_chartWidth = width;
|
||||||
const uint8_t graphPosX = DIAG_POSX + ((screenSaverOffsetX > 3) ? 1 : 0);
|
|
||||||
const uint8_t graphPosY = DIAG_POSY + ((screenSaverOffsetX > 3) ? 1 : 0);
|
|
||||||
|
|
||||||
const uint8_t horizontal_line_y = graphPosY + CHART_HEIGHT - 1;
|
// screenSaverOffsetX expected to be in range 0..6
|
||||||
|
const uint8_t graphPosX = xPos + ((screenSaverOffsetX > 3) ? 1 : 0);
|
||||||
|
const uint8_t graphPosY = yPos + ((screenSaverOffsetX > 3) ? 1 : 0);
|
||||||
|
|
||||||
|
const uint8_t horizontal_line_y = graphPosY + height - 1;
|
||||||
const uint8_t arrow_size = 2;
|
const uint8_t arrow_size = 2;
|
||||||
|
|
||||||
// draw diagram axis
|
// draw diagram axis
|
||||||
_display->drawVLine(graphPosX, graphPosY, CHART_HEIGHT);
|
_display->drawVLine(graphPosX, graphPosY, height);
|
||||||
_display->drawHLine(graphPosX, horizontal_line_y, CHART_WIDTH);
|
_display->drawHLine(graphPosX, horizontal_line_y, width);
|
||||||
|
|
||||||
// UP-arrow
|
// UP-arrow
|
||||||
_display->drawLine(graphPosX, graphPosY, graphPosX + arrow_size, graphPosY + arrow_size);
|
_display->drawLine(graphPosX, graphPosY, graphPosX + arrow_size, graphPosY + arrow_size);
|
||||||
_display->drawLine(graphPosX, graphPosY, graphPosX - arrow_size, graphPosY + arrow_size);
|
_display->drawLine(graphPosX, graphPosY, graphPosX - arrow_size, graphPosY + arrow_size);
|
||||||
|
|
||||||
// LEFT-arrow
|
// LEFT-arrow
|
||||||
_display->drawLine(graphPosX + CHART_WIDTH - 1, horizontal_line_y, graphPosX + CHART_WIDTH - 1 - arrow_size, horizontal_line_y - arrow_size);
|
_display->drawLine(graphPosX + width - 1, horizontal_line_y, graphPosX + width - 1 - arrow_size, horizontal_line_y - arrow_size);
|
||||||
_display->drawLine(graphPosX + CHART_WIDTH - 1, horizontal_line_y, graphPosX + CHART_WIDTH - 1 - arrow_size, horizontal_line_y + arrow_size);
|
_display->drawLine(graphPosX + width - 1, horizontal_line_y, graphPosX + width - 1 - arrow_size, horizontal_line_y + arrow_size);
|
||||||
|
|
||||||
// draw AC value
|
// draw AC value
|
||||||
// 4 pixels per char
|
|
||||||
_display->setFont(u8g2_font_tom_thumb_4x6_mr);
|
|
||||||
char fmtText[7];
|
char fmtText[7];
|
||||||
const float maxWatts = *std::max_element(_graphValues.begin(), _graphValues.end());
|
const float maxWatts = *std::max_element(_graphValues.begin(), _graphValues.end());
|
||||||
if (maxWatts > 999) {
|
if (maxWatts > 999) {
|
||||||
@ -91,25 +92,46 @@ void DisplayGraphicDiagramClass::redraw(uint8_t screenSaverOffsetX)
|
|||||||
} else {
|
} else {
|
||||||
snprintf(fmtText, sizeof(fmtText), "%dW", static_cast<uint16_t>(maxWatts));
|
snprintf(fmtText, sizeof(fmtText), "%dW", static_cast<uint16_t>(maxWatts));
|
||||||
}
|
}
|
||||||
const uint8_t textLength = strlen(fmtText);
|
|
||||||
_display->drawStr(graphPosX - arrow_size - textLength * 4, graphPosY + 5, fmtText);
|
if (isFullscreen) {
|
||||||
|
_display->setFont(u8g2_font_5x8_tr);
|
||||||
|
_display->setFontDirection(3);
|
||||||
|
_display->drawStr(graphPosX - arrow_size, graphPosY + _display->getStrWidth(fmtText), fmtText);
|
||||||
|
_display->setFontDirection(0);
|
||||||
|
} else {
|
||||||
|
// 4 pixels per char
|
||||||
|
_display->setFont(u8g2_font_tom_thumb_4x6_mr);
|
||||||
|
_display->drawStr(graphPosX - arrow_size - _display->getStrWidth(fmtText), graphPosY + 5, fmtText);
|
||||||
|
}
|
||||||
|
|
||||||
// draw chart
|
// draw chart
|
||||||
const float scaleFactor = maxWatts / CHART_HEIGHT;
|
const float scaleFactorY = maxWatts / static_cast<float>(height);
|
||||||
uint8_t axisTick = 1;
|
const float scaleFactorX = static_cast<float>(MAX_DATAPOINTS) / static_cast<float>(_chartWidth);
|
||||||
|
|
||||||
|
if (maxWatts > 0 && isFullscreen) {
|
||||||
|
// draw y axis ticks
|
||||||
|
const uint16_t yAxisWattPerTick = maxWatts <= 100 ? 10 : maxWatts <= 1000 ? 100 : maxWatts < 5000 ? 500 : 1000;
|
||||||
|
const uint8_t yAxisTickSizePixel = height / (maxWatts / yAxisWattPerTick);
|
||||||
|
|
||||||
|
for (int16_t tickYPos = graphPosY + height; tickYPos > graphPosY - arrow_size; tickYPos -= yAxisTickSizePixel) {
|
||||||
|
_display->drawPixel(graphPosX - 1, tickYPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t xAxisTicks = 1;
|
||||||
for (uint8_t i = 1; i < _graphValuesCount; i++) {
|
for (uint8_t i = 1; i < _graphValuesCount; i++) {
|
||||||
// draw one tick per hour to the x-axis
|
// draw one tick per hour to the x-axis
|
||||||
if (i * getSecondsPerDot() > (3600u * axisTick)) {
|
if (i * getSecondsPerDot() > (3600u * xAxisTicks)) {
|
||||||
_display->drawPixel(graphPosX + 1 + i, graphPosY + CHART_HEIGHT);
|
_display->drawPixel((graphPosX + 1 + i) * scaleFactorX, graphPosY + height);
|
||||||
axisTick++;
|
xAxisTicks++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scaleFactor == 0) {
|
if (scaleFactorY == 0 || scaleFactorX == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_display->drawLine(
|
_display->drawLine(
|
||||||
graphPosX + i - 1, horizontal_line_y - std::max<int16_t>(0, _graphValues[i - 1] / scaleFactor - 0.5),
|
graphPosX + (i - 1) / scaleFactorX, horizontal_line_y - std::max<int16_t>(0, _graphValues[i - 1] / scaleFactorY - 0.5),
|
||||||
graphPosX + i, horizontal_line_y - std::max<int16_t>(0, _graphValues[i] / scaleFactor - 0.5));
|
graphPosX + i / scaleFactorX, horizontal_line_y - std::max<int16_t>(0, _graphValues[i] / scaleFactorY - 0.5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,12 @@
|
|||||||
#include "HttpPowerMeter.h"
|
#include "HttpPowerMeter.h"
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include <WiFiClientSecure.h>
|
#include <WiFiClientSecure.h>
|
||||||
#include <FirebaseJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <Crypto.h>
|
#include <Crypto.h>
|
||||||
#include <SHA256.h>
|
#include <SHA256.h>
|
||||||
#include <base64.h>
|
#include <base64.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
|
||||||
void HttpPowerMeterClass::init()
|
void HttpPowerMeterClass::init()
|
||||||
{
|
{
|
||||||
@ -22,9 +23,6 @@ bool HttpPowerMeterClass::updateValues()
|
|||||||
{
|
{
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
char response[2000],
|
|
||||||
errorMessage[256];
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
|
for (uint8_t i = 0; i < POWERMETER_MAX_PHASES; i++) {
|
||||||
POWERMETER_HTTP_PHASE_CONFIG_T phaseConfig = config.PowerMeter.Http_Phase[i];
|
POWERMETER_HTTP_PHASE_CONFIG_T phaseConfig = config.PowerMeter.Http_Phase[i];
|
||||||
|
|
||||||
@ -34,39 +32,63 @@ bool HttpPowerMeterClass::updateValues()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (i == 0 || config.PowerMeter.HttpIndividualRequests) {
|
if (i == 0 || config.PowerMeter.HttpIndividualRequests) {
|
||||||
if (httpRequest(phaseConfig.Url, phaseConfig.AuthType, phaseConfig.Username, phaseConfig.Password, phaseConfig.HeaderKey, phaseConfig.HeaderValue, phaseConfig.Timeout,
|
if (!queryPhase(i, phaseConfig.Url, phaseConfig.AuthType, phaseConfig.Username, phaseConfig.Password, phaseConfig.HeaderKey, phaseConfig.HeaderValue, phaseConfig.Timeout,
|
||||||
response, sizeof(response), errorMessage, sizeof(errorMessage))) {
|
phaseConfig.JsonPath)) {
|
||||||
if (!getFloatValueByJsonPath(response, phaseConfig.JsonPath, power[i])) {
|
MessageOutput.printf("[HttpPowerMeter] Getting the power of phase %d failed.\r\n", i + 1);
|
||||||
MessageOutput.printf("[HttpPowerMeter] Couldn't find a value with Json query \"%s\"\r\n", phaseConfig.JsonPath);
|
MessageOutput.printf("%s\r\n", httpPowerMeterError);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MessageOutput.printf("[HttpPowerMeter] Getting the power of phase %d failed. Error: %s\r\n",
|
|
||||||
i + 1, errorMessage);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpPowerMeterClass::httpRequest(const char* url, Auth authType, const char* username, const char* password, const char* httpHeader, const char* httpValue, uint32_t timeout,
|
bool HttpPowerMeterClass::queryPhase(int phase, const String& url, Auth authType, const char* username, const char* password,
|
||||||
char* response, size_t responseSize, char* error, size_t errorSize)
|
const char* httpHeader, const char* httpValue, uint32_t timeout, const char* jsonPath)
|
||||||
{
|
{
|
||||||
String urlProtocol;
|
//hostByName in WiFiGeneric fails to resolve local names. issue described in
|
||||||
String urlHostname;
|
//https://github.com/espressif/arduino-esp32/issues/3822
|
||||||
String urlUri;
|
//and in depth analyzed in https://github.com/espressif/esp-idf/issues/2507#issuecomment-761836300
|
||||||
extractUrlComponents(url, urlProtocol, urlHostname, urlUri);
|
//in conclusion: we cannot rely on httpClient.begin(*wifiClient, url) to resolve IP adresses.
|
||||||
|
//have to do it manually here. Feels Hacky...
|
||||||
|
String protocol;
|
||||||
|
String host;
|
||||||
|
String uri;
|
||||||
|
extractUrlComponents(url, protocol, host, uri);
|
||||||
|
|
||||||
response[0] = '\0';
|
IPAddress ipaddr((uint32_t)0);
|
||||||
error[0] = '\0';
|
//first check if "host" is already an IP adress
|
||||||
|
if (!ipaddr.fromString(host))
|
||||||
|
{
|
||||||
|
//"host"" is not an IP address so try to resolve the IP adress
|
||||||
|
//first try locally via mDNS, then via DNS. WiFiGeneric::hostByName() will spam the console if done the otherway around.
|
||||||
|
const bool mdnsEnabled = Configuration.get().Mdns.Enabled;
|
||||||
|
if (!mdnsEnabled) {
|
||||||
|
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("Error resolving host %s via DNS, try to enable mDNS in Network Settings"), host.c_str());
|
||||||
|
//ensure we try resolving via DNS even if mDNS is disabled
|
||||||
|
if(!WiFiGenericClass::hostByName(host.c_str(), ipaddr)){
|
||||||
|
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("Error resolving host %s via DNS"), host.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ipaddr = MDNS.queryHost(host);
|
||||||
|
if (ipaddr == INADDR_NONE){
|
||||||
|
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("Error resolving host %s via mDNS"), host.c_str());
|
||||||
|
//when we cannot find local server via mDNS, try resolving via DNS
|
||||||
|
if(!WiFiGenericClass::hostByName(host.c_str(), ipaddr)){
|
||||||
|
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("Error resolving host %s via DNS"), host.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// secureWifiClient MUST be created before HTTPClient
|
// secureWifiClient MUST be created before HTTPClient
|
||||||
// see discussion: https://github.com/helgeerbe/OpenDTU-OnBattery/issues/381
|
// see discussion: https://github.com/helgeerbe/OpenDTU-OnBattery/issues/381
|
||||||
std::unique_ptr<WiFiClient> wifiClient;
|
std::unique_ptr<WiFiClient> wifiClient;
|
||||||
|
|
||||||
if (urlProtocol == "https") {
|
bool https = protocol == "https";
|
||||||
|
if (https) {
|
||||||
auto secureWifiClient = std::make_unique<WiFiClientSecure>();
|
auto secureWifiClient = std::make_unique<WiFiClientSecure>();
|
||||||
secureWifiClient->setInsecure();
|
secureWifiClient->setInsecure();
|
||||||
wifiClient = std::move(secureWifiClient);
|
wifiClient = std::move(secureWifiClient);
|
||||||
@ -74,13 +96,19 @@ bool HttpPowerMeterClass::httpRequest(const char* url, Auth authType, const char
|
|||||||
wifiClient = std::make_unique<WiFiClient>();
|
wifiClient = std::make_unique<WiFiClient>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return httpRequest(phase, *wifiClient, ipaddr.toString(), uri, https, authType, username, password, httpHeader, httpValue, timeout, jsonPath);
|
||||||
|
}
|
||||||
|
|
||||||
if (!httpClient.begin(*wifiClient, url)) {
|
bool HttpPowerMeterClass::httpRequest(int phase, WiFiClient &wifiClient, const String& host, const String& uri, bool https, Auth authType, const char* username,
|
||||||
snprintf_P(error, errorSize, "httpClient.begin(%s) failed", url);
|
const char* password, const char* httpHeader, const char* httpValue, uint32_t timeout, const char* jsonPath)
|
||||||
return false;
|
{
|
||||||
|
int port = (https ? 443 : 80);
|
||||||
|
if(!httpClient.begin(wifiClient, host, port, uri, https)){
|
||||||
|
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("httpClient.begin() failed for %s://%s"), (https ? "https" : "http"), host.c_str());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
prepareRequest(timeout, httpHeader, httpValue);
|
|
||||||
|
|
||||||
|
prepareRequest(timeout, httpHeader, httpValue);
|
||||||
if (authType == Auth::digest) {
|
if (authType == Auth::digest) {
|
||||||
const char *headers[1] = {"WWW-Authenticate"};
|
const char *headers[1] = {"WWW-Authenticate"};
|
||||||
httpClient.collectHeaders(headers, 1);
|
httpClient.collectHeaders(headers, 1);
|
||||||
@ -92,111 +120,108 @@ bool HttpPowerMeterClass::httpRequest(const char* url, Auth authType, const char
|
|||||||
auth.concat(base64::encode(authString));
|
auth.concat(base64::encode(authString));
|
||||||
httpClient.addHeader("Authorization", auth);
|
httpClient.addHeader("Authorization", auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
int httpCode = httpClient.GET();
|
int httpCode = httpClient.GET();
|
||||||
|
|
||||||
if (httpCode == HTTP_CODE_UNAUTHORIZED && authType == Auth::digest) {
|
if (httpCode == HTTP_CODE_UNAUTHORIZED && authType == Auth::digest) {
|
||||||
// Handle authentication challenge
|
// Handle authentication challenge
|
||||||
char realm[256]; // Buffer to store the realm received from the server
|
|
||||||
char nonce[256]; // Buffer to store the nonce received from the server
|
|
||||||
if (httpClient.hasHeader("WWW-Authenticate")) {
|
if (httpClient.hasHeader("WWW-Authenticate")) {
|
||||||
String authHeader = httpClient.header("WWW-Authenticate");
|
String authReq = httpClient.header("WWW-Authenticate");
|
||||||
if (authHeader.indexOf("Digest") != -1) {
|
String authorization = getDigestAuth(authReq, String(username), String(password), "GET", String(uri), 1);
|
||||||
int realmIndex = authHeader.indexOf("realm=\"");
|
httpClient.end();
|
||||||
int nonceIndex = authHeader.indexOf("nonce=\"");
|
if(!httpClient.begin(wifiClient, host, port, uri, https)){
|
||||||
if (realmIndex != -1 && nonceIndex != -1) {
|
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("httpClient.begin() failed for %s://%s using digest auth"), (https ? "https" : "http"), host.c_str());
|
||||||
int realmEndIndex = authHeader.indexOf("\"", realmIndex + 7);
|
return false;
|
||||||
int nonceEndIndex = authHeader.indexOf("\"", nonceIndex + 7);
|
|
||||||
if (realmEndIndex != -1 && nonceEndIndex != -1) {
|
|
||||||
authHeader.substring(realmIndex + 7, realmEndIndex).toCharArray(realm, sizeof(realm));
|
|
||||||
authHeader.substring(nonceIndex + 7, nonceEndIndex).toCharArray(nonce, sizeof(nonce));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String cnonce = String(random(1000)); // Generate client nonce
|
|
||||||
String str = username;
|
|
||||||
str += ":";
|
|
||||||
str += realm;
|
|
||||||
str += ":";
|
|
||||||
str += password;
|
|
||||||
String ha1 = sha256(str);
|
|
||||||
str = "GET:";
|
|
||||||
str += urlUri;
|
|
||||||
String ha2 = sha256(str);
|
|
||||||
str = ha1;
|
|
||||||
str += ":";
|
|
||||||
str += nonce;
|
|
||||||
str += ":00000001:";
|
|
||||||
str += cnonce;
|
|
||||||
str += ":auth:";
|
|
||||||
str += ha2;
|
|
||||||
String response = sha256(str);
|
|
||||||
|
|
||||||
String authorization = "Digest username=\"";
|
|
||||||
authorization += username;
|
|
||||||
authorization += "\", realm=\"";
|
|
||||||
authorization += realm;
|
|
||||||
authorization += "\", nonce=\"";
|
|
||||||
authorization += nonce;
|
|
||||||
authorization += "\", uri=\"";
|
|
||||||
authorization += urlUri;
|
|
||||||
authorization += "\", cnonce=\"";
|
|
||||||
authorization += cnonce;
|
|
||||||
authorization += "\", nc=00000001, qop=auth, response=\"";
|
|
||||||
authorization += response;
|
|
||||||
authorization += "\", algorithm=SHA-256";
|
|
||||||
httpClient.end();
|
|
||||||
if (!httpClient.begin(*wifiClient, url)) {
|
|
||||||
snprintf_P(error, errorSize, "httpClient.begin(%s) for digest auth failed", url);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
prepareRequest(timeout, httpHeader, httpValue);
|
|
||||||
httpClient.addHeader("Authorization", authorization);
|
|
||||||
httpCode = httpClient.GET();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepareRequest(timeout, httpHeader, httpValue);
|
||||||
|
httpClient.addHeader("Authorization", authorization);
|
||||||
|
httpCode = httpClient.GET();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bool result = tryGetFloatValueForPhase(phase, httpCode, jsonPath);
|
||||||
|
httpClient.end();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String HttpPowerMeterClass::extractParam(String& authReq, const String& param, const char delimit) {
|
||||||
|
int _begin = authReq.indexOf(param);
|
||||||
|
if (_begin == -1) { return ""; }
|
||||||
|
return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
String HttpPowerMeterClass::getcNonce(const int len) {
|
||||||
|
static const char alphanum[] = "0123456789"
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
"abcdefghijklmnopqrstuvwxyz";
|
||||||
|
String s = "";
|
||||||
|
|
||||||
|
for (int i = 0; i < len; ++i) { s += alphanum[rand() % (sizeof(alphanum) - 1)]; }
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
String HttpPowerMeterClass::getDigestAuth(String& authReq, const String& username, const String& password, const String& method, const String& uri, unsigned int counter) {
|
||||||
|
// extracting required parameters for RFC 2617 Digest
|
||||||
|
String realm = extractParam(authReq, "realm=\"", '"');
|
||||||
|
String nonce = extractParam(authReq, "nonce=\"", '"');
|
||||||
|
String cNonce = getcNonce(8);
|
||||||
|
|
||||||
|
char nc[9];
|
||||||
|
snprintf(nc, sizeof(nc), "%08x", counter);
|
||||||
|
|
||||||
|
//sha256 of the user:realm:password
|
||||||
|
String ha1 = sha256(username + ":" + realm + ":" + password);
|
||||||
|
|
||||||
|
//sha256 of method:uri
|
||||||
|
String ha2 = sha256(method + ":" + uri);
|
||||||
|
|
||||||
|
//sha256 of h1:nonce:nc:cNonce:auth:h2
|
||||||
|
String response = sha256(ha1 + ":" + nonce + ":" + String(nc) + ":" + cNonce + ":" + "auth" + ":" + ha2);
|
||||||
|
|
||||||
|
//Final authorization String;
|
||||||
|
String authorization = "Digest username=\"";
|
||||||
|
authorization += username;
|
||||||
|
authorization += "\", realm=\"";
|
||||||
|
authorization += realm;
|
||||||
|
authorization += "\", nonce=\"";
|
||||||
|
authorization += nonce;
|
||||||
|
authorization += "\", uri=\"";
|
||||||
|
authorization += uri;
|
||||||
|
authorization += "\", cnonce=\"";
|
||||||
|
authorization += cNonce;
|
||||||
|
authorization += "\", nc=";
|
||||||
|
authorization += String(nc);
|
||||||
|
authorization += ", qop=auth, response=\"";
|
||||||
|
authorization += response;
|
||||||
|
authorization += "\", algorithm=SHA-256";
|
||||||
|
|
||||||
|
return authorization;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpPowerMeterClass::tryGetFloatValueForPhase(int phase, int httpCode, const char* jsonPath)
|
||||||
|
{
|
||||||
|
bool success = false;
|
||||||
if (httpCode == HTTP_CODE_OK) {
|
if (httpCode == HTTP_CODE_OK) {
|
||||||
String responseBody = httpClient.getString();
|
httpResponse = httpClient.getString(); //very unfortunate that we cannot parse WifiClient stream directly
|
||||||
|
StaticJsonDocument<2048> json; //however creating these allocations on stack should be fine to avoid heap fragmentation
|
||||||
if (responseBody.length() > (responseSize - 1)) {
|
deserializeJson(json, httpResponse);
|
||||||
snprintf_P(error, errorSize, "Response too large! Response length: %d Body start: %s",
|
if(!json.containsKey(jsonPath))
|
||||||
httpClient.getSize(), responseBody.c_str());
|
{
|
||||||
} else {
|
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("[HttpPowerMeter] Couldn't find a value for phase %i with Json query \"%s\""), phase, jsonPath);
|
||||||
snprintf(response, responseSize, responseBody.c_str());
|
}else {
|
||||||
|
power[phase] = json[jsonPath].as<float>();
|
||||||
|
//MessageOutput.printf("Power for Phase %i: %5.2fW\r\n", phase, power[phase]);
|
||||||
|
success = true;
|
||||||
}
|
}
|
||||||
} else if (httpCode <= 0) {
|
} else if (httpCode <= 0) {
|
||||||
snprintf_P(error, errorSize, "Error(%s): %s", url, httpClient.errorToString(httpCode).c_str());
|
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("HTTP Error %s"), httpClient.errorToString(httpCode).c_str());
|
||||||
} else if (httpCode != HTTP_CODE_OK) {
|
} else if (httpCode != HTTP_CODE_OK) {
|
||||||
snprintf_P(error, errorSize, "Bad HTTP code: %d", httpCode);
|
snprintf_P(httpPowerMeterError, sizeof(httpPowerMeterError), PSTR("Bad HTTP code: %d"), httpCode);
|
||||||
}
|
}
|
||||||
|
return success;
|
||||||
httpClient.end();
|
|
||||||
|
|
||||||
if (error[0] != '\0') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float HttpPowerMeterClass::getFloatValueByJsonPath(const char* jsonString, const char* jsonPath, float& value)
|
void HttpPowerMeterClass::extractUrlComponents(const String& url, String& protocol, String& hostname, String& uri) {
|
||||||
{
|
|
||||||
FirebaseJson firebaseJson;
|
|
||||||
firebaseJson.setJsonData(jsonString);
|
|
||||||
|
|
||||||
FirebaseJsonData firebaseJsonResult;
|
|
||||||
if (!firebaseJson.get(firebaseJsonResult, jsonPath)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = firebaseJsonResult.to<float>();
|
|
||||||
|
|
||||||
firebaseJson.clear();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpPowerMeterClass::extractUrlComponents(const String& url, String& protocol, String& hostname, String& uri) {
|
|
||||||
// Find protocol delimiter
|
// Find protocol delimiter
|
||||||
int protocolEndIndex = url.indexOf(":");
|
int protocolEndIndex = url.indexOf(":");
|
||||||
if (protocolEndIndex != -1) {
|
if (protocolEndIndex != -1) {
|
||||||
@ -229,25 +254,24 @@ float HttpPowerMeterClass::getFloatValueByJsonPath(const char* jsonString, const
|
|||||||
#define HASH_SIZE 32
|
#define HASH_SIZE 32
|
||||||
|
|
||||||
String HttpPowerMeterClass::sha256(const String& data) {
|
String HttpPowerMeterClass::sha256(const String& data) {
|
||||||
SHA256 sha256;
|
SHA256 sha256;
|
||||||
uint8_t hash[HASH_SIZE];
|
uint8_t hash[HASH_SIZE];
|
||||||
|
|
||||||
sha256.reset();
|
sha256.reset();
|
||||||
sha256.update(data.c_str(), data.length());
|
sha256.update(data.c_str(), data.length());
|
||||||
sha256.finalize(hash, HASH_SIZE);
|
sha256.finalize(hash, HASH_SIZE);
|
||||||
|
|
||||||
String hashStr = "";
|
String hashStr = "";
|
||||||
for (int i = 0; i < HASH_SIZE; i++) {
|
for (int i = 0; i < HASH_SIZE; i++) {
|
||||||
String hex = String(hash[i], HEX);
|
String hex = String(hash[i], HEX);
|
||||||
if (hex.length() == 1) {
|
if (hex.length() == 1) {
|
||||||
hashStr += "0";
|
hashStr += "0";
|
||||||
|
}
|
||||||
|
hashStr += hex;
|
||||||
}
|
}
|
||||||
hashStr += hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hashStr;
|
return hashStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpPowerMeterClass::prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue) {
|
void HttpPowerMeterClass::prepareRequest(uint32_t timeout, const char* httpHeader, const char* httpValue) {
|
||||||
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||||
httpClient.setUserAgent("OpenDTU-OnBattery");
|
httpClient.setUserAgent("OpenDTU-OnBattery");
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Thomas Basler and others
|
* Copyright (C) 2023-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "InverterSettings.h"
|
#include "InverterSettings.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
@ -45,6 +45,8 @@ void InverterSettingsClass::init(Scheduler& scheduler)
|
|||||||
|
|
||||||
if (PinMapping.isValidCmt2300Config()) {
|
if (PinMapping.isValidCmt2300Config()) {
|
||||||
Hoymiles.initCMT(pin.cmt_sdio, pin.cmt_clk, pin.cmt_cs, pin.cmt_fcs, pin.cmt_gpio2, pin.cmt_gpio3);
|
Hoymiles.initCMT(pin.cmt_sdio, pin.cmt_clk, pin.cmt_cs, pin.cmt_fcs, pin.cmt_gpio2, pin.cmt_gpio3);
|
||||||
|
MessageOutput.println(F(" Setting country mode... "));
|
||||||
|
Hoymiles.getRadioCmt()->setCountryMode(static_cast<CountryModeId_t>(config.Dtu.Cmt.CountryMode));
|
||||||
MessageOutput.println(F(" Setting CMT target frequency... "));
|
MessageOutput.println(F(" Setting CMT target frequency... "));
|
||||||
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency);
|
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,7 +83,8 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
|||||||
display["screensaver"] = config.Display.ScreenSaver;
|
display["screensaver"] = config.Display.ScreenSaver;
|
||||||
display["contrast"] = config.Display.Contrast;
|
display["contrast"] = config.Display.Contrast;
|
||||||
display["language"] = config.Display.Language;
|
display["language"] = config.Display.Language;
|
||||||
display["diagramduration"] = config.Display.DiagramDuration;
|
display["diagramduration"] = config.Display.Diagram.Duration;
|
||||||
|
display["diagrammode"] = config.Display.Diagram.Mode;
|
||||||
|
|
||||||
auto leds = root.createNestedArray("led");
|
auto leds = root.createNestedArray("led");
|
||||||
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
||||||
@ -179,7 +180,8 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
|||||||
config.Display.ScreenSaver = root["display"]["screensaver"].as<bool>();
|
config.Display.ScreenSaver = root["display"]["screensaver"].as<bool>();
|
||||||
config.Display.Contrast = root["display"]["contrast"].as<uint8_t>();
|
config.Display.Contrast = root["display"]["contrast"].as<uint8_t>();
|
||||||
config.Display.Language = root["display"]["language"].as<uint8_t>();
|
config.Display.Language = root["display"]["language"].as<uint8_t>();
|
||||||
config.Display.DiagramDuration = root["display"]["diagramduration"].as<uint32_t>();
|
config.Display.Diagram.Duration = root["display"]["diagramduration"].as<uint32_t>();
|
||||||
|
config.Display.Diagram.Mode = root["display"]["diagrammode"].as<DiagramMode_t>();
|
||||||
|
|
||||||
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
for (uint8_t i = 0; i < PINMAPPING_LED_COUNT; i++) {
|
||||||
config.Led_Single[i].Brightness = root["led"][i]["brightness"].as<uint8_t>();
|
config.Led_Single[i].Brightness = root["led"][i]["brightness"].as<uint8_t>();
|
||||||
@ -191,6 +193,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
|||||||
Display.enableScreensaver = config.Display.ScreenSaver;
|
Display.enableScreensaver = config.Display.ScreenSaver;
|
||||||
Display.setContrast(config.Display.Contrast);
|
Display.setContrast(config.Display.Contrast);
|
||||||
Display.setLanguage(config.Display.Language);
|
Display.setLanguage(config.Display.Language);
|
||||||
|
Display.setDiagramMode(static_cast<DiagramMode_t>(config.Display.Diagram.Mode));
|
||||||
Display.Diagram().updatePeriod();
|
Display.Diagram().updatePeriod();
|
||||||
|
|
||||||
WebApi.writeConfig(retMsg);
|
WebApi.writeConfig(retMsg);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022-2023 Thomas Basler and others
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "WebApi_dtu.h"
|
#include "WebApi_dtu.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
@ -21,6 +21,18 @@ void WebApiDtuClass::init(AsyncWebServer& server)
|
|||||||
|
|
||||||
void WebApiDtuClass::loop()
|
void WebApiDtuClass::loop()
|
||||||
{
|
{
|
||||||
|
if (_performReload) {
|
||||||
|
// Execute stuff in main thread to avoid busy SPI bus
|
||||||
|
CONFIG_T& config = Configuration.get();
|
||||||
|
Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu.Nrf.PaLevel);
|
||||||
|
Hoymiles.getRadioCmt()->setPALevel(config.Dtu.Cmt.PaLevel);
|
||||||
|
Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu.Serial);
|
||||||
|
Hoymiles.getRadioCmt()->setDtuSerial(config.Dtu.Serial);
|
||||||
|
Hoymiles.getRadioCmt()->setCountryMode(static_cast<CountryModeId_t>(config.Dtu.Cmt.CountryMode));
|
||||||
|
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency);
|
||||||
|
Hoymiles.setPollInterval(config.Dtu.PollInterval);
|
||||||
|
_performReload = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
|
void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
|
||||||
@ -46,6 +58,19 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
|
|||||||
root["cmt_enabled"] = Hoymiles.getRadioCmt()->isInitialized();
|
root["cmt_enabled"] = Hoymiles.getRadioCmt()->isInitialized();
|
||||||
root["cmt_palevel"] = config.Dtu.Cmt.PaLevel;
|
root["cmt_palevel"] = config.Dtu.Cmt.PaLevel;
|
||||||
root["cmt_frequency"] = config.Dtu.Cmt.Frequency;
|
root["cmt_frequency"] = config.Dtu.Cmt.Frequency;
|
||||||
|
root["cmt_country"] = config.Dtu.Cmt.CountryMode;
|
||||||
|
root["cmt_chan_width"] = Hoymiles.getRadioCmt()->getChannelWidth();
|
||||||
|
|
||||||
|
auto data = root.createNestedArray("country_def");
|
||||||
|
auto countryDefs = Hoymiles.getRadioCmt()->getCountryFrequencyList();
|
||||||
|
for (const auto& definition : countryDefs) {
|
||||||
|
auto obj = data.createNestedObject();
|
||||||
|
obj["freq_default"] = definition.definition.Freq_Default;
|
||||||
|
obj["freq_min"] = definition.definition.Freq_Min;
|
||||||
|
obj["freq_max"] = definition.definition.Freq_Max;
|
||||||
|
obj["freq_legal_min"] = definition.definition.Freq_Legal_Min;
|
||||||
|
obj["freq_legal_max"] = definition.definition.Freq_Legal_Max;
|
||||||
|
}
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
@ -95,7 +120,8 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
|||||||
&& root.containsKey("verbose_logging")
|
&& root.containsKey("verbose_logging")
|
||||||
&& root.containsKey("nrf_palevel")
|
&& root.containsKey("nrf_palevel")
|
||||||
&& root.containsKey("cmt_palevel")
|
&& root.containsKey("cmt_palevel")
|
||||||
&& root.containsKey("cmt_frequency"))) {
|
&& root.containsKey("cmt_frequency")
|
||||||
|
&& root.containsKey("cmt_country"))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
@ -135,14 +161,23 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["cmt_frequency"].as<uint32_t>() < Hoymiles.getRadioCmt()->getMinFrequency()
|
if (root["cmt_country"].as<uint8_t>() >= CountryModeId_t::CountryModeId_Max) {
|
||||||
|| root["cmt_frequency"].as<uint32_t>() > Hoymiles.getRadioCmt()->getMaxFrequency()
|
retMsg["message"] = "Invalid country setting!";
|
||||||
|| root["cmt_frequency"].as<uint32_t>() % 250 > 0) {
|
retMsg["code"] = WebApiError::DtuInvalidCmtCountry;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto FrequencyDefinition = Hoymiles.getRadioCmt()->getCountryFrequencyList()[root["cmt_country"].as<CountryModeId_t>()].definition;
|
||||||
|
if (root["cmt_frequency"].as<uint32_t>() < FrequencyDefinition.Freq_Min
|
||||||
|
|| root["cmt_frequency"].as<uint32_t>() > FrequencyDefinition.Freq_Max
|
||||||
|
|| root["cmt_frequency"].as<uint32_t>() % Hoymiles.getRadioCmt()->getChannelWidth() > 0) {
|
||||||
|
|
||||||
retMsg["message"] = "Invalid CMT frequency setting!";
|
retMsg["message"] = "Invalid CMT frequency setting!";
|
||||||
retMsg["code"] = WebApiError::DtuInvalidCmtFrequency;
|
retMsg["code"] = WebApiError::DtuInvalidCmtFrequency;
|
||||||
retMsg["param"]["min"] = Hoymiles.getRadioCmt()->getMinFrequency();
|
retMsg["param"]["min"] = FrequencyDefinition.Freq_Min;
|
||||||
retMsg["param"]["max"] = Hoymiles.getRadioCmt()->getMaxFrequency();
|
retMsg["param"]["max"] = FrequencyDefinition.Freq_Max;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
@ -157,17 +192,12 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
|||||||
config.Dtu.Nrf.PaLevel = root["nrf_palevel"].as<uint8_t>();
|
config.Dtu.Nrf.PaLevel = root["nrf_palevel"].as<uint8_t>();
|
||||||
config.Dtu.Cmt.PaLevel = root["cmt_palevel"].as<int8_t>();
|
config.Dtu.Cmt.PaLevel = root["cmt_palevel"].as<int8_t>();
|
||||||
config.Dtu.Cmt.Frequency = root["cmt_frequency"].as<uint32_t>();
|
config.Dtu.Cmt.Frequency = root["cmt_frequency"].as<uint32_t>();
|
||||||
|
config.Dtu.Cmt.CountryMode = root["cmt_country"].as<CountryModeId_t>();
|
||||||
|
|
||||||
WebApi.writeConfig(retMsg);
|
WebApi.writeConfig(retMsg);
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
|
|
||||||
Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu.Nrf.PaLevel);
|
_performReload = true;
|
||||||
Hoymiles.getRadioCmt()->setPALevel(config.Dtu.Cmt.PaLevel);
|
|
||||||
Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu.Serial);
|
|
||||||
Hoymiles.getRadioCmt()->setDtuSerial(config.Dtu.Serial);
|
|
||||||
Hoymiles.getRadioCmt()->setInverterTargetFrequency(config.Dtu.Cmt.Frequency);
|
|
||||||
Hoymiles.setPollInterval(config.Dtu.PollInterval);
|
|
||||||
Hoymiles.setVerboseLogging(config.Dtu.VerboseLogging);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -200,6 +200,7 @@ void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
|||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
|
|
||||||
|
// reboot requiered as per https://github.com/helgeerbe/OpenDTU-OnBattery/issues/565#issuecomment-1872552559
|
||||||
yield();
|
yield();
|
||||||
delay(1000);
|
delay(1000);
|
||||||
yield();
|
yield();
|
||||||
@ -251,25 +252,18 @@ void WebApiPowerMeterClass::onTestHttpRequest(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char powerMeterResponse[2000],
|
|
||||||
errorMessage[256];
|
|
||||||
char response[200];
|
|
||||||
|
|
||||||
if (HttpPowerMeter.httpRequest(root["url"].as<String>().c_str(),
|
char response[256];
|
||||||
root["auth_type"].as<Auth>(), root["username"].as<String>().c_str(), root["password"].as<String>().c_str(),
|
|
||||||
root["header_key"].as<String>().c_str(), root["header_value"].as<String>().c_str(), root["timeout"].as<uint16_t>(),
|
|
||||||
powerMeterResponse, sizeof(powerMeterResponse), errorMessage, sizeof(errorMessage))) {
|
|
||||||
float power;
|
|
||||||
|
|
||||||
if (HttpPowerMeter.getFloatValueByJsonPath(powerMeterResponse,
|
int phase = 0;//"absuing" index 0 of the float power[3] in HttpPowerMeter to store the result
|
||||||
root["json_path"].as<String>().c_str(), power)) {
|
if (HttpPowerMeter.queryPhase(phase, root[F("url")].as<String>().c_str(),
|
||||||
retMsg["type"] = "success";
|
root[F("auth_type")].as<Auth>(), root[F("username")].as<String>().c_str(), root[F("password")].as<String>().c_str(),
|
||||||
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", power);
|
root[F("header_key")].as<String>().c_str(), root[F("header_value")].as<String>().c_str(), root[F("timeout")].as<uint16_t>(),
|
||||||
} else {
|
root[F("json_path")].as<String>().c_str())) {
|
||||||
snprintf_P(response, sizeof(response), "Error: Could not find value for JSON path!");
|
retMsg[F("type")] = F("success");
|
||||||
}
|
snprintf_P(response, sizeof(response), "Success! Power: %5.2fW", HttpPowerMeter.getPower(phase + 1));
|
||||||
} else {
|
} else {
|
||||||
snprintf_P(response, sizeof(response), errorMessage);
|
snprintf_P(response, sizeof(response), "%s", HttpPowerMeter.httpPowerMeterError);
|
||||||
}
|
}
|
||||||
|
|
||||||
retMsg["message"] = response;
|
retMsg["message"] = response;
|
||||||
|
|||||||
@ -82,6 +82,22 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques
|
|||||||
stream->printf("opendtu_last_update{serial=\"%s\",unit=\"%d\",name=\"%s\"} %d\n",
|
stream->printf("opendtu_last_update{serial=\"%s\",unit=\"%d\",name=\"%s\"} %d\n",
|
||||||
serial.c_str(), i, name, inv->Statistics()->getLastUpdate() / 1000);
|
serial.c_str(), i, name, inv->Statistics()->getLastUpdate() / 1000);
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
stream->print("# HELP opendtu_inverter_limit_relative current relative limit of the inverter\n");
|
||||||
|
stream->print("# TYPE opendtu_inverter_limit_relative gauge\n");
|
||||||
|
}
|
||||||
|
stream->printf("opendtu_inverter_limit_relative{serial=\"%s\",unit=\"%d\",name=\"%s\"} %f\n",
|
||||||
|
serial.c_str(), i, name, inv->SystemConfigPara()->getLimitPercent() / 100.0);
|
||||||
|
|
||||||
|
if (inv->DevInfo()->getMaxPower() > 0) {
|
||||||
|
if (i == 0) {
|
||||||
|
stream->print("# HELP opendtu_inverter_limit_absolute current relative limit of the inverter\n");
|
||||||
|
stream->print("# TYPE opendtu_inverter_limit_absolute gauge\n");
|
||||||
|
}
|
||||||
|
stream->printf("opendtu_inverter_limit_absolute{serial=\"%s\",unit=\"%d\",name=\"%s\"} %f\n",
|
||||||
|
serial.c_str(), i, name, inv->SystemConfigPara()->getLimitPercent() * inv->DevInfo()->getMaxPower() / 100.0);
|
||||||
|
}
|
||||||
|
|
||||||
// Loop all channels if Statistics have been updated at least once since DTU boot
|
// Loop all channels if Statistics have been updated at least once since DTU boot
|
||||||
if (inv->Statistics()->getLastUpdate() > 0) {
|
if (inv->Statistics()->getLastUpdate() > 0) {
|
||||||
for (auto& t : inv->Statistics()->getChannelTypes()) {
|
for (auto& t : inv->Statistics()->getChannelTypes()) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022-2023 Thomas Basler and others
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "Datastore.h"
|
#include "Datastore.h"
|
||||||
@ -139,6 +139,7 @@ void setup()
|
|||||||
Display.enableScreensaver = config.Display.ScreenSaver;
|
Display.enableScreensaver = config.Display.ScreenSaver;
|
||||||
Display.setContrast(config.Display.Contrast);
|
Display.setContrast(config.Display.Contrast);
|
||||||
Display.setLanguage(config.Display.Language);
|
Display.setLanguage(config.Display.Language);
|
||||||
|
Display.setDiagramMode(static_cast<DiagramMode_t>(config.Display.Diagram.Mode));
|
||||||
Display.setStartupDisplay();
|
Display.setStartupDisplay();
|
||||||
MessageOutput.println("done");
|
MessageOutput.println("done");
|
||||||
|
|
||||||
|
|||||||
@ -18,8 +18,8 @@
|
|||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"sortablejs": "^1.15.1",
|
"sortablejs": "^1.15.1",
|
||||||
"spark-md5": "^3.0.2",
|
"spark-md5": "^3.0.2",
|
||||||
"vue": "^3.4.5",
|
"vue": "^3.4.13",
|
||||||
"vue-i18n": "^9.8.0",
|
"vue-i18n": "^9.9.0",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -27,19 +27,21 @@
|
|||||||
"@rushstack/eslint-patch": "^1.6.1",
|
"@rushstack/eslint-patch": "^1.6.1",
|
||||||
"@tsconfig/node18": "^18.2.2",
|
"@tsconfig/node18": "^18.2.2",
|
||||||
"@types/bootstrap": "^5.2.10",
|
"@types/bootstrap": "^5.2.10",
|
||||||
"@types/node": "^20.10.6",
|
"@types/node": "^20.11.0",
|
||||||
|
"@types/pulltorefreshjs": "^0.1.7",
|
||||||
"@types/sortablejs": "^1.15.7",
|
"@types/sortablejs": "^1.15.7",
|
||||||
"@types/spark-md5": "^3.0.4",
|
"@types/spark-md5": "^3.0.4",
|
||||||
"@vitejs/plugin-vue": "^5.0.2",
|
"@vitejs/plugin-vue": "^5.0.3",
|
||||||
"@vue/eslint-config-typescript": "^12.0.0",
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-plugin-vue": "^9.19.2",
|
"eslint-plugin-vue": "^9.20.1",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
|
"pulltorefreshjs": "^0.1.22",
|
||||||
"sass": "^1.69.7",
|
"sass": "^1.69.7",
|
||||||
"terser": "^5.26.0",
|
"terser": "^5.26.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.10",
|
"vite": "^5.0.11",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-css-injected-by-js": "^3.3.1",
|
"vite-plugin-css-injected-by-js": "^3.3.1",
|
||||||
"vue-tsc": "^1.8.27"
|
"vue-tsc": "^1.8.27"
|
||||||
|
|||||||
@ -4,7 +4,12 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-11">
|
<div class="col-sm-11">
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ title }}
|
||||||
|
<span v-if="showWebSocket" :class="{
|
||||||
|
'onlineMarker': isWebsocketConnected,
|
||||||
|
'offlineMarker': !isWebsocketConnected,
|
||||||
|
}"></span>
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-1" v-if="showReload">
|
<div class="col-sm-1" v-if="showReload">
|
||||||
<button type="button" class="float-end btn btn-outline-primary"
|
<button type="button" class="float-end btn btn-outline-primary"
|
||||||
@ -28,6 +33,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { BIconArrowClockwise } from 'bootstrap-icons-vue';
|
import { BIconArrowClockwise } from 'bootstrap-icons-vue';
|
||||||
|
import PullToRefresh from 'pulltorefreshjs';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -37,7 +43,81 @@ export default defineComponent({
|
|||||||
title: { type: String, required: true },
|
title: { type: String, required: true },
|
||||||
isLoading: { type: Boolean, required: false, default: false },
|
isLoading: { type: Boolean, required: false, default: false },
|
||||||
isWideScreen: { type: Boolean, required: false, default: false },
|
isWideScreen: { type: Boolean, required: false, default: false },
|
||||||
|
isWebsocketConnected: { type: Boolean, required: false, default: false },
|
||||||
|
showWebSocket: { type: Boolean, required: false, default: false },
|
||||||
showReload: { type: Boolean, required: false, default: false },
|
showReload: { type: Boolean, required: false, default: false },
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
var self = this;
|
||||||
|
console.log("init");
|
||||||
|
PullToRefresh.init({
|
||||||
|
mainElement: 'main', // above which element?
|
||||||
|
instructionsPullToRefresh: this.$t('base.Pull'),
|
||||||
|
instructionsReleaseToRefresh: this.$t('base.Release'),
|
||||||
|
instructionsRefreshing: this.$t('base.Refreshing'),
|
||||||
|
onRefresh: function() {
|
||||||
|
self.$emit('reload');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
console.log("destroy");
|
||||||
|
PullToRefresh.destroyAll();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.ptr--text {
|
||||||
|
color: var(--bs-primary-text-emphasis) !important;
|
||||||
|
}
|
||||||
|
.ptr--icon {
|
||||||
|
color: var(--bs-primary-text-emphasis) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offlineMarker:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #ff0000;
|
||||||
|
border-color: #ff0000;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.onlineMarker:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #00bb00;
|
||||||
|
border-color: #00bb00;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.onlineMarker:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin: -12px 0 0 -12px;
|
||||||
|
border: 1px solid #00bb00;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 4px #00bb00, inset 0 0 4px rgb(56, 111, 169);
|
||||||
|
transform: scale(0);
|
||||||
|
animation: online 2.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes online {
|
||||||
|
0% {
|
||||||
|
transform: scale(.1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
transform: scale(2.5);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -35,7 +35,10 @@
|
|||||||
"Loading": "Lade...",
|
"Loading": "Lade...",
|
||||||
"Reload": "Aktualisieren",
|
"Reload": "Aktualisieren",
|
||||||
"Cancel": "Abbrechen",
|
"Cancel": "Abbrechen",
|
||||||
"Save": "Speichern"
|
"Save": "Speichern",
|
||||||
|
"Refreshing": "Aktualisieren",
|
||||||
|
"Pull": "Zum Aktualisieren nach unten ziehen",
|
||||||
|
"Release": "Loslassen zum Aktualisieren"
|
||||||
},
|
},
|
||||||
"localeswitcher": {
|
"localeswitcher": {
|
||||||
"Dark": "Dunkel",
|
"Dark": "Dunkel",
|
||||||
@ -53,6 +56,7 @@
|
|||||||
"2002": "Das Abfraginterval muss größer als 0 sein!",
|
"2002": "Das Abfraginterval muss größer als 0 sein!",
|
||||||
"2003": "Ungültige Sendeleistung angegeben!",
|
"2003": "Ungültige Sendeleistung angegeben!",
|
||||||
"2004": "Die Frequenz muss zwischen {min} und {max} kHz liegen und ein vielfaches von 250kHz betragen!",
|
"2004": "Die Frequenz muss zwischen {min} und {max} kHz liegen und ein vielfaches von 250kHz betragen!",
|
||||||
|
"2005": "Ungültige Landesauswahl!",
|
||||||
"3001": "Nichts gelöscht!",
|
"3001": "Nichts gelöscht!",
|
||||||
"3002": "Konfiguration zurückgesetzt. Starte jetzt neu...",
|
"3002": "Konfiguration zurückgesetzt. Starte jetzt neu...",
|
||||||
"4001": "@:apiresponse.2001",
|
"4001": "@:apiresponse.2001",
|
||||||
@ -418,9 +422,14 @@
|
|||||||
"CmtPaLevel": "CMT2300A Sendeleistung:",
|
"CmtPaLevel": "CMT2300A Sendeleistung:",
|
||||||
"NrfPaLevelHint": "Verwendet für HM-Wechselrichter. Stellen Sie sicher, dass Ihre Stromversorgung stabil genug ist, bevor Sie die Sendeleistung erhöhen.",
|
"NrfPaLevelHint": "Verwendet für HM-Wechselrichter. Stellen Sie sicher, dass Ihre Stromversorgung stabil genug ist, bevor Sie die Sendeleistung erhöhen.",
|
||||||
"CmtPaLevelHint": "Verwendet für HMS/HMT-Wechselrichter. Stellen Sie sicher, dass Ihre Stromversorgung stabil genug ist, bevor Sie die Sendeleistung erhöhen.",
|
"CmtPaLevelHint": "Verwendet für HMS/HMT-Wechselrichter. Stellen Sie sicher, dass Ihre Stromversorgung stabil genug ist, bevor Sie die Sendeleistung erhöhen.",
|
||||||
|
"CmtCountry": "Region/Land:",
|
||||||
|
"CmtCountryHint": "Jedes Land hat unterschiedliche Frequenzzuteilungen.",
|
||||||
|
"country_0": "Europa ({min}MHz - {max}MHz)",
|
||||||
|
"country_1": "Nordamerika ({min}MHz - {max}MHz)",
|
||||||
|
"country_2": "Brasilien ({min}MHz - {max}MHz)",
|
||||||
"CmtFrequency": "CMT2300A Frequenz:",
|
"CmtFrequency": "CMT2300A Frequenz:",
|
||||||
"CmtFrequencyHint": "Stelle sicher, dass du nur Frequenzen verwendet werden welche im entsprechenden Land erlaubt sind! Nach einer Frequenzänderung kann es bis zu 15min dauern bis eine Verbindung hergestellt wird.",
|
"CmtFrequencyHint": "Stelle sicher, dass du nur Frequenzen verwendet werden welche im entsprechenden Land erlaubt sind! Nach einer Frequenzänderung kann es bis zu 15min dauern bis eine Verbindung hergestellt wird.",
|
||||||
"CmtFrequencyWarning": "Die ausgewählte Frequenz befindet außerhalb des in der EU zugelassenen Bereiches. Vergewissere dich, dass mit dieser Auswahl keine lokalen Regularien verletzt werden.",
|
"CmtFrequencyWarning": "Die gewählte Frequenz liegt außerhalb des zulässigen Bereichs in der gewählten Region/dem Land. Vergewissere dich, dass mit dieser Auswahl keine lokalen Regularien verletzt werden.",
|
||||||
"MHz": "{mhz} MHz",
|
"MHz": "{mhz} MHz",
|
||||||
"dBm": "{dbm} dBm",
|
"dBm": "{dbm} dBm",
|
||||||
"Min": "Minimum ({db} dBm)",
|
"Min": "Minimum ({db} dBm)",
|
||||||
@ -744,6 +753,10 @@
|
|||||||
"PowerSafeHint": "Schaltet das Display aus, wenn kein Wechselrichter Strom erzeugt",
|
"PowerSafeHint": "Schaltet das Display aus, wenn kein Wechselrichter Strom erzeugt",
|
||||||
"Screensaver": "Bildschirmschoner aktivieren:",
|
"Screensaver": "Bildschirmschoner aktivieren:",
|
||||||
"ScreensaverHint": "Bewegt die Ausgabe bei jeder Aktualisierung um ein Einbrennen zu verhindern (v. a. für OLED-Displays nützlich)",
|
"ScreensaverHint": "Bewegt die Ausgabe bei jeder Aktualisierung um ein Einbrennen zu verhindern (v. a. für OLED-Displays nützlich)",
|
||||||
|
"DiagramMode": "Diagramm Modus:",
|
||||||
|
"off": "Deaktiviert",
|
||||||
|
"small": "Klein",
|
||||||
|
"fullscreen": "Vollbild",
|
||||||
"DiagramDuration": "Diagramm Periode:",
|
"DiagramDuration": "Diagramm Periode:",
|
||||||
"DiagramDurationHint": "Die Zeitperiode welche im Diagramm dargestellt wird.",
|
"DiagramDurationHint": "Die Zeitperiode welche im Diagramm dargestellt wird.",
|
||||||
"Seconds": "Sekunden",
|
"Seconds": "Sekunden",
|
||||||
|
|||||||
@ -35,7 +35,10 @@
|
|||||||
"Loading": "Loading...",
|
"Loading": "Loading...",
|
||||||
"Reload": "Reload",
|
"Reload": "Reload",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
"Save": "Save"
|
"Save": "Save",
|
||||||
|
"Refreshing": "Refreshing",
|
||||||
|
"Pull": "Pull down to refresh",
|
||||||
|
"Release": "Release to refresh"
|
||||||
},
|
},
|
||||||
"localeswitcher": {
|
"localeswitcher": {
|
||||||
"Dark": "Dark",
|
"Dark": "Dark",
|
||||||
@ -53,6 +56,7 @@
|
|||||||
"2002": "Poll interval must be greater zero!",
|
"2002": "Poll interval must be greater zero!",
|
||||||
"2003": "Invalid power level setting!",
|
"2003": "Invalid power level setting!",
|
||||||
"2004": "The frequency must be set between {min} and {max} kHz and must be a multiple of 250kHz!",
|
"2004": "The frequency must be set between {min} and {max} kHz and must be a multiple of 250kHz!",
|
||||||
|
"2005": "Invalid country selection!",
|
||||||
"3001": "Not deleted anything!",
|
"3001": "Not deleted anything!",
|
||||||
"3002": "Configuration resettet. Rebooting now...",
|
"3002": "Configuration resettet. Rebooting now...",
|
||||||
"4001": "@:apiresponse.2001",
|
"4001": "@:apiresponse.2001",
|
||||||
@ -420,9 +424,14 @@
|
|||||||
"CmtPaLevel": "CMT2300A Transmitting power:",
|
"CmtPaLevel": "CMT2300A Transmitting power:",
|
||||||
"NrfPaLevelHint": "Used for HM-Inverters. Make sure your power supply is stable enough before increasing the transmit power.",
|
"NrfPaLevelHint": "Used for HM-Inverters. Make sure your power supply is stable enough before increasing the transmit power.",
|
||||||
"CmtPaLevelHint": "Used for HMS/HMT-Inverters. Make sure your power supply is stable enough before increasing the transmit power.",
|
"CmtPaLevelHint": "Used for HMS/HMT-Inverters. Make sure your power supply is stable enough before increasing the transmit power.",
|
||||||
|
"CmtCountry": "Region/Country:",
|
||||||
|
"CmtCountryHint": "Each country has different frequency allocations.",
|
||||||
|
"country_0": "Europe ({min}MHz - {max}MHz)",
|
||||||
|
"country_1": "North America ({min}MHz - {max}MHz)",
|
||||||
|
"country_2": "Brazil ({min}MHz - {max}MHz)",
|
||||||
"CmtFrequency": "CMT2300A Frequency:",
|
"CmtFrequency": "CMT2300A Frequency:",
|
||||||
"CmtFrequencyHint": "Make sure to only use frequencies that are allowed in the respective country! After a frequency change, it can take up to 15min until a connection is established.",
|
"CmtFrequencyHint": "Make sure to only use frequencies that are allowed in the respective country! After a frequency change, it can take up to 15min until a connection is established.",
|
||||||
"CmtFrequencyWarning": "The selected frequency is outside the range allowed in the EU. Make sure that this selection does not violate any local regulations.",
|
"CmtFrequencyWarning": "The selected frequency is outside the allowed range in your selected region/country. Make sure that this selection does not violate any local regulations.",
|
||||||
"MHz": "{mhz} MHz",
|
"MHz": "{mhz} MHz",
|
||||||
"dBm": "{dbm} dBm",
|
"dBm": "{dbm} dBm",
|
||||||
"Min": "Minimum ({db} dBm)",
|
"Min": "Minimum ({db} dBm)",
|
||||||
@ -753,6 +762,10 @@
|
|||||||
"PowerSafeHint": "Turn off the display if no inverter is producing.",
|
"PowerSafeHint": "Turn off the display if no inverter is producing.",
|
||||||
"Screensaver": "Enable Screensaver:",
|
"Screensaver": "Enable Screensaver:",
|
||||||
"ScreensaverHint": "Move the display a little bit on each update to prevent burn-in. (Useful especially for OLED displays)",
|
"ScreensaverHint": "Move the display a little bit on each update to prevent burn-in. (Useful especially for OLED displays)",
|
||||||
|
"DiagramMode": "Diagram mode:",
|
||||||
|
"off": "Off",
|
||||||
|
"small": "Small",
|
||||||
|
"fullscreen": "Fullscreen",
|
||||||
"DiagramDuration": "Diagram duration:",
|
"DiagramDuration": "Diagram duration:",
|
||||||
"DiagramDurationHint": "The time period which is shown in the diagram.",
|
"DiagramDurationHint": "The time period which is shown in the diagram.",
|
||||||
"Seconds": "Seconds",
|
"Seconds": "Seconds",
|
||||||
|
|||||||
@ -35,7 +35,10 @@
|
|||||||
"Loading": "Chargement...",
|
"Loading": "Chargement...",
|
||||||
"Reload": "Reload",
|
"Reload": "Reload",
|
||||||
"Cancel": "Annuler",
|
"Cancel": "Annuler",
|
||||||
"Save": "Sauvegarder"
|
"Save": "Sauvegarder",
|
||||||
|
"Refreshing": "Refreshing",
|
||||||
|
"Pull": "Pull down to refresh",
|
||||||
|
"Release": "Release to refresh"
|
||||||
},
|
},
|
||||||
"localeswitcher": {
|
"localeswitcher": {
|
||||||
"Dark": "Sombre",
|
"Dark": "Sombre",
|
||||||
@ -53,6 +56,7 @@
|
|||||||
"2002": "L'intervalle de sondage doit être supérieur à zéro !",
|
"2002": "L'intervalle de sondage doit être supérieur à zéro !",
|
||||||
"2003": "Réglage du niveau de puissance invalide !",
|
"2003": "Réglage du niveau de puissance invalide !",
|
||||||
"2004": "The frequency must be set between {min} and {max} kHz and must be a multiple of 250kHz!",
|
"2004": "The frequency must be set between {min} and {max} kHz and must be a multiple of 250kHz!",
|
||||||
|
"2005": "Invalid country selection !",
|
||||||
"3001": "Rien n'a été supprimé !",
|
"3001": "Rien n'a été supprimé !",
|
||||||
"3002": "Configuration réinitialisée. Redémarrage maintenant...",
|
"3002": "Configuration réinitialisée. Redémarrage maintenant...",
|
||||||
"4001": "@:apiresponse.2001",
|
"4001": "@:apiresponse.2001",
|
||||||
@ -418,9 +422,14 @@
|
|||||||
"CmtPaLevel": "CMT2300A Niveau de puissance d'émission",
|
"CmtPaLevel": "CMT2300A Niveau de puissance d'émission",
|
||||||
"NrfPaLevelHint": "Used for HM-Inverters. Assurez-vous que votre alimentation est suffisamment stable avant d'augmenter la puissance d'émission.",
|
"NrfPaLevelHint": "Used for HM-Inverters. Assurez-vous que votre alimentation est suffisamment stable avant d'augmenter la puissance d'émission.",
|
||||||
"CmtPaLevelHint": "Used for HMS/HMT-Inverters. Assurez-vous que votre alimentation est suffisamment stable avant d'augmenter la puissance d'émission.",
|
"CmtPaLevelHint": "Used for HMS/HMT-Inverters. Assurez-vous que votre alimentation est suffisamment stable avant d'augmenter la puissance d'émission.",
|
||||||
|
"CmtCountry": "Region/Country:",
|
||||||
|
"CmtCountryHint": "Each country has different frequency allocations.",
|
||||||
|
"country_0": "Europe ({min}MHz - {max}MHz)",
|
||||||
|
"country_1": "North America ({min}MHz - {max}MHz)",
|
||||||
|
"country_2": "Brazil ({min}MHz - {max}MHz)",
|
||||||
"CmtFrequency": "CMT2300A Frequency:",
|
"CmtFrequency": "CMT2300A Frequency:",
|
||||||
"CmtFrequencyHint": "Make sure to only use frequencies that are allowed in the respective country! After a frequency change, it can take up to 15min until a connection is established.",
|
"CmtFrequencyHint": "Make sure to only use frequencies that are allowed in the respective country! After a frequency change, it can take up to 15min until a connection is established.",
|
||||||
"CmtFrequencyWarning": "The selected frequency is outside the range allowed in the EU. Make sure that this selection does not violate any local regulations.",
|
"CmtFrequencyWarning": "The selected frequency is outside the allowed range in your selected region/country. Make sure that this selection does not violate any local regulations.",
|
||||||
"MHz": "{mhz} MHz",
|
"MHz": "{mhz} MHz",
|
||||||
"dBm": "{dbm} dBm",
|
"dBm": "{dbm} dBm",
|
||||||
"Min": "Minimum ({db} dBm)",
|
"Min": "Minimum ({db} dBm)",
|
||||||
@ -711,6 +720,10 @@
|
|||||||
"PowerSafeHint": "Eteindre l'écran si aucun onduleur n'est en production.",
|
"PowerSafeHint": "Eteindre l'écran si aucun onduleur n'est en production.",
|
||||||
"Screensaver": "Activer l'écran de veille",
|
"Screensaver": "Activer l'écran de veille",
|
||||||
"ScreensaverHint": "Déplacez un peu l'écran à chaque mise à jour pour éviter le phénomène de brûlure. (Utile surtout pour les écrans OLED)",
|
"ScreensaverHint": "Déplacez un peu l'écran à chaque mise à jour pour éviter le phénomène de brûlure. (Utile surtout pour les écrans OLED)",
|
||||||
|
"DiagramMode": "Diagram mode:",
|
||||||
|
"off": "Off",
|
||||||
|
"small": "Small",
|
||||||
|
"fullscreen": "Fullscreen",
|
||||||
"DiagramDuration": "Diagram duration:",
|
"DiagramDuration": "Diagram duration:",
|
||||||
"DiagramDurationHint": "The time period which is shown in the diagram.",
|
"DiagramDurationHint": "The time period which is shown in the diagram.",
|
||||||
"Seconds": "Seconds",
|
"Seconds": "Seconds",
|
||||||
|
|||||||
@ -7,6 +7,7 @@ export interface Display {
|
|||||||
contrast: number;
|
contrast: number;
|
||||||
language: number;
|
language: number;
|
||||||
diagramduration: number;
|
diagramduration: number;
|
||||||
|
diagrammode: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Led {
|
export interface Led {
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
|
export interface CountryDef {
|
||||||
|
freq_default: number;
|
||||||
|
freq_min: number;
|
||||||
|
freq_max: number;
|
||||||
|
freq_legal_min: number;
|
||||||
|
freq_legal_max: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DtuConfig {
|
export interface DtuConfig {
|
||||||
serial: number;
|
serial: number;
|
||||||
pollinterval: number;
|
pollinterval: number;
|
||||||
@ -7,4 +15,7 @@ export interface DtuConfig {
|
|||||||
cmt_enabled: boolean;
|
cmt_enabled: boolean;
|
||||||
cmt_palevel: number;
|
cmt_palevel: number;
|
||||||
cmt_frequency: number;
|
cmt_frequency: number;
|
||||||
|
cmt_country: number;
|
||||||
|
country_def: Array<CountryDef>;
|
||||||
|
cmt_chan_width: number;
|
||||||
}
|
}
|
||||||
@ -1,17 +1,8 @@
|
|||||||
export const timestampToString = (value: number, includeDays = false): string => {
|
export const timestampToString = (timestampSeconds: number, includeDays = false): string => {
|
||||||
const days = Math.floor(value / (24 * 60 * 60));
|
const timeString = new Date(timestampSeconds * 1000).toLocaleTimeString([], { timeZone: "UTC" });
|
||||||
const secAfterDays = value - days * (24 * 60 * 60);
|
if (!includeDays) return timeString;
|
||||||
const hours = Math.floor(secAfterDays / (60 * 60));
|
|
||||||
const secAfterHours = secAfterDays - hours * (60 * 60);
|
|
||||||
const minutes = Math.floor(secAfterHours / 60);
|
|
||||||
const seconds = secAfterHours - minutes * 60;
|
|
||||||
|
|
||||||
const dHours = hours > 9 ? hours : "0" + hours;
|
const secondsPerDay = 60 * 60 * 24;
|
||||||
const dMins = minutes > 9 ? minutes : "0" + minutes;
|
const days = Math.floor(timestampSeconds / secondsPerDay);
|
||||||
const dSecs = seconds > 9 ? seconds : "0" + seconds;
|
return new Intl.RelativeTimeFormat().format(-days, "day") + " " + timeString;
|
||||||
|
|
||||||
if (includeDays) {
|
|
||||||
return days + " days " + dHours + ":" + dMins + ":" + dSecs;
|
|
||||||
}
|
|
||||||
return dHours + ":" + dMins + ":" + dSecs;
|
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@
|
|||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-sm-2"></div>
|
<div class="col-sm-2"></div>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<div class="btn-group" v-for="(doc, index) in pinMappingList.find(i => i.name === deviceConfigList.curPin.name)?.links" :key="index">
|
<div class="btn-group mb-2 me-2" v-for="(doc, index) in pinMappingList.find(i => i.name === deviceConfigList.curPin.name)?.links" :key="index">
|
||||||
<a :href="doc.url" class="btn btn-primary" target="_blank">{{ doc.name }}</a>
|
<a :href="doc.url" class="btn btn-primary" target="_blank">{{ doc.name }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -67,6 +67,19 @@
|
|||||||
v-model="deviceConfigList.display.screensaver" type="checkbox"
|
v-model="deviceConfigList.display.screensaver" type="checkbox"
|
||||||
:tooltip="$t('deviceadmin.ScreensaverHint')" />
|
:tooltip="$t('deviceadmin.ScreensaverHint')" />
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label">
|
||||||
|
{{ $t('deviceadmin.DiagramMode') }}
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select class="form-select" v-model="deviceConfigList.display.diagrammode">
|
||||||
|
<option v-for="mode in diagramModeList" :key="mode.key" :value="mode.key">
|
||||||
|
{{ $t(`deviceadmin.` + mode.value) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<InputElement :label="$t('deviceadmin.DiagramDuration')"
|
<InputElement :label="$t('deviceadmin.DiagramDuration')"
|
||||||
v-model="deviceConfigList.display.diagramduration" type="number"
|
v-model="deviceConfigList.display.diagramduration" type="number"
|
||||||
min=600 max=86400
|
min=600 max=86400
|
||||||
@ -183,6 +196,11 @@ export default defineComponent({
|
|||||||
{ key: 1, value: "de" },
|
{ key: 1, value: "de" },
|
||||||
{ key: 2, value: "fr" },
|
{ key: 2, value: "fr" },
|
||||||
],
|
],
|
||||||
|
diagramModeList: [
|
||||||
|
{ key: 0, value: "off" },
|
||||||
|
{ key: 1, value: "small" },
|
||||||
|
{ key: 2, value: "fullscreen" },
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|||||||
@ -51,6 +51,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3" v-if="dtuConfigList.cmt_enabled">
|
||||||
|
<label for="inputCmtCountry" class="col-sm-2 col-form-label">
|
||||||
|
{{ $t('dtuadmin.CmtCountry') }}
|
||||||
|
<BIconInfoCircle v-tooltip :title="$t('dtuadmin.CmtCountryHint')" />
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select id="inputCmtCountry" class="form-select" v-model="dtuConfigList.cmt_country">
|
||||||
|
<option v-for="(country, index) in dtuConfigList.country_def" :key="index" :value="index">
|
||||||
|
{{ $t(`dtuadmin.country_` + index, {min: country.freq_min / 1e6, max: country.freq_max / 1e6}) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3" v-if="dtuConfigList.cmt_enabled">
|
<div class="row mb-3" v-if="dtuConfigList.cmt_enabled">
|
||||||
<label for="cmtFrequency" class="col-sm-2 col-form-label">
|
<label for="cmtFrequency" class="col-sm-2 col-form-label">
|
||||||
{{ $t('dtuadmin.CmtFrequency') }}
|
{{ $t('dtuadmin.CmtFrequency') }}
|
||||||
@ -60,12 +74,12 @@
|
|||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<input type="range" class="form-control form-range"
|
<input type="range" class="form-control form-range"
|
||||||
v-model="dtuConfigList.cmt_frequency"
|
v-model="dtuConfigList.cmt_frequency"
|
||||||
min="860250" max="923500" step="250"
|
:min="cmtMinFrequency" :max="cmtMaxFrequency" :step="dtuConfigList.cmt_chan_width"
|
||||||
id="cmtFrequency" aria-describedby="basic-addon2"
|
id="cmtFrequency" aria-describedby="basic-addon2"
|
||||||
style="height: unset;" />
|
style="height: unset;" />
|
||||||
<span class="input-group-text" id="basic-addon2">{{ cmtFrequencyText }}</span>
|
<span class="input-group-text" id="basic-addon2">{{ cmtFrequencyText }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert-danger" role="alert" v-html="$t('dtuadmin.CmtFrequencyWarning')" v-if="cmtIsOutOfEu"></div>
|
<div class="alert alert-danger" role="alert" v-html="$t('dtuadmin.CmtFrequencyWarning')" v-if="cmtIsOutOfLegalRange"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -115,13 +129,30 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
cmtFrequencyText() {
|
cmtFrequencyText() {
|
||||||
return this.$t("dtuadmin.MHz", { mhz: this.$n(this.dtuConfigList.cmt_frequency / 1000, "decimalTwoDigits") });
|
return this.$t("dtuadmin.MHz", { mhz: this.$n(this.dtuConfigList.cmt_frequency / 1000000, "decimalTwoDigits") });
|
||||||
},
|
},
|
||||||
cmtPaLevelText() {
|
cmtPaLevelText() {
|
||||||
return this.$t("dtuadmin.dBm", { dbm: this.$n(this.dtuConfigList.cmt_palevel * 1) });
|
return this.$t("dtuadmin.dBm", { dbm: this.$n(this.dtuConfigList.cmt_palevel * 1) });
|
||||||
},
|
},
|
||||||
cmtIsOutOfEu() {
|
cmtMinFrequency() {
|
||||||
return this.dtuConfigList.cmt_frequency < 863000 || this.dtuConfigList.cmt_frequency > 870000;
|
return this.dtuConfigList.country_def[this.dtuConfigList.cmt_country].freq_min;
|
||||||
|
},
|
||||||
|
cmtMaxFrequency() {
|
||||||
|
return this.dtuConfigList.country_def[this.dtuConfigList.cmt_country].freq_max;
|
||||||
|
},
|
||||||
|
cmtIsOutOfLegalRange() {
|
||||||
|
return this.dtuConfigList.cmt_frequency < this.dtuConfigList.country_def[this.dtuConfigList.cmt_country].freq_legal_min
|
||||||
|
|| this.dtuConfigList.cmt_frequency > this.dtuConfigList.country_def[this.dtuConfigList.cmt_country].freq_legal_max;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'dtuConfigList.cmt_country'(newValue, oldValue) {
|
||||||
|
// Don't do anything on initial load (then oldValue equals undefined)
|
||||||
|
if (oldValue != undefined) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.dtuConfigList.cmt_frequency = this.dtuConfigList.country_def[newValue].freq_default;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<BasePage :title="$t('home.LiveData')" :isLoading="dataLoading" :isWideScreen="true">
|
<BasePage :title="$t('home.LiveData')" :isLoading="dataLoading" :isWideScreen="true" :showWebSocket="true" :isWebsocketConnected="isWebsocketConnected" @reload="reloadData">
|
||||||
<HintView :hints="liveData.hints" />
|
<HintView :hints="liveData.hints" />
|
||||||
<InverterTotalInfo :totalData="liveData.total" :totalVeData="liveData.vedirect" :totalBattData="liveData.battery" :powerMeterData="liveData.power_meter" :huaweiData="liveData.huawei"/><br />
|
<InverterTotalInfo :totalData="liveData.total" :totalVeData="liveData.vedirect" :totalBattData="liveData.battery" :powerMeterData="liveData.power_meter" :huaweiData="liveData.huawei"/><br />
|
||||||
<div class="row gy-3">
|
<div class="row gy-3">
|
||||||
@ -457,6 +457,8 @@ export default defineComponent({
|
|||||||
alertTypePower: "info",
|
alertTypePower: "info",
|
||||||
showAlertPower: false,
|
showAlertPower: false,
|
||||||
successCommandPower: "",
|
successCommandPower: "",
|
||||||
|
|
||||||
|
isWebsocketConnected: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -484,17 +486,23 @@ export default defineComponent({
|
|||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
|
console.log("Updated");
|
||||||
// Select first tab
|
// Select first tab
|
||||||
if (this.isFirstFetchAfterConnect) {
|
if (this.isFirstFetchAfterConnect) {
|
||||||
this.isFirstFetchAfterConnect = false;
|
console.log("isFirstFetchAfterConnect");
|
||||||
|
|
||||||
const firstTabEl = document.querySelector(
|
this.$nextTick(() => {
|
||||||
"#v-pills-tab:first-child button"
|
console.log("nextTick");
|
||||||
);
|
const firstTabEl = document.querySelector(
|
||||||
if (firstTabEl != null) {
|
"#v-pills-tab:first-child button"
|
||||||
const firstTab = new bootstrap.Tab(firstTabEl);
|
);
|
||||||
firstTab.show();
|
if (firstTabEl != null) {
|
||||||
}
|
this.isFirstFetchAfterConnect = false;
|
||||||
|
console.log("Show");
|
||||||
|
const firstTab = new bootstrap.Tab(firstTabEl);
|
||||||
|
firstTab.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -517,15 +525,27 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
getInitialData() {
|
getInitialData(triggerLoading : boolean = true) {
|
||||||
this.dataLoading = true;
|
if (triggerLoading) {
|
||||||
|
this.dataLoading = true;
|
||||||
|
}
|
||||||
fetch("/api/livedata/status", { headers: authHeader() })
|
fetch("/api/livedata/status", { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.liveData = data;
|
this.liveData = data;
|
||||||
this.dataLoading = false;
|
if (triggerLoading) {
|
||||||
|
this.dataLoading = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
reloadData() {
|
||||||
|
this.closeSocket();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getInitialData(false);
|
||||||
|
this.initSocket();
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
initSocket() {
|
initSocket() {
|
||||||
console.log("Starting connection to WebSocket Server");
|
console.log("Starting connection to WebSocket Server");
|
||||||
|
|
||||||
@ -549,11 +569,19 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
this.socket.onopen = function (event) {
|
this.socket.onopen = function (event) {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
console.log("Successfully connected to the echo websocket server...");
|
console.log("Successfully connected to the echo websocket server...");
|
||||||
|
self.isWebsocketConnected = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.socket.onclose = function() {
|
||||||
|
console.log("Connection to websocket closed...")
|
||||||
|
self.isWebsocketConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
||||||
window.onbeforeunload = () => {
|
window.onbeforeunload = () => {
|
||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
|
|||||||
221
webapp/yarn.lock
221
webapp/yarn.lock
@ -211,20 +211,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.8.0":
|
"@intlify/core-base@9.9.0":
|
||||||
version "9.8.0"
|
version "9.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.8.0.tgz#969ca59f55084e23e968ec0bfe71678774e568ec"
|
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.9.0.tgz#edc55a5e3dbbf8dbbbf656529ed27832c4c4f522"
|
||||||
integrity sha512-UxaSZVZ1DwqC/CltUZrWZNaWNhfmKtfyV4BJSt/Zt4Or/fZs1iFj0B+OekYk1+MRHfIOe3+x00uXGQI4PbO/9g==
|
integrity sha512-C7UXPymDIOlMGSNjAhNLtKgzITc/8BjINK5gNKXg8GiWCTwL6n3MWr55czksxn8RM5wTMz0qcLOFT+adtaVQaA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@intlify/message-compiler" "9.8.0"
|
"@intlify/message-compiler" "9.9.0"
|
||||||
"@intlify/shared" "9.8.0"
|
"@intlify/shared" "9.9.0"
|
||||||
|
|
||||||
"@intlify/message-compiler@9.8.0":
|
"@intlify/message-compiler@9.9.0":
|
||||||
version "9.8.0"
|
version "9.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.8.0.tgz#587d69b302f9b8130a4a949b0ab4add519761787"
|
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.9.0.tgz#7952759329e7af0388afbce7a984820bbeff82eb"
|
||||||
integrity sha512-McnYWhcoYmDJvssVu6QGR0shqlkJuL1HHdi5lK7fNqvQqRYaQ4lSLjYmZxwc8tRNMdIe9/KUKfyPxU9M6yCtNQ==
|
integrity sha512-yDU/jdUm9KuhEzYfS+wuyja209yXgdl1XFhMlKtXEgSFTxz4COZQCRXXbbH8JrAjMsaJ7bdoPSLsKlY6mXG2iA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@intlify/shared" "9.8.0"
|
"@intlify/shared" "9.9.0"
|
||||||
source-map-js "^1.0.2"
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
"@intlify/message-compiler@^9.4.0":
|
"@intlify/message-compiler@^9.4.0":
|
||||||
@ -240,10 +240,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.4.0.tgz#4a78d462fc82433db900981e12eb5b1aae3d6085"
|
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.4.0.tgz#4a78d462fc82433db900981e12eb5b1aae3d6085"
|
||||||
integrity sha512-AFqymip2kToqA0B6KZPg5jSrdcVHoli9t/VhGKE2iiMq9utFuMoGdDC/JOCIZgwxo6aXAk86QyU2XtzEoMuZ6A==
|
integrity sha512-AFqymip2kToqA0B6KZPg5jSrdcVHoli9t/VhGKE2iiMq9utFuMoGdDC/JOCIZgwxo6aXAk86QyU2XtzEoMuZ6A==
|
||||||
|
|
||||||
"@intlify/shared@9.8.0":
|
"@intlify/shared@9.9.0":
|
||||||
version "9.8.0"
|
version "9.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.8.0.tgz#62adf8f6ef67c8eba6cf8d521e248f3503f237d3"
|
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.9.0.tgz#56907633c0f7b2d50f53269d31e88e7b24d39187"
|
||||||
integrity sha512-TmgR0RCLjzrSo+W3wT0ALf9851iFMlVI9EYNGeWvZFUQTAJx0bvfsMlPdgVtV1tDNRiAfhkFsMKu6jtUY1ZLKQ==
|
integrity sha512-1ECUyAHRrzOJbOizyGufYP2yukqGrWXtkmTu4PcswVnWbkcjzk3YQGmJ0bLkM7JZ0ZYAaohLGdYvBYnTOGYJ9g==
|
||||||
|
|
||||||
"@intlify/unplugin-vue-i18n@^2.0.0":
|
"@intlify/unplugin-vue-i18n@^2.0.0":
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
@ -435,13 +435,18 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
|
||||||
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
|
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
|
||||||
|
|
||||||
"@types/node@^20.10.6":
|
"@types/node@^20.11.0":
|
||||||
version "20.10.6"
|
version "20.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.6.tgz#a3ec84c22965802bf763da55b2394424f22bfbb5"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.0.tgz#8e0b99e70c0c1ade1a86c4a282f7b7ef87c9552f"
|
||||||
integrity sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==
|
integrity sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~5.26.4"
|
undici-types "~5.26.4"
|
||||||
|
|
||||||
|
"@types/pulltorefreshjs@^0.1.7":
|
||||||
|
version "0.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/pulltorefreshjs/-/pulltorefreshjs-0.1.7.tgz#1948638b0c7071282e47bd236d2ccb88bdf66753"
|
||||||
|
integrity sha512-Y0g/yfuycIvpvUmP97n5NE2+HDAOwfREGVERjhMWw2Y0ODh5wvbflcQ5gXPZ+ihgoq+BQZjA1DL8apw2wAsJXA==
|
||||||
|
|
||||||
"@types/semver@^7.5.0":
|
"@types/semver@^7.5.0":
|
||||||
version "7.5.1"
|
version "7.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.1.tgz#0480eeb7221eb9bc398ad7432c9d7e14b1a5a367"
|
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.1.tgz#0480eeb7221eb9bc398ad7432c9d7e14b1a5a367"
|
||||||
@ -547,10 +552,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||||
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
|
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
|
||||||
|
|
||||||
"@vitejs/plugin-vue@^5.0.2":
|
"@vitejs/plugin-vue@^5.0.3":
|
||||||
version "5.0.2"
|
version "5.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.0.2.tgz#8428ec3f446b9c2f7a7ec950f34e3d6f3c665444"
|
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz#164b36653910d27c130cf6c945b4bd9bde5bcbee"
|
||||||
integrity sha512-kEjJHrLb5ePBvjD0SPZwJlw1QTRcjjCA9sB5VyfonoXVBxTS7TMnqL6EkLt1Eu61RDeiuZ/WN9Hf6PxXhPI2uA==
|
integrity sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==
|
||||||
|
|
||||||
"@volar/language-core@1.11.1", "@volar/language-core@~1.11.1":
|
"@volar/language-core@1.11.1", "@volar/language-core@~1.11.1":
|
||||||
version "1.11.1"
|
version "1.11.1"
|
||||||
@ -594,13 +599,13 @@
|
|||||||
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.5":
|
"@vue/compiler-core@3.4.13":
|
||||||
version "3.4.5"
|
version "3.4.13"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.5.tgz#9565aebaadef8649eb7c8e150a5f4f4e2542667d"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.13.tgz#49f499034c25b0832845028ea3cd701fe5a17367"
|
||||||
integrity sha512-Daka7P1z2AgKjzuueWXhwzIsKu0NkLB6vGbNVEV2iJ8GJTrzraZo/Sk4GWCMRtd/qVi3zwnk+Owbd/xSZbwHtQ==
|
integrity sha512-zGUdmB3j3Irn9z51GXLJ5s0EAHxmsm5/eXl0y6MBaajMeOAaiT4+zaDoxui4Ets98dwIRr8BBaqXXHtHSfm+KA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/parser" "^7.23.6"
|
"@babel/parser" "^7.23.6"
|
||||||
"@vue/shared" "3.4.5"
|
"@vue/shared" "3.4.13"
|
||||||
entities "^4.5.0"
|
entities "^4.5.0"
|
||||||
estree-walker "^2.0.2"
|
estree-walker "^2.0.2"
|
||||||
source-map-js "^1.0.2"
|
source-map-js "^1.0.2"
|
||||||
@ -613,13 +618,13 @@
|
|||||||
"@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.5":
|
"@vue/compiler-dom@3.4.13":
|
||||||
version "3.4.5"
|
version "3.4.13"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.5.tgz#c53c9d7715b777b1d6d2adcbc491bfd4f9510edd"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.13.tgz#66a80a6ee412a3d32b7175a146b75d9ec3d1c50c"
|
||||||
integrity sha512-J8YlxknJVd90SXFJ4HwGANSAXsx5I0lK30sO/zvYV7s5gXf7gZR7r/1BmZ2ju7RGH1lnc6bpBc6nL61yW+PsAQ==
|
integrity sha512-XSNbpr5Rs3kCfVAmBqMu/HDwOS+RL6y28ZZjDlnDUuf146pRWt2sQkwhsOYc9uu2lxjjJy2NcyOkK7MBLVEc7w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/compiler-core" "3.4.5"
|
"@vue/compiler-core" "3.4.13"
|
||||||
"@vue/shared" "3.4.5"
|
"@vue/shared" "3.4.13"
|
||||||
|
|
||||||
"@vue/compiler-dom@^3.3.0":
|
"@vue/compiler-dom@^3.3.0":
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
@ -629,16 +634,16 @@
|
|||||||
"@vue/compiler-core" "3.3.2"
|
"@vue/compiler-core" "3.3.2"
|
||||||
"@vue/shared" "3.3.2"
|
"@vue/shared" "3.3.2"
|
||||||
|
|
||||||
"@vue/compiler-sfc@3.4.5":
|
"@vue/compiler-sfc@3.4.13":
|
||||||
version "3.4.5"
|
version "3.4.13"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.5.tgz#f93f986dfc5c7f72b9a5e00b48be75d9116cc948"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.13.tgz#0f5f6db0e64f522c09995585453ae5f13ba54c60"
|
||||||
integrity sha512-jauvkDuSSUbP0ebhfNqljhShA90YEfX/0wZ+w40oZF43IjGyWYjqYaJbvMJwGOd+9+vODW6eSvnk28f0SGV7OQ==
|
integrity sha512-SkpmQN8xIFBd5onT413DFSDdjxULJf6jmJg/t3w/DZ9I8ZzyNlLIBLO0qFLewVHyHCiAgpPZlWqSRZXYrawk3Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/parser" "^7.23.6"
|
"@babel/parser" "^7.23.6"
|
||||||
"@vue/compiler-core" "3.4.5"
|
"@vue/compiler-core" "3.4.13"
|
||||||
"@vue/compiler-dom" "3.4.5"
|
"@vue/compiler-dom" "3.4.13"
|
||||||
"@vue/compiler-ssr" "3.4.5"
|
"@vue/compiler-ssr" "3.4.13"
|
||||||
"@vue/shared" "3.4.5"
|
"@vue/shared" "3.4.13"
|
||||||
estree-walker "^2.0.2"
|
estree-walker "^2.0.2"
|
||||||
magic-string "^0.30.5"
|
magic-string "^0.30.5"
|
||||||
postcss "^8.4.32"
|
postcss "^8.4.32"
|
||||||
@ -668,13 +673,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.5":
|
"@vue/compiler-ssr@3.4.13":
|
||||||
version "3.4.5"
|
version "3.4.13"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.5.tgz#d412a4c9b10d69172a5ce0ec78de98dad441a58d"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.13.tgz#90fa9a4116f7974d7a4e43a8a67f3fc162e8720f"
|
||||||
integrity sha512-DDdEcDzj2lWTMfUMMtEpLDhURai9LhM0zSZ219jCt7b2Vyl0/jy3keFgCPMitG0V1S1YG4Cmws3lWHWdxHQOpg==
|
integrity sha512-rwnw9SVBgD6eGKh8UucnwztieQo/R3RQrEGpE0b0cxb2xxvJeLs/fe7DoYlhEfaSyzM/qD5odkK87hl3G3oW+A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/compiler-dom" "3.4.5"
|
"@vue/compiler-dom" "3.4.13"
|
||||||
"@vue/shared" "3.4.5"
|
"@vue/shared" "3.4.13"
|
||||||
|
|
||||||
"@vue/devtools-api@^6.5.0":
|
"@vue/devtools-api@^6.5.0":
|
||||||
version "6.5.0"
|
version "6.5.0"
|
||||||
@ -716,37 +721,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.5":
|
"@vue/reactivity@3.4.13":
|
||||||
version "3.4.5"
|
version "3.4.13"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.5.tgz#68bc91cd356eed95dc5e9e0570e3f7becaee578b"
|
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.13.tgz#7eeeb9d598512f66e06a6438fd53464014b5ae59"
|
||||||
integrity sha512-BcWkKvjdvqJwb7BhhFkXPLDCecX4d4a6GATvCduJQDLv21PkPowAE5GKuIE5p6RC07/Lp9FMkkq4AYCTVF5KlQ==
|
integrity sha512-/ZdUOrGKkGVONzVJkfDqNcn2fLMvaa5VlYx2KwTbnRbX06YZ4GJE0PVTmWzIxtBYdpSTLLXgw3pDggO+96KXzg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/shared" "3.4.5"
|
"@vue/shared" "3.4.13"
|
||||||
|
|
||||||
"@vue/runtime-core@3.4.5":
|
"@vue/runtime-core@3.4.13":
|
||||||
version "3.4.5"
|
version "3.4.13"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.5.tgz#2bf253a6f6b0430af1aacf0fdfd8f5782feefce9"
|
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.13.tgz#776cad7c1d56ec5e92a48e040c8483b89f779542"
|
||||||
integrity sha512-wh9ELIOQKeWT9SaUPdLrsxRkZv14jp+SJm9aiQGWio+/MWNM3Lib0wE6CoKEqQ9+SCYyGjDBhTOTtO47kCgbkg==
|
integrity sha512-Ov4d4At7z3goxqzSqQxdfVYEcN5HY4dM1uDYL6Hu/Es9Za9BEN602zyjWhhi2+BEki5F9NizRSvn02k/tqNWlg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/reactivity" "3.4.5"
|
"@vue/reactivity" "3.4.13"
|
||||||
"@vue/shared" "3.4.5"
|
"@vue/shared" "3.4.13"
|
||||||
|
|
||||||
"@vue/runtime-dom@3.4.5":
|
"@vue/runtime-dom@3.4.13":
|
||||||
version "3.4.5"
|
version "3.4.13"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.5.tgz#b43736d66c32f6038778024587592cb9d68495de"
|
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.13.tgz#74aff1494bee49c037b9c5355d8998c793ac0977"
|
||||||
integrity sha512-n5ewvOjyG3IEpqGBahdPXODFSpVlSz3H4LF76Sx0XAqpIOqyJ5bIb2PrdYuH2ogBMAQPh+o5tnoH4nJpBr8U0Q==
|
integrity sha512-ynde9p16eEV3u1VCxUre2e0nKzD0l3NzH0r599+bXeLT1Yhac8Atcot3iL9XNqwolxYCI89KBII+2MSVzfrz6w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/runtime-core" "3.4.5"
|
"@vue/runtime-core" "3.4.13"
|
||||||
"@vue/shared" "3.4.5"
|
"@vue/shared" "3.4.13"
|
||||||
csstype "^3.1.3"
|
csstype "^3.1.3"
|
||||||
|
|
||||||
"@vue/server-renderer@3.4.5":
|
"@vue/server-renderer@3.4.13":
|
||||||
version "3.4.5"
|
version "3.4.13"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.5.tgz#4bfa7aa763217d8b2d4767d2c8d968a9d40352c1"
|
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.13.tgz#b8c9cfb2147c0a01feba7f136d3a432848dafcab"
|
||||||
integrity sha512-jOFc/VE87yvifQpNju12VcqimH8pBLxdcT+t3xMeiED1K6DfH9SORyhFEoZlW5TG2Vwfn3Ul5KE+1aC99xnSBg==
|
integrity sha512-hkw+UQyDZZtSn1q30nObMfc8beVEQv2pG08nghigxGw+iOWodR+tWSuJak0mzWAHlP/xt/qLc//dG6igfgvGEA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/compiler-ssr" "3.4.5"
|
"@vue/compiler-ssr" "3.4.13"
|
||||||
"@vue/shared" "3.4.5"
|
"@vue/shared" "3.4.13"
|
||||||
|
|
||||||
"@vue/shared@3.2.47":
|
"@vue/shared@3.2.47":
|
||||||
version "3.2.47"
|
version "3.2.47"
|
||||||
@ -758,10 +763,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.3.2.tgz#774cd9b4635ce801b70a3fc3713779a5ef5d77c3"
|
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.3.2.tgz#774cd9b4635ce801b70a3fc3713779a5ef5d77c3"
|
||||||
integrity sha512-0rFu3h8JbclbnvvKrs7Fe5FNGV9/5X2rPD7KmOzhLSUAiQH5//Hq437Gv0fR5Mev3u/nbtvmLl8XgwCU20/ZfQ==
|
integrity sha512-0rFu3h8JbclbnvvKrs7Fe5FNGV9/5X2rPD7KmOzhLSUAiQH5//Hq437Gv0fR5Mev3u/nbtvmLl8XgwCU20/ZfQ==
|
||||||
|
|
||||||
"@vue/shared@3.4.5":
|
"@vue/shared@3.4.13":
|
||||||
version "3.4.5"
|
version "3.4.13"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.5.tgz#c8b4eb6399a7fc986565ea736d938b3a1579256d"
|
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.13.tgz#a1eefce5ddffe207d53eafbc07f4ebbea0a4768a"
|
||||||
integrity sha512-6XptuzlMvN4l4cDnDw36pdGEV+9njYkQ1ZE0Q6iZLwrKefKaOJyiFmcP3/KBDHbt72cJZGtllAc1GaHe6XGAyg==
|
integrity sha512-56crFKLPpzk85WXX1L1c0QzPOuoapWlPVys8eMG8kkRmqdMjWUqK8KpFdE2d7BQA4CEbXwyyHPq6MpFr8H9rcg==
|
||||||
|
|
||||||
"@vue/tsconfig@^0.5.1":
|
"@vue/tsconfig@^0.5.1":
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
@ -1141,17 +1146,17 @@ escodegen@^2.0.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
eslint-plugin-vue@^9.19.2:
|
eslint-plugin-vue@^9.20.1:
|
||||||
version "9.19.2"
|
version "9.20.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.19.2.tgz#7ab83a001a1ac8bccae013c5b9cb5d2c644fb376"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.20.1.tgz#7ed78846898574b2cd26939f28b0b87798a7b528"
|
||||||
integrity sha512-CPDqTOG2K4Ni2o4J5wixkLVNwgctKXFu6oBpVJlpNq7f38lh9I80pRTouZSJ2MAebPJlINU/KTFSXyQfBUlymA==
|
integrity sha512-GyCs8K3lkEvoyC1VV97GJhP1SvqsKCiWGHnbn0gVUYiUhaH2+nB+Dv1uekv1THFMPbBfYxukrzQdltw950k+LQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/eslint-utils" "^4.4.0"
|
"@eslint-community/eslint-utils" "^4.4.0"
|
||||||
natural-compare "^1.4.0"
|
natural-compare "^1.4.0"
|
||||||
nth-check "^2.1.1"
|
nth-check "^2.1.1"
|
||||||
postcss-selector-parser "^6.0.13"
|
postcss-selector-parser "^6.0.13"
|
||||||
semver "^7.5.4"
|
semver "^7.5.4"
|
||||||
vue-eslint-parser "^9.3.1"
|
vue-eslint-parser "^9.4.0"
|
||||||
xml-name-validator "^4.0.0"
|
xml-name-validator "^4.0.0"
|
||||||
|
|
||||||
eslint-scope@^7.1.1:
|
eslint-scope@^7.1.1:
|
||||||
@ -2142,6 +2147,11 @@ prelude-ls@~1.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||||
integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
|
integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
|
||||||
|
|
||||||
|
pulltorefreshjs@^0.1.22:
|
||||||
|
version "0.1.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/pulltorefreshjs/-/pulltorefreshjs-0.1.22.tgz#ddb5e3feee0b2a49fd46e1b18e84fffef2c47ac0"
|
||||||
|
integrity sha512-haxNVEHnS4NCQA7NeG7TSV69z4uqy/N7nfPRuc4dPWe8H6ygUrMjdNeohE+6v0lVVX/ukSjbLYwPUGUYtFKfvQ==
|
||||||
|
|
||||||
punycode@^2.1.0:
|
punycode@^2.1.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
@ -2551,10 +2561,10 @@ vite-plugin-css-injected-by-js@^3.3.1:
|
|||||||
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.3.1.tgz#26b41f108c5554ee728359bdec01c68c93a48547"
|
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.3.1.tgz#26b41f108c5554ee728359bdec01c68c93a48547"
|
||||||
integrity sha512-PjM/X45DR3/V1K1fTRs8HtZHEQ55kIfdrn+dzaqNBFrOYO073SeSNCxp4j7gSYhV9NffVHaEnOL4myoko0ePAg==
|
integrity sha512-PjM/X45DR3/V1K1fTRs8HtZHEQ55kIfdrn+dzaqNBFrOYO073SeSNCxp4j7gSYhV9NffVHaEnOL4myoko0ePAg==
|
||||||
|
|
||||||
vite@^5.0.10:
|
vite@^5.0.11:
|
||||||
version "5.0.10"
|
version "5.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.10.tgz#1e13ef5c3cf5aa4eed81f5df6d107b3c3f1f6356"
|
resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.11.tgz#31562e41e004cb68e1d51f5d2c641ab313b289e4"
|
||||||
integrity sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==
|
integrity sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild "^0.19.3"
|
esbuild "^0.19.3"
|
||||||
postcss "^8.4.32"
|
postcss "^8.4.32"
|
||||||
@ -2575,13 +2585,26 @@ vue-eslint-parser@^9.3.1:
|
|||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
semver "^7.3.6"
|
semver "^7.3.6"
|
||||||
|
|
||||||
vue-i18n@^9.8.0:
|
vue-eslint-parser@^9.4.0:
|
||||||
version "9.8.0"
|
version "9.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.8.0.tgz#54339daf377a31b234b027c5158e774728b6bc24"
|
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.4.0.tgz#dfd22302e2992fe45748a76553cef7afa5bdde27"
|
||||||
integrity sha512-Izho+6PYjejsTq2mzjcRdBZ5VLRQoSuuexvR8029h5CpN03FYqiqBrShMyf2I1DKkN6kw/xmujcbvC+4QybpsQ==
|
integrity sha512-7KsNBb6gHFA75BtneJsoK/dbZ281whUIwFYdQxA68QrCrGMXYzUMbPDHGcOQ0OocIVKrWSKWXZ4mL7tonCXoUw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@intlify/core-base" "9.8.0"
|
debug "^4.3.4"
|
||||||
"@intlify/shared" "9.8.0"
|
eslint-scope "^7.1.1"
|
||||||
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
espree "^9.3.1"
|
||||||
|
esquery "^1.4.0"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
semver "^7.3.6"
|
||||||
|
|
||||||
|
vue-i18n@^9.9.0:
|
||||||
|
version "9.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.9.0.tgz#20d348fa7e37fc88e4c84f69781b2f1215c7769f"
|
||||||
|
integrity sha512-xQ5SxszUAqK5n84N+uUyHH/PiQl9xZ24FOxyAaNonmOQgXeN+rD9z/6DStOpOxNFQn4Cgcquot05gZc+CdOujA==
|
||||||
|
dependencies:
|
||||||
|
"@intlify/core-base" "9.9.0"
|
||||||
|
"@intlify/shared" "9.9.0"
|
||||||
"@vue/devtools-api" "^6.5.0"
|
"@vue/devtools-api" "^6.5.0"
|
||||||
|
|
||||||
vue-router@^4.2.5:
|
vue-router@^4.2.5:
|
||||||
@ -2608,16 +2631,16 @@ vue-tsc@^1.8.27:
|
|||||||
"@vue/language-core" "1.8.27"
|
"@vue/language-core" "1.8.27"
|
||||||
semver "^7.5.4"
|
semver "^7.5.4"
|
||||||
|
|
||||||
vue@^3.4.5:
|
vue@^3.4.13:
|
||||||
version "3.4.5"
|
version "3.4.13"
|
||||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.5.tgz#c08b9d903a20faaf4df7270bf2fa7487741b2294"
|
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.13.tgz#aa522baf2344d1c4c54c769f66c0151f1872f1ff"
|
||||||
integrity sha512-VH6nHFhLPjgu2oh5vEBXoNZxsGHuZNr3qf4PHClwJWw6IDqw6B3x+4J+ABdoZ0aJuT8Zi0zf3GpGlLQCrGWHrw==
|
integrity sha512-FE3UZ0p+oUZTwz+SzlH/hDFg+XsVRFvwmx0LXjdD1pRK/cO4fu5v6ltAZji4za4IBih3dV78elUK3di8v3pWIg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/compiler-dom" "3.4.5"
|
"@vue/compiler-dom" "3.4.13"
|
||||||
"@vue/compiler-sfc" "3.4.5"
|
"@vue/compiler-sfc" "3.4.13"
|
||||||
"@vue/runtime-dom" "3.4.5"
|
"@vue/runtime-dom" "3.4.13"
|
||||||
"@vue/server-renderer" "3.4.5"
|
"@vue/server-renderer" "3.4.13"
|
||||||
"@vue/shared" "3.4.5"
|
"@vue/shared" "3.4.13"
|
||||||
|
|
||||||
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.
Binary file not shown.
Loading…
Reference in New Issue
Block a user