ctucx.git: smartied

[nimlang] smarthome server

commit 113bca34ac40a6ce147a4462bc19516b50159366
parent bcd32ef15c1a9820ab3ca150843d2e8a19eef410
Author: Milan Pässler <me@pbb.lc>
Date: Sun, 14 Jul 2019 13:19:20 +0200

compiling but crashing
11 files changed, 163 insertions(+), 91 deletions(-)
A
src/backend_powermeter.nim
|
49
+++++++++++++++++++++++++++++++++++++++++++++++++
A
src/backend_relayboard.nim
|
12
++++++++++++
A
src/frontend_http.nim
|
19
+++++++++++++++++++
M
src/frontend_tcp.nim
|
21
+++++++++------------
M
src/frontend_ws.nim
|
2
+-
M
src/modbus.nim
|
91
++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
D
src/powermeter.nim
|
28
----------------------------
M
src/smartied.nim
|
8
+++-----
M
src/types.nim
|
5
++---
M
src/util.nim
|
10
++++------
A
src/vars.nim
|
9
+++++++++
diff --git a/src/backend_powermeter.nim b/src/backend_powermeter.nim
@@ -0,0 +1,49 @@
+import asyncdispatch
+import modbus
+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
+    )
+
+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)
+  server.state[key].cosphi = await mb.asyncReadFloat(device.address, 30)
+  server.state[key].power = await mb.asyncReadFloat(device.address, 12)
+
+  broadcast($(%*server.state))
+
+proc updatePowermeters() {.async.} =
+  echo "updating powermeters"
+  for key, device in server.config.devices.pairs():
+    if device.type != PowerMeter:
+      continue
+
+    try:
+      await updatePowermeter(key, device)
+    except:
+      echo("error while updating powermeter ", key)
+
+proc timerFunc(fd: AsyncFD): bool {.gcsafe.} =
+  let fut = updatePowermeters()
+  fut.addCallback(proc () {.gcsafe.} =
+      addTimer(int(server.config.powermeterUpdateIntervalSec * 1000), true, timerFunc)
+    )
+  return false
+
+addTimer(1000, true, timerFunc)
diff --git a/src/backend_relayboard.nim b/src/backend_relayboard.nim
@@ -0,0 +1,12 @@
+import types
+import modbus
+import tables
+
+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])
diff --git a/src/frontend_http.nim b/src/frontend_http.nim
@@ -0,0 +1,19 @@
+import asynchttpserver
+import asyncdispatch
+import util
+import types
+import sequtils
+import json
+import vars
+
+proc processHttpClient(req: Request) {.async.} =
+  if req.reqMethod == HttpGet:
+    await req.respond(Http200, $(%* server.state))
+  elif req.reqMethod == HttpPost:
+    await req.respond(Http200, await tryHandleRequest(req.body))
+  else:
+    await req.respond(Http405, "405 Method Not Allowed")
+
+proc serveHttp*() {.async.} =
+  var httpServer = newAsyncHttpServer()
+  await httpServer.serve(Port(server.config.httpPort), processHttpClient)
diff --git a/src/frontend_tcp.nim b/src/frontend_tcp.nim
@@ -4,18 +4,15 @@ import util
 import types
 import sequtils
 import json
-
-var clients: seq[AsyncSocket] = @[]
+import vars
 
 proc broadcast(msg: string) {.locks: true.}=
-  for client in clients:
+  for client in tcpClients:
     try:
       asyncCheck client.send(msg)
     except:
       client.close()
-      clients.keepIf(proc(x: AsyncSocket): bool = x != client)
-
-registerBroadcastHandler(broadcast)
+      tcpClients.keepIf(proc(x: AsyncSocket): bool = x != client)
 
 proc processTcpClient(client: AsyncSocket) {.async.} =
   try:

@@ -30,17 +27,18 @@ proc processTcpClient(client: AsyncSocket) {.async.} =
       await client.send(resp & '\n')
   except:
     client.close()
