desk-andon

Programmable Desktop Tower Light
git clone https://git.sinitax.com/sinitax/desk-andon
Log | Files | Refs | Submodules | sfeed.txt

main.c (12464B)


      1#include "esp_netif_ip_addr.h"
      2#include "esp_wifi_types_generic.h"
      3#include "freertos/idf_additions.h"
      4#include "ssd1306.h"
      5
      6#include "freertos/FreeRTOS.h"
      7#include "freertos/task.h"
      8#include "portmacro.h"
      9#include "driver/gpio.h"
     10#include "hal/gpio_types.h"
     11#include "esp_wifi.h"
     12#include "esp_http_server.h"
     13#include "esp_log.h"
     14#include "nvs_flash.h"
     15
     16#include <string.h>
     17#include <stdio.h>
     18
     19#define TICKS_MS(x) ((x) / portTICK_PERIOD_MS)
     20#define LOG(...) ESP_LOGI("desk-andon", __VA_ARGS__)
     21#define PANIC(...) lcd_panic(__VA_ARGS__);
     22
     23/* The event group allows multiple bits for each event, but we only care about two events:
     24 * - we are connected to the AP with an IP
     25 * - we failed to connect after the maximum amount of retries */
     26#define WIFI_CONNECTED_BIT BIT0
     27
     28#define WIFI_ATTEMPT_MAX 5
     29
     30#define WIFI_SSID "andon-net"
     31#define WIFI_PASS "uzw00ystu1"
     32
     33enum Lane {
     34	RED,
     35	ORANGE,
     36	GREEN,
     37	BLUE,
     38	WHITE,
     39	SPEAKER,
     40};
     41
     42static const uint8_t bitmap_frame[] = {
     430xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
     440xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
     450xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
     460xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     470xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     480xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     490xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     500xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     510xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     520xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     530xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     540xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     550xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     560xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     570xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     580xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     590xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     600xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     610xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     620xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     630xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     640xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     650xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     660xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     670xc0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 
     680xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
     690xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
     700xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
     71};
     72
     73static const uint8_t bitmap_light1[] = {
     740x07, 0x00, 0x0e, 0x0e, 0x00, 0x1c, 0x1c, 0x00, 0x38, 0x38, 0x00, 0x70, 0x70, 0x00, 0xe0, 0xe0, 
     750x01, 0xc0, 0xc0, 0x03, 0x80, 0x80, 0x07, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x38, 
     760x00, 0x00, 0x70, 0x00, 0x00, 0xe0, 0x01, 0x01, 0xc0, 0x03, 0x03, 0x80, 0x07, 0x07, 0x00, 0x0e, 
     770x0e, 0x00, 0x1c, 0x1c, 0x00, 0x38, 0x38, 0x00, 0x70, 0x70, 0x00, 0xe0, 0xe0, 0x01, 0xc0, 0xc0, 
     780x03, 0x80
     79};
     80
     81static const uint8_t bitmap_light2[] = {
     820x00, 0x1c, 0x00, 0x00, 0x38, 0x00, 0x00, 0x70, 0x00, 0x00, 0xe0, 0x00, 0x01, 0xc0, 0x00, 0x03, 
     830x80, 0x00, 0x07, 0x00, 0x01, 0x0e, 0x00, 0x03, 0x1c, 0x00, 0x07, 0x38, 0x00, 0x0e, 0x70, 0x00, 
     840x1c, 0xe0, 0x00, 0x38, 0xc0, 0x00, 0x70, 0x80, 0x00, 0xe0, 0x00, 0x01, 0xc0, 0x00, 0x03, 0x80, 
     850x00, 0x07, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x38, 0x00, 0x00, 0x70, 0x00, 0x00, 
     860xe0, 0x00
     87};
     88
     89static const int ANDON_PINS[SPEAKER+1] = { 10, 6, 3, 5, 7, 4 };
     90static const char *ANDON_NAMES[SPEAKER+1] = {
     91	[RED] = "red",
     92	[ORANGE] = "orange",
     93	[GREEN] = "green",
     94	[BLUE] = "blue",
     95	[WHITE] = "white",
     96	[SPEAKER] = "speaker",
     97};
     98
     99static uint8_t state = 0;
    100
    101static SSD1306_t display = { 0 };
    102static bool lcd_init = false;
    103static TaskHandle_t lcd_task = NULL;
    104
    105static char mac_str[16] = { 0 };
    106static char ip_str[17] = { 0 };
    107static bool wifi_connected = false;
    108static int wifi_connect_attempt = 0;
    109static EventGroupHandle_t wifi_event_group;
    110
    111static esp_err_t http_get_handler(httpd_req_t *req);
    112static esp_err_t http_post_handler(httpd_req_t *req);
    113static const httpd_uri_t http_get = {
    114	.uri = "/",
    115	.method = HTTP_GET,
    116	.handler = http_get_handler,
    117	.user_ctx = NULL
    118};
    119static const httpd_uri_t http_post = {
    120	.uri = "/",
    121	.method = HTTP_POST,
    122	.handler = http_post_handler,
    123	.user_ctx = NULL
    124};
    125
    126static void
    127lcd_task_main(void *_p)
    128{
    129	bool frame_state[SPEAKER] = { 0 };
    130	char mac_str_old[16] = { 0 };
    131	char mac_str_new[16] = { 0 };
    132	char ip_str_old[17] = { 0 };
    133	char ip_str_new[17] = { 0 };
    134
    135	TickType_t last = 0;
    136	while (1) {
    137		TickType_t now = xTaskGetTickCount();
    138		if (!last || (now - last) * portTICK_PERIOD_MS >= 500) {
    139			uint8_t current = state;
    140			memcpy(ip_str_new, ip_str, sizeof(ip_str));
    141			memcpy(mac_str_new, mac_str, sizeof(mac_str));
    142			bool connected = wifi_connected;
    143
    144			for (int i = 0; i < SPEAKER; i++) {
    145				if (current & (1 << i)) {
    146					frame_state[i] ^= true;
    147				}
    148			}
    149			last = now;
    150
    151			if (!lcd_init) {
    152				_ssd1306_clear(&display, false);
    153				_ssd1306_bitmaps(&display, 0, 0, bitmap_frame, 128, 28, false);
    154				lcd_init = true;
    155			}
    156
    157			const int xoff = (128 - 24 * 5 - 4) / 2;
    158			for (int i = 0; i < SPEAKER; i++) {
    159				if (current & (1 << i)) { 
    160					_ssd1306_bitmaps(&display, xoff + i * 25, 3,
    161						frame_state[i] ? bitmap_light2 : bitmap_light1,
    162						24, 22, false);
    163				} else {
    164					_ssd1306_clear_rect(&display, xoff + i * 25, 3, 24, 22, false);
    165				}
    166			}
    167
    168
    169			if (strcmp(mac_str_old, mac_str_new)) {
    170				_ssd1306_display_text(&display, 4, 40,
    171					mac_str_new, strlen(mac_str_new), false);
    172				strncpy(mac_str_old, mac_str_new, sizeof(mac_str));
    173			}
    174
    175			if (strcmp(ip_str_old, ip_str_new)) {
    176				_ssd1306_display_text(&display, connected ? 4 : 0, 52,
    177					ip_str_new, strlen(ip_str_new), false);
    178				strncpy(ip_str_old, ip_str_new, sizeof(ip_str));
    179			}
    180
    181			ssd1306_next_frame(&display);
    182		}
    183
    184		vTaskDelay(TICKS_MS(50));
    185	}
    186}
    187
    188static void
    189lcd_panic(const char *msg)
    190{
    191	vTaskSuspend(lcd_task);
    192	vTaskDelete(lcd_task);
    193	ssd1306_clear_screen(&display, false);
    194	ssd1306_display_text(&display, 20, 42, "PANIC", 10, false);
    195	while (1) {
    196		ESP_LOGE("desk-andon", "panic: %s", msg);
    197		vTaskDelay(TICKS_MS(1000));
    198	}
    199}
    200
    201static void
    202set_output(uint8_t bv)
    203{
    204	state = bv;
    205	for (int i = 0; i <= SPEAKER; i++) {
    206		gpio_set_level(ANDON_PINS[i], !(state & (1 << i)));
    207		vTaskDelay(10);
    208	}
    209}
    210
    211static void
    212wifi_event_handler(void *arg, esp_event_base_t event_base,
    213	int32_t event_id, void *event_data)
    214{
    215	if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
    216		snprintf(ip_str, sizeof(ip_str), "  connecting..  ");
    217		vTaskDelay(1);
    218		wifi_connected = false;
    219		esp_wifi_connect();
    220	} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
    221		wifi_connected = false;
    222		esp_wifi_connect();
    223		wifi_connect_attempt++;
    224		LOG("retry to connect to the AP");
    225		snprintf(ip_str, sizeof(ip_str), "  connecting..%c ",
    226			wifi_connect_attempt % 2 == 0 ? '.' : ' ');
    227		LOG("connect to the AP fail");
    228	} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
    229		ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
    230		LOG("got ip:" IPSTR, IP2STR(&event->ip_info.ip));
    231		char tmp[32];
    232		snprintf(tmp, sizeof(tmp), IPSTR, IP2STR(&event->ip_info.ip));
    233		snprintf(ip_str, sizeof(ip_str), "%-16s", tmp);
    234		wifi_connect_attempt = 0;
    235		wifi_connected = true;
    236		xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
    237	}
    238}
    239
    240static void
    241wifi_init(void)
    242{
    243	esp_err_t ret = nvs_flash_init();
    244	if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    245		ESP_ERROR_CHECK(nvs_flash_erase());
    246		ret = nvs_flash_init();
    247	}
    248	ESP_ERROR_CHECK(ret);
    249
    250	wifi_event_group = xEventGroupCreate();
    251	ESP_ERROR_CHECK(esp_netif_init());
    252
    253	ESP_ERROR_CHECK(esp_event_loop_create_default());
    254	esp_netif_create_default_wifi_sta();
    255
    256	wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    257	ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    258
    259	uint8_t mac[6] = { 0 };
    260	esp_wifi_get_mac(WIFI_IF_STA, mac);
    261	snprintf(mac_str, sizeof(mac_str), ":%02X:%02X:%02X:%02X:%02X",
    262		mac[1], mac[2], mac[3], mac[4], mac[5]);
    263
    264	esp_event_handler_instance_t instance_any_id;
    265	esp_event_handler_instance_t instance_got_ip;
    266	ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
    267		ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, &instance_any_id));
    268	ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
    269		IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, &instance_got_ip));
    270
    271	wifi_config_t wifi_config = {
    272		.sta = {
    273			.ssid = WIFI_SSID,
    274			.password = WIFI_PASS,
    275			.threshold.authmode = WIFI_AUTH_WPA2_PSK,
    276			.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
    277			.sae_h2e_identifier = "",
    278		},
    279	};
    280	ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    281	ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
    282	ESP_ERROR_CHECK(esp_wifi_start() );
    283
    284	LOG("wifi_init_sta finished.");
    285
    286	/* Waiting until either the connection is established (WIFI_CONNECTED_BIT)
    287	 * or connection failed for the maximum number of re-tries (WIFI_FAIL_BIT).
    288	 * The bits are set by event_handler() (see above) */
    289	xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT,
    290		pdFALSE, pdFALSE, portMAX_DELAY);
    291}
    292
    293static esp_err_t
    294http_get_handler(httpd_req_t *req)
    295{
    296	uint8_t current = state;
    297	for (int i = 0; i <= SPEAKER; i++) {
    298		if (current & (1 << i)) {
    299			httpd_resp_send_chunk(req, ANDON_NAMES[i],
    300				HTTPD_RESP_USE_STRLEN);
    301			httpd_resp_send_chunk(req, " ", 1);
    302		}
    303	}
    304	httpd_resp_send_chunk(req, NULL, 0);
    305	return ESP_OK;
    306}
    307
    308static esp_err_t
    309http_post_handler(httpd_req_t *req)
    310{
    311	char buf[128] = { 0 };
    312	int ret = httpd_req_recv(req, buf,
    313		MIN(sizeof(buf)-1, req->content_len));
    314	if ((ret <= 0)) {
    315		httpd_resp_send(req, "fail", 4);
    316		return ESP_FAIL;
    317	}
    318	uint8_t next = 0;
    319	for (char *c = buf; c < buf + 128; ) {
    320		size_t n = strcspn(c, "\t \n");
    321		for (int i = 0; n > 0 && i <= SPEAKER; i++) {
    322			if (n == strlen(ANDON_NAMES[i])
    323					&& !strncmp(c, ANDON_NAMES[i], n)) {
    324				next |= 1 << i;
    325				httpd_resp_send_chunk(req, ANDON_NAMES[i], n);
    326				httpd_resp_send_chunk(req, " ", 1);
    327			}
    328		}
    329		c += n + 1;
    330	}
    331	set_output(next);
    332	httpd_resp_send_chunk(req, NULL, 0);
    333	return ESP_OK;
    334}
    335
    336static void
    337http_init(void)
    338{
    339	httpd_handle_t server = NULL;
    340	httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    341	config.lru_purge_enable = true;
    342
    343	LOG("Starting http server");
    344
    345	if (httpd_start(&server, &config) != ESP_OK)
    346		PANIC("failed to start http server");
    347
    348	httpd_register_uri_handler(server, &http_get);
    349	httpd_register_uri_handler(server, &http_post);
    350}
    351
    352static void
    353test_init(void)
    354{
    355	uint8_t bv = 0;
    356	int tick = 0;
    357	while (1) {
    358		printf("Tick %i\n", ++tick);
    359		set_output(1 << bv);
    360		bv = (bv + 1) % (SPEAKER + 1);
    361		vTaskDelay(TICKS_MS(1000));
    362	}
    363}
    364
    365void
    366app_main(void)
    367{
    368	for (int i = 0; i <= SPEAKER; i++) {
    369		gpio_reset_pin(ANDON_PINS[i]);
    370		gpio_set_direction(ANDON_PINS[i], GPIO_MODE_OUTPUT_OD);
    371	 	gpio_set_level(ANDON_PINS[i], 1);
    372	}
    373
    374	printf("DESK ANDON\n");
    375	i2c_master_init(&display, 0, 1, -1);
    376	ssd1306_init(&display, 128, 64);
    377	ssd1306_clear_screen(&display, false);
    378	ssd1306_display_text(&display, 3, 24, "DESK ANDON", 10, false);
    379	vTaskDelay(TICKS_MS(500));
    380
    381	xTaskCreate(lcd_task_main, "lcd", 16 * 1024, NULL, tskIDLE_PRIORITY, &lcd_task);
    382	vTaskDelay(TICKS_MS(500));
    383
    384	// test_init();
    385
    386	wifi_init();
    387
    388	http_init();
    389
    390	while (1) vTaskDelay(1000);
    391}