Clang format changes for more readability

This commit is contained in:
2026-03-03 14:11:55 -05:00
parent cdbabe0e58
commit cfe7332899
8 changed files with 554 additions and 117 deletions

274
.clang-format Normal file
View File

@@ -0,0 +1,274 @@
---
Language: Cpp
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseArrows: false
AlignCaseColons: false
AlignConsecutiveTableGenBreakingDAGArgColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveTableGenCondOperatorColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveTableGenDefinitionColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseExpressionOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false
BreakAfterReturnType: None
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Allman
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakFunctionDefinitionParameters: false
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
BreakTemplateDeclarations: MultiLine
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: false
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLines:
AtEndOfFile: false
AtStartOfBlock: true
AtStartOfFile: true
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MainIncludeChar: Quote
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakScopeResolution: 500
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SkipMacroDefinitionBody: false
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterPlacementOperator: true
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: true
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
ExceptDoubleParentheses: false
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TableGenBreakInsideDAGArg: DontBreak
TabWidth: 8
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
...

View File

@@ -1,3 +1 @@
# Set this to your ESP32's IP address for local development
# Example: VITE_API_BASE=http://192.168.1.100
VITE_API_BASE=http://192.168.50.216
VITE_API_BASE=http://192.168.50.43

View File

