commit 5c3899701fb9fdd80e0b166304f483c59f3b48f5
parent 113bca34ac40a6ce147a4462bc19516b50159366
Author: Milan Pässler <me@pbb.lc>
Date: Sun, 14 Jul 2019 14:05:42 +0200
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(-)
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)