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
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 .#*`
--- /dev/null
+/*
+ * 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");
+}
/*
- * 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
#include <ucw/mainloop.h>
#include <ucw/opt.h>
#include <ucw/stkstring.h>
+#include <ucw/string.h>
#include <math.h>
#include <signal.h>
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)
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)
{
timer_del(&lights_button_timer[ch]);
lights_on[ch] = !lights_on[ch];
- update_lights();
+ send_lights(ch);
}
}
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;
usb_init();
noct_init();
- dmx_init();
+ // dmx_init();
pulse_init();
mpd_init();
+ mqtt_init();
static struct main_signal term_sig = {
.signum = SIGTERM,
/*
- * 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>
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 */
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, ...);