hems/smr2mqtt/smr2mqtt.go
2024-05-18 14:14:49 +02:00

237 lines
6.6 KiB
Go

package main
import (
"bufio"
_ "expvar"
"flag"
"fmt"
"log"
"math/rand"
"net/http"
"os"
"os/signal"
"regexp"
"syscall"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/tarm/serial"
)
const (
wait_TellerDemandDal uint32 = iota
wait_TellerDemandPiek
wait_TellerSupplyDal
wait_TellerSupplyPiek
wait_Tariff
wait_DemandWatt
wait_SupplyWatt
wait_Volt
)
// selection of data that we're interested in
var re_Teller1Demand = regexp.MustCompile(`^1-0:1\.8\.1\(([\d\.]+)\*kWh\)`)
var re_Teller2Demand = regexp.MustCompile(`^1-0:1\.8\.2\(([\d\.]+)\*kWh\)`)
var re_Teller1Supply = regexp.MustCompile(`^1-0:2\.8\.1\(([\d\.]+)\*kWh\)`)
var re_Teller2Supply = regexp.MustCompile(`^1-0:2\.8\.2\(([\d\.]+)\*kWh\)`)
var re_Tariff = regexp.MustCompile(`^0-0:96\.14\.0\(([\d]+)\)`)
var re_DemandWatt = regexp.MustCompile(`^1-0:1\.7\.0\(([\d\.]+)\*kW\)`)
var re_SupplyWatt = regexp.MustCompile(`^1-0:2\.7\.0\(([\d\.]+)\*kW\)`)
var re_Volt = regexp.MustCompile(`^1-0:32\.7\.0\(([\d\.]+)\*V\)`)
var state = wait_TellerDemandDal
var heartbeat uint64 = 1
var currentDemandWatt string = ""
var currentSupplyWatt string = ""
var currentVolt string = ""
var currentTeller1 string = ""
var currentTeller2 string = ""
var currentSupply1 string = ""
var currentSupply2 string = ""
var currentTariff string = ""
// MQTT_SERVER = "tcp://192.168.178.8:1883"
var broker = flag.String("mqtt", os.Getenv("MQTT_SERVER"), "the broker protocol://ip:port; protocol can be 'ws' or 'tcp'.")
var prefix = flag.String("pref", "/pm", "the 'home' prefix to the smr5 p1 message updates.")
var sendRaw = flag.Bool("raw", false, "send all P1 lines to /smr5/raw.")
var sendheartbeat = flag.Bool("hb", false, "send heartbeat.")
func main() {
flag.Parse()
rand.Seed(time.Now().UTC().UnixNano())
opts := mqtt.NewClientOptions()
opts.AddBroker(*broker)
opts.SetClientID(string(20000 + rand.Intn(10000)))
opts.SetCleanSession(true)
opts = opts.SetOnConnectHandler(func(client mqtt.Client) {
log.Printf("MQTT connected to: " + *broker + ".\n")
})
opts = opts.SetConnectionLostHandler(func(client mqtt.Client, err error) {
log.Printf("MQTT connection lost: %v.\n", err)
})
// "expvar" memory stats server at: http://<ip>:5160/debug/vars
go func() {
http.ListenAndServe(":5160", nil)
}()
// wait a while; seems mosquitto needs time to get ready after startup
time.Sleep(time.Second)
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
go readSerial(client)
go tellerUpdate(client)
sig := make(chan os.Signal)
signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
s := <-sig
fmt.Printf("\x1b[2KSignal: %v\n", s)
client.Disconnect(250)
}
// SMR5_SERIAL = /dev/ttyUSB0
func readSerial(client mqtt.Client) {
sportstring := os.Getenv("SMR5_SERIAL")
log.Printf("Serial connection to: %s\n",sportstring)
c := &serial.Config{Name: sportstring, Baud: 115200, Size: 8, Parity: serial.ParityNone, StopBits: serial.Stop1}
sport, err := serial.OpenPort(c)
if err != nil {
log.Fatal(err)
}
defer sport.Close()
reader := bufio.NewReader(sport)
for {
s, _ := reader.ReadString('\n')
handleStatement(client, s)
}
}
func tellerUpdate(client mqtt.Client) {
for {
time.Sleep(60 * time.Second)
client.Publish(*prefix+"/smr5/tariff", 0, false, currentTariff)
client.Publish(*prefix+"/smr5/kwh_demand_dal", 0, false, currentTeller1)
client.Publish(*prefix+"/smr5/kwh_demand_piek", 0, false, currentTeller2)
client.Publish(*prefix+"/smr5/kwh_supply_dal", 0, false, currentSupply1)
client.Publish(*prefix+"/smr5/kwh_supply_piek", 0, false, currentSupply2)
}
}
func handleStatement(client mqtt.Client, message string) {
if *sendRaw {
client.Publish(*prefix+"/smr5/raw", 0, false, message)
}
switch state {
case wait_TellerDemandDal:
found := re_Teller1Demand.FindStringSubmatch(message)
if found != nil {
currentTeller1 = found[1]
state = wait_TellerDemandPiek
}
case wait_TellerDemandPiek:
found := re_Teller2Demand.FindStringSubmatch(message)
if found != nil {
currentTeller2 = found[1]
state = wait_TellerSupplyDal
}
case wait_TellerSupplyDal:
found := re_Teller1Supply.FindStringSubmatch(message)
if found != nil {
currentSupply1 = found[1]
state = wait_TellerSupplyPiek
}
case wait_TellerSupplyPiek:
found := re_Teller2Supply.FindStringSubmatch(message)
if found != nil {
currentSupply2 = found[1]
state = wait_Tariff
}
case wait_Tariff:
found := re_Tariff.FindStringSubmatch(message)
if found != nil {
currentTariff = found[1]
state = wait_DemandWatt
}
case wait_DemandWatt:
found := re_DemandWatt.FindStringSubmatch(message)
if found != nil {
if currentDemandWatt != found[1] {
client.Publish(*prefix+"/smr5/watt_demand", 0, false, found[1])
client.Publish(*prefix+"/smr5/watt_supply", 0, false, "00.000")
currentDemandWatt = found[1]
}
state = wait_SupplyWatt
}
case wait_SupplyWatt:
found := re_SupplyWatt.FindStringSubmatch(message)
if found != nil {
if currentSupplyWatt != found[1] {
client.Publish(*prefix+"/smr5/watt_demand", 0, false, "00.000")
client.Publish(*prefix+"/smr5/watt_supply", 0, false, found[1])
currentSupplyWatt = found[1]
}
state = wait_Volt
}
case wait_Volt:
found := re_Volt.FindStringSubmatch(message)
if found != nil {
if *sendheartbeat {
heartbeat = heartbeat + 1
client.Publish(*prefix+"/smr5/heartbeat", 0, false, fmt.Sprintf("%d", heartbeat))
}
if currentVolt != found[1] {
client.Publish(*prefix+"/smr5/volt", 0, false, found[1])
currentVolt = found[1]
}
state = wait_TellerDemandDal
}
}
}
// SMR5 'telegram':
// # /XMX5LGBBLB2410018242
// # 1-3:0.2.8(50)
// # 0-0:1.0.0(171229103442W)
// # 0-0:96.1.1(4530303335303033373439313036343136)
// # 1-0:1.8.1(001268.417*kWh) # afgenomen stroom daltarief
// # 1-0:1.8.2(001258.762*kWh) # afgenomen stroom piektarief
// # 1-0:2.8.1(000000.000*kWh) # gesaldeerde stroom daltarief
// # 1-0:2.8.2(000000.000*kWh) # gesaldeerde stroom piektarief
// # 0-0:96.14.0(0002) # actueel tarief: piek = 2, dal = 1
// # 1-0:1.7.0(00.268*kW) # actueel verbruik
// # 1-0:2.7.0(00.000*kW) # actuele saldering
// # 0-0:96.7.21(00003)
// # 0-0:96.7.9(00000)
// # 1-0:99.97.0(0)(0-0:96.7.19)
// # 1-0:32.32.0(00001)
// # 1-0:32.36.0(00000)
// # 0-0:96.13.0()
// # 1-0:32.7.0(231.0*V) # actueel voltage
// # 1-0:31.7.0(001*A)
// # 1-0:21.7.0(00.268*kW)
// # 1-0:22.7.0(00.000*kW)
// # !8A43