-    clients.keepIf(proc(x: AsyncSocket): bool = x != client)
+    tcpClients.keepIf(proc(x: AsyncSocket): bool = x != client)
 
 proc serveTcp*() {.async.} =
+  registerBroadcastHandler(broadcast)
+  tcpClients = @[]
+
   var socket = newAsyncSocket()
   socket.setSockOpt(OptReuseAddr, true)
   socket.bindAddr(Port(server.config.tcpPort))
   socket.listen()
 
-  echo("listening on port ", server.config.tcpPort)
-
   while true:
     let client = await socket.accept()
-    clients.add(client)
-    asyncCheck processTcpClient(client)-
\ No newline at end of file
+    tcpClients.add(client)
+    asyncCheck processTcpClient(client)
diff --git a/src/frontend_ws.nim b/src/frontend_ws.nim
@@ -39,4 +39,4 @@ proc processWsClient(req: Request) {.gcsafe, async.} =
 
 proc serveWs*() {.async.} =
   var httpServer = newAsyncHttpServer()
-  asyncCheck httpServer.serve(Port(server.config.wsPort), processWsClient)
+  await httpServer.serve(Port(server.config.wsPort), processWsClient)
diff --git a/src/modbus.nim b/src/modbus.nim
@@ -1,6 +1,7 @@
 import types
 import tables
 import sequtils
+import asyncdispatch
 
 {.passL: "-lmodbus".}
 type modbus = ref object

@@ -11,41 +12,60 @@ proc modbus_free*(mb: modbus): void {.importc.}
 proc modbus_set_slave*(mb: modbus, ad: cint): void {.importc.}
 proc modbus_read_bits*(mb: modbus, ad: cint, nb: cint, dest: pointer): cint {.importc.}
 proc modbus_write_bit*(mb: modbus, ad: cint, status: cint): cint {.importc.}
