diff --git a/main.go b/main.go index b2730de..b2a6fd8 100644 --- a/main.go +++ b/main.go @@ -11,39 +11,38 @@ import ( "time" ) +type VoltageBatch struct { + Voltages []float64 `json:"voltages"` +} + var dataFile *os.File var berlinTZ *time.Location -type VoltageData struct { - Voltage float64 `json:"voltage"` -} - func init() { var err error berlinTZ, err = time.LoadLocation("Europe/Berlin") if err != nil { - log.Fatalf("Failed to load Berlin timezone: %v", err) + log.Fatal(err) } } func initDataFile() { - // Ensure the data/ directory exists - if err := os.MkdirAll("data", os.ModePerm); err != nil { - log.Fatalf("Failed to create data directory: %v", err) - } + _ = os.MkdirAll("data", 0755) - // Generate a filename with timestamp in Berlin local time - filename := filepath.Join("data", fmt.Sprintf("voltages_%s.csv", time.Now().In(berlinTZ).Format("20060102_150405"))) - file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + filename := filepath.Join( + "data", + fmt.Sprintf("voltages_%s.csv", + time.Now().In(berlinTZ).Format("20060102_150405")), + ) + + f, err := os.OpenFile(filename, + os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { - log.Fatalf("Failed to open data file: %v", err) + log.Fatal(err) } - dataFile = file - log.Printf("Logging voltage data to %s\n", filename) - - // Write CSV header - _, _ = dataFile.WriteString("timestamp,voltage\n") + dataFile = f + dataFile.WriteString("timestamp,voltage\n") } func handler(w http.ResponseWriter, r *http.Request) { @@ -59,24 +58,20 @@ func handler(w http.ResponseWriter, r *http.Request) { } defer r.Body.Close() - // Parse JSON - var v VoltageData - if err := json.Unmarshal(body, &v); err != nil { + var batch VoltageBatch + if err := json.Unmarshal(body, &batch); err != nil { http.Error(w, "invalid JSON", http.StatusBadRequest) return } - // Get current timestamp in Berlin - timestamp := time.Now().In(berlinTZ).Format("2006-01-02 15:04:05.000") + start := time.Now().In(berlinTZ) - // Print voltage to console - fmt.Printf("Received voltage: %f at %s\n", v.Voltage, timestamp) - - // Append CSV line to file - if dataFile != nil { - if _, err := dataFile.WriteString(fmt.Sprintf("%s,%.6f\n", timestamp, v.Voltage)); err != nil { - log.Printf("Failed to write to file: %v", err) - } + for i, v := range batch.Voltages { + ts := start.Add(time.Duration(i) * 33 * time.Millisecond) + dataFile.WriteString( + fmt.Sprintf("%s,%.6f\n", + ts.Format("2006-01-02 15:04:05.000"), v), + ) } w.WriteHeader(http.StatusOK) @@ -89,8 +84,6 @@ func main() { http.HandleFunc("/data", handler) - addr := ":8080" - log.Println("Listening on", addr) - log.Fatal(http.ListenAndServe(addr, nil)) + log.Fatal(http.ListenAndServe(":8080", nil)) } diff --git a/main/station_example_main.c b/main/station_example_main.c index 1afae19..1d5bd07 100755 --- a/main/station_example_main.c +++ b/main/station_example_main.c @@ -1,5 +1,5 @@ /* - Voltage measurement device over wifi from WiFi station Example + Voltage measurement device over WiFi (batched, 30 Hz) */ #include @@ -14,72 +14,62 @@ #include "esp_adc/adc_cali.h" #include "esp_adc/adc_oneshot.h" -#include "esp_adc/adc_cali.h" #include "esp_adc/adc_cali_scheme.h" #include "esp_http_client.h" /* ===================== CONSTS ===================== */ #define SERVER_URL "http://192.168.178.157:8080/data" -#define RMS_WINDOW_MS 50 -#define CHANNEL ADC_CHANNEL_6 -// TODO: Determine this +#define SAMPLE_RATE_HZ 30 +#define SAMPLE_PERIOD_MS (1000 / SAMPLE_RATE_HZ) +#define BATCH_SIZE 100 + +#define CHANNEL ADC_CHANNEL_6 #define CALIBRATION_FACTOR 1650.0f -// Settings for wifi #define EXAMPLE_ESP_WIFI_SSID "Jaguar" #define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD #define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_BOTH #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK -// Tag for logging static const char *TAG = "VoltageSensor"; /* ===================== WIFI ===================== */ -/* FreeRTOS event group to signal when we are connected*/ static EventGroupHandle_t s_wifi_event_group; -/* The event group allows multiple bits for each event, but we only care about two events: - * - we are connected to the AP with an IP - * - we failed to connect after the maximum amount of retries */ #define WIFI_CONNECTED_BIT BIT0 #define WIFI_FAIL_BIT BIT1 static int s_retry_num = 0; -// Internal wifi event_handler static void event_handler(void* arg, esp_event_base_t event_base, - int32_t event_id, void* event_data) + int32_t event_id, void* event_data) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { esp_wifi_connect(); - } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + } else if (event_base == WIFI_EVENT && + event_id == WIFI_EVENT_STA_DISCONNECTED) { if (s_retry_num < 3) { esp_wifi_connect(); s_retry_num++; - ESP_LOGI(TAG, "retry to connect to the AP"); } else { xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); } - ESP_LOGI(TAG,"connect to the AP fail"); - } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { - ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; - ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); + } else if (event_base == IP_EVENT && + event_id == IP_EVENT_STA_GOT_IP) { s_retry_num = 0; xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } } -// Initialize the wifi void wifi_init_sta(void) { s_wifi_event_group = xEventGroupCreate(); ESP_ERROR_CHECK(esp_netif_init()); - ESP_ERROR_CHECK(esp_event_loop_create_default()); esp_netif_create_default_wifi_sta(); @@ -88,66 +78,29 @@ void wifi_init_sta(void) esp_event_handler_instance_t instance_any_id; esp_event_handler_instance_t instance_got_ip; - ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, - ESP_EVENT_ANY_ID, - &event_handler, - NULL, - &instance_any_id)); - ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, - IP_EVENT_STA_GOT_IP, - &event_handler, - NULL, - &instance_got_ip)); + ESP_ERROR_CHECK(esp_event_handler_instance_register( + WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, + &instance_any_id)); + ESP_ERROR_CHECK(esp_event_handler_instance_register( + IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, + &instance_got_ip)); wifi_config_t wifi_config = { .sta = { .ssid = EXAMPLE_ESP_WIFI_SSID, .password = EXAMPLE_ESP_WIFI_PASS, - /* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (password len => 8). - * If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value - * to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to - * WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards. - */ .threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD, .sae_pwe_h2e = ESP_WIFI_SAE_MODE, }, }; - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); - ESP_ERROR_CHECK(esp_wifi_start() ); - ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); - /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum - * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */ - EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, - WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, - pdFALSE, - pdFALSE, - portMAX_DELAY); - - /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually - * happened. */ - if (bits & WIFI_CONNECTED_BIT) { - ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", - EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); - } else if (bits & WIFI_FAIL_BIT) { - ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s", - EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); - } else { - ESP_LOGE(TAG, "UNEXPECTED EVENT"); - } -} - -// Function to send the voltage to a remote server -void send_voltage(esp_http_client_handle_t client, float v) -{ - // Simple payload for the go server - char payload[64]; - snprintf(payload, sizeof(payload), "{\"voltage\":%.2f}", v); - - esp_http_client_set_post_field(client, payload, strlen(payload)); - esp_http_client_perform(client); + xEventGroupWaitBits(s_wifi_event_group, + WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, + pdFALSE, pdFALSE, portMAX_DELAY); } /* ===================== ADC ===================== */ @@ -158,7 +111,6 @@ static bool cali_enabled = false; static void adc_init(void) { - // Setup oneshot adc_oneshot_unit_init_cfg_t unit_cfg = { .unit_id = ADC_UNIT_1, .ulp_mode = ADC_ULP_MODE_DISABLE, @@ -171,84 +123,89 @@ static void adc_init(void) }; ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, CHANNEL, &chan_cfg)); - // Calibration of the adc adc_cali_scheme_ver_t scheme; - if (adc_cali_check_scheme(&scheme) != ESP_OK) { - ESP_LOGW(TAG, "ADC calibration not supported"); - return; - } + if (adc_cali_check_scheme(&scheme) == ESP_OK && + (scheme & ADC_CALI_SCHEME_VER_LINE_FITTING)) { - if (scheme & ADC_CALI_SCHEME_VER_LINE_FITTING) { adc_cali_line_fitting_config_t cfg = { .unit_id = ADC_UNIT_1, .atten = ADC_ATTEN_DB_12, .bitwidth = ADC_BITWIDTH_DEFAULT, }; - ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cfg, &cali_handle)); + ESP_ERROR_CHECK( + adc_cali_create_scheme_line_fitting(&cfg, &cali_handle)); cali_enabled = true; } } -static float read_voltage_mv(void) +static float read_voltage(void) { - int raw; - int mv = 0; - + int raw, mv = 0; ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, CHANNEL, &raw)); if (cali_enabled) { ESP_ERROR_CHECK(adc_cali_raw_to_voltage(cali_handle, raw, &mv)); } - - ESP_LOGI(TAG, "Raw measurement from sensor to calibrated value: %d -> %d", raw, mv); - // return (float)raw; return (mv - CALIBRATION_FACTOR) / CALIBRATION_FACTOR; } -/* ===================== MAIN ===================== */ +/* ===================== HTTP ===================== */ + +static void send_voltage_batch(esp_http_client_handle_t client, + const float *data, + size_t n) +{ + char payload[1024]; + char *p = payload; + + p += sprintf(p, "{\"voltages\":["); + for (size_t i = 0; i < n; i++) { + p += sprintf(p, "%.4f%s", data[i], + (i + 1 < n) ? "," : ""); + } + p += sprintf(p, "]}"); + + esp_http_client_set_post_field(client, payload, strlen(payload)); + esp_http_client_perform(client); +} + +/* ===================== TASK ===================== */ static void measure_task(void *arg) { - // Setup client to be reused esp_http_client_config_t cfg = { .url = SERVER_URL, .method = HTTP_METHOD_POST, - .timeout_ms = 2000, + .timeout_ms = 3000, }; esp_http_client_handle_t client = esp_http_client_init(&cfg); esp_http_client_set_header(client, "Content-Type", "application/json"); - // Main measure loop - while (1) { - float v = read_voltage_mv(); - vTaskDelay(pdMS_TO_TICKS(50)); // Does this do anything? - send_voltage(client, v); - vTaskDelay(pdMS_TO_TICKS(RMS_WINDOW_MS)); - } + float batch[BATCH_SIZE]; + size_t count = 0; - // Cleanup after task - esp_http_client_cleanup(client); + TickType_t last_wake = xTaskGetTickCount(); + + while (1) { + batch[count++] = read_voltage(); + + if (count == BATCH_SIZE) { + send_voltage_batch(client, batch, count); + count = 0; + } + + vTaskDelayUntil(&last_wake, + pdMS_TO_TICKS(SAMPLE_PERIOD_MS)); + } } +/* ===================== MAIN ===================== */ + void app_main(void) { - //Initialize NVS - esp_err_t ret = nvs_flash_init(); - if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { - ESP_ERROR_CHECK(nvs_flash_erase()); - ret = nvs_flash_init(); - } - ESP_ERROR_CHECK(ret); - - if (CONFIG_LOG_MAXIMUM_LEVEL > CONFIG_LOG_DEFAULT_LEVEL) { - /* If you only want to open more logs in the wifi module, you need to make the max level greater than the default level, - * and call esp_log_level_set() before esp_wifi_init() to improve the log level of the wifi module. */ - esp_log_level_set("wifi", CONFIG_LOG_MAXIMUM_LEVEL); - } - - ESP_LOGI(TAG, "ESP_WIFI_MODE_STA"); + ESP_ERROR_CHECK(nvs_flash_init()); wifi_init_sta(); adc_init(); - ESP_LOGI(TAG, "Start measure"); xTaskCreate(measure_task, "measure", 4096, NULL, 5, NULL); } + diff --git a/plot.py b/plot.py index 03bd93c..8fc887d 100644 --- a/plot.py +++ b/plot.py @@ -30,7 +30,7 @@ with open(filepath, "r") as f: # Plot plt.figure(figsize=(12, 5)) -plt.plot(timestamps, voltages, marker='o', linestyle='-') +plt.scatter(timestamps, voltages, marker='o', linestyle='-', s=0.1) plt.xlabel("Time (CET)") plt.ylabel("Voltage (V)")