ctucx.git: smartied

[nimlang] smarthome server

commit 63b29fec04dc5ab42172f55d277c42168eee9932
parent 439b9ee1933af48fd2f8283676c1d7bab83549b6
Author: Leah (ctucx) <leah@ctu.cx>
Date: Thu, 18 Feb 2021 11:38:36 +0100

implemented new devices: zigbee2mqttRemote, zigbee2mqttRelay
10 files changed, 252 insertions(+), 140 deletions(-)
M
config.json
|
26
+++++++++++++++++++-------
D
src/actionHandler.nim
|
77
-----------------------------------------------------------------------------
A
src/deviceActionHandler.nim
|
93
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
src/devices/modbusRelayboard.nim
|
8
++++----
M
src/devices/zigbee2mqttLamp.nim
|
33
++++++++++++++++-----------------
A
src/devices/zigbee2mqttRelay.nim
|
51
+++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/devices/zigbee2mqttRemote.nim
|
20
++++++++++++++++++++
M
src/frontend.nim
|
6
+++---
M
src/smartied.nim
|
4
+++-
M
src/types.nim
|
74
+++++++++++++++++++++++++++++++++++++++++++-------------------------------
diff --git a/config.json b/config.json
@@ -25,17 +25,28 @@
 		"tradfri-lamp1": {
 			"type": "Zigbee2MqttLamp",
 			"lampType": "RGB",
-			"friendlyName": "ikea_lamp_rgb"
+			"deviceName": "ikea_lamp_rgb"
 		},
 		"tradfri-lamp2": {
 			"type": "Zigbee2MqttLamp",
 			"lampType": "WhiteSpectrum",
-			"friendlyName": "ikea_lamp_whitespectrum"
+			"deviceName": "ikea_lamp_whitespectrum"
 		},
 		"tradfri-lamp3": {
 			"type": "Zigbee2MqttLamp",
 			"lampType": "Switchable",
-			"friendlyName": "ikea_lamp_switchable"
+			"deviceName": "ikea_lamp_switchable"
+		},
+		"tradfri-relay1": {
+			"type": "Zigbee2MqttRelay",
+			"deviceName": "ikea_control_outlet"
+		},
+		"tradfri-remote1": {
+			"type": "Zigbee2MqttRemote",
+			"deviceName": "ikea_remote",
+			"actions": {
+				"toggle": {"type": "SwitchStateAction", "deviceName": "modbus-10", "relay": 0, "toggle": true}
+			}
 		},
 		"lacrosse-raum": {
 			"type": "LacrosseTempSensor",

@@ -68,9 +79,9 @@
 						{ "name": "Küche", "device": "modbus-10", "relay": 1 },
 						{ "name": "Bett", "device": "modbus-10", "relay": 3 },
 						{ "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}
+						{ "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 }
 					]
 				},
 				{

@@ -80,7 +91,8 @@
 					"type": "switches",
 					"switches": [
                         { "name": "Lüfter", "device": "modbus-20", "relay": 3 },
-						{ "name": "Lüfter (leise)", "device": "modbus-20", "relay": 2 }
+						{ "name": "Lüfter (leise)", "device": "modbus-20", "relay": 2 },
+						{ "name": "Tradfri Steckdose", "device": "tradfri-relay1", "relay": 0 },
 					]
 				},
 				{
diff --git a/src/actionHandler.nim b/src/actionHandler.nim
@@ -1,77 +0,0 @@
-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/deviceActionHandler.nim b/src/deviceActionHandler.nim
@@ -0,0 +1,93 @@
+import asyncdispatch, json, tables, options
+import types, vars, utils
+import devices/[modbusRelayboard, zigbee2mqttLamp, zigbee2mqttRelay]
+
+proc handleDeviceAction*(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
+
+      case config.type
+      of RelayBoard:
+        if action.relay.isNone:
+          return JsonNode()
+        else:
+          await setModbusRelayState(action.deviceName, action.relay.get, state)
+          broadcastServerState()
+          return JsonNode()
+
+      of Zigbee2MqttLamp:
+        await setZigbee2MqttLampState(action.deviceName, state)
+        return JsonNode()
+
+      of Zigbee2MqttRelay:
+        await setZigbee2MqttRelayState(action.deviceName, state)
+        return JsonNode()
+
+      else:
+        return JsonNode()
+
+    if action.toggle.isSome:
+      case config.type
+      of RelayBoard:
+        if action.relay.isNone:
+          return JsonNode()
+        else:
+          await toggleModbusRelayState(action.deviceName, action.relay.get)
+          broadcastServerState()
+          return JsonNode()
+
+      of Zigbee2MqttLamp:
+        await toggleZigbee2MqttLampState(action.deviceName)
+        return JsonNode()
+
+      of Zigbee2MqttRelay:
+        await toggleZigbee2MqttRelayState(action.deviceName)
+        return JsonNode()
+
+      else:
+        return JsonNode()
+
+  of SetBrightnessAction:
+    let config = server.config.devices[action.deviceName]
+
+    if config.type != Zigbee2MqttLamp:
+      return JsonNode()
+
+    await setZigbee2MqttLampBrightness(action.deviceName, action.brightness)
+    return JsonNode()
+
+  of SetColorXYAction:
+    let config = server.config.devices[action.deviceName]
+
+    if config.type != Zigbee2MqttLamp:
+      return JsonNode()
+
+    await setZigbee2MqttLampColor(action.deviceName, action.colorX, action.colorY)
+    return JsonNode()
+
+  of SetColorTemperatureAction:
+    let config = server.config.devices[action.deviceName]
+
+    if config.type != Zigbee2MqttLamp:
+      return JsonNode()
+
+    await setZigbee2MqttLampColorTemperature(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/devices/modbusRelayboard.nim b/src/devices/modbusRelayboard.nim
@@ -1,19 +1,19 @@
 import asyncdispatch, tables, options
 import ../types, ../vars, ../utils, ../modbus
 
-proc setRelay*(relayboard: string, relay: uint8, value: bool) {.async.} =
+proc setModbusRelayState* (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.} =  
+proc toggleModbusRelayState* (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.} =
+proc updateRelayboards () {.async.} =
   echo "Get relayboard state"
   for key, device in server.config.devices.pairs():
     if device.type != RelayBoard:

@@ -28,7 +28,7 @@ proc updateRelayboards() {.async.} =
   
   broadcastServerState()
 
-proc initModbusRelayboards*() =
+proc initModbusRelayboards* () =
   for key, device in server.config.devices.pairs():
     if device.type != RelayBoard:
       continue
diff --git a/src/devices/zigbee2mqttLamp.nim b/src/devices/zigbee2mqttLamp.nim
@@ -2,7 +2,7 @@ import asyncdispatch, strutils, json, tables, options, times
 import ../types, ../vars, ../utils
 import nmqtt
 
-proc setLampState* (deviceName: string, value: bool) {.async.} =
+proc setZigbee2MqttLampState* (deviceName: string, value: bool) {.async.} =
   let config    = server.config.devices[deviceName]
   var sendState = "OFF"
 

@@ -12,42 +12,41 @@ proc setLampState* (deviceName: string, value: bool) {.async.} =
   if value == true:
     sendState = "ON"
 
-  await mqttContext.publish("zigbee2mqtt/" & config.friendlyName & "/set", "{\"state\": \"" & sendState & "\"}")
+  await mqttContext.publish("zigbee2mqtt/" & config.deviceName.get & "/set", "{\"state\": \"" & sendState & "\"}")
 
-proc toggleLamp* (deviceName: string) {.async.} =
+proc toggleZigbee2MqttLampState* (deviceName: string) {.async.} =
   let config = server.config.devices[deviceName]
 
   if config.type != Zigbee2MqttLamp:
     return 
 
-  await mqttContext.publish("zigbee2mqtt/" & config.friendlyName & "/set", "{\"state\": \"TOGGLE\"}")
+  await mqttContext.publish("zigbee2mqtt/" & config.deviceName.get & "/set", "{\"state\": \"TOGGLE\"}")
 
-proc setLampBrightness* (deviceName: string, brightness: uint8) {.async.} = 
+proc setZigbee2MqttLampBrightness* (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 & "\"}")
+    await mqttContext.publish("zigbee2mqtt/" & config.deviceName.get & "/set", "{\"brightness\": \"" & $brightness & "\"}")
 
-proc setLampColor* (deviceName: string, colorX: float, colorY: float) {.async.} = 
+proc setZigbee2MqttLampColor* (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 & "\"}}")
+    await mqttContext.publish("zigbee2mqtt/" & config.deviceName.get & "/set", "{\"color\": {\"X\": \"" & $colorX & "\", \"Y\": \"" & $colorY & "\"}}")
 
-proc setLampColorTemperature* (deviceName: string, colorTemperature: int) {.async.} = 
+proc setZigbee2MqttLampColorTemperature* (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 & "\"}")
+    await mqttContext.publish("zigbee2mqtt/" & config.deviceName.get & "/set", "{\"color_temp\": \"" & $colorTemperature & "\"}")
 
 proc updateLamp (topic: string, message: string) =
-  echo message
   let deviceName = zigbee2mqttDevices[topic]
   let recivedData = parseJson(message)
   

@@ -57,10 +56,10 @@ proc updateLamp (topic: string, message: string) =
     server.state[deviceName].lampLinkquality = recivedData["linkquality"].getInt
 
   if recivedData.hasKey("state"):
-    if recivedData["state"].getStr == "ON":
-      server.state[deviceName].lampState = true
+    if recivedData["state"].getStr != "ON":
+      server.state[deviceName].lampState = false
     else:
-      server.state[deviceName].lampState = false      
+      server.state[deviceName].lampState = true      
 
   if recivedData.hasKey("brightness"):
     server.state[deviceName].lampBrightness = recivedData["brightness"].getInt

@@ -79,7 +78,7 @@ proc initZigbee2MqttLamps* () {.async.} =
   for key, device in server.config.devices.pairs():
     if device.type != Zigbee2MqttLamp: continue
 
-    zigbee2mqttDevices["zigbee2mqtt/" & device.friendlyName] = key
+    zigbee2mqttDevices["zigbee2mqtt/" & device.deviceName.get] = key
     server.state[key] = DeviceState(type: Zigbee2MqttLamp, lampType: device.lampType)
 
-    await mqttContext.subscribe("zigbee2mqtt/" & device.friendlyName, 2, updateLamp)-
\ No newline at end of file
+    await mqttContext.subscribe("zigbee2mqtt/" & device.deviceName.get, 2, updateLamp)+
\ No newline at end of file
diff --git a/src/devices/zigbee2mqttRelay.nim b/src/devices/zigbee2mqttRelay.nim
@@ -0,0 +1,50 @@
+import asyncdispatch, strutils, json, tables, options, times
+import ../types, ../vars, ../utils
+import nmqtt
+
+proc setZigbee2MqttRelayState* (deviceName: string, value: bool) {.async.} =
+  let config    = server.config.devices[deviceName]
+  var sendState = "OFF"
+
+  if config.type != Zigbee2MqttRelay:
+    return 
+
+  if value == true:
+    sendState = "ON"
+
+  await mqttContext.publish("zigbee2mqtt/" & config.deviceName.get & "/set", "{\"state\": \"" & sendState & "\"}")
+
+proc toggleZigbee2MqttRelayState* (deviceName: string) {.async.} =
+  let config = server.config.devices[deviceName]
+
+  if config.type != Zigbee2MqttRelay:
+    return 
+
+  await mqttContext.publish("zigbee2mqtt/" & config.deviceName.get & "/set", "{\"state\": \"TOGGLE\"}")
+
+proc updateRelay (topic: string, message: string) =
+  let deviceName = zigbee2mqttDevices[topic]
+  let recivedData = parseJson(message)
+  
+  server.state[deviceName].lastUpdated = some(toUnix(getTime()))
+
+  if recivedData.hasKey("linkquality"):
+    server.state[deviceName].relayLinkquality = recivedData["linkquality"].getInt
+
+  if recivedData.hasKey("state"):
+    if recivedData["state"].getStr != "ON":
+      server.state[deviceName].relayState = false
+    else:
+      server.state[deviceName].relayState = true      
+
+  broadcastServerState()
+
+
+proc initZigbee2MqttRelays* () {.async.} =
+  for key, device in server.config.devices.pairs():
+    if device.type != Zigbee2MqttRelay: continue
+
+    zigbee2mqttDevices["zigbee2mqtt/" & device.deviceName.get] = key
+    server.state[key] = DeviceState(type: Zigbee2MqttRelay)
+
+    await mqttContext.subscribe("zigbee2mqtt/" & device.deviceName.get, 2, updateRelay)+
\ No newline at end of file
diff --git a/src/devices/zigbee2mqttRemote.nim b/src/devices/zigbee2mqttRemote.nim
@@ -0,0 +1,19 @@
+import asyncdispatch, strutils, json, tables, options
+import ../types, ../vars, ../deviceActionHandler
+import nmqtt
+
+proc handleRemote (topic: string, message: string) =
+  let deviceName  = zigbee2mqttDevices[topic]
+  let config      = server.config.devices[deviceName]
+  let recivedData = parseJson(message)
+
+  if config.actions.hasKey(recivedData["action"].getStr):
+    discard waitFor handleDeviceAction(config.actions[recivedData["action"].getStr])
+
+proc initZigbee2MqttRemotes* () {.async.} =
+  for key, device in server.config.devices.pairs():
+    if device.type != Zigbee2MqttRemote: continue
+
+    zigbee2mqttDevices["zigbee2mqtt/" & device.deviceName.get] = key
+
+    await mqttContext.subscribe("zigbee2mqtt/" & device.deviceName.get, 2, handleRemote)+
\ No newline at end of file
diff --git a/src/frontend.nim b/src/frontend.nim
@@ -1,6 +1,6 @@
 import asyncdispatch, asynchttpserver, json, tables, options
 import ws
-import types, vars, utils, actionHandler
+import types, vars, utils, deviceActionHandler
 
 proc processWsClient(req: Request) {.async,gcsafe.} =
   var ws: WebSocket

@@ -35,7 +35,7 @@ proc processWsClient(req: Request) {.async,gcsafe.} =
               echo "removing client (todo)"
 
           else:
-            response = await handleAction(action)
+            response = await handleDeviceAction(action)
 
           await ws.send($(%*Response(status: Ok, data: response)))
 

@@ -60,7 +60,7 @@ proc processHttpClient(req: Request) {.async,gcsafe.} =
       if not checkAccessToken(action.accessToken):
         raise newException(OsError, "invalid accessToken")
 
-      await req.respond(Http200, $(%*Response(status: Ok, data: await handleAction(action))))
+      await req.respond(Http200, $(%*Response(status: Ok, data: await handleDeviceAction(action))))
 
     except:
       let e = getCurrentException()
diff --git a/src/smartied.nim b/src/smartied.nim
@@ -1,7 +1,7 @@
 import asyncdispatch, json, tables, os
 import types, modbus, mqtt, influx, vars, utils, options
 import frontend
-import devices/[modbusPowermeter, modbusRelayboard, lacrosseSensors, zigbee2mqttLamp]
+import devices/[modbusPowermeter, modbusRelayboard, lacrosseSensors, zigbee2mqttLamp, zigbee2mqttRelay, zigbee2mqttRemote]
 
 proc main() {.async.} =
   var configFile = "./config.json"

@@ -34,6 +34,8 @@ proc main() {.async.} =
   if server.config.serverConfig.mqtt.isSome():
     asyncCheck initMqtt()
     asyncCheck initZigbee2MqttLamps()
+    asyncCheck initZigbee2MqttRelays()
+    asyncCheck initZigbee2MqttRemotes()
 
   asyncCheck serveFrontend()
 
diff --git a/src/types.nim b/src/types.nim
@@ -1,10 +1,40 @@
 import json, tables, options
 
+type ActionType* = enum
+  SwitchStateAction,
+  SetBrightnessAction,
+  SetColorXYAction,
+  SetColorTemperatureAction,
+  GetClientConfigAction,
+  SetSubscriptionStateAction
+
+type Action* = object
+  accessToken*: Option[string]
+  deviceName*: string
+  case type*: ActionType
+  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 DeviceType* = enum
   PowerMeter,
   RelayBoard,
   LacrosseTempSensor,
-  Zigbee2MqttLamp
+  Zigbee2MqttLamp,
+  Zigbee2MqttRelay,
+  Zigbee2MqttRemote
 
 type Zigbee2MqttLampType* = enum
   RGB,

@@ -34,38 +64,15 @@ type DeviceState* = object
     lampColorY*: float
     lampColorTemperature*: int
     lampLinkquality*: int
-
-
-type ActionType* = enum
-  SwitchStateAction,
-  SetBrightnessAction,
-  SetColorXYAction,
-  SetColorTemperatureAction,
-  GetClientConfigAction,
-  SetSubscriptionStateAction
-
-type Action* = object
-  accessToken*: Option[string]
-  deviceName*: string
-  case type*: ActionType
-  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
+  of Zigbee2MqttRelay:
+    relayState*: bool
+    relayLinkquality*: int
+  else:
+    unusedValue: Option[bool]
 
 type DeviceConfig* = object
   address*: Option[uint8]
+  deviceName*: Option[string]
   case type*: DeviceType
   of PowerMeter:
     model*: string

@@ -75,8 +82,13 @@ type DeviceConfig* = object
   of LacrosseTempSensor:
     id*: string
   of Zigbee2MqttLamp:
-    friendlyName*: string
     lampType*: Zigbee2MqttLampType
+  of Zigbee2MqttRemote:
+    actions*: Table[string, Action]
+  else:
+    unusedValue: Option[bool]
+
+
 
 type ModbusConfig* = object
   host*: string