ChaosDMX
Open-Source DMX-Interface
Loading...
Searching...
No Matches
config.c
Go to the documentation of this file.
1
7
8#define LOG_TAG "CONFIG"
9
10#include "config.h"
11
12#include "freertos/FreeRTOS.h"
13#include "freertos/semphr.h"
14#include "led.h"
15#include "logger.h"
16#include "nvs.h"
17#include <stdio.h>
18#include <string.h>
19
23#define NVS_NAMESPACE "app_config"
24#define NVS_BLOB_KEY \
25 "config_blob"
26
27/* --- Magic & Version Constants --- */
28#define APP_CONFIG_MAGIC 0x43444D58
29#define APP_CONFIG_VERSION 4
30
34typedef struct {
35 char ssid[32];
36 char password[64];
38
61
65static config_storage_t s_config;
66
71static SemaphoreHandle_t s_config_mutex = NULL;
72
77static bool s_is_initialized = false;
78
83static bool s_is_dirty = false;
84
85/* --- Thread-Safety Helpers --- */
86#define LOCK() \
87 xSemaphoreTake( \
88 s_config_mutex, \
89 portMAX_DELAY)
90#define UNLOCK() \
91 xSemaphoreGive( \
92 s_config_mutex)
93
102static void load_factory_defaults(void) {
108
112
113 for (int i = 0; i < APP_CONFIG_DMX_PORT_COUNT; i++) {
114 s_config.dmx_universes[i] = APP_CONFIG_DEFAULT_START_UNIVERSE + i;
115 s_config.dmx_directions[i] = APP_CONFIG_DEFAULT_DMX_DIR;
116 }
117
118 memset(&s_config.wifi_sta, 0, sizeof(app_wifi_creds_t));
119 memset(&s_config.wifi_ap, 0, sizeof(app_wifi_creds_t));
120
121 snprintf(s_config.wifi_sta.ssid, sizeof(s_config.wifi_sta.ssid), "%s",
123 snprintf(s_config.wifi_sta.password, sizeof(s_config.wifi_sta.password), "%s",
125
126 snprintf(s_config.wifi_ap.ssid, sizeof(s_config.wifi_ap.ssid), "%s",
128 snprintf(s_config.wifi_ap.password, sizeof(s_config.wifi_ap.password), "%s",
130
131 s_is_dirty = true;
132}
133
134/* --- Lifecycle Functions --- */
135
136esp_err_t config_init(void) {
137 if (s_is_initialized) {
138 LOGW("Component already initialized.");
139 return ESP_OK;
140 }
141
142 s_config_mutex = xSemaphoreCreateMutex();
143 if (s_config_mutex == NULL) {
144 LOGE("Failed to create configuration mutex.");
145 return ESP_ERR_NO_MEM;
146 }
147
148 nvs_handle_t handle;
149 esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
150 if (err != ESP_OK) {
151 if (err == ESP_ERR_NVS_NOT_FOUND) {
152 LOGW("NVS namespace missing. Staging factory defaults.");
154 } else {
155 LOGE("Failed to open NVS namespace '%s': %s", NVS_NAMESPACE,
156 esp_err_to_name(err));
157 vSemaphoreDelete(s_config_mutex);
158 s_config_mutex = NULL;
159 return err;
160 }
161 } else {
162 size_t size = sizeof(config_storage_t);
163 err = nvs_get_blob(handle, NVS_BLOB_KEY, &s_config, &size);
164
165 if (err == ESP_ERR_NVS_NOT_FOUND) {
166 LOGI("No configuration blob found. Initializing defaults.");
168 } else if (err != ESP_OK) {
169 LOGE("NVS read error: 0x%X. Falling back to defaults.", err);
171 } else if (size != sizeof(config_storage_t)) {
172 LOGW("Configuration size mismatch (%u vs %u). Resetting to defaults.",
173 (unsigned int)size, (unsigned int)sizeof(config_storage_t));
175 } else if (s_config.magic != APP_CONFIG_MAGIC) {
176 LOGW("Configuration magic mismatch (0x%08X). Data corrupted. Resetting.",
177 (unsigned int)s_config.magic);
179 } else if (s_config.version != APP_CONFIG_VERSION) {
180 LOGW("Configuration version mismatch (%u vs %u). Upgrading/Resetting.",
181 (unsigned int)s_config.version, (unsigned int)APP_CONFIG_VERSION);
183 } else {
184 LOGI("Configuration loaded successfully. Magic and Version match.");
185 s_is_dirty = false;
186 }
187 nvs_close(handle);
188 }
189
190 s_is_initialized = true;
191 return ESP_OK;
192}
193
194esp_err_t config_reset_defaults(void) {
195 if (!s_is_initialized)
196 return ESP_FAIL;
197 LOCK();
199 UNLOCK();
200 LOGI("Factory defaults staged in RAM.");
201 return ESP_OK;
202}
203
204esp_err_t config_save(void) {
205 if (!s_is_initialized)
206 return ESP_ERR_NVS_NOT_INITIALIZED;
207
208 LOCK();
209 if (!s_is_dirty) {
210 UNLOCK();
211 LOGI("Flash save skipped: No changes detected.");
212 return ESP_OK;
213 }
214
215 nvs_handle_t handle;
216 esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
217 if (err != ESP_OK) {
218 UNLOCK();
219 LOGE("Failed to open NVS for saving: 0x%X", err);
220 return err;
221 }
222
223 err = nvs_set_blob(handle, NVS_BLOB_KEY, &s_config, sizeof(config_storage_t));
224 if (err == ESP_OK) {
225 err = nvs_commit(handle);
226 }
227
228 nvs_close(handle);
229
230 if (err == ESP_OK) {
231 s_is_dirty = false;
232 LOGI("Configuration successfully persisted to NVS.");
233 } else {
234 LOGE("Failed to write/commit config to NVS: 0x%X", err);
235 }
236
237 UNLOCK();
238 return err;
239}
240
241/* --- System Settings Get/Set --- */
242
244 if (!s_is_initialized)
246 LOCK();
247 config_connection_t conn = s_config.connection;
248 UNLOCK();
249 return conn;
250}
251
253 LOGI("Setting connection type to %d", (int)conn);
255 return false;
256 LOCK();
257 if (s_config.connection == conn) {
258 UNLOCK();
259 return false;
260 }
261 s_config.connection = conn;
262 s_is_dirty = true;
263 UNLOCK();
264 return true;
265}
266
268 if (!s_is_initialized)
270 LOCK();
271 config_ip_method_t method = s_config.ip_method;
272 UNLOCK();
273 return method;
274}
275
277 if (!s_is_initialized || method > APP_CONFIG_IP_DHCP)
278 return false;
279 LOCK();
280 if (s_config.ip_method == method) {
281 UNLOCK();
282 return false;
283 }
284 s_config.ip_method = method;
285 s_is_dirty = true;
286 UNLOCK();
287 return true;
288}
289
291 if (!s_is_initialized)
293 LOCK();
294 uint8_t brightness = s_config.led_brightness;
295 UNLOCK();
296 return brightness;
297}
298
299bool config_set_led_brightness(uint8_t brightness) {
300 if (!s_is_initialized)
301 return false;
302 LOGI("Setting LED brightness to %u", (unsigned int)brightness);
303 LOCK();
304 if (s_config.led_brightness == brightness) {
305 UNLOCK();
306 return false;
307 }
308 s_config.led_brightness = brightness;
309 s_is_dirty = true;
310 UNLOCK();
311
312 led_set_brightness(brightness);
313 return true;
314}
315
316/* --- DMX Port Settings Get/Set --- */
317
318uint16_t config_get_dmx_universe(uint8_t port_index) {
319 if (!s_is_initialized || port_index >= APP_CONFIG_DMX_PORT_COUNT)
321 LOCK();
322 uint16_t universe = s_config.dmx_universes[port_index];
323 UNLOCK();
324 return universe;
325}
326
327bool config_set_dmx_universe(uint8_t port_index, uint16_t universe) {
328 if (!s_is_initialized || port_index >= APP_CONFIG_DMX_PORT_COUNT ||
329 universe == APP_CONFIG_INVALID_UNIVERSE)
330 return false;
331 LOCK();
332 if (s_config.dmx_universes[port_index] == universe) {
333 UNLOCK();
334 return false;
335 }
336 s_config.dmx_universes[port_index] = universe;
337 s_is_dirty = true;
338 UNLOCK();
339 return true;
340}
341
343 if (!s_is_initialized || port_index >= APP_CONFIG_DMX_PORT_COUNT)
345 LOCK();
346 config_direction_t dir = s_config.dmx_directions[port_index];
347 UNLOCK();
348 return dir;
349}
350
351bool config_set_dmx_direction(uint8_t port_index,
352 config_direction_t direction) {
353 if (!s_is_initialized || port_index >= APP_CONFIG_DMX_PORT_COUNT ||
354 direction > APP_CONFIG_DIR_INPUT)
355 return false;
356 LOCK();
357 if (s_config.dmx_directions[port_index] == direction) {
358 UNLOCK();
359 return false;
360 }
361 s_config.dmx_directions[port_index] = direction;
362 s_is_dirty = true;
363 UNLOCK();
364 return true;
365}
366
367void config_get_wifi_sta_config(wifi_config_t *dest) {
368 if (!s_is_initialized || !dest)
369 return;
370 LOCK();
371 memset(dest, 0, sizeof(wifi_config_t));
372 memcpy(dest->sta.ssid, s_config.wifi_sta.ssid,
373 sizeof(s_config.wifi_sta.ssid));
374 memcpy(dest->sta.password, s_config.wifi_sta.password,
375 sizeof(s_config.wifi_sta.password));
376 UNLOCK();
377}
378
379bool config_set_wifi_sta_config(const wifi_config_t *src) {
380 if (!s_is_initialized || !src)
381 return false;
382
383 if (strnlen((const char *)src->sta.ssid, 32) == 32 ||
384 strnlen((const char *)src->sta.password, 64) == 64) {
385 LOGE("Wi-Fi credentials are not null-terminated or too long!");
386 return false;
387 }
388 LOCK();
389 if (memcmp(s_config.wifi_sta.ssid, src->sta.ssid,
390 sizeof(s_config.wifi_sta.ssid)) == 0 &&
391 memcmp(s_config.wifi_sta.password, src->sta.password,
392 sizeof(s_config.wifi_sta.password)) == 0) {
393 UNLOCK();
394 return false;
395 }
396 memcpy(s_config.wifi_sta.ssid, src->sta.ssid, sizeof(s_config.wifi_sta.ssid));
397 memcpy(s_config.wifi_sta.password, src->sta.password,
398 sizeof(s_config.wifi_sta.password));
399 s_is_dirty = true;
400 UNLOCK();
401 return true;
402}
403
404void config_get_wifi_ap_config(wifi_config_t *dest) {
405 if (!s_is_initialized || !dest)
406 return;
407 LOCK();
408 memset(dest, 0, sizeof(wifi_config_t));
409 memcpy(dest->ap.ssid, s_config.wifi_ap.ssid, sizeof(s_config.wifi_ap.ssid));
410 memcpy(dest->ap.password, s_config.wifi_ap.password,
411 sizeof(s_config.wifi_ap.password));
412 UNLOCK();
413}
414
415bool config_set_wifi_ap_config(const wifi_config_t *src) {
416 if (!s_is_initialized || !src)
417 return false;
418
419 if (strnlen((const char *)src->ap.ssid, 32) == 32 ||
420 strnlen((const char *)src->ap.password, 64) == 64) {
421 LOGE("Wi-Fi credentials are not null-terminated or too long!");
422 return false;
423 }
424
425 LOCK();
426 if (memcmp(s_config.wifi_ap.ssid, src->ap.ssid,
427 sizeof(s_config.wifi_ap.ssid)) == 0 &&
428 memcmp(s_config.wifi_ap.password, src->ap.password,
429 sizeof(s_config.wifi_ap.password)) == 0) {
430 UNLOCK();
431 return false;
432 }
433 memcpy(s_config.wifi_ap.ssid, src->ap.ssid, sizeof(s_config.wifi_ap.ssid));
434 memcpy(s_config.wifi_ap.password, src->ap.password,
435 sizeof(s_config.wifi_ap.password));
436 s_is_dirty = true;
437 UNLOCK();
438 return true;
439}
440
448 if (!s_is_initialized)
450
451 LOCK();
453 switch (event) {
455 act = (config_button_action_t)s_config.btn_action_single;
456 break;
458 act = (config_button_action_t)s_config.btn_action_double;
459 break;
461 act = (config_button_action_t)s_config.btn_action_multi;
462 break;
463 default:
464 break;
465 }
466 UNLOCK();
467 return act;
468}
469
478 config_button_action_t action) {
479 if (!s_is_initialized || action >= APP_BUTTON_ACTION_MAX)
480 return false;
481 LOCK();
482 uint8_t *target = NULL;
484 target = &s_config.btn_action_single;
486 target = &s_config.btn_action_double;
488 target = &s_config.btn_action_multi;
489
490 if (target == NULL || *target == (uint8_t)action) {
491 UNLOCK();
492 return false;
493 }
494
495 *target = (uint8_t)action;
496 s_is_dirty = true;
497 UNLOCK();
498 return true;
499}
bool config_set_led_brightness(uint8_t brightness)
Sets the status LED brightness level in RAM.
Definition config.c:299
esp_err_t config_reset_defaults(void)
Resets all configuration settings back to factory defaults.
Definition config.c:194
#define UNLOCK()
Unlocks the configuration mutex after access.
Definition config.c:90
config_ip_method_t config_get_ip_method(void)
Gets the current IP allocation method.
Definition config.c:267
#define NVS_BLOB_KEY
Key under which the entire config blob is stored in NVS.
Definition config.c:24
static bool s_is_dirty
Set to true if any RAM values diverge from the persisted flash storage state.
Definition config.c:83
bool config_set_ip_method(config_ip_method_t method)
Sets the IP assignment method in RAM.
Definition config.c:276
uint8_t config_get_led_brightness(void)
Gets the current status LED brightness level.
Definition config.c:290
bool config_set_wifi_ap_config(const wifi_config_t *src)
Sets the Wi-Fi Access Point mode configuration in RAM.
Definition config.c:415
static config_storage_t s_config
Private runtime memory footprint of the active configuration.
Definition config.c:65
config_connection_t config_get_connection(void)
Gets the current network connection type.
Definition config.c:243
config_direction_t config_get_dmx_direction(uint8_t port_index)
Gets the data direction for a specific port.
Definition config.c:342
bool config_set_dmx_universe(uint8_t port_index, uint16_t universe)
Sets the DMX universe for a specific port in RAM.
Definition config.c:327
uint16_t config_get_dmx_universe(uint8_t port_index)
Gets the configured DMX universe for a specific port.
Definition config.c:318
void config_get_wifi_sta_config(wifi_config_t *dest)
Gets the Wi-Fi Station mode configuration.
Definition config.c:367
bool config_set_dmx_direction(uint8_t port_index, config_direction_t direction)
Sets the data direction for a specific port in RAM.
Definition config.c:351
#define APP_CONFIG_VERSION
Definition config.c:29
config_button_action_t config_get_button_action(app_button_event_t event)
Retrieves the action assigned to a specific button event.
Definition config.c:447
void config_get_wifi_ap_config(wifi_config_t *dest)
Gets the Wi-Fi Access Point mode configuration.
Definition config.c:404
static bool s_is_initialized
Tracks if the configuration component has been successfully initialized.
Definition config.c:77
esp_err_t config_init(void)
Initializes the configuration component.
Definition config.c:136
bool config_set_connection(config_connection_t conn)
Sets the network connection type in RAM.
Definition config.c:252
bool config_set_wifi_sta_config(const wifi_config_t *src)
Sets the Wi-Fi Station mode configuration in RAM.
Definition config.c:379
esp_err_t config_save(void)
Flushes all staged RAM changes permanently to the non-volatile storage.
Definition config.c:204
#define NVS_NAMESPACE
NVS storage namespace for configuration data.
Definition config.c:23
static void load_factory_defaults(void)
Restores all configuration fields to their factory-defined values in RAM.
Definition config.c:102
#define APP_CONFIG_MAGIC
Definition config.c:28
bool config_set_button_action(app_button_event_t event, config_button_action_t action)
Assigns a new action to a specific button event in RAM.
Definition config.c:477
#define LOCK()
Locks the configuration mutex for exclusive access.
Definition config.c:86
static SemaphoreHandle_t s_config_mutex
Mutex guard ensuring thread-safe operations on the shared configuration context.
Definition config.c:71
Thread-safe configuration component utilizing NVS for ESP32.
#define APP_CONFIG_DEFAULT_LED_BRIGHTNESS
Definition config.h:87
config_button_action_t
Actions that can be dynamically assigned to button events.
Definition config.h:46
@ APP_BUTTON_ACTION_MAX
Definition config.h:50
@ APP_BUTTON_ACTION_NONE
Definition config.h:47
#define APP_CONFIG_DEFAULT_IP_METHOD
Definition config.h:85
#define APP_CONFIG_DEFAULT_DMX_DIR
Definition config.h:100
#define APP_CONFIG_DEFAULT_STA_PASSWORD
Definition config.h:91
#define APP_CONFIG_DEFAULT_SINGLE_CLICK_ACT
Definition config.h:105
#define APP_CONFIG_INVALID_UNIVERSE
Error/Invalid indicator for DMX universe.
Definition config.h:29
#define APP_CONFIG_DEFAULT_AP_SSID_PREFIX
Definition config.h:97
#define APP_CONFIG_DEFAULT_CONNECTION
Definition config.h:83
config_connection_t
Network connection medium types.
Definition config.h:64
@ APP_CONFIG_CONN_ETHERNET
Definition config.h:68
app_button_event_t
Supported button event types that can be configured in the system.
Definition config.h:34
@ APP_BUTTON_EVENT_SINGLE_CLICK
Definition config.h:35
@ APP_BUTTON_EVENT_MULTIPLE_CLICK
Definition config.h:37
@ APP_BUTTON_EVENT_DOUBLE_CLICK
Definition config.h:36
#define APP_CONFIG_DEFAULT_DOUBLE_CLICK_ACT
Definition config.h:107
#define APP_CONFIG_DEFAULT_START_UNIVERSE
Definition config.h:102
#define APP_CONFIG_DEFAULT_STA_SSID
Definition config.h:90
config_direction_t
Data direction for dmx-port.
Definition config.h:74
@ APP_CONFIG_DIR_INPUT
Definition config.h:76
#define APP_CONFIG_DEFAULT_MULTI_CLICK_ACT
Definition config.h:109
#define APP_CONFIG_DEFAULT_AP_PASSWORD
Definition config.h:95
config_ip_method_t
IP assignment method configurations.
Definition config.h:56
@ APP_CONFIG_IP_DHCP
Definition config.h:58
#define APP_CONFIG_DMX_PORT_COUNT
Total number of physical DMX ports supported by the hardware.
Definition config.h:24
LED control component with flexible blinking and breathing modes.
void led_set_brightness(uint8_t brightness)
Dynamically updates the LED maximum brightness.
Definition led.c:188
Project-wide logging macros based on ESP-IDF's logging library.
#define LOGW(...)
Log a message at Warning level.
Definition logger.h:40
#define LOGI(...)
Log a message at Info level.
Definition logger.h:42
#define LOGE(...)
Log a message at Error level.
Definition logger.h:38
Internal configuration storage layout. Mirrors the private runtime configuration context state in RAM...
Definition config.c:43
uint8_t btn_action_single
Definition config.c:54
config_direction_t dmx_directions[APP_CONFIG_DMX_PORT_COUNT]
Definition config.c:49
uint8_t btn_action_double
Definition config.c:56
uint16_t dmx_universes[APP_CONFIG_DMX_PORT_COUNT]
Definition config.c:48
uint32_t version
Definition config.c:45
uint32_t magic
Definition config.c:44
config_connection_t connection
Definition config.c:46
config_ip_method_t ip_method
Definition config.c:47
uint8_t btn_action_multi
Definition config.c:58
uint8_t led_brightness
Definition config.c:52
app_wifi_creds_t wifi_sta
Definition config.c:50
app_wifi_creds_t wifi_ap
Definition config.c:51
WIFI credentials structure for both Station and Access Point modes.
Definition config.c:34
char password[64]
Definition config.c:36
char ssid[32]
Definition config.c:35