-proc modbus_read_registers*(mb: modbus, ad: cint, nb: cint, dest: pointer): cint {.importc.}
-proc modbus_get_float_abcd*(mb: modbus, src: pointer): cfloat {.importc.}
-
-var mb*: modbus
-
-proc modbus_read_float*(mb: modbus, ad: cint): cfloat =
-  var first = 0u16
-  var second = 0u16
-  echo mb.modbus_read_registers(cint(ad), 1, first.addr)
-  echo mb.modbus_read_registers(cint(ad+1), 1, second.addr)
-  let res = float32(uint32(second) + (uint32(first) * 65536))
-  echo(ad, ' ', first, ' ', second, ' ', res)
-  return res
-
-proc initModbus*() =
-  let port: cint = int32(server.config.modbusPort)
-  mb = modbus_new_tcp(server.config.modbusAddr, port)
-  discard mb.modbus_connect()
-
-  for key, device in server.config.devices.pairs():
-    if device.type == PowerMeter:
-      server.state[key] = DeviceState(
-        type: PowerMeter,
-        power: 0f,
-        cosphi: 0f,
-        voltage: 0f,
-        `import`: 0f,
-        frequency: 0f
-          )
-    elif device.type == RelayBoard:
-      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])
+proc modbus_read_input_registers*(mb: modbus, ad: cint, nb: cint, dest: pointer): cint {.importc.}
+proc modbus_get_float_dcba*(src: pointer): cfloat {.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]()
+  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))
+    else:
+      echo("modbus request try ", 6 - retries, " failed")
+      retries = retries - 1
+      if retries == 0:
+        echo "failing"
+        fut.fail(newException(OsError, "modbus request failed"))
+      else:
+        addTimer(500, 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 timerFunc(fd: AsyncFD): bool {.gcsafe.} =
+    mb.modbus_set_slave(cint(ad))
+
+    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
+
+  addTimer(1, true, timerFunc)
+  return fut
 
 proc deinitModbus*() =
   mb.modbus_close()
-  mb.modbus_free()-
\ No newline at end of file
+  mb.modbus_free()
diff --git a/src/powermeter.nim b/src/powermeter.nim
@@ -1,27 +0,0 @@
-import asyncdispatch
-import modbus
-import types
-import tables
-import util
-import json
-
-proc updatePowermeters*(fd: AsyncFD): bool {.gcsafe.} =
-  echo "updating powermeters"
-  for key, device in server.config.devices.pairs():
-    if device.type != PowerMeter:
-      continue
-
-    echo device.address
-    mb.modbus_set_slave(cint(device.address))
-    server.state[key].voltage = mb.modbus_read_float(0)
-    server.state[key].frequency = mb.modbus_read_float(70)
-    server.state[key].`import` = mb.modbus_read_float(72)
-    server.state[key].cosphi = mb.modbus_read_float(30)
-    server.state[key].power = mb.modbus_read_float(12)
-
-    broadcast($(%*server.state))
-
-  addTimer(int(server.config.powermeterUpdateIntervalSec * 1000), true, updatePowermeters)
-  return false
-
-addTimer(1000, true, updatePowermeters)-
\ No newline at end of file
diff --git a/src/smartied.nim b/src/smartied.nim
@@ -1,15 +1,14 @@
 import asyncdispatch
 import frontend_tcp
 import frontend_ws
-import powermeter
+import backend_powermeter
+import backend_relayboard
 import types
 import modbus
 import json
 
-initModbus()
-
 asyncCheck serveTcp()
 asyncCheck serveWs()
 runForever()
 
-deinitModbus()-
\ No newline at end of file
+deinitModbus()
diff --git a/src/types.nim b/src/types.nim
@@ -67,6 +67,6 @@ type Response* = object
   status*: ResponseStatus
   data*: JsonNode
 
-var server* = Server(config: parseJson(readFile("../config.json")).to(Config))
+var server* = Server(config: parseJson(readFile("./config.json")).to(Config))
 
-echo server.config-
\ No newline at end of file
+echo server.config
diff --git a/src/util.nim b/src/util.nim
@@ -3,6 +3,7 @@ import asyncdispatch
 import modbus
 import types
 import tables
+import backend_relayboard
 
 var broadcastHandlers: seq[proc (msg: string)] = @[]
 

@@ -20,8 +21,7 @@ proc handleRequest*(req: string): Future[JsonNode] {.async.} =
     let config = server.config.devices[action.setRelayBoard]
 
     server.state[action.setRelayBoard].relays[action.setRelay] = action.setValue
-    mb.modbus_set_slave(cint(config.address))
-    discard mb.modbus_write_bit(cint(config.firstRegister + action.setRelay), cint(server.state[action.setRelayBoard].relays[action.setRelay]))
+    await mb.asyncWriteBit(config.address, config.firstRegister + action.setRelay, server.state[action.setRelayBoard].relays[action.setRelay])
 
     broadcast($(%*server.state))
     return JsonNode()

@@ -29,8 +29,7 @@ proc handleRequest*(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]
-    mb.modbus_set_slave(cint(config.address))
-    discard mb.modbus_write_bit(cint(config.firstRegister + action.toggleRelay), cint(server.state[action.toggleRelayBoard].relays[action.toggleRelay]))
+    await mb.asyncWriteBit(config.address, config.firstRegister + action.toggleRelay, server.state[action.toggleRelayBoard].relays[action.toggleRelay])
 
     broadcast($(%*server.state))
     return JsonNode()

@@ -44,4 +43,4 @@ proc tryHandleRequest*(req: string): Future[string] {.async.} =
     resp = Response(status: Ok, data: await handleRequest(req))
   except:
     resp = Response(status: Err, data: JsonNode())
-  return $(%*resp)-
\ No newline at end of file
+  return $(%*resp)
diff --git a/src/vars.nim b/src/vars.nim
@@ -0,0 +1,9 @@
+import types
+import ws
+import asyncnet
+
+var wsClients* {.threadvar.}: seq[WebSocket]
+var tcpClients* {.threadvar.}: seq[AsyncSocket]
+var server* {.threadvar.}: Server
+var mb* {.threadvar.}: modbus
+var broadcastHandlers* {.threadvar.}: seq[proc (msg: string) {.gcsafe.}]