├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── main ├── CMakeLists.txt ├── api.c ├── api.h ├── battery_gauge.c ├── battery_gauge.h ├── battery_protection.c ├── battery_protection.h ├── bq24715_charger.c ├── bq24715_charger.h ├── bq40z50_gauge.c ├── bq40z50_gauge.h ├── buttons.c ├── buttons.h ├── delay.c ├── delay.h ├── display.c ├── display.h ├── display_bms.c ├── display_bms.h ├── display_network.c ├── display_network.h ├── display_on_battery.c ├── display_on_battery.h ├── display_power.c ├── display_power.h ├── display_screensaver.c ├── display_screensaver.h ├── display_system.c ├── display_system.h ├── ethernet.c ├── ethernet.h ├── event_bus.c ├── event_bus.h ├── fb.h ├── font_3x5.c ├── font_3x5.h ├── futil.c ├── futil.h ├── gpio_hc595.c ├── gpio_hc595.h ├── gui.c ├── gui.h ├── gui_priv.h ├── httpd.c ├── httpd.h ├── i2c_bus.c ├── i2c_bus.h ├── ina219.c ├── ina219.h ├── kvparser.c ├── kvparser.h ├── list.h ├── lm75.c ├── lm75.h ├── magic.c ├── magic.h ├── main.c ├── mime.c ├── mime.h ├── power_path.c ├── power_path.h ├── prometheus.c ├── prometheus.h ├── prometheus_exporter.c ├── prometheus_exporter.h ├── prometheus_metrics.c ├── prometheus_metrics.h ├── prometheus_metrics_battery.c ├── prometheus_metrics_battery.h ├── ring.c ├── ring.h ├── scheduler.c ├── scheduler.h ├── sensor.c ├── sensor.h ├── settings.c ├── settings.h ├── smbus.c ├── smbus.h ├── ssd1306_oled.c ├── ssd1306_oled.h ├── template.c ├── template.h ├── util.c ├── util.h ├── vendor.c ├── vendor.h ├── website.c ├── website.h ├── wifi.c └── wifi.h ├── partitions.csv ├── sdkconfig ├── sdkconfig.old └── webroot ├── binding.js ├── bootstrap.bundle.min.js ├── bootstrap.min.css ├── dcin.thtml ├── dcout.thtml ├── dcout_settings.thtml ├── index.js ├── index.thtml ├── jquery-1.8.3.min.js ├── navbar.thtml ├── resources.inc └── status_panel.thtml /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "esp-idf"] 2 | path = esp-idf 3 | url = https://github.com/espressif/esp-idf 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's CMakeLists 2 | # in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(dc_ups) 7 | 8 | spiffs_create_partition_image(webroot webroot FLASH_IN_PROJECT) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tobias Schramm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCE_FILES 2 | api.c 3 | battery_gauge.c 4 | battery_protection.c 5 | bq24715_charger.c 6 | bq40z50_gauge.c 7 | buttons.c 8 | delay.c 9 | display.c 10 | display_bms.c 11 | display_network.c 12 | display_on_battery.c 13 | display_power.c 14 | display_screensaver.c 15 | display_system.c 16 | ethernet.c 17 | event_bus.c 18 | font_3x5.c 19 | futil.c 20 | gpio_hc595.c 21 | gui.c 22 | httpd.c 23 | i2c_bus.c 24 | ina219.c 25 | kvparser.c 26 | lm75.c 27 | magic.c 28 | main.c 29 | mime.c 30 | power_path.c 31 | prometheus.c 32 | prometheus_exporter.c 33 | prometheus_metrics.c 34 | prometheus_metrics_battery.c 35 | ring.c 36 | scheduler.c 37 | sensor.c 38 | settings.c 39 | smbus.c 40 | ssd1306_oled.c 41 | template.c 42 | util.c 43 | vendor.c 44 | website.c 45 | wifi.c) 46 | 47 | idf_component_register(SRCS ${SOURCE_FILES} 48 | INCLUDE_DIRS ".") 49 | 50 | execute_process(COMMAND git describe --always --dirty OUTPUT_VARIABLE ups_app_version_) 51 | string(STRIP ${ups_app_version_} ups_app_version) 52 | add_compile_definitions(UPS_APP_VERSION=${ups_app_version}) 53 | -------------------------------------------------------------------------------- /main/api.c: -------------------------------------------------------------------------------- 1 | #include "api.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "power_path.h" 8 | 9 | static esp_err_t http_get_set_input_current_limit(struct httpd_request_ctx* ctx, void* priv) { 10 | ssize_t param_len; 11 | char* current_limit_str; 12 | unsigned long current_limit_ma; 13 | int err; 14 | 15 | if((param_len = httpd_query_string_get_param(ctx, "current_ma", ¤t_limit_str)) <= 0) { 16 | return httpd_send_error(ctx, HTTPD_400); 17 | } 18 | 19 | errno = 0; 20 | current_limit_ma = strtoul(current_limit_str, NULL, 10); 21 | if (current_limit_ma == ULONG_MAX || errno) { 22 | return httpd_send_error(ctx, HTTPD_400); 23 | } 24 | 25 | power_path_set_input_current_limit(current_limit_ma); 26 | 27 | httpd_finalize_response(ctx); 28 | return ESP_OK; 29 | } 30 | 31 | void api_init(httpd_t *httpd) { 32 | ESP_ERROR_CHECK(httpd_add_get_handler(httpd, "/api/v1/set_input_current_limit", http_get_set_input_current_limit, NULL, 1, "current_ma")); 33 | } 34 | -------------------------------------------------------------------------------- /main/api.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "httpd.h" 4 | 5 | void api_init(httpd_t *httpd); 6 | -------------------------------------------------------------------------------- /main/battery_gauge.c: -------------------------------------------------------------------------------- 1 | #include "battery_gauge.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "event_bus.h" 8 | #include "scheduler.h" 9 | #include "util.h" 10 | 11 | #define GAUGE_UPDATE_INTERVAL_MS 2000 12 | 13 | static const char *TAG = "gauge"; 14 | 15 | static int32_t battery_params[BATTERY_PARAM_MAX_ + 1] = { 0 }; 16 | static battery_gauge_t *gauge; 17 | 18 | static scheduler_task_t gauge_update_task; 19 | 20 | static void gauge_update(void *ctx); 21 | static void gauge_update(void *ctx) { 22 | battery_gauge_t *gauge = ctx; 23 | battery_param_t param; 24 | bool changed = false; 25 | 26 | for (param = BATTERY_VOLTAGE_MV; param < ARRAY_SIZE(battery_params); param++) { 27 | int err; 28 | int32_t val; 29 | 30 | err = gauge->ops->get_param(gauge, param, &val); 31 | if (err) { 32 | if (err == ENOTSUP) { 33 | ESP_LOGD(TAG, "Gauge does not support parameter %d", param); 34 | } else { 35 | ESP_LOGE(TAG, "Failed to get parameter %d from gauge: %d", param, err); 36 | } 37 | } else if (val != battery_params[param]) { 38 | battery_params[param] = val; 39 | changed = true; 40 | } 41 | } 42 | 43 | if (changed) { 44 | event_bus_notify("battery_gauge", NULL); 45 | } 46 | scheduler_schedule_task_relative(&gauge_update_task, gauge_update, gauge, MS_TO_US(GAUGE_UPDATE_INTERVAL_MS)); 47 | } 48 | 49 | void battery_gauge_init(battery_gauge_t *gauge_) { 50 | gauge = gauge_; 51 | 52 | scheduler_task_init(&gauge_update_task); 53 | scheduler_schedule_task_relative(&gauge_update_task, gauge_update, gauge, 0); 54 | } 55 | 56 | unsigned int battery_gauge_get_soc_percent(void) { 57 | return CLAMP(battery_params[BATTERY_SOC_PERCENT], 0, 100); 58 | } 59 | 60 | unsigned int battery_gauge_get_soh_percent(void) { 61 | return CLAMP(battery_params[BATTERY_SOH_PERCENT], 0, 100); 62 | } 63 | 64 | long battery_gauge_get_current_ma(void) { 65 | return battery_params[BATTERY_CURRENT_MA]; 66 | } 67 | 68 | unsigned int battery_gauge_get_time_to_empty_min(void) { 69 | return MAX(battery_params[BATTERY_TIME_TO_EMPTY_MIN], 0); 70 | } 71 | 72 | unsigned int battery_gauge_get_cell1_voltage_mv(void) { 73 | return CLAMP(battery_params[BATTERY_VOLTAGE_CELL1_MV], 0, 5000); 74 | } 75 | 76 | unsigned int battery_gauge_get_cell2_voltage_mv(void) { 77 | return CLAMP(battery_params[BATTERY_VOLTAGE_CELL2_MV], 0, 5000); 78 | } 79 | 80 | long battery_gauge_get_temperature_mdegc(void) { 81 | return battery_params[BATTERY_TEMPERATURE_MDEG_C]; 82 | } 83 | 84 | unsigned int battery_gauge_get_full_charge_capacity_mah(void) { 85 | return battery_params[BATTERY_FULL_CHARGE_CAPACITY_MAH]; 86 | } 87 | 88 | unsigned int battery_gauge_get_at_rate_time_to_empty_min(void) { 89 | return battery_params[BATTERY_AT_RATE_TIME_TO_EMPTY_MIN]; 90 | } 91 | 92 | void battery_gauge_set_at_rate(int rate_ma) { 93 | gauge->ops->set_param(gauge, BATTERY_AT_RATE_MA, rate_ma); 94 | } 95 | -------------------------------------------------------------------------------- /main/battery_gauge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef enum battery_param { 6 | BATTERY_VOLTAGE_MV, 7 | BATTERY_VOLTAGE_CELL1_MV, 8 | BATTERY_VOLTAGE_CELL2_MV, 9 | BATTERY_SOC_PERCENT, 10 | BATTERY_SOH_PERCENT, 11 | BATTERY_CURRENT_MA, 12 | BATTERY_TIME_TO_EMPTY_MIN, 13 | BATTERY_AT_RATE_TIME_TO_EMPTY_MIN, 14 | BATTERY_AT_RATE_MA, 15 | BATTERY_TEMPERATURE_MDEG_C, 16 | BATTERY_FULL_CHARGE_CAPACITY_MAH, 17 | BATTERY_PARAM_MAX_ = BATTERY_FULL_CHARGE_CAPACITY_MAH 18 | } battery_param_t; 19 | 20 | 21 | typedef struct battery_gauge battery_gauge_t; 22 | typedef struct battery_gauge_ops { 23 | int (*get_param)(battery_gauge_t *gauge, battery_param_t param, int32_t *retval); 24 | int (*set_param)(battery_gauge_t *gauge, battery_param_t param, int32_t val); 25 | } battery_gauge_ops_t; 26 | 27 | 28 | struct battery_gauge { 29 | void *priv; 30 | const battery_gauge_ops_t *ops; 31 | }; 32 | 33 | void battery_gauge_init(battery_gauge_t *gauge); 34 | 35 | unsigned int battery_gauge_get_soc_percent(void); 36 | unsigned int battery_gauge_get_soh_percent(void); 37 | long battery_gauge_get_current_ma(void); 38 | unsigned int battery_gauge_get_time_to_empty_min(void); 39 | unsigned int battery_gauge_get_cell1_voltage_mv(void); 40 | unsigned int battery_gauge_get_cell2_voltage_mv(void); 41 | long battery_gauge_get_temperature_mdegc(void); 42 | unsigned int battery_gauge_get_full_charge_capacity_mah(void); 43 | unsigned int battery_gauge_get_at_rate_time_to_empty_min(void); 44 | void battery_gauge_set_at_rate(int rate_ma); 45 | -------------------------------------------------------------------------------- /main/battery_protection.c: -------------------------------------------------------------------------------- 1 | #include "battery_protection.h" 2 | 3 | #include "battery_gauge.h" 4 | #include "power_path.h" 5 | #include "scheduler.h" 6 | 7 | #define MIN_CELL_VOLTAGE_MV 2800 8 | #define UNDERVOLTAGE_SHUTDOWN_SECONDS 10 9 | 10 | static bq40z50_t *gauge; 11 | static unsigned int undervoltage_seconds = 0; 12 | static scheduler_task_t gauge_poll_task; 13 | 14 | static bool is_undervoltage(unsigned int cell_voltage_mv) { 15 | if (cell_voltage_mv == 0) { 16 | return false; 17 | } 18 | 19 | return cell_voltage_mv < MIN_CELL_VOLTAGE_MV; 20 | } 21 | 22 | static void gauge_poll_voltage_cb(void *ctx); 23 | static void gauge_poll_voltage_cb(void *ctx) { 24 | unsigned int voltage_cell1_mv = battery_gauge_get_cell1_voltage_mv(); 25 | unsigned int voltage_cell2_mv = battery_gauge_get_cell2_voltage_mv(); 26 | 27 | if (power_path_is_running_on_battery() && 28 | (is_undervoltage(voltage_cell1_mv) || is_undervoltage(voltage_cell2_mv))) { 29 | undervoltage_seconds++; 30 | 31 | if (undervoltage_seconds >= UNDERVOLTAGE_SHUTDOWN_SECONDS) { 32 | bq40z50_shutdown(gauge); 33 | undervoltage_seconds = 0; 34 | } 35 | } else { 36 | undervoltage_seconds = 0; 37 | } 38 | 39 | scheduler_schedule_task_relative(&gauge_poll_task, gauge_poll_voltage_cb, NULL, MS_TO_US(1000)); 40 | } 41 | 42 | void battery_protection_init(bq40z50_t *gauge_) { 43 | gauge = gauge_; 44 | scheduler_task_init(&gauge_poll_task); 45 | scheduler_schedule_task_relative(&gauge_poll_task, gauge_poll_voltage_cb, NULL, MS_TO_US(5000)); 46 | } 47 | 48 | -------------------------------------------------------------------------------- /main/battery_protection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "bq40z50_gauge.h" 4 | 5 | void battery_protection_init(bq40z50_t *gauge); 6 | -------------------------------------------------------------------------------- /main/bq24715_charger.c: -------------------------------------------------------------------------------- 1 | #include "bq24715_charger.h" 2 | #include "smbus.h" 3 | 4 | #include 5 | 6 | #define SMBUS_ADDRESS 0x09 7 | #define DEVICE_ID 0x10 8 | #define MANUFACTURER_ID 0x40 9 | 10 | #define CMD_CHARGE_CURRENT 0x14 11 | #define CMD_MAX_CHARGE_VOLTAGE 0x15 12 | #define CMD_MIN_SYSTEM_VOLTAGE 0x3e 13 | #define CMD_INPUT_CURRENT 0x3f 14 | #define CMD_MANUFACTURER_ID 0xfe 15 | #define CMD_DEVICE_ID 0xff 16 | 17 | static const char *TAG = "bq24715_charger"; 18 | 19 | esp_err_t bq24715_init(bq24715_t *charger, smbus_t *smbus) { 20 | uint8_t word[2]; 21 | esp_err_t err = smbus_read_word(smbus, SMBUS_ADDRESS, CMD_MANUFACTURER_ID, word); 22 | if (err) { 23 | ESP_LOGE(TAG, "Failed to read manufacturer ID"); 24 | return err; 25 | } 26 | if (word[0] != MANUFACTURER_ID) { 27 | ESP_LOGE(TAG, "Invalid manufacturer ID, should be 0x%02X but is 0x%02X", 28 | MANUFACTURER_ID, word[0]); 29 | return ESP_ERR_INVALID_RESPONSE; 30 | } 31 | err = smbus_read_word(smbus, SMBUS_ADDRESS, CMD_DEVICE_ID, word); 32 | if (err) { 33 | ESP_LOGE(TAG, "Failed to read device ID"); 34 | return err; 35 | } 36 | if (word[0] != DEVICE_ID) { 37 | ESP_LOGE(TAG, "Invalid device ID, should be 0x%02X but is 0x%02X", 38 | DEVICE_ID, word[0]); 39 | return ESP_ERR_INVALID_RESPONSE; 40 | } 41 | 42 | charger->bus = smbus; 43 | return ESP_OK; 44 | } 45 | 46 | esp_err_t bq24715_set_charge_current(bq24715_t *charger, unsigned int current_ma) { 47 | if (current_ma > 8192) { 48 | return ESP_ERR_INVALID_ARG; 49 | } 50 | current_ma &= ~((uint16_t)0xe03f); 51 | uint8_t word[2] = { current_ma & 0xff, current_ma >> 8 }; 52 | return smbus_write_word(charger->bus, SMBUS_ADDRESS, CMD_CHARGE_CURRENT, word); 53 | } 54 | 55 | esp_err_t bq24715_set_max_charge_voltage(bq24715_t *charger, unsigned int voltage_mv) { 56 | if (voltage_mv < 4096 || voltage_mv > 14500) { 57 | return ESP_ERR_INVALID_ARG; 58 | } 59 | voltage_mv &= ~((uint16_t)0x0f); 60 | uint8_t word[2] = { voltage_mv & 0xff, voltage_mv >> 8 }; 61 | return smbus_write_word(charger->bus, SMBUS_ADDRESS, CMD_MAX_CHARGE_VOLTAGE, word); 62 | } 63 | 64 | esp_err_t bq24715_set_min_system_voltage(bq24715_t *charger, unsigned int voltage_mv) { 65 | if (voltage_mv < 4096 || voltage_mv > 14500) { 66 | return ESP_ERR_INVALID_ARG; 67 | } 68 | voltage_mv &= ~((uint16_t)0xff); 69 | uint8_t word[2] = { voltage_mv & 0xff, voltage_mv >> 8 }; 70 | return smbus_write_word(charger->bus, SMBUS_ADDRESS, CMD_MIN_SYSTEM_VOLTAGE, word); 71 | } 72 | 73 | esp_err_t bq24715_set_input_current(bq24715_t *charger, unsigned int current_ma) { 74 | if (current_ma < 128 || current_ma > 8064) { 75 | return ESP_ERR_INVALID_ARG; 76 | } 77 | current_ma &= ~((uint16_t)0x3f); 78 | uint8_t word[2] = { current_ma & 0xff, current_ma >> 8 }; 79 | return smbus_write_word(charger->bus, SMBUS_ADDRESS, CMD_INPUT_CURRENT, word); 80 | } 81 | -------------------------------------------------------------------------------- /main/bq24715_charger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "smbus.h" 4 | 5 | typedef struct bq24715 { 6 | smbus_t *bus; 7 | } bq24715_t; 8 | 9 | esp_err_t bq24715_init(bq24715_t *charger, smbus_t *smbus); 10 | esp_err_t bq24715_set_charge_current(bq24715_t *charger, unsigned int current_ma); 11 | esp_err_t bq24715_set_max_charge_voltage(bq24715_t *charger, unsigned int voltage_mv); 12 | esp_err_t bq24715_set_min_system_voltage(bq24715_t *charger, unsigned int voltage_mv); 13 | esp_err_t bq24715_set_input_current(bq24715_t *charger, unsigned int current_ma); 14 | -------------------------------------------------------------------------------- /main/bq40z50_gauge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "battery_gauge.h" 6 | #include "smbus.h" 7 | #include "sensor.h" 8 | 9 | typedef struct bq40z50 { 10 | smbus_t *bus; 11 | unsigned int address; 12 | sensor_t sensor; 13 | battery_gauge_t gauge; 14 | } bq40z50_t; 15 | 16 | typedef enum { 17 | BQ40Z50_CELL_1, 18 | BQ40Z50_CELL_2 19 | } bq40z50_cell_t; 20 | 21 | esp_err_t bq40z50_init(bq40z50_t *gauge, smbus_t *bus, int address); 22 | 23 | esp_err_t bq40z50_get_battery_voltage_mv(bq40z50_t *gauge, unsigned int *res); 24 | esp_err_t bq40z50_get_battery_temperature_mdegc(bq40z50_t *gauge, int32_t *res); 25 | esp_err_t bq40z50_get_cell_voltage_mv(bq40z50_t *gauge, bq40z50_cell_t cell, unsigned int *res); 26 | esp_err_t bq40z50_get_state_of_charge_percent(bq40z50_t *gauge, unsigned int *res); 27 | esp_err_t bq40z50_get_state_of_health_percent(bq40z50_t *gauge, unsigned int *res); 28 | esp_err_t bq40z50_get_full_charge_capacity_mah(bq40z50_t *gauge, unsigned int *res); 29 | esp_err_t bq40z50_get_current_ma(bq40z50_t *gauge, int *res); 30 | esp_err_t bq40z50_get_charging_current_ma(bq40z50_t *gauge, unsigned int *res); 31 | esp_err_t bq40z50_get_charging_voltage_mv(bq40z50_t *gauge, unsigned int *res); 32 | esp_err_t bq40z50_get_run_time_to_empty_min(bq40z50_t *gauge, unsigned int *res); 33 | esp_err_t bq40z50_shutdown(bq40z50_t *gauge); 34 | esp_err_t bq40z50_set_at_rate_ma(bq40z50_t *gauge, int rate_ma); 35 | esp_err_t bq40z50_get_at_rate_time_to_empty_min(bq40z50_t *gauge, unsigned int *res); 36 | -------------------------------------------------------------------------------- /main/buttons.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "list.h" 6 | 7 | typedef enum button { 8 | BUTTON_UP, 9 | BUTTON_DOWN, 10 | BUTTON_ENTER, 11 | BUTTON_EXIT 12 | } button_t; 13 | 14 | typedef enum button_action { 15 | BUTTON_ACTION_PRESS, 16 | BUTTON_ACTION_HOLD, 17 | BUTTON_ACTION_RELEASE, 18 | } button_action_t; 19 | 20 | typedef struct button_event { 21 | button_t button; 22 | button_action_t action; 23 | unsigned int press_duration_ms; 24 | } button_event_t; 25 | 26 | typedef enum button_event_handler_type { 27 | BUTTON_EVENT_HANDLER_MULTI, 28 | BUTTON_EVENT_HANDLER_SINGLE, 29 | } button_event_handler_type_t; 30 | 31 | typedef bool (*button_event_cb_f)(const button_event_t *event, void *priv); 32 | 33 | typedef struct button_event_handler_base_cfg { 34 | button_event_cb_f cb; 35 | void *ctx; 36 | } button_event_handler_base_cfg_t; 37 | 38 | typedef struct button_event_handler_multi_cfg { 39 | unsigned int button_filter; 40 | unsigned int action_filter; 41 | } button_event_handler_multi_cfg_t; 42 | 43 | typedef struct button_event_handler_single_cfg { 44 | button_t button; 45 | button_action_t action; 46 | unsigned int min_hold_duration_ms; 47 | } button_event_handler_single_cfg_t; 48 | 49 | typedef struct button_event_handler { 50 | struct list_head list; 51 | struct list_head shadow_list; 52 | bool enabled; 53 | button_event_handler_type_t type; 54 | button_event_handler_base_cfg_t base; 55 | union { 56 | struct { 57 | button_event_handler_multi_cfg_t cfg; 58 | } multi; 59 | struct { 60 | button_event_handler_single_cfg_t cfg; 61 | bool dispatched; 62 | } single; 63 | }; 64 | } button_event_handler_t; 65 | 66 | typedef struct button_event_handler_multi_user_cfg { 67 | button_event_handler_base_cfg_t base; 68 | button_event_handler_multi_cfg_t multi; 69 | } button_event_handler_multi_user_cfg_t; 70 | 71 | typedef struct button_event_handler_single_user_cfg { 72 | button_event_handler_base_cfg_t base; 73 | button_event_handler_single_cfg_t single; 74 | } button_event_handler_single_user_cfg_t; 75 | 76 | void buttons_init(void); 77 | const char *button_to_name(button_t button); 78 | void buttons_register_multi_button_event_handler(button_event_handler_t *handler, const button_event_handler_multi_user_cfg_t *cfg); 79 | void buttons_register_single_button_event_handler(button_event_handler_t *handler, const button_event_handler_single_user_cfg_t *cfg); 80 | void buttons_unregister_event_handler(button_event_handler_t *handler); 81 | void buttons_enable_event_handler(button_event_handler_t *handler); 82 | void buttons_disable_event_handler(button_event_handler_t *handler); 83 | void buttons_emulate_press(button_t button, unsigned int press_duration_ms); 84 | -------------------------------------------------------------------------------- /main/delay.c: -------------------------------------------------------------------------------- 1 | #include "delay.h" 2 | 3 | void delay_us(uint64_t num_of_us) { 4 | uint64_t now = esp_timer_get_time(); 5 | uint64_t then = now + num_of_us; 6 | while (esp_timer_get_time() < then); 7 | } 8 | -------------------------------------------------------------------------------- /main/delay.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | void delay_us(uint64_t num_of_us); 8 | -------------------------------------------------------------------------------- /main/display.c: -------------------------------------------------------------------------------- 1 | #include "display.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "buttons.h" 14 | #include "display_bms.h" 15 | #include "display_network.h" 16 | #include "display_on_battery.h" 17 | #include "display_power.h" 18 | #include "display_screensaver.h" 19 | #include "display_system.h" 20 | #include "event_bus.h" 21 | #include "fb.h" 22 | #include "gui.h" 23 | #include "power_path.h" 24 | #include "scheduler.h" 25 | #include "ssd1306_oled.h" 26 | 27 | #define DISPLAY_I2C_ADDRESS 0x3c 28 | #define GPIO_OLED_RESET 23 29 | 30 | #define SCREENSAVER_TIMEOUT_MS 30000 31 | 32 | typedef enum display_screen_type { 33 | DISPLAY_SCREEN_BMS, 34 | DISPLAY_SCREEN_POWER, 35 | DISPLAY_SCREEN_NETWORK, 36 | DISPLAY_SCREEN_SYSTEM, 37 | DISPLAY_SCREEN_MAX_ = DISPLAY_SCREEN_SYSTEM 38 | } display_screen_type_t; 39 | 40 | static const char *TAG = "display"; 41 | 42 | static SemaphoreHandle_t lock; 43 | static StaticSemaphore_t lock_buffer; 44 | 45 | unsigned int selected_screen = DISPLAY_SCREEN_BMS; 46 | static const display_screen_t *screens[DISPLAY_SCREEN_MAX_ + 1] = { NULL }; 47 | static const display_screen_t *active_screen = NULL; 48 | static const display_screen_t *screen_screensaver = NULL; 49 | static const display_screen_t *screen_on_battery = NULL; 50 | static bool showing_screensaver = false; 51 | static scheduler_task_t screensaver_timeout_task; 52 | 53 | static gui_label_t title_label; 54 | 55 | static ssd1306_oled_t oled; 56 | static bool display_initialized = false; 57 | 58 | static gui_t gui; 59 | 60 | static TaskHandle_t render_task = NULL; 61 | 62 | static uint8_t gui_render_fb[64 * 48]; 63 | static fb_t display_fb; 64 | 65 | button_event_handler_t button_event_handler; 66 | event_bus_handler_t power_source_event_handler; 67 | 68 | static void gui_request_render(const gui_t *gui) { 69 | if (render_task) { 70 | xTaskNotifyGive(render_task); 71 | } 72 | } 73 | 74 | const gui_ops_t gui_ops = { 75 | .request_render = gui_request_render 76 | }; 77 | 78 | static void show_screen(const display_screen_t *screen) { 79 | if (screen->name) { 80 | gui_label_set_text(&title_label, screen->name); 81 | gui_element_set_hidden(&title_label.element, false); 82 | gui_element_show(&title_label.element); 83 | } else { 84 | gui_element_set_hidden(&title_label.element, true); 85 | } 86 | screen->show(); 87 | active_screen = screen; 88 | } 89 | 90 | static void hide_screen(const display_screen_t *screen) { 91 | gui_element_set_hidden(&title_label.element, false); 92 | screen->hide(); 93 | active_screen = NULL; 94 | } 95 | 96 | static void show_screensaver(void) { 97 | if (active_screen) { 98 | hide_screen(active_screen); 99 | } 100 | showing_screensaver = true; 101 | if (power_path_is_running_on_battery()) { 102 | show_screen(screen_on_battery); 103 | } else { 104 | show_screen(screen_screensaver); 105 | } 106 | } 107 | 108 | static void show_screensaver_cb(void *ctx) { 109 | xSemaphoreTake(lock, portMAX_DELAY); 110 | show_screensaver(); 111 | xSemaphoreGive(lock); 112 | } 113 | 114 | static bool reset_screensaver(void) { 115 | bool was_screensaver_active = false; 116 | 117 | scheduler_abort_task(&screensaver_timeout_task); 118 | if (showing_screensaver) { 119 | showing_screensaver = false; 120 | hide_screen(active_screen); 121 | was_screensaver_active = true; 122 | show_screen(screens[selected_screen]); 123 | } 124 | scheduler_schedule_task_relative(&screensaver_timeout_task, show_screensaver_cb, NULL, MS_TO_US(SCREENSAVER_TIMEOUT_MS)); 125 | 126 | return was_screensaver_active; 127 | } 128 | 129 | static bool ignore_key_release = false; 130 | 131 | static bool on_button_event(const button_event_t *event, void *priv) { 132 | xSemaphoreTake(lock, portMAX_DELAY); 133 | if (event->action == BUTTON_ACTION_PRESS) { 134 | ignore_key_release = reset_screensaver(); 135 | } else { 136 | if (ignore_key_release) { 137 | ignore_key_release = false; 138 | } else { 139 | hide_screen(active_screen); 140 | selected_screen = (selected_screen + 1) % ARRAY_SIZE(screens); 141 | show_screen(screens[selected_screen]); 142 | } 143 | } 144 | xSemaphoreGive(lock); 145 | 146 | return true; 147 | } 148 | 149 | const button_event_handler_multi_user_cfg_t button_event_cfg = { 150 | .base = { 151 | .cb = on_button_event, 152 | }, 153 | .multi = { 154 | .button_filter = 1 << BUTTON_ENTER, 155 | .action_filter = (1 << BUTTON_ACTION_PRESS) | (1 << BUTTON_ACTION_RELEASE), 156 | } 157 | }; 158 | 159 | static void on_power_source_changed(void *ctx, void *data) { 160 | xSemaphoreTake(lock, portMAX_DELAY); 161 | if (showing_screensaver) { 162 | show_screensaver(); 163 | } 164 | xSemaphoreGive(lock); 165 | } 166 | 167 | void display_init(i2c_bus_t *display_bus) { 168 | esp_err_t err; 169 | 170 | lock = xSemaphoreCreateMutexStatic(&lock_buffer); 171 | 172 | err = ssd1306_oled_init(&oled, display_bus, DISPLAY_I2C_ADDRESS, GPIO_OLED_RESET); 173 | if (err) { 174 | ESP_LOGE(TAG, "Failed to initialize display: %d", err); 175 | } else { 176 | display_initialized = true; 177 | } 178 | 179 | fb_init(&display_fb); 180 | 181 | gui_init(&gui, NULL, &gui_ops); 182 | 183 | // Title label 184 | gui_label_init(&title_label, ""); 185 | gui_label_set_text_alignment(&title_label, GUI_TEXT_ALIGN_CENTER); 186 | gui_element_set_size(&title_label.element, 64, 5); 187 | gui_element_set_hidden(&title_label.element, true); 188 | gui_element_add_child(&gui.container.element, &title_label.element); 189 | 190 | screen_screensaver = display_screensaver_init(&gui); 191 | screen_on_battery = display_on_battery_init(&gui); 192 | screens[DISPLAY_SCREEN_BMS] = display_bms_init(&gui); 193 | screens[DISPLAY_SCREEN_POWER] = display_power_init(&gui); 194 | screens[DISPLAY_SCREEN_NETWORK] = display_network_init(&gui); 195 | screens[DISPLAY_SCREEN_SYSTEM] = display_system_init(&gui); 196 | 197 | buttons_register_multi_button_event_handler(&button_event_handler, &button_event_cfg); 198 | scheduler_task_init(&screensaver_timeout_task); 199 | } 200 | 201 | static void grayscale_to_monochrome(fb_t *display_fb, const uint8_t *gui_fb) { 202 | unsigned int x, y; 203 | 204 | for (y = 0; y < 48; y++) { 205 | for (x = 0; x < 64; x++) { 206 | fb_set_pixel(display_fb, x, y, !!gui_fb[y * 64 + x]); 207 | } 208 | } 209 | } 210 | 211 | void display_render_loop() { 212 | int render_ret = 0; 213 | 214 | render_task = xTaskGetCurrentTaskHandle(); 215 | 216 | show_screensaver(); 217 | event_bus_subscribe(&power_source_event_handler, "power_source", on_power_source_changed, NULL); 218 | buttons_enable_event_handler(&button_event_handler); 219 | 220 | while (1) { 221 | const gui_point_t render_size = { 222 | 64, 223 | 48 224 | }; 225 | 226 | if (render_ret < 0) { 227 | ulTaskNotifyTake(pdTRUE, portMAX_DELAY); 228 | } else if (!render_ret) { 229 | ulTaskNotifyTake(pdTRUE, 0); 230 | } else { 231 | ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(render_ret)); 232 | } 233 | gui_lock(&gui); 234 | render_ret = gui_render(&gui, gui_render_fb, 64, &render_size); 235 | gui_unlock(&gui); 236 | 237 | if (display_initialized) { 238 | grayscale_to_monochrome(&display_fb, gui_render_fb); 239 | ssd1306_oled_render_fb(&oled, &display_fb); 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /main/display.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "i2c_bus.h" 4 | 5 | typedef struct display_screen { 6 | const char *name; 7 | void (*show)(void); 8 | void (*hide)(void); 9 | } display_screen_t; 10 | 11 | void display_init(i2c_bus_t *display_bus); 12 | void display_render_loop(void); 13 | -------------------------------------------------------------------------------- /main/display_bms.c: -------------------------------------------------------------------------------- 1 | #include "display_bms.h" 2 | 3 | #include "event_bus.h" 4 | #include "battery_gauge.h" 5 | #include "scheduler.h" 6 | #include "util.h" 7 | 8 | static gui_container_t bms_container; 9 | 10 | static gui_label_t soc_label; 11 | static gui_label_t soc_text_label; 12 | static char soc_text[10]; 13 | 14 | static gui_label_t soh_label; 15 | static gui_label_t soh_text_label; 16 | static char soh_text[10]; 17 | 18 | static gui_label_t temp_label; 19 | static gui_label_t temp_text_label; 20 | static char temp_text[10]; 21 | 22 | static gui_label_t cell1_label; 23 | static gui_label_t cell1_text_label; 24 | static char cell1_text[10]; 25 | 26 | static gui_label_t cell2_label; 27 | static gui_label_t cell2_text_label; 28 | static char cell2_text[10]; 29 | 30 | static gui_label_t current_label; 31 | static gui_label_t current_text_label; 32 | static char current_text[10]; 33 | 34 | static gui_label_t capacity_label; 35 | static gui_label_t capacity_text_label; 36 | static char capacity_text[10]; 37 | 38 | static event_bus_handler_t battery_gauge_event_handler; 39 | 40 | static gui_t *gui; 41 | 42 | static void update_ui(void) { 43 | unsigned int soc = battery_gauge_get_soc_percent(); 44 | unsigned int soh = battery_gauge_get_soh_percent(); 45 | long temp = battery_gauge_get_temperature_mdegc(); 46 | unsigned int cell1 = battery_gauge_get_cell1_voltage_mv(); 47 | unsigned int cell2 = battery_gauge_get_cell2_voltage_mv(); 48 | long current = battery_gauge_get_current_ma(); 49 | unsigned int capacity = battery_gauge_get_full_charge_capacity_mah(); 50 | 51 | gui_lock(gui); 52 | snprintf(soc_text, sizeof(soc_text), "%u%%", soc); 53 | gui_label_set_text(&soc_text_label, soc_text); 54 | 55 | snprintf(soh_text, sizeof(soh_text), "%u%%", soh); 56 | gui_label_set_text(&soh_text_label, soh_text); 57 | 58 | snprintf(temp_text, sizeof(temp_text), "%dC", (int)DIV_ROUND(temp, 1000)); 59 | gui_label_set_text(&temp_text_label, temp_text); 60 | 61 | snprintf(cell1_text, sizeof(cell1_text), "%umV", cell1); 62 | gui_label_set_text(&cell1_text_label, cell1_text); 63 | 64 | snprintf(cell2_text, sizeof(cell2_text), "%umV", cell2); 65 | gui_label_set_text(&cell2_text_label, cell2_text); 66 | 67 | snprintf(current_text, sizeof(current_text), "%ldmA", current); 68 | gui_label_set_text(¤t_text_label, current_text); 69 | 70 | snprintf(capacity_text, sizeof(capacity_text), "%umAh", capacity); 71 | gui_label_set_text(&capacity_text_label, capacity_text); 72 | gui_unlock(gui); 73 | } 74 | 75 | static void on_battery_gauge_event(void *priv, void *data) { 76 | update_ui(); 77 | } 78 | 79 | static void display_bms_show(void) { 80 | update_ui(); 81 | gui_element_set_hidden(&bms_container.element, false); 82 | gui_element_show(&bms_container.element); 83 | } 84 | 85 | static void display_bms_hide(void) { 86 | gui_element_set_hidden(&bms_container.element, true); 87 | } 88 | 89 | static const display_screen_t bms_screen = { 90 | .name = "BMS", 91 | .show = display_bms_show, 92 | .hide = display_bms_hide 93 | }; 94 | 95 | const display_screen_t *display_bms_init(gui_t *gui_) { 96 | gui = gui_; 97 | 98 | // Container 99 | gui_container_init(&bms_container); 100 | gui_element_set_size(&bms_container.element, 64, 48 - 8); 101 | gui_element_set_position(&bms_container.element, 0, 8); 102 | gui_element_set_hidden(&bms_container.element, true); 103 | gui_element_add_child(&gui->container.element, &bms_container.element); 104 | 105 | // Row 1 106 | // SoC 107 | // SoC label 108 | gui_label_init(&soc_label, "SoC"); 109 | gui_label_set_text_alignment(&soc_label, GUI_TEXT_ALIGN_CENTER); 110 | gui_element_set_size(&soc_label.element, 20, 5); 111 | gui_element_set_position(&soc_label.element, 0, 0); 112 | gui_element_add_child(&bms_container.element, &soc_label.element); 113 | 114 | // SoC text 115 | gui_label_init(&soc_text_label, "???%"); 116 | gui_label_set_text_alignment(&soc_text_label, GUI_TEXT_ALIGN_CENTER); 117 | gui_element_set_size(&soc_text_label.element, 20, 5); 118 | gui_element_set_position(&soc_text_label.element, 0, 6); 119 | gui_element_add_child(&bms_container.element, &soc_text_label.element); 120 | 121 | // SoH 122 | // SoH label 123 | gui_label_init(&soh_label, "SoH"); 124 | gui_label_set_text_alignment(&soh_label, GUI_TEXT_ALIGN_CENTER); 125 | gui_element_set_size(&soh_label.element, 20, 5); 126 | gui_element_set_position(&soh_label.element, 22, 0); 127 | gui_element_add_child(&bms_container.element, &soh_label.element); 128 | 129 | // SoH text 130 | gui_label_init(&soh_text_label, "???%"); 131 | gui_label_set_text_alignment(&soh_text_label, GUI_TEXT_ALIGN_CENTER); 132 | gui_element_set_size(&soh_text_label.element, 20, 5); 133 | gui_element_set_position(&soh_text_label.element, 22, 6); 134 | gui_element_add_child(&bms_container.element, &soh_text_label.element); 135 | 136 | // Temp 137 | // Temp label 138 | gui_label_init(&temp_label, "Temp"); 139 | gui_label_set_text_alignment(&temp_label, GUI_TEXT_ALIGN_CENTER); 140 | gui_element_set_size(&temp_label.element, 20, 5); 141 | gui_element_set_position(&temp_label.element, 44, 0); 142 | gui_element_add_child(&bms_container.element, &temp_label.element); 143 | 144 | // Temp text 145 | gui_label_init(&temp_text_label, "??C"); 146 | gui_label_set_text_alignment(&temp_text_label, GUI_TEXT_ALIGN_CENTER); 147 | gui_element_set_size(&temp_text_label.element, 20, 5); 148 | gui_element_set_position(&temp_text_label.element, 44, 6); 149 | gui_element_add_child(&bms_container.element, &temp_text_label.element); 150 | 151 | // Row 2 152 | // Cell 1 153 | // Cell 1 label 154 | gui_label_init(&cell1_label, "Cell 1"); 155 | gui_label_set_text_alignment(&cell1_label, GUI_TEXT_ALIGN_CENTER); 156 | gui_element_set_size(&cell1_label.element, 32, 5); 157 | gui_element_set_position(&cell1_label.element, 0, 14); 158 | gui_element_add_child(&bms_container.element, &cell1_label.element); 159 | 160 | // Cell 1 text 161 | gui_label_init(&cell1_text_label, "????mV"); 162 | gui_label_set_text_alignment(&cell1_text_label, GUI_TEXT_ALIGN_CENTER); 163 | gui_element_set_size(&cell1_text_label.element, 32, 5); 164 | gui_element_set_position(&cell1_text_label.element, 0, 20); 165 | gui_element_add_child(&bms_container.element, &cell1_text_label.element); 166 | 167 | // Cell 2 168 | // Cell 2 label 169 | gui_label_init(&cell2_label, "Cell 2"); 170 | gui_label_set_text_alignment(&cell2_label, GUI_TEXT_ALIGN_CENTER); 171 | gui_element_set_size(&cell2_label.element, 32, 5); 172 | gui_element_set_position(&cell2_label.element, 32, 14); 173 | gui_element_add_child(&bms_container.element, &cell2_label.element); 174 | 175 | // Cell 2 text 176 | gui_label_init(&cell2_text_label, "????mV"); 177 | gui_label_set_text_alignment(&cell2_text_label, GUI_TEXT_ALIGN_CENTER); 178 | gui_element_set_size(&cell2_text_label.element, 32, 5); 179 | gui_element_set_position(&cell2_text_label.element, 32, 20); 180 | gui_element_add_child(&bms_container.element, &cell2_text_label.element); 181 | 182 | // Row 3 183 | // Current 184 | // Current label 185 | gui_label_init(¤t_label, "Current"); 186 | gui_label_set_text_alignment(¤t_label, GUI_TEXT_ALIGN_CENTER); 187 | gui_element_set_size(¤t_label.element, 32, 5); 188 | gui_element_set_position(¤t_label.element, 0, 28); 189 | gui_element_add_child(&bms_container.element, ¤t_label.element); 190 | 191 | // Current text 192 | gui_label_init(¤t_text_label, "-????mA"); 193 | gui_label_set_text_alignment(¤t_text_label, GUI_TEXT_ALIGN_CENTER); 194 | gui_element_set_size(¤t_text_label.element, 32, 5); 195 | gui_element_set_position(¤t_text_label.element, 0, 34); 196 | gui_element_add_child(&bms_container.element, ¤t_text_label.element); 197 | 198 | // Capacity 199 | // Capacity label 200 | gui_label_init(&capacity_label, "Capacity"); 201 | gui_label_set_text_alignment(&capacity_label, GUI_TEXT_ALIGN_CENTER); 202 | gui_element_set_size(&capacity_label.element, 32, 5); 203 | gui_element_set_position(&capacity_label.element, 32, 28); 204 | gui_element_add_child(&bms_container.element, &capacity_label.element); 205 | 206 | // Capacity text 207 | gui_label_init(&capacity_text_label, "????mAh"); 208 | gui_label_set_text_alignment(&capacity_text_label, GUI_TEXT_ALIGN_CENTER); 209 | gui_element_set_size(&capacity_text_label.element, 32, 5); 210 | gui_element_set_position(&capacity_text_label.element, 32, 34); 211 | gui_element_add_child(&bms_container.element, &capacity_text_label.element); 212 | 213 | event_bus_subscribe(&battery_gauge_event_handler, "battery_gauge", on_battery_gauge_event, NULL); 214 | 215 | return &bms_screen; 216 | } 217 | -------------------------------------------------------------------------------- /main/display_bms.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "display.h" 4 | #include "gui.h" 5 | 6 | const display_screen_t *display_bms_init(gui_t *gui); 7 | -------------------------------------------------------------------------------- /main/display_network.c: -------------------------------------------------------------------------------- 1 | #include "display_network.h" 2 | 3 | #include <esp_netif_ip_addr.h> 4 | 5 | #include "ethernet.h" 6 | #include "event_bus.h" 7 | #include "power_path.h" 8 | #include "scheduler.h" 9 | #include "util.h" 10 | 11 | typedef struct label_text_pair { 12 | gui_label_t label; 13 | char text[20]; 14 | } label_text_pair_t; 15 | 16 | static gui_container_t network_container; 17 | 18 | static label_text_pair_t ipv4_address_label; 19 | static label_text_pair_t link_status_label; 20 | 21 | static gui_t *gui; 22 | 23 | static event_bus_handler_t network_event_handler; 24 | 25 | static void update_ui(void) { 26 | esp_netif_ip_info_t ipv4_info; 27 | 28 | ethernet_get_ipv4_address(&ipv4_info); 29 | 30 | gui_lock(gui); 31 | snprintf(ipv4_address_label.text, 32 | sizeof(ipv4_address_label.text), 33 | IPSTR, IP2STR(&ipv4_info.ip)); 34 | gui_label_set_text(&ipv4_address_label.label, ipv4_address_label.text); 35 | 36 | if (ethernet_is_link_up()) { 37 | const char *speed; 38 | 39 | switch (ethernet_get_link_speed()) { 40 | case ETH_SPEED_10M: 41 | speed = "10M"; 42 | break; 43 | case ETH_SPEED_100M: 44 | speed = "100M"; 45 | break; 46 | default: 47 | speed = "?"; 48 | } 49 | 50 | snprintf(link_status_label.text, sizeof(link_status_label.text), 51 | "LINK UP (%s)", speed); 52 | gui_label_set_text(&link_status_label.label, link_status_label.text); 53 | } else { 54 | gui_label_set_text(&link_status_label.label, "LINK DOWN"); 55 | } 56 | gui_unlock(gui); 57 | } 58 | 59 | static void on_network_event(void *priv, void *data) { 60 | update_ui(); 61 | } 62 | 63 | static void display_network_show(void) { 64 | update_ui(); 65 | gui_element_set_hidden(&network_container.element, false); 66 | gui_element_show(&network_container.element); 67 | } 68 | 69 | static void display_network_hide(void) { 70 | gui_element_set_hidden(&network_container.element, true); 71 | } 72 | 73 | static const display_screen_t network_screen = { 74 | .name = "NETWORK", 75 | .show = display_network_show, 76 | .hide = display_network_hide 77 | }; 78 | 79 | static void setup_label(gui_label_t *label, const char *text, unsigned int pos_x, unsigned int pos_y) { 80 | gui_label_init(label, text); 81 | gui_label_set_text_alignment(label, GUI_TEXT_ALIGN_CENTER); 82 | gui_element_set_size(&label->element, 64, 5); 83 | gui_element_set_position(&label->element, pos_x, pos_y); 84 | gui_element_add_child(&network_container.element, &label->element); 85 | } 86 | 87 | const display_screen_t *display_network_init(gui_t *gui_) { 88 | gui = gui_; 89 | 90 | // Container 91 | gui_container_init(&network_container); 92 | gui_element_set_size(&network_container.element, 64, 48 - 8); 93 | gui_element_set_position(&network_container.element, 0, 8); 94 | gui_element_set_hidden(&network_container.element, true); 95 | gui_element_add_child(&gui->container.element, &network_container.element); 96 | 97 | setup_label(&link_status_label.label, "LINK UP/DONW", 0, 0); 98 | setup_label(&ipv4_address_label.label, "???.???.???.???", 0, 6); 99 | 100 | event_bus_subscribe(&network_event_handler, "network", on_network_event, NULL); 101 | 102 | return &network_screen; 103 | } 104 | -------------------------------------------------------------------------------- /main/display_network.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "display.h" 4 | #include "gui.h" 5 | 6 | const display_screen_t *display_network_init(gui_t *gui); 7 | -------------------------------------------------------------------------------- /main/display_on_battery.c: -------------------------------------------------------------------------------- 1 | #include "display_on_battery.h" 2 | 3 | #include "event_bus.h" 4 | #include "battery_gauge.h" 5 | #include "scheduler.h" 6 | #include "util.h" 7 | 8 | #define ON_BATTERY_BLINK_INTERVAL_MS 1000 9 | 10 | static gui_container_t on_battery; 11 | 12 | static gui_rectangle_t battery_contact_rect; 13 | static gui_rectangle_t battery_body_rect; 14 | static gui_rectangle_t battery_soc_rect; 15 | 16 | static gui_label_t soc_label; 17 | static char soc_text[16]; 18 | 19 | static gui_label_t remaining_label; 20 | static gui_label_t remaining_time_label; 21 | static char remaining_time_text[16]; 22 | 23 | static gui_label_t on_battery_label; 24 | static scheduler_task_t on_battery_blink_task; 25 | 26 | static event_bus_handler_t battery_gauge_event_handler; 27 | 28 | static gui_t *gui; 29 | 30 | static void update_ui(gui_t *gui) { 31 | unsigned int soc = battery_gauge_get_soc_percent(); 32 | unsigned int soc_full_height = battery_body_rect.element.area.size.y - 4; 33 | unsigned int soc_height = soc_full_height * soc / 100; 34 | unsigned int soc_width = battery_body_rect.element.area.size.x - 4; 35 | unsigned int run_time_to_empty = battery_gauge_get_time_to_empty_min(); 36 | 37 | gui_lock(gui); 38 | gui_element_set_position(&battery_soc_rect.element, 39 | battery_body_rect.element.area.position.x + 2, 40 | battery_body_rect.element.area.position.y + 2 + soc_full_height - soc_height); 41 | gui_element_set_size(&battery_soc_rect.element, soc_width, soc_height); 42 | 43 | snprintf(soc_text, sizeof(soc_text), "SoC %3u%%", soc); 44 | gui_label_set_text(&soc_label, soc_text); 45 | 46 | snprintf(remaining_time_text, sizeof(remaining_time_text), "%02u:%02u", run_time_to_empty / 60, run_time_to_empty % 60); 47 | gui_label_set_text(&remaining_time_label, remaining_time_text); 48 | gui_unlock(gui); 49 | } 50 | 51 | static void on_battery_gauge_event(void *priv, void *data) { 52 | gui_t *gui = priv; 53 | 54 | update_ui(gui); 55 | } 56 | 57 | static const display_screen_t on_battery_screen = { 58 | .name = NULL, 59 | .show = display_on_battery_show, 60 | .hide = display_on_battery_hide 61 | }; 62 | 63 | const display_screen_t *display_on_battery_init(gui_t *gui_) { 64 | gui = gui_; 65 | 66 | // Container 67 | gui_container_init(&on_battery); 68 | gui_element_set_size(&on_battery.element, 64, 48); 69 | gui_element_set_hidden(&on_battery.element, true); 70 | gui_element_add_child(&gui->container.element, &on_battery.element); 71 | 72 | // Visual battery SoC indicator 73 | gui_rectangle_init(&battery_contact_rect); 74 | gui_rectangle_set_color(&battery_contact_rect, 0xff); 75 | gui_element_set_size(&battery_contact_rect.element, 8, 2); 76 | gui_element_set_position(&battery_contact_rect.element, 7, 1); 77 | gui_element_add_child(&on_battery.element, &battery_contact_rect.element); 78 | 79 | gui_rectangle_init(&battery_body_rect); 80 | gui_rectangle_set_color(&battery_body_rect, 0xff); 81 | gui_element_set_size(&battery_body_rect.element, 19, 37); 82 | gui_element_set_position(&battery_body_rect.element, 1, 3); 83 | gui_element_add_child(&on_battery.element, &battery_body_rect.element); 84 | 85 | gui_rectangle_init(&battery_soc_rect); 86 | gui_rectangle_set_color(&battery_soc_rect, 0xff); 87 | gui_rectangle_set_filled(&battery_soc_rect, true); 88 | gui_element_set_size(&battery_soc_rect.element, 15, 37 - 4); 89 | gui_element_set_position(&battery_soc_rect.element, 1 + 2, 3 + 2); 90 | gui_element_add_child(&on_battery.element, &battery_soc_rect.element); 91 | 92 | // SoC 93 | gui_label_init(&soc_label, "SoC ???%"); 94 | gui_element_set_size(&soc_label.element, 8 * 4, 5); 95 | gui_element_set_position(&soc_label.element, 24, 4); 96 | gui_element_add_child(&on_battery.element, &soc_label.element); 97 | 98 | // Remaining runtime 99 | gui_label_init(&remaining_label, "REMAINING"); 100 | gui_label_set_text_alignment(&remaining_label, GUI_TEXT_ALIGN_CENTER); 101 | gui_element_set_size(&remaining_label.element, 64 - 24, 5); 102 | gui_element_set_position(&remaining_label.element, 24, 18); 103 | gui_element_add_child(&on_battery.element, &remaining_label.element); 104 | 105 | gui_label_init(&remaining_time_label, "12:34"); 106 | gui_label_set_text_alignment(&remaining_time_label, GUI_TEXT_ALIGN_CENTER); 107 | gui_element_set_size(&remaining_time_label.element, 64 - 24, 5); 108 | gui_element_set_position(&remaining_time_label.element, 24, 25); 109 | gui_element_add_child(&on_battery.element, &remaining_time_label.element); 110 | 111 | // Binking "ON BATTERY" indicator 112 | gui_label_init(&on_battery_label, "ON BATTERY"); 113 | gui_label_set_text_alignment(&on_battery_label, GUI_TEXT_ALIGN_CENTER); 114 | gui_element_set_size(&on_battery_label.element, 64, 5); 115 | gui_element_set_position(&on_battery_label.element, 0, 48 - 5); 116 | gui_element_add_child(&on_battery.element, &on_battery_label.element); 117 | 118 | scheduler_task_init(&on_battery_blink_task); 119 | event_bus_subscribe(&battery_gauge_event_handler, "battery_gauge", on_battery_gauge_event, gui); 120 | 121 | return &on_battery_screen; 122 | } 123 | 124 | static void toggle_battery_blink_cb(void *ctx); 125 | static void toggle_battery_blink_cb(void *ctx) { 126 | gui_t *gui = ctx; 127 | 128 | gui_lock(gui); 129 | gui_element_set_hidden(&on_battery_label.element, !on_battery_label.element.hidden); 130 | gui_unlock(gui); 131 | if (!on_battery.element.hidden) { 132 | scheduler_schedule_task_relative(&on_battery_blink_task, toggle_battery_blink_cb, gui, MS_TO_US(ON_BATTERY_BLINK_INTERVAL_MS)); 133 | } 134 | } 135 | 136 | void display_on_battery_show() { 137 | update_ui(gui); 138 | gui_element_set_hidden(&on_battery.element, false); 139 | gui_element_show(&on_battery.element); 140 | scheduler_schedule_task_relative(&on_battery_blink_task, toggle_battery_blink_cb, gui, MS_TO_US(ON_BATTERY_BLINK_INTERVAL_MS)); 141 | } 142 | 143 | void display_on_battery_hide() { 144 | gui_element_set_hidden(&on_battery.element, true); 145 | scheduler_abort_task(&on_battery_blink_task); 146 | } 147 | -------------------------------------------------------------------------------- /main/display_on_battery.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "display.h" 4 | #include "gui.h" 5 | 6 | const display_screen_t *display_on_battery_init(gui_t *gui); 7 | void display_on_battery_show(void); 8 | void display_on_battery_hide(void); 9 | -------------------------------------------------------------------------------- /main/display_power.c: -------------------------------------------------------------------------------- 1 | #include "display_power.h" 2 | 3 | #include "event_bus.h" 4 | #include "power_path.h" 5 | #include "scheduler.h" 6 | #include "util.h" 7 | 8 | static gui_container_t power_container; 9 | 10 | typedef struct label_text_pair { 11 | gui_label_t label; 12 | char text[10]; 13 | } label_text_pair_t; 14 | 15 | typedef struct power_display_column { 16 | gui_label_t heading; 17 | label_text_pair_t voltage; 18 | label_text_pair_t current; 19 | label_text_pair_t power; 20 | label_text_pair_t temperature; 21 | } power_display_column_t; 22 | 23 | static power_display_column_t column_in; 24 | static power_display_column_t column_dc; 25 | static power_display_column_t column_usb; 26 | 27 | gui_label_t input_current_limit_label; 28 | char current_limit_text[32]; 29 | 30 | static gui_t *gui; 31 | 32 | static event_bus_handler_t power_path_event_handler; 33 | 34 | #define label_text_pair_printf(pair, format, ...) \ 35 | do { \ 36 | snprintf((pair)->text, sizeof((pair)->text), (format), __VA_ARGS__); \ 37 | gui_label_set_text(&(pair)->label, (pair)->text); \ 38 | } while (0) 39 | 40 | static void populate_column_with_power_path_group_data(power_display_column_t *column, power_path_group_t group) { 41 | power_path_group_data_t group_data; 42 | 43 | power_path_get_group_data(group, &group_data); 44 | label_text_pair_printf(&column->voltage, "%.1fV", group_data.voltage_mv / 1000.f); 45 | label_text_pair_printf(&column->current, "%.1fA", group_data.current_ma / 1000.f); 46 | label_text_pair_printf(&column->power, "%.1fW", group_data.power_mw / 1000.f); 47 | label_text_pair_printf(&column->temperature, "%dC", (int)DIV_ROUND(group_data.temperature_mdegc, 1000)); 48 | } 49 | 50 | static void update_ui(void) { 51 | gui_lock(gui); 52 | populate_column_with_power_path_group_data(&column_in, POWER_PATH_GROUP_IN); 53 | populate_column_with_power_path_group_data(&column_dc, POWER_PATH_GROUP_DC); 54 | populate_column_with_power_path_group_data(&column_usb, POWER_PATH_GROUP_USB); 55 | 56 | snprintf(current_limit_text, sizeof(current_limit_text), "IN limit: %.1fA", power_path_get_input_current_limit_ma() / 1000.f); 57 | gui_label_set_text(&input_current_limit_label, current_limit_text); 58 | gui_unlock(gui); 59 | } 60 | 61 | static void on_power_path_event(void *priv, void *data) { 62 | update_ui(); 63 | } 64 | 65 | static void display_power_show(void) { 66 | update_ui(); 67 | gui_element_set_hidden(&power_container.element, false); 68 | gui_element_show(&power_container.element); 69 | } 70 | 71 | static void display_power_hide(void) { 72 | gui_element_set_hidden(&power_container.element, true); 73 | } 74 | 75 | static const display_screen_t power_screen = { 76 | .name = "POWER", 77 | .show = display_power_show, 78 | .hide = display_power_hide 79 | }; 80 | 81 | static void setup_label(gui_label_t *label, const char *text, unsigned int pos_x, unsigned int pos_y) { 82 | gui_label_init(label, text); 83 | gui_label_set_text_alignment(label, GUI_TEXT_ALIGN_CENTER); 84 | gui_element_set_size(&label->element, 20, 5); 85 | gui_element_set_position(&label->element, pos_x, pos_y); 86 | gui_element_add_child(&power_container.element, &label->element); 87 | } 88 | 89 | static void populate_column(power_display_column_t *column, const char *heading, unsigned int pos_x) { 90 | setup_label(&column->heading, heading, pos_x, 0); 91 | setup_label(&column->voltage.label, "??.?V", pos_x, 8); 92 | setup_label(&column->current.label, "??.?A", pos_x, 14); 93 | setup_label(&column->power.label, "??.?W", pos_x, 20); 94 | setup_label(&column->temperature.label, "??C", pos_x, 26); 95 | } 96 | 97 | const display_screen_t *display_power_init(gui_t *gui_) { 98 | gui = gui_; 99 | 100 | // Container 101 | gui_container_init(&power_container); 102 | gui_element_set_size(&power_container.element, 64, 48 - 8); 103 | gui_element_set_position(&power_container.element, 0, 8); 104 | gui_element_set_hidden(&power_container.element, true); 105 | gui_element_add_child(&gui->container.element, &power_container.element); 106 | 107 | populate_column(&column_in, "IN", 0); 108 | populate_column(&column_dc, "DC", 22); 109 | populate_column(&column_usb, "USB", 44); 110 | 111 | setup_label(&input_current_limit_label, "IN limit: ??.?A", 0, 35); 112 | gui_element_set_size(&input_current_limit_label.element, 64, 5); 113 | 114 | event_bus_subscribe(&power_path_event_handler, "power_path", on_power_path_event, NULL); 115 | 116 | return &power_screen; 117 | } 118 | -------------------------------------------------------------------------------- /main/display_power.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "display.h" 4 | #include "gui.h" 5 | 6 | const display_screen_t *display_power_init(gui_t *gui); 7 | -------------------------------------------------------------------------------- /main/display_screensaver.c: -------------------------------------------------------------------------------- 1 | #include "display_screensaver.h" 2 | 3 | #include <stdint.h> 4 | 5 | #include <esp_random.h> 6 | 7 | #include "battery_gauge.h" 8 | #include "event_bus.h" 9 | #include "power_path.h" 10 | #include "scheduler.h" 11 | #include "util.h" 12 | 13 | #define MOVE_INTERVAL_MS 5000 14 | 15 | typedef struct label_text_pair { 16 | gui_label_t label; 17 | char text[20]; 18 | } label_text_pair_t; 19 | 20 | static gui_container_t screensaver; 21 | static gui_label_t screensaver_soc_label; 22 | static char screensaver_soc_text[10]; 23 | static gui_label_t screensaver_power_label; 24 | static char screensaver_power_text[10]; 25 | 26 | static label_text_pair_t runtime_label; 27 | 28 | static scheduler_task_t screensaver_move_task; 29 | 30 | static gui_t *gui; 31 | 32 | event_bus_handler_t event_hander_battery_gauge; 33 | event_bus_handler_t event_hander_power_path; 34 | 35 | static void on_battery_gauge_event(void *priv, void *data) { 36 | gui_t *gui = priv; 37 | unsigned int soc; 38 | unsigned int time_to_empty = battery_gauge_get_at_rate_time_to_empty_min(); 39 | 40 | gui_lock(gui); 41 | soc = battery_gauge_get_soc_percent(); 42 | snprintf(screensaver_soc_text, sizeof(screensaver_soc_text), "%u%%", soc); 43 | gui_label_set_text(&screensaver_soc_label, screensaver_soc_text); 44 | 45 | snprintf(runtime_label.text, sizeof(runtime_label.text), "%02u:%02u", time_to_empty / 60, time_to_empty % 60); 46 | gui_label_set_text(&runtime_label.label, runtime_label.text); 47 | gui_unlock(gui); 48 | } 49 | 50 | static void on_power_path_event(void *priv, void *data) { 51 | gui_t *gui = priv; 52 | unsigned long power_mw = power_path_get_output_power_consumption_mw(); 53 | 54 | gui_lock(gui); 55 | snprintf(screensaver_power_text, sizeof(screensaver_power_text), "%.2fW", power_mw / 1000.f); 56 | gui_label_set_text(&screensaver_power_label, screensaver_power_text); 57 | gui_unlock(gui); 58 | } 59 | 60 | static void screensaver_move_cb(void *ctx); 61 | static void screensaver_move_cb(void *ctx) { 62 | uint32_t x, y; 63 | 64 | gui_lock(gui); 65 | x = esp_random() % (64 - screensaver.element.area.size.x); 66 | y = esp_random() % (48 - screensaver.element.area.size.y); 67 | gui_element_set_position(&screensaver.element, x, y); 68 | gui_unlock(gui); 69 | 70 | scheduler_schedule_task_relative(&screensaver_move_task, screensaver_move_cb, NULL, MS_TO_US(MOVE_INTERVAL_MS)); 71 | } 72 | 73 | static const display_screen_t screensaver_screen = { 74 | .name = NULL, 75 | .show = display_screensaver_show, 76 | .hide = display_screensaver_hide 77 | }; 78 | 79 | static void setup_label(gui_label_t *label, const char *text, unsigned int pos_x, unsigned int pos_y) { 80 | gui_label_init(label, text); 81 | gui_label_set_text_alignment(label, GUI_TEXT_ALIGN_CENTER); 82 | gui_element_set_size(&label->element, 24, 5); 83 | gui_element_set_position(&label->element, pos_x, pos_y); 84 | gui_element_add_child(&screensaver.element, &label->element); 85 | } 86 | 87 | const display_screen_t *display_screensaver_init(gui_t *gui_root) { 88 | gui = gui_root; 89 | 90 | gui_container_init(&screensaver); 91 | gui_element_set_size(&screensaver.element, 20, 19); 92 | gui_element_set_hidden(&screensaver.element, true); 93 | gui_element_add_child(&gui->container.element, &screensaver.element); 94 | 95 | setup_label(&screensaver_soc_label, "???%", 0, 0); 96 | setup_label(&screensaver_power_label, "??.??W", 0, 7); 97 | setup_label(&runtime_label.label, "??:??", 0, 14); 98 | 99 | scheduler_task_init(&screensaver_move_task); 100 | scheduler_schedule_task_relative(&screensaver_move_task, screensaver_move_cb, NULL, MS_TO_US(MOVE_INTERVAL_MS)); 101 | 102 | event_bus_subscribe(&event_hander_battery_gauge, "battery_gauge", on_battery_gauge_event, gui); 103 | event_bus_subscribe(&event_hander_power_path, "power_path", on_power_path_event, gui); 104 | return &screensaver_screen; 105 | } 106 | 107 | void display_screensaver_show() { 108 | gui_element_set_hidden(&screensaver.element, false); 109 | gui_element_show(&screensaver.element); 110 | } 111 | 112 | void display_screensaver_hide() { 113 | gui_element_set_hidden(&screensaver.element, true); 114 | } 115 | -------------------------------------------------------------------------------- /main/display_screensaver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "display.h" 4 | #include "gui.h" 5 | 6 | const display_screen_t *display_screensaver_init(gui_t *gui); 7 | void display_screensaver_show(void); 8 | void display_screensaver_hide(void); 9 | -------------------------------------------------------------------------------- /main/display_system.c: -------------------------------------------------------------------------------- 1 | #include "display_system.h" 2 | 3 | #include "event_bus.h" 4 | #include "power_path.h" 5 | #include "scheduler.h" 6 | #include "util.h" 7 | #include "vendor.h" 8 | 9 | typedef struct label_text_pair { 10 | gui_label_t label; 11 | char text[20]; 12 | } label_text_pair_t; 13 | 14 | static gui_container_t system_container; 15 | 16 | static gui_label_t device_serial_header_label; 17 | static label_text_pair_t device_serial_label; 18 | 19 | static gui_label_t app_version_header_label; 20 | static gui_label_t app_version_label; 21 | 22 | static gui_t *gui; 23 | 24 | static event_bus_handler_t vendor_event_handler; 25 | 26 | static void update_ui() { 27 | gui_lock(gui); 28 | vendor_lock(); 29 | snprintf(device_serial_label.text, sizeof(device_serial_label.text), "%s", vendor_get_serial_number_()); 30 | gui_label_set_text(&device_serial_label.label, device_serial_label.text); 31 | vendor_unlock(); 32 | gui_unlock(gui); 33 | } 34 | 35 | static void on_vendor_event(void *priv, void *data) { 36 | update_ui(); 37 | } 38 | 39 | static void display_system_show(void) { 40 | update_ui(); 41 | gui_element_set_hidden(&system_container.element, false); 42 | gui_element_show(&system_container.element); 43 | } 44 | 45 | static void display_system_hide(void) { 46 | gui_element_set_hidden(&system_container.element, true); 47 | } 48 | 49 | static const display_screen_t system_screen = { 50 | .name = "SYSTEM", 51 | .show = display_system_show, 52 | .hide = display_system_hide 53 | }; 54 | 55 | static void setup_label(gui_label_t *label, const char *text, unsigned int pos_x, unsigned int pos_y) { 56 | gui_label_init(label, text); 57 | gui_label_set_text_alignment(label, GUI_TEXT_ALIGN_CENTER); 58 | gui_element_set_size(&label->element, 64, 5); 59 | gui_element_set_position(&label->element, pos_x, pos_y); 60 | gui_element_add_child(&system_container.element, &label->element); 61 | } 62 | 63 | const display_screen_t *display_system_init(gui_t *gui_) { 64 | gui = gui_; 65 | 66 | // Container 67 | gui_container_init(&system_container); 68 | gui_element_set_size(&system_container.element, 64, 48 - 8); 69 | gui_element_set_position(&system_container.element, 0, 8); 70 | gui_element_set_hidden(&system_container.element, true); 71 | gui_element_add_child(&gui->container.element, &system_container.element); 72 | 73 | setup_label(&device_serial_header_label, "Serial", 0, 6); 74 | setup_label(&device_serial_label.label, "000000", 0, 6 + 6); 75 | 76 | setup_label(&app_version_header_label, "Version", 0, 40 - 12 - 6); 77 | setup_label(&app_version_label, XSTRINGIFY(UPS_APP_VERSION), 0, 40 - 12); 78 | 79 | event_bus_subscribe(&vendor_event_handler, "vendor", on_vendor_event, NULL); 80 | 81 | return &system_screen; 82 | } 83 | -------------------------------------------------------------------------------- /main/display_system.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "display.h" 4 | #include "gui.h" 5 | 6 | const display_screen_t *display_system_init(gui_t *gui); 7 | -------------------------------------------------------------------------------- /main/ethernet.c: -------------------------------------------------------------------------------- 1 | #include "ethernet.h" 2 | 3 | #include <driver/gpio.h> 4 | #include <esp_eth.h> 5 | #include <esp_event.h> 6 | #include <esp_log.h> 7 | #include <esp_netif.h> 8 | #include <hal/eth_types.h> 9 | 10 | #include "event_bus.h" 11 | #include "vendor.h" 12 | 13 | static const char *TAG = "ethernet"; 14 | 15 | static esp_netif_t *eth_netif; 16 | static esp_eth_handle_t eth_handle; 17 | 18 | static event_bus_handler_t vendor_event_handler; 19 | 20 | static void eth_event_handler(void *arg, esp_event_base_t event_base, 21 | int32_t event_id, void *event_data) { 22 | uint8_t mac_addr[6] = {0}; 23 | esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data; 24 | 25 | switch (event_id) { 26 | case ETHERNET_EVENT_CONNECTED: 27 | esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr); 28 | ESP_LOGI(TAG, "Ethernet Link Up"); 29 | ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x", 30 | mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); 31 | esp_netif_create_ip6_linklocal(eth_netif); 32 | event_bus_notify("network", NULL); 33 | break; 34 | case ETHERNET_EVENT_DISCONNECTED: 35 | ESP_LOGI(TAG, "Ethernet Link Down"); 36 | event_bus_notify("network", NULL); 37 | break; 38 | case ETHERNET_EVENT_START: 39 | ESP_LOGI(TAG, "Ethernet Started"); 40 | break; 41 | case ETHERNET_EVENT_STOP: 42 | ESP_LOGI(TAG, "Ethernet Stopped"); 43 | break; 44 | default: 45 | break; 46 | } 47 | } 48 | 49 | static void got_ip_event_handler(void *arg, esp_event_base_t event_base, 50 | int32_t event_id, void *event_data) { 51 | if (event_id == IP_EVENT_ETH_GOT_IP) { 52 | ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; 53 | const esp_netif_ip_info_t *ip_info = &event->ip_info; 54 | 55 | ESP_LOGI(TAG, "Ethernet Got IP Address"); 56 | ESP_LOGI(TAG, "~~~~~~~~~~~"); 57 | ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip)); 58 | ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask)); 59 | ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw)); 60 | ESP_LOGI(TAG, "~~~~~~~~~~~"); 61 | event_bus_notify("network", NULL); 62 | } else if (event_id == IP_EVENT_GOT_IP6) { 63 | event_bus_notify("network", NULL); 64 | } 65 | } 66 | 67 | static void update_hostname(void) { 68 | vendor_lock(); 69 | esp_netif_set_hostname(eth_netif, vendor_get_hostname_()); 70 | vendor_unlock(); 71 | } 72 | 73 | static void on_vendor_event(void *priv, void *data) { 74 | update_hostname(); 75 | } 76 | 77 | esp_err_t ethernet_init(const ethernet_config_t *cfg) { 78 | ESP_ERROR_CHECK(esp_netif_init()); 79 | ESP_ERROR_CHECK(esp_event_loop_create_default()); 80 | 81 | // Create new default instance of esp-netif for Ethernet 82 | esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH(); 83 | eth_netif = esp_netif_new(&netif_cfg); 84 | 85 | // Init MAC and PHY configs to default 86 | eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); 87 | eth_esp32_emac_config_t emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); 88 | eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); 89 | 90 | phy_config.phy_addr = cfg->phy_address; 91 | phy_config.reset_gpio_num = cfg->phy_reset_gpio; 92 | emac_config.smi_mdc_gpio_num = cfg->mdc_gpio; 93 | emac_config.smi_mdio_gpio_num = cfg->mdio_gpio; 94 | esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&emac_config, &mac_config); 95 | esp_eth_phy_t *phy = esp_eth_phy_new_lan87xx(&phy_config); 96 | 97 | esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy); 98 | eth_handle = NULL; 99 | ESP_ERROR_CHECK(esp_eth_driver_install(&config, ð_handle)); 100 | /* attach Ethernet driver to TCP/IP stack */ 101 | ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle))); 102 | 103 | update_hostname(); 104 | 105 | ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL)); 106 | ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &got_ip_event_handler, NULL)); 107 | 108 | /* start Ethernet driver state machine */ 109 | ESP_ERROR_CHECK(esp_eth_start(eth_handle)); 110 | 111 | event_bus_subscribe(&vendor_event_handler, "vendor", on_vendor_event, NULL); 112 | 113 | return ESP_OK; 114 | } 115 | 116 | esp_err_t ethernet_get_ipv4_address(esp_netif_ip_info_t *ip_info) { 117 | return esp_netif_get_ip_info(eth_netif, ip_info); 118 | } 119 | 120 | bool ethernet_is_link_up() { 121 | return esp_netif_is_netif_up(eth_netif); 122 | } 123 | 124 | eth_speed_t ethernet_get_link_speed() { 125 | eth_speed_t speed = ETH_SPEED_MAX; 126 | 127 | esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &speed); 128 | return speed; 129 | } 130 | -------------------------------------------------------------------------------- /main/ethernet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stdbool.h> 4 | 5 | #include <esp_err.h> 6 | #include <esp_netif.h> 7 | #include <hal/eth_types.h> 8 | 9 | typedef struct ethernet_config { 10 | unsigned int phy_address; 11 | int phy_reset_gpio; 12 | unsigned int mdc_gpio; 13 | unsigned int mdio_gpio; 14 | } ethernet_config_t; 15 | 16 | esp_err_t ethernet_init(const ethernet_config_t *cfg); 17 | esp_err_t ethernet_get_ipv4_address(esp_netif_ip_info_t *ip_info); 18 | bool ethernet_is_link_up(void); 19 | eth_speed_t ethernet_get_link_speed(); 20 | -------------------------------------------------------------------------------- /main/event_bus.c: -------------------------------------------------------------------------------- 1 | #include "event_bus.h" 2 | 3 | #include <freertos/FreeRTOS.h> 4 | #include <freertos/semphr.h> 5 | 6 | static DECLARE_LIST_HEAD(event_bus_handlers); 7 | static StaticSemaphore_t event_bus_lock_buffer; 8 | static SemaphoreHandle_t event_bus_lock; 9 | 10 | void event_bus_init(void) { 11 | event_bus_lock = xSemaphoreCreateRecursiveMutexStatic(&event_bus_lock_buffer); 12 | } 13 | 14 | void event_bus_subscribe(event_bus_handler_t *handler, const char *topic, eventbus_notify_cb_f notify_cb, void *priv) { 15 | INIT_LIST_HEAD(handler->list); 16 | handler->topic = topic; 17 | handler->notify_cb = notify_cb; 18 | handler->priv = priv; 19 | xSemaphoreTakeRecursive(event_bus_lock, portMAX_DELAY); 20 | LIST_APPEND(&handler->list, &event_bus_handlers); 21 | xSemaphoreGiveRecursive(event_bus_lock); 22 | } 23 | 24 | void event_bus_notify(const char *topic, void *data) { 25 | event_bus_handler_t *handler; 26 | 27 | xSemaphoreTakeRecursive(event_bus_lock, portMAX_DELAY); 28 | LIST_FOR_EACH_ENTRY(handler, &event_bus_handlers, list) { 29 | if (!strcmp(topic, handler->topic)) { 30 | handler->notify_cb(handler->priv, data); 31 | } 32 | } 33 | xSemaphoreGiveRecursive(event_bus_lock); 34 | } 35 | -------------------------------------------------------------------------------- /main/event_bus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "list.h" 4 | 5 | typedef void (*eventbus_notify_cb_f)(void *priv, void *data); 6 | 7 | typedef struct event_bus_hamdler { 8 | struct list_head list; 9 | const char *topic; 10 | eventbus_notify_cb_f notify_cb; 11 | void *priv; 12 | } event_bus_handler_t; 13 | 14 | void event_bus_init(void); 15 | void event_bus_notify(const char *topic, void *data); 16 | void event_bus_subscribe(event_bus_handler_t *handler, const char *topic, eventbus_notify_cb_f notify_cb, void *priv); 17 | -------------------------------------------------------------------------------- /main/fb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stdbool.h> 4 | #include <stdint.h> 5 | #include <string.h> 6 | 7 | typedef struct fb { 8 | uint8_t data[6 * 64]; 9 | } fb_t; 10 | 11 | static inline unsigned int fb_width(fb_t *fb) { 12 | return 64; 13 | } 14 | 15 | static inline unsigned int fb_height(fb_t *fb) { 16 | return 48; 17 | } 18 | 19 | static inline void fb_init(fb_t *fb) { 20 | memset(fb->data, 0, sizeof(fb->data)); 21 | } 22 | 23 | static inline void fb_set_pixel(fb_t *fb, unsigned int x, unsigned int y, bool val) { 24 | unsigned int offset = (y / 8) * fb_width(fb) + x; 25 | if (val) { 26 | fb->data[offset] |= (1 << (y % 8)); 27 | } else { 28 | fb->data[offset] &= ~(1 << (y % 8)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /main/font_3x5.c: -------------------------------------------------------------------------------- 1 | #include "font_3x5.h" 2 | 3 | #include <stdint.h> 4 | #include <string.h> 5 | 6 | #include "util.h" 7 | 8 | #define GLYPH_BASE 33 9 | 10 | static const uint16_t glyphs[] = { 11 | 0x4124, 0x002D, 0x5F7D, 0x7DDF, 0x52A5, 0x73CB, 0x0024, 0x4494, 0x2922, 12 | 0x0155, 0x05D0, 0x4C00, 0x01C0, 0x2000, 0x12A4, 0x2B6A, 0x749A, 0x72A3, 13 | 0x79A7, 0x49ED, 0x39CF, 0x7BCE, 0x12A7, 0x7BEF, 0x39EF, 0x0410, 0x4C20, 14 | 0x4454, 0x0E38, 0x1511, 0x21A7, 0x736F, 0x5BEF, 0x3AEB, 0x624E, 0x3B6B, 15 | 0x73CF, 0x13CF, 0x6B4E, 0x5BED, 0x7497, 0x7B24, 0x5AED, 0x7249, 0x5B7D, 16 | 0x5B6F, 0x7B6F, 0x13EF, 0x76DB, 0x5AEB, 0x388E, 0x2497, 0x7B6D, 0x3B6D, 17 | 0x5F6D, 0x5AAD, 0x25ED, 0x72A7, 0x6496, 0x4889, 0x6926, 0x016A, 0x7000, 18 | 0x0022, 0x7BE7, 0x7BC9, 0x73C0, 0x7BE4, 0x73EF, 0x25D6, 0x79FC, 0x5BC9, 19 | 0x2482, 0x7904, 0x5749, 0x6493, 0x5FC0, 0x5BC0, 0x7BC0, 0x1F78, 0x4F78, 20 | 0x13C0, 0x7CF8, 0x24BA, 0x7B40, 0x3B40, 0x7F40, 0x5540, 0x79ED, 0x77B8, 21 | 0x6456, 0x4924, 0x3513, 0x001E, 22 | }; 23 | 24 | static uint16_t get_glyph_data(char glyph) { 25 | uint16_t glyph_data = 0; 26 | 27 | if (glyph >= GLYPH_BASE && glyph - GLYPH_BASE < ARRAY_SIZE(glyphs)) { 28 | unsigned int glyph_index = glyph - GLYPH_BASE; 29 | glyph_data = glyphs[glyph_index]; 30 | } 31 | 32 | return glyph_data; 33 | } 34 | 35 | static void font_3x5_render_glyph(char glyph, fb_t *fb, unsigned int x, unsigned int y) { 36 | uint16_t glyph_data = get_glyph_data(glyph); 37 | unsigned int bit = 0; 38 | 39 | for (unsigned int off_y = 0; off_y < 5; off_y++) { 40 | for (unsigned int off_x = 0; off_x < 3; off_x++) { 41 | fb_set_pixel(fb, x + off_x, y + off_y, !!(glyph_data & (1 << bit))); 42 | bit++; 43 | } 44 | } 45 | } 46 | 47 | void font_3x5_render_string(const char *str, fb_t *fb, unsigned int x, unsigned int y) { 48 | while (*str) { 49 | font_3x5_render_glyph(*str++, fb, x, y); 50 | x += 4; 51 | } 52 | } 53 | 54 | void font_3x5_calculate_text_params(const char *str, font_text_params_t *params) { 55 | unsigned int len = strlen(str) * 4; 56 | 57 | if (len > 0) { 58 | len--; 59 | } 60 | 61 | params->effective_size.x = len; 62 | params->effective_size.y = 5; 63 | } 64 | 65 | void font_3x5_render_string2(const char *str, const font_text_params_t *params, const font_vec_t *source_offset, const font_fb_t *fb) { 66 | font_vec_t pos = { 0, 0 }; 67 | 68 | while (*str) { 69 | char c = *str++; 70 | int dst_x = pos.x - source_offset->x; 71 | int dst_y = pos.y - source_offset->y; 72 | unsigned int offset_x = 0; 73 | uint16_t glyph_data = get_glyph_data(c); 74 | 75 | if (dst_x < 0) { 76 | offset_x += -dst_x; 77 | dst_x = 0; 78 | } 79 | if (dst_x < fb->size.x && dst_y < fb->size.y && offset_x < 3) { 80 | unsigned int draw_width = MIN(3, fb->size.x - dst_x); 81 | unsigned int draw_height = MIN(5, fb->size.y - dst_y); 82 | unsigned int y = 0; 83 | 84 | if (dst_y < 0) { 85 | y = -dst_y; 86 | } 87 | for (; y < draw_height; y++) { 88 | unsigned int x = offset_x; 89 | 90 | for (; x < draw_width - offset_x; x++) { 91 | unsigned int bit = y * 3 + x; 92 | 93 | if (glyph_data & (1 << bit)) { 94 | fb->pixels[(dst_y + y) * fb->stride + dst_x + x] = 0xff; 95 | } else { 96 | fb->pixels[(dst_y + y) * fb->stride + dst_x + x] = 0; 97 | } 98 | } 99 | } 100 | } 101 | 102 | pos.x += 4; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /main/font_3x5.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "fb.h" 4 | 5 | typedef struct font_vec { 6 | int x; 7 | int y; 8 | } font_vec_t; 9 | 10 | typedef struct font_fb { 11 | uint8_t *pixels; 12 | unsigned int stride; 13 | font_vec_t size; 14 | } font_fb_t; 15 | 16 | typedef struct font_text_params { 17 | font_vec_t effective_size; 18 | } font_text_params_t; 19 | 20 | void font_3x5_render_string(const char *str, fb_t *fb, unsigned int x, unsigned int y); 21 | 22 | void font_3x5_calculate_text_params(const char *str, font_text_params_t *params); 23 | 24 | void font_3x5_render_string2(const char *str, const font_text_params_t *params, const font_vec_t *source_offset, const font_fb_t *fb); 25 | -------------------------------------------------------------------------------- /main/futil.c: -------------------------------------------------------------------------------- 1 | #include <dirent.h> 2 | #include <errno.h> 3 | #include <fcntl.h> 4 | #include <stdlib.h> 5 | #include <string.h> 6 | #include <unistd.h> 7 | 8 | #include "futil.h" 9 | 10 | void futil_normalize_path(char* path) { 11 | size_t len = strlen(path); 12 | char* slash_ptr = path, *end_ptr = path + len; 13 | while(slash_ptr < end_ptr && (slash_ptr = strchr(slash_ptr, '/'))) { 14 | while(++slash_ptr < end_ptr && *slash_ptr == '/') { 15 | memmove(slash_ptr, slash_ptr + 1, end_ptr - slash_ptr); 16 | slash_ptr--; 17 | } 18 | } 19 | } 20 | 21 | char* futil_relpath(char* path, const char* basepath) { 22 | size_t base_len = strlen(basepath); 23 | size_t path_len = strlen(path); 24 | 25 | if(base_len > path_len) { 26 | return NULL; 27 | } 28 | 29 | if(strncmp(basepath, path, base_len)) { 30 | return NULL; 31 | } 32 | 33 | return path + base_len; 34 | } 35 | 36 | esp_err_t futil_relpath_inplace(char* path, char* basepath) { 37 | size_t base_len = strlen(basepath); 38 | size_t path_len = strlen(path); 39 | 40 | if(base_len > path_len) { 41 | return ESP_ERR_INVALID_ARG; 42 | } 43 | 44 | if(strncmp(basepath, path, base_len)) { 45 | return ESP_ERR_INVALID_ARG; 46 | } 47 | 48 | memmove(path, path + base_len, path_len - base_len + 1); 49 | 50 | return ESP_OK; 51 | } 52 | 53 | bool futil_is_path_relative(char* path) { 54 | return path[0] != '/'; 55 | } 56 | 57 | int futil_dir_exists(char *path) { 58 | DIR *dir = opendir(path); 59 | if (dir) { 60 | closedir(dir); 61 | return 0; 62 | } 63 | return -errno; 64 | } 65 | 66 | char* futil_path_concat(char* path, char* basepath) { 67 | size_t abspath_len = strlen(basepath) + 1 + strlen(path) + 1; 68 | char* abspath = calloc(1, abspath_len); 69 | if(!abspath) { 70 | return NULL; 71 | } 72 | 73 | strcat(abspath, basepath); 74 | strncat(abspath, "/", abspath_len); 75 | strncat(abspath, path, abspath_len); 76 | 77 | futil_normalize_path(abspath); 78 | 79 | return abspath; 80 | } 81 | 82 | char *futil_abspath(char *path, char *basepath) { 83 | if (!futil_is_path_relative(path)) { 84 | return path; 85 | } 86 | return futil_path_concat(path, basepath); 87 | } 88 | 89 | static char* futil_get_fext_limit(char* path, char* limit) { 90 | if(limit < path) { 91 | return NULL; 92 | } 93 | char* fext_ptr = limit; 94 | while(fext_ptr-- > path) { 95 | if(*fext_ptr == '.') { 96 | return fext_ptr + 1; 97 | } 98 | } 99 | return NULL; 100 | } 101 | 102 | char* futil_get_fext(char* path) { 103 | return futil_get_fext_limit(path, path + strlen(path)); 104 | } 105 | 106 | esp_err_t futil_get_bytes(void* dst, size_t len, char* path) { 107 | esp_err_t err = ESP_OK; 108 | int fd = open(path, O_RDONLY); 109 | if(fd < 0) { 110 | err = errno; 111 | goto fail; 112 | } 113 | 114 | while(len > 0) { 115 | ssize_t read_len = read(fd, dst, len); 116 | if(read_len < 0) { 117 | err = errno; 118 | goto fail_fd; 119 | } 120 | 121 | if(read_len == 0) { 122 | err = ESP_ERR_INVALID_ARG; 123 | goto fail_fd; 124 | } 125 | 126 | dst += read_len; 127 | len -= read_len; 128 | } 129 | 130 | fail_fd: 131 | close(fd); 132 | fail: 133 | return err; 134 | } 135 | 136 | esp_err_t futil_read_file(void* ctx, char* path, futil_write_cb cb) { 137 | esp_err_t err = ESP_OK; 138 | int fd; 139 | ssize_t read_len; 140 | char buff[FUTIL_CHUNK_SIZE]; 141 | 142 | if((fd = open(path, O_RDONLY)) < 0) { 143 | err = errno; 144 | goto fail; 145 | } 146 | 147 | while((read_len = read(fd, buff, sizeof(buff))) > 0) { 148 | if((err = cb(ctx, buff, read_len))) { 149 | printf("Failed to handle chunk: %d size: %zd\n", err, read_len); 150 | goto fail_open; 151 | } 152 | } 153 | 154 | if(read_len < 0) { 155 | printf("Read failed: %s(%d)\n", strerror(errno), errno); 156 | err = errno; 157 | goto fail_open; 158 | } 159 | 160 | fail_open: 161 | close(fd); 162 | fail: 163 | return err; 164 | } 165 | 166 | const char* futil_fname(const char* path) { 167 | size_t len = strlen(path); 168 | char* slash_ptr = strrchr(path, '/'); 169 | 170 | if (!slash_ptr) { 171 | return path + len; 172 | } 173 | 174 | return slash_ptr + 1; 175 | } 176 | -------------------------------------------------------------------------------- /main/futil.h: -------------------------------------------------------------------------------- 1 | #ifndef _FUTIL_H_ 2 | #define _FUTIL_H_ 3 | 4 | #include <stdbool.h> 5 | 6 | #include "esp_err.h" 7 | 8 | #define FUTIL_CHUNK_SIZE 256 9 | 10 | typedef esp_err_t (*futil_write_cb)(void* ctx, char* buff, size_t len); 11 | 12 | void futil_normalize_path(char* path); 13 | char* futil_relpath(char* path, const char* basepath); 14 | esp_err_t futil_relpath_inplace(char* path, char* basepath); 15 | char* futil_get_fext(char* path); 16 | esp_err_t futil_get_bytes(void* dst, size_t len, char* path); 17 | esp_err_t futil_read_file(void* ctx, char* path, futil_write_cb cb); 18 | int futil_dir_exists(char *path); 19 | char *futil_abspath(char *path, char *basepath); 20 | bool futil_is_path_relative(char* path); 21 | char* futil_path_concat(char* path, char* basepath); 22 | const char* futil_fname(const char* path); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /main/gpio_hc595.c: -------------------------------------------------------------------------------- 1 | #include "gpio_hc595.h" 2 | 3 | #include "delay.h" 4 | 5 | #include <driver/gpio.h> 6 | #include <esp_log.h> 7 | 8 | static const char *TAG = "GPIO_HC595"; 9 | 10 | static const spi_device_interface_config_t hc595_spicfg = { 11 | .clock_speed_hz = 1 * 1000 * 1000, 12 | .spics_io_num = -1, 13 | .queue_size = 1, 14 | }; 15 | 16 | esp_err_t gpio_hc595_init(gpio_hc595_t *hc595, spi_host_device_t hostdev, unsigned int latch_gpio) { 17 | esp_err_t err = gpio_set_direction(latch_gpio, GPIO_MODE_OUTPUT); 18 | if (err) { 19 | return err; 20 | } 21 | 22 | err = gpio_set_level(latch_gpio, 1); 23 | if (err) { 24 | return err; 25 | } 26 | 27 | hc595->io_state = 0; 28 | hc595->latch_gpio = latch_gpio; 29 | hc595->lock = xSemaphoreCreateMutexStatic(&hc595->lock_buffer); 30 | return spi_bus_add_device(hostdev, &hc595_spicfg, &hc595->spidev); 31 | } 32 | 33 | esp_err_t gpio_hc595_set_level(gpio_hc595_t *hc595, unsigned int gpio, bool level) { 34 | if (gpio > 7) { 35 | ESP_LOGE(TAG, "gpio can not be higher than 7"); 36 | return ESP_ERR_INVALID_ARG; 37 | } 38 | 39 | xSemaphoreTake(hc595->lock, portMAX_DELAY); 40 | uint8_t io_state = hc595->io_state; 41 | /* SPI is MSB first, no need to swap bit order */ 42 | if (level) { 43 | io_state |= (1U << gpio); 44 | } else { 45 | io_state &= ~((uint8_t)1U << gpio); 46 | } 47 | spi_transaction_t xfer = { 48 | .flags = SPI_TRANS_USE_TXDATA, 49 | .length = 8, 50 | .tx_data = { io_state }, 51 | }; 52 | esp_err_t err = spi_device_transmit(hc595->spidev, &xfer); 53 | if (!err) { 54 | gpio_set_level(hc595->latch_gpio, 0); 55 | delay_us(10); 56 | gpio_set_level(hc595->latch_gpio, 1); 57 | } 58 | xSemaphoreGive(hc595->lock); 59 | return err; 60 | } 61 | -------------------------------------------------------------------------------- /main/gpio_hc595.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stdbool.h> 4 | #include <stdint.h> 5 | 6 | #include <driver/spi_master.h> 7 | #include <esp_err.h> 8 | #include <freertos/FreeRTOS.h> 9 | #include <freertos/semphr.h> 10 | 11 | typedef struct gpio_hc595 { 12 | uint8_t io_state; 13 | unsigned int latch_gpio; 14 | spi_device_handle_t spidev; 15 | SemaphoreHandle_t lock; 16 | StaticSemaphore_t lock_buffer; 17 | } gpio_hc595_t; 18 | 19 | esp_err_t gpio_hc595_init(gpio_hc595_t *hc595, spi_host_device_t hostdev, unsigned int latch_gpio); 20 | esp_err_t gpio_hc595_set_level(gpio_hc595_t *hc595, unsigned int gpio, bool level); 21 | -------------------------------------------------------------------------------- /main/gui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stdbool.h> 4 | #include <stdint.h> 5 | 6 | #include <freertos/FreeRTOS.h> 7 | #include <freertos/semphr.h> 8 | 9 | #include "list.h" 10 | 11 | typedef struct gui_point { 12 | int x; 13 | int y; 14 | } gui_point_t; 15 | 16 | typedef struct gui_area { 17 | gui_point_t position; 18 | gui_point_t size; 19 | } gui_area_t; 20 | 21 | typedef uint8_t gui_pixel_t; 22 | #define GUI_INVERT_PIXEL(px_) (((1U << (sizeof(gui_pixel_t) * 8)) - 1) - (px_)) 23 | #define GUI_COLOR_BLACK 0 24 | 25 | typedef struct gui_fb { 26 | gui_pixel_t *pixels; 27 | unsigned int stride; 28 | } gui_fb_t; 29 | 30 | typedef struct gui_element gui_element_t; 31 | 32 | typedef struct gui_element_ops { 33 | int (*render)(gui_element_t *element, const gui_point_t *source_offset, const gui_fb_t *fb, const gui_point_t *destination_size); 34 | void (*update_shown)(gui_element_t *element); 35 | void (*check_render)(gui_element_t *element); 36 | } gui_element_ops_t; 37 | 38 | typedef struct gui_element { 39 | // Managed properties 40 | struct list_head list; 41 | gui_element_t *parent; 42 | bool shown; 43 | bool dirty; 44 | 45 | // User properties 46 | bool hidden; 47 | bool inverted; 48 | gui_area_t area; 49 | const gui_element_ops_t *ops; 50 | } gui_element_t; 51 | 52 | typedef struct gui_container { 53 | gui_element_t element; 54 | 55 | // Managed properties 56 | struct list_head children; 57 | } gui_container_t; 58 | 59 | typedef struct gui_list { 60 | gui_container_t container; 61 | 62 | // Managed properties 63 | gui_element_t *selected_entry; 64 | int last_y_scroll_pos; 65 | } gui_list_t; 66 | 67 | typedef struct gui_image { 68 | gui_element_t element; 69 | const uint8_t *image_data_start; 70 | } gui_image_t; 71 | 72 | typedef struct gui_rectangle { 73 | gui_element_t element; 74 | 75 | bool filled; 76 | gui_pixel_t color; 77 | } gui_rectangle_t; 78 | 79 | typedef enum gui_text_alignment { 80 | GUI_TEXT_ALIGN_START, 81 | GUI_TEXT_ALIGN_END, 82 | GUI_TEXT_ALIGN_CENTER, 83 | } gui_text_alignment_t; 84 | 85 | typedef struct gui_label { 86 | gui_element_t element; 87 | 88 | const char *text; 89 | gui_point_t text_offset; 90 | gui_text_alignment_t align; 91 | } gui_label_t; 92 | 93 | typedef struct gui_marquee { 94 | gui_container_t container; 95 | 96 | // Managed properties 97 | int x_scroll_pos; 98 | } gui_marquee_t; 99 | 100 | typedef struct gui gui_t; 101 | 102 | typedef struct gui_ops { 103 | void (*request_render)(const gui_t *gui); 104 | } gui_ops_t; 105 | 106 | struct gui { 107 | gui_container_t container; 108 | 109 | SemaphoreHandle_t lock; 110 | StaticSemaphore_t lock_buffer; 111 | void *priv; 112 | const gui_ops_t *ops; 113 | }; 114 | 115 | // Top level GUI API 116 | gui_element_t *gui_init(gui_t *gui, void *priv, const gui_ops_t *ops); 117 | int gui_render(gui_t *gui, gui_pixel_t *fb, unsigned int stride, const gui_point_t *size); 118 | void gui_lock(gui_t *gui); 119 | void gui_unlock(gui_t *gui); 120 | 121 | // Container level GUI API 122 | gui_element_t *gui_container_init(gui_container_t *container); 123 | 124 | // Element level GUI API 125 | void gui_element_set_position(gui_element_t *elem, unsigned int x, unsigned int y); 126 | void gui_element_set_size(gui_element_t *elem, unsigned int width, unsigned int height); 127 | void gui_element_set_hidden(gui_element_t *elem, bool hidden); 128 | void gui_element_set_inverted(gui_element_t *elem, bool inverted); 129 | void gui_element_show(gui_element_t *elem); 130 | void gui_element_add_child(gui_element_t *parent, gui_element_t *child); 131 | void gui_element_remove_child(gui_element_t *parent, gui_element_t *child); 132 | 133 | // GUI image widget API 134 | gui_element_t *gui_image_init(gui_image_t *image, unsigned int width, unsigned int height, const uint8_t *image_data_start); 135 | void gui_image_set_image(gui_image_t *image, unsigned int width, unsigned int height, const uint8_t *image_data_start); 136 | 137 | // GUI list widget API 138 | gui_element_t *gui_list_init(gui_list_t *list); 139 | void gui_list_set_selected_entry(gui_list_t *list, gui_element_t *entry); 140 | 141 | // GUI rectangle widget API 142 | gui_element_t *gui_rectangle_init(gui_rectangle_t *rectangle); 143 | void gui_rectangle_set_filled(gui_rectangle_t *rectangle, bool filled); 144 | void gui_rectangle_set_color(gui_rectangle_t *rectangle, gui_pixel_t color); 145 | 146 | // GUI label widget API 147 | gui_element_t *gui_label_init(gui_label_t *label, const char *text); 148 | void gui_label_set_text(gui_label_t *label, const char *text); 149 | void gui_label_set_text_alignment(gui_label_t *label, gui_text_alignment_t align); 150 | void gui_label_set_text_offset(gui_label_t *label, int offset_x, int offset_y); 151 | 152 | // GUI marquee widget API 153 | gui_element_t *gui_marquee_init(gui_marquee_t *marquee); 154 | -------------------------------------------------------------------------------- /main/gui_priv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gui.h" 4 | 5 | gui_element_t *gui_element_init(gui_element_t *elem, const gui_element_ops_t *ops); 6 | 7 | void gui_element_invalidate(gui_element_t *elem); 8 | void gui_element_check_render(gui_element_t *elem); 9 | -------------------------------------------------------------------------------- /main/httpd.h: -------------------------------------------------------------------------------- 1 | #ifndef _HTTPD_H_ 2 | #define _HTTPD_H_ 3 | 4 | #include <esp_http_server.h> 5 | 6 | #include "esp_err.h" 7 | 8 | #include "list.h" 9 | #include "template.h" 10 | #include "magic.h" 11 | #include "kvparser.h" 12 | 13 | #ifndef HTTPD_302 14 | #define HTTPD_302 "302 Found" 15 | #endif 16 | 17 | typedef struct httpd { 18 | httpd_handle_t server; 19 | char* webroot; 20 | 21 | struct list_head handlers; 22 | 23 | struct templ templates; 24 | 25 | struct kvparser uri_kv_parser; 26 | } httpd_t; 27 | 28 | struct httpd_handler; 29 | 30 | struct httpd_handler_ops { 31 | void (*free)(struct httpd_handler* hndlr); 32 | }; 33 | 34 | struct httpd_handler { 35 | struct list_head list; 36 | httpd_uri_t uri_handler; 37 | struct httpd_handler_ops* ops; 38 | }; 39 | 40 | struct httpd_static_template_file_handler { 41 | struct httpd_handler handler; 42 | char* path; 43 | struct templ_instance* templ; 44 | }; 45 | 46 | typedef uint8_t httpd_static_file_handler_flags; 47 | 48 | struct httpd_static_file_handler { 49 | struct httpd_handler handler; 50 | struct { 51 | httpd_static_file_handler_flags gzip:1; 52 | } flags; 53 | char* path; 54 | }; 55 | 56 | struct httpd_redirect_handler { 57 | struct httpd_handler handler; 58 | char* location; 59 | }; 60 | 61 | typedef uint8_t httpd_request_ctx_flags; 62 | 63 | struct httpd_request_ctx_form_entry { 64 | char* key; 65 | char* value; 66 | }; 67 | 68 | struct httpd_request_ctx { 69 | struct list_head form_data; 70 | httpd_req_t* req; 71 | 72 | kvlist query_params; 73 | 74 | struct { 75 | httpd_request_ctx_flags has_form_data:1; 76 | } flags; 77 | }; 78 | 79 | typedef esp_err_t (*httpd_request_cb)(struct httpd_request_ctx* ctx, void* priv); 80 | 81 | struct httpd_request_handler { 82 | struct httpd* httpd; 83 | struct httpd_handler handler; 84 | char** required_keys; 85 | void* priv; 86 | httpd_request_cb cb; 87 | }; 88 | 89 | struct httpd_slice_ctx; 90 | 91 | struct httpd_slice_ctx { 92 | struct httpd_request_ctx *req_ctx; 93 | struct templ_slice *parent; 94 | struct httpd_slice_ctx *parent_ctx; 95 | }; 96 | 97 | #define HTTPD_REQ_TO_PRIV(req) \ 98 | ((req)->user_ctx) 99 | 100 | esp_err_t httpd_alloc(struct httpd** retval, const char* webroot, uint16_t max_num_handlers); 101 | esp_err_t httpd_init(struct httpd* httpd, const char* webroot, uint16_t max_num_handlers); 102 | esp_err_t __httpd_add_static_path(struct httpd* httpd, const char* dir, char* name); 103 | esp_err_t httpd_add_redirect(struct httpd* httpd, const char* from, const char* to); 104 | esp_err_t httpd_response_write(struct httpd_request_ctx* ctx, const char* buff, size_t len); 105 | ssize_t httpd_query_string_get_param(struct httpd_request_ctx* ctx, const char* param, char** value); 106 | esp_err_t httpd_add_handler(struct httpd* httpd, httpd_method_t method, const char* path, httpd_request_cb cb, void* priv, bool websocket, size_t num_param, ...); 107 | esp_err_t httpd_send_error(struct httpd_request_ctx* ctx, const char* status); 108 | esp_err_t httpd_send_error_msg(struct httpd_request_ctx* ctx, const char* status, const char* msg); 109 | char *slice_scope_get_variable(struct httpd_slice_ctx *slice, const char* name); 110 | esp_err_t httpd_response_write_string(struct httpd_request_ctx* ctx, const char* str); 111 | 112 | #define httpd_add_static_path(httpd, path) \ 113 | __httpd_add_static_path(httpd, NULL, path) 114 | 115 | static inline esp_err_t httpd_add_template(struct httpd* httpd, char *id, templ_cb cb, void *priv) { 116 | return template_add(&(httpd)->templates, id, cb, NULL, priv); 117 | } 118 | 119 | #define httpd_set_status(ctx, status) \ 120 | httpd_resp_set_status((ctx)->req, status) 121 | 122 | #define httpd_finalize_response(ctx) \ 123 | httpd_resp_send_chunk((ctx)->req, NULL, 0) 124 | 125 | #define httpd_add_get_handler(httpd, path, cb, priv, num_params, ...) \ 126 | httpd_add_handler(httpd, HTTP_GET, path, cb, priv, false, num_params, ##__VA_ARGS__) 127 | 128 | #define httpd_add_websocket_handler(httpd, path, cb, priv, num_params, ...) \ 129 | httpd_add_handler(httpd, HTTP_GET, path, cb, priv, true, num_params, ##__VA_ARGS__) 130 | 131 | #define httpd_add_post_handler(httpd, path, cb, priv, num_params, ...) \ 132 | httpd_add_handler(httpd, HTTP_POST, path, cb, priv, false, num_params, ##__VA_ARGS__) 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /main/i2c_bus.c: -------------------------------------------------------------------------------- 1 | #include <stdlib.h> 2 | #include <string.h> 3 | 4 | #include <esp_err.h> 5 | #include <esp_log.h> 6 | #include <rom/ets_sys.h> 7 | 8 | #include "i2c_bus.h" 9 | #include "util.h" 10 | 11 | #define I2C_UNSTICK_BITS 32 12 | 13 | static const char *TAG = "I2C_BUS"; 14 | 15 | static esp_err_t i2c_bus_init_(i2c_bus_t* bus) { 16 | i2c_config_t i2c_config = { 17 | .mode = I2C_MODE_MASTER, 18 | .sda_io_num = bus->gpio_sda, 19 | .scl_io_num = bus->gpio_scl, 20 | .master.clk_speed = bus->speed_hz, 21 | }; 22 | 23 | esp_err_t err = i2c_param_config(bus->i2c_port, &i2c_config); 24 | if(err) { 25 | return err; 26 | } 27 | i2c_set_timeout(bus->i2c_port, 0xFFFFF); 28 | return i2c_driver_install(bus->i2c_port, I2C_MODE_MASTER, 0, 0, 0); 29 | } 30 | 31 | static esp_err_t i2c_bus_deinit_(i2c_bus_t* bus) { 32 | return i2c_driver_delete(bus->i2c_port); 33 | } 34 | 35 | esp_err_t i2c_bus_init(i2c_bus_t* bus, i2c_port_t i2c_port, unsigned int gpio_sda, unsigned int gpio_scl, uint32_t speed_hz) { 36 | memset(bus, 0, sizeof(*bus)); 37 | bus->i2c_port = i2c_port; 38 | bus->gpio_sda = gpio_sda; 39 | bus->gpio_scl = gpio_scl; 40 | bus->speed_hz = speed_hz; 41 | 42 | esp_err_t err = i2c_bus_init_(bus); 43 | 44 | bus->lock = xSemaphoreCreateMutexStatic(&bus->lock_buffer); 45 | return err; 46 | } 47 | 48 | static void i2c_unstick_bus(i2c_bus_t* bus) { 49 | ESP_ERROR_CHECK(i2c_bus_deinit_(bus)); 50 | 51 | ESP_ERROR_CHECK(gpio_reset_pin(bus->gpio_sda)); 52 | ESP_ERROR_CHECK(gpio_set_direction(bus->gpio_sda, GPIO_MODE_INPUT)); 53 | ESP_ERROR_CHECK(gpio_reset_pin(bus->gpio_scl)); 54 | ESP_ERROR_CHECK(gpio_set_direction(bus->gpio_scl, GPIO_MODE_OUTPUT)); 55 | for (int i = 0; i < I2C_UNSTICK_BITS; i++) { 56 | ESP_ERROR_CHECK(gpio_set_level(bus->gpio_scl, 0)); 57 | ets_delay_us(DIV_ROUND_UP(1000000UL, bus->speed_hz)); 58 | ESP_ERROR_CHECK(gpio_set_level(bus->gpio_scl, 1)); 59 | ets_delay_us(DIV_ROUND_UP(1000000UL, bus->speed_hz)); 60 | } 61 | 62 | ESP_ERROR_CHECK(i2c_bus_init_(bus)); 63 | } 64 | 65 | esp_err_t i2c_bus_cmd_begin(i2c_bus_t* bus, i2c_cmd_handle_t handle, TickType_t timeout) { 66 | xSemaphoreTake(bus->lock, portMAX_DELAY); 67 | esp_err_t err = i2c_master_cmd_begin(bus->i2c_port, handle, timeout); 68 | 69 | if (err == ESP_ERR_TIMEOUT) { 70 | ESP_LOGE(TAG, "I2C bus timeout, trying to unstick bus"); 71 | i2c_unstick_bus(bus); 72 | } 73 | xSemaphoreGive(bus->lock); 74 | return err; 75 | } 76 | 77 | esp_err_t i2c_bus_scan(i2c_bus_t* bus, i2c_address_set_t addr) { 78 | esp_err_t err = ESP_OK; 79 | size_t link_buf_size = I2C_LINK_RECOMMENDED_SIZE(1); 80 | void *link_buf = malloc(link_buf_size); 81 | for (uint8_t i = 0; i < 128; i++) { 82 | I2C_ADDRESS_SET_CLEAR(addr, i); 83 | i2c_cmd_handle_t cmd = i2c_cmd_link_create_static(link_buf, link_buf_size); 84 | if(!cmd) { 85 | err = ESP_ERR_NO_MEM; 86 | continue; 87 | } 88 | if((err = i2c_master_start(cmd))) { 89 | goto fail_link; 90 | } 91 | if((err = i2c_master_write_byte(cmd, (i << 1), true))) { 92 | goto fail_link; 93 | } 94 | if((err = i2c_master_stop(cmd))) { 95 | goto fail_link; 96 | } 97 | esp_err_t nacked = i2c_bus_cmd_begin(bus, cmd, pdMS_TO_TICKS(100)); 98 | if(!nacked) { 99 | I2C_ADDRESS_SET_SET(addr, i); 100 | } 101 | i2c_cmd_link_delete_static(cmd); 102 | continue; 103 | fail_link: 104 | i2c_cmd_link_delete_static(cmd); 105 | break; 106 | } 107 | free(link_buf); 108 | return err; 109 | } 110 | 111 | void i2c_detect(i2c_bus_t* bus) { 112 | ESP_LOGI(TAG, "Scanning i2c bus %d for devices", bus->i2c_port); 113 | I2C_ADDRESS_SET(devices); 114 | esp_err_t err = i2c_bus_scan(bus, devices); 115 | if (err) { 116 | ESP_LOGE(TAG, "Failed to scan bus %d: %d", bus->i2c_port, err); 117 | } else { 118 | ESP_LOGI(TAG, "=== Detected devices ==="); 119 | for (uint8_t i = 0; i < 128; i++) { 120 | if (I2C_ADDRESS_SET_CONTAINS(devices, i)) { 121 | ESP_LOGI(TAG, " 0x%02x", i); 122 | } 123 | } 124 | ESP_LOGI(TAG, "========================"); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /main/i2c_bus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stdint.h> 4 | 5 | #include <freertos/FreeRTOS.h> 6 | #include <freertos/semphr.h> 7 | 8 | #include <esp_err.h> 9 | #include <driver/i2c.h> 10 | 11 | typedef struct i2c_bus { 12 | i2c_port_t i2c_port; 13 | unsigned int gpio_sda; 14 | unsigned int gpio_scl; 15 | uint32_t speed_hz; 16 | SemaphoreHandle_t lock; 17 | StaticSemaphore_t lock_buffer; 18 | } i2c_bus_t; 19 | 20 | #define I2C_ADDRESS_SET(name) uint8_t name[16] = { 0 } 21 | 22 | #define I2C_ADDRESS_SET_CONTAINS(set, id) \ 23 | (!!((set)[(id) / 8] & (1 << (id % 8)))) 24 | 25 | #define I2C_ADDRESS_SET_SET(set, id) \ 26 | (set)[(id) / 8] |= (1 << (id % 8)) 27 | 28 | #define I2C_ADDRESS_SET_CLEAR(set, id) \ 29 | (set)[(id) / 8] &= ~(1 << (id % 8)) 30 | 31 | typedef uint8_t* i2c_address_set_t; 32 | 33 | esp_err_t i2c_bus_init(i2c_bus_t* bus, i2c_port_t i2c_port, unsigned int gpio_sda, unsigned int gpio_scl, uint32_t speed_hz); 34 | esp_err_t i2c_bus_cmd_begin(i2c_bus_t* bus, i2c_cmd_handle_t handle, TickType_t timeout); 35 | 36 | esp_err_t i2c_bus_scan(i2c_bus_t* bus, i2c_address_set_t addr); 37 | void i2c_detect(i2c_bus_t* i2c_bus); 38 | -------------------------------------------------------------------------------- /main/ina219.c: -------------------------------------------------------------------------------- 1 | #include "ina219.h" 2 | 3 | #include <esp_log.h> 4 | #include <freertos/FreeRTOS.h> 5 | #include <freertos/task.h> 6 | 7 | #include "util.h" 8 | 9 | #define CMD_CONFIGURATION 0x00 10 | #define CMD_SHUNT_VOLTAGE 0x01 11 | #define CMD_BUS_VOLTAGE 0x02 12 | #define CMD_POWER 0x03 13 | #define CMD_CURRENT 0x04 14 | #define CMD_CALIBRATION 0x05 15 | 16 | #define CONFIGURATION_RESET (1 << 15) 17 | 18 | static const char *TAG = "INA219"; 19 | 20 | static esp_err_t write_word(ina219_t *ina, unsigned int cmd, uint16_t val) { 21 | uint8_t word[2] = { val >> 8, val & 0xff }; 22 | return smbus_write_word(ina->bus, ina->address, cmd, word); 23 | } 24 | 25 | static esp_err_t read_uword(ina219_t *ina, unsigned int cmd, unsigned int *res) { 26 | uint8_t word[2]; 27 | esp_err_t err = smbus_read_word(ina->bus, ina->address, cmd, word); 28 | if (!err) { 29 | *res = (uint16_t)((uint16_t)word[1] | ((uint16_t)word[0] << 8)); 30 | } 31 | return err; 32 | } 33 | 34 | static esp_err_t read_sword(ina219_t *ina, unsigned int cmd, int *res) { 35 | uint8_t word[2]; 36 | esp_err_t err = smbus_read_word(ina->bus, ina->address, cmd, word); 37 | if (!err) { 38 | *res = (int16_t)((int16_t)word[1] | ((int16_t)word[0] << 8)); 39 | } 40 | return err; 41 | } 42 | 43 | static esp_err_t update_bits_(ina219_t *ina, unsigned int cmd, unsigned int shift, unsigned int mask, unsigned int val) { 44 | unsigned int old_value; 45 | esp_err_t err = read_uword(ina, cmd, &old_value); 46 | if (err) { 47 | return err; 48 | } 49 | uint16_t new_value = old_value & ~(uint16_t)(mask << shift); 50 | new_value |= val << shift; 51 | return write_word(ina, cmd, new_value); 52 | } 53 | 54 | static esp_err_t update_bits(ina219_t *ina, unsigned int cmd, unsigned int shift, unsigned int mask, unsigned int val) { 55 | esp_err_t err; 56 | 57 | xSemaphoreTake(ina->lock, portMAX_DELAY); 58 | err = update_bits_(ina, cmd, shift, mask, val); 59 | xSemaphoreGive(ina->lock); 60 | 61 | return err; 62 | } 63 | 64 | static unsigned int get_num_channels(sensor_t *sensor, sensor_measurement_type_t type) { 65 | switch (type) { 66 | case SENSOR_TYPE_VOLTAGE: 67 | return 1; 68 | case SENSOR_TYPE_CURRENT: 69 | return 1; 70 | case SENSOR_TYPE_POWER: 71 | return 1; 72 | default: 73 | return 0; 74 | } 75 | } 76 | 77 | static esp_err_t measure(sensor_t *sensor, sensor_measurement_type_t type, unsigned int channel, long *res) { 78 | ina219_t *ina = container_of(sensor, ina219_t, sensor); 79 | switch (type) { 80 | case SENSOR_TYPE_VOLTAGE: { 81 | unsigned int voltage_mv; 82 | esp_err_t err = ina219_read_bus_voltage_mv(ina, &voltage_mv); 83 | if (err) { 84 | return err; 85 | } 86 | *res = voltage_mv; 87 | break; 88 | } 89 | case SENSOR_TYPE_CURRENT: { 90 | long current_ua; 91 | esp_err_t err = ina219_read_current_ua(ina, ¤t_ua); 92 | if (err) { 93 | return err; 94 | } 95 | *res = DIV_ROUND(current_ua, 1000); 96 | break; 97 | } 98 | case SENSOR_TYPE_POWER:{ 99 | long power_uw; 100 | esp_err_t err = ina219_read_power_uw(ina, &power_uw); 101 | if (err) { 102 | return err; 103 | } 104 | *res = DIV_ROUND(power_uw, 1000); 105 | break; 106 | } 107 | default: 108 | return ESP_ERR_INVALID_ARG; 109 | } 110 | 111 | return ESP_OK; 112 | } 113 | 114 | static const sensor_def_t sensor_def = { 115 | .get_num_channels = get_num_channels, 116 | .get_channel_name = NULL, 117 | .measure = measure, 118 | }; 119 | 120 | esp_err_t ina219_init(ina219_t *ina, smbus_t *bus, unsigned int address, unsigned int shunt_resistance_mohms, const char *name) { 121 | ina->bus = bus; 122 | ina->address = address; 123 | ina->shunt_resistance_mohms = shunt_resistance_mohms; 124 | ina->lock = xSemaphoreCreateMutexStatic(&ina->lock_buffer); 125 | 126 | esp_err_t err = ina219_reset(ina); 127 | if (err) { 128 | ESP_LOGE(TAG, "Failed to reset INA219"); 129 | return err; 130 | } 131 | sensor_init(&ina->sensor, &sensor_def, name); 132 | sensor_add(&ina->sensor); 133 | 134 | return err; 135 | } 136 | 137 | esp_err_t ina219_reset(ina219_t *ina) { 138 | esp_err_t err = write_word(ina, CMD_CONFIGURATION, CONFIGURATION_RESET); 139 | if (!err) { 140 | vTaskDelay(pdMS_TO_TICKS(10)); 141 | } 142 | return err; 143 | } 144 | 145 | esp_err_t ina219_set_voltage_range(ina219_t *ina, ina219_bus_voltage_range_t range) { 146 | switch (range) { 147 | case INA219_BUS_VOLTAGE_RANGE_16V: 148 | return update_bits(ina, CMD_CONFIGURATION, 13, 1, 0); 149 | case INA219_BUS_VOLTAGE_RANGE_32V: 150 | return update_bits(ina, CMD_CONFIGURATION, 13, 1, 1); 151 | } 152 | 153 | return ESP_ERR_INVALID_ARG; 154 | } 155 | 156 | esp_err_t ina219_set_shunt_voltage_range(ina219_t *ina, ina219_pga_current_gain_t gain) { 157 | return update_bits(ina, CMD_CONFIGURATION, 11, 3, gain); 158 | } 159 | 160 | esp_err_t ina219_set_shunt_voltage_resolution(ina219_t *ina, ina219_adc_resolution_t resolution) { 161 | return update_bits(ina, CMD_CONFIGURATION, 3, 15, resolution); 162 | } 163 | 164 | esp_err_t ina219_set_bus_voltage_adc_resolution(ina219_t *ina, ina219_adc_resolution_t resolution) { 165 | return update_bits(ina, CMD_CONFIGURATION, 7, 15, resolution); 166 | } 167 | 168 | esp_err_t ina219_read_shunt_voltage_uv(ina219_t *ina, long *shunt_voltage_uv) { 169 | int shunt_voltage_raw; 170 | esp_err_t err = read_sword(ina, CMD_SHUNT_VOLTAGE, &shunt_voltage_raw); 171 | if (!err) { 172 | *shunt_voltage_uv = (long)shunt_voltage_raw * 10L; 173 | } 174 | return err; 175 | } 176 | 177 | esp_err_t ina219_read_bus_voltage_mv(ina219_t *ina, unsigned int *bus_voltage_mv) { 178 | unsigned int bus_voltage_reg; 179 | esp_err_t err = read_uword(ina, CMD_BUS_VOLTAGE, &bus_voltage_reg); 180 | if (!err) { 181 | *bus_voltage_mv = (bus_voltage_reg & ~0x07U) >> 1; 182 | } 183 | return err; 184 | } 185 | 186 | esp_err_t ina219_read_current_ua(ina219_t *ina, long *current_ua) { 187 | long shunt_voltage_uv; 188 | esp_err_t err = ina219_read_shunt_voltage_uv(ina, &shunt_voltage_uv); 189 | if (!err) { 190 | *current_ua = DIV_ROUND((int32_t)shunt_voltage_uv * (int32_t)1000, 191 | (int32_t)ina->shunt_resistance_mohms); 192 | } 193 | return err; 194 | } 195 | 196 | esp_err_t ina219_read_power_uw(ina219_t *ina, long *power_uw) { 197 | long current_ua; 198 | unsigned int voltage_mv; 199 | esp_err_t err = ina219_read_bus_voltage_mv(ina, &voltage_mv); 200 | if (err) { 201 | ESP_LOGE(TAG, "Failed to read bus voltage"); 202 | return err; 203 | } 204 | err = ina219_read_current_ua(ina, ¤t_ua); 205 | if (err) { 206 | ESP_LOGE(TAG, "Failed to read current"); 207 | return err; 208 | } 209 | *power_uw = DIV_ROUND((int64_t)current_ua * (int64_t)voltage_mv, (int64_t)1000); 210 | return ESP_OK; 211 | } 212 | 213 | -------------------------------------------------------------------------------- /main/ina219.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <freertos/FreeRTOS.h> 4 | #include <freertos/semphr.h> 5 | 6 | #include <esp_err.h> 7 | 8 | #include "sensor.h" 9 | #include "smbus.h" 10 | 11 | typedef struct ina219 { 12 | smbus_t *bus; 13 | unsigned int address; 14 | unsigned int shunt_resistance_mohms; 15 | sensor_t sensor; 16 | SemaphoreHandle_t lock; 17 | StaticSemaphore_t lock_buffer; 18 | } ina219_t; 19 | 20 | typedef enum { 21 | INA219_BUS_VOLTAGE_RANGE_16V, 22 | INA219_BUS_VOLTAGE_RANGE_32V 23 | } ina219_bus_voltage_range_t; 24 | 25 | typedef enum { 26 | INA219_PGA_CURRENT_GAIN_40MV = 0, 27 | INA219_PGA_CURRENT_GAIN_80MV = 1, 28 | INA219_PGA_CURRENT_GAIN_160MV = 2, 29 | INA219_PGA_CURRENT_GAIN_320MV = 3 30 | } ina219_pga_current_gain_t; 31 | 32 | typedef enum { 33 | INA219_ADC_RESOLUTION_9BIT = 0, 34 | INA219_ADC_RESOLUTION_10BIT = 1, 35 | INA219_ADC_RESOLUTION_11BIT = 2, 36 | INA219_ADC_RESOLUTION_12BIT = 3, 37 | INA219_ADC_RESOLUTION_AVG_2 = 9, 38 | INA219_ADC_RESOLUTION_AVG_4 = 10, 39 | INA219_ADC_RESOLUTION_AVG_8 = 11, 40 | INA219_ADC_RESOLUTION_AVG_16 = 12, 41 | INA219_ADC_RESOLUTION_AVG_32 = 13, 42 | INA219_ADC_RESOLUTION_AVG_64 = 14, 43 | INA219_ADC_RESOLUTION_AVG_128 = 15, 44 | } ina219_adc_resolution_t; 45 | 46 | esp_err_t ina219_init(ina219_t *ina, smbus_t *bus, unsigned int address, unsigned int shunt_resistance_mohms, const char *name); 47 | esp_err_t ina219_reset(ina219_t *ina); 48 | esp_err_t ina219_set_voltage_range(ina219_t *ina, ina219_bus_voltage_range_t range); 49 | esp_err_t ina219_set_shunt_voltage_range(ina219_t *ina, ina219_pga_current_gain_t gain); 50 | esp_err_t ina219_set_shunt_voltage_resolution(ina219_t *ina, ina219_adc_resolution_t resolution); 51 | esp_err_t ina219_set_bus_voltage_adc_resolution(ina219_t *ina, ina219_adc_resolution_t resolution); 52 | esp_err_t ina219_read_shunt_voltage_uv(ina219_t *ina, long *shunt_voltage_uv); 53 | esp_err_t ina219_read_bus_voltage_mv(ina219_t *ina, unsigned int *bus_voltage_mv); 54 | esp_err_t ina219_read_current_ua(ina219_t *ina, long *current_ua); 55 | esp_err_t ina219_read_power_uw(ina219_t *ina, long *power_uw); 56 | -------------------------------------------------------------------------------- /main/kvparser.c: -------------------------------------------------------------------------------- 1 | #include <errno.h> 2 | #include <stdbool.h> 3 | #include <stdlib.h> 4 | #include <string.h> 5 | 6 | #include "esp_log.h" 7 | 8 | #include "kvparser.h" 9 | 10 | static int _kvparser_init(struct kvparser* parser, char* separator, char* assign) { 11 | int err; 12 | 13 | memset(parser, 0, sizeof(struct kvparser)); 14 | parser->sep = strdup(separator); 15 | if(!parser->sep) { 16 | err = ENOMEM; 17 | goto fail; 18 | } 19 | 20 | parser->assign = strdup(assign); 21 | if(!parser->assign) { 22 | err = ENOMEM; 23 | goto fail_sep_alloc; 24 | } 25 | 26 | return 0; 27 | 28 | fail_sep_alloc: 29 | free(parser->sep); 30 | fail: 31 | return err; 32 | } 33 | 34 | int kvparser_init_processors(struct kvparser* parser, char* separator, char* assign, struct kv_str_processor_def* key_proc, struct kv_str_processor_def* value_proc) { 35 | int err = _kvparser_init(parser, separator, assign); 36 | if(err) { 37 | goto fail; 38 | } 39 | 40 | parser->key_processor = key_proc; 41 | parser->value_processor = value_proc; 42 | 43 | fail: 44 | return err; 45 | } 46 | 47 | static ssize_t clone_value(char** retval, char* str, size_t len) { 48 | char* clone = strndup(str, len); 49 | if(!clone) { 50 | return -ENOMEM; 51 | } 52 | *retval = clone; 53 | return len; 54 | } 55 | 56 | struct kv_str_processor_def kv_clone_proc = { 57 | .cb = clone_value, 58 | .flags = { 59 | .dyn_alloc = 1, 60 | }, 61 | }; 62 | 63 | struct kv_str_processor_def* kv_get_clone_str_proc() { 64 | return &kv_clone_proc; 65 | } 66 | 67 | 68 | static ssize_t copy_value_ptr(char** retval, char* str, size_t len) { 69 | *retval = str; 70 | return len; 71 | } 72 | 73 | struct kv_str_processor_def kv_zerocopy_proc = { 74 | .cb = copy_value_ptr, 75 | .flags = { 76 | .dyn_alloc = 0, 77 | }, 78 | }; 79 | 80 | struct kv_str_processor_def* kv_get_zerocopy_str_proc() { 81 | return &kv_zerocopy_proc; 82 | } 83 | 84 | int kvparser_init_inplace(struct kvparser* parser, char* separator, char* assign) { 85 | return kvparser_init_processors(parser, separator, assign, &kv_zerocopy_proc, &kv_zerocopy_proc); 86 | } 87 | 88 | int kvparser_init(struct kvparser* parser, char* separator, char* assign) { 89 | return kvparser_init_processors(parser, separator, assign, &kv_clone_proc, &kv_clone_proc); 90 | } 91 | 92 | static int add_kvpair(struct kvparser* parser, kvlist* list, bool has_key, char* key_buff, size_t key_len, char* val_start, char* val_end) { 93 | int err = 0; 94 | ssize_t process_len; 95 | struct kvpair* pair; 96 | 97 | // Value without key, set key buffer to empty string 98 | if(!has_key) { 99 | key_buff = ""; 100 | key_len = 0; 101 | } 102 | 103 | pair = calloc(1, sizeof(struct kvpair)); 104 | if(!pair) { 105 | err = ENOMEM; 106 | goto fail; 107 | } 108 | 109 | // Append kvpair to list before member setup for easy cleanup 110 | LIST_APPEND_TAIL(&pair->list, list); 111 | 112 | if((process_len = parser->key_processor->cb(&pair->key, key_buff, key_len)) < 0) { 113 | err = -process_len; 114 | goto fail; 115 | } 116 | pair->key_len = process_len; 117 | 118 | if((process_len = parser->value_processor->cb(&pair->value, val_start, val_end - val_start)) < 0) { 119 | err = -process_len; 120 | goto fail; 121 | } 122 | pair->value_len = process_len; 123 | 124 | fail: 125 | return err; 126 | } 127 | 128 | #define strnmincmp(haystack, needle, nmax) \ 129 | strncmp(haystack, needle, (MIN(strlen(needle), (nmax)))) 130 | 131 | int kvparser_parse_string(struct kvparser* parser, kvlist* pairs, char* str, size_t len) { 132 | int err = 0; 133 | int state = KV_STATE_TOKEN_SEP; 134 | size_t assign_len = strlen(parser->assign); 135 | size_t sep_len = strlen(parser->sep); 136 | char* parse_pos = str; 137 | char* parse_limit = str + len; 138 | char* key_buff = NULL; 139 | char* last_token = str; 140 | size_t key_len = 0; 141 | 142 | kvlist kvpairs; 143 | 144 | INIT_LIST_HEAD(kvpairs); 145 | while(parse_pos < parse_limit) { 146 | bool token_found = false; 147 | 148 | if(!strnmincmp(parse_pos, parser->assign, parse_limit - parse_pos)) { 149 | if(state != KV_STATE_TOKEN_SEP) { 150 | err = EINVAL; 151 | goto fail; 152 | } 153 | 154 | token_found = true; 155 | 156 | // We found a key! 157 | key_buff = last_token; 158 | key_len = parse_pos - last_token; 159 | 160 | parse_pos += assign_len; 161 | state = KV_STATE_TOKEN_ASSIGN; 162 | } 163 | 164 | if(!strnmincmp(parse_pos, parser->sep, parse_limit - parse_pos)) { 165 | if(state != KV_STATE_TOKEN_SEP && state != KV_STATE_TOKEN_ASSIGN) { 166 | err = EINVAL; 167 | goto fail; 168 | } 169 | 170 | token_found = true; 171 | 172 | if((err = add_kvpair(parser, &kvpairs, state == KV_STATE_TOKEN_ASSIGN, key_buff, key_len, last_token, parse_pos))) { 173 | goto fail; 174 | } 175 | 176 | parse_pos += sep_len; 177 | state = KV_STATE_TOKEN_SEP; 178 | } 179 | 180 | if(token_found) { 181 | // Token found, update token position 182 | last_token = parse_pos; 183 | } else { 184 | // No token found, advance parse pointer 185 | parse_pos++; 186 | } 187 | } 188 | 189 | if((err = add_kvpair(parser, &kvpairs, state == KV_STATE_TOKEN_ASSIGN, key_buff, key_len, last_token, parse_limit))) { 190 | goto fail; 191 | } 192 | 193 | LIST_SPLICE(&kvpairs, pairs); 194 | 195 | return 0; 196 | 197 | fail: 198 | { 199 | kvlist* cursor, *next; 200 | LIST_FOR_EACH_SAFE(cursor, next, &kvpairs) { 201 | struct kvpair* pair = LIST_GET_ENTRY(cursor, struct kvpair, list); 202 | kvparser_free_kvpair(parser, pair); 203 | } 204 | } 205 | return err; 206 | } 207 | 208 | void kvparser_free_kvpair(struct kvparser* parser, struct kvpair* pair) { 209 | if(parser->key_processor->flags.dyn_alloc && pair->key) { 210 | free(pair->key); 211 | } 212 | if(parser->value_processor->flags.dyn_alloc && pair->value) { 213 | free(pair->value); 214 | } 215 | free(pair); 216 | } 217 | 218 | void kvparser_free(struct kvparser* parser) { 219 | free(parser->sep); 220 | free(parser->assign); 221 | } 222 | 223 | struct kvpair* kvparser_find_pair(kvlist* list, const char* key) { 224 | kvlist* cursor; 225 | LIST_FOR_EACH(cursor, list) { 226 | struct kvpair* kvpair = LIST_GET_ENTRY(cursor, struct kvpair, list); 227 | if(strlen(key) == kvpair->key_len && !strncmp(kvpair->key, key, kvpair->key_len)) { 228 | return kvpair; 229 | } 230 | } 231 | return NULL; 232 | } 233 | 234 | -------------------------------------------------------------------------------- /main/kvparser.h: -------------------------------------------------------------------------------- 1 | #ifndef _KVPARSER_H_ 2 | #define _KVPARSER_H_ 3 | 4 | #include <stddef.h> 5 | #include <stdint.h> 6 | #include <sys/types.h> 7 | 8 | #include "list.h" 9 | 10 | enum { 11 | KV_STATE_TOKEN_SEP = 0, 12 | KV_STATE_TOKEN_ASSIGN, 13 | }; 14 | 15 | struct kvparser; 16 | 17 | typedef struct list_head kvlist; 18 | 19 | struct kvpair { 20 | struct list_head list; 21 | 22 | char* key; 23 | size_t key_len; 24 | 25 | char* value; 26 | size_t value_len; 27 | }; 28 | 29 | typedef ssize_t (*kv_str_processor)(char** retval, char* str, size_t len); 30 | 31 | typedef uint8_t kv_str_processor_flags; 32 | 33 | struct kv_str_processor_def { 34 | kv_str_processor cb; 35 | struct { 36 | kv_str_processor_flags dyn_alloc:1; 37 | } flags; 38 | }; 39 | 40 | struct kvparser { 41 | char* sep; 42 | char* assign; 43 | 44 | struct kv_str_processor_def* key_processor; 45 | struct kv_str_processor_def* value_processor; 46 | }; 47 | 48 | struct kv_str_processor_def* kv_get_clone_str_proc(); 49 | struct kv_str_processor_def* kv_get_zerocopy_str_proc(); 50 | int kvparser_init(struct kvparser* parser, char* separator, char* assign); 51 | int kvparser_init_processors(struct kvparser* parser, char* separator, char* assign, struct kv_str_processor_def* key_proc, struct kv_str_processor_def* value_proc); 52 | int kvparser_init_inplace(struct kvparser* parser, char* separator, char* assign); 53 | int kvparser_parse_string(struct kvparser* parser, kvlist* pairs, char* str, size_t len); 54 | void kvparser_free_kvpair(struct kvparser* parser, struct kvpair* pair); 55 | void kvparser_free(struct kvparser* parser); 56 | struct kvpair* kvparser_find_pair(kvlist* list, const char* key); 57 | 58 | #endif 59 | 60 | -------------------------------------------------------------------------------- /main/list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stddef.h> 4 | 5 | #include "util.h" 6 | 7 | typedef struct list_head list_head_t; 8 | 9 | struct list_head { 10 | list_head_t *next, *prev; 11 | }; 12 | 13 | #define INIT_LIST_HEAD(name) \ 14 | (name) = (struct list_head){ &(name), &(name) } 15 | 16 | #define DECLARE_LIST_HEAD(name) \ 17 | list_head_t name = (struct list_head){ &(name), &(name) } 18 | 19 | #define LIST_FOR_EACH(cursor, list) \ 20 | for((cursor) = (list)->next; (cursor) != (list); (cursor) = (cursor)->next) 21 | 22 | #define LIST_FOR_EACH_ENTRY(cursor, head, member) \ 23 | for((cursor) = LIST_GET_ENTRY((head)->next, typeof(*(cursor)), member); \ 24 | &((cursor)->member) != (head); \ 25 | (cursor) = LIST_GET_ENTRY(((cursor)->member).next, typeof(*(cursor)), member)) 26 | 27 | #define LIST_FOR_EACH_SAFE(cursor, n, list) \ 28 | for((cursor) = (list)->next, (n) = (cursor)->next; (cursor) != (list); (cursor) = n, (n) = (cursor)->next) 29 | 30 | #define LIST_FOR_EACH_ENTRY_SAFE(cursor, n, head, member) \ 31 | for((cursor) = LIST_GET_ENTRY((head)->next, typeof(*(cursor)), member), (n) = (cursor)->member.next; \ 32 | &((cursor)->member) != (head); \ 33 | (cursor) = LIST_GET_ENTRY((n), typeof(*(cursor)), member), (n) = (cursor)->member.next) 34 | 35 | #define LIST_GET_ENTRY(list, type, member) \ 36 | container_of(list, type, member) 37 | 38 | static inline void _list_append(struct list_head* prev, struct list_head* next, struct list_head* entry) { 39 | entry->prev = prev; 40 | entry->next = next; 41 | prev->next = entry; 42 | next->prev = entry; 43 | } 44 | 45 | #define LIST_APPEND(entry, list) \ 46 | _list_append(list, (list)->next, entry) 47 | 48 | #define LIST_APPEND_TAIL(entry, list) \ 49 | _list_append((list)->prev, list, entry) 50 | 51 | #define LIST_DELETE(entry) \ 52 | do { \ 53 | (entry)->prev->next = (entry)->next; \ 54 | (entry)->next->prev = (entry)->prev; \ 55 | (entry)->prev = (entry); \ 56 | (entry)->next = (entry); \ 57 | } while(0) 58 | 59 | #define LIST_IS_EMPTY(list) \ 60 | ((list)->next == (list)) 61 | 62 | #define LIST_SPLICE(sublist, parent) \ 63 | do { \ 64 | if(!LIST_IS_EMPTY(sublist)) { \ 65 | (sublist)->prev->next = (parent)->next; \ 66 | (sublist)->next->prev = (parent); \ 67 | (parent)->next->prev = (sublist)->next; \ 68 | (parent)->next = (sublist)->next; \ 69 | } \ 70 | } while(0) 71 | 72 | inline size_t LIST_LENGTH(struct list_head* list) { 73 | struct list_head* cursor; 74 | size_t len = 0; 75 | 76 | LIST_FOR_EACH(cursor, list) { 77 | len++; 78 | } 79 | return len; 80 | } 81 | 82 | -------------------------------------------------------------------------------- /main/lm75.c: -------------------------------------------------------------------------------- 1 | #include "lm75.h" 2 | 3 | #include "util.h" 4 | 5 | static unsigned int get_num_channels(sensor_t *sensor, sensor_measurement_type_t type) { 6 | switch (type) { 7 | case SENSOR_TYPE_TEMPERATURE: 8 | return 1; 9 | default: 10 | return 0; 11 | } 12 | } 13 | 14 | static esp_err_t measure(sensor_t *sensor, sensor_measurement_type_t type, unsigned int channel, long *res) { 15 | lm75_t *lm75 = container_of(sensor, lm75_t, sensor); 16 | switch (type) { 17 | case SENSOR_TYPE_TEMPERATURE: { 18 | int32_t temperature_mdegc; 19 | esp_err_t err = lm75_read_temperature_mdegc(lm75, &temperature_mdegc); 20 | if (err) { 21 | return err; 22 | } 23 | *res = temperature_mdegc; 24 | break; 25 | } 26 | default: 27 | return ESP_ERR_INVALID_ARG; 28 | } 29 | 30 | return ESP_OK; 31 | } 32 | 33 | static const sensor_def_t sensor_def = { 34 | .get_num_channels = get_num_channels, 35 | .get_channel_name = NULL, 36 | .measure = measure, 37 | }; 38 | 39 | void lm75_init(lm75_t *lm75, i2c_bus_t *bus, unsigned int address, const char *name) { 40 | lm75->bus = bus; 41 | lm75->address = address; 42 | sensor_init(&lm75->sensor, &sensor_def, name); 43 | sensor_add(&lm75->sensor); 44 | } 45 | 46 | esp_err_t lm75_read_temperature_mdegc(lm75_t *lm75, int32_t *res) { 47 | i2c_cmd_handle_t cmd = i2c_cmd_link_create_static(lm75->xfers, sizeof(lm75->xfers)); 48 | uint8_t temp_data[2]; 49 | i2c_master_start(cmd); 50 | i2c_master_write_byte(cmd, (lm75->address << 1), true); 51 | i2c_master_write_byte(cmd, 0, true); 52 | i2c_master_start(cmd); 53 | i2c_master_write_byte(cmd, (lm75->address << 1) | 1, true); 54 | i2c_master_read(cmd, temp_data, sizeof(temp_data), I2C_MASTER_LAST_NACK); 55 | i2c_master_stop(cmd); 56 | esp_err_t err = i2c_bus_cmd_begin(lm75->bus, cmd, pdMS_TO_TICKS(10)); 57 | i2c_cmd_link_delete_static(cmd); 58 | if (err) { 59 | return err; 60 | } 61 | uint16_t temp_0_125C = 62 | (((int16_t)temp_data[0]) << 8) | 63 | temp_data[1]; 64 | temp_0_125C &= ~((int16_t)0x1f); 65 | temp_0_125C /= 32; 66 | *res = (int32_t)temp_0_125C * 125; 67 | return ESP_OK; 68 | } 69 | -------------------------------------------------------------------------------- /main/lm75.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stdint.h> 4 | 5 | #include <driver/i2c.h> 6 | #include <esp_err.h> 7 | 8 | #include "i2c_bus.h" 9 | #include "sensor.h" 10 | 11 | typedef struct lm75 { 12 | uint8_t address; 13 | i2c_bus_t *bus; 14 | uint8_t xfers[I2C_LINK_RECOMMENDED_SIZE(4)]; 15 | sensor_t sensor; 16 | } lm75_t; 17 | 18 | void lm75_init(lm75_t *lm75, i2c_bus_t *bus, unsigned int address, const char *name); 19 | esp_err_t lm75_read_temperature_mdegc(lm75_t *lm75, int32_t *res); 20 | -------------------------------------------------------------------------------- /main/magic.c: -------------------------------------------------------------------------------- 1 | #include <string.h> 2 | 3 | #include "magic.h" 4 | #include "futil.h" 5 | #include "util.h" 6 | 7 | MAGIC_DECLARE(MAGIC_GZIP, 0x1f, 0x8b); 8 | 9 | static esp_err_t magic_cmp_file(const uint8_t* magic, char* path) { 10 | esp_err_t err; 11 | char file_magic[2]; 12 | if((err = -futil_get_bytes(file_magic, ARRAY_SIZE(file_magic), path))) { 13 | goto fail; 14 | } 15 | 16 | return !!memcmp(file_magic, magic, ARRAY_SIZE(file_magic)); 17 | 18 | fail: 19 | return err; 20 | } 21 | 22 | esp_err_t magic_file_is_gzip(char* path) { 23 | return magic_cmp_file(MAGIC_GZIP, path); 24 | } 25 | -------------------------------------------------------------------------------- /main/magic.h: -------------------------------------------------------------------------------- 1 | #ifndef _MAGIC_H_ 2 | #define _MAGIC_H_ 3 | 4 | #include <stdint.h> 5 | 6 | #include "esp_err.h" 7 | 8 | #define MAGIC_DECLARE(name, f1, f2) \ 9 | const uint8_t name[2] = { f1, f2 } 10 | 11 | esp_err_t magic_file_is_gzip(char* path); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /main/main.c: -------------------------------------------------------------------------------- 1 | #include <driver/gpio.h> 2 | #include <driver/i2c.h> 3 | #include <driver/spi_master.h> 4 | #include <esp_err.h> 5 | #include <esp_log.h> 6 | #include <esp_spiffs.h> 7 | #include <freertos/FreeRTOS.h> 8 | 9 | #include "api.h" 10 | #include "battery_protection.h" 11 | #include "bq40z50_gauge.h" 12 | #include "buttons.h" 13 | #include "display.h" 14 | #include "ethernet.h" 15 | #include "event_bus.h" 16 | #include "font_3x5.h" 17 | #include "gpio_hc595.h" 18 | #include "httpd.h" 19 | #include "i2c_bus.h" 20 | #include "power_path.h" 21 | #include "prometheus_exporter.h" 22 | #include "prometheus_metrics.h" 23 | #include "prometheus_metrics_battery.h" 24 | #include "scheduler.h" 25 | #include "sensor.h" 26 | #include "settings.h" 27 | #include "ssd1306_oled.h" 28 | #include "util.h" 29 | #include "vendor.h" 30 | #include "website.h" 31 | #include "wifi.h" 32 | 33 | #define GPIO_HC595_DC_OUT3_OFF 1 34 | #define GPIO_HC595_DC_OUT_TEST 2 35 | #define GPIO_HC595_DC_OUT_OFF 3 36 | #define GPIO_HC595_USB_OUT_OFF 4 37 | #define GPIO_HC595_DC_OUT2_OFF 5 38 | #define GPIO_HC595_DC_OUT1_OFF 6 39 | 40 | #define SPI_HC595 SPI2_HOST 41 | #define GPIO_HC595_DATA 16 42 | #define GPIO_HC595_CLK 14 43 | #define GPIO_HC595_LATCH 33 44 | 45 | #define I2C_SMBUS I2C_NUM_1 46 | #define GPIO_SMBUS_DATA 5 47 | #define GPIO_SMBUS_CLK 4 48 | 49 | #define I2C_I2C I2C_NUM_0 50 | #define GPIO_I2C_DATA 15 51 | #define GPIO_I2C_CLK 13 52 | 53 | #define GPIO_BUTTON 39 54 | 55 | #define GPIO_PHY_RESET 32 56 | #define GPIO_PHY_MDC 2 57 | #define GPIO_PHY_MDIO 18 58 | 59 | static const char *TAG = "main"; 60 | 61 | static const esp_vfs_spiffs_conf_t spiffs_conf = { 62 | .base_path = "/webroot", 63 | .partition_label = "webroot", 64 | .max_files = 5, 65 | .format_if_mount_failed = false 66 | }; 67 | 68 | static const spi_bus_config_t hc595_spi_bus_cfg = { 69 | .mosi_io_num = GPIO_HC595_DATA, 70 | .miso_io_num = -1, 71 | .sclk_io_num = GPIO_HC595_CLK, 72 | .max_transfer_sz = 16, 73 | .flags = SPICOMMON_BUSFLAG_MASTER 74 | }; 75 | 76 | static gpio_hc595_t hc595; 77 | 78 | static i2c_bus_t i2c_bus; 79 | static i2c_bus_t smbus_i2c_bus; 80 | static smbus_t smbus_bus; 81 | 82 | static bq40z50_t bq40z50; 83 | 84 | static httpd_t httpd; 85 | 86 | prometheus_t prometheus; 87 | prometheus_metric_t metric_simple; 88 | prometheus_metric_t metric_complex; 89 | prometheus_battery_metrics_t battery_metrics; 90 | 91 | static volatile bool do_shutdown = false; 92 | 93 | static const ethernet_config_t ethernet_cfg = { 94 | .phy_address = 0, 95 | .phy_reset_gpio = GPIO_PHY_RESET, 96 | .mdc_gpio = GPIO_PHY_MDC, 97 | .mdio_gpio = GPIO_PHY_MDIO 98 | }; 99 | 100 | button_event_handler_t button_held_event_handler; 101 | 102 | static bool on_button_held_event(const button_event_t *event, void *priv) { 103 | ESP_LOGI(TAG, "Button held for 5s! Shutting down battery pack..."); 104 | bq40z50_shutdown(&bq40z50); 105 | 106 | return true; 107 | } 108 | 109 | const button_event_handler_single_user_cfg_t button_held_cfg = { 110 | .base = { 111 | .cb = on_button_held_event, 112 | }, 113 | .single = { 114 | .button = BUTTON_ENTER, 115 | .action = BUTTON_ACTION_HOLD, 116 | .min_hold_duration_ms = 5000, 117 | } 118 | }; 119 | 120 | void app_main() { 121 | ESP_ERROR_CHECK(esp_vfs_spiffs_register(&spiffs_conf)); 122 | 123 | event_bus_init(); 124 | settings_init(); 125 | vendor_init(); 126 | scheduler_init(); 127 | buttons_init(); 128 | 129 | ESP_ERROR_CHECK(ethernet_init(ðernet_cfg)); 130 | 131 | // wifi_init(); 132 | // wifi_start_ap(); 133 | 134 | ESP_ERROR_CHECK(spi_bus_initialize(SPI_HC595, &hc595_spi_bus_cfg, SPI_DMA_DISABLED)); 135 | ESP_ERROR_CHECK(gpio_hc595_init(&hc595, SPI_HC595, GPIO_HC595_LATCH)); 136 | gpio_hc595_set_level(&hc595, GPIO_HC595_DC_OUT3_OFF, 0); 137 | gpio_hc595_set_level(&hc595, GPIO_HC595_DC_OUT_TEST, 0); 138 | gpio_hc595_set_level(&hc595, GPIO_HC595_DC_OUT_OFF, 0); 139 | gpio_hc595_set_level(&hc595, GPIO_HC595_USB_OUT_OFF, 0); 140 | gpio_hc595_set_level(&hc595, GPIO_HC595_DC_OUT2_OFF, 0); 141 | gpio_hc595_set_level(&hc595, GPIO_HC595_DC_OUT1_OFF, 0); 142 | 143 | ESP_ERROR_CHECK(i2c_bus_init(&smbus_i2c_bus, I2C_SMBUS, GPIO_SMBUS_DATA, GPIO_SMBUS_CLK, KHZ(100))); 144 | smbus_init(&smbus_bus, &smbus_i2c_bus); 145 | i2c_detect(&smbus_i2c_bus); 146 | 147 | ESP_ERROR_CHECK(i2c_bus_init(&i2c_bus, I2C_I2C, GPIO_I2C_DATA, GPIO_I2C_CLK, KHZ(100))); 148 | i2c_detect(&i2c_bus); 149 | display_init(&i2c_bus); 150 | power_path_early_init(&smbus_bus, &i2c_bus); 151 | vTaskDelay(pdMS_TO_TICKS(5000)); 152 | 153 | ESP_ERROR_CHECK(bq40z50_init(&bq40z50, &smbus_bus, -1)); 154 | battery_gauge_init(&bq40z50.gauge); 155 | 156 | power_path_init(&smbus_bus, &i2c_bus); 157 | 158 | battery_protection_init(&bq40z50); 159 | 160 | buttons_register_single_button_event_handler(&button_held_event_handler, &button_held_cfg); 161 | buttons_enable_event_handler(&button_held_event_handler); 162 | 163 | ESP_ERROR_CHECK(httpd_init(&httpd, "/webroot", 32)); 164 | website_init(&httpd); 165 | api_init(&httpd); 166 | prometheus_init(&prometheus); 167 | 168 | prometheus_battery_metrics_init(&battery_metrics, &bq40z50); 169 | prometheus_add_battery_metrics(&battery_metrics, &prometheus); 170 | sensor_install_metrics(&prometheus); 171 | ESP_ERROR_CHECK(prometheus_register_exporter(&prometheus, &httpd, "/prometheus")); 172 | 173 | display_render_loop(); 174 | } 175 | -------------------------------------------------------------------------------- /main/mime.c: -------------------------------------------------------------------------------- 1 | #include <string.h> 2 | 3 | #include "mime.h" 4 | #include "futil.h" 5 | #include "util.h" 6 | 7 | const struct mime_pair mimedb[] = { 8 | { 9 | .fext = "html", 10 | .mime_type = "text/html", 11 | }, 12 | { 13 | .fext = "thtml", 14 | .mime_type = "text/html", 15 | }, 16 | { 17 | .fext = "js", 18 | .mime_type = "text/javascript", 19 | }, 20 | { 21 | .fext = "css", 22 | .mime_type = "text/css", 23 | }, 24 | { 25 | .fext = "jpg", 26 | .mime_type = "image/jpeg", 27 | }, 28 | { 29 | .fext = "jpeg", 30 | .mime_type = "image/jpeg", 31 | }, 32 | { 33 | .fext = "png", 34 | .mime_type = "image/png", 35 | }, 36 | { 37 | .fext = "json", 38 | .mime_type = "application/json", 39 | }, 40 | { 41 | .fext = MIME_FEXT_GZIP, 42 | .mime_type = "application/gzip", 43 | }, 44 | { 45 | .fext = "ico", 46 | .mime_type = "image/vnd.microsoft.icon", 47 | }, 48 | }; 49 | 50 | const char* mime_get_type_from_filename(char* path) { 51 | size_t i; 52 | const char* fext = futil_get_fext(path); 53 | if(!fext) { 54 | return NULL; 55 | } 56 | 57 | for(i = 0; i < ARRAY_SIZE(mimedb); i++) { 58 | const struct mime_pair* mime = &mimedb[i]; 59 | if(!strcmp(mime->fext, fext)) { 60 | return mime->mime_type; 61 | } 62 | } 63 | return NULL; 64 | } 65 | -------------------------------------------------------------------------------- /main/mime.h: -------------------------------------------------------------------------------- 1 | #ifndef _MIME_H_ 2 | #define _MIME_H_ 3 | 4 | #define MIME_FEXT_GZIP "gz" 5 | 6 | struct mime_pair { 7 | const char* fext; 8 | const char* mime_type; 9 | }; 10 | 11 | const char* mime_get_type_from_filename(char* path); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /main/power_path.c: -------------------------------------------------------------------------------- 1 | #include "power_path.h" 2 | 3 | #include <driver/gpio.h> 4 | #include <esp_err.h> 5 | #include <esp_log.h> 6 | 7 | #include "battery_gauge.h" 8 | #include "bq24715_charger.h" 9 | #include "event_bus.h" 10 | #include "ina219.h" 11 | #include "lm75.h" 12 | #include "scheduler.h" 13 | #include "settings.h" 14 | #include "util.h" 15 | 16 | #define GPIO_DCOK 34 17 | 18 | #define GPIO_VSEL0 35 19 | #define GPIO_VSEL1 36 20 | 21 | #define POWER_UPDATE_INTERVAL_MS 1000 22 | 23 | #define BATTERY_CHARGE_VOLTAGE_MV 8400 24 | #define BATTERY_NOMINAL_VOLTAGE_MV 7400 25 | #define DEFAULT_CHARGE_CURRENT_MA 128 26 | #define MAX_CHARGE_CURRENT_MA 1024 27 | #define MAX_INPUT_CURRENT_MA 4000 28 | 29 | typedef enum ina_type { 30 | INA_TYPE_DC_IN = 0, 31 | INA_TYPE_DC_OUT_PASSTHROUGH = 1, 32 | INA_TYPE_DC_OUT_STEP_UP = 2, 33 | INA_TYPE_USB_OUT = 3 34 | } ina_type_t; 35 | 36 | typedef struct ina_def { 37 | const char *name; 38 | unsigned int address; 39 | unsigned int shunt_resistance_mohms; 40 | } ina_def_t; 41 | 42 | typedef struct ina_state { 43 | ina219_t ina; 44 | unsigned int voltage_mv; 45 | long current_ua; 46 | } ina_state_t; 47 | 48 | typedef struct lm75_def { 49 | const char *name; 50 | unsigned int address; 51 | } lm75_def_t; 52 | 53 | typedef struct lm75_state { 54 | lm75_t lm75; 55 | int32_t temperature_mdegc; 56 | } lm75_state_t; 57 | 58 | static const char *TAG = "power_path"; 59 | 60 | static const ina_def_t ina_defs[] = { 61 | { "ina_dc_in", 0x40, 10 }, 62 | { "ina_dc_out_passthrough", 0x41, 10 }, 63 | { "ina_dc_out_step_up", 0x42, 10 }, 64 | { "ina_usb_out", 0x43, 10 }, 65 | }; 66 | 67 | static ina_state_t inas[ARRAY_SIZE(ina_defs)] = { 0 }; 68 | 69 | static const lm75_def_t lm75_defs[] = { 70 | { "lm75_charger", 0x48 }, 71 | { "lm75_dc_out", 0x49 }, 72 | { "lm75_usb_out", 0x4a }, 73 | }; 74 | 75 | static lm75_state_t lm75s[ARRAY_SIZE(lm75_defs)] = { 0 }; 76 | 77 | static scheduler_task_t power_path_update_task; 78 | 79 | static unsigned int input_current_limit_ma = 0; 80 | 81 | static bq24715_t bq24715; 82 | 83 | static unsigned long output_power_mw = 0; 84 | static bool running_on_battery = false; 85 | 86 | static power_path_group_data_t group_data[POWER_PATH_GROUP_MAX_ + 1] = { 0 }; 87 | 88 | static const unsigned int dc_output_voltage_table[] = { 89 | 9000, 90 | 12000, 91 | 12500, 92 | 15000 93 | }; 94 | 95 | static bool dc_output_enable_table[] = { 96 | true, 97 | true, 98 | true 99 | }; 100 | 101 | static void update_ina(ina_state_t *ina_state) { 102 | ina219_t *ina = &ina_state->ina; 103 | 104 | ina219_read_bus_voltage_mv(ina, &ina_state->voltage_mv); 105 | ina219_read_current_ua(ina, &ina_state->current_ua); 106 | } 107 | 108 | static long long calculate_output_power_uw(void) { 109 | int i; 110 | long long output_power_uw = 0; 111 | ina_type_t types_out[] = { 112 | INA_TYPE_DC_OUT_PASSTHROUGH, 113 | INA_TYPE_DC_OUT_STEP_UP, 114 | INA_TYPE_USB_OUT 115 | }; 116 | 117 | for (i = 0; i < ARRAY_SIZE(types_out); i++) { 118 | ina_state_t *ina_state = &inas[types_out[i]]; 119 | long long power_uw = 120 | DIV_ROUND((long long)ina_state->voltage_mv * (long long)ina_state->current_ua, 1000); 121 | output_power_uw += power_uw; 122 | } 123 | 124 | return output_power_uw; 125 | } 126 | 127 | static long long calculate_max_input_power_uw(void) { 128 | ina_state_t *ina_dc_in_state = &inas[INA_TYPE_DC_IN]; 129 | long long max_power_uw = ina_dc_in_state->voltage_mv * input_current_limit_ma; 130 | 131 | return max_power_uw; 132 | } 133 | 134 | static void update_charge_current(void) { 135 | long long current_output_power_uw = calculate_output_power_uw(); 136 | long long max_input_power_uw = calculate_max_input_power_uw(); 137 | unsigned int charge_current_setpoint_ma = 0; 138 | long predicted_discharge_current_ma = current_output_power_uw / BATTERY_NOMINAL_VOLTAGE_MV; 139 | 140 | battery_gauge_set_at_rate(-predicted_discharge_current_ma); 141 | 142 | output_power_mw = DIV_ROUND(current_output_power_uw, 1000); 143 | 144 | ESP_LOGI(TAG, "Output power: %ldmW", (long)DIV_ROUND(current_output_power_uw, 1000)); 145 | ESP_LOGI(TAG, "Max input power: %ldmW", (long)DIV_ROUND(max_input_power_uw, 1000)); 146 | if (current_output_power_uw < max_input_power_uw) { 147 | long long charging_power_uw = max_input_power_uw - current_output_power_uw; 148 | long charging_current_ma = charging_power_uw / BATTERY_CHARGE_VOLTAGE_MV; 149 | 150 | ESP_LOGI(TAG, "Charging power budget: %ldmW", (long)DIV_ROUND(charging_power_uw, 1000)); 151 | 152 | if (charging_current_ma > MAX_CHARGE_CURRENT_MA) { 153 | charging_current_ma = MAX_CHARGE_CURRENT_MA; 154 | } 155 | charge_current_setpoint_ma = charging_current_ma; 156 | } 157 | ESP_LOGI(TAG, "Charge current setpoint: %umA", charge_current_setpoint_ma); 158 | 159 | bq24715_set_charge_current(&bq24715, charge_current_setpoint_ma); 160 | bq24715_set_input_current(&bq24715, input_current_limit_ma); 161 | } 162 | 163 | static void power_path_group_add_ina_data_(power_path_group_data_t *data, const ina_state_t *ina) { 164 | unsigned long current_ma = DIV_ROUND(ina->current_ua, 1000); 165 | unsigned long power_uw = ina->voltage_mv * current_ma; 166 | unsigned long power_mw = DIV_ROUND(power_uw, 1000); 167 | 168 | data->voltage_mv = ina->voltage_mv; 169 | data->current_ma += current_ma; 170 | data->power_mw += power_mw; 171 | } 172 | 173 | static void power_path_group_set_ina_data(power_path_group_data_t *data, const ina_state_t *ina, const ina_state_t *ina2) { 174 | power_path_group_data_t data_tmp = { 0 }; 175 | 176 | if (ina2) { 177 | power_path_group_add_ina_data_(&data_tmp, ina2); 178 | } 179 | power_path_group_add_ina_data_(&data_tmp, ina); 180 | 181 | *data = data_tmp; 182 | } 183 | 184 | static void power_path_update_group_data(void) { 185 | int i; 186 | 187 | for (i = 0; i < ARRAY_SIZE(ina_defs); i++) { 188 | ina_state_t *ina_state = &inas[i]; 189 | const ina_def_t *ina_def = &ina_defs[i]; 190 | 191 | update_ina(ina_state); 192 | } 193 | 194 | power_path_group_set_ina_data(&group_data[POWER_PATH_GROUP_IN], 195 | &inas[INA_TYPE_DC_IN], NULL); 196 | power_path_group_set_ina_data(&group_data[POWER_PATH_GROUP_DC], 197 | &inas[INA_TYPE_DC_OUT_PASSTHROUGH], 198 | &inas[INA_TYPE_DC_OUT_STEP_UP]); 199 | power_path_group_set_ina_data(&group_data[POWER_PATH_GROUP_USB], 200 | &inas[INA_TYPE_USB_OUT], NULL); 201 | 202 | for (i = 0; i < ARRAY_SIZE(lm75_defs); i++) { 203 | lm75_state_t *lm75_state = &lm75s[i]; 204 | const lm75_def_t *lm75_def = &lm75_defs[i]; 205 | 206 | lm75_read_temperature_mdegc(&lm75_state->lm75, &group_data[i].temperature_mdegc); 207 | } 208 | 209 | } 210 | 211 | static void power_path_update_cb(void *ctx); 212 | static void power_path_update_cb(void *ctx) { 213 | bool on_battery = power_path_is_running_on_battery(); 214 | 215 | power_path_update_group_data(); 216 | 217 | update_charge_current(); 218 | 219 | if (running_on_battery != on_battery) { 220 | running_on_battery = on_battery; 221 | event_bus_notify("power_source", NULL); 222 | } 223 | 224 | event_bus_notify("power_path", NULL); 225 | scheduler_schedule_task_relative(&power_path_update_task, power_path_update_cb, NULL, MS_TO_US(POWER_UPDATE_INTERVAL_MS)); 226 | } 227 | 228 | static void power_path_set_input_current_limit_(unsigned int current_ma) { 229 | if (current_ma > MAX_INPUT_CURRENT_MA) { 230 | current_ma = MAX_INPUT_CURRENT_MA; 231 | } 232 | input_current_limit_ma = current_ma; 233 | } 234 | 235 | void power_path_early_init(smbus_t *smbus, i2c_bus_t *i2c_bus) { 236 | ESP_ERROR_CHECK(bq24715_init(&bq24715, smbus)); 237 | ESP_ERROR_CHECK(bq24715_set_max_charge_voltage(&bq24715, BATTERY_CHARGE_VOLTAGE_MV)); 238 | ESP_ERROR_CHECK(bq24715_set_charge_current(&bq24715, DEFAULT_CHARGE_CURRENT_MA)); 239 | } 240 | 241 | void power_path_init(smbus_t *smbus, i2c_bus_t *i2c_bus) { 242 | int i; 243 | 244 | for (i = 0; i < ARRAY_SIZE(ina_defs); i++) { 245 | ina219_t *ina = &inas[i].ina; 246 | const ina_def_t *ina_def = &ina_defs[i]; 247 | 248 | ESP_ERROR_CHECK(ina219_init(ina, smbus, ina_def->address, ina_def->shunt_resistance_mohms, ina_def->name)); 249 | ESP_ERROR_CHECK(ina219_set_shunt_voltage_range(ina, INA219_PGA_CURRENT_GAIN_80MV)); 250 | } 251 | 252 | for (i = 0; i < ARRAY_SIZE(lm75_defs); i++) { 253 | lm75_init(&lm75s[i].lm75, i2c_bus, lm75_defs[i].address, lm75_defs[i].name); 254 | } 255 | 256 | power_path_set_input_current_limit_(settings_get_input_current_limit_ma()); 257 | 258 | scheduler_task_init(&power_path_update_task); 259 | scheduler_schedule_task_relative(&power_path_update_task, power_path_update_cb, NULL, 0); 260 | } 261 | 262 | void power_path_set_input_current_limit(unsigned int current_ma) { 263 | power_path_set_input_current_limit_(current_ma); 264 | settings_set_input_current_limit_ma(current_ma); 265 | } 266 | 267 | unsigned int power_path_get_input_current_limit_ma(void) { 268 | return input_current_limit_ma; 269 | } 270 | 271 | 272 | bool power_path_is_running_on_battery() { 273 | gpio_set_direction(GPIO_DCOK, GPIO_MODE_INPUT); 274 | return !gpio_get_level(GPIO_DCOK); 275 | } 276 | 277 | unsigned int power_path_get_dc_output_voltage_mv() { 278 | gpio_set_direction(GPIO_VSEL0, GPIO_MODE_INPUT); 279 | gpio_set_direction(GPIO_VSEL1, GPIO_MODE_INPUT); 280 | 281 | unsigned int lookup_idx = 282 | (gpio_get_level(GPIO_VSEL0) ? 1 : 0) | 283 | ((gpio_get_level(GPIO_VSEL1) ? 1 : 0) << 1); 284 | return dc_output_voltage_table[lookup_idx]; 285 | } 286 | 287 | bool power_path_is_dc_output_enabled(unsigned int output_idx) { 288 | return dc_output_enable_table[output_idx]; 289 | } 290 | 291 | unsigned long power_path_get_output_power_consumption_mw() { 292 | return output_power_mw; 293 | } 294 | 295 | void power_path_get_group_data(power_path_group_t group, power_path_group_data_t *data) { 296 | *data = group_data[group]; 297 | } 298 | -------------------------------------------------------------------------------- /main/power_path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stdbool.h> 4 | #include <stdint.h> 5 | 6 | #include "i2c_bus.h" 7 | #include "smbus.h" 8 | 9 | typedef enum power_path_group { 10 | POWER_PATH_GROUP_IN, 11 | POWER_PATH_GROUP_DC, 12 | POWER_PATH_GROUP_USB, 13 | POWER_PATH_GROUP_MAX_ = POWER_PATH_GROUP_USB 14 | } power_path_group_t; 15 | 16 | typedef struct power_path_group_data { 17 | unsigned int voltage_mv; 18 | int current_ma; 19 | long power_mw; 20 | int32_t temperature_mdegc; 21 | } power_path_group_data_t; 22 | 23 | void power_path_early_init(smbus_t *smbus, i2c_bus_t *i2c_bus); 24 | void power_path_init(smbus_t *smbus, i2c_bus_t *i2c_bus); 25 | void power_path_set_input_current_limit(unsigned int current_ma); 26 | unsigned int power_path_get_input_current_limit_ma(void); 27 | bool power_path_is_running_on_battery(void); 28 | unsigned int power_path_get_dc_output_voltage_mv(void); 29 | bool power_path_is_dc_output_enabled(unsigned int output_idx); 30 | unsigned long power_path_get_output_power_consumption_mw(void); 31 | void power_path_get_group_data(power_path_group_t group, power_path_group_data_t *data); 32 | -------------------------------------------------------------------------------- /main/prometheus.c: -------------------------------------------------------------------------------- 1 | #include "prometheus.h" 2 | 3 | void prometheus_metric_init(prometheus_metric_t *metric, const prometheus_metric_def_t *def, void *priv) { 4 | INIT_LIST_HEAD(metric->list); 5 | metric->def = def; 6 | metric->priv = priv; 7 | } 8 | 9 | void prometheus_init(prometheus_t *prom) { 10 | INIT_LIST_HEAD(prom->metrics); 11 | } 12 | 13 | void prometheus_add_metric(prometheus_t *prom, prometheus_metric_t *metric) { 14 | LIST_APPEND_TAIL(&metric->list, &prom->metrics); 15 | } 16 | -------------------------------------------------------------------------------- /main/prometheus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "list.h" 4 | 5 | #define PROMETHEUS_LABEL_MAX_LEN 64 6 | #define PROMETHEUS_VALUE_MAX_LEN 64 7 | 8 | typedef enum { 9 | PROMETHEUS_METRIC_TYPE_COUNTER, 10 | PROMETHEUS_METRIC_TYPE_GAUGE, 11 | PROMETHEUS_METRIC_TYPE_UNTYPED, 12 | } prometheus_metric_type_t; 13 | 14 | typedef struct prometheus_metric prometheus_metric_t; 15 | typedef struct prometheus_metric_value prometheus_metric_value_t; 16 | 17 | typedef struct prometheus_label { 18 | const char *name; 19 | const char *value; 20 | } prometheus_label_t; 21 | 22 | struct prometheus_metric_value { 23 | /* static labels */ 24 | unsigned int num_labels; 25 | const prometheus_label_t *labels; 26 | /* dynamic labels */ 27 | unsigned int (*get_num_labels)(const prometheus_metric_value_t *val, prometheus_metric_t *metric); 28 | void (*get_label)(const prometheus_metric_value_t *val, prometheus_metric_t *metric, unsigned int index, char *label, char *value); 29 | void (*get_value)(const prometheus_metric_value_t *val, prometheus_metric_t *metric, char *value); 30 | /* dynamic metric values */ 31 | void *priv; 32 | }; 33 | 34 | typedef struct prometheus_metric_def { 35 | const char *name; 36 | const char *help; 37 | prometheus_metric_type_t type; 38 | 39 | /* static values */ 40 | const prometheus_metric_value_t *values; 41 | unsigned int num_values; 42 | 43 | /* dynamic values */ 44 | unsigned int (*get_num_values)(prometheus_metric_t *metric); 45 | void (*get_value)(prometheus_metric_t *metric, unsigned int index, prometheus_metric_value_t *value); 46 | } prometheus_metric_def_t; 47 | 48 | struct prometheus_metric { 49 | list_head_t list; 50 | const prometheus_metric_def_t *def; 51 | void *priv; 52 | }; 53 | 54 | typedef struct prometheus { 55 | list_head_t metrics; 56 | } prometheus_t; 57 | 58 | void prometheus_metric_init(prometheus_metric_t *metric, const prometheus_metric_def_t *def, void *priv); 59 | void prometheus_init(prometheus_t *prom); 60 | void prometheus_add_metric(prometheus_t *prom, prometheus_metric_t *metric); 61 | -------------------------------------------------------------------------------- /main/prometheus_exporter.c: -------------------------------------------------------------------------------- 1 | #include "prometheus_exporter.h" 2 | 3 | #include <esp_log.h> 4 | 5 | const static char *TAG = "prometheus_exporter"; 6 | 7 | static void handle_labels(const prometheus_metric_value_t *value, prometheus_metric_t *metric, struct httpd_request_ctx* ctx) { 8 | unsigned int num_static_labels = value->num_labels; 9 | unsigned int num_dynamic_labels = 0; 10 | if (value->get_num_labels) { 11 | num_dynamic_labels = value->get_num_labels(value, metric); 12 | } 13 | unsigned int num_labels = num_static_labels + num_dynamic_labels; 14 | if (!num_labels) { 15 | return; 16 | } 17 | 18 | httpd_response_write_string(ctx, "{"); 19 | for (unsigned int i = 0; i < num_static_labels; i++) { 20 | const prometheus_label_t *label; 21 | 22 | label = &value->labels[i]; 23 | httpd_response_write_string(ctx, label->name); 24 | httpd_response_write_string(ctx, "=\""); 25 | httpd_response_write_string(ctx, label->value); 26 | httpd_response_write_string(ctx, "\""); 27 | if (i < num_static_labels - 1 || num_dynamic_labels) { 28 | httpd_response_write_string(ctx, ","); 29 | } 30 | } 31 | for (unsigned int i = 0; i < num_dynamic_labels; i++) { 32 | char name[PROMETHEUS_LABEL_MAX_LEN] = { 0 }; 33 | char label[PROMETHEUS_LABEL_MAX_LEN] = { 0 }; 34 | 35 | value->get_label(value, metric, i, name, label); 36 | httpd_response_write_string(ctx, name); 37 | httpd_response_write_string(ctx, "=\""); 38 | httpd_response_write_string(ctx, label); 39 | httpd_response_write_string(ctx, "\""); 40 | if (i < num_dynamic_labels - 1) { 41 | httpd_response_write_string(ctx, ","); 42 | } 43 | } 44 | httpd_response_write_string(ctx, "} "); 45 | } 46 | 47 | static void handle_value(const prometheus_metric_value_t *value, prometheus_metric_t *metric, struct httpd_request_ctx* ctx) { 48 | const prometheus_metric_def_t *def = metric->def; 49 | httpd_response_write_string(ctx, def->name); 50 | httpd_response_write_string(ctx, " "); 51 | if (value->num_labels || value->get_num_labels) { 52 | handle_labels(value, metric, ctx); 53 | } 54 | char value_str[PROMETHEUS_VALUE_MAX_LEN] = { 0 }; 55 | value->get_value(value, metric, value_str); 56 | httpd_response_write_string(ctx, value_str); 57 | httpd_response_write_string(ctx, "\n"); 58 | } 59 | 60 | static esp_err_t handle_metric(prometheus_metric_t *metric, struct httpd_request_ctx* ctx) { 61 | const prometheus_metric_def_t *def = metric->def; 62 | if (!def->name) { 63 | ESP_LOGE(TAG, "Missing name on metric"); 64 | return ESP_ERR_INVALID_ARG; 65 | } 66 | 67 | if (def->help) { 68 | httpd_response_write_string(ctx, "# HELP "); 69 | httpd_response_write_string(ctx, def->name); 70 | httpd_response_write_string(ctx, " "); 71 | httpd_response_write_string(ctx, def->help); 72 | httpd_response_write_string(ctx, "\n"); 73 | } 74 | 75 | if (def->type != PROMETHEUS_METRIC_TYPE_UNTYPED) { 76 | const char *type_name; 77 | switch (def->type) { 78 | case PROMETHEUS_METRIC_TYPE_COUNTER: 79 | type_name = "counter"; 80 | break; 81 | case PROMETHEUS_METRIC_TYPE_GAUGE: 82 | type_name = "gauge"; 83 | break; 84 | default: 85 | ESP_LOGE(TAG, "Invalid type for metric %s", def->name); 86 | return ESP_ERR_INVALID_ARG; 87 | } 88 | httpd_response_write_string(ctx, "# TYPE "); 89 | httpd_response_write_string(ctx, def->name); 90 | httpd_response_write_string(ctx, " "); 91 | httpd_response_write_string(ctx, type_name); 92 | httpd_response_write_string(ctx, "\n"); 93 | } 94 | 95 | for (unsigned int i = 0; i < def->num_values; i++) { 96 | handle_value(&def->values[i], metric, ctx); 97 | } 98 | if (def->get_num_values) { 99 | unsigned int num_values = def->get_num_values(metric); 100 | for (unsigned int i = 0; i < num_values; i++) { 101 | prometheus_metric_value_t value; 102 | memset(&value, 0, sizeof(value)); 103 | def->get_value(metric, i, &value); 104 | handle_value(&value, metric, ctx); 105 | } 106 | } 107 | return ESP_OK; 108 | } 109 | 110 | static esp_err_t request_handler(struct httpd_request_ctx* ctx, void* priv) { 111 | prometheus_t *prometheus = priv; 112 | 113 | prometheus_metric_t *metric; 114 | LIST_FOR_EACH_ENTRY(metric, &prometheus->metrics, list) { 115 | esp_err_t err = handle_metric(metric, ctx); 116 | if (err) { 117 | ESP_LOGE(TAG, "Failed to handle metric"); 118 | httpd_send_error(ctx, HTTPD_500); 119 | return err; 120 | } 121 | httpd_response_write_string(ctx, "\n"); 122 | } 123 | 124 | httpd_finalize_response(ctx); 125 | return ESP_OK; 126 | } 127 | 128 | esp_err_t prometheus_register_exporter(prometheus_t *prometheus, httpd_t *httpd, char *path) { 129 | return httpd_add_get_handler(httpd, path, request_handler, prometheus, false, 0); 130 | } 131 | -------------------------------------------------------------------------------- /main/prometheus_exporter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <esp_err.h> 4 | 5 | #include "httpd.h" 6 | #include "prometheus.h" 7 | 8 | esp_err_t prometheus_register_exporter(prometheus_t *prometheus, httpd_t *httpd, char *path); 9 | -------------------------------------------------------------------------------- /main/prometheus_metrics.c: -------------------------------------------------------------------------------- 1 | #include "prometheus_metrics.h" 2 | 3 | #include <stddef.h> 4 | #include <stdio.h> 5 | #include <string.h> 6 | 7 | #include "util.h" 8 | 9 | static void test_metric_get_value(const prometheus_metric_value_t *val, prometheus_metric_t *metric, char *value) { 10 | strcpy(value, "42"); 11 | } 12 | 13 | static const prometheus_metric_value_t test_metric_value = { 14 | .num_labels = 0, 15 | .get_num_labels = NULL, 16 | .get_value = test_metric_get_value, 17 | }; 18 | 19 | const prometheus_metric_def_t test_metric_def = { 20 | .name = "test_metric", 21 | .help = "A simple test metric with a single value and no labels", 22 | .type = PROMETHEUS_METRIC_TYPE_GAUGE, 23 | .values = &test_metric_value, 24 | .num_values = 1, 25 | .get_num_values = NULL, 26 | }; 27 | 28 | 29 | 30 | static unsigned int num_metric_retrievals = 0; 31 | 32 | static void complex_test_metric_get_unlabeled_value(const prometheus_metric_value_t *val, prometheus_metric_t *metric, char *value) { 33 | sprintf(value, "%u", ++num_metric_retrievals); 34 | } 35 | 36 | extern const prometheus_metric_value_t complex_test_metric_values[]; 37 | 38 | static unsigned int complex_test_metric_get_num_labels(const prometheus_metric_value_t *val, prometheus_metric_t *metric) { 39 | return (unsigned int)(val - complex_test_metric_values); 40 | } 41 | 42 | static void complex_test_metric_get_label(const prometheus_metric_value_t *val, prometheus_metric_t *metric, unsigned int index, char *label, char *value) { 43 | unsigned int num_labels = (unsigned int)(val - complex_test_metric_values); 44 | if (index == num_labels - 1) { 45 | strcpy(label, "multiplier"); 46 | sprintf(value, "%u", index + 2); 47 | } else { 48 | sprintf(label, "label%u", index + 1); 49 | sprintf(value, "value%u", index + 1); 50 | } 51 | } 52 | 53 | static void complex_test_metric_get_labeled_value(const prometheus_metric_value_t *val, prometheus_metric_t *metric, char *value) { 54 | unsigned int metric_index = (unsigned int)(val - complex_test_metric_values); 55 | sprintf(value, "%u", num_metric_retrievals * (metric_index + 1)); 56 | } 57 | 58 | const prometheus_label_t complex_test_metric_labels[] = { 59 | { 60 | .name = "static_label1", 61 | .value = "static_value1", 62 | }, { 63 | .name = "static_label2", 64 | .value = "static_value2", 65 | }, 66 | }; 67 | 68 | const prometheus_metric_value_t complex_test_metric_values[] = { 69 | { 70 | .num_labels = 0, 71 | .get_num_labels = NULL, 72 | .get_value = complex_test_metric_get_unlabeled_value, 73 | }, 74 | { 75 | .labels = complex_test_metric_labels, 76 | .num_labels = ARRAY_SIZE(complex_test_metric_labels), 77 | .get_num_labels = complex_test_metric_get_num_labels, 78 | .get_label = complex_test_metric_get_label, 79 | .get_value = complex_test_metric_get_labeled_value, 80 | }, 81 | { 82 | .num_labels = 0, 83 | .get_num_labels = complex_test_metric_get_num_labels, 84 | .get_label = complex_test_metric_get_label, 85 | .get_value = complex_test_metric_get_labeled_value, 86 | } 87 | }; 88 | 89 | const prometheus_metric_def_t complex_test_metric_def = { 90 | .name = "counter_metric", 91 | .help = "A counter metric with multiple value and labels", 92 | .type = PROMETHEUS_METRIC_TYPE_COUNTER, 93 | .values = complex_test_metric_values, 94 | .num_values = ARRAY_SIZE(complex_test_metric_values), 95 | .get_num_values = NULL, 96 | }; -------------------------------------------------------------------------------- /main/prometheus_metrics.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "prometheus.h" 4 | 5 | const prometheus_metric_def_t test_metric_def; 6 | const prometheus_metric_def_t complex_test_metric_def; 7 | -------------------------------------------------------------------------------- /main/prometheus_metrics_battery.c: -------------------------------------------------------------------------------- 1 | #include "prometheus_metrics_battery.h" 2 | 3 | #include <stddef.h> 4 | #include <stdio.h> 5 | #include <string.h> 6 | 7 | #include "util.h" 8 | 9 | static void get_state_of_charge(const prometheus_metric_value_t *val, prometheus_metric_t *metric, char *value) { 10 | bq40z50_t *gauge = metric->priv; 11 | unsigned int soc = 0; 12 | bq40z50_get_state_of_charge_percent(gauge, &soc); 13 | sprintf(value, "%u", soc); 14 | } 15 | 16 | static const prometheus_metric_value_t battery_state_of_charge_value = { 17 | .num_labels = 0, 18 | .get_num_labels = NULL, 19 | .get_value = get_state_of_charge, 20 | }; 21 | 22 | static const prometheus_metric_def_t battery_state_of_charge_metric_def = { 23 | .name = "battery_state_of_charge", 24 | .help = "UPS battery state of charge in percent", 25 | .type = PROMETHEUS_METRIC_TYPE_GAUGE, 26 | .values = &battery_state_of_charge_value, 27 | .num_values = 1, 28 | .get_num_values = NULL, 29 | }; 30 | 31 | static void get_state_of_health(const prometheus_metric_value_t *val, prometheus_metric_t *metric, char *value) { 32 | bq40z50_t *gauge = metric->priv; 33 | unsigned int soh = 0; 34 | bq40z50_get_state_of_health_percent(gauge, &soh); 35 | sprintf(value, "%u", soh); 36 | } 37 | 38 | static const prometheus_metric_value_t battery_state_of_health_value = { 39 | .num_labels = 0, 40 | .get_num_labels = NULL, 41 | .get_value = get_state_of_health, 42 | }; 43 | 44 | static const prometheus_metric_def_t battery_state_of_health_metric_def = { 45 | .name = "battery_state_of_health", 46 | .help = "UPS battery state of health in percent", 47 | .type = PROMETHEUS_METRIC_TYPE_GAUGE, 48 | .values = &battery_state_of_health_value, 49 | .num_values = 1, 50 | .get_num_values = NULL, 51 | }; 52 | 53 | static void get_full_charge_capacity(const prometheus_metric_value_t *val, prometheus_metric_t *metric, char *value) { 54 | bq40z50_t *gauge = metric->priv; 55 | unsigned int fcc = 0; 56 | bq40z50_get_full_charge_capacity_mah(gauge, &fcc); 57 | sprintf(value, "%f", fcc / 1000.f); 58 | } 59 | 60 | static const prometheus_metric_value_t battery_full_charge_capacity_value = { 61 | .num_labels = 0, 62 | .get_num_labels = NULL, 63 | .get_value = get_full_charge_capacity, 64 | }; 65 | 66 | static const prometheus_metric_def_t battery_full_charge_capacity_metric_def = { 67 | .name = "battery_full_charge_capacity", 68 | .help = "UPS battery full charge capacity in Ah", 69 | .type = PROMETHEUS_METRIC_TYPE_GAUGE, 70 | .values = &battery_full_charge_capacity_value, 71 | .num_values = 1, 72 | .get_num_values = NULL, 73 | }; 74 | 75 | void prometheus_battery_metrics_init(prometheus_battery_metrics_t *metrics, bq40z50_t *gauge) { 76 | prometheus_metric_init(&metrics->state_of_charge, &battery_state_of_charge_metric_def, gauge); 77 | prometheus_metric_init(&metrics->state_of_health, &battery_state_of_health_metric_def, gauge); 78 | prometheus_metric_init(&metrics->full_charge_capacity, &battery_full_charge_capacity_metric_def, gauge); 79 | } 80 | 81 | void prometheus_add_battery_metrics(prometheus_battery_metrics_t *metrics, prometheus_t *prometheus) { 82 | prometheus_add_metric(prometheus, &metrics->state_of_charge); 83 | prometheus_add_metric(prometheus, &metrics->state_of_health); 84 | prometheus_add_metric(prometheus, &metrics->full_charge_capacity); 85 | } 86 | -------------------------------------------------------------------------------- /main/prometheus_metrics_battery.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "bq40z50_gauge.h" 4 | #include "prometheus.h" 5 | 6 | typedef struct prometheus_battery_metrics { 7 | prometheus_metric_t state_of_charge; 8 | prometheus_metric_t state_of_health; 9 | prometheus_metric_t full_charge_capacity; 10 | } prometheus_battery_metrics_t; 11 | 12 | void prometheus_battery_metrics_init(prometheus_battery_metrics_t *metrics, bq40z50_t *gauge); 13 | void prometheus_add_battery_metrics(prometheus_battery_metrics_t *metrics, prometheus_t *prometheus); 14 | -------------------------------------------------------------------------------- /main/ring.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <stdint.h> 4 | #include <string.h> 5 | #include <errno.h> 6 | #include <assert.h> 7 | #include <stdbool.h> 8 | 9 | #include "ring.h" 10 | 11 | int ring_alloc(struct ring** ret, size_t size) { 12 | int err; 13 | 14 | struct ring* ring = malloc(sizeof(struct ring)); 15 | if(!ring) { 16 | err = -ENOMEM; 17 | goto fail; 18 | } 19 | 20 | ring->data = malloc(size); 21 | if(!ring->data) { 22 | err = -ENOMEM; 23 | goto fail_ring; 24 | } 25 | ring->ptr_read = ring->data; 26 | ring->ptr_write = ring->data; 27 | ring->size = size; 28 | 29 | *ret = ring; 30 | 31 | return 0; 32 | 33 | fail_ring: 34 | free(ring); 35 | fail: 36 | return err; 37 | } 38 | 39 | void ring_free(struct ring* ring) { 40 | free(ring->data); 41 | free(ring); 42 | } 43 | 44 | 45 | // Number of bytes that can be read from ringbuffer 46 | size_t ring_available(struct ring* ring) { 47 | if(ring->ptr_write >= ring->ptr_read) { 48 | return ring->ptr_write - ring->ptr_read; 49 | } 50 | return ring->size - (ring->ptr_read - ring->ptr_write); 51 | } 52 | 53 | // Number of virtually contiguous bytes that can be read from ringbuffer 54 | size_t ring_available_contig(struct ring* ring) { 55 | if(ring->ptr_write >= ring->ptr_read) { 56 | return ring->ptr_write - ring->ptr_read; 57 | } 58 | return ring->size - (ring->ptr_read - ring->data); 59 | } 60 | 61 | // Number of free bytes 62 | size_t ring_free_space(struct ring* ring) { 63 | if(ring->ptr_read > ring->ptr_write) { 64 | return ring->ptr_read - ring->ptr_write - 1; 65 | } 66 | return ring->size - (ring->ptr_write - ring->ptr_read) - 1; 67 | } 68 | 69 | // Number of contiguous free bytes after ring->write_ptr 70 | size_t ring_free_space_contig(struct ring* ring) { 71 | if(ring->ptr_read > ring->ptr_write) { 72 | return ring->ptr_read - ring->ptr_write - 1; 73 | } 74 | return ring->size - (ring->ptr_write - ring->data) - (ring->ptr_read == ring->data ? 1 : 0); 75 | } 76 | 77 | // Take a peek into the ring buffer 78 | int ring_peek(struct ring* ring, char* data, size_t len) { 79 | size_t avail_contig; 80 | 81 | if(ring_available(ring) < len) { 82 | return -EINVAL; 83 | } 84 | 85 | avail_contig = ring_available_contig(ring); 86 | 87 | if(avail_contig >= len) { 88 | memcpy(data, ring->ptr_read, len); 89 | } else { 90 | memcpy(data, ring->ptr_read, avail_contig); 91 | memcpy(data + avail_contig, ring->data, len - avail_contig); 92 | } 93 | 94 | return 0; 95 | } 96 | 97 | // Read from this ring buffer 98 | int ring_read(struct ring* ring, char* data, size_t len) { 99 | size_t avail_contig; 100 | 101 | if(ring_available(ring) < len) { 102 | return -EINVAL; 103 | } 104 | 105 | avail_contig = ring_available_contig(ring); 106 | 107 | if(avail_contig >= len) { 108 | memcpy(data, ring->ptr_read, len); 109 | ring->ptr_read = ring_next(ring, ring->ptr_read + len - 1); 110 | } else { 111 | memcpy(data, ring->ptr_read, avail_contig); 112 | memcpy(data + avail_contig, ring->data, len - avail_contig); 113 | ring->ptr_read = ring->data + len - avail_contig; 114 | } 115 | 116 | return 0; 117 | } 118 | 119 | // Write to this ring buffer 120 | int ring_write(struct ring* ring, char* data, size_t len) { 121 | size_t free_contig; 122 | 123 | if(ring_free_space(ring) < len) { 124 | return -EINVAL; 125 | } 126 | 127 | free_contig = ring_free_space_contig(ring); 128 | 129 | if(free_contig >= len) { 130 | memcpy(ring->ptr_write, data, len); 131 | ring->ptr_write = ring_next(ring, ring->ptr_write + len - 1); 132 | } else { 133 | memcpy(ring->ptr_write, data, free_contig); 134 | memcpy(ring->data, data + free_contig, len - free_contig); 135 | ring->ptr_write = ring->data + len - free_contig; 136 | } 137 | return 0; 138 | } 139 | 140 | void ring_advance_read(struct ring* ring, off_t offset) { 141 | assert(offset >= 0); 142 | assert(offset <= ring->size); 143 | 144 | if(offset) { 145 | if(ring->ptr_read + offset < ring->data + ring->size) { 146 | ring->ptr_read = ring_next(ring, ring->ptr_read + offset - 1); 147 | } else { 148 | ring->ptr_read = offset - ring->size + ring->ptr_read; 149 | } 150 | } 151 | } 152 | 153 | void ring_advance_write(struct ring* ring, off_t offset) { 154 | assert(offset >= 0); 155 | assert(offset <= ring->size); 156 | 157 | if(offset) { 158 | if(ring->ptr_write + offset < ring->data + ring->size) { 159 | ring->ptr_write = ring_next(ring, ring->ptr_write + offset - 1); 160 | } else { 161 | ring->ptr_write = offset - ring->size + ring->ptr_write; 162 | } 163 | } 164 | } 165 | 166 | /* 167 | Behaves totally different from memcmp! 168 | Return: 169 | < 0 error (not enough data in buffer) 170 | = 0 match 171 | > 0 no match 172 | */ 173 | int ring_memcmp(struct ring* ring, char* ref, unsigned int len, char** next_pos) { 174 | size_t avail_contig; 175 | 176 | if(ring_available(ring) < len) { 177 | return -EINVAL; 178 | } 179 | 180 | avail_contig = ring_available_contig(ring); 181 | 182 | if(avail_contig >= len) { 183 | // We are lucky 184 | if(memcmp(ring->ptr_read, ref, len)) { 185 | return 1; 186 | } 187 | if(next_pos) { 188 | *next_pos = ring_next(ring, ring->ptr_read + len - 1); 189 | } else { 190 | ring_advance_read(ring, len); 191 | } 192 | return 0; 193 | } 194 | 195 | 196 | // We (may) need to perform two memcmps 197 | if(memcmp(ring->ptr_read, ref, avail_contig) || memcmp(ring->data, ref + avail_contig, len - avail_contig)) { 198 | return 1; 199 | } 200 | if(next_pos) { 201 | *next_pos = ring->data + len - avail_contig; 202 | } else { 203 | ring_advance_read(ring, len); 204 | } 205 | return 0; 206 | } 207 | -------------------------------------------------------------------------------- /main/ring.h: -------------------------------------------------------------------------------- 1 | #ifndef _RING_H_ 2 | #define _RING_H_ 3 | 4 | #include <stdbool.h> 5 | 6 | struct ring { 7 | size_t size; 8 | char* data; 9 | char* ptr_read, *ptr_write; 10 | }; 11 | 12 | int ring_alloc(struct ring** ret, size_t size); 13 | void ring_free(struct ring* ring); 14 | 15 | 16 | size_t ring_available(struct ring* ring); 17 | size_t ring_available_contig(struct ring* ring); 18 | size_t ring_free_space(struct ring* ring); 19 | size_t ring_free_space_contig(struct ring* ring); 20 | 21 | int ring_peek(struct ring* ring, char* data, size_t len); 22 | int ring_read(struct ring* ring, char* data, size_t len); 23 | int ring_write(struct ring* ring, char* data, size_t len); 24 | void ring_advance_read(struct ring* ring, off_t offset); 25 | void ring_advance_write(struct ring* ring, off_t offset); 26 | 27 | // Special zero-copy optimizations 28 | int ring_memcmp(struct ring*, char* ref, unsigned int len, char** next_pos); 29 | 30 | // Optimized inline funnctions 31 | inline bool ring_any_available(struct ring* ring) { 32 | return ring->ptr_read != ring->ptr_write; 33 | } 34 | 35 | // Take a small peek into the buffer 36 | inline char ring_peek_one(struct ring* ring) { 37 | return *ring->ptr_read; 38 | } 39 | 40 | // Pointer to next byte to read from ringbuffer 41 | inline char* ring_next(struct ring* ring, char* ptr) { 42 | if(ptr < ring->data + ring->size - 1) { 43 | return ptr + 1; 44 | } 45 | return ring->data; 46 | } 47 | 48 | // Read one byte from the buffer 49 | inline char ring_read_one(struct ring* ring) { 50 | char c = *ring->ptr_read; 51 | ring->ptr_read = ring_next(ring, ring->ptr_read); 52 | return c; 53 | } 54 | 55 | inline void ring_inc_read(struct ring* ring) { 56 | ring->ptr_read = ring_next(ring, ring->ptr_read); 57 | } 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /main/scheduler.c: -------------------------------------------------------------------------------- 1 | #include "scheduler.h" 2 | 3 | #include <freertos/FreeRTOS.h> 4 | #include <freertos/semphr.h> 5 | 6 | #include <esp_err.h> 7 | #include <esp_log.h> 8 | #include <esp_timer.h> 9 | 10 | #define SCHEDULER_TASK_STACK_SIZE 4096 11 | #define SCHEDULER_TASK_STACK_DEPTH (SCHEDULER_TASK_STACK_SIZE / sizeof(StackType_t)) 12 | 13 | typedef struct scheduler { 14 | struct list_head tasks; 15 | TaskHandle_t task; 16 | StackType_t task_stack[SCHEDULER_TASK_STACK_DEPTH]; 17 | StaticTask_t task_buffer; 18 | esp_timer_handle_t timer; 19 | SemaphoreHandle_t lock; 20 | StaticSemaphore_t lock_buffer; 21 | bool timer_running; 22 | int64_t timer_deadline_us; 23 | struct list_head tasks_schedule; 24 | struct list_head tasks_abort; 25 | } scheduler_t; 26 | 27 | static const char *TAG = "scheduler"; 28 | 29 | static scheduler_t scheduler_g; 30 | 31 | void scheduler_timer_cb(void *arg) { 32 | scheduler_t *scheduler = arg; 33 | 34 | scheduler->timer_running = false; 35 | xTaskNotifyGive(scheduler->task); 36 | } 37 | 38 | static void start_timer_for_task(scheduler_task_t *task) { 39 | scheduler_t *scheduler = &scheduler_g; 40 | int64_t now = esp_timer_get_time(); 41 | 42 | scheduler->timer_deadline_us = task->schedule_sync.deadline_us; 43 | scheduler->timer_running = true; 44 | esp_timer_start_once(scheduler->timer, task->schedule_sync.deadline_us > now ? task->schedule_sync.deadline_us - now : 0); 45 | } 46 | 47 | void scheduler_run(void *arg) { 48 | scheduler_t *scheduler = arg; 49 | 50 | while (1) { 51 | uint64_t now; 52 | scheduler_task_t *cursor; 53 | struct list_head *next; 54 | 55 | ulTaskNotifyTake(pdTRUE, portMAX_DELAY); 56 | now = esp_timer_get_time(); 57 | LIST_FOR_EACH_ENTRY_SAFE(cursor, next, &scheduler->tasks, list) { 58 | if (now >= cursor->schedule_sync.deadline_us) { 59 | LIST_DELETE(&cursor->list); 60 | cursor->schedule_sync.cb(cursor->schedule_sync.ctx); 61 | } else { 62 | break; 63 | } 64 | } 65 | 66 | xSemaphoreTakeRecursive(scheduler->lock, portMAX_DELAY); 67 | LIST_FOR_EACH_ENTRY_SAFE(cursor, next, &scheduler->tasks_abort, list_abort) { 68 | LIST_DELETE(&cursor->list); 69 | LIST_DELETE(&cursor->list_abort); 70 | } 71 | LIST_FOR_EACH_ENTRY_SAFE(cursor, next, &scheduler->tasks_schedule, list_schedule) { 72 | scheduler_task_t *cursor_; 73 | struct list_head *prior_deadline = &scheduler->tasks; 74 | 75 | if (!LIST_IS_EMPTY(&cursor->list)) { 76 | LIST_DELETE(&cursor->list); 77 | } 78 | 79 | cursor->schedule_sync = cursor->schedule_async; 80 | LIST_FOR_EACH_ENTRY(cursor_, &scheduler->tasks, list) { 81 | if (cursor_->schedule_sync.deadline_us > cursor->schedule_sync.deadline_us) { 82 | break; 83 | } 84 | prior_deadline = &cursor_->list; 85 | } 86 | LIST_APPEND(&cursor->list, prior_deadline); 87 | LIST_DELETE(&cursor->list_schedule); 88 | } 89 | xSemaphoreGiveRecursive(scheduler->lock); 90 | 91 | if (!LIST_IS_EMPTY(&scheduler->tasks)) { 92 | scheduler_task_t *task = LIST_GET_ENTRY(scheduler->tasks.next, scheduler_task_t, list); 93 | 94 | if (scheduler->timer_deadline_us > task->schedule_sync.deadline_us && scheduler->timer_running) { 95 | esp_timer_stop(scheduler->timer); 96 | scheduler->timer_running = false; 97 | } 98 | if (!scheduler->timer_running) { 99 | start_timer_for_task(task); 100 | } 101 | } 102 | } 103 | } 104 | 105 | void scheduler_init() { 106 | scheduler_t *scheduler = &scheduler_g; 107 | esp_timer_create_args_t timer_args = { 108 | .callback = scheduler_timer_cb, 109 | .arg = scheduler, 110 | .dispatch_method = ESP_TIMER_TASK, 111 | .skip_unhandled_events = true 112 | }; 113 | 114 | INIT_LIST_HEAD(scheduler->tasks); 115 | INIT_LIST_HEAD(scheduler->tasks_abort); 116 | INIT_LIST_HEAD(scheduler->tasks_schedule); 117 | ESP_ERROR_CHECK(esp_timer_create(&timer_args, &scheduler->timer)); 118 | scheduler->lock = xSemaphoreCreateRecursiveMutexStatic(&scheduler->lock_buffer); 119 | scheduler->task = xTaskCreateStatic(scheduler_run, "scheduler", SCHEDULER_TASK_STACK_DEPTH, 120 | scheduler, 1, scheduler->task_stack, &scheduler->task_buffer); 121 | scheduler->timer_running = false; 122 | } 123 | 124 | void scheduler_task_init(scheduler_task_t *task) { 125 | INIT_LIST_HEAD(task->list); 126 | INIT_LIST_HEAD(task->list_schedule); 127 | INIT_LIST_HEAD(task->list_abort); 128 | } 129 | 130 | void scheduler_schedule_task(scheduler_task_t *task, scheduler_cb_f cb, void *ctx, int64_t deadline_us) { 131 | scheduler_t *scheduler = &scheduler_g; 132 | 133 | xSemaphoreTakeRecursive(scheduler->lock, portMAX_DELAY); 134 | task->schedule_async.deadline_us = deadline_us; 135 | task->schedule_async.cb = cb; 136 | task->schedule_async.ctx = ctx; 137 | 138 | LIST_DELETE(&task->list_abort); 139 | if (LIST_IS_EMPTY(&task->list_schedule)) { 140 | LIST_APPEND(&task->list_schedule, &scheduler->tasks_schedule); 141 | } 142 | xTaskNotifyGive(scheduler->task); 143 | xSemaphoreGiveRecursive(scheduler->lock); 144 | } 145 | 146 | void scheduler_schedule_task_relative(scheduler_task_t *task, scheduler_cb_f cb, void *ctx, int64_t timeout_us) { 147 | int64_t now = esp_timer_get_time(); 148 | 149 | scheduler_schedule_task(task, cb, ctx, now + timeout_us); 150 | } 151 | 152 | void scheduler_abort_task(scheduler_task_t *task) { 153 | scheduler_t *scheduler = &scheduler_g; 154 | 155 | xSemaphoreTakeRecursive(scheduler->lock, portMAX_DELAY); 156 | LIST_DELETE(&task->list_schedule); 157 | if (LIST_IS_EMPTY(&task->list_abort)) { 158 | LIST_APPEND(&task->list_abort, &scheduler->tasks_abort); 159 | } 160 | xTaskNotifyGive(scheduler->task); 161 | xSemaphoreGiveRecursive(scheduler->lock); 162 | } 163 | -------------------------------------------------------------------------------- /main/scheduler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stdbool.h> 4 | #include <stdint.h> 5 | 6 | #include "list.h" 7 | 8 | typedef void (*scheduler_cb_f)(void *ctx); 9 | 10 | typedef struct scheduler_task_schedule { 11 | int64_t deadline_us; 12 | scheduler_cb_f cb; 13 | void *ctx; 14 | } scheduler_task_schedule_t; 15 | 16 | typedef struct scheduler_task { 17 | struct list_head list; 18 | struct list_head list_abort; 19 | struct list_head list_schedule; 20 | scheduler_task_schedule_t schedule_sync; 21 | scheduler_task_schedule_t schedule_async; 22 | } scheduler_task_t; 23 | 24 | void scheduler_init(); 25 | void scheduler_task_init(scheduler_task_t *task); 26 | void scheduler_schedule_task(scheduler_task_t *task, scheduler_cb_f cb, void *ctx, int64_t deadline_us); 27 | void scheduler_schedule_task_relative(scheduler_task_t *task, scheduler_cb_f cb, void *ctx, int64_t timeout_us); 28 | void scheduler_abort_task(scheduler_task_t *task); 29 | -------------------------------------------------------------------------------- /main/sensor.c: -------------------------------------------------------------------------------- 1 | #include "sensor.h" 2 | 3 | #include <esp_log.h> 4 | 5 | #include "util.h" 6 | 7 | #define METRIC_PRIV(type_) ((void *)((unsigned int)(type_) & 0xf)) 8 | #define METRIC_PRIV_TYPE(priv_) ((sensor_measurement_type_t)((unsigned int)(priv_) & 0xf)) 9 | 10 | #define VALUE_PRIV(index_, type_) ((void *)(((unsigned int)(index_) << 4) | ((unsigned int)(type_) & 0xf))) 11 | #define VALUE_PRIV_INDEX(priv_) ((unsigned int)(priv_) >> 4) 12 | #define VALUE_PRIV_TYPE(priv_) ((sensor_measurement_type_t)((unsigned int)(priv_) & 0xf)) 13 | 14 | static const char *TAG = "sensor"; 15 | 16 | static DECLARE_LIST_HEAD(sensors); 17 | 18 | static unsigned int get_num_values(prometheus_metric_t *metric) { 19 | sensor_measurement_type_t type = METRIC_PRIV_TYPE(metric->priv); 20 | sensor_t *sensor; 21 | unsigned int num_values = 0; 22 | LIST_FOR_EACH_ENTRY(sensor, &sensors, list) { 23 | num_values += sensor->def->get_num_channels(sensor, type); 24 | } 25 | return num_values; 26 | } 27 | 28 | static sensor_t *get_sensor_and_channel_by_type_and_index(sensor_measurement_type_t type, unsigned int index, unsigned int *channel) { 29 | sensor_t *sensor; 30 | LIST_FOR_EACH_ENTRY(sensor, &sensors, list) { 31 | unsigned int num_values = sensor->def->get_num_channels(sensor, type); 32 | if (num_values > index) { 33 | if (channel) { 34 | *channel = num_values - index - 1; 35 | } 36 | return sensor; 37 | } 38 | index -= num_values; 39 | } 40 | return NULL; 41 | } 42 | 43 | static unsigned int get_num_labels(const prometheus_metric_value_t *val, prometheus_metric_t *metric) { 44 | unsigned int value_index = VALUE_PRIV_INDEX(val->priv); 45 | sensor_measurement_type_t type = VALUE_PRIV_TYPE(val->priv); 46 | unsigned int channel; 47 | sensor_t *sensor = get_sensor_and_channel_by_type_and_index(type, value_index, &channel); 48 | if (sensor->def->get_channel_name && sensor->def->get_channel_name(sensor, type, channel)) { 49 | return 2; 50 | } else { 51 | return 1; 52 | } 53 | } 54 | 55 | static void get_label(const prometheus_metric_value_t *val, prometheus_metric_t *metric, unsigned int index, char *label, char *value) { 56 | unsigned int value_index = VALUE_PRIV_INDEX(val->priv); 57 | sensor_measurement_type_t type = VALUE_PRIV_TYPE(val->priv); 58 | unsigned int channel; 59 | sensor_t *sensor = get_sensor_and_channel_by_type_and_index(type, value_index, &channel); 60 | if (index == 0) { 61 | strcpy(label, "sensor"); 62 | strcpy(value, sensor->name); 63 | } else { 64 | strcpy(label, "channel"); 65 | strcpy(value, sensor->def->get_channel_name(sensor, type, channel)); 66 | } 67 | } 68 | 69 | static void get_value(const prometheus_metric_value_t *val, prometheus_metric_t *metric, char *value) { 70 | unsigned int value_index = VALUE_PRIV_INDEX(val->priv); 71 | sensor_measurement_type_t type = VALUE_PRIV_TYPE(val->priv); 72 | unsigned int channel; 73 | sensor_t *sensor = get_sensor_and_channel_by_type_and_index(type, value_index, &channel); 74 | long res; 75 | esp_err_t err = sensor->def->measure(sensor, type, channel, &res); 76 | if (err) { 77 | ESP_LOGE(TAG, "Failed to read %u channel %u of sensor %s", type, channel, sensor->name); 78 | } else { 79 | sprintf(value, "%s%01lu.%03lu", res < 0 ? "-" : "", ABS(res) / 1000, ABS(res) % 1000); 80 | } 81 | } 82 | 83 | static void get_metric_value(prometheus_metric_t *metric, unsigned int index, prometheus_metric_value_t *value) { 84 | sensor_measurement_type_t type = METRIC_PRIV_TYPE(metric->priv); 85 | value->priv = VALUE_PRIV(index, type); 86 | value->get_num_labels = get_num_labels; 87 | value->get_label = get_label; 88 | value->get_value = get_value; 89 | } 90 | 91 | static const prometheus_metric_def_t voltage_metric_def = { 92 | .name = "voltage", 93 | .help = "Voltage sensors", 94 | .type = PROMETHEUS_METRIC_TYPE_GAUGE, 95 | .num_values = 0, 96 | .get_num_values = get_num_values, 97 | .get_value = get_metric_value, 98 | }; 99 | 100 | static const prometheus_metric_def_t current_metric_def = { 101 | .name = "current", 102 | .help = "Current sensors", 103 | .type = PROMETHEUS_METRIC_TYPE_GAUGE, 104 | .num_values = 0, 105 | .get_num_values = get_num_values, 106 | .get_value = get_metric_value, 107 | }; 108 | 109 | static const prometheus_metric_def_t power_metric_def = { 110 | .name = "power", 111 | .help = "Power sensors", 112 | .type = PROMETHEUS_METRIC_TYPE_GAUGE, 113 | .num_values = 0, 114 | .get_num_values = get_num_values, 115 | .get_value = get_metric_value, 116 | }; 117 | 118 | static const prometheus_metric_def_t temperature_metric_def = { 119 | .name = "temperature", 120 | .help = "Temperature sensors", 121 | .type = PROMETHEUS_METRIC_TYPE_GAUGE, 122 | .num_values = 0, 123 | .get_num_values = get_num_values, 124 | .get_value = get_metric_value, 125 | }; 126 | 127 | static prometheus_metric_t metric_voltage; 128 | static prometheus_metric_t metric_current; 129 | static prometheus_metric_t metric_power; 130 | static prometheus_metric_t metric_temperature; 131 | 132 | void sensor_init(sensor_t *sensor, const sensor_def_t *def, const char *name) { 133 | INIT_LIST_HEAD(sensor->list); 134 | sensor->def = def; 135 | sensor->name = name; 136 | } 137 | 138 | void sensor_install_metrics(prometheus_t *prometheus) { 139 | prometheus_metric_init(&metric_voltage, &voltage_metric_def, METRIC_PRIV(SENSOR_TYPE_VOLTAGE)); 140 | prometheus_metric_init(&metric_current, ¤t_metric_def, METRIC_PRIV(SENSOR_TYPE_CURRENT)); 141 | prometheus_metric_init(&metric_power, &power_metric_def, METRIC_PRIV(SENSOR_TYPE_POWER)); 142 | prometheus_metric_init(&metric_temperature, &temperature_metric_def, METRIC_PRIV(SENSOR_TYPE_TEMPERATURE)); 143 | prometheus_add_metric(prometheus, &metric_voltage); 144 | prometheus_add_metric(prometheus, &metric_current); 145 | prometheus_add_metric(prometheus, &metric_power); 146 | prometheus_add_metric(prometheus, &metric_temperature); 147 | } 148 | 149 | void sensor_add(sensor_t *sensor) { 150 | LIST_APPEND_TAIL(&sensor->list, &sensors); 151 | } 152 | 153 | sensor_t *sensor_find_by_name(const char *name) { 154 | sensor_t *sensor; 155 | LIST_FOR_EACH_ENTRY(sensor, &sensors, list) { 156 | if (strcmp(sensor->name, name) == 0) { 157 | return sensor; 158 | } 159 | } 160 | 161 | return NULL; 162 | } 163 | -------------------------------------------------------------------------------- /main/sensor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <esp_err.h> 4 | 5 | #include "list.h" 6 | #include "prometheus.h" 7 | 8 | #define SENSOR_CHANNEL_NAME_LEN 64 9 | 10 | typedef enum { 11 | SENSOR_TYPE_VOLTAGE = 0, 12 | SENSOR_TYPE_CURRENT = 1, 13 | SENSOR_TYPE_POWER = 2, 14 | SENSOR_TYPE_TEMPERATURE = 3, 15 | } sensor_measurement_type_t; 16 | 17 | typedef struct sensor sensor_t; 18 | 19 | typedef struct sensor_def { 20 | unsigned int (*get_num_channels)(sensor_t *sensor, sensor_measurement_type_t type); 21 | const char *(*get_channel_name)(sensor_t *sensor, sensor_measurement_type_t type, unsigned int index); 22 | esp_err_t (*measure)(sensor_t *sensor, sensor_measurement_type_t type, unsigned int index, long *res); 23 | } sensor_def_t; 24 | 25 | struct sensor { 26 | const sensor_def_t *def; 27 | const char *name; 28 | list_head_t list; 29 | }; 30 | 31 | void sensor_init(sensor_t *sensor, const sensor_def_t *def, const char *name); 32 | void sensor_install_metrics(prometheus_t *prometheus); 33 | void sensor_add(sensor_t *sensor); 34 | sensor_t *sensor_find_by_name(const char *name); 35 | 36 | static inline esp_err_t sensor_measure(sensor_t *sensor, sensor_measurement_type_t type, unsigned int index, long *res) { 37 | return sensor->def->measure(sensor, type, index, res); 38 | } 39 | 40 | static inline esp_err_t sensor_measure_voltage(sensor_t *sensor, unsigned int index, long *res) { 41 | return sensor->def->measure(sensor, SENSOR_TYPE_VOLTAGE, index, res); 42 | } 43 | 44 | static inline esp_err_t sensor_measure_current(sensor_t *sensor, unsigned int index, long *res) { 45 | return sensor->def->measure(sensor, SENSOR_TYPE_CURRENT, index, res); 46 | } 47 | 48 | static inline esp_err_t sensor_measure_power(sensor_t *sensor, unsigned int index, long *res) { 49 | return sensor->def->measure(sensor, SENSOR_TYPE_POWER, index, res); 50 | } 51 | 52 | static inline esp_err_t sensor_measure_temperature(sensor_t *sensor, unsigned int index, long *res) { 53 | return sensor->def->measure(sensor, SENSOR_TYPE_TEMPERATURE, index, res); 54 | } 55 | -------------------------------------------------------------------------------- /main/settings.c: -------------------------------------------------------------------------------- 1 | #include "settings.h" 2 | 3 | #include <stdlib.h> 4 | #include <string.h> 5 | 6 | #include <freertos/FreeRTOS.h> 7 | #include <freertos/semphr.h> 8 | 9 | #include <esp_err.h> 10 | #include <esp_log.h> 11 | #include <nvs_flash.h> 12 | 13 | static const char *TAG = "settings"; 14 | 15 | static nvs_handle_t nvs; 16 | 17 | void settings_init() { 18 | esp_err_t err; 19 | 20 | err = nvs_flash_init(); 21 | if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { 22 | ESP_ERROR_CHECK(nvs_flash_erase()); 23 | err = nvs_flash_init(); 24 | } 25 | ESP_ERROR_CHECK(err); 26 | 27 | ESP_ERROR_CHECK(nvs_open("settings", NVS_READWRITE, &nvs)); 28 | } 29 | 30 | static const char *ellipsize_key(const char *key) { 31 | if (strlen(key) >= NVS_KEY_NAME_MAX_SIZE) { 32 | key += strlen(key) - NVS_KEY_NAME_MAX_SIZE + 1; 33 | ESP_LOGW(TAG, "Ellipsized NVS key to \"%s\"", key); 34 | } 35 | 36 | return key; 37 | } 38 | 39 | static char *nvs_get_string(const char *key) { 40 | char *str; 41 | size_t len; 42 | esp_err_t err; 43 | 44 | key = ellipsize_key(key); 45 | 46 | err = nvs_get_str(nvs, key, NULL, &len); 47 | if (err) { 48 | if (err != ESP_ERR_NVS_NOT_FOUND) { 49 | ESP_LOGE(TAG, "Failed to load string size '%s' from NVS: %d", key, err); 50 | } 51 | return NULL; 52 | } 53 | 54 | str = calloc(1, len); 55 | if (!str) { 56 | return NULL; 57 | } 58 | err = nvs_get_str(nvs, key, str, &len); 59 | if (err) { 60 | if (err != ESP_ERR_NVS_NOT_FOUND) { 61 | ESP_LOGE(TAG, "Failed to load string '%s' from NVS: %d", key, err); 62 | } 63 | free(str); 64 | return NULL; 65 | } 66 | 67 | return str; 68 | } 69 | 70 | static void nvs_set_string(const char *key, const char *value) { 71 | key = ellipsize_key(key); 72 | 73 | if (value) { 74 | esp_err_t err = nvs_set_str(nvs, key, value); 75 | if (err) { 76 | ESP_LOGE(TAG, "Failed to store string '%s' to NVS: %d", key, err); 77 | } 78 | } else { 79 | nvs_erase_key(nvs, key); 80 | } 81 | } 82 | 83 | static bool nvs_get_bool(const char *key, bool default_value) { 84 | esp_err_t err; 85 | uint8_t val; 86 | 87 | key = ellipsize_key(key); 88 | err = nvs_get_u8(nvs, key, &val); 89 | if (err) { 90 | if (err != ESP_ERR_NVS_NOT_FOUND) { 91 | ESP_LOGE(TAG, "Failed to load bool '%s' from NVS: %d", key, err); 92 | } 93 | return default_value; 94 | } 95 | 96 | return !!val; 97 | } 98 | 99 | static void nvs_set_bool(const char *key, bool value) { 100 | esp_err_t err; 101 | 102 | key = ellipsize_key(key); 103 | err = nvs_set_u8(nvs, key, value ? 1 : 0); 104 | if (err) { 105 | ESP_LOGE(TAG, "Failed to store bool '%s' to NVS: %d", key, err); 106 | } 107 | } 108 | 109 | static unsigned int nvs_get_uint(const char *key, unsigned int default_value) { 110 | esp_err_t err; 111 | uint16_t val; 112 | 113 | key = ellipsize_key(key); 114 | err = nvs_get_u16(nvs, key, &val); 115 | if (err) { 116 | if (err != ESP_ERR_NVS_NOT_FOUND) { 117 | ESP_LOGE(TAG, "Failed to load uint '%s' from NVS: %d", key, err); 118 | } 119 | return default_value; 120 | } 121 | 122 | return val; 123 | } 124 | 125 | static void nvs_set_uint(const char *key, unsigned int value) { 126 | esp_err_t err; 127 | 128 | key = ellipsize_key(key); 129 | err = nvs_set_u16(nvs, key, (uint16_t)value); 130 | if (err) { 131 | ESP_LOGE(TAG, "Failed to store uint '%s' to NVS: %d", key, err); 132 | } 133 | } 134 | 135 | void settings_set_serial_number(const char *str) { 136 | nvs_set_string("Serial", str); 137 | } 138 | 139 | char *settings_get_serial_number(void) { 140 | return nvs_get_string("Serial"); 141 | } 142 | 143 | void settings_set_input_current_limit_ma(unsigned int current_ma) { 144 | nvs_set_uint("MaxInCurrent", current_ma); 145 | } 146 | 147 | unsigned int settings_get_input_current_limit_ma(void) { 148 | return nvs_get_uint("MaxInCurrent", 1000); 149 | } 150 | -------------------------------------------------------------------------------- /main/settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stdbool.h> 4 | 5 | void settings_init(void); 6 | 7 | void settings_set_serial_number(const char *str); 8 | char *settings_get_serial_number(void); 9 | 10 | void settings_set_input_current_limit_ma(unsigned int current_ma); 11 | unsigned int settings_get_input_current_limit_ma(void); 12 | -------------------------------------------------------------------------------- /main/smbus.c: -------------------------------------------------------------------------------- 1 | #include <stddef.h> 2 | #include <stdlib.h> 3 | 4 | #include <driver/i2c.h> 5 | #include <esp_log.h> 6 | 7 | #include "smbus.h" 8 | 9 | static const char *TAG = "smbus"; 10 | 11 | void smbus_init(smbus_t *bus, i2c_bus_t *i2c) { 12 | bus->i2c = i2c; 13 | bus->lock = xSemaphoreCreateMutexStatic(&bus->lock_buffer); 14 | } 15 | 16 | esp_err_t smbus_write(smbus_t* bus, uint8_t slave, uint8_t smcmd, void *data, size_t len) { 17 | xSemaphoreTake(bus->lock, portMAX_DELAY); 18 | esp_err_t err; 19 | i2c_cmd_handle_t cmd = i2c_cmd_link_create_static(bus->cmd_buf, sizeof(bus->cmd_buf)); 20 | if(!cmd) { 21 | err = ESP_ERR_NO_MEM; 22 | goto fail; 23 | } 24 | if((err = i2c_master_start(cmd))) { 25 | goto fail_link; 26 | } 27 | if((err = i2c_master_write_byte(cmd, slave << 1, 1))) { 28 | goto fail_link; 29 | } 30 | if((err = i2c_master_write_byte(cmd, smcmd, 1))) { 31 | goto fail_link; 32 | } 33 | if((err = i2c_master_write(cmd, data, len, true))) { 34 | goto fail_link; 35 | } 36 | if((err = i2c_master_stop(cmd))) { 37 | goto fail_link; 38 | } 39 | err = i2c_bus_cmd_begin(bus->i2c, cmd, pdMS_TO_TICKS(100)); 40 | fail_link: 41 | i2c_cmd_link_delete_static(cmd); 42 | fail: 43 | xSemaphoreGive(bus->lock); 44 | return err; 45 | } 46 | 47 | esp_err_t smbus_read(smbus_t* bus, uint8_t slave, uint8_t smcmd, void *data, size_t len) { 48 | xSemaphoreTake(bus->lock, portMAX_DELAY); 49 | esp_err_t err; 50 | i2c_cmd_handle_t cmd = i2c_cmd_link_create_static(bus->cmd_buf, sizeof(bus->cmd_buf)); 51 | if(!cmd) { 52 | err = ESP_ERR_NO_MEM; 53 | goto fail; 54 | } 55 | if((err = i2c_master_start(cmd))) { 56 | goto fail_link; 57 | } 58 | if((err = i2c_master_write_byte(cmd, slave << 1, 1))) { 59 | goto fail_link; 60 | } 61 | if((err = i2c_master_write_byte(cmd, smcmd, 1))) { 62 | goto fail_link; 63 | } 64 | if((err = i2c_master_start(cmd))) { 65 | goto fail_link; 66 | } 67 | if((err = i2c_master_write_byte(cmd, (slave << 1) | 1, 1))) { 68 | goto fail_link; 69 | } 70 | if((err = i2c_master_read(cmd, (uint8_t*)data, len, I2C_MASTER_LAST_NACK))) { 71 | goto fail_link; 72 | } 73 | if((err = i2c_master_stop(cmd))) { 74 | goto fail_link; 75 | } 76 | err = i2c_bus_cmd_begin(bus->i2c, cmd, pdMS_TO_TICKS(100)); 77 | fail_link: 78 | i2c_cmd_link_delete_static(cmd); 79 | fail: 80 | xSemaphoreGive(bus->lock); 81 | return err; 82 | } 83 | 84 | esp_err_t smbus_write_block(smbus_t* bus, uint8_t slave, uint8_t smcmd, void *data, size_t len) { 85 | xSemaphoreTake(bus->lock, portMAX_DELAY); 86 | esp_err_t err; 87 | i2c_cmd_handle_t cmd = i2c_cmd_link_create_static(bus->cmd_buf, sizeof(bus->cmd_buf)); 88 | if(!cmd) { 89 | err = ESP_ERR_NO_MEM; 90 | goto fail; 91 | } 92 | if((err = i2c_master_start(cmd))) { 93 | goto fail_link; 94 | } 95 | if((err = i2c_master_write_byte(cmd, slave << 1, 1))) { 96 | goto fail_link; 97 | } 98 | if((err = i2c_master_write_byte(cmd, smcmd, 1))) { 99 | goto fail_link; 100 | } 101 | if((err = i2c_master_write_byte(cmd, len, 1))) { 102 | goto fail_link; 103 | } 104 | if((err = i2c_master_write(cmd, data, len, true))) { 105 | goto fail_link; 106 | } 107 | if((err = i2c_master_stop(cmd))) { 108 | goto fail_link; 109 | } 110 | err = i2c_bus_cmd_begin(bus->i2c, cmd, pdMS_TO_TICKS(1000)); 111 | fail_link: 112 | i2c_cmd_link_delete_static(cmd); 113 | fail: 114 | xSemaphoreGive(bus->lock); 115 | return err; 116 | } 117 | 118 | esp_err_t smbus_read_block(smbus_t* bus, uint8_t slave, uint8_t smcmd, void *data, size_t len, size_t *data_len) { 119 | xSemaphoreTake(bus->lock, portMAX_DELAY); 120 | esp_err_t err; 121 | i2c_cmd_handle_t cmd = i2c_cmd_link_create_static(bus->cmd_buf, sizeof(bus->cmd_buf)); 122 | if(!cmd) { 123 | err = ESP_ERR_NO_MEM; 124 | goto fail; 125 | } 126 | if((err = i2c_master_start(cmd))) { 127 | goto fail_link; 128 | } 129 | if((err = i2c_master_write_byte(cmd, slave << 1, 1))) { 130 | goto fail_link; 131 | } 132 | if((err = i2c_master_write_byte(cmd, smcmd, 1))) { 133 | goto fail_link; 134 | } 135 | if((err = i2c_master_start(cmd))) { 136 | goto fail_link; 137 | } 138 | if((err = i2c_master_write_byte(cmd, (slave << 1) | 1, 1))) { 139 | goto fail_link; 140 | } 141 | uint8_t data_len_; 142 | if((err = i2c_master_read_byte(cmd, &data_len_, I2C_MASTER_ACK))) { 143 | goto fail_link; 144 | } 145 | if((err = i2c_master_read(cmd, (uint8_t*)data, len, I2C_MASTER_LAST_NACK))) { 146 | goto fail_link; 147 | } 148 | if((err = i2c_master_stop(cmd))) { 149 | goto fail_link; 150 | } 151 | err = i2c_bus_cmd_begin(bus->i2c, cmd, pdMS_TO_TICKS(1000)); 152 | if (!err && data_len) { 153 | *data_len = data_len_; 154 | } 155 | fail_link: 156 | i2c_cmd_link_delete_static(cmd); 157 | fail: 158 | xSemaphoreGive(bus->lock); 159 | return err; 160 | } 161 | -------------------------------------------------------------------------------- /main/smbus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stdint.h> 4 | 5 | #include "i2c_bus.h" 6 | 7 | typedef struct smbus { 8 | i2c_bus_t *i2c; 9 | uint8_t cmd_buf[I2C_LINK_RECOMMENDED_SIZE(5)]; 10 | SemaphoreHandle_t lock; 11 | StaticSemaphore_t lock_buffer; 12 | } smbus_t; 13 | 14 | void smbus_init(smbus_t *bus, i2c_bus_t *i2c); 15 | 16 | esp_err_t smbus_read(smbus_t* bus, uint8_t slave, uint8_t smcmd, void *data, size_t len); 17 | esp_err_t smbus_write(smbus_t* bus, uint8_t slave, uint8_t smcmd, void *data, size_t len); 18 | esp_err_t smbus_write_block(smbus_t* bus, uint8_t slave, uint8_t smcmd, void *data, size_t len); 19 | esp_err_t smbus_read_block(smbus_t* bus, uint8_t slave, uint8_t smcmd, void *data, size_t len, size_t *data_len); 20 | 21 | static inline esp_err_t smbus_read_byte(smbus_t* bus, uint8_t slave, uint8_t smcmd, void *data) { 22 | return smbus_read(bus, slave, smcmd, data, 1); 23 | } 24 | 25 | static inline esp_err_t smbus_write_byte(smbus_t* bus, uint8_t slave, uint8_t smcmd, void *data) { 26 | return smbus_write(bus, slave, smcmd, data, 1); 27 | } 28 | 29 | static inline esp_err_t smbus_read_word(smbus_t* bus, uint8_t slave, uint8_t smcmd, void *data) { 30 | return smbus_read(bus, slave, smcmd, data, 2); 31 | } 32 | 33 | static inline esp_err_t smbus_write_word(smbus_t* bus, uint8_t slave, uint8_t smcmd, void *data) { 34 | return smbus_write(bus, slave, smcmd, data, 2); 35 | } 36 | -------------------------------------------------------------------------------- /main/ssd1306_oled.c: -------------------------------------------------------------------------------- 1 | #include "ssd1306_oled.h" 2 | 3 | #include <string.h> 4 | 5 | #include <driver/gpio.h> 6 | #include <esp_log.h> 7 | #include <freertos/FreeRTOS.h> 8 | #include <freertos/task.h> 9 | 10 | #include "delay.h" 11 | #include "util.h" 12 | 13 | const uint8_t init_sequence[] = { 14 | 0xae, /* display off */ 15 | 0x00, /* set low column address */ 16 | 0x12, /* set high column address */ 17 | 0x40, /* set display start line */ 18 | 0xB0, /* set page address */ 19 | 0x81, /* set contrast */ 20 | 0xff, /* contrast = 128 */ 21 | 0xA1, /* set segment remap */ 22 | 0xa6, /* normal / reverse */ 23 | 0xa8, /* set multiplex ratio */ 24 | 0x2f, /* multiplex ratio = 1/48 */ 25 | 0xc8, /* set COM scan direction */ 26 | 0xd3, /* set display offset */ 27 | 0x00, /* display offset = 0 */ 28 | 0xd5, /* set clock divider */ 29 | 0x80, /* clock speed = ? */ 30 | 0xd9, /* set pre-charge period */ 31 | 0x21, /* pre-charge period */ 32 | 0xda, /* set COM pins */ 33 | 0x12, 34 | 0x8d, /* enable charge pump */ 35 | 0x14, 36 | 0x20, /* set horizontal addressing mode */ 37 | 0x00, 38 | 0xaf /* display on */ 39 | }; 40 | 41 | static esp_err_t send_command(ssd1306_oled_t *oled, uint8_t command) { 42 | i2c_cmd_handle_t cmd = i2c_cmd_link_create_static(oled->xfers_cmd, sizeof(oled->xfers_cmd)); 43 | i2c_master_start(cmd); 44 | i2c_master_write_byte(cmd, (oled->address << 1), true); 45 | i2c_master_write_byte(cmd, 0x00, true); 46 | i2c_master_write_byte(cmd, command, true); 47 | i2c_master_stop(cmd); 48 | esp_err_t err = i2c_bus_cmd_begin(oled->bus, cmd, pdMS_TO_TICKS(10)); 49 | i2c_cmd_link_delete_static(cmd); 50 | return err; 51 | } 52 | 53 | static esp_err_t write_gdram(ssd1306_oled_t *oled, uint8_t* data, unsigned int len) { 54 | i2c_cmd_handle_t cmd = i2c_cmd_link_create_static(oled->xfers_cmd, sizeof(oled->xfers_cmd)); 55 | i2c_master_start(cmd); 56 | i2c_master_write_byte(cmd, (oled->address << 1), true); 57 | i2c_master_write_byte(cmd, 0x40, true); 58 | i2c_master_write(cmd, data, len, I2C_MASTER_ACK); 59 | i2c_master_stop(cmd); 60 | esp_err_t err = i2c_bus_cmd_begin(oled->bus, cmd, pdMS_TO_TICKS(10)); 61 | i2c_cmd_link_delete_static(cmd); 62 | return err; 63 | } 64 | 65 | static esp_err_t send_command_list(ssd1306_oled_t *oled, const uint8_t *cmds, unsigned int num_cmds) { 66 | while (num_cmds--) { 67 | esp_err_t err = send_command(oled, *cmds++); 68 | if (err) { 69 | return err; 70 | } 71 | } 72 | return ESP_OK; 73 | } 74 | 75 | static void reset(ssd1306_oled_t *oled) { 76 | if (oled->reset_gpio >= 0) { 77 | gpio_set_level(oled->reset_gpio, 0); 78 | delay_us(10); 79 | gpio_set_level(oled->reset_gpio, 1); 80 | vTaskDelay(pdMS_TO_TICKS(100)); 81 | } 82 | } 83 | 84 | esp_err_t ssd1306_oled_init(ssd1306_oled_t *oled, i2c_bus_t *bus, unsigned int address, int reset_gpio) { 85 | memset(oled, 0, sizeof(*oled)); 86 | oled->bus = bus; 87 | oled->address = address; 88 | oled->reset_gpio = reset_gpio; 89 | if (reset_gpio >= 0) { 90 | gpio_set_direction(reset_gpio, GPIO_MODE_OUTPUT); 91 | } 92 | reset(oled); 93 | return send_command_list(oled, init_sequence, ARRAY_SIZE(init_sequence)); 94 | } 95 | 96 | esp_err_t ssd1306_oled_render_fb(ssd1306_oled_t *oled, fb_t *fb) { 97 | const uint8_t address_setup[] = { 98 | 0x21, 99 | 32, 100 | 95, 101 | 0x22, 102 | 0, 103 | 5 104 | }; 105 | esp_err_t err = send_command_list(oled, address_setup, ARRAY_SIZE(address_setup)); 106 | if (err) { 107 | return err; 108 | } 109 | return write_gdram(oled, fb->data, sizeof(fb->data)); 110 | } 111 | 112 | -------------------------------------------------------------------------------- /main/ssd1306_oled.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stdbool.h> 4 | #include <stdint.h> 5 | 6 | #include <driver/i2c.h> 7 | 8 | #include "fb.h" 9 | #include "i2c_bus.h" 10 | 11 | typedef struct ssd1306_oled { 12 | i2c_bus_t *bus; 13 | unsigned int address; 14 | int reset_gpio; 15 | uint8_t xfers_cmd[I2C_LINK_RECOMMENDED_SIZE(3)]; 16 | } ssd1306_oled_t; 17 | 18 | esp_err_t ssd1306_oled_init(ssd1306_oled_t *oled, i2c_bus_t *bus, unsigned int address, int reset_gpio); 19 | esp_err_t ssd1306_oled_render_fb(ssd1306_oled_t *oled, fb_t *fb); 20 | -------------------------------------------------------------------------------- /main/template.h: -------------------------------------------------------------------------------- 1 | #ifndef _TEMPLATE_H_ 2 | #define _TEMPLATE_H_ 3 | 4 | #include "esp_err.h" 5 | 6 | #include "list.h" 7 | 8 | #define TEMPLATE_ID_LEN_DEFAULT 16 9 | #define TEMPLATE_ID_PREFIX "{{" 10 | #define TEMPLATE_ID_SUFFIX "}}" 11 | 12 | #define TEMPLATE_MAX_ARG_LEN 200 13 | #define TEMPLATE_BUFF_SIZE 256 14 | 15 | struct templ { 16 | struct list_head templates; 17 | }; 18 | 19 | struct templ_slice; 20 | 21 | // priv comes from templ_entry struct, ctx from current execution 22 | typedef esp_err_t (*templ_cb)(void* ctx, void* priv, struct templ_slice* slice); 23 | 24 | typedef esp_err_t (*prepare_cb)(void* priv, struct templ_slice* slice); 25 | 26 | struct templ_entry { 27 | struct list_head list; 28 | 29 | char* id; 30 | void* priv; 31 | templ_cb cb; 32 | prepare_cb prepare; 33 | }; 34 | 35 | struct templ_instance { 36 | struct list_head slices; 37 | }; 38 | 39 | struct templ_slice_arg { 40 | struct list_head list; 41 | 42 | char* key; 43 | char* value; 44 | }; 45 | 46 | struct templ_slice { 47 | struct list_head list; 48 | 49 | void *priv; 50 | size_t start; 51 | size_t end; 52 | struct templ_entry* entry; 53 | struct list_head args; 54 | }; 55 | 56 | typedef esp_err_t (*templ_write_cb)(void* ctx, char* buff, size_t len); 57 | 58 | void template_init(struct templ* templ); 59 | void template_free_instance(struct templ_instance* instance); 60 | esp_err_t template_alloc_instance(struct templ_instance** retval, struct templ* templ, char* path); 61 | esp_err_t template_alloc_instance_fd(struct templ_instance** retval, struct templ* templ, int fd); 62 | esp_err_t template_add(struct templ* templ, char* id, templ_cb cb, prepare_cb prepare, void* priv); 63 | esp_err_t template_apply(struct templ_instance* instance, char* path, templ_write_cb cb, void* ctx); 64 | esp_err_t template_apply_fd(struct templ_instance* instance, int fd, templ_write_cb cb, void* ctx); 65 | void template_free_templates(struct templ* templ); 66 | struct templ_slice_arg* template_slice_get_option(struct templ_slice* slice, const char* id); 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /main/util.c: -------------------------------------------------------------------------------- 1 | #include <errno.h> 2 | #include <sys/types.h> 3 | 4 | #include "util.h" 5 | 6 | void strntr(char* str, size_t len, char a, char b) { 7 | while(len-- > 0) { 8 | if(*str == a) { 9 | *str = b; 10 | } 11 | str++; 12 | } 13 | } 14 | 15 | ssize_t hex_decode_inplace(uint8_t *ptr, size_t len) { 16 | uint8_t *dst = ptr; 17 | size_t i; 18 | 19 | if (len % 2) { 20 | return -EINVAL; 21 | } 22 | 23 | for (i = 0; i < len; i++) { 24 | *dst++ = hex_to_byte(ptr); 25 | ptr += 2; 26 | } 27 | 28 | return len / 2; 29 | } 30 | -------------------------------------------------------------------------------- /main/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <stddef.h> 4 | #include <stdint.h> 5 | #include <string.h> 6 | 7 | #ifndef ARRAY_SIZE 8 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*(arr))) 9 | #endif 10 | 11 | #define paddr__(x) ((uint32_t)(x)) 12 | 13 | #ifndef BIT 14 | #define BIT(x) (1 << (x)) 15 | #endif 16 | 17 | #define KHZ(x) ((x) * 1000UL) 18 | #define MHZ(x) (KHZ((x)) * 1000UL) 19 | 20 | #define KOHM(x) ((x) * 1000UL) 21 | 22 | #define KIB(bytes) ((bytes) * 1024) 23 | 24 | #define SWAP(a, b) \ 25 | do { \ 26 | __typeof__(a) tmp_ = b; \ 27 | b = a; \ 28 | a = tmp_; \ 29 | } while(0) 30 | 31 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 32 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 33 | #define CLAMP(x, min_, max_) MAX(MIN((x), (max_)), (min_)) 34 | 35 | #define DIV_ROUND_UP(u, v) (((u) + ((v) - 1)) / (v)) 36 | #define DIV_ROUND(u, v) (((u) + ((v) / 2)) / (v)) 37 | 38 | #define BITSWAP_U8(x) \ 39 | ((((x) & 0x80) >> 7) | \ 40 | (((x) & 0x40) >> 5) | \ 41 | (((x) & 0x20) >> 3) | \ 42 | (((x) & 0x10) >> 1) | \ 43 | (((x) & 0x08) << 1) | \ 44 | (((x) & 0x04) << 3) | \ 45 | (((x) & 0x02) << 5) | \ 46 | (((x) & 0x01) << 7)) 47 | 48 | #define ABS(x) ((x) < 0 ? -(x) : (x)) 49 | 50 | #define ALIGN_UP(x, align) ((x) + (align) - (x) % (align)) 51 | 52 | #ifndef container_of 53 | #define container_of(ptr, type, member) ({\ 54 | void *__mptr = (void *)(ptr);\ 55 | ((type *)(__mptr - offsetof(type, member))); }) 56 | #endif 57 | 58 | #define DIRENT_FOR_EACH(cursor, dir) \ 59 | for((cursor) = readdir((dir)); (cursor); (cursor) = readdir((dir))) 60 | 61 | void strntr(char* str, size_t len, char a, char b); 62 | 63 | #define strtr(str, a, b) \ 64 | strntr(str, strlen(str), a, b); 65 | 66 | #define hex_to_nibble(hex) \ 67 | (((hex) >= '0' && (hex) <= '9' ? (uint8_t)((hex) - '0') : \ 68 | (hex) >= 'A' && (hex) <= 'F' ? (uint8_t)((hex) - 'A' + 0xA) : \ 69 | (hex) >= 'a' && (hex) <= 'f' ? (uint8_t)((hex) - 'a' + 0xA) : \ 70 | 0 \ 71 | ) & 0xF) 72 | 73 | #define hex_to_byte(hex) \ 74 | ((hex_to_nibble((hex)[0]) << 4) | hex_to_nibble((hex)[1])) 75 | 76 | #define MS_TO_US(ms) ((ms) * 1000) 77 | 78 | #define COALESCE(x, default_) ((x) ? (x) : (default_)) 79 | 80 | #define STR_NULL(s) COALESCE((s), "(NULL)") 81 | 82 | #define MS_TO_US(ms) ((ms) * 1000) 83 | 84 | #ifndef STRINGIFY 85 | #define STRINGIFY(str) #str 86 | #endif 87 | 88 | #ifndef XSTRINGIFY 89 | #define XSTRINGIFY(str) STRINGIFY(str) 90 | #endif 91 | -------------------------------------------------------------------------------- /main/vendor.c: -------------------------------------------------------------------------------- 1 | #include "vendor.h" 2 | 3 | #include <string.h> 4 | 5 | #include <freertos/FreeRTOS.h> 6 | #include <freertos/semphr.h> 7 | 8 | #include "event_bus.h" 9 | #include "settings.h" 10 | #include "util.h" 11 | 12 | #define HOSTNAME_PREFIX "dc-ups-" 13 | 14 | #define FALLBACK_SERIAL "000000" 15 | #define FALLBACK_HOSTNAME HOSTNAME_PREFIX FALLBACK_SERIAL 16 | 17 | static StaticSemaphore_t lock_buffer; 18 | static SemaphoreHandle_t lock; 19 | 20 | static char *serial_number = NULL; 21 | static char *hostname = NULL; 22 | 23 | static void set_serial_number_(const char *serial) { 24 | if (hostname) { 25 | free(hostname); 26 | hostname = NULL; 27 | serial_number = NULL; 28 | } 29 | if (serial) { 30 | unsigned int hostname_len; 31 | 32 | hostname_len = strlen(HOSTNAME_PREFIX) + strlen(serial) + 1; 33 | hostname = calloc(1, hostname_len); 34 | if (hostname) { 35 | snprintf(hostname, hostname_len, "%s%s", HOSTNAME_PREFIX, serial); 36 | serial_number = hostname + strlen(HOSTNAME_PREFIX); 37 | } 38 | } 39 | } 40 | 41 | void vendor_init(void) { 42 | char *serial; 43 | 44 | lock = xSemaphoreCreateMutexStatic(&lock_buffer); 45 | 46 | serial = settings_get_serial_number(); 47 | set_serial_number_(serial); 48 | if (serial) { 49 | free(serial); 50 | } 51 | } 52 | 53 | void vendor_lock(void) { 54 | xSemaphoreTake(lock, portMAX_DELAY); 55 | } 56 | 57 | void vendor_unlock(void) { 58 | xSemaphoreGive(lock); 59 | } 60 | 61 | void vendor_set_serial_number(const char *serial) { 62 | vendor_lock(); 63 | set_serial_number_(serial); 64 | settings_set_serial_number(serial); 65 | vendor_unlock(); 66 | event_bus_notify("vendor", NULL); 67 | } 68 | 69 | const char *vendor_get_serial_number_(void) { 70 | return COALESCE(serial_number, FALLBACK_SERIAL); 71 | } 72 | 73 | const char *vendor_get_hostname_(void) { 74 | return COALESCE(hostname, FALLBACK_HOSTNAME); 75 | } 76 | -------------------------------------------------------------------------------- /main/vendor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void vendor_init(void); 4 | void vendor_lock(void); 5 | void vendor_unlock(void); 6 | 7 | void vendor_set_serial_number(const char *serial); 8 | 9 | const char *vendor_get_serial_number_(void); 10 | const char *vendor_get_hostname_(void); 11 | -------------------------------------------------------------------------------- /main/website.c: -------------------------------------------------------------------------------- 1 | #include "website.h" 2 | 3 | #include <stdio.h> 4 | #include <stdlib.h> 5 | 6 | #include <esp_err.h> 7 | #include <esp_log.h> 8 | 9 | #include "power_path.h" 10 | #include "sensor.h" 11 | 12 | const char *TAG = "web"; 13 | 14 | static const char *get_sensor_name(const char *name) { 15 | if (name && !strcmp(name, "ina_dc_out")) { 16 | if (power_path_is_running_on_battery()) { 17 | return "ina_dc_out_step_up"; 18 | } else { 19 | return "ina_dc_out_passthrough"; 20 | } 21 | } 22 | 23 | if (name && !strcmp(name, "lm75_dc_in")) { 24 | return "lm75_charger"; 25 | } 26 | 27 | return name; 28 | } 29 | 30 | static esp_err_t measure_sensor(struct httpd_slice_ctx *slice_ctx, const char *sensor_name_variable, sensor_measurement_type_t type, long *res) { 31 | const char *sensor_name = get_sensor_name(slice_scope_get_variable(slice_ctx, sensor_name_variable)); 32 | sensor_t *sensor = sensor_find_by_name(sensor_name); 33 | if (!sensor) { 34 | ESP_LOGW(TAG, "Failed to find sensor %s", sensor_name ? sensor_name : "(null)"); 35 | return ESP_FAIL; 36 | } 37 | return sensor_measure(sensor, type, 0, res); 38 | } 39 | 40 | static esp_err_t status_panel_voltage_cb(void* ctx, void* priv, struct templ_slice* slice) { 41 | struct httpd_slice_ctx *slice_ctx = ctx; 42 | long voltage_mv; 43 | esp_err_t err = measure_sensor(slice_ctx, "ina", SENSOR_TYPE_VOLTAGE, &voltage_mv); 44 | if (err) { 45 | return httpd_response_write_string(slice_ctx->req_ctx, "Measurement failed"); 46 | } 47 | 48 | ESP_LOGI(TAG, "INA voltage: %ld mV", voltage_mv); 49 | 50 | char strbuf[32]; 51 | snprintf(strbuf, sizeof(strbuf), "%ld.%02ld V", voltage_mv / 1000L, (voltage_mv % 1000L) / 10); 52 | return httpd_response_write_string(slice_ctx->req_ctx, strbuf); 53 | } 54 | 55 | static esp_err_t status_panel_current_cb(void* ctx, void* priv, struct templ_slice* slice) { 56 | struct httpd_slice_ctx *slice_ctx = ctx; 57 | long current_ma; 58 | esp_err_t err = measure_sensor(slice_ctx, "ina", SENSOR_TYPE_CURRENT, ¤t_ma); 59 | if (err) { 60 | return httpd_response_write_string(slice_ctx->req_ctx, "Measurement failed"); 61 | } 62 | 63 | char strbuf[32]; 64 | snprintf(strbuf, sizeof(strbuf), "%s%ld.%02ld A", current_ma < 0 ? "-" : "", ABS(current_ma) / 1000L, (ABS(current_ma) % 1000L) / 10); 65 | return httpd_response_write_string(slice_ctx->req_ctx, strbuf); 66 | } 67 | 68 | static esp_err_t status_panel_power_cb(void* ctx, void* priv, struct templ_slice* slice) { 69 | struct httpd_slice_ctx *slice_ctx = ctx; 70 | long power_mw; 71 | esp_err_t err = measure_sensor(slice_ctx, "ina", SENSOR_TYPE_POWER, &power_mw); 72 | if (err) { 73 | return httpd_response_write_string(slice_ctx->req_ctx, "Measurement failed"); 74 | } 75 | 76 | char strbuf[32]; 77 | snprintf(strbuf, sizeof(strbuf), "%s%ld.%02ld W", power_mw < 0 ? "-" : "", ABS(power_mw) / 1000L, (ABS(power_mw) % 1000L) / 10); 78 | return httpd_response_write_string(slice_ctx->req_ctx, strbuf); 79 | } 80 | 81 | static esp_err_t status_panel_temperature_cb(void* ctx, void* priv, struct templ_slice* slice) { 82 | struct httpd_slice_ctx *slice_ctx = ctx; 83 | long temperature_mdegc; 84 | esp_err_t err = measure_sensor(slice_ctx, "lm75", SENSOR_TYPE_TEMPERATURE, &temperature_mdegc); 85 | if (err) { 86 | return httpd_response_write_string(slice_ctx->req_ctx, "Measurement failed"); 87 | } 88 | 89 | char strbuf[32]; 90 | snprintf(strbuf, sizeof(strbuf), "%s%ld.%02ld C", temperature_mdegc < 0 ? "-" : "", ABS(temperature_mdegc) / 1000L, (ABS(temperature_mdegc) % 1000L) / 10); 91 | return httpd_response_write_string(slice_ctx->req_ctx, strbuf); 92 | } 93 | 94 | static esp_err_t dcout_voltage_cb(void* ctx, void* priv, struct templ_slice* slice) { 95 | struct httpd_slice_ctx *slice_ctx = ctx; 96 | unsigned int voltage_mv = power_path_get_dc_output_voltage_mv(); 97 | char strbuf[32]; 98 | snprintf(strbuf, sizeof(strbuf), "%u.%01u V", voltage_mv / 1000, (voltage_mv % 1000) / 100); 99 | return httpd_response_write_string(slice_ctx->req_ctx, strbuf); 100 | } 101 | 102 | static esp_err_t dcout_enabled_cb(void* ctx, void* priv, struct templ_slice* slice) { 103 | struct httpd_slice_ctx *slice_ctx = ctx; 104 | const char *output_id_str = slice_scope_get_variable(slice_ctx, "output"); 105 | if (!output_id_str) { 106 | ESP_LOGE(TAG, "Missing output variable in slice scope"); 107 | return ESP_FAIL; 108 | } 109 | int output_id = atoi(output_id_str); 110 | if (output_id < 1 || output_id > 3) { 111 | ESP_LOGE(TAG, "Invalid output ID"); 112 | return ESP_FAIL; 113 | } 114 | bool output_enabled = power_path_is_dc_output_enabled(output_id - 1); 115 | if (output_enabled) { 116 | return httpd_response_write_string(slice_ctx->req_ctx, "checked"); 117 | } 118 | return ESP_OK; 119 | } 120 | 121 | void website_init(httpd_t *httpd) { 122 | /* Templates */ 123 | ESP_ERROR_CHECK(httpd_add_template(httpd, "status_panel.voltage", status_panel_voltage_cb, NULL)); 124 | ESP_ERROR_CHECK(httpd_add_template(httpd, "status_panel.current", status_panel_current_cb, NULL)); 125 | ESP_ERROR_CHECK(httpd_add_template(httpd, "status_panel.power", status_panel_power_cb, NULL)); 126 | ESP_ERROR_CHECK(httpd_add_template(httpd, "status_panel.temperature", status_panel_temperature_cb, NULL)); 127 | ESP_ERROR_CHECK(httpd_add_template(httpd, "dcout.voltage", dcout_voltage_cb, NULL)); 128 | ESP_ERROR_CHECK(httpd_add_template(httpd, "dcout.enabled", dcout_enabled_cb, NULL)); 129 | /* Index */ 130 | ESP_ERROR_CHECK(httpd_add_redirect(httpd, "/", "/index.thtml")); 131 | /* Static files */ 132 | ESP_ERROR_CHECK(httpd_add_static_path(httpd, "/webroot/binding.js")); 133 | ESP_ERROR_CHECK(httpd_add_static_path(httpd, "/webroot/bootstrap.bundle.min.js")); 134 | ESP_ERROR_CHECK(httpd_add_static_path(httpd, "/webroot/bootstrap.min.css")); 135 | ESP_ERROR_CHECK(httpd_add_static_path(httpd, "/webroot/jquery-1.8.3.min.js")); 136 | ESP_ERROR_CHECK(httpd_add_static_path(httpd, "/webroot/index.thtml")); 137 | ESP_ERROR_CHECK(httpd_add_static_path(httpd, "/webroot/dcin.thtml")); 138 | ESP_ERROR_CHECK(httpd_add_static_path(httpd, "/webroot/dcout.thtml")); 139 | /* 140 | ESP_ERROR_CHECK(httpd_add_static_path(httpd, "/webroot/usbout.thtml")); 141 | ESP_ERROR_CHECK(httpd_add_static_path(httpd, "/webroot/battery.thtml")); 142 | ESP_ERROR_CHECK(httpd_add_static_path(httpd, "/webroot/network.thtml")); 143 | */ 144 | } -------------------------------------------------------------------------------- /main/website.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "httpd.h" 4 | 5 | void website_init(httpd_t *httpd); 6 | -------------------------------------------------------------------------------- /main/wifi.c: -------------------------------------------------------------------------------- 1 | #include <dhcpserver/dhcpserver.h> 2 | #include <esp_log.h> 3 | #include <esp_wifi.h> 4 | 5 | #include "util.h" 6 | #include "wifi.h" 7 | 8 | #define WIFI_AP_SSID "dc-ups" 9 | #define WIFI_AP_PSK "DangerDangerHighVoltage!" 10 | 11 | static const char *TAG = "wifi"; 12 | 13 | static void wifi_event_handler(void *arg, esp_event_base_t event_base, 14 | int32_t event_id, void* event_data) { 15 | /* 16 | if (event_id == WIFI_EVENT_AP_STACONNECTED) { 17 | wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data; 18 | 19 | ESP_LOGI(TAG, "station "MACSTR" join, AID=%d", 20 | MAC2STR(event->mac), event->aid); 21 | } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) { 22 | wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data; 23 | 24 | ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d", 25 | MAC2STR(event->mac), event->aid); 26 | } else if (event_id == WIFI_EVENT_STA_CONNECTED) { 27 | ESP_LOGI(TAG, "connected to AP"); 28 | } else if (event_id == WIFI_EVENT_STA_DISCONNECTED) { 29 | ESP_LOGI(TAG, "disconnected from AP"); 30 | } 31 | */ 32 | } 33 | 34 | 35 | void wifi_init() { 36 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 37 | esp_netif_t *ap_iface; 38 | dhcps_offer_t dhcps_flag_false = 0; 39 | 40 | ESP_ERROR_CHECK(esp_netif_init()); 41 | ESP_ERROR_CHECK(!(ap_iface = esp_netif_create_default_wifi_ap())); 42 | 43 | ESP_ERROR_CHECK(esp_netif_dhcps_option(ap_iface, ESP_NETIF_OP_SET, 44 | ESP_NETIF_ROUTER_SOLICITATION_ADDRESS, 45 | &dhcps_flag_false, sizeof(dhcps_flag_false))); 46 | ESP_ERROR_CHECK(esp_netif_dhcps_option(ap_iface, ESP_NETIF_OP_SET, 47 | ESP_NETIF_DOMAIN_NAME_SERVER, 48 | &dhcps_flag_false, sizeof(dhcps_flag_false))); 49 | 50 | ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 51 | 52 | ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, 53 | ESP_EVENT_ANY_ID, 54 | &wifi_event_handler, 55 | NULL, 56 | NULL)); 57 | } 58 | 59 | void wifi_start_ap() { 60 | wifi_config_t wifi_ap_config = { 61 | .ap = { 62 | .ssid = WIFI_AP_SSID, 63 | .ssid_len = strlen(WIFI_AP_SSID), 64 | .password = WIFI_AP_PSK, 65 | .channel = 6, 66 | .max_connection = 8, 67 | .authmode = WIFI_AUTH_WPA2_PSK, 68 | }, 69 | }; 70 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); 71 | ESP_ERROR_CHECK(esp_wifi_start()); 72 | ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config)); 73 | } 74 | -------------------------------------------------------------------------------- /main/wifi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void wifi_init(void); 4 | void wifi_start_ap(void); 5 | -------------------------------------------------------------------------------- /partitions.csv: -------------------------------------------------------------------------------- 1 | # ESP-IDF Partition Table 2 | # Name, Type, SubType, Offset, Size, Flags 3 | nvs, data, nvs, , 0x6000, 4 | phy_init, data, phy, , 0x1000, 5 | factory, app, factory, , 1M, 6 | webroot, data, spiffs, , 1M, 7 | -------------------------------------------------------------------------------- /webroot/binding.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bindingPrototype = { 4 | _uiUpdate: function(newValue) { 5 | var request = new SetRequest(this, newValue); 6 | var requestId = this.manager.sendRequest(request); 7 | if (requestId === null) { 8 | 9 | } else { 10 | this.activeRequests[requestId] = newValue; 11 | } 12 | }, 13 | 14 | _updateFailed: function(requestId) { 15 | delete this.activeRequests[requestId]; 16 | }, 17 | 18 | _updateSuccess: function(requestId) { 19 | this.value = this.activeRequests[requestId]; 20 | delete this.activeRequests[requestId]; 21 | }, 22 | 23 | addEventListener: function(type, listener) { 24 | this.eventListeners[type].push(listener); 25 | }, 26 | 27 | remoteUpdate: function(newValue) { 28 | this.updateFunc(this.elem, newValue); 29 | this.value = newValue; 30 | }, 31 | 32 | update: function() { 33 | var getRequest = new GetRequest(this); 34 | this.manager.sendRequest(getRequest); 35 | } 36 | }; 37 | 38 | function Binding(name, manager, elem, updateFunc) { 39 | this.name = name; 40 | this.manager = manager; 41 | this.eventListeners = { 42 | remoteUpdate: [], 43 | remoteUpdateError: [] 44 | }; 45 | this.activeRequests = { }; 46 | this.elem = elem; 47 | var binding = this; 48 | elem.addEventListener("change", function(event) { 49 | binding._uiUpdate(event.target.value); 50 | }); 51 | this.updateFunc = updateFunc; 52 | } 53 | 54 | Binding.prototype = bindingPrototype; 55 | 56 | //Object.assign(Binding.prototype, bindingPrototype); 57 | 58 | var setRequestPrototype = { 59 | getRequestString: function() { 60 | return "SET " + this.requestId.toString() + " " + 61 | this.binding.name + " " + this.newValue; 62 | }, 63 | 64 | handleResponse: function() { 65 | this.binding._updateSuccess(this.requestId); 66 | }, 67 | 68 | handleTimeout: function() { 69 | this.binding._updateFailed(this.requestId); 70 | } 71 | }; 72 | 73 | function SetRequest(binding, newValue) { 74 | this.timeout = null; 75 | this.requestId = null; 76 | this.binding = binding; 77 | this.newValue = newValue; 78 | } 79 | 80 | SetRequest.prototype = setRequestPrototype; 81 | //Object.assign(SetRequest.prototype, setRequestPrototype); 82 | 83 | var getRequestPrototype = { 84 | getRequestString: function() { 85 | return "GET " + this.requestId.toString() + " " + 86 | this.binding.name; 87 | }, 88 | 89 | handleResponse: function(payload) { 90 | this.binding.remoteUpdate(payload); 91 | }, 92 | 93 | handleTimeout: function() { 94 | console.log("Get timeout"); 95 | } 96 | }; 97 | 98 | function GetRequest(binding) { 99 | this.timeout = null; 100 | this.requestId = null; 101 | this.binding = binding; 102 | } 103 | 104 | GetRequest.prototype = getRequestPrototype; 105 | //Object.assign(GetRequest.prototype, getRequestPrototype); 106 | 107 | var bindingManagerPrototype = { 108 | _handleRemoteUpdate: function(nameAndValue) { 109 | var nameEnd = nameAndValue.indexOf(" "); 110 | if (nameEnd === -1) { 111 | console.log("Invalid message from remote, no parameter name found"); 112 | return; 113 | } 114 | var name = nameAndValue.substring(0, nameEnd); 115 | var value = nameAndValue.substring(nameEnd + 1); 116 | var binding = this.bindings[name]; 117 | if (binding) { 118 | binding.remoteUpdate(value); 119 | } else { 120 | console.log("Got unsolicited update for unknown parameter " + name); 121 | } 122 | }, 123 | 124 | _handleResponse: function(requestIdAndPayload) { 125 | var requestIdEnd = requestIdAndPayload.indexOf(" "); 126 | if (requestIdEnd === -1) { 127 | requestIdEnd = requestIdAndPayload.length; 128 | } else { 129 | requestIdEnd += 1; 130 | } 131 | var requestIdStr = requestIdAndPayload.substring(0, requestIdEnd); 132 | var requestId = parseInt(requestIdStr); 133 | var request = this.activeRequests[requestId]; 134 | if (request) { 135 | var payload = requestIdAndPayload.substring(requestIdEnd); 136 | clearTimeout(request.timeout); 137 | request.handleResponse(payload); 138 | delete this.activeRequests[requestId]; 139 | } else { 140 | console.log("Received response to unknown request (" + requestIdStr + "), expired?"); 141 | } 142 | }, 143 | 144 | _messageReceived: function(msg) { 145 | var verbEnd = msg.indexOf(" "); 146 | if (verbEnd === -1) { 147 | console.log("Invalid message from remote, no verb found"); 148 | return; 149 | } 150 | var verb = msg.substring(0, verbEnd); 151 | var remainder = msg.substring(verbEnd + 1); 152 | 153 | if (verb == "UPD") { 154 | this._handleRemoteUpdate(remainder); 155 | } else { 156 | this._handleResponse(remainder); 157 | } 158 | }, 159 | 160 | _clearActiveRequests: function() { 161 | for (var key in this.activeRequests) { 162 | var request = this.activeRequests[key]; 163 | request.handleTimeout(); 164 | delete this.activeRequests[key]; 165 | } 166 | }, 167 | 168 | _updateBindings: function() { 169 | var bindings = this.bindings; 170 | for (var key in bindings) { 171 | bindings[key].update(); 172 | } 173 | }, 174 | 175 | sendRequest: function(request) { 176 | if (this.isConnected()) { 177 | var requestId = this.requestId++; 178 | request.requestId = requestId; 179 | this.activeRequests[requestId] = request; 180 | var manager = this; 181 | var timeout = setTimeout(function() { 182 | delete manager.activeRequests[requestId]; 183 | request.handleTimeout(); 184 | }, this.requestTimeoutMs); 185 | request.timeout = timeout; 186 | this.websocket.send(request.getRequestString()); 187 | return requestId; 188 | } 189 | return null; 190 | }, 191 | 192 | addEventListener: function(type, listener) { 193 | this.eventListeners[type].push(listener); 194 | }, 195 | 196 | dispatchEvent: function(type, event) { 197 | var listeners = this.eventListeners[type]; 198 | for (var i = 0; i < listeners.length; i++) { 199 | listeners[i](event); 200 | } 201 | }, 202 | 203 | bind: function(elem, name) { 204 | var binding = new Binding(name, this, elem, function(elem, value) { 205 | elem.value = value; 206 | }); 207 | if (this.isConnected()) { 208 | binding.update(); 209 | } 210 | this.bindings[name] = binding; 211 | return binding; 212 | }, 213 | 214 | bindReadonly: function(elem, name) { 215 | var binding = new Binding(name, this, elem, function(elem, value) { 216 | elem.innerText = value; 217 | }); 218 | if (this.isConnected()) { 219 | binding.update(); 220 | } 221 | this.bindings[name] = binding; 222 | return binding; 223 | }, 224 | 225 | connect: function(url) { 226 | url = url || this.url; 227 | var manager = this; 228 | var websocket = new WebSocket(url); 229 | websocket.addEventListener("open", function() { 230 | manager.websocket = websocket; 231 | manager._updateBindings(); 232 | }); 233 | websocket.addEventListener("error", function(event) { 234 | manager.dispatchEvent("error", event); 235 | }); 236 | websocket.addEventListener("message", function(event) { 237 | manager._messageReceived(event.data); 238 | }); 239 | websocket.addEventListener("close", function(event) { 240 | manager.dispatchEvent("close", event); 241 | manager._clearActiveRequests(); 242 | }); 243 | }, 244 | 245 | disconnect: function() { 246 | if (this.websocket) { 247 | this.websocket.close(); 248 | } 249 | this._clearActiveRequests(); 250 | }, 251 | 252 | isConnected: function() { 253 | return this.websocket && this.websocket.readyState === 1; 254 | } 255 | }; 256 | 257 | function BindingManager(url) { 258 | this.requestId = 0; 259 | this.activeRequests = { }; 260 | this.websocket = null; 261 | this.requestTimeoutMs = 10000; 262 | this.url = url; 263 | this.eventListeners = { 264 | error: [], 265 | close: [] 266 | }; 267 | this.bindings = { }; 268 | } 269 | 270 | BindingManager.prototype = bindingManagerPrototype; 271 | //Object.assign(BindingManager.prototype, bindingManagerPrototype); 272 | -------------------------------------------------------------------------------- /webroot/bootstrap.bundle.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TobleMiner/dc-ups-firmware/6e7fbdc9c6549408a07bb3e2367fbed680d9c87d/webroot/bootstrap.bundle.min.js -------------------------------------------------------------------------------- /webroot/bootstrap.min.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TobleMiner/dc-ups-firmware/6e7fbdc9c6549408a07bb3e2367fbed680d9c87d/webroot/bootstrap.min.css -------------------------------------------------------------------------------- /webroot/dcin.thtml: -------------------------------------------------------------------------------- 1 | <html> 2 | <head> 3 | <meta charset="utf-8"> 4 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> 5 | <title>DC UPS 6 | {{include,file=resources.inc}} 7 | 8 | 9 | 10 | {{include,file=navbar.thtml}} 11 |
12 |
13 | {{include,file=status_panel.thtml,status_panel_title=DC input,status_panel_id=dcin,lm75=lm75_charger,ina=ina_dc_in}} 14 |
15 |
16 |
17 |
Input settings
18 |
19 |
20 | Current limit 21 | 22 | mA 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /webroot/dcout.thtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DC UPS 6 | {{include,file=resources.inc}} 7 | 8 | 9 | 10 | {{include,file=navbar.thtml}} 11 |
12 |
13 | {{include,file=status_panel.thtml,status_panel_title=DC output,status_panel_id=dcout,lm75=lm75_dc_out,ina=ina_dc_out}} 14 |
15 |
16 |
17 |
Output hardware settings
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
Voltage:{{dcout.voltage}}
26 |
27 |
28 |
29 |
30 |
31 | {{include,file=dcout_settings.thtml,output=1}} 32 | {{include,file=dcout_settings.thtml,output=2}} 33 | {{include,file=dcout_settings.thtml,output=3}} 34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /webroot/dcout_settings.thtml: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
DC output {{variable,name=output}}
5 |
6 | 7 | 8 |
9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /webroot/index.js: -------------------------------------------------------------------------------- 1 | window.onerror = function(error, source, lineno, colno) { 2 | alert(error + source + lineno + colno); 3 | }; 4 | 5 | window.onload = function() { 6 | var bindingManager = new BindingManager("ws://168.119.52.218:9655"); 7 | var binding = bindingManager.bindReadonly(document.getElementsByClassName("js-wifi")[0], "wifi.status"); 8 | var binding = bindingManager.bindReadonly(document.getElementsByClassName("js-ethernet")[0], "ethernet.status"); 9 | var binding = bindingManager.bind(document.getElementsByClassName("js-input-number")[0], "stored.number"); 10 | bindingManager.addEventListener("error", function(event) { 11 | console.log("Error: " + event); 12 | }); 13 | bindingManager.connect(); 14 | }; 15 | -------------------------------------------------------------------------------- /webroot/index.thtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DC UPS 6 | {{include,file=resources.inc}} 7 | 8 | 9 | 10 | {{include,file=navbar.thtml}} 11 |
12 |
13 |
14 |
15 |
16 |
System
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
WiFi:--
Ethernet:--
Uptime:--
33 |
34 |
35 |
36 | {{include,file=status_panel.thtml,status_panel_title=DC input,status_panel_id=dcin,lm75=lm75_charger,ina=ina_dc_in}} 37 |
38 |
39 | {{include,file=status_panel.thtml,status_panel_title=DC output,status_panel_id=dcout,lm75=lm75_dc_out,ina=ina_dc_out}} 40 | {{include,file=status_panel.thtml,status_panel_title=USB output,status_panel_id=usbout,lm75=lm75_usb_out,ina=ina_usb_out}} 41 |
42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /webroot/jquery-1.8.3.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TobleMiner/dc-ups-firmware/6e7fbdc9c6549408a07bb3e2367fbed680d9c87d/webroot/jquery-1.8.3.min.js -------------------------------------------------------------------------------- /webroot/navbar.thtml: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /webroot/resources.inc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /webroot/status_panel.thtml: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
{{variable,name=status_panel_title}}
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
Voltage:{{status_panel.voltage}}
Current:{{status_panel.current}}
Power:{{status_panel.power}}
Temperature:{{status_panel.temperature}}
25 |
26 |
27 |
28 | --------------------------------------------------------------------------------