init commit

Signed-off-by: YoungSoo Shin <shinys000114@gmail.com>
This commit is contained in:
2025-08-20 18:56:07 +09:00
commit 2383894664
46 changed files with 7834 additions and 0 deletions

31
main/CMakeLists.txt Normal file
View File

@@ -0,0 +1,31 @@
# Define the web application source directory and the final output file
set(WEB_APP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/page)
set(GZ_OUTPUT_FILE ${WEB_APP_SOURCE_DIR}/dist/index.html.gz)
# Register the component. Now, CMake knows how GZ_OUTPUT_FILE is generated
# and can correctly handle the dependency for embedding.
idf_component_register(SRC_DIRS "app" "nconfig" "wifi" "indicator" "system" "service" "ina226"
INCLUDE_DIRS "include"
EMBED_FILES ${GZ_OUTPUT_FILE}
)
# Define a custom command to build the web app.
# This command explicitly tells CMake that it produces the GZ_OUTPUT_FILE.
add_custom_command(
OUTPUT ${GZ_OUTPUT_FILE}
COMMAND npm install
COMMAND npm run build
WORKING_DIRECTORY ${WEB_APP_SOURCE_DIR}
# Re-run the build if package.json or vite.config.js changes
DEPENDS ${WEB_APP_SOURCE_DIR}/index.html ${WEB_APP_SOURCE_DIR}/src/main.js ${WEB_APP_SOURCE_DIR}/src/style.css
COMMENT "Building Node.js project to produce ${GZ_OUTPUT_FILE}"
VERBATIM
)
# Create a target that depends on the output file. When this target is built,
# it ensures the custom command above is executed first.
add_custom_target(build_web_app ALL
DEPENDS ${GZ_OUTPUT_FILE}
)

82
main/Kconfig Normal file
View File

@@ -0,0 +1,82 @@
menu "ODROID-MONITOR"
menu "GPIO"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
config GPIO_INA226_SCL
int "INA226 SCL GPIO Num"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 0
help
GPIO number for I2C Master data line.
config GPIO_INA226_SDA
int "INA226 SDA GPIO Num"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 1
help
GPIO number for I2C Master data line.
config GPIO_INA226_INT
int "INA226 ALERT GPIO Num"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 10
help
GPIO number for I2C Master data line.
config GPIO_UART_TX
int "UART TX GPIO Num"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 6
help
GPIO number for UART data line.
config GPIO_UART_RX
int "UART RX GPIO Num"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 7
help
GPIO number for UART data line.
config GPIO_LED_STATUS
int "Status LED GPIO Num"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 8
help
GPIO number for LED.
config GPIO_LED_WIFI
int "Wi-Fi LED GPIO Num"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 9
help
GPIO number for LED.
config GPIO_SW_12V
int "12v Load Switch GPIO Num"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 4
help
GPIO number for Load switch.
config GPIO_SW_5V
int "5v Load Switch GPIO Num"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 5
help
GPIO number for Load switch.
config GPIO_TRIGGER_POWER
int "Trigger power GPIO Num"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 2
help
GPIO number for Trigger.
config GPIO_TRIGGER_RESET
int "Trigger reset GPIO Num"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 3
help
GPIO number for Trigger.
endmenu
endmenu

View File

@@ -0,0 +1,50 @@
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "driver/uart.h"
#include "esp_http_server.h"
#include "indicator.h"
#include "nconfig.h"
#include "system.h"
#include "wifi.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
static const char *TAG = "odroid-remote";
void app_main(void) {
init_led();
led_set(LED_BLU, BLINK_TRIPLE);
led_off(LED_BLU);
// 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);
// 네트워크 초기화
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(init_nconfig());
// WiFi 연결
wifi_connect();
sync_time();
start_webserver();
}

3
main/idf_component.yml Normal file
View File

@@ -0,0 +1,3 @@
dependencies:
espressif/led_indicator: ^1.1.1
joltwallet/littlefs: ==1.20.1

118
main/ina226/ina226.c Normal file
View File

@@ -0,0 +1,118 @@
#include "ina226.h"
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <esp_err.h>
#define INA226_REG_CONFIG (0x00)
#define INA226_REG_SHUNT_VOLTAGE (0x01)
#define INA226_REG_BUS_VOLTAGE (0x02)
#define INA226_REG_POWER (0x03)
#define INA226_REG_CURRENT (0x04)
#define INA226_REG_CALIBRATION (0x05)
#define INA226_REG_ALERT_MASK (0x06)
#define INA226_REG_ALERT_LIMIT (0x07)
#define INA226_REG_MANUFACTURER_ID (0xFE)
#define INA226_REG_DIE_ID (0xFF)
#define INA226_CFG_AVERAGING_OFFSET 9
#define INA226_CFG_BUS_VOLTAGE_OFFSET 6
#define INA226_CFG_SHUNT_VOLTAGE_OFFSET 3
static esp_err_t ina226_read_reg(ina226_t *handle, uint8_t reg_addr, uint16_t *data, size_t len)
{
return i2c_master_transmit_receive(handle->dev_handle, &reg_addr, 1, (uint8_t *)data, len, handle->timeout_ms);
}
static esp_err_t ina226_write_reg(ina226_t *handle, uint8_t reg_addr, uint16_t value)
{
uint8_t write_buf[3] = {reg_addr, value >> 8, value & 0xFF};
return i2c_master_transmit(handle->dev_handle, write_buf, sizeof(write_buf), handle->timeout_ms);
}
esp_err_t ina226_get_manufacturer_id(ina226_t *device, uint16_t *manufacturer_id)
{
return ina226_read_reg(device, INA226_REG_MANUFACTURER_ID, manufacturer_id, 2);
}
esp_err_t ina226_get_die_id(ina226_t *device, uint16_t *die_id)
{
return ina226_read_reg(device, INA226_REG_DIE_ID, die_id, 2);
}
esp_err_t ina226_get_shunt_voltage(ina226_t *device, float *voltage)
{
uint8_t data[2];
esp_err_t err = ina226_read_reg(device, INA226_REG_SHUNT_VOLTAGE, (uint16_t*)data, 2);
*voltage = (float) (data[0] << 8 | data[1]) * 2.5e-6f; /* fixed to 2.5 uV */
return err;
}
esp_err_t ina226_get_bus_voltage(ina226_t *device, float *voltage)
{
uint8_t data[2];
esp_err_t err = ina226_read_reg(device, INA226_REG_BUS_VOLTAGE, (uint16_t*)data, 2);
*voltage = (float) (data[0] << 8 | data[1]) * 0.00125f;
return err;
}
esp_err_t ina226_get_current(ina226_t *device, float *current)
{
uint8_t data[2];
esp_err_t err = ina226_read_reg(device, INA226_REG_CURRENT, (uint16_t*)data, 2);
*current = ((float) (data[0] << 8 | data[1])) * device->current_lsb;
return err;
}
esp_err_t ina226_get_power(ina226_t *device, float *power)
{
uint8_t data[2];
esp_err_t err = ina226_read_reg(device, INA226_REG_POWER, (uint16_t*)data, 2);
*power = (float) (data[0] << 8 | data[1]) * device->power_lsb;
return err;
}
esp_err_t ina226_init(ina226_t *device, i2c_master_dev_handle_t dev_handle, const ina226_config_t *config)
{
esp_err_t err;
device->timeout_ms = config->timeout_ms;
device->dev_handle = dev_handle;
uint16_t bitmask = 0;
bitmask |= (config->averages << INA226_CFG_AVERAGING_OFFSET);
bitmask |= (config->bus_conv_time << INA226_CFG_BUS_VOLTAGE_OFFSET);
bitmask |= (config->shunt_conv_time << INA226_CFG_SHUNT_VOLTAGE_OFFSET);
bitmask |= config->mode;
err = ina226_write_reg(device, INA226_REG_CONFIG, bitmask);
if(err != ESP_OK) return err;
/* write calibration*/
float minimum_lsb = config->max_current / 32767;
float current_lsb = (uint16_t)(minimum_lsb * 100000000);
current_lsb /= 100000000;
current_lsb /= 0.0001;
current_lsb = ceil(current_lsb);
current_lsb *= 0.0001;
device->current_lsb = current_lsb;
device->power_lsb = current_lsb * 25;
uint16_t calibration_value = (uint16_t)((0.00512) / (current_lsb * config->r_shunt));
err = ina226_write_reg(device, INA226_REG_CALIBRATION, calibration_value);
if(err != ESP_OK) return err;
return ESP_OK;
}
esp_err_t ina226_get_alert_mask(ina226_t *device, ina226_alert_t *alert_mask)
{
return ina226_read_reg(device, INA226_REG_ALERT_MASK, (uint16_t *)alert_mask, 2);
}
esp_err_t ina226_set_alert_mask(ina226_t *device, ina226_alert_t alert_mask)
{
return ina226_write_reg(device, INA226_REG_ALERT_MASK, (uint16_t)alert_mask);
}
esp_err_t ina226_set_alert_limit(ina226_t *device, float voltage)
{
return ina226_write_reg(device, INA226_REG_ALERT_LIMIT, (uint16_t)(voltage));
}

105
main/include/ina226.h Normal file
View File

