diff --git a/.vscode/settings.json b/.vscode/settings.json index 9f1d6db..719111c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,6 +29,9 @@ "queue.h": "c", "limits": "c", "*.tcc": "c", - "scale.h": "c" + "scale.h": "c", + "rotary_button.h": "c", + "freertos.h": "c", + "lwipopts_examples_common.h": "c" } } diff --git a/scripts/html2header.py b/scripts/html2header.py index 0373453..408eb7c 100644 --- a/scripts/html2header.py +++ b/scripts/html2header.py @@ -51,6 +51,9 @@ def main(input_filepth, output_filepath, skip_minify): else: minified_html = input_file + # Append HTML header + minified_html = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" + minified_html + # Escape characters escaped_html = minified_html.replace("\\", "\\\\") escaped_html = escaped_html.replace('"', '\\"') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b3b543b..71f1b7e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,17 +25,17 @@ add_custom_command( add_dependencies("${TARGET_NAME}" generate_dashboard_header) -# mobile_portal.html +# mobile_frontend.html add_custom_target( generate_mobile_portal_header ALL - DEPENDS "${SRC_DIRECTORY}/generated/mobile_portal.html.h" + DEPENDS "${SRC_DIRECTORY}/generated/mobile_frontend.html.h" ) add_custom_command( - OUTPUT "${SRC_DIRECTORY}/generated/mobile_portal.html.h" - DEPENDS "${SRC_DIRECTORY}/html/mobile_portal.html" - COMMAND "${Python_EXECUTABLE}" "${SCRIPTS_DIRECTORY}/html2header.py" -vv --no-minify -f ${SRC_DIRECTORY}/html/mobile_portal.html -o ${SRC_DIRECTORY}/generated/mobile_portal.html.h - COMMENT "Generating mobile_portal.html header" + OUTPUT "${SRC_DIRECTORY}/generated/mobile_frontend.html.h" + DEPENDS "${SRC_DIRECTORY}/html/mobile_frontend.html" + COMMAND "${Python_EXECUTABLE}" "${SCRIPTS_DIRECTORY}/html2header.py" -vv --no-minify -f ${SRC_DIRECTORY}/html/mobile_frontend.html -o ${SRC_DIRECTORY}/generated/mobile_frontend.html.h + COMMENT "Generating mobile_frontend.html header" ) add_dependencies("${TARGET_NAME}" generate_mobile_portal_header) diff --git a/src/button.c b/src/button.c index 9fcaf9c..26cef89 100644 --- a/src/button.c +++ b/src/button.c @@ -29,4 +29,4 @@ ButtonEncoderEvent_t button_wait_for_input(bool block) { } return button_encoder_event; -} \ No newline at end of file +} diff --git a/src/charge_mode.cpp b/src/charge_mode.cpp index 5a4a8cb..9235235 100644 --- a/src/charge_mode.cpp +++ b/src/charge_mode.cpp @@ -51,16 +51,17 @@ const eeprom_charge_mode_data_t default_charge_mode_data = { TaskHandle_t scale_measurement_render_task_handler = NULL; static char title_string[30]; +// Menu system +extern AppState_t exit_state; +extern QueueHandle_t encoder_event_queue; +// Definitions typedef enum { - CHARGE_MODE_WAIT_FOR_ZERO, - CHARGE_MODE_WAIT_FOR_COMPLETE, - CHARGE_MODE_WAIT_FOR_CUP_REMOVAL, - CHARGE_MODE_WAIT_FOR_CUP_RETURN, - CHARGE_MODE_EXIT, - -} ChargeModeState_t; + CHARGE_MODE_EVENT_NO_EVENT = (1 << 0), + CHARGE_MODE_EVENT_UNDER_CHARGE = (1 << 1), + CHARGE_MODE_EVENT_OVER_CHARGE = (1 << 2), +} ChargeModeEventBit_t; void scale_measurement_render_task(void *p) { @@ -107,7 +108,7 @@ void scale_measurement_render_task(void *p) { } -ChargeModeState_t charge_mode_wait_for_zero(ChargeModeState_t prev_state) { +void charge_mode_wait_for_zero() { // Set colour to not ready neopixel_led_set_colour( NEOPIXEL_LED_DEFAULT_COLOUR, @@ -129,7 +130,8 @@ ChargeModeState_t charge_mode_wait_for_zero(ChargeModeState_t prev_state) { // Non block waiting for the input ButtonEncoderEvent_t button_encoder_event = button_wait_for_input(false); if (button_encoder_event == BUTTON_RST_PRESSED) { - return CHARGE_MODE_EXIT; + charge_mode_config.charge_mode_state = CHARGE_MODE_EXIT; + return; } else if (button_encoder_event == BUTTON_ENCODER_PRESSED) { scale_config.scale_handle->force_zero(); @@ -153,10 +155,10 @@ ChargeModeState_t charge_mode_wait_for_zero(ChargeModeState_t prev_state) { vTaskDelayUntil(&last_measurement_tick, pdMS_TO_TICKS(300)); } - return CHARGE_MODE_WAIT_FOR_COMPLETE; + charge_mode_config.charge_mode_state = CHARGE_MODE_WAIT_FOR_COMPLETE; } -ChargeModeState_t charge_mode_wait_for_complete(ChargeModeState_t prev_state) { +void charge_mode_wait_for_complete() { // Set colour to under charge neopixel_led_set_colour( NEOPIXEL_LED_DEFAULT_COLOUR, @@ -197,7 +199,8 @@ ChargeModeState_t charge_mode_wait_for_complete(ChargeModeState_t prev_state) { // Non block waiting for the input ButtonEncoderEvent_t button_encoder_event = button_wait_for_input(false); if (button_encoder_event == BUTTON_RST_PRESSED) { - return CHARGE_MODE_EXIT; + charge_mode_config.charge_mode_state = CHARGE_MODE_EXIT; + return; } // Run the PID controlled loop to start charging @@ -259,10 +262,10 @@ ChargeModeState_t charge_mode_wait_for_complete(ChargeModeState_t prev_state) { vTaskDelay(pdMS_TO_TICKS(20)); // Wait for other tasks to complete - return CHARGE_MODE_WAIT_FOR_CUP_REMOVAL; + charge_mode_config.charge_mode_state = CHARGE_MODE_WAIT_FOR_CUP_REMOVAL; } -ChargeModeState_t charge_mode_wait_for_cup_removal(ChargeModeState_t prev_state) { +void charge_mode_wait_for_cup_removal() { // Update current status snprintf(title_string, sizeof(title_string), "Remove Cup", charge_mode_config.target_charge_weight); @@ -284,6 +287,9 @@ ChargeModeState_t charge_mode_wait_for_cup_removal(ChargeModeState_t prev_state) charge_mode_config.eeprom_charge_mode_data.neopixel_over_charge_colour, true ); + + // Set over charge + charge_mode_config.charge_mode_event |= CHARGE_MODE_EVENT_OVER_CHARGE; } // Under charged else if (error >= charge_mode_config.eeprom_charge_mode_data.fine_stop_threshold) { @@ -293,6 +299,10 @@ ChargeModeState_t charge_mode_wait_for_cup_removal(ChargeModeState_t prev_state) charge_mode_config.eeprom_charge_mode_data.neopixel_under_charge_colour, true ); + + // Set under charge flag + charge_mode_config.charge_mode_event |= CHARGE_MODE_EVENT_UNDER_CHARGE; + } // Normal else { @@ -302,6 +312,9 @@ ChargeModeState_t charge_mode_wait_for_cup_removal(ChargeModeState_t prev_state) charge_mode_config.eeprom_charge_mode_data.neopixel_normal_charge_colour, true ); + + // Clear over and under charge bit + charge_mode_config.charge_mode_event &= ~(CHARGE_MODE_EVENT_UNDER_CHARGE | CHARGE_MODE_EVENT_OVER_CHARGE); } // Stop condition: 5 stable measurements in 300ms apart (1.5 seconds minimum) @@ -311,7 +324,8 @@ ChargeModeState_t charge_mode_wait_for_cup_removal(ChargeModeState_t prev_state) // Non block waiting for the input ButtonEncoderEvent_t button_encoder_event = button_wait_for_input(false); if (button_encoder_event == BUTTON_RST_PRESSED) { - return CHARGE_MODE_EXIT; + charge_mode_config.charge_mode_state = CHARGE_MODE_EXIT; + return; } // Perform measurement @@ -337,10 +351,10 @@ ChargeModeState_t charge_mode_wait_for_cup_removal(ChargeModeState_t prev_state) // Reset LED to default colour neopixel_led_set_colour(NEOPIXEL_LED_DEFAULT_COLOUR, NEOPIXEL_LED_DEFAULT_COLOUR, NEOPIXEL_LED_DEFAULT_COLOUR, true); - return CHARGE_MODE_WAIT_FOR_CUP_RETURN; + charge_mode_config.charge_mode_state = CHARGE_MODE_WAIT_FOR_CUP_RETURN; } -ChargeModeState_t charge_mode_wait_for_cup_return(ChargeModeState_t prev_state) { +void charge_mode_wait_for_cup_return() { // Set colour to not ready neopixel_led_set_colour( NEOPIXEL_LED_DEFAULT_COLOUR, @@ -359,7 +373,8 @@ ChargeModeState_t charge_mode_wait_for_cup_return(ChargeModeState_t prev_state) // Non block waiting for the input ButtonEncoderEvent_t button_encoder_event = button_wait_for_input(false); if (button_encoder_event == BUTTON_RST_PRESSED) { - return CHARGE_MODE_EXIT; + charge_mode_config.charge_mode_state = CHARGE_MODE_EXIT; + return; } else if (button_encoder_event == BUTTON_ENCODER_PRESSED) { scale_config.scale_handle->force_zero(); @@ -380,37 +395,37 @@ ChargeModeState_t charge_mode_wait_for_cup_return(ChargeModeState_t prev_state) vTaskDelayUntil(&last_sample_tick, pdMS_TO_TICKS(20)); } - return CHARGE_MODE_WAIT_FOR_ZERO; + charge_mode_config.charge_mode_state = CHARGE_MODE_WAIT_FOR_ZERO; } -uint8_t charge_mode_menu() { +uint8_t charge_mode_menu(bool charge_mode_skip_user_input) { // Reset LED to default colour neopixel_led_set_colour(NEOPIXEL_LED_DEFAULT_COLOUR, NEOPIXEL_LED_DEFAULT_COLOUR, NEOPIXEL_LED_DEFAULT_COLOUR, true); - // Create target weight - switch (charge_mode_config.eeprom_charge_mode_data.decimal_places) { - case DP_2: - charge_mode_config.target_charge_weight = charge_weight_digits[4] * 100 + \ - charge_weight_digits[3] * 10 + \ - charge_weight_digits[2] * 1 + \ - charge_weight_digits[1] * 0.1 + \ - charge_weight_digits[0] * 0.01; - break; - case DP_3: - charge_mode_config.target_charge_weight = charge_weight_digits[4] * 10 + \ - charge_weight_digits[3] * 1 + \ - charge_weight_digits[2] * 0.1 + \ - charge_weight_digits[1] * 0.01 + \ - charge_weight_digits[0] * 0.001; - break; - default: - charge_mode_config.target_charge_weight = 0; - break; + // Create target weight, if the charge mode weight is built by charge_weight_digits + if (!charge_mode_skip_user_input) { + switch (charge_mode_config.eeprom_charge_mode_data.decimal_places) { + case DP_2: + charge_mode_config.target_charge_weight = charge_weight_digits[4] * 100 + \ + charge_weight_digits[3] * 10 + \ + charge_weight_digits[2] * 1 + \ + charge_weight_digits[1] * 0.1 + \ + charge_weight_digits[0] * 0.01; + break; + case DP_3: + charge_mode_config.target_charge_weight = charge_weight_digits[4] * 10 + \ + charge_weight_digits[3] * 1 + \ + charge_weight_digits[2] * 0.1 + \ + charge_weight_digits[1] * 0.01 + \ + charge_weight_digits[0] * 0.001; + break; + default: + charge_mode_config.target_charge_weight = 0; + break; + } } - printf("Target Charge Weight: %f\n", charge_mode_config.target_charge_weight); - // If the display task is never created then we shall create one, otherwise we shall resume the task if (scale_measurement_render_task_handler == NULL) { // The render task shall have lower priority than the current one @@ -425,22 +440,22 @@ uint8_t charge_mode_menu() { motor_enable(SELECT_COARSE_TRICKLER_MOTOR, true); motor_enable(SELECT_FINE_TRICKLER_MOTOR, true); - ChargeModeState_t state = CHARGE_MODE_WAIT_FOR_ZERO; + charge_mode_config.charge_mode_state = CHARGE_MODE_WAIT_FOR_ZERO; bool quit = false; while (quit == false) { - switch (state) { + switch (charge_mode_config.charge_mode_state) { case CHARGE_MODE_WAIT_FOR_ZERO: - state = charge_mode_wait_for_zero(state); + charge_mode_wait_for_zero(); break; case CHARGE_MODE_WAIT_FOR_COMPLETE: - state = charge_mode_wait_for_complete(state); + charge_mode_wait_for_complete(); break; case CHARGE_MODE_WAIT_FOR_CUP_REMOVAL: - state = charge_mode_wait_for_cup_removal(state); + charge_mode_wait_for_cup_removal(); break; case CHARGE_MODE_WAIT_FOR_CUP_RETURN: - state = charge_mode_wait_for_cup_return(state); + charge_mode_wait_for_cup_return(); break; case CHARGE_MODE_EXIT: default: @@ -572,6 +587,7 @@ bool http_rest_charge_mode_config(struct fs_file *file, int num_params, char *pa // Response snprintf(charge_mode_json_buffer, sizeof(charge_mode_json_buffer), + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n" "{\"c1\":\"#%06x\",\"c2\":\"#%06x\",\"c3\":\"#%06x\",\"c4\":\"#%06x\"," "\"c5\":%.3f,\"c6\":%.3f,\"c7\":%.3f,\"c8\":%.3f,\"c9\":%d}", charge_mode_config.eeprom_charge_mode_data.neopixel_normal_charge_colour, @@ -595,21 +611,36 @@ bool http_rest_charge_mode_config(struct fs_file *file, int num_params, char *pa } -bool http_rest_charge_mode_setpoint(struct fs_file *file, int num_params, char *params[], char *values[]) { +bool http_rest_charge_mode_set_point(struct fs_file *file, int num_params, char *params[], char *values[]) { + // Mappings + // c0 (float): Charge weight set point (unitless) + static char charge_mode_json_buffer[64]; + float target_charge_weight = -1; // Control for (int idx = 0; idx < num_params; idx += 1) { - if (strcmp(params[idx], "target_charge_weight") == 0) { - float target_charge_weight = strtof(values[idx], NULL); + if (strcmp(params[idx], "c0") == 0) { + target_charge_weight = strtof(values[idx], NULL); charge_mode_config.target_charge_weight = target_charge_weight; } } + // Perform action + if (target_charge_weight > 0) { + // Set exit_status for the menu + exit_state = APP_STATE_ENTER_CHARGE_MODE; + + // Then signal the menu to stop + ButtonEncoderEvent_t button_event = OVERRIDE_FROM_REST; + xQueueSend(encoder_event_queue, &button_event, portMAX_DELAY); + } + // Response snprintf(charge_mode_json_buffer, sizeof(charge_mode_json_buffer), - "{\"target_charge_weight\":%0.3f}", + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n" + "{\"c0\":%0.3f}", charge_mode_config.target_charge_weight); size_t data_length = strlen(charge_mode_json_buffer); @@ -618,5 +649,48 @@ bool http_rest_charge_mode_setpoint(struct fs_file *file, int num_params, char * file->index = data_length; file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + return true; +} + + +bool http_rest_charge_mode_status(struct fs_file *file, int num_params, char *params[], char *values[]) { + // Mappings + // s0 (float): Charge weight set point (unitless) + // s1 (float): Current weight (unitless) + // s2 (ChargeModeState_t | int): Charge mode state + // s3 (uint32_t): Charge mode event + + static char charge_mode_json_buffer[128]; + + // Control + for (int idx = 0; idx < num_params; idx += 1) { + if (strcmp(params[idx], "s2") == 0) { + charge_mode_config.charge_mode_state = (ChargeModeState_t) atoi(values[idx]); + + // Also send termination request via button + ButtonEncoderEvent_t button_event = BUTTON_RST_PRESSED; + xQueueSend(encoder_event_queue, &button_event, portMAX_DELAY); + } + } + + // Response + snprintf(charge_mode_json_buffer, + sizeof(charge_mode_json_buffer), + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n" + "{\"s0\":%0.3f,\"s1\":%0.3f,\"s2\":%d,\"s3\":%lu}", + charge_mode_config.target_charge_weight, + scale_get_current_measurement(), + (int) charge_mode_config.charge_mode_state, + charge_mode_config.charge_mode_event); + + // Clear events + charge_mode_config.charge_mode_event = 0; + + size_t data_length = strlen(charge_mode_json_buffer); + file->data = charge_mode_json_buffer; + file->len = data_length; + file->index = data_length; + file->flags = FS_FILE_FLAGS_HEADER_INCLUDED; + return true; } \ No newline at end of file diff --git a/src/charge_mode.h b/src/charge_mode.h index 701689f..d084822 100644 --- a/src/charge_mode.h +++ b/src/charge_mode.h @@ -10,6 +10,14 @@ #define WEIGHT_STRING_LEN 8 +typedef enum { + CHARGE_MODE_WAIT_FOR_ZERO = 0, + CHARGE_MODE_WAIT_FOR_COMPLETE = 1, + CHARGE_MODE_WAIT_FOR_CUP_REMOVAL = 2, + CHARGE_MODE_WAIT_FOR_CUP_RETURN = 3, + CHARGE_MODE_EXIT = 4, +} ChargeModeState_t; + typedef struct { uint16_t charge_mode_data_rev; @@ -32,11 +40,13 @@ typedef struct { typedef struct { eeprom_charge_mode_data_t eeprom_charge_mode_data; float target_charge_weight; + uint32_t charge_mode_event; + ChargeModeState_t charge_mode_state; } charge_mode_config_t; bool charge_mode_config_init(void); -uint8_t charge_mode_menu(); +uint8_t charge_mode_menu(bool charge_mode_skip_user_input); // C Functions #ifdef __cplusplus @@ -47,7 +57,9 @@ bool charge_mode_config_save(void); // REST interface bool http_rest_charge_mode_config(struct fs_file *file, int num_params, char *params[], char *values[]); -bool http_rest_charge_mode_setpoint(struct fs_file *file, int num_params, char *params[], char *values[]); +bool http_rest_charge_mode_set_point(struct fs_file *file, int num_params, char *params[], char *values[]); +bool http_rest_charge_mode_status(struct fs_file *file, int num_params, char *params[], char *values[]); + #ifdef __cplusplus } // __cplusplus diff --git a/src/eeprom.c b/src/eeprom.c index efd3f66..11ca48c 100644 --- a/src/eeprom.c +++ b/src/eeprom.c @@ -230,6 +230,7 @@ bool http_rest_system_control(struct fs_file *file, int num_params, char *params // Response snprintf(eeprom_config_json_buffer, sizeof(eeprom_config_json_buffer), + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n" "{\"s0\":\"%s\",\"s1\":\"%s\",\"s2\":\"%s\",\"s3\":\"%s\",\"s4\":%s,\"s5\":%s,\"s6\":%s}", metadata.unique_id, version_string, vcs_hash, build_type, boolean_to_string(save_to_eeprom_flag), diff --git a/src/html/mobile_portal.html b/src/html/mobile_frontend.html similarity index 63% rename from src/html/mobile_portal.html rename to src/html/mobile_frontend.html index 68fca00..d8746da 100644 --- a/src/html/mobile_portal.html +++ b/src/html/mobile_frontend.html @@ -25,9 +25,6 @@ -