ctucx.git: sdm2mqtt

Publish data from SDM120M meters via MQTT

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
88 import std/[asyncdispatch, asyncnet]
import std/[os, posix]
import std/[tables, json, options]
import std/math
import nmqtt

import types, modbus

var mqttContext* {.threadvar.} : MqttCtx

proc round* [T: float32|float64](value: T, places: int = 0): float = 
  if places == 0:
    result = round(value)
  else:
    result = value * pow(10.0, T(places))
    result = floor(result)
    result = result / pow(10.0, T(places))

proc CtrlCHook* () {.noconv.} =
  echo "Ctrl+C fired! \nStopping Server now!"
  waitFor mqttContext.disconnect()
  quit()

proc updatePowermeter (deviceAddress: uint8, deviceName: string) {.async.} = 
  let json = %* {
    "id":         deviceAddress,
    "deviceName": deviceName,
    "voltage":    mbFloatDCBA(await readInputRegisters(deviceAddress, 0, 2)).round(3),
    "power":      mbFloatDCBA(await readInputRegisters(deviceAddress, 12, 2)).round(3),
    "frequency":  mbFloatDCBA(await readInputRegisters(deviceAddress, 70, 2)).round(3),
    "cosphi":     mbFloatDCBA(await readInputRegisters(deviceAddress, 30, 2)).round(3),
    "import":     mbFloatDCBA(await readInputRegisters(deviceAddress, 72, 2)).round(3)
  }

  if mqttContext.isConnected:
    await mqttContext.publish("sdm2mqtt/"&deviceName, $json, 2, true)

    for key, value in json:
      if key != "deviceName":
        await sleepAsync(250)
        await mqttContext.publish("sdm2mqtt/" & deviceName & "/" & key, $value, 2, true)

proc updatePowermeters (config: Config) {.async.} =
  await sleepAsync(500)
  while true:
    for name, address in config.devices.pairs():
      try:
        await updatePowermeter(address, name)
      except:
        echo "Error[updatePowermeters]:\n", getCurrentExceptionMsg()
    await sleepAsync(int(config.updateInterval * 1000))


proc main () {.async.} =
  setControlCHook(CtrlCHook)

  onSignal(SIGTERM):
    echo "Got SIGTERM! \nStopping Server now!"
    waitFor mqttContext.disconnect()
    quit()

  var configFile = "./config.json"

  if getEnv("CONFIG_PATH") != "":
    configFile = getEnv("CONFIG_PATH")

  if not fileExists(configFile):
    echo "Config file not found"
    quit()

  let config = parseFile(configFile).to(Config)

  await initModbus(config.modbus.host, config.modbus.port)

  mqttContext = newMqttCtx("sdm2mqtt")
  mqttContext.set_host(config.mqtt.host, config.mqtt.port)
  mqttContext.set_verbosity(1)

  if (config.mqtt.username.isSome and config.mqtt.password.isSome):
    mqttContext.set_auth(config.mqtt.username.get, config.mqtt.password.get)

  await mqttContext.start()

  asyncCheck updatePowermeters(config)

  runForever()

waitFor main()