#include #include #include #include #include #include #include #include #include #include #include "libfn.h" volatile bool shutdown = false; /* will be set to TRUE in our signal handler */ char *httpd_root; /* where static HTML content is located */ int httpd_port; /* TCP port the webserver should listen to */ int fnordlicht_fd; /* file descriptor for serial port */ struct rgb_color_t parse_color(const char *identifier) { struct rgb_color_t color; if (strlen(identifier) != 6) { fprintf(stderr, "Invalid color definition: %s", identifier); } unsigned short int red, green, blue; sscanf(identifier, "%2hX%2hX%2hX", &red, &green, &blue); color.red = red; color.green = green; color.blue = blue; return color; } void quit(int sig) { shutdown = true; } const char * get_filename_ext(char *filename) { const char *dot = strrchr(filename, '.'); if (!dot || dot == filename) { return NULL; } return dot + 1; } const char * get_content_type(char *filename) { const char *ext = get_filename_ext(filename); if (strcmp(ext, "html") == 0) return "text/html"; else if (strcmp(ext, "js") == 0) return "text/javascript"; else if (strcmp(ext, "css") == 0) return "text/css"; else if (strcmp(ext, "png") == 0) return "image/png"; else return "text/plain"; } void print_cmd(void *data, size_t n) { printf("Command sent: "); int i; for (i = 0; i < n; i++) { printf("%02X", *((uint8_t *) data+i)); } printf("\n"); } int load_file(const char *filename, char **result) { int size = 0; FILE *f = fopen(filename, "rb"); if (f == NULL) { *result = NULL; return -1; /* failed to open file */ } fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); *result = (char *) malloc(size+1); if (*result == NULL) { return -3; /* failed to allocate memory */ } if (size != fread(*result, sizeof(char), size, f)) { free(*result); return -2; /* failed to read file */ } fclose(f); (*result)[size] = '\0'; /* zero terminated */ return size; } int handle_request(void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls) { static struct rgb_color_t current = {{{0, 0, 0}}}; /* start with black */ if (strcasecmp(method, "get") == 0) { struct MHD_Response *response; char *filename = malloc(1024); /* max path length */ if (strcmp(url+1, "color") == 0) { char *color_str = malloc(8); snprintf(color_str, 7, "%02x%02x%02x\n", current.red, current.green, current.blue); response = MHD_create_response_from_data(strlen(color_str), (void *) color_str, 1, 0); MHD_add_response_header(response, "Content-type", "text/plain"); } else if (strcmp(url+1, "count") == 0) { char *count_str = malloc(5); snprintf(count_str, 4, "%i\n", fn_count_devices(fnordlicht_fd)); response = MHD_create_response_from_data(strlen(count_str), (void *) count_str, 1, 0); MHD_add_response_header(response, "Content-type", "text/plain"); } else { /* get file from disk */ /* construct filename */ filename = getcwd(filename, 1024); filename = strncat(filename, httpd_root, 1024); filename = strncat(filename, (strlen(url) > 1) ? url : "/index.html", 1024); /* try to open file */ char *response_str; int resonse_size = load_file(filename, &response_str); if (resonse_size > 0) { response = MHD_create_response_from_data(resonse_size, (void *) response_str, 1, 0); MHD_add_response_header(response, "Content-type", get_content_type(filename)); } else { const char *error_str = "

File not found!

