Skip to content

Commit

Permalink
Feature: Support SMA HomeManager 2.0 as PowerMeter
Browse files Browse the repository at this point in the history
  • Loading branch information
Snoopy-HSS authored and schlimmchen committed Mar 17, 2024
1 parent 9003267 commit d420b27
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 7 deletions.
3 changes: 2 additions & 1 deletion include/PowerMeter.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ class PowerMeterClass {
SOURCE_SDM1PH = 1,
SOURCE_SDM3PH = 2,
SOURCE_HTTP = 3,
SOURCE_SML = 4
SOURCE_SML = 4,
SOURCE_SMAHM2 = 5
};
void init(Scheduler& scheduler);
float getPowerTotal(bool forceUpdate = true);
Expand Down
29 changes: 29 additions & 0 deletions include/SMA_HM.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2024 Holger-Steffen Stapf
*/
#pragma once

#include <cstdint>
#include <TaskSchedulerDeclarations.h>

class SMA_HMClass {
public:
void init(Scheduler& scheduler);
void loop();
void event1();
float getPowerTotal();
float getPowerL1();
float getPowerL2();
float getPowerL3();
uint32_t serial = 0;
private:
uint32_t _lastUpdate = 0;
float _powerMeterPower = 0.0;
float _powerMeterL1 = 0.0;
float _powerMeterL2 = 0.0;
float _powerMeterL3 = 0.0;
uint32_t previousMillis = 0;
Task _loopTask;
};
extern SMA_HMClass SMA_HM;
13 changes: 12 additions & 1 deletion src/PowerMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "MessageOutput.h"
#include <ctime>
#include <SoftwareSerial.h>
#include <SMA_HM.h>

PowerMeterClass PowerMeter;

Expand Down Expand Up @@ -72,6 +73,10 @@ void PowerMeterClass::init(Scheduler& scheduler)
inputSerial.enableTx(false);
inputSerial.flush();
break;

case SOURCE_SMAHM2:
SMA_HM.init(scheduler);
break;
}
}

Expand Down Expand Up @@ -169,7 +174,7 @@ void PowerMeterClass::loop()
void PowerMeterClass::readPowerMeter()
{
CONFIG_T& config = Configuration.get();

uint8_t _address = config.PowerMeter.SdmAddress;

if (config.PowerMeter.Source == SOURCE_SDM1PH) {
Expand Down Expand Up @@ -223,6 +228,12 @@ void PowerMeterClass::readPowerMeter()
_lastPowerMeterUpdate = millis();
}
}
else if (config.PowerMeter.Source == SOURCE_SMAHM2) {
_powerMeter1Power = SMA_HM.getPowerL1();
_powerMeter2Power = SMA_HM.getPowerL2();
_powerMeter3Power = SMA_HM.getPowerL3();
_lastPowerMeterUpdate = millis();
}
}

bool PowerMeterClass::smlReadLoop()
Expand Down
192 changes: 192 additions & 0 deletions src/SMA_HM.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2024 Holger-Steffen Stapf
*/
#include "SMA_HM.h"
#include <Arduino.h>
#include "Configuration.h"
#include "NetworkSettings.h"
#include <WiFiUdp.h>
#include "MessageOutput.h"

unsigned int multicastPort = 9522; // local port to listen on
IPAddress multicastIP(239, 12, 255, 254);
WiFiUDP SMAUdp;

const uint32_t interval = 1000;

static void Soutput(int kanal, int index, int art, int tarif, String Bezeichnung, double value, int timestamp){
MessageOutput.print(Bezeichnung);
MessageOutput.print('=');
MessageOutput.println(value);
}

SMA_HMClass SMA_HM;

void SMA_HMClass::init(Scheduler& scheduler)
{
scheduler.addTask(_loopTask);
_loopTask.setCallback(std::bind(&SMA_HMClass::loop, this));
_loopTask.setIterations(TASK_FOREVER);
_loopTask.enable();
SMAUdp.begin(multicastPort);
SMAUdp.beginMulticast(multicastIP, multicastPort);
}

