ctucx.git: smartied

[nimlang] smarthome server

commit 439b9ee1933af48fd2f8283676c1d7bab83549b6
parent 583f9b189f449a53d74c586c0737eb96b8dbd7d5
Author: Leah (ctucx) <leah@ctu.cx>
Date: Thu, 18 Feb 2021 10:07:08 +0100

removed tcp backend, refactor code, implement influx export, implement mqtt support, implement device: zigbee2mqttLamp
23 files changed, 764 insertions(+), 474 deletions(-)
M
config.json
|
79
++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
M
smartied.nimble
|
7
++++---
A
src/actionHandler.nim
|
77
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D
src/backend_lacrosse.nim
|
54
------------------------------------------------------
D
src/backend_powermeter.nim
|
51
---------------------------------------------------
D
src/backend_relayboard.nim
|
25
-------------------------
A
src/devices/lacrosseSensors.nim
|
67
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/devices/modbusPowermeter.nim
|
63
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/devices/modbusRelayboard.nim
|
38
++++++++++++++++++++++++++++++++++++++
A
src/devices/zigbee2mqttLamp.nim
|
86
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/frontend.nim
|
119
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D
src/frontend_http.nim
|
24
------------------------
D
src/frontend_prometheus.nim
|
59
-----------------------------------------------------------
D
src/frontend_tcp.nim
|
30
------------------------------
D
src/frontend_ws.nim
|
30
------------------------------
A
src/influx.nim
|
63
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
src/modbus.nim
|
13
+++++--------
A
src/mqtt.nim
|
13
+++++++++++++
M
src/smartied.nim
|
53
+++++++++++++++++++++++++++++++----------------------
M
src/types.nim
|
136
++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
D
src/util.nim
|
83
-------------------------------------------------------------------------------
A
src/utils.nim
|
60
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
src/vars.nim
|
8
+++++---
diff --git a/config.json b/config.json
@@ -22,42 +22,39 @@
 			"model": "SDM120",
 			"address": 60
 		},
+		"tradfri-lamp1": {
+			"type": "Zigbee2MqttLamp",
+			"lampType": "RGB",
+			"friendlyName": "ikea_lamp_rgb"
+		},
+		"tradfri-lamp2": {
+			"type": "Zigbee2MqttLamp",
+			"lampType": "WhiteSpectrum",
+			"friendlyName": "ikea_lamp_whitespectrum"
+		},
+		"tradfri-lamp3": {
+			"type": "Zigbee2MqttLamp",
+			"lampType": "Switchable",
+			"friendlyName": "ikea_lamp_switchable"
+		},
 		"lacrosse-raum": {
 			"type": "LacrosseTempSensor",
-			"id": "21",
-			"address": 21
+			"id": "21"
 		},
 		"lacrosse-kuehlschrank": {
 			"type": "LacrosseTempSensor",
-			"id": "3a",
-			"address": 31
+			"id": "3a"
 		},
 		"lacrosse-draussen": {
 			"type": "LacrosseTempSensor",
-			"id": "26",
-			"address": 26
+			"id": "26"
 		},
 		"lacrosse-bad": {
 			"type": "LacrosseTempSensor",
-			"id": "3f",
-			"address": 3
+			"id": "3f"
 		}
 	},
 	"clientConfigs": {
-		"fernbedienung": {
-			"views": [
-				{
-					"name": "Lights",
-					"type": "switches",
-					"switches": [
-						{ "name": "Decke", "device": "modbus-10", "relay": 0 },
-						{ "name": "Bett", "device": "modbus-10", "relay": 1 },
-						{ "name": "Küche", "device": "modbus-10", "relay": 2 },
-						{ "name": "Bad", "device": "modbus-10", "relay": 3 }
-					]
-				}
-			]
-		},
 		"smarthome-pwa": {
 			"views": [
 				{

@@ -70,7 +67,10 @@
 						{ "name": "Decke", "device": "modbus-10", "relay": 2 },
 						{ "name": "Küche", "device": "modbus-10", "relay": 1 },
 						{ "name": "Bett", "device": "modbus-10", "relay": 3 },
-						{ "name": "Bad", "device": "modbus-20", "relay": 0 }
+						{ "name": "Bad", "device": "modbus-20", "relay": 0 },
+						{ "name": "Decke: RGB", "device": "tradfri-lamp1", "relay": 0},
+						{ "name": "Decke: Weiß-Spektrum", "device": "tradfri-lamp2", "relay": 0},
+						{ "name": "Decke: Schaltbar", "device": "tradfri-lamp3", "relay": 0}
 					]
 				},
 				{

@@ -129,14 +129,27 @@
 			]
 		}
 	},
-	"httpPort": 5000,
-	"tcpPort": 5001,
-	"wsPort": 5002,
-	"prometheusPort": 5003,
-	"modbusAddr": "10.0.0.1",
-	"modbusPort": 502,
-	"lacrosseAddr": "10.0.0.1",
-	"lacrossePort": 2342,
-	"powermeterUpdateIntervalSec": 20,
-	"accessToken": "penis123"
+	"serverConfig": {
+		"frontendPort": 5000,
+		"modbus": {
+			"host": "10.0.0.1",
+			"port": 502
+		},
+		"mqtt": {
+			"host": "10.0.0.1",
+			"port": 1883
+		},
+		"lacrosse": {
+			"host": "10.0.0.1",
+			"port": 2342
+		},
+		"influx": {
+			"host": "10.0.0.1",
+			"port": 8086,
+			"powermeterDatabase": "powermeter",
+			"lacrosseDatabase": "temperatureSensors"
+		},
+		"powermeterUpdateIntervalSec": 20,
+		"accessToken": "penis123"
+	}
 }
diff --git a/smartied.nimble b/smartied.nimble
@@ -1,7 +1,7 @@
 # Package
 
-version       = "0.1.0"
-author        = "Milan P\xC3\xA4ssler"
+version       = "0.2.0"
+author        = "Leah(ctucx), Milan P\xC3\xA4ssler"
 description   = "A new awesome nimble package"
 license       = "AGPL-3.0"
 srcDir        = "src"

@@ -12,4 +12,5 @@ bin           = @["smartied"]
 # Dependencies
 
 requires "nim >= 0.20.0"
