Merge branch 'development'
This commit is contained in:
commit
7ba2058625
@ -37,7 +37,7 @@ Like to show your own build? Just send me a Pull Request.
|
||||
|
||||
####
|
||||
Power Limiter States
|
||||

|
||||

|
||||
|
||||
### Web-Live-Interface:
|
||||

|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram name="Page-1" id="b5b7bab2-c9e2-2cf4-8b2a-24fd1a2a6d21">
|
||||
<mxGraphModel dx="1539" dy="843" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="none" math="0" shadow="0">
|
||||
<mxGraphModel dx="1370" dy="985" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="none" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
@ -18,7 +18,7 @@
|
||||
<mxPoint x="205" y="370" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="sqCMRMHiXPc9LqBIY9SA-6" value="<p style="margin:0px;margin-top:4px;text-align:center;">STATE_OFF</p><hr><p></p><p style="margin:0px;margin-left:8px;text-align:left;">entry / stop inverter, limit lower limit<br>do / nothing<br>exit / start inverter</p>" style="shape=mxgraph.sysml.simpleState;html=1;overflow=fill;whiteSpace=wrap;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxCell id="sqCMRMHiXPc9LqBIY9SA-6" value="<p style="margin:0px;margin-top:4px;text-align:center;">STATE_OFF</p><hr><p></p><p style="margin:0px;margin-left:8px;text-align:left;">entry / stop inverter, limit lower limit<br>do / nothing<br>exit / nothing</p>" style="shape=mxgraph.sysml.simpleState;html=1;overflow=fill;whiteSpace=wrap;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="170" y="585" width="200" height="100" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="sqCMRMHiXPc9LqBIY9SA-7" value="<p style="margin:0px;margin-top:4px;text-align:center;">STATE_CONSUME_SOLAR_POWER_ONLY</p><hr><p></p><p style="margin:0px;margin-left:8px;text-align:left;">entry /<br>do / setNewLimit<br>exit /</p>" style="shape=mxgraph.sysml.simpleState;html=1;overflow=fill;whiteSpace=wrap;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
@ -119,32 +119,19 @@
|
||||
<mxPoint x="530" y="760" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="24" value="isStartThresholdReached &amp;&amp; newPowerLimit &gt; lowerPowerLimit" style="rhombus;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="595" y="930" width="130" height="130" as="geometry"/>
|
||||
<mxCell id="41" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="24" target="25">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="25" value="cnUseDirectSolarPower &amp;&amp; newPowerLimit &gt; lowerPowerLimit" style="rhombus;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="595" y="745" width="130" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="26" value="" style="endArrow=classic;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="24" target="25" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="860" y="950" as="sourcePoint"/>
|
||||
<mxPoint x="750" y="840" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="32" value="no" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="26" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.0933" y="-1" relative="1" as="geometry">
|
||||
<mxCell id="42" value="no" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="41">
|
||||
<mxGeometry x="0.36" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="28" value="yes" style="endArrow=classic;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.113;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="24" target="sqCMRMHiXPc9LqBIY9SA-8" edge="1">
|
||||
<mxGeometry x="-0.2308" y="10" width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="620" y="510" as="sourcePoint"/>
|
||||
<mxPoint x="670" y="460" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="993" y="1000"/>
|
||||
</Array>
|
||||
<mxPoint x="1" as="offset"/>
|
||||
</mxGeometry>
|
||||
<mxCell id="24" value="canUseDirectSolarPower" style="rhombus;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="600" y="930" width="130" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="25" value="isStartThresholdReached" style="rhombus;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="830" y="930" width="130" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="29" value="" style="endArrow=classic;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="21" target="5" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
@ -157,43 +144,18 @@
|
||||
<mxPoint x="3" y="-15" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="33" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="25" target="sqCMRMHiXPc9LqBIY9SA-7" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="620" y="480" as="sourcePoint"/>
|
||||
<mxPoint x="670" y="430" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="34" value="yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="33" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.424" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="35" value="" style="endArrow=classic;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="25" target="38" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="390" y="910" as="sourcePoint"/>
|
||||
<mxPoint x="440" y="860" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="36" value="no" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="35" vertex="1" connectable="0">
|
||||
<mxGeometry x="0.4667" relative="1" as="geometry">
|
||||
<mxPoint x="-19" y="-5" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="38" value="" style="shape=ellipse;html=1;dashed=0;whitespace=wrap;aspect=fixed;strokeWidth=5;perimeter=ellipsePerimeter;" parent="1" vertex="1">
|
||||
<mxGeometry x="795" y="795" width="30" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="JKiNQljIdbqBsyxyxwz1-38" value="" style="endArrow=classic;html=1;rounded=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="sqCMRMHiXPc9LqBIY9SA-7" target="sqCMRMHiXPc9LqBIY9SA-6" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="570" y="820" as="sourcePoint"/>
|
||||
<mxPoint x="620" y="770" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JKiNQljIdbqBsyxyxwz1-39" value="!Inverter-&gt;isProducing ||<br>isStopThresholdReached ||<br>newLimit &lt; lowerLimit" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="JKiNQljIdbqBsyxyxwz1-38" vertex="1" connectable="0">
|
||||
<mxCell id="JKiNQljIdbqBsyxyxwz1-39" value="isStopThresholdReached ||<br>(!canUseDirectSolarPower <br>&amp;&amp; EMPTY_WHEN_FULL)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="JKiNQljIdbqBsyxyxwz1-38" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.2129" y="-2" relative="1" as="geometry">
|
||||
<mxPoint x="-14" y="-33" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JKiNQljIdbqBsyxyxwz1-40" value="!canUseDirectSolarPower ||<br>isStartThresholdReached" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="sqCMRMHiXPc9LqBIY9SA-7" target="sqCMRMHiXPc9LqBIY9SA-8" edge="1">
|
||||
<mxCell id="JKiNQljIdbqBsyxyxwz1-40" value="isStartThresholdReached ||<br style="border-color: var(--border-color);">(!canUseDirectSolarPower<br style="border-color: var(--border-color);">&amp;&amp; EMPTY_AT_NIGHT)" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="sqCMRMHiXPc9LqBIY9SA-7" target="sqCMRMHiXPc9LqBIY9SA-8" edge="1">
|
||||
<mxGeometry x="-0.0286" y="25" width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="570" y="560" as="sourcePoint"/>
|
||||
<mxPoint x="620" y="510" as="targetPoint"/>
|
||||
@ -210,7 +172,7 @@
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JKiNQljIdbqBsyxyxwz1-42" value="!Inverter-&gt;isProducing ||<br>isStopThresholdReached ||<br>newLimit &lt; lowerLimit" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="JKiNQljIdbqBsyxyxwz1-41" vertex="1" connectable="0">
|
||||
<mxCell id="JKiNQljIdbqBsyxyxwz1-42" value="isStopThresholdReached ||" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="JKiNQljIdbqBsyxyxwz1-41" vertex="1" connectable="0">
|
||||
<mxGeometry x="-0.2129" y="-2" relative="1" as="geometry">
|
||||
<mxPoint x="-151" y="32" as="offset"/>
|
||||
</mxGeometry>
|
||||
@ -257,6 +219,56 @@
|
||||
<mxPoint x="16" y="33" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="38" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="24" target="sqCMRMHiXPc9LqBIY9SA-7">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="580" y="710" as="sourcePoint"/>
|
||||
<mxPoint x="630" y="660" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="39" value="yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="38">
|
||||
<mxGeometry x="0.104" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="40" value="" style="endArrow=classic;html=1;entryX=1;entryY=0.75;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" target="sqCMRMHiXPc9LqBIY9SA-7">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="970" y="658" as="sourcePoint"/>
|
||||
<mxPoint x="800" y="660" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="48" value="canUseDirectSolarPower<br style="border-color: var(--border-color);">&amp;&amp; EMPTY_AT_NIGHT" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="40">
|
||||
<mxGeometry x="-0.04" y="1" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="43" value="" style="endArrow=classic;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="25" target="sqCMRMHiXPc9LqBIY9SA-8">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="960" y="980" as="sourcePoint"/>
|
||||
<mxPoint x="1010" y="930" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="1020" y="995"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="44" value="yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="43">
|
||||
<mxGeometry x="0.1135" y="2" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="45" value="" style="shape=ellipse;html=1;dashed=0;whitespace=wrap;aspect=fixed;strokeWidth=5;perimeter=ellipsePerimeter;" vertex="1" parent="1">
|
||||
<mxGeometry x="880" y="850" width="30" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="46" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="25" target="45">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="580" y="710" as="sourcePoint"/>
|
||||
<mxPoint x="630" y="660" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="47" value="no" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="46">
|
||||
<mxGeometry x="-0.08" relative="1" as="geometry">
|
||||
<mxPoint as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 147 KiB |
@ -99,13 +99,20 @@ struct CONFIG_T {
|
||||
|
||||
bool Mqtt_Hass_Expire;
|
||||
|
||||
bool PowerMeter_Enabled;
|
||||
uint32_t PowerMeter_Interval;
|
||||
uint32_t PowerMeter_Source;
|
||||
char PowerMeter_MqttTopicPowerMeter1[MQTT_MAX_TOPIC_STRLEN + 1];
|
||||
char PowerMeter_MqttTopicPowerMeter2[MQTT_MAX_TOPIC_STRLEN + 1];
|
||||
char PowerMeter_MqttTopicPowerMeter3[MQTT_MAX_TOPIC_STRLEN + 1];
|
||||
uint32_t PowerMeter_SdmBaudrate;
|
||||
uint32_t PowerMeter_SdmAddress;
|
||||
|
||||
|
||||
bool PowerLimiter_Enabled;
|
||||
bool PowerLimiter_SolarPassTroughEnabled;
|
||||
uint8_t PowerLimiter_BatteryDrainStategy;
|
||||
uint32_t PowerLimiter_Interval;
|
||||
char PowerLimiter_MqttTopicPowerMeter1[MQTT_MAX_TOPIC_STRLEN + 1];
|
||||
char PowerLimiter_MqttTopicPowerMeter2[MQTT_MAX_TOPIC_STRLEN + 1];
|
||||
char PowerLimiter_MqttTopicPowerMeter3[MQTT_MAX_TOPIC_STRLEN + 1];
|
||||
bool PowerLimiter_IsInverterBehindPowerMeter;
|
||||
uint8_t PowerLimiter_InverterId;
|
||||
uint8_t PowerLimiter_InverterChannelId;
|
||||
|
||||
@ -26,12 +26,10 @@ public:
|
||||
void loop();
|
||||
plStates getPowerLimiterState();
|
||||
int32_t getLastRequestedPowewrLimit();
|
||||
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
|
||||
|
||||
private:
|
||||
uint32_t _lastCommandSent;
|
||||
uint32_t _lastLoop;
|
||||
uint32_t _lastPowerMeterUpdate;
|
||||
int32_t _lastRequestedPowerLimit;
|
||||
plStates _plState = STATE_DISCOVER;
|
||||
|
||||
|
||||
45
include/PowerMeter.h
Normal file
45
include/PowerMeter.h
Normal file
@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include "Configuration.h"
|
||||
#include <espMqttClient.h>
|
||||
#include <Arduino.h>
|
||||
#include <Hoymiles.h>
|
||||
#include <memory>
|
||||
#include "SDM.h"
|
||||
|
||||
#ifndef SDM_RX_PIN
|
||||
#define SDM_RX_PIN 13
|
||||
#endif
|
||||
|
||||
#ifndef SDM_TX_PIN
|
||||
#define SDM_TX_PIN 32
|
||||
#endif
|
||||
|
||||
class PowerMeterClass {
|
||||
public:
|
||||
void init();
|
||||
void mqtt();
|
||||
void loop();
|
||||
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
|
||||
float getPowerTotal();
|
||||
uint32_t getLastPowerMeterUpdate();
|
||||
|
||||
private:
|
||||
uint32_t _interval;
|
||||
uint32_t _lastPowerMeterUpdate;
|
||||
|
||||
float _powerMeter1Power = 0.0;
|
||||
float _powerMeter2Power = 0.0;
|
||||
float _powerMeter3Power = 0.0;
|
||||
float _powerMeterTotalPower = 0.0;
|
||||
float _powerMeter1Voltage = 0.0;
|
||||
float _powerMeter2Voltage = 0.0;
|
||||
float _powerMeter3Voltage = 0.0;
|
||||
float _PowerMeterImport = 0.0;
|
||||
float _PowerMeterExport = 0.0;
|
||||
|
||||
bool mqttInitDone = false;
|
||||
};
|
||||
|
||||
extern PowerMeterClass PowerMeter;
|
||||
@ -15,6 +15,7 @@
|
||||
#include "WebApi_network.h"
|
||||
#include "WebApi_ntp.h"
|
||||
#include "WebApi_power.h"
|
||||
#include "WebApi_powermeter.h"
|
||||
#include "WebApi_powerlimiter.h"
|
||||
#include "WebApi_prometheus.h"
|
||||
#include "WebApi_security.h"
|
||||
@ -55,6 +56,7 @@ private:
|
||||
WebApiNetworkClass _webApiNetwork;
|
||||
WebApiNtpClass _webApiNtp;
|
||||
WebApiPowerClass _webApiPower;
|
||||
WebApiPowerMeterClass _webApiPowerMeter;
|
||||
WebApiPowerLimiterClass _webApiPowerLimiter;
|
||||
WebApiPrometheusClass _webApiPrometheus;
|
||||
WebApiSecurityClass _webApiSecurity;
|
||||
|
||||
18
include/WebApi_powermeter.h
Normal file
18
include/WebApi_powermeter.h
Normal file
@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
|
||||
class WebApiPowerMeterClass {
|
||||
public:
|
||||
void init(AsyncWebServer* server);
|
||||
void loop();
|
||||
|
||||
private:
|
||||
void onStatus(AsyncWebServerRequest* request);
|
||||
void onAdminGet(AsyncWebServerRequest* request);
|
||||
void onAdminPost(AsyncWebServerRequest* request);
|
||||
|
||||
AsyncWebServer* _server;
|
||||
};
|
||||
@ -92,6 +92,13 @@
|
||||
#define VEDIRECT_UPDATESONLY true
|
||||
#define VEDIRECT_POLL_INTERVAL 5
|
||||
|
||||
#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_PASSTROUGH_ENABLED true
|
||||
#define POWERLIMITER_BATTERY_DRAIN_STRATEGY 0
|
||||
|
||||
@ -170,8 +170,8 @@ uint8_t DevInfoParser::getDevIdx()
|
||||
/* struct tm to seconds since Unix epoch */
|
||||
time_t DevInfoParser::timegm(struct tm* t)
|
||||
{
|
||||
register uint32_t year;
|
||||
register time_t result;
|
||||
uint32_t year;
|
||||
time_t result;
|
||||
#define MONTHSPERYEAR 12 /* months per calendar year */
|
||||
static const int cumdays[MONTHSPERYEAR] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
|
||||
|
||||
|
||||
254
lib/SdmEnergyMeter/SDM.cpp
Normal file
254
lib/SdmEnergyMeter/SDM.cpp
Normal file
@ -0,0 +1,254 @@
|
||||
/* Library for reading SDM 72/120/220/230/630 Modbus Energy meters.
|
||||
* Reading via Hardware or Software Serial library & rs232<->rs485 converter
|
||||
* 2016-2022 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.8.10 & 2.5.2 esp8266 core)
|
||||
* crc calculation by Jaime García (https://github.com/peninquen/Modbus-Energy-Monitor-Arduino/)
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
#include "SDM.h"
|
||||
//------------------------------------------------------------------------------
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
#if defined ( ESP8266 )
|
||||
SDM::SDM(HardwareSerial& serial, long baud, int dere_pin, int config, bool swapuart) : sdmSer(serial) {
|
||||
this->_baud = baud;
|
||||
this->_dere_pin = dere_pin;
|
||||
this->_config = config;
|
||||
this->_swapuart = swapuart;
|
||||
}
|
||||
#elif defined ( ESP32 )
|
||||
SDM::SDM(HardwareSerial& serial, long baud, int dere_pin, int config, int8_t rx_pin, int8_t tx_pin) : sdmSer(serial) {
|
||||
this->_baud = baud;
|
||||
this->_dere_pin = dere_pin;
|
||||
this->_config = config;
|
||||
this->_rx_pin = rx_pin;
|
||||
this->_tx_pin = tx_pin;
|
||||
}
|
||||
#else
|
||||
SDM::SDM(HardwareSerial& serial, long baud, int dere_pin, int config) : sdmSer(serial) {
|
||||
this->_baud = baud;
|
||||
this->_dere_pin = dere_pin;
|
||||
this->_config = config;
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 )
|
||||
SDM::SDM(SoftwareSerial& serial, long baud, int dere_pin, int config, int8_t rx_pin, int8_t tx_pin) : sdmSer(serial) {
|
||||
this->_baud = baud;
|
||||
this->_dere_pin = dere_pin;
|
||||
this->_config = config;
|
||||
this->_rx_pin = rx_pin;
|
||||
this->_tx_pin = tx_pin;
|
||||
}
|
||||
#else
|
||||
SDM::SDM(SoftwareSerial& serial, long baud, int dere_pin) : sdmSer(serial) {
|
||||
this->_baud = baud;
|
||||
this->_dere_pin = dere_pin;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
SDM::~SDM() {
|
||||
}
|
||||
|
||||
void SDM::begin(void) {
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
#if defined ( ESP8266 )
|
||||
sdmSer.begin(_baud, (SerialConfig)_config);
|
||||
#elif defined ( ESP32 )
|
||||
sdmSer.begin(_baud, _config, _rx_pin, _tx_pin);
|
||||
#else
|
||||
sdmSer.begin(_baud, _config);
|
||||
#endif
|
||||
#else
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 )
|
||||
sdmSer.begin(_baud, (SoftwareSerialConfig)_config, _rx_pin, _tx_pin);
|
||||
#else
|
||||
sdmSer.begin(_baud);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined ( USE_HARDWARESERIAL ) && defined ( ESP8266 )
|
||||
if (_swapuart)
|
||||
sdmSer.swap();
|
||||
#endif
|
||||
if (_dere_pin != NOT_A_PIN) {
|
||||
pinMode(_dere_pin, OUTPUT); //set output pin mode for DE/RE pin when used (for control MAX485)
|
||||
}
|
||||
dereSet(LOW); //set init state to receive from SDM -> DE Disable, /RE Enable (for control MAX485)
|
||||
}
|
||||
|
||||
float SDM::readVal(uint16_t reg, uint8_t node) {
|
||||
uint16_t temp;
|
||||
unsigned long resptime;
|
||||
uint8_t sdmarr[FRAMESIZE] = {node, SDM_B_02, 0, 0, SDM_B_05, SDM_B_06, 0, 0, 0};
|
||||
float res = NAN;
|
||||
uint16_t readErr = SDM_ERR_NO_ERROR;
|
||||
|
||||
sdmarr[2] = highByte(reg);
|
||||
sdmarr[3] = lowByte(reg);
|
||||
|
||||
temp = calculateCRC(sdmarr, FRAMESIZE - 3); //calculate out crc only from first 6 bytes
|
||||
|
||||
sdmarr[6] = lowByte(temp);
|
||||
sdmarr[7] = highByte(temp);
|
||||
|
||||
#if !defined ( USE_HARDWARESERIAL )
|
||||
sdmSer.listen(); //enable softserial rx interrupt
|
||||
#endif
|
||||
|
||||
flush(); //read serial if any old data is available
|
||||
|
||||
dereSet(HIGH); //transmit to SDM -> DE Enable, /RE Disable (for control MAX485)
|
||||
|
||||
delay(2); //fix for issue (nan reading) by sjfaustino: https://github.com/reaper7/SDM_Energy_Meter/issues/7#issuecomment-272111524
|
||||
|
||||
sdmSer.write(sdmarr, FRAMESIZE - 1); //send 8 bytes
|
||||
|
||||
sdmSer.flush(); //clear out tx buffer
|
||||
|
||||
dereSet(LOW); //receive from SDM -> DE Disable, /RE Enable (for control MAX485)
|
||||
|
||||
resptime = millis();
|
||||
|
||||
while (sdmSer.available() < FRAMESIZE) {
|
||||
if (millis() - resptime > msturnaround) {
|
||||
readErr = SDM_ERR_TIMEOUT; //err debug (4)
|
||||
break;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
if (readErr == SDM_ERR_NO_ERROR) { //if no timeout...
|
||||
|
||||
if (sdmSer.available() >= FRAMESIZE) {
|
||||
|
||||
for(int n=0; n<FRAMESIZE; n++) {
|
||||
sdmarr[n] = sdmSer.read();
|
||||
}
|
||||
|
||||
if (sdmarr[0] == node && sdmarr[1] == SDM_B_02 && sdmarr[2] == SDM_REPLY_BYTE_COUNT) {
|
||||
|
||||
if ((calculateCRC(sdmarr, FRAMESIZE - 2)) == ((sdmarr[8] << 8) | sdmarr[7])) { //calculate crc from first 7 bytes and compare with received crc (bytes 7 & 8)
|
||||
((uint8_t*)&res)[3]= sdmarr[3];
|
||||
((uint8_t*)&res)[2]= sdmarr[4];
|
||||
((uint8_t*)&res)[1]= sdmarr[5];
|
||||
((uint8_t*)&res)[0]= sdmarr[6];
|
||||
} else {
|
||||
readErr = SDM_ERR_CRC_ERROR; //err debug (1)
|
||||
}
|
||||
|
||||
} else {
|
||||
readErr = SDM_ERR_WRONG_BYTES; //err debug (2)
|
||||
}
|
||||
|
||||
} else {
|
||||
readErr = SDM_ERR_NOT_ENOUGHT_BYTES; //err debug (3)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
flush(mstimeout); //read serial if any old data is available and wait for RESPONSE_TIMEOUT (in ms)
|
||||
|
||||
if (sdmSer.available()) //if serial rx buffer (after RESPONSE_TIMEOUT) still contains data then something spam rs485, check node(s) or increase RESPONSE_TIMEOUT
|
||||
readErr = SDM_ERR_TIMEOUT; //err debug (4) but returned value may be correct
|
||||
|
||||
if (readErr != SDM_ERR_NO_ERROR) { //if error then copy temp error value to global val and increment global error counter
|
||||
readingerrcode = readErr;
|
||||
readingerrcount++;
|
||||
} else {
|
||||
++readingsuccesscount;
|
||||
}
|
||||
|
||||
#if !defined ( USE_HARDWARESERIAL )
|
||||
sdmSer.stopListening(); //disable softserial rx interrupt
|
||||
#endif
|
||||
|
||||
return (res);
|
||||
}
|
||||
|
||||
uint16_t SDM::getErrCode(bool _clear) {
|
||||
uint16_t _tmp = readingerrcode;
|
||||
if (_clear == true)
|
||||
clearErrCode();
|
||||
return (_tmp);
|
||||
}
|
||||
|
||||
uint32_t SDM::getErrCount(bool _clear) {
|
||||
uint32_t _tmp = readingerrcount;
|
||||
if (_clear == true)
|
||||
clearErrCount();
|
||||
return (_tmp);
|
||||
}
|
||||
|
||||
uint32_t SDM::getSuccCount(bool _clear) {
|
||||
uint32_t _tmp = readingsuccesscount;
|
||||
if (_clear == true)
|
||||
clearSuccCount();
|
||||
return (_tmp);
|
||||
}
|
||||
|
||||
void SDM::clearErrCode() {
|
||||
readingerrcode = SDM_ERR_NO_ERROR;
|
||||
}
|
||||
|
||||
void SDM::clearErrCount() {
|
||||
readingerrcount = 0;
|
||||
}
|
||||
|
||||
void SDM::clearSuccCount() {
|
||||
readingsuccesscount = 0;
|
||||
}
|
||||
|
||||
void SDM::setMsTurnaround(uint16_t _msturnaround) {
|
||||
if (_msturnaround < SDM_MIN_DELAY)
|
||||
msturnaround = SDM_MIN_DELAY;
|
||||
else if (_msturnaround > SDM_MAX_DELAY)
|
||||
msturnaround = SDM_MAX_DELAY;
|
||||
else
|
||||
msturnaround = _msturnaround;
|
||||
}
|
||||
|
||||
void SDM::setMsTimeout(uint16_t _mstimeout) {
|
||||
if (_mstimeout < SDM_MIN_DELAY)
|
||||
mstimeout = SDM_MIN_DELAY;
|
||||
else if (_mstimeout > SDM_MAX_DELAY)
|
||||
mstimeout = SDM_MAX_DELAY;
|
||||
else
|
||||
mstimeout = _mstimeout;
|
||||
}
|
||||
|
||||
uint16_t SDM::getMsTurnaround() {
|
||||
return (msturnaround);
|
||||
}
|
||||
|
||||
uint16_t SDM::getMsTimeout() {
|
||||
return (mstimeout);
|
||||
}
|
||||
|
||||
uint16_t SDM::calculateCRC(uint8_t *array, uint8_t len) {
|
||||
uint16_t _crc, _flag;
|
||||
_crc = 0xFFFF;
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
_crc ^= (uint16_t)array[i];
|
||||
for (uint8_t j = 8; j; j--) {
|
||||
_flag = _crc & 0x0001;
|
||||
_crc >>= 1;
|
||||
if (_flag)
|
||||
_crc ^= 0xA001;
|
||||
}
|
||||
}
|
||||
return _crc;
|
||||
}
|
||||
|
||||
void SDM::flush(unsigned long _flushtime) {
|
||||
unsigned long flushstart = millis();
|
||||
while (sdmSer.available() || (millis() - flushstart < _flushtime)) {
|
||||
if (sdmSer.available()) //read serial if any old data is available
|
||||
sdmSer.read();
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
void SDM::dereSet(bool _state) {
|
||||
if (_dere_pin != NOT_A_PIN)
|
||||
digitalWrite(_dere_pin, _state); //receive from SDM -> DE Disable, /RE Enable (for control MAX485)
|
||||
}
|
||||
299
lib/SdmEnergyMeter/SDM.h
Normal file
299
lib/SdmEnergyMeter/SDM.h
Normal file
@ -0,0 +1,299 @@
|
||||
/* Library for reading SDM 72/120/220/230/630 Modbus Energy meters.
|
||||
* Reading via Hardware or Software Serial library & rs232<->rs485 converter
|
||||
* 2016-2022 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.8.10 & 2.5.2 esp8266 core)
|
||||
* crc calculation by Jaime García (https://github.com/peninquen/Modbus-Energy-Monitor-Arduino/)
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
#ifndef SDM_h
|
||||
#define SDM_h
|
||||
//------------------------------------------------------------------------------
|
||||
#include <Arduino.h>
|
||||
#include <SDM_Config_User.h>
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
#include <HardwareSerial.h>
|
||||
#else
|
||||
#include <SoftwareSerial.h>
|
||||
#endif
|
||||
//------------------------------------------------------------------------------
|
||||
//DEFAULT CONFIG (DO NOT CHANGE ANYTHING!!! for changes use SDM_Config_User.h):
|
||||
//------------------------------------------------------------------------------
|
||||
#if !defined ( SDM_UART_BAUD )
|
||||
#define SDM_UART_BAUD 4800 // default baudrate
|
||||
#endif
|
||||
|
||||
#if !defined ( DERE_PIN )
|
||||
#define DERE_PIN NOT_A_PIN // default digital pin for control MAX485 DE/RE lines (connect DE & /RE together to this pin)
|
||||
#endif
|
||||
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
|
||||
#if !defined ( SDM_UART_CONFIG )
|
||||
#define SDM_UART_CONFIG SERIAL_8N1 // default hardware uart config
|
||||
#endif
|
||||
|
||||
#if defined ( ESP8266 ) && !defined ( SWAPHWSERIAL )
|
||||
#define SWAPHWSERIAL 0 // (only esp8266) when hwserial used, then swap uart pins from 3/1 to 13/15 (default not swap)
|
||||
#endif
|
||||
|
||||
#if defined ( ESP32 )
|
||||
#if !defined ( SDM_RX_PIN )
|
||||
#define SDM_RX_PIN -1 // use default rx pin for selected port
|
||||
#endif
|
||||
#if !defined ( SDM_TX_PIN )
|
||||
#define SDM_TX_PIN -1 // use default tx pin for selected port
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 )
|
||||
#if !defined ( SDM_UART_CONFIG )
|
||||
#define SDM_UART_CONFIG SWSERIAL_8N1 // default softwareware uart config for esp8266/esp32
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// #if !defined ( SDM_RX_PIN ) || !defined ( SDM_TX_PIN )
|
||||
// #error "SDM_RX_PIN and SDM_TX_PIN must be defined in SDM_Config_User.h for Software Serial option)"
|
||||
// #endif
|
||||
|
||||
#if !defined ( SDM_RX_PIN )
|
||||
#define SDM_RX_PIN -1
|
||||
#endif
|
||||
#if !defined ( SDM_TX_PIN )
|
||||
#define SDM_TX_PIN -1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined ( WAITING_TURNAROUND_DELAY )
|
||||
#define WAITING_TURNAROUND_DELAY 200 // time in ms to wait for process current request
|
||||
#endif
|
||||
|
||||
#if !defined ( RESPONSE_TIMEOUT )
|
||||
#define RESPONSE_TIMEOUT 500 // time in ms to wait for return response from all devices before next request
|
||||
#endif
|
||||
|
||||
#if !defined ( SDM_MIN_DELAY )
|
||||
#define SDM_MIN_DELAY 20 // minimum value (in ms) for WAITING_TURNAROUND_DELAY and RESPONSE_TIMEOUT
|
||||
#endif
|
||||
|
||||
#if !defined ( SDM_MAX_DELAY )
|
||||
#define SDM_MAX_DELAY 5000 // maximum value (in ms) for WAITING_TURNAROUND_DELAY and RESPONSE_TIMEOUT
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#define SDM_ERR_NO_ERROR 0 // no error
|
||||
#define SDM_ERR_CRC_ERROR 1 // crc error
|
||||
#define SDM_ERR_WRONG_BYTES 2 // bytes b0,b1 or b2 wrong
|
||||
#define SDM_ERR_NOT_ENOUGHT_BYTES 3 // not enough bytes from sdm
|
||||
#define SDM_ERR_TIMEOUT 4 // timeout
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#define FRAMESIZE 9 // size of out/in array
|
||||
#define SDM_REPLY_BYTE_COUNT 0x04 // number of bytes with data
|
||||
|
||||
#define SDM_B_01 0x01 // BYTE 1 -> slave address (default value 1 read from node 1)
|
||||
#define SDM_B_02 0x04 // BYTE 2 -> function code (default value 0x04 read from 3X input registers)
|
||||
#define SDM_B_05 0x00 // BYTE 5
|
||||
#define SDM_B_06 0x02 // BYTE 6
|
||||
// BYTES 3 & 4 (BELOW)
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// REGISTERS LIST FOR SDM DEVICES |
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// REGISTER NAME REGISTER ADDRESS UNIT | SDM630 | SDM230 | SDM220 | SDM120CT| SDM120 | SDM72D | SDM72 V2|
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
#define SDM_PHASE_1_VOLTAGE 0x0000 // V | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_VOLTAGE 0x0002 // V | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_VOLTAGE 0x0004 // V | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_CURRENT 0x0006 // A | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_CURRENT 0x0008 // A | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_CURRENT 0x000A // A | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_POWER 0x000C // W | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_POWER 0x000E // W | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_POWER 0x0010 // W | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_APPARENT_POWER 0x0012 // VA | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_APPARENT_POWER 0x0014 // VA | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_APPARENT_POWER 0x0016 // VA | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_REACTIVE_POWER 0x0018 // VAr | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_REACTIVE_POWER 0x001A // VAr | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_REACTIVE_POWER 0x001C // VAr | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_POWER_FACTOR 0x001E // | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_PHASE_2_POWER_FACTOR 0x0020 // | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_3_POWER_FACTOR 0x0022 // | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_ANGLE 0x0024 // Degrees | 1 | 1 | 1 | 1 | | | |
|
||||
#define SDM_PHASE_2_ANGLE 0x0026 // Degrees | 1 | | | | | | |
|
||||
#define SDM_PHASE_3_ANGLE 0x0028 // Degrees | 1 | | | | | | |
|
||||
#define SDM_AVERAGE_L_TO_N_VOLTS 0x002A // V | 1 | | | | | | 1 |
|
||||
#define SDM_AVERAGE_LINE_CURRENT 0x002E // A | 1 | | | | | | 1 |
|
||||
#define SDM_SUM_LINE_CURRENT 0x0030 // A | 1 | | | | | | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_POWER 0x0034 // W | 1 | | | | | 1 | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_APPARENT_POWER 0x0038 // VA | 1 | | | | | | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_REACTIVE_POWER 0x003C // VAr | 1 | | | | | | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_POWER_FACTOR 0x003E // | 1 | | | | | | 1 |
|
||||
#define SDM_TOTAL_SYSTEM_PHASE_ANGLE 0x0042 // Degrees | 1 | | | | | | |
|
||||
#define SDM_FREQUENCY 0x0046 // Hz | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_IMPORT_ACTIVE_ENERGY 0x0048 // kWh/MWh | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
||||
#define SDM_EXPORT_ACTIVE_ENERGY 0x004A // kWh/MWh | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
||||
#define SDM_IMPORT_REACTIVE_ENERGY 0x004C // kVArh/MVArh | 1 | 1 | 1 | 1 | 1 | | |
|
||||
#define SDM_EXPORT_REACTIVE_ENERGY 0x004E // kVArh/MVArh | 1 | 1 | 1 | 1 | 1 | | |
|
||||
#define SDM_VAH_SINCE_LAST_RESET 0x0050 // kVAh/MVAh | 1 | | | | | | |
|
||||
#define SDM_AH_SINCE_LAST_RESET 0x0052 // Ah/kAh | 1 | | | | | | |
|
||||
#define SDM_TOTAL_SYSTEM_POWER_DEMAND 0x0054 // W | 1 | 1 | | | | | |
|
||||
#define SDM_MAXIMUM_TOTAL_SYSTEM_POWER_DEMAND 0x0056 // W | 1 | 1 | | | | | |
|
||||
#define SDM_CURRENT_SYSTEM_POSITIVE_POWER_DEMAND 0x0058 // W | | 1 | | | | | |
|
||||
#define SDM_MAXIMUM_SYSTEM_POSITIVE_POWER_DEMAND 0x005A // W | | 1 | | | | | |
|
||||
#define SDM_CURRENT_SYSTEM_REVERSE_POWER_DEMAND 0x005C // W | | 1 | | | | | |
|
||||
#define SDM_MAXIMUM_SYSTEM_REVERSE_POWER_DEMAND 0x005E // W | | 1 | | | | | |
|
||||
#define SDM_TOTAL_SYSTEM_VA_DEMAND 0x0064 // VA | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_TOTAL_SYSTEM_VA_DEMAND 0x0066 // VA | 1 | | | | | | |
|
||||
#define SDM_NEUTRAL_CURRENT_DEMAND 0x0068 // A | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_NEUTRAL_CURRENT 0x006A // A | 1 | | | | | | |
|
||||
#define SDM_LINE_1_TO_LINE_2_VOLTS 0x00C8 // V | 1 | | | | | | 1 |
|
||||
#define SDM_LINE_2_TO_LINE_3_VOLTS 0x00CA // V | 1 | | | | | | 1 |
|
||||
#define SDM_LINE_3_TO_LINE_1_VOLTS 0x00CC // V | 1 | | | | | | 1 |
|
||||
#define SDM_AVERAGE_LINE_TO_LINE_VOLTS 0x00CE // V | 1 | | | | | | 1 |
|
||||
#define SDM_NEUTRAL_CURRENT 0x00E0 // A | 1 | | | | | | 1 |
|
||||
#define SDM_PHASE_1_LN_VOLTS_THD 0x00EA // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_2_LN_VOLTS_THD 0x00EC // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_3_LN_VOLTS_THD 0x00EE // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_1_CURRENT_THD 0x00F0 // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_2_CURRENT_THD 0x00F2 // % | 1 | | | | | | |
|
||||
#define SDM_PHASE_3_CURRENT_THD 0x00F4 // % | 1 | | | | | | |
|
||||
#define SDM_AVERAGE_LINE_TO_NEUTRAL_VOLTS_THD 0x00F8 // % | 1 | | | | | | |
|
||||
#define SDM_AVERAGE_LINE_CURRENT_THD 0x00FA // % | 1 | | | | | | |
|
||||
#define SDM_TOTAL_SYSTEM_POWER_FACTOR_INV 0x00FE // | 1 | | | | | | |
|
||||
#define SDM_PHASE_1_CURRENT_DEMAND 0x0102 // A | 1 | 1 | | | | | |
|
||||
#define SDM_PHASE_2_CURRENT_DEMAND 0x0104 // A | 1 | | | | | | |
|
||||
#define SDM_PHASE_3_CURRENT_DEMAND 0x0106 // A | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_PHASE_1_CURRENT_DEMAND 0x0108 // A | 1 | 1 | | | | | |
|
||||
#define SDM_MAXIMUM_PHASE_2_CURRENT_DEMAND 0x010A // A | 1 | | | | | | |
|
||||
#define SDM_MAXIMUM_PHASE_3_CURRENT_DEMAND 0x010C // A | 1 | | | | | | |
|
||||
#define SDM_LINE_1_TO_LINE_2_VOLTS_THD 0x014E // % | 1 | | | | | | |
|
||||
#define SDM_LINE_2_TO_LINE_3_VOLTS_THD 0x0150 // % | 1 | | | | | | |
|
||||
#define SDM_LINE_3_TO_LINE_1_VOLTS_THD 0x0152 // % | 1 | | | | | | |
|
||||
#define SDM_AVERAGE_LINE_TO_LINE_VOLTS_THD 0x0154 // % | 1 | | | | | | |
|
||||
#define SDM_TOTAL_ACTIVE_ENERGY 0x0156 // kWh | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
||||
#define SDM_TOTAL_REACTIVE_ENERGY 0x0158 // kVArh | 1 | 1 | 1 | 1 | 1 | | 1 |
|
||||
#define SDM_L1_IMPORT_ACTIVE_ENERGY 0x015A // kWh | 1 | | | | | | |
|
||||
#define SDM_L2_IMPORT_ACTIVE_ENERGY 0x015C // kWh | 1 | | | | | | |
|
||||
#define SDM_L3_IMPORT_ACTIVE_ENERGY 0x015E // kWh | 1 | | | | | | |
|
||||
#define SDM_L1_EXPORT_ACTIVE_ENERGY 0x0160 // kWh | 1 | | | | | | |
|
||||
#define SDM_L2_EXPORT_ACTIVE_ENERGY 0x0162 // kWh | 1 | | | | | | |
|
||||
#define SDM_L3_EXPORT_ACTIVE_ENERGY 0x0164 // kWh | 1 | | | | | | |
|
||||
#define SDM_L1_TOTAL_ACTIVE_ENERGY 0x0166 // kWh | 1 | | | | | | |
|
||||
#define SDM_L2_TOTAL_ACTIVE_ENERGY 0x0168 // kWh | 1 | | | | | | |
|
||||
#define SDM_L3_TOTAL_ACTIVE_ENERGY 0x016a // kWh | 1 | | | | | | |
|
||||
#define SDM_L1_IMPORT_REACTIVE_ENERGY 0x016C // kVArh | 1 | | | | | | |
|
||||
#define SDM_L2_IMPORT_REACTIVE_ENERGY 0x016E // kVArh | 1 | | | | | | |
|
||||
#define SDM_L3_IMPORT_REACTIVE_ENERGY 0x0170 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L1_EXPORT_REACTIVE_ENERGY 0x0172 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L2_EXPORT_REACTIVE_ENERGY 0x0174 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L3_EXPORT_REACTIVE_ENERGY 0x0176 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L1_TOTAL_REACTIVE_ENERGY 0x0178 // kVArh | 1 | | | | | | |
|
||||
#define SDM_L2_TOTAL_REACTIVE_ENERGY 0x017A // kVArh | 1 | | | | | | |
|
||||
#define SDM_L3_TOTAL_REACTIVE_ENERGY 0x017C // kVArh | 1 | | | | | | |
|
||||
#define SDM_CURRENT_RESETTABLE_TOTAL_ACTIVE_ENERGY 0x0180 // kWh | | 1 | | | | 1 | 1 |
|
||||
#define SDM_CURRENT_RESETTABLE_TOTAL_REACTIVE_ENERGY 0x0182 // kVArh | | 1 | | | | | |
|
||||
#define SDM_CURRENT_RESETTABLE_IMPORT_ENERGY 0x0184 // kWh | | | | | | 1 | 1 |
|
||||
#define SDM_CURRENT_RESETTABLE_EXPORT_ENERGY 0x0186 // kWh | | | | | | 1 | 1 |
|
||||
#define SDM_NET_KWH 0x018C // kWh | | | | | | | 1 |
|
||||
#define SDM_IMPORT_POWER 0x0500 // W | | | | | | 1 | 1 |
|
||||
#define SDM_EXPORT_POWER 0x0502 // W | | | | | | 1 | 1 |
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTERS LIST FOR DDM DEVICE |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTER NAME REGISTER ADDRESS UNIT | DDM18SD |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
#define DDM_PHASE_1_VOLTAGE 0x0000 // V | 1 |
|
||||
#define DDM_PHASE_1_CURRENT 0x0008 // A | 1 |
|
||||
#define DDM_PHASE_1_POWER 0x0012 // W | 1 |
|
||||
#define DDM_PHASE_1_REACTIVE_POWER 0x001A // VAr | 1 |
|
||||
#define DDM_PHASE_1_POWER_FACTOR 0x002A // | 1 |
|
||||
#define DDM_FREQUENCY 0x0036 // Hz | 1 |
|
||||
#define DDM_IMPORT_ACTIVE_ENERGY 0x0100 // kWh | 1 |
|
||||
#define DDM_IMPORT_REACTIVE_ENERGY 0x0400 // kVArh | 1 |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTERS LIST FOR DEVNAME DEVICE |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
// REGISTER NAME REGISTER ADDRESS UNIT | DEVNAME |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
//#define DEVNAME_VOLTAGE 0x0000 // V | 1 |
|
||||
//#define DEVNAME_CURRENT 0x0002 // A | 1 |
|
||||
//#define DEVNAME_POWER 0x0004 // W | 1 |
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
class SDM {
|
||||
public:
|
||||
#if defined ( USE_HARDWARESERIAL ) // hardware serial
|
||||
#if defined ( ESP8266 ) // on esp8266
|
||||
SDM(HardwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN, int config = SDM_UART_CONFIG, bool swapuart = SWAPHWSERIAL);
|
||||
#elif defined ( ESP32 ) // on esp32
|
||||
SDM(HardwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN, int config = SDM_UART_CONFIG, int8_t rx_pin = SDM_RX_PIN, int8_t tx_pin = SDM_TX_PIN);
|
||||
#else // on avr
|
||||
SDM(HardwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN, int config = SDM_UART_CONFIG);
|
||||
#endif
|
||||
#else // software serial
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 ) // on esp8266/esp32
|
||||
SDM(SoftwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN, int config = SDM_UART_CONFIG, int8_t rx_pin = SDM_RX_PIN, int8_t tx_pin = SDM_TX_PIN);
|
||||
#else // on avr
|
||||
SDM(SoftwareSerial& serial, long baud = SDM_UART_BAUD, int dere_pin = DERE_PIN);
|
||||
#endif
|
||||
#endif
|
||||
virtual ~SDM();
|
||||
|
||||
void begin(void);
|
||||
float readVal(uint16_t reg, uint8_t node = SDM_B_01); // read value from register = reg and from deviceId = node
|
||||
uint16_t getErrCode(bool _clear = false); // return last errorcode (optional clear this value, default flase)
|
||||
uint32_t getErrCount(bool _clear = false); // return total errors count (optional clear this value, default flase)
|
||||
uint32_t getSuccCount(bool _clear = false); // return total success count (optional clear this value, default false)
|
||||
void clearErrCode(); // clear last errorcode
|
||||
void clearErrCount(); // clear total errors count
|
||||
void clearSuccCount(); // clear total success count
|
||||
void setMsTurnaround(uint16_t _msturnaround = WAITING_TURNAROUND_DELAY); // set new value for WAITING_TURNAROUND_DELAY (ms), min=SDM_MIN_DELAY, max=SDM_MAX_DELAY
|
||||
void setMsTimeout(uint16_t _mstimeout = RESPONSE_TIMEOUT); // set new value for RESPONSE_TIMEOUT (ms), min=SDM_MIN_DELAY, max=SDM_MAX_DELAY
|
||||
uint16_t getMsTurnaround(); // get current value of WAITING_TURNAROUND_DELAY (ms)
|
||||
uint16_t getMsTimeout(); // get current value of RESPONSE_TIMEOUT (ms)
|
||||
|
||||
private:
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
HardwareSerial& sdmSer;
|
||||
#else
|
||||
SoftwareSerial& sdmSer;
|
||||
#endif
|
||||
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
int _config = SDM_UART_CONFIG;
|
||||
#if defined ( ESP8266 )
|
||||
bool _swapuart = SWAPHWSERIAL;
|
||||
#elif defined ( ESP32 )
|
||||
int8_t _rx_pin = -1;
|
||||
int8_t _tx_pin = -1;
|
||||
#endif
|
||||
#else
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 )
|
||||
int _config = SDM_UART_CONFIG;
|
||||
#endif
|
||||
int8_t _rx_pin = -1;
|
||||
int8_t _tx_pin = -1;
|
||||
#endif
|
||||
long _baud = SDM_UART_BAUD;
|
||||
int _dere_pin = DERE_PIN;
|
||||
uint16_t readingerrcode = SDM_ERR_NO_ERROR; // 4 = timeout; 3 = not enough bytes; 2 = number of bytes OK but bytes b0,b1 or b2 wrong, 1 = crc error
|
||||
uint16_t msturnaround = WAITING_TURNAROUND_DELAY;
|
||||
uint16_t mstimeout = RESPONSE_TIMEOUT;
|
||||
uint32_t readingerrcount = 0; // total errors counter
|
||||
uint32_t readingsuccesscount = 0; // total success counter
|
||||
uint16_t calculateCRC(uint8_t *array, uint8_t len);
|
||||
void flush(unsigned long _flushtime = 0); // read serial if any old data is available or for a given time in ms
|
||||
void dereSet(bool _state = LOW); // for control MAX485 DE/RE pins, LOW receive from SDM, HIGH transmit to SDM
|
||||
};
|
||||
#endif // SDM_h
|
||||
93
lib/SdmEnergyMeter/SDM_Config_User.h
Normal file
93
lib/SdmEnergyMeter/SDM_Config_User.h
Normal file
@ -0,0 +1,93 @@
|
||||
/* Library for reading SDM 72/120/220/230/630 Modbus Energy meters.
|
||||
* Reading via Hardware or Software Serial library & rs232<->rs485 converter
|
||||
* 2016-2022 Reaper7 (tested on wemos d1 mini->ESP8266 with Arduino 1.8.10 & 2.5.2 esp8266 core)
|
||||
* crc calculation by Jaime García (https://github.com/peninquen/Modbus-Energy-Monitor-Arduino/)
|
||||
*/
|
||||
|
||||
/*
|
||||
* USER CONFIG:
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define or undefine USE_HARDWARESERIAL (uncomment only one or none)
|
||||
*/
|
||||
//#undef USE_HARDWARESERIAL
|
||||
#define USE_HARDWARESERIAL
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user baudrate
|
||||
*/
|
||||
#define SDM_UART_BAUD 9600
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user SDM_RX_PIN and SDM_TX_PIN for esp/avr Software Serial option
|
||||
* or ESP32 with Hardware Serial if default core pins are not suitable
|
||||
*/
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
#if defined ( ESP32 )
|
||||
#define SDM_RX_PIN 13
|
||||
#define SDM_TX_PIN 32
|
||||
#endif
|
||||
#else
|
||||
#if defined ( ESP8266 ) || defined ( ESP32 )
|
||||
#define SDM_RX_PIN 13
|
||||
#define SDM_TX_PIN 15
|
||||
#else
|
||||
#define SDM_RX_PIN 10
|
||||
#define SDM_TX_PIN 11
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user DERE_PIN for control MAX485 DE/RE lines (connect DE & /RE together to this pin)
|
||||
*/
|
||||
//#define DERE_PIN NOT_A_PIN
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if defined ( USE_HARDWARESERIAL )
|
||||
|
||||
/*
|
||||
* define user SDM_UART_CONFIG for hardware serial
|
||||
*/
|
||||
//#define SDM_UART_CONFIG SERIAL_8N1
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user SWAPHWSERIAL, if true(1) then swap uart pins from 3/1 to 13/15 (only ESP8266)
|
||||
*/
|
||||
//#define SWAPHWSERIAL 0
|
||||
|
||||
#else
|
||||
|
||||
/*
|
||||
* define user SDM_UART_CONFIG for software serial
|
||||
*/
|
||||
//#define SDM_UART_CONFIG SWSERIAL_8N1
|
||||
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user WAITING_TURNAROUND_DELAY time in ms to wait for process current request
|
||||
*/
|
||||
//#define WAITING_TURNAROUND_DELAY 200
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* define user RESPONSE_TIMEOUT time in ms to wait for return response from all devices before next request
|
||||
*/
|
||||
//#define RESPONSE_TIMEOUT 500
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@ -20,13 +20,17 @@ platform = espressif32@>=6.0.1
|
||||
build_flags =
|
||||
-DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/js/app.js.gz
|
||||
-Wall -Wextra -Werror
|
||||
-std=c++17
|
||||
-std=gnu++17
|
||||
build_unflags =
|
||||
-std=gnu++11
|
||||
|
||||
lib_deps =
|
||||
https://github.com/yubox-node-org/ESPAsyncWebServer
|
||||
bblanchon/ArduinoJson @ ^6.21.0
|
||||
https://github.com/bertmelis/espMqttClient.git#v1.4.1
|
||||
nrf24/RF24 @ ^1.4.5
|
||||
olikraus/U8g2 @ ^2.34.13
|
||||
olikraus/U8g2 @ ^2.34.16
|
||||
buelowp/sunset @ ^1.1.7
|
||||
|
||||
extra_scripts =
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
;upload_port = COM4
|
||||
|
||||
|
||||
|
||||
; you can define your personal board and/or settings here
|
||||
; non functional example:
|
||||
|
||||
|
||||
@ -115,14 +115,21 @@ bool ConfigurationClass::write()
|
||||
vedirect["updates_only"] = config.Vedirect_UpdatesOnly;
|
||||
vedirect["poll_interval"] = config.Vedirect_PollInterval;
|
||||
|
||||
JsonObject powermeter = doc.createNestedObject("powermeter");
|
||||
powermeter["enabled"] = config.PowerMeter_Enabled;
|
||||
powermeter["interval"] = config.PowerMeter_Interval;
|
||||
powermeter["source"] = config.PowerMeter_Source;
|
||||
powermeter["mqtt_topic_powermeter_1"] = config.PowerMeter_MqttTopicPowerMeter1;
|
||||
powermeter["mqtt_topic_powermeter_2"] = config.PowerMeter_MqttTopicPowerMeter2;
|
||||
powermeter["mqtt_topic_powermeter_3"] = config.PowerMeter_MqttTopicPowerMeter3;
|
||||
powermeter["sdmbaudrate"] = config.PowerMeter_SdmBaudrate;
|
||||
powermeter["sdmaddress"] = config.PowerMeter_SdmAddress;
|
||||
|
||||
JsonObject powerlimiter = doc.createNestedObject("powerlimiter");
|
||||
powerlimiter["enabled"] = config.PowerLimiter_Enabled;
|
||||
powerlimiter["solar_passtrough_enabled"] = config.PowerLimiter_SolarPassTroughEnabled;
|
||||
powerlimiter["battery_drain_strategy"] = config.PowerLimiter_BatteryDrainStategy;
|
||||
powerlimiter["interval"] = config.PowerLimiter_Interval;
|
||||
powerlimiter["mqtt_topic_powermeter_1"] = config.PowerLimiter_MqttTopicPowerMeter1;
|
||||
powerlimiter["mqtt_topic_powermeter_2"] = config.PowerLimiter_MqttTopicPowerMeter2;
|
||||
powerlimiter["mqtt_topic_powermeter_3"] = config.PowerLimiter_MqttTopicPowerMeter3;
|
||||
powerlimiter["is_inverter_behind_powermeter"] = config.PowerLimiter_IsInverterBehindPowerMeter;
|
||||
powerlimiter["inverter_id"] = config.PowerLimiter_InverterId;
|
||||
powerlimiter["inverter_channel_id"] = config.PowerLimiter_InverterChannelId;
|
||||
@ -281,14 +288,22 @@ bool ConfigurationClass::read()
|
||||
config.Vedirect_UpdatesOnly = vedirect["updates_only"] | VEDIRECT_UPDATESONLY;
|
||||
config.Vedirect_PollInterval = vedirect["poll_interval"] | VEDIRECT_POLL_INTERVAL;
|
||||
|
||||
JsonObject powermeter = doc["powermeter"];
|
||||
config.PowerMeter_Enabled = powermeter["enabled"] | POWERMETER_ENABLED;
|
||||
config.PowerMeter_Interval = powermeter["interval"] | POWERMETER_INTERVAL;
|
||||
config.PowerMeter_Source = powermeter["source"] | POWERMETER_SOURCE;
|
||||
strlcpy(config.PowerMeter_MqttTopicPowerMeter1, powermeter["mqtt_topic_powermeter_1"] | "", sizeof(config.PowerMeter_MqttTopicPowerMeter1));
|
||||
strlcpy(config.PowerMeter_MqttTopicPowerMeter2, powermeter["mqtt_topic_powermeter_2"] | "", sizeof(config.PowerMeter_MqttTopicPowerMeter2));
|
||||
strlcpy(config.PowerMeter_MqttTopicPowerMeter3, powermeter["mqtt_topic_powermeter_3"] | "", sizeof(config.PowerMeter_MqttTopicPowerMeter3));
|
||||
config.PowerMeter_SdmBaudrate = powermeter["sdmbaudrate"] | POWERMETER_SDMBAUDRATE;
|
||||
config.PowerMeter_SdmAddress = powermeter["sdmaddress"] | POWERMETER_SDMADDRESS;
|
||||
|
||||
|
||||
JsonObject powerlimiter = doc["powerlimiter"];
|
||||
config.PowerLimiter_Enabled = powerlimiter["enabled"] | POWERLIMITER_ENABLED;
|
||||
config.PowerLimiter_SolarPassTroughEnabled = powerlimiter["solar_passtrough_enabled"] | POWERLIMITER_SOLAR_PASSTROUGH_ENABLED;
|
||||
config.PowerLimiter_BatteryDrainStategy = powerlimiter["battery_drain_strategy"] | POWERLIMITER_BATTERY_DRAIN_STRATEGY;
|
||||
config.PowerLimiter_Interval = POWERLIMITER_INTERVAL;
|
||||
strlcpy(config.PowerLimiter_MqttTopicPowerMeter1, powerlimiter["mqtt_topic_powermeter_1"] | "", sizeof(config.PowerLimiter_MqttTopicPowerMeter1));
|
||||
strlcpy(config.PowerLimiter_MqttTopicPowerMeter2, powerlimiter["mqtt_topic_powermeter_2"] | "", sizeof(config.PowerLimiter_MqttTopicPowerMeter2));
|
||||
strlcpy(config.PowerLimiter_MqttTopicPowerMeter3, powerlimiter["mqtt_topic_powermeter_3"] | "", sizeof(config.PowerLimiter_MqttTopicPowerMeter3));
|
||||
config.PowerLimiter_IsInverterBehindPowerMeter = powerlimiter["is_inverter_behind_powermeter"] | POWERLIMITER_IS_INVERTER_BEHIND_POWER_METER;
|
||||
config.PowerLimiter_InverterId = powerlimiter["inverter_id"] | POWERLIMITER_INVERTER_ID;
|
||||
config.PowerLimiter_InverterChannelId = powerlimiter["inverter_channel_id"] | POWERLIMITER_INVERTER_CHANNEL_ID;
|
||||
|
||||
@ -126,9 +126,12 @@ void MqttHandleInverterClass::publishField(std::shared_ptr<InverterAbstract> inv
|
||||
return;
|
||||
}
|
||||
|
||||
MqttSettings.publish(topic, String(
|
||||
String value = String(
|
||||
inv->Statistics()->getChannelFieldValue(type, channel, fieldId),
|
||||
static_cast<unsigned int>(inv->Statistics()->getChannelFieldDigits(type, channel, fieldId))));
|
||||
static_cast<unsigned int>(inv->Statistics()->getChannelFieldDigits(type, channel, fieldId)));
|
||||
value.trim();
|
||||
|
||||
MqttSettings.publish(topic, value);
|
||||
}
|
||||
|
||||
String MqttHandleInverterClass::getTopic(std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
#include "Battery.h"
|
||||
#include "PowerMeter.h"
|
||||
#include "PowerLimiter.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttSettings.h"
|
||||
@ -16,60 +17,17 @@ PowerLimiterClass PowerLimiter;
|
||||
|
||||
void PowerLimiterClass::init()
|
||||
{
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
using std::placeholders::_3;
|
||||
using std::placeholders::_4;
|
||||
using std::placeholders::_5;
|
||||
using std::placeholders::_6;
|
||||
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
// Zero export power limiter
|
||||
if (strlen(config.PowerLimiter_MqttTopicPowerMeter1) != 0) {
|
||||
MqttSettings.subscribe(config.PowerLimiter_MqttTopicPowerMeter1, 0, std::bind(&PowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
}
|
||||
|
||||
if (strlen(config.PowerLimiter_MqttTopicPowerMeter2) != 0) {
|
||||
MqttSettings.subscribe(config.PowerLimiter_MqttTopicPowerMeter2, 0, std::bind(&PowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
}
|
||||
|
||||
if (strlen(config.PowerLimiter_MqttTopicPowerMeter3) != 0) {
|
||||
MqttSettings.subscribe(config.PowerLimiter_MqttTopicPowerMeter3, 0, std::bind(&PowerLimiterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
}
|
||||
|
||||
_lastCommandSent = 0;
|
||||
_lastLoop = 0;
|
||||
_lastPowerMeterUpdate = 0;
|
||||
_lastRequestedPowerLimit = 0;
|
||||
}
|
||||
|
||||
void PowerLimiterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (strcmp(topic, config.PowerLimiter_MqttTopicPowerMeter1) == 0) {
|
||||
_powerMeter1Power = std::stof(std::string(reinterpret_cast<const char*>(payload), (unsigned int)len));
|
||||
}
|
||||
|
||||
if (strcmp(topic, config.PowerLimiter_MqttTopicPowerMeter2) == 0) {
|
||||
_powerMeter2Power = std::stof(std::string(reinterpret_cast<const char*>(payload), (unsigned int)len));
|
||||
}
|
||||
|
||||
if (strcmp(topic, config.PowerLimiter_MqttTopicPowerMeter3) == 0) {
|
||||
_powerMeter3Power = std::stof(std::string(reinterpret_cast<const char*>(payload), (unsigned int)len));
|
||||
}
|
||||
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
|
||||
void PowerLimiterClass::loop()
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (!config.PowerLimiter_Enabled
|
||||
|| !MqttSettings.getConnected()
|
||||
|| !config.PowerMeter_Enabled
|
||||
|| !Hoymiles.getRadio()->isIdle()
|
||||
|| (millis() - _lastCommandSent) < (config.PowerLimiter_Interval * 1000)
|
||||
|| (millis() - _lastLoop) < (config.PowerLimiter_Interval * 1000)) {
|
||||
@ -93,7 +51,7 @@ void PowerLimiterClass::loop()
|
||||
return;
|
||||
}
|
||||
|
||||
if (millis() - _lastPowerMeterUpdate < (30 * 1000)) {
|
||||
if (millis() - PowerMeter.getLastPowerMeterUpdate() < (30 * 1000)) {
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] dcVoltage: %.2f Voltage Start Threshold: %.2f Voltage Stop Threshold: %.2f inverter->isProducing(): %d\r\n",
|
||||
dcVoltage, config.PowerLimiter_VoltageStartThreshold, config.PowerLimiter_VoltageStopThreshold, inverter->isProducing());
|
||||
}
|
||||
@ -209,7 +167,7 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inve
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
int32_t newPowerLimit = round(_powerMeter1Power + _powerMeter2Power + _powerMeter3Power);
|
||||
int32_t newPowerLimit = round(PowerMeter.getPowerTotal());
|
||||
|
||||
float efficency = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_EFF);
|
||||
int32_t victronChargePower = this->getDirectSolarPower();
|
||||
@ -218,7 +176,7 @@ int32_t PowerLimiterClass::calcPowerLimit(std::shared_ptr<InverterAbstract> inve
|
||||
MessageOutput.printf("[PowerLimiterClass::loop] victronChargePower: %d, efficiency: %.2f, consumeSolarPowerOnly: %s, powerConsumption: %d \r\n",
|
||||
victronChargePower, efficency, consumeSolarPowerOnly ? "true" : "false", newPowerLimit);
|
||||
|
||||
if (millis() - _lastPowerMeterUpdate < (30 * 1000)) {
|
||||
if (millis() - PowerMeter.getLastPowerMeterUpdate() < (30 * 1000)) {
|
||||
if (config.PowerLimiter_IsInverterBehindPowerMeter) {
|
||||
// If the inverter the behind the power meter (part of measurement),
|
||||
// the produced power of this inverter has also to be taken into account.
|
||||
|
||||
120
src/PowerMeter.cpp
Normal file
120
src/PowerMeter.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "PowerMeter.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "SDM.h"
|
||||
#include "MessageOutput.h"
|
||||
#include <ctime>
|
||||
|
||||
PowerMeterClass PowerMeter;
|
||||
|
||||
SDM sdm(Serial2, 9600, NOT_A_PIN, SERIAL_8N1, SDM_RX_PIN, SDM_TX_PIN);
|
||||
|
||||
void PowerMeterClass::init()
|
||||
{
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
using std::placeholders::_3;
|
||||
using std::placeholders::_4;
|
||||
using std::placeholders::_5;
|
||||
using std::placeholders::_6;
|
||||
|
||||
_lastPowerMeterUpdate = 0;
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
MqttSettings.subscribe(config.PowerMeter_MqttTopicPowerMeter1, 0, std::bind(&PowerMeterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
MqttSettings.subscribe(config.PowerMeter_MqttTopicPowerMeter2, 0, std::bind(&PowerMeterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
MqttSettings.subscribe(config.PowerMeter_MqttTopicPowerMeter3, 0, std::bind(&PowerMeterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
|
||||
mqttInitDone = true;
|
||||
|
||||
sdm.begin();
|
||||
}
|
||||
|
||||
void PowerMeterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
if(config.PowerMeter_Enabled && config.PowerMeter_Source == 0){
|
||||
|
||||
if (strcmp(topic, config.PowerMeter_MqttTopicPowerMeter1) == 0) {
|
||||
_powerMeter1Power = std::stof(std::string(reinterpret_cast<const char*>(payload), (unsigned int)len));
|
||||
}
|
||||
|
||||
if (strcmp(topic, config.PowerMeter_MqttTopicPowerMeter2) == 0) {
|
||||
_powerMeter2Power = std::stof(std::string(reinterpret_cast<const char*>(payload), (unsigned int)len));
|
||||
}
|
||||
|
||||
if (strcmp(topic, config.PowerMeter_MqttTopicPowerMeter3) == 0) {
|
||||
_powerMeter3Power = std::stof(std::string(reinterpret_cast<const char*>(payload), (unsigned int)len));
|
||||
}
|
||||
|
||||
MessageOutput.printf("PowerMeterClass: TotalPower: %5.2f\n", getPowerTotal());
|
||||
}
|
||||
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
|
||||
float PowerMeterClass::getPowerTotal(){
|
||||
return _powerMeter1Power + _powerMeter2Power + _powerMeter3Power;
|
||||
}
|
||||
|
||||
uint32_t PowerMeterClass::getLastPowerMeterUpdate(){
|
||||
return _lastPowerMeterUpdate;
|
||||
}
|
||||
|
||||
void PowerMeterClass::mqtt(){
|
||||
if (!MqttSettings.getConnected()){
|
||||
return;
|
||||
}else{
|
||||
String topic = "powermeter";
|
||||
MqttSettings.publish(topic + "/power1", String(_powerMeter1Power));
|
||||
MqttSettings.publish(topic + "/power2", String(_powerMeter2Power));
|
||||
MqttSettings.publish(topic + "/power3", String(_powerMeter3Power));
|
||||
MqttSettings.publish(topic + "/powertotal", String(getPowerTotal()));
|
||||
MqttSettings.publish(topic + "/voltage1", String(_powerMeter1Voltage));
|
||||
MqttSettings.publish(topic + "/voltage2", String(_powerMeter2Voltage));
|
||||
MqttSettings.publish(topic + "/voltage3", String(_powerMeter3Voltage));
|
||||
MqttSettings.publish(topic + "/import", String(_PowerMeterImport));
|
||||
MqttSettings.publish(topic + "/export", String(_PowerMeterExport));
|
||||
}
|
||||
}
|
||||
|
||||
void PowerMeterClass::loop()
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if(config.PowerMeter_Enabled && millis() - _lastPowerMeterUpdate >= (config.PowerMeter_Interval * 1000)){
|
||||
uint8_t _address = config.PowerMeter_SdmAddress;
|
||||
if(config.PowerMeter_Source == 1){
|
||||
_powerMeter1Power = static_cast<float>(sdm.readVal(SDM_PHASE_1_POWER, _address));
|
||||
_powerMeter2Power = 0.0;
|
||||
_powerMeter3Power = 0.0;
|
||||
_powerMeter1Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_1_VOLTAGE, _address));
|
||||
_powerMeter2Voltage = 0.0;
|
||||
_powerMeter3Voltage = 0.0;
|
||||
_PowerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
|
||||
_PowerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
|
||||
}
|
||||
if(config.PowerMeter_Source == 2){
|
||||
_powerMeter1Power = static_cast<float>(sdm.readVal(SDM_PHASE_1_POWER, _address));
|
||||
_powerMeter2Power = static_cast<float>(sdm.readVal(SDM_PHASE_2_POWER, _address));
|
||||
_powerMeter3Power = static_cast<float>(sdm.readVal(SDM_PHASE_3_POWER, _address));
|
||||
_powerMeter1Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_1_VOLTAGE, _address));
|
||||
_powerMeter2Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_2_VOLTAGE, _address));
|
||||
_powerMeter3Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_3_VOLTAGE, _address));
|
||||
_PowerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
|
||||
_PowerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
|
||||
}
|
||||
|
||||
MessageOutput.printf("PowerMeterClass: TotalPower: %5.2f\n", getPowerTotal());
|
||||
|
||||
mqtt();
|
||||
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
}
|
||||
@ -31,6 +31,7 @@ void WebApiClass::init()
|
||||
_webApiNetwork.init(&_server);
|
||||
_webApiNtp.init(&_server);
|
||||
_webApiPower.init(&_server);
|
||||
_webApiPowerMeter.init(&_server);
|
||||
_webApiPowerLimiter.init(&_server);
|
||||
_webApiPrometheus.init(&_server);
|
||||
_webApiSecurity.init(&_server);
|
||||
@ -60,6 +61,7 @@ void WebApiClass::loop()
|
||||
_webApiNetwork.loop();
|
||||
_webApiNtp.loop();
|
||||
_webApiPower.loop();
|
||||
_webApiPowerMeter.loop();
|
||||
_webApiPowerLimiter.loop();
|
||||
_webApiSecurity.loop();
|
||||
_webApiSysstatus.loop();
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
#include "WebApi.h"
|
||||
#include "WebApi_errors.h"
|
||||
#include "helper.h"
|
||||
#include "PowerLimiter.h"
|
||||
#include "PowerMeter.h"
|
||||
#include <AsyncJson.h>
|
||||
|
||||
void WebApiMqttClass::init(AsyncWebServer* server)
|
||||
@ -318,6 +320,8 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
||||
MqttSettings.performReconnect();
|
||||
MqttHandleHass.forceUpdate();
|
||||
MqttHandleVedirectHass.forceUpdate();
|
||||
PowerMeter.init();
|
||||
PowerLimiter.init();
|
||||
}
|
||||
|
||||
String WebApiMqttClass::getRootCaCertInfo(const char* cert)
|
||||
|
||||
@ -8,7 +8,9 @@
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttHandleHass.h"
|
||||
#include "MqttHandleVedirectHass.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "PowerMeter.h"
|
||||
#include "PowerLimiter.h"
|
||||
#include "WebApi.h"
|
||||
#include "helper.h"
|
||||
@ -38,9 +40,6 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request)
|
||||
root[F("enabled")] = config.PowerLimiter_Enabled;
|
||||
root[F("solar_passtrough_enabled")] = config.PowerLimiter_SolarPassTroughEnabled;
|
||||
root[F("battery_drain_strategy")] = config.PowerLimiter_BatteryDrainStategy;
|
||||
root[F("mqtt_topic_powermeter_1")] = config.PowerLimiter_MqttTopicPowerMeter1;
|
||||
root[F("mqtt_topic_powermeter_2")] = config.PowerLimiter_MqttTopicPowerMeter2;
|
||||
root[F("mqtt_topic_powermeter_3")] = config.PowerLimiter_MqttTopicPowerMeter3;
|
||||
root[F("is_inverter_behind_powermeter")] = config.PowerLimiter_IsInverterBehindPowerMeter;
|
||||
root[F("inverter_id")] = config.PowerLimiter_InverterId;
|
||||
root[F("inverter_channel_id")] = config.PowerLimiter_InverterChannelId;
|
||||
@ -122,9 +121,6 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
config.PowerLimiter_Enabled = root[F("enabled")].as<bool>();
|
||||
config.PowerLimiter_SolarPassTroughEnabled = root[F("solar_passtrough_enabled")].as<bool>();
|
||||
config.PowerLimiter_BatteryDrainStategy= root[F("battery_drain_strategy")].as<uint8_t>();
|
||||
strlcpy(config.PowerLimiter_MqttTopicPowerMeter1, root[F("mqtt_topic_powermeter_1")].as<String>().c_str(), sizeof(config.PowerLimiter_MqttTopicPowerMeter1));
|
||||
strlcpy(config.PowerLimiter_MqttTopicPowerMeter2, root[F("mqtt_topic_powermeter_2")].as<String>().c_str(), sizeof(config.PowerLimiter_MqttTopicPowerMeter2));
|
||||
strlcpy(config.PowerLimiter_MqttTopicPowerMeter3, root[F("mqtt_topic_powermeter_3")].as<String>().c_str(), sizeof(config.PowerLimiter_MqttTopicPowerMeter3));
|
||||
config.PowerLimiter_IsInverterBehindPowerMeter = root[F("is_inverter_behind_powermeter")].as<bool>();
|
||||
config.PowerLimiter_InverterId = root[F("inverter_id")].as<uint8_t>();
|
||||
config.PowerLimiter_InverterChannelId = root[F("inverter_channel_id")].as<uint8_t>();
|
||||
@ -146,7 +142,4 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
MqttSettings.performReconnect(); // TODO(helge) is this really needed
|
||||
PowerLimiter.init();
|
||||
}
|
||||
|
||||
125
src/WebApi_powermeter.cpp
Normal file
125
src/WebApi_powermeter.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_powermeter.h"
|
||||
#include "VeDirectFrameHandler.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "MqttHandleVedirectHass.h"
|
||||
#include "MqttHandleHass.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "PowerLimiter.h"
|
||||
#include "PowerMeter.h"
|
||||
#include "WebApi.h"
|
||||
#include "helper.h"
|
||||
|
||||
void WebApiPowerMeterClass::init(AsyncWebServer* server)
|
||||
{
|
||||
using std::placeholders::_1;
|
||||
|
||||
_server = server;
|
||||
|
||||
_server->on("/api/powermeter/status", HTTP_GET, std::bind(&WebApiPowerMeterClass::onStatus, this, _1));
|
||||
_server->on("/api/powermeter/config", HTTP_GET, std::bind(&WebApiPowerMeterClass::onAdminGet, this, _1));
|
||||
_server->on("/api/powermeter/config", HTTP_POST, std::bind(&WebApiPowerMeterClass::onAdminPost, this, _1));
|
||||
}
|
||||
|
||||
void WebApiPowerMeterClass::loop()
|
||||
{
|
||||
}
|
||||
|
||||
void WebApiPowerMeterClass::onStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
root[F("enabled")] = config.PowerMeter_Enabled;
|
||||
root[F("source")] = config.PowerMeter_Source;
|
||||
root[F("interval")] = config.PowerMeter_Interval;
|
||||
root[F("mqtt_topic_powermeter_1")] = config.PowerMeter_MqttTopicPowerMeter1;
|
||||
root[F("mqtt_topic_powermeter_2")] = config.PowerMeter_MqttTopicPowerMeter2;
|
||||
root[F("mqtt_topic_powermeter_3")] = config.PowerMeter_MqttTopicPowerMeter3;
|
||||
root[F("sdmbaudrate")] = config.PowerMeter_SdmBaudrate;
|
||||
root[F("sdmaddress")] = config.PowerMeter_SdmAddress;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void WebApiPowerMeterClass::onAdminGet(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentials(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->onStatus(request);
|
||||
}
|
||||
|
||||
void WebApiPowerMeterClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentials(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > 1024) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicJsonDocument root(1024);
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.containsKey("enabled") && root.containsKey("source"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
config.PowerMeter_Enabled = root[F("enabled")].as<bool>();
|
||||
config.PowerMeter_Source = root[F("source")].as<uint8_t>();
|
||||
config.PowerMeter_Interval = root[F("interval")].as<uint32_t>();
|
||||
strlcpy(config.PowerMeter_MqttTopicPowerMeter1, root[F("mqtt_topic_powermeter_1")].as<String>().c_str(), sizeof(config.PowerMeter_MqttTopicPowerMeter1));
|
||||
strlcpy(config.PowerMeter_MqttTopicPowerMeter2, root[F("mqtt_topic_powermeter_2")].as<String>().c_str(), sizeof(config.PowerMeter_MqttTopicPowerMeter2));
|
||||
strlcpy(config.PowerMeter_MqttTopicPowerMeter3, root[F("mqtt_topic_powermeter_3")].as<String>().c_str(), sizeof(config.PowerMeter_MqttTopicPowerMeter3));
|
||||
config.PowerMeter_SdmBaudrate = root[F("sdmbaudrate")].as<uint32_t>();
|
||||
config.PowerMeter_SdmAddress = root[F("sdmaddress")].as<uint8_t>();
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
yield();
|
||||
delay(1000);
|
||||
yield();
|
||||
ESP.restart();
|
||||
}
|
||||
@ -61,14 +61,16 @@ void WebApiWsLiveClass::loop()
|
||||
if (millis() - _lastWsPublish > (10 * 1000) || (maxTimeStamp != _newestInverterTimestamp)) {
|
||||
|
||||
try {
|
||||
String buffer;
|
||||
// free JsonDocument as soon as possible
|
||||
{
|
||||
DynamicJsonDocument root(40960);
|
||||
JsonVariant var = root;
|
||||
generateJsonResponse(var);
|
||||
|
||||
String buffer;
|
||||
if (buffer) {
|
||||
serializeJson(root, buffer);
|
||||
}
|
||||
|
||||
if (buffer) {
|
||||
if (Configuration.get().Security_AllowReadonly) {
|
||||
_ws.setAuthentication("", "");
|
||||
} else {
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include "SunPosition.h"
|
||||
#include "Utils.h"
|
||||
#include "WebApi.h"
|
||||
#include "PowerMeter.h"
|
||||
#include "PowerLimiter.h"
|
||||
#include "defaults.h"
|
||||
#include <Arduino.h>
|
||||
@ -146,6 +147,8 @@ void setup()
|
||||
} else {
|
||||
MessageOutput.println(F("Invalid pin config"));
|
||||
}
|
||||
// Power meter
|
||||
PowerMeter.init();
|
||||
|
||||
// Dynamic power limiter
|
||||
PowerLimiter.init();
|
||||
@ -165,6 +168,8 @@ void loop()
|
||||
{
|
||||
NetworkSettings.loop();
|
||||
yield();
|
||||
PowerMeter.loop();
|
||||
yield();
|
||||
PowerLimiter.loop();
|
||||
yield();
|
||||
InverterSettings.loop();
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^0.9.2",
|
||||
"@intlify/unplugin-vue-i18n": "^0.9.3",
|
||||
"@rushstack/eslint-patch": "^1.2.0",
|
||||
"@types/bootstrap": "^5.2.6",
|
||||
"@types/node": "^18.15.3",
|
||||
@ -34,8 +34,8 @@
|
||||
"npm-run-all": "^4.1.5",
|
||||
"sass": "^1.59.3",
|
||||
"terser": "^5.16.6",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.2.0",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.2.1",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-css-injected-by-js": "^3.1.0",
|
||||
"vue-tsc": "^1.2.0"
|
||||
|
||||
@ -51,6 +51,9 @@
|
||||
<li>
|
||||
<router-link @click="onClick" class="dropdown-item" to="/settings/vedirect">{{ $t('menu.VedirectSettings') }}</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link @click="onClick" class="dropdown-item" to="/settings/powermeter">{{ $t('menu.PowerMeterSettings') }}</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link @click="onClick" class="dropdown-item" to="/settings/powerlimiter">Dynamic Power Limiter</router-link>
|
||||
</li>
|
||||
|
||||
@ -11,8 +11,8 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(category) in categories">
|
||||
<tr v-for="(prop, prop_idx) in properties(category)">
|
||||
<template v-for="(category) in categories" :key="category">
|
||||
<tr v-for="(prop, prop_idx) in properties(category)" :key="prop">
|
||||
<td v-if="prop_idx == 0" :rowspan="properties(category).length">
|
||||
{{ capitalizeFirstLetter(category) }}</td>
|
||||
<td :class="{ 'table-danger': !isEqual(category, prop) }">{{ prop }}</td>
|
||||
@ -80,14 +80,25 @@ export default defineComponent({
|
||||
return Array.from(new Set(total)).sort();
|
||||
},
|
||||
isEqual(category: string, prop: string): boolean {
|
||||
if (!((this.selectedPinAssignment as Device)[category as keyof Device])) {
|
||||
return false;
|
||||
let comSel = 999999;
|
||||
let comCur = 999999;
|
||||
|
||||
if ((this.selectedPinAssignment as Device)[category as keyof Device]) {
|
||||
comSel = (this.selectedPinAssignment as any)[category][prop];
|
||||
}
|
||||
if (!((this.currentPinAssignment as Device)[category as keyof Device])) {
|
||||
return false;
|
||||
if ((this.currentPinAssignment as Device)[category as keyof Device]) {
|
||||
comCur = (this.currentPinAssignment as any)[category][prop];
|
||||
}
|
||||
|
||||
return (this.selectedPinAssignment as any)[category][prop] == (this.currentPinAssignment as any)[category][prop];
|
||||
if (comSel == -1 || comSel == 255 || comSel == undefined) {
|
||||
comSel = 999999;
|
||||
}
|
||||
|
||||
if (comCur == -1 || comCur == 255 || comSel == undefined) {
|
||||
comCur = 999999;
|
||||
}
|
||||
|
||||
return comSel == comCur;
|
||||
},
|
||||
capitalizeFirstLetter(value: string): string {
|
||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||
|
||||
@ -452,6 +452,24 @@
|
||||
"UpdatesOnly": "Nur Änderungen senden:",
|
||||
"Save": "@:dtuadmin.Save"
|
||||
},
|
||||
"powermeteradmin":{
|
||||
"PowerMeterSettings": "Stromzähler Einstellungen",
|
||||
"PowerMeterConfiguration": "Stromzähler Konfiguration",
|
||||
"PowerMeterEnable": "Aktiviere Stromzähler",
|
||||
"PowerMeterParameter": "Power Meter Parameter",
|
||||
"PowerMeterSource": "Stromzählertyp",
|
||||
"MQTT": "MQTT Konfiguration",
|
||||
"typeMQTT": "MQTT",
|
||||
"typeSDM1ph": "SDM 1 phase (SDM120/220/230)",
|
||||
"typeSDM3ph": "SDM 3 phase (SDM72/630)",
|
||||
"MqttTopicPowerMeter1": "MQTT topic - Stromzähler #1",
|
||||
"MqttTopicPowerMeter2": "MQTT topic - Stromzähler #2 (Optional)",
|
||||
"MqttTopicPowerMeter3": "MQTT topic - Stromzähler #3 (Optional)",
|
||||
"SDM": "SDM-Stromzähler Konfiguration",
|
||||
"sdmbaudrate": "Baudrate",
|
||||
"sdmaddress": "Modbus Adresse",
|
||||
"Save": "@:dtuadmin.Save"
|
||||
},
|
||||
"powerlimiteradmin": {
|
||||
"PowerLimiterSettings": "Power Limiter Einstellungen",
|
||||
"PowerLimiterConfiguration": "Power Limiter Konfiguration",
|
||||
@ -472,10 +490,7 @@
|
||||
"TargetPowerConsumptionHysteresisHint": "Wert um den der Zielstromverbrauch schwanken darf, ohne dass nachgeregelt wird.",
|
||||
"LowerPowerLimit": "Unteres Leistungslimit",
|
||||
"UpperPowerLimit": "Oberes Leistungslimit",
|
||||
"PowerMeters": "Leistungsmesser - MQTT",
|
||||
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
|
||||
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2 (Optional)",
|
||||
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3 (Optional)",
|
||||
"PowerMeters": "Leistungsmesser",
|
||||
"BatterySocStartThreshold": "Akku SOC - Start",
|
||||
"BatterySocStopThreshold": "Akku SOC - Stop",
|
||||
"VoltageStartThreshold": "DC Spannung - Start",
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
"DTUSettings": "DTU Settings",
|
||||
"DeviceManager": "Device-Manager",
|
||||
"VedirectSettings": "Ve.direct Settings",
|
||||
"PowerMeterSettings": "Power Meter Settings",
|
||||
"BatterySettings": "@:batteryadmin.BatterySettings",
|
||||
"ConfigManagement": "Config Management",
|
||||
"FirmwareUpgrade": "Firmware Upgrade",
|
||||
@ -452,6 +453,24 @@
|
||||
"UpdatesOnly": "Send only updates:",
|
||||
"Save": "@:dtuadmin.Save"
|
||||
},
|
||||
"powermeteradmin":{
|
||||
"PowerMeterSettings": "Power Meter Settings",
|
||||
"PowerMeterConfiguration": "Power Meter Configuration",
|
||||
"PowerMeterEnable": "Enable Power Meter",
|
||||
"PowerMeterParameter": "Power Meter Parameter",
|
||||
"PowerMeterSource": "Power Meter type",
|
||||
"MQTT": "MQTT Parameter",
|
||||
"typeMQTT": "MQTT",
|
||||
"typeSDM1ph": "SDM 1 phase (SDM120/220/230)",
|
||||
"typeSDM3ph": "SDM 3 phase (SDM72/630)",
|
||||
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
|
||||
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2",
|
||||
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3",
|
||||
"SDM": "SDM-Power Meter Parameter",
|
||||
"sdmbaudrate": "Baudrate",
|
||||
"sdmaddress": "Modbus Address",
|
||||
"Save": "@:dtuadmin.Save"
|
||||
},
|
||||
"powerlimiteradmin": {
|
||||
"PowerLimiterSettings": "Power Limiter Settings",
|
||||
"PowerLimiterConfiguration": "Power Limiter Configuration",
|
||||
@ -472,7 +491,7 @@
|
||||
"TargetPowerConsumptionHysteresisHint": "Value around which the target power consumption fluctuates without readjustment.",
|
||||
"LowerPowerLimit": "Lower power limit",
|
||||
"UpperPowerLimit": "Upper power limit",
|
||||
"PowerMeters": "Power meters - MQTT",
|
||||
"PowerMeters": "Power meter",
|
||||
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
|
||||
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2 (optional)",
|
||||
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3 (optional)",
|
||||
|
||||
@ -7,6 +7,7 @@ import DtuAdminView from '@/views/DtuAdminView.vue';
|
||||
import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue';
|
||||
import HomeView from '@/views/HomeView.vue';
|
||||
import VedirectAdminView from '@/views/VedirectAdminView.vue'
|
||||
import PowerMeterAdminView from '@/views/PowerMeterAdminView.vue'
|
||||
import PowerLimiterAdminView from '@/views/PowerLimiterAdminView.vue'
|
||||
import VedirectInfoView from '@/views/VedirectInfoView.vue'
|
||||
import InverterAdminView from '@/views/InverterAdminView.vue';
|
||||
@ -86,6 +87,11 @@ const router = createRouter({
|
||||
name: 'Ve.direct Settings',
|
||||
component: VedirectAdminView
|
||||
},
|
||||
{
|
||||
path: '/settings/powermeter',
|
||||
name: 'Power meter Settings',
|
||||
component: PowerMeterAdminView
|
||||
},
|
||||
{
|
||||
path: '/settings/powerlimiter',
|
||||
name: 'Power limiter Settings',
|
||||
|
||||
@ -2,9 +2,6 @@ export interface PowerLimiterConfig {
|
||||
enabled: boolean;
|
||||
solar_passtrough_enabled: boolean;
|
||||
battery_drain_strategy: number;
|
||||
mqtt_topic_powermeter_1: string;
|
||||
mqtt_topic_powermeter_2: string;
|
||||
mqtt_topic_powermeter_3: string;
|
||||
is_inverter_behind_powermeter: boolean;
|
||||
inverter_id: number;
|
||||
inverter_channel_id: number;
|
||||
|
||||
10
webapp/src/types/PowerMeterConfig.ts
Normal file
10
webapp/src/types/PowerMeterConfig.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export interface PowerMeterConfig {
|
||||
enabled: boolean;
|
||||
source: number;
|
||||
interval: number;
|
||||
mqtt_topic_powermeter_1: string;
|
||||
mqtt_topic_powermeter_2: string;
|
||||
mqtt_topic_powermeter_3: string;
|
||||
sdmbaudrate: number;
|
||||
sdmaddress: number;
|
||||
}
|
||||
@ -114,36 +114,6 @@
|
||||
<CardElement :text="$t('powerlimiteradmin.PowerMeters')" textVariant="text-bg-primary" add-space
|
||||
v-show="powerLimiterConfigList.enabled"
|
||||
>
|
||||
<div class="row mb-3">
|
||||
<label for="inputMqttTopicPowerMeter1" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.MqttTopicPowerMeter1') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="inputMqttTopicPowerMeter1"
|
||||
placeholder="shellies/shellyem3/emeter/0/power" v-model="powerLimiterConfigList.mqtt_topic_powermeter_1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputMqttTopicPowerMeter2" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.MqttTopicPowerMeter2') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="inputMqttTopicPowerMeter2"
|
||||
placeholder="shellies/shellyem3/emeter/1/power" v-model="powerLimiterConfigList.mqtt_topic_powermeter_2" required/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputMqttTopicPowerMeter3" class="col-sm-2 col-form-label">{{ $t('powerlimiteradmin.MqttTopicPowerMeter3') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="inputMqttTopicPowerMeter3"
|
||||
placeholder="shellies/shellyem3/emeter/2/power" v-model="powerLimiterConfigList.mqtt_topic_powermeter_3" required/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 form-check-label" for="inputRetain">{{ $t('powerlimiteradmin.InverterIsBehindPowerMeter') }}</label>
|
||||
<div class="col-sm-10">
|
||||
|
||||
160
webapp/src/views/PowerMeterAdminView.vue
Normal file
160
webapp/src/views/PowerMeterAdminView.vue
Normal file
@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<BasePage :title="$t('powermeteradmin.PowerMeterSettings')" :isLoading="dataLoading">
|
||||
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
|
||||
{{ alertMessage }}
|
||||
</BootstrapAlert>
|
||||
|
||||
<form @submit="savePowerMeterConfig">
|
||||
<div class="card">
|
||||
<div class="card-header text-bg-primary">{{ $t('powermeteradmin.PowerMeterConfiguration') }}</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 form-check-label" for="inputPowerMeterEnable">{{ $t('powermeteradmin.PowerMeterEnable') }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="inputPowerMeterEnable"
|
||||
v-model="powerMeterConfigList.enabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3" v-show="powerMeterConfigList.enabled">
|
||||
<label for="inputTimezone" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.PowerMeterSource') }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-select" v-model="powerMeterConfigList.source">
|
||||
<option v-for="source in powerMeterSourceList" :key="source.key" :value="source.key">
|
||||
{{ source.value }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" v-if="powerMeterConfigList.source === 0 && powerMeterConfigList.enabled" >
|
||||
<div class="card-header text-bg-primary">{{ $t('powermeteradmin.MQTT') }}</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<label for="inputMqttTopicPowerMeter1" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.MqttTopicPowerMeter1') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="inputMqttTopicPowerMeter1"
|
||||
placeholder="shellies/shellyem3/emeter/0/power" v-model="powerMeterConfigList.mqtt_topic_powermeter_1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputMqttTopicPowerMeter2" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.MqttTopicPowerMeter2') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="inputMqttTopicPowerMeter2"
|
||||
placeholder="shellies/shellyem3/emeter/1/power" v-model="powerMeterConfigList.mqtt_topic_powermeter_2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="inputMqttTopicPowerMeter3" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.MqttTopicPowerMeter3') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="inputMqttTopicPowerMeter3"
|
||||
placeholder="shellies/shellyem3/emeter/2/power" v-model="powerMeterConfigList.mqtt_topic_powermeter_3" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card" v-if="(powerMeterConfigList.source === 1 || powerMeterConfigList.source === 2) && powerMeterConfigList.enabled" >
|
||||
<div class="card-header text-bg-primary">{{ $t('powermeteradmin.SDM') }}</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<label for="sdmbaudrate" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.sdmbaudrate') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="sdmbaudrate"
|
||||
placeholder="9600" v-model="powerMeterConfigList.sdmbaudrate" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="sdmaddress" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.sdmaddress') }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="sdmaddress"
|
||||
placeholder="1" v-model="powerMeterConfigList.sdmaddress" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mb-3">{{ $t('powermeteradmin.Save') }}</button>
|
||||
</form>
|
||||
</BasePage>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import BasePage from '@/components/BasePage.vue';
|
||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
||||
import { handleResponse, authHeader } from '@/utils/authentication';
|
||||
import type { PowerMeterConfig } from "@/types/PowerMeterConfig";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
BasePage,
|
||||
BootstrapAlert,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataLoading: true,
|
||||
powerMeterConfigList: {} as PowerMeterConfig,
|
||||
powerMeterSourceList: [
|
||||
{ key: 0, value: this.$t('powermeteradmin.typeMQTT') },
|
||||
{ key: 1, value: this.$t('powermeteradmin.typeSDM1ph') },
|
||||
{ key: 2, value: this.$t('powermeteradmin.typeSDM3ph') },
|
||||
],
|
||||
alertMessage: "",
|
||||
alertType: "info",
|
||||
showAlert: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getPowerMeterConfig();
|
||||
},
|
||||
methods: {
|
||||
getPowerMeterConfig() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/powermeter/config", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.powerMeterConfigList = data;
|
||||
this.dataLoading = false;
|
||||
});
|
||||
},
|
||||
savePowerMeterConfig(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("data", JSON.stringify(this.powerMeterConfigList));
|
||||
|
||||
fetch("/api/powermeter/config", {
|
||||
method: "POST",
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
this.alertMessage = response.message;
|
||||
this.alertType = response.type;
|
||||
this.showAlert = true;
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -2,6 +2,7 @@
|
||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"compilerOptions": {
|
||||
"ignoreDeprecations": "5.0",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
|
||||
@ -168,16 +168,17 @@
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
||||
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
||||
|
||||
"@intlify/bundle-utils@^5.1.2":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-5.2.0.tgz#9dc11138d232d7cfb1163feb850f653ce4dfedaf"
|
||||
integrity sha512-rIfoNUTBoZK6IfaEeuoYMQZSuAXhPyZoy+UsdZj+V4eM632ynN1bGt5ttkpGO8xe0c+esfYslgJxBz//bdu4qg==
|
||||
"@intlify/bundle-utils@^5.3.1":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-5.3.1.tgz#e0c609920448f5d3289ec967c8a51b0c6f80fa9d"
|
||||
integrity sha512-Lfrl3zlVmUy9Gqf9K9uuCCdhd5WxjkSQWIdEYQn1Uso4bjy4iHq9GZQYyvoA6+KFiDoVOS0LE3T+c7jwhLkqgg==
|
||||
dependencies:
|
||||
"@intlify/message-compiler" next
|
||||
"@intlify/shared" next
|
||||
acorn "^8.8.2"
|
||||
estree-walker "^2.0.2"
|
||||
jsonc-eslint-parser "^1.0.1"
|
||||
magic-string "^0.30.0"
|
||||
source-map "0.6.1"
|
||||
yaml-eslint-parser "^0.3.2"
|
||||
|
||||
@ -224,12 +225,12 @@
|
||||
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.3.0-beta.16.tgz#74f254dbb7eac633b86d690a341349db29573896"
|
||||
integrity sha512-kXbm4svALe3lX+EjdJxfnabOphqS4yQ1Ge/iIlR8tvUiYRCoNz3hig1M4336iY++Dfx5ytEQJPNjIcknNIuvig==
|
||||
|
||||
"@intlify/unplugin-vue-i18n@^0.9.2":
|
||||
version "0.9.2"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-0.9.2.tgz#7d9166a1a84343da6632c80815150487ac7f533f"
|
||||
integrity sha512-cNfa90+NVNdYJ0qqwRaEb2kGGp9zAve2xaAKCL7EzcQcvSWw42mhiOxcNkUc1QKlXnSHERMd6aT4/GUlFT1zBw==
|
||||
"@intlify/unplugin-vue-i18n@^0.9.3":
|
||||
version "0.9.3"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-0.9.3.tgz#2f8dab79492a8c7218f55526954d0b5d8940009b"
|
||||
integrity sha512-23DMh2r0qA7UZfaQhF09ZHhifgTyKcbmVsCo+qHvu9q1EU8OF18VlhxMHMksDR5NBDvRXj3Lmu8lT84XDrUlSw==
|
||||
dependencies:
|
||||
"@intlify/bundle-utils" "^5.1.2"
|
||||
"@intlify/bundle-utils" "^5.3.1"
|
||||
"@intlify/shared" next
|
||||
"@rollup/pluginutils" "^5.0.2"
|
||||
"@vue/compiler-sfc" "^3.2.47"
|
||||
@ -277,7 +278,7 @@
|
||||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10":
|
||||
"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13":
|
||||
version "1.4.14"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||
@ -1670,6 +1671,13 @@ magic-string@^0.25.7:
|
||||
dependencies:
|
||||
sourcemap-codec "^1.4.8"
|
||||
|
||||
magic-string@^0.30.0:
|
||||
version "0.30.0"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.0.tgz#fd58a4748c5c4547338a424e90fa5dd17f4de529"
|
||||
integrity sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.4.13"
|
||||
|
||||
memorystream@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
|
||||
@ -2251,10 +2259,10 @@ type-fest@^0.20.2:
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
||||
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||
|
||||
typescript@^4.9.5:
|
||||
version "4.9.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
|
||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
||||
typescript@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.2.tgz#891e1a90c5189d8506af64b9ef929fca99ba1ee5"
|
||||
integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==
|
||||
|
||||
unbox-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
@ -2315,10 +2323,10 @@ vite-plugin-css-injected-by-js@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.1.0.tgz#d1160c975d40f256692e2465832e6ff18c22b3a3"
|
||||
integrity sha512-qogCmpocZfcbSAYZQjS88ieIY0PzLUm7RkLFWFgAxkXdz3N6roZbSTNTxeIOj5IxFbZWACUPuVBBoo6qCuXDcw==
|
||||
|
||||
vite@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-4.2.0.tgz#d4e6eafbc034f3faf0ab376bd5b76ac15775eb99"
|
||||
integrity sha512-AbDTyzzwuKoRtMIRLGNxhLRuv1FpRgdIw+1y6AQG73Q5+vtecmvzKo/yk8X/vrHDpETRTx01ABijqUHIzBXi0g==
|
||||
vite@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-4.2.1.tgz#6c2eb337b0dfd80a9ded5922163b94949d7fc254"
|
||||
integrity sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==
|
||||
dependencies:
|
||||
esbuild "^0.17.5"
|
||||
postcss "^8.4.21"
|
||||
|
||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user