Add module.nix

This commit is contained in:
niten 2023-04-24 09:49:57 -07:00
parent ff0822d1bc
commit 08f8d721fa
4 changed files with 98 additions and 13 deletions

View File

@ -17,7 +17,7 @@
packages = rec { packages = rec {
default = snooper-server; default = snooper-server;
snooper-server = helpers.packages."${system}".mkClojureBin { snooper-server = helpers.packages."${system}".mkClojureBin {
name = "org.fudo/snooper"; name = "org.fudo/snooper-server";
primaryNamespace = "snooper.cli"; primaryNamespace = "snooper.cli";
src = ./.; src = ./.;
}; };

71
module.nix Normal file
View File

@ -0,0 +1,71 @@
packages:
{ config, lib, pkgs, ... }:
with lib;
let
snooper-server = packages."${pkgs.system}.snooper-server";
cfg = config.services.snooper;
in {
options.services.snooper = with types; {
enable = mkEnableOption "Enable Snooper notifiaction server.";
verbose = mkEnableOption "Generate verbose logs and output.";
event-topics = mkOption {
type = listOf str;
description = "MQTT topics on which to listen for detection events.";
};
notification-topic = mkOption {
type = str;
description = "MQTT topic on which to send notifications.";
};
mqtt-client = {
host = mkOption {
type = str;
description = "Hostname of the MQTT server.";
};
port = mkOption {
type = port;
description = "Port on which the MQTT server is listening.";
default = 1883;
};
username = mkOption {
type = str;
description = "User as which to connect to the MQTT server.";
};
password-file = mkOption {
type = str;
description =
"File (on the local host) containing the password for the MQTT server.";
};
};
};
config = mkIf cfg.enable {
systemd.services.snooper = {
path = [ snooper-server ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
DynamicUser = true;
LoadCredential = [ "mqtt.passwd:${cfg.mqtt-client.password-file}" ];
ExecStart = pkgs.writeShellScript "snooper-server.sh"
(concatStringsSep " " ([
"snooper-server"
"--mqtt-host=${cfg.mqtt.host}"
"--mqtt-port=${toString cfg.mqtt.port}"
"--mqtt-user=${cfg.mqtt.username}"
"--mqtt-password-file=$CREDENTIALS_DIRECTORY/mqtt.passwd"
"--notification-topic=${cfg.notification-topic}"
] ++ (map (topic: "--event-topic=${topic}") cfg.event-topics)
++ (optional cfg.verbose "--verbose")));
};
};
};
}

View File

@ -5,10 +5,12 @@
[clojure.string :as str] [clojure.string :as str]
[snooper.core :as snooper] [snooper.core :as snooper]
[milquetoast.client :as mqtt] [milquetoast.client :as mqtt]
[fudo-clojure.logging :as log])) [fudo-clojure.logging :as log])
(:gen-class))
(def cli-opts (def cli-opts
[["-v" "--verbose" "Provide verbose output."] [["-v" "--verbose" "Provide verbose output."]
["-h" "--help" "Print this message."]
[nil "--mqtt-host HOSTNAME" "Hostname of MQTT server."] [nil "--mqtt-host HOSTNAME" "Hostname of MQTT server."]
[nil "--mqtt-port PORT" "Port on which to connect to the MQTT server." [nil "--mqtt-port PORT" "Port on which to connect to the MQTT server."
@ -44,13 +46,15 @@
(defn -main [& args] (defn -main [& args]
(let [required-args #{:mqtt-host :mqtt-port :mqtt-user :mqtt-password-file :event-topic :notification-topic} (let [required-args #{:mqtt-host :mqtt-port :mqtt-user :mqtt-password-file :event-topic :notification-topic}
{:keys [options _ errors summary]} (parse-opts args required-args cli-opts)] {:keys [options _ errors summary]} (parse-opts args required-args cli-opts)]
(when (:help options) (msg-quit 0 (usage summary)))
(when (seq errors) (msg-quit 1 (usage summary errors))) (when (seq errors) (msg-quit 1 (usage summary errors)))
(let [{:keys [mqtt-host (let [{:keys [mqtt-host
mqtt-port mqtt-port
mqtt-user mqtt-user
mqtt-password-file mqtt-password-file
notification-topic notification-topic
event-topic]} options event-topic
verbose]} options
catch-shutdown (async/chan) catch-shutdown (async/chan)
mqtt-client (mqtt/connect-json! :host mqtt-host mqtt-client (mqtt/connect-json! :host mqtt-host
:port mqtt-port :port mqtt-port
@ -59,13 +63,18 @@
(slurp) (slurp)
(str/trim))) (str/trim)))
logger (log/print-logger)] logger (log/print-logger)]
(snooper/listen! :mqtt-client mqtt-client (when verbose
(println (format "launching snooper server to listen on %s and report events on %s"
event-topic notification-topic)))
(snooper/listen! :mqtt-client mqtt-client
:notification-topic notification-topic :notification-topic notification-topic
:event-topics event-topic :event-topics event-topic
:logger logger) :logger logger
:verbose verbose)
(.addShutdownHook (Runtime/getRuntime) (.addShutdownHook (Runtime/getRuntime)
(Thread. (fn [] (>!! catch-shutdown true)))) (Thread. (fn [] (>!! catch-shutdown true))))
(<!! catch-shutdown) (<!! catch-shutdown)
(println "stopping snooper server")
;; Stopping the MQTT will stop tattler ;; Stopping the MQTT will stop tattler
(mqtt/stop! mqtt-client) (mqtt/stop! mqtt-client)
(System/exit 0)))) (System/exit 0))))

View File

@ -1,18 +1,21 @@
(ns snooper.core (ns snooper.core
(:require [clojure.core.async :refer [go-loop alts! chan]] (:require [clojure.core.async :refer [go-loop alts!]]
[clojure.pprint :refer [pprint]]
[fudo-clojure.logging :as log] [fudo-clojure.logging :as log]
[milquetoast.client :as mqtt] [milquetoast.client :as mqtt]
[malli.core :as t])) [malli.core :as t]))
(defn pthru [o] (clojure.pprint/pprint o) o)
(def critical-objects [:person :bear]) (def critical-objects [:person :bear])
(def normal-objects [:cat :dog]) (def normal-objects [:cat :dog])
(defn- verbose-pthru [verbose obj]
(when verbose (pprint obj))
obj)
(defn- objects-criticality [objs] (defn- objects-criticality [objs]
(cond (some (partial contains? objs) critical-objects) :high (cond (some (partial contains? objs) critical-objects) :high
(some (partial contains? objs) normal-objects) :medium (some (partial contains? objs) normal-objects) :medium
:else nil)) :else :low))
(defn- objects-probability [objs] (defn- objects-probability [objs]
(let [prob (apply max (vals objs))] (let [prob (apply max (vals objs))]
@ -58,7 +61,7 @@
(defmethod event-summary :possibly [{:keys [description location]}] (defmethod event-summary :possibly [{:keys [description location]}]
(format "There could possibly be %s at the %s" description location)) (format "There could possibly be %s at the %s" description location))
(defmethod event-summary :likely [{:keys [description location]}] (defmethod event-summary :likely [{:keys [description location]}]
(format "There's might %s at the %s" description location)) (format "There might be %s at the %s" description location))
(defmethod event-summary :probably [{:keys [description location]}] (defmethod event-summary :probably [{:keys [description location]}]
(format "There's probably %s at the %s" description location)) (format "There's probably %s at the %s" description location))
(defmethod event-summary :definitely [{:keys [description location]}] (defmethod event-summary :definitely [{:keys [description location]}]
@ -84,17 +87,19 @@
[& {mqtt-client :mqtt-client [& {mqtt-client :mqtt-client
notification-topic :notification-topic notification-topic :notification-topic
event-topics :event-topics event-topics :event-topics
logger :logger}] logger :logger
verbose :verbose}]
(let [incoming (map (partial mqtt/subscribe! mqtt-client) event-topics) (let [incoming (map (partial mqtt/subscribe! mqtt-client) event-topics)
valid-evt? (t/validator MotionEvent)] valid-evt? (t/validator MotionEvent)]
(go-loop [evts (alts! incoming)] (go-loop [evts (alts! incoming)]
(let [evt (first evts)] (let [evt (first evts)]
(clojure.pprint/pprint evt) (when verbose (pprint evt))
(cond (nil? evt) (log/info! logger "stopping") (cond (nil? evt) (log/info! logger "stopping")
(valid-evt? evt) (do (log/info! logger (format "received motion event id %s from %s" (valid-evt? evt) (do (log/info! logger (format "received motion event id %s from %s"
(:id evt) (:id evt)
(:topic evt))) (:topic evt)))
(mqtt/send! mqtt-client notification-topic (pthru (translate-event (:payload evt)))) (mqtt/send! mqtt-client notification-topic
(verbose-pthru verbose (translate-event (:payload evt))))
(recur (alts! incoming))) (recur (alts! incoming)))
:else (do (log/error! logger (format "invalid motion event: %s" evt)) :else (do (log/error! logger (format "invalid motion event: %s" evt))
(recur (alts! incoming)))))))) (recur (alts! incoming))))))))