"; fprintf(stderr, "Failed to open file: %s\n", filename); response = MHD_create_response_from_data(strlen (error_str), (void *) error_str, 0, 0); } } if (response) { int ret = MHD_queue_response(connection, MHD_HTTP_OK, response); MHD_destroy_response(response); return ret; } else { return MHD_NO; } } else if (strcasecmp(method, "post") == 0) { struct remote_msg_t msg; struct MHD_Response *response; const char *response_str; memset(&msg, 0, sizeof(struct remote_msg_t)); const char *address = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "address"); const char *mask = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "mask"); msg.address = (address) ? atoi(address) : REMOTE_ADDR_BROADCAST; if (strcmp(url+1, "fade") == 0) { const char *color = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "color"); const char *step = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "step"); const char *delay = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "delay"); if (color == NULL) { return MHD_NO; } current = parse_color(color); /* parse and updating current color */ printf("Fading to color: #%02x%02x%02x\n", current.red, current.green, current.blue); msg.cmd = REMOTE_CMD_FADE_RGB; msg.fade_rgb.step = (step) ? atoi(step) : 255; msg.fade_rgb.delay = (delay) ? atoi(delay) : 0; msg.fade_rgb.color = current; } else if (strcmp(url+1, "start") == 0) { // TODO not working: padding bytes in program_params_t? const char *script = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "script"); union program_params_t params; switch (atoi(script)) { case 0: printf("Start program: colorwheel\n"); params.colorwheel.hue_start = 0; params.colorwheel.hue_step = 60; params.colorwheel.add_addr = 0; params.colorwheel.saturation = 255; params.colorwheel.value = 255; params.colorwheel.fade_step = 1; params.colorwheel.fade_delay = 2; params.colorwheel.fade_sleep = 0; break; case 1: printf("Start program: random\n"); params.random.seed = (uint16_t) (rand() % 0xffff); params.random.use_address = 0; params.random.wait_for_fade = 1; params.random.min_distance = 60; params.random.saturation = 255; params.random.value = 255; params.random.fade_step = 1; params.random.fade_delay = 3; params.random.fade_sleep = 100; break; default: return MHD_NO; } msg.cmd = REMOTE_CMD_START_PROGRAM; msg.start_program.script = atoi(script); msg.start_program.params = params; memset(&msg.start_program.params, 0xff, 3); } else if (strcmp(url+1, "stop") == 0) { msg.cmd = REMOTE_CMD_STOP; printf("Stop fading\n"); } else if (strcmp(url+1, "shutdown") == 0) { msg.cmd = REMOTE_CMD_POWERDOWN; printf("Shutdown fnordlichts\n"); } else { return MHD_NO; } /* send command */ int p = (mask) ? fn_send_mask(fnordlicht_fd, mask, &msg) : fn_send(fnordlicht_fd, &msg); print_cmd(&msg, sizeof(msg)); printf("Sent %i bytes to fnordlichts\n", p); response_str = (p > 0) ? "success" : "failed"; response = MHD_create_response_from_data(strlen(response_str), (void *) response_str, 1, 1); int ret = MHD_queue_response(connection, (p > 0) ? MHD_HTTP_OK : MHD_HTTP_INTERNAL_SERVER_ERROR, response); MHD_destroy_response(response); return ret; } else { return MHD_NO; } } int main(int argc, char *argv[]) { /* bind signals */ struct sigaction action; sigemptyset(&action.sa_mask); action.sa_flags = 0; action.sa_handler = quit; sigaction(SIGINT, &action, NULL); /* catch ctrl-c from terminal */ sigaction(SIGHUP, &action, NULL); /* catch hangup signal */ sigaction(SIGTERM, &action, NULL); /* catch kill signal */ if (argc < 3 || argc > 4) { fprintf(stderr, "usage: fnweb SERIAL-PORT WEB-DIRECTORY [HTTPD-PORT]\n"); return EXIT_FAILURE; } /* connect to fnordlichts */ fnordlicht_fd = open(argv[1], O_RDWR | O_NOCTTY); if (fnordlicht_fd < 0) { fprintf(stderr, "Failed to open fnordlichts: %s\n", strerror(errno)); exit(EXIT_FAILURE); } struct termios oldtio = fn_init(fnordlicht_fd); int p = fn_sync(fnordlicht_fd); if (p <= 0) { fprintf(stderr, "Failed to initialize fnordlichts!\n"); exit(EXIT_FAILURE); } /* seed PNRG for random program */ srand(time(0)); /* start embedded HTTPd */ httpd_port = (argc == 4) ? atoi(argv[3]) : 80; /* default port */ httpd_root = argv[2]; printf("Starting HTTPd on port: %i with root dir: %s\n", httpd_port, httpd_root); struct MHD_Daemon *httpd = MHD_start_daemon( MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, httpd_port, NULL, NULL, &handle_request, NULL, //MHD_OPTION_CONNECTION_LIMIT, 1, MHD_OPTION_END ); if (httpd == NULL) { fprintf(stderr, "Failed to start HTTPd\n"); return EXIT_FAILURE; } /* busy loop */ while (!shutdown) { } /* stop embedded HTTPd */ MHD_stop_daemon(httpd); /* reset and close connection */ tcsetattr(fnordlicht_fd, TCSANOW, &oldtio); close(fnordlicht_fd); return EXIT_SUCCESS; }