-requires "ws"
+requires "ws == 0.4.3"
+requires "nmqtt == 1.0.4"
diff --git a/src/actionHandler.nim b/src/actionHandler.nim
@@ -0,0 +1,77 @@
+import asyncdispatch, json, tables, options
+import types, vars, utils
+import devices/[modbusRelayboard, zigbee2mqttLamp]
+
+proc handleAction*(action: Action): Future[JsonNode] {.async.} =
+  case action.type
+  of SwitchStateAction:
+    let config = server.config.devices[action.deviceName]
+
+    if action.state.isNone and action.toggle.isNone:
+      return JsonNode()
+
+    if action.state.isSome and action.toggle.isSome:
+      return JsonNode()
+
+    if action.state.isSome:
+      let state = action.state.get
+
+      if config.type == RelayBoard:
+        if action.relay.isNone:
+          return JsonNode()
+        else:
+          await setRelay(action.deviceName, action.relay.get, state)
+          broadcastServerState()
+          return JsonNode()
+
+      if config.type == Zigbee2MqttLamp:
+        await setLampState(action.deviceName, state)
+        return JsonNode()
+
+    if action.toggle.isSome:
+      if config.type == RelayBoard:
+        if action.relay.isNone:
+          return JsonNode()
+        else:
+          await toggleRelay(action.deviceName, action.relay.get)
+          broadcastServerState()
+          return JsonNode()
+
+      if config.type == Zigbee2MqttLamp:
+        await toggleLamp(action.deviceName)
+        return JsonNode()
+
+  of SetBrightnessAction:
+    let config = server.config.devices[action.deviceName]
+
+    if config.type != Zigbee2MqttLamp:
+      return JsonNode()
+
+    await setLampBrightness(action.deviceName, action.brightness)
+    return JsonNode()
+
+  of SetColorXYAction:
+    let config = server.config.devices[action.deviceName]
+
+    if config.type != Zigbee2MqttLamp:
+      return JsonNode()
+
+    await setLampColor(action.deviceName, action.colorX, action.colorY)
+    return JsonNode()
+
+  of SetColorTemperatureAction:
+    let config = server.config.devices[action.deviceName]
+
+    if config.type != Zigbee2MqttLamp:
+      return JsonNode()
+
+    await setLampColorTemperature(action.deviceName, action.colorTemperature)
+    return JsonNode()
+
+  of GetClientConfigAction:
+    let clientConfig: JsonNode = server.config.clientConfigs[action.configName]
+    return clientConfig
+
+  else:
+    return JsonNode()
+
diff --git a/src/backend_lacrosse.nim b/src/backend_lacrosse.nim
@@ -1,54 +0,0 @@
-import asyncdispatch
-import asyncnet
-import types
-import tables
-import util
-import json
-import vars
-import times
-
-proc lacrosseHandleLoop(sock: AsyncSocket) {.async.} =
-  while true:
-    try:
-      let line = await sock.recvLine()
-      if line == "":
-        break
-
-      let msg = parseJson(line).to(LacrosseMessage)
-
-      for key, device in server.config.devices.pairs():
-        if device.type != LacrosseTempSensor:
-          continue
-        if device.id != msg.id:
-          continue
-
-        if msg.hum == 106:
-          server.state[key].humidity = -1
-        else:
-          server.state[key].humidity = msg.hum
-        server.state[key].temperature = msg.temp
-        server.state[key].weakBattery = bool(msg.weakBatt)
-        server.state[key].lastUpdated2 = toUnix(getTime())
-
-        broadcast($(%*server.state))
-    except:
-      let e = getCurrentException()
-      #echo("error while updating lacrosse: ", e.msg)
-
-proc lacrosseConnectLoop() {.async.} =
-  while true:
-    try:
-      let sock = await asyncnet.dial(server.config.lacrosseAddr, Port(server.config.lacrossePort))
-      await lacrosseHandleLoop(sock)
-    except:
-      let e = getCurrentException()
-      echo("error while connectiong to lacrosse relay: ", e.msg)
-    await sleepAsync(1000)
-
-proc initBackendLacrosse*() =
-  for key, device in server.config.devices.pairs():
-    if device.type != LacrosseTempSensor:
-      continue
-    server.state[key] = DeviceState(type: LacrosseTempSensor)
-
-  asyncCheck lacrosseConnectLoop()
diff --git a/src/backend_powermeter.nim b/src/backend_powermeter.nim
@@ -1,51 +0,0 @@
-import asyncdispatch
-import modbus
-import types
-import tables
-import util
-import json
-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)
-
-  server.state[key].voltage = voltage
-  server.state[key].frequency = frequency
-  server.state[key].`import` = `import`
-  server.state[key].cosphi = cosphi
-  server.state[key].power = power
-
-  server.state[key].lastUpdated = toUnix(getTime())
-
-  broadcast($(%*server.state))
-
-proc updatePowermeters() {.async.} =
-  for key, device in server.config.devices.pairs():
-    if device.type != PowerMeter:
-      continue
-
-    try:
-      await updatePowermeter(key, device)
-    except:
-      let e = getCurrentException()
-      echo("error while updating powermeter ", key, ": ", e.msg)
-
-proc powermetersLoop() {.async.} =
-  await sleepAsync(500)
-  while true:
-    await updatePowermeters()
-    await sleepAsync(int(server.config.powermeterUpdateIntervalSec * 1000))
-
-proc initBackendPowermeter*() =
-  for key, device in server.config.devices.pairs():
-    if device.type != PowerMeter:
-      continue
-
-    server.state[key] = DeviceState(type: PowerMeter)
-
-  asyncCheck powermetersLoop()
diff --git a/src/backend_relayboard.nim b/src/backend_relayboard.nim
@@ -1,25 +0,0 @@
-import asyncdispatch
-import types
-import modbus
-import tables
-import vars
-
-proc updateRelayboards() {.async.} =
-  echo "updating relayboards"
-  for key, device in server.config.devices.pairs():
-    if device.type != RelayBoard:
-      continue
-
-    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*() =
-  for key, device in server.config.devices.pairs():
-    if device.type != RelayBoard:
-      continue
-    server.state[key] = DeviceState(type: RelayBoard)
-  asyncCheck updateRelayboards()
diff --git a/src/devices/lacrosseSensors.nim b/src/devices/lacrosseSensors.nim
@@ -0,0 +1,67 @@
+import asyncdispatch, asyncnet, tables, json, times, options
+import ../types, ../vars, ../utils, ../influx
+
+proc lacrosseHandleLoop (sock: AsyncSocket) {.async.} =
+  while true:
+    try:
+      let line = await sock.recvLine()
+      if line == "":
+        break
+
+      let msg = parseJson(line).to(LacrosseMessage)
+
+      for key, device in server.config.devices.pairs():
+        if device.type != LacrosseTempSensor:
+          continue
+        if device.id != msg.id:
+          continue
+
+        if msg.hum == 106:
+          server.state[key].humidity = -1
+        else:
+          server.state[key].humidity = msg.hum
+        server.state[key].temperature = msg.temp.isaRound(2)
+        server.state[key].weakBattery = bool(msg.weakBatt)
+        server.state[key].lastUpdated = some(toUnix(getTime()))
+
+
+        if server.config.serverConfig.influx.isSome:
+          let config = server.config.serverConfig.influx.get
+
+          if config.lacrosseDatabase.isSome:
+            var tags, fields = initTable[string, string]()
+
+            tags["device"] = key
+
+            if msg.hum != 106:
+              fields["humidity"] = $msg.hum
+
+            fields["temperature"] = $msg.temp.isaRound(2)
+
+            discard await config.insertDatabase(config.lacrosseDatabase.get, key, tags, fields, server.state[key].lastUpdated.get)
+
+
+        broadcastServerState()
+
+    except:
+      let e = getCurrentException()
+      echo("error while updating lacrosse: ", e.msg)
+
+proc lacrosseConnectLoop () {.async.} =
+  while true:
+    try:
+      let config = server.config.serverConfig.lacrosse.get
+      let sock = await asyncnet.dial(config.host, Port(config.port))
+      await lacrosseHandleLoop(sock)
+    except:
+      let e = getCurrentException()
+      echo("error while connectiong to lacrosse relay: ", e.msg)
+    await sleepAsync(1000)
+
+proc initLacrosseSensors* () =
+  for key, device in server.config.devices.pairs():
+    if device.type != LacrosseTempSensor:
+      continue
+    server.state[key] = DeviceState(type: LacrosseTempSensor)
+
+  asyncCheck lacrosseConnectLoop()
diff --git a/src/devices/modbusPowermeter.nim b/src/devices/modbusPowermeter.nim
@@ -0,0 +1,63 @@
+import asyncdispatch, tables, times, options, tables
+import ../types, ../vars, ../modbus, ../influx, ../utils
+
+proc updatePowermeter (key: string, device: DeviceConfig) {.async.} =
+  let deviceAddress = device.address.get
+
+  let voltage = await mb.asyncReadFloat(deviceAddress, 0)
+  let frequency = await mb.asyncReadFloat(deviceAddress, 70)
+  let `import` = await mb.asyncReadFloat(deviceAddress, 72)
+  let cosphi = await mb.asyncReadFloat(deviceAddress, 30)
+  let power = await mb.asyncReadFloat(deviceAddress, 12)
+
+  server.state[key].voltage = voltage
+  server.state[key].frequency = frequency
+  server.state[key].`import` = `import`
+  server.state[key].cosphi = cosphi
+  server.state[key].power = power
+
+  server.state[key].lastUpdated = some(toUnix(getTime()))
+
+  if server.config.serverConfig.influx.isSome:
+    let config = server.config.serverConfig.influx.get
+
+    if config.powermeterDatabase.isSome:
+      var tags, fields = initTable[string, string]()
+
+      tags["device"] = key
+
+      fields["voltage"]   = $voltage.isaRound(2)
+      fields["frequency"] = $frequency.isaRound(2)
+      fields["import"]    = $`import`.isaRound(3)
+      fields["cosphi"]    = $cosphi.isaRound(3)
+      fields["power"]     = $power.isaRound(2)
+
+      discard await config.insertDatabase(config.powermeterDatabase.get, key, tags, fields, server.state[key].lastUpdated.get)
+
+  broadcastServerState()
+
+proc updatePowermeters () {.async.} =
+  for key, device in server.config.devices.pairs():
+    if device.type != PowerMeter:
+      continue
+
+    try:
+      await updatePowermeter(key, device)
+    except:
+      let e = getCurrentException()
+      echo("error while updating powermeter ", key, ": ", e.msg)
+
+proc powermetersLoop () {.async.} =
+  await sleepAsync(500)
+  while true:
+    await updatePowermeters()
+    await sleepAsync(int(server.config.serverConfig.powermeterUpdateIntervalSec * 1000))
+
+proc initModbusPowermeters* () =
+  for key, device in server.config.devices.pairs():
+    if device.type != PowerMeter:
+      continue
+
+    server.state[key] = DeviceState(type: PowerMeter)
+
+  asyncCheck powermetersLoop()
diff --git a/src/devices/modbusRelayboard.nim b/src/devices/modbusRelayboard.nim
@@ -0,0 +1,38 @@
+import asyncdispatch, tables, options
+import ../types, ../vars, ../utils, ../modbus
+
+proc setRelay*(relayboard: string, relay: uint8, value: bool) {.async.} =
+  let config = server.config.devices[relayboard]
+  
+  server.state[relayboard].relays[relay] = value
+  await mb.asyncWriteBit(config.address.get, config.firstRegister + relay, server.state[relayboard].relays[relay])
+
+proc toggleRelay*(relayboard: string, relay: uint8) {.async.} =  
+  let config = server.config.devices[relayboard]
+
+  server.state[relayboard].relays[relay] = not server.state[relayboard].relays[relay]
+  await mb.asyncWriteBit(config.address.get, config.firstRegister + relay, server.state[relayboard].relays[relay])
+
+proc updateRelayboards() {.async.} =
+  echo "Get relayboard state"
+  for key, device in server.config.devices.pairs():
+    if device.type != RelayBoard:
+      continue
+
+    try:
+      let data = await mb.asyncReadBits(device.address.get, device.firstRegister, device.count)
+      server.state[key] = DeviceState(type: RelayBoard, relays: data)
+    except:
+      let e = getCurrentException()
+      echo("error while updating relayboard ", key, ": ", e.msg)
+  
+  broadcastServerState()
+
+proc initModbusRelayboards*() =
+  for key, device in server.config.devices.pairs():
+    if device.type != RelayBoard:
+      continue
+
+    server.state[key] = DeviceState(type: RelayBoard)
+
+  asyncCheck updateRelayboards()
diff --git a/src/devices/zigbee2mqttLamp.nim b/src/devices/zigbee2mqttLamp.nim
@@ -0,0 +1,85 @@
+import asyncdispatch, strutils, json, tables, options, times
+import ../types, ../vars, ../utils
+import nmqtt
+
+proc setLampState* (deviceName: string, value: bool) {.async.} =
+  let config    = server.config.devices[deviceName]
+  var sendState = "OFF"
+
+  if config.type != Zigbee2MqttLamp:
+    return 
+
+  if value == true:
+    sendState = "ON"
+
+  await mqttContext.publish("zigbee2mqtt/" & config.friendlyName & "/set", "{\"state\": \"" & sendState & "\"}")
+
+proc toggleLamp* (deviceName: string) {.async.} =
+  let config = server.config.devices[deviceName]
+
+  if config.type != Zigbee2MqttLamp:
+    return 
+
+  await mqttContext.publish("zigbee2mqtt/" & config.friendlyName & "/set", "{\"state\": \"TOGGLE\"}")
+
+proc setLampBrightness* (deviceName: string, brightness: uint8) {.async.} = 
+    let config = server.config.devices[deviceName]
+
+    if config.type != Zigbee2MqttLamp:
+      return 
+
+    await mqttContext.publish("zigbee2mqtt/" & config.friendlyName & "/set", "{\"brightness\": \"" & $brightness & "\"}")
+
+proc setLampColor* (deviceName: string, colorX: float, colorY: float) {.async.} = 
+    let config = server.config.devices[deviceName]
+
+    if config.type != Zigbee2MqttLamp:
+      return 
+
+    await mqttContext.publish("zigbee2mqtt/" & config.friendlyName & "/set", "{\"color\": {\"X\": \"" & $colorX & "\", \"Y\": \"" & $colorY & "\"}}")
+
+proc setLampColorTemperature* (deviceName: string, colorTemperature: int) {.async.} = 
+    let config = server.config.devices[deviceName]
+
+    if config.type != Zigbee2MqttLamp:
+      return 
+
+    await mqttContext.publish("zigbee2mqtt/" & config.friendlyName & "/set", "{\"color_temp\": \"" & $colorTemperature & "\"}")
+
+proc updateLamp (topic: string, message: string) =
+  echo message
+  let deviceName = zigbee2mqttDevices[topic]
+  let recivedData = parseJson(message)
+  
+  server.state[deviceName].lastUpdated = some(toUnix(getTime()))
+
+  if recivedData.hasKey("linkquality"):
+    server.state[deviceName].lampLinkquality = recivedData["linkquality"].getInt
+
+  if recivedData.hasKey("state"):
+    if recivedData["state"].getStr == "ON":
+      server.state[deviceName].lampState = true
+    else:
+      server.state[deviceName].lampState = false      
+
+  if recivedData.hasKey("brightness"):
+    server.state[deviceName].lampBrightness = recivedData["brightness"].getInt
+
+  if recivedData.hasKey("color"):
+    server.state[deviceName].lampColorX = recivedData["color"]["x"].getFloat
+    server.state[deviceName].lampColorY = recivedData["color"]["y"].getFloat
+
+  if recivedData.hasKey("color_temp"):
+    server.state[deviceName].lampColorTemperature = recivedData["color_temp"].getInt
+
+  broadcastServerState()
+
+
+proc initZigbee2MqttLamps* () {.async.} =
+  for key, device in server.config.devices.pairs():
+    if device.type != Zigbee2MqttLamp: continue
+
+    zigbee2mqttDevices["zigbee2mqtt/" & device.friendlyName] = key
+    server.state[key] = DeviceState(type: Zigbee2MqttLamp, lampType: device.lampType)
+
+    await mqttContext.subscribe("zigbee2mqtt/" & device.friendlyName, 2, updateLamp)+
\ No newline at end of file
diff --git a/src/frontend.nim b/src/frontend.nim
@@ -0,0 +1,119 @@
+import asyncdispatch, asynchttpserver, json, tables, options
+import ws
+import types, vars, utils, actionHandler
+
+proc processWsClient(req: Request) {.async,gcsafe.} =
+  var ws: WebSocket
+  cleanupConnections()
+
+  try:
+    ws = await newWebsocket(req)
+    lastClientId += 1
+    setupPings(ws, 2)
+
+  except:
+    asyncCheck req.respond(Http404, "404")
+    return
+
+  try:
+    while ws.readyState == Open:
+      let req = await ws.receiveStrPacket()
+
+      if req != "":
+        try:
+          let action   = parseJson(req).to(Action)
+          var response = JsonNode()
+
+          if not checkAccessToken(action.accessToken):
+            raise newException(OsError, "invalid accessToken")
+
+          if action.type == SetSubscriptionStateAction:
+            if action.subscribed:
+              echo "adding client"
+              subscribedConnections.add(ws)
+            else:
+              echo "removing client (todo)"
+
+          else:
+            response = await handleAction(action)
+
+          await ws.send($(%*Response(status: Ok, data: response)))
+
+        except:
+          let e = getCurrentException()
+          await ws.send($(%*Response(status: Err, data: newJString(e.msg))))
+
+  except WebSocketError:
+    echo "socket closed:", getCurrentExceptionMsg()
+
+proc processHttpClient(req: Request) {.async,gcsafe.} =
+  if req.reqMethod == HttpGet:
+    if req.headers.hasKey("Authorization") and req.headers["Authorization"] == "Bearer " & server.config.serverConfig.accessToken:
+      await req.respond(Http200, $(%* server.state))
+    else:
+      await req.respond(Http401, "401 Unauthorized")
+
+  elif req.reqMethod == HttpPost:
+    try:
+      let action  = parseJson(req.body).to(Action)
+      
+      if not checkAccessToken(action.accessToken):
+        raise newException(OsError, "invalid accessToken")
+
+      await req.respond(Http200, $(%*Response(status: Ok, data: await handleAction(action))))
+
+    except:
+      let e = getCurrentException()
+      await req.respond(Http500, $(%*Response(status: Err, data: newJString(e.msg))))
+  else:
+    await req.respond(Http405, "405 Method Not Allowed")
+
+proc processPrometheusClient(req: Request) {.async,gcsafe.} =
+  if req.reqMethod == HttpGet:
+    if req.headers.hasKey("Authorization") and req.headers["Authorization"] == "Bearer " & server.config.serverConfig.accessToken:
+      var resp = ""
+      for key, device in server.config.devices.pairs():
+        if device.type == PowerMeter:
+          if server.state[key].frequency == 0:
+            continue
+          let lastUpdated = server.state[key].lastUpdated.get
+          resp.addVal("powermeter_import", key, $(server.state[key].import), lastUpdated)
+          resp.addVal("powermeter_cosphi", key, $(server.state[key].cosphi), lastUpdated)
+          resp.addVal("powermeter_power", key, $(server.state[key].power), lastUpdated)
+          resp.addVal("powermeter_frequency", key, $(server.state[key].frequency), lastUpdated)
+          resp.addVal("powermeter_voltage", key, $(server.state[key].voltage), lastUpdated)
+
+        if device.type == RelayBoard:
+          for i, val in server.state[key].relays:
+            resp.addVal("relayboard_relay", key & "\",relay=\"" & $(i) & "\",name=\"" & $(i), fmtBool(val), 0)
+
+        if device.type == LacrosseTempSensor:
+          let lastUpdated = server.state[key].lastUpdated.get
+          resp.addVal("lacrossetempsensor_temperature", key, $(server.state[key].temperature), lastUpdated)
+          resp.addVal("lacrossetempsensor_weakbattery", key, fmtBool(server.state[key].weakBattery), lastUpdated)
+          if server.state[key].humidity < 0:
+            continue
+          resp.addVal("lacrossetempsensor_humidity", key, $(server.state[key].humidity), lastUpdated)
+
+      await req.respond(Http200, resp)
+
+    else:
+      await req.respond(Http401, "401 Unauthorized")
+
+  else:
+    await req.respond(Http405, "405 Method Not Allowed")
+
+proc processRequest(req: Request) {.async,gcsafe.} =
+  case req.url.path
+  of "/":
+    await processHttpClient(req)
+  of "/ws":
+    await processWsClient(req)
+  of "/metrics":
+    await processPrometheusClient(req)
+  else:
+    asyncCheck req.respond(Http404, "404")
+
+proc serveFrontend*() {.async.} =
+  var httpServer = newAsyncHttpServer()
+  await httpServer.serve(Port(server.config.serverConfig.frontendPort), processRequest)
diff --git a/src/frontend_http.nim b/src/frontend_http.nim
@@ -1,24 +0,0 @@
-import asynchttpserver
-import asyncdispatch
-import util
-import types
-import sequtils
-import json
-import vars
-
-proc processHttpClient(req: Request) {.async,gcsafe.} =
-  if req.reqMethod == HttpGet:
-    if req.headers.hasKey("Authorization") and req.headers["Authorization"] == "Bearer " & server.config.accessToken:
-      await req.respond(Http200, $(%* server.state))
-    else:
-      await req.respond(Http401, "401 Unauthorized")
-  elif req.reqMethod == HttpPost:
-    let client = Client(id: lastClientId, sendProc: proc (msg: string) {.async.} = await req.respond(Http200, msg))
-    lastClientId += 1
-    await client.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_prometheus.nim b/src/frontend_prometheus.nim
@@ -1,59 +0,0 @@
-import asynchttpserver
-import asyncdispatch
-import util
-import types
-import sequtils
-import json
-import vars
-import tables
-
-proc addVal(resp: var string, name: string, key: string, val: string, lastUpdated: int64) =
-  resp = resp & name & "{"
-  resp = resp & "device=\"" & key & "\""
-  resp = resp & "} "
-  resp = resp & val
-  if lastUpdated != 0:
-    resp = resp & " "
-    resp = resp & $(lastUpdated * 1000)
-  resp = resp & "\n"
-
-proc fmtBool(b: bool): string =
-  if b:
-    return "1"
-  else:
-    return "0"
-
-proc processPrometheusClient(req: Request) {.async,gcsafe.} =
-  if req.reqMethod == HttpGet:
-    if req.headers.hasKey("Authorization") and req.headers["Authorization"] == "Bearer " & server.config.accessToken:
-      var resp = ""
-      for key, device in server.config.devices.pairs():
-        if device.type == PowerMeter:
-          if server.state[key].frequency == 0:
-            continue
-          resp.addVal("powermeter_import", key, $(server.state[key].import), server.state[key].lastUpdated)
-          resp.addVal("powermeter_cosphi", key, $(server.state[key].cosphi), server.state[key].lastUpdated)
-          resp.addVal("powermeter_power", key, $(server.state[key].power), server.state[key].lastUpdated)
-          resp.addVal("powermeter_frequency", key, $(server.state[key].frequency), server.state[key].lastUpdated)
-          resp.addVal("powermeter_voltage", key, $(server.state[key].voltage), server.state[key].lastUpdated)
-
-        if device.type == RelayBoard:
-          for i, val in server.state[key].relays:
-            resp.addVal("relayboard_relay", key & "\",relay=\"" & $(i), fmtBool(val), 0)
-
-        if device.type == LacrosseTempSensor:
-          resp.addVal("lacrossetempsensor_temperature", key, $(server.state[key].temperature), server.state[key].lastUpdated2)
-          resp.addVal("lacrossetempsensor_weakbattery", key, fmtBool(server.state[key].weakBattery), server.state[key].lastUpdated2)
-          if server.state[key].humidity < 0:
-            continue
-          resp.addVal("lacrossetempsensor_humidity", key, $(server.state[key].humidity), server.state[key].lastUpdated2)
-
-      await req.respond(Http200, resp)
-    else:
-      await req.respond(Http401, "401 Unauthorized")
-  else:
-    await req.respond(Http405, "405 Method Not Allowed")
-
-proc servePrometheus*() {.async.} =
-  var prometheusServer = newAsyncHttpServer()
-  await prometheusServer.serve(Port(server.config.prometheusPort), processPrometheusClient)
diff --git a/src/frontend_tcp.nim b/src/frontend_tcp.nim
@@ -1,30 +0,0 @@
-import asyncnet
-import asyncdispatch
-import util
-import types
-import sequtils
-import json
-import vars
-
-proc processTcpClient(sock: AsyncSocket) {.async.} =
-  let client = Client(id: lastClientId, sendProc: proc (msg: string) {.async.} = await sock.send(msg & '\n'))
-  lastClientId += 1
-  try:
-    while true:
-      let req = await sock.recvLine()
-      if req == "":
-        sock.close()
-        break
-      await client.tryHandleRequest(req)
-  except:
-    sock.close()
-
-proc serveTcp*() {.async.} =
-  var socket = newAsyncSocket()
-  socket.setSockOpt(OptReuseAddr, true)
-  socket.bindAddr(Port(server.config.tcpPort))
-  socket.listen()
-
-  while true:
-    let sock = await socket.accept()
-    asyncCheck processTcpClient(sock)
diff --git a/src/frontend_ws.nim b/src/frontend_ws.nim
@@ -1,30 +0,0 @@
-import asynchttpserver
-import asyncdispatch
-import util
-import types
-import ws
-import sequtils
-import json
-import vars
-
-proc processWsClient(req: Request) {.async,gcsafe.} =
-  var ws: WebSocket
-  var client: Client
-  try:
-    ws = await newWebsocket(req)
-    lastClientId += 1
-    client = Client(id: lastClientId, sendProc: proc (msg: string) {.async.} = await ws.send(msg))
-  except:
-    asyncCheck req.respond(Http404, "404")
-    return
-
-  try:
-    while true:
-      let req = await ws.receiveStrPacket()
-      await client.tryHandleRequest(req)
-  except:
-    ws.close()
-
-proc serveWs*() {.async.} =
-  var httpServer = newAsyncHttpServer()
-  await httpServer.serve(Port(server.config.wsPort), processWsClient)
diff --git a/src/influx.nim b/src/influx.nim
@@ -0,0 +1,63 @@
+import asyncdispatch, strutils, tables, httpclient, options, json, base64, uri
+import types, vars
+
+proc existsDatabase* (config: InfluxConfig, databaseName: string): Future[bool] {.async.}=
+  var client = newAsyncHttpClient()
+
+  if config.username.isSome and config.password.isSome:
+    client.headers["Authorization"] = "Basic " & base64.encode(config.username.get & ":" & config.password.get)
+  
+  let baseUrl  = "http://" & config.host & ":" & $config.port & "/"
+  let response = await client.getContent(baseUrl & "query?" & encodeQuery({"db": databaseName, "q": "select * FROM test LIMIT 1"}))
+  let data     = parseJson(response)
+
+  client.close()
+
+  if data["results"][0].hasKey("error"):
+   return false
+
+  return true  
+
+proc insertDatabase* (config: InfluxConfig, databaseName: string, tableName: string, tags: Table[string, string], fields: Table[string, string], timestamp: int64): Future[bool] {.async.} =
+  var client = newAsyncHttpClient()
+
+  if config.username.isSome and config.password.isSome:
+    client.headers["Authorization"] = "Basic " & base64.encode(config.username.get & ":" & config.password.get)
+  
+  let baseUrl  = "http://" & config.host & ":" & $config.port & "/"
+
+  var tagsCombined: seq[string]
+  var fieldsCombined: seq[string]
+
+  for key, value in pairs(tags):
+    tagsCombined.add(key & "=" & value)
+
+  for key, value in pairs(fields):
+    fieldsCombined.add(key & "=" & value)
+
+  let body = tableName & "," & tagsCombined.join(",") & " " & fieldsCombined.join(",") & " " & $timestamp
+
+  let response = await client.request(baseUrl & "write?db=" & databaseName, httpMethod = HttpPost, body = body)
+
+  if response.code != Http204:
+    return false
+
+  return true
+
+proc initInflux* () =
+  let config = server.config.serverConfig.influx.get
+
+  try:
+    if config.powermeterDatabase.isSome:
+      if not waitFor config.existsDatabase(config.powermeterDatabase.get):
+        echo "Specified Influxdatabase for Powermeter does not exist!"
+        quit()
+
+    if config.lacrosseDatabase.isSome:
+      if not waitFor config.existsDatabase(config.lacrosseDatabase.get):
+        echo "Specified Influxdatabase for Lacrosse does not exist!"
+        quit()
+
+  except:
+    let e = getCurrentException()
+    echo e.msg
diff --git a/src/modbus.nim b/src/modbus.nim
@@ -1,9 +1,5 @@
-import types
-import tables
-import sequtils
-import asyncdispatch
-import posix
-import vars
+import asyncdispatch, options, posix, tables
+import types, vars
 
 {.passL: "-lmodbus".}
 proc modbus_new_tcp*(ad: cstring, port: cint): modbus {.importc, dynlib: "libmodbus.so.5"}