@@ -0,0 +1,105 @@
#ifndef _INA226_H_
#define _INA226_H_
#include <stdint.h>
#include "esp_err.h"
#include "driver/i2c_master.h"
typedef enum
{
INA226_AVERAGES_1 = 0b000,
INA226_AVERAGES_4 = 0b001,
INA226_AVERAGES_16 = 0b010,
INA226_AVERAGES_64 = 0b011,
INA226_AVERAGES_128 = 0b100,
INA226_AVERAGES_256 = 0b101,
INA226_AVERAGES_512 = 0b110,
INA226_AVERAGES_1024 = 0b111
} ina226_averages_t;
typedef enum
{
INA226_BUS_CONV_TIME_140_US = 0b000,
INA226_BUS_CONV_TIME_204_US = 0b001,
INA226_BUS_CONV_TIME_332_US = 0b010,
INA226_BUS_CONV_TIME_588_US = 0b011,
INA226_BUS_CONV_TIME_1100_US = 0b100,
INA226_BUS_CONV_TIME_2116_US = 0b101,
INA226_BUS_CONV_TIME_4156_US = 0b110,
INA226_BUS_CONV_TIME_8244_US = 0b111
} ina226_bus_conv_time_t;
typedef enum
{
INA226_SHUNT_CONV_TIME_140_US = 0b000,
INA226_SHUNT_CONV_TIME_204_US = 0b001,
INA226_SHUNT_CONV_TIME_332_US = 0b010,
INA226_SHUNT_CONV_TIME_588_US = 0b011,
INA226_SHUNT_CONV_TIME_1100_US = 0b100,
INA226_SHUNT_CONV_TIME_2116_US = 0b101,
INA226_SHUNT_CONV_TIME_4156_US = 0b110,
INA226_SHUNT_CONV_TIME_8244_US = 0b111
} ina226_shunt_conv_time_t;
typedef enum
{
INA226_MODE_POWER_DOWN = 0b000,
INA226_MODE_SHUNT_TRIG = 0b001,
INA226_MODE_BUS_TRIG = 0b010,
INA226_MODE_SHUNT_BUS_TRIG = 0b011,
INA226_MODE_ADC_OFF = 0b100,
INA226_MODE_SHUNT_CONT = 0b101,
INA226_MODE_BUS_CONT = 0b110,
INA226_MODE_SHUNT_BUS_CONT = 0b111,
} ina226_mode_t;
typedef enum
{
INA226_ALERT_SHUNT_OVER_VOLTAGE = 0xf,
INA226_ALERT_SHUNT_UNDER_VOLTAGE = 0xe,
INA226_ALERT_BUS_OVER_VOLTAGE = 0xd,
INA226_ALERT_BUS_UNDER_VOLTAGE = 0xc,
INA226_ALERT_POWER_OVER_LIMIT = 0xb,
INA226_ALERT_CONVERSION_READY = 0xa,
INA226_ALERT_FUNCTION_FLAG = 0x4,
INA226_ALERT_CONVERSION_READY_FLAG = 0x3,
INA226_ALERT_MATH_OVERFLOW_FLAG = 0x2,
INA226_ALERT_POLARITY = 0x1,
INA226_ALERT_LATCH_ENABLE = 0x0
} ina226_alert_t;
typedef struct
{
i2c_port_t i2c_port;
int i2c_addr;
int timeout_ms;
ina226_averages_t averages;
ina226_bus_conv_time_t bus_conv_time;
ina226_shunt_conv_time_t shunt_conv_time;
ina226_mode_t mode;
float r_shunt; /* ohm */
float max_current; /* amps */
} ina226_config_t;
typedef struct
{
i2c_master_dev_handle_t dev_handle;
int timeout_ms;
float current_lsb;
float power_lsb;
} ina226_t;
esp_err_t ina226_get_manufacturer_id(ina226_t *device, uint16_t *manufacturer_id);
esp_err_t ina226_get_die_id(ina226_t *device, uint16_t *die_id);
esp_err_t ina226_get_shunt_voltage(ina226_t *device, float *voltage);
esp_err_t ina226_get_bus_voltage(ina226_t *device, float *voltage);
esp_err_t ina226_get_current(ina226_t *device, float *current);
esp_err_t ina226_get_power(ina226_t *device, float *power);
esp_err_t ina226_get_alert_mask(ina226_t *device, ina226_alert_t *alert_mask);
esp_err_t ina226_set_alert_mask(ina226_t *device, ina226_alert_t alert_mask);
esp_err_t ina226_set_alert_limit(ina226_t *device, float voltage);
esp_err_t ina226_init(ina226_t *device, i2c_master_dev_handle_t dev_handle, const ina226_config_t *config);
#endif

29
main/include/indicator.h Normal file
View File

@@ -0,0 +1,29 @@
//
// Created by shinys on 25. 7. 29.
//
#ifndef LED_H
#define LED_H
enum blink_type
{
BLINK_SLOW = 0,
BLINK_FAST,
BLINK_DOUBLE,
BLINK_TRIPLE,
BLINK_SOLID,
BLINK_MAX,
};
enum blink_led
{
LED_RED = 0,
LED_BLU = 1,
LED_MAX,
};
void init_led(void);
void led_set(enum blink_led led, enum blink_type type);
void led_off(enum blink_led led);
#endif //LED_H

46
main/include/nconfig.h Normal file
View File

@@ -0,0 +1,46 @@
//
// Created by shinys on 25. 7. 10.
//
#ifndef NCONFIG_H
#define NCONFIG_H
#include "nvs.h"
#include "esp_err.h"
#define NCONFIG_NVS_NAMESPACE "er"
#define NCONFIG_NOT_FOUND ESP_ERR_NVS_NOT_FOUND
esp_err_t init_nconfig();
enum nconfig_type
{
WIFI_SSID,
WIFI_PASSWORD,
WIFI_MODE,
AP_SSID,
AP_PASSWORD,
NETIF_HOSTNAME,
NETIF_IP,
NETIF_GATEWAY,
NETIF_SUBNET,
NETIF_DNS1,
NETIF_DNS2,
NETIF_TYPE,
UART_BAUD_RATE,
NCONFIG_TYPE_MAX,
};
// Write config
esp_err_t nconfig_write(enum nconfig_type type, const char* data);
// Check config is set and get config value length
esp_err_t nconfig_get_str_len(enum nconfig_type type, size_t *len);
// Read config
esp_err_t nconfig_read(enum nconfig_type type, char* data, size_t len);
// Remove key
esp_err_t nconfig_delete(enum nconfig_type type);
#endif //NCONFIG_H

12
main/include/system.h Normal file
View File

@@ -0,0 +1,12 @@
//
// Created by shinys on 25. 8. 5.
//
#ifndef SYSTEM_H
#define SYSTEM_H
void start_reboot_timer(int sec);
void stop_reboot_timer();
void start_webserver();
#endif //SYSTEM_H

23
main/include/wifi.h Normal file
View File

@@ -0,0 +1,23 @@
//
// Created by shinys on 25. 7. 10.
//
#ifndef WIFI_H
#define WIFI_H
#include "esp_err.h"
#include "esp_netif_types.h"
#include "esp_wifi_types_generic.h"
const char* auth_mode_str(wifi_auth_mode_t mode);
esp_err_t wifi_connect(void);
esp_err_t wifi_disconnect(void);
void wifi_scan_aps(wifi_ap_record_t **ap_records, uint16_t* count);
esp_err_t wifi_get_current_ap_info(wifi_ap_record_t *ap_info);
esp_err_t wifi_get_current_ip_info(esp_netif_ip_info_t *ip_info);
esp_err_t wifi_get_dns_info(esp_netif_dns_type_t type, esp_netif_dns_info_t *dns_info);
esp_err_t wifi_use_dhcp(void);
esp_err_t wifi_use_static(const char *ip, const char *gw, const char *netmask, const char *dns1, const char *dns2);
esp_err_t wifi_switch_mode(const char* mode);
void sync_time();
#endif //WIFI_H

109
main/indicator/indicator.c Normal file
View File

@@ -0,0 +1,109 @@
//
// Created by shinys on 25. 7. 29.
//
#include "indicator.h"
#include <led_indicator.h>
#define LED_STATUS_GPIO CONFIG_GPIO_LED_STATUS
#define LED_WIFI_GPIO CONFIG_GPIO_LED_WIFI
static const blink_step_t slow_blink[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 500},
{LED_BLINK_HOLD, LED_STATE_OFF, 500},
{LED_BLINK_HOLD, LED_STATE_ON, 500},
{LED_BLINK_HOLD, LED_STATE_OFF, 500},
{LED_BLINK_LOOP, 0, 0},
};
static const blink_step_t fast_blink[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 100},
{LED_BLINK_HOLD, LED_STATE_OFF, 100},
{LED_BLINK_HOLD, LED_STATE_ON, 100},
{LED_BLINK_HOLD, LED_STATE_OFF, 100},
{LED_BLINK_LOOP, 0, 0},
};
static const blink_step_t double_blink[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 100},
{LED_BLINK_HOLD, LED_STATE_OFF, 100},
{LED_BLINK_HOLD, LED_STATE_ON, 100},
{LED_BLINK_HOLD, LED_STATE_OFF, 500},
{LED_BLINK_LOOP, 0, 0},
};
static const blink_step_t triple_blink[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 100},
{LED_BLINK_HOLD, LED_STATE_OFF, 100},
{LED_BLINK_HOLD, LED_STATE_ON, 100},
{LED_BLINK_HOLD, LED_STATE_OFF, 100},
{LED_BLINK_HOLD, LED_STATE_ON, 100},
{LED_BLINK_HOLD, LED_STATE_OFF, 500},
{LED_BLINK_LOOP, 0, 0},
};
static const blink_step_t solid_blink[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 100},
{LED_BLINK_LOOP, 0, 0},
};
blink_step_t const *led_mode[] = {
[BLINK_SLOW] = slow_blink,
[BLINK_FAST] = fast_blink,
[BLINK_DOUBLE] = double_blink,
[BLINK_TRIPLE] = triple_blink,
[BLINK_SOLID] = solid_blink,
[BLINK_MAX] = NULL,
};
led_indicator_handle_t led_handle[LED_MAX] = {0};
int recent_type[LED_MAX] = {-1, -1};
void init_led(void)
{
led_indicator_ledc_config_t ledc_config = {0};
led_indicator_config_t config = {0};
ledc_config.is_active_level_high = true;
ledc_config.timer_inited = false;
ledc_config.timer_num = LEDC_TIMER_0;
ledc_config.gpio_num = LED_STATUS_GPIO;
ledc_config.channel = LEDC_CHANNEL_0;
config.mode = LED_LEDC_MODE;
config.led_indicator_ledc_config = &ledc_config;
config.blink_lists = led_mode;
config.blink_list_num = BLINK_MAX;
led_handle[LED_RED] = led_indicator_create(&config);
ledc_config.is_active_level_high = true;
ledc_config.timer_inited = false;
ledc_config.timer_num = LEDC_TIMER_0;
ledc_config.gpio_num = LED_WIFI_GPIO;
ledc_config.channel = LEDC_CHANNEL_1;
config.mode = LED_LEDC_MODE;
config.led_indicator_ledc_config = &ledc_config;
config.blink_lists = led_mode;
config.blink_list_num = BLINK_MAX;
led_handle[LED_BLU] = led_indicator_create(&config);
}
void led_set(enum blink_led led, enum blink_type type)
{
if (recent_type[led] != -1)
led_indicator_stop(led_handle[led], recent_type[led]);
recent_type[led] = type;
led_indicator_start(led_handle[led], type);
}
void led_off(enum blink_led led)
{
if (recent_type[led] != -1)
led_indicator_stop(led_handle[led], recent_type[led]);
recent_type[led] = -1;
}

