ctucx.git: smartied

[nimlang] smarthome server

commit f07b88e6dbb7a7ee81895d773e7cbac9ebafb651
parent b6e16b841aaaa7088d089e78f73d74e4d7c9c20f
Author: Milan Pässler <me@pbb.lc>
Date: Wed, 11 Sep 2019 22:00:36 +0200

replace libmodbus
6 files changed, 143 insertions(+), 109 deletions(-)
M
config.json
|
51
+++++++++++++++++++++++++++++++++++++++++----------
M
src/backend_powermeter.nim
|
10
+++++-----
M
src/backend_relayboard.nim
|
3
++-
M
src/modbus.nim
|
163
++++++++++++++++++++++++++++++++++++++++---------------------------------------
M
src/smartied.nim
|
21
+++++++++++----------
M
src/util.nim
|
4
++--
diff --git a/config.json b/config.json
@@ -2,7 +2,7 @@
  	"devices": {
 		"modbus-10": {
 			"type": "RelayBoard",
-			"firstRegister": 101,
+			"firstRegister": 0,
 			"count": 8,
 			"address": 10
 		},

@@ -16,15 +16,20 @@
 			"model": "SDM120",
 			"address": 60
 		},
-		"lacrosse-21": {
+		"lacrosse-raum": {
 			"type": "LacrosseTempSensor",
-			"id": "21",
-			"address": 21
+			"id": "5",
+			"address": 5
 		},
-		"lacrosse-31": {
+		"lacrosse-draussen": {
 			"type": "LacrosseTempSensor",
 			"id": "31",
 			"address": 31
+		},
+		"lacrosse-bad": {
+			"type": "LacrosseTempSensor",
+			"id": "3f",
+			"address": 42
 		}
 	},
 	"clientConfigs": {

@@ -43,7 +48,6 @@
 			]
 		},
 		"smarthome-pwa": {
-			"source": "https://git.pbb.lc/petabyteboy/smarthome-pwa",
 			"views": [
 				{
 					"url": "lights",

@@ -54,16 +58,18 @@
 						{ "name": "Decke", "device": "modbus-10", "relay": 0 },
 						{ "name": "Bett", "device": "modbus-10", "relay": 1 },
 						{ "name": "Küche", "device": "modbus-10", "relay": 2 },
+						{ "name": "Regal", "device": "modbus-10", "relay": 7 },
 						{ "name": "Bad", "device": "modbus-10", "relay": 3 }
 					]
 				},
 				{
 					"url": "switches",
 					"name": "Switches",
-					"icon": "switch",
+					"icon": "power_settings_new",
 					"type": "switches",
 					"switches": [
-						{ "name": "Verstärker", "device": "modbus-10", "relay": 7 }
+                                                { "name": "Bett-Monitor", "device": "modbus-10", "relay": 5 },
+						{ "name": "Verstärker", "device": "modbus-10", "relay": 6 }
 					]
 				},
 				{

@@ -77,6 +83,17 @@
 					]
 				},
 				{
+					"url": "temperature",
+					"name": "Temperature",
+					"icon": "brightness_7",
+					"type": "temperature",
+					"sensors": [
+						{ "name": "Raum", "device": "lacrosse-raum" },
+						{ "name": "Bad", "device": "lacrosse-bad" },
+						{ "name": "Draussen", "device": "lacrosse-draussen" }
+					]
+				},
+				{
 					"url": "departures",
 					"name": "Departures",
 					"icon": "departure_board",

@@ -88,7 +105,21 @@
 					"name": "netdata",
 					"icon": "show_chart",
 					"type": "redirect",
-					"destination": "/netdata/"
+					"destination": "http://192.168.1.1/netdata/"
+				},
+				{
+					"url": "fritzbox",
+					"name": "Fritz!Box",
+					"icon": "router",
+					"type": "redirect",
+					"destination": "http://10.0.0.1/"
+				},
+				{
+					"url": "settings",
+					"name": "Settings",
+					"icon": "settings",
+					"type": "settings",
+					"sourceLink": "https://git.pbb.lc/petabyteboy/smarthome-pwa"
 				}
 			]
 		}

