]> mj.ucw.cz Git - ursary.git/blob - ursaryd.c
c128e50cce4fd881f18aadafc9e0464a3931f9f4
[ursary.git] / ursaryd.c
1 /*
2  *      The Ursary Audio Controls
3  *
4  *      (c) 2014--2020 Martin Mares <mj@ucw.cz>
5  */
6
7 #undef LOCAL_DEBUG
8
9 #include <ucw/lib.h>
10 #include <ucw/clists.h>
11 #include <ucw/daemon.h>
12 #include <ucw/log.h>
13 #include <ucw/mainloop.h>
14 #include <ucw/opt.h>
15 #include <ucw/stkstring.h>
16
17 #include <math.h>
18 #include <signal.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <syslog.h>
23
24 #include "ursaryd.h"
25 #include "usb.h"
26
27 /*
28  *      Map of all controls
29  *
30  *              rotary          red button      green button
31  *              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32  *      0       sink PCH        mute            switch to PCH
33  *      1       sink BT         mute            switch to BT
34  *      2       -               -               -
35  *      3       desk brightness -               desk lights on
36  *      4       MPD             mute            MPD play/pause
37  *      5       Albireo MPV     mute            MPD stop
38  *      6       Albireo other   mute            MPD prev
39  *      7       other machines  mute            MPD next
40  *
41  *      center  -
42  *      slider  light color temperature
43  */
44
45 #define PCH_SINK "alsa_output.pci-0000_00_1f.3.analog-stereo"
46 #define BT_SINK "bluez_sink.CC_98_8B_D0_8C_06.a2dp_sink"
47
48 /*** Sink controls ***/
49
50 static double volume_from_pa(pa_volume_t vol)
51 {
52   return (double) vol / PA_VOLUME_NORM;
53 }
54
55 static pa_volume_t volume_to_pa(double vol)
56 {
57   return vol * PA_VOLUME_NORM + 0.0001;
58 }
59
60 static void update_ring_from_sink(int ring, const char *sink_name)
61 {
62   struct pulse_sink *s = pulse_sink_by_name(sink_name);
63   if (!s)
64     {
65       noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
66       noct_set_button(ring, 0);
67       return;
68     }
69
70   if (s->mute)
71     {
72       noct_set_ring(ring, RING_MODE_SINGLE_ON, 0x7f);
73       noct_set_button(ring, 1);
74       return;
75     }
76
77   double vol = CLAMP(volume_from_pa(s->volume), 0, 1);
78   noct_set_ring(ring, RING_MODE_LEFT, CLAMP((int)(0x7f * vol), 12, 0x7f));
79   noct_set_button(ring, 0);
80 }
81
82 static void update_sink_from_rotary(int delta, const char *sink_name)
83 {
84   struct pulse_sink *s = pulse_sink_by_name(sink_name);
85   if (!s)
86     return;
87
88   double vol = volume_from_pa(s->volume) + delta * 0.02;
89   pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, 1));
90   if (pavol == s->volume)
91     return;
92   pa_cvolume cvol;
93   pa_cvolume_set(&cvol, s->channels, pavol);
94
95   DBG("## Setting volume of sink %s to %d", s->name, cvol.values[0]);
96   pulse_sink_set_volume(s->idx, &cvol);
97 }
98
99 static void update_sink_mute_from_button(int on, const char *sink_name)
100 {
101   if (!on)
102     return;
103
104   struct pulse_sink *s = pulse_sink_by_name(sink_name);
105   if (!s)
106     return;
107
108   DBG("## Setting mute of sink %s to %d", s->name, !s->mute);
109   pulse_sink_set_mute(s->idx, !s->mute);
110 }
111
112 /*** Client controls ***/
113
114 struct client_map {
115   int group;
116   const char *client;
117   const char *host;
118 };
119
120 static struct client_map client_map[] = {
121   { 4, "Music Player Daemon",   "albireo",      },
122   { 5, "mpv",                   "albireo",      },
123   { 6, NULL,                    "albireo",      },
124   { 7, NULL,                    NULL,           },
125 };
126
127 #define CLIENT_MAP_SIZE ARRAY_SIZE(client_map)
128
129 struct group_config {
130   bool enabled;
131   double range;
132 };
133
134 struct group_state {
135   double volume;
136   bool have_muted[2];
137 };
138
139 #define NUM_GROUPS 9
140
141 static struct group_config group_config[NUM_GROUPS] = {
142   [4] = { .enabled = 1, .range = 1.5 },
143   [5] = { .enabled = 1, .range = 1.5 },
144   [6] = { .enabled = 1, .range = 1.5 },
145   [7] = { .enabled = 1, .range = 1.5 },
146 };
147
148 static struct group_state group_state[NUM_GROUPS];
149
150 static void calc_groups(void)
151 {
152   bzero(group_state, sizeof(group_state));
153
154   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
155     {
156       s->noct_group_idx = -1;
157
158       if (s->client_idx < 0 || s->sink_idx < 0)
159         continue;
160
161       struct pulse_client *c = pulse_client_by_idx(s->client_idx);
162       if (!c)
163         continue;
164
165       for (uns i=0; i < CLIENT_MAP_SIZE; i++)
166         {
167           struct client_map *cm = &client_map[i];
168           if ((!cm->client || !strcmp(cm->client, c->name)) &&
169               (!cm->host || !strcmp(cm->host, c->host)))
170             {
171               int g = cm->group;
172               struct group_state *gs = &group_state[g];
173               DBG("@@ Client #%d, sink input #%d -> group %d", s->client_idx, s->idx, g);
174               s->noct_group_idx = g;
175               gs->volume = MAX(gs->volume, s->volume);
176               gs->have_muted[!!s->mute] = 1;
177               break;
178             }
179         }
180     }
181 }
182
183 static void update_groups(void)
184 {
185   calc_groups();
186
187   for (uns i=0; i < NUM_GROUPS; i++)
188     {
189       struct group_config *gc = &group_config[i];
190       struct group_state *gs = &group_state[i];
191       if (!gc->enabled)
192         continue;
193
194       DBG("@@ Group #%d: mute=%d/%d volume=%.3f/%.3f", i, gs->have_muted[0], gs->have_muted[1], volume_from_pa(gs->volume), gc->range);
195
196       if (!gs->have_muted[0] && !gs->have_muted[1])
197         {
198           noct_set_ring(i, RING_MODE_LEFT, 0);
199           noct_set_button(i, 0);
200         }
201       else if (!gs->have_muted[0])
202         {
203           noct_set_ring(i, RING_MODE_SINGLE_ON, 0x7f);
204           noct_set_button(i, 1);
205         }
206       else
207         {
208           double vol = CLAMP(volume_from_pa(gs->volume), 0, gc->range);
209           int val = 0x7f * vol / gc->range;
210           val = CLAMP(val, 12, 0x7f);
211           noct_set_ring(i, RING_MODE_LEFT, val);
212           noct_set_button(i, 0);
213         }
214     }
215 }
216
217 static void update_group_from_rotary(int i, int delta)
218 {
219   if (i >= NUM_GROUPS)
220     return;
221   struct group_config *gc = &group_config[i];
222   struct group_state *gs = &group_state[i];
223   if (!gc->enabled)
224     return;
225
226   calc_groups();
227   double vol = volume_from_pa(gs->volume) + delta*0.02;
228   pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, gc->range));
229
230   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
231     {
232       if (s->noct_group_idx == i && s->volume != pavol)
233         {
234           DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol);
235           pa_cvolume cvol;
236           pa_cvolume_set(&cvol, s->channels, pavol);
237           pulse_sink_input_set_volume(s->idx, &cvol);
238         }
239     }
240 }
241
242 static void update_group_from_button(int i, int on)
243 {
244   if (!on)
245     return;
246   if (i >= NUM_GROUPS)
247     return;
248   struct group_config *gc = &group_config[i];
249   struct group_state *gs = &group_state[i];
250   if (!gc->enabled)
251     return;
252
253   calc_groups();
254   if (!gs->have_muted[0] && !gs->have_muted[1])
255     return;
256   uns mute = !gs->have_muted[1];
257
258   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
259     {
260       if (s->noct_group_idx == i)
261         {
262           DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute);
263           pulse_sink_input_set_mute(s->idx, mute);
264         }
265     }
266 }
267
268 static int find_touched_client(void)
269 {
270   int touched = -1;
271
272   for (uns i=0; i < NUM_GROUPS; i++)
273     if (group_config[i].enabled && noct_rotary_touched[i])
274       {
275         if (touched >= 0)
276           return -1;
277         touched = i;
278       }
279   return touched;
280 }
281
282 /*** Default sink controls ***/
283
284 static const char *get_client_sink(int i)
285 {
286   const char *sink = NULL;
287
288   CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
289     if (s->noct_group_idx == i)
290       {
291         struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL;
292         const char *ss = sk ? sk->name : NULL;
293         if (!sink)
294           sink = ss;
295         else if (strcmp(sink, ss))
296           sink = "?";
297       }
298   return sink ? : "?";
299 }
300
301 static void update_default_sink(void)
302 {
303   int i = find_touched_client();
304   const char *sink;
305   if (i >= 0)
306     sink = get_client_sink(i);
307   else
308     sink = pulse_default_sink_name ? : "?";
309
310   if (!strcmp(sink, PCH_SINK))
311     {
312       noct_set_button(8, 1);
313       noct_set_button(9, 0);
314     }
315   else if (!strcmp(sink, BT_SINK))
316     {
317       noct_set_button(8, 0);
318       noct_set_button(9, 1);
319     }
320   else
321     {
322       noct_set_button(8, 0);
323       noct_set_button(9, 0);
324     }
325 }
326
327 static void update_default_sink_from_button(int button, int on)
328 {
329   if (!on)
330     return;
331
332   int i = find_touched_client();
333 #if 0
334   const char *sink;
335   if (i >= 0)
336     sink = get_client_sink(i);
337   else
338     sink = pulse_default_sink_name ? : "?";
339 #endif
340
341   const char *switch_to = NULL;
342   if (button == 8)
343     switch_to = PCH_SINK;
344   else if (button == 9)
345     switch_to = BT_SINK;
346
347   if (!switch_to)
348     return;
349
350   if (i >= 0)
351     {
352       struct pulse_sink *sk = pulse_sink_by_name(switch_to);
353       if (!sk)
354         return;
355
356       CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list)
357         if (s->noct_group_idx == i)
358           {
359             DBG("Moving input #%d to sink #%d", s->idx, sk->idx);
360             pulse_sink_input_move(s->idx, sk->idx);
361           }
362     }
363   else
364     {
365       DBG("Switching default sink to %s", switch_to);
366       pulse_server_set_default_sink(switch_to);
367     }
368 }
369
370 /*** MPD controls ***/
371
372 static bool mpd_flash_state;
373
374 static void mpd_flash_timeout(struct main_timer *t)
375 {
376   mpd_flash_state ^= 1;
377   noct_set_button(12, mpd_flash_state);
378   timer_add_rel(t, 500);
379 }
380
381 static struct main_timer mpd_flash_timer = {
382   .handler = mpd_flash_timeout,
383 };
384
385 static void update_mpd(void)
386 {
387   const char *state = mpd_get_player_state();
388   if (!strcmp(state, "play"))
389     {
390       noct_set_button(12, 1);
391       timer_del(&mpd_flash_timer);
392     }
393   else if (!strcmp(state, "pause"))
394     {
395       if (!timer_is_active(&mpd_flash_timer))
396         {
397           mpd_flash_state = 1;
398           mpd_flash_timeout(&mpd_flash_timer);
399         }
400     }
401   else
402     {
403       noct_set_button(12, 0);
404       timer_del(&mpd_flash_timer);
405     }
406 }
407
408 static void mpd_button_timeout(struct main_timer *t)
409 {
410   DBG("MPD stop");
411   timer_del(t);
412   mpd_stop();
413 }
414
415 static void update_mpd_from_button(int button UNUSED, int on)
416 {
417   static struct main_timer mpd_button_timer = {
418     .handler = mpd_button_timeout,
419   };
420
421   const char *state = mpd_get_player_state();
422
423   if (!on)
424     {
425       if (timer_is_active(&mpd_button_timer))
426         {
427           timer_del(&mpd_button_timer);
428           if (!strcmp(state, "play"))
429             {
430               DBG("MPD pause");
431               mpd_pause(1);
432             }
433           else if (!strcmp(state, "pause"))
434             {
435               DBG("MPD resume");
436               mpd_pause(0);
437             }
438         }
439       return;
440     }
441
442   if (!strcmp(state, "stop"))
443     {
444       DBG("MPD play");
445       mpd_play();
446     }
447   else
448     {
449       DBG("MPD starting button timer");
450       timer_add_rel(&mpd_button_timer, 1000);
451     }
452 }
453
454 /*** Lights ***/
455
456 static bool lights_on[2];
457 static double lights_brightness[2];
458 static double lights_temperature[2];
459
460 static void update_lights(void)
461 {
462   if (!dmx_is_ready())
463     {
464       noct_set_ring(3, RING_MODE_SINGLE_ON, 0x7f);
465       noct_set_button(11, 0);
466       return;
467     }
468
469   for (uint i=0; i<1; i++)
470     {
471       uint warm, cold;
472       if (lights_on[i])
473         {
474           noct_set_ring(3, RING_MODE_LEFT, lights_brightness[i] * 127);
475           noct_set_button(11, 1);
476           double r = 2;
477           double x = (exp(r*lights_brightness[i]) - 1) / (exp(r) - 1);
478           double t = lights_temperature[i];
479           double w = 2*x*(1-t);
480           double c = 2*x*t;
481           warm = CLAMP((int)(255*w), 0, 255);
482           cold = CLAMP((int)(255*c), 0, 255);
483         }
484       else
485         {
486           noct_set_ring(3, RING_MODE_LEFT, 0);
487           noct_set_button(11, 0);
488           warm = cold = 0;
489         }
490       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);
491       dmx_set_pwm(2*i, warm);
492       dmx_set_pwm(2*i+1, cold);
493     }
494 }
495
496 static void update_lights_from_rotary(int ch, int delta)
497 {
498   if (lights_on[ch])
499     lights_brightness[ch] = CLAMP(lights_brightness[ch] + 0.015*delta*abs(delta), 0., 1.);
500   update_lights();
501 }
502
503 static void update_lights_from_slider(int value)
504 {
505   lights_temperature[0] = value / 127.;
506   update_lights();
507 }
508
509 static void lights_button_timeout(struct main_timer *t)
510 {
511   int ch = (uintptr_t) t->data;
512   DBG("Lights[%d]: Full throttle!", ch);
513   timer_del(t);
514   lights_on[ch] = 1;
515   lights_brightness[ch] = 1;
516   update_lights();
517 }
518
519 static void update_lights_from_button(int ch, int on)
520 {
521   static struct main_timer lights_button_timer[2] = {{
522     .handler = lights_button_timeout,
523     .data = (void *)(uintptr_t) 0,
524   },{
525     .handler = lights_button_timeout,
526     .data = (void *)(uintptr_t) 1,
527   }};
528
529   if (on)
530     {
531       lights_on[ch] = !lights_on[ch];
532       update_lights();
533       timer_add_rel(&lights_button_timer[ch], 1000);
534     }
535   else
536     timer_del(&lights_button_timer[ch]);
537 }
538
539 /*** Main update routines ***/
540
541 static struct main_timer update_timer;
542 static timestamp_t last_touch_time;
543
544 enum update_state {
545   US_OFFLINE,
546   US_ONLINE,
547   US_SLEEPING,
548   US_PULSE_DEAD,
549 };
550
551 static enum update_state update_state;
552
553 static bool want_sleep_p(void)
554 {
555   CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list)
556     if (!s->suspended)
557       return 0;
558   return 1;
559 }
560
561 static void do_update(struct main_timer *t)
562 {
563   DBG("## UPDATE in state %u", update_state);
564   timer_del(t);
565
566   // Nocturn dead?
567   if (!noct_is_ready())
568     {
569       DBG("## UPDATE: Nocturn is not ready");
570       update_state = US_OFFLINE;
571       return;
572     }
573   else if (update_state == US_OFFLINE)
574     {
575       DBG("## UPDATE: Going online");
576       update_state = US_ONLINE;
577     }
578
579   // Pulse dead?
580   if (!pulse_is_ready())
581     {
582       DBG("## UPDATE: Pulse is not online");
583       if (update_state != US_PULSE_DEAD)
584         {
585           update_state = US_PULSE_DEAD;
586           noct_clear();
587           for (int i=0; i<8; i++)
588             noct_set_button(i, 1);
589         }
590       return;
591     }
592   else if (update_state == US_PULSE_DEAD)
593     {
594       DBG("## UPDATE: Waking up from the dead");
595       update_state = US_ONLINE;
596       noct_clear();
597     }
598
599 #ifdef LOCAL_DEBUG
600   pulse_dump();
601 #endif
602
603   // Sleeping?
604   bool want_sleep = want_sleep_p();
605   if (!want_sleep)
606     last_touch_time = main_get_now();
607   timestamp_t since_touch = last_touch_time ? main_get_now() - last_touch_time : 0;
608   timestamp_t sleep_in = 30000;
609   if (since_touch >= sleep_in)
610     {
611       DBG("UPDATE: Sleeping");
612       if (update_state == US_ONLINE)
613         {
614           update_state = US_SLEEPING;
615           noct_clear();
616           noct_set_ring(8, RING_MODE_LEFT, 127);
617         }
618       return;
619     }
620   else
621     {
622       if (update_state == US_SLEEPING)
623         {
624           DBG("UPDATE: Waking up");
625           update_state = US_ONLINE;
626           noct_clear();
627         }
628       if (want_sleep)
629         {
630           timestamp_t t = sleep_in - since_touch + 10;
631           DBG("UPDATE: Scheduling sleep in %d ms", (int) t);
632           timer_add_rel(&update_timer, t);
633         }
634     }
635
636   // Everything normal
637   update_ring_from_sink(0, PCH_SINK);
638   update_ring_from_sink(1, BT_SINK);
639   update_groups();
640   update_default_sink();
641   update_mpd();
642   update_lights();
643 }
644
645 void schedule_update(void)
646 {
647   timer_add_rel(&update_timer, 10);
648 }
649
650 static bool prepare_notify(void)
651 {
652   if (!pulse_is_ready())
653     {
654       DBG("## NOTIFY: Pulse is not online");
655       return 0;
656     }
657
658   last_touch_time = main_get_now();
659   if (update_state == US_SLEEPING)
660     {
661       DBG("## NOTIFY: Scheduling wakeup");
662       schedule_update();
663     }
664
665   return 1;
666 }
667
668 void notify_rotary(int rotary, int delta)
669 {
670   if (!prepare_notify())
671     return;
672
673   switch (rotary)
674     {
675     case 0:
676       update_sink_from_rotary(delta, PCH_SINK);
677       break;
678     case 1:
679       update_sink_from_rotary(delta, BT_SINK);
680       break;
681     case 3:
682       update_lights_from_rotary(0, delta);
683       break;
684     case 9:
685       update_lights_from_slider(delta);
686       break;
687     default:
688       update_group_from_rotary(rotary, delta);
689     }
690 }
691
692 void notify_button(int button, int on)
693 {
694   if (!prepare_notify())
695     return;
696
697   switch (button)
698     {
699     case 0:
700       update_sink_mute_from_button(on, PCH_SINK);
701       break;
702     case 1:
703       update_sink_mute_from_button(on, BT_SINK);
704       break;
705     case 8:
706     case 9:
707       update_default_sink_from_button(button, on);
708       break;
709     case 11:
710       update_lights_from_button(0, on);
711       break;
712     case 12:
713       update_mpd_from_button(button, on);
714       break;
715     case 13:
716       if (on)
717         mpd_stop();
718       break;
719     case 14:
720       if (on)
721         mpd_prev();
722       break;
723     case 15:
724       if (on)
725         mpd_next();
726       break;
727     default:
728       update_group_from_button(button, on);
729     }
730 }
731
732 void notify_touch(int rotary UNUSED, int on UNUSED)
733 {
734   if (!prepare_notify())
735     return;
736
737   // Rotary touches switch meaning of LEDs, this is handled inside display updates
738   if (rotary >= 4 && rotary < 8)
739     schedule_update();
740 }
741
742 /*** Main entry point ***/
743
744 static int debug;
745 static int no_fork;
746
747 static struct opt_section options = {
748   OPT_ITEMS {
749     OPT_HELP("Control console for the Ursary"),
750     OPT_HELP(""),
751     OPT_HELP("Options:"),
752     OPT_HELP_OPTION,
753     OPT_BOOL('d', "debug", debug, 0, "\tEnable debugging mode (no fork etc.)"),
754     OPT_BOOL(0, "no-fork", no_fork, 0, "\tDo not fork\n"),
755     OPT_END
756   }
757 };
758
759 static void sigterm_handler(struct main_signal *ms UNUSED)
760 {
761   main_shut_down();
762 }
763
764 static void daemon_body(struct daemon_params *dp)
765 {
766   main_init();
767   update_timer.handler = do_update;
768
769   usb_init();
770   noct_init();
771   dmx_init();
772   pulse_init();
773   mpd_init();
774
775   static struct main_signal term_sig = {
776     .signum = SIGTERM,
777     .handler = sigterm_handler,
778   };
779   signal_add(&term_sig);
780
781   msg(L_INFO, "Ursary daemon starting");
782   main_loop();
783   msg(L_INFO, "Ursary daemon shut down");
784   daemon_exit(dp);
785 }
786
787 int main(int argc UNUSED, char **argv)
788 {
789   opt_parse(&options, argv+1);
790   unsetenv("DISPLAY");
791   unsetenv("HOME");
792
793   struct daemon_params dp = {
794     .flags = ((debug || no_fork) ? DAEMON_FLAG_SIMULATE : 0),
795     .pid_file = "/run/ursaryd.pid",
796     .run_as_user = "ursary",
797   };
798   daemon_init(&dp);
799
800   log_init(argv[0]);
801   if (!debug)
802     {
803       struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
804       ls->levels = ~(1U << L_DEBUG);
805       log_set_default_stream(ls);
806     }
807
808   daemon_run(&dp, daemon_body);
809   return 0;
810 }