85
main/nconfig/nconfig.c Normal file
View File

@@ -0,0 +1,85 @@
//
// Created by shinys on 25. 7. 10.
//
#include "nconfig.h"
#include "nvs_flash.h"
#include "esp_err.h"
static nvs_handle_t handle;
const static char *keys[NCONFIG_TYPE_MAX] = {
[WIFI_SSID] = "wifi_ssid",
[WIFI_PASSWORD] = "wifi_pw",
[WIFI_MODE] = "wifi_mode",
[AP_SSID] = "ap_ssid",
[AP_PASSWORD] = "ap_pw",
[NETIF_HOSTNAME] = "hostname",
[NETIF_IP] = "ip",
[NETIF_GATEWAY] = "gw",
[NETIF_SUBNET] = "sn",
[NETIF_DNS1] = "dns1",
[NETIF_DNS2] = "dns2",
[NETIF_TYPE] = "dhcp",
[UART_BAUD_RATE] = "baudrate",
};
struct default_value {
enum nconfig_type type;
const char *value;
};
struct default_value const default_values[] = {
{WIFI_SSID, "HK_BOB_24G"},
{WIFI_PASSWORD, ""},
{NETIF_TYPE, "dhcp"},
{NETIF_HOSTNAME, "odroid"},
{UART_BAUD_RATE, "1500000"},
{NETIF_DNS1, "8.8.8.8"},
{NETIF_DNS2, "8.8.4.4"},
};
esp_err_t init_nconfig()
{
esp_err_t ret = nvs_open(NCONFIG_NVS_NAMESPACE, NVS_READWRITE, &handle);
if (ret != ESP_OK) return ret;
for (int i = 0; i < sizeof(default_values) / sizeof(default_values[0]); ++i) {
// check key is not exist or value is null
size_t len = 0;
nconfig_get_str_len(default_values[i].type, &len);
if (len <= 1) // nconfig_get_str_len return err or value is '\0'
{
if (nconfig_write(default_values[i].type, default_values[i].value) != ESP_OK) // if nconfig write fail, system panic
return ESP_FAIL;
}
}
return ESP_OK;
}
esp_err_t nconfig_write(enum nconfig_type type, const char* data)
{
return nvs_set_str(handle, keys[type], data);
}
esp_err_t nconfig_delete(enum nconfig_type type)
{
return nvs_erase_key(handle, keys[type]);
}
esp_err_t nconfig_get_str_len(enum nconfig_type type, size_t *len)
{
return nvs_get_str(handle, keys[type], NULL, len);
}
esp_err_t nconfig_read(enum nconfig_type type, char* data, size_t len)
{
return nvs_get_str(handle, keys[type], data, &len);
}
esp_err_t nconfig_read_bool(enum nconfig_type type, char* data, size_t len)
{
return nvs_get_str(handle, keys[type], data, &len);
}

192
main/service/control.c Normal file
View File

@@ -0,0 +1,192 @@
#include "webserver.h"
#include "driver/gpio.h"
#include "esp_http_server.h"
#include "esp_log.h"
#include "cJSON.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
static const char *TAG = "CONTROL";
// --- GPIO 핀 정의 ---
#define GPIO_12V_SWITCH CONFIG_GPIO_SW_12V
#define GPIO_5V_SWITCH CONFIG_GPIO_SW_5V
#define GPIO_POWER_TRIGGER CONFIG_GPIO_TRIGGER_POWER
#define GPIO_RESET_TRIGGER CONFIG_GPIO_TRIGGER_RESET
// --- 상태 변수, 뮤텍스 및 타이머 핸들 ---
static bool status_12v_on = false;
static bool status_5v_on = false;
static SemaphoreHandle_t state_mutex;
static esp_timer_handle_t power_trigger_timer;
static esp_timer_handle_t reset_trigger_timer;
/**
* @brief 타이머 만료 시 GPIO를 다시 HIGH로 설정하는 콜백 함수
*/
static void trigger_off_callback(void* arg)
{
gpio_num_t gpio_pin = (int) arg;
gpio_set_level(gpio_pin, 1); // 핀을 다시 HIGH로 복구
ESP_LOGI(TAG, "GPIO %d trigger finished.", gpio_pin);
}
static void update_gpio_switches()
{
gpio_set_level(GPIO_12V_SWITCH, status_12v_on);
gpio_set_level(GPIO_5V_SWITCH, status_5v_on);
ESP_LOGI(TAG, "Switches updated: 12V=%s, 5V=%s", status_12v_on ? "ON" : "OFF", status_5v_on ? "ON" : "OFF");
}
static esp_err_t control_get_handler(httpd_req_t *req)
{
cJSON *root = cJSON_CreateObject();
xSemaphoreTake(state_mutex, portMAX_DELAY);
cJSON_AddBoolToObject(root, "load_12v_on", status_12v_on);
cJSON_AddBoolToObject(root, "load_5v_on", status_5v_on);
xSemaphoreGive(state_mutex);
char *json_string = cJSON_Print(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_string, strlen(json_string));
free(json_string);
cJSON_Delete(root);
return ESP_OK;
}
static esp_err_t control_post_handler(httpd_req_t *req)
{
char buf[128];
int ret, remaining = req->content_len;
if (remaining >= sizeof(buf)) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Request content too long");
return ESP_FAIL;
}
ret = httpd_req_recv(req, buf, remaining);
if (ret <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
}
return ESP_FAIL;
}
buf[ret] = '\0';
ESP_LOGI(TAG, "Received JSON: %s", buf);
cJSON *root = cJSON_Parse(buf);
if (root == NULL) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON format");
return ESP_FAIL;
}
bool state_changed = false;
xSemaphoreTake(state_mutex, portMAX_DELAY);
cJSON *item_12v = cJSON_GetObjectItem(root, "load_12v_on");
if (cJSON_IsBool(item_12v)) {
status_12v_on = cJSON_IsTrue(item_12v);
state_changed = true;
}
cJSON *item_5v = cJSON_GetObjectItem(root, "load_5v_on");
if (cJSON_IsBool(item_5v)) {
status_5v_on = cJSON_IsTrue(item_5v);
state_changed = true;
}
if (state_changed) {
update_gpio_switches();
}
xSemaphoreGive(state_mutex);
cJSON *power_trigger = cJSON_GetObjectItem(root, "power_trigger");
if (cJSON_IsTrue(power_trigger)) {
ESP_LOGI(TAG, "Triggering GPIO %d LOW for 3 seconds...", GPIO_POWER_TRIGGER);
gpio_set_level(GPIO_POWER_TRIGGER, 0);
esp_timer_stop(power_trigger_timer); // Stop timer if it's already running
ESP_ERROR_CHECK(esp_timer_start_once(power_trigger_timer, 3000000)); // 3초
}
cJSON *reset_trigger = cJSON_GetObjectItem(root, "reset_trigger");
if (cJSON_IsTrue(reset_trigger)) {
ESP_LOGI(TAG, "Triggering GPIO %d LOW for 3 seconds...", GPIO_RESET_TRIGGER);
gpio_set_level(GPIO_RESET_TRIGGER, 0);
esp_timer_stop(reset_trigger_timer); // Stop timer if it's already running
ESP_ERROR_CHECK(esp_timer_start_once(reset_trigger_timer, 3000000)); // 3초
}
cJSON_Delete(root);
httpd_resp_sendstr(req, "{\"status\":\"ok\"}");
return ESP_OK;
}
static void control_module_init(void)
{
state_mutex = xSemaphoreCreateMutex();
gpio_config_t switch_conf = {
.pin_bit_mask = (1ULL << GPIO_12V_SWITCH) | (1ULL << GPIO_5V_SWITCH),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&switch_conf);
update_gpio_switches();
gpio_config_t trigger_conf = {
.pin_bit_mask = (1ULL << GPIO_POWER_TRIGGER) | (1ULL << GPIO_RESET_TRIGGER),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&trigger_conf);
gpio_set_level(GPIO_POWER_TRIGGER, 1);
gpio_set_level(GPIO_RESET_TRIGGER, 1);
const esp_timer_create_args_t power_timer_args = {
.callback = &trigger_off_callback,
.arg = (void*) GPIO_POWER_TRIGGER,
.name = "power_trigger_off"
};
ESP_ERROR_CHECK(esp_timer_create(&power_timer_args, &power_trigger_timer));
const esp_timer_create_args_t reset_timer_args = {
.callback = &trigger_off_callback,
.arg = (void*) GPIO_RESET_TRIGGER,
.name = "reset_trigger_off"
};
ESP_ERROR_CHECK(esp_timer_create(&reset_timer_args, &reset_trigger_timer));
ESP_LOGI(TAG, "Control module initialized");
}
void register_control_endpoint(httpd_handle_t server)
{
control_module_init();
httpd_uri_t get_uri = {
.uri = "/api/control",
.method = HTTP_GET,
.handler = control_get_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &get_uri);
httpd_uri_t post_uri = {
.uri = "/api/control",
.method = HTTP_POST,
.handler = control_post_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &post_uri);
ESP_LOGI(TAG, "Registered /api/control endpoints (GET, POST)");
}

