From e7d97c1d6feb17764f499514ac3f9480976c8f46 Mon Sep 17 00:00:00 2001 From: YoungSoo Shin Date: Thu, 20 Nov 2025 08:57:14 +0900 Subject: [PATCH 1/5] delete unuse partition Signed-off-by: YoungSoo Shin --- partitions.csv | 1 - 1 file changed, 1 deletion(-) diff --git a/partitions.csv b/partitions.csv index d95c518..e6d484d 100644 --- a/partitions.csv +++ b/partitions.csv @@ -3,4 +3,3 @@ nvs,data,nvs,0x9000,24K, phy_init,data,phy,0xf000,4K, factory,app,factory,0x10000,2M, -littlefs, data, littlefs, ,1536K, \ No newline at end of file From 0765c47e4a5d072b8a43a43256132a0a4b901f13 Mon Sep 17 00:00:00 2001 From: YoungSoo Shin Date: Thu, 20 Nov 2025 09:01:51 +0900 Subject: [PATCH 2/5] example: add plot option Signed-off-by: YoungSoo Shin --- example/logger/README.md | 2 +- example/logger/csv_2_plot.py | 81 +++++++++++++++++++++--------------- 2 files changed, 48 insertions(+), 35 deletions(-) diff --git a/example/logger/README.md b/example/logger/README.md index edd95b8..8150a40 100644 --- a/example/logger/README.md +++ b/example/logger/README.md @@ -35,7 +35,7 @@ python3 logger.py -u admin -p password -o test.csv 192.168.30.5 #### Plot data ```shell -python3 csv_2_plot.py test.csv plot.png +python3 csv_2_plot.py test.csv plot.png [--type power voltage current] ``` ![plot.png](plot.png) \ No newline at end of file diff --git a/example/logger/csv_2_plot.py b/example/logger/csv_2_plot.py index 5bcb5d0..0f1ad20 100644 --- a/example/logger/csv_2_plot.py +++ b/example/logger/csv_2_plot.py @@ -1,17 +1,19 @@ import argparse + import matplotlib.dates as mdates import matplotlib.pyplot as plt -import os import pandas as pd -def plot_power_data(csv_path, output_path): +def plot_power_data(csv_path, output_path, plot_types): """ Reads power data from a CSV file and generates a plot image. Args: csv_path (str): The path to the input CSV file. output_path (str): The path to save the output plot image. + plot_types (list): A list of strings indicating which plots to generate + (e.g., ['power', 'voltage', 'current']). """ try: # Read the CSV file into a pandas DataFrame @@ -25,44 +27,47 @@ def plot_power_data(csv_path, output_path): print(f"An error occurred while reading the CSV file: {e}") return - # Create a figure and a set of subplots (3 rows, 1 column) + # --- Plotting Configuration --- + plot_configs = { + 'power': {'title': 'Power Consumption', 'ylabel': 'Power (W)', + 'cols': ['vin_power', 'main_power', 'usb_power']}, + 'voltage': {'title': 'Voltage', 'ylabel': 'Voltage (V)', + 'cols': ['vin_voltage', 'main_voltage', 'usb_voltage']}, + 'current': {'title': 'Current', 'ylabel': 'Current (A)', 'cols': ['vin_current', 'main_current', 'usb_current']} + } + channel_labels = ['VIN', 'MAIN', 'USB'] + channel_colors = ['red', 'green', 'blue'] + + num_plots = len(plot_types) + if num_plots == 0: + print("No plot types selected. Exiting.") + return + + # Create a figure and a set of subplots based on the number of selected plot types. # sharex=True makes all subplots share the same x-axis (time) - fig, axes = plt.subplots(3, 1, figsize=(15, 18), sharex=True) + # squeeze=False ensures that 'axes' is always a 2D array, even if num_plots is 1. + fig, axes = plt.subplots(num_plots, 1, figsize=(15, 6 * num_plots), sharex=True, squeeze=False) + axes = axes.flatten() # Flatten the 2D array to 1D for easier iteration - # --- Plot 1: Power (W) --- - ax1 = axes[0] - ax1.plot(df['timestamp'], df['vin_power'], label='VIN', color='red') - ax1.plot(df['timestamp'], df['main_power'], label='MAIN', color='green') - ax1.plot(df['timestamp'], df['usb_power'], label='USB', color='blue') - ax1.set_title('Power Consumption') - ax1.set_ylabel('Power (W)') - ax1.legend() - ax1.grid(True, which='both', linestyle='--', linewidth=0.5) + # --- Loop through selected plot types and generate plots --- + for i, plot_type in enumerate(plot_types): + ax = axes[i] + config = plot_configs[plot_type] - # --- Plot 2: Voltage (V) --- - ax2 = axes[1] - ax2.plot(df['timestamp'], df['vin_voltage'], label='VIN', color='red') - ax2.plot(df['timestamp'], df['main_voltage'], label='MAIN', color='green') - ax2.plot(df['timestamp'], df['usb_voltage'], label='USB', color='blue') - ax2.set_title('Voltage') - ax2.set_ylabel('Voltage (V)') - ax2.legend() - ax2.grid(True, which='both', linestyle='--', linewidth=0.5) + for j, col_name in enumerate(config['cols']): + ax.plot(df['timestamp'], df[col_name], label=channel_labels[j], color=channel_colors[j]) - # --- Plot 3: Current (A) --- - ax3 = axes[2] - ax3.plot(df['timestamp'], df['vin_current'], label='VIN', color='red') - ax3.plot(df['timestamp'], df['main_current'], label='MAIN', color='green') - ax3.plot(df['timestamp'], df['usb_current'], label='USB', color='blue') - ax3.set_title('Current') - ax3.set_ylabel('Current (A)') - ax3.legend() - ax3.grid(True, which='both', linestyle='--', linewidth=0.5) + ax.set_title(config['title']) + ax.set_ylabel(config['ylabel']) + ax.legend() + ax.grid(True, which='both', linestyle='--', linewidth=0.5) # --- Formatting the x-axis (Time) --- # Improve date formatting on the x-axis - ax3.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) - ax3.xaxis.set_major_locator(plt.MaxNLocator(15)) # Limit the number of ticks + # Apply formatting to the last subplot's x-axis + last_ax = axes[-1] + last_ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) + last_ax.xaxis.set_major_locator(plt.MaxNLocator(15)) # Limit the number of ticks plt.xlabel('Time') plt.xticks(rotation=45) @@ -86,9 +91,17 @@ def main(): parser = argparse.ArgumentParser(description="Generate a plot from an Odroid PowerMate CSV log file.") parser.add_argument("input_csv", help="Path to the input CSV log file.") parser.add_argument("output_image", help="Path to save the output plot image (e.g., plot.png).") + parser.add_argument( + "-t", "--type", + nargs='+', + choices=['power', 'voltage', 'current'], + default=['power', 'voltage', 'current'], + help="Types of plots to generate. Choose from 'power', 'voltage', 'current'. " + "Default is to generate all three." + ) args = parser.parse_args() - plot_power_data(args.input_csv, args.output_image) + plot_power_data(args.input_csv, args.output_image, args.type) if __name__ == "__main__": From b33db504a345fb48cc0b727fd4409d278618cf37 Mon Sep 17 00:00:00 2001 From: YoungSoo Shin Date: Thu, 20 Nov 2025 09:05:26 +0900 Subject: [PATCH 3/5] example: add .gitignore Signed-off-by: YoungSoo Shin --- example/logger/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 example/logger/.gitignore diff --git a/example/logger/.gitignore b/example/logger/.gitignore new file mode 100644 index 0000000..d993eb6 --- /dev/null +++ b/example/logger/.gitignore @@ -0,0 +1,5 @@ +/.venv/ +/venv/ +status_pb2.py +test.csv +plot.png From 388e75864a3d42a1bfac5991284f442726ebdff1 Mon Sep 17 00:00:00 2001 From: YoungSoo Shin Date: Thu, 20 Nov 2025 09:31:53 +0900 Subject: [PATCH 4/5] update readme Signed-off-by: YoungSoo Shin --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6fecede..50d6df6 100644 --- a/README.md +++ b/README.md @@ -77,4 +77,13 @@ sudo apt install nodejs npm nanopb 1. After flashing, the ESP32 will either connect to the pre-configured Wi-Fi network or start an Access Point (APSTA). 2. Check the serial monitor logs to find the IP address assigned to the device in STA mode, or the default AP address (usually `192.168.4.1`). 3. Open a web browser and navigate to the device's IP address. -4. You should now see the ODROID Remote control panel. \ No newline at end of file +4. You should now see the ODROID Remote control panel. + +## Docs + +- Hardkernel WiKi: [https://wiki.odroid.com/accessory/powermate](https://wiki.odroid.com/accessory/powermate) + +## Repo + +- Hardkernel Github: [https://github.com/hardkernel/odroid-powermate](https://github.com/hardkernel/odroid-powermate) +- Original Repo: [https://github.com/shinys000114/odroid-powermate](https://github.com/shinys000114/odroid-powermate) \ No newline at end of file From cefe34c7bc34a686711ec15312b25f97615f52c7 Mon Sep 17 00:00:00 2001 From: YoungSoo Shin Date: Fri, 5 Dec 2025 12:10:06 +0900 Subject: [PATCH 5/5] sta: fix reconnect ap when ap lost Signed-off-by: YoungSoo Shin --- main/wifi/priv_wifi.h | 1 + main/wifi/sta.c | 18 ++++++++++++++++++ main/wifi/wifi.c | 16 ++++++++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/main/wifi/priv_wifi.h b/main/wifi/priv_wifi.h index 1ff4bb3..ea6ced8 100644 --- a/main/wifi/priv_wifi.h +++ b/main/wifi/priv_wifi.h @@ -8,5 +8,6 @@ void wifi_init_sta(void); void wifi_init_ap(void); void initialize_sntp(void); +void wifi_set_auto_reconnect(bool enable); #endif // ODROID_POWER_MATE_PRIV_WIFI_H diff --git a/main/wifi/sta.c b/main/wifi/sta.c index 79cdd1a..9b96acd 100644 --- a/main/wifi/sta.c +++ b/main/wifi/sta.c @@ -81,6 +81,14 @@ void wifi_scan_aps(wifi_ap_record_t** ap_records, uint16_t* count) *count = 0; *ap_records = NULL; + wifi_set_auto_reconnect(false); + + wifi_ap_record_t ap_info; + if (esp_wifi_sta_get_ap_info(&ap_info) != ESP_OK) + { + esp_wifi_disconnect(); + } + // Start scan, this is a blocking call if (esp_wifi_scan_start(NULL, true) == ESP_OK) { @@ -100,6 +108,16 @@ void wifi_scan_aps(wifi_ap_record_t** ap_records, uint16_t* count) } } } + + wifi_set_auto_reconnect(true); + + if (esp_wifi_sta_get_ap_info(&ap_info) != ESP_OK) + { + if (!nconfig_value_is_not_set(WIFI_SSID)) + { + wifi_connect(); + } + } } esp_err_t wifi_get_current_ap_info(wifi_ap_record_t* ap_info) diff --git a/main/wifi/wifi.c b/main/wifi/wifi.c index f0c1581..ff1e0c8 100644 --- a/main/wifi/wifi.c +++ b/main/wifi/wifi.c @@ -16,9 +16,13 @@ #include "wifi.h" #include "indicator.h" +static bool s_auto_reconnect = true; static const char* TAG = "WIFI"; +void wifi_set_auto_reconnect(bool enable) { s_auto_reconnect = enable; } + + static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) @@ -46,10 +50,18 @@ static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t e } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { - led_set(LED_RED, BLINK_TRIPLE); + led_set(LED_BLU, BLINK_TRIPLE); wifi_event_sta_disconnected_t* event = (wifi_event_sta_disconnected_t*)event_data; ESP_LOGW(TAG, "Disconnected from AP, reason: %s", wifi_reason_str(event->reason)); - // ESP-IDF will automatically try to reconnect by default. + + if (event->reason != WIFI_REASON_ASSOC_LEAVE) + { + if (s_auto_reconnect && !nconfig_value_is_not_set(WIFI_SSID)) + { + ESP_LOGI(TAG, "Connection lost, attempting to reconnect..."); + esp_wifi_connect(); + } + } } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {