Ver código fonte

Start of 5.X work

Sebastien L 1 semana atrás
pai
commit
73bd096f37
100 arquivos alterados com 7606 adições e 5169 exclusões
  1. 14 0
      .clang-format
  2. 0 32
      .cproject
  3. 0 21
      .devcontainer/devcontainer.json
  4. 7 0
      .gitignore
  5. 0 20
      .project
  6. 106 84
      CMakeLists.txt
  7. 57 163
      Dockerfile
  8. 0 22
      Makefile
  9. 0 3
      Makefile_std.mk
  10. 0 47
      buildFirmware.sh
  11. 0 113
      build_flash_cmd.sh
  12. 1 1
      components/display/CMakeLists.txt
  13. 3 3
      components/display/ILI9341.c
  14. 2 2
      components/display/SH1106.c
  15. 2 2
      components/display/SSD1306.c
  16. 2 2
      components/display/SSD1322.c
  17. 3 3
      components/display/SSD132x.c
  18. 2 2
      components/display/SSD1351.c
  19. 25 24
      components/display/SSD1675.c
  20. 3 3
      components/display/ST77xx.c
  21. 3 3
      components/display/core/gds.c
  22. 3 3
      components/display/core/gds.h
  23. 3 2
      components/display/core/gds_font.c
  24. 18 29
      components/display/display.c
  25. 53 60
      components/driver_bt/bt_app_core.c
  26. 21 16
      components/driver_bt/bt_app_core.h
  27. 5 7
      components/driver_bt/bt_app_sink.c
  28. 71 70
      components/driver_bt/bt_app_source.c
  29. 1 1
      components/led_strip/CMakeLists.txt
  30. 10 9
      components/led_strip/led_vu.c
  31. 1 1
      components/metrics/CMakeLists.txt
  32. 1 2
      components/metrics/Metrics.cpp
  33. 4 2
      components/platform_config/CMakeLists.txt
  34. 266 0
      components/platform_config/Config.cpp
  35. 49 0
      components/platform_config/Config.h
  36. 0 480
      components/platform_config/Configurator.cpp
  37. 0 107
      components/platform_config/Configurator.h
  38. 45 0
      components/platform_config/Locking.cpp
  39. 71 0
      components/platform_config/Locking.h
  40. 272 0
      components/platform_config/PBW.cpp
  41. 331 0
      components/platform_config/PBW.h
  42. 845 0
      components/platform_config/WifiList.cpp
  43. 185 0
      components/platform_config/WifiList.h
  44. 20 0
      components/platform_config/test/CMakeLists.txt
  45. 29 0
      components/platform_config/test/DAC_test_extra.proto
  46. 30 0
      components/platform_config/test/DAC_test_extra2.proto
  47. 956 0
      components/platform_config/test/test_platform_config.cpp
  48. 1 1
      components/platform_console/CMakeLists.txt
  49. 1 1
      components/platform_console/app_squeezelite/CMakeLists.txt
  50. 98 115
      components/platform_console/app_squeezelite/cmd_squeezelite.c
  51. 463 415
      components/platform_console/cmd_config.c
  52. 48 49
      components/platform_console/cmd_i2ctools.c
  53. 460 347
      components/platform_console/cmd_system.c
  54. 1 3
      components/platform_console/cmd_system.h
  55. 1 1
      components/platform_console/cmd_wifi.c
  56. 319 381
      components/platform_console/platform_console.c
  57. 1 0
      components/platform_console/platform_console.h
  58. 1 1
      components/platform_console/test/test_system.c
  59. 1 1
      components/raop/CMakeLists.txt
  60. 1 1
      components/raop/raop_sink.c
  61. 2 1
      components/services/CMakeLists.txt
  62. 27 29
      components/services/accessors.c
  63. 14 11
      components/services/accessors.h
  64. 419 487
      components/services/audio_controls.c
  65. 16 15
      components/services/audio_controls.h
  66. 4 4
      components/services/battery.c
  67. 348 338
      components/services/buttons.c
  68. 0 3
      components/services/globdefs.h
  69. 15 3
      components/services/gpio_exp.c
  70. 4 0
      components/services/gpio_exp.h
  71. 33 30
      components/services/led.c
  72. 2 2
      components/services/led.h
  73. 263 260
      components/services/messaging.c
  74. 7 9
      components/services/monitor.c
  75. 8 2
      components/services/monitor.h
  76. 281 158
      components/services/services.c
  77. 2 2
      components/services/services.h
  78. 44 0
      components/services/system_time.c
  79. 2 0
      components/services/system_time.h
  80. 8 7
      components/spotify/Shim.cpp
  81. 3 3
      components/spotify/cspot/bell/external/nanopb/generator/nanopb_generator.py
  82. 32 20
      components/spotify/cspot/bell/external/nanopb/pb_decode.c
  83. 2 1
      components/spotify/cspot_sink.c
  84. 0 10
      components/squeezelite-ota/component.mk
  85. 0 41
      components/squeezelite-ota/github.pem
  86. 20 3
      components/squeezelite-ota/squeezelite-ota.c
  87. 4 1
      components/squeezelite-ota/squeezelite-ota.h
  88. 2 2
      components/squeezelite/CMakeLists.txt
  89. 6 6
      components/squeezelite/ac101/ac101.c
  90. 5 4
      components/squeezelite/adac.h
  91. 10 11
      components/squeezelite/adac_core.c
  92. 8 7
      components/squeezelite/controls.c
  93. 430 432
      components/squeezelite/cs4265/cs4265.c
  94. 396 383
      components/squeezelite/decode_external.c
  95. 6 3
      components/squeezelite/embedded.c
  96. 10 11
      components/squeezelite/equalizer.c
  97. 87 64
      components/squeezelite/esp32_main.c
  98. 145 136
      components/squeezelite/external/dac_external.c
  99. 6 4
      components/squeezelite/output_bt.c
  100. 19 11
      components/squeezelite/output_embedded.c

+ 14 - 0
.clang-format

@@ -0,0 +1,14 @@
+{
+    "BasedOnStyle": "LLVM",
+    "UseTab": "Never",
+    "IndentWidth": 4,
+    "TabWidth": 4,
+    "ColumnLimit": 150,
+    "PointerAlignment": "Left",
+    "AlignAfterOpenBracket": "DontAlign",
+    "BreakBeforeBraces": "Attach",
+    "AllowShortIfStatementsOnASingleLine": true,
+    "AllowShortFunctionsOnASingleLine": "All",
+    "IndentCaseLabels": false,
+    "SpacesBeforeTrailingComments": 1
+}

+ 0 - 32
.cproject

@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
-    	
-    <storageModule moduleId="org.eclipse.cdt.core.settings">
-        		
-        <cconfiguration id="org.eclipse.cdt.core.default.config.959280881">
-            			
-            <storageModule buildSystemId="org.eclipse.cdt.core.defaultConfigDataProvider" id="org.eclipse.cdt.core.default.config.959280881" moduleId="org.eclipse.cdt.core.settings" name="Configuration">
-                				
-                <externalSettings/>
-                				
-                <extensions/>
-                			
-            </storageModule>
-            			
-            <storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
-            		
-        </cconfiguration>
-        	
-    </storageModule>
-    	
-    <storageModule moduleId="org.eclipse.cdt.core.pathentry">
-        		
-        <pathentry excluding="**/CMakeFiles/**" kind="out" path="build"/>
-        	
-    </storageModule>
-    	
-    <storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
-    	
-    <storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"/>
-    
-</cproject>

+ 0 - 21
.devcontainer/devcontainer.json

@@ -1,21 +0,0 @@
-{
-    "name": "ESP-IDF Development",
-    "image": "espressif/idf:v4.4.6",
-    "workspaceFolder": "/project",
-    "workspaceMount": "source=${localWorkspaceFolder},target=/project,type=bind",
-    "customizations": {
-        "vscode": {
-            "settings": {
-                "terminal.integrated.shell.linux": "/bin/bash"
-            },
-            "extensions": [
-                "ms-vscode.cpptools",
-                "usernamehw.errorlens",
-                "espressif.esp-idf-extension"  // ESP-IDF extension
-            ]
-        }
-    },
-    "runArgs": [
-        "--privileged" // If needed for accessing certain hardware resources
-    ]
-}

+ 7 - 0
.gitignore

@@ -18,3 +18,10 @@ components/wifi-manager/UML-State-Machine-in-C
 envfile.txt
 artifacts
 web-installer
+esp-idf-vscode-generated.gdb
+.vscode/.espidf.peripherals.state.json
+.vscode/squeezelite-esp32.code-workspace
+debug.log
+components/driver_bt/bt_app_source - Copy.c.old
+components/driver_bt/bt_app_source - Copy.c.old
+components/driver_bt/bt_app_source - Copy.c.old

+ 0 - 20
.project

@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>squeezelite-esp32-idfv4</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.cdt.core.cBuilder</name>
-			<triggers>clean,full,incremental,</triggers>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.cdt.core.cnature</nature>
-		<nature>org.eclipse.cdt.core.ccnature</nature>
-		<nature>com.espressif.idf.core.idfNature</nature>
-	</natures>
-</projectDescription>

+ 106 - 84
CMakeLists.txt

@@ -10,7 +10,12 @@ endif()
 
 # State machine hierarchy enabled and logging enabled
 add_definitions(-DSTATE_MACHINE_LOGGER=1)
-add_definitions(-DHIERARCHICAL_STATES=1)
+#add_definitions(-DHIERARCHICAL_STATES=1)
+
+# Align the nanopb library options across the entire project
+# otherwise this results in nasty structures misalignment 
+# when implementing callbacks
+add_definitions(-DPB_ENABLE_MALLOC -DPB_FIELD_32BIT)
 
 # Uncomment line below to get memory usage trace details
 # add_definitions(-DENABLE_MEMTRACE=1)
@@ -18,9 +23,11 @@ add_definitions(-DHIERARCHICAL_STATES=1)
 #add_definitions(-DNETWORK_ETHERNET_LOG_LEVEL=ESP_LOG_DEBUG)
 #uncomment line below to get network status debug logs
 #add_definitions(-DNETWORK_STATUS_LOG_LEVEL=ESP_LOG_DEBUG)
-#add_definitions(-DNETWORK_HANDLERS_LOG_LEVEL=ESP_LOG_DEBUG)
+add_definitions(-DNETWORK_HANDLERS_LOG_LEVEL=ESP_LOG_DEBUG)
+
 #add_definitions(-DNETWORK_WIFI_LOG_LEVEL=ESP_LOG_DEBUG)
-#add_definitions(-DNETWORK_MANAGER_LOG_LEVEL=ESP_LOG_DEBUG)
+add_definitions(-DNETWORK_MANAGER_LOG_LEVEL=ESP_LOG_DEBUG)
+
 #add_definitions(-DNETWORK_HTTP_SERVER_LOG_LEVEL=ESP_LOG_DEBUG)
 
 # utility to build sizes
@@ -53,7 +60,6 @@ endfunction()
 set(EXTRA_COMPONENT_DIRS components/platform_console/app_recovery components/platform_console/app_squeezelite  )
 
 project(recovery)
-spiffs_create_partition_image(spiffs  spiffs  FLASH_IN_PROJECT DEPENDS generate_spiffs_bin )
 
 # we need own "esp_app_desc" to take precedence
 add_custom_command(
@@ -106,7 +112,7 @@ esptool_py_flash_target_image(flash squeezelite "${otaapp_offset}" "${BUILD_DIR}
 add_custom_target(_jtag_scripts  ALL
 					BYPRODUCTS "flash_dbg_project_args"  
 					POST_BUILD
-					COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_SOURCE_DIR}/generate_debug_scripts.cmake"
+					COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_debug_scripts.cmake"
 )
 
 if(CMAKE_HOST_UNIX)
@@ -136,78 +142,95 @@ endif()
 
 # Include the protocol_buffers.cmake file
 add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/protobuf ${CMAKE_CURRENT_BINARY_DIR}/protobuf)
+add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/spiffs_src ${CMAKE_CURRENT_BINARY_DIR}/spiffs)
+
 
 
 # ======================= DEBUG FLAGS ============================
 
+# target_compile_definitions(__idf_wifi-manager PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_app_recovery PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO)
+# target_compile_definitions(__idf_esp_event PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO)
+# target_compile_definitions(__idf_esp_netif PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_bt PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_ERROR)
+# target_compile_definitions(__idf_mdns PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_tcpip_adapter PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_tcp_transport PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+
+# target_compile_definitions(__idf_app_squeezelite PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_app_trace PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_app_update PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_asio PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_audio PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_bootloader_support PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_display PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_driver PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_driver_bt PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_platform_config PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_squeezelite PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_squeezelite-ota PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_telnet PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_main PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+
+
+
+
+
+
+
+
+
+
+
+
+
 #target_compile_definitions(__idf_esp_eth PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO)
+# target_compile_definitions(__idf_services PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_driver PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 
-target_compile_definitions(__idf_services PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 
-target_compile_definitions(__idf_driver PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_wifi-manager PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_wifi PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_platform_console PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_wifi PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_platform_console PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 
-target_compile_definitions(__idf_app_recovery PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO)
-target_compile_definitions(__idf_esp_eth PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO)
-target_compile_definitions(__idf_esp_event PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO)
-target_compile_definitions(__idf_esp_netif PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
- 
-target_compile_definitions(__idf_freertos PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_bt PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_ERROR)
-target_compile_definitions(__idf_mdns PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_tcpip_adapter PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_tcp_transport PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-
-target_compile_definitions(__idf_app_squeezelite PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_app_trace PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_app_update PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_asio PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_audio PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_bootloader_support PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-
-target_compile_definitions(__idf_cbor PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_cmock PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_coap PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_console PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_cxx PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_display PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_driver PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_driver_bt PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_efuse PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp-dsp PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp-tls PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp32 PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_espcoredump PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_adc_cal PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_common PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_gdbstub PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_hid PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_https_ota PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_http_client PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_http_server PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_hw_support PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_ipc PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_local_ctrl PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_pm PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_ringbuf PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_rom PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_serial_slave_link PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_system PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_esp_timer PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_expat PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_fatfs PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_freemodbus PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_hal PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_heap PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_jsmn PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_json PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+
+
+
+# target_compile_definitions(__idf_esp_eth PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO)
+
+
+
+# target_compile_definitions(__idf_freertos PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+
+
+# target_compile_definitions(__idf_cbor PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_cmock PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_coap PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_cxx PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_adc_cal PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_https_ota PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_http_client PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_http_server PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_hw_support PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_ipc PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_local_ctrl PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_pm PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_ringbuf PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_rom PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_serial_slave_link PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_system PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_esp_timer PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_expat PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_fatfs PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_freemodbus PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_hal PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_heap PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_jsmn PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_json PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 # target_compile_definitions(__idf_libsodium PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 # target_compile_definitions(__idf_log PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 # target_compile_definitions(__idf_lwip PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_main PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_mbedtls PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+
+# target_compile_definitions(__idf_mbedtls PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 # target_compile_definitions(mbedcrypto PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 # target_compile_definitions(mbedtls PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 # target_compile_definitions(mbedx509 PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
@@ -217,24 +240,23 @@ target_compile_definitions(__idf_mbedtls PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG
 # target_compile_definitions(__idf_nvs_flash PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 # target_compile_definitions(__idf_openssl PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 # target_compile_definitions(__idf_perfmon PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_platform_config PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_protobuf-c PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_protocomm PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_pthread PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_raop PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_sdmmc PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-
-target_compile_definitions(__idf_soc PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_spiffs PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_spi_flash PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_squeezelite PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_squeezelite-ota PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_telnet PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_tools PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_ulp PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_unity PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+
+
+# target_compile_definitions(__idf_protobuf-c PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_protocomm PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_pthread PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_raop PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_sdmmc PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+
+# target_compile_definitions(__idf_soc PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_spiffs PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_spi_flash PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+
+# target_compile_definitions(__idf_tools PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_ulp PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_unity PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 # target_compile_definitions(__idf_vfs PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_wear_levelling PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
-target_compile_definitions(__idf_wifi_provisioning PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_wear_levelling PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
+# target_compile_definitions(__idf_wifi_provisioning PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 # target_compile_definitions(__idf_wpa_supplicant PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
 # target_compile_definitions(__idf_xtensa PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)

+ 57 - 163
Dockerfile

@@ -1,188 +1,82 @@
-FROM ubuntu:20.04
+FROM espressif/idf:v4.4.6
+
+# Install additional dependencies
+RUN apt-get update && apt-get install -y \
+  python3-protobuf 
+
+RUN DEBIAN_FRONTEND=noninteractive TZ=America/Toronto apt-get -y install tzdata
+  # Add any other dependencies you need
+
+  # To build the image for a branch or a tag of IDF, pass --build-arg IDF_CLONE_BRANCH_OR_TAG=name.
+  # To build the image with a specific commit ID of IDF, pass --build-arg IDF_CHECKOUT_REF=commit-id.
+  # It is possibe to combine both, e.g.:
+  #   IDF_CLONE_BRANCH_OR_TAG=release/vX.Y
+  #   IDF_CHECKOUT_REF=<some commit on release/vX.Y branch>.
+  # Docker build for release 4.3.5 as of 2023/05/18
+  # docker build . --build-arg IDF_CHECKOUT_REF=6d04316cbe4dc35ea7e4885e9821bd9958ac996d -t sle118/squeezelite-esp32-idfv446 
+  # Updating the docker image in the repository
+  # docker push sle118/squeezelite-esp32-idfv446
+  # or to do both:
+  # docker build . --build-arg IDF_CHECKOUT_REF=6d04316cbe4dc35ea7e4885e9821bd9958ac996d -t sle118/squeezelite-esp32-idfv446 && docker push sle118/squeezelite-esp32-idfv446
+  # docker build . -t sle118/squeezelite-esp32-idfv446 && docker push sle118/squeezelite-esp32-idfv446
+  #docker run --isolation=process --device="class/86E0D1E0-8089-11D0-9CE4-08003E301F73" mcr.microsoft.com/windows/servercore:1809
+  # (windows) To run the image interactive : 
+  # docker run --rm -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv446
+  # (windows powershell)
+  # docker run --rm -v ${PWD}:/project -w /project -it sle118/squeezelite-esp32-idfv446
+  # (linux) To run the image interactive :
+  # docker run --rm -v `pwd`:/project -w /project -it sle118/squeezelite-esp32-idfv446
+  # to build the web app inside of the interactive session
+  # pushd components/wifi-manager/webapp/ && npm install && npm run-script build && popd
+  #
+  # to run the docker with netwotrk port published on the host:
+  # docker run --rm -p 5000:5000/tcp -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv446
 
 
-ARG DEBIAN_FRONTEND=noninteractive
-ENV GCC_TOOLS_BASE=/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-
-# To build the image for a branch or a tag of IDF, pass --build-arg IDF_CLONE_BRANCH_OR_TAG=name.
-# To build the image with a specific commit ID of IDF, pass --build-arg IDF_CHECKOUT_REF=commit-id.
-# It is possibe to combine both, e.g.:
-#   IDF_CLONE_BRANCH_OR_TAG=release/vX.Y
-#   IDF_CHECKOUT_REF=<some commit on release/vX.Y branch>.
-# Docker build for release 4.3.5 as of 2023/05/18
-# docker build . --build-arg IDF_CHECKOUT_REF=6d04316cbe4dc35ea7e4885e9821bd9958ac996d -t sle118/squeezelite-esp32-idfv435 
-# Updating the docker image in the repository
-# docker push sle118/squeezelite-esp32-idfv435
-# or to do both:
-# docker build . --build-arg IDF_CHECKOUT_REF=6d04316cbe4dc35ea7e4885e9821bd9958ac996d -t sle118/squeezelite-esp32-idfv435 && docker push sle118/squeezelite-esp32-idfv435
-#
-# (windows) To run the image interactive : 
-# docker run --rm -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv435
-# (windows powershell)
-# docker run --rm -v ${PWD}:/project -w /project -it sle118/squeezelite-esp32-idfv435
-# (linux) To run the image interactive :
-# docker run --rm -v `pwd`:/project -w /project -it sle118/squeezelite-esp32-idfv435
-# to build the web app inside of the interactive session
-# pushd components/wifi-manager/webapp/ && npm install && npm run-script build && popd
-#
-# to run the docker with netwotrk port published on the host:
-# docker run --rm -p 5000:5000/tcp -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv435
-
-ARG IDF_CLONE_URL=https://github.com/espressif/esp-idf.git
-ARG IDF_CLONE_BRANCH_OR_TAG=master
-ARG IDF_CHECKOUT_REF=6d04316cbe4dc35ea7e4885e9821bd9958ac996d
-
-ENV IDF_PATH=/opt/esp/idf
-ENV IDF_TOOLS_PATH=/opt/esp
-
-# We need libpython2.7 due to GDB tools
-# we also need npm 8 for the webapp to work
-RUN : \
-  && apt-get update \
-  && apt-get install -y \
-    apt-utils \
-    build-essential \
-    bison \
-    ca-certificates \
-    ccache \
-    check \
-    curl \
-    flex \
-    git \
-    git-lfs \    
-    gperf \
-    lcov \
-    libbsd-dev \    
-    libpython3.8 \
-    libffi-dev \
-    libncurses-dev \
-    libusb-1.0-0-dev \
-    make \
-    ninja-build \
-    python3.8 \
-    python3-pip \
-    python3-venv \
-    ruby \
-    unzip \
-    wget \
-    xz-utils \
-    zip \
-   	npm \
-  	nodejs \
-  && apt-get autoremove -y \
-  && rm -rf /var/lib/apt/lists/* \
-  && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 \
-  && python -m pip install --upgrade \
-    pip \
-    virtualenv \
-  && cd /opt \  
-  && git clone https://github.com/HBehrens/puncover.git \
-  && cd puncover \
-  && python setup.py -q install \
-  && echo IDF_CHECKOUT_REF=$IDF_CHECKOUT_REF IDF_CLONE_BRANCH_OR_TAG=$IDF_CLONE_BRANCH_OR_TAG \
-  && git clone --recursive \
-      ${IDF_CLONE_BRANCH_OR_TAG:+-b $IDF_CLONE_BRANCH_OR_TAG} \
-      $IDF_CLONE_URL $IDF_PATH \
-	&& if [ -n "$IDF_CHECKOUT_REF" ]; then \
-      cd $IDF_PATH \
-  &&  git checkout $IDF_CHECKOUT_REF \
-  &&  git submodule update --init --recursive; \
-    fi \
-  && update-ca-certificates --fresh \
-  && $IDF_PATH/tools/idf_tools.py --non-interactive install required \
-  && $IDF_PATH/tools/idf_tools.py --non-interactive install cmake \
-  && $IDF_PATH/tools/idf_tools.py --non-interactive install-python-env \
-  && :
 RUN : \
   echo Installing pygit2  ******************************************************** \
-  && . /opt/esp/python_env/idf4.3_py3.8_env/bin/activate \
-  && ln -sf /opt/esp/python_env/idf4.3_py3.8_env/bin/python  /usr/local/bin/python \
+  && . /opt/esp/python_env/idf4.4_py3.8_env/bin/activate \
+  && ln -sf /opt/esp/python_env/idf4.4_py3.8_env/bin/python  /usr/local/bin/python \
   && pip install pygit2 requests \
   && pip show pygit2 \ 
   && python --version \  
   && pip --version \
   && pip install protobuf  grpcio-tools \
   && rm -rf $IDF_TOOLS_PATH/dist \
+  && apt-get install -y  nodejs \
   && :
 
-COPY docker/patches $IDF_PATH
+# COPY docker/patches $IDF_PATH
 
 #set idf environment variabies
-ENV PATH /opt/esp/idf/components/esptool_py/esptool:/opt/esp/idf/components/espcoredump:/opt/esp/idf/components/partition_table:/opt/esp/idf/components/app_update:/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32-elf/bin:/opt/esp/tools/xtensa-esp32s2-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32s2-elf/bin:/opt/esp/tools/xtensa-esp32s3-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32s3-elf/bin:/opt/esp/tools/riscv32-esp-elf/esp-2021r2-patch3-8.4.0/riscv32-esp-elf/bin:/opt/esp/tools/esp32ulp-elf/2.28.51-esp-20191205/esp32ulp-elf-binutils/bin:/opt/esp/tools/esp32s2ulp-elf/2.28.51-esp-20191205/esp32s2ulp-elf-binutils/bin:/opt/esp/tools/cmake/3.16.4/bin:/opt/esp/tools/openocd-esp32/v0.11.0-esp32-20220706/openocd-esp32/bin:/opt/esp/python_env/idf4.3_py3.8_env/bin:/opt/esp/idf/tools:$PATH
+ENV PATH /opt/esp/idf/components/esptool_py/esptool:/opt/esp/idf/components/espcoredump:/opt/esp/idf/components/partition_table:/opt/esp/idf/components/app_update:/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32-elf/bin:/opt/esp/tools/xtensa-esp32s2-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32s2-elf/bin:/opt/esp/tools/xtensa-esp32s3-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32s3-elf/bin:/opt/esp/tools/riscv32-esp-elf/esp-2021r2-patch3-8.4.0/riscv32-esp-elf/bin:/opt/esp/tools/esp32ulp-elf/2.28.51-esp-20191205/esp32ulp-elf-binutils/bin:/opt/esp/tools/esp32s2ulp-elf/2.28.51-esp-20191205/esp32s2ulp-elf-binutils/bin:/opt/esp/tools/cmake/3.16.4/bin:/opt/esp/tools/openocd-esp32/v0.11.0-esp32-20220706/openocd-esp32/bin:/opt/esp/python_env/idf4.4_py3.8_env/bin:/opt/esp/idf/tools:$PATH
 ENV GCC_TOOLS_BASE="/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-"
-ENV IDF_PATH="/opt/esp/idf"
-ENV IDF_PYTHON_ENV_PATH="/opt/esp/python_env/idf4.3_py3.8_env"
-ENV IDF_TOOLS_EXPORT_CMD="/opt/esp/idf/export.sh"
-ENV IDF_TOOLS_INSTALL_CMD="/opt/esp/idf/install.sh"
-ENV IDF_TOOLS_PATH="/opt/esp"
-ENV NODE_PATH="/v8/lib/node_modules"
-ENV NODE_VERSION="8"
+ENV IDF_PYTHON_ENV_PATH="/opt/esp/python_env/idf4.4_py3.8_env"
 ENV OPENOCD_SCRIPTS="/opt/esp/tools/openocd-esp32/v0.10.0-esp32-20211111/openocd-esp32/share/openocd/scripts"
-# Ccache is installed, enable it by default
-
-# The constraint file has been downloaded and the right Python package versions installed. No need to check and
-# download this at every invocation of the container.
-ENV IDF_PYTHON_CHECK_CONSTRAINTS=no
-
-# Ccache is installed, enable it by default
-ENV IDF_CCACHE_ENABLE=1
-
-# Install QEMU runtime dependencies
-RUN : \
-  && apt-get update && apt-get install -y -q \
-    libglib2.0-0 \
-    libpixman-1-0 \
-  && rm -rf /var/lib/apt/lists/* \
-  && :
-
-# Install QEMU
-ARG QEMU_VER=esp-develop-20220919
-ARG QEMU_DIST=qemu-${QEMU_VER}.tar.bz2
-ARG QEMU_SHA256=f6565d3f0d1e463a63a7f81aec94cce62df662bd42fc7606de4b4418ed55f870
-RUN : \
-  && wget --no-verbose https://github.com/espressif/qemu/releases/download/${QEMU_VER}/${QEMU_DIST} \
-  && echo "${QEMU_SHA256} *${QEMU_DIST}" | sha256sum --check --strict - \
-  && tar -xf ${QEMU_DIST} -C /opt \
-  && rm ${QEMU_DIST} \
-  && :
 
 COPY docker/entrypoint.sh /opt/esp/entrypoint.sh
 COPY components/wifi-manager/webapp/package.json /opt
-
-ENV NODE_VERSION 8
-
+ENV NODE_PATH="/v8/lib/node_modules"
+ENV NODE_VERSION="8"
+ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
+ENV PATH $IDF_PYTHON_ENV_PATH:$NVM_DIR/v$NODE_VERSION/bin:$PATH
 SHELL ["/bin/bash", "--login", "-c"]
-# Install nvm with node and npm
-# RUN wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash \
-#     && export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" \
-#     && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" \
-#     && nvm install $NODE_VERSION \
-#     && nvm alias default $NODE_VERSION \
-#     && nvm use default \
-#     && echo installing nodejs version 16  \
-#     && curl -sL https://deb.nodesource.com/setup_16.x | bash - \
-#     && echo installing node modules  \
-#     && cd /opt \
-#     && nvm use default \
-#     && npm install -g \  
-#     && :    
-
-RUN : \
-  && curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \
-  && apt-get install -y nodejs jq \
-  && echo installing dev node modules globally \
-  && cd /opt \
-  && cat ./package.json | jq '.devDependencies | keys[] as $k | "\($k)@\(.[$k])"' | xargs -t npm install --global \
-  && echo installing npm global packages \
-  && npm i -g npm \
-  && node --version \
-  && npm install -g \  
-  && :      
-RUN : \
-  && npm install -g html-webpack-plugin 
 
+# Install NVM, Node.js, and global NPM packages
+RUN wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash \
+    && export NVM_DIR="$HOME/.nvm" \
+    && . "$NVM_DIR/nvm.sh" \
+    && nvm install 16 \
+    && nvm alias default 16 \
+    && nvm use default \
+    && echo "Node.js version:" && node --version \
+    && echo "NPM version:" && npm --version \
+    && echo "Installing global NPM packages..." \
+    && cd /opt \
+    && cat ./package.json | jq '.devDependencies | keys[] as $k | "\($k)@\(.[$k])"' | xargs -t npm install --global \
+    && npm install -g html-webpack-plugin \
+    && :
 
-ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
-ENV PATH $IDF_PYTHON_ENV_PATH:$NVM_DIR/v$NODE_VERSION/bin:$PATH
 COPY ./docker/build_tools.py /usr/sbin/build_tools.py
 RUN : \
   && echo Changing permissions ********************************************************  \

+ 0 - 22
Makefile

@@ -1,22 +0,0 @@
-# build system (Jenkins) should pass the following
-#export PROJECT_BUILD_NAME="${build_version_prefix}${BUILD_NUMBER}"
-#export PROJECT_BUILD_TARGET="${config_target}"
-
-#PROJECT_BUILD_NAME?=local
-#PROJECT_CONFIG_TARGET?=custom
-#PROJECT_NAME?=squeezelite.$(PROJECT_CONFIG_TARGET)
-#PROJECT_VER?="$(PROJECT_BUILD_NAME)-$(PROJECT_CONFIG_TARGET)"
-
-
-
-#recovery: PROJECT_NAME:=recovery.$(PROJECT_CONFIG_TARGET)
-#recovery: EXTRA_CPPFLAGS+=-DRECOVERY_APPLICATION=1
-
-PROJECT_NAME?=squeezelite
-EXTRA_CPPFLAGS+=  -I$(PROJECT_PATH)/main 
-
-#/-Wno-error=maybe-uninitialized 
-include $(IDF_PATH)/make/project.mk 
-
-# for future gcc version, this could be needed: CPPFLAGS+= -Wno-error=format-overflow -Wno-error=stringop-truncation
-

+ 0 - 3
Makefile_std.mk

@@ -1,3 +0,0 @@
-PROJECT_NAME?= squeezelite
-EXTRA_COMPONENT_DIRS := $(PROJECT_PATH)/esp-dsp
-include $(IDF_PATH)/make/project.mk

+ 0 - 47
buildFirmware.sh

@@ -1,47 +0,0 @@
-#!/bin/bash
-
-
-
-echo "Build process started"
-echo "Setting up build name and build number"
-if [ -z "${TARGET_BUILD_NAME}" ]
-then
-    export TARGET_BUILD_NAME="I2S-4MFlash"
-    echo "TARGET_BUILD_NAME is not set. Defaulting to ${TARGET_BUILD_NAME}"
-fi
-if [ -z "${BUILD_NUMBER}" ]
-then
-    export BUILD_NUMBER="500"
-    echo "BUILD_NUMBER is not set. Defaulting to ${BUILD_NUMBER}"
-fi
-if [ -z "$DEPTH" ]
-then
-    export DEPTH="16"
-    echo "DEPTH is not set. Defaulting to ${DEPTH}"
-fi
-if [ -z "$tag" ]
-then
-    branch_name="$(git rev-parse --abbrev-ref HEAD)"
-    branch_name="${branch_name//[^a-zA-Z0-9\-~!@_\.]/}"
-    app_name="${TARGET_BUILD_NAME}.${DEPTH}.dev-$(git log --pretty=format:'%h' --max-count=1).${branch_name}" 
-    echo "${app_name}">version.txt
-    echo "app_name is not set. Defaulting to ${app_name}"
-else
-    echo "${tag}" >version.txt
-fi
-
-echo "Copying target sdkconfig"
-cp build-scripts/${TARGET_BUILD_NAME}-sdkconfig.defaults sdkconfig
-echo "Building project"
-idf.py build -DDEPTH=${DEPTH} -DBUILD_NUMBER=${BUILD_NUMBER}-${DEPTH} 
-echo "Generating size report"
-idf.py size-components >build/size_components.txt
-idf.py size-components-squeezelite build/size_components_squeezelite.txt
-if [ -z "${artifact_file_name}" ]
-then
-    echo "No artifact file name set.  Will not generate zip file."
-else
-    echo "Generating build artifact zip file"
-    zip -r build_output.zip build
-    zip build/${artifact_file_name} partitions*.csv components/ build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt
-fi

+ 0 - 113
build_flash_cmd.sh

@@ -1,113 +0,0 @@
-#!/bin/bash
-echo 
-echo =================================================================
-echo Build flash command
-echo =================================================================
-# Location of partitions.csv relative to this script
-partitionsCsv="../partitions.csv"
-
-# File to output readme instructions to
-outputReadme="./flash_cmd.txt"
-
-# File to output bash script to
-outputBashScript="./writeSequeezeEsp.sh"
-
-# File to output bat script to
-outputBatScript="./writeSequeezeEsp.bat"
-
-# The name of partitions to ignore from partitions.csv
-paritionsToIgnore=(
-  "nvs"
-  "phy_init"
-  "storage"
-  "coredump"
-  "settings"
-)
-
-# Function that maps partition name to actual bin file
-# defaults to "[PARTION_NAME_FROM_CSV].bin"
-function partitionNameToBinFile {
-  if [[ "$1" == "otadata" ]]; then
-    echo "ota_data_initial.bin"
-  elif [[ "$1" == "ota_0" || "$1" == "factory" ]]; then
-    echo "squeezelite.bin"
-  else
-    echo $1.bin
-  fi
-}
-
-# write parameters for esptool.py
-writeParameters="$writeParameters write_flash"
-writeParameters="$writeParameters --flash_mode dio --flash_freq 80m --flash_size detect"
-
-# bootloader.bin and partitions.bin not in partitions.csv so manually add here
-partitionsParameters=" 0x1000 bootloader/bootloader.bin"
-partitionsParameters="$partitionsParameters 0x8000 partitions.bin"
-
-# ==============================================================================
-
-# Loop over partitions.csv and add partition bins and offsets to partitionsParameters
-
-for line in $($IDF_PATH/components/partition_table/gen_esp32part.py --quiet build/partitions.bin | grep '^[^#]')
-do
-  partitionName=$(echo $line | awk -F',' '{printf "%s", $1}' )
-  partitionOffset=$(echo $line |awk -F',' '{printf "%s", $4}' )
-  partitionFile=$(partitionNameToBinFile $partitionName)
-	
-  if [[ " ${paritionsToIgnore[@]} " =~ " ${partitionName} " ]]; then
-	continue
-  fi
-
-  partitionsParameters="$partitionsParameters $partitionOffset $partitionFile"
-  echo "$partitionsParameters"
-  
-done
-
-# Write README Instructions
-if [ ! -f "$outputReadme" ]; then
-  touch $outputReadme
-fi
-
-echo "" >> $outputReadme
-echo "====LINUX====" >> $outputReadme
-echo "To flash sequeezelite run the following script:" >> $outputReadme
-echo "$outputBashScript [PORT_HERE] [BAUD_RATE]" >> $outputReadme
-echo "e.g. $outputBashScript /dev/ttyUSB0 115200" >> $outputReadme
-echo "" >> $outputReadme
-echo "====WINDOWS====" >> $outputReadme
-echo "To flash sequeezelite run the following script:" >> $outputReadme
-echo "$outputBatScript [PORT_HERE] [BAUD_RATE]" >> $outputReadme
-echo "e.g. $outputBatScript COM11 115200" >> $outputReadme
-echo "" >> $outputReadme
-echo "If you don't know how to run the BAT file with arguments then you can" >> $outputReadme
-echo "edit the bat file in Notepad. Open the file up and edit the following:" >> $outputReadme
-echo "Change 'set port=%1' to 'set port=[PORT_HERE]'. E.g. 'set port=COM11'" >> $outputReadme
-echo "Change 'set baud=%2' to 'set baud=[BAUD_RATE]'. E.g. 'set baud=115200'" >> $outputReadme
-echo "" >> $outputReadme
-echo "====MANUAL====" >> $outputReadme
-echo "Python esptool.py --port [PORT_HERE] --baud [BAUD_RATE] $writeParameters $partitionsParameters" >> $outputReadme
-
-# Write Linux BASH File
-if [ ! -f "$outputBashScript" ]; then
-  touch $outputBashScript
-fi
-
-echo "#!/bin/bash" >> $outputBashScript
-echo >> $outputBashScript
-echo "port=\$1" >> $outputBashScript
-echo "baud=\$2" >> $outputBashScript
-linuxFlashCommand="Python esptool.py --port \$port --baud \$baud"
-echo "$linuxFlashCommand $writeParameters $partitionsParameters" >> $outputBashScript
-
-# Write Windows BAT File
-if [ ! -f "$outputBatScript" ]; then
-  touch $outputBatScript
-fi
-
-echo "echo off" >> $outputBatScript
-echo "" >> $outputBatScript
-echo "set port=%1" >> $outputBatScript
-echo "set baud=%2" >> $outputBatScript
-windowsFlashCommand="Python esptool.py --port %port% --baud %baud%"
-echo "$windowsFlashCommand $writeParameters $partitionsParameters" >> $outputBatScript
-

+ 1 - 1
components/display/CMakeLists.txt

@@ -3,7 +3,7 @@ set(TJPGD tjpgd)
 
 idf_component_register(SRC_DIRS . core core/ifaces 
 						INCLUDE_DIRS . core
-						REQUIRES platform_config tools esp_common spiffs
+						REQUIRES tools platform_config  esp_common spiffs
 						PRIV_REQUIRES services freertos driver ${TJPGD}
 						EMBED_FILES note.jpg )		
 

+ 3 - 3
components/display/ILI9341.c

@@ -319,12 +319,12 @@ static const struct GDS_Device ILI9341_X = {
 	.Mode = GDS_RGB565, .Depth = 16,
 };		
 
-struct GDS_Device* ILI9341_Detect(sys_Display * Driver, struct GDS_Device* Device) {
+struct GDS_Device* ILI9341_Detect(sys_display_config * Driver, struct GDS_Device* Device) {
 	uint8_t Model;
 	int Depth=16;		// 16bit colordepth
 	
-	if(Driver->common.driver == sys_DisplayDriverEnum_ILI9341) Model = ILI9341;
-	else if(Driver->common.driver == sys_DisplayDriverEnum_ILI9341_24) Model = ILI9341_24;
+	if(Driver->common.driver == sys_display_drivers_ILI9341) Model = ILI9341;
+	else if(Driver->common.driver == sys_display_drivers_ILI9341_24) Model = ILI9341_24;
 	else return NULL;
 	
 	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));

+ 2 - 2
components/display/SH1106.c

@@ -152,9 +152,9 @@ static const struct GDS_Device SH1106 = {
 #endif		
 };	
 
-struct GDS_Device* SH1106_Detect(sys_Display * Driver, struct GDS_Device* Device) {
+struct GDS_Device* SH1106_Detect(sys_display_config * Driver, struct GDS_Device* Device) {
 	// if (!strcasestr(Driver, "SH1106")) return NULL;
-	if(Driver->common.driver != sys_DisplayDriverEnum_SH1106) return NULL;
+	if(Driver->common.driver != sys_display_drivers_SH1106) return NULL;
 	
 	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
 	*Device = SH1106;

+ 2 - 2
components/display/SSD1306.c

@@ -162,8 +162,8 @@ static const struct GDS_Device SSD1306 = {
 #endif		
 };	
 
-struct GDS_Device* SSD1306_Detect(sys_Display * Driver, struct GDS_Device* Device) {
-	if(Driver->common.driver != sys_DisplayDriverEnum_SSD1306) return NULL;
+struct GDS_Device* SSD1306_Detect(sys_display_config * Driver, struct GDS_Device* Device) {
+	if(Driver->common.driver != sys_display_drivers_SSD1306) return NULL;
 	
 	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
 	*Device = SSD1306;	

+ 2 - 2
components/display/SSD1322.c

@@ -191,8 +191,8 @@ static const struct GDS_Device SSD1322 = {
 	.Mode = GDS_GRAYSCALE, .Depth = 4,
 };	
 
-struct GDS_Device* SSD1322_Detect(sys_Display * Driver, struct GDS_Device* Device) {
-	if(Driver->common.driver != sys_DisplayDriverEnum_SSD1322) return NULL;
+struct GDS_Device* SSD1322_Detect(sys_display_config * Driver, struct GDS_Device* Device) {
+	if(Driver->common.driver != sys_display_drivers_SSD1322) return NULL;
 		
 	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
 	

+ 3 - 3
components/display/SSD132x.c

@@ -319,12 +319,12 @@ static const struct GDS_Device SSD132x = {
 	.Mode = GDS_GRAYSCALE, .Depth = 4,
 };	
 
-struct GDS_Device* SSD132x_Detect(sys_Display * Driver, struct GDS_Device* Device) {
+struct GDS_Device* SSD132x_Detect(sys_display_config * Driver, struct GDS_Device* Device) {
 	uint8_t Model;
 	int Depth;
 		
-	if(Driver->common.driver == sys_DisplayDriverEnum_SSD1326) Model = SSD1326;
-	else if(Driver->common.driver == sys_DisplayDriverEnum_SSD1327) Model = SSD1327;
+	if(Driver->common.driver == sys_display_drivers_SSD1326) Model = SSD1326;
+	else if(Driver->common.driver == sys_display_drivers_SSD1327) Model = SSD1327;
 	return NULL;
 	
 	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));

+ 2 - 2
components/display/SSD1351.c

@@ -268,10 +268,10 @@ static const struct GDS_Device SSD1351 = {
 	.Mode = GDS_RGB565, .Depth = 16,
 };	
 
-struct GDS_Device* SSD1351_Detect(sys_Display * Driver, struct GDS_Device* Device) {
+struct GDS_Device* SSD1351_Detect(sys_display_config * Driver, struct GDS_Device* Device) {
 	int Depth;
 	
-	if(Driver->common.driver != sys_DisplayDriverEnum_SSD1351) return NULL;
+	if(Driver->common.driver != sys_display_drivers_SSD1351) return NULL;
 	
 	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
 	

+ 25 - 24
components/display/SSD1675.c

@@ -224,30 +224,31 @@ static bool Init( struct GDS_Device* Device ) {
 	Update( Device );
 	
 	return true;
-}	
+}
 
 static const struct GDS_Device SSD1675 = {
-	.DrawBitmapCBR = DrawBitmapCBR, .ClearWindow = ClearWindow,
-	.DrawPixelFast = _DrawPixel,
-	.Update = Update, .Init = Init,
-	.Mode = GDS_MONO, .Depth = 1,
-	.Alloc = GDS_ALLOC_NONE,
-};	
-
-struct GDS_Device* SSD1675_Detect(sys_Display * Driver, struct GDS_Device* Device) {
-	if(Driver->common.driver != sys_DisplayDriverEnum_SSD1675) return NULL;
-	
-	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
-	*Device = SSD1675;	
-	
-	char *p;
-	struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private;
-	Private->ReadyPin = -1;
-	if(Driver->common.has_ready && Driver->common.ready.pin >=0){
-		Private->ReadyPin = Driver->common.ready.pin;
-	}
-	
-	ESP_LOGI(TAG, "SSD1675 driver with ready GPIO %d", Private->ReadyPin);
-	
-	return Device;
+    .DrawBitmapCBR = DrawBitmapCBR,
+    .ClearWindow = ClearWindow,
+    .DrawPixelFast = _DrawPixel,
+    .Update = Update,
+    .Init = Init,
+    .Mode = GDS_MONO,
+    .Depth = 1,
+    .Alloc = GDS_ALLOC_NONE,
+};
+
+struct GDS_Device* SSD1675_Detect(sys_display_config* Driver, struct GDS_Device* Device) {
+    if (Driver->common.driver != sys_display_drivers_SSD1675) return NULL;
+
+    if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
+    *Device = SSD1675;
+    struct PrivateSpace* Private = (struct PrivateSpace*)Device->Private;
+    Private->ReadyPin = -1;
+    if (Driver->common.ready >= 0) {
+        Private->ReadyPin = Driver->common.ready;
+    }
+
+    ESP_LOGI(TAG, "SSD1675 driver with ready GPIO %d", Private->ReadyPin);
+
+    return Device;
 }

+ 3 - 3
components/display/ST77xx.c

@@ -273,11 +273,11 @@ static const struct GDS_Device ST77xx = {
 	.Mode = GDS_RGB565, .Depth = 16,
 };		
 
-struct GDS_Device* ST77xx_Detect(sys_Display * Driver, struct GDS_Device* Device) {
+struct GDS_Device* ST77xx_Detect(sys_display_config * Driver, struct GDS_Device* Device) {
 	uint8_t Model;
 	int Depth;
-	if(Driver->common.driver == sys_DisplayDriverEnum_ST7735)  Model = ST7735;
-	else if(Driver->common.driver == sys_DisplayDriverEnum_ST7789)  Model = ST7789;
+	if(Driver->common.driver == sys_display_drivers_ST7735)  Model = ST7735;
+	else if(Driver->common.driver == sys_display_drivers_ST7789)  Model = ST7789;
 	else return NULL;
 		
 	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));

+ 3 - 3
components/display/core/gds.c

@@ -15,7 +15,7 @@
 #include "driver/gpio.h"
 #include "driver/ledc.h"
 #include "esp_log.h"
-#include "Configurator.h"
+#include "Config.h"
 #include "gds.h"
 #include "gds_private.h"
 
@@ -30,8 +30,8 @@ static struct GDS_BacklightPWM PWMConfig;
 
 static char TAG[] = "gds";
 
-struct GDS_Device* GDS_AutoDetect( sys_Display * Driver, GDS_DetectFunc* DetectFunc[], struct GDS_BacklightPWM* PWM ) {
-	if (!Driver->has_common || Driver->common.driver == sys_DisplayDriverEnum_UNSPECIFIED_DRIVER) return NULL;
+struct GDS_Device* GDS_AutoDetect( sys_display_config * Driver, GDS_DetectFunc* DetectFunc[], struct GDS_BacklightPWM* PWM ) {
+	if (!Driver->has_common || Driver->common.driver == sys_display_drivers_UNSPECIFIED) return NULL;
 	if (PWM) PWMConfig = *PWM;
 	
 	for (int i = 0; DetectFunc[i]; i++) {

+ 3 - 3
components/display/core/gds.h

@@ -3,7 +3,7 @@
 
 #include <stdint.h>
 #include <stdbool.h>
-#include "Configurator.h"
+#include "Config.h"
 
 /* NOTE for drivers:
  The build-in DrawPixel(Fast), DrawCBR and ClearWindow have optimized for 1 bit 
@@ -34,9 +34,9 @@ struct GDS_Layout {
 	bool ColorSwap;
 };
 
-typedef struct GDS_Device* GDS_DetectFunc(sys_Display * Driver, struct GDS_Device *Device);
+typedef struct GDS_Device* GDS_DetectFunc(sys_display_config * Driver, struct GDS_Device *Device);
 
-struct GDS_Device*	GDS_AutoDetect( sys_Display * Driver, GDS_DetectFunc* DetectFunc[], struct GDS_BacklightPWM *PWM );
+struct GDS_Device*	GDS_AutoDetect( sys_display_config * Driver, GDS_DetectFunc* DetectFunc[], struct GDS_BacklightPWM *PWM );
 
 void 	GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast );
 void 	GDS_DisplayOn( struct GDS_Device* Device );

+ 3 - 2
components/display/core/gds_font.c

@@ -40,9 +40,10 @@ static bool LoadFont(struct GDS_FontDef ** fontPtr, const char * fileName){
         ESP_LOGE(TAG, "Invalid pointer for LoadFont");
         return false;
     }
-
+    char font_file_name[CONFIG_SPIFFS_OBJ_NAME_LEN+1]={0};
+    snprintf(font_file_name,sizeof(font_file_name),"/spiffs/fonts/%s",fileName);
     // Allocate DMA-capable memory for the font
-    struct GDS_FontDef* loadedFont = load_file_dma(NULL,"fonts",fileName);
+    struct GDS_FontDef* loadedFont = load_file_dma(NULL,font_file_name);
 
     // Check if allocation succeeded
     if (loadedFont == NULL) {

+ 18 - 29
components/display/display.c

@@ -22,7 +22,7 @@
 #include "gds_text.h"
 #include "gds_font.h"
 #include "gds_image.h"
-#include "Configurator.h"
+#include "Config.h"
 static const char *TAG = "display";
 
 #define min(a,b) (((a) < (b)) ? (a) : (b))
@@ -44,7 +44,7 @@ static EXT_RAM_ATTR struct {
 	char header[HEADER_SIZE + 1];
 	char string[SCROLLABLE_SIZE + 1];
 	int offset, boundary;
-	sys_Metadata *metadata_config;
+	sys_metadata_config *metadata_config;
 	bool timer, refresh;
 	uint32_t elapsed;
 	struct {
@@ -76,10 +76,11 @@ GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD
 void display_init(char *welcome) {
 	bool init = false;
 	int width = -1, height = -1, backlight_pin = -1, RST_pin = -1;
-	sys_Display * sys_display;
-	sys_DispCommon * common;
+	sys_display_config * sys_display;
+	sys_display_common * common;
 
-	if(!SYS_DISPLAY(sys_display) || !SYS_DISPLAY_COMMON(common)){
+	if(!SYS_DISPLAY(sys_display) || !SYS_DISPLAY_COMMON(common) || common->driver == sys_display_drivers_UNSPECIFIED){
+		ESP_LOGI(TAG,"No display configuration");
 		return;
 	}
 	// // so far so good
@@ -87,11 +88,10 @@ void display_init(char *welcome) {
 		ESP_LOGE(TAG,"Misconfigured display missing data");
 		return;
 	}	
-
-	ESP_LOGI(TAG, "Trying to configure display type %s, driver: %s", 
-				sys_DeviceTypeEnum_name(sys_display->type),
-				sys_DisplayDriverEnum_name(common->driver));
-	if (common->has_back && common->back.pin >= 0) {
+	ESP_LOGI(TAG, "Initializing display type %s, driver: %s", 
+				sys_dev_common_types_name(sys_display->type),
+				sys_display_drivers_name(common->driver));
+	if (common->back >= 0) {
 		struct GDS_BacklightPWM PWMConfig = { .Channel = pwm_system.base_channel++, .Timer = pwm_system.timer, .Max = pwm_system.max, .Init = false	};
 		display = GDS_AutoDetect(sys_display, drivers, &PWMConfig);
 	} else {
@@ -99,38 +99,27 @@ void display_init(char *welcome) {
 	}
 
 	if (display) {
-		if(common->has_reset){
-			RST_pin = common->reset.pin;
-		}
-		if(common->has_back){
-			backlight_pin = common->back.pin;
-		}		
+		RST_pin = common->reset;
+		backlight_pin = common->back;
 		width = common->width;
 		height = common->height;
 
 		// Detect driver interface
-		if (sys_display->which_dispType == sys_Display_i2c_tag && i2c_system_port != -1){
+		if (sys_display->which_dispType == sys_display_config_i2c_tag && platform->dev.i2c.port != sys_i2c_port_UNSPECIFIED){
 			int address = 0x3C;
 			
 			address = sys_display->dispType.i2c.address;
 			init = true;
-			GDS_I2CInit( i2c_system_port, -1, -1, i2c_system_speed ) ;
+			GDS_I2CInit( platform->dev.i2c.port-sys_i2c_port_PORT0, -1, -1, platform->dev.i2c.speed ) ;
 			GDS_I2CAttachDevice( display, width, height, address, RST_pin, backlight_pin );
 		
 			ESP_LOGI(TAG, "Display is I2C on port %u", address);
-		} else if (sys_display->which_dispType == sys_Display_spi_tag && spi_system_host != -1) {
+		} else if (sys_display->which_dispType == sys_display_config_spi_tag && spi_system_host != -1) {
 			int CS_pin = -1, speed = 0, mode = 0;
-			if(sys_display->dispType.spi.has_cs){
-				CS_pin = sys_display->dispType.spi.cs.pin;
-			}		
+			CS_pin = sys_display->dispType.spi.cs;
 			speed = sys_display->dispType.spi.speed;
+			mode = sys_display->dispType.spi.mode;
 			
-			//todo: what is mode? 
-
-			// PARSE_PARAM(config, "mode", '=', mode);
-
-			// todo: need to handle display offsets 
-		
 			init = true;
 			GDS_SPIInit( spi_system_host, spi_system_dc_gpio );
 			GDS_SPIAttachDevice( display, width, height, CS_pin, RST_pin, backlight_pin, speed, mode );
@@ -174,7 +163,7 @@ void display_init(char *welcome) {
 		
 			// leave room for artwork is display is horizontal-style
 			if (displayer.metadata_config->has_artwork && displayer.metadata_config->artwork.enabled) {
-				// todo : check for resize flag
+				#pragma message("todo: check for resize flag and possibly offsets?")
 				displayer.artwork.enable = true;
 				displayer.artwork.fit = true;
 				if (height <= 64 && width > height * 2)

+ 53 - 60
components/driver_bt/bt_app_core.c

@@ -6,31 +6,31 @@
    CONDITIONS OF ANY KIND, either express or implied.
 */
 
-#include <stdint.h>
-#include <string.h>
-#include <stdbool.h>
+#include "bt_app_core.h"
+#include "esp_bt.h"
+#include "esp_bt_main.h"
+#include "esp_gap_bt_api.h"
 #include "esp_log.h"
 #include "freertos/FreeRTOS.h"
 #include "freertos/queue.h"
 #include "freertos/task.h"
-#include "esp_bt.h"
-#include "esp_bt_main.h"
-#include "esp_gap_bt_api.h"
-#include "bt_app_core.h"
 #include "tools.h"
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
 
-static const char *TAG = "btappcore";
+static const char* TAG = "btappcore";
 
-static void bt_app_task_handler(void *arg);
-static bool bt_app_send_msg(bt_app_msg_t *msg);
-static void bt_app_work_dispatched(bt_app_msg_t *msg);
+static void bt_app_task_handler(void* arg);
+static bool bt_app_send_msg(bt_app_msg_t* msg);
+static void bt_app_work_dispatched(bt_app_msg_t* msg);
 
 static xQueueHandle s_bt_app_task_queue;
 static bool running;
 
-bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback)
-{
-	ESP_LOGV(TAG,"%s event 0x%x, param len %d", __func__, event, param_len);
+bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void* p_params, int param_len,
+    bt_app_copy_cb_t p_copy_cback) {
+    ESP_LOGV(TAG, "%s event 0x%x, param len %d", __func__, event, param_len);
 
     bt_app_msg_t msg;
     memset(&msg, 0, sizeof(bt_app_msg_t));
@@ -54,36 +54,33 @@ bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, i
     return false;
 }
 
-static bool bt_app_send_msg(bt_app_msg_t *msg)
-{
+static bool bt_app_send_msg(bt_app_msg_t* msg) {
     if (msg == NULL) {
         return false;
     }
 
     if (xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) {
-    	ESP_LOGE(TAG,"%s xQueue send failed", __func__);
+        ESP_LOGE(TAG, "%s xQueue send failed", __func__);
         return false;
     }
     return true;
 }
 
-static void bt_app_work_dispatched(bt_app_msg_t *msg)
-{
+static void bt_app_work_dispatched(bt_app_msg_t* msg) {
     if (msg->cb) {
         msg->cb(msg->event, msg->param);
     }
 }
 
-static void bt_app_task_handler(void *arg)
-{
+static void bt_app_task_handler(void* arg) {
     bt_app_msg_t msg;
-	esp_err_t err;
-	
-	s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
-	
-	esp_bt_controller_mem_release(ESP_BT_MODE_BLE);
+    esp_err_t err;
+
+    s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
+
+    esp_bt_controller_mem_release(ESP_BT_MODE_BLE);
     esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
-	if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE ) {
+    if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
         if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
             ESP_LOGE(TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(err));
             goto exit;
@@ -104,17 +101,16 @@ static void bt_app_task_handler(void *arg)
             goto exit;
         }
     }
-	
-	/* Bluetooth device name, connection mode and profile set up */
-	bt_app_work_dispatch((bt_av_hdl_stack_evt_t*) arg, BT_APP_EVT_STACK_UP, NULL, 0, NULL);
-	
+
+    /* Bluetooth device name, connection mode and profile set up */
+    bt_app_work_dispatch((bt_av_hdl_stack_evt_t*)arg, BT_APP_EVT_STACK_UP, NULL, 0, NULL);
 #if (CONFIG_BT_SSP_ENABLED)
     /* Set default parameters for Secure Simple Pairing */
     esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
     esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
     esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
 #endif
-	
+
     /*
      * Set default parameters for Legacy Pairing
      * Use variable pin, input pin code when pairing
@@ -122,19 +118,19 @@ static void bt_app_task_handler(void *arg)
     esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
     esp_bt_pin_code_t pin_code;
     esp_bt_gap_set_pin(pin_type, 0, pin_code);
-	
-	running = true;
-	
-	while (running) {
+
+    running = true;
+
+    while (running) {
         if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) {
-        	ESP_LOGV(TAG,"%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event);
-			
+            ESP_LOGV(TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event);
+
             switch (msg.sig) {
             case BT_APP_SIG_WORK_DISPATCH:
                 bt_app_work_dispatched(&msg);
                 break;
             default:
-                ESP_LOGW(TAG,"%s, unhandled sig: %d", __func__, msg.sig);
+                ESP_LOGW(TAG, "%s, unhandled sig: %d", __func__, msg.sig);
                 break;
             }
 
@@ -142,40 +138,37 @@ static void bt_app_task_handler(void *arg)
                 free(msg.param);
             }
         } else {
-        	ESP_LOGW(TAG,"No messaged received from queue.");
+            ESP_LOGW(TAG, "No messaged received from queue.");
         }
     }
-	
-	ESP_LOGD(TAG, "bt_app_task shutting down");
-	
-	if (esp_bluedroid_disable() != ESP_OK) goto exit;
-	// this disable has a sleep timer BTA_DISABLE_DELAY in bt_target.h and 
-	// if we don't wait for it then disable crashes... don't know why
-	vTaskDelay(2*200 / portTICK_PERIOD_MS);	
-	
+
+    ESP_LOGD(TAG, "bt_app_task shutting down");
+
+    if (esp_bluedroid_disable() != ESP_OK) goto exit;
+    // this disable has a sleep timer BTA_DISABLE_DELAY in bt_target.h and
+    // if we don't wait for it then disable crashes... don't know why
+    vTaskDelay(2 * 200 / portTICK_PERIOD_MS);
+
     ESP_LOGD(TAG, "esp_bluedroid_disable called successfully");
     if (esp_bluedroid_deinit() != ESP_OK) goto exit;
-	
+
     ESP_LOGD(TAG, "esp_bluedroid_deinit called successfully");
     if (esp_bt_controller_disable() != ESP_OK) goto exit;
-	
+
     ESP_LOGD(TAG, "esp_bt_controller_disable called successfully");
     if (esp_bt_controller_deinit() != ESP_OK) goto exit;
-	
-	ESP_LOGD(TAG, "bt stopped successfully");	
+
+    ESP_LOGD(TAG, "bt stopped successfully");
 
 exit:
-	vQueueDelete(s_bt_app_task_queue);
-	running = false;		
+    vQueueDelete(s_bt_app_task_queue);
+    running = false;
     vTaskDelete(NULL);
 }
 
-void bt_app_task_start_up(bt_av_hdl_stack_evt_t* handler)
-{
+void bt_app_task_start_up(bt_av_hdl_stack_evt_t* handler) {
+
     xTaskCreate(bt_app_task_handler, "BtAppT", 4096, handler, configMAX_PRIORITIES - 3, NULL);
 }
 
-void bt_app_task_shut_down(void)
-{
-	running = false;
-}
+void bt_app_task_shut_down(void) { running = false; }

+ 21 - 16
components/driver_bt/bt_app_core.h

@@ -8,53 +8,58 @@
 
 #ifndef __BT_APP_CORE_H__
 #define __BT_APP_CORE_H__
+#include "Status.pb.h"
 #include "esp_log.h"
 #include "time.h"
-#include <stdint.h>
 #include <stdbool.h>
+#include <stdint.h>
 #include <stdio.h>
-#include "Status.pb.h"
-
-#define BT_APP_CORE_TAG                   "BT_APP_CORE"
-#define BT_APP_SIG_WORK_DISPATCH          (0x01)
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define BT_APP_CORE_TAG "BT_APP_CORE"
+#define BT_APP_SIG_WORK_DISPATCH (0x01)
 
 enum {
     BT_APP_EVT_STACK_UP = 0,
 };
 
-
-extern sys_AV_STATE bt_app_source_get_a2d_state();
-extern sys_MEDIA_STATE bt_app_source_get_media_state();
+extern sys_status_av_states bt_app_source_get_a2d_state();
+extern sys_status_media_states bt_app_source_get_media_state();
 /**
  * @brief     handler for the dispatched work
  */
-typedef void (* bt_app_cb_t) (uint16_t event, void *param);
+typedef void (*bt_app_cb_t)(uint16_t event, void* param);
 
 /* message to be sent */
 typedef struct {
-    uint16_t             sig;      /*!< signal to bt_app_task */
-    uint16_t             event;    /*!< message event id */
-    bt_app_cb_t          cb;       /*!< context switch callback */
-    void                 *param;   /*!< parameter area needs to be last */
+    uint16_t sig;   /*!< signal to bt_app_task */
+    uint16_t event; /*!< message event id */
+    bt_app_cb_t cb; /*!< context switch callback */
+    void* param;    /*!< parameter area needs to be last */
 } bt_app_msg_t;
 
 /**
  * @brief     parameter deep-copy function to be customized
  */
-typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src);
+typedef void (*bt_app_copy_cb_t)(bt_app_msg_t* msg, void* p_dest, void* p_src);
 
 /**
  * @brief     callback for startup event
  */
-typedef void bt_av_hdl_stack_evt_t(uint16_t event, void *p_param);
+typedef void bt_av_hdl_stack_evt_t(uint16_t event, void* p_param);
 
 /**
  * @brief     work dispatcher for the application task
  */
-bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback);
+bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void* p_params, int param_len,
+    bt_app_copy_cb_t p_copy_cback);
 
 void bt_app_task_start_up(bt_av_hdl_stack_evt_t* handler);
 
 void bt_app_task_shut_down(void);
 
 #endif /* __BT_APP_CORE_H__ */
+#ifdef __cplusplus
+}
+#endif

+ 5 - 7
components/driver_bt/bt_app_sink.c

@@ -21,7 +21,7 @@
 #include "esp_gap_bt_api.h"
 #include "esp_a2dp_api.h"
 #include "esp_avrc_api.h"
-#include "Configurator.h"
+#include "Config.h"
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
 #include "tools.h"
@@ -45,7 +45,6 @@ static const char BT_RC_CT_TAG[] = "RCCT";
 #define CONFIG_BT_NAME	"ESP32-BT"
 #endif
 
-static char * bt_name = NULL;
 
 static bool (*bt_app_a2d_cmd_cb)(bt_sink_cmd_t cmd, ...);
 static void (*bt_app_a2d_data_cb)(const uint8_t *data, uint32_t len);
@@ -511,7 +510,7 @@ static void volume_set_by_local_host(int value, bool is_step)
 	_lock_release(&s_volume_lock);
     if(sys_state->bt_sink_volume != s_volume){
         sys_state->bt_sink_volume = s_volume;
-        configurator_raise_state_changed();
+        config_raise_state_changed();
     }
 	
     if (s_volume_notify) {
@@ -567,8 +566,8 @@ void bt_sink_init(bt_cmd_vcb_t cmd_cb, bt_data_cb_t data_cb)
 	bt_app_a2d_cmd_cb = cmd_handler;
 	cmd_handler_chain = cmd_cb;
   	bt_app_a2d_data_cb = data_cb;
-    sys_BluetoothSink * bt_sink;
-    if(!SYS_SERVICES_BTSINK(bt_sink)){
+    sys_services_bt_sink * bt_sink;
+    if(!sys_services_config_BTSINK(bt_sink)){
         return;
     }
     char pin_code[ESP_BT_PIN_CODE_LEN+1] = "1234\0";
@@ -583,7 +582,7 @@ void bt_sink_init(bt_cmd_vcb_t cmd_cb, bt_data_cb_t data_cb)
      */
     esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED;
     strncpy(pin_code,bt_sink->pin,sizeof(pin_code));
-    if(SYS_SERVICES_BTSINK(bt_sink) && strlen(bt_sink->pin)>ESP_BT_PIN_CODE_LEN){
+    if( strlen(bt_sink->pin)>ESP_BT_PIN_CODE_LEN){
 
     	ESP_LOGW(BT_AV_TAG, "BT Sink pin code [%s] too long. ", bt_sink->pin);
     	pin_code[ESP_BT_PIN_CODE_LEN] = '\0';
@@ -606,7 +605,6 @@ void bt_sink_init(bt_cmd_vcb_t cmd_cb, bt_data_cb_t data_cb)
     if (bError) memcpy(esp_pin_code, "1234", 4);
 	esp_bt_gap_set_pin(pin_type, strlen(pin_code), esp_pin_code);
 	
-	free(pin_code);
 }
 
 void bt_sink_deinit(void)

+ 71 - 70
components/driver_bt/bt_app_source.c

@@ -20,8 +20,10 @@
 #include "cJSON.h"
 #include "tools.h"
 #include "accessors.h"
-#include "Configurator.h"
+#include "Config.h"
 #include "Status.pb.h"
+#include "bootstate.h"
+
 
 static const char * TAG = "bt_app_source";
 static const char * BT_RC_CT_TAG="RCCT";
@@ -31,11 +33,12 @@ extern void 	output_bt_stop(void);
 extern void 	output_bt_start(void);
 extern char*	output_state_str(void);
 extern bool		output_stopped(void);
-extern bool is_recovery_running;
+extern sys_squeezelite_config* get_profile(const char* name);
+
 
 static void bt_app_av_state_connecting(uint16_t event, void *param);
 static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param);
-sys_OutputBT * out_bt = NULL;
+sys_squeezelite_bt * out_bt = NULL;
 
 
 #define BT_APP_HEART_BEAT_EVT                (0xff00)
@@ -76,8 +79,8 @@ static void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *even
 
 static esp_bd_addr_t s_peer_bda = {0};
 static uint8_t s_peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1];
-sys_AV_STATE bt_app_source_a2d_state = sys_AV_STATE_A_IDLE;
-sys_MEDIA_STATE bt_app_source_media_state = sys_MEDIA_STATE_M_IDLE;
+sys_status_av_states bt_app_source_a2d_state = sys_status_av_states_A_IDLE;
+sys_status_media_states bt_app_source_media_state = sys_status_media_states_M_IDLE;
 static uint32_t s_pkt_cnt = 0;
 static TimerHandle_t s_tmr=NULL;
 static int prev_duration=10000;
@@ -175,23 +178,23 @@ static void peers_list_maintain(const char * s_peer_bdname, int32_t rssi){
     }    
 }
 
-sys_AV_STATE bt_app_source_get_a2d_state(){
+sys_status_av_states bt_app_source_get_a2d_state(){
     if(!is_recovery_running){
         // if we are in recovery mode, don't log BT status
-        ESP_LOGD(TAG,"a2dp status: %u = %s", bt_app_source_a2d_state, sys_AV_STATE_name(bt_app_source_a2d_state));
+        ESP_LOGD(TAG,"a2dp status: %u = %s", bt_app_source_a2d_state, sys_status_av_states_name(bt_app_source_a2d_state));
     }
     return bt_app_source_a2d_state;
 }
 
 
-sys_MEDIA_STATE bt_app_source_get_media_state(){
-    ESP_LOGD(TAG,"media state : %s ", sys_MEDIA_STATE_name(bt_app_source_media_state));
+sys_status_media_states bt_app_source_get_media_state(){
+    ESP_LOGD(TAG,"media state : %s ", sys_status_media_states_name(bt_app_source_media_state));
     return bt_app_source_media_state;
 }
 
 void set_app_source_state(int new_state){
     if(bt_app_source_a2d_state!=new_state){
-        ESP_LOGD(TAG, "Updating state from %s to %s", sys_AV_STATE_name(bt_app_source_a2d_state), sys_AV_STATE_name(new_state));
+        ESP_LOGD(TAG, "Updating state from %s to %s", sys_status_av_states_name(bt_app_source_a2d_state), sys_status_av_states_name(new_state));
         bt_app_source_a2d_state=new_state;
     }
 }
@@ -202,39 +205,37 @@ void set_a2dp_media_state(int new_state){
     }
 }
 
-void hal_bluetooth_init()
-{
-   
-    if(!ASSIGN_COND_VAL_3LVL(services,squeezelite,output_bt,out_bt) ){
-        ESP_LOGD(TAG,"Bluetooth not configured");
+void hal_bluetooth_init() {
+    sys_squeezelite_config* config = get_profile(NULL); // get the active profile
+    sys_squeezelite_bt* out_bt = (config && config->has_output_bt) ? &config->output_bt : NULL;
+    if (!out_bt) {
+        ESP_LOGD(TAG, "Bluetooth not configured");
         return;
     }
-    if(strlen(out_bt->sink_name) == 0){
-        ESP_LOGE(TAG,"Sink name not configured!");
+    if (strlen(out_bt->sink_name) == 0) {
+        ESP_LOGE(TAG, "Sink name not configured!");
         return;
     }
-	ESP_LOGD(TAG,"Initializing bluetooth sink");
-
+    ESP_LOGD(TAG, "Initializing bluetooth sink");
 
-	// create task and run event loop
+    // create task and run event loop
     bt_app_task_start_up(bt_av_hdl_stack_evt);
 
-	/*
-	 * Set default parameters for Legacy Pairing
-	 * Use variable pin, input pin code when pairing
-	*/
-	esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
+    /*
+     * Set default parameters for Legacy Pairing
+     * Use variable pin, input pin code when pairing
+     */
+    esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
     uint8_t pin_code_len;
-    uint8_t *pin_code;
-    if(strlen(out_bt->pin) == 0){
-        pin_code = (uint8_t *)"0000";
+    uint8_t* pin_code;
+    if (strlen(out_bt->pin) == 0) {
+        pin_code = (uint8_t*)"0000";
         pin_code_len = 4;
-    }
-    else {
-        pin_code = (uint8_t *) out_bt->pin;
+    } else {
+        pin_code = (uint8_t*)out_bt->pin;
         pin_code_len = strlen(out_bt->pin);
     }
-	esp_bt_gap_set_pin(pin_type, pin_code_len,pin_code);
+    esp_bt_gap_set_pin(pin_type, pin_code_len, pin_code);
 }
 
 void hal_bluetooth_stop(void) {
@@ -301,8 +302,8 @@ static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa
 		
 		if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
             peers_list_maintain(NULL, PEERS_LIST_MAINTAIN_PURGE);
-            if (bt_app_source_a2d_state == sys_AV_STATE_A_DISCOVERED) {
-				set_app_source_state(sys_AV_STATE_A_CONNECTING);
+            if (bt_app_source_a2d_state == sys_status_av_states_A_DISCOVERED) {
+				set_app_source_state(sys_status_av_states_A_CONNECTING);
 				ESP_LOGI(TAG,"Discovery completed.  Ready to start connecting to %s. ", s_peer_bdname);
                 esp_a2d_source_connect(s_peer_bda);
             } else {
@@ -403,7 +404,7 @@ static const char * conn_state_str(esp_a2d_connection_state_t state){
 
 static void unexpected_connection_state(int from, esp_a2d_connection_state_t to)
 {
-    ESP_LOGW(TAG,"Unexpected connection state change. App State: %s (%u) Connection State %s (%u)", sys_AV_STATE_name(from), from,conn_state_str(to), to);
+    ESP_LOGW(TAG,"Unexpected connection state change. App State: %s (%u) Connection State %s (%u)", sys_status_av_states_name(from), from,conn_state_str(to), to);
 }
 
 static void handle_connect_state_unconnected(uint16_t event, esp_a2d_cb_param_t *param){
@@ -444,20 +445,20 @@ static void handle_connect_state_connecting(uint16_t event, esp_a2d_cb_param_t *
             else {
                 ESP_LOGW(TAG,"A2DP connect unsuccessful");
             }
-            set_app_source_state(sys_AV_STATE_A_UNCONNECTED);
+            set_app_source_state(sys_status_av_states_A_UNCONNECTED);
             break;
         case ESP_A2D_CONNECTION_STATE_CONNECTING:
             break;
         case ESP_A2D_CONNECTION_STATE_CONNECTED:
-            set_app_source_state(sys_AV_STATE_A_CONNECTED);
-            set_a2dp_media_state(sys_MEDIA_STATE_M_IDLE);
+            set_app_source_state(sys_status_av_states_A_CONNECTED);
+            set_a2dp_media_state(sys_status_media_states_M_IDLE);
             ESP_LOGD(TAG,"Setting scan mode to ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE");
             esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
             ESP_LOGD(TAG,"Done setting scan mode. App state is now CONNECTED and media state IDLE.");
             break;
         case ESP_A2D_CONNECTION_STATE_DISCONNECTING:
             unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state); 
-            set_app_source_state(sys_AV_STATE_A_CONNECTING);       
+            set_app_source_state(sys_status_av_states_A_CONNECTING);       
             break;
         default:
             break;
@@ -469,7 +470,7 @@ static void handle_connect_state_connected(uint16_t event, esp_a2d_cb_param_t *p
     {
         case ESP_A2D_CONNECTION_STATE_DISCONNECTED:
             ESP_LOGW(TAG,"a2dp disconnected");
-            set_app_source_state(sys_AV_STATE_A_UNCONNECTED);
+            set_app_source_state(sys_status_av_states_A_UNCONNECTED);
             esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
             break;
         case ESP_A2D_CONNECTION_STATE_CONNECTING:
@@ -479,7 +480,7 @@ static void handle_connect_state_connected(uint16_t event, esp_a2d_cb_param_t *p
             unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state);
             break;
         case ESP_A2D_CONNECTION_STATE_DISCONNECTING:
-            set_app_source_state(sys_AV_STATE_A_DISCONNECTING);
+            set_app_source_state(sys_status_av_states_A_DISCONNECTING);
 
             break;
         default:
@@ -493,7 +494,7 @@ static void handle_connect_state_disconnecting(uint16_t event, esp_a2d_cb_param_
     {
         case ESP_A2D_CONNECTION_STATE_DISCONNECTED:
             ESP_LOGI(TAG,"a2dp disconnected");
-            set_app_source_state(sys_AV_STATE_A_UNCONNECTED);
+            set_app_source_state(sys_status_av_states_A_UNCONNECTED);
             esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);        
             break;
         case ESP_A2D_CONNECTION_STATE_CONNECTING:
@@ -513,24 +514,24 @@ static void handle_connect_state_disconnecting(uint16_t event, esp_a2d_cb_param_
 
 static void bt_app_av_sm_hdlr(uint16_t event, void *param)
 {
-    ESP_LOGV(TAG,"bt_app_av_sm_hdlr.%s a2d state: %s", event==BT_APP_HEART_BEAT_EVT?"Heart Beat.":"",sys_AV_STATE_name(bt_app_source_a2d_state));
+    ESP_LOGV(TAG,"bt_app_av_sm_hdlr.%s a2d state: %s", event==BT_APP_HEART_BEAT_EVT?"Heart Beat.":"",sys_status_av_states_name(bt_app_source_a2d_state));
     switch (bt_app_source_a2d_state) {
-    case sys_AV_STATE_A_DISCOVERING:
-    	ESP_LOGV(TAG,"state %s, evt 0x%x, output state: %s", sys_AV_STATE_name(bt_app_source_a2d_state), event, output_state_str());
+    case sys_status_av_states_A_DISCOVERING:
+    	ESP_LOGV(TAG,"state %s, evt 0x%x, output state: %s", sys_status_av_states_name(bt_app_source_a2d_state), event, output_state_str());
     	break;
-    case sys_AV_STATE_A_DISCOVERED:
-    	ESP_LOGV(TAG,"state %s, evt 0x%x, output state: %s", sys_AV_STATE_name(bt_app_source_a2d_state), event, output_state_str());
+    case sys_status_av_states_A_DISCOVERED:
+    	ESP_LOGV(TAG,"state %s, evt 0x%x, output state: %s", sys_status_av_states_name(bt_app_source_a2d_state), event, output_state_str());
         break;
-    case sys_AV_STATE_A_UNCONNECTED:
+    case sys_status_av_states_A_UNCONNECTED:
         bt_app_av_state_unconnected(event, param);
         break;
-    case sys_AV_STATE_A_CONNECTING:
+    case sys_status_av_states_A_CONNECTING:
         bt_app_av_state_connecting(event, param);
         break;
-    case sys_AV_STATE_A_CONNECTED:
+    case sys_status_av_states_A_CONNECTED:
         bt_app_av_state_connected(event, param);
         break;
-    case sys_AV_STATE_A_DISCONNECTING:
+    case sys_status_av_states_A_DISCONNECTING:
         bt_app_av_state_disconnecting(event, param);
         break;
     default:
@@ -591,7 +592,7 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
     uint8_t nameLen = 0;
     esp_bt_gap_dev_prop_t *p;
     memset(bda_str, 0x00, sizeof(bda_str));
-    if(bt_app_source_a2d_state != sys_AV_STATE_A_DISCOVERING)
+    if(bt_app_source_a2d_state != sys_status_av_states_A_DISCOVERING)
     {
     	// Ignore messages that might have been queued already
     	// when we've discovered the target device.
@@ -650,7 +651,7 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
     if (squeezelite_conf.sink_name && strlen(squeezelite_conf.sink_name) >0 && strcmp((char *)s_peer_bdname, squeezelite_conf.sink_name) == 0) {
         ESP_LOGI(TAG,"Found our target device. address %s, name %s", bda_str, s_peer_bdname);
 		memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
-        set_app_source_state(sys_AV_STATE_A_DISCOVERED);
+        set_app_source_state(sys_status_av_states_A_DISCOVERED);
         esp_bt_gap_cancel_discovery();
     } else {
     	ESP_LOGV(TAG,"Not the device we are looking for (%s). Continuing scan", squeezelite_conf.sink_name?squeezelite_conf.sink_name:"N/A");
@@ -698,7 +699,7 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
 
         /* start device discovery */
         ESP_LOGI(TAG,"Starting device discovery...");
-        set_app_source_state(sys_AV_STATE_A_DISCOVERING);
+        set_app_source_state(sys_status_av_states_A_DISCOVERING);
         esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
 
         /* create and start heart beat timer */
@@ -722,7 +723,7 @@ static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t
     case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
     case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT:
     case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: {
-        ESP_LOGD(TAG,"Received %s message", sys_ESP_AVRC_CT_name(event));
+        ESP_LOGD(TAG,"Received %s message", sys_status_avrc_ct_name(event));
         bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL);
         break;
     }
@@ -735,7 +736,7 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
 {
     esp_a2d_cb_param_t *a2d = NULL;
     switch (bt_app_source_media_state) {
-    case sys_MEDIA_STATE_M_IDLE: {
+    case sys_status_media_states_M_IDLE: {
     	if (event == BT_APP_HEART_BEAT_EVT) {
             if(!output_stopped())
             {
@@ -749,34 +750,34 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
 					a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS
 					) {
 				ESP_LOGI(TAG,"a2dp media ready, starting playback!");
-				set_a2dp_media_state(sys_MEDIA_STATE_M_STARTING);
+				set_a2dp_media_state(sys_status_media_states_M_STARTING);
 				esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START);
 			}
         }
         break;
     }
 
-    case sys_MEDIA_STATE_M_STARTING: {
+    case sys_status_media_states_M_STARTING: {
     	if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
             a2d = (esp_a2d_cb_param_t *)(param);
             if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START &&
                     a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
             	ESP_LOGI(TAG,"a2dp media started successfully.");
                 output_bt_start();
-                set_a2dp_media_state(sys_MEDIA_STATE_M_STARTED);
+                set_a2dp_media_state(sys_status_media_states_M_STARTED);
             } else {
                 // not started succesfully, transfer to idle state
             	ESP_LOGI(TAG,"a2dp media start failed.");
-                set_a2dp_media_state(sys_MEDIA_STATE_M_IDLE);
+                set_a2dp_media_state(sys_status_media_states_M_IDLE);
             }
         }
         break;
     }
-    case sys_MEDIA_STATE_M_STARTED: {
+    case sys_status_media_states_M_STARTED: {
         if (event == BT_APP_HEART_BEAT_EVT) {
         	if(output_stopped()) {
         		ESP_LOGI(TAG,"Output state is %s. Stopping a2dp media ...", output_state_str());
-                set_a2dp_media_state(sys_MEDIA_STATE_M_STOPPING);
+                set_a2dp_media_state(sys_status_media_states_M_STOPPING);
                 esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
             } else {
 				output_bt_tick();	
@@ -784,15 +785,15 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
         }
         break;
     }
-    case sys_MEDIA_STATE_M_STOPPING: {
-    	ESP_LOG_DEBUG_EVENT(TAG,QUOTE(sys_MEDIA_STATE_M_STOPPING));
+    case sys_status_media_states_M_STOPPING: {
+    	ESP_LOG_DEBUG_EVENT(TAG,QUOTE(sys_status_media_states_M_STOPPING));
         if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
             a2d = (esp_a2d_cb_param_t *)(param);
             if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_STOP &&
                     a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
                 ESP_LOGI(TAG,"a2dp media stopped successfully...");
                 output_bt_stop();
-               	set_a2dp_media_state(sys_MEDIA_STATE_M_IDLE);
+               	set_a2dp_media_state(sys_status_media_states_M_IDLE);
             } else {
                 ESP_LOGI(TAG,"a2dp media stopping...");
                 esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
@@ -801,9 +802,9 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
         break;
     }
 
-    case sys_MEDIA_STATE_M_WAIT_DISCONNECT:{
+    case sys_status_media_states_M_WAIT_DISCONNECT:{
     	esp_a2d_source_disconnect(s_peer_bda);
-		set_app_source_state(sys_AV_STATE_A_DISCONNECTING);
+		set_app_source_state(sys_status_av_states_A_DISCONNECTING);
 		ESP_LOGI(TAG,"a2dp disconnecting...");
     }
     }
@@ -843,11 +844,11 @@ static void bt_app_av_state_unconnected(uint16_t event, void *param)
         uint8_t *p = s_peer_bda;
         ESP_LOGI(TAG, "a2dp connecting to %s, BT peer: %02x:%02x:%02x:%02x:%02x:%02x",s_peer_bdname,p[0], p[1], p[2], p[3], p[4], p[5]);
         if(esp_a2d_source_connect(s_peer_bda)==ESP_OK) {  
-            set_app_source_state(sys_AV_STATE_A_CONNECTING);
+            set_app_source_state(sys_status_av_states_A_CONNECTING);
             s_connecting_intv = 0;
 		}
 		else {
-            set_app_source_state(sys_AV_STATE_A_UNCONNECTED);
+            set_app_source_state(sys_status_av_states_A_UNCONNECTED);
 			// there was an issue connecting... continue to discover
 			ESP_LOGE(TAG,"Attempt at connecting failed, restart at discover...");
 			esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
@@ -877,7 +878,7 @@ static void bt_app_av_state_connecting(uint16_t event, void *param)
     	break;
     case BT_APP_HEART_BEAT_EVT:
         if (++s_connecting_intv >= 2) {
-            set_app_source_state(sys_AV_STATE_A_UNCONNECTED);
+            set_app_source_state(sys_status_av_states_A_UNCONNECTED);
             ESP_LOGW(TAG,"A2DP Connect time out!  Setting state to Unconnected. ");
             s_connecting_intv = 0;
         }

+ 1 - 1
components/led_strip/CMakeLists.txt

@@ -1,7 +1,7 @@
 
 idf_component_register(SRC_DIRS .
 						INCLUDE_DIRS .
-						REQUIRES platform_config tools esp_common
+						REQUIRES tools platform_config  esp_common
 						PRIV_REQUIRES services freertos driver           
 )
 

+ 10 - 9
components/led_strip/led_vu.c

@@ -23,7 +23,7 @@
 #include "globdefs.h"
 #include "monitor.h"
 #include "led_strip.h"
-#include "Configurator.h"
+#include "Config.h"
 #include "accessors.h"
 #include "led_vu.h"
 
@@ -76,22 +76,23 @@ static void battery_svc(float value, int cells) {
  * 
  */
 void led_vu_init()
-{
-    sys_LEDStrip * config = NULL;
+{   
+    sys_dev_led_strip*config;
     if(!SYS_DEV_LEDSTRIP(config)){
+        ESP_LOGI(TAG,"No LED Strip configuration");
         return;
     }
-    if(!!config->has_gpio ){
+    if(config->gpio<0){
+        ESP_LOGI(TAG,"LED Strip has no GPIO configured");
         return;
     }
-    // char* config = config_alloc_get_str("led_vu_config", NULL, "N/A");
-
+    ESP_LOGI(TAG, "Initializing led_vu");    
     // Initialize led VU strip 
     strip.length = config->length;
-    strip.gpio = config->gpio.pin;
+    strip.gpio = config->gpio;
     
     // check for valid configuration
-    if (config->strip_type == sys_LEDStripType_LS_UNKNOWN || !config->has_gpio || config->gpio.pin <0) {
+    if (config->strip_type == sys_dev_strip_types_LS_UNKNOWN || config->gpio <0) {
         ESP_LOGI(TAG, "led_vu configuration invalid");
         return;
     }
@@ -117,7 +118,7 @@ void led_vu_init()
     ESP_LOGI(TAG, "vu meter using length:%d left:%d right:%d status:%d", strip.vu_length, strip.vu_start_l, strip.vu_start_r, strip.vu_status);
 
     // create driver configuration
-    led_strip_config.rgb_led_type = config->strip_type == sys_LEDStripType_LS_WS2812?RGB_LED_TYPE_WS2812:RGB_LED_TYPE_MAX;
+    led_strip_config.rgb_led_type = config->strip_type == sys_dev_strip_types_LS_WS2812?RGB_LED_TYPE_WS2812:RGB_LED_TYPE_MAX;
     led_strip_config.access_semaphore = xSemaphoreCreateBinary();
     led_strip_config.led_strip_length = strip.length;
     led_strip_config.led_strip_working = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT);

+ 1 - 1
components/metrics/CMakeLists.txt

@@ -1,5 +1,5 @@
 idf_component_register(SRC_DIRS .
 						INCLUDE_DIRS .
-						REQUIRES json tools platform_config wifi-manager esp-tls platform_config
+						REQUIRES json tools platform_config wifi-manager esp-tls 
 						PRIV_REQUIRES esp32 freertos
 )

+ 1 - 2
components/metrics/Metrics.cpp

@@ -20,13 +20,12 @@
 #include "cJSON.h"
 #include "freertos/timers.h"
 #include "network_manager.h"
-// #include "Configurator.h"
+// #include "Config.h"
 #pragma message("fixme: search for TODO below")
 
 static const char* TAG = "metrics";
 
 #if CONFIG_WITH_METRICS
-extern bool is_network_connected();
 #define METRICS_CLIENT_ID_LEN 50
 #define MAX_HTTP_RECV_BUFFER 512
 

+ 4 - 2
components/platform_config/CMakeLists.txt

@@ -1,5 +1,7 @@
+set(CMAKE_CXX_STANDARD 20)
 idf_component_register(SRC_DIRS .
 						INCLUDE_DIRS . 
-						PRIV_REQUIRES   tools newlib  console esp_common freertos tools services
-						REQUIRES spiffs
+						PRIV_REQUIRES    newlib  console esp_common freertos  
+						REQUIRES spiffs tools services esp_http_server
 )
+add_dependencies(${COMPONENT_LIB} generate_plugins_files)

+ 266 - 0
components/platform_config/Config.cpp

@@ -0,0 +1,266 @@
+#define LOG_LOCAL_LEVEL ESP_LOG_INFO
+#include "Config.h"
+#include "PBW.h"
+#include "WifiList.h"
+#include "bootstate.h"
+#include "DAC.pb.h"
+#include "esp_log.h"
+#include "esp_system.h"
+#include "pb_common.h" // Nanopb header for encoding (serialization)
+#include "pb_decode.h" // Nanopb header for decoding (deserialization)
+#include "pb_encode.h" // Nanopb header for encoding (serialization)
+#include "tools.h"
+#include <algorithm>
+#include <cctype>
+#include <sstream>
+#include <stdexcept>
+#include <string.h>
+
+static const char* TAG = "Configurator";
+
+using namespace System;
+__attribute__((section(".ext_ram.bss"))) sys_config* platform;
+__attribute__((section(".ext_ram.bss"))) sys_state_data* sys_state;
+__attribute__((section(".ext_ram.bss"))) sys_dac_default_sets* default_dac_sets;
+
+__attribute__((section(".ext_ram.bss"))) System::PB<sys_config> configWrapper("config", &sys_config_msg, sizeof(sys_config_msg));
+__attribute__((section(".ext_ram.bss"))) System::PB<sys_state_data> stateWrapper("state", &sys_state_data_msg, sizeof(sys_state_data_msg));
+__attribute__((section(".ext_ram.bss"))) System::PB<sys_dac_default_sets> defaultSets("default_sets", &sys_dac_default_sets_msg, sizeof(sys_dac_default_sets_msg));
+
+const int MaxDelay = 1000;
+bool config_update_mac_string(const pb_msgdesc_t* desc, uint32_t field_tag, void* message) {
+    pb_field_iter_t iter;
+    if (pb_field_iter_begin(&iter, desc, message) && pb_field_iter_find(&iter, field_tag)) {
+        if (!iter.pData) {
+            ESP_LOGW(TAG, "Unable to check mac string member. Data not initialized");
+            return false;
+        }
+        if (iter.pData) {
+            auto curvalue = std::string((char*)iter.pData);
+            if (curvalue.find(get_mac_str()) != std::string::npos) {
+                ESP_LOGD(TAG, "Entry already has mac string: %s", curvalue.c_str());
+                return true;
+            }
+            if (curvalue.find("@@init_from_mac@@") == std::string::npos) {
+                ESP_LOGW(TAG, "Member not configured for mac address or was overwritten: %s", curvalue.c_str());
+                return false;
+            }
+            auto newval = std::string("squeezelite-") + get_mac_str();
+            if (PB_ATYPE(iter.type) == PB_ATYPE_POINTER) {
+                ESP_LOGD(TAG, "Field is a pointer. Freeing previous value if any: %s", STR_OR_BLANK((char*)iter.pField));
+                FREE_AND_NULL(*(char**)iter.pField);
+                ESP_LOGD(TAG, "Field is a pointer. Setting new value as %s", newval.c_str());
+                *(char**)iter.pField = strdup_psram(newval.c_str());
+            } else if (PB_ATYPE(iter.type) == PB_ATYPE_STATIC) {
+                ESP_LOGD(TAG, "Static string. Setting new value as %s from %s", newval.c_str(), STR_OR_BLANK((char*)iter.pData));
+                memset((char*)iter.pData, 0x00, iter.data_size);
+                strncpy((char*)iter.pData, newval.c_str(), iter.data_size);
+            }
+        } else {
+            ESP_LOGE(TAG, "Set mac string failed: member should be initialized with default");
+            return false;
+        }
+    }
+    return true;
+}
+bool set_pb_string_from_mac(pb_ostream_t* stream, const pb_field_t* field, void* const* arg) {
+    if (!stream) {
+        // This is a size calculation pass, return true to indicate field presence
+        return true;
+    }
+
+    // Generate the string based on MAC and prefix
+    const char* prefix = reinterpret_cast<const char*>(*arg);
+    char* value = alloc_get_string_with_mac(prefix && strlen(prefix) > 0 ? prefix : "squeezelite-");
+
+    // Write the string to the stream
+    if (!pb_encode_string(stream, (uint8_t*)value, strlen(value))) {
+        free(value); // Free memory if encoding fails
+        return false;
+    }
+
+    free(value); // Free memory after encoding
+    return true;
+}
+
+bool config_erase_config() {
+    // make sure the config object doesn't have
+    // any pending changes to commit
+    ESP_LOGW(TAG, "Erasing configuration object and rebooting");
+    configWrapper.ResetModified();
+    // Erase the file and reboot
+    erase_path(configWrapper.GetFileName().c_str(), false);
+    guided_factory();
+    return true;
+}
+void set_mac_string() {
+    auto expected = std::string("squeezelite-") + get_mac_str();
+    bool changed = false;
+    auto config = configWrapper.get();
+    changed = config_update_mac_string(&sys_names_config_msg, sys_names_config_device_tag, &config->names) || changed;
+    changed = config_update_mac_string(&sys_names_config_msg, sys_names_config_airplay_tag, &config->names) || changed;
+    changed = config_update_mac_string(&sys_names_config_msg, sys_names_config_spotify_tag, &config->names) || changed;
+    changed = config_update_mac_string(&sys_names_config_msg, sys_names_config_bluetooth_tag, &config->names) || changed;
+    changed = config_update_mac_string(&sys_names_config_msg, sys_names_config_squeezelite_tag, &config->names) || changed;
+    changed = config_update_mac_string(&sys_names_config_msg, sys_names_config_wifi_ap_name_tag, &config->names) || changed;
+
+    if (changed) {
+        ESP_LOGI(TAG, "One or more name was changed. Committing");
+        configWrapper.RaiseChangedAsync();
+    }
+}
+
+void config_load() {
+    ESP_LOGI(TAG, "Loading configuration.");
+    bool restart = false;
+    sys_state = stateWrapper.get();
+    platform = configWrapper.get();
+    default_dac_sets = defaultSets.get();
+
+    assert(platform != nullptr);
+    assert(sys_state != nullptr);
+    assert(default_dac_sets != nullptr);
+    
+    configWrapper.get()->net.credentials = reinterpret_cast<sys_net_wifi_entry*>(new WifiList("wifi"));
+    assert(configWrapper.get()->net.credentials != nullptr);
+
+    if (!stateWrapper.FileExists()) {
+        ESP_LOGI(TAG, "State file not found or is empty. ");
+        stateWrapper.Reinitialize(true);
+        stateWrapper.SetTarget(CONFIG_FW_PLATFORM_NAME);
+    } else {
+        stateWrapper.LoadFile();
+    }
+    if (!configWrapper.FileExists()) {
+        ESP_LOGI(TAG, "Configuration file not found or is empty. ");
+        configWrapper.Reinitialize(true);
+        ESP_LOGI(TAG, "Current device name after load: %s", platform->names.device);
+        
+        configWrapper.SetTarget(CONFIG_FW_PLATFORM_NAME,true);
+        set_mac_string();
+        ESP_LOGW(TAG, "Restart required after initializing configuration");
+        restart = true;
+
+    } else {
+        configWrapper.LoadFile();
+        if (configWrapper.GetTargetName().empty() && !std::string(CONFIG_FW_PLATFORM_NAME).empty()) {
+            ESP_LOGW(TAG, "Config target is empty. Updating to %s", CONFIG_FW_PLATFORM_NAME);
+            configWrapper.Reinitialize(false,true,std::string(CONFIG_FW_PLATFORM_NAME));
+            ESP_LOGW(TAG, "Restart required due to target change");
+            restart = true;
+        }
+    }
+    if (!defaultSets.FileExists()) {
+        ESP_LOGE(TAG, "Default Sets file not found or is empty. (%s)", defaultSets.GetFileName().c_str());
+    } else {
+        defaultSets.LoadFile();
+    }        
+    if (restart) {
+        network_async_reboot(OTA);
+    }
+}
+void config_dump_config() {
+    auto serialized = configWrapper.Encode();
+    dump_data(serialized.data(), serialized.size());
+}
+
+void config_set_target(const char* target_name, bool reset) {
+    std::string new_target = std::string(target_name);
+    bool restart = false;
+    if (configWrapper.GetTargetName() != new_target) {
+        ESP_LOGI(TAG, "Setting configuration target name to %s, %s", target_name, reset ? "full reset" : "reapply only");
+        if (reset) {
+            ESP_LOGD(TAG, "Reinitializing Config structure");
+            configWrapper.Reinitialize(false,true,new_target);
+        } else {
+            ESP_LOGD(TAG, "Loading Config target values for %s", target_name);
+            configWrapper.SetTarget(target_name,true);
+            configWrapper.LoadTargetValues();
+            configWrapper.RaiseChangedAsync();
+        }
+        restart = true;
+    } else {
+        ESP_LOGW(TAG, "Target name has no change");
+    }
+    if (stateWrapper.GetTargetName() != new_target) {
+        ESP_LOGI(TAG, "Setting state target name to %s, %s", target_name, reset ? "full reset" : "reapply only");
+        restart=true;
+        if (reset) {
+            ESP_LOGD(TAG, "Reinitializing State structure");
+            stateWrapper.Reinitialize(false,true,new_target);
+        } else {
+            ESP_LOGD(TAG, "Loading State target values for %s", target_name);
+            stateWrapper.SetTarget(target_name,true);
+            stateWrapper.LoadTargetValues();
+            stateWrapper.RaiseChangedAsync();
+        }
+    }
+    ESP_LOGD(TAG, "Done updating target to %s", target_name);
+    if(restart){
+        network_async_reboot(RESTART);
+    }
+}
+void config_set_target_no_reset(const char* target_name) { config_set_target(target_name, false); }
+void config_set_target_reset(const char* target_name) { config_set_target(target_name, true); }
+void config_raise_changed(bool sync) {
+    if (sync) {
+        configWrapper.CommitChanges();
+    } else {
+        configWrapper.RaiseChangedAsync();
+    }
+}
+void config_commit_protowrapper(void* protoWrapper) {
+    ESP_LOGD(TAG, "Committing synchronously");
+    PBHelper::SyncCommit(protoWrapper);
+}
+
+void config_commit_state() { stateWrapper.CommitChanges(); }
+void config_raise_state_changed() { stateWrapper.RaiseChangedAsync(); }
+
+bool config_has_changes() { return configWrapper.HasChanges() || stateWrapper.HasChanges(); }
+
+bool config_waitcommit() { return configWrapper.WaitForCommit(2) && stateWrapper.WaitForCommit(2); }
+
+bool config_http_send_config(httpd_req_t* req) {
+    try {
+        auto data = configWrapper.Encode();
+        httpd_resp_send(req, (const char*)data.data(), data.size());
+        return true;
+    } catch (const std::runtime_error& e) {
+        std::string errdesc = (std::string("Unable to get configuration: ") + e.what());
+        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, errdesc.c_str());
+    }
+    return false;
+}
+
+bool system_set_string(const pb_msgdesc_t* desc, uint32_t field_tag, void* message, const char* value) {
+    pb_field_iter_t iter;
+    ESP_LOGD(TAG,"system_set_string. Getting new value");
+    std::string newval = std::string(STR_OR_BLANK(value));
+    ESP_LOGD(TAG,"system_set_string. Done getting new value");
+    ESP_LOGD(TAG, "Setting value [%s] in message field tag %d", newval.c_str(), field_tag);
+    if (pb_field_iter_begin(&iter, desc, message) && pb_field_iter_find(&iter, field_tag)) {
+        std::string old= std::string((char*)iter.pData);
+        if (iter.pData && old == newval) {
+            ESP_LOGW(TAG, "No change, from and to values are the same: [%s]", newval.c_str());
+            return false;
+        }
+        if (PB_ATYPE(iter.type) == PB_ATYPE_POINTER) {
+            ESP_LOGD(TAG, "Field is a pointer. Freeing previous value if any: %s", STR_OR_BLANK((char*)iter.pField));
+            FREE_AND_NULL(*(char**)iter.pField);
+            ESP_LOGD(TAG, "Field is a pointer. Setting new value ");
+            if (!newval.empty()) {
+                *(char**)iter.pField = strdup_psram(newval.c_str());
+            }
+
+        } else if (PB_ATYPE(iter.type) == PB_ATYPE_STATIC) {
+            ESP_LOGD(TAG, "Static string. Setting new value. Existing value: %s", (char*)iter.pData);
+            memset((char*)iter.pData, 0x00, iter.data_size);
+            if (!newval.empty()) {
+                strncpy((char*)iter.pData, newval.c_str(), iter.data_size);
+            }
+        }
+        ESP_LOGD(TAG, "Done setting value ");
+    }
+    return true;
+}

+ 49 - 0
components/platform_config/Config.h

@@ -0,0 +1,49 @@
+#pragma once
+#include "State.pb.h"
+#include "Status.pb.h"
+#include "configuration.pb.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "freertos/semphr.h"
+#include "freertos/timers.h"
+#include "status.pb.h"
+#include "accessors.h"
+#include "esp_http_server.h"
+#include "PBW.h"
+
+#ifdef __cplusplus
+extern System::PB<sys_state_data> stateWrapper;
+extern System::PB<sys_config> configWrapper;
+extern "C" {
+#endif
+#define PLATFORM_GET_PTR(base, sname)                                                              \
+    {                                                                                              \
+        (base && (base)->##has_##(sname) ? &(base)->sname : NULL)
+#define PLATFORM_DEVICES PLATFORM_GET_PTR(platform)
+void config_load();
+bool config_waitcommit();
+bool config_has_changes();
+void config_raise_changed(bool sync);
+void config_raise_state_changed();
+bool config_has_changes();
+bool config_waitcommit();
+void config_set_target(const char* target_name);
+void config_set_target_no_reset(const char* target_name);
+void config_set_target_reset(const char* target_name);
+
+void config_commit_protowrapper(void * protoWrapper);
+bool config_http_send_config(httpd_req_t* req);
+bool config_erase_config();
+void set_mac_string();
+void config_dump_config();
+bool system_set_string(
+    const pb_msgdesc_t* desc, uint32_t field_tag, void* message, const char* value);
+extern sys_config* platform;
+extern sys_state_data* sys_state;
+extern sys_config* presets;
+#ifdef __cplusplus
+}
+
+
+#endif
+

+ 0 - 480
components/platform_config/Configurator.cpp

@@ -1,480 +0,0 @@
-#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
-#include "Configurator.h"
-#include "esp_log.h"
-#include "esp_system.h"
-#include "pb_common.h" // Nanopb header for encoding (serialization)
-#include "pb_decode.h" // Nanopb header for decoding (deserialization)
-#include "pb_encode.h" // Nanopb header for encoding (serialization)
-// #include "sys_options.h"
-#include "tools.h"
-#include <string.h>
-#include <algorithm>
-static const char* TAG = "Configurator";
-static const char* targets_folder = "targets";
-static const char* config_file_name = "settings.bin";
-static const char* state_file_name = "state.bin";
-__attribute__((section(".ext_ram.bss"))) PlatformConfig::Configurator configurator;
-sys_Config* platform = NULL;
-sys_State* sys_state = NULL;
-
-bool set_pb_string_from_mac(pb_ostream_t* stream, const pb_field_t* field, void* const* arg) {
-    if (!stream) {
-        // This is a size calculation pass, return true to indicate field presence
-        return true;
-    }
-
-    // Generate the string based on MAC and prefix
-    const char* prefix = reinterpret_cast<const char*>(*arg);
-    char* value = alloc_get_string_with_mac(prefix && strlen(prefix) > 0 ? prefix : "squeezelite-");
-
-    // Write the string to the stream
-    if (!pb_encode_string(stream, (uint8_t*)value, strlen(value))) {
-        free(value); // Free memory if encoding fails
-        return false;
-    }
-
-    free(value); // Free memory after encoding
-    return true;
-}
-
-namespace PlatformConfig {
-
-EXT_RAM_ATTR static const int NO_COMMIT_PENDING = BIT0;
-EXT_RAM_ATTR static const int LOAD_BIT = BIT1;
-EXT_RAM_ATTR static const int NO_STATE_COMMIT_PENDING = BIT2;
-
-const int Configurator::MaxDelay = 1000;
-const int Configurator::LockMaxWait = 20 * Configurator::MaxDelay;
-
-EXT_RAM_ATTR TimerHandle_t Configurator::_timer;
-EXT_RAM_ATTR SemaphoreHandle_t Configurator::_mutex;
-EXT_RAM_ATTR SemaphoreHandle_t Configurator::_state_mutex;
-EXT_RAM_ATTR EventGroupHandle_t Configurator::_group;
-
-static void ConfiguratorCallback(TimerHandle_t xTimer) {
-    static int cnt = 0, scnt = 0;
-    if (configurator.HasChanges()) {
-        ESP_LOGI(TAG, "Configuration has some uncommitted entries");
-        configurator.CommitChanges();
-    } else {
-        if (++cnt >= 15) {
-            ESP_LOGV(TAG, "commit timer: commit flag not set");
-            cnt = 0;
-        }
-    }
-    if (configurator.HasStateChanges()) {
-        ESP_LOGI(TAG, "State has some uncommitted changes");
-        configurator.CommitState();
-    } else {
-        if (++scnt >= 15) {
-            ESP_LOGV(TAG, "commit timer: commit flag not set");
-            cnt = 0;
-        }
-    }    
-    xTimerReset(xTimer, 10);
-}
-void Configurator::RaiseStateModified() { SetGroupBit(NO_STATE_COMMIT_PENDING, false); }
-void Configurator::RaiseModified() { SetGroupBit(NO_COMMIT_PENDING, false); }
-void Configurator::ResetModified() {
-    ESP_LOGV(TAG, "Resetting the global commit flag.");
-    SetGroupBit(NO_COMMIT_PENDING, false);
-}
-void Configurator::ResetStateModified() {
-    ESP_LOGV(TAG, "Resetting the state commit flag.");
-    SetGroupBit(NO_STATE_COMMIT_PENDING, false);
-}
-bool Configurator::SetGroupBit(int bit_num, bool flag) {
-    bool result = true;
-    int curFlags = xEventGroupGetBits(_group);
-    if ((curFlags & LOAD_BIT) && bit_num == NO_COMMIT_PENDING) {
-        ESP_LOGD(TAG, "Loading config, ignoring changes");
-        result = false;
-    }
-    if (result) {
-        bool curBit = (xEventGroupGetBits(_group) & bit_num);
-        if (curBit == flag) {
-            ESP_LOGV(TAG, "Flag %d already %s", bit_num, flag ? "Set" : "Cleared");
-            result = false;
-        }
-    }
-    if (result) {
-        ESP_LOGV(TAG, "%s Flag %d ", flag ? "Setting" : "Clearing", bit_num);
-        if (!flag) {
-            xEventGroupClearBits(_group, bit_num);
-        } else {
-            xEventGroupSetBits(_group, bit_num);
-        }
-    }
-    return result;
-}
-bool Configurator::Lock() {
-    ESP_LOGV(TAG, "Locking Configurator");
-    if (xSemaphoreTake(_mutex, LockMaxWait) == pdTRUE) {
-        ESP_LOGV(TAG, "Configurator locked!");
-        return true;
-    } else {
-        ESP_LOGE(TAG, "Semaphore take failed. Unable to lock Configurator");
-        return false;
-    }
-}
-bool Configurator::LockState() {
-    ESP_LOGV(TAG, "Locking State");
-    if (xSemaphoreTake(_state_mutex, LockMaxWait) == pdTRUE) {
-        ESP_LOGV(TAG, "State locked!");
-        return true;
-    } else {
-        ESP_LOGE(TAG, "Semaphore take failed. Unable to lock State");
-        return false;
-    }
-}
-void* Configurator::AllocGetConfigBuffer(size_t* sz, sys_Config* config) {
-    size_t datasz;
-    pb_byte_t* data = NULL;
-
-    if (!pb_get_encoded_size(&datasz, sys_Config_fields, (const void*)platform) || datasz <= 0) {
-        return data;
-    }
-    data = (pb_byte_t*)malloc_init_external(datasz * sizeof(pb_byte_t));
-    pb_ostream_t stream = pb_ostream_from_buffer(data, datasz);
-    pb_encode(&stream, sys_Config_fields, (const void*)platform);
-    if (sz) {
-        *sz = datasz * sizeof(pb_byte_t);
-    }
-    return data;
-}
-void* Configurator::AllocGetConfigBuffer(size_t* sz) {
-    return AllocGetConfigBuffer(sz, &this->_root);
-}
-bool Configurator::WaitForCommit() {
-    bool commit_pending = (xEventGroupGetBits(_group) & NO_COMMIT_PENDING) == 0;
-    while (commit_pending) {
-        ESP_LOGW(TAG, "Waiting for config commit ...");
-        commit_pending = (xEventGroupWaitBits(_group, NO_COMMIT_PENDING | NO_STATE_COMMIT_PENDING, pdFALSE, pdTRUE,
-                              (MaxDelay * 2) / portTICK_PERIOD_MS) &
-                             ( NO_COMMIT_PENDING | NO_STATE_COMMIT_PENDING)) == 0;
-        if (commit_pending) {
-            ESP_LOGW(TAG, "Timeout waiting for config commit.");
-        } else {
-            ESP_LOGI(TAG, "Config committed!");
-        }
-    }
-    return !commit_pending;
-}
-void Configurator::CommitChanges() {
-    esp_err_t err = ESP_OK;
-    ESP_LOGI(TAG, "Committing configuration to flash. Locking config object.");
-    if (!Lock()) {
-        ESP_LOGE(TAG, "Unable to lock config for commit ");
-        return;
-    }
-    ESP_LOGV(TAG, "Config Locked. Committing");
-    Commit(&_root);
-    ResetModified();
-    Unlock();
-    ESP_LOGI(TAG, "Done Committing configuration to flash.");
-}
-
-bool Configurator::CommitState() {
-    esp_err_t err = ESP_OK;
-    ESP_LOGI(TAG, "Committing configuration to flash. Locking config object.");
-    if (!LockState()) {
-        ESP_LOGE(TAG, "Unable to lock config for commit ");
-        return false;
-    }
-    ESP_LOGV(TAG, "Config Locked. Committing");
-    CommitState(&_sys_state);   
-    ResetStateModified();
-    Unlock();
-    ESP_LOGI(TAG, "Done Committing configuration to flash.");
-    return true;
-}
-bool Configurator::HasChanges() { return (xEventGroupGetBits(_group) & NO_COMMIT_PENDING); }
-bool Configurator::HasStateChanges() { return (xEventGroupGetBits(_group) & NO_STATE_COMMIT_PENDING); }
-void Configurator::Unlock() {
-    ESP_LOGV(TAG, "Unlocking Configurator!");
-    xSemaphoreGive(_mutex);
-}
-void Configurator::UnlockState() {
-    ESP_LOGV(TAG, "Unlocking State!");
-    xSemaphoreGive(_state_mutex);
-}
-
-void Configurator::ResetStructure(sys_Config* config) {
-    if (!config) {
-        return;
-    }
-    sys_Config blankconfig = sys_Config_init_default;
-    memcpy(config, &blankconfig, sizeof(blankconfig));
-}
-bool Configurator::LoadDecodeBuffer(void* buffer, size_t buffer_size) {
-    size_t msgsize = 0;
-    size_t newsize = 0;
-    sys_Config config = sys_Config_init_default;
-    bool result = Configurator::LoadDecode(buffer, buffer_size, &config);
-    if (result) {
-        Configurator::ApplyTargetSettings(&config);
-    }
-    if (result) {
-        void* currentbuffer = AllocGetConfigBuffer(&msgsize);
-        void* newbuffer = AllocGetConfigBuffer(&newsize, &config);
-        if (msgsize != newsize || !memcmp(currentbuffer, newbuffer, msgsize)) {
-            ESP_LOGI(TAG, "Config change detected.");
-            // todo: here we are assuming that all strings and repeated elements have fixed size
-            //  and therefore size should always be the same.
-            result = Configurator::LoadDecode(buffer, buffer_size, &this->_root);
-            RaiseModified();
-        }
-        free(currentbuffer);
-        free(newbuffer);
-    }
-    return result;
-}
-bool Configurator::LoadDecodeState() {
-    bool result = true;
-    sys_State blank_state = sys_State_init_default;
-    FILE* file = open_file("rb", state_file_name);
-    if (file == nullptr) {
-        ESP_LOGD(TAG,"No state file found. Initializing ");
-        pb_release(&sys_State_msg,(void *)&_sys_state);
-        memcpy(&_sys_state, &blank_state, sizeof(sys_State));
-        ESP_LOGD(TAG,"Done Initializing state");
-        return true;
-    }
-    ESP_LOGD(TAG, "Creating binding");
-    pb_istream_t filestream = {&in_file_binding,NULL,0};
-    ESP_LOGD(TAG, "Starting encode");
-    if (!pb_decode(&filestream, &sys_State_msg, (void*)&_sys_state)) {
-        ESP_LOGE(TAG, "Decoding failed: %s\n", PB_GET_ERROR(&filestream));
-        result = false;
-    }
-
-    fclose(file);
-    configurator_raise_state_changed();
-    ESP_LOGD(TAG, "State loaded");
-    return true;
-}
-bool Configurator::LoadDecode(
-    void* buffer, size_t buffer_size, sys_Config* conf_root, bool noinit) {
-    if (!conf_root || !buffer) {
-        ESP_LOGE(TAG, "Invalid arguments passed to Load");
-    }
-    bool result = true;
-    // Prepare to read the data into the 'config' structure
-    pb_istream_t stream = pb_istream_from_buffer((uint8_t*)buffer, buffer_size);
-
-    // Decode the Protocol Buffers message
-    if (noinit) {
-        ESP_LOGD(TAG, "Decoding WITHOUT initialization");
-        result = pb_decode_noinit(&stream, &sys_Config_msg, conf_root);
-    } else {
-        ESP_LOGD(TAG, "Decoding WITH initialization");
-        result = pb_decode(&stream, &sys_Config_msg, conf_root);
-    }
-    if (!result) {
-        ESP_LOGE(TAG, "Failed to decode settings: %s", PB_GET_ERROR(&stream));
-        return false;
-    }
-    ESP_LOGD(TAG, "Settings decoded");
-    return true;
-}
-
-bool Configurator::Commit(sys_Config* config) {
-    if (!config) {
-        ESP_LOGE(TAG, "Invalid configuration structure!");
-        return false;
-    }
-    FILE* file = open_file("wb", config_file_name);
-    bool result = true;
-    if (file == nullptr) {
-        return false;
-    }
-    ESP_LOGD(TAG, "Creating binding");
-    pb_ostream_t filestream = {&out_file_binding, file, SIZE_MAX, 0};
-    ESP_LOGD(TAG, "Starting encode");
-    if (!pb_encode(&filestream, sys_Config_fields, (void*)config)) {
-        ESP_LOGE(TAG, "Encoding failed: %s\n", PB_GET_ERROR(&filestream));
-        result = false;
-    }
-    ESP_LOGD(TAG, "Encoded size: %d", filestream.bytes_written);
-    if (filestream.bytes_written == 0) {
-        ESP_LOGE(TAG, "Empty configuration!");
-        ESP_LOGD(TAG, "Device name: %s", config->names.device);
-    }
-
-    fclose(file);
-    return result;
-}
-
-bool Configurator::CommitState(sys_State* state) {
-    if (!state) {
-        ESP_LOGE(TAG, "Invalid state structure!");
-        return false;
-    }
-    FILE* file = open_file("wb", state_file_name);
-    bool result = true;
-    if (file == nullptr) {
-        return false;
-    }
-    ESP_LOGD(TAG, "Creating binding for state commit");
-    pb_ostream_t filestream = {&out_file_binding, file, SIZE_MAX, 0};
-    ESP_LOGD(TAG, "Starting state encode");
-    if (!pb_encode(&filestream, sys_Config_fields, (void*)state)) {
-        ESP_LOGE(TAG, "Encoding failed: %s\n", PB_GET_ERROR(&filestream));
-        result = false;
-    }
-    ESP_LOGD(TAG, "Encoded size: %d", filestream.bytes_written);
-    if (filestream.bytes_written == 0) {
-        ESP_LOGE(TAG, "Empty state!");
-    }
-
-    fclose(file);
-    return result;
-}
-
-
-void Configurator::InitLoadConfig(const char* filename) {
-    return Configurator::InitLoadConfig(filename, &this->_root);
-}
-void Configurator::InitLoadConfig(const char* filename, sys_Config* conf_root, bool noinit) {
-    esp_err_t err = ESP_OK;
-    size_t data_length = 0;
-    bool result = false;
-    ESP_LOGI(TAG, "Loading settings from %s", filename);
-    void* data = load_file(&data_length, filename);
-    if (!data) {
-        ESP_LOGW(TAG, "Config file %s was empty. ", filename);
-        return;
-    } else {
-        result = LoadDecode(data, data_length, conf_root, noinit);
-        free(data);
-    }
-    if (ApplyTargetSettings(conf_root)) {
-        result = true;
-    }
-    if (result) {
-        _timer = xTimerCreate(
-            "configTimer", MaxDelay / portTICK_RATE_MS, pdFALSE, NULL, ConfiguratorCallback);
-        if (xTimerStart(_timer, MaxDelay / portTICK_RATE_MS) != pdPASS) {
-            ESP_LOGE(TAG, "config commitment timer failed to start.");
-        }
-    }
-
-    return;
-}
-bool Configurator::ApplyTargetSettings() { return ApplyTargetSettings(&this->_root); }
-bool Configurator::ApplyTargetSettings(sys_Config* conf_root) {
-    size_t data_length = 0;
-    bool result = false;
-    std::string target_name = conf_root->target; 
-    std::string target_file;
-
-#ifdef CONFIG_FW_PLATFORM_NAME
-    if( target_name.empty()){
-        target_name = CONFIG_FW_PLATFORM_NAME;
-    }
-#endif
-    target_file = target_name+ std::string(".bin");
-
-    std::transform(target_file.begin(), target_file.end(), target_file.begin(),
-        [](unsigned char c){ return std::tolower(c); });
-    if (target_file.empty() || !get_file_info(NULL, targets_folder, target_file.c_str())) {
-        ESP_LOGD(TAG, "Platform settings file not found: %s", target_file.c_str());
-        return result;
-    }
-
-    ESP_LOGI(TAG, "Applying target %s settings", target_name.c_str());
-    void* data = load_file(&data_length, targets_folder, target_file.c_str());
-    if (!data) {
-        ESP_LOGE(TAG, "File read fail");
-        return false;
-    } else {
-
-        result = LoadDecode(data, data_length, conf_root, true);
-        if (result) {
-            ESP_LOGI(TAG, "Target %s settings loaded", target_name.c_str());
-        }
-        free(data);
-    }
-
-    return result;
-}
-
-}; // namespace PlatformConfig
-
-void configurator_reset_configuration() {
-    ESP_LOGI(TAG, "Creating default configuration file. ");
-    sys_Config config = sys_Config_init_default;
-    ESP_LOGD(TAG, "Device name before target settings: %s", config.names.device);
-    PlatformConfig::Configurator::ApplyTargetSettings(&config);
-    ESP_LOGD(TAG, "Device name after target settings: %s", config.names.device);
-    ESP_LOGD(TAG, "Committing new structure");
-    PlatformConfig::Configurator::Commit(&config);
-}
-
-void configurator_load() {
-    struct stat fileInformation;
-    ESP_LOGI(TAG, "Loading system settings file");
-    ESP_LOGD(TAG, "Checking if file %s exists", config_file_name);
-    bool found = get_file_info(&fileInformation, config_file_name);
-    if (!found || fileInformation.st_size == 0) {
-        ESP_LOGI(TAG, "Configuration file not found or is empty. ");
-        configurator_reset_configuration();
-    }
-    configurator.InitLoadConfig(config_file_name);
-    ESP_LOGD(TAG, "Assigning global config pointer");
-    platform = configurator.Root();
-    configurator.LoadDecodeState();
-    sys_state = configurator.RootState();
-}
-bool configurator_lock() { return configurator.Lock(); }
-
-void configurator_unlock() { configurator.Unlock(); }
-void configurator_raise_changed() { configurator.RaiseModified(); }
-void configurator_raise_state_changed() { configurator.RaiseStateModified(); }
-
-bool configurator_has_changes() { return configurator.HasChanges(); }
-
-bool configurator_waitcommit() { return configurator.WaitForCommit(); }
-
-void* configurator_alloc_get_config(size_t* sz) { return configurator.AllocGetConfigBuffer(sz); }
-bool configurator_parse_config(void* buffer, size_t buffer_size) {
-    // Load and decode buffer. The method also applies any overlay if needed.
-    return configurator.LoadDecodeBuffer(buffer, buffer_size);
-}
-pb_type_t configurator_get_field_type(const pb_msgdesc_t* desc, uint32_t tag) {
-    pb_field_iter_t iter;
-    if (pb_field_iter_begin(&iter, desc, NULL) && pb_field_iter_find(&iter, tag)) {
-        /* Found our field. */
-        return iter.type;
-    }
-    return 0;
-}
-bool configurator_set_string(
-    const pb_msgdesc_t* desc, uint32_t field_tag, void* message, const char* value) {
-    pb_field_iter_t iter;
-    const char * newval = STR_OR_BLANK(value);
-    ESP_LOGD(TAG, "Setting value [%s] in message field tag %d",newval , field_tag);
-    if (pb_field_iter_begin(&iter, desc, message) && pb_field_iter_find(&iter, field_tag)) {
-        if (iter.pData && !strcmp((char*)iter.pData, newval)) {
-            ESP_LOGW(TAG, "No change, from and to values are the same: [%s]",  STR_OR_BLANK(newval));
-            return false;
-        }
-        if (PB_ATYPE(iter.type) == PB_ATYPE_POINTER) {
-            ESP_LOGD(TAG, "Field is a pointer. Freeing previous value if any");
-            FREE_AND_NULL(iter.pData);
-            ESP_LOGD(TAG, "Field is a pointer. Setting new value ");
-            if(newval && strlen(newval)>0){
-                iter.pData = strdup_psram(newval);
-            }
-            
-        } else if (PB_ATYPE(iter.type) == PB_ATYPE_STATIC) {
-            ESP_LOGD(TAG, "Static string. Setting new value");
-            memset(iter.pData,0x00,iter.data_size);
-            if(newval && strlen(newval)>0){
-                strncpy((char*)iter.pData, newval, iter.data_size);
-            }
-        }
-        ESP_LOGD(TAG, "Done setting value ");
-    }
-    return true;
-}

+ 0 - 107
components/platform_config/Configurator.h

@@ -1,107 +0,0 @@
-#pragma once
-#include "State.pb.h"
-#include "configuration.pb.h"
-#include "esp_log.h"
-#include "freertos/FreeRTOS.h"
-#include "freertos/event_groups.h"
-#include "freertos/semphr.h"
-#include "freertos/timers.h"
-#include "status.pb.h"
-#include <strings.h>
-#include "accessors.h"
-#ifdef __cplusplus
-#include <cstdlib>
-#include <string>
-#include <vector>
-
-extern "C" {
-#endif
-#define PLATFORM_GET_PTR(base, sname)                                                              \
-    {                                                                                              \
-        (base && (base)->##has_##(sname) ? &(base)->sname : NULL)
-#define PLATFORM_DEVICES PLATFORM_GET_PTR(platform)
-void configurator_load();
-bool configurator_waitcommit();
-bool configurator_has_changes();
-bool configurator_lock();
-void configurator_unlock();
-void configurator_raise_changed();
-void configurator_raise_state_changed();
-bool configurator_has_changes();
-bool configurator_waitcommit();
-void* configurator_alloc_get_config(size_t* sz);
-bool configurator_parse_config(void* buffer, size_t buffer_size);
-void configurator_reset_configuration();
-pb_type_t configurator_get_field_type(const pb_msgdesc_t* desc, uint32_t tag);
-bool configurator_set_string(
-    const pb_msgdesc_t* desc, uint32_t field_tag, void* message, const char* value);
-extern sys_Config* platform;
-extern sys_State* sys_state;
-#ifdef __cplusplus
-}
-#endif
-
-#ifdef __cplusplus
-
-namespace PlatformConfig {
-
-class Configurator {
-  private:
-    static const int MaxDelay;
-    static const int LockMaxWait;
-
-    EXT_RAM_ATTR static TimerHandle_t _timer;
-    EXT_RAM_ATTR static SemaphoreHandle_t _mutex;
-    EXT_RAM_ATTR static SemaphoreHandle_t _state_mutex;
-    EXT_RAM_ATTR static EventGroupHandle_t _group;
-    bool SetGroupBit(int bit_num, bool flag);
-    void ResetModified();
-    void ResetStateModified();
-    sys_Config _root;
-    sys_State _sys_state;
-
-  public:
-    sys_Config* Root() { return &_root; }
-    sys_State* RootState() { return &_sys_state; }
-    bool WaitForCommit();
-    bool Lock();
-    bool LockState();
-    void Unlock();
-    void UnlockState();
-
-    void* AllocGetConfigBuffer(size_t* sz);
-    static void* AllocGetConfigBuffer(size_t* sz, sys_Config* config);
-    static void ResetStructure(sys_Config* config);
-    bool LoadDecodeState();
-
-    void CommitChanges();
-    bool Commit();
-    static bool Commit(sys_Config* config);
-
-    bool CommitState();
-    static bool CommitState(sys_State* state);
-    sys_Config* AllocDefaultStruct();
-
-    static bool ApplyTargetSettings(sys_Config* conf_root);
-    bool ApplyTargetSettings();
-    bool HasStateChanges();
-
-    bool LoadDecodeBuffer(void* buffer, size_t buffer_size);
-    void InitLoadConfig(const char* filename);
-    void InitLoadConfig(const char* filename, sys_Config* conf_root, bool noinit = false);
-    static bool LoadDecode(
-        void* buffer, size_t buffer_size, sys_Config* conf_root, bool noinit = false);
-
-    Configurator() {
-        _mutex = xSemaphoreCreateMutex();
-        _state_mutex = xSemaphoreCreateMutex();
-        _group = xEventGroupCreate();
-    }
-    void RaiseStateModified();
-    void RaiseModified();
-    bool HasChanges();
-    ~Configurator() {}
-};
-} // namespace PlatformConfig
-extern PlatformConfig::Configurator configurator;
-#endif

+ 45 - 0
components/platform_config/Locking.cpp

@@ -0,0 +1,45 @@
+
+#include "Locking.h"
+#include "esp_log.h"
+#include "tools.h"
+static const char* TAG = "Locking";
+
+using namespace System;
+
+const int Locking::MaxDelay = 1000;
+const int Locking::LockMaxWait = 20 * Locking::MaxDelay;
+
+// C++ methods
+Locking* Locking::Create(std::string name) { return new Locking(name); }
+
+void Locking::Destroy(Locking* lock) { delete lock; }
+
+LockingHandle* Locking_Create(const char* name) {
+    return reinterpret_cast<LockingHandle*>(Locking::Create(std::string(name)));
+}
+
+void Locking_Destroy(LockingHandle* lock) { Locking::Destroy(reinterpret_cast<Locking*>(lock)); }
+
+bool Locking_Lock(LockingHandle* lock, TickType_t maxWait_ms) {
+    return reinterpret_cast<Locking*>(lock)->Lock(maxWait_ms);
+}
+
+void Locking_Unlock(LockingHandle* lock) { reinterpret_cast<Locking*>(lock)->Unlock(); }
+
+bool Locking_IsLocked(LockingHandle* lock) { return reinterpret_cast<Locking*>(lock)->IsLocked(); }
+
+bool Locking::Lock(TickType_t maxWait_ms) {
+    assert(_mutex != nullptr);
+    ESP_LOGV(TAG, "Locking %s", _name.c_str());
+    if (xSemaphoreTakeRecursive(_mutex, pdMS_TO_TICKS(maxWait_ms)) == pdTRUE) {
+        ESP_LOGV(TAG, "locked %s", _name.c_str());
+        return true;
+    } else {
+        ESP_LOGE(TAG, "Unable to lock %s", _name.c_str());
+        return false;
+    }
+}
+void Locking::Unlock() {
+    ESP_LOGV(TAG, "Unlocking %s", _name.c_str());
+    xSemaphoreGiveRecursive(_mutex);
+}

+ 71 - 0
components/platform_config/Locking.h

@@ -0,0 +1,71 @@
+/*
+ *
+ *      Sebastien L. 2023, sle118@hotmail.com
+ *      Philippe G. 2023, philippe_44@outlook.com
+ *
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
+ *
+ *  License Overview:
+ *  ----------------
+ *  The MIT License is a permissive open source license. As a user of this software, you are free to:
+ *  - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
+ *  - Use the software for private, commercial, or any other purposes.
+ *
+ *  Conditions:
+ *  - You must include the above copyright notice and this permission notice in all
+ *    copies or substantial portions of the Software.
+ *
+ *  The MIT License offers a high degree of freedom and is well-suited for both open source and
+ *  commercial applications. It places minimal restrictions on how the software can be used,
+ *  modified, and redistributed. For more details on the MIT License, please refer to the link above.
+ */
+
+#pragma once
+#include "esp_attr.h"
+#include "esp_system.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct LockingHandle LockingHandle;
+
+#ifdef __cplusplus
+} // extern "C"
+
+#include <string>
+namespace System {
+class Locking {
+  private:
+    SemaphoreHandle_t _mutex;
+    static const int MaxDelay;
+    static const int LockMaxWait;
+    std::string _name;
+
+  public:
+    Locking(std::string name) : _mutex(xSemaphoreCreateRecursiveMutex()), _name(name) {}
+    bool Lock(TickType_t maxWait_ms = LockMaxWait);
+    void Unlock();
+    bool IsLocked() { return xSemaphoreGetMutexHolder(_mutex) != nullptr; }
+    ~Locking() { vSemaphoreDelete(_mutex); }
+    static Locking* Create(std::string name);
+    static void Destroy(Locking* lock);
+};
+
+} // namespace PlatformConfig
+
+extern "C" {
+#endif
+
+LockingHandle* Locking_Create(const char* name);
+void Locking_Destroy(LockingHandle* lock);
+bool Locking_Lock(LockingHandle* lock, TickType_t maxWait_ms);
+void Locking_Unlock(LockingHandle* lock);
+bool Locking_IsLocked(LockingHandle* lock);
+
+#ifdef __cplusplus
+}
+#endif

+ 272 - 0
components/platform_config/PBW.cpp

@@ -0,0 +1,272 @@
+#define LOG_LOCAL_LEVEL ESP_LOG_INFO
+#include "Locking.h"
+#include "cpp_tools.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+
+#include "PBW.h"
+#include "accessors.h"
+#include "esp_log.h"
+#include "network_manager.h"
+#include "pb.h"
+#include "pb_decode.h" // Nanopb header for decoding (deserialization)
+#include "pb_encode.h" // Nanopb header for encoding (serialization)
+#include "tools.h"
+#include <functional>
+#include <sstream>
+#include <vector>
+
+namespace System {
+const char* PBHelper::PROTOTAG = "PB";
+
+void PBHelper::ResetModified() {
+    ESP_LOGD(PROTOTAG, "Resetting the global commit flag for %s.", name.c_str());
+    SetGroupBit(Flags::COMMITTED, true);
+}
+
+bool PBHelper::SetGroupBit(Flags flags, bool flag) {
+    bool result = true;
+    int bit_num = static_cast<int>(flags);
+    int curFlags = xEventGroupGetBits(_group);
+    if ((curFlags & static_cast<int>(Flags::LOAD)) && flags == Flags::COMMITTED) {
+        ESP_LOGD(PROTOTAG, "Loading %s, ignoring changes", name.c_str());
+        result = false;
+    }
+    if (result) {
+        if ((curFlags & bit_num) == flag) {
+            ESP_LOGV(PROTOTAG, "Flag %d already %s", bit_num, flag ? "Set" : "Cleared");
+            result = false;
+        }
+    }
+    if (result) {
+        ESP_LOGV(PROTOTAG, "%s Flag %d ", flag ? "Setting" : "Clearing", bit_num);
+        if (!flag) {
+            xEventGroupClearBits(_group, bit_num);
+        } else {
+            xEventGroupSetBits(_group, bit_num);
+        }
+    }
+    return result;
+}
+
+void PBHelper::SyncCommit(void* protoWrapper) {
+    IPBBase* protoWrapperBase = reinterpret_cast<IPBBase*>(protoWrapper);
+    if (protoWrapperBase) {
+        protoWrapperBase->CommitChanges();
+    }
+}
+std::vector<pb_byte_t> PBHelper::EncodeData(
+    const pb_msgdesc_t* fields, const void* src_struct) {
+    size_t datasz;
+    ESP_LOGV(PROTOTAG, "EncodeData: getting size");
+    if (!pb_get_encoded_size(&datasz, fields, src_struct)) {
+        throw std::runtime_error("Failed to get encoded size.");
+    }
+    ESP_LOGV(PROTOTAG, "EncodeData: size: %d. Encoding", datasz);
+    std::vector<pb_byte_t> data(datasz);
+    pb_ostream_t stream = pb_ostream_from_buffer(data.data(), datasz);
+    if (!pb_encode(&stream, fields, src_struct)) {
+        throw std::runtime_error("Failed to encode data.");
+    }
+    ESP_LOGV(PROTOTAG, "EncodeData: Done");
+    return data;
+}
+
+void PBHelper::DecodeData(
+    std::vector<pb_byte_t> data, const pb_msgdesc_t* fields, void* target, bool noinit) {
+    pb_istream_t stream = pb_istream_from_buffer((uint8_t*)data.data(), data.size());
+    bool result = false;
+
+    // Decode the Protocol Buffers message
+    if (noinit) {
+        ESP_LOGD(PROTOTAG, "Decoding WITHOUT initialization");
+        result = pb_decode_noinit(&stream, fields, data.data());
+    } else {
+        ESP_LOGD(PROTOTAG, "Decoding WITH initialization");
+        result = pb_decode(&stream, fields, target);
+    }
+    if (!result) {
+        throw std::runtime_error(
+            std::string("Failed to decode settings: %s", PB_GET_ERROR(&stream)));
+    }
+    ESP_LOGD(PROTOTAG, "Data decoded");
+}
+void PBHelper::CommitFile(
+    const std::string& filename, const pb_msgdesc_t* fields, const void* src_struct) {
+    size_t datasz = 0;
+    ESP_LOGD(PROTOTAG, "Committing data to file File %s", filename.c_str());
+
+
+    if (!pb_get_encoded_size(&datasz, fields, src_struct)) {
+        throw std::runtime_error("Failed to get encoded size.");
+    }
+
+    if (datasz == 0) {
+        ESP_LOGW(PROTOTAG, "File %s not written. Data size is zero", filename.c_str());
+        return;
+    }
+
+    ESP_LOGD(PROTOTAG, "Committing to file %s", filename.c_str());
+    if (!src_struct) {
+        throw std::runtime_error("Null pointer received.");
+    }
+    FILE* file = fopen(filename.c_str(), "wb");
+    if (file == nullptr) {
+        throw std::runtime_error(std::string("Error opening file ") + filename.c_str());
+    }
+    pb_ostream_t filestream = PB_OSTREAM_SIZING;
+    filestream.callback = &out_file_binding;
+    filestream.state = file;
+    filestream.max_size = SIZE_MAX;
+    ESP_LOGD(PROTOTAG, "Starting file encode for %s", filename.c_str());
+    if (!pb_encode(&filestream, fields, (void*)src_struct)) {
+        fclose(file);
+        throw std::runtime_error("Encoding file failed");
+    }
+    ESP_LOGD(PROTOTAG, "Encoded size: %d", filestream.bytes_written);
+    if (filestream.bytes_written == 0) {
+        ESP_LOGW(PROTOTAG, "Empty structure for file %s", filename.c_str());
+    }
+    fclose(file);
+}
+
+bool PBHelper::IsDataDifferent(
+    const pb_msgdesc_t* fields, const void* src_struct, const void* other_struct) {
+    bool changed = false;
+    try {
+        ESP_LOGV(PROTOTAG, "Encoding Source data");
+        auto src_data = EncodeData(fields, src_struct);
+        ESP_LOGV(PROTOTAG, "Encoding Compared data");
+        auto other_data = EncodeData(fields, other_struct);
+        if (src_data.size() != other_data.size()) {
+            ESP_LOGD(PROTOTAG, "IsDataDifferent: source and target size different: [%d!=%d]",
+                src_data.size(), other_data.size());
+            changed = true;
+        } else if (src_data != other_data) {
+            ESP_LOGD(PROTOTAG, "IsDataDifferent: source and target not the same");
+            changed = true;
+        }
+        if (changed && esp_log_level_get(PROTOTAG) >= ESP_LOG_DEBUG) {
+            ESP_LOGD(PROTOTAG, "Source data: ");
+            dump_data((const uint8_t*)src_data.data(), src_data.size());
+            ESP_LOGD(PROTOTAG, "Compared data: ");
+            dump_data((const uint8_t*)other_data.data(), src_data.size());
+        }
+    } catch (const std::runtime_error& e) {
+        throw std::runtime_error(std::string("Comparison failed: ") + e.what());
+    }
+    ESP_LOGD(PROTOTAG, "IsDataDifferent: %s", changed ? "TRUE" : "FALSE");
+    return changed;
+}
+
+void PBHelper::CopyStructure(
+    const void* src_data, const pb_msgdesc_t* fields, void* target_data) {
+    try {
+        auto src = EncodeData(fields, src_data);
+        ESP_LOGD(PROTOTAG, "Encoded structure to copy has %d bytes", src.size());
+        DecodeData(src, fields, target_data, false);
+    } catch (const std::runtime_error& e) {
+        throw std::runtime_error(std::string("Copy failed: ") + e.what());
+    }
+}
+void PBHelper::LoadFile(
+    const std::string& filename, const pb_msgdesc_t* fields, void* target_data, bool noinit) {
+    struct stat fileInformation;
+    if (!get_file_info(&fileInformation, filename.c_str()) || fileInformation.st_size == 0) {
+        throw FileNotFoundException("filename");
+    }
+
+    FILE* file = fopen(filename.c_str(), "rb");
+    ESP_LOGI(PROTOTAG, "Loading file %s", filename.c_str());
+    if (file == nullptr) {
+        int errNum = errno;
+        ESP_LOGE(
+            PROTOTAG, "Unable to open file: %s. Error: %s", filename.c_str(), strerror(errNum));
+        throw std::runtime_error(
+            "Unable to open file: " + filename + ". Error: " + strerror(errNum));
+    }
+    pb_istream_t filestream = PB_ISTREAM_EMPTY;
+    filestream.callback = &in_file_binding;
+    filestream.state = file;
+    filestream.bytes_left = fileInformation.st_size;
+
+    ESP_LOGV(PROTOTAG, "Starting decode.");
+    bool result = false;
+    if (noinit) {
+        ESP_LOGV(PROTOTAG, "Decoding WITHOUT initialization");
+        result = pb_decode_noinit(&filestream, fields, target_data);
+    } else {
+        ESP_LOGV(PROTOTAG, "Decoding WITH initialization");
+        result = pb_decode(&filestream, fields, target_data);
+    }
+    fclose(file);
+    if (!result) {
+        throw System::DecodeError(PB_GET_ERROR(&filestream));
+    }
+    ESP_LOGV(PROTOTAG, "Decode done.");
+}
+
+bool PBHelper::FileExists(std::string filename) {
+    struct stat fileInformation;
+    ESP_LOGD(PROTOTAG, "Checking if file %s exists", filename.c_str());
+    return get_file_info(&fileInformation, filename.c_str()) && fileInformation.st_size > 0;
+}
+bool PBHelper::FileExists() { return FileExists(filename); }
+bool PBHelper::IsLoading() { return xEventGroupGetBits(_group) & static_cast<int>(Flags::LOAD); }
+void PBHelper::SetLoading(bool active) { SetGroupBit(Flags::LOAD, active); }
+
+bool PBHelper::WaitForCommit(uint8_t retries=2) {
+    auto remain= retries;
+    auto bits = xEventGroupGetBits(_group);
+    bool commit_pending = HasChanges();
+    ESP_LOGD(PROTOTAG, "Entering WaitForCommit bits: %d, changes? %s", bits,
+        commit_pending ? "YES" : "NO");
+    while (commit_pending && remain-->0) {
+        ESP_LOGD(PROTOTAG, "Waiting for config commit ...");
+        auto bits = xEventGroupWaitBits(
+            _group, static_cast<int>(Flags::COMMITTED), pdFALSE, pdTRUE, (MaxDelay * 2) / portTICK_PERIOD_MS);
+        commit_pending = !(bits & static_cast<int>(Flags::COMMITTED));
+        ESP_LOGD(
+            PROTOTAG, "WaitForCommit bits: %d, changes? %s", bits, commit_pending ? "YES" : "NO");
+        if (commit_pending) {
+            ESP_LOGW(PROTOTAG, "Timeout waiting for config commit for [%s]", name.c_str());
+        } else {
+            ESP_LOGI(PROTOTAG, "Changes to %s committed", name.c_str());
+        }
+    }
+    return !commit_pending;
+}
+
+void PBHelper::RaiseChangedAsync() {
+    if(_no_save){
+        ESP_LOGD(PROTOTAG,"Ignoring changes for %s, as it is marked not to be saved", name.c_str());
+    }
+    ESP_LOGI(PROTOTAG, "Changes made to %s", name.c_str());
+    if (IsLoading()) {
+        ESP_LOGD(PROTOTAG, "Ignoring raise modified during load of %s", name.c_str());
+    } else {
+        SetGroupBit(Flags::COMMITTED, false);
+        network_async_commit_protowrapper(
+            static_cast<void*>(static_cast<IPBBase*>(this)));
+    }
+}
+const std::string& PBHelper::GetFileName() { return filename; }
+bool PBHelper::HasChanges() { return !(xEventGroupGetBits(_group) & static_cast<int>(Flags::COMMITTED)); }
+} // namespace PlatformConfig
+
+bool proto_load_file(
+    const char* filename, const pb_msgdesc_t* fields, void* target_data, bool noinit = false) {
+    try {
+        ESP_LOGI(System::PBHelper::PROTOTAG,"Loading file %s",filename);
+        System::PBHelper::LoadFile(std::string(filename), fields, target_data, noinit);
+    } catch (const std::exception& e) {
+        if(!noinit){
+            // initialize the structure 
+            pb_istream_t stream = pb_istream_from_buffer(nullptr, 0);
+            pb_decode(&stream, fields, target_data);
+        }
+        ESP_LOGE(System::PBHelper::PROTOTAG, "Error loading file %s: %s", filename, e.what());
+        return false;
+    }
+    return true;
+}

+ 331 - 0
components/platform_config/PBW.h

@@ -0,0 +1,331 @@
+/*
+ *
+ *      Sebastien L. 2023, sle118@hotmail.com
+ *      Philippe G. 2023, philippe_44@outlook.com
+ *
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
+ *
+ *  License Overview:
+ *  ----------------
+ *  The MIT License is a permissive open source license. As a user of this software, you are free to:
+ *  - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
+ *  - Use the software for private, commercial, or any other purposes.
+ *
+ *  Conditions:
+ *  - You must include the above copyright notice and this permission notice in all
+ *    copies or substantial portions of the Software.
+ *
+ *  The MIT License offers a high degree of freedom and is well-suited for both open source and
+ *  commercial applications. It places minimal restrictions on how the software can be used,
+ *  modified, and redistributed. For more details on the MIT License, please refer to the link above.
+ */
+#pragma once
+#ifndef LOG_LOCAL_LEVEL
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
+
+#include "Locking.h"
+#include "MessageDefinition.pb.h"
+#include "accessors.h"
+#include "configuration.pb.h"
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "network_manager.h"
+#include "pb.h"
+#include "pb_decode.h" // Nanopb header for decoding (deserialization)
+#include "pb_encode.h" // Nanopb header for encoding (serialization)
+#include "tools.h"
+#include "tools_spiffs_utils.h"
+#include "bootstate.h"
+#include "State.pb.h"
+
+#ifdef __cplusplus
+// #include <functional>
+#include <sstream>
+#include <vector>
+
+namespace System {
+template <typename T> struct has_target_implementation : std::false_type {};
+
+class FileNotFoundException : public std::runtime_error {
+  public:
+    explicit FileNotFoundException(const std::string& message) : std::runtime_error(message) {}
+};
+class DecodeError : public std::runtime_error {
+  public:
+    explicit DecodeError(const std::string& message) : std::runtime_error(message) {}
+};
+class IPBBase {
+  public:
+    virtual void CommitChanges() = 0;
+    virtual ~IPBBase() {}
+};
+class PBHelper : public IPBBase {
+
+  protected:
+    const pb_msgdesc_t* _fields;
+    EventGroupHandle_t _group;
+    std::string name;
+    std::string filename;
+    static const int MaxDelay = 1000;
+    Locking _lock;
+    size_t _datasize;    
+    bool _no_save;
+
+  public:
+    enum class Flags { COMMITTED = BIT0, LOAD = BIT1 };
+    bool SetGroupBit(Flags flags, bool flag);
+    static const char* PROTOTAG;
+    std::string& GetName() { return name; }
+    const char* GetCName() { return name.c_str(); }
+    static std::string GetDefFileName(std::string name){
+        return std::string(spiffs_base_path) + "/data/def_" + name + ".bin";
+    }
+    std::string GetDefFileName(){
+        return GetDefFileName(this->name);
+    }
+    size_t GetDataSize(){
+        return  _datasize;
+    }
+    PBHelper(std::string name, const pb_msgdesc_t* fields, size_t defn_size, size_t datasize, bool no_save = false)
+        : _fields(fields), _group(xEventGroupCreate()), name(std::move(name)), _lock(this->name),_datasize(datasize), _no_save(no_save) {
+        sys_message_def definition = sys_message_def_init_default;
+        bool savedef = false;
+        ESP_LOGD(PROTOTAG,"Getting definition file name");
+        auto deffile = GetDefFileName();
+        ESP_LOGD(PROTOTAG,"Instantiating with definition size %d and data size %d", defn_size,datasize);
+        
+        try {
+            PBHelper::LoadFile(deffile, &sys_message_def_msg, static_cast<void*>(&definition));
+            if (definition.data->size != defn_size || definition.datasize != _datasize) {
+                ESP_LOGW(PROTOTAG, "Structure definition %s has changed", this->name.c_str());
+                if (!is_recovery_running) {
+                    savedef = true;
+                    pb_release(&sys_message_def_msg, &definition);
+                } else {
+                    ESP_LOGW(PROTOTAG, "Using existing definition for recovery");
+                    _fields = reinterpret_cast<const pb_msgdesc_t*>(definition.data->bytes);
+                    _datasize = definition.datasize;
+                }
+            }
+        } catch (const FileNotFoundException& e) {
+            savedef = true;
+        }
+
+        if (savedef) {
+            ESP_LOGW(PROTOTAG, "Saving definition for structure %s", this->name.c_str());
+            auto data = (pb_bytes_array_t*)malloc_init_external(sizeof(pb_bytes_array_t)+defn_size);
+            memcpy(&data->bytes, fields, defn_size);
+            data->size = defn_size;
+            definition.data = data;
+            definition.datasize = _datasize;
+            ESP_LOGD(PROTOTAG,"Committing structure with %d bytes ",data->size);
+            PBHelper::CommitFile(deffile, &sys_message_def_msg, &definition);
+            ESP_LOGD(PROTOTAG,"Releasing memory");
+            free(data);
+        }
+    }
+    void ResetModified();
+    static void SyncCommit(void* protoWrapper);
+    static void CommitFile(const std::string& filename, const pb_msgdesc_t* fields, const void* src_struct);
+    static bool IsDataDifferent(const pb_msgdesc_t* fields, const void* src_struct, const void* other_struct);
+
+    static void CopyStructure(const void* src_data, const pb_msgdesc_t* fields, void* target_data);
+    static void LoadFile(const std::string& filename, const pb_msgdesc_t* fields, void* target_data, bool noinit = false);
+    static std::vector<pb_byte_t> EncodeData(const pb_msgdesc_t* fields, const void* src_struct);
+    static void DecodeData(std::vector<pb_byte_t> data, const pb_msgdesc_t* fields, void* target, bool noinit = false);
+    bool FileExists(std::string filename);
+    bool FileExists();
+    bool IsLoading();
+    void SetLoading(bool active);
+    bool WaitForCommit(uint8_t retries );
+    void RaiseChangedAsync();
+    const std::string& GetFileName();
+    bool HasChanges();
+};
+template <typename T> class PB : public PBHelper {
+  private:
+    T *_root;
+
+    // Generic _setTarget implementation
+    void _setTarget(std::string target, std::false_type) { ESP_LOGE(PROTOTAG, "Setting target not implemented for %s", name.c_str()); }
+
+    // Special handling for sys_config
+    void _setTarget(std::string target, std::true_type) {
+        if (_root->target) {
+            free(_root->target);
+        }
+        _root->target = strdup_psram(target.c_str());
+    }
+    std::string _getTargetName(std::false_type) { return ""; }
+    std::string _getTargetName(std::true_type) { return STR_OR_BLANK(_root->target); }
+
+  public:
+    // Accessor for the underlying structure
+    T& Root() { return *_root; }
+    // Const accessor for the underlying structure
+
+    const T& Root() const { return *_root; }
+    T* get()  { return _root; }
+    const T* get() const { return (const T*)_root; }
+
+    // Constructor
+    explicit PB(std::string name, const pb_msgdesc_t* fields, size_t defn_size, bool no_save = false) : 
+        PBHelper(std::move(name), fields,defn_size, sizeof(T), no_save) {
+        ESP_LOGD(PROTOTAG, "Instantiating PB class for %s with data size %d", this->name.c_str(), sizeof(T));
+        ResetModified();
+        filename = std::string(spiffs_base_path) + "/data/" + this->name + ".bin";
+        _root = (T*)(malloc_init_external(_datasize));
+        memset(_root, 0x00, sizeof(_datasize));
+    }
+
+    std::string GetTargetName() { return _getTargetName(has_target_implementation<T>{}); }
+    void SetTarget(std::string targetname, bool skip_commit = false) {
+        std::string newtarget = trim(targetname);
+        std::string currenttarget = trim(GetTargetName());
+        ESP_LOGD(PROTOTAG, "SetTarget called with %s", newtarget.c_str());
+        if (newtarget == currenttarget && !newtarget.empty()) {
+            ESP_LOGD(PROTOTAG, "Target name %s not changed for %s", currenttarget.c_str(), name.c_str());
+        } else if (newtarget.empty() && !currenttarget.empty()) {
+            ESP_LOGW(PROTOTAG, "Target name %s was removed for %s ", currenttarget.c_str(), name.c_str());
+        }
+        ESP_LOGI(PROTOTAG, "Setting target %s for %s", newtarget.c_str(), name.c_str());
+        _setTarget(newtarget, has_target_implementation<T>{});
+        if (!skip_commit) {
+            ESP_LOGD(PROTOTAG, "Raising changed flag to commit new target name.");
+            RaiseChangedAsync();
+        } else {
+            SetGroupBit(Flags::COMMITTED, false);
+        }
+    }
+    std::string GetTargetFileName() {
+        if (GetTargetName().empty()) {
+            return "";
+        }
+        auto target_name = GetTargetName();
+        return std::string(spiffs_base_path) + "/targets/" + toLowerStr(target_name) + "/" + name + ".bin";
+    }
+    void Reinitialize(bool skip_target = false, bool commit = false, std::string target_name = "") {
+        ESP_LOGW(PROTOTAG, "Initializing %s", name.c_str());
+        pb_istream_t stream = PB_ISTREAM_EMPTY;
+        // initialize blank structure by
+        // decoding a dummy stream
+        pb_decode(&stream, _fields, _root);
+        SetLoading(true);
+        try {
+            std::string fullpath = std::string(spiffs_base_path) + "/defaults/" + this->name + ".bin";
+            ESP_LOGD(PROTOTAG, "Attempting to load defaults file for %s", fullpath.c_str());
+            PBHelper::LoadFile(fullpath.c_str(), _fields, static_cast<void*>(_root), true);
+        } catch (FileNotFoundException&) {
+            ESP_LOGW(PROTOTAG, "No defaults found for %s", name.c_str());
+        } catch (std::runtime_error& e) {
+            ESP_LOGE(PROTOTAG, "Error loading Target %s overrides file: %s", GetTargetName().c_str(), e.what());
+        }
+        SetLoading(false);
+        if (!skip_target) {
+            if (!target_name.empty()) {
+                SetTarget(target_name, true);
+            }
+            LoadTargetValues();
+        }
+        if (commit) {
+            CommitChanges();
+        }
+    }
+    void LoadFile(bool skip_target = false, bool noinit = false) {
+        SetLoading(true);
+        PBHelper::LoadFile(filename, _fields, static_cast<void*>(_root), noinit);
+        SetLoading(false);
+        if (!skip_target) {
+            LoadTargetValues();
+        }
+    }
+    void LoadTargetValues() {
+        ESP_LOGD(PROTOTAG, "Loading target %s values for %s", GetTargetName().c_str(), name.c_str());
+        if (GetTargetFileName().empty()) {
+            ESP_LOGD(PROTOTAG, "No target file to load for %s", name.c_str());
+            return;
+        }
+        try {
+            // T old;
+            // CopyTo(old);
+            ESP_LOGI(PROTOTAG, "Loading target %s values for %s", GetTargetName().c_str(), name.c_str());
+            PBHelper::LoadFile(GetTargetFileName(), _fields, static_cast<void*>(_root), true);
+            // don't commit the values here, as it doesn't work well with
+            // repeated values
+            // if (*this != old) {
+            //     ESP_LOGI(PROTOTAG, "Changes detected from target values.");
+            //     RaiseChangedAsync();
+            // }
+            SetGroupBit(Flags::COMMITTED, false);
+
+        } catch (FileNotFoundException&) {
+            ESP_LOGD(PROTOTAG, "Target %s overrides file not found for %s", GetTargetName().c_str(), name.c_str());
+        } catch (std::runtime_error& e) {
+            ESP_LOGE(PROTOTAG, "Error loading Target %s overrides file: %s", GetTargetName().c_str(), e.what());
+        }
+    }
+    void CommitChanges() override {
+        ESP_LOGI(PROTOTAG, "Committing %s to flash.", name.c_str());
+        if (!_lock.Lock()) {
+            ESP_LOGE(PROTOTAG, "Unable to lock config for commit ");
+            return;
+        }
+        ESP_LOGV(PROTOTAG, "Config Locked. Committing");
+        try {
+            CommitFile(filename, _fields, _root);
+        } catch (...) {
+        }
+        ResetModified();
+        _lock.Unlock();
+        ESP_LOGI(PROTOTAG, "Done committing %s to flash.", name.c_str());
+    }
+    bool Lock() { return _lock.Lock(); }
+    void Unlock() { return _lock.Unlock(); }
+    std::vector<pb_byte_t> Encode() {
+        auto data = std::vector<pb_byte_t>();
+        if (!_lock.Lock()) {
+            throw std::runtime_error("Unable to lock object");
+        }
+        data = EncodeData(_fields, this->_root);
+        _lock.Unlock();
+        return data;
+    }
+
+    void CopyTo(T& target_data) {
+        if (!_lock.Lock()) {
+            ESP_LOGE(PROTOTAG, "Lock failed for %s", name.c_str());
+            throw std::runtime_error("Lock failed ");
+        }
+        CopyStructure(_root, _fields, &target_data);
+        _lock.Unlock();
+    }
+    void CopyFrom(const T& source_data) {
+        if (!_lock.Lock()) {
+            ESP_LOGE(PROTOTAG, "Lock failed for %s", name.c_str());
+            throw std::runtime_error("Lock failed ");
+        }
+        CopyStructure(&source_data, _fields, _root);
+        _lock.Unlock();
+    }
+
+    bool operator!=(const T& other) const { return IsDataDifferent(_fields, _root, &other); }
+    bool operator==(const T& other) const { return !IsDataDifferent(_fields, _root, &other); }
+    void DecodeData(const std::vector<pb_byte_t> data, bool noinit = false) { PBHelper::DecodeData(data, _fields, (void*)_root, noinit); }
+
+    ~PB() { vEventGroupDelete(_group); };
+};
+template <> struct has_target_implementation<sys_config> : std::true_type {};
+
+template <> struct has_target_implementation<sys_state_data> : std::true_type {};
+
+} // namespace PlatformConfig
+extern "C" {
+#endif
+bool proto_load_file(const char* filename, const pb_msgdesc_t* fields, void* target_data, bool noinit);
+
+#ifdef __cplusplus
+}
+#endif

+ 845 - 0
components/platform_config/WifiList.cpp

@@ -0,0 +1,845 @@
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#include "WifiList.h"
+#include "Config.h"
+#include "esp_check.h"
+#include "esp_log.h"
+#include "esp_system.h"
+#include <memory>
+
+static const char* TAG_CRED_MANAGER = "credentials_manager";
+bool sys_status_wifi_callback(pb_istream_t* istream, pb_ostream_t* ostream, const pb_field_iter_t* field) {
+    return sys_net_config_callback(istream, ostream, field);
+}
+bool sys_net_config_callback(pb_istream_t* istream, pb_ostream_t* ostream, const pb_field_iter_t* field) {
+    WifiList** managerPtr = static_cast<WifiList**>(field->pData);
+    WifiList* manager = *managerPtr;
+
+    if (istream != NULL && (field->tag == sys_net_config_credentials_tag || field->tag == sys_status_wifi_scan_result_tag)) {
+        if (manager == nullptr) {
+            ESP_LOGE(TAG_CRED_MANAGER, "Invalid pointer to wifi list manager");
+            return false;
+        }
+        ESP_LOGV(TAG_CRED_MANAGER, "Decoding credentials");
+        sys_net_wifi_entry entry = sys_net_wifi_entry_init_default;
+        if (!pb_decode(istream, &sys_net_wifi_entry_msg, &entry)) return false;
+        printf("\nFound ssid %s, password %s\n", entry.ssid, entry.password);
+        try {
+            manager->AddUpdate(entry); // Add to the manager
+        } catch (const std::exception& e) {
+            ESP_LOGE(TAG_CRED_MANAGER, "decode exception: %s", e.what());
+            return false;
+        }
+        ESP_LOGV(TAG_CRED_MANAGER, "Credentials decoding completed");
+    } else if (ostream != NULL && (field->tag == sys_net_config_credentials_tag || field->tag == sys_status_wifi_scan_result_tag)) {
+        if (manager == nullptr) {
+            ESP_LOGV(TAG_CRED_MANAGER, "No wifi entries manager instance. nothing to encode");
+            return true;
+        }
+        ESP_LOGV(TAG_CRED_MANAGER, "Encoding %d access points", manager->GetCount());
+
+        for (int i = 0; i < manager->GetCount(); i++) {
+            ESP_LOGV(TAG_CRED_MANAGER, "Encoding credential #%d: SSID: %s, PASS: %s", i, manager->GetIndex(i)->ssid, manager->GetIndex(i)->password);
+            if (!pb_encode_tag_for_field(ostream, field)) {
+                return false;
+            }
+            if (!pb_encode_submessage(ostream, &sys_net_wifi_entry_msg, manager->GetIndex(i))) {
+                return false;
+            }
+        }
+        ESP_LOGV(TAG_CRED_MANAGER, "Credentials encoding completed");
+    }
+
+    return true;
+}
+std::string WifiList::GetBSSID(const wifi_event_sta_connected_t* evt) {
+    char buffer[18]={};
+    FormatBSSID(buffer, sizeof(buffer), evt->bssid);
+    ESP_LOGD(TAG_CRED_MANAGER, "Formatted BSSID: %s", buffer);
+    return std::string(buffer);
+}
+bool WifiList::OffsetTimeStamp(google_protobuf_Timestamp* ts) {
+    timeval tts;
+    google_protobuf_Timestamp gts;
+    gettimeofday((struct timeval*)&tts, NULL);
+    gts.nanos = tts.tv_usec * 1000;
+    gts.seconds = tts.tv_sec;
+    if (tts.tv_sec < 1704143717) {
+        ESP_LOGE(TAG_CRED_MANAGER, "Error updating time stamp. Clock doesn't seem right");
+        return false;
+    }
+    if (ts && ts->seconds < 1704143717) {
+        ESP_LOGV(TAG_CRED_MANAGER, "Updating time stamp based on new clock value");
+        ts->seconds = gts.seconds - ts->seconds;
+        ts->nanos = gts.nanos - ts->nanos;
+        return true;
+    }
+    ESP_LOGD(TAG_CRED_MANAGER, "Time stamp already updated. Skipping");
+    return false;
+}
+bool WifiList::UpdateTimeStamp(google_protobuf_Timestamp* ts, bool& has_flag_val) {
+    ESP_RETURN_ON_FALSE(ts != nullptr, false, TAG_CRED_MANAGER, "Null pointer!");
+    bool changed = false;
+    timeval tts;
+    google_protobuf_Timestamp gts;
+    gettimeofday((struct timeval*)&tts, NULL);
+    gts.nanos = tts.tv_usec * 1000;
+    gts.seconds = tts.tv_sec;
+    if (!has_flag_val || gts.nanos != ts->nanos || gts.seconds != ts->seconds) {
+        ts->seconds = gts.seconds;
+        ts->nanos = gts.nanos;
+        has_flag_val = true;
+        changed = true;
+    }
+    return changed;
+}
+
+bool WifiList::isEmpty(const char* str, size_t len) {
+    for (size_t i = 0; i < len; ++i) {
+        if (str[i] != '\0') {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool WifiList::Update(const wifi_ap_record_t* ap, bool connected) {
+    if (!Lock()) {
+        throw std::runtime_error("Lock failed");
+    }
+    auto existing = Get(ap);
+    if (!existing) {
+        return false;
+    }
+    auto updated = ToSTAEntry(ap);
+    updated.connected = connected;
+    bool changed = Update(*existing, updated);
+    Release(&updated);
+    Unlock();
+    return changed;
+}
+bool WifiList::Update(sys_net_wifi_entry& existingEntry, sys_net_wifi_entry& updated) {
+
+    // Check if any relevant fields have changed
+    bool hasChanged = false;
+    if (!isEmpty(updated.ssid, sizeof(updated.ssid)) && memcmp(existingEntry.ssid, updated.ssid, sizeof(existingEntry.ssid)) != 0) {
+        memcpy(existingEntry.ssid, updated.ssid, sizeof(existingEntry.ssid));
+        hasChanged = true;
+    }
+
+    // Check and copy BSSID if the compared BSSID is not empty
+    if (!isEmpty(updated.bssid, sizeof(updated.bssid)) && strcmp(updated.bssid, "00:00:00:00:00:00") != 0 &&
+        memcmp(existingEntry.bssid, updated.bssid, sizeof(existingEntry.bssid)) != 0) {
+        memcpy(existingEntry.bssid, updated.bssid, sizeof(existingEntry.bssid));
+        hasChanged = true;
+    }
+
+    // Check and copy password if the compared password is not empty
+    if (!isEmpty(updated.password, sizeof(updated.password)) &&
+        memcmp(existingEntry.password, updated.password, sizeof(existingEntry.password)) != 0) {
+        memcpy(existingEntry.password, updated.password, sizeof(existingEntry.password));
+        hasChanged = true;
+    }
+
+    if (existingEntry.channel != updated.channel && updated.channel > 0) {
+        existingEntry.channel = updated.channel;
+        hasChanged = true;
+    }
+
+    if (existingEntry.auth_type != updated.auth_type && updated.auth_type != sys_net_auth_types_AUTH_UNKNOWN) {
+        existingEntry.auth_type = updated.auth_type;
+        hasChanged = true;
+    }
+
+    if (areRadioTypesDifferent(existingEntry.radio_type, existingEntry.radio_type_count, updated.radio_type, updated.radio_type_count) &&
+        updated.radio_type_count > 0 && updated.radio_type[0] != sys_net_radio_types_UNKNOWN) {
+
+        if (existingEntry.radio_type != nullptr) {
+            // Free the old radio_type array if it exists
+            delete[] existingEntry.radio_type;
+        }
+        // Allocate new memory and copy the updated radio types
+        existingEntry.radio_type_count = updated.radio_type_count;
+        existingEntry.radio_type = new sys_net_radio_types[updated.radio_type_count];
+        std::copy(updated.radio_type, updated.radio_type + updated.radio_type_count, existingEntry.radio_type);
+        hasChanged = true;
+    }
+
+    if (updated.has_last_try) {
+        if (memcmp(&existingEntry.last_try, &updated.last_try, sizeof(existingEntry.last_try)) != 0) {
+            memcpy(&existingEntry.last_try, &updated.last_try, sizeof(existingEntry.last_try));
+            hasChanged = true;
+        }
+    }
+    if (updated.has_last_seen) {
+        if (memcmp(&existingEntry.last_seen, &updated.last_seen, sizeof(existingEntry.last_seen)) != 0) {
+            memcpy(&existingEntry.last_seen, &updated.last_seen, sizeof(existingEntry.last_seen));
+            hasChanged = true;
+        }
+    }
+    if (existingEntry.has_last_seen != updated.has_last_seen && updated.has_last_seen) {
+        existingEntry.has_last_seen = updated.has_last_seen;
+        hasChanged = true;
+    }
+    if (existingEntry.has_last_try != updated.has_last_try && updated.has_last_try) {
+        existingEntry.has_last_try = updated.has_last_try;
+        hasChanged = true;
+    }
+
+    if (existingEntry.connected != updated.connected && updated.connected) {
+        existingEntry.connected = updated.connected;
+        hasChanged = true;
+    }
+
+    if (existingEntry.rssi != updated.rssi && updated.rssi != 0) {
+        existingEntry.rssi = updated.rssi;
+        hasChanged = true;
+    }
+
+    return hasChanged;
+}
+
+std::string WifiList::formatRadioTypes(const sys_net_radio_types* radioTypes, pb_size_t count) {
+    std::string result;
+
+    for (pb_size_t i = 0; i < count; ++i) {
+        switch (radioTypes[i]) {
+        case sys_net_radio_types_PHY_11B:
+            result += "B";
+            break;
+        case sys_net_radio_types_PHY_11G:
+            result += "G";
+            break;
+        case sys_net_radio_types_PHY_11N:
+            result += "N";
+            break;
+        case sys_net_radio_types_LR:
+            result += "L";
+            break;
+        case sys_net_radio_types_WPS:
+            result += "W";
+            break;
+        case sys_net_radio_types_FTM_RESPONDER:
+            result += "FR";
+            break;
+        case sys_net_radio_types_FTM_INITIATOR:
+            result += "FI";
+            break;
+        case sys_net_radio_types_UNKNOWN:
+        default:
+            result += "U";
+            break;
+        }
+        if (i < count - 1) {
+            result += ",";
+        }
+    }
+
+    return result;
+}
+
+bool WifiList::Update(const wifi_sta_config_t* sta, bool connected) {
+    if (!sta) {
+        return false; // Invalid input
+    }
+    if (!Lock()) {
+        throw std::runtime_error("Lock failed");
+    }
+    sys_net_wifi_entry* existingEntry = Get(sta);
+
+    // If the entry does not exist, nothing to update
+    if (!existingEntry) {
+        Unlock();
+        return false;
+    }
+    auto updated = ToSTAEntry(sta);
+    // Check if any relevant fields have changed
+    bool hasChanged = false;
+
+    if (strlen(updated.ssid) > 0 && memcmp(existingEntry->ssid, updated.ssid, sizeof(existingEntry->ssid)) != 0) {
+        memcpy(existingEntry->ssid, updated.ssid, sizeof(existingEntry->ssid));
+        hasChanged = true;
+    }
+
+    if (strlen(updated.bssid) > 0 && strcmp(updated.bssid, "00:00:00:00:00:00") != 0 &&
+        memcmp(existingEntry->bssid, updated.bssid, sizeof(existingEntry->bssid)) != 0) {
+        memcpy(existingEntry->bssid, updated.bssid, sizeof(existingEntry->bssid));
+        hasChanged = true;
+    }
+
+    if (existingEntry->channel != updated.channel) {
+        existingEntry->channel = updated.channel;
+        hasChanged = true;
+    }
+
+    if (existingEntry->auth_type != updated.auth_type && updated.auth_type != sys_net_auth_types_AUTH_UNKNOWN) {
+        existingEntry->auth_type = updated.auth_type;
+        hasChanged = true;
+    }
+
+    if (areRadioTypesDifferent(existingEntry->radio_type, existingEntry->radio_type_count, updated.radio_type, updated.radio_type_count) &&
+        updated.radio_type_count > 0 && updated.radio_type[0] != sys_net_radio_types_UNKNOWN) {
+        // Free the old radio_type array if it exists
+        delete[] existingEntry->radio_type;
+
+        // Allocate new memory and copy the updated radio types
+        existingEntry->radio_type_count = updated.radio_type_count;
+        existingEntry->radio_type = new sys_net_radio_types[updated.radio_type_count];
+        std::copy(updated.radio_type, updated.radio_type + updated.radio_type_count, existingEntry->radio_type);
+
+        hasChanged = true;
+    }
+
+    if (updated.has_last_try) {
+        if (memcmp(&existingEntry->last_try, &updated.last_try, sizeof(existingEntry->last_try)) != 0) {
+            memcpy(&existingEntry->last_try, &updated.last_try, sizeof(existingEntry->last_try));
+            hasChanged = true;
+        }
+    }
+
+    if (updated.has_last_seen) {
+        if (memcmp(&existingEntry->last_seen, &updated.last_seen, sizeof(existingEntry->last_seen)) != 0) {
+            memcpy(&existingEntry->last_seen, &updated.last_seen, sizeof(existingEntry->last_seen));
+            hasChanged = true;
+        }
+    }
+    if (existingEntry->has_last_try != updated.has_last_try) {
+        existingEntry->has_last_try = updated.has_last_try;
+        hasChanged = true;
+    }
+    if (existingEntry->has_last_seen != updated.has_last_seen) {
+        existingEntry->has_last_seen = updated.has_last_seen;
+        hasChanged = true;
+    }
+    if (existingEntry->connected != (connected | updated.connected)) {
+        existingEntry->connected = connected | updated.connected;
+        hasChanged = true;
+    }
+
+    if (strlen(updated.password) == 0 && strlen(existingEntry->password) > 0) {
+        ESP_LOGW(TAG_CRED_MANAGER, "Updated password is empty, while existing password is %s for %s. Ignoring.", existingEntry->password,
+            existingEntry->ssid);
+    } else {
+        if (memcmp(existingEntry->password, updated.password, sizeof(existingEntry->password)) != 0) {
+            memcpy(existingEntry->password, updated.password, sizeof(existingEntry->password));
+            hasChanged = true;
+        }
+    }
+
+    if (existingEntry->rssi != updated.rssi && updated.rssi != 0) {
+        existingEntry->rssi = updated.rssi;
+        hasChanged = true;
+    }
+    Release(&updated);
+    Unlock();
+    return hasChanged;
+}
+sys_net_wifi_entry WifiList::ToSTAEntry(const sys_net_wifi_entry* sta) {
+  if (!sta) {
+        throw std::runtime_error("Null STA entry provided");
+    }
+    sys_net_wifi_entry result = *sta;
+    if (result.radio_type_count > 0) {
+        std::unique_ptr<sys_net_radio_types[]> newRadioTypes(new sys_net_radio_types[result.radio_type_count]);
+        if (!newRadioTypes) {
+            throw std::runtime_error("Failed to allocate memory for radio types");
+        }
+        memcpy(newRadioTypes.get(), sta->radio_type, sizeof(sys_net_radio_types) * result.radio_type_count);
+        result.radio_type = newRadioTypes.release();
+    } else {
+        result.radio_type = nullptr;
+    }
+
+    ESP_LOGD(TAG_CRED_MANAGER, "ToSTAEntry: SSID: %s, PASS: %s", result.ssid, result.password);
+    return result;
+}
+sys_net_wifi_entry WifiList::ToSTAEntry(const wifi_sta_config_t* sta, sys_net_auth_types auth_type) {
+    return ToSTAEntry(sta, GetRadioTypes(nullptr), auth_type);
+}
+sys_net_wifi_entry WifiList::ToSTAEntry(
+    const wifi_sta_config_t* sta, const std::list<sys_net_radio_types>& radio_types, sys_net_auth_types auth_type) {
+    sys_net_wifi_entry item = sys_net_wifi_entry_init_default;
+    ESP_LOGD(TAG_CRED_MANAGER,"%s (sta_config)","toSTAEntry");
+    auto result = ToSTAEntry(sta, item, radio_types);
+    ESP_LOGV(TAG_CRED_MANAGER, "ToSTAEntry: SSID: %s, PASS: %s", result.ssid, result.password);
+    return result;
+}
+sys_net_wifi_entry& WifiList::ToSTAEntry(const wifi_ap_record_t* ap, sys_net_wifi_entry& item) {
+    if (ap) {
+        auto radioTypes = GetRadioTypes(ap);
+        item.radio_type_count=radioTypes.size();
+        item.radio_type = new sys_net_radio_types[item.radio_type_count];
+        int i = 0;
+        for (const auto& type : radioTypes) {
+            item.radio_type[i++] = type;
+        }
+        item.auth_type = GetAuthType(ap);
+        FormatBSSID(ap, item);
+        item.channel = ap->primary;
+        item.rssi = ap->rssi;
+        strncpy(item.ssid, GetSSID(ap).c_str(), sizeof(item.ssid));
+    }
+    return item;
+}
+sys_net_wifi_entry WifiList::ToSTAEntry(const wifi_ap_record_t* ap) {
+    sys_net_wifi_entry item = sys_net_wifi_entry_init_default;
+    return ToSTAEntry(ap, item);
+}
+sys_net_wifi_entry& WifiList::ToSTAEntry(
+    const wifi_sta_config_t* sta, sys_net_wifi_entry& item, const std::list<sys_net_radio_types>& radio_types, sys_net_auth_types auth_type) {
+    if (!sta) {
+        ESP_LOGE(TAG_CRED_MANAGER, "Invalid access point entry");
+        return item;
+    }
+    
+    std::string ssid = GetSSID(sta);         // Convert SSID to std::string
+    std::string password = GetPassword(sta); // Convert password to std::string
+
+    if (ssid.empty()) {
+        ESP_LOGE(TAG_CRED_MANAGER, "Invalid access point ssid");
+        return item;
+    }
+    memset(item.ssid, 0x00, sizeof(item.ssid));
+    memset(item.password, 0x00, sizeof(item.password));
+    strncpy(item.ssid, ssid.c_str(), sizeof(item.ssid));             // Copy SSID
+    strncpy(item.password, password.c_str(), sizeof(item.password)); // Copy password
+    if (LOG_LOCAL_LEVEL > ESP_LOG_DEBUG) {
+        WifiList::FormatBSSID(item.bssid, sizeof(item.bssid), sta->bssid); // Format BSSID
+    }
+    item.channel = sta->channel;
+
+    item.auth_type = auth_type;
+
+    // Handle the radio_type array
+    if (item.radio_type != nullptr) {
+        delete[] item.radio_type; // Free existing array if any
+        item.radio_type_count = 0;
+    }
+    item.radio_type_count = radio_types.size();
+    item.radio_type = new sys_net_radio_types[item.radio_type_count];
+    int i = 0;
+    for (const auto& type : radio_types) {
+        item.radio_type[i++] = type;
+    }
+
+    ESP_LOGV(TAG_CRED_MANAGER, "ToSTAEntry wifi : %s, password: %s", item.ssid, item.password);
+    return item;
+}
+bool WifiList::RemoveCredential(const wifi_sta_config_t* sta) { return RemoveCredential(GetSSID(sta)); }
+bool WifiList::RemoveCredential(const std::string& ssid) {
+    auto it = credentials_.find(ssid);
+    if (it != credentials_.end()) {
+        // Release any dynamically allocated fields in the structure
+        Release(&it->second);
+        // Erase the entry from the map
+        credentials_.erase(it);
+        return true;
+    }
+    return false;
+}
+
+void WifiList::Clear() {
+    if (Lock()) {
+        for (auto& e : credentials_) {
+            Release( &e.second);
+        }
+        credentials_.clear();
+        Unlock();
+    }
+}
+bool WifiList::ResetRSSI() {
+    if (!Lock()) {
+        ESP_LOGE(TAG_CRED_MANAGER, "Unable to lock structure %s", name_.c_str());
+        return false;
+    }
+    for (auto& e : credentials_) {
+        e.second.rssi = 0;
+    }
+    Unlock();
+    return true;
+}
+bool WifiList::ResetConnected() {
+    if (!Lock()) {
+        throw std::runtime_error("Lock failed");
+    }
+    for (auto& e : credentials_) {
+        e.second.connected = false;
+    }
+    Unlock();
+    return true;
+}
+sys_net_wifi_entry* WifiList::Get(const std::string& ssid) {
+    auto it = credentials_.find(ssid);
+    if (it != credentials_.end()) {
+        return &(it->second);
+    }
+    return nullptr;
+}
+const sys_net_wifi_entry* WifiList::GetConnected() {
+    if (!Lock()) {
+        ESP_LOGE(TAG_CRED_MANAGER, "Unable to lock structure %s", name_.c_str());
+        return nullptr;
+    }
+    for (auto& e : credentials_) {
+        if (e.second.connected) {
+            return &e.second;
+        }
+    }
+    Unlock();
+    return nullptr;
+}
+bool WifiList::SetConnected(const wifi_event_sta_connected_t* evt, bool connected) {
+    auto ssid = GetSSID(evt);
+    auto bssid = GetBSSID(evt);
+    auto existing = Get(ssid);
+    if (existing) {
+        if (bssid != existing->bssid || existing->connected != connected || existing->auth_type != GetAuthType(evt->authmode) ||
+            existing->channel != evt->channel) {
+            ResetConnected();
+            if (!Lock()) {
+                throw std::runtime_error("Lock failed");
+            }            
+            strncpy(existing->bssid, bssid.c_str(), sizeof(existing->bssid));
+            existing->connected = connected;
+            existing->auth_type = GetAuthType(evt->authmode);
+            existing->channel = evt->channel;
+            config_raise_changed(false);
+            Unlock();
+            return true;
+        }
+    } else {
+        ESP_LOGE(TAG_CRED_MANAGER, "Cannot set unknown ssid %s as connected", ssid.c_str());
+    }
+    return false;
+}
+
+
+
+sys_net_wifi_entry& WifiList::AddUpdate(const wifi_sta_config_t* sta, sys_net_auth_types auth_type) {
+    return AddUpdate(sta, GetRadioTypes(nullptr), auth_type);
+}
+sys_net_wifi_entry& WifiList::AddUpdate(
+    const wifi_sta_config_t* sta, const std::list<sys_net_radio_types>& radio_types, sys_net_auth_types auth_type) {
+    auto ssid = GetSSID(sta);
+    
+    if (!Exists(sta)) {
+        auto entry = ToSTAEntry(sta, radio_types, auth_type);
+        ESP_LOGD(TAG_CRED_MANAGER, "Added new entry %s to list %s", ssid.c_str(), name_.c_str());
+        if (!Lock()) {
+            throw std::runtime_error("Lock failed");
+        }
+        credentials_[ssid] = entry;
+        Unlock();
+    } else {
+        Update(sta);
+    }
+    ESP_LOGV(TAG_CRED_MANAGER, "AddUpdate: SSID: %s, PASS: %s", ssid.c_str(), credentials_[ssid].password);
+    return credentials_[ssid];
+}
+bool WifiList::UpdateFromClock() {
+    bool changed = false;
+    if (Lock()) {
+
+        for (auto iter = credentials_.begin(); iter != credentials_.end(); ++iter) {
+            bool entrychanged = false;
+            if (iter->second.has_last_seen) {
+                entrychanged |= OffsetTimeStamp(&iter->second.last_seen);
+            }
+            if (iter->second.has_last_try) {
+                entrychanged |= OffsetTimeStamp(&iter->second.last_try);
+            }
+            if (entrychanged) {
+                ESP_LOGD(TAG_CRED_MANAGER, "Updated from clock");
+                PrintWifiSTAEntry(iter->second);
+            }
+            changed |= entrychanged;
+        }
+
+        Unlock();
+    }
+    return changed;
+}
+void WifiList::PrintTimeStamp(const google_protobuf_Timestamp* timestamp) {
+    if (timestamp == NULL) {
+        printf("Timestamp is NULL\n");
+        return;
+    }
+
+    char buffer[80];
+
+    // Check for special case of time == 0
+    if (timestamp->seconds == 0) {
+        if (timestamp->nanos != 0) {
+            printf("nanos not empty!");
+        }
+        snprintf(buffer, sizeof(buffer), "%-26s", "N/A");
+    }
+    // Check for timestamps less than 1704143717 (2024-01-01)
+    else if (timestamp->seconds > 0 && timestamp->seconds < 1704143717) {
+        // Convert seconds to time_t for use with localtime
+        time_t rawtime = (time_t)timestamp->seconds;
+        struct tm* timeinfo = localtime(&rawtime);
+
+        strftime(buffer, sizeof(buffer), "%H:%M:%S", timeinfo);
+    } else {
+        // Convert seconds to time_t for use with localtime
+        time_t rawtime = (time_t)timestamp->seconds;
+        struct tm* timeinfo = localtime(&rawtime);
+
+        strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", timeinfo);
+    }
+
+    printf("%-26s", buffer);
+}
+bool WifiList::UpdateLastTry(const std::string ssid) {
+    if (!Lock()) {
+        throw std::runtime_error("Lock failed");
+    }
+    auto entry = Get(ssid);
+    ESP_RETURN_ON_FALSE(entry != nullptr, false, TAG_CRED_MANAGER, "Unknown ssid %s", ssid.c_str());
+    ESP_RETURN_ON_FALSE(entry->ssid != nullptr, false, TAG_CRED_MANAGER, "Invalid pointer!");
+    bool changed = UpdateLastTry(entry);
+    Unlock();
+    return changed;
+}
+bool WifiList::UpdateLastTry(sys_net_wifi_entry* entry) {
+    ESP_RETURN_ON_FALSE(entry != nullptr, false, TAG_CRED_MANAGER, "Invalid pointer!");
+    ESP_RETURN_ON_FALSE(entry->ssid != nullptr, false, TAG_CRED_MANAGER, "Invalid pointer!");
+    ESP_LOGV(TAG_CRED_MANAGER, "Updating last try for %s", entry->ssid);
+    return UpdateTimeStamp(&entry->last_try, entry->has_last_try);
+}
+bool WifiList::UpdateLastTry(const wifi_sta_config_t* sta) { return UpdateLastTry(GetSSID(sta)); }
+bool WifiList::UpdateLastTry(const wifi_ap_record_t* ap) { return UpdateLastTry(GetSSID(ap)); }
+bool WifiList::UpdateLastSeen(const wifi_sta_config_t* sta) { return UpdateLastSeen(GetSSID(sta)); }
+bool WifiList::UpdateLastSeen(const wifi_ap_record_t* ap) { return UpdateLastSeen(GetSSID(ap)); }
+bool WifiList::UpdateLastSeen(const std::string ssid) {
+    if (!Lock()) {
+        throw std::runtime_error("Lock failed");
+    }
+    auto entry = Get(ssid);
+    bool changed = false;
+    if (entry != nullptr) {
+        changed = UpdateLastSeen(entry);
+    }
+    Unlock();
+    return changed;
+}
+bool WifiList::UpdateLastSeen(sys_net_wifi_entry* entry) {
+    ESP_RETURN_ON_FALSE(entry != nullptr, false, TAG_CRED_MANAGER, "Invalid pointer!");
+    ESP_LOGV(TAG_CRED_MANAGER, "Updating last seen for %s", entry->ssid);
+    return UpdateTimeStamp(&entry->last_seen, entry->has_last_seen);
+}
+sys_net_wifi_entry& WifiList::AddUpdate(const wifi_ap_record_t* scan_rec) {
+    auto ssid = GetSSID(scan_rec);
+    if (!Exists(scan_rec)) {
+        ESP_LOGV(TAG_CRED_MANAGER, "Added new entry %s to list %s", ssid.c_str(), name_.c_str());
+        if (!Lock()) {
+            throw std::runtime_error("Lock failed");
+        }
+        credentials_[ssid] = ToSTAEntry(scan_rec);
+        Unlock();
+    } else {
+        Update(scan_rec);
+    }
+    return credentials_[ssid];
+}
+sys_net_wifi_entry& WifiList::AddUpdate(const char* ssid, const char* password) {
+    if (ssid == nullptr || password == nullptr) {
+        throw std::invalid_argument("SSID and password cannot be null");
+    }
+    // Ensure that the SSID and password are not too long
+    if (std::strlen(ssid) >= sizeof(sys_net_wifi_entry::ssid) || std::strlen(password) >= sizeof(sys_net_wifi_entry::password)) {
+        throw std::length_error("SSID or password is too long");
+    }
+    if (!Exists(ssid)) {
+        if (!Lock()) {
+            throw std::runtime_error("Lock failed");
+        }
+        sys_net_wifi_entry newEntry = sys_net_wifi_entry_init_default;
+        // Copy the SSID and password into the new entry, ensuring null termination
+        std::strncpy(newEntry.ssid, ssid, sizeof(newEntry.ssid) - 1);
+        newEntry.ssid[sizeof(newEntry.ssid) - 1] = '\0';
+        std::strncpy(newEntry.password, password, sizeof(newEntry.password) - 1);
+        newEntry.password[sizeof(newEntry.password) - 1] = '\0';
+        ESP_LOGV(TAG_CRED_MANAGER, "Added new entry %s to list %s", ssid, name_.c_str());
+        credentials_[ssid] = newEntry;
+        Unlock();
+    } else {
+        auto existing = Get(ssid);
+        if (strncmp(existing->password, password, sizeof(existing->password)) != 0) {
+            strncpy(existing->password, password, sizeof(existing->password));
+            existing->password[sizeof(existing->password) - 1] = '\0';
+        }
+    }
+    return credentials_[ssid];
+}
+sys_net_wifi_entry& WifiList::AddUpdate(const sys_net_wifi_entry* sta, const char* password) {
+    if (sta == nullptr) {
+        throw std::invalid_argument("Entry pointer cannot be null");
+    }
+    auto converted = ToSTAEntry(sta);
+    strncpy(converted.password, password, sizeof(converted.password));        
+    if (!Exists(sta->ssid)) {
+        ESP_LOGD(TAG_CRED_MANAGER, "Added new entry %s to list %s", sta->ssid, name_.c_str());
+        if (!Lock()) {
+            throw std::runtime_error("Lock failed");
+        }
+        credentials_[sta->ssid] = converted;
+        Unlock();
+    } else {
+        auto existing = Get(sta->ssid);
+        Update(*existing, converted);
+        // release the converted structure now
+        Release(&converted);
+    }
+    return credentials_[sta->ssid];
+}
+void WifiList::PrintString(const char* pData, size_t length, const char* format) {
+    std::string buffer;
+    for (size_t i = 0; i < length && pData[i]; i++) {
+        if (isprint((char)pData[i])) {
+            buffer += (char)pData[i]; // Print as a character
+        } else {
+            buffer += '?';
+        }
+    }
+    printf(format, buffer.c_str());
+}
+void WifiList::PrintWifiSTAEntryTitle() {
+    printf("-----------------------------------------------------------------------------------------------------------------------------------------"
+           "--------------------\n");
+    printf("CONN SSID                PASSWORD                 BSSID               RSSI  CHAN AUTH          RADIO    LAST TRY                   LAST "
+           "SEEN\n");
+    printf("-----------------------------------------------------------------------------------------------------------------------------------------"
+           "--------------------\n");
+}
+
+void WifiList::PrintWifiSTAEntry(const sys_net_wifi_entry& entry) {
+    google_protobuf_Timestamp gts = google_protobuf_Timestamp_init_default;
+    printf("%-5c", entry.connected ? 'X' : ' ');
+    printf("%-20s", entry.ssid);
+    PrintString(entry.password, sizeof(entry.password), "%-25s");
+    PrintString(entry.bssid, sizeof(entry.bssid), "%-20s");
+    printf("%-4ddB", entry.rssi);
+    printf("%3u  ", static_cast<unsigned>(entry.channel));
+    printf("%-14s", sys_net_auth_types_name(entry.auth_type));
+    printf("%-9s", formatRadioTypes(entry.radio_type, entry.radio_type_count).c_str());
+    if (entry.has_last_try) {
+        PrintTimeStamp(&entry.last_try);
+    } else {
+        PrintTimeStamp(&gts);
+    }
+    if (entry.has_last_seen) {
+        PrintTimeStamp(&entry.last_seen);
+    } else {
+        PrintTimeStamp(&gts);
+    }
+    printf("\n");
+}
+sys_net_wifi_entry* WifiList::GetIndex(size_t index) {
+    if (index >= credentials_.size()) {
+        return nullptr;
+    }
+    auto it = credentials_.begin();
+    std::advance(it, index);
+    return &(it->second);
+}
+sys_net_wifi_entry& WifiList::GetStrongestSTA() {
+    if (credentials_.empty()) {
+        throw std::runtime_error("No credentials available");
+    }
+
+    auto strongestIter = credentials_.begin();
+    for (auto iter = credentials_.begin(); iter != credentials_.end(); ++iter) {
+        if (iter->second.rssi > strongestIter->second.rssi) {
+            strongestIter = iter;
+        }
+    }
+
+    return strongestIter->second;
+}
+
+std::list<sys_net_radio_types> WifiList::GetRadioTypes(const wifi_ap_record_t* sta) {
+    std::list<sys_net_radio_types> result;
+    if (sta == nullptr) {
+        result.push_back(sys_net_radio_types_UNKNOWN);
+    } else {
+
+        // Check each bit field and return the corresponding enum value
+        if (sta->phy_11b) {
+            result.push_back(sys_net_radio_types_PHY_11B);
+        }
+        if (sta->phy_11g) {
+            result.push_back(sys_net_radio_types_PHY_11G);
+        }
+        if (sta->phy_11n) {
+            result.push_back(sys_net_radio_types_PHY_11N);
+        }
+        if (sta->phy_lr) {
+            result.push_back(sys_net_radio_types_LR);
+        }
+        if (sta->wps) {
+            result.push_back(sys_net_radio_types_WPS);
+        }
+        if (sta->ftm_responder) {
+            result.push_back(sys_net_radio_types_FTM_RESPONDER);
+        }
+        if (sta->ftm_initiator) {
+            result.push_back(sys_net_radio_types_FTM_INITIATOR);
+        }
+    }
+    return result;
+}
+
+wifi_auth_mode_t WifiList::GetESPAuthMode(sys_net_auth_types auth_type) {
+    switch (auth_type) {
+    case sys_net_auth_types_OPEN:
+        return WIFI_AUTH_OPEN;
+    case sys_net_auth_types_WEP:
+        return WIFI_AUTH_WEP;
+    case sys_net_auth_types_WPA_PSK:
+        return WIFI_AUTH_WPA_PSK;
+    case sys_net_auth_types_WPA2_PSK:
+        return WIFI_AUTH_WPA2_PSK;
+    case sys_net_auth_types_WPA_WPA2_PSK:
+        return WIFI_AUTH_WPA_WPA2_PSK;
+    case sys_net_auth_types_WPA2_ENTERPRISE:
+        return WIFI_AUTH_WPA2_ENTERPRISE;
+    case sys_net_auth_types_WPA3_PSK:
+        return WIFI_AUTH_WPA3_PSK;
+    case sys_net_auth_types_WPA2_WPA3_PSK:
+        return WIFI_AUTH_WPA2_WPA3_PSK;
+    case sys_net_auth_types_WAPI_PSK:
+        return WIFI_AUTH_WAPI_PSK;
+    default:
+        return WIFI_AUTH_OPEN; // Default case
+    }
+}
+sys_net_auth_types WifiList::GetAuthType(const wifi_ap_record_t* ap) {
+    return ap ? GetAuthType(ap->authmode) : sys_net_auth_types_AUTH_UNKNOWN;
+}
+sys_net_auth_types WifiList::GetAuthType(const wifi_auth_mode_t mode) {
+
+    switch (mode) {
+    case WIFI_AUTH_OPEN:
+        return sys_net_auth_types_OPEN;
+    case WIFI_AUTH_WEP:
+        return sys_net_auth_types_WEP;
+    case WIFI_AUTH_WPA_PSK:
+        return sys_net_auth_types_WPA_PSK;
+    case WIFI_AUTH_WPA2_PSK:
+        return sys_net_auth_types_WPA2_PSK;
+    case WIFI_AUTH_WPA_WPA2_PSK:
+        return sys_net_auth_types_WPA_WPA2_PSK;
+    case WIFI_AUTH_WPA2_ENTERPRISE:
+        return sys_net_auth_types_WPA2_ENTERPRISE;
+    case WIFI_AUTH_WPA3_PSK:
+        return sys_net_auth_types_WPA3_PSK;
+    case WIFI_AUTH_WPA2_WPA3_PSK:
+        return sys_net_auth_types_WPA2_WPA3_PSK;
+    case WIFI_AUTH_WAPI_PSK:
+        return sys_net_auth_types_WAPI_PSK;
+    case WIFI_AUTH_MAX:
+        return sys_net_auth_types_OPEN;
+    }
+    return sys_net_auth_types_AUTH_UNKNOWN;
+}

+ 185 - 0
components/platform_config/WifiList.h

@@ -0,0 +1,185 @@
+/*
+ *
+ *      Sebastien L. 2023, sle118@hotmail.com
+ *      Philippe G. 2023, philippe_44@outlook.com
+ *
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
+ *
+ *  License Overview:
+ *  ----------------
+ *  The MIT License is a permissive open source license. As a user of this software, you are free to:
+ *  - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
+ *  - Use the software for private, commercial, or any other purposes.
+ *
+ *  Conditions:
+ *  - You must include the above copyright notice and this permission notice in all
+ *    copies or substantial portions of the Software.
+ *
+ *  The MIT License offers a high degree of freedom and is well-suited for both open source and
+ *  commercial applications. It places minimal restrictions on how the software can be used,
+ *  modified, and redistributed. For more details on the MIT License, please refer to the link above.
+ */
+#pragma once
+#include "Network.pb.h"
+#include "Status.pb.h"
+#include "esp_log.h"
+#include "esp_wifi.h"
+#include "PBW.h"
+#include "pb_decode.h"
+#include "pb_encode.h"
+#ifdef __cplusplus
+#include "Locking.h"
+#include <cstring>
+#include <list>
+#include <map>
+#include <string>
+
+class WifiList : public System::Locking {
+  public:
+    WifiList(std::string name) : System::Locking(name), name_(name) {}
+    ~WifiList(){
+        Clear();
+    }
+    static std::string toString(const uint8_t* data, size_t max_length) {
+        // Find the actual length of the string up to max_length
+        size_t length = strnlen(reinterpret_cast<const char*>(data), max_length);
+        // Construct a std::string using the data and its length
+        auto p = std::string(reinterpret_cast<const char*>(data), length);
+        return p;
+    }
+    static void Release(sys_net_wifi_entry& entry){
+        Release(&entry);
+    }
+    static void Release(sys_net_wifi_entry* entry){
+        pb_release(&sys_net_wifi_entry_msg,entry);
+    }
+    static std::list<sys_net_radio_types> GetRadioTypes(const wifi_ap_record_t* sta);
+    static wifi_auth_mode_t GetESPAuthMode(sys_net_auth_types auth_type);
+    static sys_net_auth_types GetAuthType(const wifi_ap_record_t* ap);
+    static sys_net_auth_types GetAuthType(const wifi_auth_mode_t mode);
+    static bool areRadioTypesDifferent(const sys_net_radio_types* types1, pb_size_t count1, const sys_net_radio_types* types2, pb_size_t count2) {
+        if (count1 != count2) {
+            return true;
+        }
+
+        for (pb_size_t i = 0; i < count1; ++i) {
+            if (types1[i] != types2[i]) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+    static std::string GetPassword(const wifi_sta_config_t* config) {
+        auto p = toString(config->password, sizeof(config->password));
+        return p;
+    }
+
+    static std::string GetSSID(const wifi_event_sta_connected_t* evt) { return toString(evt->ssid, sizeof(evt->ssid)); }
+    static std::string GetSSID(const wifi_sta_config_t* config) { return toString(config->ssid, sizeof(config->ssid)); }
+    static std::string GetSSID(const wifi_ap_record_t* ap) { return toString(ap->ssid, sizeof(ap->ssid)); }
+    static void FormatBSSID(char* buffer, size_t len, const uint8_t* bssid) {
+        memset(buffer,0x00,len);
+        snprintf(buffer, len, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
+    }
+    static void FormatBSSID(const wifi_ap_record_t* ap, sys_net_wifi_entry& entry) {
+        memset(entry.bssid,0x00,sizeof(entry.bssid));
+        if (!ap) return;
+        FormatBSSID(entry.bssid, sizeof(entry.bssid), ap->bssid);
+    }
+    static std::string GetBSSID(const wifi_event_sta_connected_t* evt);
+    static bool isEmpty(const char* str, size_t len);
+    static void PrintString(const char* pData, size_t length, const char* format);
+    static void PrintWifiSTAEntryTitle();
+
+    sys_net_wifi_entry& GetStrongestSTA();
+    bool RemoveCredential(const std::string& ssid);
+    bool RemoveCredential(const wifi_sta_config_t* sta);
+    void Clear();
+
+    static sys_net_wifi_entry ToSTAEntry(const sys_net_wifi_entry* sta);
+    static sys_net_wifi_entry ToSTAEntry(const wifi_sta_config_t* sta, const std::list<sys_net_radio_types>& radio_types,
+        sys_net_auth_types auth_type = sys_net_auth_types_AUTH_UNKNOWN);
+    static sys_net_wifi_entry ToSTAEntry(const wifi_sta_config_t* sta, sys_net_auth_types auth_type = sys_net_auth_types_AUTH_UNKNOWN);
+    static sys_net_wifi_entry& ToSTAEntry(const wifi_ap_record_t* ap, sys_net_wifi_entry& item);
+    static sys_net_wifi_entry ToSTAEntry(const wifi_ap_record_t* ap);
+    static sys_net_wifi_entry& ToSTAEntry(const wifi_sta_config_t* sta, sys_net_wifi_entry& item, const std::list<sys_net_radio_types>& radio_types,
+        sys_net_auth_types auth_type = sys_net_auth_types_AUTH_UNKNOWN);
+
+    static void PrintTimeStamp(const google_protobuf_Timestamp* timestamp);
+    static void PrintWifiSTAEntry(const sys_net_wifi_entry& entry);
+    static std::string formatRadioTypes(const sys_net_radio_types* radioTypes, pb_size_t count);
+
+    static bool OffsetTimeStamp(google_protobuf_Timestamp * ts);
+    static bool UpdateTimeStamp(google_protobuf_Timestamp* ts, bool& has_flag_val);
+
+    bool ResetRSSI();
+    bool ResetConnected();
+    const sys_net_wifi_entry* GetConnected();
+
+    bool SetConnected(const wifi_event_sta_connected_t* evt, bool connected = true);
+    size_t GetCount() const { return credentials_.size(); }
+    sys_net_wifi_entry* GetIndex(size_t index);
+
+    bool Exists(const std::string& ssid) { return Get(ssid) != nullptr; }
+    bool Exists(const char* ssid) { return Get(ssid) != nullptr; }
+    bool Exists(const wifi_ap_record_t* ap) { return ap != nullptr && Get(GetSSID(ap)) != nullptr; }
+    bool Exists(const wifi_sta_config_t* sta) { return Exists(GetSSID(sta)); }
+
+    bool Update(const wifi_sta_config_t* sta, bool connected = false);
+    bool Update(const wifi_ap_record_t* ap, bool connected = false);
+    static bool Update(sys_net_wifi_entry& existingEntry, sys_net_wifi_entry& compared);
+
+    sys_net_wifi_entry* Get(const wifi_sta_config_t* sta) { return Get(GetSSID(sta)); }
+    sys_net_wifi_entry* Get(const wifi_ap_record_t* ap) { return Get(GetSSID(ap)); }
+    sys_net_wifi_entry* Get(const char* ssid) { return Get(std::string(ssid)); }
+    sys_net_wifi_entry* Get(const std::string& ssid);
+
+    bool UpdateFromClock();
+    bool UpdateLastTry(const wifi_sta_config_t* sta);
+    bool UpdateLastTry(const wifi_ap_record_t* ap);
+    bool UpdateLastTry(const std::string ssid);
+    bool UpdateLastTry(sys_net_wifi_entry* entry);
+    bool UpdateLastSeen(const wifi_sta_config_t* sta);
+    bool UpdateLastSeen(const wifi_ap_record_t* ap);
+    bool UpdateLastSeen(const std::string ssid);
+    bool UpdateLastSeen(sys_net_wifi_entry* entry);
+    sys_net_wifi_entry& AddUpdate(const wifi_sta_config_t* sta, sys_net_auth_types auth_type = sys_net_auth_types_AUTH_UNKNOWN);
+    sys_net_wifi_entry& AddUpdate(const wifi_sta_config_t* sta, const std::list<sys_net_radio_types>& radio_types,
+        sys_net_auth_types auth_type = sys_net_auth_types_AUTH_UNKNOWN);
+    sys_net_wifi_entry& AddUpdate(const wifi_ap_record_t* scan_rec);
+    sys_net_wifi_entry& AddUpdate(const char* ssid = "", const char* password = "");
+    sys_net_wifi_entry& AddUpdate(const sys_net_wifi_entry* existing, const char* password = "");
+    // this one below is used by pb_decode
+    void AddUpdate(const sys_net_wifi_entry& entry) {
+        if (!Lock()) {
+            throw std::runtime_error("Lock failed");
+        }
+        credentials_[entry.ssid] = entry;
+        Unlock();
+    }
+    using Iterator = std::map<std::string, sys_net_wifi_entry>::iterator;
+    using ConstIterator = std::map<std::string, sys_net_wifi_entry>::const_iterator;
+    Iterator begin() { return credentials_.begin(); }
+    ConstIterator begin() const { return credentials_.begin(); }
+    Iterator end() { return credentials_.end(); }
+    ConstIterator end() const { return credentials_.end(); }
+
+  private:
+    static std::string FormatTimestamp(const google_protobuf_Timestamp& timestamp) {
+        // Format the timestamp as needed
+        // This is a placeholder implementation.
+        return std::to_string(timestamp.seconds) + "s";
+    }
+    std::map<std::string, sys_net_wifi_entry> credentials_;
+    std::string name_; // Name of the WifiCredentialsManager
+};
+
+extern "C" {
+#endif
+typedef struct WifiList WifiList;
+
+#ifdef __cplusplus
+}
+#endif

+ 20 - 0
components/platform_config/test/CMakeLists.txt

@@ -0,0 +1,20 @@
+idf_component_register(SRC_DIRS .
+                      INCLUDE_DIRS "."
+                      REQUIRES  tools platform_config unity )
+# target_compile_options(__idf_platform_config PRIVATE --coverage)
+target_include_directories(${COMPONENT_LIB}  PUBLIC SYSTEM ${CMAKE_SOURCE_DIR}/test_main)
+
+message(STATUS "** PLATFORM PROTOBUF")
+include(../../../tools/protoc_utils/protobuf_utils.cmake)
+configure_env()
+file(GLOB  PROTO_FILES *.proto)
+set(NANOPB_OPTIONS "-I${CMAKE_CURRENT_SOURCE_DIR}" "-I${PROJECT_ROOT_DIR}/protobuf/proto")
+nanopb_generate_cpp(PROTO_SRCS PROTO_HDRS RELPATH ${CMAKE_CURRENT_SOURCE_DIR} ${PROTO_FILES})
+
+
+# Create a custom target to generate the proto files
+set_source_files_properties(${PROTO_SRCS} ${PROTO_HDRS} PROPERTIES GENERATED TRUE)
+add_custom_target(generate_test_proto ALL DEPENDS ${PROTO_SRCS} ${PROTO_HDRS})
+target_sources(${COMPONENT_LIB} PRIVATE ${PROTO_SRCS})
+add_dependencies(${COMPONENT_LIB} generate_test_proto) 
+target_include_directories(${COMPONENT_LIB} PUBLIC "include" ${CMAKE_CURRENT_BINARY_DIR} ${NANOPB_INCLUDE_DIRS} ${EXTRA_INCLUDES})

+ 29 - 0
components/platform_config/test/DAC_test_extra.proto

@@ -0,0 +1,29 @@
+syntax = "proto3";
+
+package sys.dac.extra;
+import "DAC.proto";
+import "GPIO.proto";
+import "I2CBus.proto";
+import "DacControlSet.proto";
+import "customoptions.proto";
+import "nanopb.proto";
+option (nanopb_fileopt).enum_to_string = true;
+
+message config {
+  option (nanopb_msgopt).packed_struct  = true;
+  option (nanopb_msgopt).msgid  = 80008;
+  int32 bck = 1 [(cust_field).v_int32=-1];
+  int32 ws = 2 [(cust_field).v_int32=-1];
+  int32 dout = 3 [(cust_field).v_int32=-1];
+  MCK mck = 4;
+  gpio.pinmute = 5 [(cust_field).v_msg='{"pin":-1,"level":"LOW"}'];
+  Models model = 6;
+  I2CBus i2c = 7;
+  dac.control.Set daccontrolset = 8;
+  bool jack_mutes_amp = 9;
+  uint32 addr = 10;
+  int32 din = 11 [(cust_field).v_int32=-1];
+  int32 dummy1 = 20;
+  int64 dummy2 = 21;  
+  gpio.pindummy3 = 22;
+}

+ 30 - 0
components/platform_config/test/DAC_test_extra2.proto

@@ -0,0 +1,30 @@
+syntax = "proto3";
+
+package sys.dac.extra2;
+import "DAC.proto";
+import "GPIO.proto";
+import "I2CBus.proto";
+import "DacControlSet.proto";
+import "customoptions.proto";
+import "nanopb.proto";
+option (nanopb_fileopt).enum_to_string = true;
+
+message config {
+  option (nanopb_msgopt).packed_struct  = true;
+  option (nanopb_msgopt).msgid  = 90008;
+  int32 bck = 1 [(cust_field).v_int32=-1];
+  int32 ws = 2 [(cust_field).v_int32=-1];
+  int32 dout = 3 [(cust_field).v_int32=-1];
+  MCK mck = 4;
+  gpio.config mute = 5 [(cust_field).v_msg='{"pin":-1,"level":"LOW"}'];
+  Models model = 6;
+  I2CBus i2c = 7;
+  dac.control.Set daccontrolset = 8;
+  bool jack_mutes_amp = 9;
+  uint32 addr = 10;
+  int32 din = 11 [(cust_field).v_int32=-1];
+  int32 dummy1 = 20;
+  int64 dummy2 = 21;  
+  gpio.config dummy3 = 22;
+  gpio.config dummy4 = 23;
+}

+ 956 - 0
components/platform_config/test/test_platform_config.cpp

@@ -0,0 +1,956 @@
+
+#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
+#include "Config.h"
+#include "DAC.pb.h"
+#include "DAC_test_extra.pb.h"
+#include "DAC_test_extra2.pb.h"
+#include "Locking.h"
+#include "PBW.h"
+#include "State.pb.h"
+#include "WifiList.h"
+#include "configuration.pb.h"
+#include "esp_log.h"
+#include "test_common_init.h"
+#include "tools.h"
+#include "unity.h"
+
+using namespace System;
+
+// Helper macro to stringify the expanded value of a macro
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+
+// Use the helper macro to stringify LOG_LOCAL_LEVEL
+#pragma message("The current log local level value is " TOSTRING(LOG_LOCAL_LEVEL))
+#define AUTH_MODE_INDEX(i) ((start_auth_mode + i) % WIFI_AUTH_MAX == 0 ? (wifi_auth_mode_t)1 : (wifi_auth_mode_t)(start_auth_mode + i))
+static const char* config_file_name = "settings.bin";
+
+static const uint8_t bssid[6] = {0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56};
+uint8_t fill_last = 0x56;
+uint8_t start_rssi = 70;
+uint8_t start_channel = 2;
+auto start_auth_mode = WIFI_AUTH_WEP;
+uint8_t fill_bssid[6] = {0xAB, 0xCD, 0xEF, 0x12, 0x34, fill_last};
+static const char* char_bssid = "AB:CD:EF:12:34:56";
+static const char* TAG = "test_platform_config";
+const char* password = "TestPassword";
+bool HasMemoryUsageIncreased(int round) {
+    static const size_t thresholdInternal = 500; // Example threshold for internal memory
+    static const size_t thresholdSPIRAM = 1000;  // Example threshold for SPI RAM
+    size_t postTestFreeInternal = 0;
+    size_t postTestFreeSPIRAM = 0;
+    static size_t initialFreeInternal = 0;
+    static size_t initialFreeSPIRAM = 0;
+    auto minFreeInternal = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
+    auto minFreeSPIRAM = heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM);
+    if (round > 0) {
+        postTestFreeInternal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
+        postTestFreeSPIRAM = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
+        minFreeInternal = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
+        minFreeSPIRAM = heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM);
+
+        printf("Memory usage summary (after round %d): "
+               "Internal: %zu->%zu bytes. Min free: %zu. Increase: %d bytes. "
+               "SPIRAM: %zu->%zu bytes. Min free: %zu. Increase: %d bytes\n",
+            round, initialFreeInternal, postTestFreeInternal, minFreeInternal, initialFreeInternal - postTestFreeInternal, initialFreeSPIRAM,
+            postTestFreeSPIRAM, minFreeSPIRAM, initialFreeSPIRAM - postTestFreeSPIRAM);
+        int32_t diffInternal = initialFreeInternal > postTestFreeInternal ? initialFreeInternal - postTestFreeInternal : 0;
+        int32_t diffSPIRAM = initialFreeSPIRAM > postTestFreeSPIRAM ? initialFreeSPIRAM - postTestFreeSPIRAM : 0;
+        if (diffSPIRAM > 0 || diffInternal > 0) {
+            ESP_LOGW(TAG, "Internal increase: %d, SPIRAM: %d", diffInternal, diffSPIRAM);
+            return true;
+        }
+    } else {
+        initialFreeInternal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
+        initialFreeSPIRAM = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
+        printf("Memory usage at start: "
+               "Internal: %zu bytes. Min free: %zu. "
+               "SPIRAM: %zu bytes. Min free: %zu.\n",
+            initialFreeInternal, minFreeInternal, initialFreeSPIRAM, minFreeSPIRAM);
+    }
+    return false;
+}
+void advanceTime(int seconds) {
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    tv.tv_sec += seconds;
+    tv.tv_usec += seconds * 100;
+    settimeofday((const timeval*)&tv, 0);
+}
+wifi_event_sta_connected_t getMockConnectedEvent(int i = 0) {
+    wifi_event_sta_connected_t mock = {};
+    mock.authmode = WIFI_AUTH_WPA3_PSK;
+    memset(mock.ssid, 0x00, sizeof(mock.ssid));
+    memset(mock.bssid, 0x00, sizeof(mock.bssid));
+    snprintf((char*)mock.ssid, sizeof(mock.ssid), "SSID_%d", i);
+    fill_bssid[5] = fill_last + i;
+    memcpy(mock.bssid, fill_bssid, sizeof(mock.bssid));
+    mock.channel = 6 + i;
+    mock.ssid_len = strlen((char*)mock.ssid);
+    return mock;
+}
+wifi_ap_record_t getMockAPRec(int i = 0) {
+    wifi_ap_record_t mock = {};
+    mock.primary = start_channel + i; // Set some channel
+    mock.rssi = start_rssi + i;       // Set some RSSI value
+    memset(mock.ssid, 0x00, sizeof(mock.ssid));
+    memset(mock.bssid, 0x00, sizeof(mock.bssid));
+    snprintf((char*)mock.ssid, sizeof(mock.ssid), "SSID_%d", i);
+    fill_bssid[5] = fill_last + i;
+    memcpy(mock.bssid, fill_bssid, sizeof(mock.bssid));
+    mock.second = WIFI_SECOND_CHAN_ABOVE;
+    mock.authmode = AUTH_MODE_INDEX(i);                  /**< authmode of AP */
+    mock.pairwise_cipher = WIFI_CIPHER_TYPE_AES_GMAC128; /**< pairwise cipher of AP */
+    mock.group_cipher = WIFI_CIPHER_TYPE_TKIP_CCMP;      /**< group cipher of AP */
+    mock.ant = WIFI_ANT_ANT1;                            /**< antenna used to receive beacon from AP */
+    mock.phy_11b = 1;                                    /**< bit: 0 flag to identify if 11b mode is enabled or not */
+    mock.phy_11g = 0;                                    /**< bit: 1 flag to identify if 11g mode is enabled or not */
+    mock.phy_11n = 1;                                    /**< bit: 2 flag to identify if 11n mode is enabled or not */
+    mock.phy_lr = 0;                                     /**< bit: 3 flag to identify if low rate is enabled or not */
+    mock.wps = 1;                                        /**< bit: 4 flag to identify if WPS is supported or not */
+    mock.ftm_responder = 0;                              /**< bit: 5 flag to identify if FTM is supported in responder mode */
+    mock.ftm_initiator = 1;                              /**< bit: 6 flag to identify if FTM is supported in initiator mode */
+    return mock;
+}
+wifi_sta_config_t getMockSTA(int i = 0) {
+    wifi_sta_config_t mock = {};
+    memset(mock.ssid, 0x00, sizeof(mock.ssid));
+    memset(mock.bssid, 0x00, sizeof(mock.bssid));
+    snprintf((char*)mock.password, sizeof(mock.password), "Password_%d", i);
+    snprintf((char*)mock.ssid, sizeof(mock.ssid), "SSID_%d", i);
+    fill_bssid[5] = fill_last + i;
+    memcpy(mock.bssid, fill_bssid, sizeof(mock.bssid));
+    mock.channel = start_channel + i;
+    mock.failure_retry_cnt = 2 + i;
+    mock.listen_interval = 4 + i;
+    mock.mbo_enabled = 1;
+    mock.scan_method = WIFI_ALL_CHANNEL_SCAN;
+    return mock;
+}
+sys_net_wifi_entry getMockEntry(int i = 0) {
+    sys_net_wifi_entry mock = sys_net_wifi_entry_init_default;
+    mock.auth_type = WifiList::GetAuthType(AUTH_MODE_INDEX(i));
+    snprintf((char*)mock.password, sizeof(mock.password), "Password_%d", i);
+    snprintf((char*)mock.ssid, sizeof(mock.ssid), "SSID_%d", i);
+    fill_bssid[5] = fill_last + i;
+    WifiList::FormatBSSID(mock.bssid, sizeof(mock.bssid), fill_bssid);
+    mock.channel = start_channel + i;
+    mock.connected = false;
+    mock.has_last_seen = false;
+    mock.has_last_try = false;
+    mock.radio_type_count = 3;
+    mock.radio_type = new sys_net_radio_types[mock.radio_type_count];
+    mock.radio_type[0] = sys_net_radio_types_PHY_11B;
+    mock.radio_type[1] = sys_net_radio_types_PHY_11G;
+    mock.radio_type[2] = sys_net_radio_types_PHY_11N;
+    mock.rssi = start_rssi + i;
+    return mock;
+}
+
+void FillSSIDs(WifiList& manager, int count, int start=1) {
+    for (int i = start; i <= count; i++) {
+        auto mock = getMockSTA(i);
+        auto& entry = manager.AddUpdate(&mock);
+        entry.rssi = 70 + i;
+        entry.connected = true;
+    }
+}
+void FillSSIDFromAPRec(WifiList& manager, int count, int start=1) {
+    for (int i = start; i <= count; i++) {
+        auto mock = getMockAPRec(i);
+        auto& entry = manager.AddUpdate(&mock);
+        entry.rssi = 70 + i;
+        entry.connected = true;
+    }
+}
+void FillSSIDFromMockEntry(WifiList& manager, int count, int start=1) {
+    for (int i = start; i <= count; i++) {
+        auto mock = getMockEntry(i);
+        auto& entry = manager.AddUpdate(&mock);
+        entry.rssi = 70 + i;
+        entry.connected = true;
+        WifiList::Release(mock);
+    }
+}
+void eraseConfigFile() {
+    PB<sys_config> wrapper(std::string("config"), &sys_config_msg, sizeof(sys_config_msg), false);
+    erase_path(wrapper.GetFileName().c_str(), false);
+}
+
+TEST_CASE("Test empty target settings empty", "[platform_config]") {
+    PB<sys_config> wrapper(std::string("Config"), &sys_config_msg, sizeof(sys_config_msg), false);
+    sys_config* conf = wrapper.get();
+    assert(conf != nullptr);
+    conf->target = strdup_psram("");
+#ifdef CONFIG_FW_PLATFORM_NAME
+    system_set_string(&sys_config_msg, sys_config_target_tag, conf, CONFIG_FW_PLATFORM_NAME);
+    TEST_ASSERT_TRUE(strcmp(conf->target, CONFIG_FW_PLATFORM_NAME) == 0);
+#endif
+}
+
+TEST_CASE("Test init from default config", "[platform_config]") {
+    struct stat fileInformation;
+    PB<sys_config> wrapper(std::string("config"), &sys_config_msg, sizeof(sys_config_msg), false);
+    sys_config* confprt = wrapper.get();
+    auto filename = wrapper.GetFileName();
+    erase_path(filename.c_str(), false);
+    TEST_ASSERT_FALSE(get_file_info(&fileInformation, config_file_name));
+    TEST_ASSERT_TRUE(strlen(STR_OR_BLANK(confprt->target)) == 0);
+    TEST_ASSERT_FALSE(confprt->has_dev);
+}
+const sys_config* GetTestConfig() {
+    static sys_config test_config;
+    memset(&test_config, 0x00, sizeof(test_config));
+
+    // Assuming test_config is an instance of sys_config or a similar structure
+    test_config.has_dev = true;
+    test_config.dev.has_spi = true;
+    test_config.dev.spi.mosi = 4;
+    test_config.dev.spi.clk = 5;
+    test_config.dev.spi.dc = 18;
+    test_config.dev.spi.host = sys_dev_common_hosts_Host1;
+
+    test_config.dev.has_dac = true;
+    test_config.dev.dac.bck = 25;
+    test_config.dev.dac.ws = 26;
+    test_config.dev.dac.dout = 33;
+    test_config.dev.dac.model = sys_dac_models_WM8978;
+
+    test_config.dev.has_display = true;
+    test_config.dev.display.has_common = true;
+    test_config.dev.display.common.width = 256;
+    test_config.dev.display.common.height = 64;
+    test_config.dev.display.common.HFlip = false;
+    test_config.dev.display.common.VFlip = false;
+    test_config.dev.display.common.rotate = false;
+    test_config.dev.display.common.driver = sys_display_drivers_SSD1322;
+    test_config.dev.display.common.reset = 21;
+    test_config.dev.display.which_dispType = sys_display_config_spi_tag;
+    test_config.dev.display.dispType.spi.cs = 19;
+    test_config.dev.display.dispType.spi.speed = 8000000;
+    test_config.dev.has_rotary = true;
+
+    test_config.dev.rotary.A = 23;
+    test_config.dev.rotary.B = 22;
+    test_config.dev.rotary.SW = 34;
+
+    test_config.dev.rotary.volume = true;
+    test_config.dev.rotary.longpress = true;
+    test_config.has_names = true;
+    strcpy(test_config.names.device, "test_name");
+    if (!test_config.target) {
+        test_config.target = strdup_psram("test_target");
+    }
+    return &test_config;
+}
+void check_sys_config_structure(sys_config* config) {
+    auto check = GetTestConfig();
+    // Test SPI configuration
+    TEST_ASSERT_EQUAL(check->dev.has_spi, config->dev.has_spi);
+    TEST_ASSERT_EQUAL(check->dev.spi.mosi, config->dev.spi.mosi);
+    TEST_ASSERT_EQUAL(check->dev.spi.clk, config->dev.spi.clk);
+    TEST_ASSERT_EQUAL(check->dev.spi.dc, config->dev.spi.dc);
+    TEST_ASSERT_EQUAL(check->dev.spi.host, config->dev.spi.host);
+    TEST_ASSERT_EQUAL(check->dev.has_dac, config->dev.has_dac);
+    TEST_ASSERT_EQUAL(check->dev.dac.bck, config->dev.dac.bck);
+    TEST_ASSERT_EQUAL(check->dev.dac.ws, config->dev.dac.ws);
+    TEST_ASSERT_EQUAL(check->dev.dac.dout, config->dev.dac.dout);
+    TEST_ASSERT_EQUAL(check->dev.dac.model, config->dev.dac.model);
+    TEST_ASSERT_EQUAL(check->dev.has_display, config->dev.has_display);
+    TEST_ASSERT_EQUAL(check->dev.display.common.width, config->dev.display.common.width);
+    TEST_ASSERT_EQUAL(check->dev.display.common.height, config->dev.display.common.height);
+    TEST_ASSERT_EQUAL(check->dev.display.common.HFlip, config->dev.display.common.HFlip);
+    TEST_ASSERT_EQUAL(check->dev.display.common.VFlip, config->dev.display.common.VFlip);
+    TEST_ASSERT_EQUAL(check->dev.display.common.rotate, config->dev.display.common.rotate);
+    TEST_ASSERT_EQUAL(check->dev.display.common.driver, config->dev.display.common.driver);
+    TEST_ASSERT_EQUAL(check->dev.display.common.reset, config->dev.display.common.reset);
+    TEST_ASSERT_EQUAL(check->dev.display.which_dispType, config->dev.display.which_dispType);
+    TEST_ASSERT_EQUAL(check->dev.display.dispType.spi.cs, config->dev.display.dispType.spi.cs);
+    TEST_ASSERT_EQUAL(check->dev.display.dispType.spi.speed, config->dev.display.dispType.spi.speed);
+    TEST_ASSERT_EQUAL(check->dev.has_rotary, config->dev.has_rotary);
+    TEST_ASSERT_EQUAL(check->dev.rotary.A, config->dev.rotary.A);
+    TEST_ASSERT_EQUAL(check->dev.rotary.B, config->dev.rotary.B);
+    TEST_ASSERT_EQUAL(check->dev.rotary.SW, config->dev.rotary.SW);
+    TEST_ASSERT_EQUAL(check->dev.rotary.volume, config->dev.rotary.volume);
+    TEST_ASSERT_EQUAL(check->dev.rotary.longpress, config->dev.rotary.longpress);
+    TEST_ASSERT_EQUAL_STRING(check->names.device, config->names.device);
+    TEST_ASSERT_EQUAL_STRING(check->target, config->target);
+}
+TEST_CASE("Test change platform", "[platform_config]") {
+    struct stat fileInformation;
+    eraseConfigFile();
+    std::stringstream test_target_file;
+    test_target_file << spiffs_base_path << "/targets/" << GetTestConfig()->target << "/config.bin";
+    PlatformConfig::ProtoWrapperHelper::CommitFile(test_target_file.str().c_str(), &sys_config_msg, GetTestConfig());
+
+    // first ensure that the target state file exists.
+    TEST_ASSERT_TRUE(get_file_info(&fileInformation, test_target_file.str().c_str()));
+    TEST_ASSERT_TRUE(fileInformation.st_size > 0);
+    platform = nullptr;
+
+    // here we must use the configurator object
+    // since we're testing some config_ functions
+    std::string expectedTarget = "ESP32";
+    TEST_ASSERT_NULL(platform);
+    config_load();
+    TEST_ASSERT_NOT_NULL(platform);
+    TEST_ASSERT_NOT_NULL(platform->target)
+    TEST_ASSERT_EQUAL_STRING(expectedTarget.c_str(), platform->target);
+    config_set_target_reset(GetTestConfig()->target);
+    check_sys_config_structure(platform);
+    TEST_ASSERT_EQUAL_STRING(platform->target, GetTestConfig()->target);
+    TEST_ASSERT_TRUE(erase_path(test_target_file.str().c_str(), false));
+    TEST_ASSERT_FALSE(get_file_info(&fileInformation, test_target_file.str().c_str()));
+}
+
+// TEST_CASE("Test load state", "[platform_config]") {
+//     eraseConfigFile();
+//     const char* urlvalue = "http://somerandomurl";
+//     config_load();
+//     system_set_string(&sys_state_data_msg, sys_state_data_ota_url_tag, sys_state, urlvalue);
+//     TEST_ASSERT_EQUAL_STRING(urlvalue, sys_state->ota_url);
+//     ESP_LOGI(TAG, "Raising state change");
+//     config_raise_state_changed();
+
+// // create an async timer lambda to trigger the commit after 1 second so
+// //we can test waitcommit
+// config_commit_config
+
+// TEST_CASE("Test Raise State Change", "[platform_config]") {
+//     // config_load();
+//     ESP_LOGI(TAG, "Raising state change");
+//     TEST_ASSERT_FALSE(configurator.HasStateChanges());
+//     TEST_ASSERT_FALSE(configurator.HasChanges());
+//     config_raise_state_changed();
+//     TEST_ASSERT_TRUE(configurator.HasStateChanges());
+//     TEST_ASSERT_FALSE(configurator.HasChanges());
+//     configurator.ResetStateModified();
+//     TEST_ASSERT_FALSE(configurator.HasStateChanges());
+//     TEST_ASSERT_FALSE(configurator.HasChanges());
+// }
+// TEST_CASE("Test Raise Change", "[platform_config]") {
+//     // config_load();
+//     ESP_LOGI(TAG, "Raising change");
+//     PlatformConfig::PB wrapper =
+//         PlatformConfig::PB("config", "", &sys_config_msg, Init_sys_config);
+//     TEST_ASSERT_FALSE(configurator.HasStateChanges());
+//     TEST_ASSERT_FALSE(configurator.HasChanges());
+//     config_raise_changed();
+//     TEST_ASSERT_TRUE(configurator.HasChanges());
+//     TEST_ASSERT_FALSE(configurator.HasStateChanges());
+//     configurator.ResetModified();
+//     TEST_ASSERT_FALSE(configurator.HasChanges());
+//     TEST_ASSERT_FALSE(configurator.HasStateChanges());
+// }
+
+TEST_CASE("Test Lock Unlock", "[platform_config]") {
+    auto lock = PlatformConfig::Locking("test");
+    TEST_ASSERT_FALSE(lock.IsLocked());
+    TEST_ASSERT_TRUE(lock.Lock());
+    TEST_ASSERT_TRUE(lock.Lock());
+    TEST_ASSERT_TRUE(lock.IsLocked());
+    lock.Unlock();
+    TEST_ASSERT_TRUE(lock.IsLocked());
+    lock.Unlock();
+    TEST_ASSERT_FALSE(lock.IsLocked());
+}
+
+TEST_CASE("Recovery not updating message definition", "[platform_config]") {
+    is_recovery_running = false;
+    auto name = std::string("extra");
+    struct stat struct_info_extra;
+    struct stat struct_info_reco;
+    // create instance with fresh definition
+    erase_path(PBHelper::GetDefFileName(name).c_str(), false);
+    PB<sys_dac_extra_config> wrapper_extra(name.c_str(), &sys_dac_extra_config_msg, sizeof(sys_dac_extra_config_msg));
+    TEST_ASSERT_TRUE(get_file_info(&struct_info_extra, wrapper_extra.GetDefFileName().c_str()));
+    TEST_ASSERT_TRUE(struct_info_extra.st_size > 0);
+    auto& extra = wrapper_extra.Root();
+    extra.dummy1 = 20;
+    extra.dummy2 = 30;
+    extra.has_dummy3 = true;
+    extra.dummy3.level = sys_gpio_lvl_HIGH;
+    extra.dummy3.pin = 22;
+    wrapper_extra.CommitChanges();
+
+    is_recovery_running = true;
+    PB<sys_dac_config> wrapper(name, &sys_dac_config_msg, sizeof(sys_dac_config_msg));
+    TEST_ASSERT_TRUE(get_file_info(&struct_info_reco, wrapper.GetDefFileName().c_str()));
+    TEST_ASSERT_TRUE(struct_info_reco.st_size == struct_info_extra.st_size);
+    TEST_ASSERT_EQUAL(wrapper_extra.GetDataSize(), wrapper.GetDataSize());
+    wrapper.LoadFile(true);
+
+    sys_dac_extra_config* config = reinterpret_cast<sys_dac_extra_config*>(wrapper.get());
+    TEST_ASSERT_EQUAL(config->dummy1, extra.dummy1);
+    TEST_ASSERT_EQUAL(config->dummy2, extra.dummy2);
+    TEST_ASSERT_EQUAL(config->has_dummy3, extra.has_dummy3);
+    TEST_ASSERT_EQUAL(config->dummy3.level, extra.dummy3.level);
+    TEST_ASSERT_EQUAL(config->dummy3.pin, extra.dummy3.pin);
+
+    config->bck = 55;
+    wrapper.CommitChanges();
+    PB<sys_dac_extra_config> check_structure(name.c_str(), &sys_dac_extra_config_msg, sizeof(sys_dac_extra_config_msg));
+    check_structure.LoadFile(true);
+    auto config_check = check_structure.get();
+    TEST_ASSERT_EQUAL(config->bck, check_structure.get()->bck);
+    TEST_ASSERT_EQUAL(config_check->dummy1, extra.dummy1);
+    TEST_ASSERT_EQUAL(config_check->dummy2, extra.dummy2);
+    TEST_ASSERT_EQUAL(config_check->has_dummy3, extra.has_dummy3);
+    TEST_ASSERT_EQUAL(config_check->dummy3.level, extra.dummy3.level);
+    TEST_ASSERT_EQUAL(config_check->dummy3.pin, extra.dummy3.pin);
+
+    // not simulate an update
+    is_recovery_running = false;
+    PB<sys_dac_extra2_config> extra2(name.c_str(), &sys_dac_extra2_config_msg, sizeof(sys_dac_extra2_config_msg));
+    extra2.LoadFile(true);
+    auto config_extra2 = extra2.get();
+    config_extra2->has_dummy4 = true;
+    config_extra2->dummy4.pin = 99;
+    extra2.CommitChanges();
+    is_recovery_running = true;
+    PB<sys_dac_config> wrapper2(name, &sys_dac_config_msg, sizeof(sys_dac_config_msg));
+
+    wrapper2.LoadFile(true);
+    wrapper2.Root().bck = 88;
+    wrapper2.CommitChanges();
+
+    is_recovery_running = false;
+    extra2.LoadFile(true);
+    TEST_ASSERT_EQUAL(config_extra2->bck, 88);
+    TEST_ASSERT_TRUE(config_extra2->has_dummy4);
+    TEST_ASSERT_EQUAL(config_extra2->dummy4.pin, 99);
+}
+TEST_CASE("String conversion from uint8_t array", "[WifiCredentialsManager]") {
+    // Prepare test data
+    const uint8_t testData[] = {'T', 'e', 's', 't', '\0', 'D', 'a', 't', 'a'};
+    const size_t testDataLength = 9; // Including '\0'
+
+    // Call the method
+    std::string result = WifiList::toString(testData, testDataLength);
+
+    // Assert expectations
+    TEST_ASSERT_EQUAL_STRING_LEN("Test", result.c_str(), result.length());
+}
+TEST_CASE("Get SSID from wifi_sta_config_t", "[WifiCredentialsManager]") {
+    // Prepare test data
+    wifi_sta_config_t testConfig = getMockSTA(1);
+    // Call the method
+    std::string result = WifiList::GetSSID(&testConfig);
+    TEST_ASSERT_EQUAL_STRING("SSID_1", result.c_str());
+}
+TEST_CASE("Get SSID from wifi_event_sta_connected_t", "[WifiCredentialsManager]") {
+    wifi_event_sta_connected_t testEvent = {};
+    const char* ssid = "EventSSID";
+    memcpy(testEvent.ssid, ssid, strlen(ssid) + 1); // Including '\0'
+
+    std::string result = WifiList::GetSSID(&testEvent);
+    TEST_ASSERT_EQUAL_STRING(ssid, result.c_str());
+}
+TEST_CASE("Get SSID from wifi_ap_record_t", "[WifiCredentialsManager]") {
+    wifi_ap_record_t testRecord = {};
+    const char* ssid = "RecordSSID";
+    memcpy(testRecord.ssid, ssid, strlen(ssid) + 1); // Including '\0'
+
+    std::string result = WifiList::GetSSID(&testRecord);
+    TEST_ASSERT_EQUAL_STRING(ssid, result.c_str());
+}
+TEST_CASE("Get Password from wifi_sta_config_t", "[WifiCredentialsManager]") {
+    wifi_sta_config_t testConfig = {};
+    memcpy(testConfig.password, password, strlen(password) + 1); // Including '\0'
+
+    std::string result = WifiList::GetPassword(&testConfig);
+    TEST_ASSERT_EQUAL_STRING(password, result.c_str());
+}
+TEST_CASE("Get BSSID from wifi_event_sta_connected_t", "[WifiCredentialsManager]") {
+    wifi_event_sta_connected_t testEvent = {};
+    memcpy(testEvent.bssid, bssid, sizeof(testEvent.bssid));
+    std::string result = WifiCredentialsManager::GetBSSID(&testEvent);
+    TEST_ASSERT_EQUAL_STRING(char_bssid, result.c_str());
+}
+TEST_CASE("Format BSSID from uint8_t array", "[WifiCredentialsManager]") {
+    char buffer[18] = {0};
+    WifiCredentialsManager::FormatBSSID(buffer, sizeof(buffer), bssid);
+    TEST_ASSERT_EQUAL_STRING(char_bssid, buffer);
+}
+
+TEST_CASE("Update timestamp", "[WifiCredentialsManager]") {
+    WifiCredentialsManager manager("test_manager");
+
+    // Test with a non-null timestamp and an uninitialized flag
+    google_protobuf_Timestamp ts = {0, 0};
+    bool has_flag = false;
+
+    bool result = manager.UpdateTimeStamp(&ts, has_flag);
+
+    TEST_ASSERT_TRUE(result);             // Check if the method returns true for change
+    TEST_ASSERT_TRUE(has_flag);           // Check if the flag is set to true
+    TEST_ASSERT_NOT_EQUAL(0, ts.seconds); // Assuming gettimeofday() gives a non-zero time
+    TEST_ASSERT_NOT_EQUAL(0, ts.nanos);
+
+    // Store the updated values for comparison
+    long prev_seconds = ts.seconds;
+    long prev_nanos = ts.nanos;
+    advanceTime(2);
+
+    // Call the method again with the same timestamp
+    result = manager.UpdateTimeStamp(&ts, has_flag);
+
+    // Since the timestamp should be updated, check if the new values are different
+    TEST_ASSERT_TRUE(result);
+    TEST_ASSERT_TRUE(has_flag);
+    TEST_ASSERT_NOT_EQUAL(prev_seconds, ts.seconds);
+    TEST_ASSERT_NOT_EQUAL(prev_nanos, ts.nanos);
+
+    // Test with a null timestamp
+    result = manager.UpdateTimeStamp(nullptr, has_flag);
+
+    // The method should return false, and the flag should remain unchanged
+    TEST_ASSERT_FALSE(result);
+    TEST_ASSERT_TRUE(has_flag); // Flag remains unchanged
+    advanceTime(-2);
+}
+
+TEST_CASE("Update wifi entry", "[WifiCredentialsManager]") {
+    WifiCredentialsManager manager("test_manager");
+
+    sys_net_wifi_entry existingEntry = getMockEntry();
+    sys_net_wifi_entry updatedEntry = getMockEntry();
+
+    // Modify updatedEntry with different values
+    strcpy(updatedEntry.password, "NewPassword");
+    updatedEntry.channel = 5;
+    updatedEntry.auth_type = sys_net_auth_types_WPA2_PSK;
+    delete[] updatedEntry.radio_type;
+    updatedEntry.radio_type_count = 1;
+    updatedEntry.radio_type = new sys_net_radio_types[updatedEntry.radio_type_count]; // Dynamic allocation for radio_type
+    updatedEntry.radio_type[0] = sys_net_radio_types_PHY_11N;
+
+    updatedEntry.rssi = 42;
+    updatedEntry.connected = true;
+
+    // Call the Update method
+    bool result = manager.Update(existingEntry, updatedEntry);
+
+    // Assert that the Update method returns true
+    TEST_ASSERT_TRUE(result);
+
+    // Assert that the existingEntry is updated correctly
+    TEST_ASSERT_EQUAL_STRING("NewPassword", existingEntry.password);
+    TEST_ASSERT_EQUAL(updatedEntry.channel, existingEntry.channel);
+    TEST_ASSERT_EQUAL(sys_net_auth_types_WPA2_PSK, existingEntry.auth_type);
+    TEST_ASSERT_EQUAL(sys_net_radio_types_PHY_11N, existingEntry.radio_type[0]);
+    TEST_ASSERT_EQUAL(updatedEntry.radio_type_count, existingEntry.radio_type_count);
+    TEST_ASSERT_EQUAL(updatedEntry.rssi, existingEntry.rssi);
+    TEST_ASSERT_TRUE(existingEntry.connected);
+
+    // Clean up dynamically allocated memory
+    delete[] updatedEntry.radio_type;
+    delete[] existingEntry.radio_type;
+}
+
+TEST_CASE("Update wifi entry from AP record", "[WifiCredentialsManager]") {
+    WifiCredentialsManager manager("test_manager");
+
+    // Create a mock wifi_ap_record_t
+    wifi_ap_record_t mockAP = {};
+    // Initialize mockAP with test data
+    // ...
+
+    // Add the initial entry
+    sys_net_wifi_entry initialEntry = manager.ToSTAEntry(&mockAP);
+    initialEntry.connected = false; // Initially not connected
+    manager.AddUpdate(initialEntry);
+
+    // Now call the update with the same AP but with 'connected' status
+    bool result = manager.Update(&mockAP, true);
+
+    // Assert that the update was successful
+    TEST_ASSERT_TRUE(result);
+
+    // Retrieve the entry and check if it's updated
+    auto updatedEntry = manager.Get(&mockAP);
+    TEST_ASSERT_NOT_NULL(updatedEntry);
+    TEST_ASSERT_TRUE(updatedEntry->connected); // Check if connected is now true
+    manager.Clear();
+}
+
+TEST_CASE("Format radio types", "[WifiCredentialsManager]") {
+    WifiCredentialsManager manager("test_manager");
+
+    // Create an array of radio types for testing
+    sys_net_radio_types radioTypes[] = {sys_net_radio_types_PHY_11B, sys_net_radio_types_PHY_11G, sys_net_radio_types_PHY_11N, sys_net_radio_types_LR,
+        sys_net_radio_types_WPS, sys_net_radio_types_FTM_RESPONDER, sys_net_radio_types_FTM_INITIATOR, sys_net_radio_types_UNKNOWN};
+    pb_size_t count = sizeof(radioTypes) / sizeof(radioTypes[0]);
+
+    // Call the formatRadioTypes method
+    std::string formattedString = manager.formatRadioTypes(radioTypes, count);
+
+    // Define the expected result
+    std::string expectedResult = "B,G,N,L,W,FR,FI,U";
+
+    // Assert that the formatted string is as expected
+    TEST_ASSERT_EQUAL_STRING(expectedResult.c_str(), formattedString.c_str());
+    manager.Clear();
+}
+
+TEST_CASE("Update wifi entry from STA config", "[WifiCredentialsManager]") {
+    WifiCredentialsManager manager("test_manager");
+
+    // Create a mock wifi_sta_config_t
+    wifi_sta_config_t mockSTA = getMockSTA();
+
+    // Create and add an initial entry
+    sys_net_wifi_entry initialEntry = manager.ToSTAEntry(&mockSTA);
+    initialEntry.connected = false; // Initially not connected
+    manager.AddUpdate(initialEntry);
+
+    // Modify the mockSTA for the update
+    mockSTA.channel = 6; // Change the channel
+
+    // Now call the update with the modified mockSTA and 'connected' status
+    bool result = manager.Update(&mockSTA, true);
+
+    // Assert that the update was successful
+    TEST_ASSERT_TRUE(result);
+
+    // Retrieve the updated entry and check if it's correctly updated
+    auto updatedEntry = manager.Get(&mockSTA);
+    TEST_ASSERT_NOT_NULL(updatedEntry);
+    TEST_ASSERT_EQUAL(mockSTA.channel, updatedEntry->channel);
+    TEST_ASSERT_TRUE(updatedEntry->connected); // Check if connected is now true
+    manager.Clear();
+}
+
+TEST_CASE("Convert AP record to STA entry", "[WifiCredentialsManager]") {
+    int testindex = 1;
+    WifiCredentialsManager manager("test_manager");
+
+    // Create a mock wifi_ap_record_t
+    wifi_ap_record_t mockAP = getMockAPRec(testindex);
+
+    sys_net_wifi_entry entry; // Create an entry to be populated
+
+    // Call ToSTAEntry
+    sys_net_wifi_entry& resultEntry = manager.ToSTAEntry(&mockAP, entry);
+
+    // Assert that the entry is populated correctly
+    TEST_ASSERT_EQUAL_STRING("SSID_1", resultEntry.ssid);
+    TEST_ASSERT_EQUAL(start_channel + testindex, resultEntry.channel);
+    TEST_ASSERT_EQUAL(start_rssi + testindex, resultEntry.rssi);
+    TEST_ASSERT_EQUAL(WifiList::GetAuthType(AUTH_MODE_INDEX(testindex)), resultEntry.auth_type);
+    TEST_ASSERT_EQUAL(4, resultEntry.radio_type_count);
+    std::string formattedString = manager.formatRadioTypes(resultEntry.radio_type, resultEntry.radio_type_count);
+    // Define the expected result
+    std::string expectedResult = "B,N,W,FI";
+    TEST_ASSERT_EQUAL_STRING(expectedResult.c_str(), formattedString.c_str());
+    char bssid_buffer[20] = {};
+    WifiList::FormatBSSID(bssid_buffer, sizeof(bssid_buffer), mockAP.bssid);
+    TEST_ASSERT_EQUAL_STRING((char*)bssid_buffer, resultEntry.bssid);
+
+    manager.Clear();
+}
+
+TEST_CASE("Remove entries from manager instance", "[WifiCredentialsManager]") {
+    WifiCredentialsManager manager("test_manager");
+    FillSSIDs(manager, 4);
+    TEST_ASSERT_EQUAL(4, manager.GetCount());
+    TEST_ASSERT_TRUE(manager.RemoveCredential("SSID_1"));
+    TEST_ASSERT_EQUAL(3, manager.GetCount());
+    TEST_ASSERT_FALSE(manager.RemoveCredential("SSID_1"));
+    TEST_ASSERT_EQUAL(3, manager.GetCount());
+    manager.Clear();
+    TEST_ASSERT_EQUAL(0, manager.GetCount());
+
+    FillSSIDs(manager, 4);
+    TEST_ASSERT_EQUAL(4, manager.GetCount());
+    TEST_ASSERT_TRUE(manager.RemoveCredential(std::string("SSID_1")));
+    TEST_ASSERT_FALSE(manager.RemoveCredential(std::string("SSID_1")));
+    TEST_ASSERT_TRUE(manager.RemoveCredential("SSID_2"));
+    TEST_ASSERT_FALSE(manager.RemoveCredential(std::string("SSID_2")));
+    TEST_ASSERT_EQUAL(2, manager.GetCount());
+    manager.Clear();
+    TEST_ASSERT_EQUAL(0, manager.GetCount());
+}
+TEST_CASE("Reset all entries", "[WifiCredentialsManager]") {
+    WifiCredentialsManager manager("test_manager");
+    FillSSIDs(manager, 4);
+    for (auto& e : manager) {
+        TEST_ASSERT_TRUE(e.second.rssi > 0);
+        TEST_ASSERT_TRUE(e.second.connected);
+    }
+    manager.ResetRSSI();
+    for (auto& e : manager) {
+        TEST_ASSERT_TRUE(e.second.rssi == 0);
+        TEST_ASSERT_TRUE(e.second.connected);
+    }
+    manager.Clear();
+    FillSSIDs(manager, 4);
+    manager.ResetConnected();
+    for (auto& e : manager) {
+        TEST_ASSERT_TRUE(e.second.rssi > 0);
+        TEST_ASSERT_FALSE(e.second.connected);
+    }
+}
+
+TEST_CASE("Getting/setting Connected", "[WifiCredentialsManager]") {
+    WifiCredentialsManager manager("test_manager");
+    auto conn_mock_4 = getMockConnectedEvent(4);
+    auto conn_mock_5 = getMockConnectedEvent(5);
+    auto entry_5 = getMockEntry(5);
+    char bssid_buffer[20] = {};
+    FillSSIDs(manager, 4);
+    for (auto& e : manager) {
+        if (strcmp(e.second.ssid, "SSID_3") != 0) {
+            e.second.connected = false;
+        }
+    }
+    auto entry = manager.GetConnected();
+    TEST_ASSERT_EQUAL_STRING("SSID_3", entry->ssid);
+    TEST_ASSERT_FALSE(manager.SetConnected(&conn_mock_5, true));
+    manager.AddUpdate(&entry_5);
+    WifiCredentialsManager::Release(entry_5);
+    TEST_ASSERT_TRUE(manager.SetConnected(&conn_mock_5, true));
+    entry = manager.GetConnected();
+    TEST_ASSERT_EQUAL_STRING_LEN((char*)conn_mock_5.ssid, entry->ssid, conn_mock_5.ssid_len);
+    WifiCredentialsManager::FormatBSSID(bssid_buffer, sizeof(bssid_buffer), conn_mock_5.bssid);
+    TEST_ASSERT_EQUAL_STRING((char*)bssid_buffer, entry->bssid);
+    TEST_ASSERT_EQUAL(WifiList::GetAuthType(conn_mock_5.authmode), entry->auth_type);
+    TEST_ASSERT_EQUAL(conn_mock_5.channel, entry->channel);
+    TEST_ASSERT_TRUE(manager.SetConnected(&conn_mock_4, true));
+    entry = manager.GetConnected();
+    TEST_ASSERT_EQUAL_STRING_LEN((char*)conn_mock_4.ssid, entry->ssid, conn_mock_5.ssid_len);
+    WifiList::FormatBSSID(bssid_buffer, sizeof(bssid_buffer), conn_mock_4.bssid);
+    TEST_ASSERT_EQUAL_STRING((char*)bssid_buffer, entry->bssid);
+    TEST_ASSERT_EQUAL(WifiList::GetAuthType(conn_mock_4.authmode), entry->auth_type);
+    TEST_ASSERT_EQUAL(conn_mock_4.channel, entry->channel);
+    manager.ResetConnected();
+    TEST_ASSERT_NULL(manager.GetConnected());
+    manager.Clear();
+}
+
+TEST_CASE("Getting by index/by name", "[WifiCredentialsManager]") {
+    char buffer[25] = {};
+    WifiCredentialsManager manager("test_manager");
+    FillSSIDs(manager, 4);
+    for (int i = 0; i < manager.GetCount(); i++) {
+        sprintf(buffer, "SSID_%d", i + 1);
+        auto mockap = getMockAPRec(i + 1);
+        auto mockSTA = getMockSTA(i + 1);
+        auto entry = manager.GetIndex(i);
+        TEST_ASSERT_EQUAL_STRING(buffer, entry->ssid);
+        auto pEntry = manager.Get(std::string(buffer));
+        TEST_ASSERT_EQUAL_STRING(buffer, pEntry->ssid);
+        pEntry = manager.Get(buffer);
+        TEST_ASSERT_EQUAL_STRING(buffer, pEntry->ssid);
+        pEntry = manager.Get(&mockap);
+        TEST_ASSERT_EQUAL_STRING(buffer, pEntry->ssid);
+        pEntry = manager.Get(&mockSTA);
+        TEST_ASSERT_EQUAL_STRING(buffer, pEntry->ssid);
+    }
+    auto entry = manager.GetStrongestSTA();
+    TEST_ASSERT_EQUAL_STRING("SSID_4", entry.ssid);
+    manager.Clear();
+}
+
+TEST_CASE("Update last try", "[WifiCredentialsManager]") {
+    char buffer[25] = {};
+    google_protobuf_Timestamp ts;
+    bool flag;
+    WifiCredentialsManager::UpdateTimeStamp(&ts, flag);
+
+    // now advance the time to ensure tests are a success
+    advanceTime(2);
+
+    WifiList manager("test_manager");
+    FillSSIDs(manager, 4);
+    for (int i = 0; i < manager.GetCount(); i++) {
+        sprintf(buffer, "SSID_%d", i + 1);
+        auto mockap = getMockAPRec(i + 1);
+        auto mockSTA = getMockSTA(i + 1);
+        auto entry = manager.GetIndex(i);
+        entry->has_last_try = false;
+        memset(&entry->last_try, 0x00, sizeof(entry->last_try));
+        manager.UpdateLastTry(entry);
+        TEST_ASSERT_TRUE(entry->has_last_try);
+        TEST_ASSERT_TRUE(entry->last_try.seconds >= ts.seconds);
+        entry->has_last_try = false;
+        memset(&entry->last_try, 0x00, sizeof(entry->last_try));
+        manager.UpdateLastTry(std::string(buffer));
+        TEST_ASSERT_TRUE(entry->has_last_try);
+        TEST_ASSERT_TRUE(entry->last_try.seconds >= ts.seconds);
+        entry->has_last_try = false;
+        memset(&entry->last_try, 0x00, sizeof(entry->last_try));
+        manager.UpdateLastTry(&mockap);
+        TEST_ASSERT_TRUE(entry->has_last_try);
+        TEST_ASSERT_TRUE(entry->last_try.seconds >= ts.seconds);
+        entry->has_last_try = false;
+        memset(&entry->last_try, 0x00, sizeof(entry->last_try));
+        manager.UpdateLastTry(&mockSTA);
+        TEST_ASSERT_TRUE(entry->has_last_try);
+        TEST_ASSERT_TRUE(entry->last_try.seconds >= ts.seconds);
+    }
+    auto entry = manager.GetStrongestSTA();
+    TEST_ASSERT_EQUAL_STRING("SSID_4", entry.ssid);
+    manager.Clear();
+    advanceTime(-2);
+}
+
+TEST_CASE("Update last seen", "[WifiCredentialsManager]") {
+    char buffer[25] = {};
+    google_protobuf_Timestamp ts;
+    bool flag;
+    WifiCredentialsManager::UpdateTimeStamp(&ts, flag);
+
+    // now advance the time to ensure tests are a success
+    advanceTime(2);
+
+    WifiList manager("test_manager");
+    FillSSIDs(manager, 4);
+    for (int i = 0; i < manager.GetCount(); i++) {
+        sprintf(buffer, "SSID_%d", i + 1);
+        auto mockap = getMockAPRec(i + 1);
+        auto mockSTA = getMockSTA(i + 1);
+        auto entry = manager.GetIndex(i);
+        entry->has_last_seen = false;
+        memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+        manager.UpdateLastSeen(entry);
+        TEST_ASSERT_TRUE(entry->has_last_seen);
+        TEST_ASSERT_TRUE(entry->last_seen.seconds >= ts.seconds);
+        entry->has_last_seen = false;
+        memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+        manager.UpdateLastSeen(std::string(buffer));
+        TEST_ASSERT_TRUE(entry->has_last_seen);
+        TEST_ASSERT_TRUE(entry->last_seen.seconds >= ts.seconds);
+        entry->has_last_seen = false;
+        memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+        manager.UpdateLastSeen(&mockap);
+        TEST_ASSERT_TRUE(entry->has_last_seen);
+        TEST_ASSERT_TRUE(entry->last_seen.seconds >= ts.seconds);
+        entry->has_last_seen = false;
+        memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+        manager.UpdateLastSeen(&mockSTA);
+        TEST_ASSERT_TRUE(entry->has_last_seen);
+        TEST_ASSERT_TRUE(entry->last_seen.seconds >= ts.seconds);
+    }
+
+    manager.Clear();
+    advanceTime(-2);
+}
+
+TEST_CASE("Memory leak test", "[WifiCredentialsManager]") {
+    char buffer[25] = {};
+    char err_buffer[55] = {};
+    int fillqty=20;
+    google_protobuf_Timestamp ts;
+    bool flag;
+
+    WifiCredentialsManager::UpdateTimeStamp(&ts, flag);
+
+    // now advance the time to ensure tests are a success
+    advanceTime(2);
+    HasMemoryUsageIncreased(0);
+
+    for (int runs = 0; runs < 100; runs++) {
+        WifiCredentialsManager manager("test_manager");
+        FillSSIDs(manager, fillqty);
+        for (int i = 1; i <= manager.GetCount(); i++) {
+            sprintf(buffer, "SSID_%d", i);
+            sprintf(err_buffer,"Round %d, SSID_%d",runs,i);
+            auto mockap = getMockAPRec(i);
+            auto mockSTA = getMockSTA(i);
+            auto entry = manager.Get(buffer);
+            TEST_ASSERT_EQUAL_STRING(buffer,entry->ssid);
+            entry->has_last_seen = false;
+            memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+            manager.UpdateLastSeen(entry);
+            TEST_ASSERT_TRUE(entry->has_last_seen);
+            TEST_ASSERT_TRUE(entry->last_seen.seconds >= ts.seconds);
+            entry->has_last_seen = false;
+            memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+            manager.UpdateLastSeen(std::string(buffer));
+            TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
+            TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
+            entry->has_last_seen = false;
+            memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+            manager.UpdateLastSeen(&mockap);
+            TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
+            TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
+            entry->has_last_seen = false;
+            memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+            manager.UpdateLastSeen(&mockSTA);
+            TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
+            TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
+        }
+        manager.Clear();
+    }
+    TEST_ASSERT_FALSE(HasMemoryUsageIncreased(1));
+
+    for (int runs = 0; runs < 100; runs++) {
+        WifiCredentialsManager manager("test_manager");
+        FillSSIDFromAPRec(manager, fillqty);
+        for (int i = 1; i <= manager.GetCount(); i++) {
+            sprintf(buffer, "SSID_%d", i);
+            sprintf(err_buffer,"Round %d, SSID_%d",runs,i);
+            auto mockap = getMockAPRec(i);
+            auto mockSTA = getMockSTA(i);
+            auto entry = manager.GetIndex(i-1);
+            entry->has_last_seen = false;
+            memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+            manager.UpdateLastSeen(entry);
+            TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
+            TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
+            entry->has_last_seen = false;
+            memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+            manager.UpdateLastSeen(std::string(buffer));
+            TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
+            TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
+            entry->has_last_seen = false;
+            memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+            manager.UpdateLastSeen(&mockap);
+            TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
+            TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
+            entry->has_last_seen = false;
+            memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+            manager.UpdateLastSeen(&mockSTA);
+            TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
+            TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
+        }
+        manager.Clear();
+    }
+    TEST_ASSERT_FALSE(HasMemoryUsageIncreased(2));
+
+    for (int runs = 0; runs < 100; runs++) {
+        WifiCredentialsManager manager("test_manager");
+        FillSSIDFromMockEntry(manager, fillqty);
+        for (int i = 1; i <= manager.GetCount(); i++) {
+            sprintf(buffer, "SSID_%d", i);
+            sprintf(err_buffer,"Round %d, SSID_%d",runs,i);
+            auto mockap = getMockAPRec(i);
+            auto mockSTA = getMockSTA(i);
+            auto entry = manager.GetIndex(i-1);
+            entry->has_last_seen = false;
+            memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+            manager.UpdateLastSeen(entry);
+            TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
+            TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
+            entry->has_last_seen = false;
+            memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+            manager.UpdateLastSeen(std::string(buffer));
+            TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
+            TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
+            entry->has_last_seen = false;
+            memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+            manager.UpdateLastSeen(&mockap);
+            TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
+            TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
+            entry->has_last_seen = false;
+            memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
+            manager.UpdateLastSeen(&mockSTA);
+            TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
+            TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
+        }
+        manager.Clear();
+    }
+    advanceTime(-2);
+    TEST_ASSERT_FALSE(HasMemoryUsageIncreased(3));
+}

+ 1 - 1
components/platform_console/CMakeLists.txt

@@ -7,7 +7,7 @@ idf_component_register( SRCS
 							cmd_config.c
 						INCLUDE_DIRS .   
 						REQUIRES nvs_flash 
-						PRIV_REQUIRES console app_update tools services spi_flash  platform_config vfs pthread wifi-manager platform_config newlib  telnet display squeezelite tools metrics)
+						PRIV_REQUIRES console app_update tools services spi_flash tools platform_config vfs pthread wifi-manager  newlib  telnet display squeezelite  metrics)
 
 set_source_files_properties(cmd_config.c
     PROPERTIES COMPILE_FLAGS

+ 1 - 1
components/platform_console/app_squeezelite/CMakeLists.txt

@@ -1,7 +1,7 @@
 idf_build_get_property(idf_path IDF_PATH)
 idf_component_register( SRCS cmd_squeezelite.c 
 						INCLUDE_DIRS . 
-						PRIV_REQUIRES spi_flash bootloader_support  partition_table bootloader_support console codecs squeezelite newlib pthread tools platform_config display tools services)
+						PRIV_REQUIRES spi_flash bootloader_support  partition_table bootloader_support console codecs squeezelite newlib pthread tools platform_config display  services)
 						
 
 target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--undefined=feof")

+ 98 - 115
components/platform_console/app_squeezelite/cmd_squeezelite.c

@@ -1,23 +1,27 @@
-#include <stdio.h>
-#include <string.h>
-#include "application_name.h"
-#include "esp_log.h"
-#include "esp_console.h"
 #include "../cmd_system.h"
+#include "Config.h"
+#include "application_name.h"
 #include "argtable3/argtable3.h"
+#include "esp_app_format.h"
+#include "esp_console.h"
+#include "esp_log.h"
 #include "freertos/FreeRTOS.h"
 #include "freertos/event_groups.h"
-#include "pthread.h"
+#include "messaging.h"
 #include "platform_esp32.h"
-#include "Configurator.h"
-#include "esp_app_format.h"
+#include "pthread.h"
 #include "tools.h"
-#include "messaging.h"
-extern esp_err_t process_recovery_ota(const char * bin_url, char * bin_buffer, uint32_t length);
+#include <stdio.h>
+#include <string.h>
+#include "bootstate.h"
+
+extern esp_err_t process_recovery_ota(const char* bin_url, char* bin_buffer, uint32_t length);
 extern int squeezelite_main_start();
+extern sys_dac_default_sets* default_dac_sets;
 
-static const char * TAG = "squeezelite_cmd";
-#define SQUEEZELITE_THREAD_STACK_SIZE (8*1024)
+
+static const char* TAG = "squeezelite_cmd";
+#define SQUEEZELITE_THREAD_STACK_SIZE (8 * 1024)
 #define ADDITIONAL_SQUEEZELITE_ARGS 5
 
 const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
@@ -45,112 +49,91 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
 extern void register_audio_config(void);
 // extern void register_rotary_config(void);
 extern void register_nvs();
-extern cJSON * get_gpio_list_handler(bool refresh);
-cJSON * get_gpio_list(bool refresh){
-#if CONFIG_WITH_CONFIG_UI		
-	return get_gpio_list_handler(refresh);
+extern cJSON* get_gpio_list_handler(bool refresh);
+cJSON* get_gpio_list(bool refresh) {
+#if CONFIG_WITH_CONFIG_UI
+    return get_gpio_list_handler(refresh);
 #else
-	return cJSON_CreateArray();
+    return cJSON_CreateArray();
 #endif
 }
 void register_optional_cmd(void) {
-// #if CONFIG_WITH_CONFIG_UI	
-//     register_rotary_config();
-// #endif
+    // #if CONFIG_WITH_CONFIG_UI
+    //     register_rotary_config();
+    // #endif
     register_audio_config();
-	// register_ledvu_config();
-
+    // register_ledvu_config();
 }
 
-/** Arguments used by 'squeezelite' function */
 static struct {
-    struct arg_str *parameters;
-    struct arg_end *end;
-} squeezelite_args;
-static struct {
-	int argc;
-	char ** argv;
-} thread_parms ;
-
-static void squeezelite_thread(void *arg){  
-	ESP_LOGV(TAG ,"Number of args received: %u",thread_parms.argc );
-	ESP_LOGV(TAG ,"Values:");
-    for(int i = 0;i<thread_parms.argc; i++){
-    	ESP_LOGV(TAG ,"     %s",thread_parms.argv[i]);
-    }
-    ESP_LOGI(TAG ,"Calling squeezelite");
-    int ret = squeezelite_main_start(thread_parms.argc, thread_parms.argv);
-        
-    cmd_send_messaging("cfg-audio-tmpl",ret > 1 ?  MESSAGING_ERROR : MESSAGING_WARNING,"squeezelite exited with error code %d\n", ret);
-
-    if (ret <= 1) {
-        int wait = 60;
-        // wait_for_commit();
-		// TODO: Add support for the commented code
-        cmd_send_messaging("cfg-audio-tmpl",MESSAGING_ERROR,"Rebooting in %d sec\n", wait);
-        vTaskDelay( pdMS_TO_TICKS(wait * 1000));
-        esp_restart();
+    int argc;
+    char** argv;
+} thread_parms;
+
+static void squeezelite_thread(void* arg) {
+    ESP_LOGI(TAG, "Calling squeezelite");
+    int wait = 60;
+    int ret = squeezelite_main_start();
+
+    if (ret == -2) {
+        cmd_send_messaging("cfg-audio-tmpl", MESSAGING_ERROR,
+            "Squeezelite not configured. Rebooting to factory in %d sec\n", wait);
+        vTaskDelay(pdMS_TO_TICKS(wait * 1000));
+        guided_factory();
     } else {
-		cmd_send_messaging("cfg-audio-tmpl",MESSAGING_ERROR,"Correct command line and reboot\n");
-        vTaskSuspend(NULL);
+        cmd_send_messaging("cfg-audio-tmpl", ret > 1 ? MESSAGING_ERROR : MESSAGING_WARNING,
+            "squeezelite exited with error code %d\n", ret);
+        if (ret <= 1) {
+
+            cmd_send_messaging(
+                "cfg-audio-tmpl", MESSAGING_ERROR, "Rebooting to factory in %d sec\n", wait);
+            vTaskDelay(pdMS_TO_TICKS(wait * 1000));
+            guided_factory();
+        } else {
+
+            cmd_send_messaging(
+                "cfg-audio-tmpl", MESSAGING_ERROR, "Correct command line and reboot\n");
+            vTaskSuspend(NULL);
+        }
     }
 
-	ESP_LOGV(TAG, "Exited from squeezelite's main(). Freeing argv structure.");
+    ESP_LOGV(TAG, "Exited from squeezelite's main(). Freeing argv structure.");
 
-	for(int i=0;i<thread_parms.argc;i++) free(thread_parms.argv[i]);
-	free(thread_parms.argv);
+    for (int i = 0; i < thread_parms.argc; i++)
+        free(thread_parms.argv[i]);
+    free(thread_parms.argv);
 }
 
-static int launchsqueezelite(int argc, char **argv) {
-	static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
-	static EXT_RAM_ATTR StackType_t xStack[SQUEEZELITE_THREAD_STACK_SIZE] __attribute__ ((aligned (4)));
-	static bool isRunning = false;
-
-	if (isRunning) {
-		ESP_LOGE(TAG,"Squeezelite already running. Exiting!");
-		return -1;
-	}
-	
-	ESP_LOGV(TAG ,"Begin");
-	isRunning = true;
-
-	// ESP_LOGV(TAG, "Parameters:");
-    // for(int i = 0;i<argc; i++){
-    // 	ESP_LOGV(TAG, "     %s",argv[i]);
-    // }
-    // ESP_LOGV(TAG,"Saving args in thread structure");
-
-    // thread_parms.argc=0;
-    // thread_parms.argv = malloc_init_external(sizeof(char**)*(argc+ADDITIONAL_SQUEEZELITE_ARGS));
-
-	// for(int i=0;i<argc;i++){
-	// 	ESP_LOGD(TAG ,"assigning parm %u : %s",i,argv[i]);
-	// 	thread_parms.argv[thread_parms.argc++]=strdup(argv[i]);
-	// }
-
-	// if(argc==1){
-	// 	// There isn't a default configuration that would actually work
-	// 	// if no parameter is passed.
-	// 	ESP_LOGV(TAG ,"Adding argv value at %u. Prev value: %s",thread_parms.argc,thread_parms.argv[thread_parms.argc-1]);
-	// 	thread_parms.argv[thread_parms.argc++]=strdup("-?");
-	// }
-
-	ESP_LOGD(TAG,"Starting Squeezelite Thread");
-	xTaskCreateStaticPinnedToCore(squeezelite_thread, "squeezelite", SQUEEZELITE_THREAD_STACK_SIZE, 
-					  NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT, xStack, &xTaskBuffer, CONFIG_PTHREAD_TASK_CORE_DEFAULT);
-	ESP_LOGD(TAG ,"Back to console thread!");
+static int launchsqueezelite(int argc, char** argv) {
+    static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__((aligned(4)));
+    static EXT_RAM_ATTR StackType_t xStack[SQUEEZELITE_THREAD_STACK_SIZE]
+        __attribute__((aligned(4)));
+    static bool isRunning = false;
+
+    if (isRunning) {
+        ESP_LOGE(TAG, "Squeezelite already running. Exiting!");
+        return -1;
+    }
+
+    ESP_LOGV(TAG, "Begin");
+    isRunning = true;
+
+    // load the presets here, as the squeezelite cannot access the 
+    // spiffs storage
+    // proto_load_file("/spiffs/presets/default_sets.bin", &sys_dac_default_sets_msg, default_dac_sets, false);
+
+    ESP_LOGD(TAG, "Starting Squeezelite Thread");
+    xTaskCreateStaticPinnedToCore(squeezelite_thread, "squeezelite", SQUEEZELITE_THREAD_STACK_SIZE,
+        NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT, xStack, &xTaskBuffer,
+        CONFIG_PTHREAD_TASK_CORE_DEFAULT);
 
     return 0;
 }
-void start_squeezelite(){
-	launchsqueezelite(0,NULL);
-}
+void start_squeezelite() { launchsqueezelite(0, NULL); }
 // void register_squeezelite() {
-// 	squeezelite_args.parameters = arg_str0(NULL, NULL, "<parms>", "command line for squeezelite. -h for help, --defaults to launch with default values.");
-// 	squeezelite_args.end = arg_end(1);
-// 	const esp_console_cmd_t launch_squeezelite = {
-// 		.command = "squeezelite",
-// 		.help = "Starts squeezelite",
+// 	squeezelite_args.parameters = arg_str0(NULL, NULL, "<parms>", "command line for squeezelite. -h
+// for help, --defaults to launch with default values."); 	squeezelite_args.end = arg_end(1); 	const
+// esp_console_cmd_t launch_squeezelite = { 		.command = "squeezelite", 		.help = "Starts squeezelite",
 // 		.hint = NULL,
 // 		.func = &launchsqueezelite,
 // 		.argtable = &squeezelite_args
@@ -158,19 +141,19 @@ void start_squeezelite(){
 // 	ESP_ERROR_CHECK( esp_console_cmd_register(&launch_squeezelite) );
 // }
 
-esp_err_t start_ota(const char * bin_url, char * bin_buffer, uint32_t length) {
-	if(!bin_url || strlen(bin_url)==0){
-		ESP_LOGE(TAG,"missing URL parameter. Unable to start OTA");
-		return ESP_ERR_INVALID_ARG;
-	}
-	ESP_LOGW(TAG, "Called to update the firmware from url: %s",bin_url);
-	configurator_set_string(&sys_State_msg,sys_State_ota_url_tag,sys_state,bin_url);
-	configurator_raise_state_changed();
-
-	if(!configurator_waitcommit()){
-		ESP_LOGW(TAG,"Unable to commit configuration. ");
-	}
-	ESP_LOGW(TAG, "Rebooting to recovery to complete the installation");
-	return guided_factory();
-	return ESP_OK;
+esp_err_t start_ota(const char* bin_url, char* bin_buffer, uint32_t length) {
+    if (!bin_url || strlen(bin_url) == 0) {
+        ESP_LOGE(TAG, "missing URL parameter. Unable to start OTA");
+        return ESP_ERR_INVALID_ARG;
+    }
+    ESP_LOGW(TAG, "Called to update the firmware from url: %s", bin_url);
+    system_set_string(&sys_state_data_msg, sys_state_data_ota_url_tag, sys_state, bin_url);
+    config_raise_state_changed();
+
+    if (!config_waitcommit()) {
+        ESP_LOGW(TAG, "Unable to commit configuration. ");
+    }
+    ESP_LOGW(TAG, "Rebooting to recovery to complete the installation");
+    return guided_factory();
+    return ESP_OK;
 }

Diferenças do arquivo suprimidas por serem muito extensas
+ 463 - 415
components/platform_console/cmd_config.c


+ 48 - 49
components/platform_console/cmd_i2ctools.c

@@ -16,7 +16,7 @@
 #include "driver/i2c.h"
 #include "esp_log.h"
 #include "messaging.h"
-// #include "Configurator.h"
+// #include "Config.h"
 #pragma message("fixme: look for TODO below")
 #include "platform_console.h"
 #include "stdio.h"
@@ -41,19 +41,18 @@ const char* desc_spiconfig = "SPI Bus Parameters";
 const char* desc_i2c = "I2C Bus Parameters";
 const char* desc_display = "Display";
 
-#ifdef CONFIG_I2C_LOCKED
-static i2c_port_t i2c_port = I2C_NUM_1;
-#else
-static i2c_port_t i2c_port = I2C_NUM_0;
-#endif
 
 static struct {
+    struct arg_int* port;
     struct arg_int* chip_address;
     struct arg_int* register_address;
     struct arg_int* data_length;
     struct arg_end* end;
 } i2cget_args;
-
+static struct {
+    struct arg_int* port;
+    struct arg_end* end;
+} i2cdetect_args;
 static struct {
     struct arg_int* chip_address;
     struct arg_int* port;
@@ -288,31 +287,31 @@ static esp_err_t i2c_get_port(int port, i2c_port_t* i2c_port) {
     }
     return ESP_OK;
 }
-static esp_err_t i2c_master_driver_install(const char* cmdname) {
-    esp_err_t err = ESP_OK;
-    cmd_send_messaging(cmdname, MESSAGING_INFO, "Installing i2c driver on port %u\n", i2c_port);
-    if ((err = i2c_driver_install(i2c_port, I2C_MODE_MASTER, I2C_MASTER_RX_BUF_DISABLE,
-             I2C_MASTER_TX_BUF_DISABLE, 0)) != ESP_OK) {
-        cmd_send_messaging(
-            cmdname, MESSAGING_ERROR, "Driver install failed! %s\n", esp_err_to_name(err));
-    }
-    return err;
-}
-
-static esp_err_t i2c_master_driver_initialize(const char* cmdname, i2c_config_t* conf) {
-    esp_err_t err = ESP_OK;
-    cmd_send_messaging(cmdname, MESSAGING_INFO,
-        "Initializing i2c driver configuration.\n   mode = I2C_MODE_MASTER, \n   scl_pullup_en = "
-        "GPIO_PULLUP_ENABLE, \n   i2c port = %u, \n   sda_io_num = %u, \n   sda_pullup_en = "
-        "GPIO_PULLUP_ENABLE, \n   scl_io_num = %u, \n   scl_pullup_en = GPIO_PULLUP_ENABLE, \n   "
-        "master.clk_speed = %u\n",
-        i2c_port, conf->sda_io_num, conf->scl_io_num, conf->master.clk_speed);
-    if ((err = i2c_param_config(i2c_port, conf)) != ESP_OK) {
-        cmd_send_messaging(
-            cmdname, MESSAGING_ERROR, "i2c driver config load failed. %s\n", esp_err_to_name(err));
-    }
-    return err;
-}
+// static esp_err_t i2c_master_driver_install(const char* cmdname) {
+//     esp_err_t err = ESP_OK;
+//     cmd_send_messaging(cmdname, MESSAGING_INFO, "Installing i2c driver on port %u\n", i2c_port);
+//     if ((err = i2c_driver_install(i2c_port, I2C_MODE_MASTER, I2C_MASTER_RX_BUF_DISABLE,
+//              I2C_MASTER_TX_BUF_DISABLE, 0)) != ESP_OK) {
+//         cmd_send_messaging(
+//             cmdname, MESSAGING_ERROR, "Driver install failed! %s\n", esp_err_to_name(err));
+//     }
+//     return err;
+// }
+
+// static esp_err_t i2c_master_driver_initialize(const char* cmdname, i2c_config_t* conf) {
+//     esp_err_t err = ESP_OK;
+//     cmd_send_messaging(cmdname, MESSAGING_INFO,
+//         "Initializing i2c driver configuration.\n   mode = I2C_MODE_MASTER, \n   scl_pullup_en = "
+//         "GPIO_PULLUP_ENABLE, \n   i2c port = %u, \n   sda_io_num = %u, \n   sda_pullup_en = "
+//         "GPIO_PULLUP_ENABLE, \n   scl_io_num = %u, \n   scl_pullup_en = GPIO_PULLUP_ENABLE, \n   "
+//         "master.clk_speed = %u\n",
+//         i2c_port, conf->sda_io_num, conf->scl_io_num, conf->master.clk_speed);
+//     if ((err = i2c_param_config(i2c_port, conf)) != ESP_OK) {
+//         cmd_send_messaging(
+//             cmdname, MESSAGING_ERROR, "i2c driver config load failed. %s\n", esp_err_to_name(err));
+//     }
+//     return err;
+// }
 
 #if CONFIG_WITH_CONFIG_UI
 static int do_i2c_set_display(int argc, char** argv) {
@@ -763,12 +762,9 @@ static int do_i2cget_cmd(int argc, char** argv) {
     if (i2cget_args.data_length->count) {
         len = i2cget_args.data_length->ival[0];
     }
-    i2c_port_t loc_i2c_port = i2c_port;
-    if (i2cset_args.port->count &&
-        i2c_get_port(i2cset_args.port->ival[0], &loc_i2c_port) != ESP_OK) {
-        return 1;
-    }
-
+    i2c_port_t loc_i2c_port = i2cget_args.port->ival[0];
+    const sys_i2c_bus*i2c = get_i2c_bus(loc_i2c_port);
+    #pragma message("TODO: Fix this!")
     char* buf = NULL;
     size_t buf_size = 0;
     FILE* f = open_memstream(&buf, &buf_size);
@@ -897,18 +893,21 @@ esp_err_t cmd_i2ctools_scan_bus(FILE* f, int sda, int scl) {
 
     return 0;
 }
+
 static int do_i2cdetect_cmd(int argc, char** argv) {
 #ifdef CONFIG_WITH_CONFIG_UI
     uint8_t matches[128] = {};
     int last_match = 0;
 #endif
     esp_err_t ret = ESP_OK;
-    i2c_port_t loc_i2c_port = i2c_port;
+    i2c_port_t loc_i2c_port = i2cdetect_args.port->ival[0];
+    
     // if (i2cset_args.port->count && i2c_get_port(i2cset_args.port->ival[0], &loc_i2c_port) !=
     // ESP_OK) { 	return 1;
     // }
-    int i2c_port;
-    const i2c_config_t* i2c = config_i2c_get(&i2c_port);
+
+    const sys_i2c_bus*i2c = get_i2c_bus(loc_i2c_port);
+
 
     uint8_t address;
     char* buf = NULL;
@@ -919,8 +918,7 @@ static int do_i2cdetect_cmd(int argc, char** argv) {
         return 1;
     }
 
-    if (!GPIO_IS_VALID_OUTPUT_GPIO(i2c->scl_io_num) || !GPIO_IS_VALID_GPIO(i2c->scl_io_num) ||
-        !GPIO_IS_VALID_OUTPUT_GPIO(i2c->sda_io_num) || !GPIO_IS_VALID_GPIO(i2c->sda_io_num)) {
+    if (!i2c) {
         fprintf(f, "I2C bus configuration invalid.\r\n");
     } else {
 
@@ -1007,12 +1005,13 @@ static void register_i2c_set_display() {
 }
 #endif
 static void register_i2cdectect(void) {
+    i2cdetect_args.port = arg_int1(NULL,NULL, "<0|1>", "Specify the i2c port number");
+    i2cdetect_args.end = arg_end(1);
     const esp_console_cmd_t i2cdetect_cmd = {.command = "i2cdetect",
         .help = "Scan I2C bus for devices",
         .hint = NULL,
         .func = &do_i2cdetect_cmd,
-        .argtable = NULL};
-    cmd_to_json(&i2cdetect_cmd);
+        .argtable = &i2cdetect_args};
     ESP_ERROR_CHECK(esp_console_cmd_register(&i2cdetect_cmd));
 }
 
@@ -1023,6 +1022,7 @@ static void register_i2cget(void) {
         "r", "register", "<register_addr>", "Specify the address on that chip to read from");
     i2cget_args.data_length =
         arg_int0("l", "length", "<length>", "Specify the length to read from that data address");
+    i2cget_args.port = arg_int1(NULL,NULL, "<0|1>", "Specify the i2c port number");
     i2cget_args.end = arg_end(1);
     const esp_console_cmd_t i2cget_cmd = {.command = "i2cget",
         .help = "Read registers visible through the I2C bus",
@@ -1070,8 +1070,7 @@ static void register_i2cdump(void) {
 
 cJSON* i2config_cb() {
     cJSON* values = cJSON_CreateObject();
-    int i2c_port;
-    const i2c_config_t* i2c = config_i2c_get(&i2c_port);
+    const i2c_config_t* i2c = config_i2c_get(&platform->dev.i2c);
     if (i2c->scl_io_num > 0) {
         cJSON_AddNumberToObject(values, "scl", i2c->scl_io_num);
     }
@@ -1081,8 +1080,8 @@ cJSON* i2config_cb() {
     if (i2c->master.clk_speed > 0) {
         cJSON_AddNumberToObject(values, "speed", i2c->master.clk_speed);
     }
-    if (i2c_port >= 0) {
-        cJSON_AddNumberToObject(values, "port", i2c_port);
+    if (platform->dev.i2c.port-sys_i2c_port_PORT0 >= 0) {
+        cJSON_AddNumberToObject(values, "port", platform->dev.i2c.port-sys_i2c_port_PORT0);
     }
     return values;
 }

Diferenças do arquivo suprimidas por serem muito extensas
+ 460 - 347
components/platform_console/cmd_system.c


+ 1 - 3
components/platform_console/cmd_system.h

@@ -14,9 +14,7 @@ extern "C" {
 
 // Register system functions
 void register_system();
-esp_err_t guided_factory();
-esp_err_t guided_restart_ota();
-void simple_restart();
+
 FILE * system_open_memstream(const char * cmdname,char **buf,size_t *buf_size);
 #ifdef __cplusplus
 }

+ 1 - 1
components/platform_console/cmd_wifi.c

@@ -54,7 +54,7 @@ static struct {
 
 
 
-// todo: implement access point config - cmd_to_json(&i2cdetect_cmd);
+
 
 static void event_handler(void* arg, esp_event_base_t event_base,
                                 int32_t event_id, void* event_data)

+ 319 - 381
components/platform_console/platform_console.c

@@ -9,24 +9,24 @@
 
 #include "platform_console.h"
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include "esp_log.h"
+#include "argtable3/argtable3.h"
+#include "bootstate.h"
+#include "cmd_decl.h"
+#include "driver/uart.h"
 #include "esp_console.h"
+#include "esp_log.h"
 #include "esp_vfs_dev.h"
-#include "driver/uart.h"
 #include "linenoise/linenoise.h"
-#include "argtable3/argtable3.h"
-#include "nvs.h" 
+#include "nvs.h"
 #include "nvs_flash.h"
-#include "pthread.h"
 #include "platform_esp32.h"
-#include "cmd_decl.h"
+#include "pthread.h"
 #include "trace.h"
-// #include "Configurator.h"
-#pragma message("fixme: search for TODO below")
-#include "telnet.h" 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "telnet.h"
 #include "tools.h"
 #if defined(CONFIG_WITH_METRICS)
 #include "metrics.h"
@@ -35,19 +35,19 @@
 
 #include "config.h"
 static pthread_t thread_console;
-static void * console_thread();
+static void* console_thread();
 void console_start();
-static const char * TAG = "console";
+static const char* TAG = "console";
 extern bool bypass_network_manager;
 extern void launchsqueezelite();
 
 static EXT_RAM_ATTR QueueHandle_t uart_queue;
 static EXT_RAM_ATTR struct {
-		uint8_t _buf[512];
-		StaticRingbuffer_t _ringbuf;
-		RingbufHandle_t handle;
-		QueueSetHandle_t queue_set;
-} stdin_redir;	
+    uint8_t _buf[512];
+    StaticRingbuffer_t _ringbuf;
+    RingbufHandle_t handle;
+    QueueSetHandle_t queue_set;
+} stdin_redir;
 
 /* Prompt to be printed before each line.
  * This can be customized, made dynamic, etc.
@@ -62,94 +62,99 @@ const char* recovery_prompt = LOG_COLOR_E "recovery-squeezelite-esp32> " LOG_RES
 
 #define MOUNT_PATH "/data"
 #define HISTORY_PATH MOUNT_PATH "/history.txt"
-static esp_err_t run_command(char * line);
-#define ADD_TO_JSON(o,t,n) if (t->n) cJSON_AddStringToObject(o,QUOTE(n),t->n);
-#define ADD_PARMS_TO_CMD(o,t,n) { cJSON * parms = ParmsToJSON(&t.n->hdr); if(parms) cJSON_AddItemToObject(o,QUOTE(n),parms); }
-cJSON * cmdList;
-cJSON * values_fn_list;
-cJSON * get_cmd_list(){
-	cJSON * element;
-	cJSON * values=cJSON_CreateObject();
-	cJSON * list = cJSON_CreateObject();
-	cJSON_AddItemReferenceToObject(list,"commands",cmdList);
-	cJSON_AddItemToObject(list,"values",values);
-	cJSON_ArrayForEach(element,cmdList){
-		cJSON * name = cJSON_GetObjectItem(element,"name");
-		cJSON * vals_fn = cJSON_GetObjectItem(values_fn_list,cJSON_GetStringValue(name));
-		if(vals_fn!=NULL ){
-			parm_values_fn_t *parm_values_fn = (parm_values_fn_t *)strtoul(cJSON_GetStringValue(vals_fn), NULL, 16);;
-
-			if(parm_values_fn){
-				cJSON_AddItemToObject(values,cJSON_GetStringValue(name),parm_values_fn());
-			}
-		}
-	}
-	return list;
+static esp_err_t run_command(char* line);
+#define ADD_TO_JSON(o, t, n)                                                                                                                         \
+    if (t->n) cJSON_AddStringToObject(o, QUOTE(n), t->n);
+#define ADD_PARMS_TO_CMD(o, t, n)                                                                                                                    \
+    {                                                                                                                                                \
+        cJSON* parms = ParmsToJSON(&t.n->hdr);                                                                                                       \
+        if (parms) cJSON_AddItemToObject(o, QUOTE(n), parms);                                                                                        \
+    }
+cJSON* cmdList;
+cJSON* values_fn_list;
+cJSON* get_cmd_list() {
+    cJSON* element;
+    cJSON* values = cJSON_CreateObject();
+    cJSON* list = cJSON_CreateObject();
+    cJSON_AddItemReferenceToObject(list, "commands", cmdList);
+    cJSON_AddItemToObject(list, "values", values);
+    cJSON_ArrayForEach(element, cmdList) {
+        cJSON* name = cJSON_GetObjectItem(element, "name");
+        cJSON* vals_fn = cJSON_GetObjectItem(values_fn_list, cJSON_GetStringValue(name));
+        if (vals_fn != NULL) {
+            parm_values_fn_t* parm_values_fn = (parm_values_fn_t*)strtoul(cJSON_GetStringValue(vals_fn), NULL, 16);
+            ;
+
+            if (parm_values_fn) {
+                cJSON_AddItemToObject(values, cJSON_GetStringValue(name), parm_values_fn());
+            }
+        }
+    }
+    return list;
 }
-void console_set_bool_parameter(cJSON * root,char * nvs_name, struct arg_lit *arg){
-    char * p=NULL;
-	bool enabled = false;
-	if(!root) {
-        ESP_LOGE(TAG,"Invalid json parameter. Cannot set %s from %s",arg->hdr.longopts?arg->hdr.longopts:arg->hdr.glossary,nvs_name);
+void console_set_bool_parameter(cJSON* root, char* nvs_name, struct arg_lit* arg) {
+    char* p = NULL;
+    bool enabled = false;
+    if (!root) {
+        ESP_LOGE(TAG, "Invalid json parameter. Cannot set %s from %s", arg->hdr.longopts ? arg->hdr.longopts : arg->hdr.glossary, nvs_name);
         return;
     }
-//     if ((p = config_alloc_get(NVS_TYPE_STR, nvs_name)) != NULL) {
-// 		enabled = strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0;
-// 		cJSON_AddBoolToObject(root,arg->hdr.longopts,enabled);
-//         FREE_AND_NULL(p);
-//     }
-// #if defined(CONFIG_WITH_METRICS)	
-// 	if(enabled){
-// 		metrics_add_feature(nvs_name,"enabled");
-// 	}
-// #endif
-// TODO: Add support for the commented code
-	
+    //     if ((p = config_alloc_get(NVS_TYPE_STR, nvs_name)) != NULL) {
+    // 		enabled = strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0;
+    // 		cJSON_AddBoolToObject(root,arg->hdr.longopts,enabled);
+    //         FREE_AND_NULL(p);
+    //     }
+    // #if defined(CONFIG_WITH_METRICS)
+    // 	if(enabled){
+    // 		metrics_add_feature(nvs_name,"enabled");
+    // 	}
+    // #endif
+    // TODO: Add support for the commented code
 }
-struct arg_end *getParmsEnd(struct arg_hdr * * argtable){
-	if(!argtable) return NULL;
-	struct arg_hdr * *table = (struct arg_hdr * *)argtable;
-	int tabindex = 0;
-	while (!(table[tabindex]->flag & ARG_TERMINATOR))
-	{
-		tabindex++;
-	}
-	return (struct arg_end *)table[tabindex];
+struct arg_end* getParmsEnd(struct arg_hdr** argtable) {
+    if (!argtable) return NULL;
+    struct arg_hdr** table = (struct arg_hdr**)argtable;
+    int tabindex = 0;
+    while (!(table[tabindex]->flag & ARG_TERMINATOR)) {
+        tabindex++;
+    }
+    return (struct arg_end*)table[tabindex];
 }
-cJSON * ParmsToJSON(struct arg_hdr * * argtable){
-	if(!argtable) return NULL;
-	cJSON * arg_list = cJSON_CreateArray();
-	struct arg_hdr * *table = (struct arg_hdr * *)argtable;
-	int tabindex = 0;
-	while (!(table[tabindex]->flag & ARG_TERMINATOR))
-	{
-		cJSON * entry = cJSON_CreateObject();
-		ADD_TO_JSON(entry,table[tabindex],datatype);
-		ADD_TO_JSON(entry,table[tabindex],glossary);
-		ADD_TO_JSON(entry,table[tabindex],longopts);
-		ADD_TO_JSON(entry,table[tabindex],shortopts);
-		cJSON_AddBoolToObject(entry, "checkbox", (table[tabindex]->flag & ARG_HASOPTVALUE)==0 && (table[tabindex]->flag & ARG_HASVALUE)==0 && (table[tabindex]->longopts || table[tabindex]->shortopts) );
-		cJSON_AddBoolToObject(entry, "remark", (table[tabindex]->flag & ARG_HASOPTVALUE)==0 && (table[tabindex]->flag & ARG_HASVALUE)==0 && (!table[tabindex]->longopts && !table[tabindex]->shortopts));
-		cJSON_AddBoolToObject(entry, "hasvalue", table[tabindex]->flag & ARG_HASVALUE);
-		cJSON_AddNumberToObject(entry,"mincount",table[tabindex]->mincount);
-		cJSON_AddNumberToObject(entry,"maxcount",table[tabindex]->maxcount);
-		cJSON_AddItemToArray(arg_list, entry);
-		tabindex++;
-	}
-	return arg_list;
+cJSON* ParmsToJSON(struct arg_hdr** argtable) {
+    if (!argtable) return NULL;
+    cJSON* arg_list = cJSON_CreateArray();
+    struct arg_hdr** table = (struct arg_hdr**)argtable;
+    int tabindex = 0;
+    while (!(table[tabindex]->flag & ARG_TERMINATOR)) {
+        cJSON* entry = cJSON_CreateObject();
+        ADD_TO_JSON(entry, table[tabindex], datatype);
+        ADD_TO_JSON(entry, table[tabindex], glossary);
+        ADD_TO_JSON(entry, table[tabindex], longopts);
+        ADD_TO_JSON(entry, table[tabindex], shortopts);
+        cJSON_AddBoolToObject(entry, "checkbox",
+            (table[tabindex]->flag & ARG_HASOPTVALUE) == 0 && (table[tabindex]->flag & ARG_HASVALUE) == 0 &&
+                (table[tabindex]->longopts || table[tabindex]->shortopts));
+        cJSON_AddBoolToObject(entry, "remark",
+            (table[tabindex]->flag & ARG_HASOPTVALUE) == 0 && (table[tabindex]->flag & ARG_HASVALUE) == 0 &&
+                (!table[tabindex]->longopts && !table[tabindex]->shortopts));
+        cJSON_AddBoolToObject(entry, "hasvalue", table[tabindex]->flag & ARG_HASVALUE);
+        cJSON_AddNumberToObject(entry, "mincount", table[tabindex]->mincount);
+        cJSON_AddNumberToObject(entry, "maxcount", table[tabindex]->maxcount);
+        cJSON_AddItemToArray(arg_list, entry);
+        tabindex++;
+    }
+    return arg_list;
 }
 
-esp_err_t cmd_to_json(const esp_console_cmd_t *cmd){
-	return cmd_to_json_with_cb(cmd, NULL);
-}
+esp_err_t cmd_to_json(const esp_console_cmd_t* cmd) { return cmd_to_json_with_cb(cmd, NULL); }
 
-esp_err_t cmd_to_json_with_cb(const esp_console_cmd_t *cmd, parm_values_fn_t parm_values_fn){
-	if(!cmdList){
-		cmdList=cJSON_CreateArray();
-	}
-	if(!values_fn_list){
-		values_fn_list=cJSON_CreateObject();
-	}
+esp_err_t cmd_to_json_with_cb(const esp_console_cmd_t* cmd, parm_values_fn_t parm_values_fn) {
+    if (!cmdList) {
+        cmdList = cJSON_CreateArray();
+    }
+    if (!values_fn_list) {
+        values_fn_list = cJSON_CreateObject();
+    }
 
     if (cmd->command == NULL) {
         return ESP_ERR_INVALID_ARG;
@@ -157,27 +162,26 @@ esp_err_t cmd_to_json_with_cb(const esp_console_cmd_t *cmd, parm_values_fn_t par
     if (strchr(cmd->command, ' ') != NULL) {
         return ESP_ERR_INVALID_ARG;
     }
-    cJSON * jsoncmd = cJSON_CreateObject();
-    ADD_TO_JSON(jsoncmd,cmd,help);
-    ADD_TO_JSON(jsoncmd,cmd,hint);
-	if(parm_values_fn){
-		char addr[11]={0};
-		snprintf(addr,sizeof(addr),"%lx",(unsigned long)parm_values_fn);
-		cJSON_AddStringToObject(values_fn_list,cmd->command,addr);
-	}
-	cJSON_AddBoolToObject(jsoncmd,"hascb",parm_values_fn!=NULL);
-
-    if(cmd->argtable){
-    	cJSON_AddItemToObject(jsoncmd,"argtable",ParmsToJSON(cmd->argtable));
+    cJSON* jsoncmd = cJSON_CreateObject();
+    ADD_TO_JSON(jsoncmd, cmd, help);
+    ADD_TO_JSON(jsoncmd, cmd, hint);
+    if (parm_values_fn) {
+        char addr[11] = {0};
+        snprintf(addr, sizeof(addr), "%lx", (unsigned long)parm_values_fn);
+        cJSON_AddStringToObject(values_fn_list, cmd->command, addr);
     }
-    if (cmd->hint) {
-    	cJSON_AddStringToObject(jsoncmd, "hint", cmd->hint);
+    cJSON_AddBoolToObject(jsoncmd, "hascb", parm_values_fn != NULL);
+
+    if (cmd->argtable) {
+        cJSON_AddItemToObject(jsoncmd, "argtable", ParmsToJSON(cmd->argtable));
     }
-    else if (cmd->argtable) {
+    if (cmd->hint) {
+        cJSON_AddStringToObject(jsoncmd, "hint", cmd->hint);
+    } else if (cmd->argtable) {
         /* Generate hint based on cmd->argtable */
-        char *buf = NULL;
+        char* buf = NULL;
         size_t buf_size = 0;
-        FILE *f = open_memstream(&buf, &buf_size);
+        FILE* f = open_memstream(&buf, &buf_size);
         if (f != NULL) {
             arg_print_syntax(f, cmd->argtable, NULL);
             fflush(f);
@@ -187,303 +191,237 @@ esp_err_t cmd_to_json_with_cb(const esp_console_cmd_t *cmd, parm_values_fn_t par
         FREE_AND_NULL(buf);
     }
     cJSON_AddStringToObject(jsoncmd, "name", cmd->command);
-    char * b=cJSON_Print(jsoncmd);
-    if(b){
-    	ESP_LOGD(TAG,"Adding command table %s",b);
-    	free(b);
+    char* b = cJSON_Print(jsoncmd);
+    if (b) {
+        ESP_LOGD(TAG, "Adding command table %s", b);
+        free(b);
     }
     cJSON_AddItemToArray(cmdList, jsoncmd);
     return ESP_OK;
 }
-int arg_parse_msg(int argc, char **argv, struct arg_hdr ** args){
-    int nerrors = arg_parse(argc, argv, (void **)args);
+int arg_parse_msg(int argc, char** argv, struct arg_hdr** args) {
+    int nerrors = arg_parse(argc, argv, (void**)args);
 
     if (nerrors != 0) {
-    	char *buf = NULL;
-		size_t buf_size = 0;
-		FILE *f = open_memstream(&buf, &buf_size);
-		if (f != NULL) {
-			arg_print_errors(f, getParmsEnd(args), argv[0]);
-			fflush (f);
-			cmd_send_messaging(argv[0],MESSAGING_ERROR,"%s", buf);
-		}
+        char* buf = NULL;
+        size_t buf_size = 0;
+        FILE* f = open_memstream(&buf, &buf_size);
+        if (f != NULL) {
+            arg_print_errors(f, getParmsEnd(args), argv[0]);
+            fflush(f);
+            cmd_send_messaging(argv[0], MESSAGING_ERROR, "%s", buf);
+        }
         fclose(f);
         FREE_AND_NULL(buf);
     }
     return nerrors;
 }
-void process_autoexec(){
-	int i=1;
-	char autoexec_name[21]={0};
-	char * autoexec_value=NULL;
-	uint8_t autoexec_flag=0;
-	// TODO: Add support for the commented code
-	void * cmd = run_command; 
-
-	// char * str_flag = config_alloc_get(NVS_TYPE_STR, "autoexec");
-	// if(!bypass_network_manager){
-	// 	ESP_LOGW(TAG, "Processing autoexec commands while network manager active.  Wifi related commands will be ignored.");
-	// }
-	// if(is_recovery_running){
-	// 	ESP_LOGD(TAG, "Processing autoexec commands in recovery mode.  Squeezelite commands will be ignored.");
-	// }
-	// if(str_flag !=NULL ){
-	// 	autoexec_flag=atoi(str_flag);
-	// 	ESP_LOGI(TAG,"autoexec is set to %s auto-process", autoexec_flag>0?"perform":"skip");
-	// 	if(autoexec_flag == 1) {
-	// 		do {
-	// 			snprintf(autoexec_name,sizeof(autoexec_name)-1,"autoexec%u",i++);
-	// 			ESP_LOGD(TAG,"Getting command name %s", autoexec_name);
-	// 			autoexec_value= config_alloc_get(NVS_TYPE_STR, autoexec_name);
-	// 			if(autoexec_value!=NULL ){
-	// 				if(!bypass_network_manager && strstr(autoexec_value, "join ")!=NULL ){
-	// 					ESP_LOGW(TAG,"Ignoring wifi join command.");
-	// 				}
-	// 				else if(is_recovery_running && !strstr(autoexec_value, "squeezelite " ) ){
-	// 					ESP_LOGW(TAG,"Ignoring command. ");
-	// 				}
-	// 				else {
-	// 					ESP_LOGI(TAG,"Running command %s = %s", autoexec_name, autoexec_value);
-	// 					run_command(autoexec_value);
-	// 				}
-	// 				ESP_LOGD(TAG,"Freeing memory for command %s name", autoexec_name);
-	// 				free(autoexec_value);
-	// 			}
-	// 			else {
-	// 				ESP_LOGD(TAG,"No matching command found for name %s", autoexec_name);
-	// 				break;
-	// 			}
-	// 		} while(1);
-	// 	}
-	// 	free(str_flag);
-	
-	// }
-	// else
-	// {
-	// 	ESP_LOGD(TAG,"No matching command found for name autoexec.");
-	// }
-	// TODO: Add support for the commented code
-}
-
 static ssize_t stdin_read(int fd, void* data, size_t size) {
-	size_t bytes = -1;
-	
-	while (1) {
-		QueueSetMemberHandle_t activated = xQueueSelectFromSet(stdin_redir.queue_set, portMAX_DELAY);
-	
-		if (activated == uart_queue) {
-			uart_event_t event;
-			
-			xQueueReceive(uart_queue, &event, 0);
-	
-			if (event.type == UART_DATA) {
-				bytes = uart_read_bytes(CONFIG_ESP_CONSOLE_UART_NUM, data, size < event.size ? size : event.size, 0);
-				// we have to do our own line ending translation here 
-				for (int i = 0; i < bytes; i++) if (((char*)data)[i] == '\r') ((char*)data)[i] = '\n';
-				break;
-			}	
-		} else if (xRingbufferCanRead(stdin_redir.handle, activated)) {
-			char *p = xRingbufferReceiveUpTo(stdin_redir.handle, &bytes, 0, size);
-			// we might receive strings, replace null by \n
-			for (int i = 0; i < bytes; i++) if (p[i] == '\0' || p[i] == '\r') p[i] = '\n';						
-			memcpy(data, p, bytes);
-			vRingbufferReturnItem(stdin_redir.handle, p);
-			break;
-		}
-	}	
-	
-	return bytes;
+    size_t bytes = -1;
+    static size_t remain = 0;
+    if (remain > 0) {
+        bytes = uart_read_bytes(CONFIG_ESP_CONSOLE_UART_NUM, data, size < remain ? size : remain, 0);
+        remain -= bytes;
+		for (int i = 0; i < bytes; i++)
+			if (((char*)data)[i] == '\r') ((char*)data)[i] = '\n';		
+		return bytes;
+    }
+    while (1) {
+        QueueSetMemberHandle_t activated = xQueueSelectFromSet(stdin_redir.queue_set, portMAX_DELAY);
+
+        if (activated == uart_queue) {
+            uart_event_t event;
+            xQueueReceive(uart_queue, &event, 0);
+            if (event.type == UART_DATA) {
+                bytes = uart_read_bytes(CONFIG_ESP_CONSOLE_UART_NUM, data, size < event.size ? size : event.size, 0);
+                // we have to do our own line ending translation here
+                for (int i = 0; i < bytes; i++)
+                    if (((char*)data)[i] == '\r') ((char*)data)[i] = '\n';
+                if (event.size > bytes) {
+                    remain = event.size - bytes;
+                }
+                break;
+            }
+        } else if (xRingbufferCanRead(stdin_redir.handle, activated)) {
+            char* p = xRingbufferReceiveUpTo(stdin_redir.handle, &bytes, 0, size);
+            // we might receive strings, replace null by \n
+            for (int i = 0; i < bytes; i++)
+                if (p[i] == '\0' || p[i] == '\r') p[i] = '\n';
+            memcpy(data, p, bytes);
+            vRingbufferReturnItem(stdin_redir.handle, p);
+            xRingbufferPrintInfo(stdin_redir.handle);
+            break;
+        }
+    }
+
+    return bytes;
 }
 
-static int stdin_dummy(const char * path, int flags, int mode) {	return 0; }
+static int stdin_dummy(const char* path, int flags, int mode) { return 0; }
 
 void initialize_console() {
-	/* Minicom, screen, idf_monitor send CR when ENTER key is pressed (unused if we redirect stdin) */
-	esp_vfs_dev_uart_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR);
-	/* Move the caret to the beginning of the next line on '\n' */
-	esp_vfs_dev_uart_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF);
-
-	/* Configure UART. Note that REF_TICK is used so that the baud rate remains
-	 * correct while APB frequency is changing in light sleep mode.
-	 */
-	const uart_config_t uart_config = { .baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE, 
-			.data_bits = UART_DATA_8_BITS,
-			.parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1,
-	};
-	ESP_ERROR_CHECK(uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config));
-
-	/* Install UART driver for interrupt-driven reads and writes */
-	ESP_ERROR_CHECK( uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 3, &uart_queue, 0));
-	
-	/* Tell VFS to use UART driver */
-	esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
-		
-	/* re-direct stdin to our own driver so we can gather data from various sources */
-	stdin_redir.queue_set = xQueueCreateSet(2);
-	stdin_redir.handle = xRingbufferCreateStatic(sizeof(stdin_redir._buf), RINGBUF_TYPE_BYTEBUF, stdin_redir._buf, &stdin_redir._ringbuf);
-	xRingbufferAddToQueueSetRead(stdin_redir.handle, stdin_redir.queue_set);
-	xQueueAddToSet(uart_queue, stdin_redir.queue_set);
-	
-	esp_vfs_t vfs = { };
-	vfs.flags = ESP_VFS_FLAG_DEFAULT;
-	vfs.open = stdin_dummy;
-	vfs.read = stdin_read;
-
-	ESP_ERROR_CHECK(esp_vfs_register("/dev/redirect", &vfs, NULL));
+    /* Minicom, screen, idf_monitor send CR when ENTER key is pressed (unused if we redirect stdin) */
+    esp_vfs_dev_uart_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR);
+    /* Move the caret to the beginning of the next line on '\n' */
+    esp_vfs_dev_uart_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF);
+
+    /* Configure UART. Note that REF_TICK is used so that the baud rate remains
+     * correct while APB frequency is changing in light sleep mode.
+     */
+    const uart_config_t uart_config = {
+        .baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE,
+        .data_bits = UART_DATA_8_BITS,
+        .parity = UART_PARITY_DISABLE,
+        .stop_bits = UART_STOP_BITS_1,
+    };
+    ESP_ERROR_CHECK(uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config));
+
+    /* Install UART driver for interrupt-driven reads and writes */
+    ESP_ERROR_CHECK(uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 3, &uart_queue, 0));
+
+    /* Tell VFS to use UART driver */
+    esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
+
+    /* re-direct stdin to our own driver so we can gather data from various sources */
+    stdin_redir.queue_set = xQueueCreateSet(2);
+    stdin_redir.handle = xRingbufferCreateStatic(sizeof(stdin_redir._buf), RINGBUF_TYPE_BYTEBUF, stdin_redir._buf, &stdin_redir._ringbuf);
+    xRingbufferAddToQueueSetRead(stdin_redir.handle, stdin_redir.queue_set);
+    xQueueAddToSet(uart_queue, stdin_redir.queue_set);
+
+    esp_vfs_t vfs = {};
+    vfs.flags = ESP_VFS_FLAG_DEFAULT;
+    vfs.open = stdin_dummy;
+    vfs.read = stdin_read;
+
+    ESP_ERROR_CHECK(esp_vfs_register("/dev/redirect", &vfs, NULL));
     freopen("/dev/redirect", "r", stdin);
 
-	/* Disable buffering on stdin */
-	setvbuf(stdin, NULL, _IONBF, 0);
+    /* Disable buffering on stdin */
+    setvbuf(stdin, NULL, _IONBF, 0);
 
-	/* Initialize the console */
-	esp_console_config_t console_config = { .max_cmdline_args = 28,
-			.max_cmdline_length = 600,
+    /* Initialize the console */
+    esp_console_config_t console_config = {
+        .max_cmdline_args = 28,
+        .max_cmdline_length = 600,
 #if CONFIG_LOG_COLORS
-			.hint_color = atoi(LOG_COLOR_CYAN)
+        .hint_color = atoi(LOG_COLOR_CYAN)
 #endif
-			};
-	ESP_ERROR_CHECK(esp_console_init(&console_config));
+    };
+    ESP_ERROR_CHECK(esp_console_init(&console_config));
 
-	/* Configure linenoise line completion library */
-	/* Enable multiline editing. If not set, long commands will scroll within
-	 * single line.
-	 */
-	linenoiseSetMultiLine(1);
+    /* Configure linenoise line completion library */
+    /* Enable multiline editing. If not set, long commands will scroll within
+     * single line.
+     */
+    linenoiseSetMultiLine(1);
 
-	/* Tell linenoise where to get command completions and hints */
-	linenoiseSetCompletionCallback(&esp_console_get_completion);
-	linenoiseSetHintsCallback((linenoiseHintsCallback*) &esp_console_get_hint);
+    /* Tell linenoise where to get command completions and hints */
+    linenoiseSetCompletionCallback(&esp_console_get_completion);
+    linenoiseSetHintsCallback((linenoiseHintsCallback*)&esp_console_get_hint);
 
-	/* Set command history size */
-	linenoiseHistorySetMaxLen(100);
+    /* Set command history size */
+    linenoiseHistorySetMaxLen(100);
 
-	/* Load command history from filesystem */
-	//linenoiseHistoryLoad(HISTORY_PATH);
 }
 
-bool console_push(const char *data, size_t size) {
-	return xRingbufferSend(stdin_redir.handle, data, size, pdMS_TO_TICKS(100)) == pdPASS;
-}	
+bool console_push(const char* data, size_t size) { return xRingbufferSend(stdin_redir.handle, data, size, pdMS_TO_TICKS(100)) == pdPASS; }
 
 void console_start() {
-	/* we always run console b/c telnet sends commands to stdin */
-	initialize_console();
-
-	/* Register commands */
-	MEMTRACE_PRINT_DELTA_MESSAGE("Registering help command");
-	esp_console_register_help_command();
-	MEMTRACE_PRINT_DELTA_MESSAGE("Registering system commands");
-	register_system();
-	MEMTRACE_PRINT_DELTA_MESSAGE("Registering config commands");
-	register_config_cmd();
-	MEMTRACE_PRINT_DELTA_MESSAGE("Registering wifi commands");
-	register_wifi();
-
-	if(is_recovery_running){
-		MEMTRACE_PRINT_DELTA_MESSAGE("Registering recovery commands");
-		register_ota_cmd();
-	}
-	MEMTRACE_PRINT_DELTA_MESSAGE("Registering i2c commands");
-	register_i2ctools();
-	
-	printf("\n");
-	if(is_recovery_running){
-		printf("****************************************************************\n"
-		"RECOVERY APPLICATION\n"
-		"This mode is used to flash Squeezelite into the OTA partition\n"
-		"****\n\n");
-	}
-	printf("Type 'help' to get the list of commands.\n"
-	"Use UP/DOWN arrows to navigate through command history.\n"
-	"Press TAB when typing command name to auto-complete.\n"
-	"\n");
-	if(!is_recovery_running){
-		printf("To automatically execute lines at startup:\n"
-				"\tSet NVS variable autoexec (U8) = 1 to enable, 0 to disable automatic execution.\n"
-				"\tSet NVS variable autoexec[1~9] (string)to a command that should be executed automatically\n");
-	}
-	printf("\n\n");
-
-	/* Figure out if the terminal supports escape sequences */
-	int probe_status = linenoiseProbe();
-	if (probe_status) { /* zero indicates success */
-		printf("\n****************************\n"
-				"Your terminal application does not support escape sequences.\n"
-				"Line editing and history features are disabled.\n"
-				"On Windows, try using Putty instead.\n"
-				"****************************\n");
-		linenoiseSetDumbMode(1);
-#if CONFIG_LOG_COLORS
-		/* Since the terminal doesn't support escape sequences,
-		 * don't use color codes in the prompt.
-		 */
-		if(is_recovery_running){
-			recovery_prompt=  "recovery-squeezelite-esp32>";
-		}
-		prompt = "squeezelite-esp32> ";
-#endif //CONFIG_LOG_COLORS
-	}
-	esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
-	cfg.thread_name= "console";
-	cfg.inherit_cfg = true;
-	cfg.stack_size = 4*1024;
-	if(is_recovery_running){
-		prompt = recovery_prompt;
-	}
-		MEMTRACE_PRINT_DELTA_MESSAGE("Creating console thread with stack size of 4096 bytes");
-	esp_pthread_set_cfg(&cfg);
-	pthread_create(&thread_console, NULL, console_thread, NULL);
-	MEMTRACE_PRINT_DELTA_MESSAGE("Console thread created");
 
-}
+    /* Register commands */
+    esp_console_register_help_command();
+    register_system();
+    register_config_cmd();
+    register_wifi();
 
-static esp_err_t run_command(char * line){
-	/* Try to run the command */
-	int ret;
-	esp_err_t err = esp_console_run(line, &ret);
-
-	if (err == ESP_ERR_NOT_FOUND) {
-		ESP_LOGE(TAG,"Unrecognized command: %s", line);
-	} else if (err == ESP_ERR_INVALID_ARG) {
-		// command was empty
-	} else if (err != ESP_OK && ret != ESP_OK) {
-		ESP_LOGW(TAG,"Command returned non-zero error code: 0x%x (%s)", ret,
-		esp_err_to_name(err));
-	} else if (err == ESP_OK && ret != ESP_OK) {
-		ESP_LOGW(TAG,"Command returned in error");
-		err = ESP_FAIL;
-	} else if (err != ESP_OK) {
-		ESP_LOGE(TAG,"Internal error: %s", esp_err_to_name(err));
-	}
-	return err;
+    if (is_recovery_running) {
+        register_ota_cmd();
+    }
+    register_i2ctools();
+
+    printf("\n");
+    if (is_recovery_running) {
+        printf("****************************************************************\n"
+               "RECOVERY APPLICATION\n"
+               "This mode is used to flash Squeezelite into the OTA partition\n"
+               "****\n\n");
+    }
+    printf("Type 'help' to get the list of commands.\n"
+           "Use UP/DOWN arrows to navigate through command history.\n"
+           "Press TAB when typing command name to auto-complete.\n"
+           "\n");
+    printf("\n\n");
+
+    /* Figure out if the terminal supports escape sequences */
+    int probe_status = linenoiseProbe();
+    if (probe_status) { /* zero indicates success */
+        printf("\n****************************\n"
+               "Your terminal application does not support escape sequences.\n"
+               "Line editing and history features are disabled.\n"
+               "On Windows, try using Putty instead.\n"
+               "****************************\n");
+        linenoiseSetDumbMode(1);
+#if CONFIG_LOG_COLORS
+        /* Since the terminal doesn't support escape sequences,
+         * don't use color codes in the prompt.
+         */
+        if (is_recovery_running) {
+            recovery_prompt = "recovery-squeezelite-esp32>";
+        }
+        prompt = "squeezelite-esp32> ";
+#endif // CONFIG_LOG_COLORS
+    }
+    esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
+    cfg.thread_name = "console";
+    cfg.inherit_cfg = true;
+    cfg.stack_size = 4 * 1024;
+    if (is_recovery_running) {
+        prompt = recovery_prompt;
+    }
+    esp_pthread_set_cfg(&cfg);
+    pthread_create(&thread_console, NULL, console_thread, NULL);
 }
 
-static void * console_thread() {
-	if(!is_recovery_running){
-		MEMTRACE_PRINT_DELTA_MESSAGE("Running autoexec");
-		process_autoexec();
-		MEMTRACE_PRINT_DELTA_MESSAGE("Autoexec done");
-	}
-	/* Main loop */
-	while (1) {
-		/* Get a line using linenoise.
-		 * The line is returned when ENTER is pressed.
-		 */
-		char* line = linenoise(prompt);
-		if (line == NULL) { /* Ignore empty lines */
-			continue;
-		}
-		/* Add the command to the history */
-		linenoiseHistoryAdd(line);
-
-		/* Save command history to filesystem */
-		linenoiseHistorySave(HISTORY_PATH);
-		printf("\n");
-		run_command(line);
-		/* linenoise allocates line buffer on the heap, so need to free it */
-		linenoiseFree(line);
-		taskYIELD();
-	}
-	return NULL;
+static esp_err_t run_command(char* line) {
+    /* Try to run the command */
+    int ret;
+    esp_err_t err = esp_console_run(line, &ret);
+
+    if (err == ESP_ERR_NOT_FOUND) {
+        ESP_LOGE(TAG, "Unrecognized command: %s", line);
+    } else if (err == ESP_ERR_INVALID_ARG) {
+        // command was empty
+    } else if (err != ESP_OK && ret != ESP_OK) {
+        ESP_LOGW(TAG, "Command returned non-zero error code: 0x%x (%s)", ret, esp_err_to_name(err));
+    } else if (err == ESP_OK && ret != ESP_OK) {
+        ESP_LOGW(TAG, "Command returned in error");
+        err = ESP_FAIL;
+    } else if (err != ESP_OK) {
+        ESP_LOGE(TAG, "Internal error: %s", esp_err_to_name(err));
+    }
+    return err;
 }
 
+static void* console_thread() {
+    /* Main loop */
+    while (1) {
+        /* Get a line using linenoise.
+         * The line is returned when ENTER is pressed.
+         */
+        char* line = linenoise(prompt);
+        if (line == NULL) { /* Ignore empty lines */
+            continue;
+        }
+        /* Add the command to the history */
+        linenoiseHistoryAdd(line);
+
+        /* Save command history to filesystem */
+        linenoiseHistorySave(HISTORY_PATH);
+        printf("\n");
+        run_command(line);
+        /* linenoise allocates line buffer on the heap, so need to free it */
+        linenoiseFree(line);
+        taskYIELD();
+    }
+    return NULL;
+}

+ 1 - 0
components/platform_console/platform_console.h

@@ -22,6 +22,7 @@ typedef cJSON * parm_values_fn_t(void);
 esp_err_t cmd_to_json(const esp_console_cmd_t *cmd);
 esp_err_t cmd_to_json_with_cb(const esp_console_cmd_t *cmd, parm_values_fn_t parm_values_fn);
 int arg_parse_msg(int argc, char **argv, struct arg_hdr ** args);
+void initialize_console();
 cJSON * get_cmd_list();
 #ifdef __cplusplus
 }

+ 1 - 1
components/platform_console/test/test_system.c

@@ -11,7 +11,7 @@
 #include "unity.h"
 #include "platform_console.h"
 #include "platform_esp32.h"
-// #include "Configurator.h"
+// #include "Config.h"
 #pragma message("fixme: search for TODO below")
 #include "string.h"
 struct arg_lit *arglit;

+ 1 - 1
components/raop/CMakeLists.txt

@@ -1,7 +1,7 @@
 
 idf_component_register(SRC_DIRS .    
 						INCLUDE_DIRS .   
-						PRIV_REQUIRES newlib freertos pthread platform_config mdns services codecs tools display wifi-manager
+						PRIV_REQUIRES newlib freertos pthread tools platform_config mdns services codecs  display wifi-manager
 						  
 )
 set_source_files_properties(raop.c

+ 1 - 1
components/raop/raop_sink.c

@@ -9,7 +9,7 @@
 #include "esp_pthread.h"
 #include "esp_system.h"
 #include "freertos/timers.h"
-#include "Configurator.h"
+#include "Config.h"
 #include "raop.h"
 #include "audio_controls.h"
 #include "display.h"

+ 2 - 1
components/services/CMakeLists.txt

@@ -1,5 +1,6 @@
 idf_component_register(SRC_DIRS .
 						INCLUDE_DIRS .
-						REQUIRES json tools platform_config display wifi-manager esp-tls platform_config
+						REQUIRES json tools platform_config display wifi-manager esp-tls 
 						PRIV_REQUIRES soc esp32
 )
+

+ 27 - 29
components/services/accessors.c

@@ -5,15 +5,13 @@
    software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    CONDITIONS OF ANY KIND, either express or implied.
 */
-#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
-
+#define LOG_LOCAL_LEVEL ESP_LOG_INFO
 #include <stdio.h>
 #include "esp_log.h"
 #include "driver/gpio.h"
 #include "driver/i2c.h"
 #include "driver/spi_master.h"
-// #include "Configurator.h"
-#pragma message("fixme: look for TODO below")
+#include "Config.h"
 #include "accessors.h"
 #include "globdefs.h"
 #include "display.h"
@@ -94,12 +92,20 @@ bool is_dac_config_locked(){
 }
 
 
-
+const sys_i2c_bus* get_i2c_bus(i2c_port_t port){
+    if(platform->dev.has_i2c && port == platform->dev.i2c.port-sys_i2c_port_PORT0 && platform->dev.i2c.scl>=0){
+        return &platform->dev.i2c;
+    }
+    if(platform->dev.has_dac && platform->dev.dac.has_i2c && platform->dev.dac.i2c.port == port && platform->dev.dac.i2c.scl>=0){
+        return &platform->dev.dac.i2c;
+    }
+    return NULL;
+}
 /****************************************************************************************
  * 
  */
-const i2c_config_t * config_i2c_get(int * i2c_port) {
-	sys_I2CBus * bus = NULL;
+const i2c_config_t * config_i2c_get(sys_i2c_bus * bus) {
+	 
 	static i2c_config_t i2c = {
 		.mode = I2C_MODE_MASTER,
 		.sda_io_num = -1,
@@ -109,38 +115,30 @@ const i2c_config_t * config_i2c_get(int * i2c_port) {
 		.master.clk_speed = 0,
 	};
 
-	if(SYS_I2CBUS(bus)){
-		if(bus->has_scl){
-			i2c.scl_io_num = bus->scl.pin;
+	if(bus && bus->scl >=0  && bus->sda >=0 ){
+		if(bus->scl>=0){
+			i2c.scl_io_num = bus->scl;
 		}
 		else {
 			ESP_LOGE(TAG,"%s missing for i2c","SCL");
 		}
 		
-		if(bus->has_sda){
-			i2c.sda_io_num= bus->sda.pin;
+		if(bus->sda>=0){
+			i2c.sda_io_num= bus->sda;
 		}
 		else {
 			ESP_LOGE(TAG,"%s missing for i2c","SDA");
 		}
 		if(bus->speed>0){
 			i2c.master.clk_speed = bus->speed;
-		}
-		if(bus->port != sys_I2CPortEnum_UNSPECIFIED_PORT){
-			i2c_system_port = bus->port - sys_I2CPortEnum_I2CPort0;
-		}
-		// TODO: untangle i2c system port handling
-		if(i2c_port) {
-			*i2c_port=i2c_system_port;
-		}
-		
+		}		
 	}
 
 	return &i2c;
 }
 
 
-void config_set_gpio(int * pin, sys_GPIO * gpio,bool has_value, const char * name, bool mandatory){
+void config_set_gpio(int * pin, sys_gpio_config * gpio,bool has_value, const char * name, bool mandatory){
 	if(has_value){
 		ESP_LOGD(TAG, "Setting pin %d as %s", gpio->pin, name);
 		if(pin) *pin= gpio->pin;
@@ -166,14 +164,14 @@ const spi_bus_config_t * config_spi_get(spi_host_device_t * spi_host) {
         .quadhd_io_num = -1
     };
 	if(platform->has_dev && platform->dev.has_spi){
-		ESP_LOGI(TAG,"SPI Configuration found");
-		ASSIGN_GPIO(spi.mosi_io_num,platform->dev.spi,mosi,true);
-		ASSIGN_GPIO(spi.miso_io_num,platform->dev.spi,miso,false);
-		ASSIGN_GPIO(spi.sclk_io_num,platform->dev.spi,clk,true);
-		ASSIGN_GPIO(spi_system_dc_gpio,platform->dev.spi,dc,true);
+		ESP_LOGD(TAG,"SPI Configuration found");
+		spi.mosi_io_num = platform->dev.spi.mosi;
+		spi.miso_io_num = platform->dev.spi.miso;
+		spi.sclk_io_num = platform->dev.spi.clk;
+		spi_system_dc_gpio = platform->dev.spi.dc;
 		// only VSPI (1) can be used as Flash and PSRAM run at 80MHz
-		if(platform->dev.spi.host!=sys_HostEnum_UNSPECIFIED_HOST){
-			spi_system_host = platform->dev.spi.host;
+		if(platform->dev.spi.host!=sys_dev_common_hosts_NONE){
+			spi_system_host = platform->dev.spi.host-sys_dev_common_hosts_Host0;
 		}
 	}
 	else {

+ 14 - 11
components/services/accessors.h

@@ -13,6 +13,7 @@
 #include "driver/i2s.h"
 #include "driver/spi_master.h"
 #include "gpio_exp.h"
+#include "I2CBus.pb.h"
 #define ASSIGN_GPIO(pin, root, name, mandatory) config_set_gpio(&pin, &(root).name, (root).has_##name, #name, mandatory)
 
 #define ASSIGN_COND_VAL_1LVL(name, targetval) \
@@ -31,7 +32,9 @@
 #define SYS_NET(target) ASSIGN_COND_VAL_1LVL(net,target)
 #define SYS_DISPLAY(target) ASSIGN_COND_VAL_2LVL(dev,display,target)
 #define SYS_DEV_LEDSTRIP(target) ASSIGN_COND_VAL_2LVL(dev,led_strip,target)
+#define SYS_DEV(target) ASSIGN_COND_VAL_1LVL(dev,target)
 #define SYS_DEV_ROTARY(target) ASSIGN_COND_VAL_2LVL(dev,rotary,target)
+#define SYS_DEV_IR(target) ASSIGN_COND_VAL_2LVL(dev,ir,target)
 #define SYS_DISPLAY_COMMON(target) ASSIGN_COND_VAL_3LVL(dev,display,common,target)
 #define SYS_DISPLAY_SPI(target) ASSIGN_COND_VAL_3LVL(dev,display,spi,target)
 #define SYS_DISPLAY_I2C(target) ASSIGN_COND_VAL_3LVL(dev,display,i2c,target)
@@ -40,25 +43,25 @@
 #define SYS_ETH_COMMON(target) ASSIGN_COND_VAL_3LVL(dev,eth,common,target)
 
 
-#define SYS_I2CBUS(target) ASSIGN_COND_VAL_2LVL(dev,i2c,target)
+#define sys_i2c_bus(target) ASSIGN_COND_VAL_2LVL(dev,i2c,target)
 #define SYS_GPIOS_NAME(name,target) ASSIGN_COND_VAL_2LVL(gpios,name,target)
-#define SYS_SERVICES(target) ASSIGN_COND_VAL_1LVL(services,target)
-#define SYS_SERVICES_SPOTIFY(target) ASSIGN_COND_VAL_2LVL(services,cspot,target)
-#define SYS_SERVICES_METADATA(target) ASSIGN_COND_VAL_2LVL(services,metadata,target)
-#define SYS_SERVICES_AIRPLAY(target) ASSIGN_COND_VAL_2LVL(services,airplay,target)
-#define SYS_SERVICES_SLEEP(target) ASSIGN_COND_VAL_2LVL(services,sleep,target)
-#define SYS_SERVICES_EQUALIZER(target) ASSIGN_COND_VAL_2LVL(services,equalizer,target)
+#define sys_services_config(target) ASSIGN_COND_VAL_1LVL(services,target)
+#define sys_services_config_SPOTIFY(target) ASSIGN_COND_VAL_2LVL(services,cspot,target)
+#define sys_services_config_METADATA(target) ASSIGN_COND_VAL_2LVL(services,metadata,target)
+#define sys_services_config_AIRPLAY(target) ASSIGN_COND_VAL_2LVL(services,airplay,target)
+#define sys_services_config_SLEEP(target) ASSIGN_COND_VAL_2LVL(services,sleep,target)
+#define sys_services_config_EQUALIZER(target) ASSIGN_COND_VAL_2LVL(services,equalizer,target)
 
-#define SYS_SERVICES_TELNET(target) ASSIGN_COND_VAL_2LVL(services,telnet,target)
-#define SYS_SERVICES_BTSINK(target) ASSIGN_COND_VAL_2LVL(services,bt_sink,target)
-#define SYS_SERVICES_SQUEEZELITE(target) ASSIGN_COND_VAL_2LVL(services,squeezelite,target)
+#define sys_services_config_TELNET(target) ASSIGN_COND_VAL_2LVL(services,telnet,target)
+#define sys_services_config_BTSINK(target) ASSIGN_COND_VAL_2LVL(services,bt_sink,target)
 
 
 #define SYS_DEV_BATTERY(target) ASSIGN_COND_VAL_2LVL(dev,battery,target)
 
-const i2c_config_t * 		config_i2c_get(int * i2c_port);
+const i2c_config_t * 		config_i2c_get(sys_i2c_bus * bus);
 const spi_bus_config_t * 	config_spi_get(spi_host_device_t * spi_host);
 const gpio_exp_config_t *   config_gpio_exp_get(int index);
+const sys_i2c_bus*           get_i2c_bus(i2c_port_t port);
 
 bool 						is_dac_config_locked();
 bool 						are_statistics_enabled();

+ 419 - 487
components/services/audio_controls.c

@@ -1,4 +1,4 @@
-/* 
+/*
  *  audio control callbacks
  *
  *  (c) Sebastien 2019
@@ -8,113 +8,93 @@
  *  https://opensource.org/licenses/MIT
  *
  */
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#include "audio_controls.h"
+#include "Config.h"
+#include "accessors.h"
+#include "buttons.h"
+#include "cJSON.h"
+#include "esp_log.h"
+#include "esp_system.h"
 #include "freertos/FreeRTOS.h"
 #include "freertos/timers.h"
-#include "esp_system.h"
-#include "esp_log.h"
-#include "cJSON.h"
-#include "buttons.h"
-#include "Configurator.h"
-#include "accessors.h"
 #include "services.h"
-#include "audio_controls.h"
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 
-typedef esp_err_t (actrls_config_map_handler) (const cJSON * member, actrls_config_t *cur_config,uint32_t offset);
+typedef esp_err_t(actrls_config_map_handler)(const cJSON* member, actrls_config_t* cur_config, uint32_t offset);
 typedef struct {
-	char * member;
-	uint32_t offset;
-	actrls_config_map_handler * handler;
+    char* member;
+    uint32_t offset;
+    actrls_config_map_handler* handler;
 } actrls_config_map_t;
 
-static esp_err_t actrls_process_member(const cJSON * member, actrls_config_t *cur_config);
-static esp_err_t actrls_process_button(const cJSON * button, actrls_config_t *cur_config);
-static esp_err_t actrls_process_int (const cJSON * member, actrls_config_t *cur_config, uint32_t offset);
-static esp_err_t actrls_process_type (const cJSON * member, actrls_config_t *cur_config, uint32_t offset);
-static esp_err_t actrls_process_bool (const cJSON * member, actrls_config_t *cur_config, uint32_t offset);
-static esp_err_t actrls_process_action (const cJSON * member, actrls_config_t *cur_config, uint32_t offset);
-
-static esp_err_t actrls_init_json(const char *profile_name, bool create);
-static void control_rotary_handler(void *client, rotary_event_e event, bool long_press);
-static void rotary_timer( TimerHandle_t xTimer );
-
-static const actrls_config_map_t actrls_config_map[] =
-		{
-			{"gpio", offsetof(actrls_config_t,gpio), actrls_process_int},
-			{"debounce", offsetof(actrls_config_t,debounce), actrls_process_int},
-			{"type", offsetof(actrls_config_t,type), actrls_process_type},
-			{"pull", offsetof(actrls_config_t,pull), actrls_process_bool},
-			{"long_press", offsetof(actrls_config_t,long_press),actrls_process_int},
-			{"shifter_gpio", offsetof(actrls_config_t,shifter_gpio), actrls_process_int},
-			{"normal", offsetof(actrls_config_t,normal), actrls_process_action},
-			{"shifted", offsetof(actrls_config_t,shifted), actrls_process_action},
-			{"longpress", offsetof(actrls_config_t,longpress), actrls_process_action},
-			{"longshifted", offsetof(actrls_config_t,longshifted), actrls_process_action},
-			{"", 0, NULL}
-		};
-
-// BEWARE: the actions below need to stay aligned with the corresponding enum to properly support json parsing
-//   along with the actrls_t controls in LMS_controls, bt_sink and raop_sink
-#define EP(x) [x] = #x  /* ENUM PRINT */
-static const char * actrls_action_s[ ] = { EP(ACTRLS_POWER),EP(ACTRLS_VOLUP),EP(ACTRLS_VOLDOWN),EP(ACTRLS_TOGGLE),EP(ACTRLS_PLAY),
-									EP(ACTRLS_PAUSE),EP(ACTRLS_STOP),EP(ACTRLS_REW),EP(ACTRLS_FWD),EP(ACTRLS_PREV),EP(ACTRLS_NEXT),
-									EP(BCTRLS_UP),EP(BCTRLS_DOWN),EP(BCTRLS_LEFT),EP(BCTRLS_RIGHT), 
-									EP(BCTRLS_PS0),EP(BCTRLS_PS1),EP(BCTRLS_PS2),EP(BCTRLS_PS3),EP(BCTRLS_PS4),EP(BCTRLS_PS5),EP(BCTRLS_PS6),EP(BCTRLS_PS7),EP(BCTRLS_PS8),EP(BCTRLS_PS9),
-									EP(KNOB_LEFT),EP(KNOB_RIGHT),EP(KNOB_PUSH), EP(ACTRLS_SLEEP),
-									""} ;
-									
-static const char * TAG = "audio controls";
-static actrls_config_t *json_config;
-cJSON * control_profiles = NULL;
+static void actrls_parse_action(const char* name);
+static esp_err_t actrls_init_profile(const char* profile_name, bool create);
+
+static esp_err_t actrls_process_press(const sys_btns_press* action, sys_btns_press* cur_config_press);
+static esp_err_t actrls_process_button(const sys_btns_btn* button, actrls_config_t* cur_config);
+static void control_rotary_handler(void* client, rotary_event_e event, bool long_press);
+static void rotary_timer(TimerHandle_t xTimer);
+
+static const char* TAG = "audio controls";
+static actrls_config_t* json_config;
+cJSON* control_profiles = NULL;
 static EXT_RAM_ATTR actrls_t default_controls, current_controls;
 static actrls_hook_t *default_hook, *current_hook;
 static bool default_raw_controls, current_raw_controls;
 static actrls_ir_handler_t *default_ir_handler, *current_ir_handler;
 
 static EXT_RAM_ATTR struct {
-	bool long_state;
-	bool volume_lock;
-	TimerHandle_t timer;
-	bool click_pending;
-	int left_count;
+    bool long_state;
+    bool volume_lock;
+    TimerHandle_t timer;
+    bool click_pending;
+    int left_count;
 } rotary;
 
-static const struct ir_action_map_s{
-		uint32_t code;
-		actrls_action_e action;
-} ir_action_map[] = {	
-	{0x7689b04f, BCTRLS_DOWN}, {0x7689906f, BCTRLS_LEFT}, {0x7689d02f, BCTRLS_RIGHT}, {0x7689e01f, BCTRLS_UP},
-	{0x768900ff, ACTRLS_VOLDOWN}, {0x7689807f, ACTRLS_VOLUP}, 
-	{0x7689c03f, ACTRLS_PREV}, {0x7689a05f, ACTRLS_NEXT},
-	{0x768920df, ACTRLS_PAUSE}, {0x768910ef, ACTRLS_PLAY},
-	{0x00, 0x00},
+static const struct ir_action_map_s {
+    uint32_t code;
+    sys_btns_actions action;
+} ir_action_map[] = {
+    {0x7689b04f, sys_btns_actions_B_DOWN},
+    {0x7689906f, sys_btns_actions_B_LEFT},
+    {0x7689d02f, sys_btns_actions_B_RIGHT},
+    {0x7689e01f, sys_btns_actions_B_UP},
+    {0x768900ff, sys_btns_actions_A_VOLDOWN},
+    {0x7689807f, sys_btns_actions_A_VOLUP},
+    {0x7689c03f, sys_btns_actions_A_PREV},
+    {0x7689a05f, sys_btns_actions_A_NEXT},
+    {0x768920df, sys_btns_actions_A_PAUSE},
+    {0x768910ef, sys_btns_actions_A_PLAY},
+    {0x00, 0x00},
 };
 
 /****************************************************************************************
  * This function can be called to map IR codes to default actions
  */
 bool actrls_ir_action(uint16_t addr, uint16_t cmd) {
-	uint32_t code = (addr << 16) | cmd;
-	struct ir_action_map_s const *map = ir_action_map;
-	
-	while (map->code && map->code != code) map++;
-	
-	if (map->code && current_controls[map->action]) {
-		current_controls[map->action](true);
-		return true;
-	} else {
-		return false;	
-	}	
+    uint32_t code = (addr << 16) | cmd;
+    struct ir_action_map_s const* map = ir_action_map;
+
+    while (map->code && map->code != code)
+        map++;
+
+    if (map->code && current_controls[map->action]) {
+        current_controls[map->action](true);
+        return true;
+    } else {
+        return false;
+    }
 }
 
 /****************************************************************************************
- * 
+ *
  */
 static void ir_handler(uint16_t addr, uint16_t cmd) {
-	ESP_LOGD(TAG, "recaived IR %04hx:%04hx", addr, cmd);
-	if (current_ir_handler) current_ir_handler(addr, cmd);
+    ESP_LOGD(TAG, "recaived IR %04hx:%04hx", addr, cmd);
+    if (current_ir_handler) current_ir_handler(addr, cmd);
 }
 
 /****************************************************************************************
@@ -122,484 +102,436 @@ static void ir_handler(uint16_t addr, uint16_t cmd) {
  */
 esp_err_t actrls_init(const char* profile_name) {
     esp_err_t err = ESP_OK;
-    sys_Rotary* dev_config = NULL;
-    if (!SYS_DEV_ROTARY(dev_config) ) {
+    sys_dev_ir* ir = NULL;
+    sys_btns_rotary* dev_config = NULL;
+    if (!SYS_DEV_ROTARY(dev_config)) {
         ESP_LOGD(TAG, "Rotary not configured");
         return ESP_OK;
     }
 
     char* p;
     int A = -1, B = -1, SW = -1, longpress = 0;
-    A = dev_config->A.pin;
-    B = dev_config->B.pin;
-    SW = dev_config->SW.pin;
-
-    if (dev_config->has_knobonly && dev_config->knobonly.enable) {
-        p = strchr(p, '=');
-
-        int double_press =
-            dev_config->knobonly.delay_ms > 0 ? dev_config->knobonly.delay_ms : 350;
-        rotary.timer =
-            xTimerCreate("knobTimer", double_press / portTICK_RATE_MS, pdFALSE, NULL, rotary_timer);
-        longpress = 500;
-        ESP_LOGI(TAG, "single knob navigation %d", double_press);
-    } else {
-
-        if (dev_config->volume) rotary.volume_lock = true;
-        if (dev_config->longpress) longpress = 1000;
-    }
+    A = dev_config->A;
+    B = dev_config->B;
+    SW = dev_config->SW;
+    if (A >= 0 && B >= 0) {
+        if (dev_config->has_knobonly && dev_config->knobonly.enable) {
+            p = strchr(p, '=');
+
+            int double_press = dev_config->knobonly.delay_ms > 0 ? dev_config->knobonly.delay_ms : 350;
+            rotary.timer = xTimerCreate("knobTimer", double_press / portTICK_RATE_MS, pdFALSE, NULL, rotary_timer);
+            longpress = 500;
+            ESP_LOGI(TAG, "single knob navigation %d", double_press);
+        } else {
 
-    // create rotary (no handling of long press)
-    err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL;
+            if (dev_config->volume) rotary.volume_lock = true;
+            if (dev_config->longpress) longpress = 1000;
+        }
 
-    if (platform->dev.has_ir && platform->dev.ir.gpio.pin >= 0) {
+        // create rotary (no handling of long press)
+        err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL;
+    } else {
+        ESP_LOGI(TAG, "Rotary control not configured.");
+    }
 
-        if (platform->dev.ir.type == sys_InfraredType_IR_NEC) {
-            create_infrared(platform->dev.ir.gpio.pin, ir_handler, IR_RC5);
+    if (SYS_DEV_IR(ir) && ir->gpio >= 0 && ir->type != sys_dev_ir_types_IR_UNKNOWN) {
+        ESP_LOGD(TAG, "Infrared config found on pin %d, protocol: %s", ir->gpio, sys_dev_ir_types_name(ir->type));
+        if (ir->type == sys_dev_ir_types_IR_NEC) {
+            create_infrared(ir->gpio, ir_handler, IR_RC5);
         } else {
-            create_infrared(platform->dev.ir.gpio.pin, ir_handler, IR_NEC);
+            create_infrared(ir->gpio, ir_handler, IR_NEC);
         }
     }
     if (!err)
-        return actrls_init_json(profile_name, true);
+        return actrls_init_profile(profile_name, true);
     else
         return err;
     return err;
 }
 
 /****************************************************************************************
- * 
+ *
  */
-static void control_handler(void *client, button_event_e event, button_press_e press, bool long_press) {
-	actrls_config_t *key = (actrls_config_t*) client;
-	actrls_action_detail_t  action_detail;
-
-	switch(press) {
-	case BUTTON_NORMAL:
-		if (long_press) action_detail = key->longpress[event == BUTTON_PRESSED ? 0 : 1];
-		else action_detail = key->normal[event == BUTTON_PRESSED ? 0 : 1];
-		break;
-	case BUTTON_SHIFTED:
-		if (long_press) action_detail = key->longshifted[event == BUTTON_PRESSED ? 0 : 1];
-		else action_detail = key->shifted[event == BUTTON_PRESSED ? 0 : 1];
-		break;
-	default:
-		action_detail.action = ACTRLS_NONE;
-		break;
-	}
-	
-	ESP_LOGD(TAG, "control gpio:%u press:%u long:%u event:%u action:%u", key->gpio, press, long_press, event, action_detail.action);
-
-	// stop here if control hook served the request
-	if (current_hook && (*current_hook)(key->gpio, action_detail.action, event, press, long_press)) return;
-    
-   	// in raw mode, we just do normal action press *and* release, there is no longpress nor shift
-	if (current_raw_controls && action_detail.action != ACTRLS_SLEEP) {
-        actrls_action_e action = key->normal[0].action != ACTRLS_NONE ? key->normal[0].action : key->normal[1].action;
-		ESP_LOGD(TAG, "calling action %u in raw mode", action);
-		if (action != ACTRLS_NONE && current_controls[action]) current_controls[action](event == BUTTON_PRESSED);
-		return;
-	}
-
-	// otherwise process using configuration
-	if (action_detail.action == ACTRLS_REMAP) {
-		// remap requested
-		ESP_LOGD(TAG, "remapping buttons to profile %s",action_detail.name);
-		cJSON * profile_obj = cJSON_GetObjectItem(control_profiles,action_detail.name);
-		if (profile_obj) {
-			actrls_config_t *cur_config  = (actrls_config_t *) cJSON_GetStringValue(profile_obj);
-			if (cur_config) {
-				ESP_LOGD(TAG,"Remapping all the buttons that are found in the new profile");
-				while (cur_config->gpio != -1) {
-					ESP_LOGD(TAG,"Remapping button with gpio %u", cur_config->gpio);
-					button_remap((void*) cur_config, cur_config->gpio, control_handler, cur_config->long_press, cur_config->shifter_gpio);
-					cur_config++;
-				}
-			} else {
-				ESP_LOGE(TAG,"Profile %s exists, but is empty. Cannot remap buttons",action_detail.name);
-			}
-		} else {
-			ESP_LOGE(TAG,"Invalid profile name %s. Cannot remap buttons",action_detail.name);
-		}	
-	} else if (action_detail.action == ACTRLS_SLEEP) {
-        ESP_LOGI(TAG, "special sleep button pressed");
-        services_sleep_activate(SLEEP_ONKEY);
-    } else if (action_detail.action != ACTRLS_NONE) {
-		ESP_LOGD(TAG, "calling action %u", action_detail.action);
-		if (current_controls[action_detail.action]) (*current_controls[action_detail.action])(event == BUTTON_PRESSED);
-	}	
+static const char* get_action_desc(sys_btns_action* action_detail) {
+    if (action_detail->type != sys_btns_actions_REMAP) {
+        return sys_btns_actions_name(action_detail->type);
+    }
+    return STR_OR_ALT(action_detail->profile_name, "");
 }
 
 /****************************************************************************************
- * 
+ *
  */
-static void control_rotary_handler(void *client, rotary_event_e event, bool long_press) {
-	actrls_action_e action = ACTRLS_NONE;
-	bool pressed = true;
-	
-	// in raw mode, we just pass rotary events
-	if (current_raw_controls) {
-		if (event == ROTARY_LEFT) (*current_controls[KNOB_LEFT])(true);
-		else if (event == ROTARY_RIGHT) (*current_controls[KNOB_RIGHT])(true);
-		else (*current_controls[KNOB_PUSH])(event == ROTARY_PRESSED);
-		return;
-	}
-	
-	switch(event) {
-	case ROTARY_LEFT:
-		if (rotary.timer) {
-			if (rotary.left_count) {
-				action = KNOB_LEFT;
-				// need to add a left button the first time
-				if (rotary.left_count == 1) (*current_controls[KNOB_LEFT])(true);
-			}
-			xTimerStart(rotary.timer, 20 / portTICK_RATE_MS);
-			rotary.left_count++;
-		}
-		else if (rotary.long_state) action = ACTRLS_PREV;
-		else if (rotary.volume_lock) action = ACTRLS_VOLDOWN;
-		else action = KNOB_LEFT;
-		break;
-	case ROTARY_RIGHT:
-		if (rotary.timer) {
-			if (rotary.left_count == 1) {
-				action = ACTRLS_PAUSE;
-				rotary.left_count = 0;
-				xTimerStop(rotary.timer, 0);
-			} else action = KNOB_RIGHT;
-		}	
-		else if (rotary.long_state) action = ACTRLS_NEXT;
-		else if (rotary.volume_lock) action = ACTRLS_VOLUP;
-		else action = KNOB_RIGHT;
-		break;
-	case ROTARY_PRESSED:
-		if (rotary.timer) {
-			if (long_press) action = ACTRLS_PLAY;
-			else if (rotary.click_pending) {
-				action = BCTRLS_LEFT;
-				xTimerStop(rotary.timer, 0);
-			} 
-			else xTimerStart(rotary.timer, 20 / portTICK_RATE_MS);
-			rotary.click_pending = !rotary.click_pending;
-		} 
-		else if (long_press) rotary.long_state = !rotary.long_state;
-		else if (rotary.volume_lock) action = ACTRLS_TOGGLE;
-		else action = KNOB_PUSH;
-		break;
-	default:
-		break;
-	}
-	
-	if (action != ACTRLS_NONE) (*current_controls[action])(pressed);
-}
+static void control_handler(void* client, button_event_e event, button_press_e press, bool long_press) {
+    actrls_config_t* key = (actrls_config_t*)client;
+    sys_btns_action* action_detail;
+    static sys_btns_action actionNone = sys_btns_action_init_default;
+
+    switch (press) {
+    case BUTTON_NORMAL:
+        if (long_press)
+            action_detail = (event == BUTTON_PRESSED ? &key->longpress.pressed : &key->longpress.released);
+        else
+            action_detail = (event == BUTTON_PRESSED ? &key->normal.pressed : &key->normal.released);
+        break;
+    case BUTTON_SHIFTED:
+        if (long_press)
+            action_detail = (event == BUTTON_PRESSED ? &key->longshifted.pressed : &key->longshifted.released);
+        else
+            action_detail = (event == BUTTON_PRESSED ? &key->shifted.pressed : &key->shifted.released);
+        break;
+    default:
+        action_detail = &actionNone;
+        break;
+    }
 
-/****************************************************************************************
- * 
- */
-static void rotary_timer( TimerHandle_t xTimer ) {
-	if (rotary.click_pending) {
-		(*current_controls[KNOB_PUSH])(true);
-		rotary.click_pending = false;
-	} else if (rotary.left_count) {
-		if (rotary.left_count == 1) (*current_controls[KNOB_LEFT])(true);
-		rotary.left_count = 0;
-	}
-}
+    ESP_LOGD(TAG, "control gpio:%u press:%u long:%u event:%u action:%s", key->gpio, press, long_press, event, get_action_desc(action_detail));
 
-/****************************************************************************************
- * 
- */
-static actrls_action_e actrls_parse_action_json(const char * name){
-	actrls_action_e action = ACTRLS_NONE;
-	
-	if(!strcasecmp("ACTRLS_NONE",name)) return ACTRLS_NONE;
-	for(int i=0;i<ACTRLS_MAX && actrls_action_s[i][0]!='\0' ;i++){
-		if(!strcmp(actrls_action_s[i], name)){
-			return (actrls_action_e) i;
-		}
-	}
-	// Action name wasn't recognized.
-	// Check if this is a profile name that has a match in nvs
-	ESP_LOGD(TAG,"unknown action %s, trying to find matching profile ", name);
-	cJSON * existing = cJSON_GetObjectItem(control_profiles, name);
-
-	if (!existing) {
-		ESP_LOGD(TAG,"Loading new audio control profile with name: %s", name);
-		if (actrls_init_json(name, false) == ESP_OK) {
-			action = ACTRLS_REMAP;
-		}
-	} else {
-		ESP_LOGD(TAG,"Existing profile %s was referenced", name);
-		action = ACTRLS_REMAP;
-	}
-
-	return action;
+    // stop here if control hook served the request
+    if (current_hook && (*current_hook)(key->gpio, action_detail, event, press, long_press)) return;
+
+    // in raw mode, we just do normal action press *and* release, there is no longpress nor shift
+    if (current_raw_controls && action_detail->type != sys_btns_actions_A_SLEEP) {
+        sys_btns_actions action =
+            key->normal.has_pressed && key->normal.pressed.type != sys_btns_actions_A_NONE ? key->normal.pressed.type : key->normal.released.type;
+        ESP_LOGD(TAG, "calling action %s in raw mode", sys_btns_actions_name(action));
+        if (action != sys_btns_actions_A_NONE && current_controls[action]) current_controls[action](event == BUTTON_PRESSED);
+        return;
+    }
+
+    // otherwise process using configuration
+    if (action_detail->type == sys_btns_actions_REMAP) {
+        // remap requested
+        ESP_LOGD(TAG, "remapping buttons to profile %s", action_detail->profile_name);
+        cJSON* profile_obj = cJSON_GetObjectItem(control_profiles, action_detail->profile_name);
+        if (profile_obj) {
+            actrls_config_t* cur_config = (actrls_config_t*)cJSON_GetStringValue(profile_obj);
+            if (cur_config) {
+                ESP_LOGD(TAG, "Remapping all the buttons that are found in the new profile");
+                while (cur_config->gpio != -1) {
+                    ESP_LOGD(TAG, "Remapping button with gpio %u", cur_config->gpio);
+                    button_remap((void*)cur_config, cur_config->gpio, control_handler, cur_config->long_press, cur_config->shifter_gpio);
+                    cur_config++;
+                }
+            } else {
+                ESP_LOGE(TAG, "Profile %s exists, but is empty. Cannot remap buttons", action_detail->profile_name);
+            }
+        } else {
+            ESP_LOGE(TAG, "Invalid profile name %s. Cannot remap buttons", action_detail->profile_name);
+        }
+    } else if (action_detail->type == sys_btns_actions_A_SLEEP) {
+        ESP_LOGI(TAG, "special sleep button pressed");
+        services_sleep_activate(SLEEP_ONKEY);
+    } else if (action_detail->type != sys_btns_actions_A_NONE) {
+        ESP_LOGD(TAG, "calling action %s", sys_btns_actions_name(action_detail->type));
+        if (current_controls[action_detail->type]) (*current_controls[action_detail->type])(event == BUTTON_PRESSED);
+    }
 }
 
 /****************************************************************************************
- * 
+ *
  */
-static esp_err_t actrls_process_int (const cJSON * member, actrls_config_t *cur_config,uint32_t offset){
-	esp_err_t err = ESP_OK;
-	ESP_LOGD(TAG,"Processing int member");
-	int *value = (int*)((char*) cur_config + offset);
-	*value = member->valueint;
-	return err;
+static void control_rotary_handler(void* client, rotary_event_e event, bool long_press) {
+    sys_btns_actions action = sys_btns_actions_A_NONE;
+    bool pressed = true;
+
+    // in raw mode, we just pass rotary events
+    if (current_raw_controls) {
+        if (event == ROTARY_LEFT)
+            (*current_controls[sys_btns_actions_KNOB_LEFT])(true);
+        else if (event == ROTARY_RIGHT)
+            (*current_controls[sys_btns_actions_KNOB_RIGHT])(true);
+        else
+            (*current_controls[sys_btns_actions_KNOB_PUSH])(event == ROTARY_PRESSED);
+        return;
+    }
+
+    switch (event) {
+    case ROTARY_LEFT:
+        if (rotary.timer) {
+            if (rotary.left_count) {
+                action = sys_btns_actions_KNOB_LEFT;
+                // need to add a left button the first time
+                if (rotary.left_count == 1) (*current_controls[sys_btns_actions_KNOB_LEFT])(true);
+            }
+            xTimerStart(rotary.timer, 20 / portTICK_RATE_MS);
+            rotary.left_count++;
+        } else if (rotary.long_state)
+            action = sys_btns_actions_A_PREV;
+        else if (rotary.volume_lock)
+            action = sys_btns_actions_A_VOLDOWN;
+        else
+            action = sys_btns_actions_KNOB_LEFT;
+        break;
+    case ROTARY_RIGHT:
+        if (rotary.timer) {
+            if (rotary.left_count == 1) {
+                action = sys_btns_actions_A_PAUSE;
+                rotary.left_count = 0;
+                xTimerStop(rotary.timer, 0);
+            } else
+                action = sys_btns_actions_KNOB_RIGHT;
+        } else if (rotary.long_state)
+            action = sys_btns_actions_A_NEXT;
+        else if (rotary.volume_lock)
+            action = sys_btns_actions_A_VOLUP;
+        else
+            action = sys_btns_actions_KNOB_RIGHT;
+        break;
+    case ROTARY_PRESSED:
+        if (rotary.timer) {
+            if (long_press)
+                action = sys_btns_actions_A_PLAY;
+            else if (rotary.click_pending) {
+                action = sys_btns_actions_B_LEFT;
+                xTimerStop(rotary.timer, 0);
+            } else
+                xTimerStart(rotary.timer, 20 / portTICK_RATE_MS);
+            rotary.click_pending = !rotary.click_pending;
+        } else if (long_press)
+            rotary.long_state = !rotary.long_state;
+        else if (rotary.volume_lock)
+            action = sys_btns_actions_A_TOGGLE;
+        else
+            action = sys_btns_actions_KNOB_PUSH;
+        break;
+    default:
+        break;
+    }
+
+    if (action != sys_btns_actions_A_NONE) (*current_controls[action])(pressed);
 }
 
 /****************************************************************************************
- * 
+ *
  */
-static esp_err_t actrls_process_type (const cJSON * member, actrls_config_t *cur_config, uint32_t offset){
-	esp_err_t err = ESP_OK;
-	ESP_LOGD(TAG,"Processing type member");
-	int *value = (int *)((char*) cur_config + offset);
-	if (member->type == cJSON_String) {
-		*value =
-				!strcmp(member->valuestring,
-						"BUTTON_LOW") ?
-						BUTTON_LOW : BUTTON_HIGH;
-	} else {
-		ESP_LOGE(TAG,
-				"Button type value expected string value of BUTTON_LOW or BUTTON_HIGH, none found");
-		err=ESP_FAIL;
-	}
-	return err;
+static void rotary_timer(TimerHandle_t xTimer) {
+    if (rotary.click_pending) {
+        (*current_controls[sys_btns_actions_KNOB_PUSH])(true);
+        rotary.click_pending = false;
+    } else if (rotary.left_count) {
+        if (rotary.left_count == 1) (*current_controls[sys_btns_actions_KNOB_LEFT])(true);
+        rotary.left_count = 0;
+    }
 }
 
 /****************************************************************************************
- * 
+ *
  */
-static esp_err_t actrls_process_bool (const cJSON * member, actrls_config_t *cur_config, uint32_t offset){
-	esp_err_t err = ESP_OK;
-	if (!member) {
-		ESP_LOGE(TAG,"Null json member pointer!");
-		err = ESP_FAIL;
-	} else {
-		ESP_LOGD(TAG,"Processing bool member ");
-		if (cJSON_IsBool(member)) {
-			bool*value = (bool*)((char*) cur_config + offset);
-			*value = cJSON_IsTrue(member);
-		} else {
-			ESP_LOGE(TAG,"Member %s is not a boolean", member->string?member->string:"unknown");
-			err = ESP_FAIL;
-		}
-	}
-
-	return err;
+static void actrls_parse_action(const char* name) {
+    // Check if there is a profile name that has a match
+    ESP_LOGD(TAG, "unknown action %s, trying to find matching profile ", name);
+    cJSON* existing = cJSON_GetObjectItem(control_profiles, name);
+
+    if (!existing) {
+        ESP_LOGD(TAG, "Loading new audio control profile with name: %s", name);
+        actrls_init_profile(name, false);
+    } else {
+        ESP_LOGD(TAG, "Existing profile %s was referenced", name);
+    }
 }
 
 /****************************************************************************************
- * 
+ *
  */
-static esp_err_t actrls_process_action (const cJSON * member, actrls_config_t *cur_config, uint32_t offset){
-	esp_err_t err = ESP_OK;
-	cJSON * button_action= cJSON_GetObjectItemCaseSensitive(member, "pressed");
-	actrls_action_detail_t*value = (actrls_action_detail_t*)((char *)cur_config + offset);
-	if (button_action != NULL) {
-		value[0].action = actrls_parse_action_json( button_action->valuestring);
-		if(value[0].action == ACTRLS_REMAP){
-			value[0].name = strdup(button_action->valuestring);
-		}
-	} 
-	button_action = cJSON_GetObjectItemCaseSensitive(member, "released");
-	if (button_action != NULL) {
-		value[1].action = actrls_parse_action_json( button_action->valuestring);
-		if (value[1].action == ACTRLS_REMAP){
-			value[1].name = strdup(button_action->valuestring);
-		}
-	}
-
-	return err;
-}
+static esp_err_t actrls_process_action(const sys_btns_action* action, sys_btns_action* cur_config_act) {
+    bool valid_name = action->profile_name && strlen(action->profile_name) > 0;
+    cur_config_act->type = action->type;
+    if (valid_name) {
+        cur_config_act->profile_name = strdup_psram(action->profile_name);
+        if (!cur_config_act->profile_name) {
+            ESP_LOGE(TAG, "Error allocating memory for action profile name %s", action->profile_name);
+            return ESP_ERR_NO_MEM;
+        }
+    }
 
+    if (action->type == sys_btns_actions_REMAP) {
+        if (!valid_name) {
+            ESP_LOGE(TAG, "Missing name for action %s", sys_btns_actions_name(action->type));
+            return ESP_ERR_INVALID_ARG;
+        }
+        actrls_parse_action(action->profile_name);
+    }
+    return ESP_OK;
+}
+static esp_err_t actrls_process_press(const sys_btns_press* press, sys_btns_press* cur_config_press) {
+    esp_err_t err = ESP_OK;
+    if (press->has_pressed) {
+        cur_config_press->has_pressed = true;
+        err = actrls_process_action(&press->pressed, &cur_config_press->pressed);
+    }
+    if (err == ESP_OK && press->has_released) {
+        cur_config_press->has_released = true;
+        err = actrls_process_action(&press->released, &cur_config_press->released);
+    }
+    return err;
+}
 /****************************************************************************************
- * 
+ *
  */
-static esp_err_t actrls_process_member(const cJSON * member, actrls_config_t *cur_config) {
-	esp_err_t err = ESP_OK;
-	const actrls_config_map_t * h=actrls_config_map;
-
-	char * str = cJSON_Print(member);
-	while (h->handler && strcmp(member->string, h->member)) { h++; }
-
-	if (h->handler) {
-		ESP_LOGD(TAG,"found handler for member %s, json value %s", h->member,str?str:"");
-		err = h->handler(member, cur_config, h->offset);
-	} else {
-		err = ESP_FAIL;
-		ESP_LOGE(TAG, "Unknown json structure member : %s", str?str:"");
-	}
-
-	if (str) free(str);
-	return err;
+static esp_err_t actrls_process_button(const sys_btns_btn* button, actrls_config_t* cur_config) {
+    esp_err_t err = ESP_OK;
+    if (button->has_gpio && button->gpio.pin >= 0) {
+        cur_config->type = button->gpio.level == sys_gpio_lvl_LOW ? BUTTON_LOW : BUTTON_HIGH;
+        cur_config->gpio = button->gpio.pin;
+    }
+    cur_config->pull = button->pull;
+    cur_config->debounce = button->debounce;
+    cur_config->long_press = button->longduration;
+    err = actrls_process_press(&button->normal, &cur_config->normal);
+    if (err == ESP_OK) err = actrls_process_press(&button->longpress, &cur_config->longpress);
+    if (err == ESP_OK) err = actrls_process_press(&button->shifted, &cur_config->shifted);
+    if (err == ESP_OK) err = actrls_process_press(&button->longshifted, &cur_config->longshifted);
+    cur_config->shifter_gpio = button->shifter;
+    return err;
 }
 
 /****************************************************************************************
- * 
+ *
  */
-static esp_err_t actrls_process_button(const cJSON * button, actrls_config_t *cur_config) {
-	esp_err_t err= ESP_OK;
-	const cJSON *member;
-
-	cJSON_ArrayForEach(member, button)
-	{
-		ESP_LOGD(TAG,"Processing member %s. ", member->string);
-		esp_err_t loc_err = actrls_process_member(member, cur_config);
-		err = (err == ESP_OK) ? loc_err : err;
-	}
-	return err;
+static actrls_config_t* actrls_init_alloc_structure(const sys_btns_profile* buttons, const char* name) {
+    actrls_config_t* json_config = NULL;
 
+    // Check if the main profiles array was created
+    if (!control_profiles) {
+        control_profiles = cJSON_CreateObject();
+    }
+    ESP_LOGD(TAG, "config contains %u button definitions", buttons->buttons_count);
+    if (buttons->buttons_count != 0) {
+        json_config = calloc(sizeof(actrls_config_t) * (buttons->buttons_count + 1), 1);
+        if (json_config) {
+            json_config[buttons->buttons_count].gpio = -1;
+        } else {
+            ESP_LOGE(TAG, "Unable to allocate memory to hold configuration for %u buttons ", buttons->buttons_count);
+        }
+    } else {
+        ESP_LOGE(TAG, "No button found in configuration structure");
+    }
+
+    // we're cheating here; we're going to store the control profile
+    // pointer as a string reference;  this will prevent cJSON
+    // from trying to free the structure if we ever want to free the object
+    cJSON* new_profile = cJSON_CreateStringReference((const char*)json_config);
+    cJSON_AddItemToObject(control_profiles, name, new_profile);
+
+    return json_config;
 }
 
 /****************************************************************************************
- * 
+ *
  */
-static actrls_config_t * actrls_init_alloc_structure(const cJSON *buttons, const char * name){
-	int member_count = 0;
-	const cJSON *button;
-	actrls_config_t * json_config=NULL;
-
-	// Check if the main profiles array was created
-	if(!control_profiles){
-		control_profiles = cJSON_CreateObject();
-	}
-
-	ESP_LOGD(TAG,"Counting the number of buttons definition");
-	cJSON_ArrayForEach(button, buttons)	{
-		member_count++;
-	}
-
-	ESP_LOGD(TAG, "config contains %u button definitions",	member_count);
-	if (member_count != 0) {
-		json_config = calloc(sizeof(actrls_config_t) * (member_count + 1), 1);
-		if (json_config){
-			json_config[member_count].gpio = -1;
-		} else {	
-			ESP_LOGE(TAG,"Unable to allocate memory to hold configuration for %u buttons ",member_count);
-		}
-	} else {
-		ESP_LOGE(TAG,"No button found in configuration structure");
-	}
-
-	// we're cheating here; we're going to store the control profile
-	// pointer as a string reference;  this will prevent cJSON
-	// from trying to free the structure if we ever want to free the object
-	cJSON * new_profile = cJSON_CreateStringReference((const char *)json_config);
-	cJSON_AddItemToObject(control_profiles, name, new_profile);
-
-	return json_config;
+static void actrls_defaults(actrls_config_t* config) {
+    sys_btns_press PressDefault = sys_btns_press_init_default;
+    config->type = BUTTON_LOW;
+    config->pull = false;
+    config->debounce = 0;
+    config->long_press = 0;
+    config->shifter_gpio = -1;
+    config->normal = PressDefault;
+    config->longpress = PressDefault;
+    config->shifted = PressDefault;
+    config->longshifted = PressDefault;
 }
 
 /****************************************************************************************
- * 
+ *
  */
-static void actrls_defaults(actrls_config_t *config) {
-	config->type = BUTTON_LOW;
-	config->pull = false;
-	config->debounce = 0;
-	config->long_press = 0;
-	config->shifter_gpio = -1;
-	config->normal[0].action = config->normal[1].action = ACTRLS_NONE;
-	config->longpress[0].action = config->longpress[1].action = ACTRLS_NONE;
-	config->shifted[0].action = config->shifted[1].action = ACTRLS_NONE;
-	config->longshifted[0].action = config->longshifted[1].action = ACTRLS_NONE;
+static sys_btns_profile* get_profile(const char* profile_name) {
+    ESP_LOGD(TAG, "Looking for profile name %s in %d profile(s)", profile_name, platform->dev.buttons_profiles_count);
+    for (int i = 0; i < platform->dev.buttons_profiles_count; i++) {
+        if (strcasecmp(platform->dev.buttons_profiles[i].profile_name, profile_name) == 0) {
+            ESP_LOGD(TAG, "Found profile name %s", platform->dev.buttons_profiles[i].profile_name);
+            return &platform->dev.buttons_profiles[i];
+        } else {
+            ESP_LOGD(TAG, "Profile name %s doesn't match %s", platform->dev.buttons_profiles[i].profile_name, profile_name);
+        }
+    }
+    ESP_LOGW(TAG, "Button control profile %s not found", profile_name);
+    return NULL;
 }
 
-
 /****************************************************************************************
- * 
+ *
  */
-static esp_err_t actrls_init_json(const char *profile_name, bool create) {
-	esp_err_t err = ESP_OK;
-	#pragma message("Add support to button profile names")
-// 	actrls_config_t *cur_config = NULL;
-// 	actrls_config_t *config_root = NULL;
-// 	char *config=NULL;
-// 	const cJSON *button;
-	
-// 	if (!profile_name) return ESP_OK;
-// 	//if ((config = config_alloc_get_str(profile_name, NULL, CONFIG_AUDIO_CONTROLS)) == NULL) return ESP_FAIL;
-// 	// TODO: Add support for the commented code
-// 	if (!*config) goto exit;
-		
-// 	ESP_LOGD(TAG,"Parsing JSON structure %s", config);
-// 	cJSON *buttons = cJSON_Parse(config);
-// 	if (buttons == NULL) {
-// 		ESP_LOGE(TAG,"JSON Parsing failed for %s", config);
-// 		err = ESP_FAIL;
-// 	} else {
-// 		ESP_LOGD(TAG,"Json parsing completed");
-// 		if (cJSON_IsArray(buttons)) {
-// 			ESP_LOGD(TAG,"configuration is an array as expected");
-// 			cur_config =config_root= actrls_init_alloc_structure(buttons, profile_name);
-// 			if(!cur_config) {
-// 				ESP_LOGE(TAG,"Config buffer was empty. ");
-// 				cJSON_Delete(buttons);
-// 				err = ESP_FAIL;
-// 				goto exit;
-// 			}
-// 			ESP_LOGD(TAG,"Processing button definitions. ");
-// 			cJSON_ArrayForEach(button, buttons){
-// 				char * str = cJSON_Print(button);
-// 				ESP_LOGD(TAG,"Processing %s. ", str?str:"");
-// 				if(str){
-// 					free(str);
-// 				}
-// 				actrls_defaults(cur_config);
-// 				esp_err_t loc_err = actrls_process_button(button, cur_config);
-// 				err = (err == ESP_OK) ? loc_err : err;
-// 				if (loc_err == ESP_OK) {
-// 					if (create) button_create((void*) cur_config, cur_config->gpio,cur_config->type, 
-// 												cur_config->pull,cur_config->debounce, control_handler, 
-// 												cur_config->long_press, cur_config->shifter_gpio);
-// 				} else {
-// 					ESP_LOGE(TAG,"Error parsing button structure.  Button will not be registered.");
-// 				}
-
-// 				cur_config++;
-// 			}
-// 		} else {
-// 			ESP_LOGE(TAG,"Invalid configuration; array is expected and none received in %s ", config);
-// 		}
-// 		cJSON_Delete(buttons);
-// 	}
-// 	// Now update the global json_config object.  If we are recursively processing menu structures,
-// 	// the last init that completes will assigh the first json config object found, which will match
-// 	// the default config from nvs.
-// 	json_config = config_root;
-// exit:	
-// 	free(config);
-	return err;
-}
+static esp_err_t actrls_init_profile(const char* profile_name, bool create) {
+    esp_err_t err = ESP_OK;
+    actrls_config_t* cur_config = NULL;
+    actrls_config_t* config_root = NULL;
+    sys_btns_profile* config;
 
+    if (!profile_name || strlen(profile_name) == 0) {
+        ESP_LOGI(TAG, "No control button configured");
+        return ESP_OK;
+    }
+    ESP_LOGI(TAG, "Initializing button control profile %s", profile_name);
+    config = get_profile(profile_name);
+    if (!config) {
+        ESP_LOGE(TAG, "Invalid button control profile %s", profile_name);
+        goto exit;
+    }
+
+    if (config->buttons_count == 0) {
+        ESP_LOGE(TAG, "No button found %s", profile_name);
+        err = ESP_FAIL;
+    } else {
+        ESP_LOGD(TAG, "Number of buttons: %d", config->buttons_count);
+        cur_config = config_root = actrls_init_alloc_structure(config, profile_name);
+        if (!cur_config) {
+            ESP_LOGE(TAG, "Config buffer was empty. ");
+            err = ESP_FAIL;
+            goto exit;
+        }
+        ESP_LOGD(TAG, "Processing button definitions. ");
+        for (int i = 0; i < config->buttons_count; i++) {
+            ESP_LOGD(TAG, "Processing button %d of %d for profile %s. ", i + 1, config->buttons_count, profile_name);
+            actrls_defaults(cur_config);
+            esp_err_t loc_err = actrls_process_button(&config->buttons[i], cur_config);
+            err = (err == ESP_OK) ? loc_err : err;
+            if (loc_err == ESP_OK) {
+                if (create)
+                    button_create((void*)cur_config, cur_config->gpio, cur_config->type, cur_config->pull, cur_config->debounce, control_handler,
+                        cur_config->long_press, cur_config->shifter_gpio);
+            } else {
+                ESP_LOGE(TAG, "Error parsing button structure.  Button will not be registered.");
+            }
+
+            cur_config++;
+        }
+    }
+    // Now update the global json_config object.  If we are recursively processing menu structures,
+    // the last init that completes will assigh the first json config object found, which will match
+    // the default config from nvs.
+    json_config = config_root;
+exit:
+    return err;
+}
 /****************************************************************************************
  *
  */
-void actrls_set_default(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler) {
-	memcpy(default_controls, controls, sizeof(actrls_t));
-	memcpy(current_controls, default_controls, sizeof(actrls_t));
-	default_hook = current_hook = hook;
-	default_raw_controls = current_raw_controls = raw_controls;
-	default_ir_handler = current_ir_handler = ir_handler;
+void actrls_set_default(const actrls_t controls, bool raw_controls, actrls_hook_t* hook, actrls_ir_handler_t* ir_handler) {
+    memcpy(default_controls, controls, sizeof(actrls_t));
+    memcpy(current_controls, default_controls, sizeof(actrls_t));
+    default_hook = current_hook = hook;
+    default_raw_controls = current_raw_controls = raw_controls;
+    default_ir_handler = current_ir_handler = ir_handler;
 }
 
 /****************************************************************************************
- * 
+ *
  */
-void actrls_set(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler) {
-	memcpy(current_controls, controls, sizeof(actrls_t));
-	current_hook = hook;
-	current_raw_controls = raw_controls;
-	current_ir_handler = ir_handler;
+void actrls_set(const actrls_t controls, bool raw_controls, actrls_hook_t* hook, actrls_ir_handler_t* ir_handler) {
+    memcpy(current_controls, controls, sizeof(actrls_t));
+    current_hook = hook;
+    current_raw_controls = raw_controls;
+    current_ir_handler = ir_handler;
 }
 
 /****************************************************************************************
- * 
+ *
  */
 void actrls_unset(void) {
-	memcpy(current_controls, default_controls, sizeof(actrls_t));
-	current_hook = default_hook;
-	current_raw_controls = default_raw_controls;
-	current_ir_handler = default_ir_handler;
+    memcpy(current_controls, default_controls, sizeof(actrls_t));
+    current_hook = default_hook;
+    current_raw_controls = default_raw_controls;
+    current_ir_handler = default_ir_handler;
 }

+ 16 - 15
components/services/audio_controls.h

@@ -9,27 +9,28 @@
 #pragma once
 
 #include "buttons.h"
+#include "Buttons.pb.h"
 
 // BEWARE: this is the index of the array of action below (change actrls_action_s as well!)
-typedef enum { 	ACTRLS_NONE = -1, ACTRLS_POWER, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY, 
-				ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT, 
-				BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT, 
-				BCTRLS_PS0,BCTRLS_PS1,BCTRLS_PS2,BCTRLS_PS3,BCTRLS_PS4,BCTRLS_PS5,BCTRLS_PS6,BCTRLS_PS7,BCTRLS_PS8,BCTRLS_PS9,
-				KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
-                ACTRLS_SLEEP,
-				ACTRLS_REMAP, ACTRLS_MAX 
-		} actrls_action_e;
+// typedef enum { 	ACTRLS_NONE = -1, ACTRLS_POWER, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY, 
+// 				ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT, 
+// 				BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT, 
+// 				BCTRLS_PS0,BCTRLS_PS1,BCTRLS_PS2,BCTRLS_PS3,BCTRLS_PS4,BCTRLS_PS5,BCTRLS_PS6,BCTRLS_PS7,BCTRLS_PS8,BCTRLS_PS9,
+// 				KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
+//                 ACTRLS_SLEEP,
+// 				ACTRLS_REMAP, ACTRLS_MAX 
+// 		} actrls_action_e;
 
 typedef void (*actrls_handler)(bool pressed);
-typedef actrls_handler actrls_t[ACTRLS_MAX - ACTRLS_NONE - 1];
-typedef bool actrls_hook_t(int gpio, actrls_action_e action, button_event_e event, button_press_e press, bool long_press);
+typedef actrls_handler actrls_t[sys_btns_actions_MAX- sys_btns_actions_A_NONE - 1];
+typedef bool actrls_hook_t(int gpio, sys_btns_action *action, button_event_e event, button_press_e press, bool long_press);
 typedef bool actrls_ir_handler_t(uint16_t addr, uint16_t cmd);
 
 // BEWARE any change to struct below must be mapped to actrls_config_map
-typedef struct {
-	actrls_action_e action;
-	const char * name;
-} actrls_action_detail_t;
+// typedef struct {
+// 	actrls_action_e action;
+// 	const char * name;
+// } actrls_action_detail_t;
 typedef struct actrl_config_s {
 	int gpio;
 	int type;
@@ -37,7 +38,7 @@ typedef struct actrl_config_s {
 	int	debounce;
 	int long_press;
 	int shifter_gpio;
-	actrls_action_detail_t normal[2], longpress[2], shifted[2], longshifted[2];	// [0] keypressed, [1] keyreleased
+	sys_btns_press normal, longpress, shifted, longshifted;	// [0] keypressed, [1] keyreleased
 } actrls_config_t;
 
 esp_err_t actrls_init(const char *profile_name);

+ 4 - 4
components/services/battery.c

@@ -16,7 +16,7 @@
 #include "esp_log.h"
 #include "driver/adc.h"
 #include "battery.h"
-#include "Configurator.h"
+#include "Config.h"
 
 /* 
  There is a bug in esp32 which causes a spurious interrupt on gpio 36/39 when
@@ -32,11 +32,11 @@ static const char *TAG = "battery";
 static struct {
 	float sum, avg, scale;
 	int count;
-	sys_Battery * battery_config;
+	sys_battery_config * battery_config;
 	TimerHandle_t timer;
 } battery;
-#define BATTERY_CHANNEL(b) (b.battery_config?b.battery_config->channel - sys_BatteryChannelEnum_CH0:-1)
-#define ATTENUATION(b) (b.battery_config?b.battery_config->atten - sys_BatteryAttenEnum_ATT_0:-1)
+#define BATTERY_CHANNEL(b) (b.battery_config?b.battery_config->channel - sys_battery_channels_CH0:-1)
+#define ATTENUATION(b) (b.battery_config?b.battery_config->atten - sys_battery_atten_ATT_0:-1)
 void (*battery_handler_svc)(float value, int cells);
 
 /****************************************************************************************

+ 348 - 338
components/services/buttons.c

@@ -1,4 +1,4 @@
-/* 
+/*
  *  a crude button press/long-press/shift management based on GPIO
  *
  *  (c) Philippe G. 2019, philippe_44@outlook.com
@@ -7,448 +7,458 @@
  *  https://opensource.org/licenses/MIT
  *
  */
- 
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#include "buttons.h"
+#include "driver/gpio.h"
+#include "driver/rmt.h"
+#include "esp_log.h"
+#include "esp_system.h"
+#include "esp_task.h"
 #include "freertos/FreeRTOS.h"
+#include "freertos/queue.h"
 #include "freertos/task.h"
 #include "freertos/timers.h"
-#include "freertos/queue.h"
-#include "esp_system.h"
-#include "esp_log.h"
-#include "esp_task.h"
-#include "driver/gpio.h"
-#include "driver/rmt.h"
+#include "globdefs.h"
 #include "gpio_exp.h"
-#include "buttons.h"
-#include "services.h"
 #include "rotary_encoder.h"
-#include "globdefs.h"
+#include "services.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 
-static const char * TAG = "buttons";
+static const char* TAG = "buttons";
 
 static EXT_RAM_ATTR int n_buttons;
 static EXT_RAM_ATTR uint32_t buttons_idle_since;
 
-#define BUTTON_STACK_SIZE	4096
-#define MAX_BUTTONS			32
-#define DEBOUNCE			50
-#define BUTTON_QUEUE_LEN	10
+#define BUTTON_STACK_SIZE 4096
+#define MAX_BUTTONS 32
+#define DEBOUNCE 50
+#define BUTTON_QUEUE_LEN 10
 
 static EXT_RAM_ATTR struct button_s {
-	void *client;
-	int gpio;
-	int debounce;
-	button_handler handler;
-	struct button_s *self, *shifter;
-	int shifter_gpio;	// this one is just for post-creation						
-	int	long_press;
-	bool long_timer, shifted, shifting;
-	int type, level;	
-	TimerHandle_t timer;
+    void* client;
+    int gpio;
+    int debounce;
+    button_handler handler;
+    struct button_s *self, *shifter;
+    int shifter_gpio; // this one is just for post-creation
+    int long_press;
+    bool long_timer, shifted, shifting;
+    int type, level;
+    TimerHandle_t timer;
 } buttons[MAX_BUTTONS];
 
 // can't use EXT_RAM_ATTR for initialized structure
 static struct {
-	int gpio, level;
-	struct button_s *button;
-} polled_gpio[] = { {36, -1, NULL}, {39, -1, NULL}, {-1, -1, NULL} };
+    int gpio, level;
+    struct button_s* button;
+} polled_gpio[] = {{36, -1, NULL}, {39, -1, NULL}, {-1, -1, NULL}};
 
 static TimerHandle_t polled_timer;
 
 static EXT_RAM_ATTR struct {
-	QueueHandle_t queue;
-	void *client;
-	rotary_encoder_info_t info;
-	int A, B, SW;
-	rotary_handler handler;
+    QueueHandle_t queue;
+    void* client;
+    rotary_encoder_info_t info;
+    int A, B, SW;
+    rotary_handler handler;
 } rotary;
 
 static EXT_RAM_ATTR struct {
-	RingbufHandle_t rb;
-	infrared_handler handler;
+    RingbufHandle_t rb;
+    infrared_handler handler;
 } infrared;
 
 static EXT_RAM_ATTR QueueHandle_t button_queue;
 static EXT_RAM_ATTR QueueSetHandle_t common_queue_set;
 
 static void buttons_task(void* arg);
-static void buttons_handler(struct button_s *button, int level);
+static void buttons_handler(struct button_s* button, int level);
 
 /****************************************************************************************
  * Start task needed by button,s rotaty and infrared
  */
 static void common_task_init(void) {
-	static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
-	static EXT_RAM_ATTR StackType_t xStack[BUTTON_STACK_SIZE] __attribute__ ((aligned (4)));
-	
-	if (!common_queue_set) {
-		common_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
-		xTaskCreateStatic( (TaskFunction_t) buttons_task, "buttons", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 2, xStack, &xTaskBuffer);
-	}
- }	
+    static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__((aligned(4)));
+    static EXT_RAM_ATTR StackType_t xStack[BUTTON_STACK_SIZE] __attribute__((aligned(4)));
+
+    if (!common_queue_set) {
+		ESP_LOGD(TAG,"Creating buttons task with a queue set length of %d",BUTTON_QUEUE_LEN+1);
+        common_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
+        xTaskCreateStatic((TaskFunction_t)buttons_task, "buttons", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 2, xStack, &xTaskBuffer);
+    }
+}
 
 /****************************************************************************************
  * GPIO low-level ISR handler
  */
-static void IRAM_ATTR gpio_isr_handler(void* arg)
-{
-	struct button_s *button = (struct button_s*) arg;
-	BaseType_t woken = pdFALSE;
-
-	if (xTimerGetPeriod(button->timer) > pdMS_TO_TICKS(button->debounce)) {
-		if (button->gpio < GPIO_NUM_MAX) xTimerChangePeriodFromISR(button->timer, pdMS_TO_TICKS(button->debounce), &woken); 
-		else xTimerChangePeriod(button->timer, pdMS_TO_TICKS(button->debounce), pdMS_TO_TICKS(10)); 
-	} else {
-		if (button->gpio < GPIO_NUM_MAX) xTimerResetFromISR(button->timer, &woken);
-		else xTimerReset(button->timer, portMAX_DELAY);
-	}
-
-	if (woken) portYIELD_FROM_ISR();
-
-	ESP_EARLY_LOGD(TAG, "INT gpio %u level %u", button->gpio, button->level);
+static void IRAM_ATTR gpio_isr_handler(void* arg) {
+    struct button_s* button = (struct button_s*)arg;
+    BaseType_t woken = pdFALSE;
+
+    if (xTimerGetPeriod(button->timer) > pdMS_TO_TICKS(button->debounce)) {
+        if (button->gpio < GPIO_NUM_MAX)
+            xTimerChangePeriodFromISR(button->timer, pdMS_TO_TICKS(button->debounce), &woken);
+        else
+            xTimerChangePeriod(button->timer, pdMS_TO_TICKS(button->debounce), pdMS_TO_TICKS(10));
+    } else {
+        if (button->gpio < GPIO_NUM_MAX)
+            xTimerResetFromISR(button->timer, &woken);
+        else
+            xTimerReset(button->timer, portMAX_DELAY);
+    }
+
+    if (woken) portYIELD_FROM_ISR();
+
+    ESP_EARLY_LOGD(TAG, "INT gpio %u level %u", button->gpio, button->level);
 }
 
 /****************************************************************************************
  * Buttons debounce/longpress timer
  */
-static void buttons_timer_handler( TimerHandle_t xTimer ) {
-	struct button_s *button = (struct button_s*) pvTimerGetTimerID (xTimer);
-	// if this is an expanded GPIO, must give cache a chance
-	buttons_handler(button, gpio_exp_get_level(button->gpio, (button->debounce * 3) / 2, NULL));
+static void buttons_timer_handler(TimerHandle_t xTimer) {
+    struct button_s* button = (struct button_s*)pvTimerGetTimerID(xTimer);
+    // if this is an expanded GPIO, must give cache a chance
+    buttons_handler(button, gpio_exp_get_level(button->gpio, (button->debounce * 3) / 2, NULL));
 }
 
 /****************************************************************************************
  * Buttons polling timer
  */
-static void buttons_polling( TimerHandle_t xTimer ) {
-	for (int i = 0; polled_gpio[i].gpio != -1; i++) {
-		if (!polled_gpio[i].button) continue;
-		
-		int level = gpio_get_level(polled_gpio[i].gpio);
-	
-		if (level != polled_gpio[i].level) {
-			polled_gpio[i].level = level;
-			buttons_handler(polled_gpio[i].button, level);
-		}	
-	}	
+static void buttons_polling(TimerHandle_t xTimer) {
+    for (int i = 0; polled_gpio[i].gpio != -1; i++) {
+        if (!polled_gpio[i].button) continue;
+
+        int level = gpio_get_level(polled_gpio[i].gpio);
+
+        if (level != polled_gpio[i].level) {
+            polled_gpio[i].level = level;
+            buttons_handler(polled_gpio[i].button, level);
+        }
+    }
 }
 
 /****************************************************************************************
  * Buttons timer handler for press/longpress
  */
-static void buttons_handler(struct button_s *button, int level) {
-	button->level = level;
-
-	if (button->shifter && button->shifter->type == button->shifter->level) button->shifter->shifting = true;
-
-	if (button->long_press && !button->long_timer && button->level == button->type) {
-		// detect a long press, so hold event generation
-		ESP_LOGD(TAG, "setting long timer gpio:%u level:%u", button->gpio, button->level);
-		xTimerChangePeriod(button->timer, button->long_press / portTICK_RATE_MS, 0);
-		button->long_timer = true;
-	} else {
-		// send a button pressed/released event (content is copied in queue)
-		ESP_LOGD(TAG, "sending event for gpio:%u level:%u", button->gpio, button->level);
-		// queue will have a copy of button's context
-		xQueueSend(button_queue, button, 0);
-		button->long_timer = false;
-	}
+static void buttons_handler(struct button_s* button, int level) {
+    button->level = level;
+
+    if (button->shifter && button->shifter->type == button->shifter->level) button->shifter->shifting = true;
+
+    if (button->long_press && !button->long_timer && button->level == button->type) {
+        // detect a long press, so hold event generation
+        ESP_LOGD(TAG, "setting long timer gpio:%u level:%u", button->gpio, button->level);
+        xTimerChangePeriod(button->timer, button->long_press / portTICK_RATE_MS, 0);
+        button->long_timer = true;
+    } else {
+        // send a button pressed/released event (content is copied in queue)
+        ESP_LOGD(TAG, "sending event for gpio:%u level:%u", button->gpio, button->level);
+        // queue will have a copy of button's context
+        xQueueSend(button_queue, button, 0);
+        button->long_timer = false;
+    }
 }
 
 /****************************************************************************************
  * Get inactivity callback
  */
-static uint32_t buttons_idle_callback(void) {
-    return pdTICKS_TO_MS(xTaskGetTickCount()) - buttons_idle_since;
-}    
+static uint32_t buttons_idle_callback(void) { return pdTICKS_TO_MS(xTaskGetTickCount()) - buttons_idle_since; }
 
 /****************************************************************************************
  * Tasks that calls the appropriate functions when buttons are pressed
  */
 static void buttons_task(void* arg) {
-	ESP_LOGI(TAG, "starting button tasks");   
-    
-    buttons_idle_since = pdTICKS_TO_MS(xTaskGetTickCount());    
+    ESP_LOGI(TAG, "starting button tasks");
+
+    buttons_idle_since = pdTICKS_TO_MS(xTaskGetTickCount());
     services_sleep_setsleeper(buttons_idle_callback);
-	
+
     while (1) {
-		QueueSetMemberHandle_t xActivatedMember;
+        QueueSetMemberHandle_t xActivatedMember;
         bool active = true;
 
-		// wait on button, rotary and infrared queues 
-		if ((xActivatedMember = xQueueSelectFromSet( common_queue_set, portMAX_DELAY )) == NULL) continue;
-        	
-		if (xActivatedMember == button_queue) {
-			struct button_s button;
-			button_event_e event;
-			button_press_e press;
-			
-			// received a button event
-			xQueueReceive(button_queue, &button, 0);
-
-			event = (button.level == button.type) ? BUTTON_PRESSED : BUTTON_RELEASED;		
-
-			ESP_LOGD(TAG, "received event:%u from gpio:%u level:%u (timer %u shifting %u)", event, button.gpio, button.level, button.long_timer, button.shifting);
-
-			// find if shifting is activated
-			if (button.shifter && button.shifter->type == button.shifter->level) press = BUTTON_SHIFTED;
-			else press = BUTTON_NORMAL;
-	
-			/* 
-			long_timer will be set either because we truly have a long press 
-			or we have a release before the long press timer elapsed, so two 
-			events shall be sent
-			*/
-			if (button.long_timer) {
-				if (event == BUTTON_RELEASED) {
-					// early release of a long-press button, send press/release
-					if (!button.shifting) {
-						button.handler(button.client, BUTTON_PRESSED, press, false);		
-						button.handler(button.client, BUTTON_RELEASED, press, false);		
-					}
-					// button is a copy, so need to go to real context
-					button.self->shifting = false;
-				} else if (!button.shifting) {
-					// normal long press and not shifting so don't discard
-					button.handler(button.client, BUTTON_PRESSED, press, true);
-				}  
-			} else {
-				// normal press/release of a button or release of a long-press button
-				if (!button.shifting) button.handler(button.client, event, press, button.long_press);
-				// button is a copy, so need to go to real context
-				button.self->shifting = false;
-			}
-		} else if (xActivatedMember == rotary.queue) {
-			rotary_encoder_event_t event = { 0 };
-			
-			// received a rotary event
-		    xQueueReceive(rotary.queue, &event, 0);
-
-			ESP_LOGD(TAG, "Event: position %d, direction %s", event.state.position,
-					event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
-			
-			rotary.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? 
-											ROTARY_RIGHT : ROTARY_LEFT, false);   
-		} else {
-			// this is IR
-			active = infrared_receive(infrared.rb, infrared.handler);
-		}	
-        
+        // wait on button, rotary and infrared queues
+        if ((xActivatedMember = xQueueSelectFromSet(common_queue_set, portMAX_DELAY)) == NULL) continue;
+
+        if (xActivatedMember == button_queue) {
+            struct button_s button;
+            button_event_e event;
+            button_press_e press;
+
+            // received a button event
+            xQueueReceive(button_queue, &button, 0);
+
+            event = (button.level == button.type) ? BUTTON_PRESSED : BUTTON_RELEASED;
+
+            ESP_LOGD(TAG, "received event:%u from gpio:%u level:%u (timer %u shifting %u)", event, button.gpio, button.level, button.long_timer,
+                button.shifting);
+
+            // find if shifting is activated
+            if (button.shifter && button.shifter->type == button.shifter->level)
+                press = BUTTON_SHIFTED;
+            else
+                press = BUTTON_NORMAL;
+
+            /*
+            long_timer will be set either because we truly have a long press
+            or we have a release before the long press timer elapsed, so two
+            events shall be sent
+            */
+            if (button.long_timer) {
+                if (event == BUTTON_RELEASED) {
+                    // early release of a long-press button, send press/release
+                    if (!button.shifting) {
+                        button.handler(button.client, BUTTON_PRESSED, press, false);
+                        button.handler(button.client, BUTTON_RELEASED, press, false);
+                    }
+                    // button is a copy, so need to go to real context
+                    button.self->shifting = false;
+                } else if (!button.shifting) {
+                    // normal long press and not shifting so don't discard
+                    button.handler(button.client, BUTTON_PRESSED, press, true);
+                }
+            } else {
+                // normal press/release of a button or release of a long-press button
+                if (!button.shifting) button.handler(button.client, event, press, button.long_press);
+                // button is a copy, so need to go to real context
+                button.self->shifting = false;
+            }
+        } else if (xActivatedMember == rotary.queue) {
+            rotary_encoder_event_t event = {0};
+
+            // received a rotary event
+            xQueueReceive(rotary.queue, &event, 0);
+
+            ESP_LOGD(TAG, "Event: position %d, direction %s", event.state.position,
+                event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
+
+            rotary.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? ROTARY_RIGHT : ROTARY_LEFT, false);
+        } else {
+            // this is IR
+            active = infrared_receive(infrared.rb, infrared.handler);
+        }
+
         // mark the last activity
         if (active) buttons_idle_since = pdTICKS_TO_MS(xTaskGetTickCount());
     }
-}	
-	
+}
+
 /****************************************************************************************
  * dummy button handler
- */	
-void dummy_handler(void *id, button_event_e event, button_press_e press) {
-	ESP_LOGW(TAG, "should not be here");
-}
+ */
+void dummy_handler(void* id, button_event_e event, button_press_e press) { ESP_LOGW(TAG, "should not be here"); }
 
 /****************************************************************************************
- * Create buttons 
+ * Create buttons
  */
-void button_create(void *client, int gpio, int type, bool pull, int debounce, button_handler handler, int long_press, int shifter_gpio) { 
-	if (n_buttons >= MAX_BUTTONS) return;
-
-	ESP_LOGI(TAG, "Creating button using GPIO %u, type %u, pull-up/down %u, long press %u shifter %d", gpio, type, pull, long_press, shifter_gpio);
-
-	if (!n_buttons) {
-		button_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
-		common_task_init();
-		xQueueAddToSet( button_queue, common_queue_set );
-	}
-	
-	// just in case this structure is allocated in a future release
-	memset(buttons + n_buttons, 0, sizeof(struct button_s));
-
-	// set mandatory parameters
-	buttons[n_buttons].client = client;
- 	buttons[n_buttons].gpio = gpio;
- 	buttons[n_buttons].debounce = debounce ? debounce: DEBOUNCE;
-	buttons[n_buttons].handler = handler;
-	buttons[n_buttons].long_press = long_press;
-	buttons[n_buttons].shifter_gpio = shifter_gpio;
-	buttons[n_buttons].type = type;
-	buttons[n_buttons].timer = xTimerCreate("buttonTimer", buttons[n_buttons].debounce / portTICK_RATE_MS, pdFALSE, (void *) &buttons[n_buttons], buttons_timer_handler);
-	buttons[n_buttons].self = buttons + n_buttons;
-
-	for (int i = 0; i < n_buttons; i++) {
-		// first try to find our shifter
-		if (buttons[i].gpio == shifter_gpio) {
-			buttons[n_buttons].shifter = buttons + i;
-			// a shifter must have a long-press handler
-			if (!buttons[i].long_press) buttons[i].long_press = -1;
-		}
-		// then try to see if we are a non-assigned shifter
-		if (buttons[i].shifter_gpio == gpio) {
-			buttons[i].shifter = buttons + n_buttons;
-			ESP_LOGI(TAG, "post-assigned shifter gpio %u", buttons[i].gpio);			
-		}	
-	}
-
-	gpio_pad_select_gpio_x(gpio);
-	gpio_set_direction_x(gpio, GPIO_MODE_INPUT);
-
-	// do we need pullup or pulldown
-	if (pull) {
-		if (GPIO_IS_VALID_OUTPUT_GPIO(gpio) || gpio >= GPIO_NUM_MAX) {
-			if (type == BUTTON_LOW) gpio_set_pull_mode_x(gpio, GPIO_PULLUP_ONLY);
-			else gpio_set_pull_mode_x(gpio, GPIO_PULLDOWN_ONLY);
-		} else {	
-			ESP_LOGW(TAG, "cannot set pull up/down for gpio %u", gpio);
-		}
-	}
-	
-	// and initialize level ...
-	buttons[n_buttons].level = gpio_get_level_x(gpio);
-	
-	// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
-	for (int i = 0; polled_gpio[i].gpio != -1; i++) if (polled_gpio[i].gpio == gpio) {
-		if (!polled_timer) {
-			polled_timer = xTimerCreate("buttonsPolling", 100 / portTICK_RATE_MS, pdTRUE, polled_gpio, buttons_polling);		
-			xTimerStart(polled_timer, portMAX_DELAY);
-		}	
-	
-		polled_gpio[i].button = buttons + n_buttons;					
-		polled_gpio[i].level = gpio_get_level(gpio);
-		ESP_LOGW(TAG, "creating polled gpio %u, level %u", gpio, polled_gpio[i].level);		
-	
-		gpio = -1;
-		break;
-	}
-	
-	// only create ISR if this is not a polled gpio
-	if (gpio != -1) {
-		// we need any edge detection
-		gpio_set_intr_type_x(gpio, GPIO_INTR_ANYEDGE);
-		gpio_isr_handler_add_x(gpio, gpio_isr_handler, buttons + n_buttons);
-		gpio_intr_enable_x(gpio);
-	}	
-
-	n_buttons++;
-}	
+void button_create(void* client, int gpio, int type, bool pull, int debounce, button_handler handler, int long_press, int shifter_gpio) {
+    if (n_buttons >= MAX_BUTTONS) return;
+
+    ESP_LOGI(TAG, "Creating button using GPIO %u, type %s, %s pull-up/down, long press %u shifter %d", gpio,
+        sys_gpio_lvl_name(type == 0 ? sys_gpio_lvl_LOW : sys_gpio_lvl_HIGH), pull?"with":"without", long_press, shifter_gpio);
+
+    if (!n_buttons) {
+		ESP_LOGD(TAG,"Creating new buttton message queue with a length of %d entries",BUTTON_QUEUE_LEN);
+        button_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
+        common_task_init();
+        xQueueAddToSet(button_queue, common_queue_set);
+    }
+
+    // just in case this structure is allocated in a future release
+    memset(buttons + n_buttons, 0, sizeof(struct button_s));
+
+    // set mandatory parameters
+    buttons[n_buttons].client = client;
+    buttons[n_buttons].gpio = gpio;
+    buttons[n_buttons].debounce = debounce ? debounce : DEBOUNCE;
+    buttons[n_buttons].handler = handler;
+    buttons[n_buttons].long_press = long_press;
+    buttons[n_buttons].shifter_gpio = shifter_gpio;
+    buttons[n_buttons].type = type;
+    buttons[n_buttons].timer =
+        xTimerCreate("buttonTimer", buttons[n_buttons].debounce / portTICK_RATE_MS, pdFALSE, (void*)&buttons[n_buttons], buttons_timer_handler);
+    buttons[n_buttons].self = buttons + n_buttons;
+
+    for (int i = 0; i < n_buttons; i++) {
+        // first try to find our shifter
+        if (buttons[i].gpio == shifter_gpio) {
+            buttons[n_buttons].shifter = buttons + i;
+            // a shifter must have a long-press handler
+            if (!buttons[i].long_press) buttons[i].long_press = -1;
+        }
+        // then try to see if we are a non-assigned shifter
+        if (buttons[i].shifter_gpio == gpio) {
+            buttons[i].shifter = buttons + n_buttons;
+            ESP_LOGI(TAG, "post-assigned shifter gpio %u", buttons[i].gpio);
+        }
+    }
+
+    gpio_pad_select_gpio_x(gpio);
+    gpio_set_direction_x(gpio, GPIO_MODE_INPUT);
+
+    // do we need pullup or pulldown
+    if (pull) {
+        if (GPIO_IS_VALID_OUTPUT_GPIO(gpio) || gpio >= GPIO_NUM_MAX) {
+            if (type == BUTTON_LOW)
+                gpio_set_pull_mode_x(gpio, GPIO_PULLUP_ONLY);
+            else
+                gpio_set_pull_mode_x(gpio, GPIO_PULLDOWN_ONLY);
+        } else {
+            ESP_LOGW(TAG, "cannot set pull up/down for gpio %u", gpio);
+        }
+    }
+
+    // and initialize level ...
+    buttons[n_buttons].level = gpio_get_level_x(gpio);
+
+    // nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
+    for (int i = 0; polled_gpio[i].gpio != -1; i++)
+        if (polled_gpio[i].gpio == gpio) {
+            if (!polled_timer) {
+                polled_timer = xTimerCreate("buttonsPolling", 100 / portTICK_RATE_MS, pdTRUE, polled_gpio, buttons_polling);
+                xTimerStart(polled_timer, portMAX_DELAY);
+            }
+
+            polled_gpio[i].button = buttons + n_buttons;
+            polled_gpio[i].level = gpio_get_level(gpio);
+            ESP_LOGW(TAG, "creating polled gpio %u, level %u", gpio, polled_gpio[i].level);
+
+            gpio = -1;
+            break;
+        }
+
+    // only create ISR if this is not a polled gpio
+    if (gpio != -1) {
+        // we need any edge detection
+        gpio_set_intr_type_x(gpio, GPIO_INTR_ANYEDGE);
+        gpio_isr_handler_add_x(gpio, gpio_isr_handler, buttons + n_buttons);
+        gpio_intr_enable_x(gpio);
+    }
+
+    n_buttons++;
+}
 
 /****************************************************************************************
  * Get stored id
  */
-void *button_get_client(int gpio) {
-	 for (int i = 0; i < n_buttons; i++) {
-		 if (buttons[i].gpio == gpio) return buttons[i].client;
-	 }
-	 return NULL;
+void* button_get_client(int gpio) {
+    for (int i = 0; i < n_buttons; i++) {
+        if (buttons[i].gpio == gpio) return buttons[i].client;
+    }
+    return NULL;
 }
 
 /****************************************************************************************
  * Get stored id
  */
-bool button_is_pressed(int gpio, void *client) {
-	for (int i = 0; i < n_buttons; i++) {
-		if (gpio != -1 && buttons[i].gpio == gpio) return buttons[i].level == buttons[i].type;
-		else if (client && buttons[i].client == client) return buttons[i].level == buttons[i].type;
-	}
-	return false; 
+bool button_is_pressed(int gpio, void* client) {
+    for (int i = 0; i < n_buttons; i++) {
+        if (gpio != -1 && buttons[i].gpio == gpio)
+            return buttons[i].level == buttons[i].type;
+        else if (client && buttons[i].client == client)
+            return buttons[i].level == buttons[i].type;
+    }
+    return false;
 }
 
 /****************************************************************************************
- * Update buttons 
+ * Update buttons
  */
-void *button_remap(void *client, int gpio, button_handler handler, int long_press, int shifter_gpio) { 
-	int i;
-	struct button_s *button = NULL;
-	void *prev_client;
-	
-	ESP_LOGI(TAG, "remapping GPIO %u, long press %u shifter %u", gpio, long_press, shifter_gpio);
-
-	// find button
-	for (i = 0; i < n_buttons; i++) {
-		if (buttons[i].gpio == gpio) {
-			button = buttons + i;
-			break;
-		}	
-	}	
-	
-	// don't know what we are doing here
-	if (!button) return NULL;	
-	
-	prev_client = button->client;
-	button->client = client;
- 	button->handler = handler;
-	button->long_press = long_press;
-	button->shifter_gpio = shifter_gpio;
-
-	// find our shifter	(if any)	
-	for (i = 0; shifter_gpio != -1 && i < n_buttons; i++) {
-		if (buttons[i].gpio == shifter_gpio) {
-			button->shifter = buttons + i;
-			// a shifter must have a long-press handler
-			if (!buttons[i].long_press) buttons[i].long_press = -1;
-			break;
-		}
-	}
-	
-	return prev_client;
+void* button_remap(void* client, int gpio, button_handler handler, int long_press, int shifter_gpio) {
+    int i;
+    struct button_s* button = NULL;
+    void* prev_client;
+
+    ESP_LOGI(TAG, "remapping GPIO %u, long press %u shifter %u", gpio, long_press, shifter_gpio);
+
+    // find button
+    for (i = 0; i < n_buttons; i++) {
+        if (buttons[i].gpio == gpio) {
+            button = buttons + i;
+            break;
+        }
+    }
+
+    // don't know what we are doing here
+    if (!button) return NULL;
+
+    prev_client = button->client;
+    button->client = client;
+    button->handler = handler;
+    button->long_press = long_press;
+    button->shifter_gpio = shifter_gpio;
+
+    // find our shifter	(if any)
+    for (i = 0; shifter_gpio != -1 && i < n_buttons; i++) {
+        if (buttons[i].gpio == shifter_gpio) {
+            button->shifter = buttons + i;
+            // a shifter must have a long-press handler
+            if (!buttons[i].long_press) buttons[i].long_press = -1;
+            break;
+        }
+    }
+
+    return prev_client;
 }
 
 /****************************************************************************************
  * Rotary encoder handler
  */
-static void rotary_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
-	ESP_LOGI(TAG, "Rotary push-button %d", event);
-	rotary.handler(id, event == BUTTON_PRESSED ? ROTARY_PRESSED : ROTARY_RELEASED, long_press);
+static void rotary_button_handler(void* id, button_event_e event, button_press_e mode, bool long_press) {
+    ESP_LOGI(TAG, "Rotary push-button %d", event);
+    rotary.handler(id, event == BUTTON_PRESSED ? ROTARY_PRESSED : ROTARY_RELEASED, long_press);
 }
 
 /****************************************************************************************
  * Create rotary encoder
  */
-bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler) {
-	// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
-	if (A == -1 || B == -1 || A == 36 || A == 39 || B == 36 || B == 39) {
-		ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B);
-		return false;
-	}
-
-	rotary.A = A;
-	rotary.B = B;
-	rotary.SW = SW;
-	rotary.client = id;
-	rotary.handler = handler;
-	
+bool create_rotary(void* id, int A, int B, int SW, int long_press, rotary_handler handler) {
+    // nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
+    if (A == -1 || B == -1 || A == 36 || A == 39 || B == 36 || B == 39) {
+        ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B);
+        return false;
+    }
+
+    rotary.A = A;
+    rotary.B = B;
+    rotary.SW = SW;
+    rotary.client = id;
+    rotary.handler = handler;
+
     // Initialise the rotary encoder device with the GPIOs for A and B signals
     rotary_encoder_init(&rotary.info, A, B);
-		
+
     // Create a queue for events from the rotary encoder driver.
     rotary.queue = rotary_encoder_create_queue();
     rotary_encoder_set_queue(&rotary.info, rotary.queue);
-	
-	common_task_init();
-	xQueueAddToSet( rotary.queue, common_queue_set );
-
-	// create companion button if rotary has a switch
-	if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, rotary_button_handler, long_press, -1);
-	
-	ESP_LOGI(TAG, "Created rotary encoder A:%d B:%d, SW:%d", A, B, SW);
-	
-	return true;
-}	
+
+    common_task_init();
+    xQueueAddToSet(rotary.queue, common_queue_set);
+
+    // create companion button if rotary has a switch
+    if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, rotary_button_handler, long_press, -1);
+
+    ESP_LOGI(TAG, "Created rotary encoder A:%d B:%d, SW:%d", A, B, SW);
+
+    return true;
+}
 
 /****************************************************************************************
  * Create Infrared
  */
 bool create_infrared(int gpio, infrared_handler handler, infrared_mode_t mode) {
-	// initialize IR infrastructure
-	infrared_init(&infrared.rb, gpio, mode);
-	infrared.handler = handler;
-	
-	// join the queue set
-	common_task_init();
-	xRingbufferAddToQueueSetRead(infrared.rb, common_queue_set);
-	
-	ESP_LOGI(TAG, "Created infrared receiver using GPIO %u", gpio);	
-	
-	return (infrared.rb != NULL);
-}	
+    // initialize IR infrastructure
+    infrared_init(&infrared.rb, gpio, mode);
+    infrared.handler = handler;
+
+    // join the queue set
+    common_task_init();
+    xRingbufferAddToQueueSetRead(infrared.rb, common_queue_set);
+
+    ESP_LOGI(TAG, "Created infrared receiver using GPIO %u", gpio);
+
+    return (infrared.rb != NULL);
+}

+ 0 - 3
components/services/globdefs.h

@@ -10,14 +10,11 @@
  
 #pragma once
 
-#define I2C_SYSTEM_PORT		1
 #define SPI_SYSTEM_HOST		SPI2_HOST
 
 #define RMT_NEXT_TX_CHANNEL() rmt_system_base_tx_channel++;
 #define RMT_NEXT_RX_CHANNEL() rmt_system_base_rx_channel--;
 
-extern int i2c_system_port;
-extern int i2c_system_speed;
 extern int spi_system_host;
 extern int spi_system_dc_gpio;
 extern int rmt_system_base_tx_channel;

+ 15 - 3
components/services/gpio_exp.c

@@ -18,7 +18,7 @@
 #include "driver/i2c.h"
 #include "driver/spi_master.h"
 #include "gpio_exp.h"
-
+#include "GPIO.pb.h"
 #define GPIO_EXP_INTR	0x100
 #define	GPIO_EXP_WRITE	0x200
 
@@ -181,7 +181,6 @@ gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) {
 		ESP_LOGE(TAG, "Cannot create GPIO expander %s, check i2c/spi configuration", config->model);
 		return NULL;
 	}
-
 	n_expanders++;
 	expander->first = config->base;
 	expander->last = config->base + config->count - 1;
@@ -197,6 +196,12 @@ gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) {
 		message_queue = xQueueCreate(4, sizeof(queue_request_t));
 		service_task = xTaskCreateStatic(service_handler, "gpio_expander", sizeof(xStack), NULL, ESP_TASK_PRIO_MIN + 1, xStack, xTaskBuffer);
 	}
+	if(config->phy.ena_pin>=0){
+		ESP_LOGD(TAG,"Enabling expander with pin %d level %d",config->phy.ena_pin,config->phy.ena_lvl);
+		gpio_pad_select_gpio(config->phy.ena_pin);
+		gpio_set_direction(config->phy.ena_pin, GPIO_MODE_DEF_OUTPUT);
+		gpio_set_level(config->phy.ena_pin, config->phy.ena_lvl);
+	}
 
 	// set interrupt if possible
 	if (config->intr >= 0) {
@@ -222,7 +227,7 @@ gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) {
 		gpio_intr_enable(config->intr);						
 	}
 	
-	ESP_LOGI(TAG, "Create GPIO expander %s at base %u with intr %d at @%x on port/host %d/%d", config->model, config->base, config->intr, config->phy.addr, config->phy.port, config->phy.host);
+	ESP_LOGI(TAG, "Create GPIO expander %s at base %u with intr %d at @%x on port/host %d/%d, enable pin: %d:%d", config->model, config->base, config->intr, config->phy.addr, config->phy.port, config->phy.host,config->phy.ena_pin,config->phy.ena_lvl);
 	return expander;
 }
 
@@ -377,6 +382,13 @@ esp_err_t gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, gpio_exp_t *ex
 /******************************************************************************
  * Wrapper function
  */
+
+void esp_rom_gpio_pad_select_gpio_x(uint32_t gpio){
+	if (gpio < GPIO_NUM_MAX) {
+		esp_rom_gpio_pad_select_gpio(gpio);
+	} 
+}
+
 esp_err_t gpio_set_pull_mode_x(int gpio, gpio_pull_mode_t mode) {
 	if (gpio < GPIO_NUM_MAX) return gpio_set_pull_mode(gpio, mode);
 	return gpio_exp_set_pull_mode(gpio, mode, NULL);

+ 4 - 0
components/services/gpio_exp.h

@@ -11,6 +11,7 @@
 #include <stdint.h>
 #include "freertos/FreeRTOS.h"
 #include "driver/gpio.h"
+#include "esp_rom_gpio.h"
 
 struct gpio_exp_s;
 
@@ -29,6 +30,8 @@ typedef struct {
 			uint8_t host;	
 			uint8_t cs_pin; 		
 		};
+		int8_t ena_pin; // enable pin
+		int8_t ena_lvl; // enable level
 	} phy;	
 } gpio_exp_config_t;
 
@@ -42,6 +45,7 @@ struct gpio_exp_s*  gpio_exp_get_expander(int gpio);
  For all functions below when <expander> is provided, GPIO's can be numbered from 0. If <expander>
  is NULL, then GPIO must start from base OR be on-chip
 */
+void esp_rom_gpio_pad_select_gpio_x(uint32_t iopad_num);
 esp_err_t	gpio_exp_set_direction(int gpio, gpio_mode_t mode, struct gpio_exp_s *expander);
 esp_err_t   gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, struct gpio_exp_s *expander);
 int         gpio_exp_get_level(int gpio, int age, struct gpio_exp_s *expander);

+ 33 - 30
components/services/led.c

@@ -5,7 +5,10 @@
    software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    CONDITIONS OF ANY KIND, either express or implied.
 */
-
+#define LOG_LOCAL_LEVEL ESP_LOG_INFO
+#include "led.h"
+#include "Config.h"
+#include "accessors.h"
 #include "driver/gpio.h"
 #include "driver/ledc.h"
 #include "driver/rmt.h"
@@ -14,17 +17,14 @@
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
 #include "freertos/timers.h"
+#include "globdefs.h"
+#include "gpio_exp.h"
+#include "services.h"
 #include <math.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include "Configurator.h"
-#include "accessors.h"
-#include "globdefs.h"
-#include "gpio_exp.h"
-#include "led.h"
-#include "services.h"
 
 #define MAX_LED 8
 #define BLOCKTIME 10 // up to portMAX_DELAY
@@ -43,7 +43,7 @@ static int8_t led_rmt_channel = -1;
 static uint32_t scale24(uint32_t bright, uint8_t);
 
 static const struct rmt_led_param_s {
-    sys_LedTypesEnum type;
+    sys_led_types type;
     uint8_t bits;
     // number of ticks in nanoseconds converted in RMT_CLK ticks
     rmt_item32_t bit_0;
@@ -51,7 +51,7 @@ static const struct rmt_led_param_s {
     uint32_t green, red;
     uint32_t (*scale)(uint32_t, uint8_t);
 } rmt_led_param[] = {
-    {sys_LedTypesEnum_LED_TYPE_WS2812, 24, {{{350 / RMT_CLK, 1, 1000 / RMT_CLK, 0}}},
+    {sys_led_types_WS2812, 24, {{{350 / RMT_CLK, 1, 1000 / RMT_CLK, 0}}},
         {{{1000 / RMT_CLK, 1, 350 / RMT_CLK, 0}}}, 0xff0000, 0x00ff00, scale24},
     {.type = -1}};
 
@@ -73,15 +73,9 @@ static struct led_config_s {
     int gpio;
     int color;
     int bright;
-    sys_LedTypesEnum type;
-} green = {.gpio = CONFIG_LED_GREEN_GPIO,
-    .color = 0,
-    .bright = -1,
-    .type = sys_LedTypesEnum_LED_TYPE_GPIO},
-  red = {.gpio = CONFIG_LED_RED_GPIO,
-      .color = 0,
-      .bright = -1,
-      .type = sys_LedTypesEnum_LED_TYPE_GPIO};
+    sys_led_types type;
+} green = {.gpio = -1, .color = 0, .bright = -1, .type = sys_led_types_GPIO},
+  red = {.gpio = -1, .color = 0, .bright = -1, .type = sys_led_types_GPIO};
 
 static int led_max = 2;
 
@@ -127,7 +121,7 @@ static void vCallbackFunction(TimerHandle_t xTimer) {
     if (!led->timer) return;
 
     led->on = !led->on;
-    ESP_EARLY_LOGD(TAG, "led vCallbackFunction setting gpio %d level %d (bright:%d)", led->gpio,
+    ESP_EARLY_LOGV(TAG, "led vCallbackFunction setting gpio %d level %d (bright:%d)", led->gpio,
         led->on, led->bright);
     set_level(led, led->on);
 
@@ -231,12 +225,13 @@ int led_allocate(void) {
 /****************************************************************************************
  *
  */
-bool led_config(int idx, sys_LED* led_config) {
+bool led_config(int idx, sys_led_config* led_config) {
     if (!led_config->has_gpio) {
+        ESP_LOGD(TAG,"No GPIO configured for %s LED",idx == LED_GREEN ? "GREEN" : "RED");
         return false;
     }
     if (led_config->gpio.pin < 0) {
-        ESP_LOGW(TAG, "LED GPIO -1 ignored");
+        ESP_LOGD(TAG,"GPIO -1 ignored for %s LED",idx == LED_GREEN ? "GREEN" : "RED");
         return false;
     }
     if (idx >= MAX_LED) return false;
@@ -247,7 +242,7 @@ bool led_config(int idx, sys_LED* led_config) {
     leds[idx].rmt = NULL;
     leds[idx].bright = -1;
 
-    if (led_config->led_type != sys_LedTypesEnum_LED_TYPE_GPIO) {
+    if (led_config->led_type != sys_led_types_GPIO) {
         // first make sure we have a known addressable led
         for (const struct rmt_led_param_s* p = rmt_led_param; !leds[idx].rmt && p->type >= 0; p++)
             if (p->type == led_config->led_type) leds[idx].rmt = p;
@@ -285,7 +280,7 @@ bool led_config(int idx, sys_LED* led_config) {
 
     set_level(leds + idx, false);
     ESP_LOGI(TAG, "Configuring LED %s %d (on:%d rmt:%s %d%% )", idx == LED_GREEN ? "GREEN" : "RED",
-        led_config->gpio.pin, led_config->gpio.level, sys_LedTypesEnum_name(led_config->led_type),
+        led_config->gpio.pin, led_config->gpio.level, sys_led_types_name(led_config->led_type),
         led_config->brightness);
     return true;
 }
@@ -294,6 +289,7 @@ bool led_config(int idx, sys_LED* led_config) {
  *
  */
 static void led_suspend(void) {
+    ESP_LOGD(TAG,"led_suspend: turning off leds");
     led_off(LED_GREEN);
     led_off(LED_RED);
 }
@@ -316,12 +312,12 @@ void set_led_gpio(int gpio, char* value) {
     while ((p = strchr(p, ':')) != NULL) {
         p++;
         if ((strcasestr(p, "ws2812")) != NULL)
-            config->type = sys_LedTypesEnum_LED_TYPE_WS2812;
+            config->type = sys_led_types_WS2812;
         else
             config->color = atoi(p);
     }
 
-    if (config->type != sys_LedTypesEnum_LED_TYPE_GPIO) {
+    if (config->type != sys_led_types_GPIO) {
         for (const struct rmt_led_param_s* p = rmt_led_param; p->type >= 0; p++) {
             if (p->type == config->type) {
                 if (config == &green)
@@ -335,18 +331,25 @@ void set_led_gpio(int gpio, char* value) {
 }
 
 void led_svc_init(void) {
-    sys_Gpios* gpios = NULL;
+    sys_gpios_config* gpios = NULL;
+    bool found = false;
+    
     if (!platform->has_gpios) {
+        ESP_LOGI(TAG, "No LED configured");
         return;
     }
+    ESP_LOGI(TAG,"Setting up leds");
     gpios = &platform->gpios;
     if (gpios->has_greenLED) {
-        led_config(LED_GREEN, &gpios->greenLED);
+        found = found | led_config(LED_GREEN, &gpios->greenLED);
     }
     if (gpios->has_redLED) {
-        led_config(LED_RED, &gpios->redLED);
+        found = found | led_config(LED_RED, &gpios->redLED);
     }
-
+    ESP_LOGD(TAG,"Done setting up leds");
     // make sure we switch off all leds (useful for gpio expanders)
-    services_sleep_setsuspend(led_suspend);
+    if (found) {
+        ESP_LOGD(TAG,"Switching leds off");
+        services_sleep_setsuspend(led_suspend);
+    }
 }

+ 2 - 2
components/services/led.h

@@ -12,7 +12,7 @@
 #ifndef LED_H
 #define LED_H
 #include "driver/gpio.h"
-#include "Configurator.h"
+#include "Config.h"
 
 enum { LED_GREEN = 0, LED_RED };
 #define led_on(idx)						led_blink_core(idx, 1, 0, false)
@@ -21,7 +21,7 @@ enum { LED_GREEN = 0, LED_RED };
 #define led_blink_pushed(idx, on, off)	led_blink_core(idx, on, off, true)
 
 // if type is LED_GPIO then color set the GPIO logic value for "on"
-bool led_config(int idx, sys_LED * led_config);
+bool led_config(int idx, sys_led_config * led_config);
 bool led_brightness(int idx, int percent);
 bool led_blink_core(int idx, int ontime, int offtime, bool push);
 bool led_unpush(int idx);

+ 263 - 260
components/services/messaging.c

@@ -1,20 +1,20 @@
-	/**
+/**
  *
  */
-#include <stdlib.h> // Required for libtelnet.h
-#include <esp_log.h>
+#include "config.h"
+#include "esp_app_trace.h"
+#include "esp_attr.h"
 #include "stdbool.h"
+#include <errno.h>
+#include <esp_log.h>
 #include <lwip/def.h>
 #include <lwip/sockets.h>
-#include <errno.h>
+#include <stdlib.h> // Required for libtelnet.h
 #include <string.h>
-#include "esp_app_trace.h"
-#include "esp_attr.h"
-#include "config.h"
 // #include "nvs_utilities.h"
 
-#include "platform_esp32.h"
 #include "messaging.h"
+#include "platform_esp32.h"
 #include "tools.h"
 /************************************
  * Globals
@@ -22,290 +22,293 @@
 
 const static char tag[] = "messaging";
 typedef struct {
-	struct messaging_list_t * next;
-	char * subscriber_name;
-	size_t max_count;
-	RingbufHandle_t buf_handle;
+    struct messaging_list_t* next;
+    char* subscriber_name;
+    size_t max_count;
+    RingbufHandle_t buf_handle;
 } messaging_list_t;
 static messaging_list_t top;
 #define MSG_LENGTH_AVG 1024
 
-messaging_list_t * get_struct_ptr(messaging_handle_t handle){
-	return (messaging_list_t *)handle;
-}
-messaging_handle_t  get_handle_ptr(messaging_list_t * handle){
-	return (messaging_handle_t )handle;
-}
+messaging_list_t* get_struct_ptr(messaging_handle_t handle) { return (messaging_list_t*)handle; }
+messaging_handle_t get_handle_ptr(messaging_list_t* handle) { return (messaging_handle_t)handle; }
 
-RingbufHandle_t messaging_create_ring_buffer(uint8_t max_count){
-	RingbufHandle_t buf_handle = NULL;
-	StaticRingbuffer_t *buffer_struct = malloc_init_external(sizeof(StaticRingbuffer_t));
-	if (buffer_struct != NULL) {
-		size_t buf_size = (size_t )(sizeof(single_message_t)+8+MSG_LENGTH_AVG)*(size_t )(max_count>0?max_count:5); // no-split buffer requires an additional 8 bytes
-		buf_size = buf_size - (buf_size % 4);
-		uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_32BIT);
-		if (buffer_storage== NULL) {
-			ESP_LOGE(tag,"buff alloc failed");
-		}
-		else {
-			buf_handle = xRingbufferCreateStatic(buf_size, RINGBUF_TYPE_NOSPLIT, buffer_storage, buffer_struct);
-		}
-	}
-	else {
-		ESP_LOGE(tag,"ringbuf alloc failed");
-	}
-	return buf_handle;
+RingbufHandle_t messaging_create_ring_buffer(uint8_t max_count) {
+    RingbufHandle_t buf_handle = NULL;
+    StaticRingbuffer_t* buffer_struct = malloc_init_external(sizeof(StaticRingbuffer_t));
+    if (buffer_struct != NULL) {
+        size_t buf_size =
+            (size_t)(sizeof(single_message_t) + 8 + MSG_LENGTH_AVG) *
+            (size_t)(max_count > 0 ? max_count
+                                   : 5); // no-split buffer requires an additional 8 bytes
+        buf_size = buf_size - (buf_size % 4);
+        uint8_t* buffer_storage =
+            (uint8_t*)heap_caps_malloc(buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_32BIT);
+        if (buffer_storage == NULL) {
+            ESP_LOGE(tag, "buff alloc failed");
+        } else {
+            buf_handle = xRingbufferCreateStatic(
+                buf_size, RINGBUF_TYPE_NOSPLIT, buffer_storage, buffer_struct);
+        }
+    } else {
+        ESP_LOGE(tag, "ringbuf alloc failed");
+    }
+    return buf_handle;
 }
-void messaging_fill_messages(messaging_list_t * target_subscriber){
-	single_message_t * message=NULL;
+void messaging_fill_messages(messaging_list_t* target_subscriber) {
+    single_message_t* message = NULL;
     UBaseType_t uxItemsWaiting;
-
+    if (!top.buf_handle) {
+        ESP_LOGE(tag, "Queue initialization error!");
+        return;
+    }
     vRingbufferGetInfo(top.buf_handle, NULL, NULL, NULL, NULL, &uxItemsWaiting);
-    for(size_t i=0;i<uxItemsWaiting;i++){
-    	message= messaging_retrieve_message(top.buf_handle);
-    	if(message){
-			//re-post to original queue so it is available to future subscribers
-			messaging_post_to_queue(get_handle_ptr(&top), message, message->msg_size);
-			// post to new subscriber
-			messaging_post_to_queue(get_handle_ptr(target_subscriber) , message, message->msg_size);
-			FREE_AND_NULL(message);
-    	}
+    for (size_t i = 0; i < uxItemsWaiting; i++) {
+        message = messaging_retrieve_message(top.buf_handle);
+        if (message) {
+            // re-post to original queue so it is available to future subscribers
+            messaging_post_to_queue(get_handle_ptr(&top), message, message->msg_size);
+            // post to new subscriber
+            messaging_post_to_queue(get_handle_ptr(target_subscriber), message, message->msg_size);
+            FREE_AND_NULL(message);
+        }
     }
 }
-messaging_handle_t messaging_register_subscriber(uint8_t max_count, char * name){
-	messaging_list_t * cur=&top;
-	while(cur->next){
-		cur = get_struct_ptr(cur->next);
-	}
-	cur->next=malloc_init_external(sizeof(messaging_list_t));
-	if(!cur->next){
-		ESP_LOGE(tag,"subscriber alloc failed");
-		return NULL;
-	}
-	memset(cur->next,0x00,sizeof(messaging_list_t));
-	cur = get_struct_ptr(cur->next);
-	cur->max_count=max_count;
-	cur->subscriber_name=strdup_psram(name);
-	cur->buf_handle = messaging_create_ring_buffer(max_count);
-	if(cur->buf_handle){
-		messaging_fill_messages(cur);
-	}
-	return cur->buf_handle;
+messaging_handle_t messaging_register_subscriber(uint8_t max_count, char* name) {
+    messaging_list_t* cur = &top;
+    while (cur->next) {
+        cur = get_struct_ptr(cur->next);
+    }
+    cur->next = malloc_init_external(sizeof(messaging_list_t));
+    if (!cur->next) {
+        ESP_LOGE(tag, "subscriber alloc failed");
+        return NULL;
+    }
+    memset(cur->next, 0x00, sizeof(messaging_list_t));
+    cur = get_struct_ptr(cur->next);
+    cur->max_count = max_count;
+    cur->subscriber_name = strdup_psram(name);
+    cur->buf_handle = messaging_create_ring_buffer(max_count);
+    if (cur->buf_handle) {
+        messaging_fill_messages(cur);
+    }
+    return cur->buf_handle;
 }
-void messaging_service_init(){
-	size_t max_count=15;
-	top.buf_handle = messaging_create_ring_buffer(max_count);
-	if(!top.buf_handle){
-		ESP_LOGE(tag, "messaging service init failed.");
-	}
-	else {
-		top.max_count = max_count;
-		top.subscriber_name = strdup_psram("messaging");
-	}
-	return;
+void messaging_service_init() {
+    size_t max_count = 15;
+    ESP_LOGI(tag, "Setting up messaging");
+    top.buf_handle = messaging_create_ring_buffer(max_count);
+    if (!top.buf_handle) {
+        ESP_LOGE(tag, "messaging service init failed.");
+    } else {
+        top.max_count = max_count;
+        top.subscriber_name = strdup_psram("messaging");
+    }
+    return;
 }
 
-const char * messaging_get_type_desc(messaging_types msg_type){
-	switch (msg_type) {
-	CASE_TO_STR(MESSAGING_INFO);
-	CASE_TO_STR(MESSAGING_WARNING);
-	CASE_TO_STR(MESSAGING_ERROR);
-		default:
-			return "Unknown";
-			break;
-	}
+const char* messaging_get_type_desc(messaging_types msg_type) {
+    switch (msg_type) {
+        CASE_TO_STR(MESSAGING_INFO);
+        CASE_TO_STR(MESSAGING_WARNING);
+        CASE_TO_STR(MESSAGING_ERROR);
+    default:
+        return "Unknown";
+        break;
+    }
 }
-const char * messaging_get_class_desc(messaging_classes msg_class){
-	switch (msg_class) {
-	CASE_TO_STR(MESSAGING_CLASS_OTA);
-	CASE_TO_STR(MESSAGING_CLASS_SYSTEM);
-	CASE_TO_STR(MESSAGING_CLASS_STATS);
-	CASE_TO_STR(MESSAGING_CLASS_CFGCMD);
-	CASE_TO_STR(MESSAGING_CLASS_BT);
-		default:
-			return "Unknown";
-			break;
-	}
+const char* messaging_get_class_desc(messaging_classes msg_class) {
+    switch (msg_class) {
+        CASE_TO_STR(MESSAGING_CLASS_OTA);
+        CASE_TO_STR(MESSAGING_CLASS_SYSTEM);
+        CASE_TO_STR(MESSAGING_CLASS_STATS);
+        CASE_TO_STR(MESSAGING_CLASS_CFGCMD);
+        CASE_TO_STR(MESSAGING_CLASS_BT);
+    default:
+        return "Unknown";
+        break;
+    }
 }
 
-cJSON *  messaging_retrieve_messages(RingbufHandle_t buf_handle){
-	single_message_t * message=NULL;
-	cJSON * json_messages=cJSON_CreateArray();
-	cJSON * json_message=NULL;
-	size_t item_size;
+cJSON* messaging_retrieve_messages(RingbufHandle_t buf_handle) {
+    single_message_t* message = NULL;
+    cJSON* json_messages = cJSON_CreateArray();
+    cJSON* json_message = NULL;
+    size_t item_size;
     UBaseType_t uxItemsWaiting;
     vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, NULL, &uxItemsWaiting);
-	for(int i = 0;i<uxItemsWaiting;i++){
-		message = (single_message_t *)xRingbufferReceive(buf_handle, &item_size, pdMS_TO_TICKS(50));
-		//Check received data
-		if (message== NULL) {
-			ESP_LOGE(tag,"received null ptr");
-		}
-		else {
-			json_message = cJSON_CreateObject();
-			cJSON_AddStringToObject(json_message, "message", message->message);
-			cJSON_AddStringToObject(json_message, "type", messaging_get_type_desc(message->type));
-			cJSON_AddStringToObject(json_message, "class", messaging_get_class_desc(message->msg_class));
-			cJSON_AddNumberToObject(json_message,"sent_time",message->sent_time);
-			cJSON_AddNumberToObject(json_message,"current_time",esp_timer_get_time() / 1000);
-			cJSON_AddItemToArray(json_messages,json_message);
-			vRingbufferReturnItem(buf_handle, (void *)message);
-		}
-	}
-	return json_messages;
+    for (int i = 0; i < uxItemsWaiting; i++) {
+        message = (single_message_t*)xRingbufferReceive(buf_handle, &item_size, pdMS_TO_TICKS(50));
+        // Check received data
+        if (message == NULL) {
+            ESP_LOGE(tag, "received null ptr");
+        } else {
+            json_message = cJSON_CreateObject();
+            cJSON_AddStringToObject(json_message, "message", message->message);
+            cJSON_AddStringToObject(json_message, "type", messaging_get_type_desc(message->type));
+            cJSON_AddStringToObject(
+                json_message, "class", messaging_get_class_desc(message->msg_class));
+            cJSON_AddNumberToObject(json_message, "sent_time", message->sent_time);
+            cJSON_AddNumberToObject(json_message, "current_time", esp_timer_get_time() / 1000);
+            cJSON_AddItemToArray(json_messages, json_message);
+            vRingbufferReturnItem(buf_handle, (void*)message);
+        }
+    }
+    return json_messages;
 }
-single_message_t *  messaging_retrieve_message(RingbufHandle_t buf_handle){
-	single_message_t * message=NULL;
-	single_message_t * message_copy=NULL;
-	size_t item_size;
+single_message_t* messaging_retrieve_message(RingbufHandle_t buf_handle) {
+    single_message_t* message = NULL;
+    single_message_t* message_copy = NULL;
+    size_t item_size;
     UBaseType_t uxItemsWaiting;
     vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, NULL, &uxItemsWaiting);
-	if(uxItemsWaiting>0){
-		message = (single_message_t *)xRingbufferReceive(buf_handle, &item_size, pdMS_TO_TICKS(50));
-		message_copy  = clone_obj_psram(message,item_size);
-		vRingbufferReturnItem(buf_handle, (void *)message);
-	}
-	return message_copy;
+    if (uxItemsWaiting > 0) {
+        message = (single_message_t*)xRingbufferReceive(buf_handle, &item_size, pdMS_TO_TICKS(50));
+        message_copy = clone_obj_psram(message, item_size);
+        vRingbufferReturnItem(buf_handle, (void*)message);
+    }
+    return message_copy;
 }
 
-esp_err_t messaging_post_to_queue(messaging_handle_t subscriber_handle, single_message_t * message, size_t message_size){
-	size_t item_size=0;
-	messaging_list_t * subscriber=get_struct_ptr(subscriber_handle);
-	if(!subscriber->buf_handle){
-		ESP_LOGE(tag,"post failed: null buffer for %s", str_or_unknown(subscriber->subscriber_name));
-		return ESP_FAIL;
-	}
-	void * pItem=NULL;
-	UBaseType_t res=pdFALSE;
-	while(1){
-		ESP_LOGD(tag,"Attempting to reserve %d bytes for %s",message_size, str_or_unknown(subscriber->subscriber_name));
-		res =  xRingbufferSendAcquire(subscriber->buf_handle, &pItem, message_size, pdMS_TO_TICKS(50));
-		if(res == pdTRUE && pItem){
-			ESP_LOGD(tag,"Reserving complete for %s", str_or_unknown(subscriber->subscriber_name));
-			memcpy(pItem,message,message_size);
-			xRingbufferSendComplete(subscriber->buf_handle, pItem);
-			break;
-		}
-		ESP_LOGD(tag,"Dropping for %s",str_or_unknown(subscriber->subscriber_name));
-		single_message_t * dummy = (single_message_t *)xRingbufferReceive(subscriber->buf_handle, &item_size, pdMS_TO_TICKS(50));
-		if (dummy== NULL) {
-			ESP_LOGE(tag,"Dropping message failed");
-			break;
-		}
-		else {
-			ESP_LOGD(tag,"Dropping message of %d bytes for %s",item_size, str_or_unknown(subscriber->subscriber_name));
-			vRingbufferReturnItem(subscriber->buf_handle, (void *)dummy);
-		}
-	}
-	if (res != pdTRUE) {
-		ESP_LOGE(tag,"post to %s failed",str_or_unknown(subscriber->subscriber_name));
-		return ESP_FAIL;
-	}
-	return ESP_OK;
+esp_err_t messaging_post_to_queue(
+    messaging_handle_t subscriber_handle, single_message_t* message, size_t message_size) {
+    size_t item_size = 0;
+    messaging_list_t* subscriber = get_struct_ptr(subscriber_handle);
+    if (!subscriber->buf_handle) {
+        ESP_LOGE(
+            tag, "post failed: null buffer for %s", str_or_unknown(subscriber->subscriber_name));
+        return ESP_FAIL;
+    }
+    void* pItem = NULL;
+    UBaseType_t res = pdFALSE;
+    while (1) {
+        ESP_LOGD(tag, "Attempting to reserve %d bytes for %s", message_size,
+            str_or_unknown(subscriber->subscriber_name));
+        res =
+            xRingbufferSendAcquire(subscriber->buf_handle, &pItem, message_size, pdMS_TO_TICKS(50));
+        if (res == pdTRUE && pItem) {
+            ESP_LOGD(tag, "Reserving complete for %s", str_or_unknown(subscriber->subscriber_name));
+            memcpy(pItem, message, message_size);
+            xRingbufferSendComplete(subscriber->buf_handle, pItem);
+            break;
+        }
+        ESP_LOGD(tag, "Dropping for %s", str_or_unknown(subscriber->subscriber_name));
+        single_message_t* dummy = (single_message_t*)xRingbufferReceive(
+            subscriber->buf_handle, &item_size, pdMS_TO_TICKS(50));
+        if (dummy == NULL) {
+            ESP_LOGE(tag, "Dropping message failed");
+            break;
+        } else {
+            ESP_LOGD(tag, "Dropping message of %d bytes for %s", item_size,
+                str_or_unknown(subscriber->subscriber_name));
+            vRingbufferReturnItem(subscriber->buf_handle, (void*)dummy);
+        }
+    }
+    if (res != pdTRUE) {
+        ESP_LOGE(tag, "post to %s failed", str_or_unknown(subscriber->subscriber_name));
+        return ESP_FAIL;
+    }
+    return ESP_OK;
+}
+esp_err_t messaging_type_to_err_type(messaging_types type) {
+    switch (type) {
+    case MESSAGING_INFO:
+        return ESP_LOG_INFO;
+        break;
+    case MESSAGING_ERROR:
+        return ESP_LOG_ERROR;
+        break;
+    case MESSAGING_WARNING:
+        return ESP_LOG_WARN;
+        break;
+    default:
+        return ESP_LOG_DEBUG;
+        break;
+    }
+    return ESP_LOG_DEBUG;
 }
-	esp_err_t messaging_type_to_err_type(messaging_types type){
-		switch (type) {
-		case MESSAGING_INFO:
-			return ESP_LOG_INFO;
-			break;
-		case MESSAGING_ERROR:
-			return ESP_LOG_ERROR;
-			break;
-		case MESSAGING_WARNING:
-			return ESP_LOG_WARN;
-			break;
-		default:
-			return ESP_LOG_DEBUG;
-			break;
-		}
-		return ESP_LOG_DEBUG;
-	}
-    
-void messaging_post_message(messaging_types type,messaging_classes msg_class, const char *fmt, ...){
+
+void messaging_post_message(
+    messaging_types type, messaging_classes msg_class, const char* fmt, ...) {
     va_list va;
-	va_start(va, fmt);
+    va_start(va, fmt);
     vmessaging_post_message(type, msg_class, fmt, va);
     va_end(va);
 }
-    
-void vmessaging_post_message(messaging_types type,messaging_classes msg_class, const char *fmt, va_list va){    
-	single_message_t * message=NULL;
-	size_t msg_size=0;
-	size_t ln =0;
-	messaging_list_t * cur=&top;
-	ln = vsnprintf(NULL, 0, fmt, va)+1;
-	msg_size = sizeof(single_message_t)+ln;
-	message = (single_message_t *)malloc_init_external(msg_size);
-	vsprintf(message->message, fmt, va);
-	message->msg_size = msg_size;
-	message->type = type;
-	message->msg_class = msg_class;
-	message->sent_time = esp_timer_get_time() / 1000;
-	if(type==MESSAGING_WARNING) {
-		ESP_LOGW(tag,"%s",message->message);	
-	}
-	else if(type==MESSAGING_ERROR) {
-		ESP_LOGE(tag,"%s",message->message);	
-	}
-	else {
-		ESP_LOGD(tag,"Post: %s",message->message);
-	}
 
-	while(cur){
-		messaging_post_to_queue(get_handle_ptr(cur),  message, msg_size);
-		cur = get_struct_ptr(cur->next);
-	}
-	FREE_AND_NULL(message);
-	return;
+void vmessaging_post_message(
+    messaging_types type, messaging_classes msg_class, const char* fmt, va_list va) {
+    single_message_t* message = NULL;
+    size_t msg_size = 0;
+    size_t ln = 0;
+    messaging_list_t* cur = &top;
+    ln = vsnprintf(NULL, 0, fmt, va) + 1;
+    msg_size = sizeof(single_message_t) + ln;
+    message = (single_message_t*)malloc_init_external(msg_size);
+    vsprintf(message->message, fmt, va);
+    message->msg_size = msg_size;
+    message->type = type;
+    message->msg_class = msg_class;
+    message->sent_time = esp_timer_get_time() / 1000;
+    if (type == MESSAGING_WARNING) {
+        ESP_LOGW(tag, "%s", message->message);
+    } else if (type == MESSAGING_ERROR) {
+        ESP_LOGE(tag, "%s", message->message);
+    } else {
+        ESP_LOGD(tag, "Post: %s", message->message);
+    }
 
+    while (cur) {
+        messaging_post_to_queue(get_handle_ptr(cur), message, msg_size);
+        cur = get_struct_ptr(cur->next);
+    }
+    FREE_AND_NULL(message);
+    return;
 }
-char * messaging_alloc_format_string(const char *fmt, ...) {
-	va_list va;
-	va_start(va, fmt);
-	size_t ln = vsnprintf(NULL, 0, fmt, va)+1;
-	char * message_txt = malloc_init_external(ln);
-	if(message_txt){
-		vsprintf(message_txt, fmt, va);
-		va_end(va);
-	}
-	else{
-		ESP_LOGE(tag, "Memory allocation failed while sending message");
-	}
-	return message_txt;
+char* messaging_alloc_format_string(const char* fmt, ...) {
+    va_list va;
+    va_start(va, fmt);
+    size_t ln = vsnprintf(NULL, 0, fmt, va) + 1;
+    char* message_txt = malloc_init_external(ln);
+    if (message_txt) {
+        vsprintf(message_txt, fmt, va);
+        va_end(va);
+    } else {
+        ESP_LOGE(tag, "Memory allocation failed while sending message");
+    }
+    return message_txt;
 }
-void log_send_messaging(messaging_types msgtype,const char *fmt, ...) {
-	va_list va;
-	va_start(va, fmt);
-	size_t ln = vsnprintf(NULL, 0, fmt, va)+1;
-	char * message_txt = malloc_init_external(ln);
-	if(message_txt){
-		vsprintf(message_txt, fmt, va);
-		va_end(va);
-		ESP_LOG_LEVEL_LOCAL(messaging_type_to_err_type(msgtype),tag, "%s",message_txt);
-		messaging_post_message(msgtype, MESSAGING_CLASS_SYSTEM, message_txt );
-		free(message_txt);
-	}
-	else{
-		ESP_LOGE(tag, "Memory allocation failed while sending message");
-	}
+void log_send_messaging(messaging_types msgtype, const char* fmt, ...) {
+    va_list va;
+    va_start(va, fmt);
+    size_t ln = vsnprintf(NULL, 0, fmt, va) + 1;
+    char* message_txt = malloc_init_external(ln);
+    if (message_txt) {
+        vsprintf(message_txt, fmt, va);
+        va_end(va);
+        ESP_LOG_LEVEL_LOCAL(messaging_type_to_err_type(msgtype), tag, "%s", message_txt);
+        messaging_post_message(msgtype, MESSAGING_CLASS_SYSTEM, message_txt);
+        free(message_txt);
+    } else {
+        ESP_LOGE(tag, "Memory allocation failed while sending message");
+    }
 }
 
-void cmd_send_messaging(const char * cmdname,messaging_types msgtype, const char *fmt, ...){
-	va_list va;
-	va_start(va, fmt);
-	size_t cmd_len = strlen(cmdname)+1;
-	size_t ln = vsnprintf(NULL, 0, fmt, va)+1;
-	char * message_txt = malloc_init_external(ln+cmd_len);
-	if(message_txt){
-		strcpy(message_txt,cmdname);
-		strcat(message_txt,"\n");
-		vsprintf((message_txt+cmd_len), fmt, va);
-		va_end(va);
-		ESP_LOG_LEVEL_LOCAL(messaging_type_to_err_type(msgtype),tag, "%s",message_txt);
-		messaging_post_message(msgtype, MESSAGING_CLASS_CFGCMD, message_txt );
-		free(message_txt);
-	}
-	else{
-		ESP_LOGE(tag, "Memory allocation failed while sending message");
-	}
+void cmd_send_messaging(const char* cmdname, messaging_types msgtype, const char* fmt, ...) {
+    va_list va;
+    va_start(va, fmt);
+    size_t cmd_len = strlen(cmdname) + 1;
+    size_t ln = vsnprintf(NULL, 0, fmt, va) + 1;
+    char* message_txt = malloc_init_external(ln + cmd_len);
+    if (message_txt) {
+        strcpy(message_txt, cmdname);
+        strcat(message_txt, "\n");
+        vsprintf((message_txt + cmd_len), fmt, va);
+        va_end(va);
+        ESP_LOG_LEVEL_LOCAL(messaging_type_to_err_type(msgtype), tag, "%s", message_txt);
+        messaging_post_message(msgtype, MESSAGING_CLASS_CFGCMD, message_txt);
+        free(message_txt);
+    } else {
+        ESP_LOGE(tag, "Memory allocation failed while sending message");
+    }
 }

+ 7 - 9
components/services/monitor.c

@@ -5,7 +5,7 @@
    software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    CONDITIONS OF ANY KIND, either express or implied.
 */
-
+#define LOG_LOCAL_LEVEL ESP_LOG_INFO
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -20,8 +20,6 @@
 #include "buttons.h"
 #include "led.h"
 #include "globdefs.h"
-// #include "Configurator.h"
-// TODO: Add support for the commented code: search for TODO in the code below")
 #include "accessors.h"
 #include "messaging.h"
 #include "cJSON.h"
@@ -163,7 +161,7 @@ static void jack_handler_default(void *id, button_event_e event, button_press_e
  *
  */
 bool jack_inserted_svc (void) {
-	sys_GPIO * jack=NULL;
+	sys_gpio_config * jack=NULL;
 	if(SYS_GPIOS_NAME(jack,jack)){
 		return button_is_pressed(jack->pin, NULL);
 	}
@@ -184,7 +182,7 @@ static void spkfault_handler_default(void *id, button_event_e event, button_pres
  *
  */
 bool spkfault_svc (void) {
-	sys_GPIO * spkfault=NULL;
+	sys_gpio_config * spkfault=NULL;
 	if(SYS_GPIOS_NAME(spkfault,spkfault)){
 		return button_is_pressed(spkfault->pin, NULL);
 	}
@@ -209,10 +207,10 @@ static void pseudo_idle(void *arg) {
  */
 void monitor_svc_init(void) {
  	ESP_LOGI(TAG, "Initializing monitoring");
-	sys_Services * services = NULL;
-	sys_GPIO * gpio=NULL;
+	sys_services_config * services = NULL;
+	sys_gpio_config * gpio=NULL;
 	if(SYS_GPIOS_NAME(jack,gpio) && gpio->pin>=0){
-		ESP_LOGI(TAG,"Adding jack (%s) detection GPIO %d", gpio->level ? "high" : "low", gpio->pin);
+		ESP_LOGI(TAG,"Adding jack (%s) detection GPIO %d",  sys_gpio_lvl_name(gpio->level), gpio->pin);
 		button_create(NULL, gpio->pin, gpio->level ? BUTTON_HIGH : BUTTON_LOW, false, 250, jack_handler_default, 0, -1);
 	}
 	if(SYS_GPIOS_NAME(spkfault,gpio) && gpio->pin>=0){
@@ -220,7 +218,7 @@ void monitor_svc_init(void) {
 		button_create(NULL, gpio->pin, gpio->level ? BUTTON_HIGH : BUTTON_LOW, false, 0, spkfault_handler_default, 0, -1);		
 	}
 	// do we want stats
-	monitor_stats = SYS_SERVICES(services) && services->statistics;
+	monitor_stats = sys_services_config(services) && services->statistics;
 
 	ESP_LOGI(TAG, "Heap internal:%zu (min:%zu) external:%zu (min:%zu) dma:%zu (min:%zu)",
 			heap_caps_get_free_size(MALLOC_CAP_INTERNAL),

+ 8 - 2
components/services/monitor.h

@@ -1,4 +1,4 @@
-/* 
+/*
  *  Squeezelite for esp32
  *
  *  (c) Philippe G. 2019, philippe_44@outlook.com
@@ -7,8 +7,11 @@
  *  https://opensource.org/licenses/MIT
  *
  */
- 
+
 #pragma once
+#ifdef __cplusplus
+extern "C" {
+#endif
 extern void (*pseudo_idle_svc)(uint32_t now);
 
 extern void (*jack_handler_svc)(bool inserted);
@@ -20,3 +23,6 @@ extern bool spkfault_svc(void);
 extern void (*battery_handler_svc)(float value, int cells);
 extern float battery_value_svc(void);
 extern uint16_t battery_level_svc(void);
+#ifdef __cplusplus
+}
+#endif

+ 281 - 158
components/services/services.c

@@ -5,44 +5,46 @@
    software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    CONDITIONS OF ANY KIND, either express or implied.
 */
-
-#include <stdio.h>
 #include "freertos/FreeRTOS.h"
 #include "freertos/timers.h"
-#include "esp_log.h"
-#include "esp_sleep.h"
-#include "driver/rtc_io.h"
-#include "driver/ledc.h"
+#include <stdio.h>
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#include "Config.h"
+#include "accessors.h"
+#include "battery.h"
+#include "buttons.h"
 #include "driver/i2c.h"
+#include "driver/ledc.h"
 #include "driver/rmt.h"
-#include "Configurator.h"
+#include "driver/rtc_io.h"
+#include "esp_log.h"
+#include "esp_sleep.h"
+#include "globdefs.h"
 #include "gpio_exp.h"
-#include "battery.h"
 #include "led.h"
-#include "monitor.h"
-#include "globdefs.h"
-#include "accessors.h"
 #include "messaging.h"
-#include "buttons.h"
+#include "monitor.h"
 #include "services.h"
+#include "tools.h"
 
 extern void battery_svc_init(void);
 extern void monitor_svc_init(void);
 extern void led_svc_init(void);
 
-int i2c_system_port = I2C_SYSTEM_PORT;
-int i2c_system_speed = 400000;
 int spi_system_host = SPI_SYSTEM_HOST;
 int spi_system_dc_gpio = -1;
 int rmt_system_base_tx_channel = RMT_CHANNEL_0;
-int rmt_system_base_rx_channel = RMT_CHANNEL_MAX-1;
+int rmt_system_base_rx_channel = RMT_CHANNEL_MAX - 1;
 
 pwm_system_t pwm_system = {
-		.timer = LEDC_TIMER_0,
-		.base_channel = LEDC_CHANNEL_0,
-		.max = (1 << LEDC_TIMER_13_BIT),
+    .timer = LEDC_TIMER_0,
+    .base_channel = LEDC_CHANNEL_0,
+    .max = (1 << LEDC_TIMER_13_BIT),
 };
-static sys_SleepService * sleep_config;
+static sys_sleep_config* sleep_config;
+static EXT_RAM_ATTR uint8_t gpio_exp_count = 0;
+static EXT_RAM_ATTR bool spi_configured = false;
+static EXT_RAM_ATTR bool i2c_configured = false;
 static EXT_RAM_ATTR struct {
     uint64_t wake_gpio, wake_level;
     uint64_t rtc_gpio, rtc_level;
@@ -55,31 +57,93 @@ static EXT_RAM_ATTR struct {
     uint32_t (*sleeper[10])(void);
 } sleep_context;
 
-static const char *TAG = "services";
+static const char* TAG = "services";
 
-void set_gpio_level(sys_GPIO*gpio,const char * name, gpio_mode_t mode){
-    gpio_pad_select_gpio(gpio->pin);
-    gpio_set_direction(gpio->pin, mode);
-    gpio_set_level(gpio->pin, gpio->level);    
-    ESP_LOGI(TAG, "set GPIO %u to %s, level %d", gpio->pin,name,  gpio->level);
-}
-void set_chip_power_gpio(sys_Gpios*gpios) {
-    
-    if(gpios->has_power){
-        gpios->power.level = 1;
-        set_gpio_level(&gpios->power,"vcc", GPIO_MODE_OUTPUT);
+bool are_GPIOExp_equal(const sys_exp_config* exp1, const sys_exp_config* exp2) {
+    if (exp1 == NULL || exp2 == NULL) {
+        return false; // Safeguard against NULL pointers
     }
-    if(gpios->has_GND){
-        gpios->GND.level = 0;
-        set_gpio_level(&gpios->GND,"gnd", GPIO_MODE_OUTPUT);
+
+    // Check if model, address, and base are the same
+    if (exp1->model != exp2->model || exp1->addr != exp2->addr || exp1->base != exp2->base) {
+        return false;
+    }
+
+    // Check if intr structure (pin and level) are the same
+    if (exp1->intr != exp2->intr) {
+        return false;
     }
 
+    return true;
+}
+
+bool sys_dev_config_callback(pb_istream_t* istream, pb_ostream_t* ostream, const pb_field_iter_t* field) {
+    ESP_LOGV(TAG, "Decoding/Encoding Devices, tag: %d", field->tag);
+    sys_exp_config** pExp = (sys_exp_config**)field->pData;
+    sys_exp_config* exp = NULL;
+
+    if (istream != NULL && field->tag == sys_dev_config_gpio_exp_tag) {
+        ESP_LOGD(TAG, "Decoding GPIO Expander #%d", gpio_exp_count + 1);
+        sys_exp_config entry = sys_exp_config_init_default;
+        if (!pb_decode(istream, &sys_exp_config_msg, &entry)) {
+            return false;
+        }
+        if (entry.model == sys_exp_models_UNSPECIFIED_EXP) {
+            ESP_LOGD(TAG, "Skipping GPIO Expander model %s", sys_exp_models_name(entry.model));
+            return true;
+        }
+        // Don't add the expander if it was already decoded. This could
+        // happen if both the configuration and the platform configuration
+        // contain the definition.
+        for (int i = 0; i < gpio_exp_count; i++) {
+            if (are_GPIOExp_equal(&(*pExp)[i], &entry)) {
+                ESP_LOGW(TAG, "GPIO Expander entry already exists, skipping addition.");
+                return true; // Skip adding as it already exists
+            }
+        }
+
+        gpio_exp_count++;
+
+        *pExp = heap_caps_realloc(*pExp, sizeof(sys_exp_config) * gpio_exp_count, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+
+        // Assert after realloc to ensure memory allocation was successful
+        assert(*pExp != NULL);
+        exp = (*pExp) + gpio_exp_count - 1; // Simplified pointer arithmetic
+        memcpy(exp, &entry, sizeof(entry));
+        ESP_LOGD(TAG, "GPIO Expander #%d model %s", gpio_exp_count, sys_exp_models_name(entry.model));
+
+    } else if (ostream != NULL && field->tag == sys_dev_config_gpio_exp_tag) {
+        ESP_LOGV(TAG, "Encoding %d GPIO Expanders", gpio_exp_count);
+
+        for (int i = 0; i < gpio_exp_count; i++) {
+            if (!pb_encode_tag_for_field(ostream, field)) {
+                return false;
+            }
+            if (!pb_encode_submessage(ostream, &sys_exp_config_msg, &(*pExp)[i])) {
+                return false;
+            }
+        }
+        ESP_LOGV(TAG, "GPIO Expander encoding completed");
+    }
+
+    return true;
+}
+
+void set_gpio_level(sys_gpio_config* gpio, const char* name, gpio_mode_t mode) {
+    ESP_LOGI(TAG, "set GPIO %u to %s, level %d", gpio->pin, name, gpio->level);
+    if (gpio->pin < 0) {
+        ESP_LOGW(TAG, "Invalid gpio %d for %s", gpio->pin, name);
+        return;
+    }
+    gpio_pad_select_gpio(gpio->pin);
+    gpio_set_direction(gpio->pin, mode);
+    gpio_set_level(gpio->pin, gpio->level);
 }
 
 /****************************************************************************************
  *
  */
-static void sleep_gpio_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
+static void sleep_gpio_handler(void* id, button_event_e event, button_press_e mode, bool long_press) {
     if (event == BUTTON_PRESSED) services_sleep_activate(SLEEP_ONGPIO);
 }
 
@@ -96,7 +160,7 @@ static void sleep_timer(uint32_t now) {
     if (!first) first = now;
 
     // only query callbacks every 30s if we have at least one sleeper
-    if (!*sleep_context.sleeper || now < last + 30*1000) return;
+    if (!*sleep_context.sleeper || now < last + 30 * 1000) return;
     last = now;
 
     // time to evaluate if we had spurious wake-up
@@ -151,12 +215,13 @@ static void sleep_battery(float level, int cells) {
  *
  */
 void services_sleep_init(void) {
-    ESP_LOGD(TAG,"Initializing sleep services");
-    if(!SYS_SERVICES_SLEEP(sleep_config)){
-     ESP_LOGD(TAG,"No sleep service configured")   ;
+    ESP_LOGD(TAG, "Initializing sleep services");
+    if (!sys_services_config_SLEEP(sleep_config)) {
+        ESP_LOGD(TAG, "No sleep service configured");
+        return;
     }
     // get the wake criteria
-    for(int i=0;i<sleep_config->wake_count;i++){
+    for (int i = 0; i < sleep_config->wake_count; i++) {
         if (!rtc_gpio_is_valid_gpio(sleep_config->wake[i].pin)) {
             ESP_LOGE(TAG, "invalid wake GPIO %d (not in RTC domain)", sleep_config->wake[i].pin);
         } else {
@@ -165,7 +230,8 @@ void services_sleep_init(void) {
             sleep_context.wake_level |= sleep_config->wake[i].level << sleep_config->wake[i].pin;
         }
     }
-    // when moving to esp-idf more recent than 4.4.x, multiple gpio wake-up with level specific can be done
+    // when moving to esp-idf more recent than 4.4.x, multiple gpio wake-up with level specific can
+    // be done
     if (sleep_context.wake_gpio) {
         ESP_LOGI(TAG, "Sleep wake-up gpio bitmap 0x%llx (active 0x%llx)", sleep_context.wake_gpio, sleep_context.wake_level);
     }
@@ -177,7 +243,7 @@ void services_sleep_init(void) {
         battery_handler_svc = sleep_battery;
         ESP_LOGI(TAG, "Sleep on battery level of %.2f", sleep_context.battery_level);
     }
-    for(int i = 0;i<sleep_config->rtc_count;i++){
+    for (int i = 0; i < sleep_config->rtc_count; i++) {
         if (!rtc_gpio_is_valid_gpio(sleep_config->rtc[i].pin)) {
             ESP_LOGE(TAG, "invalid rtc GPIO %d", sleep_config->rtc[i].pin);
         } else {
@@ -185,20 +251,21 @@ void services_sleep_init(void) {
             sleep_context.rtc_level |= sleep_config->rtc[i].level << sleep_config->rtc[i].pin;
         }
     }
-    // when moving to esp-idf more recent than 4.4.x, multiple gpio wake-up with level specific can be done
+    // when moving to esp-idf more recent than 4.4.x, multiple gpio wake-up with level specific can
+    // be done
     if (sleep_context.rtc_gpio) {
         ESP_LOGI(TAG, "RTC forced gpio bitmap 0x%llx (active 0x%llx)", sleep_context.rtc_gpio, sleep_context.rtc_level);
     }
 
     // get the GPIOs that activate sleep (we could check that we have a valid wake)
-    if(sleep_config->has_sleep && sleep_config->sleep.pin >=0 ){
-		ESP_LOGI(TAG, "Sleep activation gpio %d (active %d)", sleep_config->sleep.pin, sleep_config->sleep.level);
+    if (sleep_config->has_sleep && sleep_config->sleep.pin >= 0) {
+        ESP_LOGI(TAG, "Sleep activation gpio %d (active %d)", sleep_config->sleep.pin, sleep_config->sleep.level);
         button_create(NULL, sleep_config->sleep.pin, sleep_config->sleep.level ? BUTTON_HIGH : BUTTON_LOW, true, 0, sleep_gpio_handler, 0, -1);
     }
 
     // do we want delay sleep
-    
-    sleep_context.delay = sleep_config->delay*60*1000;
+
+    sleep_context.delay = sleep_config->delay * 60 * 1000;
 
     // now check why we woke-up
     esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
@@ -207,17 +274,19 @@ void services_sleep_init(void) {
 
         // find the type of wake-up
         uint64_t wake_gpio;
-        if (cause == ESP_SLEEP_WAKEUP_EXT0) wake_gpio = sleep_context.wake_gpio;
-        else wake_gpio = esp_sleep_get_ext1_wakeup_status();
+        if (cause == ESP_SLEEP_WAKEUP_EXT0)
+            wake_gpio = sleep_context.wake_gpio;
+        else
+            wake_gpio = esp_sleep_get_ext1_wakeup_status();
 
         // we might be woken up by infrared in which case we want a short sleep
         if (infrared_gpio() >= 0 && ((1LL << infrared_gpio()) & wake_gpio)) {
             sleep_context.spurious = 1;
-            if(sleep_config->spurious>0){
+            if (sleep_config->spurious > 0) {
                 sleep_context.spurious = sleep_config->spurious;
             }
-            sleep_context.spurious *= 60*1000;
-            
+            sleep_context.spurious *= 60 * 1000;
+
             ESP_LOGI(TAG, "spurious wake-up detection during %d sec", sleep_context.spurious / 1000);
         }
     }
@@ -226,7 +295,7 @@ void services_sleep_init(void) {
     if (sleep_context.delay || sleep_context.spurious) {
         sleep_context.idle_chain = pseudo_idle_svc;
         pseudo_idle_svc = sleep_timer;
-        if (sleep_context.delay) ESP_LOGI(TAG, "inactivity timer of %d minute(s)", sleep_context.delay / (60*1000));
+        if (sleep_context.delay) ESP_LOGI(TAG, "inactivity timer of %d minute(s)", sleep_context.delay / (60 * 1000));
     }
 }
 
@@ -235,7 +304,8 @@ void services_sleep_init(void) {
  */
 void services_sleep_activate(sleep_cause_e cause) {
     // call all sleep hooks that might want to do something
-    for (void (**suspend)(void) = sleep_context.suspend; *suspend; suspend++) (*suspend)();
+    for (void (**suspend)(void) = sleep_context.suspend; *suspend; suspend++)
+        (*suspend)();
 
     // isolate all possible GPIOs, except the wake-up and RTC-maintaines ones
     esp_sleep_config_gpio_isolate();
@@ -249,9 +319,11 @@ void services_sleep_activate(sleep_cause_e cause) {
 
         // do we need to maintain a pull-up or down of that GPIO
         if ((1LL << i) & sleep_context.rtc_gpio) {
-            if ((sleep_context.rtc_level >> i) & 0x01) rtc_gpio_pullup_en(i);
-            else rtc_gpio_pulldown_en(i);
-        // or is this not wake-up GPIO, just isolate it
+            if ((sleep_context.rtc_level >> i) & 0x01)
+                rtc_gpio_pullup_en(i);
+            else
+                rtc_gpio_pulldown_en(i);
+            // or is this not wake-up GPIO, just isolate it
         } else if (!((1LL << i) & sleep_context.wake_gpio)) {
             rtc_gpio_isolate(i);
         }
@@ -261,10 +333,11 @@ void services_sleep_activate(sleep_cause_e cause) {
     if (sleep_context.wake_gpio & (sleep_context.wake_gpio - 1)) {
         ESP_LOGI(TAG, "going to sleep cause %d, wake-up on multiple GPIO, any '1' wakes up 0x%llx", cause, sleep_context.wake_gpio);
 #if defined(CONFIG_IDF_TARGET_ESP32S3) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
-        if (!sleep_context.wake_level) esp_sleep_enable_ext1_wakeup(sleep_context.wake_gpio, ESP_EXT1_WAKEUP_ANY_LOW);
+        if (!sleep_context.wake_level)
+            esp_sleep_enable_ext1_wakeup(sleep_context.wake_gpio, ESP_EXT1_WAKEUP_ANY_LOW);
         else
 #endif
-        esp_sleep_enable_ext1_wakeup(sleep_context.wake_gpio, ESP_EXT1_WAKEUP_ANY_HIGH);
+            esp_sleep_enable_ext1_wakeup(sleep_context.wake_gpio, ESP_EXT1_WAKEUP_ANY_HIGH);
     } else if (sleep_context.wake_gpio) {
         int gpio = __builtin_ctzll(sleep_context.wake_gpio);
         int level = (sleep_context.wake_level >> gpio) & 0x01;
@@ -274,136 +347,186 @@ void services_sleep_activate(sleep_cause_e cause) {
         ESP_LOGW(TAG, "going to sleep cause %d, no wake-up option", cause);
     }
 
-    // we need to use a timer in case the same button is used for sleep and wake-up and it's "pressed" vs "released" selected
-    if (cause == SLEEP_ONKEY) xTimerStart(xTimerCreate("sleepTimer", pdMS_TO_TICKS(1000), pdFALSE, NULL, (void (*)(void*)) esp_deep_sleep_start), 0);
-    else esp_deep_sleep_start();
+    // we need to use a timer in case the same button is used for sleep and wake-up and it's
+    // "pressed" vs "released" selected
+    if (cause == SLEEP_ONKEY)
+        xTimerStart(xTimerCreate("sleepTimer", pdMS_TO_TICKS(1000), pdFALSE, NULL, (void (*)(void*))esp_deep_sleep_start), 0);
+    else
+        esp_deep_sleep_start();
 }
 
-
 /****************************************************************************************
  *
  */
-static void register_method(void **store, size_t size, void *method) {
-    for (int i = 0; i < size; i++, *store++) if (!*store) {
-        *store = method;
-        return;
-    }
+static void register_method(void** store, size_t size, void* method) {
+    for (int i = 0; i < size; i++, *store++)
+        if (!*store) {
+            *store = method;
+            return;
+        }
 }
 
 /****************************************************************************************
  *
  */
 void services_sleep_setsuspend(void (*hook)(void)) {
-    register_method((void**) sleep_context.suspend, sizeof(sleep_context.suspend)/sizeof(*sleep_context.suspend), (void*) hook);
+    register_method((void**)sleep_context.suspend, sizeof(sleep_context.suspend) / sizeof(*sleep_context.suspend), (void*)hook);
 }
 
 /****************************************************************************************
  *
  */
 void services_sleep_setsleeper(uint32_t (*sleeper)(void)) {
-    register_method((void**) sleep_context.sleeper, sizeof(sleep_context.sleeper)/sizeof(*sleep_context.sleeper), (void*) sleeper);
+    register_method((void**)sleep_context.sleeper, sizeof(sleep_context.sleeper) / sizeof(*sleep_context.sleeper), (void*)sleeper);
 }
+void services_ports_init(void) {
+    esp_err_t err = ESP_OK;
+
+    ESP_LOGI(TAG, "Initializing ports");
+    gpio_install_isr_service(0);
+    ESP_LOGD(TAG, "Checking i2c port usage");
+    if (platform->dev.dac.has_i2c && platform->dev.has_i2c && platform->dev.dac.i2c.port !=  sys_i2c_port_UNSPECIFIED &&
+        platform->dev.dac.i2c.port == platform->dev.i2c.port) {
+        ESP_LOGE(TAG, "Port %s is used for internal DAC use. Switching to ", sys_i2c_port_name(platform->dev.dac.i2c.port));
+        platform->dev.i2c.port = platform->dev.i2c.port == sys_i2c_port_PORT0 ? sys_i2c_port_PORT1 : sys_i2c_port_PORT0;
+        config_raise_changed(false);
+    }
 
-/****************************************************************************************
- *
- */
-void services_init(void) {
-	messaging_service_init();
-	gpio_install_isr_service(0);
-    // todo: untangle i2c stuff
-#ifdef CONFIG_I2C_LOCKED
-	if (i2c_system_port == 0) {
-		i2c_system_port = 1;
-		ESP_LOGE(TAG, "Port 0 is reserved for internal DAC use");
-	}
-#endif
+    // shared I2C bus
+    ESP_LOGD(TAG, "Configuring I2C");
+    const i2c_config_t* i2c_config = config_i2c_get(&platform->dev.i2c);
+    ESP_LOGD(TAG, "Stored I2C configuration [sda:%d scl:%d port:%s speed:%u]", i2c_config->sda_io_num, i2c_config->scl_io_num,
+        sys_i2c_port_name(platform->dev.i2c.port), i2c_config->master.clk_speed);
+    if (i2c_config->sda_io_num != -1 && i2c_config->scl_io_num != -1) {
+        ESP_LOGI(TAG, "Configuring I2C sda:%d scl:%d port:%s speed:%u", i2c_config->sda_io_num, i2c_config->scl_io_num,
+            sys_i2c_port_name(platform->dev.i2c.port), i2c_config->master.clk_speed);
+        i2c_param_config(platform->dev.i2c.port - sys_i2c_port_PORT0, i2c_config);
+        if ((err = i2c_driver_install(platform->dev.i2c.port - sys_i2c_port_PORT0, i2c_config->mode, 0, 0, 0)) != ESP_OK) {
+            ESP_LOGE(TAG, "Error setting up i2c: %s", esp_err_to_name(err));
+        } else {
+            i2c_configured = true;
+        }
+    } else {
+        if (platform->dev.has_display && platform->dev.display.has_common && platform->dev.display.which_dispType == sys_display_config_i2c_tag) {
+            ESP_LOGE(TAG, "I2C configuration missing for display %s", sys_display_drivers_name(platform->dev.display.common.driver));
+        } else {
+            ESP_LOGI(TAG, "Shared I2C not configured");
+        }
+    }
 
-	// set potential power GPIO on chip first in case expanders are power using these
-	set_chip_power_gpio(&platform->gpios);
-
-	// shared I2C bus
-	const i2c_config_t * i2c_config = config_i2c_get(&i2c_system_port);
-	ESP_LOGI(TAG,"Configuring I2C sda:%d scl:%d port:%u speed:%u", i2c_config->sda_io_num, i2c_config->scl_io_num, i2c_system_port, i2c_config->master.clk_speed);
-
-	if (i2c_config->sda_io_num != -1 && i2c_config->scl_io_num != -1) {
-		i2c_param_config(i2c_system_port, i2c_config);
-		i2c_driver_install(i2c_system_port, i2c_config->mode, 0, 0, 0 );
-	} else {
-		i2c_system_port = -1;
-		ESP_LOGW(TAG, "no I2C configured");
-	}
-
-	const spi_bus_config_t * spi_config = config_spi_get((spi_host_device_t*) &spi_system_host);
-	ESP_LOGI(TAG,"Configuring SPI mosi:%d miso:%d clk:%d host:%u dc:%d", spi_config->mosi_io_num, spi_config->miso_io_num, spi_config->sclk_io_num, spi_system_host, spi_system_dc_gpio);
-
-	if (spi_config->mosi_io_num != -1 && spi_config->sclk_io_num != -1) {
-		spi_bus_initialize( spi_system_host, spi_config, SPI_DMA_CH_AUTO );
-		if (spi_system_dc_gpio != -1) {
-			gpio_reset_pin(spi_system_dc_gpio);
-			gpio_set_direction( spi_system_dc_gpio, GPIO_MODE_OUTPUT );
-			gpio_set_level( spi_system_dc_gpio, 0 );
-		} else {
-			ESP_LOGW(TAG, "No DC GPIO set, SPI display will not work");
-		}
-	} else {
-		spi_system_host = -1;
-		ESP_LOGW(TAG, "no SPI configured");
-	}
-
-	// create GPIO expanders
-	gpio_exp_config_t gpio_exp_config;
-    if(platform->has_dev && platform->dev.gpio_exp_count>0){
-        for(int count = 0;count<platform->dev.gpio_exp_count;count++){
-            sys_GPIOExp * exp = &platform->dev.gpio_exp[count];
-            gpio_exp_config.base =  exp->base;
+    const spi_bus_config_t* spi_config = config_spi_get((spi_host_device_t*)&spi_system_host);
+    ESP_LOGD(TAG, "Stored SPI configuration[mosi:%d miso:%d clk:%d host:%u dc:%d]", spi_config->mosi_io_num, spi_config->miso_io_num,
+        spi_config->sclk_io_num, spi_system_host, spi_system_dc_gpio);
+    if (spi_config->mosi_io_num != -1 && spi_config->sclk_io_num != -1) {
+        ESP_LOGI(TAG, "Configuring SPI mosi:%d miso:%d clk:%d host:%u dc:%d", spi_config->mosi_io_num, spi_config->miso_io_num,
+            spi_config->sclk_io_num, spi_system_host, spi_system_dc_gpio);
+        if ((err = spi_bus_initialize(spi_system_host, spi_config, SPI_DMA_CH_AUTO)) != ESP_OK) {
+            ESP_LOGE(TAG, "Error setting up SPI bus: %s", esp_err_to_name(err));
+        } else {
+            spi_configured = true;
+        }
+        if (spi_system_dc_gpio != -1) {
+            gpio_reset_pin(spi_system_dc_gpio);
+            gpio_set_direction(spi_system_dc_gpio, GPIO_MODE_OUTPUT);
+            gpio_set_level(spi_system_dc_gpio, 0);
+        } else {
+            ESP_LOGW(TAG, "No DC GPIO set, SPI display will not work");
+        }
+    } else {
+        spi_system_host = -1;
+        if (platform->dev.has_display && platform->dev.display.has_common &&
+            platform->dev.display.common.driver != sys_display_drivers_UNSPECIFIED &&
+            platform->dev.display.which_dispType == sys_display_config_spi_tag) {
+            ESP_LOGE(TAG, "SPI bus configuration missing for display %s", sys_display_drivers_name(platform->dev.display.common.driver));
+        } else {
+            ESP_LOGI(TAG, "SPI bus not configured");
+        }
+    }
+}
+void services_gpio_init(void) {
+    ESP_LOGI(TAG, "Initializing GPIOs");
+    // set potential power GPIO on chip first in case expanders are power using these
+    sys_gpio_config* gpio = NULL;
+    if (SYS_GPIOS_NAME(power, gpio) && gpio->pin >= 0) {
+        ESP_LOGD(TAG, "Handling power gpio");
+        gpio->level = sys_gpio_lvl_HIGH;
+        set_gpio_level(gpio, "power", GPIO_MODE_OUTPUT);
+    } else {
+        ESP_LOGD(TAG, "No power GPIO defined");
+    }
+
+    if (SYS_GPIOS_NAME(GND, gpio) && gpio->pin >= 0) {
+        ESP_LOGD(TAG, "Handling GND gpio");
+        gpio->level = sys_gpio_lvl_LOW;
+        set_gpio_level(gpio, "GND", GPIO_MODE_OUTPUT);
+    } else {
+        ESP_LOGD(TAG, "No GND gpio defined");
+    }
+
+    // create GPIO expanders
+    gpio_exp_config_t gpio_exp_config;
+    if (platform->has_dev && gpio_exp_count > 0 && platform->dev.gpio_exp[0].model != sys_exp_models_UNSPECIFIED_EXP) {
+        ESP_LOGI(TAG, "Initializing %d GPIO Expander(s)", gpio_exp_count);
+        for (int count = 0; count < gpio_exp_count; count++) {
+            sys_exp_config* exp = &platform->dev.gpio_exp[count];
+            if (exp->model == sys_exp_models_UNSPECIFIED_EXP) {
+                ESP_LOGD(TAG, "Skipping unknown model");
+                continue;
+            }
+            gpio_exp_config.phy.ena_pin = -1;
+            gpio_exp_config.base = exp->base;
             gpio_exp_config.count = exp->count;
             gpio_exp_config.phy.addr = exp->addr;
-            if(exp->has_intr){
-                gpio_exp_config.intr = exp->intr.pin;
+            gpio_exp_config.intr = exp->intr;
+            if (exp->has_ena && exp->ena.pin >= 0) {
+                gpio_exp_config.phy.ena_pin = exp->ena.pin;
+                gpio_exp_config.phy.ena_lvl = exp->ena.level;
             }
-            else {
-                ESP_LOGW(TAG,"Expander doesn't have intr pin");
+            if (exp->which_ExpType == sys_exp_config_spi_tag) {
+                if (!spi_configured) {
+                    ESP_LOGE(TAG, "SPI bus not configured for GPIO Expander index %d (%s)", count, sys_exp_models_name(exp->model));
+                    continue;
+                }
+                gpio_exp_config.phy.cs_pin = exp->ExpType.spi.cs;
+                gpio_exp_config.phy.host =
+                    (!platform->dev.has_spi || (platform->dev.has_spi  &&  platform->dev.spi.host == sys_dev_common_hosts_NONE) ? sys_dev_common_hosts_Host0 : platform->dev.spi.host) - sys_dev_common_hosts_Host0;
+                gpio_exp_config.phy.speed = exp->ExpType.spi.speed > 0 ? exp->ExpType.spi.speed : 0;
+            } else {
+                if (!i2c_configured) {
+                    ESP_LOGE(TAG, "I2C bus not configured for GPIO Expander index %d (%s)", count, sys_exp_models_name(exp->model));
+                    continue;
+                }
+                gpio_exp_config.phy.port =
+                   ((!platform->dev.has_i2c || (platform->dev.has_i2c && platform->dev.i2c.port == sys_i2c_port_UNSPECIFIED) )? sys_dev_common_ports_SYSTEM : platform->dev.i2c.port) - sys_dev_common_ports_SYSTEM ;
             }
-            if(exp->which_ExpType == sys_GPIOExp_spi_tag){
-                gpio_exp_config.phy.cs_pin= exp->ExpType.spi.cs.pin;
-                gpio_exp_config.phy.host = exp->ExpType.spi.host == sys_HostEnum_UNSPECIFIED_HOST ?sys_HostEnum_Host0:exp->ExpType.spi.host -1;
-                gpio_exp_config.phy.speed = exp->ExpType.spi.speed>0?exp->ExpType.spi.speed:0;
-            }
-            else {
-                gpio_exp_config.phy.port = exp->ExpType.i2c.port == sys_PortEnum_UNSPECIFIED_SYSTPORT?sys_PortEnum_SYSTEM:exp->ExpType.i2c.port -1;
-            }
-            strncpy(gpio_exp_config.model,sys_GPIOExpModelEnum_name(exp->model),sizeof(gpio_exp_config.model)-1);
+            strncpy(gpio_exp_config.model, sys_exp_models_name(exp->model), sizeof(gpio_exp_config.model) - 1);
             gpio_exp_create(&gpio_exp_config);
         }
-	}
-    if(platform->has_gpios ){
-        // if(platform->gpios.has_GND){
-        //     platform->gpios.GND.level = 0;
-        //     set_gpio_level(&platform->gpios.GND,"GND", GPIO_MODE_OUTPUT);
-        // }
-        // if(platform->gpios.has_Vcc){
-        //     platform->gpios.Vcc.level = 1;
-        //     set_gpio_level(&platform->gpios.Vcc,"VCC", GPIO_MODE_OUTPUT);
-        // }
-        set_chip_power_gpio(&platform->gpios);
+    } else if (gpio_exp_count > 0) {
+        ESP_LOGW(TAG, "GPIO Expander count %d but none is valid", gpio_exp_count);
     }
-    
+}
+/****************************************************************************************
+ *
+ */
+void services_init(void) {
+    ESP_LOGI(TAG, "Initializing services");
+    esp_err_t err = ESP_OK;
 
-	// system-wide PWM timer configuration
-	ledc_timer_config_t pwm_timer = {
-		.duty_resolution = LEDC_TIMER_13_BIT,
-		.freq_hz = 5000,
+    // system-wide PWM timer configuration
+    ledc_timer_config_t pwm_timer = {
+        .duty_resolution = LEDC_TIMER_13_BIT,
+        .freq_hz = 5000,
 #ifdef CONFIG_IDF_TARGET_ESP32S3
         .speed_mode = LEDC_LOW_SPEED_MODE,
 #else
-		.speed_mode = LEDC_HIGH_SPEED_MODE,
+        .speed_mode = LEDC_HIGH_SPEED_MODE,
 #endif
-		.timer_num = pwm_system.timer,
-	};
-
-	ledc_timer_config(&pwm_timer);
+        .timer_num = pwm_system.timer,
+    };
 
-	led_svc_init();
-	battery_svc_init();
-	monitor_svc_init();
+    ledc_timer_config(&pwm_timer);
+    led_svc_init();
+    battery_svc_init();
+    monitor_svc_init();
 }

+ 2 - 2
components/services/services.h

@@ -7,7 +7,7 @@
  *  https://opensource.org/licenses/MIT
  *
  */
-#include "Configurator.h" 
+#include "Config.h" 
 #include "driver/gpio.h"
 
 #pragma once
@@ -17,4 +17,4 @@ void services_sleep_activate(sleep_cause_e cause);
 void services_sleep_setsuspend(void (*hook)(void));
 void services_sleep_setsleeper(uint32_t (*sleeper)(void));
 void services_sleep_init(void);
-void set_gpio_level(sys_GPIO*gpio,const char * name, gpio_mode_t mode);
+void set_gpio_level(sys_gpio_config*gpio,const char * name, gpio_mode_t mode);

+ 44 - 0
components/services/system_time.c

@@ -0,0 +1,44 @@
+/* LwIP SNTP example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+#include "Config.h"
+#include "esp_attr.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "esp_sleep.h"
+#include "esp_sntp.h"
+#include "esp_system.h"
+#include "tools.h"
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+static const char* TAG = "system_time";
+
+void system_time_init(void) {
+    char strftime_buf[64];
+    time_t now;
+    struct tm timeinfo;
+    const char* timezone =
+        platform->has_services && platform->services.timezone && strlen(platform->services.timezone) > 0 ? platform->services.timezone : "EST5EDT,M3.2.0/2,M11.1.0";
+    setenv("TZ", timezone, 1);
+    tzset();
+    time(&now);
+    localtime_r(&now, &timeinfo);
+    // Is time set? If not, tm_year will be (1970 - 1900).
+    if (timeinfo.tm_year < (2016 - 1900)) {
+        ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
+        sntp_servermode_dhcp(2);
+        esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
+        esp_sntp_setservername(0, "pool.ntp.org");
+        esp_sntp_init();
+
+    } else {
+        strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
+        ESP_LOGI(TAG, "Current date/time is: %s for time zone %s", strftime_buf, timezone);
+    }
+}

+ 2 - 0
components/services/system_time.h

@@ -0,0 +1,2 @@
+#pragma once;
+void system_time_init(void);

+ 8 - 7
components/spotify/Shim.cpp

@@ -10,7 +10,6 @@
 #include <PlainConnection.h>
 #include <memory>
 #include <vector>
-#include <iostream>
 #include <inttypes.h>
 #include <fstream>
 #include <stdarg.h>
@@ -29,9 +28,11 @@
 #include "esp_http_server.h"
 #include "cspot_private.h"
 #include "cspot_sink.h"
-#include "Configurator.h"
+#include "Config.h"
 #include "tools.h"
 #include "accessors.h"
+#include "tools_http_utils.h"
+
 static class cspotPlayer *player;
 
 static const struct {
@@ -69,7 +70,7 @@ private:
     void enableZeroConf(void);
 
     void runTask();
-    sys_Spotify * cspot_config = NULL;
+    sys_spotify_config * cspot_config = NULL;
 
 public:
     typedef enum {TRACK_INIT, TRACK_NOTIFY, TRACK_STREAM, TRACK_END} TrackStatus;
@@ -86,7 +87,7 @@ cspotPlayer::cspotPlayer(const char* name, httpd_handle_t server, int port, cspo
                         serverHandle(server), serverPort(port),
                         cmdHandler(cmdHandler), dataHandler(dataHandler) {
 
-    if(!SYS_SERVICES_SPOTIFY(cspot_config)){
+    if(!sys_services_config_SPOTIFY(cspot_config)){
         return;        
     }
     volume = cspot_config->volume;
@@ -360,8 +361,8 @@ void cspotPlayer::runTask() {
             // we might have been forced to use zeroConf, so store credentials and reset zeroConf usage
             if (!zeroConf) {
                 useZeroConf = false;
-                if(configurator_set_string(&sys_State_msg,sys_State_cspot_credentials_tag,sys_state,credentials.c_str())){
-                    configurator.RaiseStateModified();
+                if(system_set_string(&sys_state_data_msg,sys_state_data_cspot_credentials_tag,sys_state,credentials.c_str())){
+                    config_raise_state_changed();
                 }
             }
 
@@ -416,7 +417,7 @@ void cspotPlayer::runTask() {
                 if (state == DISCO) {
                     // update volume then
                     cspot_config->volume = volume;
-                    configurator_raise_changed();
+                    config_raise_changed(false);
                     // in ZeroConf mod, stay connected (in this loop)
                     if (!zeroConf) state = LINKED;
                 }

+ 3 - 3
components/spotify/cspot/bell/external/nanopb/generator/nanopb_generator.py

@@ -1754,6 +1754,9 @@ class ProtoFile:
             for extension in self.extensions:
                 yield extension.extension_decl()
             yield '\n'
+        yield '#ifdef __cplusplus\n'
+        yield 'extern "C" {\n'
+        yield '#endif\n\n'
 
         if self.enums:
                 yield '/* Helper constants for enums */\n'
@@ -1761,9 +1764,6 @@ class ProtoFile:
                     yield enum.auxiliary_defines() + '\n'
                 yield '\n'
 
-        yield '#ifdef __cplusplus\n'
-        yield 'extern "C" {\n'
-        yield '#endif\n\n'
 
         if self.messages:
             yield '/* Initializer values for message structs */\n'

+ 32 - 20
components/spotify/cspot/bell/external/nanopb/pb_decode.c

@@ -17,6 +17,24 @@
 #include "pb_decode.h"
 #include "pb_common.h"
 #include <stdio.h>
+#include <inttypes.h> // Include this header for PRIu64
+
+
+// Macros for debugging encode/decode
+// #define DUMP_PB_ENABLE 1
+#ifdef DUMP_PB_ENABLE
+#define DUMP_PB_FIELD(iter,msg) dump_pb_field(iter,msg, __FUNCTION__, __LINE__)
+#define DUMP_PB_HEAD(iter,msg) dump_pb_head(iter,msg, __FUNCTION__, __LINE__)
+#define DUMP_PB_MARK(msg) printf("MRK\t%-30s\t%-20s\t%-5d\n",msg, __FUNCTION__, __LINE__)
+#define WRITE_DEBUG_HEADER() write_debug_header()
+#else
+#define DUMP_PB_FIELD(iter,msg)
+#define DUMP_PB_HEAD(iter,msg) 
+#define DUMP_PB_MARK(msg) 
+#define WRITE_DEBUG_HEADER()
+#endif
+
+
 /**************************************
  * Declarations internal to this file *
  **************************************/
@@ -122,13 +140,13 @@ void dump_pb_field(const pb_field_iter_t* iter, const char* msg, const char* fun
            "| %-20s"
            "| %-20s"
            "| %-20s"
-           "| %-10zu|\n",
+           "| %-10zu| %-20zu|\n",
         "FLD", msg, func, line, 
         "","","",
         iter->submessage_index,iter->index, iter->field_info_index, iter->required_field_index, 
         iter->tag, iter->data_size, iter->array_size, 
         pb_ltype_description(iter->type), pb_htype_description(iter->type), pb_atype_description(iter->type), 
-        iter->type);
+        iter->type,PB_LTYPE(iter->type)==PB_LTYPE_STRING && iter->pData && strlen(iter->pData)>0?iter->pData:"");
 }
 void dump_pb_head(const pb_msgdesc_t* desc, const char* msg, const char* func, int line) {
     if (!desc) {
@@ -160,18 +178,7 @@ void dump_pb_head(const pb_msgdesc_t* desc, const char* msg, const char* func, i
         "");
 }
 
-// Macros for convenience
-#ifdef DUMP_PB_ENABLE
-#define DUMP_PB_FIELD(iter,msg) dump_pb_field(iter,msg, __FUNCTION__, __LINE__)
-#define DUMP_PB_HEAD(iter,msg) dump_pb_head(iter,msg, __FUNCTION__, __LINE__)
-#define DUMP_PB_MARK(msg) printf("MRK\t%-30s\t%-20s\t%-5d\n",msg, __FUNCTION__, __LINE__)
-#define WRITE_DEBUG_HEADER write_debug_header()
-#else
-#define DUMP_PB_FIELD(iter,msg)
-#define DUMP_PB_HEAD(iter,msg) 
-#define DUMP_PB_MARK(msg) 
-#define WRITE_DEBUG_HEADER()
-#endif
+
 
 static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count);
 static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof);
@@ -1282,7 +1289,7 @@ static bool checkreturn pb_decode_inner(pb_istream_t *stream, const pb_msgdesc_t
         if (PB_HTYPE(iter.type) == PB_HTYPE_REQUIRED
             && iter.required_field_index < PB_MAX_REQUIRED_FIELDS)
         {
-            DUMP_PB_FIELD("Mark field as seen",&iter);
+            DUMP_PB_FIELD(&iter,"Mark field as seen");
             uint32_t tmp = ((uint32_t)1 << (iter.required_field_index & 31));
             fields_seen.bitfield[iter.required_field_index >> 5] |= tmp;
         }
@@ -1595,6 +1602,7 @@ static bool checkreturn pb_dec_bool(pb_istream_t *stream, const pb_field_iter_t
     return pb_decode_bool(stream, (bool*)field->pData);
 }
 
+
 static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_iter_t *field)
 {
     if (PB_LTYPE(field->type) == PB_LTYPE_UVARINT)
@@ -1615,8 +1623,11 @@ static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_iter_
         else
             PB_RETURN_ERROR(stream, "invalid data_size");
 
-        if (clamped != value)
-            PB_RETURN_ERROR(stream, "integer too large");
+if (clamped != value) {
+    printf("Clamped value: %" PRIu64 ", Original value: %" PRIu64 "\n", clamped, value);
+    PB_RETURN_ERROR(stream, "integer too large");
+}
+
 
         return true;
     }
@@ -1660,8 +1671,10 @@ static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_iter_
         else
             PB_RETURN_ERROR(stream, "invalid data_size");
 
-        if (clamped != svalue)
+        if (clamped != svalue){
+            printf("Clamped value: %" PRIi64 ", Original value: %" PRIi64 "\n", clamped, svalue);
             PB_RETURN_ERROR(stream, "integer too large");
+        }
 
         return true;
     }
@@ -1749,12 +1762,11 @@ static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_iter_
 
     if (!pb_read(stream, dest, (size_t)size))
         return false;
-
 #ifdef PB_VALIDATE_UTF8
     if (!pb_validate_utf8((const char*)dest))
         PB_RETURN_ERROR(stream, "invalid utf8");
 #endif
-
+    DUMP_PB_FIELD(field,"String");
     return true;
 }
 

+ 2 - 1
components/spotify/cspot_sink.c

@@ -6,7 +6,7 @@
 #include "esp_console.h"
 #include "esp_pthread.h"
 #include "esp_system.h"
-#include "Configurator.h"
+#include "Config.h"
 #include "audio_controls.h"
 #include "display.h"
 #include "accessors.h"
@@ -15,6 +15,7 @@
 #include "tools.h"
 #include "cspot_private.h"
 #include "cspot_sink.h"
+#include "tools_http_utils.h"
 
 char EXT_RAM_ATTR deviceId[16];
 

+ 0 - 10
components/squeezelite-ota/component.mk

@@ -1,10 +0,0 @@
-#
-# "main" pseudo-component makefile.
-#
-# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
-
-# todo: add support for https
-COMPONENT_ADD_INCLUDEDIRS := .
-CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO -DCONFIG_OTA_ALLOW_HTTP=1
-
-

+ 0 - 41
components/squeezelite-ota/github.pem

@@ -1,41 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIHQjCCBiqgAwIBAgIQCgYwQn9bvO1pVzllk7ZFHzANBgkqhkiG9w0BAQsFADB1
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
-d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk
-IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE4MDUwODAwMDAwMFoXDTIwMDYwMzEy
-MDAwMFowgccxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB
-BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF
-Ewc1MTU3NTUwMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG
-A1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYD
-VQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
-xjyq8jyXDDrBTyitcnB90865tWBzpHSbindG/XqYQkzFMBlXmqkzC+FdTRBYyneZ
-w5Pz+XWQvL+74JW6LsWNc2EF0xCEqLOJuC9zjPAqbr7uroNLghGxYf13YdqbG5oj
-/4x+ogEG3dF/U5YIwVr658DKyESMV6eoYV9mDVfTuJastkqcwero+5ZAKfYVMLUE
-sMwFtoTDJFmVf6JlkOWwsxp1WcQ/MRQK1cyqOoUFUgYylgdh3yeCDPeF22Ax8AlQ
-xbcaI+GwfQL1FB7Jy+h+KjME9lE/UpgV6Qt2R1xNSmvFCBWu+NFX6epwFP/JRbkM
-fLz0beYFUvmMgLtwVpEPSwIDAQABo4IDeTCCA3UwHwYDVR0jBBgwFoAUPdNQpdag
-re7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFMnCU2FmnV+rJfQmzQ84mqhJ6kipMCUG
-A1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1UdDwEB/wQE
-AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0fBG4wbDA0
-oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2VydmVyLWcy
-LmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItZXYtc2Vy
-dmVyLWcyLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG/WwCATAqMCgGCCsGAQUFBwIB
-FhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGIBggrBgEF
-BQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBS
-BggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0
-U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAA
-MIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdgCkuQmQtBhYFIe7E6LMZ3AKPDWY
-BPkb37jjd80OyA3cEAAAAWNBYm0KAAAEAwBHMEUCIQDRZp38cTWsWH2GdBpe/uPT
-Wnsu/m4BEC2+dIcvSykZYgIgCP5gGv6yzaazxBK2NwGdmmyuEFNSg2pARbMJlUFg
-U5UAdgBWFAaaL9fC7NP14b1Esj7HRna5vJkRXMDvlJhV1onQ3QAAAWNBYm0tAAAE
-AwBHMEUCIQCi7omUvYLm0b2LobtEeRAYnlIo7n6JxbYdrtYdmPUWJQIgVgw1AZ51
-vK9ENinBg22FPxb82TvNDO05T17hxXRC2IYAdgC72d+8H4pxtZOUI5eqkntHOFeV
-CqtS6BqQlmQ2jh7RhQAAAWNBYm3fAAAEAwBHMEUCIQChzdTKUU2N+XcqcK0OJYrN
-8EYynloVxho4yPk6Dq3EPgIgdNH5u8rC3UcslQV4B9o0a0w204omDREGKTVuEpxG
-eOQwDQYJKoZIhvcNAQELBQADggEBAHAPWpanWOW/ip2oJ5grAH8mqQfaunuCVE+v
-ac+88lkDK/LVdFgl2B6kIHZiYClzKtfczG93hWvKbST4NRNHP9LiaQqdNC17e5vN
-HnXVUGw+yxyjMLGqkgepOnZ2Rb14kcTOGp4i5AuJuuaMwXmCo7jUwPwfLe1NUlVB
-Kqg6LK0Hcq4K0sZnxE8HFxiZ92WpV2AVWjRMEc/2z2shNoDvxvFUYyY1Oe67xINk
-myQKc+ygSBZzyLnXSFVWmHr3u5dcaaQGGAR42v6Ydr4iL38Hd4dOiBma+FXsXBIq
-WUjbST4VXmdaol7uzFMojA4zkxQDZAvF5XgJlAFadfySna/teik=
------END CERTIFICATE-----

+ 20 - 3
components/squeezelite-ota/squeezelite-ota.c

@@ -21,7 +21,7 @@
 #include "esp_err.h"
 #include "squeezelite-ota.h"
 #include "esp_netif.h"
-#include "Configurator.h"
+#include "Config.h"
 #include <time.h>
 #include <sys/time.h>
 #include <stdarg.h>
@@ -40,6 +40,7 @@
 #include "lwip/sockets.h"
 #include "globdefs.h"
 #include "tools.h"
+#include "bootstate.h"
 
 #define IF_DISPLAY(x) if(display) { x; }
 
@@ -685,8 +686,8 @@ esp_err_t process_recovery_ota(const char * bin_url, char * bin_buffer, uint32_t
 	if(bin_url){
 		ESP_LOGI(TAG,"Processing recovery OTA for url %s",STR_OR_ALT(bin_url,"N/A"));
 		ota_thread_parms.url =strdup_psram(bin_url);
-		configurator_set_string(&sys_State_msg,sys_State_ota_url_tag,sys_state,NULL);
-		configurator_raise_state_changed();
+		system_set_string(&sys_state_data_msg,sys_state_data_ota_url_tag,sys_state,NULL);
+		config_raise_state_changed();
 		ESP_LOGD(TAG, "Starting ota on core %u for : %s", OTA_CORE,ota_thread_parms.url);
 	}
 	else {
@@ -769,3 +770,19 @@ in_addr_t discover_ota_server(int max) {
 	return s.sin_addr.s_addr;
 }
 
+
+// Callback to handle ota when an IP address is obtained
+void cb_handle_ota(nm_state_t new_state, int sub_state) {
+    if (sys_state && sys_state->ota_url && strlen(sys_state->ota_url)) {
+        ESP_LOGD(TAG, "Found OTA URL %s", sys_state->ota_url);
+        if (is_recovery_running) {
+            ESP_LOGI(TAG, "Updating firmware from link: %s", sys_state->ota_url);
+#if defined(CONFIG_WITH_METRICS)
+            metrics_event("fw_update");
+#endif
+            start_ota(sys_state->ota_url, NULL, 0);
+        } else {
+            ESP_LOGE(TAG, "Restarted to application partition. We're not going to perform OTA!");
+        }
+    }
+}

+ 4 - 1
components/squeezelite-ota/squeezelite-ota.h

@@ -9,7 +9,7 @@
 #include "esp_attr.h"
 #include "esp_image_format.h"
 #include "esp_ota_ops.h"
-//
+#include "network_services.h"
 
 // ERASE BLOCK needs to be a multiple of sector size. If a different multiple is passed
 // the OTA process will adjust. Here, we need to strike the balance between speed and
@@ -32,3 +32,6 @@ uint8_t ota_get_pct_complete();
 
 esp_err_t start_ota(const char * bin_url, char * bin_buffer, uint32_t length);
 in_addr_t discover_ota_server(int max);
+
+// Callback to handle ota when an IP address is obtained
+void cb_handle_ota(nm_state_t new_state, int sub_state);

+ 2 - 2
components/squeezelite/CMakeLists.txt

@@ -3,19 +3,19 @@ if(IDF_TARGET STREQUAL "esp32")
 	set(target_requires "driver_bt" "platform_config")
 endif()
 
-idf_component_register( SRC_DIRS . external ac101 tas57xx wm8978
+idf_component_register( SRC_DIRS . external ac101 tas57xx wm8978 cs4265
 						INCLUDE_DIRS .  ac101 
 						PRIV_REQUIRES 	
 									codecs
 									newlib 
 									esp_common 
 									esp-dsp
+									tools
 						  			platform_config 
 						 			services 
 									spotify
 						 			raop   
 						 			display
-						 			tools
 						 			audio
 									led_strip
 									_override

+ 6 - 6
components/squeezelite/ac101/ac101.c

@@ -32,7 +32,7 @@
 #include <driver/i2s.h>
 #include "adac.h"
 #include "ac101.h"
-#include "Configurator.h"
+#include "Config.h"
 static const char TAG[] = "AC101";
 
 #define SPKOUT_EN ((1 << 9) | (1 << 11) | (1 << 7) | (1 << 5))
@@ -48,13 +48,13 @@ static const char TAG[] = "AC101";
         return b;\
     }
 	
-static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck);
+static bool init(sys_dac_config *config, i2s_config_t *i2s_config, bool *mck);
 static void speaker(bool active);
 static void headset(bool active);
 static bool volume(unsigned left, unsigned right);
 static void power(adac_power_e mode);
 
-const struct adac_s dac_ac101 = { sys_DACModelEnum_AC101, init, adac_deinit, power, speaker, headset, volume };
+const struct adac_s dac_ac101 = { sys_dac_models_AC101, init, adac_deinit, power, speaker, headset, volume };
 
 static void ac101_start(ac_module_t mode);
 static void ac101_stop(void);
@@ -64,11 +64,11 @@ static void ac101_set_spk_volume(uint8_t volume);
 /****************************************************************************************
  * init
  */
-static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck) {	 
-	adac_init(config, i2c_port);
+static bool init(sys_dac_config *config, i2s_config_t *i2s_config, bool *mck) {	 
+	adac_init(config);
 	if (adac_read_word(AC101_ADDR, CHIP_AUDIO_RS) == 0xffff) {
 		ESP_LOGW(TAG, "No AC101 detected");
-		i2c_driver_delete(i2c_port);
+		i2c_driver_delete(config->i2c.port-sys_i2c_port_PORT0);
 		return false;		
 	}
 	

+ 5 - 4
components/squeezelite/adac.h

@@ -12,12 +12,12 @@
 #include "freertos/FreeRTOS.h"
 #include "driver/i2s.h"
 #include "driver/i2c.h"
-#include "Configurator.h"
+#include "Config.h"
 typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e;
 
 struct adac_s {
-	sys_DACModelEnum model;
-	bool (*init)(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck);
+	sys_dac_models model;
+	bool (*init)(sys_dac_config *config,i2s_config_t *i2s_config, bool *mck);
 	void (*deinit)(void);
 	void (*power)(adac_power_e mode);
 	void (*speaker)(bool active);
@@ -29,9 +29,10 @@ extern const struct adac_s dac_tas57xx;
 extern const struct adac_s dac_tas5713;
 extern const struct adac_s dac_ac101;
 extern const struct adac_s dac_wm8978;
+extern const struct adac_s dac_cs4265;
 extern const struct adac_s dac_external;
 
-int 		adac_init(char *config, int i2c_port);
+int 		adac_init(sys_dac_config *config);
 void		adac_deinit(void);
 esp_err_t 	adac_write(int i2c_addr, uint8_t reg, uint8_t *data, size_t count);
 esp_err_t 	adac_write_byte(int i2c_addr, uint8_t reg, uint8_t val);

+ 10 - 11
components/squeezelite/adac_core.c

@@ -23,16 +23,13 @@
 } while (0)
 
 static const char TAG[] = "DAC core";
-static int i2c_port = -1;
-
+i2c_port_t i2c_port = -1;
 /****************************************************************************************
  * init
  */
-int adac_init(char *config, int i2c_port_num) {	 
+int adac_init(sys_dac_config *config) {	 
 	int i2c_addr = 0;
 
-	i2c_port = i2c_port_num;
-
 	// configure i2c
 	i2c_config_t i2c_config = {
 			.mode = I2C_MODE_MASTER,
@@ -42,17 +39,19 @@ int adac_init(char *config, int i2c_port_num) {
 			.scl_pullup_en = GPIO_PULLUP_ENABLE,
 			.master.clk_speed = 250000,
 		};
-
-	PARSE_PARAM(config, "i2c", '=', i2c_addr);
-	PARSE_PARAM(config, "sda", '=', i2c_config.sda_io_num);
-	PARSE_PARAM(config, "scl", '=', i2c_config.scl_io_num);
+	i2c_addr = config->addr;
+	if(config->has_i2c){
+		i2c_config.sda_io_num = config->i2c.sda;
+		i2c_config.scl_io_num = config->i2c.scl;
+		i2c_config.master.clk_speed =  config->i2c.speed>0?config->i2c.speed:i2c_config.master.clk_speed;
+	}
 
 	if (i2c_config.sda_io_num == -1 || i2c_config.scl_io_num == -1) {
 		ESP_LOGW(TAG, "DAC does not use i2c");
 		return i2c_addr;
 	}	
-	
-	ESP_LOGI(TAG, "DAC uses I2C port:%d, sda:%d, scl:%d", i2c_port, i2c_config.sda_io_num, i2c_config.scl_io_num);
+	i2c_port = config->i2c.port-sys_i2c_port_PORT0;
+	ESP_LOGI(TAG, "DAC uses I2C port:%s, sda:%d, scl:%d", sys_i2c_port_name(config->i2c.port), i2c_config.sda_io_num, i2c_config.scl_io_num);
 	
 	// we have an I2C configured	
 	i2c_param_config(i2c_port, &i2c_config);

+ 8 - 7
components/squeezelite/controls.c

@@ -7,7 +7,7 @@
  */
 
 #include "squeezelite.h"
-// #include "Configurator.h"
+// #include "Config.h"
 #pragma message("fixme: look for TODO below")
 #include "audio_controls.h"
 
@@ -54,7 +54,7 @@ static u16_t server_cport;
 static int cli_sock = -1;
 static u8_t mac[6];
 static void	(*chained_notify)(in_addr_t, u16_t, u16_t);
-static bool raw_mode;
+static bool raw_mode=false;
 
 static void cli_send_cmd(char *cmd);
 
@@ -244,15 +244,16 @@ static bool ir_handler(u16_t addr, u16_t cmd) {
  */
 void sb_controls_init(void) {
 	// TODO: Add support for the commented code
+	#pragma message("sb_controls_init needs to be implemented")
 	// char *p = config_alloc_get_default(NVS_TYPE_STR, "lms_ctrls_raw", "n", 0);
 	// raw_mode = p && (*p == '1' || *p == 'Y' || *p == 'y');
 	// free(p);
 	
-	// LOG_INFO("initializing audio (buttons/rotary/ir) controls (raw:%u)", raw_mode);
+	LOG_INFO("initializing audio (buttons/rotary/ir) controls (raw:%u)", raw_mode);
 	
-	// get_mac(mac);
-	// actrls_set_default(LMS_controls, raw_mode, NULL, ir_handler);
+	get_mac(mac);
+	actrls_set_default(LMS_controls, raw_mode, NULL, ir_handler);
 	
-	// chained_notify = server_notify;
-	// server_notify = notify;
+	chained_notify = server_notify;
+	server_notify = notify;
 }

+ 430 - 432
components/squeezelite/cs4265/cs4265.c

@@ -1,4 +1,4 @@
-/* 
+/*
  *  Squeezelite for esp32
  *
  *  (c) Sebastien 2019
@@ -8,21 +8,21 @@
  *  https://opensource.org/licenses/MIT
  *
  */
- 
+
 #include <string.h>
-//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
-#include "freertos/FreeRTOS.h"
-#include "freertos/task.h"
-#include "driver/i2s.h"
-#include "driver/i2c.h"
+#define LOG_LOCAL_LEVEL ESP_LOG_INFO
+#include "Config.h"
+#include "adac.h"
 #include "driver/gpio.h"
+#include "driver/i2c.h"
+#include "driver/i2s.h"
 #include "esp_log.h"
-#include "adac.h"
-#include "stdio.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
 #include "math.h"
-#include "Configurator.h"
-#define CS4265_PULL_UP (0x4F )
-#define CS4265_PULL_DOWN (0x4E )
+#include "stdio.h"
+#define CS4265_PULL_UP (0x4F)
+#define CS4265_PULL_DOWN (0x4E)
 
 #ifndef ARRAY_SIZE
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
@@ -30,188 +30,181 @@
 
 static const char TAG[] = "CS4265";
 
-static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
+static bool init(sys_dac_config * config, i2s_config_t* i2s_config, bool* mck);
 static void speaker(bool active);
 static void headset(bool active);
 static bool volume(unsigned left, unsigned right);
 static void power(adac_power_e mode);
-static esp_err_t cs4265_update_bit(uint8_t reg_no,uint8_t mask,uint8_t val );
+static esp_err_t cs4265_update_bit(uint8_t reg_no, uint8_t mask, uint8_t val);
 static esp_err_t set_clock();
-const struct adac_s dac_cs4265 = { sys_DACModelEnum_CS4265, init, adac_deinit, power, speaker, headset, volume };
+const struct adac_s dac_cs4265 = {
+    sys_dac_models_CS4265, init, adac_deinit, power, speaker, headset, volume};
 
 struct cs4265_cmd_s {
-	uint8_t reg;
-	uint8_t value;
+    uint8_t reg;
+    uint8_t value;
 };
 struct cs4265_private {
-	uint8_t format;
-	uint32_t sysclk;
-	i2s_config_t *i2s_config;
-	int i2c_port;
-};
+    uint8_t format;
+    uint32_t sysclk;
+    i2s_config_t* i2s_config;
+	i2c_port_t port;
+	};
 struct cs4265_private cs4265;
 
-#define CS4265_CHIP_ID				0x1
-#define CS4265_CHIP_ID_VAL			0xD0
-#define CS4265_CHIP_ID_MASK			0xF0
-#define CS4265_REV_ID_MASK			0x0F
-
-#define CS4265_PWRCTL				0x02
-#define CS4265_PWRCTL_PDN			(1 << 0)
-#define CS4265_PWRCTL_PDN_DAC       (1 << 1)
-#define CS4265_PWRCTL_PDN_ADC       (1 << 2)
-#define CS4265_PWRCTL_PDN_MIC       (1 << 3)
-#define CS4265_PWRCTL_FREEZE        (1 << 7)
-#define CS4265_PWRCTL_PDN_ALL   	CS4265_PWRCTL_PDN | CS4265_PWRCTL_PDN_ADC | CS4265_PWRCTL_PDN_DAC | CS4265_PWRCTL_PDN_MIC
-
-
-
-#define CS4265_DAC_CTL				0x3
+#define CS4265_CHIP_ID 0x1
+#define CS4265_CHIP_ID_VAL 0xD0
+#define CS4265_CHIP_ID_MASK 0xF0
+#define CS4265_REV_ID_MASK 0x0F
+
+#define CS4265_PWRCTL 0x02
+#define CS4265_PWRCTL_PDN (1 << 0)
+#define CS4265_PWRCTL_PDN_DAC (1 << 1)
+#define CS4265_PWRCTL_PDN_ADC (1 << 2)
+#define CS4265_PWRCTL_PDN_MIC (1 << 3)
+#define CS4265_PWRCTL_FREEZE (1 << 7)
+#define CS4265_PWRCTL_PDN_ALL                                                                      \
+    CS4265_PWRCTL_PDN | CS4265_PWRCTL_PDN_ADC | CS4265_PWRCTL_PDN_DAC | CS4265_PWRCTL_PDN_MIC
+
+#define CS4265_DAC_CTL 0x3
 // De-Emphasis Control (Bit 1)
 // The standard 50/15 i2s digital de-emphasis filter response may be implemented for a sample
-// rate of 44.1 kHz when the DeEmph bit is set.  NOTE: De-emphasis is available only in Single-Speed Mode.
-#define CS4265_DAC_CTL_DEEMPH		(1 << 1)
+// rate of 44.1 kHz when the DeEmph bit is set.  NOTE: De-emphasis is available only in Single-Speed
+// Mode.
+#define CS4265_DAC_CTL_DEEMPH (1 << 1)
 // MUTE DAC
-// The DAC outputs will mute and the MUTEC pin will become active when this bit is set. Though this bit is
-// active high, it should be noted that the MUTEC pin is active low. The common mode voltage on the outputs
-// will be retained when this bit is set. The muting function is effected, similar to attenuation changes, by the
-// DACSoft and DACZero bits in the DAC Control 2 register.
-#define CS4265_DAC_CTL_MUTE			(1 << 2)
-// The required relationship between LRCK, SCLK and SDIN for the DAC is defined by the DAC Digital Interface
-// DAC_DIF1 DAC_DIF0 Description                                    Format Figure
-// 0        0        Left Justified, up to 24-bit data (default)    0       5
-// 0        1        I²S, up to 24-bit data                         1       6
+// The DAC outputs will mute and the MUTEC pin will become active when this bit is set. Though this
+// bit is active high, it should be noted that the MUTEC pin is active low. The common mode voltage
+// on the outputs will be retained when this bit is set. The muting function is effected, similar to
+// attenuation changes, by the DACSoft and DACZero bits in the DAC Control 2 register.
+#define CS4265_DAC_CTL_MUTE (1 << 2)
+// The required relationship between LRCK, SCLK and SDIN for the DAC is defined by the DAC Digital
+// Interface DAC_DIF1 DAC_DIF0 Description                                    Format Figure 0 0 Left
+// Justified, up to 24-bit data (default)    0       5 0        1        I²S, up to 24-bit data 1 6
 // 1        0        Right-Justified, 16-bit Data                   2       7
 // 1        1        Right-Justified, 24-bit Data                   3       7
-#define CS4265_DAC_CTL_DIF0			(1 << 4)
-// The required relationship between LRCK, SCLK and SDIN for the DAC is defined by the DAC Digital Interface
-// DAC_DIF1 DAC_DIF0 Description                                    Format Figure
-// 0        0        Left Justified, up to 24-bit data (default)    0       5
-// 0        1        I²S, up to 24-bit data                         1       6
+#define CS4265_DAC_CTL_DIF0 (1 << 4)
+// The required relationship between LRCK, SCLK and SDIN for the DAC is defined by the DAC Digital
+// Interface DAC_DIF1 DAC_DIF0 Description                                    Format Figure 0 0 Left
+// Justified, up to 24-bit data (default)    0       5 0        1        I²S, up to 24-bit data 1 6
 // 1        0        Right-Justified, 16-bit Data                   2       7
 // 1        1        Right-Justified, 24-bit Data                   3       7
-#define CS4265_DAC_CTL_DIF1			(1 << 5)
+#define CS4265_DAC_CTL_DIF1 (1 << 5)
 
+#define CS4265_ADC_CTL 0x4
+#define CS4265_ADC_MASTER 1
 
+#define CS4265_ADC_CTL_MUTE (1 << 2)
+#define CS4265_ADC_DIF (1 << 4)
+#define CS4265_ADC_FM (3 << 6)
 
-#define CS4265_ADC_CTL				0x4
-#define CS4265_ADC_MASTER			1
-
-#define CS4265_ADC_CTL_MUTE   		(1 << 2)
-#define CS4265_ADC_DIF				(1 << 4)
-#define CS4265_ADC_FM				(3 << 6)
-
-//Master Clock Dividers (Bits 6:4)
-//Sets the frequency of the supplied MCLK signal. 
+// Master Clock Dividers (Bits 6:4)
+// Sets the frequency of the supplied MCLK signal.
 //
-//MCLK Divider MCLK Freq2 MCLK Freq1 MCLK Freq0
-// ÷   1 	   0          0           0
-// ÷   1.5 	   0          0           1
-// ÷   2 	   0          1           0
-// ÷   3 	   0          1           1
-// ÷   4 	   1          0           0
-// NA          1          0           1
-// NA 		   1          1           x 
-#define CS4265_MCLK_FREQ			0x5
-#define CS4265_MCLK_FREQ_1_0X	(0b000<<4 )
-#define CS4265_MCLK_FREQ_1_5X	(0b001<<4 )
-#define CS4265_MCLK_FREQ_2_0X	(0b010<<4 )
-#define CS4265_MCLK_FREQ_3_0X	(0b011<<4 )
-#define CS4265_MCLK_FREQ_4_0X	(0b100<<4 )
-
-
-#define CS4265_MCLK_FREQ_MASK			(7 << 4)
-
-#define CS4265_SIG_SEL				0x6
-#define CS4265_SIG_SEL_LOOP			(1 << 1)
-#define CS4265_SIG_SEL_SDIN2		(1 << 7)
-#define CS4265_SIG_SEL_SDIN1		(0 << 7)
+// MCLK Divider MCLK Freq2 MCLK Freq1 MCLK Freq0
+//  ÷   1 	   0          0           0
+//  ÷   1.5 	   0          0           1
+//  ÷   2 	   0          1           0
+//  ÷   3 	   0          1           1
+//  ÷   4 	   1          0           0
+//  NA          1          0           1
+//  NA 		   1          1           x
+#define CS4265_MCLK_FREQ 0x5
+#define CS4265_MCLK_FREQ_1_0X (0b000 << 4)
+#define CS4265_MCLK_FREQ_1_5X (0b001 << 4)
+#define CS4265_MCLK_FREQ_2_0X (0b010 << 4)
+#define CS4265_MCLK_FREQ_3_0X (0b011 << 4)
+#define CS4265_MCLK_FREQ_4_0X (0b100 << 4)
+
+#define CS4265_MCLK_FREQ_MASK (7 << 4)
+
+#define CS4265_SIG_SEL 0x6
+#define CS4265_SIG_SEL_LOOP (1 << 1)
+#define CS4265_SIG_SEL_SDIN2 (1 << 7)
+#define CS4265_SIG_SEL_SDIN1 (0 << 7)
 
 // Sets the gain or attenuation for the ADC input PGA stage. The gain may be adjusted from -12 dB to
-// +12 dB in 0.5 dB steps. The gain bits are in two’s complement with the Gain0 bit set for a 0.5 dB step.
-// Register settings outside of the ±12 dB range are reserved and must not be used. See Table 13 for example settings
-#define CS4265_CHB_PGA_CTL			0x7
+// +12 dB in 0.5 dB steps. The gain bits are in two’s complement with the Gain0 bit set for a 0.5 dB
+// step. Register settings outside of the ±12 dB range are reserved and must not be used. See Table
+// 13 for example settings
+#define CS4265_CHB_PGA_CTL 0x7
 // Sets the gain or attenuation for the ADC input PGA stage. The gain may be adjusted from -12 dB to
-// +12 dB in 0.5 dB steps. The gain bits are in two’s complement with the Gain0 bit set for a 0.5 dB step.
-// Register settings outside of the ±12 dB range are reserved and must not be used. See Table 13 for example settings
-#define CS4265_CHA_PGA_CTL			0x8
+// +12 dB in 0.5 dB steps. The gain bits are in two’s complement with the Gain0 bit set for a 0.5 dB
+// step. Register settings outside of the ±12 dB range are reserved and must not be used. See Table
+// 13 for example settings
+#define CS4265_CHA_PGA_CTL 0x8
 // Gain[5:0]    Setting
 // 101000       -12 dB
 // 000000       0 dB
 // 011000       +12 dB
 
-
-#define CS4265_ADC_CTL2				0x9
-
-// The digital volume control allows the user to attenuate the signal in 0.5 dB increments from 0 to -127 dB.
-// The Vol0 bit activates a 0.5 dB attenuation when set, and no attenuation when cleared. The Vol[7:1] bits
-// activate attenuation equal to their decimal equivalent (in dB). 
-//Binary Code 	Volume Setting
-//00000000 		0 dB
-//00000001 		-0.5 dB
-//00101000 		-20 dB
-//00101001 		-20.5 dB
-//11111110 		-127 dB
-//11111111 		-127.5 dB
-#define CS4265_DAC_CHA_VOL			0xA
-// The digital volume control allows the user to attenuate the signal in 0.5 dB increments from 0 to -127 dB.
-// The Vol0 bit activates a 0.5 dB attenuation when set, and no attenuation when cleared. The Vol[7:1] bits
-// activate attenuation equal to their decimal equivalent (in dB). 
-//Binary Code 	Volume Setting
-//00000000 		0 dB
-//00000001 		-0.5 dB
-//00101000 		-20 dB
-//00101001 		-20.5 dB
-//11111110 		-127 dB
-//11111111 		-127.5 dB
-#define CS4265_DAC_CHB_VOL			0xB
-#define CS4265_DAC_VOL_ATT_000_0		0b00000000
-#define CS4265_DAC_VOL_ATT_000_5		0b00000001
-#define CS4265_DAC_VOL_ATT_020_0		0b00101000
-#define CS4265_DAC_VOL_ATT_020_5		0b00101001
-#define CS4265_DAC_VOL_ATT_127_0		0b11111110
-#define CS4265_DAC_VOL_ATT_127_5		0b11111111
+#define CS4265_ADC_CTL2 0x9
+
+// The digital volume control allows the user to attenuate the signal in 0.5 dB increments from 0 to
+// -127 dB. The Vol0 bit activates a 0.5 dB attenuation when set, and no attenuation when cleared.
+// The Vol[7:1] bits activate attenuation equal to their decimal equivalent (in dB).
+// Binary Code 	Volume Setting
+// 00000000 		0 dB
+// 00000001 		-0.5 dB
+// 00101000 		-20 dB
+// 00101001 		-20.5 dB
+// 11111110 		-127 dB
+// 11111111 		-127.5 dB
+#define CS4265_DAC_CHA_VOL 0xA
+// The digital volume control allows the user to attenuate the signal in 0.5 dB increments from 0 to
+// -127 dB. The Vol0 bit activates a 0.5 dB attenuation when set, and no attenuation when cleared.
+// The Vol[7:1] bits activate attenuation equal to their decimal equivalent (in dB).
+// Binary Code 	Volume Setting
+// 00000000 		0 dB
+// 00000001 		-0.5 dB
+// 00101000 		-20 dB
+// 00101001 		-20.5 dB
+// 11111110 		-127 dB
+// 11111111 		-127.5 dB
+#define CS4265_DAC_CHB_VOL 0xB
+#define CS4265_DAC_VOL_ATT_000_0 0b00000000
+#define CS4265_DAC_VOL_ATT_000_5 0b00000001
+#define CS4265_DAC_VOL_ATT_020_0 0b00101000
+#define CS4265_DAC_VOL_ATT_020_5 0b00101001
+#define CS4265_DAC_VOL_ATT_127_0 0b11111110
+#define CS4265_DAC_VOL_ATT_127_5 0b11111111
 
 // DAC Soft Ramp or Zero Cross Enable (Bits 7:6)
 //
 // Soft Ramp Enable
-// Soft Ramp allows level changes, both muting and attenuation, to be implemented by incrementally ramping, in 1/8 dB steps, from the current level to the new level at a rate of 1 dB per 8 left/right clock periods.
-// See Table 17.
-// Zero Cross Enable
-// Zero Cross Enable dictates that signal-level changes, either by attenuation changes or muting, will occur
-// on a signal zero crossing to minimize audible artifacts. The requested level change will occur after a timeout period between 512 and 1024 sample periods (10.7 ms to 21.3 ms at 48 kHz sample rate) if the signal
-// does not encounter a zero crossing. The zero cross function is independently monitored and implemented
-// for each channel. See Table 17.
-// Soft Ramp and Zero Cross Enable
-// Soft Ramp and Zero Cross Enable dictate that signal-level changes, either by attenuation changes or muting, will occur in 1/8 dB steps and be implemented on a signal zero crossing. The 1/8 dB level change will
-// occur after a time-out period between 512 and 1024 sample periods (10.7 ms to 21.3 ms at 48 kHz sample rate) if the signal does not encounter a zero crossing. The zero cross function is independently monitored and implemented for each channel
-// DACSoft DACZeroCross Mode
-// 0 0 Changes to affect immediately
-// 0 1 Zero Cross enabled
-// 1 0 Soft Ramp enabled
-// 1 1 Soft Ramp and Zero Cross enabled (default)
-#define CS4265_DAC_CTL2								0xC
-#define CS4265_DAC_CTL2_ZERO_CROSS_EN  				(uint8_t)(0b01 <<7)
-#define CS4265_DAC_CTL2_SOFT_RAMP_EN  				(uint8_t)(0b10 <<7)
-#define CS4265_DAC_CTL2_SOFT_RAMP_ZERO_CROSS_EN  	(uint8_t)(0b11 <<7)
-
-
-#define CS4265_INT_STATUS			0xD
-#define CS4265_INT_STATUS_ADC_UNDF  (1<<0)
-#define CS4265_INT_STATUS_ADC_OVF   (1<<1)
-#define CS4265_INT_STATUS_CLKERR    (1<<3)
-
-
-#define CS4265_INT_MASK				0xE
-#define CS4265_STATUS_MODE_MSB			0xF
-#define CS4265_STATUS_MODE_LSB			0x10
-
-//Transmitter Control 1 - Address 11h
-#define CS4265_SPDIF_CTL1			0x11
-
-
-
-#define CS4265_SPDIF_CTL2			0x12
+// Soft Ramp allows level changes, both muting and attenuation, to be implemented by incrementally
+// ramping, in 1/8 dB steps, from the current level to the new level at a rate of 1 dB per 8
+// left/right clock periods. See Table 17. Zero Cross Enable Zero Cross Enable dictates that
+// signal-level changes, either by attenuation changes or muting, will occur on a signal zero
+// crossing to minimize audible artifacts. The requested level change will occur after a timeout
+// period between 512 and 1024 sample periods (10.7 ms to 21.3 ms at 48 kHz sample rate) if the
+// signal does not encounter a zero crossing. The zero cross function is independently monitored and
+// implemented for each channel. See Table 17. Soft Ramp and Zero Cross Enable Soft Ramp and Zero
+// Cross Enable dictate that signal-level changes, either by attenuation changes or muting, will
+// occur in 1/8 dB steps and be implemented on a signal zero crossing. The 1/8 dB level change will
+// occur after a time-out period between 512 and 1024 sample periods (10.7 ms to 21.3 ms at 48 kHz
+// sample rate) if the signal does not encounter a zero crossing. The zero cross function is
+// independently monitored and implemented for each channel DACSoft DACZeroCross Mode 0 0 Changes to
+// affect immediately 0 1 Zero Cross enabled 1 0 Soft Ramp enabled 1 1 Soft Ramp and Zero Cross
+// enabled (default)
+#define CS4265_DAC_CTL2 0xC
+#define CS4265_DAC_CTL2_ZERO_CROSS_EN (uint8_t)(0b01 << 7)
+#define CS4265_DAC_CTL2_SOFT_RAMP_EN (uint8_t)(0b10 << 7)
+#define CS4265_DAC_CTL2_SOFT_RAMP_ZERO_CROSS_EN (uint8_t)(0b11 << 7)
+
+#define CS4265_INT_STATUS 0xD
+#define CS4265_INT_STATUS_ADC_UNDF (1 << 0)
+#define CS4265_INT_STATUS_ADC_OVF (1 << 1)
+#define CS4265_INT_STATUS_CLKERR (1 << 3)
+
+#define CS4265_INT_MASK 0xE
+#define CS4265_STATUS_MODE_MSB 0xF
+#define CS4265_STATUS_MODE_LSB 0x10
+
+// Transmitter Control 1 - Address 11h
+#define CS4265_SPDIF_CTL1 0x11
+
+#define CS4265_SPDIF_CTL2 0x12
 // Transmitter Digital Interface Format (Bits 7:6)
 // Function:
 // The required relationship between LRCK, SCLK and SDIN for the transmitter is defined
@@ -220,343 +213,348 @@ struct cs4265_private cs4265;
 // 0 1 I²S, up to 24-bit data 1 6
 // 1 0 Right-Justified, 16-bit Data 2 7
 // 1 1 Right-Justified, 24-bit Data 3 7
-#define CS4265_SPDIF_CTL2_MMTLR         (1<<0)
-#define CS4265_SPDIF_CTL2_MMTCS         (1<<1)
-#define CS4265_SPDIF_CTL2_MMT           (1<<2)
-#define CS4265_SPDIF_CTL2_V             (1<<3)
-#define CS4265_SPDIF_CTL2_TXMUTE        (1<<4)
-#define CS4265_SPDIF_CTL2_TXOFF         (1<<5)
-#define CS4265_SPDIF_CTL2_MUTE			(1 << 4)
-#define CS4265_SPDIF_CTL2_DIF			(3 << 6)
-#define CS4265_SPDIF_CTL2_DIF0			(1 << 6)
-#define CS4265_SPDIF_CTL2_DIF1			(1 << 7)
-
-
-
-
-
-
-#define CS4265_C_DATA_BUFF			0x13
-#define CS4265_MAX_REGISTER			0x2A
+#define CS4265_SPDIF_CTL2_MMTLR (1 << 0)
+#define CS4265_SPDIF_CTL2_MMTCS (1 << 1)
+#define CS4265_SPDIF_CTL2_MMT (1 << 2)
+#define CS4265_SPDIF_CTL2_V (1 << 3)
+#define CS4265_SPDIF_CTL2_TXMUTE (1 << 4)
+#define CS4265_SPDIF_CTL2_TXOFF (1 << 5)
+#define CS4265_SPDIF_CTL2_MUTE (1 << 4)
+#define CS4265_SPDIF_CTL2_DIF (3 << 6)
+#define CS4265_SPDIF_CTL2_DIF0 (1 << 6)
+#define CS4265_SPDIF_CTL2_DIF1 (1 << 7)
+
+#define CS4265_C_DATA_BUFF 0x13
+#define CS4265_MAX_REGISTER 0x2A
 struct cs4265_clk_para {
-	uint32_t mclk;
-	uint32_t rate;
-	uint8_t fm_mode; /* values 1, 2, or 4 */
-	uint8_t mclkdiv;
+    uint32_t mclk;
+    uint32_t rate;
+    uint8_t fm_mode; /* values 1, 2, or 4 */
+    uint8_t mclkdiv;
 };
 static const struct cs4265_clk_para clk_map_table[] = {
-	/*32k*/
-	{8192000, 32000, 0, 0},
-	{12288000, 32000, 0, 1},
-	{16384000, 32000, 0, 2},
-	{24576000, 32000, 0, 3},
-	{32768000, 32000, 0, 4},
-
-	/*44.1k*/
-	{11289600, 44100, 0, 0},
-	{16934400, 44100, 0, 1},
-	{22579200, 44100, 0, 2},
-	{33868000, 44100, 0, 3},
-	{45158400, 44100, 0, 4},
-
-	/*48k*/
-	{12288000, 48000, 0, 0},
-	{18432000, 48000, 0, 1},
-	{24576000, 48000, 0, 2},
-	{36864000, 48000, 0, 3},
-	{49152000, 48000, 0, 4},
-
-	/*64k*/
-	{8192000, 64000, 1, 0},
-	{12288000, 64000, 1, 1},
-	{16934400, 64000, 1, 2},
-	{24576000, 64000, 1, 3},
-	{32768000, 64000, 1, 4},
-
-	/* 88.2k */
-	{11289600, 88200, 1, 0},
-	{16934400, 88200, 1, 1},
-	{22579200, 88200, 1, 2},
-	{33868000, 88200, 1, 3},
-	{45158400, 88200, 1, 4},
-
-	/* 96k */
-	{12288000, 96000, 1, 0},
-	{18432000, 96000, 1, 1},
-	{24576000, 96000, 1, 2},
-	{36864000, 96000, 1, 3},
-	{49152000, 96000, 1, 4},
-
-	/* 128k */
-	{8192000, 128000, 2, 0},
-	{12288000, 128000, 2, 1},
-	{16934400, 128000, 2, 2},
-	{24576000, 128000, 2, 3},
-	{32768000, 128000, 2, 4},
-
-	/* 176.4k */
-	{11289600, 176400, 2, 0},
-	{16934400, 176400, 2, 1},
-	{22579200, 176400, 2, 2},
-	{33868000, 176400, 2, 3},
-	{49152000, 176400, 2, 4},
-
-	/* 192k */
-	{12288000, 192000, 2, 0},
-	{18432000, 192000, 2, 1},
-	{24576000, 192000, 2, 2},
-	{36864000, 192000, 2, 3},
-	{49152000, 192000, 2, 4},
+    /*32k*/
+    {8192000, 32000, 0, 0},
+    {12288000, 32000, 0, 1},
+    {16384000, 32000, 0, 2},
+    {24576000, 32000, 0, 3},
+    {32768000, 32000, 0, 4},
+
+    /*44.1k*/
+    {11289600, 44100, 0, 0},
+    {16934400, 44100, 0, 1},
+    {22579200, 44100, 0, 2},
+    {33868000, 44100, 0, 3},
+    {45158400, 44100, 0, 4},
+
+    /*48k*/
+    {12288000, 48000, 0, 0},
+    {18432000, 48000, 0, 1},
+    {24576000, 48000, 0, 2},
+    {36864000, 48000, 0, 3},
+    {49152000, 48000, 0, 4},
+
+    /*64k*/
+    {8192000, 64000, 1, 0},
+    {12288000, 64000, 1, 1},
+    {16934400, 64000, 1, 2},
+    {24576000, 64000, 1, 3},
+    {32768000, 64000, 1, 4},
+
+    /* 88.2k */
+    {11289600, 88200, 1, 0},
+    {16934400, 88200, 1, 1},
+    {22579200, 88200, 1, 2},
+    {33868000, 88200, 1, 3},
+    {45158400, 88200, 1, 4},
+
+    /* 96k */
+    {12288000, 96000, 1, 0},
+    {18432000, 96000, 1, 1},
+    {24576000, 96000, 1, 2},
+    {36864000, 96000, 1, 3},
+    {49152000, 96000, 1, 4},
+
+    /* 128k */
+    {8192000, 128000, 2, 0},
+    {12288000, 128000, 2, 1},
+    {16934400, 128000, 2, 2},
+    {24576000, 128000, 2, 3},
+    {32768000, 128000, 2, 4},
+
+    /* 176.4k */
+    {11289600, 176400, 2, 0},
+    {16934400, 176400, 2, 1},
+    {22579200, 176400, 2, 2},
+    {33868000, 176400, 2, 3},
+    {49152000, 176400, 2, 4},
+
+    /* 192k */
+    {12288000, 192000, 2, 0},
+    {18432000, 192000, 2, 1},
+    {24576000, 192000, 2, 2},
+    {36864000, 192000, 2, 3},
+    {49152000, 192000, 2, 4},
 };
 static const struct cs4265_cmd_s cs4265_init_sequence[] = {
-	{CS4265_PWRCTL, CS4265_PWRCTL_PDN_ADC | CS4265_PWRCTL_FREEZE | CS4265_PWRCTL_PDN_DAC | CS4265_PWRCTL_PDN_MIC},
- 	{CS4265_DAC_CTL, CS4265_DAC_CTL_DIF0 | CS4265_DAC_CTL_MUTE}, 
- 	{CS4265_SIG_SEL, CS4265_SIG_SEL_SDIN1},/// SDIN1
- 	{CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_DIF0 },//
- 	{CS4265_ADC_CTL, 0x00 },// // Set the serial audio port in slave mode
- 	{CS4265_MCLK_FREQ, CS4265_MCLK_FREQ_1_0X },// // no divider 
- 	{CS4265_CHB_PGA_CTL, 0x00 },// // sets the gain to 0db on channel B
- 	{CS4265_CHA_PGA_CTL, 0x00 },// // sets the gain to 0db on channel A
- 	{CS4265_ADC_CTL2, 0x19 },//
- 	{CS4265_DAC_CHA_VOL,CS4265_DAC_VOL_ATT_000_0   },// Full volume out 
- 	{CS4265_DAC_CHB_VOL, CS4265_DAC_VOL_ATT_000_0 },// // Full volume out 
- 	{CS4265_DAC_CTL2, CS4265_DAC_CTL2_SOFT_RAMP_ZERO_CROSS_EN },//
- 	{CS4265_SPDIF_CTL1, 0x00 },//
- 	{CS4265_INT_MASK, 0x00 },//
- 	{CS4265_STATUS_MODE_MSB, 0x00 },//
- 	{CS4265_STATUS_MODE_LSB, 0x00 },//
-	{0xff,0xff}
-};
-
+    {CS4265_PWRCTL, CS4265_PWRCTL_PDN_ADC | CS4265_PWRCTL_FREEZE | CS4265_PWRCTL_PDN_DAC |
+                        CS4265_PWRCTL_PDN_MIC},
+    {CS4265_DAC_CTL, CS4265_DAC_CTL_DIF0 | CS4265_DAC_CTL_MUTE},
+    {CS4265_SIG_SEL, CS4265_SIG_SEL_SDIN1},         /// SDIN1
+    {CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_DIF0},    //
+    {CS4265_ADC_CTL, 0x00},                         // // Set the serial audio port in slave mode
+    {CS4265_MCLK_FREQ, CS4265_MCLK_FREQ_1_0X},      // // no divider
+    {CS4265_CHB_PGA_CTL, 0x00},                     // // sets the gain to 0db on channel B
+    {CS4265_CHA_PGA_CTL, 0x00},                     // // sets the gain to 0db on channel A
+    {CS4265_ADC_CTL2, 0x19},                        //
+    {CS4265_DAC_CHA_VOL, CS4265_DAC_VOL_ATT_000_0}, // Full volume out
+    {CS4265_DAC_CHB_VOL, CS4265_DAC_VOL_ATT_000_0}, // // Full volume out
+    {CS4265_DAC_CTL2, CS4265_DAC_CTL2_SOFT_RAMP_ZERO_CROSS_EN}, //
+    {CS4265_SPDIF_CTL1, 0x00},                                  //
+    {CS4265_INT_MASK, 0x00},                                    //
+    {CS4265_STATUS_MODE_MSB, 0x00},                             //
+    {CS4265_STATUS_MODE_LSB, 0x00},                             //
+    {0xff, 0xff}};
 
 // matching orders
-typedef enum { cs4265_ACTIVE = 0, cs4265_STANDBY, cs4265_DOWN, cs4265_ANALOGUE_OFF, cs4265_ANALOGUE_ON, cs4265_VOLUME } dac_cmd_e;
-
-
+typedef enum {
+    cs4265_ACTIVE = 0,
+    cs4265_STANDBY,
+    cs4265_DOWN,
+    cs4265_ANALOGUE_OFF,
+    cs4265_ANALOGUE_ON,
+    cs4265_VOLUME
+} dac_cmd_e;
 
 static int cs4265_addr;
 
 static void dac_cmd(dac_cmd_e cmd, ...);
 static int cs4265_detect(void);
-static uint32_t calc_rnd_mclk_freq(){
-	float m_scale = (cs4265.i2s_config->sample_rate > 96000 && cs4265.i2s_config->bits_per_sample > 16) ? 4 : 8;
-	float num_channels = cs4265.i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 2 : 1;
-     return (uint32_t) round(cs4265.i2s_config->bits_per_sample*i2s_get_clk(cs4265.i2c_port)* m_scale*num_channels/100)*100;
+static uint32_t calc_rnd_mclk_freq() {
+    float m_scale =
+        (cs4265.i2s_config->sample_rate > 96000 && cs4265.i2s_config->bits_per_sample > 16) ? 4 : 8;
+    float num_channels = cs4265.i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 2 : 1;
+    return (uint32_t)round(cs4265.i2s_config->bits_per_sample * i2s_get_clk(cs4265.port) *
+                           m_scale * num_channels / 100) *
+           100;
 }
-static int cs4265_get_clk_index(int mclk, int rate)
-{
-	for (int i = 0; i < ARRAY_SIZE(clk_map_table); i++) {
-		if (clk_map_table[i].rate == rate &&
-				clk_map_table[i].mclk == mclk)
-			return i;
-	}
-	return -1;
+static int cs4265_get_clk_index(int mclk, int rate) {
+    for (int i = 0; i < ARRAY_SIZE(clk_map_table); i++) {
+        if (clk_map_table[i].rate == rate && clk_map_table[i].mclk == mclk) return i;
+    }
+    return -1;
 }
 
-static esp_err_t set_clock(){
+static esp_err_t set_clock() {
     esp_err_t err = ESP_OK;
-	uint32_t mclk = calc_rnd_mclk_freq();
-    int index = cs4265_get_clk_index(mclk,cs4265.i2s_config->sample_rate );
-	if (index >= 0) {
-        ESP_LOGD(TAG, "Setting clock for mclk %u, rate %u (fm mode:%u, clk div:%u))", mclk,cs4265.i2s_config->sample_rate,clk_map_table[index].fm_mode,clk_map_table[index].mclkdiv);
-		err=cs4265_update_bit(CS4265_ADC_CTL,CS4265_ADC_FM, clk_map_table[index].fm_mode << 6);
-		err|=cs4265_update_bit( CS4265_MCLK_FREQ,CS4265_MCLK_FREQ_MASK,clk_map_table[index].mclkdiv << 4);
-	} else {
-		ESP_LOGE(TAG,"can't get correct mclk for ");
-		return -1;
-	}
+    uint32_t mclk = calc_rnd_mclk_freq();
+    int index = cs4265_get_clk_index(mclk, cs4265.i2s_config->sample_rate);
+    if (index >= 0) {
+        ESP_LOGD(TAG, "Setting clock for mclk %u, rate %u (fm mode:%u, clk div:%u))", mclk,
+            cs4265.i2s_config->sample_rate, clk_map_table[index].fm_mode,
+            clk_map_table[index].mclkdiv);
+        err = cs4265_update_bit(CS4265_ADC_CTL, CS4265_ADC_FM, clk_map_table[index].fm_mode << 6);
+        err |= cs4265_update_bit(
+            CS4265_MCLK_FREQ, CS4265_MCLK_FREQ_MASK, clk_map_table[index].mclkdiv << 4);
+    } else {
+        ESP_LOGE(TAG, "can't get correct mclk for ");
+        return -1;
+    }
     return err;
 }
 
-
-static void get_status(){
-    uint8_t sts1= adac_read_byte(cs4265_addr, CS4265_INT_STATUS);	
-    ESP_LOGD(TAG,"Status: %s",sts1&CS4265_INT_STATUS_CLKERR?"CLK Error":"CLK OK");
+static void get_status() {
+    uint8_t sts1 = adac_read_byte(cs4265_addr, CS4265_INT_STATUS);
+    ESP_LOGD(TAG, "Status: %s", sts1 & CS4265_INT_STATUS_CLKERR ? "CLK Error" : "CLK OK");
 }
 
-
 /****************************************************************************************
  * init
  */
-static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) {	 
-	// find which TAS we are using (if any)
-	cs4265_addr = adac_init(config, i2c_port);
-	cs4265.i2s_config = i2s_config;
-	cs4265.i2c_port=i2c_port;
-	if (!cs4265_addr) cs4265_addr = cs4265_detect();
-	if (!cs4265_addr) {
-		ESP_LOGE(TAG, "No cs4265 detected");
-		adac_deinit();
-		return false;
-	}
-	#if BYTES_PER_FRAME == 8
-		ESP_LOGE(TAG,"The CS4265 does not support 32 bits mode. ");
-		adac_deinit();
-		return false;
-	#endif	
-	// configure MLK
-    ESP_LOGD(TAG, "Configuring MCLK on GPIO0");
-	PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
-   	REG_WRITE(PIN_CTRL, 0xFFFFFFF0);
-	i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
-	for (int i = 0; cs4265_init_sequence[i].reg != 0xff; i++) {
-		i2c_master_start(i2c_cmd);
-		i2c_master_write_byte(i2c_cmd, (cs4265_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
-		i2c_master_write_byte(i2c_cmd, cs4265_init_sequence[i].reg, I2C_MASTER_NACK);
-		i2c_master_write_byte(i2c_cmd, cs4265_init_sequence[i].value, I2C_MASTER_NACK);
-		ESP_LOGD(TAG, "i2c write %x at %u", cs4265_init_sequence[i].reg, cs4265_init_sequence[i].value);
-	}
-
-	i2c_master_stop(i2c_cmd);	
-	esp_err_t res = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_RATE_MS);
-    i2c_cmd_link_delete(i2c_cmd);
-
-	if (res != ESP_OK) {
-		ESP_LOGE(TAG, "could not intialize cs4265 %d", res);
-		return false;
-	}	
+static bool init(sys_dac_config * config, i2s_config_t* i2s_config, bool* mck) {
+
+    // find which CS4265 we are using (if any)
+    cs4265_addr = adac_init(config);
+    cs4265.i2s_config = i2s_config;
+    if (!cs4265_addr) cs4265_addr = cs4265_detect();
+    if (!cs4265_addr) {
+        ESP_LOGE(TAG, "No cs4265 detected");
+        adac_deinit();
+        return false;
+    }
+	cs4265.port = config->i2c.port-sys_i2c_port_PORT0;
+#if BYTES_PER_FRAME == 8
+    ESP_LOGE(TAG, "The CS4265 does not support 32 bits mode. ");
+    adac_deinit();
+    return false;
+#endif
+    // we need mclk for this DAC
+    *mck = true;
+
+	// Initialize the chip
+    i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
+    for (int i = 0; cs4265_init_sequence[i].reg != 0xff; i++) {
+        i2c_master_start(i2c_cmd);
+        i2c_master_write_byte(i2c_cmd, (cs4265_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
+        i2c_master_write_byte(i2c_cmd, cs4265_init_sequence[i].reg, I2C_MASTER_NACK);
+        i2c_master_write_byte(i2c_cmd, cs4265_init_sequence[i].value, I2C_MASTER_NACK);
+        ESP_LOGD(
+            TAG, "i2c write %x at %u", cs4265_init_sequence[i].reg, cs4265_init_sequence[i].value);
+    }
 	
-	return true;
+    i2c_master_stop(i2c_cmd);
+    esp_err_t res = i2c_master_cmd_begin(cs4265.port, i2c_cmd, 500 / portTICK_RATE_MS);
+    i2c_cmd_link_delete(i2c_cmd);
+    if (res != ESP_OK) {
+        ESP_LOGE(TAG, "could not intialize cs4265 %d", res);
+        return false;
+    }
 
-}	
+    return true;
+}
 
-static esp_err_t cs4265_update_bit(uint8_t reg_no,uint8_t mask,uint8_t val ){
-    esp_err_t ret=ESP_OK;
-    uint8_t old= adac_read_byte(cs4265_addr, reg_no);
+static esp_err_t cs4265_update_bit(uint8_t reg_no, uint8_t mask, uint8_t val) {
+    esp_err_t ret = ESP_OK;
+    uint8_t old = adac_read_byte(cs4265_addr, reg_no);
     uint8_t newval = (old & ~mask) | (val & mask);
-	bool change = old != newval;
-	if (change){
-		ret = adac_write_byte(cs4265_addr, reg_no, newval);
-		if(ret != ESP_OK){
-        	ESP_LOGE(TAG,"Unable to change dac register 0x%02x [0x%02x->0x%02x] from value 0x%02x, mask 0x%02x  ",reg_no,old,newval,val,mask);
-    	}
-    	else {
-	        ESP_LOGD(TAG,"Changed dac register 0x%02x [0x%02x->0x%02x] from value 0x%02x, mask 0x%02x ",reg_no,old,newval,val,mask);
-	    }
-	}
-	
+    bool change = old != newval;
+    if (change) {
+        ret = adac_write_byte(cs4265_addr, reg_no, newval);
+        if (ret != ESP_OK) {
+            ESP_LOGE(TAG,
+                "Unable to change dac register 0x%02x [0x%02x->0x%02x] from value 0x%02x, mask "
+                "0x%02x  ",
+                reg_no, old, newval, val, mask);
+        } else {
+            ESP_LOGD(TAG,
+                "Changed dac register 0x%02x [0x%02x->0x%02x] from value 0x%02x, mask 0x%02x ",
+                reg_no, old, newval, val, mask);
+        }
+    }
+
     return ret;
 }
 
 /****************************************************************************************
  * change volume
  */
-static bool volume(unsigned left, unsigned right) { 
-	return false; 
-}
+static bool volume(unsigned left, unsigned right) { return false; }
 
 /****************************************************************************************
  * power
  */
 static void power(adac_power_e mode) {
-	switch(mode) {
-	case ADAC_STANDBY:
-		dac_cmd(cs4265_STANDBY);
-		break;
-	case ADAC_ON:
-		dac_cmd(cs4265_ACTIVE);
-		break;		
-	case ADAC_OFF:
-		dac_cmd(cs4265_DOWN);
-		break;				
-	default:
-		ESP_LOGW(TAG, "unknown DAC command");
-		break;
-	}
+    switch (mode) {
+    case ADAC_STANDBY:
+        dac_cmd(cs4265_STANDBY);
+        break;
+    case ADAC_ON:
+        dac_cmd(cs4265_ACTIVE);
+        break;
+    case ADAC_OFF:
+        dac_cmd(cs4265_DOWN);
+        break;
+    default:
+        ESP_LOGW(TAG, "unknown DAC command");
+        break;
+    }
 }
 
 /****************************************************************************************
  * speaker
  */
 static void speaker(bool active) {
-	if (active) dac_cmd(cs4265_ANALOGUE_ON);
-	else dac_cmd(cs4265_ANALOGUE_OFF);
-} 
+    if (active)
+        dac_cmd(cs4265_ANALOGUE_ON);
+    else
+        dac_cmd(cs4265_ANALOGUE_OFF);
+}
 
 /****************************************************************************************
  * headset
  */
-static void headset(bool active) { } 
- 
+static void headset(bool active) {}
+
 /****************************************************************************************
  * DAC specific commands
  */
 void dac_cmd(dac_cmd_e cmd, ...) {
-	va_list args;
-	esp_err_t ret = ESP_OK;
-	
-	va_start(args, cmd);
+    va_list args;
+    esp_err_t ret = ESP_OK;
+
+    va_start(args, cmd);
 
-	switch(cmd) {
-	case cs4265_VOLUME:
-		ESP_LOGE(TAG, "DAC volume not handled yet");
-		break;
+    switch (cmd) {
+    case cs4265_VOLUME:
+        ESP_LOGE(TAG, "DAC volume not handled yet");
+        break;
     case cs4265_ACTIVE:
-		ESP_LOGD(TAG, "Activating DAC");
-        adac_write_byte(cs4265_addr, CS4265_PWRCTL,0);
-        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,0);
-        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,0);
-		cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,0);				
-		break;
+        ESP_LOGD(TAG, "Activating DAC");
+        adac_write_byte(cs4265_addr, CS4265_PWRCTL, 0);
+        cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXOFF, 0);
+        cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXMUTE, 0);
+        cs4265_update_bit(CS4265_DAC_CTL, CS4265_DAC_CTL_MUTE, 0);
+        break;
     case cs4265_STANDBY:
-		ESP_LOGD(TAG, "DAC Stand-by");
-        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,CS4265_SPDIF_CTL2_TXOFF);
-        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,CS4265_SPDIF_CTL2_TXMUTE);
-		cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,CS4265_DAC_CTL_MUTE);
-		break;
+        ESP_LOGD(TAG, "DAC Stand-by");
+        cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXOFF, CS4265_SPDIF_CTL2_TXOFF);
+        cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXMUTE, CS4265_SPDIF_CTL2_TXMUTE);
+        cs4265_update_bit(CS4265_DAC_CTL, CS4265_DAC_CTL_MUTE, CS4265_DAC_CTL_MUTE);
+        break;
     case cs4265_DOWN:
-		ESP_LOGD(TAG, "DAC Power Down");
-        adac_write_byte(cs4265_addr, CS4265_PWRCTL,CS4265_PWRCTL_PDN_ALL);
-		break;
+        ESP_LOGD(TAG, "DAC Power Down");
+        adac_write_byte(cs4265_addr, CS4265_PWRCTL, CS4265_PWRCTL_PDN_ALL);
+        break;
     case cs4265_ANALOGUE_OFF:
-		ESP_LOGD(TAG, "DAC Analog off");
-        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,CS4265_SPDIF_CTL2_TXOFF);
-        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,CS4265_SPDIF_CTL2_TXMUTE);
-		cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,CS4265_DAC_CTL_MUTE);
-		break;
+        ESP_LOGD(TAG, "DAC Analog off");
+        cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXOFF, CS4265_SPDIF_CTL2_TXOFF);
+        cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXMUTE, CS4265_SPDIF_CTL2_TXMUTE);
+        cs4265_update_bit(CS4265_DAC_CTL, CS4265_DAC_CTL_MUTE, CS4265_DAC_CTL_MUTE);
+        break;
     case cs4265_ANALOGUE_ON:
-		ESP_LOGD(TAG, "DAC Analog on");
-		adac_write_byte(cs4265_addr, CS4265_PWRCTL,0);
-        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,0);
-        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,0);
-		cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,0);		
-		break;
-	}
-	
-  	if (ret != ESP_OK) {
-		ESP_LOGE(TAG, "could not use cs4265 %d", ret);
-	}
+        ESP_LOGD(TAG, "DAC Analog on");
+        adac_write_byte(cs4265_addr, CS4265_PWRCTL, 0);
+        cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXOFF, 0);
+        cs4265_update_bit(CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_TXMUTE, 0);
+        cs4265_update_bit(CS4265_DAC_CTL, CS4265_DAC_CTL_MUTE, 0);
+        break;
+    }
+
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "could not use cs4265 %d", ret);
+    }
     get_status();
-	// now set the clock
-	ret=set_clock(cs4265.i2s_config,cs4265.i2c_port);
-	if (ret != ESP_OK) {
-		ESP_LOGE(TAG, "could not set the cs4265's clock %d", ret);
-	}	
+    // now set the clock
+    ret = set_clock();
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "could not set the cs4265's clock %d", ret);
+    }
 
-	va_end(args);
+    va_end(args);
 }
 
 /****************************************************************************************
  * TAS57 detection
  */
 static int cs4265_detect(void) {
-	uint8_t addr[] = {CS4265_PULL_DOWN,CS4265_PULL_UP};
-	
-	for (int i = 0; i < sizeof(addr); i++) {
-		ESP_LOGI(TAG,"Looking for CS4265 @0x%x",addr[i]);
-		uint8_t reg=adac_read_byte(addr[i], CS4265_CHIP_ID);
-		if(reg==255){
-			continue;
-		}
-			// found a device at that address
-		uint8_t devid = reg & CS4265_CHIP_ID_MASK;
-		if (devid != CS4265_CHIP_ID_VAL) {
-			ESP_LOGE(TAG,"CS4265 Device ID (%X). Expected %X",devid, CS4265_CHIP_ID);
-			return 0;
-		}
-		ESP_LOGI(TAG,"Found DAC @0x%x, Version %x",addr[i], reg & CS4265_REV_ID_MASK);
-		return addr[i];	
-	}
-	return 0;
+    uint8_t addr[] = {CS4265_PULL_DOWN, CS4265_PULL_UP};
+    ESP_LOGD(TAG, "Detecting chip connection type/address");
+    for (int i = 0; i < sizeof(addr); i++) {
+        ESP_LOGI(TAG, "Looking for CS4265 @0x%x", addr[i]);
+        uint8_t reg = adac_read_byte(addr[i], CS4265_CHIP_ID);
+        if (reg == 255) {
+            ESP_LOGD(TAG, "Nothing there");
+            continue;
+        }
+        // found a device at that address
+        ESP_LOGD(TAG, "Found a device. Check signature");
+        uint8_t devid = reg & CS4265_CHIP_ID_MASK;
+        if (devid != CS4265_CHIP_ID_VAL) {
+            ESP_LOGE(TAG, "CS4265 Device ID (%X). Expected %X", devid, CS4265_CHIP_ID);
+            return 0;
+        }
+        ESP_LOGI(TAG, "Found DAC @0x%x, Version %x", addr[i], reg & CS4265_REV_ID_MASK);
+        return addr[i];
+    }
+    return 0;
 }
-

+ 396 - 383
components/squeezelite/decode_external.c

@@ -1,4 +1,4 @@
-/* 
+/*
  *  Squeezelite for esp32
  *
  *  (c) Sebastien 2019
@@ -11,14 +11,16 @@
 
 #include <math.h>
 #ifdef ESP_PLATFORM
+#include "Services.pb.h"
+#include "Squeezelite.pb.h"
 #include "freertos/FreeRTOS.h"
 #include "freertos/timers.h"
+extern sys_squeezelite_config* get_profile(const char* name);
 #endif
-#include "Configurator.h"
+#include "Config.h"
 #include "accessors.h"
 #include "squeezelite.h"
 
-
 #if CONFIG_BT_SINK
 #include "bt_app_sink.h"
 static bool enable_bt_sink;
@@ -34,91 +36,93 @@ static bool enable_cspot;
 static bool enable_airplay;
 
 #define RAOP_OUTPUT_SIZE (((RAOP_SAMPLE_RATE * BYTES_PER_FRAME * 2 * 120) / 100) & ~BYTES_PER_FRAME)
-#define SYNC_WIN_SLOW	32
-#define SYNC_WIN_CHECK	8
-#define SYNC_WIN_FAST	2
+#define SYNC_WIN_SLOW 32
+#define SYNC_WIN_CHECK 8
+#define SYNC_WIN_FAST 2
 
-static raop_event_t	raop_state;
-static sys_Squeezelite * squeezelite;
+static raop_event_t raop_state;
+static sys_squeezelite_config* squeezelite;
 
 static EXT_RAM_ATTR struct {
-	bool enabled;
-	int sum, count, win, errors[SYNC_WIN_SLOW];
-	s32_t len;
-	u32_t start_time, playtime;
+    bool enabled;
+    int sum, count, win, errors[SYNC_WIN_SLOW];
+    s32_t len;
+    u32_t start_time, playtime;
 } raop_sync;
 #endif
 
 static enum { SINK_RUNNING, SINK_ABORT, SINK_DISCARD } sink_state;
 
-#define LOCK_O   mutex_lock(outputbuf->mutex)
+#define LOCK_O mutex_lock(outputbuf->mutex)
 #define UNLOCK_O mutex_unlock(outputbuf->mutex)
-#define LOCK_D   mutex_lock(decode.mutex);
+#define LOCK_D mutex_lock(decode.mutex);
 #define UNLOCK_D mutex_unlock(decode.mutex);
 
 enum { DECODE_BT = 1, DECODE_RAOP, DECODE_CSPOT };
 
 extern struct outputstate output;
 extern struct decodestate decode;
-extern struct buffer *outputbuf;
+extern struct buffer* outputbuf;
 // this is the only system-wide loglevel variable
 extern log_level loglevel;
 
 /****************************************************************************************
  * Common sink data handler
  */
-static uint32_t sink_data_handler(const uint8_t *data, uint32_t len, int retries)
-{
+static uint32_t sink_data_handler(const uint8_t* data, uint32_t len, int retries) {
     size_t bytes, space;
-    uint32_t written = 0;    
-	int wait = retries + 1;
-		
-	// would be better to lock output, but really, it does not matter
-	if (!output.external) {
-		LOG_SDEBUG("Cannot use external sink while LMS is controlling player");
-		return 0;
-	} 
-
-	LOCK_O;
-	if (sink_state == SINK_ABORT) sink_state = SINK_RUNNING;
-
-	// there will always be room at some point
-	while (len && wait && sink_state == SINK_RUNNING) {
-		bytes = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / (BYTES_PER_FRAME / 4);
-		bytes = min(len, bytes);
+    uint32_t written = 0;
+    int wait = retries + 1;
+
+    // would be better to lock output, but really, it does not matter
+    if (!output.external) {
+        LOG_SDEBUG("Cannot use external sink while LMS is controlling player");
+        return 0;
+    }
+
+    LOCK_O;
+    if (sink_state == SINK_ABORT) sink_state = SINK_RUNNING;
+
+    // there will always be room at some point
+    while (len && wait && sink_state == SINK_RUNNING) {
+        bytes = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / (BYTES_PER_FRAME / 4);
+        bytes = min(len, bytes);
 #if BYTES_PER_FRAME == 4
-		memcpy(outputbuf->writep, data, bytes);
+        memcpy(outputbuf->writep, data, bytes);
 #else
-		{
-			s16_t *iptr = (s16_t*) data;
-			ISAMPLE_T *optr = (ISAMPLE_T *) outputbuf->writep;
-			size_t n = bytes / 2;
-			while (n--) *optr++ = *iptr++ << 16;
-		}
-#endif	
-		_buf_inc_writep(outputbuf, bytes * BYTES_PER_FRAME / 4);
-		space = _buf_space(outputbuf);
-
-		len -= bytes;
-		data += bytes;
+        {
+            s16_t* iptr = (s16_t*)data;
+            ISAMPLE_T* optr = (ISAMPLE_T*)outputbuf->writep;
+            size_t n = bytes / 2;
+            while (n--)
+                *optr++ = *iptr++ << 16;
+        }
+#endif
+        _buf_inc_writep(outputbuf, bytes * BYTES_PER_FRAME / 4);
+        space = _buf_space(outputbuf);
+
+        len -= bytes;
+        data += bytes;
         written += bytes;
-				
-		// allow i2s to empty the buffer if needed
-		if (len && !space) {
+
+        // allow i2s to empty the buffer if needed
+        if (len && !space) {
             if (!retries) break;
-			wait--;
-			UNLOCK_O; usleep(50000); LOCK_O;
-		}
-	}	
+            wait--;
+            UNLOCK_O;
+            usleep(50000);
+            LOCK_O;
+        }
+    }
 
-	if (!wait) {
+    if (!wait) {
         // re-align the buffer according to what we threw away
         _buf_inc_writep(outputbuf, outputbuf->size - (BYTES_PER_FRAME - (len % BYTES_PER_FRAME)));
-		LOG_WARN("Waited too long, dropping frames %d", len);
-	}
-    
+        LOG_WARN("Waited too long, dropping frames %d", len);
+    }
+
     UNLOCK_O;
-    
+
     return written;
 }
 
@@ -126,77 +130,77 @@ static uint32_t sink_data_handler(const uint8_t *data, uint32_t len, int retries
  * BT sink data handler
  */
 #if CONFIG_BT_SINK
-static void bt_sink_data_handler(const uint8_t *data, uint32_t len) {
+static void bt_sink_data_handler(const uint8_t* data, uint32_t len) {
     sink_data_handler(data, len, 10);
-}    
+}
 
 /****************************************************************************************
  * BT sink command handler
  */
-static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args) 
-{
-	// don't LOCK_O as there is always a chance that LMS takes control later anyway
-	if (output.external != DECODE_BT && output.state > OUTPUT_STOPPED) {
-		LOG_WARN("Cannot use BT sink while LMS/AirPlay/CSpot are controlling player %d", output.external);
-		return false;
-	} 	
-
-	LOCK_D;
-
-	if (cmd != BT_SINK_VOLUME) LOCK_O;
-		
-	switch(cmd) {
-	case BT_SINK_AUDIO_STARTED:
-		_buf_flush(outputbuf);
-		_buf_limit(outputbuf, 0);
-		output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
-		output.external = DECODE_BT;
-		output.state = OUTPUT_STOPPED;
-		output.frames_played = 0;
-		if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
-		LOG_INFO("BT sink started");
-		break;
-	case BT_SINK_AUDIO_STOPPED:	
-		if (output.external == DECODE_BT) {
-			if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
-			output.external = 0;
-			output.stop_time = gettime_ms();
-			LOG_INFO("BT sink stopped");
-		}	
-		break;
-	case BT_SINK_PLAY:
-		output.state = OUTPUT_RUNNING;
-		LOG_INFO("BT play");
-		break;
-	case BT_SINK_STOP:		
-		_buf_flush(outputbuf);
-		output.state = OUTPUT_STOPPED;
-		output.stop_time = gettime_ms();
-		sink_state = SINK_ABORT;
-		LOG_INFO("BT stop");
-		break;
-	case BT_SINK_PAUSE:		
-		output.stop_time = gettime_ms();
-		LOG_INFO("BT pause, just silence");
-		break;
-	case BT_SINK_RATE:
-		output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
-		LOG_INFO("Setting BT sample rate %u", output.next_sample_rate);
-		break;
-	case BT_SINK_VOLUME: {
-		u32_t volume = va_arg(args, u32_t);
-		volume = 65536 * powf(volume / 128.0f, 3);
-		set_volume(volume, volume);
-		break;
-	default:
-		break;
-	}
-	}
-	
-	if (cmd != BT_SINK_VOLUME) UNLOCK_O;
-	UNLOCK_D;
-
-	return true;
+static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args) {
+    // don't LOCK_O as there is always a chance that LMS takes control later anyway
+    if (output.external != DECODE_BT && output.state > OUTPUT_STOPPED) {
+        LOG_WARN("Cannot use BT sink while LMS/AirPlay/CSpot are controlling player %d",
+            output.external);
+        return false;
+    }
+
+    LOCK_D;
+
+    if (cmd != BT_SINK_VOLUME) LOCK_O;
+
+    switch (cmd) {
+    case BT_SINK_AUDIO_STARTED:
+        _buf_flush(outputbuf);
+        _buf_limit(outputbuf, 0);
+        output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
+        output.external = DECODE_BT;
+        output.state = OUTPUT_STOPPED;
+        output.frames_played = 0;
+        if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
+        LOG_INFO("BT sink started");
+        break;
+    case BT_SINK_AUDIO_STOPPED:
+        if (output.external == DECODE_BT) {
+            if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
+            output.external = 0;
+            output.stop_time = gettime_ms();
+            LOG_INFO("BT sink stopped");
+        }
+        break;
+    case BT_SINK_PLAY:
+        output.state = OUTPUT_RUNNING;
+        LOG_INFO("BT play");
+        break;
+    case BT_SINK_STOP:
+        _buf_flush(outputbuf);
+        output.state = OUTPUT_STOPPED;
+        output.stop_time = gettime_ms();
+        sink_state = SINK_ABORT;
+        LOG_INFO("BT stop");
+        break;
+    case BT_SINK_PAUSE:
+        output.stop_time = gettime_ms();
+        LOG_INFO("BT pause, just silence");
+        break;
+    case BT_SINK_RATE:
+        output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
+        LOG_INFO("Setting BT sample rate %u", output.next_sample_rate);
+        break;
+    case BT_SINK_VOLUME: {
+        u32_t volume = va_arg(args, u32_t);
+        volume = 65536 * powf(volume / 128.0f, 3);
+        set_volume(volume, volume);
+        break;
+    default:
+        break;
+    }
+    }
+
+    if (cmd != BT_SINK_VOLUME) UNLOCK_O;
+    UNLOCK_D;
+
+    return true;
 }
 #endif
 
@@ -204,144 +208,154 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
  * raop sink data handler
  */
 #if CONFIG_AIRPLAY_SINK
-static void raop_sink_data_handler(const uint8_t *data, uint32_t len, u32_t playtime) {
-	
-	raop_sync.playtime = playtime;
-	raop_sync.len = len;
+static void raop_sink_data_handler(const uint8_t* data, uint32_t len, u32_t playtime) {
 
-	sink_data_handler(data, len, 10);
-}	
+    raop_sync.playtime = playtime;
+    raop_sync.len = len;
+
+    sink_data_handler(data, len, 10);
+}
 
 /****************************************************************************************
  * AirPlay sink command handler
  */
-static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
-{
-	// don't LOCK_O as there is always a chance that LMS takes control later anyway
-	if (output.external != DECODE_RAOP && output.state > OUTPUT_STOPPED) {
-		LOG_WARN("Cannot use Airplay sink while LMS/BT/CSpot are controlling player %d", output.external);
-		return false;
-	} 	
-
-	LOCK_D;
-	
-	if (event != RAOP_VOLUME) LOCK_O;
-	
-	// this is async, so player might have been deleted
-	switch (event) {
-		case RAOP_TIMING: {
-			if (!raop_sync.enabled || output.state != OUTPUT_RUNNING || output.frames_played_dmp < output.device_frames) break;
-
-			u32_t ms, now = gettime_ms();
-			u32_t level = _buf_used(outputbuf);
-			int error;
-				
-			// in how many ms will the most recent block play 
-			ms = (((s32_t)(level - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 10) / (RAOP_SAMPLE_RATE / 100) - (s32_t) (now - output.updated);
-				
-			// when outputbuf is empty, it means we have a network black-out or something
-			error = level ? (raop_sync.playtime - now) - ms : 0;
-				
-			if (loglevel == lDEBUG || !level) {
-				LOG_INFO("head local:%d, remote:%d (delta:%d)", ms, raop_sync.playtime - now, error);
-				LOG_INFO("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process);
-			}	
-			
-			// calculate sum, error and update sliding window
-			raop_sync.errors[raop_sync.count++ % raop_sync.win] = error;
-			raop_sync.sum += error;
-			error = raop_sync.sum / min(raop_sync.count, raop_sync.win);
-
-			// wait till we have enough data or there is a strong deviation
-			if ((raop_sync.count >= raop_sync.win && abs(error) > 10) || (raop_sync.count >= SYNC_WIN_CHECK && abs(error) > 100)) {
-				if (error < 0) {
-					output.skip_frames = -(error * RAOP_SAMPLE_RATE) / 1000;
-					output.state = OUTPUT_SKIP_FRAMES;					
-					LOG_INFO("skipping %u frames (count:%d)", output.skip_frames, raop_sync.count);
-				} else {
-					output.pause_frames = (error * RAOP_SAMPLE_RATE) / 1000;
-					output.state = OUTPUT_PAUSE_FRAMES;
-					LOG_INFO("pausing for %u frames (count: %d)", output.pause_frames, raop_sync.count);
-				}
-				
-				raop_sync.sum = raop_sync.count = 0;
-				memset(raop_sync.errors, 0, sizeof(raop_sync.errors));
-			}	
-			
-			// move to normal mode if possible			
-			if (raop_sync.win == 1) {
-				raop_sync.win = SYNC_WIN_FAST;
-				LOG_INFO("backend played %u, desired %u, (delta:%d)", ms, raop_sync.playtime - now, error);
-			} else if (raop_sync.win == SYNC_WIN_FAST && raop_sync.count >= SYNC_WIN_FAST && abs(error) < 10) {
-				raop_sync.win = SYNC_WIN_SLOW;
-				LOG_INFO("switching to slow sync mode %u", raop_sync.win);
-			}	
-
-			break;
-		}
-		case RAOP_SETUP: {
-			uint8_t **buffer = va_arg(args, uint8_t**);
-			size_t *size = va_arg(args, size_t*);
-
-			// steal buffer tail from outputbuf but do not reallocate
-			*size = _buf_limit(outputbuf, RAOP_OUTPUT_SIZE);
-			*buffer = outputbuf->writep + RAOP_OUTPUT_SIZE;
-
-			output.frames_played = 0;
-			output.external = DECODE_RAOP;
-			output.state = OUTPUT_STOPPED;
-			if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
-			LOG_INFO("resizing buffer %u", outputbuf->size);
-			break;
-		}
-		case RAOP_STREAM:
-			LOG_INFO("Stream", NULL);
-			raop_state = event;
-			raop_sync.win = 1;
-			raop_sync.sum = raop_sync.count = 0;
-			memset(raop_sync.errors, 0, sizeof(raop_sync.errors));
-			raop_sync.enabled = !strcasestr(output.device, "BT");
-			output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;
-			break;
-        case RAOP_STALLED:
-		case RAOP_STOP:
-			output.external = 0;
-			__attribute__ ((fallthrough));
-		case RAOP_FLUSH:
-			LOG_INFO("%s", event == RAOP_FLUSH ? "Flush" : "Stop");
-			_buf_flush(outputbuf);
-			raop_state = event;
-			if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
-			sink_state = SINK_ABORT;
-			output.frames_played = 0;
-			output.stop_time = gettime_ms();
-			break;
-		case RAOP_PLAY: {
-			LOG_INFO("Play", NULL);
-			if (raop_state != RAOP_PLAY) {
-				output.state = OUTPUT_START_AT;
-				output.start_at = va_arg(args, u32_t);
-				raop_sync.start_time = output.start_at;
-				LOG_INFO("Starting at %u (in %d ms)", output.start_at, output.start_at - gettime_ms());
-			}
-			raop_state = event;
-			break;
-		}
-		case RAOP_VOLUME: {
-			float volume = va_arg(args, double);
-			LOG_INFO("Volume[0..1] %0.4f", volume);
-			volume = 65536 * powf(volume, 3);
-			set_volume(volume, volume);
-			break;
-		}
-		default:
-			break;
-	}
-	
-	if (event != RAOP_VOLUME) UNLOCK_O;
-	
-	UNLOCK_D;
-	return true;
+static bool raop_sink_cmd_handler(raop_event_t event, va_list args) {
+    // don't LOCK_O as there is always a chance that LMS takes control later anyway
+    if (output.external != DECODE_RAOP && output.state > OUTPUT_STOPPED) {
+        LOG_WARN("Cannot use Airplay sink while LMS/BT/CSpot are controlling player %d",
+            output.external);
+        return false;
+    }
+
+    LOCK_D;
+
+    if (event != RAOP_VOLUME) LOCK_O;
+
+    // this is async, so player might have been deleted
+    switch (event) {
+    case RAOP_TIMING: {
+        if (!raop_sync.enabled || output.state != OUTPUT_RUNNING ||
+            output.frames_played_dmp < output.device_frames)
+            break;
+
+        u32_t ms, now = gettime_ms();
+        u32_t level = _buf_used(outputbuf);
+        int error;
+
+        // in how many ms will the most recent block play
+        ms = (((s32_t)(level - raop_sync.len) / BYTES_PER_FRAME + output.device_frames +
+                  output.frames_in_process) *
+                 10) /
+                 (RAOP_SAMPLE_RATE / 100) -
+             (s32_t)(now - output.updated);
+
+        // when outputbuf is empty, it means we have a network black-out or something
+        error = level ? (raop_sync.playtime - now) - ms : 0;
+
+        if (loglevel == lDEBUG || !level) {
+            LOG_INFO("head local:%d, remote:%d (delta:%d)", ms, raop_sync.playtime - now, error);
+            LOG_INFO("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf),
+                raop_sync.len, output.device_frames, output.frames_in_process);
+        }
+
+        // calculate sum, error and update sliding window
+        raop_sync.errors[raop_sync.count++ % raop_sync.win] = error;
+        raop_sync.sum += error;
+        error = raop_sync.sum / min(raop_sync.count, raop_sync.win);
+
+        // wait till we have enough data or there is a strong deviation
+        if ((raop_sync.count >= raop_sync.win && abs(error) > 10) ||
+            (raop_sync.count >= SYNC_WIN_CHECK && abs(error) > 100)) {
+            if (error < 0) {
+                output.skip_frames = -(error * RAOP_SAMPLE_RATE) / 1000;
+                output.state = OUTPUT_SKIP_FRAMES;
+                LOG_INFO("skipping %u frames (count:%d)", output.skip_frames, raop_sync.count);
+            } else {
+                output.pause_frames = (error * RAOP_SAMPLE_RATE) / 1000;
+                output.state = OUTPUT_PAUSE_FRAMES;
+                LOG_INFO("pausing for %u frames (count: %d)", output.pause_frames, raop_sync.count);
+            }
+
+            raop_sync.sum = raop_sync.count = 0;
+            memset(raop_sync.errors, 0, sizeof(raop_sync.errors));
+        }
+
+        // move to normal mode if possible
+        if (raop_sync.win == 1) {
+            raop_sync.win = SYNC_WIN_FAST;
+            LOG_INFO(
+                "backend played %u, desired %u, (delta:%d)", ms, raop_sync.playtime - now, error);
+        } else if (raop_sync.win == SYNC_WIN_FAST && raop_sync.count >= SYNC_WIN_FAST &&
+                   abs(error) < 10) {
+            raop_sync.win = SYNC_WIN_SLOW;
+            LOG_INFO("switching to slow sync mode %u", raop_sync.win);
+        }
+
+        break;
+    }
+    case RAOP_SETUP: {
+        uint8_t** buffer = va_arg(args, uint8_t**);
+        size_t* size = va_arg(args, size_t*);
+
+        // steal buffer tail from outputbuf but do not reallocate
+        *size = _buf_limit(outputbuf, RAOP_OUTPUT_SIZE);
+        *buffer = outputbuf->writep + RAOP_OUTPUT_SIZE;
+
+        output.frames_played = 0;
+        output.external = DECODE_RAOP;
+        output.state = OUTPUT_STOPPED;
+        if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
+        LOG_INFO("resizing buffer %u", outputbuf->size);
+        break;
+    }
+    case RAOP_STREAM:
+        LOG_INFO("Stream", NULL);
+        raop_state = event;
+        raop_sync.win = 1;
+        raop_sync.sum = raop_sync.count = 0;
+        memset(raop_sync.errors, 0, sizeof(raop_sync.errors));
+        raop_sync.enabled = !strcasestr(output.device, "BT");
+        output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;
+        break;
+    case RAOP_STALLED:
+    case RAOP_STOP:
+        output.external = 0;
+        __attribute__((fallthrough));
+    case RAOP_FLUSH:
+        LOG_INFO("%s", event == RAOP_FLUSH ? "Flush" : "Stop");
+        _buf_flush(outputbuf);
+        raop_state = event;
+        if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
+        sink_state = SINK_ABORT;
+        output.frames_played = 0;
+        output.stop_time = gettime_ms();
+        break;
+    case RAOP_PLAY: {
+        LOG_INFO("Play", NULL);
+        if (raop_state != RAOP_PLAY) {
+            output.state = OUTPUT_START_AT;
+            output.start_at = va_arg(args, u32_t);
+            raop_sync.start_time = output.start_at;
+            LOG_INFO("Starting at %u (in %d ms)", output.start_at, output.start_at - gettime_ms());
+        }
+        raop_state = event;
+        break;
+    }
+    case RAOP_VOLUME: {
+        float volume = va_arg(args, double);
+        LOG_INFO("Volume[0..1] %0.4f", volume);
+        volume = 65536 * powf(volume, 3);
+        set_volume(volume, volume);
+        break;
+    }
+    default:
+        break;
+    }
+
+    if (event != RAOP_VOLUME) UNLOCK_O;
+
+    UNLOCK_D;
+    return true;
 }
 #endif
 
@@ -349,98 +363,98 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
  * cspot sink data handler
  */
 #if CONFIG_CSPOT_SINK
-static uint32_t cspot_sink_data_handler(const uint8_t *data, uint32_t len) {
+static uint32_t cspot_sink_data_handler(const uint8_t* data, uint32_t len) {
     return sink_data_handler(data, len, 0);
-}    
+}
 
 /****************************************************************************************
  * cspot sink command handler
  */
 
-static bool cspot_cmd_handler(cspot_event_t cmd, va_list args) 
-{
-	// don't LOCK_O as there is always a chance that LMS takes control later anyway
-	if (output.external != DECODE_CSPOT && output.state > OUTPUT_STOPPED) {
-		LOG_WARN("Cannot use CSpot sink while LMS/BT/Airplay are controlling player %d", output.external);
-		return false;
-	} 	
-
-	LOCK_D;
-	
-	if (cmd != CSPOT_VOLUME) LOCK_O;
-
-	switch(cmd) {
-	case CSPOT_START:
-		output.current_sample_rate = output.next_sample_rate = va_arg(args, u32_t);
-		output.external = DECODE_CSPOT;
-		output.frames_played = 0;
+static bool cspot_cmd_handler(cspot_event_t cmd, va_list args) {
+    // don't LOCK_O as there is always a chance that LMS takes control later anyway
+    if (output.external != DECODE_CSPOT && output.state > OUTPUT_STOPPED) {
+        LOG_WARN("Cannot use CSpot sink while LMS/BT/Airplay are controlling player %d",
+            output.external);
+        return false;
+    }
+
+    LOCK_D;
+
+    if (cmd != CSPOT_VOLUME) LOCK_O;
+
+    switch (cmd) {
+    case CSPOT_START:
+        output.current_sample_rate = output.next_sample_rate = va_arg(args, u32_t);
+        output.external = DECODE_CSPOT;
+        output.frames_played = 0;
         // in 1/10 of seconds
         output.threshold = 25;
-		output.state = OUTPUT_STOPPED;
+        output.state = OUTPUT_STOPPED;
         sink_state = SINK_ABORT;
-		_buf_flush(outputbuf);
-		if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
-		LOG_INFO("CSpot start track");
-		break;
-	case CSPOT_DISC:
-		_buf_flush(outputbuf);
-		sink_state = SINK_ABORT;
-		output.external = 0;
-		output.state = OUTPUT_STOPPED;
-		output.stop_time = gettime_ms();
-		LOG_INFO("CSpot disconnected");
-		break;
-	case CSPOT_PLAY:
-		sink_state = SINK_RUNNING;			
-		output.state = OUTPUT_RUNNING;
-		LOG_INFO("CSpot play");
-		break;
-	case CSPOT_SEEK:
-		_buf_flush(outputbuf);		
-		sink_state = SINK_ABORT;
-		LOG_INFO("CSpot seek by %d", va_arg(args, uint32_t));
-		break;
-	case CSPOT_FLUSH:
-		_buf_flush(outputbuf);
-		sink_state = SINK_DISCARD;
-		output.state = OUTPUT_STOPPED;
-		LOG_INFO("CSpot flush");	
-		break;		
-	case CSPOT_PAUSE:		
-		output.state = OUTPUT_STOPPED;
-		output.stop_time = gettime_ms();
-		LOG_INFO("CSpot pause");
-		break;
+        _buf_flush(outputbuf);
+        if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
+        LOG_INFO("CSpot start track");
+        break;
+    case CSPOT_DISC:
+        _buf_flush(outputbuf);
+        sink_state = SINK_ABORT;
+        output.external = 0;
+        output.state = OUTPUT_STOPPED;
+        output.stop_time = gettime_ms();
+        LOG_INFO("CSpot disconnected");
+        break;
+    case CSPOT_PLAY:
+        sink_state = SINK_RUNNING;
+        output.state = OUTPUT_RUNNING;
+        LOG_INFO("CSpot play");
+        break;
+    case CSPOT_SEEK:
+        _buf_flush(outputbuf);
+        sink_state = SINK_ABORT;
+        LOG_INFO("CSpot seek by %d", va_arg(args, uint32_t));
+        break;
+    case CSPOT_FLUSH:
+        _buf_flush(outputbuf);
+        sink_state = SINK_DISCARD;
+        output.state = OUTPUT_STOPPED;
+        LOG_INFO("CSpot flush");
+        break;
+    case CSPOT_PAUSE:
+        output.state = OUTPUT_STOPPED;
+        output.stop_time = gettime_ms();
+        LOG_INFO("CSpot pause");
+        break;
     case CSPOT_TRACK_MARK:
         output.track_start = outputbuf->writep;
         break;
     case CSPOT_QUERY_REMAINING: {
-        uint32_t *remaining = va_arg(args, uint32_t*);
+        uint32_t* remaining = va_arg(args, uint32_t*);
         *remaining = (_buf_used(outputbuf) * 1000) / (output.current_sample_rate * BYTES_PER_FRAME);
-        break;      
+        break;
     }
     case CSPOT_QUERY_STARTED: {
-        uint32_t *started = va_arg(args, uint32_t*);
+        uint32_t* started = va_arg(args, uint32_t*);
         *started = output.track_started;
         // this is a read_and_clear event
         output.track_started = false;
-        break;      
+        break;
+    }
+    case CSPOT_VOLUME: {
+        u32_t volume = va_arg(args, u32_t);
+        LOG_INFO("CSpot volume %u", volume);
+        volume = 65536 * powf(volume / 65536.0f, 4);
+        set_volume(volume, volume);
+        break;
+    default:
+        break;
+    }
     }
-	case CSPOT_VOLUME: {
-		u32_t volume = va_arg(args, u32_t);
-		LOG_INFO("CSpot volume %u", volume);
-		volume = 65536 * powf(volume / 65536.0f, 4);
-		set_volume(volume, volume);
-		break;
-	default:
-		break;
-	}
-	}
-	
-	if (cmd != CSPOT_VOLUME) UNLOCK_O;
-	UNLOCK_D;
-	
-	return true;
+
+    if (cmd != CSPOT_VOLUME) UNLOCK_O;
+    UNLOCK_D;
+
+    return true;
 }
 #endif
 
@@ -448,80 +462,79 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
  * We provide the generic codec register option
  */
 void register_external(void) {
-	sys_BluetoothSink * bt_sink;
-	sys_AirPlay * airplay;
-	sys_Spotify * spotify;
+    squeezelite = get_profile(NULL); // get the active profile
 #if CONFIG_BT_SINK
-	enable_bt_sink= (SYS_SERVICES_BTSINK(bt_sink) && bt_sink->enabled);
-	if ( enable_bt_sink) {
-		#pragma message ("Is the BT sink logic correct?")
-		if(SYS_SERVICES_SQUEEZELITE(squeezelite) && squeezelite->output_type == sys_OutputTypeEnum_OUTPUT_Bluetooth ){
-			LOG_ERROR("BT Sink cannot be enabled with Bluetooth output");
-		}
-		else {
-			bt_sink_init(bt_sink_cmd_handler,  bt_sink_data_handler);
-		}
-	}		
-#endif	
+    sys_services_bt_sink* bt_sink;
+    enable_bt_sink = (sys_services_config_BTSINK(bt_sink) && bt_sink->enabled);
+    if (enable_bt_sink && squeezelite) {
+#pragma message("Is the BT sink logic correct?")
+        if (squeezelite->output_type == sys_squeezelite_outputs_BT) {
+            LOG_ERROR("BT Sink cannot be enabled with Bluetooth output");
+        } else {
+            bt_sink_init(bt_sink_cmd_handler, bt_sink_data_handler);
+        }
+    }
+#endif
 
 #if CONFIG_AIRPLAY_SINK
-	enable_airplay = SYS_SERVICES_AIRPLAY(airplay) && airplay->enabled;
-	if (enable_airplay){
-		raop_sink_init(raop_sink_cmd_handler, raop_sink_data_handler);
-		LOG_INFO("Initializing AirPlay sink");
-	}
-	
-#endif	
-	
-#if CONFIG_CSPOT_SINK	
-	enable_cspot = SYS_SERVICES_SPOTIFY(spotify) && spotify->enabled;
-	if (enable_cspot){
-		cspot_sink_init(cspot_cmd_handler, cspot_sink_data_handler);
-		LOG_INFO("Initializing CSpot sink");
-	}	
-#endif	
+    sys_airplay_config* airplay;
+    enable_airplay = sys_services_config_AIRPLAY(airplay) && airplay->enabled;
+    if (enable_airplay) {
+        raop_sink_init(raop_sink_cmd_handler, raop_sink_data_handler);
+        LOG_INFO("Initializing AirPlay sink");
+    }
 
+#endif
+
+#if CONFIG_CSPOT_SINK
+    sys_spotify_config* spotify;
+    enable_cspot = sys_services_config_SPOTIFY(spotify) && spotify->enabled;
+    if (enable_cspot) {
+        cspot_sink_init(cspot_cmd_handler, cspot_sink_data_handler);
+        LOG_INFO("Initializing CSpot sink");
+    }
+#endif
 }
 
 void deregister_external(void) {
 #if CONFIG_BT_SINK
-	sys_Squeezelite * squeezelite;
-	if(SYS_SERVICES_SQUEEZELITE(squeezelite) && squeezelite->output_type != sys_OutputTypeEnum_OUTPUT_Bluetooth && enable_bt_sink ){
-		bt_sink_deinit();
-	}
+    squeezelite = get_profile(NULL); // get the active profile
+    if (squeezelite && squeezelite->output_type == sys_squeezelite_outputs_BT && enable_bt_sink) {
+        bt_sink_deinit();
+    }
 #endif
 
 #if CONFIG_AIRPLAY_SINK
-	if (enable_airplay){
-		LOG_INFO("Stopping AirPlay sink");		
-		raop_sink_deinit();
-	}
+    if (enable_airplay) {
+        LOG_INFO("Stopping AirPlay sink");
+        raop_sink_deinit();
+    }
 #endif
 
 #if CONFIG_CSPOT_SINK
-	if (enable_cspot){
-		LOG_INFO("Stopping CSpot sink");		
-		cspot_sink_deinit();
-	}
+    if (enable_cspot) {
+        LOG_INFO("Stopping CSpot sink");
+        cspot_sink_deinit();
+    }
 #endif
 }
 
 void decode_restore(int external) {
-	switch (external) {
-#if CONFIG_BT_SINK		
-	case DECODE_BT:
-		bt_disconnect();
-		break;
+    switch (external) {
+#if CONFIG_BT_SINK
+    case DECODE_BT:
+        bt_disconnect();
+        break;
 #endif
 #if CONFIG_AIRPLAY_SINK
-	case DECODE_RAOP:
-		raop_disconnect();
-		break;
+    case DECODE_RAOP:
+        raop_disconnect();
+        break;
 #endif
 #if CONFIG_CSPOT_SINK
-	case DECODE_CSPOT:
-		cspot_disconnect();
-		break;
-#endif			
-	}
+    case DECODE_CSPOT:
+        cspot_disconnect();
+        break;
+#endif
+    }
 }

+ 6 - 3
components/squeezelite/embedded.c

@@ -17,13 +17,13 @@
 #include "esp_wifi.h"
 #include "esp_log.h"
 #include "monitor.h"
-#include "Configurator.h"
+#include "Config.h"
 #include "messaging.h"
 #include "gpio_exp.h"
 #include "accessors.h"
 
 static const char TAG[] = "embedded";
-static sys_GPIO * power=NULL;
+static sys_gpio_config * power=NULL;
 
 extern void sb_controls_init(void);
 extern bool sb_displayer_init(void);
@@ -69,7 +69,9 @@ uint32_t _gettime_ms_(void) {
 
 int embedded_init(void) {
 	mutex_create(slimp_mutex);
+	ESP_LOGI(TAG,"Initializing controls");
 	sb_controls_init();
+	ESP_LOGI(TAG,"Initializing displayer");
 	custom_player_id = sb_displayer_init() ? 100 : 101;
 
 	
@@ -88,10 +90,11 @@ void embedded_exit(int code) {
 }    
 
 void powering(bool on) {
-    if (power->pin != -1) {
+    if (SYS_GPIOS_NAME(power,power) && power->pin != -1) {
         ESP_LOGI(TAG, "powering player %s", on ? "ON" : "OFF");	
         gpio_set_level_x(power->pin, on ? power->level : !power->level);
     }
+
 }
 
 u16_t get_RSSI(void) {

+ 10 - 11
components/squeezelite/equalizer.c

@@ -9,7 +9,7 @@
  */
 
 #include "math.h"
-#include "Configurator.h"
+#include "Config.h"
 #include "squeezelite.h"
 #include "equalizer.h"
 #include "esp_equalizer.h"
@@ -24,7 +24,7 @@ static EXT_RAM_ATTR struct {
     float volume;
 	float loudness_gain[EQ_BANDS];
 	bool update;
-    sys_Equalizer *state; 
+    sys_equalizer_config *state; 
 } equalizer;
 
 
@@ -83,19 +83,18 @@ static void calculate_loudness(void) {
  * initialize equalizer
  */
 void equalizer_init(void) {
-    sys_Services * services;
-    sys_Equalizer blank_eq = sys_Equalizer_init_default;
-
+    sys_services_config * services;
+    sys_equalizer_config blank_eq = sys_equalizer_config_init_default;
     equalizer.state = &sys_state->equalizer;
     if(!sys_state->has_equalizer ){
         sys_state->has_equalizer = true;
-        if(SYS_SERVICES(services) && services->has_equalizer){
-            memcpy(equalizer.state,&services->equalizer,sizeof(sys_Equalizer));
+        if(sys_services_config(services) && services->has_equalizer){
+            memcpy(equalizer.state,&services->equalizer,sizeof(sys_equalizer_config));
         }
         else {
-            memcpy(equalizer.state,&blank_eq,sizeof(sys_Equalizer)); 
+            memcpy(equalizer.state,&blank_eq,sizeof(sys_equalizer_config)); 
         }
-        configurator_raise_state_changed();
+        config_raise_state_changed();
     }
 }
 
@@ -160,7 +159,7 @@ void equalizer_set_gain(int8_t *gain) {
     // update only if something changed
     if (!memcmp(&equalizer.state->gains, gain, EQ_BANDS)) {
         equalizer.update = true;
-        configurator_raise_state_changed();
+        config_raise_state_changed();
     }
 
     LOG_INFO("equalizer gain %s", config);
@@ -178,7 +177,7 @@ void equalizer_set_loudness(uint8_t loudness) {
    // update loudness gains as a factor of loudness and volume
     if (equalizer.state->loudness != loudness / 10.0) {
         equalizer.state->loudness = loudness / 10.0;
-        configurator_raise_state_changed();
+        config_raise_state_changed();
         calculate_loudness();
         equalizer.update = true;
     }

+ 87 - 64
components/squeezelite/esp32_main.c

@@ -1,34 +1,20 @@
 /*
- *  Squeezelite - lightweight headless squeezebox emulator
+ *  Squeezelite for esp32
  *
- *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
- *      Ralph Irving 2015-2017, ralph_irving@hotmail.com
+ *  (c) Sebastien 2024
+ *      Philippe G. 2024, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * Additions (c) Paul Hermann, 2015-2017 under the same license terms
- *   -Control of Raspberry pi GPIO for amplifier power
- *   -Launch script on power status change from LMS
  */
-
+#include "Config.h"
 #include "squeezelite.h"
 #include <signal.h>
-#include "Configurator.h"
 
 extern bool user_rates;
 static unsigned int rates[MAX_SUPPORTED_SAMPLERATES] = {0};
-sys_Squeezelite* config;
+sys_squeezelite_config* config;
 log_level loglevel = lDEBUG;
 static void sighandler(int signum) {
     slimproto_stop();
@@ -38,8 +24,8 @@ static void sighandler(int signum) {
 }
 
 unsigned int* get_rates() {
-    unsigned int ref[]  TEST_RATES;
-    sys_RatesOption* ratescfg = &config->rates;
+    unsigned int ref[] TEST_RATES;
+    sys_squeezelite_rates_opt* ratescfg = &config->rates;
     if (!config->has_rates || ((ratescfg->list_count == 0 || ratescfg->list[0] == 0) &&
                                   ratescfg->min == 0 && ratescfg->max == 0)) {
         user_rates = false;
@@ -71,73 +57,105 @@ unsigned int* get_rates() {
     user_rates = true;
     return rates;
 }
-log_level log_level_from_sys_level(sys_DebugLevelEnum level) {
+log_level log_level_from_sys_level(sys_squeezelite_debug_levels level) {
     switch (level) {
-    case sys_DebugLevelEnum_DEFAULT:
+    case sys_squeezelite_debug_levels_DEFAULT:
         return lWARN;
         break;
-    case sys_DebugLevelEnum_INFO:
+    case sys_squeezelite_debug_levels_INFO:
         return lINFO;
         break;
-    case sys_DebugLevelEnum_ERROR:
+    case sys_squeezelite_debug_levels_ERROR:
         return lERROR;
         break;
-    case sys_DebugLevelEnum_WARN:
+    case sys_squeezelite_debug_levels_WARN:
         return lWARN;
         break;
-    case sys_DebugLevelEnum_DEBUG:
+    case sys_squeezelite_debug_levels_DEBUG:
         return lDEBUG;
         break;
-    case sys_DebugLevelEnum_SDEBUG:
+    case sys_squeezelite_debug_levels_SDEBUG:
         return lSDEBUG;
         break;
     default:
         return lWARN;
     }
 }
-void build_codec_string(sys_CodexEnum* list, size_t count, char* buffer, size_t buf_size) {
-    const char* prefix = STR(sys_CodexEnum) "_c_";
+void build_codec_string(sys_squeezelite_codecs* list, size_t count, char* buffer, size_t buf_size) {
+    const char* prefix = STR(sys_squeezelite_codecs) "_c_";
     const char* name = NULL;
     for (int i = 0; i < count; i++) {
         if (i > 0) {
             strncat(buffer, ", ", buf_size);
         }
-        name = sys_CodexEnum_name(list[i]) + strlen(prefix);
+        name = sys_squeezelite_codecs_name(list[i]) + strlen(prefix);
         LOG_INFO("Found codec: %s ", name);
         strncat(buffer, name, buf_size);
     }
     LOG_INFO("Codec list: %s ", buffer);
 }
+
+sys_squeezelite_config* get_profile(const char* name) {
+    const char* resolved_name = name && strlen(name) > 0 ? name : platform->services.current_profile;
+    LOG_DEBUG("get_profile called with name: %s, resolved to: %s", 
+              name ? name : "NULL", 
+              resolved_name ? resolved_name : "NULL or EMPTY");
+
+    if (!platform->has_services || platform->services.squeezelite_profiles_count <= 0) {
+        LOG_ERROR("No squeezelite profiles available");
+        return NULL;
+    }
+
+    if (!resolved_name || strlen(resolved_name) == 0) {
+        LOG_WARN("No specific profile requested, using the first available profile");
+        return &platform->services.squeezelite_profiles[0].profile;
+    }
+
+    for (int i = 0; i < platform->services.squeezelite_profiles_count; i++) {
+        LOG_DEBUG("Checking profile: %s", platform->services.squeezelite_profiles[i].name);
+        if (strcmp(platform->services.squeezelite_profiles[i].name, resolved_name) == 0) {
+            LOG_DEBUG("Profile matched: %s", platform->services.squeezelite_profiles[i].name);
+            return &platform->services.squeezelite_profiles[i].profile;
+        }
+    }
+    if(platform->services.squeezelite_profiles[0].name && strlen(platform->services.squeezelite_profiles[0].name)>0){
+        LOG_WARN("Could not find profile %s. Falling back to %s", resolved_name,platform->services.squeezelite_profiles[0].name);
+        return &platform->services.squeezelite_profiles[0].profile;
+    }
+
+    LOG_ERROR("Profile '%s' not found", resolved_name);
+    return NULL;
+}
+
 int squeezelite_main_start() {
     u8_t mac[6];
     unsigned output_buf_size = 0;
+    LOG_INFO("Starting squeezelite");
     char include_codecs[101] = {0};
     char exclude_codecs[101] = {0};
-    config = platform->has_services && platform->services.has_squeezelite
-                 ? &platform->services.squeezelite
-                 : NULL;
+    config = get_profile(NULL); // get the active profile
     if (!config) {
         LOG_ERROR("Squeezelite not configured");
-        return -1;
+        return -2;
     }
-
+    LOG_INFO("Running embedded init");
     int err = embedded_init();
     if (err) return err;
     get_mac(mac);
-    unsigned int * rates = get_rates();
-
-	signal(SIGINT, sighandler);
-	signal(SIGTERM, sighandler);
-    #if defined(SIGQUIT)
-    	signal(SIGQUIT, sighandler);
-    #endif
-    #if defined(SIGHUP)
-    	signal(SIGHUP, sighandler);
-    #endif
-    output_buf_size = config->buffers.output;
-
-    // set the output buffer size if not specified on the command line, take account of resampling
-    if (!output_buf_size) {
+    unsigned int* rates = get_rates();
+
+    signal(SIGINT, sighandler);
+    signal(SIGTERM, sighandler);
+#if defined(SIGQUIT)
+    signal(SIGQUIT, sighandler);
+#endif
+#if defined(SIGHUP)
+    signal(SIGHUP, sighandler);
+#endif
+    output_buf_size = config->buffers.output*1024;
+
+    // set the output buffer size if not specified in the parameters, take account of resampling
+    if (output_buf_size == 0) {
         output_buf_size = OUTPUTBUF_SIZE;
         if (strlen(config->resample) > 0) {
             unsigned scale = 8;
@@ -151,32 +169,37 @@ int squeezelite_main_start() {
     }
     build_codec_string(config->excluded_codex, config->excluded_codex_count, exclude_codecs,
         sizeof(exclude_codecs));
-    build_codec_string(
-        config->included_codex, config->included_codex, include_codecs, sizeof(include_codecs));
+    build_codec_string(config->included_codex, config->included_codex_count, include_codecs,
+        sizeof(include_codecs));
 
-    unsigned int stream_buf_size =
-        config->buffers.stream > 0 ? config->buffers.stream : STREAMBUF_SIZE;
-    stream_init(
-        log_level_from_sys_level(platform->services.squeezelite.log.stream), stream_buf_size);
+    unsigned int stream_buf_size =STREAMBUF_SIZE;
+    if(config->buffers.stream > 0){
+        stream_buf_size = config->buffers.stream *1024;
+        LOG_DEBUG("Found stream buffer configuration: %d (%d)",config->buffers.stream,stream_buf_size);
+    }
+    else {
+        LOG_DEBUG("Using default stream buffer: %d",stream_buf_size);
+    }
+    stream_init(log_level_from_sys_level(config->log.stream), stream_buf_size);
     output_init_embedded();
-    decode_init(log_level_from_sys_level(platform->services.squeezelite.log.decode), include_codecs,
-        exclude_codecs);
+    decode_init(log_level_from_sys_level(config->log.decode),
+        strlen(include_codecs) > 0 ? include_codecs : NULL, exclude_codecs);
 
 #if RESAMPLE || RESAMPLE16
-    if (strlen(config->resample) > 0) {
+    if (config->resample && strlen(config->resample) > 0) {
         process_init(config->resample);
     }
 #endif
 
     if (!config->enabled) {
-        LOG_ERROR("LMS is disabled");
+        LOG_WARN("LMS is disabled");
         while (1)
             sleep(3600);
     }
     char* name = strlen(platform->names.squeezelite) > 0 ? platform->names.squeezelite
                                                          : platform->names.device;
-    slimproto(log_level_from_sys_level(platform->services.squeezelite.log.slimproto),
-        config->server_name_ip, mac, name, NULL, NULL, config->max_rate);
+    slimproto(log_level_from_sys_level(config->log.slimproto), config->server_name_ip, mac, name,
+        NULL, NULL, config->max_rate);
 
     decode_close();
     stream_close();

+ 145 - 136
components/squeezelite/external/dac_external.c

@@ -1,4 +1,4 @@
-/* 
+/*
  *  Squeezelite for esp32
  *
  *  (c) Sebastien 2019
@@ -8,18 +8,19 @@
  *  https://opensource.org/licenses/MIT
  *
  */
- 
-#include <freertos/FreeRTOS.h>
-#include <freertos/task.h>
-#include <driver/i2s.h>
+#define LOG_LOCAL_LEVEL ESP_LOG_INFO
+#include "Config.h"
+#include "adac.h"
+#include "cJSON.h"
 #include "driver/i2c.h"
 #include "esp_log.h"
+#include "esp_check.h"
 #include "gpio_exp.h"
-#include "cJSON.h"
+#include "inttypes.h"
 #include "string.h"
-// #include "Configurator.h"
-#pragma message("fixme: look for TODO below")
-#include "adac.h"
+#include <driver/i2s.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
 
 static const char TAG[] = "DAC external";
 
@@ -27,159 +28,167 @@ static void speaker(bool active);
 static void headset(bool active);
 static bool volume(unsigned left, unsigned right) { return false; }
 static void power(adac_power_e mode);
-static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck);
+static bool init(sys_dac_config* config, i2s_config_t* i2s_config, bool* mck);
 
-static bool i2c_json_execute(char *set);
+// static bool i2c_json_execute(char *set);
+static bool i2c_execute_cmd(sys_dac_control_type cmd_type);
 
-const struct adac_s dac_external = { sys_DACModelEnum_I2S, init, adac_deinit, power, speaker, headset, volume };
-static cJSON *i2c_json;
+const struct adac_s dac_external = {sys_dac_models_I2S, init, adac_deinit, power, speaker, headset, volume};
 static int i2c_addr;
-
-static const struct {
-	char *model;
-	bool mclk;
-	char *controlset;
-} codecs[] = {
-	{ "es8388", true,
-		"{\"init\":[ 																						\
-			{\"reg\":8,\"val\":0}, {\"reg\":2,\"val\":243}, {\"reg\":43,\"val\":128}, {\"reg\":0,\"val\":5}, 		\
-			{\"reg\":1,\"val\":64}, {\"reg\":4,\"val\":60},"
-#if BYTES_PER_FRAME == 8
-		   "{\"reg\":23,\"val\":32},"
-#else			
-		   "{\"reg\":23,\"val\":24},"
-#endif
-		   "{\"reg\":24,\"val\":2},		\
-			{\"reg\":26,\"val\":0}, {\"reg\":27,\"val\":0}, {\"reg\":25,\"val\":50}, {\"reg\":38,\"val\":0},		\
-			{\"reg\":39,\"val\":184}, {\"reg\":42,\"val\":184}, {\"reg\":46,\"val\":30}, {\"reg\":47,\"val\":30},	\
-			{\"reg\":48,\"val\":30}, {\"reg\":49,\"val\":30}, {\"reg\":2,\"val\":170}]}" },
-	{ NULL, false, NULL }		
-};
+extern sys_dac_default_sets* default_dac_sets;
+static EXT_RAM_ATTR sys_dac_control_set* i2c_default_controlset = NULL;
+static EXT_RAM_ATTR sys_dac_control_set* i2c_controlset = NULL;
 
 /****************************************************************************************
  * init
  */
-static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck) {	 
-	char *p=NULL;	
-	void * dummy = &codecs;
-	// i2c_addr = adac_init(config, i2c_port_num);
-	// if (!i2c_addr) return true;
-	
-	// ESP_LOGI(TAG, "DAC on I2C @%d", i2c_addr);
-	
-	// p = config_alloc_get_str("dac_controlset", CONFIG_DAC_CONTROLSET, NULL);
-
-	// if ((!p || !*p) && (p = strcasestr(config, "model")) != NULL) {
-	// 	char model[32] = "";
-	// 	int i;
-	// 	sscanf(p, "%*[^=]=%31[^,]", model);
-	// 	for (i = 0; *model && ((p = codecs[i].controlset) != NULL) && strcasecmp(codecs[i].model, model); i++);
-	//  	if (p) *mck = codecs[i].mclk;
-	//  }	 
-
-	// i2c_json = cJSON_Parse(p);
-	
-	// if (!i2c_json) {
-	// 	ESP_LOGW(TAG, "no i2c controlset found");
-	// 	return true;
-	// }	
-		
-	// if (!i2c_json_execute("init")) {	
-	// 	ESP_LOGE(TAG, "could not intialize DAC");
-	// 	return false;
-	// }	
-	// TODO: Add support for the commented code
-
-	return true;
-}	
+static bool init(sys_dac_config* config, i2s_config_t* i2s_config, bool* mck) {
+
+#ifdef BYTES_PER_FRAME
+    uint32_t bytes_per_frame = BYTES_PER_FRAME;
+#else
+    uint32_t bytes_per_frame = 4;
+#endif
+    i2c_addr = adac_init(config);
+    if (!i2c_addr) return true;
+    ESP_LOGI(TAG, "DAC on I2C @%d", i2c_addr);
+    ESP_LOGD(TAG, "Checkinf if there's a default control set for DAC %s in the list of %d presets", sys_dac_models_name(config->model),
+        default_dac_sets->sets_count);
+
+    for (int i = 0; i < default_dac_sets->sets_count; i++) {
+        ESP_LOGD(TAG, "Checkinf if preset #%d (%s[%d]) matches %s[%d]", i + 1, sys_dac_models_name(default_dac_sets->sets[i].model),
+            default_dac_sets->sets[i].bytes_per_frame, sys_dac_models_name(config->model), bytes_per_frame);
+        if (default_dac_sets->sets[i].bytes_per_frame == bytes_per_frame && default_dac_sets->sets[i].model == config->model) {
+            if (!default_dac_sets->sets[i].valid) {
+                ESP_LOGE(TAG, "DAC %s is unsupported with %d bytes per frame", sys_dac_models_name(config->model), bytes_per_frame);
+                return false;
+            }
+            i2c_default_controlset = &default_dac_sets->sets[i].set;
+        }
+    }
+
+    if (config->has_daccontrolset && config->daccontrolset.commands_count > 0) {
+        i2c_controlset = &config->daccontrolset;
+    }
+    if (!i2c_controlset && !i2c_default_controlset) {
+        ESP_LOGE(TAG, "DAC configuration invalid for %s", sys_dac_models_name(config->model));
+        return false;
+    }
+    if (mck) {
+        *mck = i2c_controlset != NULL ? i2c_controlset->mclk_needed : i2c_default_controlset->mclk_needed;
+        ESP_LOGD(TAG, "Master clock is %s", (*mck) ? "NEEDED" : "NOT NEEDED");
+    }
+    if (!i2c_execute_cmd(sys_dac_control_type_INIT)) {
+        ESP_LOGE(TAG, "could not intialize DAC");
+        return false;
+    }
+
+    return true;
+}
 
 /****************************************************************************************
  * power
  */
 static void power(adac_power_e mode) {
-	if (mode == ADAC_STANDBY || mode == ADAC_OFF) i2c_json_execute("poweroff");
-	else i2c_json_execute("poweron");
+    if (mode == ADAC_STANDBY || mode == ADAC_OFF)
+        i2c_execute_cmd(sys_dac_control_type_POWER_OFF);
+    else
+        i2c_execute_cmd(sys_dac_control_type_POWER_ON);
 }
 
 /****************************************************************************************
  * speaker
  */
 static void speaker(bool active) {
-	if (active) i2c_json_execute("speakeron");
-	else i2c_json_execute("speakeroff");
-} 
+    if (active)
+        i2c_execute_cmd(sys_dac_control_type_SPEAKER_ON);
+    else
+        i2c_execute_cmd(sys_dac_control_type_SPEAKER_OFF);
+}
 
 /****************************************************************************************
  * headset
  */
 static void headset(bool active) {
-	if (active) i2c_json_execute("headseton");
-	else i2c_json_execute("headsetoff");
+    if (active)
+        i2c_execute_cmd(sys_dac_control_type_HEADSET_ON);
+    else
+        i2c_execute_cmd(sys_dac_control_type_HEADSET_OFF);
 }
 
 /****************************************************************************************
- * 
+ *
  */
-bool i2c_json_execute(char *set) {
-	cJSON *json_set = cJSON_GetObjectItemCaseSensitive(i2c_json, set);
-	cJSON *item;
-
-	if (!json_set) return true;
-	
-	cJSON_ArrayForEach(item, json_set) {
-        cJSON *action;
-        
-        // is this a delay
-        if ((action = cJSON_GetObjectItemCaseSensitive(item, "delay")) != NULL) {
-            vTaskDelay(pdMS_TO_TICKS(action->valueint));
-            ESP_LOGI(TAG, "DAC waiting %d ms", action->valueint);
-            continue;
+static const sys_dac_control_itm* i2c_get_controlset_cmd(sys_dac_control_set* set, sys_dac_control_type cmd_type, pb_size_t* items_count) {
+    ESP_RETURN_ON_FALSE(set!=NULL,NULL,TAG,"Invalid set");
+    ESP_RETURN_ON_FALSE(items_count!=NULL,NULL,TAG,"Invalid items count");
+    ESP_LOGD(TAG,"Looking for command %s in control from a list of %d commands",sys_dac_control_type_name(cmd_type),set->commands_count);
+    *items_count = 0;
+    for (int i = 0; i < set->commands_count; i++) {
+        if (set->commands[i].type == cmd_type) {
+            *items_count = set->commands[i].items_count;
+            return set->commands[i].items;
         }
-        
-        // is this a gpio toggle
-        if ((action = cJSON_GetObjectItemCaseSensitive(item, "gpio")) != NULL) {
-            cJSON *level = cJSON_GetObjectItemCaseSensitive(item, "level");
-            ESP_LOGI(TAG, "set GPIO %d at %d", action->valueint, level->valueint);
-            gpio_set_direction_x(action->valueint, GPIO_MODE_OUTPUT);
-            gpio_set_level_x(action->valueint, level->valueint);
-            continue;
+    }
+    ESP_LOGD(TAG,"No control set found for command %s",sys_dac_control_type_name(cmd_type));
+    return NULL;
+}
+
+/****************************************************************************************
+ *
+ */
+bool i2c_execute_cmd(sys_dac_control_type cmd_type) {
+    pb_size_t items_count;
+    const sys_dac_control_itm* cmd = NULL;
+    esp_err_t err = ESP_OK;
+    ESP_LOGD(TAG, "Getting control set for command %s", sys_dac_control_type_name(cmd_type));
+    if(i2c_controlset){
+        cmd = i2c_get_controlset_cmd(i2c_controlset, cmd_type, &items_count);
+    }
+    if (!cmd || items_count == 0) {
+        ESP_LOGD(TAG, "Not found. Checking if defaults are known %s", sys_dac_control_type_name(cmd_type));
+        cmd = i2c_get_controlset_cmd(i2c_default_controlset, cmd_type, &items_count);
+    }
+    if (!cmd || items_count == 0) {
+        ESP_LOGD(TAG, "No commands for %s", sys_dac_control_type_name(cmd_type));
+        return true;
+    }
+    for (pb_size_t i = 0; i < items_count && err==ESP_OK; i++) {
+        switch (cmd->which_item_type) {
+        case sys_dac_control_itm_reg_action_tag:
+            ESP_LOGD(TAG, "Setting reg %d mode %s value %d ", cmd->item_type.reg_action.reg,
+                sys_dac_control_mode_name(cmd->item_type.reg_action.mode), cmd->item_type.reg_action.val);
+            if (cmd->item_type.reg_action.mode == sys_dac_control_mode_NOTHING) {
+                err = adac_write_byte(i2c_addr, cmd->item_type.reg_action.reg, cmd->item_type.reg_action.val);
+            } else if (cmd->item_type.reg_action.mode == sys_dac_control_mode_OR) {
+                uint8_t data = adac_read_byte(i2c_addr, cmd->item_type.reg_action.reg);
+                data |= (uint8_t)cmd->item_type.reg_action.val;
+                err = adac_write_byte(i2c_addr, cmd->item_type.reg_action.reg, data);
+            } else if (cmd->item_type.reg_action.mode == sys_dac_control_mode_AND) {
+                uint8_t data = adac_read_byte(i2c_addr, cmd->item_type.reg_action.reg);
+                data &= (uint8_t)cmd->item_type.reg_action.val;
+                err = adac_write_byte(i2c_addr, cmd->item_type.reg_action.reg, data);
+            }
+
+            break;
+        case sys_dac_control_itm_regs_action_tag:
+            ESP_LOGD(TAG, "Setting reg %d with %d byte(s)", cmd->item_type.regs_action.reg, cmd->item_type.regs_action.vals_count);
+            err = adac_write(i2c_addr, cmd->item_type.regs_action.reg, cmd->item_type.regs_action.vals, cmd->item_type.regs_action.vals_count);
+            break;
+        case sys_dac_control_itm_gpio_action_tag:
+            ESP_LOGI(TAG, "set GPIO %d at %s", cmd->item_type.gpio_action.gpio, sys_dac_control_lvl_name(cmd->item_type.gpio_action.level));
+            err = gpio_set_direction_x(cmd->item_type.gpio_action.gpio, GPIO_MODE_OUTPUT);
+            if(err == ESP_OK) err = gpio_set_level_x(cmd->item_type.gpio_action.gpio, cmd->item_type.gpio_action.level - sys_dac_control_lvl_LV_0);
+            break;
+        case sys_dac_control_itm_delay_action_tag:
+            vTaskDelay(pdMS_TO_TICKS(cmd->item_type.delay_action.delay));
+            ESP_LOGI(TAG, "DAC waiting %" PRIu64 " ms", cmd->item_type.delay_action.delay);
+            break;
+
+        default:
+            break;
         }
-                    
-		action= cJSON_GetObjectItemCaseSensitive(item, "reg");
-		cJSON *val = cJSON_GetObjectItemCaseSensitive(item, "val");
-		
-        // this is gpio register setting or crap
-		if (cJSON_IsArray(val)) {
-			cJSON *value;			
-			uint8_t *data = malloc(cJSON_GetArraySize(val));
-			int count = 0;
-			
-			if (!data) continue;
-			
-			cJSON_ArrayForEach(value, val) {
-				data[count++] = value->valueint;		
-			}
-			
-			adac_write(i2c_addr, action->valueint, data, count);
-			free(data);			
-		} else {
-			cJSON *mode = cJSON_GetObjectItemCaseSensitive(item, "mode");
-
-			if (!action || !val) continue;
-
-			if (!mode) {
-				adac_write_byte(i2c_addr, action->valueint, val->valueint);
-			} else if (!strcasecmp(mode->valuestring, "or")) {
-				uint8_t data = adac_read_byte(i2c_addr, action->valueint);
-				data |= (uint8_t) val->valueint;
-				adac_write_byte(i2c_addr, action->valueint, data);
-			} else if (!strcasecmp(mode->valuestring, "and")) {
-				uint8_t data = adac_read_byte(i2c_addr, action->valueint);
-				data &= (uint8_t) val->valueint;
-				adac_write_byte(i2c_addr, action->valueint, data);
-			}
-		}
-	}
-	
-	return true;
-}	
+    }
+
+    return (err==ESP_OK);
+}

+ 6 - 4
components/squeezelite/output_bt.c

@@ -16,9 +16,10 @@
 #include "perf_trace.h"
 #include "services.h"
 #include "led.h"
-#include "Configurator.h"
-extern log_level log_level_from_sys_level(sys_DebugLevelEnum level);
-static sys_Squeezelite * config = NULL;
+#include "Config.h"
+extern log_level log_level_from_sys_level(sys_squeezelite_debug_levels level);
+extern sys_squeezelite_config* get_profile(const char* name);
+static sys_squeezelite_config * config = NULL;
 extern struct outputstate output;
 extern struct buffer *outputbuf;
 extern struct buffer *streambuf;
@@ -76,7 +77,8 @@ static uint32_t bt_idle_callback(void) {
  * Init BT sink
  */    
 void output_init_bt() {
-	config = &platform->services.squeezelite;
+	config = get_profile(NULL); // get the active profile
+	if(!config) return;
 	loglevel = log_level_from_sys_level(config->log.output);
 	bt_idle_since = pdTICKS_TO_MS(xTaskGetTickCount());
     services_sleep_setsleeper(bt_idle_callback);

+ 19 - 11
components/squeezelite/output_embedded.c

@@ -10,9 +10,10 @@
  */
 #include "squeezelite.h"
 #include "equalizer.h"
-#include "Configurator.h"
-extern log_level log_level_from_sys_level(sys_DebugLevelEnum level);
-static sys_Squeezelite* config = NULL;
+#include "Config.h"
+extern log_level log_level_from_sys_level(sys_squeezelite_debug_levels level);
+extern sys_squeezelite_config* get_profile(const char* name);
+static sys_squeezelite_config* config = NULL;
 
 
 extern unsigned int* get_rates() ;
@@ -76,9 +77,9 @@ static bool handler(u8_t* data, int len) {
 }
 
 void output_init_embedded() {
-    config = &platform->services.squeezelite;
+    config = get_profile(NULL); // get the active profile
     loglevel = log_level_from_sys_level(config->log.output);
-    LOG_INFO("init device: %s", sys_OutputTypeEnum_name(config->output_type));
+    LOG_INFO("init device: %s", sys_squeezelite_outputs_name(config->output_type));
 
     // chain handlers
     slimp_handler_chain = slimp_handler;
@@ -87,15 +88,22 @@ void output_init_embedded() {
     // init equalizer before backends
     equalizer_init();
     memset(&output, 0, sizeof(output));
-
-    output_init_common(loglevel, sys_OutputTypeEnum_name(config->output_type),
-        config->buffers.output, get_rates(), config->amp_gpio_timeout);
+    unsigned int output_buffer = OUTPUTBUF_SIZE;
+    if(config->buffers.output > 0){
+        output_buffer = config->buffers.output *1024;
+        LOG_DEBUG("Found output buffer configuration: %d (%d)",config->buffers.output,output_buffer);
+    }
+    else {
+        LOG_DEBUG("Using default output buffer: %d",output_buffer);
+    }    
+    output_init_common(loglevel, sys_squeezelite_outputs_name(config->output_type),
+        output_buffer, get_rates(), config->amp_gpio_timeout);
     output.start_frames = FRAME_BLOCK;
     #pragma message("Rate delay logic incomplete")
 	output.rate_delay = 0;
 
 #if CONFIG_BT_SINK
-    if (config->output_type == sys_OutputTypeEnum_OUTPUT_Bluetooth) {
+    if (config->output_type == sys_squeezelite_outputs_BT) {
         LOG_INFO("init Bluetooth");
         close_cb = &output_close_bt;
         output_init_bt(get_rates());
@@ -132,7 +140,7 @@ void set_volume(unsigned left, unsigned right) {
 
 bool test_open(const char* device, unsigned rates[], bool userdef_rates) {
     memset(rates, 0, MAX_SUPPORTED_SAMPLERATES * sizeof(unsigned));
-    if (config->output_type == sys_OutputTypeEnum_OUTPUT_I2S) {
+    if (config->output_type == sys_squeezelite_outputs_I2S) {
         unsigned _rates[] = {
 #if BYTES_PER_FRAME == 4
             192000,
@@ -152,7 +160,7 @@ bool test_open(const char* device, unsigned rates[], bool userdef_rates) {
             0
         };
         memcpy(rates, _rates, sizeof(_rates));
-    } else if (config->output_type == sys_OutputTypeEnum_OUTPUT_SPDIF) {
+    } else if (config->output_type == sys_squeezelite_outputs_SPDIF) {
         unsigned _rates[] = {
             96000, 88200, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 0};
         memcpy(rates, _rates, sizeof(_rates));

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff