diff --git a/raspberry/ledcontrol/ledcontrol.c b/raspberry/ledcontrol/ledcontrol.c index b358bf1..e50ae00 100644 --- a/raspberry/ledcontrol/ledcontrol.c +++ b/raspberry/ledcontrol/ledcontrol.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "../common/i2c_command.h" #include "../common/mqtt_service.h" @@ -16,6 +17,10 @@ const char* MQTT_HOST = "platon.n39.eu"; const int MQTT_PORT = 1883; const char* MQTT_AMPEL_TOPIC = "Netz39/Things/Ampel/Light"; +const char* MQTT_NIGHTMODE_TOPIC= "Netz39/Nightmode"; + +/* Duration (seconds) to keep LEDs on after an ampel change during nightmode */ +#define NIGHTMODE_VISIBILITY_SECS 2 struct ampel_state_t { bool red; @@ -23,6 +28,16 @@ struct ampel_state_t { bool blink; }; +/* Global nightmode state */ +static bool nightmode_active = false; + +/* Most recently requested ampel state (independent of physical output) */ +static struct ampel_state_t requested_state = { false, false, false }; + +/* Monotonic time (seconds) when the brief nightmode visibility expires; + * 0 means no timer is running. */ +static time_t nightmode_show_until = 0; + ///// I2C stuff ///// /** @@ -55,6 +70,16 @@ void I3C_reset_ampel() { ///// Ampel ///// +static struct ampel_state_t AMPEL_OFF = { false, false, false }; +static struct ampel_state_t AMPEL_RED = { true, false, false }; +static struct ampel_state_t AMPEL_GREEN = { false, true, false }; + +static time_t monotonic_now(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec; +} + uint8_t ampel_set_color(struct ampel_state_t color) { uint8_t val = 0; val |= color.red ? AMPEL_VAL_RED : 0; @@ -68,6 +93,23 @@ uint8_t ampel_set_color(struct ampel_state_t color) { return ret; } +/** + * Apply physical LED output based on current nightmode state. + * During nightmode the LEDs stay off unless the brief visibility timer is + * active. Outside nightmode the requested state is shown as-is. + */ +static void apply_physical_state(void) { + if (!nightmode_active) { + ampel_set_color(requested_state); + return; + } + + if (nightmode_show_until != 0 && monotonic_now() < nightmode_show_until) + ampel_set_color(requested_state); + else + ampel_set_color(AMPEL_OFF); +} + ///// Command events void mqtt_message_callback(struct mosquitto *mosq, @@ -79,9 +121,9 @@ void mqtt_message_callback(struct mosquitto *mosq, else syslog(LOG_INFO, "Got empty message for topic '%s'\n", message->topic); - bool match = false; - mosquitto_topic_matches_sub(MQTT_AMPEL_TOPIC, message->topic, &match); - if (match) { + bool match_ampel = false; + mosquitto_topic_matches_sub(MQTT_AMPEL_TOPIC, message->topic, &match_ampel); + if (match_ampel) { const char* command = message->payload; struct ampel_state_t state = { .red = false, .green = false, .blink = false }; @@ -101,8 +143,35 @@ void mqtt_message_callback(struct mosquitto *mosq, state.blink = true; } - // Set the traffic light state - ampel_set_color(state); + requested_state = state; + + if (nightmode_active) { + /* Show state briefly; (re)start the visibility timer */ + nightmode_show_until = monotonic_now() + NIGHTMODE_VISIBILITY_SECS; + } + + apply_physical_state(); + return; + } + + bool match_nightmode = false; + mosquitto_topic_matches_sub(MQTT_NIGHTMODE_TOPIC, message->topic, &match_nightmode); + if (match_nightmode) { + const char* payload = message->payload; + if (!payload) + return; + + if (strcmp(payload, "on") == 0) { + syslog(LOG_INFO, "Nightmode enabled."); + nightmode_active = true; + nightmode_show_until = 0; + ampel_set_color(AMPEL_OFF); + } else if (strcmp(payload, "off") == 0) { + syslog(LOG_INFO, "Nightmode disabled."); + nightmode_active = false; + nightmode_show_until = 0; + ampel_set_color(requested_state); + } } } @@ -114,6 +183,14 @@ int main(int argc, char *argv[]) { // initialize I2C I2C_init(); + // Show a start-up sequence + ampel_set_color(AMPEL_RED); + sleep(1); + ampel_set_color(AMPEL_GREEN); + sleep(1); + ampel_set_color(AMPEL_OFF); + sleep(1); + // initialize MQTT struct mosquitto *mosq = mqtt_service_init("ampel"); @@ -127,15 +204,27 @@ int main(int argc, char *argv[]) { mosquitto_message_callback_set(mosq, mqtt_message_callback); mosquitto_subscribe(mosq, NULL, MQTT_AMPEL_TOPIC, 0); + mosquitto_subscribe(mosq, NULL, MQTT_NIGHTMODE_TOPIC, 0); } service_notify_ready(); while (service_is_running()) { mqtt_service_loop(mosq, 100); + + /* During nightmode, check if the brief visibility timer has just expired */ + if (nightmode_active && nightmode_show_until != 0 + && monotonic_now() >= nightmode_show_until) { + nightmode_show_until = 0; + ampel_set_color(AMPEL_OFF); + } + sleep(1); } + // Leave illumination in a safe state on shutdown. + ampel_set_color(AMPEL_OFF); + mqtt_service_cleanup(mosq); service_stop("Ampel controller finished."); return 0;