ctucx.git: smartied

[nimlang] smarthome server

commit 583f9b189f449a53d74c586c0737eb96b8dbd7d5
parent 03c8bf4a6af1a1574dd4976bbdc6f44ddeb22c39
Author: Leah (ctucx) <leah@ctu.cx>
Date: Sun, 14 Feb 2021 19:10:12 +0100

replace native modbus implementation with libmodbus again
6 files changed, 77 insertions(+), 135 deletions(-)
M
src/backend_powermeter.nim
|
10
+++++-----
M
src/backend_relayboard.nim
|
3
+--
C
src/modbus.nim -> src/modbus-native.nim
|
0
M
src/modbus.nim
|
193
++++++++++++++++++++++++++++---------------------------------------------------
M
src/smartied.nim
|
2
+-
M
src/util.nim
|
4
++--
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 = 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))
+  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)
 
   server.state[key].voltage = voltage
   server.state[key].frequency = frequency
diff --git a/src/backend_relayboard.nim b/src/backend_relayboard.nim
@@ -3,7 +3,6 @@ import types
 import modbus
 import tables
 import vars
-import sequtils
 
 proc updateRelayboards() {.async.} =
   echo "updating relayboards"

@@ -12,7 +11,7 @@ proc updateRelayboards() {.async.} =
       continue
 
     try:
-      let data = map(await readRegisters(device.address, device.firstRegister, device.count), proc (i: uint16): bool = i != 0)
+      let data = await mb.asyncReadBits(device.address, device.firstRegister, device.count)
       server.state[key] = DeviceState(type: RelayBoard, relays: data)
     except:
       let e = getCurrentException()
diff --git a/src/modbus.nim b/src/modbus-native.nim
diff --git a/src/modbus.nim b/src/modbus.nim
@@ -1,135 +1,78 @@
-import asyncnet
+import types
+import tables
+import sequtils
 import asyncdispatch
-import strutils
+import posix
 import vars
-import tables
-
-### modbus general ###
-
-var sock {.threadvar.}: AsyncSocket
-var transaction_id {.threadvar.}: uint16
-var transactions {.threadvar.}: Table[uint16, proc(msg: string)]
-
-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 reconnect() {.async.} =
-  echo "verbindung putt, ich fix das mal"
-  await sleepAsync(5000)
-  echo "jetzt"
-  sock = await asyncnet.dial(server.config.modbusAddr, Port(server.config.modbusPort))
-
-proc readPacket_mbTcp(): Future[(uint16, string)] {.async.} =
-  var res = ""
-
-  res = await sock.recv(8)
-  while res == "":
-    await reconnect()
-    res = await sock.recv(8)
 
-  let transaction_id = fromHex[uint16](toHex(res[0..1]))
-  let length = fromHex[uint16](toHex(res[4..5]))
-  let function_code = cast[uint8](res[7])
-
-  res = await sock.recv(int(length) - 2)
-  while res == "":
-    await reconnect()
-    res = await sock.recv(8)
-
-  if function_code >= 128u8:
-    raise newException(OsError, "mordbus error: " & toHex(res))
-
-  return (transaction_id, res)
-
-proc processAnswers() {.async.} =
-  while true:
-    try:
-      let (transaction_id, mb_packet) = await readPacket_mbTcp()
-      transactions[transaction_id](mb_packet)
-    except:
-      let e = getCurrentException()
-      echo("error while processing mordbus answer: ", e.msg)
-
-proc doRequest[T](req: string, parse_proc: proc(foo: string): T): Future[T] =
+{.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_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_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]()
-
-  let tcp_packet = mkPacket_mbTcp(req)
-  asyncCheck sock.send(tcp_packet)
-  transactions[transaction_id] = proc(answer: string) =
-    #transactions.del(transaction_id)
-    fut.complete(parse_proc(answer))
-
-  return fut
-
-proc retry[T](req: string, parse_proc: proc(foo: string): T): Future[T] {.async.} =
   var retries = 5
-  var res: T
 
-  while retries > 0:
-    try:
-      res = await doRequest(req, parse_proc)
-      return res
-    except:
+  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
-      let e = getCurrentException()
-      echo("error while processing mordbus answer: ", e.msg)
-
-### 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 bytes = cast[uint8](packet[0])
-  var i = 1
-  while i < int(bytes):
-    res.add(fromHex[uint16](toHex(packet[i..i+1])))
-    i += 2
-  return res
-
-proc readInputRegisters*(unit_id: uint8, address: uint16, count: uint16): Future[seq[uint16]] {.async.} =
-  return await retry(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 bytes = cast[uint8](packet[0])
-  var i = 1
-  while i < int(bytes):
-    res.add(fromHex[uint16](toHex(packet[i..i+1])))
-    i += 2
-  return res
+      if retries == 0:
+        echo "failing"
+        let errmsg = $(modbus_strerror(errno))
+        fut.fail(newException(OsError, "modbus request failed: " & errmsg))
+      else:
+        addTimer(100, true, timerFunc)
 
-proc readRegisters*(unit_id: uint8, address: uint16, count: uint16): Future[seq[uint16]] {.async.} =
-  return await retry(mkPacket_readRegisters(unit_id, address, count), parsePacket_readRegisters)
+    return true
 
-### 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 retry(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 ###
+  addTimer(1, true, timerFunc)
+  return fut
 
-proc initModbus*() {.async.} =
-  sock = await asyncnet.dial(server.config.modbusAddr, Port(server.config.modbusPort))
-  transaction_id = 0u16
-  transactions = initTable[uint16, proc(msg: string)]()
-  asyncCheck processAnswers()
+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 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)
+    )
+
+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]
+
+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()
diff --git a/src/smartied.nim b/src/smartied.nim
@@ -18,7 +18,7 @@ if getEnv("ACCESS_TOKEN") != "":
 
 proc main() {.async.} =
   initUtil()
-  await initModbus()
+  initModbus()
   initBackendPowermeter()
   initBackendRelayboard()
   initBackendLacrosse()
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
-    discard await writeRegister(config.address, config.firstRegister + action.setRelay, uint16(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()

@@ -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]
-    discard await writeRegister(config.address, config.firstRegister + action.toggleRelay, uint16(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()