175
main/service/datalog.c Normal file
View File

@@ -0,0 +1,175 @@
#include "datalog.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include "esp_littlefs.h"
#include "esp_log.h"
static const char* TAG = "DATALOG";
static const char* LOG_FILE_PATH = "/littlefs/datalog.csv";
static const char* TEMP_LOG_FILE_PATH = "/littlefs/datalog.tmp";
#define MAX_LOG_SIZE (1024 * 1024)
void datalog_init(void)
{
ESP_LOGI(TAG, "Initializing DataLog with LittleFS");
esp_vfs_littlefs_conf_t conf = {
.base_path = "/littlefs",
.partition_label = "littlefs",
.format_if_mount_failed = true,
.dont_mount = false,
};
esp_err_t ret = esp_vfs_littlefs_register(&conf);
if (ret != ESP_OK)
{
if (ret == ESP_FAIL)
{
ESP_LOGE(TAG, "Failed to mount or format filesystem");
}
else if (ret == ESP_ERR_NOT_FOUND)
{
ESP_LOGE(TAG, "Failed to find LittleFS partition");
}
else
{
ESP_LOGE(TAG, "Failed to initialize LittleFS (%s)", esp_err_to_name(ret));
}
return;
}
size_t total = 0, used = 0;
ret = esp_littlefs_info(NULL, &total, &used);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to get LittleFS partition information (%s)", esp_err_to_name(ret));
}
else
{
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
}
// Check if file exists
FILE* f = fopen(LOG_FILE_PATH, "r");
if (f == NULL)
{
ESP_LOGI(TAG, "Log file not found, creating new one.");
FILE* f_write = fopen(LOG_FILE_PATH, "w");
if (f_write == NULL)
{
ESP_LOGE(TAG, "Failed to create log file.");
}
else
{
// Add header
fprintf(f_write, "timestamp,voltage,current,power\n");
fclose(f_write);
}
}
else
{
ESP_LOGI(TAG, "Log file found.");
fclose(f);
}
}
void datalog_add(uint32_t timestamp, float voltage, float current, float power)
{
char new_line[100];
int new_line_len = snprintf(new_line, sizeof(new_line), "%lu,%.3f,%.3f,%.3f\n", timestamp, voltage, current, power);
struct stat st;
long size = 0;
if (stat(LOG_FILE_PATH, &st) == 0)
{
size = st.st_size;
}
if (size + new_line_len <= MAX_LOG_SIZE)
{
FILE* f = fopen(LOG_FILE_PATH, "a");
if (f == NULL)
{
ESP_LOGE(TAG, "Failed to open log file for appending.");
return;
}
fputs(new_line, f);
fclose(f);
}
else
{
ESP_LOGI(TAG, "Log file is full. Rotating log file.");
FILE* f_read = fopen(LOG_FILE_PATH, "r");
if (f_read == NULL)
{
ESP_LOGE(TAG, "Could not open log for reading");
return;
}
FILE* f_write = fopen(TEMP_LOG_FILE_PATH, "w");
if (f_write == NULL)
{
ESP_LOGE(TAG, "Could not open temp file for writing");
fclose(f_read);
return;
}
long size_to_remove = (size + new_line_len) - MAX_LOG_SIZE;
char line[256];
// Keep header
if (fgets(line, sizeof(line), f_read) != NULL)
{
fputs(line, f_write);
}
else
{
ESP_LOGE(TAG, "Could not read header");
fclose(f_read);
fclose(f_write);
return;
}
long bytes_skipped = 0;
while (fgets(line, sizeof(line), f_read) != NULL)
{
bytes_skipped += strlen(line);
if (bytes_skipped >= size_to_remove)
{
fputs(line, f_write);
break;
}
}
while (fgets(line, sizeof(line), f_read) != NULL)
{
fputs(line, f_write);
}
fputs(new_line, f_write);
fclose(f_read);
fclose(f_write);
if (remove(LOG_FILE_PATH) != 0)
{
ESP_LOGE(TAG, "Failed to remove old log file");
}
else if (rename(TEMP_LOG_FILE_PATH, LOG_FILE_PATH) != 0)
{
ESP_LOGE(TAG, "Failed to rename temp file");
}
else
{
ESP_LOGI(TAG, "Log file rotated successfully.");
}
}
}
const char* datalog_get_path(void)
{
return LOG_FILE_PATH;
}

10
main/service/datalog.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef MAIN_SERVICE_DATALOG_H_
#define MAIN_SERVICE_DATALOG_H_
#include <stdint.h>
void datalog_init(void);
void datalog_add(uint32_t timestamp, float voltage, float current, float power);
const char* datalog_get_path(void);
#endif /* MAIN_SERVICE_DATALOG_H_ */

135
main/service/monitor.c Normal file
View File

