Skip to content

Commit

Permalink
Refactor existing code to support WebGUI for Safari
Browse files Browse the repository at this point in the history
  • Loading branch information
eamars committed Mar 15, 2024
1 parent 5923a63 commit c3ced3d
Show file tree
Hide file tree
Showing 17 changed files with 312 additions and 81 deletions.
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
3 changes: 3 additions & 0 deletions scripts/html2header.py
Original file line number Diff line number Diff line change
Expand Up @@ -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('"', '\\"')
Expand Down
12 changes: 6 additions & 6 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/button.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ ButtonEncoderEvent_t button_wait_for_input(bool block) {
}

return button_encoder_event;
}
}
176 changes: 125 additions & 51 deletions src/charge_mode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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();
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand All @@ -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) {
Expand All @@ -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 {
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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();
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -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;
}
Loading

0 comments on commit c3ced3d

Please sign in to comment.