237 lines
6.6 KiB
Go
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
|
|
|
|
|
|
|