@@ -0,0 +1,135 @@
//
// Created by shinys on 25. 8. 18..
//
#include "monitor.h"
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "cJSON.h"
#include "esp_netif.h"
#include "esp_wifi_types_generic.h"
#include "ina226.h"
#include "webserver.h"
#include "wifi.h"
#include "datalog.h"
#define INA226_SDA CONFIG_GPIO_INA226_SDA
#define INA226_SCL CONFIG_GPIO_INA226_SCL
ina226_t ina;
i2c_master_bus_handle_t bus_handle;
i2c_master_dev_handle_t dev_handle;
// Timer callback function to read sensor data
static void sensor_timer_callback(void *arg)
{
// Generate random sensor data
float voltage = 0;
float current = 0;
float power = 0;
ina226_get_bus_voltage(&ina, &voltage);
ina226_get_power(&ina, &power);
ina226_get_current(&ina, &current);
// Get system uptime
int64_t uptime_us = esp_timer_get_time();
uint32_t uptime_sec = (uint32_t)(uptime_us / 1000000);
uint32_t timestamp = (uint32_t)time(NULL);
datalog_add(timestamp, voltage, current, power);
// Create JSON object with sensor data
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "type", "sensor_data");
cJSON_AddNumberToObject(root, "voltage", voltage);
cJSON_AddNumberToObject(root, "current", current);
cJSON_AddNumberToObject(root, "power", power);
cJSON_AddNumberToObject(root, "timestamp", timestamp);
cJSON_AddNumberToObject(root, "uptime_sec", uptime_sec);
// Push data to WebSocket clients
push_data_to_ws(root);
}
static void status_wifi_callback(void *arg)
{
wifi_ap_record_t ap_info;
cJSON *root = cJSON_CreateObject();
if (wifi_get_current_ap_info(&ap_info) == ESP_OK) {
cJSON_AddStringToObject(root, "type", "wifi_status");
cJSON_AddBoolToObject(root, "connected", true);
cJSON_AddStringToObject(root, "ssid", (const char *)ap_info.ssid);
cJSON_AddNumberToObject(root, "rssi", ap_info.rssi);
} else {
cJSON_AddBoolToObject(root, "connected", false);
}
push_data_to_ws(root);
}
ina226_config_t ina_config = {
.i2c_port = I2C_NUM_0,
.i2c_addr = 0x40,
.timeout_ms = 100,
.averages = INA226_AVERAGES_16,
.bus_conv_time = INA226_BUS_CONV_TIME_1100_US,
.shunt_conv_time = INA226_SHUNT_CONV_TIME_1100_US,
.mode = INA226_MODE_SHUNT_BUS_CONT,
.r_shunt = 0.01f,
.max_current = 8
};
static void init_ina226()
{
i2c_master_bus_config_t bus_config = {
.i2c_port = I2C_NUM_0,
.sda_io_num = (gpio_num_t) INA226_SDA,
.scl_io_num = (gpio_num_t) INA226_SCL,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = false,
};
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &bus_handle));
i2c_device_config_t dev_config = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = 0x40,
.scl_speed_hz = 400000,
};
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_config, &dev_handle));
ESP_ERROR_CHECK(ina226_init(&ina, dev_handle, &ina_config));
}
static esp_timer_handle_t sensor_timer;
static esp_timer_handle_t wifi_status_timer;
void init_status_monitor()
{
init_ina226();
datalog_init();
// Timer configuration
const esp_timer_create_args_t sensor_timer_args = {
.callback = &sensor_timer_callback,
.name = "sensor_reading_timer" // Optional name for debugging
};
const esp_timer_create_args_t wifi_timer_args = {
.callback = &status_wifi_callback,
.name = "wifi_status_timer" // Optional name for debugging
};
ESP_ERROR_CHECK(esp_timer_create(&sensor_timer_args, &sensor_timer));
ESP_ERROR_CHECK(esp_timer_create(&wifi_timer_args, &wifi_status_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(sensor_timer, 1000000)); // 1sec
ESP_ERROR_CHECK(esp_timer_start_periodic(wifi_status_timer, 1000000 * 5)); // 5s in microseconds
}

25
main/service/monitor.h Normal file
View File

@@ -0,0 +1,25 @@
//
// Created by shinys on 25. 8. 18..
//
#ifndef ODROID_REMOTE_HTTP_MONITOR_H
#define ODROID_REMOTE_HTTP_MONITOR_H
#include <stdint.h>
#include "esp_http_server.h"
// 버퍼에 저장할 데이터의 개수
#define SENSOR_BUFFER_SIZE 100
// 단일 센서 데이터를 저장하기 위한 구조체
typedef struct {
float voltage;
float current;
float power;
uint32_t timestamp; // 데이터를 읽은 시간 (부팅 후 ms)
} sensor_data_t;
void init_status_monitor();
#endif //ODROID_REMOTE_HTTP_MONITOR_H

249
main/service/setting.c Normal file
View File

@@ -0,0 +1,249 @@
#include "webserver.h"
#include "cJSON.h"
#include "esp_http_server.h"
#include "esp_log.h"
#include "nconfig.h"
#include "wifi.h"
#include "system.h"
#include "esp_netif.h"
#include "freertos/task.h"
static const char *TAG = "webserver";
static esp_err_t setting_get_handler(httpd_req_t *req)
{
wifi_ap_record_t ap_info;
cJSON *root = cJSON_CreateObject();
char mode_buf[16];
if (nconfig_read(WIFI_MODE, mode_buf, sizeof(mode_buf)) == ESP_OK) {
cJSON_AddStringToObject(root, "mode", mode_buf);
} else {
cJSON_AddStringToObject(root, "mode", "sta"); // Default to sta
}
char net_type_buf[16];
if (nconfig_read(NETIF_TYPE, net_type_buf, sizeof(net_type_buf)) == ESP_OK) {
cJSON_AddStringToObject(root, "net_type", net_type_buf);
} else {
cJSON_AddStringToObject(root, "net_type", "dhcp"); // Default to dhcp
}
// Add baudrate to the response
char baud_buf[16];
if (nconfig_read(UART_BAUD_RATE, baud_buf, sizeof(baud_buf)) == ESP_OK) {
cJSON_AddStringToObject(root, "baudrate", baud_buf);
}
if (wifi_get_current_ap_info(&ap_info) == ESP_OK) {
cJSON_AddBoolToObject(root, "connected", true);
cJSON_AddStringToObject(root, "ssid", (const char *)ap_info.ssid);
cJSON_AddNumberToObject(root, "rssi", ap_info.rssi);
esp_netif_ip_info_t ip_info;
cJSON* ip_obj = cJSON_CreateObject();
if (wifi_get_current_ip_info(&ip_info) == ESP_OK) {
char ip_str[16];
esp_ip4addr_ntoa(&ip_info.ip, ip_str, sizeof(ip_str));
cJSON_AddStringToObject(ip_obj, "ip", ip_str);
esp_ip4addr_ntoa(&ip_info.gw, ip_str, sizeof(ip_str));
cJSON_AddStringToObject(ip_obj, "gateway", ip_str);
esp_ip4addr_ntoa(&ip_info.netmask, ip_str, sizeof(ip_str));
cJSON_AddStringToObject(ip_obj, "subnet", ip_str);
}
esp_netif_dns_info_t dns_info;
char dns_str[16];
if (wifi_get_dns_info(ESP_NETIF_DNS_MAIN, &dns_info) == ESP_OK) {
esp_ip4addr_ntoa(&dns_info.ip.u_addr.ip4, dns_str, sizeof(dns_str));
cJSON_AddStringToObject(ip_obj, "dns1", dns_str);
}
if (wifi_get_dns_info(ESP_NETIF_DNS_BACKUP, &dns_info) == ESP_OK) {
esp_ip4addr_ntoa(&dns_info.ip.u_addr.ip4, dns_str, sizeof(dns_str));
cJSON_AddStringToObject(ip_obj, "dns2", dns_str);
}
cJSON_AddItemToObject(root, "ip", ip_obj);
} else {
cJSON_AddBoolToObject(root, "connected", false);
}
const char *json_string = cJSON_Print(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_string, HTTPD_RESP_USE_STRLEN);
cJSON_Delete(root);
free((void*)json_string);
return ESP_OK;
}
static esp_err_t wifi_scan(httpd_req_t *req)
{
wifi_ap_record_t *ap_records;
uint16_t count;
wifi_scan_aps(&ap_records, &count);
cJSON *root = cJSON_CreateArray();
for (int i = 0; i < count; i++)
{
cJSON *ap_obj = cJSON_CreateObject();
cJSON_AddStringToObject(ap_obj, "ssid", (const char *)ap_records[i].ssid);
cJSON_AddNumberToObject(ap_obj, "rssi", ap_records[i].rssi);
cJSON_AddStringToObject(ap_obj, "authmode", auth_mode_str(ap_records[i].authmode));
cJSON_AddItemToArray(root, ap_obj);
}
if (count > 0)
free(ap_records);
const char *json_string = cJSON_Print(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_string, HTTPD_RESP_USE_STRLEN);
cJSON_Delete(root);
free((void*)json_string);
return ESP_OK;
}
static esp_err_t setting_post_handler(httpd_req_t *req)
{
char buf[512];
int received = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (received <= 0) {
if (received == HTTPD_SOCK_ERR_TIMEOUT) httpd_resp_send_408(req);
return ESP_FAIL;
}
buf[received] = '\0';
cJSON *root = cJSON_Parse(buf);
if (root == NULL) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
}
cJSON *mode_item = cJSON_GetObjectItem(root, "mode");
cJSON *net_type_item = cJSON_GetObjectItem(root, "net_type");
cJSON *ssid_item = cJSON_GetObjectItem(root, "ssid");
cJSON *baud_item = cJSON_GetObjectItem(root, "baudrate");
if (mode_item && cJSON_IsString(mode_item)) {
const char* mode = mode_item->valuestring;
ESP_LOGI(TAG, "Received mode switch request: %s", mode);
if (strcmp(mode, "sta") == 0 || strcmp(mode, "apsta") == 0) {
if (strcmp(mode, "apsta") == 0) {
cJSON *ap_ssid_item = cJSON_GetObjectItem(root, "ap_ssid");
cJSON *ap_pass_item = cJSON_GetObjectItem(root, "ap_password");
if (ap_ssid_item && cJSON_IsString(ap_ssid_item)) {
nconfig_write(AP_SSID, ap_ssid_item->valuestring);
} else {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "AP SSID required for APSTA mode");
cJSON_Delete(root);
return ESP_FAIL;
}
if (ap_pass_item && cJSON_IsString(ap_pass_item)) {
nconfig_write(AP_PASSWORD, ap_pass_item->valuestring);
} else {
nconfig_delete(AP_PASSWORD); // Open network
}
}
wifi_switch_mode(mode);
httpd_resp_sendstr(req, "{\"status\":\"mode_switch_initiated\"}");
} else {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid mode");
}
} else if (net_type_item && cJSON_IsString(net_type_item)) {
const char* type = net_type_item->valuestring;
ESP_LOGI(TAG, "Received network config: %s", type);
if (strcmp(type, "static") == 0) {
cJSON *ip_item = cJSON_GetObjectItem(root, "ip");
cJSON *gw_item = cJSON_GetObjectItem(root, "gateway");
cJSON *sn_item = cJSON_GetObjectItem(root, "subnet");
cJSON *d1_item = cJSON_GetObjectItem(root, "dns1");
cJSON *d2_item = cJSON_GetObjectItem(root, "dns2");
const char* ip = cJSON_IsString(ip_item) ? ip_item->valuestring : NULL;
const char* gw = cJSON_IsString(gw_item) ? gw_item->valuestring : NULL;
const char* sn = cJSON_IsString(sn_item) ? sn_item->valuestring : NULL;
const char* d1 = cJSON_IsString(d1_item) ? d1_item->valuestring : NULL;
const char* d2 = cJSON_IsString(d2_item) ? d2_item->valuestring : NULL;
if (ip && gw && sn && d1) {
nconfig_write(NETIF_TYPE, "static");
nconfig_write(NETIF_IP, ip);
nconfig_write(NETIF_GATEWAY, gw);
nconfig_write(NETIF_SUBNET, sn);
nconfig_write(NETIF_DNS1, d1);
if (d2) nconfig_write(NETIF_DNS2, d2); else nconfig_delete(NETIF_DNS2);
wifi_use_static(ip, gw, sn, d1, d2);
httpd_resp_sendstr(req, "{\"status\":\"static_config_applied\"}");
} else {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing static IP fields");
}
} else if (strcmp(type, "dhcp") == 0) {
nconfig_write(NETIF_TYPE, "dhcp");
wifi_use_dhcp();
httpd_resp_sendstr(req, "{\"status\":\"dhcp_config_applied\"}");
}
} else if (ssid_item && cJSON_IsString(ssid_item)) {
cJSON *pass_item = cJSON_GetObjectItem(root, "password");
if (cJSON_IsString(pass_item)) {
nconfig_write(WIFI_SSID, ssid_item->valuestring);
nconfig_write(WIFI_PASSWORD, pass_item->valuestring);
nconfig_write(NETIF_TYPE, "dhcp"); // Default to DHCP on new connection
httpd_resp_sendstr(req, "{\"status\":\"connection_initiated\"}");
wifi_disconnect();
vTaskDelay(pdMS_TO_TICKS(500));
wifi_connect();
} else {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Password required");
}
} else if (baud_item && cJSON_IsString(baud_item)) {
const char* baudrate = baud_item->valuestring;
ESP_LOGI(TAG, "Received baudrate set request: %s", baudrate);
nconfig_write(UART_BAUD_RATE, baudrate);
change_baud_rate(strtol(baudrate, NULL, 10));
httpd_resp_sendstr(req, "{\"status\":\"baudrate_updated\"}");
} else {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid payload");
}
cJSON_Delete(root);
return ESP_OK;
}
void register_wifi_endpoint(httpd_handle_t server)
{
httpd_uri_t status = {
.uri = "/api/setting",
.method = HTTP_GET,
.handler = setting_get_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &status);
httpd_uri_t set = {
.uri = "/api/setting",
.method = HTTP_POST,
.handler = setting_post_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &set);
httpd_uri_t scan = {
.uri = "/api/wifi/scan",
.method = HTTP_GET,
.handler = wifi_scan,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &scan);
}

