ctucx.git: smartied

[nimlang] smarthome server

commit 5c3899701fb9fdd80e0b166304f483c59f3b48f5
parent 113bca34ac40a6ce147a4462bc19516b50159366
Author: Milan Pässler <me@pbb.lc>
Date: Sun, 14 Jul 2019 14:05:42 +0200

forgot to commit everything properly
7 files changed, 106 insertions(+), 72 deletions(-)
M
src/backend_powermeter.nim
|
36
+++++++++++++++++++-----------------
M
src/backend_relayboard.nim
|
23
++++++++++++++++-------
M
src/frontend_ws.nim
|
17
++++++++---------
M
src/modbus.nim
|
75
+++++++++++++++++++++++++++++++++++++++++----------------------------------
M
src/smartied.nim
|
14
++++++++++++++
M
src/types.nim
|
3
+--
M
src/util.nim
|
10
+++++++---
diff --git a/src/backend_powermeter.nim b/src/backend_powermeter.nim
@@ -4,22 +4,9 @@ import types
 import tables
 import util
 import json
-
-for key, device in server.config.devices.pairs():
-  if device.type != PowerMeter:
-    continue
-
-  server.state[key] = DeviceState(
-      type: PowerMeter,
-      power: 0f,
-      cosphi: 0f,
-      voltage: 0f,
-      `import`: 0f,
-      frequency: 0f
-    )
+import vars
 
 proc updatePowermeter(key: string, device: DeviceConfig) {.async.} =
-  echo device.address
   server.state[key].voltage = await mb.asyncReadFloat(device.address, 0)
   server.state[key].frequency = await mb.asyncReadFloat(device.address, 70)
   server.state[key].`import` = await mb.asyncReadFloat(device.address, 72)

@@ -29,6 +16,7 @@ proc updatePowermeter(key: string, device: DeviceConfig) {.async.} =
   broadcast($(%*server.state))
 
 proc updatePowermeters() {.async.} =
+  #GC_fullCollect()
   echo "updating powermeters"
   for key, device in server.config.devices.pairs():
     if device.type != PowerMeter:

@@ -37,13 +25,27 @@ proc updatePowermeters() {.async.} =
     try:
       await updatePowermeter(key, device)
     except:
-      echo("error while updating powermeter ", key)
+      let e = getCurrentException()
+      echo("error while updating powermeter ", key, ": ", e.msg)
 
 proc timerFunc(fd: AsyncFD): bool {.gcsafe.} =
   let fut = updatePowermeters()
-  fut.addCallback(proc () {.gcsafe.} =
+  fut.addCallback(proc () =
       addTimer(int(server.config.powermeterUpdateIntervalSec * 1000), true, timerFunc)
     )
   return false
 
-addTimer(1000, true, timerFunc)
+proc initBackendPowermeter*() =
+  addTimer(1000, true, timerFunc)
+  for key, device in server.config.devices.pairs():
+    if device.type != PowerMeter:
+      continue
+
+    server.state[key] = DeviceState(
+        type: PowerMeter,
+        power: 0f,
+        cosphi: 0f,
+        voltage: 0f,
+        `import`: 0f,
+        frequency: 0f
+      )
diff --git a/src/backend_relayboard.nim b/src/backend_relayboard.nim
@@ -1,12 +1,21 @@
+import asyncdispatch
 import types
 import modbus
 import tables
+import vars
 
-for key, device in server.config.devices.pairs():
-  if device.type != RelayBoard:
-    continue
+proc updateRelayboards() {.async.} =
+  echo "updating relayboards"
+  for key, device in server.config.devices.pairs():
+    if device.type != RelayBoard:
+      continue
 
-  var data: array[255, bool]
-  mb.modbus_set_slave(cint(device.address))
-  discard mb.modbus_read_bits(cint(device.firstRegister), cint(device.count), data.addr)
-  server.state[key] = DeviceState(type: RelayBoard, relays: @data[0..device.count-1])
+    try:
+      let data = await mb.asyncReadBits(device.address, device.firstRegister, device.count)
+      server.state[key] = DeviceState(type: RelayBoard, relays: data)
+    except:
+      let e = getCurrentException()
+      echo("error while updating relayboard ", key, ": ", e.msg)
+
+proc initBackendRelayboard*() =
+  asyncCheck updateRelayboards()
diff --git a/src/frontend_ws.nim b/src/frontend_ws.nim
@@ -5,24 +5,21 @@ import types
 import ws
 import sequtils
 import json
-
-var clients: seq[WebSocket] = @[]
+import vars
 
 proc broadcast(msg: string) {.locks: true.} =
-  for client in clients:
+  for client in wsClients:
     try:
       asyncCheck client.send(msg)
     except:
       client.close()
-      clients.keepIf(proc(x: WebSocket): bool = x != client)
-
-registerBroadcastHandler(broadcast)
+      wsClients.keepIf(proc(x: WebSocket): bool = x != client)
 
-proc processWsClient(req: Request) {.gcsafe, async.} =
+proc processWsClient(req: Request) {.async.} =
   var ws: WebSocket
   try:
     ws = await newWebsocket(req)
-    clients.add(ws)
+    wsClients.add(ws)
     await ws.send($(%*server.state))
   except:
     asyncCheck req.respond(Http404, "404")

@@ -35,8 +32,10 @@ proc processWsClient(req: Request) {.gcsafe, async.} =
       await ws.send(resp)
   except:
     ws.close()
-    clients.keepIf(proc(x: WebSocket): bool = x != ws)
+    wsClients.keepIf(proc(x: WebSocket): bool = x != ws)
 
 proc serveWs*() {.async.} =
+  registerBroadcastHandler(broadcast)
+  wsClients = @[]
   var httpServer = newAsyncHttpServer()
   await httpServer.serve(Port(server.config.wsPort), processWsClient)
diff --git a/src/modbus.nim b/src/modbus.nim
@@ -2,9 +2,10 @@ import types
 import tables
 import sequtils
 import asyncdispatch
+import posix
+import vars
 
 {.passL: "-lmodbus".}
-type modbus = ref object
 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.}

