ctucx.git: lacrosse2mqtt

Publish data from LaCrosse sensors via MQTT

commit 9e1f7dbb533ae1db3e63e7900be6d1ffe8ac83c6
Author: Leah (ctucx) <git@ctu.cx>
Date: Thu, 8 Dec 2022 18:13:43 +0100

initial commit
9 files changed, 248 insertions(+), 0 deletions(-)
A
.gitignore
|
1
+
A
config.json
|
7
+++++++
A
flake.lock
|
43
+++++++++++++++++++++++++++++++++++++++++++
A
flake.nix
|
55
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
lacrosse2mqtt.nimble
|
16
++++++++++++++++
A
package.nix
|
19
+++++++++++++++++++
A
src/lacrosse2mqtt.nim
|
95
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/nim.cfg
|
1
+
A
src/types.nim
|
11
+++++++++++
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+result
diff --git a/config.json b/config.json
@@ -0,0 +1,7 @@
+{
+	"mqtt": {
+		"host": "10.0.0.1",
+		"port": 1883
+	},
+	"serialDevice": "/dev/ttyUSB0"
+}
diff --git a/flake.lock b/flake.lock
@@ -0,0 +1,43 @@
+{
+  "nodes": {
+    "flake-utils": {
+      "locked": {
+        "lastModified": 1667395993,
+        "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1670543317,
+        "narHash": "sha256-4mMR56rtxKr+Gwz399jFr4i76SQZxsLWxxyfQlPXRm0=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "7a6a010c3a1d00f8470a5ca888f2f927f1860a19",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-22.11",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "nixpkgs": "nixpkgs"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
@@ -0,0 +1,54 @@
+{
+  description = "Exporter for LaCrosse sensors to mqtt, written in nim";
+
+  inputs = {
+    flake-utils.url = "github:numtide/flake-utils";
+    nixpkgs.url     = "github:NixOS/nixpkgs/nixos-22.11";
+  };
+
+  outputs = { self, nixpkgs, flake-utils }: {
+
+    overlay = final: prev: {
+
+      lacrosse2mqtt = (
+        let
+          nmqtt = final.fetchFromGitHub {
+            owner  = "zevv";
+            repo   = "nmqtt";
+            rev    = "v1.0.4";
+            sha256 = "1by0xyqz754dny19lf8rpkg42passnj0rs6rk3jr763m1zr803mc";
+          };
+
+        in final.nimPackages.buildNimPackage {
+          name        = "lacrosse2mqtt";
+          src         = self;
+
+          buildInputs = [ nmqtt ];
+
+          nimBinOnly  = true;
+          nimRelease  = true;
+        }
+      );
+
+    };
+
+  } // (flake-utils.lib.eachDefaultSystem (system:
+    let
+      pkgs = import nixpkgs {
+        inherit system;
+        overlays = [ self.overlay ];
+      };
+
+    in rec {
+
+      packages.default       = pkgs.lacrosse2mqtt;
+      packages.lacrosse2mqtt = pkgs.lacrosse2mqtt;
+
+      apps.default = {
+        type = "app";
+        program = "${pkgs.lacrosse2mqtt}/bin/lacrosse2mqtt";
+      };
+
+    }
+  ));
+}+
\ No newline at end of file
diff --git a/lacrosse2mqtt.nimble b/lacrosse2mqtt.nimble
@@ -0,0 +1,15 @@
+# Package
+
+version       = "0.1.0"
+author        = "Leah(ctucx)"
+description   = "Exports data from LaCrosse sensors to mqtt"
+license       = "AGPL-3.0"
+srcDir        = "./src"
+bin           = @["lacrosse2mqtt"]
+
+
+
+# Dependencies
+requires "nim >= 0.20.0"
+requires "nmqtt == 1.0.4"
+requires "serial >= 1.0.0"+
\ No newline at end of file
diff --git a/package.nix b/package.nix
@@ -0,0 +1,19 @@
+{ nimPackages, fetchFromGitHub }:
+
+let
+
+  nmqtt = fetchFromGitHub {
+    owner  = "zevv";
+    repo   = "nmqtt";
+    rev    = "v1.0.4";
+    sha256 = "1by0xyqz754dny19lf8rpkg42passnj0rs6rk3jr763m1zr803mc";
+  };
+
+in nimPackages.buildNimPackage {
+  name       = "lacrosse2mqtt";
+
+  nimBinOnly  = true;
+  buildInputs = [ nmqtt ];
+
+  src = ./src;
+}
diff --git a/src/lacrosse2mqtt.nim b/src/lacrosse2mqtt.nim
@@ -0,0 +1,95 @@
+import std/asyncdispatch
+import std/[os, posix, threadpool]
+import std/[tables, json, options]
+
+import nmqtt
+import types
+
+var mqttContext*  {.threadvar.} : MqttCtx
+
+proc CtrlCHook* () {.noconv.} =
+  echo "Ctrl+C fired! \nStopping now!"
+  waitFor mqttContext.disconnect()
+  quit()
+
+proc asyncReadLine*(file: File): Future[string] =
+  var fut = newFuture[string]()
+  var flowVar = spawn file.readLine()
+
+  addTimer(50, false, proc(fd: AsyncFD): bool =
+      if flowVar.isReady():
+        fut.complete(^flowVar)
+        return true
+    )
+
+  return fut
+
+proc readSerial(serial: string) {.async.} =
+  let file = open(serial, fmReadWrite)
+  while true:
+    try:
+      let line = await file.asyncReadLine()
+      let data = parseJson(line)
+
+      let json = %* {
+        "id":          data["id"].getStr,
+        "temperature": data["temp"].getFloat,
+        "newBattery":  data["newBatt"].getBool,
+        "weakBattery": data["weakBatt"].getBool
+      }
+
+      if (data["hum"].getInt != 106):
+        json.add("humidity", newJInt(data["hum"].getInt))
+
+      if mqttContext.isConnected:
+        await mqttContext.publish("lacrosse2mqtt/" & json["id"].getStr,                  $json,                2, true)
+        await mqttContext.publish("lacrosse2mqtt/" & json["id"].getStr & "/temperature", $json["temperature"], 2, true)
+
+        if (data["hum"].getInt != 106):
+          await mqttContext.publish("lacrosse2mqtt/" & json["id"].getStr & "/humidity",  $json["humidity"],    2, true)
+
+        await mqttContext.publish("lacrosse2mqtt/" & json["id"].getStr & "/newBattery",  $json["newBattery"],  2, true)
+        await mqttContext.publish("lacrosse2mqtt/" & json["id"].getStr & "/weakBattery", $json["weakBattery"], 2, true)
+
+    except JsonParsingError:
+      continue
+    except:
+      let
+        e   = getCurrentException()
+        msg = getCurrentExceptionMsg()
+
+      echo "Got exception ", repr(e), " with message ", msg
+
+
+proc main () {.async.} =
+  setControlCHook(CtrlCHook)
+
+  onSignal(SIGTERM):
+    echo "Got SIGTERM! \nStopping now!"
+    waitFor mqttContext.disconnect()
+    quit()
+
+  var configFile = "./config.json"
+
+  if getEnv("CONFIG_PATH") != "":
+    configFile = getEnv("CONFIG_PATH")
+
+  if not fileExists(configFile):
+    echo "Config file not found"
+    quit()
+
+  let config   = parseFile(configFile).to(Config)
+
+  mqttContext = newMqttCtx("lacrosse2mqtt")
+  mqttContext.set_host(config.mqtt.host, config.mqtt.port)
+  mqttContext.set_verbosity(1)
+
+  if (config.mqtt.username.isSome and config.mqtt.password.isSome):
+    mqttContext.set_auth(config.mqtt.username.get, config.mqtt.password.get)
+
+  await mqttContext.start()
+
+  asyncCheck readSerial(config.serialDevice)
+
+asyncCheck main()
+runForever()
diff --git a/src/nim.cfg b/src/nim.cfg
@@ -0,0 +1 @@
+--threads:on -d:nimNoLentIterators
diff --git a/src/types.nim b/src/types.nim
@@ -0,0 +1,11 @@
+import std/options
+
+type MqttConfig* = object
+  host*:     string
+  port*:     int
+  username*: Option[string]
+  password*: Option[string]
+
+type Config* = object
+  mqtt*: MqttConfig
+  serialDevice*: string