@@ -101,5 +132,5 @@
 	"lacrosseAddr": "192.168.1.1",
 	"lacrossePort": 2342,
 	"powermeterUpdateIntervalSec": 20,
-	"accessToken": "passwort"
+	"accessToken": "penis123"
 }
diff --git a/src/backend_powermeter.nim b/src/backend_powermeter.nim
@@ -8,11 +8,11 @@ import vars
 import times
 
 proc updatePowermeter(key: string, device: DeviceConfig) {.async.} =
-  let voltage = await mb.asyncReadFloat(device.address, 0)
-  let frequency = await mb.asyncReadFloat(device.address, 70)
-  let `import` = await mb.asyncReadFloat(device.address, 72)
-  let cosphi = await mb.asyncReadFloat(device.address, 30)
-  let power = await mb.asyncReadFloat(device.address, 12)
+  let voltage = mbFloatDCBA(await readInputRegisters(device.address, 0, 2))
+  let frequency = mbFloatDCBA(await readInputRegisters(device.address, 70, 2))
+  let `import` = mbFloatDCBA(await readInputRegisters(device.address, 72, 2))
+  let cosphi = mbFloatDCBA(await readInputRegisters(device.address, 30, 2))
+  let power = mbFloatDCBA(await readInputRegisters(device.address, 12, 2))
 
   server.state[key].voltage = voltage
   server.state[key].frequency = frequency
diff --git a/src/backend_relayboard.nim b/src/backend_relayboard.nim
@@ -3,6 +3,7 @@ import types
 import modbus
 import tables
 import vars
+import sequtils
 
 proc updateRelayboards() {.async.} =
   echo "updating relayboards"

@@ -11,7 +12,7 @@ proc updateRelayboards() {.async.} =
       continue
 
     try:
-      let data = await mb.asyncReadBits(device.address, device.firstRegister, device.count)
+      let data = map(await readRegisters(device.address, device.firstRegister, device.count), proc (i: uint16): bool = i != 0)
       server.state[key] = DeviceState(type: RelayBoard, relays: data)
     except:
       let e = getCurrentException()
diff --git a/src/modbus.nim b/src/modbus.nim
@@ -1,84 +1,85 @@
-import types
-import tables
-import sequtils
+import asyncnet
 import asyncdispatch
-import posix
+import strutils
 import vars
 