@@ -14,57 +15,63 @@ proc modbus_read_bits*(mb: modbus, ad: cint, nb: cint, dest: pointer): cint {.im
 proc modbus_write_bit*(mb: modbus, ad: cint, status: 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.}
 
-let port: cint = int32(server.config.modbusPort)
-var mb* = modbus_new_tcp(server.config.modbusAddr, port)
-
-discard mb.modbus_connect()
-
-proc asyncReadFloat*(mb: modbus, ad: uint8, reg: uint8): Future[float32] =
-  var fut = newFuture[float32]()
+proc retry*[T](mb: modbus, fun: proc(): (cint, T) {.gcsafe.}): Future[T] =
+  var fut = newFuture[T]()
   var retries = 5
-  var data = [0u32]
 
   proc timerFunc(fd: AsyncFD): bool {.gcsafe.} =
-    mb.modbus_set_slave(cint(ad))
-
-    if mb.modbus_read_input_registers(cint(reg), 2, data.addr) != -1:
-      fut.complete(modbus_get_float_dcba(data.addr))
+    let (status, res) = fun()
+    if status != -1:
+      fut.complete(res)
     else:
-      echo("modbus request try ", 6 - retries, " failed")
+      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"
-        fut.fail(newException(OsError, "modbus request failed"))
+        let errmsg = $(modbus_strerror(errno))
+        fut.fail(newException(OsError, "modbus request failed: " & errmsg))
       else:
-        addTimer(500, true, timerFunc)
+        addTimer(100, true, timerFunc)
 
     return true
 
   addTimer(1, true, timerFunc)
   return fut
 
-proc asyncWriteBit*(mb: modbus, ad: uint8, reg: uint8, val: bool): Future[void] =
-  var fut = newFuture[void]()
-  var retries = 5
+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 timerFunc(fd: AsyncFD): bool {.gcsafe.} =
-    mb.modbus_set_slave(cint(ad))
+proc asyncWriteBit*(mb: modbus, ad: uint8, reg: uint8, val: bool): Future[void] {.async.} =
+  discard await mb.retry(proc (): (cint, bool) =
+      mb.modbus_set_slave(cint(ad))
+      let status = mb.modbus_write_bit(cint(reg), cint(val))
+      return (status, false)
+    )
 
-    if mb.modbus_write_bit(cint(reg), cint(val)) != -1:
-      fut.complete()
-    else:
-      echo("modbus request try ", 6 - retries, " failed")
-      retries = retries - 1
-      if retries == 0:
-        fut.fail(newException(OsError, "modbus request failed"))
-      else:
-        addTimer(500, true, timerFunc)
-
-    return true
+proc asyncReadBits*(mb: modbus, ad: uint8, reg: uint8, nb: uint8): Future[seq[bool]] {.async.} =
+  var data: array[256, bool]
+  discard await mb.retry(proc (): (cint, bool) =
+      mb.modbus_set_slave(cint(ad))
+      let status = mb.modbus_read_bits(cint(reg), cint(nb), data.addr)
+      return (status, false)
+    )
+  return @data[0..nb-1]
 
-  addTimer(1, true, timerFunc)
-  return fut
+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()
diff --git a/src/smartied.nim b/src/smartied.nim
@@ -1,14 +1,28 @@
 import asyncdispatch
 import frontend_tcp
 import frontend_ws
+import frontend_http
 import backend_powermeter
 import backend_relayboard
 import types
 import modbus
 import json
+import vars
+import util
+import tables
+
+server = Server(config: parseJson(readFile("./config.json")).to(Config))
+echo server.state
+
+initUtil()
+initModbus()
+initBackendPowermeter()
+initBackendRelayboard()
 
 asyncCheck serveTcp()
 asyncCheck serveWs()
+asyncCheck serveHttp()
+
 runForever()
 
 deinitModbus()
diff --git a/src/types.nim b/src/types.nim
@@ -67,6 +67,5 @@ type Response* = object
   status*: ResponseStatus
   data*: JsonNode
 
-var server* = Server(config: parseJson(readFile("./config.json")).to(Config))
+type modbus* = ref object
 
-echo server.config
diff --git a/src/util.nim b/src/util.nim
@@ -4,10 +4,12 @@ import modbus
 import types
 import tables
 import backend_relayboard
+import vars
 
-var broadcastHandlers: seq[proc (msg: string)] = @[]
+proc initUtil*() =
+  broadcastHandlers = @[]
 
-proc registerBroadcastHandler*(handler: proc (msg: string) {.locks: true.}) =
+proc registerBroadcastHandler*(handler: proc (msg: string) {.gcsafe, locks: true.}) =
   broadcastHandlers.add(handler)
 
 proc broadcast*(msg: string) =

@@ -38,9 +40,11 @@ proc handleRequest*(req: string): Future[JsonNode] {.async.} =
     return clientConfig
 
 proc tryHandleRequest*(req: string): Future[string] {.async.} =
+  GC_fullCollect()
   var resp: Response
   try:
     resp = Response(status: Ok, data: await handleRequest(req))
   except:
-    resp = Response(status: Err, data: JsonNode())
+    let e = getCurrentException()
+    resp = Response(status: Err, data: newJString(e.msg))
   return $(%*resp)