]> mj.ucw.cz Git - ursary.git/commitdiff
Lights are now controlled via MQTT
authorMartin Mares <mj@ucw.cz>
Sun, 20 Feb 2022 00:07:22 +0000 (01:07 +0100)
committerMartin Mares <mj@ucw.cz>
Sun, 20 Feb 2022 00:07:22 +0000 (01:07 +0100)
Makefile
mqtt.c [new file with mode: 0644]
ursaryd.c
ursaryd.h

index f4b376a890d9b15dde3405cc94696fc727534688..d7b66d1f1cf8ac25058255cccd11b0003e2ace26 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -10,12 +10,12 @@ LIBPULSE_LIBS := $(shell $(PC) --libs libpulse)
 LIBUCW_CFLAGS := $(shell $(PC) --cflags libucw)
 LIBUCW_LIBS := $(shell $(PC) --libs libucw)
 
-CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99 $(LIBUCW_CFLAGS) $(LIBUSB_CFLAGS) $(LIBPULSE_CFLAGS) -g2
-LDLIBS=$(LIBUCW_LIBS) $(LIBUSB_LIBS) $(LIBPULSE_LIBS) -lm
+CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wno-sign-compare -Wredundant-decls -std=gnu99 $(LIBUCW_CFLAGS) $(LIBUSB_CFLAGS) $(LIBPULSE_CFLAGS) -g2
+LDLIBS=$(LIBUCW_LIBS) $(LIBUSB_LIBS) $(LIBPULSE_LIBS) -lmosquitto
 
 all: ursaryd
 
-ursaryd: ursaryd.o mpd.o nocturn.o pulse.o pulse-ucw.o usb.o dmx.o
+ursaryd: ursaryd.o mpd.o nocturn.o pulse.o pulse-ucw.o usb.o dmx.o mqtt.o
 
 ursaryd.o: ursaryd.c ursaryd.h usb.h
 mpd.o: mpd.c ursaryd.h
@@ -24,6 +24,7 @@ pulse.o: pulse.c ursaryd.h
 pulse-ucw.o: pulse-ucw.c ursaryd.h
 usb.o: usb.c ursaryd.h usb.h
 dmx.o: dmx.c ursaryd.h usb.h dmx-interface.h
+mqtt.o: mqtt.c ursaryd.h
 
 clean:
        rm -f `find . -name "*~" -or -name "*.[oa]" -or -name "\#*\#" -or -name TAGS -or -name core -or -name .depend -or -name .#*`