@@ -69,8 +65,9 @@ proc asyncReadBits*(mb: modbus, ad: uint8, reg: uint8, nb: uint8): Future[seq[bo
   return @data[0..nb-1]
 
 proc initModbus*() =
-  let port: cint = int32(server.config.modbusPort)
-  mb = modbus_new_tcp(server.config.modbusAddr, port)
+  let config = server.config.serverConfig.modbus.get
+  let port: cint = int32(config.port)
+  mb = modbus_new_tcp(config.host, port)
   discard mb.modbus_connect()
 
 proc deinitModbus*() =
diff --git a/src/mqtt.nim b/src/mqtt.nim
@@ -0,0 +1,12 @@
+import asyncdispatch, options
+import types, vars
+import nmqtt
+
+
+proc initMqtt* () {.async.} =
+  var config = server.config.serverConfig.mqtt.get
+
+  mqttContext = newMqttCtx("smartied")
+  mqttContext.set_host(config.host, config.port)
+  
+  await mqttContext.start()+
\ No newline at end of file
diff --git a/src/smartied.nim b/src/smartied.nim
@@ -1,32 +1,41 @@
-import asyncdispatch, types, modbus, json, vars, util, tables, os
-import frontend_tcp, frontend_ws, frontend_http, frontend_prometheus
-import backend_powermeter, backend_relayboard, backend_lacrosse
+import asyncdispatch, json, tables, os
+import types, modbus, mqtt, influx, vars, utils, options
+import frontend
+import devices/[modbusPowermeter, modbusRelayboard, lacrosseSensors, zigbee2mqttLamp]
 
-var configFile = "./config.json"
+proc main() {.async.} =
+  var configFile = "./config.json"
 
-if getEnv("CONFIG_PATH") != "":
-  configFile = getEnv("CONFIG_PATH")
+  if getEnv("CONFIG_PATH") != "":
+    configFile = getEnv("CONFIG_PATH")
 
-if not fileExists(configFile):
-  echo "Config file not found"
-  quit()
+  if not fileExists(configFile):
+    echo "Config file not found"
+    quit()
 
-server = Server(config: parseFile(configFile).to(Config))
+  server = Server(config: parseFile(configFile).to(Config))
 
-if getEnv("ACCESS_TOKEN") != "":
-  server.config.accessToken = getEnv("ACCESS_TOKEN")
+  if getEnv("ACCESS_TOKEN") != "":
+    server.config.serverConfig.accessToken = getEnv("ACCESS_TOKEN")
 
-proc main() {.async.} =
   initUtil()
-  initModbus()
-  initBackendPowermeter()
-  initBackendRelayboard()
-  initBackendLacrosse()
-
-  asyncCheck serveTcp()
-  asyncCheck serveWs()
-  asyncCheck serveHttp()
-  asyncCheck servePrometheus()
+
+  if server.config.serverConfig.influx.isSome():
+    initInflux()
+
+  if server.config.serverConfig.modbus.isSome():
+    initModbus()
+    initModbusPowermeters()
+    initModbusRelayboards()
+
+  if server.config.serverConfig.lacrosse.isSome():
+    initLacrosseSensors()
+  
+  if server.config.serverConfig.mqtt.isSome():
+    asyncCheck initMqtt()
+    asyncCheck initZigbee2MqttLamps()
+
+  asyncCheck serveFrontend()
 
   runForever()
 
diff --git a/src/types.nim b/src/types.nim
@@ -1,40 +1,18 @@
-import asyncnet
-import asyncdispatch
-import json
-import tables
-import sequtils
+import json, tables, options
 
 type DeviceType* = enum
   PowerMeter,
   RelayBoard,
-  LacrosseTempSensor
+  LacrosseTempSensor,
+  Zigbee2MqttLamp
 
-type DeviceConfig* = object
-  address*: uint8
-  case type*: DeviceType
-  of PowerMeter:
-    model*: string
-  of RelayBoard:
-    firstRegister*: uint8
-    count*: uint8
-  of LacrosseTempSensor:
-    id*: string
-
-type Config* = object
-  tcpPort*: uint16
-  wsPort*: uint16
-  httpPort*: uint16
-  prometheusPort*: uint16
-  modbusAddr*: string
-  modbusPort*: uint16
-  lacrosseAddr*: string
-  lacrossePort*: uint16
-  powermeterUpdateIntervalSec*: uint
-  devices*: Table[string, DeviceConfig]
-  clientConfigs*: Table[string, JsonNode]
-  accessToken*: string
+type Zigbee2MqttLampType* = enum
+  RGB,
+  WhiteSpectrum
+  Switchable 
 
 type DeviceState* = object
+  lastUpdated*: Option[int64]
   case type*: DeviceType
   of PowerMeter:
     power*: float32

@@ -42,41 +20,104 @@ type DeviceState* = object
     voltage*: float32
     `import`*: float32
     frequency*: float32
-    lastUpdated*: int64
   of RelayBoard:
     relays*: seq[bool]
   of LacrosseTempSensor:
     humidity*: float32
     temperature*: float32
     weakBattery*: bool
-    lastUpdated2*: int64
+  of Zigbee2MqttLamp:
+    lampType*: Zigbee2MqttLampType
+    lampState*: bool
+    lampBrightness*:  int
+    lampColorX*: float
+    lampColorY*: float
+    lampColorTemperature*: int
+    lampLinkquality*: int
 
-type Server* = object
-  state*: Table[string, DeviceState]
-  config*: Config
-  clients*: seq[AsyncSocket]
 
 type ActionType* = enum
-  SetRelayAction,
-  ToggleRelayAction,
+  SwitchStateAction,
+  SetBrightnessAction,
+  SetColorXYAction,
+  SetColorTemperatureAction,
   GetClientConfigAction,
   SetSubscriptionStateAction
 
 type Action* = object
-  accessToken*: string
+  accessToken*: Option[string]
+  deviceName*: string
   case type*: ActionType
-  of SetRelayAction:
-    setRelayBoard*: string
-    setRelay*: uint8
-    setValue*: bool
-  of ToggleRelayAction:
-    toggleRelayBoard*: string
-    toggleRelay*: uint8
+  of SwitchStateAction:
+    relay*: Option[uint8]
+    state*: Option[bool]
+    toggle*: Option[bool]
+  of SetBrightnessAction:
+    brightness*: uint8
+  of SetColorXYAction:
+    colorX*: float
+    colorY*: float
+  of SetColorTemperatureAction:
+    colorTemperature*: int
   of GetClientConfigAction:
     configName*: string
   of SetSubscriptionStateAction:
     subscribed*: bool
 
+type DeviceConfig* = object
+  address*: Option[uint8]
+  case type*: DeviceType
+  of PowerMeter:
+    model*: string
+  of RelayBoard:
+    firstRegister*: uint8
+    count*: uint8
+  of LacrosseTempSensor:
+    id*: string
+  of Zigbee2MqttLamp:
+    friendlyName*: string
+    lampType*: Zigbee2MqttLampType
+
+type ModbusConfig* = object
+  host*: string
+  port*: uint16
+
+type MqttConfig* = object
+  host*:     string
+  port*:     int
+  username*: Option[string]
+  password*: Option[string]
+
+type LacrosseConfig* = object
+  host*: string
+  port*: uint16
+
+type InfluxConfig* = object
+  host*:               string
+  port*:               int
+  username*:           Option[string]
+  password*:           Option[string]
+  powermeterDatabase*: Option[string]
+  lacrosseDatabase*:   Option[string]
+
+type SmartiedConfig* = object
+  frontendPort*: uint16
+  modbus*: Option[ModbusConfig]
+  mqtt*: Option[MqttConfig]
+  lacrosse*: Option[LacrosseConfig]
+  influx*: Option[InfluxConfig]
+  powermeterUpdateIntervalSec*: uint
+  accessToken*: string
+
+type Config* = object
+  devices*: Table[string, DeviceConfig]
+  clientConfigs*: Table[string, JsonNode]
+  serverConfig*: SmartiedConfig
+
+type Server* = object
+  state*: Table[string, DeviceState]
+  config*: Config
+
 type ResponseStatus* = enum
   Err,
   Ok

@@ -93,6 +134,3 @@ type LacrosseMessage* = object
 
 type modbus* = pointer
 
-type Client* = object
-  sendProc*: proc (msg: string): Future[void] {.gcsafe.}
-  id*: int
diff --git a/src/util.nim b/src/util.nim
@@ -1,83 +0,0 @@
-import json
-import asyncdispatch
-import modbus
-import types
-import tables
-import backend_relayboard
-import vars
-import sequtils
-
-proc trySend*(client: Client, msg: string) {.async.} =
-  try:
-    await client.sendProc(msg)
-  except:
-    let e = getCurrentException()
-    echo("error while sending data to client: ", e.msg)
-    echo("removing client ", client.id)
-    echo clients.map(proc(x: Client): int = x.id)
-    clients.keepIf(proc(x: Client): bool = x.id != client.id)
-    echo clients.map(proc(x: Client): int = x.id)
-
-proc broadcast*(msg: string) =
-  for client in clients.filter(proc (x: Client): bool = true):
-    asyncCheck client.trySend(msg)
-
-proc initUtil*() =
-  addTimer(1000, false, proc (fd: AsyncFD): bool {.gcsafe.} =
-      broadcast("")
-    )
-
-  lastClientId = 1
-  clients = @[]
-
-proc handleRequest*(client: Client, req: string): Future[JsonNode] {.async.} =
-  let action = parseJson(req).to(Action)
-
-  if action.accessToken != server.config.accessToken:
-    raise newException(OsError, "invalid accessToken")
-
-  case action.type
-  of SetRelayAction:
-    let config = server.config.devices[action.setRelayBoard]
-
-    server.state[action.setRelayBoard].relays[action.setRelay] = action.setValue
-    await mb.asyncWriteBit(config.address, config.firstRegister + action.setRelay, server.state[action.setRelayBoard].relays[action.setRelay])
-
-    broadcast($(%*server.state))
-    return JsonNode()
-  of ToggleRelayAction:
-    let config = server.config.devices[action.toggleRelayBoard]
-
-    server.state[action.toggleRelayBoard].relays[action.toggleRelay] = not 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()
-  of GetClientConfigAction:
-    let clientConfig: JsonNode = server.config.clientConfigs[action.configName]
-    return clientConfig
-  of SetSubscriptionStateAction:
-    if action.subscribed:
-      echo("adding client ", client.id)
-      echo clients.map(proc(x: Client): int = x.id)
-      clients.keepIf(proc(x: Client): bool = x.id != client.id)
-      echo clients.map(proc(x: Client): int = x.id)
-      clients.add(client)
-      echo clients.map(proc(x: Client): int = x.id)
-      await client.sendProc($(%*server.state))
-    else:
-      echo("removing client ", client.id)
-      echo clients.map(proc(x: Client): int = x.id)
-      clients.keepIf(proc(x: Client): bool = x.id != client.id)
-      echo clients.map(proc(x: Client): int = x.id)
-    return JsonNode()
-
-proc tryHandleRequest*(client: Client, req: string): Future[void] {.async.} =
-  GC_fullCollect()
-  var resp: Response
-  try:
-    resp = Response(status: Ok, data: await client.handleRequest(req))
-  except:
-    let e = getCurrentException()
-    resp = Response(status: Err, data: newJString(e.msg))
-  await client.sendProc($(%*resp))
diff --git a/src/utils.nim b/src/utils.nim
@@ -0,0 +1,60 @@
+import asyncdispatch, sequtils, json, tables, math, options
+import ws
+import types, vars
+
+proc initUtil*() =
+  lastClientId = 1
+  subscribedConnections = @[]
+
+proc addVal* (resp: var string, name: string, key: string, val: string, lastUpdated: int64) =
+  resp = resp & name & "{"
+  resp = resp & "device=\"" & key & "\""
+  resp = resp & "} "
+  resp = resp & val
+  if lastUpdated != 0:
+    resp = resp & " "
+    resp = resp & $(lastUpdated * 1000)
+  resp = resp & "\n"
+
+proc fmtBool* (b: bool): string =
+  if b:
+    return "1"
+  else:
+    return "0"
+
+proc cleanupConnections*() =
+#  echo "subscribedConnections: " & $subscribedConnections.len
+  subscribedConnections.keepIf(proc(x: Websocket): bool = x.readyState != Closed)
+#  echo "subscribedConnections(after clenup): " & $subscribedConnections.len
+
+proc broadcast* (msg: string) =
+  cleanupConnections()
+
+  try:
+    for socket in subscribedConnections:
+      if socket.readyState == Open:
+        asyncCheck socket.send(msg)
+
+  except WebSocketError:
+    echo "socket closed:", getCurrentExceptionMsg()
+
+proc broadcastServerState* () =
+  broadcast($(%*server.state))
+
+proc isaRound* [T: float32|float64](value: T, places: int = 0): float = 
+  if places == 0:
+    result = round(value)
+  else:
+    result = value * pow(10.0, T(places))
+    result = floor(result)
+    result = result / pow(10.0, T(places))
+
+proc checkAccessToken* (token: Option[string]): bool =
+  if not token.isNone:
+    if server.config.serverConfig.accessToken != token.get:
+      return false
+  else:
+    if server.config.serverConfig.accessToken != "":
+      return false
+
+  return true
diff --git a/src/vars.nim b/src/vars.nim
@@ -1,8 +1,10 @@
+import tables
+import ws, nmqtt
 import types
-import ws
-import asyncnet
 
-var clients* {.threadvar.}: seq[Client]
+var subscribedConnections* {.threadvar.}: seq[Websocket]
 var server* {.threadvar.}: Server
 var mb* {.threadvar.}: modbus
+var mqttContext* {.threadvar.}: MqttCtx
 var lastClientId* {.threadvar.}: int
+var zigbee2mqttDevices* {.threadvar.}: Table[string, string]