void SMA_HMClass::loop()
{
uint32_t currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
event1();
}
}

void SMA_HMClass::event1()
{
uint8_t buffer[1024];
int packetSize = SMAUdp.parsePacket();
float Pbezug,BezugL1,BezugL2,BezugL3;
Pbezug = 0;
BezugL1 = 0;
BezugL2 = 0;
BezugL3 = 0;
float Peinspeisung,EinspeisungL1,EinspeisungL2,EinspeisungL3;
Peinspeisung = 0;
EinspeisungL1 = 0;
EinspeisungL2 = 0;
EinspeisungL3 = 0;
int count =0;
if (packetSize) {
int rSize = SMAUdp.read(buffer, 1024);
if (buffer[0] != 'S' || buffer[1] != 'M' || buffer[2] != 'A') {
MessageOutput.println("Not an SMA packet?");
return;
}
uint16_t grouplen;
uint16_t grouptag;
uint8_t* offset = buffer + 4;
do {
grouplen = (offset[0] << 8) + offset[1];
grouptag = (offset[2] << 8) + offset[3];
offset += 4;
if (grouplen == 0xffff) return;
if (grouptag == 0x02A0 && grouplen == 4) {
offset += 4;
} else if (grouptag == 0x0010) {
uint8_t* endOfGroup = offset + grouplen;
uint16_t protocolID = (offset[0] << 8) + offset[1];
offset += 2;
uint16_t susyID = (offset[0] << 8) + offset[1];
offset += 2;
uint32_t serial = (offset[0] << 24) + (offset[1] << 16) + (offset[2] << 8) + offset[3];
SMA_HM.serial=serial;
offset += 4;
uint32_t timestamp = (offset[0] << 24) + (offset[1] << 16) + (offset[2] << 8) + offset[3];
offset += 4;
while (offset < endOfGroup) {
uint8_t kanal = offset[0];
uint8_t index = offset[1];
uint8_t art = offset[2];
uint8_t tarif = offset[3];
offset += 4;
if (art == 8) {
uint64_t data = ((uint64_t)offset[0] << 56) +
((uint64_t)offset[1] << 48) +
((uint64_t)offset[2] << 40) +
((uint64_t)offset[3] << 32) +
((uint64_t)offset[4] << 24) +
((uint64_t)offset[5] << 16) +
((uint64_t)offset[6] << 8) +
offset[7];
offset += 8;
} else if (art == 4) {
uint32_t data = (offset[0] << 24) +
(offset[1] << 16) +
(offset[2] << 8) +
offset[3];
offset += 4;
switch (index) {
case (1):
Pbezug = data * 0.1;
count +=1;
break;
case (2):
Peinspeisung = data * 0.1;
count +=1;
break;
case (21):
BezugL1 = data * 0.1;
count +=1;
break;
case (22):
EinspeisungL1 = data * 0.1;
count +=1;
break;
case (41):
BezugL2 = data * 0.1;
count +=1;
break;
case (42):
EinspeisungL2 = data * 0.1;
count +=1;
break;
case (61):
BezugL3 = data * 0.1;
count +=1;
break;
case (62):
count +=1;
EinspeisungL3 = data * 0.1;
break;
default:
break; // Wird nicht benötigt, wenn Statement(s) vorhanden sind
}
if (count == 8){
_powerMeterPower = Peinspeisung - Pbezug;
_powerMeterL1=EinspeisungL1-BezugL1;
_powerMeterL2=EinspeisungL2-BezugL2;
_powerMeterL3=EinspeisungL3-BezugL3;
Soutput(kanal, index, art, tarif, "Leistung", _powerMeterPower, timestamp);
Soutput(kanal, index, art, tarif, "Leistung L1", _powerMeterL1, timestamp);
Soutput(kanal, index, art, tarif, "Leistung L2", _powerMeterL2, timestamp);
Soutput(kanal, index, art, tarif, "Leistung L3", _powerMeterL3, timestamp);
count=0;
}
} else if (kanal==144) {
// Optional: Versionsnummer auslesen... aber interessiert die?
offset += 4;
} else {
offset += art;
MessageOutput.println("Strange measurement skipped");
}
}
} else if (grouptag == 0) {
// end marker
offset += grouplen;
} else {
MessageOutput.print("unhandled group ");
MessageOutput.print(grouptag);
MessageOutput.print(" with len=");
MessageOutput.println(grouplen);
offset += grouplen;
}
} while (grouplen > 0 && offset + 4 < buffer + rSize);
}
}
float SMA_HMClass::getPowerTotal()
{
return _powerMeterPower;
}
float SMA_HMClass::getPowerL1()
{
return _powerMeterL1;
}
float SMA_HMClass::getPowerL2()
{
return _powerMeterL2;
}
float SMA_HMClass::getPowerL3()
{
return _powerMeterL3;
}
3 changes: 2 additions & 1 deletion webapp/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@
"typeSDM3ph": "SDM 3 phase (SDM72/630)",
"typeHTTP": "HTTP(S) + JSON",
"typeSML": "SML (OBIS 16.7.0)",
"typeSMAHM2": "SMA Homemanager 2.0",
"MqttTopicPowerMeter1": "MQTT topic - Stromzähler #1",
"MqttTopicPowerMeter2": "MQTT topic - Stromzähler #2 (Optional)",
"MqttTopicPowerMeter3": "MQTT topic - Stromzähler #3 (Optional)",
Expand Down Expand Up @@ -817,7 +818,7 @@
"Close": "Schließen",
"SetVoltageLimit": "Spannungslimit:",
"SetCurrentLimit": "Stromlimit:",
"CurrentLimit": "Aktuelles Limit: "
"CurrentLimit": "Aktuelles Limit: "
},
"acchargeradmin": {
"ChargerSettings": "AC Ladegerät Einstellungen",
Expand Down
3 changes: 2 additions & 1 deletion webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@
"typeSDM3ph": "SDM 3 phase (SDM72/630)",
"typeHTTP": "HTTP(s) + JSON",
"typeSML": "SML (OBIS 16.7.0)",
"typeSMAHM2": "SMA Homemanager 2.0",
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2",
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3",
Expand Down Expand Up @@ -824,7 +825,7 @@
"Close": "close",
"SetVoltageLimit": "Voltage limit:",
"SetCurrentLimit": "Current limit:",
"CurrentLimit": "Current limit:"
"CurrentLimit": "Current limit:"
},
"acchargeradmin": {
"ChargerSettings": "AC Charger Settings",
Expand Down
7 changes: 4 additions & 3 deletions webapp/src/views/PowerMeterAdminView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType" ref="alert">
{{ alertMessage }}
</BootstrapAlert>

<form @submit="savePowerMeterConfig">
<CardElement :text="$t('powermeteradmin.PowerMeterConfiguration')"
textVariant="text-bg-primary">
Expand Down Expand Up @@ -83,7 +83,7 @@
</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">
Expand Down Expand Up @@ -126,7 +126,7 @@
placeholder="http://admin:[email protected]/status"
prefix="GET "
:tooltip="$t('powermeteradmin.httpUrlDescription')" />

<div class="row mb-3">
<label for="inputTimezone" class="col-sm-2 col-form-label">{{ $t('powermeteradmin.httpAuthorization') }}</label>
<div class="col-sm-10">
Expand Down Expand Up @@ -236,6 +236,7 @@ export default defineComponent({
{ key: 2, value: this.$t('powermeteradmin.typeSDM3ph') },
{ key: 3, value: this.$t('powermeteradmin.typeHTTP') },
{ key: 4, value: this.$t('powermeteradmin.typeSML') },
{ key: 5, value: this.$t('powermeteradmin.typeSMAHM2') },
],
powerMeterAuthList: [
{ key: 0, value: "None" },
Expand Down

0 comments on commit d420b27

Please sign in to comment.