diff --git a/mqtt.c b/mqtt.c
new file mode 100644 (file)
index 0000000..a84ea2b
--- /dev/null
+++ b/mqtt.c
@@ -0,0 +1,121 @@
+/*
+ *     Interaction with MQTT
+ *
+ *     (c) 2022 Martin Mares <mj@ucw.cz>
+ */
+
+#undef LOCAL_DEBUG
+
+#include <ucw/lib.h>
+#include <ucw/io.h>
+#include <ucw/mainloop.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include <mosquitto.h>
+
+#include "ursaryd.h"
+
+static struct mosquitto *mosq;
+
+int mqtt_pipes[2];
+static struct main_file mqtt_rx_pipe;
+
+struct mqtt_pipe_msg {
+  char topic[100];
+  char val[100];
+};
+
+void mqtt_publish(const char *topic, const char *fmt, ...)
+{
+  va_list args;
+  va_start(args, fmt);
+  char m[256];
+  int l = vsnprintf(m, sizeof(m), fmt, args);
+  DBG("MQTT > %s %s", topic, m);
+  if (mosquitto_publish(mosq, NULL, topic, l, m, 0, true) != MOSQ_ERR_SUCCESS)
+    msg(L_ERROR, "Mosquitto: publish failed");
+  va_end(args);
+}
+
+static void mqtt_conn_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int status)
+{
+  if (!status)
+    {
+      msg(L_DEBUG, "MQTT: Connection established, subscribing");
+      if (mosquitto_subscribe(mosq, NULL, "burrow/lights/catarium/#", 1) != MOSQ_ERR_SUCCESS)
+       die("Mosquitto: subscribe failed");
+
+      mqtt_publish("status/ursary", "ok");
+    }
+}
+
+static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level UNUSED, const char *message UNUSED)
+{
+  // msg(L_INFO, "MQTT(%d): %s", level, message);
+}
+
+static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m)
+{
+  struct mqtt_pipe_msg pm;
+
+  if (snprintf(pm.topic, sizeof(pm.topic), m->topic) >= sizeof(pm.topic))
+    {
+      msg(L_ERROR, "MQTT: Topic %s too long", m->topic);
+      return;
+    }
+
+  if (m->payloadlen >= sizeof(pm.val) - 1)
+    {
+      msg(L_ERROR, "MQTT: Invalid value for topic %s", m->topic);
+      return;
+    }
+  memcpy(pm.val, m->payload, m->payloadlen);
+  pm.val[m->payloadlen] = 0;
+
+  careful_write(mqtt_pipes[1], &pm, sizeof(pm));
+}
+
+static int mqtt_pipe_read_callback(struct main_file *fi)
+{
+  struct mqtt_pipe_msg pm;
+  if (careful_read(fi->fd, &pm, sizeof(pm)) <= 0)
+    die("MQTT pipe read error: %m");
+
+  DBG("MQTT < %s %s", pm.topic, pm.val);
+  notify_mqtt(pm.topic, pm.val);
+
+  return HOOK_IDLE;
+}
+
+void mqtt_init(void)
+{
+  mosquitto_lib_init();
+  mosq = mosquitto_new("ursary", 1, NULL);
+  if (!mosq)
+    die("Mosquitto: initialization failed");
+
+  if (pipe(mqtt_pipes) < 0)
+    die("Cannot create pipes: %m");
+
+  mqtt_rx_pipe.fd = mqtt_pipes[0];
+  mqtt_rx_pipe.read_handler = mqtt_pipe_read_callback;
+  file_add(&mqtt_rx_pipe);
+
+  mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
+  mosquitto_log_callback_set(mosq, mqtt_log_callback);
+  mosquitto_message_callback_set(mosq, mqtt_msg_callback);
+
+  if (mosquitto_will_set(mosq, "status/ursary", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
+    die("Mosquitto: unable to set will");
+
+  if (mosquitto_tls_set(mosq, "/etc/burrow-mqtt/ca.crt", NULL, "/etc/burrow-mqtt/client.crt", "/etc/burrow-mqtt/client.key", NULL) != MOSQ_ERR_SUCCESS)
+    die("Mosquitto: unable to set TLS parameters");
+
+  if (mosquitto_connect_async(mosq, "burrow-mqtt", 8883, 60) != MOSQ_ERR_SUCCESS)
+    die("Mosquitto: connect failed");
+
+  if (mosquitto_loop_start(mosq))
+    die("Mosquitto: cannot start service thread");
+}
index f9ce3b9dcc63e94fa2b9e2aedbf5efad492afa34..910482ca843b43be538476c1a18fed8c023cf1b2 100644 (file)
--- a/ursaryd.c
+++ b/ursaryd.c
@@ -1,7 +1,7 @@
 /*
- *     The Ursary Audio Controls
+ *     The Ursary Control Panel
  *
- *     (c) 2014--2020 Martin Mares <mj@ucw.cz>
+ *     (c) 2014--2022 Martin Mares <mj@ucw.cz>
  */
 
 #undef LOCAL_DEBUG
@@ -13,6 +13,7 @@
 #include <ucw/mainloop.h>
 #include <ucw/opt.h>
 #include <ucw/stkstring.h>
+#include <ucw/string.h>
 
 #include <math.h>
 #include <signal.h>
@@ -489,56 +490,51 @@ static void update_mpd_from_button(int button UNUSED, int on)
 static bool lights_on[2];
 static double lights_brightness[2];
 static double lights_temperature[2];
+static timestamp_t lights_last_update[2];
 
 static void update_lights(void)
 {
-  if (!dmx_is_ready())
+  for (uint ch=0; ch < 2; ch++)
     {
-      noct_set_ring(3, RING_MODE_SINGLE_ON, 0x7f);
-      noct_set_button(10, 0);
-      noct_set_button(11, 0);
-      return;
-    }
-
-  for (uint i=0; i<2; i++)
-    {
-      uint warm, cold;
-      if (lights_on[i])
+      if (lights_on[ch])
        {
-         noct_set_ring(3-i, RING_MODE_LEFT, lights_brightness[i] * 127);
-         noct_set_button(11-i, 1);
-         double r = 2;
-         double x = (exp(r*lights_brightness[i]) - 1) / (exp(r) - 1);
-         double t = lights_temperature[i];
-         double w = 2*x*(1-t);
-         double c = 2*x*t;
-         warm = CLAMP((int)(255*w), 0, 255);
-         cold = CLAMP((int)(255*c), 0, 255);
+         noct_set_ring(3-ch, RING_MODE_LEFT, lights_brightness[ch] * 127);
+         noct_set_button(11-ch, 1);
        }
       else
        {
-         noct_set_ring(3-i, RING_MODE_LEFT, 0);
-         noct_set_button(11-i, 0);
-         warm = cold = 0;
+         noct_set_ring(3-ch, RING_MODE_LEFT, 0);
+         noct_set_button(11-ch, 0);
        }
-      DBG("Lights[%d]: on=%d bri=%.3f temp=%.3f -> warm=%d cold=%d", i, lights_on[i], lights_brightness[i], lights_temperature[i], warm, cold);
-      dmx_set_pwm(2*i, warm);
-      dmx_set_pwm(2*i+1, cold);
     }
 }
 
+static void send_lights(int ch)
+{
+  DBG("Lights[%d]: on=%d bri=%.3f temp=%.3f", ch, lights_on[ch], lights_brightness[ch], lights_temperature[ch]);
+  double b = lights_on[ch] ? lights_brightness[ch] : 0;
+  double t = lights_on[ch] ? lights_temperature[ch] : 0;
+  char topic[100], val[100];
+  snprintf(topic, sizeof(topic), "burrow/lights/catarium/%s", (ch ? "top" : "bottom"));
+  snprintf(val, sizeof(val), "%.3f %.3f", b, t);
+  mqtt_publish(topic, val);
+  lights_last_update[ch] = main_get_now();
+  update_lights();
+}
+
 static void update_lights_from_rotary(int ch, int delta)
 {
   if (lights_on[ch])
     lights_brightness[ch] = CLAMP(lights_brightness[ch] + 0.015*delta*abs(delta), 0., 1.);
-  update_lights();
+  send_lights(ch);
 }
 
 static void update_lights_from_slider(int value)
 {
   lights_temperature[0] = value / 127.;
   lights_temperature[1] = value / 127.;
-  update_lights();
+  send_lights(0);
+  send_lights(1);
 }
 
 static void lights_button_timeout(struct main_timer *t)
@@ -548,7 +544,7 @@ static void lights_button_timeout(struct main_timer *t)
   timer_del(t);
   lights_on[ch] = 1;
   lights_brightness[ch] = 1;
-  update_lights();
+  send_lights(ch);
 }
 
 static void update_lights_from_button(int ch, int on)
@@ -567,7 +563,7 @@ static void update_lights_from_button(int ch, int on)
     {
       timer_del(&lights_button_timer[ch]);
       lights_on[ch] = !lights_on[ch];
-      update_lights();
+      send_lights(ch);
     }
 }
 
