8 #include <sys/socket.h>
13 #include <readline/readline.h>
14 #include <readline/history.h>
16 #include <murphy/common.h>
19 #define DEFAULT_PROMPT "test-pep> "
27 const char *addrstr; /* server address */
28 int zone; /* run in zone control mode */
29 int verbose; /* verbose mode */
31 mrp_mainloop_t *ml; /* murphy mainloop */
32 void *pep; /* enforcement point client */
33 int fd; /* fd for terminal input */
34 mrp_io_watch_t *iow; /* I/O watch for terminal input */
42 * device and stream definitions
45 #define NDEVICE (MRP_ARRAY_SIZE(devices) - 1)
46 #define DEVICE_NCOLUMN 4
55 static device_t devices[] = {
56 { "builtin-speaker" , "speaker" , TRUE , TRUE },
57 { "builtin-earpiece", "speaker" , FALSE, TRUE },
58 { "usb-speaker" , "speaker" , TRUE , FALSE },
59 { "a2dp-speaker" , "speaker" , TRUE , FALSE },
60 { "wired-headset" , "headset" , FALSE, FALSE },
61 { "usb-headphone" , "headphone", FALSE, FALSE },
62 { "a2dp-headphone" , "headphone", FALSE, FALSE },
63 { "sco-headset" , "headset" , FALSE, FALSE },
64 { NULL , NULL , FALSE, FALSE }
67 #define NSTREAM (MRP_ARRAY_SIZE(streams) - 1)
68 #define STREAM_NCOLUMN 4
77 static stream_t streams[] = {
78 { "player1", "player" , 1234, FALSE },
79 { "player2", "player" , 4321, FALSE },
80 { "navit" , "navigator", 5432, FALSE },
81 { "phone" , "call" , 6666, FALSE },
82 { NULL , NULL , 0 , FALSE }
87 * device and stream descriptors
90 MRP_PEP_TABLE_COLUMNS(device_columns,
91 MRP_PEP_STRING ("name", 32 , TRUE ),
92 MRP_PEP_STRING ("type", 32 , FALSE),
93 MRP_PEP_INTEGER("public" , FALSE),
94 MRP_PEP_INTEGER("available", FALSE));
96 MRP_PEP_TABLE_COLUMNS(stream_columns,
97 MRP_PEP_STRING ("name", 32, TRUE ),
98 MRP_PEP_STRING ("role", 32, FALSE),
99 MRP_PEP_UNSIGNED("owner" , FALSE),
100 MRP_PEP_INTEGER ("playing" , FALSE));
102 MRP_PEP_TABLES(media_tables,
103 MRP_PEP_TABLE("devices", device_columns),
104 MRP_PEP_TABLE("streams", stream_columns));
108 * zone and call definitions
111 #define NZONE (MRP_ARRAY_SIZE(zones) - 1)
112 #define ZONE_NCOLUMN 3
120 static zone_t zones[] = {
121 { "driver" , TRUE , FALSE },
122 { "fearer" , FALSE, TRUE },
123 { "back-left" , TRUE , FALSE },
124 { "back-center", FALSE, FALSE },
125 { "back-right" , TRUE , TRUE },
126 { NULL , FALSE, FALSE }
130 #define NCALL (MRP_ARRAY_SIZE(calls) - 1)
131 #define CALL_NCOLUMN 3
139 static call_t calls[] = {
140 { 1, "active" , "modem1" },
141 { 2, "ringing" , "modem1" },
142 { 3, "held" , "modem2" },
143 { 4, "alerting", "modem2" },
149 * zone and call descriptors
152 MRP_PEP_TABLE_COLUMNS(zone_columns,
153 MRP_PEP_STRING ("name", 32, TRUE),
154 MRP_PEP_INTEGER("occupied", FALSE),
155 MRP_PEP_INTEGER("active" , FALSE));
157 MRP_PEP_TABLE_COLUMNS(call_columns,
158 MRP_PEP_INTEGER("id" , TRUE ),
159 MRP_PEP_STRING ("state", 32, FALSE),
160 MRP_PEP_STRING ("modem", 32, FALSE));
162 MRP_PEP_TABLES(zone_tables,
163 MRP_PEP_TABLE("zones", zone_columns),
164 MRP_PEP_TABLE("calls", call_columns));
168 mrp_pep_table_t *exports;
170 mrp_pep_table_t *imports;
174 static client_t *client;
178 static void fatal_msg(int error, const char *format, ...);
179 static void error_msg(const char *format, ...);
180 static void info_msg(const char *format, ...);
182 static void terminal_input_cb(char *input);
184 static void export_data(client_t *c);
187 static void plug_device(client_t *c, const char *name, int plug)
193 error_msg("cannot plug/unplug, client is in zone mode");
199 for (d = devices; d->name != NULL; d++) {
200 if (!strcmp(d->name, name)) {
201 changed = plug ^ d->available;
208 info_msg("device '%s' is now %splugged", d->name, plug ? "" : "un");
214 static void list_devices(void)
219 for (d = devices, n = 0; d->name != NULL; d++, n++) {
220 info_msg("device '%s': (%s, %s), %s",
221 d->name, d->type, d->public ? "public" : "private",
222 d->available ? "available" : "currently unplugged");
226 info_msg("devices: none");
230 static void play_stream(client_t *c, const char *name, int play)
236 error_msg("cannot control streams, client is in zone mode");
242 for (s = streams; s->name != NULL; s++) {
243 if (!strcmp(s->name, name)) {
244 changed = play ^ s->playing;
251 info_msg("stream '%s' is now %s", s->name, play ? "playing":"stopped");
257 static void list_streams(void)
262 for (s = streams, n = 0; s->name != NULL; s++, n++) {
263 info_msg("stream '%s': role %s, owner %u, currently %splaying",
264 s->name, s->role, s->owner, s->playing ? "" : "not ");
268 info_msg("streams: none");
272 static void set_zone_state(client_t *c, char *config)
275 int occupied, active, changed, len;
276 char name[256], *end;
279 error_msg("cannot control zones, client is not in zone mode");
283 while (*config == ' ' || *config == '\t')
286 end = strchr(config, ' ');
291 strncpy(name, config, len);
295 while (*config == ' ' || *config == '\t')
302 if (strstr(config, "occupied"))
304 if (strstr(config, "active"))
307 for (z = zones; z->name != NULL; z++) {
308 if (!strcmp(z->name, name)) {
309 changed = (active & z->active) | (occupied ^ z->occupied);
311 z->occupied = occupied;
317 info_msg("zone '%s' is now %s and %s", z->name,
318 z->occupied ? "occupied" : "free",
319 z->active ? "actvie" : "idle");
325 static void list_zones(void)
330 for (z = zones, n = 0; z->name != NULL; z++, n++) {
331 info_msg("zone '%s' is now %s and %s", z->name,
332 z->occupied ? "occupied" : "free",
333 z->active ? "actvie" : "idle");
337 info_msg("zones: none");
341 static void set_call_state(client_t *c, const char *config)
344 char idstr[64], *state, *end;
345 int id, changed, len;
348 error_msg("cannot control calls, client is not in zone mode");
352 while (*config == ' ' || *config == '\t')
355 end = strchr(config, ' ');
360 strncpy(idstr, config, len);
364 while (*config == ' ' || *config == '\t')
366 state = (char *)config;
368 id = strtoul(idstr, &end, 10);
371 error_msg("invalid call id '%s'", idstr);
375 for (call = calls; call->id > 0; call++) {
376 if (call->id == id) {
377 if (strcmp(call->state, state)) {
378 mrp_free((char *)call->state);
379 call->state = mrp_strdup(state);
387 info_msg("call #%d is now %s", call->id, call->state);
393 static void list_calls(void)
398 for (c = calls, n = 0; c->id > 0; c++, n++) {
399 info_msg("call #%d: %s (on modem %s)", c->id, c->state, c->modem);
403 info_msg("calls: none");
407 static void reset_devices(void)
413 void update_devices(mrp_pep_data_t *data)
419 if (data->ncolumn != DEVICE_NCOLUMN) {
420 error_msg("incorrect number of columns (%d) in device update",
425 if (data->nrow > (int)NDEVICE) {
426 error_msg("too many rows (%d) in device update", data->nrow);
436 for (i = 0; i < data->nrow; i++) {
437 mrp_free((char *)d->name);
438 mrp_free((char *)d->type);
440 d->name = mrp_strdup(v[0].str);
441 d->type = mrp_strdup(v[1].str);
442 d->public = v[2].s32;
443 d->available = v[3].s32;
454 static void reset_streams(void)
460 void update_streams(mrp_pep_data_t *data)
466 if (data->ncolumn != STREAM_NCOLUMN) {
467 error_msg("incorrect number of columns (%d) in stream update",
472 if (data->nrow > (int)NSTREAM) {
473 error_msg("too many rows (%d) in stream update", data->nrow);
483 for (i = 0; i < data->nrow; i++) {
484 mrp_free((char *)s->name);
485 mrp_free((char *)s->role);
487 s->name = mrp_strdup(v[0].str);
488 s->role = mrp_strdup(v[1].str);
490 s->playing = v[3].s32;
500 static void reset_zones(void)
506 void update_zones(mrp_pep_data_t *data)
512 if (data->ncolumn != ZONE_NCOLUMN) {
513 error_msg("incorrect number of columns (%d) in zone update",
518 if (data->nrow > (int)NZONE) {
519 error_msg("too many rows (%d) in zone update", data->nrow);
530 for (i = 0; i < data->nrow; i++) {
531 mrp_free((char *)z->name);
533 z->name = mrp_strdup(v[0].str);
534 z->occupied = v[1].s32;
535 z->active = v[2].s32;
546 static void reset_calls(void)
552 void update_calls(mrp_pep_data_t *data)
558 if (data->ncolumn != CALL_NCOLUMN) {
559 error_msg("incorrect number of columns (%d) in call update.",
564 if (data->nrow > (int)NCALL) {
565 error_msg("too many rows (%d) in call update", data->nrow);
575 for (i = 0; i < data->nrow; i++) {
576 mrp_free((char *)c->state);
577 mrp_free((char *)c->modem);
580 c->state = mrp_strdup(v[1].str);
581 c->modem = mrp_strdup(v[2].str);
592 void update_imports(client_t *c, mrp_pep_data_t *data, int ntable)
596 for (i = 0; i < ntable; i++) {
599 update_devices(data + i);
601 update_streams(data + i);
605 update_zones(data + i);
607 update_calls(data + i);
614 static void terminal_prompt_erase(void)
616 int n = strlen(DEFAULT_PROMPT);
625 static void terminal_prompt_display(void)
627 rl_callback_handler_remove();
628 rl_callback_handler_install(DEFAULT_PROMPT, terminal_input_cb);
632 static void show_help(void)
636 P("Available commands:");
637 P(" help show this help");
638 P(" list list all data");
639 P(" list {devices|streams|zones|calls} list the requested data");
640 P(" plug <device> update <device> as plugged");
641 P(" unplug <device> update <device> as unplugged");
642 P(" play <stream> update <stream> as playing");
643 P(" stop <stream> update <stream> as stopped");
644 P(" call <call> <state> update state of <call>");
645 P(" zone <zone> [occupied,[active]] update state of <zone>");
651 static void terminal_process_input(char *input)
657 if (input == NULL || !strcmp(input, "exit")) {
658 terminal_prompt_erase();
661 else if (!strcmp(input, "help")) {
664 else if (!strcmp(input, "list")) {
670 else if (!strcmp(input, "list devices"))
672 else if (!strcmp(input, "list streams"))
674 else if (!strcmp(input, "list zones"))
676 else if (!strcmp(input, "list calls"))
678 else if (!strncmp(input, "plug " , len=sizeof("plug ") - 1) ||
679 !strncmp(input, "unplug ", len=sizeof("unplug ") - 1)) {
680 plug_device(client, input + len, *input == 'p');
682 else if (!strncmp(input, "play " , len=sizeof("play ") - 1) ||
683 !strncmp(input, "stop ", len=sizeof("stop ") - 1)) {
684 play_stream(client, input + len, *input == 'p');
686 else if (!strncmp(input, "call " , len=sizeof("call ") - 1)) {
687 set_call_state(client, input + len);
689 else if (!strncmp(input, "zone " , len=sizeof("zone ") - 1)) {
690 set_zone_state(client, input + len);
695 static void terminal_input_cb(char *input)
697 terminal_process_input(input);
702 static void terminal_cb(mrp_mainloop_t *ml, mrp_io_watch_t *w, int fd,
703 mrp_io_event_t events, void *user_data)
707 MRP_UNUSED(user_data);
709 if (events & MRP_IO_EVENT_IN)
710 rl_callback_read_char();
712 if (events & MRP_IO_EVENT_HUP)
713 mrp_mainloop_quit(ml, 0);
717 static void terminal_setup(client_t *c)
719 mrp_io_event_t events;
721 c->fd = fileno(stdin);
722 events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP;
723 c->iow = mrp_add_io_watch(c->ml, c->fd, events, terminal_cb, c);
726 fatal_msg(1, "Failed to create terminal input I/O watch.");
728 terminal_prompt_display();
732 static void terminal_cleanup(client_t *c)
734 mrp_del_io_watch(c->iow);
737 rl_callback_handler_remove();
741 static void fatal_msg(int error, const char *format, ...)
745 terminal_prompt_erase();
747 fprintf(stderr, "fatal error: ");
748 va_start(ap, format);
749 vfprintf(stderr, format, ap);
751 fprintf(stderr, "\n");
758 static void error_msg(const char *format, ...)
762 terminal_prompt_erase();
764 fprintf(stderr, "error: ");
765 va_start(ap, format);
766 vfprintf(stderr, format, ap);
768 fprintf(stderr, "\n");
771 terminal_prompt_display();
775 static void info_msg(const char *format, ...)
779 terminal_prompt_erase();
781 va_start(ap, format);
782 vfprintf(stdout, format, ap);
784 fprintf(stdout, "\n");
787 terminal_prompt_display();
791 static void signal_handler(mrp_mainloop_t *ml, mrp_sighandler_t *h,
792 int signum, void *user_data)
795 MRP_UNUSED(user_data);
799 info_msg("Got SIGINT, stopping...");
800 mrp_mainloop_quit(ml, 0);
806 static void connect_notify(mrp_pep_t *pep, int connected, int errcode,
807 const char *errmsg, void *user_data)
810 MRP_UNUSED(user_data);
813 info_msg("Successfully registered to server.");
817 error_msg("No connection to server (%d: %s).", errcode, errmsg);
821 static void data_notify(mrp_pep_t *pep, mrp_pep_data_t *tables,
822 int ntable, void *user_data)
824 client_t *client = (client_t *)user_data;
828 update_imports(client, tables, ntable);
832 static void export_notify(mrp_pep_t *pep, int errcode, const char *errmsg,
836 MRP_UNUSED(user_data);
839 error_msg("Data set request failed (%d: %s).", errcode, errmsg);
844 static void export_data(client_t *c)
846 mrp_pep_value_t values[NVALUE], *col, *val;
847 mrp_pep_data_t tables[2];
855 tables[0].nrow = NDEVICE;
856 tables[0].columns = col;
858 for (i = 0; i < (int)NDEVICE; i++) {
859 col[0].str = devices[i].name;
860 col[1].str = devices[i].type;
861 col[2].s32 = devices[i].public;
862 col[3].s32 = devices[i].available;
867 tables[1].nrow = NSTREAM;
868 tables[1].columns = col;
870 for (i = 0; i < (int)NSTREAM; i++) {
871 col[0].str = streams[i].name;
872 col[1].str = streams[i].role;
873 col[2].u32 = streams[i].owner;
874 col[3].s32 = streams[i].playing;
880 tables[0].nrow = NZONE;
881 tables[0].columns = col;
883 for (i = 0; i < (int)NZONE; i++) {
884 col[0].str = zones[i].name;
885 col[1].s32 = zones[i].occupied;
886 col[2].s32 = zones[i].active;
891 tables[1].nrow = NCALL;
892 tables[1].columns = col;
894 for (i = 0; i < (int)NCALL; i++) {
895 col[0].s32 = calls[i].id;
896 col[1].str = calls[i].state;
897 col[2].str = calls[i].modem;
902 if (!mrp_pep_set_data(c->pep, tables, MRP_ARRAY_SIZE(tables),
904 error_msg("Failed to send data set request to server.");
908 static void client_setup(client_t *c)
913 ml = mrp_mainloop_create();
917 exports = media_tables;
918 nexport = MRP_ARRAY_SIZE(media_tables);
919 imports = zone_tables;
920 nimport = MRP_ARRAY_SIZE(zone_tables);
923 exports = zone_tables;
924 nexport = MRP_ARRAY_SIZE(zone_tables);
925 imports = media_tables;
926 nimport = MRP_ARRAY_SIZE(media_tables);
929 pep = mrp_pep_create(c->zone ? "zone-pep" : "media-pep", ml,
930 exports, nexport, imports, nimport,
931 connect_notify, data_notify, c);
937 mrp_add_sighandler(ml, SIGINT, signal_handler, c);
943 for (z = zones; z->name != NULL; z++) {
944 z->name = mrp_strdup(z->name);
947 for (call = calls; call->id > 0; call++) {
948 call->state = mrp_strdup(call->state);
949 call->modem = mrp_strdup(call->modem);
959 for (d = devices; d->name != NULL; d++) {
960 d->name = mrp_strdup(d->name);
961 d->type = mrp_strdup(d->type);
964 for (s = streams; s->name != NULL; s++) {
965 s->name = mrp_strdup(s->name);
966 s->role = mrp_strdup(s->role);
974 fatal_msg(1, "Failed to create enforcement point.");
977 fatal_msg(1, "Failed to create mainloop.");
981 static void client_cleanup(client_t *c)
983 mrp_mainloop_destroy(c->ml);
984 mrp_pep_destroy(c->pep);
991 static void client_run(client_t *c)
993 if (mrp_pep_connect(c->pep, c->addrstr))
994 info_msg("Connected to server at %s.", c->addrstr);
996 error_msg("Failed to connect to server at %s.", c->addrstr);
998 mrp_mainloop_run(c->ml);
1002 static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
1012 printf("usage: %s [options]\n\n"
1013 "The possible options are:\n"
1014 " -s, --server <address> connect to murphy at given address\n"
1015 " -z, --zone run as zone controller\n"
1016 " -v, --verbose run in verbose mode\n"
1017 " -h, --help show this help on usage\n",
1027 static void client_set_defaults(client_t *c)
1030 c->addrstr = MRP_DEFAULT_PEP_ADDRESS;
1036 int parse_cmdline(client_t *c, int argc, char **argv)
1038 # define OPTIONS "vzhs:"
1039 struct option options[] = {
1040 { "server" , required_argument, NULL, 's' },
1041 { "zone" , no_argument , NULL, 'z' },
1042 { "verbose" , optional_argument, NULL, 'v' },
1043 { "help" , no_argument , NULL, 'h' },
1044 { NULL, 0, NULL, 0 }
1049 client_set_defaults(c);
1051 while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
1062 c->addrstr = optarg;
1066 print_usage(argv[0], -1, "");
1071 print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
1079 int main(int argc, char *argv[])
1083 client_set_defaults(&c);
1084 parse_cmdline(&c, argc, argv);
1092 terminal_cleanup(&c);