#include #include "ad5940.h" #include "ad5941_port.h" #include "eis.h" #include "echem.h" #include "protocol.h" #include "wifi_transport.h" #include "temp.h" #include "refs.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "nvs_flash.h" #include "esp_netif.h" #include "esp_event.h" #include "esp_timer.h" #define AD5941_EXPECTED_ADIID 0x4144 static EISConfig cfg; static uint16_t measurement_counter = 0; static EISPoint results[EIS_MAX_POINTS]; static LSVPoint lsv_results[ECHEM_MAX_POINTS]; static AmpPoint amp_results[ECHEM_MAX_POINTS]; static ClPoint cl_results[ECHEM_MAX_POINTS]; static RefStore ref_store; static void do_sweep(void) { eis_init(&cfg); uint32_t ts_ms = (uint32_t)(esp_timer_get_time() / 1000); measurement_counter++; uint32_t n = eis_calc_num_points(&cfg); send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz, ts_ms, measurement_counter); int got = eis_sweep(results, n, send_eis_point); printf("Sweep complete: %d points\n", got); send_sweep_end(); } void app_main(void) { esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { nvs_flash_erase(); nvs_flash_init(); } printf("EIS4: AD5941 bring-up\n"); AD5940_MCUResourceInit(NULL); vTaskDelay(pdMS_TO_TICKS(50)); AD5940_HWReset(); vTaskDelay(pdMS_TO_TICKS(200)); AD5940_Initialize(); uint32_t adiid = AD5940_ReadReg(REG_AFECON_ADIID); printf("ADIID: 0x%04lX %s\n", adiid, adiid == AD5941_EXPECTED_ADIID ? "(OK)" : "(FAIL)"); if (adiid != AD5941_EXPECTED_ADIID) return; eis_default_config(&cfg); eis_load_open_cal(); eis_load_cell_k(); eis_load_cl_factor(); eis_load_ph_cal(); temp_init(); esp_netif_init(); esp_event_loop_create_default(); protocol_init(); wifi_transport_init(); printf("EIS4: WiFi transport ready, waiting for clients\n"); Command cmd; for (;;) { if (protocol_recv_command(&cmd, UINT32_MAX) != 0) continue; switch (cmd.type) { case CMD_SET_SWEEP: cfg.freq_start_hz = cmd.sweep.freq_start; cfg.freq_stop_hz = cmd.sweep.freq_stop; cfg.points_per_decade = cmd.sweep.ppd; eis_reconfigure(&cfg); printf("Sweep: %.0f-%.0f Hz, %u ppd\n", cfg.freq_start_hz, cfg.freq_stop_hz, cfg.points_per_decade); break; case CMD_SET_RTIA: if (cmd.rtia < RTIA_COUNT) { cfg.rtia = cmd.rtia; eis_reconfigure(&cfg); printf("RTIA: %u\n", cfg.rtia); } break; case CMD_SET_RCAL: if (cmd.rcal < RCAL_COUNT) { cfg.rcal = cmd.rcal; eis_reconfigure(&cfg); printf("RCAL: %u\n", cfg.rcal); } break; case CMD_SET_ELECTRODE: if (cmd.electrode < ELEC_COUNT) { cfg.electrode = cmd.electrode; eis_reconfigure(&cfg); printf("Electrode: %s\n", cfg.electrode == ELEC_3WIRE ? "3-wire (CE0/RE0/SE0)" : "4-wire (AIN)"); } break; case CMD_START_SWEEP: printf("Config: %.0f-%.0f Hz, %u ppd, rtia=%u, rcal=%u, elec=%s\n", cfg.freq_start_hz, cfg.freq_stop_hz, cfg.points_per_decade, cfg.rtia, cfg.rcal, cfg.electrode == ELEC_3WIRE ? "3-wire" : "4-wire"); do_sweep(); break; case CMD_GET_CONFIG: send_config(&cfg); break; case CMD_START_LSV: { LSVConfig lsv_cfg; lsv_cfg.v_start = cmd.lsv.v_start; lsv_cfg.v_stop = cmd.lsv.v_stop; lsv_cfg.scan_rate = cmd.lsv.scan_rate; lsv_cfg.lp_rtia = cmd.lsv.lp_rtia; uint32_t max_pts = ECHEM_MAX_POINTS; if (cmd.lsv.num_points > 0 && cmd.lsv.num_points < ECHEM_MAX_POINTS) max_pts = cmd.lsv.num_points; printf("LSV: %.0f-%.0f mV, %.0f mV/s, rtia=%u, max_pts=%lu\n", lsv_cfg.v_start, lsv_cfg.v_stop, lsv_cfg.scan_rate, lsv_cfg.lp_rtia, (unsigned long)max_pts); uint32_t ts_ms = (uint32_t)(esp_timer_get_time() / 1000); measurement_counter++; uint32_t n = echem_lsv_calc_steps(&lsv_cfg, max_pts); send_lsv_start(n, lsv_cfg.v_start, lsv_cfg.v_stop, ts_ms, measurement_counter); int got = echem_lsv(&lsv_cfg, lsv_results, max_pts, send_lsv_point); printf("LSV complete: %d points\n", got); send_lsv_end(); break; } case CMD_START_AMP: { AmpConfig amp_cfg; amp_cfg.v_hold = cmd.amp.v_hold; amp_cfg.interval_ms = cmd.amp.interval_ms; amp_cfg.duration_s = cmd.amp.duration_s; amp_cfg.lp_rtia = cmd.amp.lp_rtia; printf("Amp: %.0f mV, %.0f ms interval, %.0f s\n", amp_cfg.v_hold, amp_cfg.interval_ms, amp_cfg.duration_s); { uint32_t ts_ms = (uint32_t)(esp_timer_get_time() / 1000); measurement_counter++; send_amp_start(amp_cfg.v_hold, ts_ms, measurement_counter); } int got = echem_amp(&_cfg, amp_results, ECHEM_MAX_POINTS, send_amp_point); printf("Amp complete: %d points\n", got); send_amp_end(); break; } case CMD_GET_TEMP: send_temp(temp_get()); break; case CMD_START_PH: { PhConfig ph_cfg; ph_cfg.stabilize_s = cmd.ph.stabilize_s; ph_cfg.temp_c = temp_get(); printf("pH: stabilize %.0f s, temp %.1f C\n", ph_cfg.stabilize_s, ph_cfg.temp_c); PhResult ph_result; echem_ph_ocp(&ph_cfg, &ph_result); printf("pH: OCP=%.1f mV, pH=%.2f\n", ph_result.v_ocp_mv, ph_result.ph); { uint32_t ts_ms = (uint32_t)(esp_timer_get_time() / 1000); measurement_counter++; send_ph_result(ph_result.v_ocp_mv, ph_result.ph, ph_result.temp_c, ts_ms, measurement_counter); } break; } case CMD_START_CLEAN: printf("Clean: %.0f mV, %.0f s\n", cmd.clean.v_mv, cmd.clean.duration_s); echem_clean(cmd.clean.v_mv, cmd.clean.duration_s); break; case CMD_START_REFS: printf("Ref collection starting\n"); refs_collect(&ref_store, &cfg); break; case CMD_GET_REFS: refs_send(&ref_store); break; case CMD_CLEAR_REFS: refs_clear(&ref_store); printf("Refs cleared\n"); break; case CMD_OPEN_CAL: { printf("Open-circuit cal starting\n"); eis_init(&cfg); uint32_t ts_ms = (uint32_t)(esp_timer_get_time() / 1000); measurement_counter++; uint32_t n = eis_calc_num_points(&cfg); send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz, ts_ms, measurement_counter); int got = eis_open_cal(results, n, send_eis_point); printf("Open-circuit cal: %d points\n", got); send_sweep_end(); break; } case CMD_CLEAR_OPEN_CAL: eis_clear_open_cal(); printf("Open-circuit cal cleared\n"); break; case CMD_SET_CELL_K: eis_set_cell_k(cmd.cell_k); send_cell_k(cmd.cell_k); printf("Cell K set: %.4f cm^-1\n", cmd.cell_k); break; case CMD_GET_CELL_K: send_cell_k(eis_get_cell_k()); break; case CMD_SET_CL_FACTOR: eis_set_cl_factor(cmd.cl_factor); send_cl_factor(cmd.cl_factor); printf("Cl factor set: %.6f\n", cmd.cl_factor); break; case CMD_GET_CL_FACTOR: send_cl_factor(eis_get_cl_factor()); break; case CMD_SET_PH_CAL: eis_set_ph_cal(cmd.ph_cal.slope, cmd.ph_cal.offset); send_ph_cal(cmd.ph_cal.slope, cmd.ph_cal.offset); printf("pH cal set: slope=%.4f offset=%.4f\n", cmd.ph_cal.slope, cmd.ph_cal.offset); break; case CMD_GET_PH_CAL: send_ph_cal(eis_get_ph_slope(), eis_get_ph_offset()); break; case CMD_START_CL: { ClConfig cl_cfg; cl_cfg.v_cond = cmd.cl.v_cond; cl_cfg.t_cond_ms = cmd.cl.t_cond_ms; cl_cfg.v_free = cmd.cl.v_free; cl_cfg.v_total = cmd.cl.v_total; cl_cfg.t_dep_ms = cmd.cl.t_dep_ms; cl_cfg.t_meas_ms = cmd.cl.t_meas_ms; cl_cfg.lp_rtia = cmd.cl.lp_rtia; uint32_t n_per = (uint32_t)(cl_cfg.t_meas_ms / 50.0f + 0.5f); if (n_per < 2) n_per = 2; { uint32_t ts_ms = (uint32_t)(esp_timer_get_time() / 1000); measurement_counter++; send_cl_start(2 * n_per, ts_ms, measurement_counter); } ClResult cl_result; int got = echem_chlorine(&cl_cfg, cl_results, ECHEM_MAX_POINTS, &cl_result, send_cl_point); printf("Cl complete: %d points, free=%.3f uA, total=%.3f uA\n", got, cl_result.i_free_ua, cl_result.i_total_ua); send_cl_result(cl_result.i_free_ua, cl_result.i_total_ua); send_cl_end(); break; } case CMD_SESSION_CREATE: { uint8_t id = session_create(cmd.session_create.name, cmd.session_create.name_len); if (id != 0xFF) { send_session_created(id, cmd.session_create.name, cmd.session_create.name_len); printf("Session created: %u \"%.*s\"\n", id, cmd.session_create.name_len, cmd.session_create.name); } break; } case CMD_SESSION_SWITCH: if (session_switch(cmd.session_switch.id) == 0) { send_session_switched(cmd.session_switch.id); printf("Session switched: %u\n", cmd.session_switch.id); } break; case CMD_SESSION_LIST: send_session_list(); break; case CMD_SESSION_RENAME: if (session_rename(cmd.session_rename.id, cmd.session_rename.name, cmd.session_rename.name_len) == 0) { send_session_renamed(cmd.session_rename.id, cmd.session_rename.name, cmd.session_rename.name_len); printf("Session renamed: %u \"%.*s\"\n", cmd.session_rename.id, cmd.session_rename.name_len, cmd.session_rename.name); } break; case CMD_HEARTBEAT: send_client_list((uint8_t)wifi_get_client_count()); send_session_list(); send_config(&cfg); break; } } }