102
main/service/webserver.c Normal file
View File

@@ -0,0 +1,102 @@
#include "webserver.h"
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "driver/uart.h"
#include "esp_http_server.h"
#include "indicator.h"
#include "nconfig.h"
#include "monitor.h"
#include "wifi.h"
#include "datalog.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
static const char *TAG = "WEBSERVER";
static esp_err_t index_handler(httpd_req_t *req) {
extern const unsigned char index_html_start[] asm("_binary_index_html_gz_start");
extern const unsigned char index_html_end[] asm("_binary_index_html_gz_end");
const size_t index_html_size = (index_html_end - index_html_start);
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, (const char *)index_html_start, index_html_size);
return ESP_OK;
}
static esp_err_t datalog_download_handler(httpd_req_t *req)
{
const char *filepath = datalog_get_path();
FILE *f = fopen(filepath, "r");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open datalog file for reading");
httpd_resp_send_404(req);
return ESP_FAIL;
}
httpd_resp_set_type(req, "text/csv");
httpd_resp_set_hdr(req, "Content-Disposition", "attachment; filename=\"datalog.csv\"");
char buffer[1024];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), f)) > 0) {
if (httpd_resp_send_chunk(req, buffer, bytes_read) != ESP_OK) {
ESP_LOGE(TAG, "File sending failed!");
fclose(f);
httpd_resp_send_chunk(req, NULL, 0);
httpd_resp_send_500(req);
return ESP_FAIL;
}
}
fclose(f);
httpd_resp_send_chunk(req, NULL, 0);
return ESP_OK;
}
// HTTP 서버 시작
void start_webserver(void) {
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.stack_size = 1024 * 8;
config.max_uri_handlers = 10;
if (httpd_start(&server, &config) != ESP_OK) {
return ;
}
// Index page
httpd_uri_t index = {
.uri = "/",
.method = HTTP_GET,
.handler = index_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &index);
httpd_uri_t datalog_uri = {
.uri = "/datalog.csv",
.method = HTTP_GET,
.handler = datalog_download_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &datalog_uri);
register_wifi_endpoint(server);
register_ws_endpoint(server);
register_control_endpoint(server);
init_status_monitor();
}

17
main/service/webserver.h Normal file
View File

@@ -0,0 +1,17 @@
//
// Created by shinys on 25. 8. 18..
//
#ifndef ODROID_REMOTE_HTTP_WEBSERVER_H
#define ODROID_REMOTE_HTTP_WEBSERVER_H
#include "cJSON.h"
#include "esp_http_server.h"
#include "system.h"
void register_wifi_endpoint(httpd_handle_t server);
void register_ws_endpoint(httpd_handle_t server);
void register_control_endpoint(httpd_handle_t server);
void push_data_to_ws(cJSON *data);
esp_err_t change_baud_rate(int baud_rate);
#endif //ODROID_REMOTE_HTTP_WEBSERVER_H

178
main/service/ws.c Normal file
View File

