/* * Copyright (c) 2018 Phytec Messtechnik GmbH * Copyright (c) 2018 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mesh.h" #include "board.h" #define STORAGE_PARTITION storage_partition #define STORAGE_PARTITION_DEV FIXED_PARTITION_DEVICE(STORAGE_PARTITION) #define STORAGE_PARTITION_OFFSET FIXED_PARTITION_OFFSET(STORAGE_PARTITION) #define STORAGE_PARTITION_SIZE FIXED_PARTITION_SIZE(STORAGE_PARTITION) enum font_size { FONT_SMALL = 0, FONT_MEDIUM = 1, FONT_BIG = 2, }; enum screen_ids { SCREEN_MAIN = 0, SCREEN_SENSORS = 1, SCREEN_STATS = 2, SCREEN_LAST, }; struct font_info { uint8_t columns; } fonts[] = { [FONT_BIG] = { .columns = 12 }, [FONT_MEDIUM] = { .columns = 16 }, [FONT_SMALL] = { .columns = 25 }, }; #define LONG_PRESS_TIMEOUT K_SECONDS(1) #define STAT_COUNT 128 static const struct device *const epd_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)); static bool pressed; static uint8_t screen_id = SCREEN_MAIN; static struct k_work_delayable epd_work; static struct k_work_delayable long_press_work; static char str_buf[256]; static const struct gpio_dt_spec leds[] = { GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios), GPIO_DT_SPEC_GET(DT_ALIAS(led1), gpios), GPIO_DT_SPEC_GET(DT_ALIAS(led2), gpios), }; static const struct gpio_dt_spec sw0_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios); struct k_work_delayable led_timer; static size_t print_line(enum font_size font_size, int row, const char *text, size_t len, bool center) { uint8_t font_height, font_width; uint8_t line[fonts[FONT_SMALL].columns + 1]; int pad; cfb_framebuffer_set_font(epd_dev, font_size); len = MIN(len, fonts[font_size].columns); memcpy(line, text, len); line[len] = '\0'; if (center) { pad = (fonts[font_size].columns - len) / 2U; } else { pad = 0; } cfb_get_font_size(epd_dev, font_size, &font_width, &font_height); if (cfb_print(epd_dev, line, font_width * pad, font_height * row)) { printk("Failed to print a string\n"); } return len; } static size_t get_len(enum font_size font, const char *text) { const char *space = NULL; size_t i; for (i = 0; i <= fonts[font].columns; i++) { switch (text[i]) { case '\n': case '\0': return i; case ' ': space = &text[i]; break; default: continue; } } /* If we got more characters than fits a line, and a space was * encountered, fall back to the last space. */ if (space) { return space - text; } return fonts[font].columns; } void board_blink_leds(void) { k_work_reschedule(&led_timer, K_MSEC(100)); } void board_show_text(const char *text, bool center, k_timeout_t duration) { int i; cfb_framebuffer_clear(epd_dev, false); for (i = 0; i < 3; i++) { size_t len; while (*text == ' ' || *text == '\n') { text++; } len = get_len(FONT_BIG, text); if (!len) { break; } text += print_line(FONT_BIG, i, text, len, center); if (!*text) { break; } } cfb_framebuffer_finalize(epd_dev); if (!K_TIMEOUT_EQ(duration, K_FOREVER)) { k_work_reschedule(&epd_work, duration); } } static struct stat { uint16_t addr; char name[9]; uint8_t min_hops; uint8_t max_hops; uint16_t hello_count; uint16_t heartbeat_count; } stats[STAT_COUNT] = { [0 ... (STAT_COUNT - 1)] = { .min_hops = BT_MESH_TTL_MAX, .max_hops = 0, }, }; static uint32_t stat_count; #define NO_UPDATE -1 static int add_hello(uint16_t addr, const char *name) { int i; for (i = 0; i < ARRAY_SIZE(stats); i++) { struct stat *stat = &stats[i]; if (!stat->addr) { stat->addr = addr; strncpy(stat->name, name, sizeof(stat->name) - 1); stat->hello_count = 1U; stat_count++; return i; } if (stat->addr == addr) { /* Update name, incase it has changed */ strncpy(stat->name, name, sizeof(stat->name) - 1); if (stat->hello_count < 0xffff) { stat->hello_count++; return i; } return NO_UPDATE; } } return NO_UPDATE; } static int add_heartbeat(uint16_t addr, uint8_t hops) { int i; for (i = 0; i < ARRAY_SIZE(stats); i++) { struct stat *stat = &stats[i]; if (!stat->addr) { stat->addr = addr; stat->heartbeat_count = 1U; stat->min_hops = hops; stat->max_hops = hops; stat_count++; return i; } if (stat->addr == addr) { if (hops < stat->min_hops) { stat->min_hops = hops; } else if (hops > stat->max_hops) { stat->max_hops = hops; } if (stat->heartbeat_count < 0xffff) { stat->heartbeat_count++; return i; } return NO_UPDATE; } } return NO_UPDATE; } void board_add_hello(uint16_t addr, const char *name) { uint32_t sort_i; sort_i = add_hello(addr, name); if (sort_i != NO_UPDATE) { } } void board_add_heartbeat(uint16_t addr, uint8_t hops) { uint32_t sort_i; sort_i = add_heartbeat(addr, hops); if (sort_i != NO_UPDATE) { } } static void show_statistics(void) { int top[4] = { -1, -1, -1, -1 }; int len, i, line = 0; struct stat *stat; char str[32]; cfb_framebuffer_clear(epd_dev, false); len = snprintk(str, sizeof(str), "Own Address: 0x%04x", mesh_get_addr()); print_line(FONT_SMALL, line++, str, len, false); len = snprintk(str, sizeof(str), "Node Count: %u", stat_count + 1); print_line(FONT_SMALL, line++, str, len, false); /* Find the top sender */ for (i = 0; i < ARRAY_SIZE(stats); i++) { int j; stat = &stats[i]; if (!stat->addr) { break; } if (!stat->hello_count) { continue; } for (j = 0; j < ARRAY_SIZE(top); j++) { if (top[j] < 0) { top[j] = i; break; } if (stat->hello_count <= stats[top[j]].hello_count) { continue; } /* Move other elements down the list */ if (j < ARRAY_SIZE(top) - 1) { memmove(&top[j + 1], &top[j], ((ARRAY_SIZE(top) - j - 1) * sizeof(top[j]))); } top[j] = i; break; } } if (stat_count > 0) { len = snprintk(str, sizeof(str), "Most messages from:"); print_line(FONT_SMALL, line++, str, len, false); for (i = 0; i < ARRAY_SIZE(top); i++) { if (top[i] < 0) { break; } stat = &stats[top[i]]; len = snprintk(str, sizeof(str), "%-3u 0x%04x %s", stat->hello_count, stat->addr, stat->name); print_line(FONT_SMALL, line++, str, len, false); } } cfb_framebuffer_finalize(epd_dev); } static void show_sensors_data(k_timeout_t interval) { struct sensor_value val[3]; uint8_t line = 0U; uint16_t len = 0U; cfb_framebuffer_clear(epd_dev, false); /* hdc1010 */ if (get_hdc1010_val(val)) { goto _error_get; } len = snprintf(str_buf, sizeof(str_buf), "Temperature:%d.%d C\n", val[0].val1, val[0].val2 / 100000); print_line(FONT_SMALL, line++, str_buf, len, false); len = snprintf(str_buf, sizeof(str_buf), "Humidity:%d%%\n", val[1].val1); print_line(FONT_SMALL, line++, str_buf, len, false); /* mma8652 */ if (get_mma8652_val(val)) { goto _error_get; } len = snprintf(str_buf, sizeof(str_buf), "AX :%10.3f\n", sensor_value_to_double(&val[0])); print_line(FONT_SMALL, line++, str_buf, len, false); len = snprintf(str_buf, sizeof(str_buf), "AY :%10.3f\n", sensor_value_to_double(&val[1])); print_line(FONT_SMALL, line++, str_buf, len, false); len = snprintf(str_buf, sizeof(str_buf), "AZ :%10.3f\n", sensor_value_to_double(&val[2])); print_line(FONT_SMALL, line++, str_buf, len, false); /* apds9960 */ if (get_apds9960_val(val)) { goto _error_get; } len = snprintf(str_buf, sizeof(str_buf), "Light :%d\n", val[0].val1); print_line(FONT_SMALL, line++, str_buf, len, false); len = snprintf(str_buf, sizeof(str_buf), "Proximity:%d\n", val[1].val1); print_line(FONT_SMALL, line++, str_buf, len, false); cfb_framebuffer_finalize(epd_dev); k_work_reschedule(&epd_work, interval); return; _error_get: printk("Failed to get sensor data or print a string\n"); } static void show_main(void) { char buf[CONFIG_BT_DEVICE_NAME_MAX]; int i; strncpy(buf, bt_get_name(), sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; /* Convert commas to newlines */ for (i = 0; buf[i] != '\0'; i++) { if (buf[i] == ',') { buf[i] = '\n'; } } board_show_text(buf, true, K_FOREVER); } static void epd_update(struct k_work *work) { switch (screen_id) { case SCREEN_STATS: show_statistics(); return; case SCREEN_SENSORS: show_sensors_data(K_SECONDS(2)); return; case SCREEN_MAIN: show_main(); return; } } static void long_press(struct k_work *work) { /* Treat as release so actual release doesn't send messages */ pressed = false; screen_id = (screen_id + 1) % SCREEN_LAST; printk("Change screen to id = %d\n", screen_id); board_refresh_display(); } static bool button_is_pressed(void) { return gpio_pin_get_dt(&sw0_gpio) > 0; } static void button_interrupt(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { if (button_is_pressed() == pressed) { return; } pressed = !pressed; printk("Button %s\n", pressed ? "pressed" : "released"); if (pressed) { k_work_reschedule(&long_press_work, LONG_PRESS_TIMEOUT); return; } k_work_cancel_delayable(&long_press_work); if (!mesh_is_initialized()) { return; } /* Short press for views */ switch (screen_id) { case SCREEN_SENSORS: case SCREEN_STATS: return; case SCREEN_MAIN: if (pins & BIT(sw0_gpio.pin)) { uint32_t uptime = k_uptime_get_32(); static uint32_t bad_count, press_ts; if (uptime - press_ts < 500) { bad_count++; } else { bad_count = 0U; } press_ts = uptime; if (bad_count) { if (bad_count > 5) { mesh_send_baduser(); bad_count = 0U; } else { printk("Ignoring press\n"); } } else { mesh_send_hello(); } } return; default: return; } } static int configure_button(void) { static struct gpio_callback button_cb; if (!gpio_is_ready_dt(&sw0_gpio)) { printk("%s: device not ready.\n", sw0_gpio.port->name); return -ENODEV; } gpio_pin_configure_dt(&sw0_gpio, GPIO_INPUT); gpio_pin_interrupt_configure_dt(&sw0_gpio, GPIO_INT_EDGE_BOTH); gpio_init_callback(&button_cb, button_interrupt, BIT(sw0_gpio.pin)); gpio_add_callback(sw0_gpio.port, &button_cb); return 0; } int set_led_state(uint8_t id, bool state) { return gpio_pin_set_dt(&leds[id], state); } static void led_timeout(struct k_work *work) { static int led_cntr; int i; /* Disable all LEDs */ for (i = 0; i < ARRAY_SIZE(leds); i++) { set_led_state(i, 0); } /* Stop after 5 iterations */ if (led_cntr >= (ARRAY_SIZE(leds) * 5)) { led_cntr = 0; return; } /* Select and enable current LED */ i = led_cntr++ % ARRAY_SIZE(leds); set_led_state(i, 1); k_work_reschedule(&led_timer, K_MSEC(100)); } static int configure_leds(void) { int i; for (i = 0; i < ARRAY_SIZE(leds); i++) { if (!gpio_is_ready_dt(&leds[i])) { printk("%s: device not ready.\n", leds[i].port->name); return -ENODEV; } gpio_pin_configure_dt(&leds[i], GPIO_OUTPUT_INACTIVE); } k_work_init_delayable(&led_timer, led_timeout); return 0; } static int erase_storage(void) { const struct device *dev = STORAGE_PARTITION_DEV; if (!device_is_ready(dev)) { printk("Flash device not ready\n"); return -ENODEV; } return flash_erase(dev, STORAGE_PARTITION_OFFSET, STORAGE_PARTITION_SIZE); } void board_refresh_display(void) { k_work_reschedule(&epd_work, K_NO_WAIT); } int board_init(void) { if (!device_is_ready(epd_dev)) { printk("%s: device not ready.\n", epd_dev->name); return -ENODEV; } if (cfb_framebuffer_init(epd_dev)) { printk("Framebuffer initialization failed\n"); return -EIO; } cfb_framebuffer_clear(epd_dev, true); if (configure_button()) { printk("Failed to configure button\n"); return -EIO; } if (configure_leds()) { printk("LED init failed\n"); return -EIO; } k_work_init_delayable(&epd_work, epd_update); k_work_init_delayable(&long_press_work, long_press); pressed = button_is_pressed(); if (pressed) { printk("Erasing storage\n"); board_show_text("Resetting Device", false, K_SECONDS(4)); erase_storage(); } return 0; }