-{.passL: "-lmodbus".}
-proc modbus_new_tcp*(ad: cstring, port: cint): modbus {.importc, dynlib: "libmodbus.so.5"}
-proc modbus_connect*(mb: modbus): cint {.importc.}
-proc modbus_close*(mb: modbus): void {.importc.}
-proc modbus_free*(mb: modbus): void {.importc.}
-proc modbus_set_slave*(mb: modbus, ad: cint): void {.importc.}
-proc modbus_read_registers*(mb: modbus, ad: cint, nb: cint, dest: pointer): cint {.importc.}
-proc modbus_write_register*(mb: modbus, ad: cint, val: cint): cint {.importc.}
-proc modbus_read_input_registers*(mb: modbus, ad: cint, nb: cint, dest: pointer): cint {.importc.}
-proc modbus_get_float_dcba*(src: pointer): cfloat {.importc.}
-proc modbus_strerror*(src: cint): cstring {.importc.}
-
-proc retry*[T](mb: modbus, fun: proc(): (cint, T) {.gcsafe.}): Future[T] =
-  var fut = newFuture[T]()
-  var retries = 5
-
-  proc timerFunc(fd: AsyncFD): bool {.gcsafe.} =
-    let (status, res) = fun()
-    if status != -1:
-      fut.complete(res)
-    else:
-      if errno == EBADF or errno == ECONNRESET or errno == EPIPE or errno == 112345691:
-        mb.modbus_close()
-        discard mb.modbus_connect()
-      echo("modbus request try ", 6 - retries, " failed: ", modbus_strerror(errno))
-      retries = retries - 1
-      if retries == 0:
-        echo "failing"
-        let errmsg = $(modbus_strerror(errno))
-        fut.fail(newException(OsError, "modbus request failed: " & errmsg))
-      else:
-        addTimer(100, true, timerFunc)
-
-    return true
-
-  addTimer(1, true, timerFunc)
-  return fut
-
-proc asyncReadFloat*(mb: modbus, ad: uint8, reg: uint8): Future[float32] {.async.} =
-  return await mb.retry(proc (): (cint, float32) =
-      var data = [0u32]
-      mb.modbus_set_slave(cint(ad))
-      let status = mb.modbus_read_input_registers(cint(reg), 2, data.addr)
-      let res = modbus_get_float_dcba(data.addr)
-      return (status, float32(res))
-    )
-
-proc asyncWriteBits*(mb: modbus, ad: uint8, reg: uint8, val: seq[bool]): Future[void] {.async.} =
-  var data = 0u16
-  for i, v in val.pairs:
-    data = data + uint8(v) shl i
-  echo val
-  echo data
-  discard await mb.retry(proc (): (cint, bool) =
-      mb.modbus_set_slave(cint(ad))
-      let status = mb.modbus_write_register(cint(reg), cint(data))
-      return (status, false)
-    )
-
-proc asyncReadBits*(mb: modbus, ad: uint8, reg: uint8, nb: uint8): Future[seq[bool]] {.async.} =
-  var data: array[256, bool]
-  let num_registers = uint8((nb - 1) * 8) + 1
-  discard await mb.retry(proc (): (cint, bool) =
-      mb.modbus_set_slave(cint(ad))
-      let status = mb.modbus_read_registers(cint(reg), cint(num_registers), data.addr)
-      return (status, false)
-    )
-  return @data[0..nb-1]
-
-proc initModbus*() =
-  let port: cint = int32(server.config.modbusPort)
-  mb = modbus_new_tcp(server.config.modbusAddr, port)
-  discard mb.modbus_connect()
-
-proc deinitModbus*() =
-  mb.modbus_close()
-  mb.modbus_free()
+### modbus general ###
+
+var sock {.threadvar.}: AsyncSocket
+var transaction_id {.threadvar.}: uint16
+
+proc mkPacket_mbTcp(mb_packet: string): string =
+  inc(transaction_id)
+  return parseHexStr(toHex(transaction_id) & toHex(0u16) & toHex(uint16(len(mb_packet)))) & mb_packet
+
+proc readPacket_mbTcp(): Future[string] {.async.} =
+  var res = ""
+
+  res = await sock.recv(6)
+  if res == "":
+    raise newException(OsError, "verbindung putt")
+  let length = fromHex[uint16](toHex(res[4..5]))
+
+  res = await sock.recv(int(length))
+  if res == "":
+    raise newException(OsError, "verbindung putt")
+  return res
+
+proc doRequest[T](req: string, parse_proc: proc(foo: string): T): Future[T] {.async.} =
+  let tcp_packet = mkPacket_mbTcp(req)
+  await sock.send(tcp_packet)
+  let answer = await readPacket_mbTcp()
+  return parse_proc(answer)
+
+### readInputRegisters ###
+
+proc mkPacket_readInputRegisters(unit_id: uint8, address: uint16, count: uint16): string =
+  return parseHexStr(toHex(unit_id) & toHex(4u8) & toHex(address) & toHex(count))
+
+proc parsePacket_readInputRegisters(packet: string): seq[uint16] =
+  var res: seq[uint16] = @[]
+  let count = fromHex[int](toHex(packet[2..2])) / 2
+  for i in int(1)..int(count):
+    res.add(fromHex[uint16](toHex(packet[2*i+1..2*i+2])))
+  return res
+
+proc readInputRegisters*(unit_id: uint8, address: uint16, count: uint16): Future[seq[uint16]] {.async.} =
+  return await doRequest(mkPacket_readInputRegisters(unit_id, address, count), parsePacket_readInputRegisters)
+
+### readRegisters ###
+
+proc mkPacket_readRegisters(unit_id: uint8, address: uint16, count: uint16): string =
+  return parseHexStr(toHex(unit_id) & toHex(3u8) & toHex(address) & toHex(count))
+
+proc parsePacket_readRegisters(packet: string): seq[uint16] =
+  var res: seq[uint16] = @[]
+  let count = fromHex[int](toHex(packet[2..2])) / 2
+  for i in int(1)..int(count):
+    res.add(fromHex[uint16](toHex(packet[2*i+1..2*i+2])))
+  return res
+
+proc readRegisters*(unit_id: uint8, address: uint16, count: uint16): Future[seq[uint16]] {.async.} =
+  return await doRequest(mkPacket_readRegisters(unit_id, address, count), parsePacket_readRegisters)
+
+### writeRegister ###
+
+proc mkPacket_writeRegister(unit_id: uint8, address: uint16, value: uint16): string =
+  return parseHexStr(toHex(unit_id) & toHex(6u8) & toHex(address) & toHex(value))
+
+proc parsePacket_writeRegister(packet: string): bool =
+  return true
+
+proc writeRegister*(unit_id: uint8, address: uint16, value: uint16): Future[bool] {.async.} =
+  return await doRequest(mkPacket_writeRegister(unit_id, address, value), parsePacket_writeRegister)
+
+### conversion ###
+
+proc mbFloatDCBA*(input: seq[uint16]): float32 =
+  let i: uint32 = uint32(input[0]) * 65536u32 + uint32(input[1])
+  return cast[float32](i)
+
+### main ###
+
+proc initModbus*() {.async.} =
+  sock = await asyncnet.dial(server.config.modbusAddr, Port(server.config.modbusPort))
+  transaction_id = 0u16
diff --git a/src/smartied.nim b/src/smartied.nim
@@ -14,16 +14,17 @@ import tables
 
 server = Server(config: parseJson(readFile("./config.json")).to(Config))
 
