Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 941326d9d5 | |||
| 9f4cab68d9 | |||
| 27c686479a | |||
| 358da628c5 |
8
Makefile
8
Makefile
@@ -1,12 +1,16 @@
|
||||
help:
|
||||
# plot - Plot the latest measurement
|
||||
# monitor - Build and monitor onto esp if connected to /dev/ttyUSB0
|
||||
# mon - Monitor onto esp if connected to /dev/ttyUSB0
|
||||
# push - Build and monitor onto esp if connected to /dev/ttyUSB0
|
||||
# env - Start the esp environment on nix
|
||||
# server - Start the go server
|
||||
|
||||
monitor:
|
||||
push:
|
||||
idf.py -p /dev/ttyUSB0 flash monitor
|
||||
|
||||
mon:
|
||||
idf.py -p /dev/ttyUSB0 monitor
|
||||
|
||||
env:
|
||||
nix --experimental-features 'nix-command flakes' develop github:mirrexagon/nixpkgs-esp-dev#esp32-idf
|
||||
|
||||
|
||||
63
main.go
63
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,26 +58,24 @@ 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),
|
||||
)
|
||||
}
|
||||
|
||||
log.Println("Got batch!")
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("ok"))
|
||||
}
|
||||
@@ -89,8 +86,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))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/*
|
||||
Voltage measurement device over wifi from WiFi station Example
|
||||
Voltage measurement device over WiFi (batched, ~30 Hz with jitter)
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
@@ -11,75 +11,64 @@
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#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 SAMPLE_RATE_HZ 20
|
||||
#define SAMPLE_PERIOD_MS (1000 / SAMPLE_RATE_HZ)
|
||||
#define BATCH_SIZE 100
|
||||
|
||||
#define CHANNEL ADC_CHANNEL_6
|
||||
#define CALIBRATION_FACTOR 1600.0f
|
||||
|
||||
// TODO: Determine this
|
||||
#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 +77,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 +110,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 +122,90 @@ 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;
|
||||
}
|
||||
|
||||
int jitter = rand() % 41; // 0..40 ms
|
||||
vTaskDelayUntil(&last_wake,
|
||||
pdMS_TO_TICKS(SAMPLE_PERIOD_MS + jitter));
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================== 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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user