@@ -784,6 +780,41 @@ void notify_touch(int rotary UNUSED, int on UNUSED)
     schedule_update();
 }
 
+void notify_mqtt(const char *topic, const char *val)
+{
+  const char blc[] = "burrow/lights/catarium/";
+  if (str_has_prefix(topic, blc))
+    {
+      topic += strlen(blc);
+      int ch;
+      if (!strcmp(topic, "top"))
+       ch = 1;
+      else if (!strcmp(topic, "bottom"))
+       ch = 0;
+      else
+       return;
+
+      double b, t;
+      if (sscanf(val, "%lf %lf", &b, &t) != 2)
+       return;
+
+      timestamp_t now = main_get_now();
+      if (!lights_last_update[ch] || lights_last_update[ch] + 1000 < now)
+       {
+         DBG("Received foreign light settings");
+         if (!b)
+           lights_on[ch] = 0;
+         else
+           {
+             lights_on[ch] = 1;
+             lights_brightness[ch] = b;
+             lights_temperature[ch] = t;
+           }
+         update_lights();
+       }
+    }
+}
+
 /*** Main entry point ***/
 
 static int debug;
@@ -813,9 +844,10 @@ static void daemon_body(struct daemon_params *dp)
 
   usb_init();
   noct_init();
-  dmx_init();
+  // dmx_init();
   pulse_init();
   mpd_init();
+  mqtt_init();
 
   static struct main_signal term_sig = {
     .signum = SIGTERM,
index d60a426f8f6aac20c4430e97a9986acbe0d6e6fc..6250ff8930e6b567bbc1a387223074025cc7b5ac 100644 (file)
--- a/ursaryd.h
+++ b/ursaryd.h
@@ -1,7 +1,7 @@
 /*
- *     The Ursary Audio Controls
+ *     The Ursary Control Panel
  *
- *     (c) 2014 Martin Mares <mj@ucw.cz>
+ *     (c) 2014--2022 Martin Mares <mj@ucw.cz>
  */
 
 #include <pulse/pulseaudio.h>
@@ -15,6 +15,7 @@ void schedule_update(void);
 void notify_rotary(int rotary, int delta);
 void notify_touch(int rotary, int on);
 void notify_button(int button, int on);
+void notify_mqtt(const char *topic, const char *val);
 
 /* nocturn.c */
 
@@ -134,3 +135,8 @@ void mpd_stop(void);
 void mpd_pause(int arg);
 void mpd_next(void);
 void mpd_prev(void);
+
+/* mqtt.c */
+
+void mqtt_init(void);
+void mqtt_publish(const char *topic, const char *fmt, ...);