diff --git a/main/leds.c b/main/leds.c new file mode 100644 index 0000000..35962d5 --- /dev/null +++ b/main/leds.c @@ -0,0 +1,226 @@ +#include "leds.h" + +#include +#include +#include +#include +#include "shared.h" +#include "esp8266/gpio_register.h" +#include "rom/ets_sys.h" +#include "driver/gpio.h" + +uint32_t g_serial_out_buffer[122]; +union Led* g_leds = ((union Led*)g_serial_out_buffer + 1); + +struct Gradient g_default_gradient; +struct Gradient g_current_gradient; +int g_leds_are_default = 1; +enum LedsSendStatus g_leds_send_state = LEDS_SEND_WAITING; + +SemaphoreHandle_t g_led_mutex; // mutex governing access to data for leds + // use for all global data defined in this header + +struct LedThreadData g_led_thread_data = { + .task = 0, + .func = &leds_thread +}; + +/// ======================================= +// leds.h internal helper functions +/// ======================================= + +static inline +uint8_t lerp_uint8(uint8_t a, uint8_t b, float t) { + if(t <= 0) return a; + else if(t >= 1.0) return b; + else { + int dir = b - a; + return a + dir * t; + } +} + +static inline +void lerp_led(union Led* out, const union Led* from, const union Led* to, float t) { + out->components.red = lerp_uint8(from->components.red, to->components.red, t); + out->components.green = lerp_uint8(from->components.green, to->components.green, t); + out->components.blue = lerp_uint8(from->components.blue, to->components.blue, t); + uint8_t glob_from = from->components.global & ~0xE0; + uint8_t glob_to = to->components.global & ~0xE0; + out->components.global = GLOBAL(lerp_uint8(glob_from, glob_to, t)); +} + +static inline +void lerp_points_between(const struct GradientPoint from, const struct GradientPoint to) { + const int dif = to.offset - from.offset; + float t = 0.f; + for(int led = from.offset; led <= to.offset; ++led) { + t = (float)(led - from.offset) / (float)dif; + lerp_led(g_leds + led, &from.led, &to.led, t); + } +} + +static +void leds_config_gpio() { + gpio_config_t config = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 0x18030, + .pull_up_en = 0, + .pull_down_en = 0, + }; + gpio_config(&config); +} + +static +void serial_write(int high) { + // set clock out to high, triggering a rising edge + gpio_set_level(CLOCK, 1); + // write bit to data out + gpio_set_level(DATA, high); + + ets_delay_us(1); + + // set clock to low, triggering a falling edge, shifting the LEDs shift register + gpio_set_level(CLOCK, 0); +} + + +/// ======================================= +// leds.h external interface functions +/// ======================================= + + +void send_leds() { + // index of the bit being written + // fixed point number where the first 5 bits are the bit of a 32bit integger, and the rest is the integer + int write_bit = 0; + int write_next = 0; + + gpio_set_level(CLOCK, 0); + + while(write_bit < sizeof(g_serial_out_buffer) * 8) { + // fetch the bit being addressed + write_next = 0x1 & (g_serial_out_buffer[write_bit >> 5] >> (32 - (write_bit & 0x1F))); + + serial_write(write_next); + + write_bit++; + } + + gpio_set_level(CLOCK, 0); + gpio_set_level(DATA, 0); + +} + +void set_led_range(int start, int end, union Led value) { + for(int i = start; i < end; ++i) { + g_leds[i] = value; + } +} + +void leds_set_default_gradient(const struct Gradient* gradient) { + g_default_gradient = *gradient; +} + +void leds_set_current_gradient(const struct Gradient* gradient, int defer_send) { + struct GradientPoint from = gradient->points[0]; + struct GradientPoint to; + + set_led_range(0, gradient->points[0].offset, gradient->points[0].led); + set_led_range(gradient->points[gradient->points_len-1].offset, 120, gradient->points[gradient->points_len-1].led); + + g_current_gradient = *gradient; + + for(int i = 1; i < gradient->points_len; ++i) { + to = gradient->points[i]; + lerp_points_between(from, to); + from = to; + } + + if(!defer_send) { + send_leds(); + } +} + +void leds_reset_gradient(int defer_send) { + leds_set_current_gradient(&g_default_gradient, defer_send); +} + +// swap to ranges of memory using a temporary block +static +void memswap(void* d, void* s, size_t n) { + void* tmp = malloc(n); + memcpy(tmp, s, n); + memcpy(s, d, n); + memcpy(d, tmp, n); + free(tmp); +} + +void leds_animate() { + for(size_t i = 0; i < g_current_gradient.points_len; ++i) { + // The gradient point at i + struct GradientPoint* point = g_current_gradient.points + i; + // move towards end + if(point->movement > 0) { + // without moving past it + point->offset = min(point->offset + 1, 120); + + // swap with next point if we pass it + if(point->offset > (point+1)->offset) { + memswap(point, point+1, sizeof(struct GradientPoint)); + } + // move towards start + } else if(point->movement < 0) { + // without passing it + point->offset = max(0, point->offset - 1); + + // swap with previous point if we fall below it + if(point->offset < (point-1)->offset) { + memswap(point, point-1, sizeof(struct GradientPoint)); + } + } + } + leds_set_current_gradient(&g_current_gradient, 1); +} + +void leds_thread() { + send_leds(); + float timer = 0; + for(;;) { + // wait for 10 milliseconds, + // giving FreeRTOS time to run other tasks + vTaskDelay(10 / portTICK_RATE_MS); + xSemaphoreTake(g_led_mutex, portMAX_DELAY); + { + send_leds(); + leds_animate(); + + if(timer > g_current_gradient.duration) { + timer = 0.f; + leds_set_current_gradient(&g_default_gradient, 0); + } + } + xSemaphoreGive(g_led_mutex); + } +} + +void leds_init() { + g_serial_out_buffer[0] = 0u; + g_serial_out_buffer[61] = ~0u; + set_led_range(0, 120, + (union Led){.components = + (struct LedComponents) { + .red = 0, + .green = 0, + .blue = 0, + .global = GLOBAL(5) + }} + ); + + g_led_mutex = xSemaphoreCreateMutex(); + + xTaskCreate(g_led_thread_data.func, "Leds", 1024, NULL, 1, &g_led_thread_data.task); + + // initialize mutex for leds data + leds_config_gpio(); +} \ No newline at end of file diff --git a/main/leds.h b/main/leds.h index 46ce6a8..f871feb 100644 --- a/main/leds.h +++ b/main/leds.h @@ -8,23 +8,18 @@ #define _potion_leds_h #include -#include -#include -#include -#include "shared.h" -#include "esp8266/gpio_register.h" +#include +#include #include "esp_system.h" -#include "rom/ets_sys.h" -#include "driver/gpio.h" -enum leds_send_state_t { +enum LedsSendStatus { LEDS_SEND_WAITING, LEDS_SEND_REQUESTED, LEDS_SENDING, }; // pack the struct to match exactly 8 * 4 = 32bits -struct __attribute__((__packed__)) led_components_t { +struct __attribute__((__packed__)) LedComponents { // RGB component values uint8_t red; uint8_t green; @@ -34,233 +29,52 @@ struct __attribute__((__packed__)) led_components_t { // union of components and their representation as a u32 // allows for easier sending of data over serial -union led_t { - struct led_components_t components; +union Led { + struct LedComponents components; uint32_t bits; }; // point on a gradient -struct gradient_point_t { - union led_t led; // value of the led at this point +struct GradientPoint { + union Led led; // value of the led at this point size_t offset; // offset (measured in leds) from the beginning short movement; // direction of movement over time }; -struct gradient_t { - struct gradient_point_t points[16]; // array of gradient points, support at most 16 points in a gradient +struct Gradient { + struct GradientPoint points[16]; // array of gradient points, support at most 16 points in a gradient size_t points_len; // number of used gradient points float duration; // amount of time to allow this gradient to last // positive means an amount in second 0 or negative means indefinitely until further notice }; -// buffer that will be written out to the led strip over serial -uint32_t g_serial_out_buffer[122]; -// 120-long slice of the out buffer that represents the first few leds -union led_t* g_leds = ((union led_t*)g_serial_out_buffer + 1); - -struct gradient_t g_default_gradient; -struct gradient_t g_current_gradient; -int g_leds_are_default = 1; -enum leds_send_state_t g_leds_send_state = LEDS_SEND_WAITING; - -SemaphoreHandle_t g_led_mutex; // mutex governing access to data for leds - // use for all global data defined in this header - -#define CLOCK 4 -#define DATA 5 - -static -void leds_config_gpio() { - gpio_config_t config = { - .intr_type = GPIO_INTR_DISABLE, - .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = 0x18030, - .pull_up_en = 0, - .pull_down_en = 0, - }; - gpio_config(&config); - -} - -static -void serial_write(int high) { - // set clock out to high, triggering a rising edge - gpio_set_level(CLOCK, 1); - // write bit to data out - gpio_set_level(DATA, high); - - ets_delay_us(1); - - // set clock to low, triggering a falling edge, shifting the LEDs shift register - gpio_set_level(CLOCK, 0); -} - -static -void send_leds() { - // index of the bit being written - // fixed point number where the first 5 bits are the bit of a 32bit integger, and the rest is the integer - int write_bit = 0; - int write_next = 0; - - gpio_set_level(CLOCK, 0); - - while(write_bit < sizeof(g_serial_out_buffer) * 8) { - // fetch the bit being addressed - write_next = 0x1 & (g_serial_out_buffer[write_bit >> 5] >> (32 - (write_bit & 0x1F))); - - serial_write(write_next); - - write_bit++; - } - - gpio_set_level(CLOCK, 0); - gpio_set_level(DATA, 0); - -} - -static inline -uint8_t lerp_uint8(uint8_t a, uint8_t b, float t) { - if(t <= 0) return a; - else if(t >= 1.0) return b; - else { - int dir = b - a; - return a + dir * t; - } -} - -static inline -void lerp_led(union led_t* out, const union led_t* from, const union led_t* to, float t) { - out->components.red = lerp_uint8(from->components.red, to->components.red, t); - out->components.green = lerp_uint8(from->components.green, to->components.green, t); - out->components.blue = lerp_uint8(from->components.blue, to->components.blue, t); - uint8_t glob_from = from->components.global & ~0xE0; - uint8_t glob_to = to->components.global & ~0xE0; - out->components.global = GLOBAL(lerp_uint8(glob_from, glob_to, t)); -} - -static inline -void lerp_points_between(const struct gradient_point_t from, const struct gradient_point_t to) { - const int dif = to.offset - from.offset; - float t = 0.f; - for(int led = from.offset; led <= to.offset; ++led) { - t = (float)(led - from.offset) / (float)dif; - lerp_led(g_leds + led, &from.led, &to.led, t); - } -} - -static -void set_led_range(int start, int end, union led_t value) { - for(int i = start; i < end; ++i) { - g_leds[i] = value; - } -} - -static -void leds_set_default_gradient(const struct gradient_t* gradient) { - g_default_gradient = *gradient; -} - -static -void leds_set_current_gradient(const struct gradient_t* gradient, int defer_send) { - struct gradient_point_t from = gradient->points[0]; - struct gradient_point_t to; - - set_led_range(0, gradient->points[0].offset, gradient->points[0].led); - set_led_range(gradient->points[gradient->points_len-1].offset, 120, gradient->points[gradient->points_len-1].led); - - g_current_gradient = *gradient; - - for(int i = 1; i < gradient->points_len; ++i) { - to = gradient->points[i]; - lerp_points_between(from, to); - from = to; - } - - if(!defer_send) { - send_leds(); - } -} - -void memswap(void* d, void* s, size_t n) { - void* tmp = malloc(n); - memcpy(tmp, s, n); - memcpy(s, d, n); - memcpy(d, tmp, n); - free(tmp); -} - -void leds_animate() { - for(size_t i = 0; i < g_current_gradient.points_len; ++i) { - struct gradient_point_t* point = g_current_gradient.points + i; - if(point->movement > 0) { - point->offset = min(point->offset + 1, 120); - LOGLN("move result %i", point->offset); - LOGLN("next %d", (point+1)->offset); - - if(point->offset > (point+1)->offset) { - memswap(point, point+1, sizeof(struct gradient_point_t)); - LOGLN("swap down"); - } - } else if(point->movement < 0) { - point->offset = max(0, point->offset - 1); - LOGLN("move result %i", point->offset); - LOGLN("next %d", (point-1)->offset); - - if(point->offset < (point-1)->offset) { - memswap(point, point-1, sizeof(struct gradient_point_t)); - LOGLN("swap up"); - } - } - } - leds_set_current_gradient(&g_current_gradient, 1); -} - -struct led_thread_data_t { +struct LedThreadData { TaskHandle_t task; TaskFunction_t func; }; -static void leds_thread(); +// buffer that will be written out to the led strip over serial +extern uint32_t g_serial_out_buffer[122]; +// 120-long slice of the out buffer that represents the first few leds +extern union Led* g_leds; +extern struct Gradient g_default_gradient; +extern struct Gradient g_current_gradient; +extern int g_leds_are_default; +extern enum LedsSendStatus g_leds_send_state; +extern SemaphoreHandle_t g_led_mutex; +extern struct LedThreadData g_led_thread_data; -static struct led_thread_data_t _thread_data = { - .task = 0, - .func = &leds_thread -}; +#define CLOCK 4 +#define DATA 5 -static -void leds_thread() { - send_leds(); - for(;;) { - vTaskDelay(10); - xSemaphoreTake(g_led_mutex, portMAX_DELAY); - send_leds(); - leds_animate(); - xSemaphoreGive(g_led_mutex); - } -} +extern void send_leds(); +extern void set_led_range(int start, int end, union Led value); +extern void leds_set_default_gradient(const struct Gradient* gradient); +extern void leds_set_current_gradient(const struct Gradient* gradient, int defer_send); +extern void leds_reset_gradient(int defer_send); - -static -void leds_init() { - g_serial_out_buffer[0] = 0u; - g_serial_out_buffer[61] = ~0u; - set_led_range(0, 120, - (union led_t){.components = - (struct led_components_t) { - .red = 0, - .green = 0, - .blue = 0, - .global = GLOBAL(5) - }} - ); - - g_led_mutex = xSemaphoreCreateMutex(); - - xTaskCreate(_thread_data.func, "Leds", 1024, NULL, 1, &_thread_data.task); - - // initialize mutex for leds data - leds_config_gpio(); -} +extern void leds_thread(); +extern void leds_init(); #endif // !_leds_h diff --git a/main/network.c b/main/network.c new file mode 100644 index 0000000..c4ab586 --- /dev/null +++ b/main/network.c @@ -0,0 +1,66 @@ +#include "network.h" + +#include "shared.h" +#include "parse.h" +#include +#include +#include +#include + +static +void on_station_connects(wifi_event_ap_staconnected_t* event) { + LOGLN("Station %d connected", event->aid); +} + +static +void on_station_disconnects(wifi_event_ap_stadisconnected_t* event) { + LOGLN("Station %d disconnected", event->aid); +} + +static +void on_wifi_event(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) { + switch(event_id) { + case WIFI_EVENT_AP_STACONNECTED: + on_station_connects(event_data); + break; + case WIFI_EVENT_AP_STADISCONNECTED: + on_station_disconnects(event_data); + break; + } +} + +void wifi_init() { + LOGLN("Configuring WIFI"); + // init tcp/ip stack + tcpip_adapter_init(); + // initialize wifi + wifi_init_config_t wifi_startup = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&wifi_startup)); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &on_wifi_event, NULL)); + LOGLN("WiFi initialized"); +} + +void softap_init() { + LOGLN("Starting wireless Access Point"); + // set mode to wifi + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); + + // configure wifi hardware to serve as a wifi access point + wifi_config_t accesspoint_startup_config = { + .ap = { + .ssid = SSID, + .ssid_len = strlen(SSID), + .password = PASSW, + .max_connection=32, + .authmode = WIFI_AUTH_WPA_WPA2_PSK + } + }; + + // configure with created startup config + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &accesspoint_startup_config)); + // start wifi access point + ESP_ERROR_CHECK(esp_wifi_start()); + + LOGLN("Opened AP SSID: \"%s\" PW: \"%s\"", SSID, PASSW); +} \ No newline at end of file diff --git a/main/network.h b/main/network.h index 0221f12..c4595a8 100644 --- a/main/network.h +++ b/main/network.h @@ -5,71 +5,10 @@ // /// -#ifndef _server_h -#define _server_h +#ifndef _potion_party_network_h +#define _potion_party_network_h -#include "esp_wifi_types.h" -#include "shared.h" -#include "esp_event.h" +void wifi_init(); +void softap_init(); -static -void on_station_connects(wifi_event_ap_staconnected_t* event) { - LOGLN("Station %d connected", event->aid); -} - -static -void on_station_disconnects(wifi_event_ap_stadisconnected_t* event) { - LOGLN("Station %d disconnected", event->aid); -} - -static -void on_wifi_event(void* arg, esp_event_base_t event_base, - int32_t event_id, void* event_data) { - switch(event_id) { - case WIFI_EVENT_AP_STACONNECTED: - on_station_connects(event_data); - break; - case WIFI_EVENT_AP_STADISCONNECTED: - on_station_disconnects(event_data); - break; - } -} - -static inline -void wifi_init() { - LOGLN("Configuring WIFI"); - // init tcp/ip stack - tcpip_adapter_init(); - // initialize wifi - wifi_init_config_t wifi_startup = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_wifi_init(&wifi_startup)); - ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &on_wifi_event, NULL)); - LOGLN("WiFi initialized"); -} - -static inline -void softap_init() { - LOGLN("Starting wireless Access Point"); - // set mode to wifi - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); - - // configure wifi hardware to serve as a wifi access point - wifi_config_t accesspoint_startup_config = { - .ap = { - .ssid = SSID, - .ssid_len = strlen(SSID), - .password = PASSW, - .max_connection=32, - .authmode = WIFI_AUTH_WPA_WPA2_PSK - } - }; - - // configure with created startup config - ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &accesspoint_startup_config)); - // start wifi access point - ESP_ERROR_CHECK(esp_wifi_start()); - - LOGLN("Opened AP SSID: \"%s\" PW: \"%s\"", SSID, PASSW); -} - -#endif // !_server_h +#endif diff --git a/main/parse.c b/main/parse.c new file mode 100644 index 0000000..8cee177 --- /dev/null +++ b/main/parse.c @@ -0,0 +1,96 @@ +#include "shared.h" +#include "leds.h" +#include +#include + +// parse a URL query as described in api-doc.txt into a valid Gradient struct. +// If the gradient is invalid, the is_ok flag on the return value will be set, and error will be set to a message describing the problem. +struct result_t parse_leds_query(char* query_string, size_t query_size) { + char query_value[16]; + char query_key[3]; + struct Gradient* gradient = malloc(sizeof(struct Gradient)); + + // Fetch the &l length parameter. + // Interpret as a positive integer number of points on the gradient. + if(httpd_query_key_value(query_string, "l", query_value, sizeof(query_value)) == ESP_OK) { + gradient->points_len = max(0, atoi(query_value)); + } else { + free(gradient); + return PARSE_ERR("ERROR: Failed to find length parameter &l"); + } + + // Get the &d 'duration' parameter from the query. + // Interpreted as a floating point number of seconds before returning to default state. + if(httpd_query_key_value(query_string, "d", query_value, sizeof(query_value)) == ESP_OK) { + gradient->duration = atof(query_string); + } else { + gradient->duration = 0; + } + + LOGLN("Reading %zu points of gradient query:", gradient->points_len); + LOGLN("duration: %f", gradient->duration); + + // Get the gradient point components for every point that was promised by the &l parameter + for(int point = 0; point < gradient->points_len; ++point) { + // Get the r, g, and b components as 8 bit integers + sprintf(query_key, "r%d", point); + if(httpd_query_key_value(query_string, query_key, query_value, sizeof(query_size)) == ESP_OK) { + gradient->points[point].led.components.red = atoi(query_value); + } else { + free(gradient); + return PARSE_ERR("ERROR: Point missing red component &r."); + } + sprintf(query_key, "g%d", point); + if(httpd_query_key_value(query_string, query_key, query_value, sizeof(query_size)) == ESP_OK) { + gradient->points[point].led.components.green = atoi(query_value); + } else { + free(gradient); + return PARSE_ERR("ERROR: Point missing green component &g."); + } + sprintf(query_key, "b%d", point); + if(httpd_query_key_value(query_string, query_key, query_value, sizeof(query_value)) == ESP_OK) { + gradient->points[point].led.components.blue = atoi(query_value); + } else { + free(gradient); + return PARSE_ERR("ERROR: Point missing blue component &b."); + } + // Get the global variable, passed as alpha. Limited to 0-32 (a 5bit unsigned int) + // Make sure the most significant 3 bits are all ones + sprintf(query_key, "a%d", point); + if(httpd_query_key_value(query_string, query_key, query_value, sizeof(query_value)) == ESP_OK) { + gradient->points[point].led.components.global = GLOBAL((uint8_t)atoi(query_value)); + } else { + free(gradient); + return PARSE_ERR("ERROR: Point missing alpha component &a."); + } + // Get the time of the gradient as a number ranging from 0 - 120. + // Interpreted as an integer offset from the first led to the last + sprintf(query_key, "t%d", point); + if(httpd_query_key_value(query_string, query_key, query_value, sizeof(query_value)) == ESP_OK) { + gradient->points[point].offset = clamp(0, 120, atoi(query_value)); + } else { + free(gradient); + return PARSE_ERR("ERROR: Point missing time component &t."); + } + // Get the movement variable &m. + // Interpreted as an integer number from -1 to +1 + sprintf(query_key, "m%d", point); + if(gradient->duration > 0 && httpd_query_key_value(query_string, query_key, query_value, sizeof(query_value)) == ESP_OK) { + gradient->points[point].movement = clamp(-1, +1, atoi(query_value)); + } else { + gradient->points[point].movement = 0; + } + + // Log fetched fields + LOGLN("led[%d]:", point); + LOGLN(" r %d", gradient->points[point].led.components.red); + LOGLN(" g %d", gradient->points[point].led.components.green); + LOGLN(" b %d", gradient->points[point].led.components.blue); + LOGLN(" global %d", gradient->points[point].led.components.global >> 3); + // may show up as a compile error on modern computers, + // x86_64 size_t is usually an unsigned long int, on the ESP8266 it is an unsigned int + LOGLN(" t %u", gradient->points[point].offset); + } + + return PARSE_OK(gradient); +} \ No newline at end of file diff --git a/main/parse.h b/main/parse.h new file mode 100644 index 0000000..1e861d3 --- /dev/null +++ b/main/parse.h @@ -0,0 +1,9 @@ +#ifndef _potion_party_parse_h +#define _potion_party_parse_h + +#include "shared.h" +#include + +extern Result parse_leds_query(char* query_string, size_t query_size); + +#endif diff --git a/main/potion_party.c b/main/potion_party.c index 5e0ccdf..a8eb4f6 100644 --- a/main/potion_party.c +++ b/main/potion_party.c @@ -3,8 +3,13 @@ #include "network.h" #include "server.h" #include "rom/ets_sys.h" +#include "esp_system.h" +#include "esp_netif.h" +#include "esp_event.h" #include +#include +#include #include // starts the basic subsystems we will be using @@ -21,7 +26,7 @@ void TEST_leds() { // TEST: after a delay, set the leds to a gradient of red - black sleep(1); - union led_t led = { + union Led led = { .components = { .red = 0, .green = 255, @@ -37,12 +42,12 @@ void TEST_leds() { sleep(1); - struct gradient_t gradient; + struct Gradient gradient; gradient.points_len = 4; gradient.points[0].offset = 0; gradient.points[0].movement = 0; - gradient.points[0].led.components = (struct led_components_t) { + gradient.points[0].led.components = (struct LedComponents) { .global = GLOBAL(0), .red = 0, .green = 0, @@ -51,7 +56,7 @@ void TEST_leds() { gradient.points[1].offset = 59; gradient.points[1].movement = -1; - gradient.points[1].led.components = (struct led_components_t){ + gradient.points[1].led.components = (struct LedComponents){ .global = GLOBAL(10), .red = 40, .green = 200, @@ -59,7 +64,7 @@ void TEST_leds() { }; gradient.points[2].offset = 61; gradient.points[2].movement = 1; - gradient.points[2].led.components = (struct led_components_t){ + gradient.points[2].led.components = (struct LedComponents){ .global = GLOBAL(10), .red = 40, .green = 200, @@ -67,7 +72,7 @@ void TEST_leds() { }; gradient.points[3].offset = 120; gradient.points[3].movement = 0; - gradient.points[3].led.components = (struct led_components_t){ + gradient.points[3].led.components = (struct LedComponents){ .global = GLOBAL(0), .red = 0, .green = 0, diff --git a/main/server.c b/main/server.c new file mode 100644 index 0000000..174063c --- /dev/null +++ b/main/server.c @@ -0,0 +1,124 @@ +#include "server.h" + +#include +#include +#include +#include "shared.h" +#include "leds.h" +#include "parse.h" +#include +#include +#include +#include + +const char* http_response_ok = "OK!"; +static httpd_handle_t g_http_server = NULL; + +// convert a (valid) request as described in api-doc.txt to a gradient. +static +struct result_t request_to_gradient(httpd_req_t* request) { + // buffer for query string + char* query_buffer; + size_t query_length = httpd_req_get_url_query_len(request) + 1; + + struct result_t result = { .error = NULL }; + + if(query_length > 1) { + query_buffer = malloc(query_length * sizeof(char)); + if(httpd_req_get_url_query_str(request, query_buffer, query_length) == ESP_OK) { + LOGLN("Received query."); + result = parse_leds_query(query_buffer, query_length); + } + free(query_buffer); + } + + return result; +} + +// receives HTTP GET requests on root +static +esp_err_t on_http_get_root(httpd_req_t* request) { + const char* response_msg = http_response_ok; + LOGLN("GET received on '/next'."); + // convert the request url to a gradient + struct result_t result = request_to_gradient(request); + // an error was returned, pass it on to the API caller + if(!result.is_ok) { + httpd_resp_set_status(request, "400 Bad Request"); + response_msg = result.error; + } else { + // grab a lock on the leds data + xSemaphoreTake(g_led_mutex, portMAX_DELAY); + // modify leds data + leds_set_current_gradient(result.ok, 0); + // release lock + xSemaphoreGive(g_led_mutex); + // request to gradient allocates the gradient on the heap, + // we can free that after use + free(result.ok); + } + // respond to http caller + httpd_resp_send(request, response_msg, strlen(response_msg)); + return ESP_OK; +} +httpd_uri_t get_root_uri = { + .uri="/", + .method=HTTP_GET, + .handler=&on_http_get_root, + .user_ctx = NULL +}; + +static +esp_err_t on_http_get_default(httpd_req_t* request) { + const char* response_msg = http_response_ok; + LOGLN("GET received on '/default'."); + // convert the request to a gradient object + struct result_t result = request_to_gradient(request); + // handle invalid query + if(!result.is_ok) { + httpd_resp_set_status(request, "400 Bad Request"); + response_msg = result.error; + } else { + // set default gradient + // take lock on leds data + xSemaphoreTake(g_led_mutex, portMAX_DELAY); + leds_set_default_gradient(result.ok); + // release lock on leds data + xSemaphoreGive(g_led_mutex); + // we allocated a gradient_t struct with request_to_gradient, + // we no longer need it so we'll free it asap + free(result.ok); + } + // send http response + httpd_resp_send(request, response_msg, strlen(response_msg)); + return ESP_OK; +} +httpd_uri_t get_default_uri = { + .uri="/default", + .method=HTTP_GET, + .handler=&on_http_get_default, + .user_ctx=NULL +}; + +// Configure server and enable the http handler. +static +httpd_handle_t start_webserver(void) { + httpd_handle_t server = NULL; + httpd_config_t server_config = HTTPD_DEFAULT_CONFIG(); + + LOGLN("Starting HTTPd server ':%d'.", server_config.server_port); + + if(httpd_start(&server, &server_config) == ESP_OK) { + httpd_register_uri_handler(server, &get_root_uri); + httpd_register_uri_handler(server, &get_default_uri); + return server; + } + + LOGLN("Failed to start HTTPd server."); + return NULL; +} + +// Start an http server and store it's handle in g_http_server. +void server_init(void) { + g_http_server = start_webserver(); +} \ No newline at end of file diff --git a/main/server.h b/main/server.h index 4f3418f..992ec4c 100644 --- a/main/server.h +++ b/main/server.h @@ -14,189 +14,6 @@ #ifndef _potion_party_server_h #define _potion_party_server_h -#include -#include -#include -#include "shared.h" -#include "leds.h" -#include "esp_http_server.h" -#include "esp_system.h" -#include "esp_netif.h" -#include "sys_arch.h" - -static -httpd_handle_t g_http_server = NULL; - -struct parse_error_t { - const char* error; -}; - -// Parse a gradient query -static -struct parse_error_t parse_leds_query(char* query_string, size_t query_size) { - char query_value[16]; - char query_key[3]; - struct gradient_t gradient; - - // Fetch the &l length parameter. - // Interpret as a positive integer number of points on the gradient. - if(httpd_query_key_value(query_string, "l", query_value, sizeof(query_value)) == ESP_OK) { - gradient.points_len = max(0, atoi(query_value)); - } else { - leds_set_current_gradient(&g_default_gradient, 0); - return (struct parse_error_t) { - .error = "ERROR: Failed to find length parameter &l" - }; - } - - // Get the &d 'duration' parameter from the query. - // Interpreted as a floating point number of seconds before returning to default state. - if(httpd_query_key_value(query_string, "d", query_value, sizeof(query_value)) == ESP_OK) { - gradient.duration = atof(query_string); - } else { - gradient.duration = 0; - } - - LOGLN("Reading %zu points of gradient query:", gradient.points_len); - LOGLN("duration: %f", gradient.duration); - - // Get the gradient point components for every point that was promised by the &l parameter - for(int point = 0; point < gradient.points_len; ++point) { - // Get the r, g, and b components as 8 bit integers - sprintf(query_key, "r%d", point); - if(httpd_query_key_value(query_string, query_key, query_value, sizeof(query_size)) == ESP_OK) { - gradient.points[point].led.components.red = atoi(query_value); - } else { - return (struct parse_error_t) { - .error = "ERROR: Point missing red component &r." - }; - } - sprintf(query_key, "g%d", point); - if(httpd_query_key_value(query_string, query_key, query_value, sizeof(query_size)) == ESP_OK) { - gradient.points[point].led.components.green = atoi(query_value); - } else { - return (struct parse_error_t) { - .error = "ERROR: Point missing green component &g." - }; - } - sprintf(query_key, "b%d", point); - if(httpd_query_key_value(query_string, query_key, query_value, sizeof(query_value)) == ESP_OK) { - gradient.points[point].led.components.blue = atoi(query_value); - } else { - return (struct parse_error_t) { - .error = "ERROR: Point missing blue component &b." - }; - } - // Get the global variable, passed as alpha. Limited to 0-32 (a 5bit unsigned int) - // Make sure the most significant 3 bits are all ones - sprintf(query_key, "a%d", point); - if(httpd_query_key_value(query_string, query_key, query_value, sizeof(query_value)) == ESP_OK) { - gradient.points[point].led.components.global = GLOBAL((uint8_t)atoi(query_value)); - } else { - return (struct parse_error_t) { - .error = "ERROR: Point missing alpha component &a." - }; - } - // Get the time of the gradient as a number ranging from 0 - 120. - // Interpreted as an integer offset from the first led to the last - sprintf(query_key, "t%d", point); - if(httpd_query_key_value(query_string, query_key, query_value, sizeof(query_value)) == ESP_OK) { - gradient.points[point].offset = clamp(0, 120, atoi(query_value)); - } else { - return (struct parse_error_t) { - .error = "ERROR: Point missing time component &t." - }; - } - // Get the movement variable &m. - // Interpreted as an integer number from -1 to +1 - sprintf(query_key, "m%d", point); - if(gradient.duration > 0 && httpd_query_key_value(query_string, query_key, query_value, sizeof(query_value)) == ESP_OK) { - gradient.points[point].movement = clamp(-1, +1, atoi(query_value)); - } else { - gradient.points[point].movement = 0; - } - - // Log fetched fields - LOGLN("led[%d]:", point); - LOGLN(" r %d", gradient.points[point].led.components.red); - LOGLN(" g %d", gradient.points[point].led.components.green); - LOGLN(" b %d", gradient.points[point].led.components.blue); - LOGLN(" global %d", gradient.points[point].led.components.global >> 3); - // may show up as a compile error on modern computers, - // x86_64 size_t is usually an unsigned long int, on the ESP8266 it is an unsigned int - LOGLN(" t %u", gradient.points[point].offset); - } - - xSemaphoreTake(g_led_mutex, portMAX_DELAY); - - leds_set_current_gradient(&gradient, 0); - - xSemaphoreGive(g_led_mutex); - - return (struct parse_error_t) { .error = NULL }; -} - -// receives HTTP GET requests on root -static -esp_err_t on_http_get_root(httpd_req_t* request) { - // Error mewsages for cases of good and bad - static const char* response_ok = "OK!"; - - const char* response_msg = response_ok; - - LOGLN("POST received on '/'."); - - // buffer for query string - char* query_buffer; - size_t query_length = httpd_req_get_url_query_len(request) + 1; - struct parse_error_t result = { .error = NULL }; - - if(query_length > 1) { - query_buffer = malloc(query_length * sizeof(char)); - if(httpd_req_get_url_query_str(request, query_buffer, query_length) == ESP_OK) { - LOGLN("Received query."); - result = parse_leds_query(query_buffer, query_length); - } - } - - // an error was returned, pass it on to the API caller - if(result.error != NULL) { - httpd_resp_set_status(request, "400 Bad Request"); - response_msg = result.error; - } - - httpd_resp_send(request, response_msg, strlen(response_msg)); - - return ESP_OK; -} -httpd_uri_t get_root_uri = { - .uri="/", - .method=HTTP_GET, - .handler=&on_http_get_root, - .user_ctx = NULL -}; - -// Configure server and enable the http handler. -static -httpd_handle_t start_webserver(void) { - httpd_handle_t server = NULL; - httpd_config_t server_config = HTTPD_DEFAULT_CONFIG(); - - LOGLN("Starting HTTPd server ':%d'.", server_config.server_port); - - if(httpd_start(&server, &server_config) == ESP_OK) { - httpd_register_uri_handler(server, &get_root_uri); - return server; - } - - LOGLN("Failed to start HTTPd server."); - return NULL; -} - -// Start an http server and store it's handle in g_http_server. -static -void server_init(void) { - g_http_server = start_webserver(); -} +void server_init(); #endif // !_potion_party_server_h diff --git a/main/shared.h b/main/shared.h index 24dbdd2..d72ab9b 100644 --- a/main/shared.h +++ b/main/shared.h @@ -7,10 +7,7 @@ #ifndef _shared_h #define _shared_h -#include -#include -#include -#include "esp_system.h" +#include // wifi configuration #define SSID "ESP8266" @@ -36,4 +33,18 @@ int clamp(int x, int mi, int ma) { #define GLOBAL(__a) (uint8_t)(__a|0xE0) +struct result_t { + uint8_t is_ok; + union { + void* ok; + const char* error; + }; +}; + +typedef struct result_t Result; + +#define PARSE_ERR(__err) (Result){.is_ok=0,.error=__err} +#define PARSE_OK(__result) (Result){.is_ok=1,.ok=__result} + + #endif // !_shared_h