@@ -0,0 +1,178 @@
//
// Created by shinys on 25. 8. 18..
//
#include "cJSON.h"
#include "webserver.h"
#include "esp_err.h"
#include "esp_http_server.h"
#include "esp_log.h"
#include "nconfig.h"
#include "driver/uart.h"
#define UART_NUM UART_NUM_1
#define BUF_SIZE (4096)
#define RD_BUF_SIZE (BUF_SIZE)
#define UART_TX_PIN CONFIG_GPIO_UART_TX
#define UART_RX_PIN CONFIG_GPIO_UART_RX
static const char *TAG = "ws-uart";
static int client_fd = -1;
struct status_message
{
cJSON *data;
};
QueueHandle_t status_queue;
// Status task
static void status_task(void *arg)
{
httpd_handle_t server = (httpd_handle_t)arg;
struct status_message msg;
while (1) {
if (xQueueReceive(status_queue, &msg, portMAX_DELAY)) {
if (client_fd <= 0) continue;
char *json_string = cJSON_Print(msg.data);
httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.payload = (uint8_t *)json_string;
ws_pkt.len = strlen(json_string);
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
esp_err_t err = httpd_ws_send_frame_async(server, client_fd, &ws_pkt);
free(json_string);
cJSON_Delete(msg.data);
if (err != ESP_OK)
{
// try close...
httpd_ws_frame_t close_frame = {
.final = true,
.fragmented = false,
.type = HTTPD_WS_TYPE_CLOSE,
.payload = NULL,
.len = 0
};
httpd_ws_send_frame_async(server, client_fd, &close_frame);
client_fd = -1;
}
}
vTaskDelay(1);
}
}
// UART task
static void uart_read_task(void *arg) {
httpd_handle_t server = (httpd_handle_t)arg;
uint8_t data[RD_BUF_SIZE];
while (1) {
int len = uart_read_bytes(UART_NUM, data, RD_BUF_SIZE, 10 / portTICK_PERIOD_MS);
if (len > 0 && client_fd != -1) {
httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.payload = data;
ws_pkt.len = len;
ws_pkt.type = HTTPD_WS_TYPE_BINARY;
esp_err_t err = httpd_ws_send_frame_async(server, client_fd, &ws_pkt);
if (err != ESP_OK)
{
// try close...
httpd_ws_frame_t close_frame = {
.final = true,
.fragmented = false,
.type = HTTPD_WS_TYPE_CLOSE,
.payload = NULL,
.len = 0
};
httpd_ws_send_frame_async(server, client_fd, &close_frame);
client_fd = -1;
}
}
vTaskDelay(1);
}
}
// 웹소켓 처리 핸들러
static esp_err_t ws_handler(httpd_req_t *req) {
if (req->method == HTTP_GET) {
ESP_LOGI(TAG, "Accept websocket connection");
client_fd = httpd_req_to_sockfd(req);
xQueueReset(status_queue);
return ESP_OK;
}
httpd_ws_frame_t ws_pkt;
uint8_t buf[BUF_SIZE];
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.payload = buf;
ws_pkt.type = HTTPD_WS_TYPE_BINARY;
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, BUF_SIZE);
if (ret != ESP_OK) {
ESP_LOGI(TAG, "웹소켓 프레임 수신 실패");
return ret;
}
uart_write_bytes(UART_NUM, (const char *)ws_pkt.payload, ws_pkt.len);
return ESP_OK;
}
void register_ws_endpoint(httpd_handle_t server)
{
size_t baud_rate_len;
nconfig_get_str_len(UART_BAUD_RATE, &baud_rate_len);
char buf[baud_rate_len];
nconfig_read(UART_BAUD_RATE, buf, baud_rate_len);
uart_config_t uart_config = {
.baud_rate = strtol(buf, NULL, 10),
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
// .source_clk = UART_SCLK_APB,
};
ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(UART_NUM, UART_TX_PIN, UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
ESP_ERROR_CHECK(uart_driver_install(UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 0, NULL, ESP_INTR_FLAG_IRAM));
httpd_uri_t ws = {
.uri = "/ws",
.method = HTTP_GET,
.handler = ws_handler,
.user_ctx = NULL,
.is_websocket = true
};
httpd_register_uri_handler(server, &ws);
status_queue = xQueueCreate(10, sizeof(struct status_message));
xTaskCreate(uart_read_task, "uart_read_task", 1024*6, server, 8, NULL);
xTaskCreate(status_task, "status_task", 4096, server, 7, NULL);
}
void push_data_to_ws(cJSON *data)
{
struct status_message msg;
msg.data = data;
if (xQueueSend(status_queue, &msg, 10) != pdPASS)
{
ESP_LOGW(TAG, "Queue full");
}
}
esp_err_t change_baud_rate(int baud_rate)
{
return uart_set_baudrate(UART_NUM, baud_rate);
}

46
main/system/system.c Normal file
View File

@@ -0,0 +1,46 @@
//
// Created by shinys on 25. 8. 5.
//
#include <system.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_log.h>
static const char *TAG = "odroid";
int t = 0;
TaskHandle_t reboot_handle = NULL;
static void reboot_task(void *arg)
{
while (t > 0)
{
ESP_LOGW(TAG, "ESP will reboot in [%d] sec..., If you want stop reboot, use command \"reboot -s\"", t);
vTaskDelay(1000 / portTICK_PERIOD_MS);
--t;
}
esp_restart();
}
void start_reboot_timer(int sec)
{
if (reboot_handle != NULL)
{
ESP_LOGW(TAG, "The reboot timer is already running.");
return;
}
t = sec;
xTaskCreate(reboot_task, "reboot_task", 2048, NULL, 8, &reboot_handle);
}
void stop_reboot_timer()
{
if (reboot_handle == NULL)
{
return;
}
vTaskDelete(reboot_handle);
}

550
main/wifi/wifi.c Normal file
View File

@@ -0,0 +1,550 @@
//
// Created by shinys on 25. 7. 10.
//
#include "wifi.h"
#include "nconfig.h"
#include "indicator.h"
#include <string.h>
#include <system.h>
#include <lwip/sockets.h>
#include <time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "esp_wifi_default.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_netif_sntp.h"
#include "rom/ets_sys.h"
static const char *TAG = "odroid";
#define MAX_RETRY 10
#define MAX_SCAN 20
const char* auth_mode_str(wifi_auth_mode_t mode)
{
switch (mode)
{
case WIFI_AUTH_OPEN:
return "OPEN";
case WIFI_AUTH_WEP:
return "WEP";
case WIFI_AUTH_WPA_PSK:
return "WPA_PSK";
case WIFI_AUTH_WPA2_PSK:
return "WPA2_PSK";
case WIFI_AUTH_WPA_WPA2_PSK:
return "WPA_WPA2_PSK";
case WIFI_AUTH_ENTERPRISE:
return "ENTERPRISE";
case WIFI_AUTH_WPA3_PSK:
return "WPA3_PSK";
case WIFI_AUTH_WPA2_WPA3_PSK:
return "WPA2_WPA3_PSK";
case WIFI_AUTH_WAPI_PSK:
return "WAPI_PSK";
case WIFI_AUTH_OWE:
return "OWE";
case WIFI_AUTH_WPA3_ENT_192:
return "WPA3_ENT_192";
case WIFI_AUTH_WPA3_EXT_PSK:
return "WPA3_EXT_PSK";
case WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE:
return "WPA3_EXT_PSK_MIXED_MODE";
case WIFI_AUTH_DPP:
return "DPP";
case WIFI_AUTH_WPA3_ENTERPRISE:
return "WPA3_ENTERPRISE";
case WIFI_AUTH_WPA2_WPA3_ENTERPRISE:
return "WPA2_WPA3_ENTERPRISE";
default:
return "UNKNOWN";
}
}
static const char* wifi_reason_str(wifi_err_reason_t reason) {
switch (reason) {
case WIFI_REASON_UNSPECIFIED: return "UNSPECIFIED";
case WIFI_REASON_AUTH_EXPIRE: return "AUTH_EXPIRE";
case WIFI_REASON_AUTH_LEAVE: return "AUTH_LEAVE";
case WIFI_REASON_ASSOC_EXPIRE: return "ASSOC_EXPIRE";
case WIFI_REASON_ASSOC_TOOMANY: return "ASSOC_TOOMANY";
case WIFI_REASON_NOT_AUTHED: return "NOT_AUTHED";
case WIFI_REASON_NOT_ASSOCED: return "NOT_ASSOCED";
case WIFI_REASON_ASSOC_LEAVE: return "ASSOC_LEAVE";
case WIFI_REASON_ASSOC_NOT_AUTHED: return "ASSOC_NOT_AUTHED";
case WIFI_REASON_DISASSOC_PWRCAP_BAD: return "DISASSOC_PWRCAP_BAD";
case WIFI_REASON_DISASSOC_SUPCHAN_BAD: return "DISASSOC_SUPCHAN_BAD";
case WIFI_REASON_IE_INVALID: return "IE_INVALID";
case WIFI_REASON_MIC_FAILURE: return "MIC_FAILURE";
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: return "4WAY_HANDSHAKE_TIMEOUT";
case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: return "GROUP_KEY_UPDATE_TIMEOUT";
case WIFI_REASON_IE_IN_4WAY_DIFFERS: return "IE_IN_4WAY_DIFFERS";
case WIFI_REASON_GROUP_CIPHER_INVALID: return "GROUP_CIPHER_INVALID";
case WIFI_REASON_PAIRWISE_CIPHER_INVALID: return "PAIRWISE_CIPHER_INVALID";
case WIFI_REASON_AKMP_INVALID: return "AKMP_INVALID";
case WIFI_REASON_UNSUPP_RSN_IE_VERSION: return "UNSUPP_RSN_IE_VERSION";
case WIFI_REASON_INVALID_RSN_IE_CAP: return "INVALID_RSN_IE_CAP";
case WIFI_REASON_802_1X_AUTH_FAILED: return "802_1X_AUTH_FAILED";
case WIFI_REASON_CIPHER_SUITE_REJECTED: return "CIPHER_SUITE_REJECTED";
case WIFI_REASON_INVALID_PMKID: return "INVALID_PMKID";
case WIFI_REASON_BEACON_TIMEOUT: return "BEACON_TIMEOUT";
case WIFI_REASON_NO_AP_FOUND: return "NO_AP_FOUND";
case WIFI_REASON_AUTH_FAIL: return "AUTH_FAIL";
case WIFI_REASON_ASSOC_FAIL: return "ASSOC_FAIL";
case WIFI_REASON_HANDSHAKE_TIMEOUT: return "HANDSHAKE_TIMEOUT";
case WIFI_REASON_CONNECTION_FAIL: return "CONNECTION_FAIL";
case WIFI_REASON_AP_TSF_RESET: return "AP_TSF_RESET";
case WIFI_REASON_ROAMING: return "ROAMING";
default: return "UNKNOWN";
}
}
static esp_netif_t *wifi_sta_netif = NULL;
static esp_netif_t *wifi_ap_netif = NULL;
static int s_retry_num = 0;
static esp_err_t wifi_sta_do_disconnect(void);
static void sntp_sync_time_cb(struct timeval *tv)
{
time_t now = 0;
struct tm timeinfo = { 0 };
time(&now);
localtime_r(&now, &timeinfo);
char strftime_buf[64];
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI(TAG, "Time synchronized: %s", strftime_buf);
}
static void handler_on_wifi_disconnect(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
s_retry_num++;
if (s_retry_num > MAX_RETRY) {
ESP_LOGW(TAG, "WiFi Connect failed %d times, stop reconnect.", s_retry_num);
/* let example_wifi_sta_do_connect() return */
wifi_sta_do_disconnect();
start_reboot_timer(60);
return;
}
wifi_event_sta_disconnected_t *disconn = event_data;
if (disconn->reason == WIFI_REASON_ROAMING) {
ESP_LOGD(TAG, "station roaming, do nothing");
return;
}
ESP_LOGW(TAG, "Wi-Fi disconnected, reason: (%s)", wifi_reason_str(disconn->reason));
ESP_LOGI(TAG, "Trying to reconnect...");
esp_err_t err = esp_wifi_connect();
if (err == ESP_ERR_WIFI_NOT_STARTED) {
return;
}
ESP_ERROR_CHECK(err);
}
static void handler_on_wifi_connect(void *esp_netif, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
}
static void handler_on_sta_got_ip(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
stop_reboot_timer();
s_retry_num = 0;
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
if (strcmp("sta", esp_netif_get_desc(event->esp_netif)) != 0) {
return;
}
ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip));
ESP_LOGI(TAG, "- IPv4 address: " IPSTR ",", IP2STR(&event->ip_info.ip));
sync_time();
led_set(LED_BLU, BLINK_SOLID);
}
static void wifi_ap_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
MAC2STR(event->mac), event->aid);
}
}
static esp_err_t set_hostname(esp_netif_t* esp_netif, const char *hostname)
{
if (esp_netif_set_hostname(esp_netif, hostname) != ESP_OK) return ESP_FAIL;
return ESP_OK;
}
static void wifi_start(wifi_mode_t mode)
{
size_t hostname_len;
char type_buf[16];
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
if (mode == WIFI_MODE_STA || mode == WIFI_MODE_APSTA) {
esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_WIFI_STA();
wifi_sta_netif = esp_netif_create_wifi(WIFI_IF_STA, &esp_netif_config);
if (nconfig_read(NETIF_TYPE, type_buf, sizeof(type_buf)) == ESP_OK && strcmp(type_buf, "static") == 0) {
ESP_LOGI(TAG, "Using static IP configuration");
char ip_buf[16], gw_buf[16], mask_buf[16], dns1_buf[16], dns2_buf[16];
nconfig_read(NETIF_IP, ip_buf, sizeof(ip_buf));
nconfig_read(NETIF_GATEWAY, gw_buf, sizeof(gw_buf));
nconfig_read(NETIF_SUBNET, mask_buf, sizeof(mask_buf));
const char* dns1 = (nconfig_read(NETIF_DNS1, dns1_buf, sizeof(dns1_buf)) == ESP_OK) ? dns1_buf : NULL;
const char* dns2 = (nconfig_read(NETIF_DNS2, dns2_buf, sizeof(dns2_buf)) == ESP_OK) ? dns2_buf : NULL;
if (dns1 == NULL)
wifi_use_static(ip_buf, gw_buf, mask_buf, "8.8.8.8", "8.8.4.4");
else
wifi_use_static(ip_buf, gw_buf, mask_buf, dns1, dns2);
} else {
ESP_LOGI(TAG, "Using DHCP configuration");
wifi_use_dhcp();
}
nconfig_get_str_len(NETIF_HOSTNAME, &hostname_len);
char buf[hostname_len];
nconfig_read(NETIF_HOSTNAME, buf, sizeof(buf));
set_hostname(wifi_sta_netif, buf);
}
if (mode == WIFI_MODE_AP || mode == WIFI_MODE_APSTA) {
esp_netif_inherent_config_t esp_netif_config_ap = ESP_NETIF_INHERENT_DEFAULT_WIFI_AP();
wifi_ap_netif = esp_netif_create_wifi(WIFI_IF_AP, &esp_netif_config_ap);
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_AP_STACONNECTED, &wifi_ap_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_AP_STADISCONNECTED, &wifi_ap_event_handler, NULL));
}
esp_wifi_set_default_wifi_sta_handlers();
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_wifi_set_mode(mode));
ESP_ERROR_CHECK(esp_wifi_start());
}
static void wifi_stop(void)
{
esp_err_t err = esp_wifi_stop();
if (err == ESP_ERR_WIFI_NOT_INIT) {
return;
}
ESP_ERROR_CHECK(err);
if (wifi_ap_netif) {
esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_AP_STACONNECTED, &wifi_ap_event_handler);
esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_AP_STADISCONNECTED, &wifi_ap_event_handler);
}
ESP_ERROR_CHECK(esp_wifi_deinit());
if (wifi_sta_netif) {
esp_netif_destroy(wifi_sta_netif);
wifi_sta_netif = NULL;
}
if (wifi_ap_netif) {
esp_netif_destroy(wifi_ap_netif);
wifi_ap_netif = NULL;
}
}
static esp_err_t wifi_sta_do_connect(wifi_config_t wifi_config)
{
stop_reboot_timer();
s_retry_num = 0;
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, handler_on_wifi_disconnect, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, handler_on_sta_got_ip, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, handler_on_wifi_connect, wifi_sta_netif));
ESP_LOGI(TAG, "Connecting to %s...", (char*)wifi_config.sta.ssid);
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
esp_err_t ret = esp_wifi_connect();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "WiFi connect failed! ret:%x", ret);
return ret;
}
return ESP_OK;
}
static esp_err_t wifi_sta_do_disconnect(void)
{
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &handler_on_wifi_disconnect));
ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &handler_on_sta_got_ip));
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &handler_on_wifi_connect));
led_set(LED_BLU, BLINK_DOUBLE);
return esp_wifi_disconnect();
}
static void wifi_shutdown(void)
{
wifi_sta_do_disconnect();
wifi_stop();
}
static esp_err_t do_connect(void)
{
esp_err_t err;
char mode_buf[16] = {0};
wifi_mode_t mode = WIFI_MODE_STA; // Default mode
if (nconfig_read(WIFI_MODE, mode_buf, sizeof(mode_buf)) == ESP_OK) {
if (strcmp(mode_buf, "apsta") == 0) {
mode = WIFI_MODE_APSTA;
ESP_LOGI(TAG, "Starting in APSTA mode");
} else { // "sta" or anything else defaults to STA
mode = WIFI_MODE_STA;
ESP_LOGI(TAG, "Starting in STA mode");
}
} else {
ESP_LOGI(TAG, "WIFI_MODE not set, defaulting to STA mode");
}
wifi_start(mode);
// Configure and connect STA interface if needed
if (mode == WIFI_MODE_STA || mode == WIFI_MODE_APSTA) {
wifi_config_t sta_config = {0};
bool sta_creds_ok = false;
if (nconfig_read(WIFI_SSID, (char*)sta_config.sta.ssid, 32) == ESP_OK && strlen((char*)sta_config.sta.ssid) > 0) {
if (nconfig_read(WIFI_PASSWORD, (char*)sta_config.sta.password, 64) == ESP_OK) {
sta_creds_ok = true;
}
}
if (sta_creds_ok) {
err = wifi_sta_do_connect(sta_config);
if (err != ESP_OK && mode == WIFI_MODE_STA) {
// In STA-only mode, failure to connect is a fatal error
return err;
}
} else if (mode == WIFI_MODE_STA) {
// In STA-only mode, missing credentials is a fatal error
ESP_LOGE(TAG, "Missing STA credentials in STA mode.");
return ESP_FAIL;
} else {
// In APSTA mode, missing credentials is a warning
ESP_LOGW(TAG, "Missing STA credentials in APSTA mode. STA will not connect.");
}
}
// Configure AP interface if needed
if (mode == WIFI_MODE_AP || mode == WIFI_MODE_APSTA) {
char ap_ssid[32], ap_pass[64];
wifi_config_t ap_config = {
.ap = {
.channel = 1,
.max_connection = 4,
.authmode = WIFI_AUTH_WPA2_PSK,
.ssid_hidden = 0,
},
};
if (nconfig_read(AP_SSID, ap_ssid, sizeof(ap_ssid)) == ESP_OK && strlen(ap_ssid) > 0) {
strcpy((char*)ap_config.ap.ssid, ap_ssid);
} else {
strcpy((char*)ap_config.ap.ssid, "ODROID-REMOTE-AP");
}
if (nconfig_read(AP_PASSWORD, ap_pass, sizeof(ap_pass)) == ESP_OK && strlen(ap_pass) >= 8) {
strcpy((char*)ap_config.ap.password, ap_pass);
} else {
ap_config.ap.authmode = WIFI_AUTH_OPEN;
memset(ap_config.ap.password, 0, sizeof(ap_config.ap.password));
}
ap_config.ap.ssid_len = strlen((char*)ap_config.ap.ssid);
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config));
ESP_LOGI(TAG, "AP configured, SSID: %s", ap_config.ap.ssid);
}
return ESP_OK;
}
esp_err_t wifi_connect(void)
{
led_set(LED_BLU, BLINK_DOUBLE);
static esp_sntp_config_t ntp_cfg = ESP_NETIF_SNTP_DEFAULT_CONFIG_MULTIPLE(3,
ESP_SNTP_SERVER_LIST("time.windows.com", "pool.ntp.org", "216.239.35.0")); // google public ntp
ntp_cfg.start = false;
ntp_cfg.sync_cb = sntp_sync_time_cb;
ntp_cfg.smooth_sync = true; // Sync immediately when started
esp_netif_sntp_init(&ntp_cfg);
if (do_connect() != ESP_OK) {
return ESP_FAIL;
}
ESP_ERROR_CHECK(esp_register_shutdown_handler(&wifi_shutdown));
return ESP_OK;
}
esp_err_t wifi_disconnect(void)
{
wifi_shutdown();
ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&wifi_shutdown));
return ESP_OK;
}
void wifi_scan_aps(wifi_ap_record_t **ap_records, uint16_t* count)
{
ESP_LOGI(TAG, "Starting WiFi scan...");
esp_err_t err = esp_wifi_scan_start(NULL, true);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_wifi_scan_start failed: %s", esp_err_to_name(err));
*count = 0;
*ap_records = NULL;
return;
}
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(count));
ESP_LOGI(TAG, "Found %u access points", *count);
if (*count == 0)
*ap_records = NULL;
else
*ap_records = calloc(*count, sizeof(wifi_ap_record_t));
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(count, *ap_records));
ESP_LOGI(TAG, "Scan done");
}
esp_err_t wifi_get_current_ap_info(wifi_ap_record_t *ap_info)
{
esp_err_t ret = esp_wifi_sta_get_ap_info(ap_info);
if (ret != ESP_OK) {
// Clear ssid and set invalid rssi on error
memset(ap_info->ssid, 0, sizeof(ap_info->ssid));
ap_info->rssi = -127;
}
return ret;
}
esp_err_t wifi_get_current_ip_info(esp_netif_ip_info_t *ip_info)
{
return esp_netif_get_ip_info(wifi_sta_netif, ip_info);
}
esp_err_t wifi_get_dns_info(esp_netif_dns_type_t type, esp_netif_dns_info_t *dns_info)
{
if (wifi_sta_netif) {
return esp_netif_get_dns_info(wifi_sta_netif, type, dns_info);
}
return ESP_FAIL;
}
esp_err_t wifi_use_static(const char *ip, const char *gw, const char *netmask, const char *dns1, const char *dns2)
{
if (wifi_sta_netif == NULL) {
return ESP_FAIL;
}
esp_err_t err = esp_netif_dhcpc_stop(wifi_sta_netif);
if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_NOT_STOPPED) {
ESP_LOGE(TAG, "Failed to stop DHCP client: %s", esp_err_to_name(err));
return err;
}
esp_netif_ip_info_t ip_info;
inet_pton(AF_INET, ip, &ip_info.ip);
inet_pton(AF_INET, gw, &ip_info.gw);
inet_pton(AF_INET, netmask, &ip_info.netmask);
err = esp_netif_set_ip_info(wifi_sta_netif, &ip_info);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set static IP info: %s", esp_err_to_name(err));
return err;
}
esp_netif_dns_info_t dns_info;
if (dns1 && strlen(dns1) > 0) {
inet_pton(AF_INET, dns1, &dns_info.ip.u_addr.ip4);
esp_netif_set_dns_info(wifi_sta_netif, ESP_NETIF_DNS_MAIN, &dns_info);
} else {
esp_netif_set_dns_info(wifi_sta_netif, ESP_NETIF_DNS_MAIN, NULL);
}
if (dns2 && strlen(dns2) > 0) {
inet_pton(AF_INET, dns2, &dns_info.ip.u_addr.ip4);
esp_netif_set_dns_info(wifi_sta_netif, ESP_NETIF_DNS_BACKUP, &dns_info);
} else {
esp_netif_set_dns_info(wifi_sta_netif, ESP_NETIF_DNS_BACKUP, NULL);
}
return ESP_OK;
}
esp_err_t wifi_use_dhcp(void)
{
if (wifi_sta_netif == NULL) {
return ESP_FAIL;
}
esp_err_t err = esp_netif_dhcpc_start(wifi_sta_netif);
if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
ESP_LOGE(TAG, "Failed to start DHCP client: %s", esp_err_to_name(err));
return err;
}
return ESP_OK;
}
esp_err_t wifi_switch_mode(const char* mode)
{
if (strcmp(mode, "sta") != 0 && strcmp(mode, "apsta") != 0) {
ESP_LOGE(TAG, "Invalid mode specified: %s. Use 'sta' or 'apsta'.", mode);
return ESP_ERR_INVALID_ARG;
}
char current_mode_buf[16] = {0};
if (nconfig_read(WIFI_MODE, current_mode_buf, sizeof(current_mode_buf)) == ESP_OK) {
if (strcmp(current_mode_buf, mode) == 0) {
ESP_LOGI(TAG, "Already in %s mode.", mode);
return ESP_OK;
}
}
ESP_LOGI(TAG, "Switching Wi-Fi mode to %s.", mode);
esp_err_t err = nconfig_write(WIFI_MODE, mode);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to save new Wi-Fi mode to NVS");
return err;
}
wifi_disconnect();
err = wifi_connect();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to connect in new mode %s", mode);
return err;
}
ESP_LOGI(TAG, "Successfully switched to %s mode.", mode);
return ESP_OK;
}
void sync_time()
{
esp_netif_sntp_start();
ESP_LOGI(TAG, "SNTP service started, waiting for time synchronization...");
}