-initUtil()
-initModbus()
-initBackendPowermeter()
-initBackendRelayboard()
-initBackendLacrosse()
+proc main() {.async.} =
+  initUtil()
+  await initModbus()
+  initBackendPowermeter()
+  initBackendRelayboard()
+  initBackendLacrosse()
 
-asyncCheck serveTcp()
-asyncCheck serveWs()
-asyncCheck serveHttp()
+  asyncCheck serveTcp()
+  asyncCheck serveWs()
+  asyncCheck serveHttp()
 
-runForever()
+  runForever()
 
-deinitModbus()
+waitFor main()
diff --git a/src/util.nim b/src/util.nim
@@ -41,7 +41,7 @@ proc handleRequest*(client: Client, req: string): Future[JsonNode] {.async.} =
     let config = server.config.devices[action.setRelayBoard]
 
     server.state[action.setRelayBoard].relays[action.setRelay] = action.setValue
-    await mb.asyncWriteBits(config.address, config.firstRegister + action.setRelay, server.state[action.setRelayBoard].relays)
+    discard await writeRegister(config.address, config.firstRegister + action.setRelay, uint16(server.state[action.setRelayBoard].relays[action.setRelay]))
 
     broadcast($(%*server.state))
     return JsonNode()

@@ -49,7 +49,7 @@ proc handleRequest*(client: Client, req: string): Future[JsonNode] {.async.} =
     let config = server.config.devices[action.toggleRelayBoard]
 
     server.state[action.toggleRelayBoard].relays[action.toggleRelay] = not server.state[action.toggleRelayBoard].relays[action.toggleRelay]
-    await mb.asyncWriteBits(config.address, config.firstRegister + action.toggleRelay, server.state[action.toggleRelayBoard].relays)
+    discard await writeRegister(config.address, config.firstRegister + action.toggleRelay, uint16(server.state[action.toggleRelayBoard].relays[action.toggleRelay]))
 
     broadcast($(%*server.state))
     return JsonNode()