@@ -11,7 +11,8 @@
#include "appstate.hpp"
#include "types.hpp"
internal esp_err_t api_system_info_handler(httpd_req_t *req) {
internal esp_err_t api_system_info_handler(httpd_req_t *req)
{
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
@@ -21,13 +22,20 @@ internal esp_err_t api_system_info_handler(httpd_req_t *req) {
esp_chip_info(&chip_info);
const char *model = "ESP32-S3";
if (chip_info.model == CHIP_ESP32) {
if (chip_info.model == CHIP_ESP32)
{
model = "ESP32";
} else if (chip_info.model == CHIP_ESP32S2) {
}
else if (chip_info.model == CHIP_ESP32S2)
{
model = "ESP32-S2";
} else if (chip_info.model == CHIP_ESP32S3) {
}
else if (chip_info.model == CHIP_ESP32S3)
{
model = "ESP32-S3";
} else if (chip_info.model == CHIP_ESP32C3) {
}
else if (chip_info.model == CHIP_ESP32C3)
{
model = "ESP32-C3";
}
@@ -43,9 +51,12 @@ internal esp_err_t api_system_info_handler(httpd_req_t *req) {
cJSON_AddStringToObject(root, "firmware", app_desc->version);
const char *conn_type = "offline";
if (g_Ethernet_Initialized) {
if (g_Ethernet_Initialized)
{
conn_type = "ethernet";
} else if (g_Wifi_Initialized) {
}
else if (g_Wifi_Initialized)
{
conn_type = "wifi";
}
cJSON_AddStringToObject(root, "connection", conn_type);

View File

@@ -7,7 +7,8 @@
internal void restart_timer_callback(void *arg) { esp_restart(); }
internal esp_err_t api_system_reboot_handler(httpd_req_t *req) {
internal esp_err_t api_system_reboot_handler(httpd_req_t *req)
{
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");

View File

@@ -8,10 +8,11 @@
#include "esp_eth_driver.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "ethernet_init.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/timers.h"
// Project includes
#include "types.hpp"
@@ -41,10 +42,13 @@ internal bool s_ethernet_connected = false;
#endif
void ethernet_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) {
if (event_base == IP_EVENT && event_id == IP_EVENT_ETH_GOT_IP) {
int32_t event_id, void *event_data)
{
if (event_base == IP_EVENT && event_id == IP_EVENT_ETH_GOT_IP)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
if (event->esp_netif != s_eth_netif) {
if (event->esp_netif != s_eth_netif)
{
return;
}
ESP_LOGI(kLogEthernet, "Got IPv4 event: Interface \"%s\" address: " IPSTR,
@@ -52,31 +56,39 @@ void ethernet_event_handler(void *arg, esp_event_base_t event_base,
s_current_ip_info = event->ip_info;
xSemaphoreGive(s_semph_get_ip_addrs);
} else if (event_base == ETH_EVENT && event_id == ETHERNET_EVENT_CONNECTED) {
}
else if (event_base == ETH_EVENT && event_id == ETHERNET_EVENT_CONNECTED)
{
ESP_LOGI(kLogEthernet, "Ethernet Link Up");
s_eth_link_up = true;
if (s_semph_eth_link) {
if (s_semph_eth_link)
{
xSemaphoreGive(s_semph_eth_link);
}
} else if (event_base == ETH_EVENT &&
event_id == ETHERNET_EVENT_DISCONNECTED) {
}
else if (event_base == ETH_EVENT && event_id == ETHERNET_EVENT_DISCONNECTED)
{
ESP_LOGI(kLogEthernet, "Ethernet Link Down");
s_eth_link_up = false;
}
}
void teardown_ethernet() {
if (s_semph_eth_link != nullptr) {
void teardown_ethernet()
{
if (s_semph_eth_link != nullptr)
{
vSemaphoreDelete(s_semph_eth_link);
s_semph_eth_link = nullptr;
}
if (s_semph_get_ip_addrs != nullptr) {
if (s_semph_get_ip_addrs != nullptr)
{
vSemaphoreDelete(s_semph_get_ip_addrs);
s_semph_get_ip_addrs = nullptr;
}
if (s_eth_netif) {
if (s_eth_netif)
{
ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP,
&ethernet_event_handler));
@@ -96,7 +108,8 @@ void teardown_ethernet() {
esp_netif_deinit();
}
internal esp_err_t connect_ethernet(bool blockUntilIPAcquired) {
internal esp_err_t connect_ethernet(bool blockUntilIPAcquired)
{
#ifndef NDEBUG
assert(!s_ethernet_connected &&
"Ethernet connect called but already connected!");
@@ -105,7 +118,8 @@ internal esp_err_t connect_ethernet(bool blockUntilIPAcquired) {
s_semph_get_ip_addrs = xSemaphoreCreateBinary();
s_semph_eth_link = xSemaphoreCreateBinary();
if (s_semph_get_ip_addrs == NULL || s_semph_eth_link == NULL) {
if (s_semph_get_ip_addrs == NULL || s_semph_eth_link == NULL)
{
return ESP_ERR_NO_MEM;
}
// Connection is split in two steps. First we open the connection and ask for
@@ -139,7 +153,8 @@ internal esp_err_t connect_ethernet(bool blockUntilIPAcquired) {
// Will teardown connection properly on shutdown
ESP_ERROR_CHECK(esp_register_shutdown_handler(&teardown_ethernet));
if (blockUntilIPAcquired) {
if (blockUntilIPAcquired)
{
ESP_LOGI(kLogEthernet, "Waiting for IP address.");
xSemaphoreTake(s_semph_get_ip_addrs, portMAX_DELAY);
blink_last_ip_octet();
@@ -148,7 +163,8 @@ internal esp_err_t connect_ethernet(bool blockUntilIPAcquired) {
return ESP_OK;
}
void disconnect_ethernet() {
void disconnect_ethernet()
{
#ifndef NDEBUG
assert(s_ethernet_connected &&
"Ethernet disconnect called but not connected!");
@@ -159,10 +175,13 @@ void disconnect_ethernet() {
ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&teardown_ethernet));
}
internal esp_err_t check_ethernet_connection(uint32_t timeoutSeconds) {
internal esp_err_t check_ethernet_connection(uint32_t timeoutSeconds)
{
// Wait up to 5000ms for the physical link to negotiate
if (!s_eth_link_up) {
if (!xSemaphoreTake(s_semph_eth_link, pdMS_TO_TICKS(5000))) {
if (!s_eth_link_up)
{
if (!xSemaphoreTake(s_semph_eth_link, pdMS_TO_TICKS(5000)))
{
ESP_LOGE(kLogEthernet,
"No physical Ethernet link detected. Skipping DHCP wait.");
return ESP_ERR_INVALID_STATE; // Special error to skip retries
@@ -172,10 +191,13 @@ internal esp_err_t check_ethernet_connection(uint32_t timeoutSeconds) {
ESP_LOGI(kLogEthernet, "Waiting for IP address for %d seconds.",
timeoutSeconds);
if (xSemaphoreTake(s_semph_get_ip_addrs,
pdMS_TO_TICKS(timeoutSeconds * 1000))) {
pdMS_TO_TICKS(timeoutSeconds * 1000)))
{
blink_last_ip_octet();
return ESP_OK;
} else {
}
else
{
return ESP_ERR_TIMEOUT;
}
}
@@ -187,25 +209,41 @@ internal esp_netif_t *s_wifi_netif = nullptr;
internal SemaphoreHandle_t s_semph_get_wifi_ip_addrs = nullptr;
internal SemaphoreHandle_t s_semph_wifi_link = nullptr;
internal volatile bool s_wifi_link_up = false;
internal TimerHandle_t s_wifi_reconnect_timer = nullptr;
#ifndef NDEBUG
internal bool s_wifi_connected = false;
#endif
// Forward declaration for timer callback
internal void wifi_reconnect_timer_cb(TimerHandle_t xTimer);
void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED)
{
ESP_LOGI(kLogWifi, "WiFi associated with AP.");
s_wifi_link_up = true;
if (s_semph_wifi_link) {
if (s_semph_wifi_link)
{
xSemaphoreGive(s_semph_wifi_link);
}
} else if (event_base == WIFI_EVENT &&
event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(kLogWifi, "WiFi disconnected.");
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
ESP_LOGI(kLogWifi, "WiFi disconnected. Scheduling reconnect...");
s_wifi_link_up = false;
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
if (s_wifi_reconnect_timer != nullptr)
{
xTimerStart(s_wifi_reconnect_timer, 0);
}
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
if (event->esp_netif != s_wifi_netif) {
if (event->esp_netif != s_wifi_netif)
{
return;
}
ESP_LOGI(kLogWifi, "Got IPv4 event: Interface \"%s\" address: " IPSTR,
@@ -216,13 +254,29 @@ void wifi_event_handler(void *arg, esp_event_base_t event_base,
}
}
void teardown_wifi() {
if (s_semph_wifi_link != nullptr) {
internal void wifi_reconnect_timer_cb(TimerHandle_t xTimer)
{
ESP_LOGI(kLogWifi, "Timer expired. Executing esp_wifi_connect()...");
esp_wifi_connect();
}
void teardown_wifi()
{
if (s_wifi_reconnect_timer != nullptr)
{
xTimerStop(s_wifi_reconnect_timer, 0);
xTimerDelete(s_wifi_reconnect_timer, 0);
s_wifi_reconnect_timer = nullptr;
}
if (s_semph_wifi_link != nullptr)
{
vSemaphoreDelete(s_semph_wifi_link);
s_semph_wifi_link = nullptr;
}
if (s_semph_get_wifi_ip_addrs != nullptr) {
if (s_semph_get_wifi_ip_addrs != nullptr)
{
vSemaphoreDelete(s_semph_get_wifi_ip_addrs);
s_semph_get_wifi_ip_addrs = nullptr;
}
@@ -234,7 +288,8 @@ void teardown_wifi() {
esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
&wifi_event_handler);
if (s_wifi_netif) {
if (s_wifi_netif)
{
esp_wifi_disconnect();
esp_wifi_stop();
esp_wifi_deinit();
@@ -244,7 +299,8 @@ void teardown_wifi() {
}
internal esp_err_t connect_wifi(const char *ssid, const char *password,
bool blockUntilIPAcquired) {
bool blockUntilIPAcquired)
{
#ifndef NDEBUG
assert(!s_wifi_connected && "WiFi connect called but already connected!");
s_wifi_connected = true;
@@ -252,10 +308,16 @@ internal esp_err_t connect_wifi(const char *ssid, const char *password,
s_semph_get_wifi_ip_addrs = xSemaphoreCreateBinary();
s_semph_wifi_link = xSemaphoreCreateBinary();
if (s_semph_get_wifi_ip_addrs == NULL || s_semph_wifi_link == NULL) {
if (s_semph_get_wifi_ip_addrs == NULL || s_semph_wifi_link == NULL)
{
return ESP_ERR_NO_MEM;
}
// Create a 5-second timer to avoid spamming connect requests
s_wifi_reconnect_timer =
xTimerCreate("wifi_recon", pdMS_TO_TICKS(5000), pdFALSE, (void *)0,
wifi_reconnect_timer_cb);
// esp_netif_init() is already called by Ethernet, but safe to call multiple
// times or we can assume it's initialized.
s_wifi_netif = esp_netif_create_default_wifi_sta();
@@ -285,7 +347,8 @@ internal esp_err_t connect_wifi(const char *ssid, const char *password,
ESP_ERROR_CHECK(esp_register_shutdown_handler(&teardown_wifi));
if (blockUntilIPAcquired) {
if (blockUntilIPAcquired)
{
ESP_LOGI(kLogWifi, "Waiting for IP address.");
xSemaphoreTake(s_semph_get_wifi_ip_addrs, portMAX_DELAY);
blink_last_ip_octet();
@@ -294,7 +357,8 @@ internal esp_err_t connect_wifi(const char *ssid, const char *password,
return ESP_OK;
}
void disconnect_wifi() {
void disconnect_wifi()
{
#ifndef NDEBUG
assert(s_wifi_connected && "WiFi disconnect called but not connected!");
s_wifi_connected = false;
@@ -304,36 +368,57 @@ void disconnect_wifi() {
ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&teardown_wifi));
}
internal esp_err_t check_wifi_connection(uint32_t timeoutSeconds) {
internal esp_err_t check_wifi_connection(uint32_t timeoutSeconds)
{
// Wait up to 10000ms for the physical link to associate with the AP
if (!s_wifi_link_up) {
if (!xSemaphoreTake(s_semph_wifi_link, pdMS_TO_TICKS(10000))) {
ESP_LOGE(kLogWifi,
"Failed to associate with WiFi AP. Skipping DHCP wait.");
return ESP_ERR_INVALID_STATE; // Special error to skip retries
if (!s_wifi_link_up)
{
// If the timer isn't already running, kickstart a connection attempt now
if (s_wifi_reconnect_timer != nullptr &&
xTimerIsTimerActive(s_wifi_reconnect_timer) == pdFALSE)
{
ESP_LOGI(kLogWifi,
"Physical link is down. Requesting immediate AP association...");
esp_err_t err = esp_wifi_connect();
if (err != ESP_OK && err != ESP_ERR_WIFI_CONN)
{
ESP_LOGE(kLogWifi, "esp_wifi_connect failed: %s", esp_err_to_name(err));
}
}
if (!xSemaphoreTake(s_semph_wifi_link, pdMS_TO_TICKS(10000)))
{
ESP_LOGE(kLogWifi, "Failed to associate with WiFi AP.");
return ESP_ERR_TIMEOUT; // Return timeout so main.cpp triggers a retry
}
}
ESP_LOGI(kLogWifi, "Waiting for IP address for %d seconds.", timeoutSeconds);
if (xSemaphoreTake(s_semph_get_wifi_ip_addrs,
pdMS_TO_TICKS(timeoutSeconds * 1000))) {
pdMS_TO_TICKS(timeoutSeconds * 1000)))
{
blink_last_ip_octet();
return ESP_OK;
} else {
}
else
{
return ESP_ERR_TIMEOUT;
}
}
internal esp_err_t get_ip_info(esp_netif_ip_info_t *ip_info) {
internal esp_err_t get_ip_info(esp_netif_ip_info_t *ip_info)
{
*ip_info = s_current_ip_info;
return ESP_OK;
}
internal void led_blink_number(int n, uint8_t r, uint8_t g, uint8_t b);
internal void blink_last_ip_octet() {
internal void blink_last_ip_octet()
{
esp_netif_ip_info_t ip_info;
if (get_ip_info(&ip_info) == ESP_OK) {
if (get_ip_info(&ip_info) == ESP_OK)
{
uint8_t last_octet = (ip_info.ip.addr >> 24) & 0xFF;
printf("IP Address: " IPSTR "\n", IP2STR(&ip_info.ip));
@@ -342,10 +427,12 @@ internal void blink_last_ip_octet() {
int t = (last_octet / 10) % 10;
int u = last_octet % 10;
if (h > 0) {
if (h > 0)
{
led_blink_number(h, 255, 255, 255);
}
if (h > 0 || t > 0) {
if (h > 0 || t > 0)
{
led_blink_number(t, 255, 255, 255);
}
led_blink_number(u, 255, 255, 255);

View File

@@ -19,78 +19,107 @@ constexpr uint8 kGZ_Extension_Length = sizeof(".gz") - 1;
#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128)
#define SCRATCH_BUFSIZE 4096
typedef struct {
typedef struct
{
char scratch[SCRATCH_BUFSIZE];
} http_server_data_t;
#ifdef CONFIG_CALENDINK_DEPLOY_WEB_PAGES
// Set HTTP response content type according to file extension
internal esp_err_t set_content_type_from_file(httpd_req_t *req,
const char *filepath) {
const char *filepath)
{
const char *type = "text/plain";
if (strstr(filepath, ".html")) {
if (strstr(filepath, ".html"))
{
type = "text/html";
} else if (strstr(filepath, ".js")) {
}
else if (strstr(filepath, ".js"))
{
type = "application/javascript";
} else if (strstr(filepath, ".css")) {
}
else if (strstr(filepath, ".css"))
{
type = "text/css";
} else if (strstr(filepath, ".png")) {
}
else if (strstr(filepath, ".png"))
{
type = "image/png";
} else if (strstr(filepath, ".ico")) {
}
else if (strstr(filepath, ".ico"))
{
type = "image/x-icon";
} else if (strstr(filepath, ".svg")) {
}
else if (strstr(filepath, ".svg"))
{
type = "text/xml";
}
return httpd_resp_set_type(req, type);
}
// Handler to serve static files from LittleFS
internal esp_err_t static_file_handler(httpd_req_t *req) {
internal esp_err_t static_file_handler(httpd_req_t *req)
{
char filepath[FILE_PATH_MAX];
// Construct real file path
strlcpy(filepath, "/www", sizeof(filepath));
if (req->uri[strlen(req->uri) - 1] == '/') {
if (req->uri[strlen(req->uri) - 1] == '/')
{
strlcat(filepath, "/index.html", sizeof(filepath));
} else {
}
else
{
strlcat(filepath, req->uri, sizeof(filepath));
}
// Default to index.html if file doesn't exist (SPA routing support)
struct stat file_stat;
if (stat(filepath, &file_stat) == -1) {
if (stat(filepath, &file_stat) == -1)
{
// Try gzipped first, then fallback to index.html
char filepath_gz[FILE_PATH_MAX + kGZ_Extension_Length];
snprintf(filepath_gz, sizeof(filepath_gz), "%s.gz", filepath);
if (stat(filepath_gz, &file_stat) == 0) {
if (stat(filepath_gz, &file_stat) == 0)
{
strlcpy(filepath, filepath_gz, sizeof(filepath));
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
} else {
}
else
{
ESP_LOGW(TAG, "File not found: %s, falling back to index.html", filepath);
snprintf(filepath, sizeof(filepath), "%s/index.html", "/www");
if (stat(filepath, &file_stat) == -1) {
if (stat(filepath, &file_stat) == -1)
{
// If index.html doesn't exist, try index.html.gz
snprintf(filepath_gz, sizeof(filepath_gz), "%s/index.html.gz", "/www");
if (stat(filepath_gz, &file_stat) == 0) {
if (stat(filepath_gz, &file_stat) == 0)
{
strlcpy(filepath, filepath_gz, sizeof(filepath));
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
} else {
}
else
{
ESP_LOGE(TAG, "index.html not found too.");
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File not found");
return ESP_FAIL;
}
}
}
} else {
}
else
{
// Since ESP32 handles .gz transparently if we tell it to via headers
// If we requested explicitly a .gz file, set the header
if (strstr(filepath, ".gz")) {
if (strstr(filepath, ".gz"))
{
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
}
}
int fd = open(filepath, O_RDONLY, 0);
if (fd == -1) {
if (fd == -1)
{
ESP_LOGE(TAG, "Failed to open file: %s", filepath);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Failed to read existing file");
@@ -103,12 +132,17 @@ internal esp_err_t static_file_handler(httpd_req_t *req) {
char *chunk = rest_context->scratch;
ssize_t read_bytes;
do {
do
{
read_bytes = read(fd, chunk, SCRATCH_BUFSIZE);
if (read_bytes == -1) {
if (read_bytes == -1)
{
ESP_LOGE(TAG, "Failed to read file: %s", filepath);
} else if (read_bytes > 0) {
if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) {
}
else if (read_bytes > 0)
{
if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK)
{
close(fd);
ESP_LOGE(TAG, "File sending failed!");
httpd_resp_sendstr_chunk(req, NULL); // Abort sending
@@ -125,7 +159,8 @@ internal esp_err_t static_file_handler(httpd_req_t *req) {
#endif
// Handler for CORS Preflight OPTIONS requests
internal esp_err_t cors_options_handler(httpd_req_t *req) {
internal esp_err_t cors_options_handler(httpd_req_t *req)
{
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "GET, POST, OPTIONS");
httpd_resp_set_hdr(req, "Access-Control-Allow-Headers", "Content-Type");
@@ -134,10 +169,12 @@ internal esp_err_t cors_options_handler(httpd_req_t *req) {
return ESP_OK;
}
internal httpd_handle_t start_webserver(void) {
internal httpd_handle_t start_webserver(void)
{
http_server_data_t *rest_context =
(http_server_data_t *)calloc(1, sizeof(http_server_data_t));
if (rest_context == NULL) {
if (rest_context == NULL)
{
ESP_LOGE(TAG, "No memory for rest context");
return NULL;
}
@@ -149,7 +186,8 @@ internal httpd_handle_t start_webserver(void) {
httpd_handle_t server = NULL;
ESP_LOGI(TAG, "Starting HTTP Server on port: '%d'", config.server_port);
if (httpd_start(&server, &config) == ESP_OK) {
if (httpd_start(&server, &config) == ESP_OK)
{
// Register CORS OPTIONS handler for API routes
httpd_uri_t cors_options_uri = {.uri = "/api/*",
.method = HTTP_OPTIONS,
@@ -178,8 +216,10 @@ internal httpd_handle_t start_webserver(void) {
return NULL;
}
internal void stop_webserver(httpd_handle_t server) {
if (server) {
internal void stop_webserver(httpd_handle_t server)
{
if (server)
{
httpd_stop(server);
}
}

View File

@@ -7,7 +7,8 @@
// Could be a config but its the GPIO on my ESP32-S3-ETH
#define LED_GPIO GPIO_NUM_21
enum class led_status : uint8 {
enum class led_status : uint8
{
ConnectingEthernet,
ConnectingWifi,
ReadyEthernet,
@@ -17,7 +18,8 @@ enum class led_status : uint8 {
internal led_strip_handle_t led_strip;
internal void setup_led(void) {
internal void setup_led(void)
{
/* LED strip initialization with the GPIO and pixels number*/
led_strip_config_t strip_config = {};
strip_config.strip_gpio_num = LED_GPIO;
@@ -34,8 +36,10 @@ internal void setup_led(void) {
internal void destroy_led(void) { led_strip_clear(led_strip); }
internal void set_led_status(led_status status) {
switch (status) {
internal void set_led_status(led_status status)
{
switch (status)
{
case led_status::ConnectingEthernet:
led_strip_set_pixel(led_strip, 0, 255, 165, 0);
break;
@@ -54,9 +58,12 @@ internal void set_led_status(led_status status) {
}
led_strip_refresh(led_strip);
}
internal void led_blink_number(int n, uint8_t r, uint8_t g, uint8_t b) {
if (n <= 0) {
for (int i = 0; i < 2; i++) {
internal void led_blink_number(int n, uint8_t r, uint8_t g, uint8_t b)
{
if (n <= 0)
{
for (int i = 0; i < 2; i++)
{
led_strip_set_pixel(led_strip, 0, r, g, b);
led_strip_refresh(led_strip);
vTaskDelay(pdMS_TO_TICKS(50));
@@ -67,7 +74,8 @@ internal void led_blink_number(int n, uint8_t r, uint8_t g, uint8_t b) {
vTaskDelay(pdMS_TO_TICKS(1000));
return;
}
for (int i = 0; i < n; i++) {
for (int i = 0; i < n; i++)
{
led_strip_set_pixel(led_strip, 0, r, g, b);
led_strip_refresh(led_strip);
vTaskDelay(pdMS_TO_TICKS(300));

View File

@@ -22,7 +22,8 @@
internal constexpr bool kBlockUntilEthernetEstablished = false;
extern "C" void app_main() {
extern "C" void app_main()
{
printf("Hello, Worldi!\n");
httpd_handle_t web_server = NULL;
@@ -35,25 +36,30 @@ extern "C" void app_main() {
set_led_status(led_status::ConnectingEthernet);
g_Ethernet_Initialized = true;
esp_err_t result = connect_ethernet(kBlockUntilEthernetEstablished);
if (result != ESP_OK) {
if (result != ESP_OK)
{
set_led_status(led_status::Failed);
vTaskDelay(pdMS_TO_TICKS(1000));
goto shutdown;
}
// Check for ethernet connection until its made
if (!kBlockUntilEthernetEstablished) {
if (!kBlockUntilEthernetEstablished)
{
uint8 retries = 1;
do {
do
{
set_led_status(led_status::ConnectingEthernet);
result = check_ethernet_connection(retries);
if (result == ESP_ERR_INVALID_STATE) {
if (result == ESP_ERR_INVALID_STATE)
{
printf("Ethernet cable not plugged in, skipping retries.\n");
break;
}
if (result != ESP_OK) {
if (result != ESP_OK)
{
set_led_status(led_status::Failed);
vTaskDelay(pdMS_TO_TICKS(1000));
}
@@ -63,7 +69,8 @@ extern "C" void app_main() {
retries <= CONFIG_CALENDINK_ETH_RETRIES);
}
if (result != ESP_OK) {
if (result != ESP_OK)
{
printf("Ethernet failed, trying wifi\n");
disconnect_ethernet();
g_Ethernet_Initialized = false;
@@ -73,19 +80,23 @@ extern "C" void app_main() {
result =
connect_wifi(CONFIG_CALENDINK_WIFI_SSID, CONFIG_CALENDINK_WIFI_PASSWORD,
kBlockUntilEthernetEstablished);
if (result != ESP_OK) {
if (result != ESP_OK)
{
set_led_status(led_status::Failed);
vTaskDelay(pdMS_TO_TICKS(1000));
goto shutdown;
}
if (!kBlockUntilEthernetEstablished) {
if (!kBlockUntilEthernetEstablished)
{
uint8 retries = 1;
do {
do
{
set_led_status(led_status::ConnectingWifi);
result = check_wifi_connection(retries);
if (result != ESP_OK) {
if (result != ESP_OK)
{
set_led_status(led_status::Failed);
vTaskDelay(pdMS_TO_TICKS(1000));
}
@@ -95,14 +106,17 @@ extern "C" void app_main() {
retries <= CONFIG_CALENDINK_WIFI_RETRIES);
}
if (result != ESP_OK) {
if (result != ESP_OK)
{
printf("Wifi failed.\n");
goto shutdown;
}
set_led_status(led_status::ReadyWifi);
printf("Will use Wifi!\n");
} else {
}
else
{
set_led_status(led_status::ReadyEthernet);
printf("Will use Ethernet!\n");
}
@@ -113,23 +127,27 @@ extern "C" void app_main() {
web_server = start_webserver();
// Keep the main task alive indefinitely
while (true) {
while (true)
{
vTaskDelay(pdMS_TO_TICKS(1000));
}
shutdown:
printf("Shutting down.\n");
if (web_server) {
if (web_server)
{
stop_webserver(web_server);
web_server = NULL;
}
if (g_Ethernet_Initialized) {
if (g_Ethernet_Initialized)
{
disconnect_ethernet();
g_Ethernet_Initialized = false;
}
if (g_Wifi_Initialized) {
if (g_Wifi_Initialized)
{
disconnect_wifi();
g_Wifi_Initialized = false;
}