@@ -43,10 +43,15 @@ static const char _encoding_json[] = "application/json";
4343
4444static const char *TAG = " Web" ;
4545
46+ static QueueHandle_t webCmdQueue = nullptr ;
47+ static SemaphoreHandle_t webCmdDone = nullptr ;
48+
4649AsyncWebServer asyncServer (80 );
4750AsyncWebServer asyncApiServer (8081 );
4851void Web::startup () {
4952 ESP_LOGI (TAG, " Launching web server..." );
53+ if (!webCmdQueue) webCmdQueue = xQueueCreate (WEB_CMD_QUEUE_SIZE, sizeof (web_command_t ));
54+ if (!webCmdDone) webCmdDone = xSemaphoreCreateBinary ();
5055
5156 asyncServer.on (" /loginContext" , HTTP_GET, [](AsyncWebServerRequest *request) {
5257 AsyncJsonResponse *response = new AsyncJsonResponse ();
@@ -64,8 +69,96 @@ void Web::startup() {
6469 ESP_LOGI (TAG, " Async API server started on port 8081" );
6570}
6671void Web::loop () {
72+ this ->processQueue ();
6773 delay (1 );
6874}
75+ bool Web::queueCommand (const web_command_t &cmd) {
76+ if (!webCmdQueue || !webCmdDone) return false ;
77+ // Clear any stale signal
78+ xSemaphoreTake (webCmdDone, 0 );
79+ if (xQueueSend (webCmdQueue, &cmd, pdMS_TO_TICKS (100 )) != pdTRUE) {
80+ ESP_LOGE (TAG, " Command queue full, dropping command" );
81+ return false ;
82+ }
83+ // Wait for main loop to process it
84+ if (xSemaphoreTake (webCmdDone, pdMS_TO_TICKS (WEB_CMD_TIMEOUT_MS)) != pdTRUE) {
85+ ESP_LOGW (TAG, " Command queue timeout waiting for processing" );
86+ return false ;
87+ }
88+ return true ;
89+ }
90+ void Web::processQueue () {
91+ if (!webCmdQueue || !webCmdDone) return ;
92+ web_command_t cmd;
93+ while (xQueueReceive (webCmdQueue, &cmd, 0 ) == pdTRUE) {
94+ switch (cmd.type ) {
95+ case web_cmd_t ::shade_command: {
96+ SomfyShade *shade = somfy.getShadeById (cmd.shadeId );
97+ if (shade) {
98+ if (cmd.target <= 100 ) shade->moveToTarget (shade->transformPosition (cmd.target ));
99+ else shade->sendCommand (cmd.command , cmd.repeat > 0 ? cmd.repeat : shade->repeats , cmd.stepSize );
100+ }
101+ break ;
102+ }
103+ case web_cmd_t ::group_command: {
104+ SomfyGroup *group = somfy.getGroupById (cmd.groupId );
105+ if (group) group->sendCommand (cmd.command , cmd.repeat >= 0 ? cmd.repeat : group->repeats , cmd.stepSize );
106+ break ;
107+ }
108+ case web_cmd_t ::tilt_command: {
109+ SomfyShade *shade = somfy.getShadeById (cmd.shadeId );
110+ if (shade) {
111+ if (cmd.target <= 100 ) shade->moveToTiltTarget (shade->transformPosition (cmd.target ));
112+ else shade->sendTiltCommand (cmd.command );
113+ }
114+ break ;
115+ }
116+ case web_cmd_t ::shade_repeat: {
117+ SomfyShade *shade = somfy.getShadeById (cmd.shadeId );
118+ if (shade) {
119+ if (shade->shadeType == shade_types::garage1 && cmd.command == somfy_commands::Prog) cmd.command = somfy_commands::Toggle;
120+ if (!shade->isLastCommand (cmd.command )) shade->sendCommand (cmd.command , cmd.repeat >= 0 ? cmd.repeat : shade->repeats , cmd.stepSize );
121+ else shade->repeatFrame (cmd.repeat >= 0 ? cmd.repeat : shade->repeats );
122+ }
123+ break ;
124+ }
125+ case web_cmd_t ::group_repeat: {
126+ SomfyGroup *group = somfy.getGroupById (cmd.groupId );
127+ if (group) {
128+ if (!group->isLastCommand (cmd.command )) group->sendCommand (cmd.command , cmd.repeat >= 0 ? cmd.repeat : group->repeats , cmd.stepSize );
129+ else group->repeatFrame (cmd.repeat >= 0 ? cmd.repeat : group->repeats );
130+ }
131+ break ;
132+ }
133+ case web_cmd_t ::set_positions: {
134+ SomfyShade *shade = somfy.getShadeById (cmd.shadeId );
135+ if (shade) {
136+ if (cmd.position >= 0 ) shade->target = shade->currentPos = cmd.position ;
137+ if (cmd.tiltPosition >= 0 && shade->tiltType != tilt_types::none) shade->tiltTarget = shade->currentTiltPos = cmd.tiltPosition ;
138+ shade->emitState ();
139+ }
140+ break ;
141+ }
142+ case web_cmd_t ::shade_sensor: {
143+ SomfyShade *shade = somfy.getShadeById (cmd.shadeId );
144+ if (shade) {
145+ shade->sendSensorCommand (cmd.windy , cmd.sunny , cmd.repeat >= 0 ? (uint8_t )cmd.repeat : shade->repeats );
146+ shade->emitState ();
147+ }
148+ break ;
149+ }
150+ case web_cmd_t ::group_sensor: {
151+ SomfyGroup *group = somfy.getGroupById (cmd.groupId );
152+ if (group) {
153+ group->sendSensorCommand (cmd.windy , cmd.sunny , cmd.repeat >= 0 ? (uint8_t )cmd.repeat : group->repeats );
154+ group->emitState ();
155+ }
156+ break ;
157+ }
158+ }
159+ xSemaphoreGive (webCmdDone);
160+ }
161+ }
69162bool Web::isAuthenticated (AsyncWebServerRequest *request, bool cfg) {
70163 ESP_LOGD (TAG, " Checking async authentication" );
71164 if (settings.Security .type == security_types::None) return true ;
@@ -521,8 +614,15 @@ void Web::handleShadeCommand(AsyncWebServerRequest *request, JsonVariant &json)
521614 else { request->send (500 , _encoding_json, F (" {\" status\" :\" ERROR\" ,\" desc\" :\" No shade object supplied.\" }" )); return ; }
522615 SomfyShade *shade = somfy.getShadeById (shadeId);
523616 if (shade) {
524- if (target <= 100 ) shade->moveToTarget (shade->transformPosition (target));
525- else shade->sendCommand (command, repeat > 0 ? repeat : shade->repeats , stepSize);
617+ ESP_LOGI (TAG, " handleShadeCommand shade=%u target=%u command=%s" , shadeId, target, translateSomfyCommand (command).c_str ());
618+ web_command_t cmd = {};
619+ cmd.type = web_cmd_t ::shade_command;
620+ cmd.shadeId = shadeId;
621+ cmd.target = target;
622+ cmd.command = command;
623+ cmd.repeat = repeat;
624+ cmd.stepSize = stepSize;
625+ this ->queueCommand (cmd);
526626 AsyncJsonResp resp;
527627 resp.beginResponse (request, g_async_content, sizeof (g_async_content));
528628 resp.beginObject ();
@@ -556,7 +656,14 @@ void Web::handleGroupCommand(AsyncWebServerRequest *request, JsonVariant &json)
556656 else { request->send (500 , _encoding_json, F (" {\" status\" :\" ERROR\" ,\" desc\" :\" No group object supplied.\" }" )); return ; }
557657 SomfyGroup *group = somfy.getGroupById (groupId);
558658 if (group) {
559- group->sendCommand (command, repeat >= 0 ? repeat : group->repeats , stepSize);
659+ ESP_LOGI (TAG, " handleGroupCommand group=%u command=%s" , groupId, translateSomfyCommand (command).c_str ());
660+ web_command_t cmd = {};
661+ cmd.type = web_cmd_t ::group_command;
662+ cmd.groupId = groupId;
663+ cmd.command = command;
664+ cmd.repeat = repeat;
665+ cmd.stepSize = stepSize;
666+ this ->queueCommand (cmd);
560667 AsyncJsonResp resp;
561668 resp.beginResponse (request, g_async_content, sizeof (g_async_content));
562669 resp.beginObject ();
@@ -587,8 +694,13 @@ void Web::handleTiltCommand(AsyncWebServerRequest *request, JsonVariant &json) {
587694 else { request->send (500 , _encoding_json, F (" {\" status\" :\" ERROR\" ,\" desc\" :\" No shade object supplied.\" }" )); return ; }
588695 SomfyShade *shade = somfy.getShadeById (shadeId);
589696 if (shade) {
590- if (target <= 100 ) shade->moveToTiltTarget (shade->transformPosition (target));
591- else shade->sendTiltCommand (command);
697+ ESP_LOGI (TAG, " handleTiltCommand shade=%u target=%u command=%s" , shadeId, target, translateSomfyCommand (command).c_str ());
698+ web_command_t cmd = {};
699+ cmd.type = web_cmd_t ::tilt_command;
700+ cmd.shadeId = shadeId;
701+ cmd.target = target;
702+ cmd.command = command;
703+ this ->queueCommand (cmd);
592704 AsyncJsonResp resp;
593705 resp.beginResponse (request, g_async_content, sizeof (g_async_content));
594706 resp.beginObject ();
@@ -620,11 +732,16 @@ void Web::handleRepeatCommand(AsyncWebServerRequest *request, JsonVariant &json)
620732 if (!obj[" repeat" ].isNull ()) repeat = obj[" repeat" ].as <uint8_t >();
621733 }
622734 if (shadeId != 255 ) {
735+ ESP_LOGI (TAG, " handleRepeatCommand shade=%u command=%s" , shadeId, translateSomfyCommand (command).c_str ());
623736 SomfyShade *shade = somfy.getShadeById (shadeId);
624737 if (!shade) { request->send (500 , _encoding_json, F (" {\" status\" :\" ERROR\" ,\" desc\" :\" Shade reference could not be found.\" }" )); return ; }
625- if (shade->shadeType == shade_types::garage1 && command == somfy_commands::Prog) command = somfy_commands::Toggle;
626- if (!shade->isLastCommand (command)) shade->sendCommand (command, repeat >= 0 ? repeat : shade->repeats , stepSize);
627- else shade->repeatFrame (repeat >= 0 ? repeat : shade->repeats );
738+ web_command_t cmd = {};
739+ cmd.type = web_cmd_t ::shade_repeat;
740+ cmd.shadeId = shadeId;
741+ cmd.command = command;
742+ cmd.repeat = repeat;
743+ cmd.stepSize = stepSize;
744+ this ->queueCommand (cmd);
628745 AsyncJsonResp resp;
629746 resp.beginResponse (request, g_async_content, sizeof (g_async_content));
630747 resp.beginArray ();
@@ -633,10 +750,16 @@ void Web::handleRepeatCommand(AsyncWebServerRequest *request, JsonVariant &json)
633750 resp.endResponse ();
634751 }
635752 else if (groupId != 255 ) {
753+ ESP_LOGI (TAG, " handleRepeatCommand group=%u command=%s" , groupId, translateSomfyCommand (command).c_str ());
636754 SomfyGroup *group = somfy.getGroupById (groupId);
637755 if (!group) { request->send (500 , _encoding_json, F (" {\" status\" :\" ERROR\" ,\" desc\" :\" Group reference could not be found.\" }" )); return ; }
638- if (!group->isLastCommand (command)) group->sendCommand (command, repeat >= 0 ? repeat : group->repeats , stepSize);
639- else group->repeatFrame (repeat >= 0 ? repeat : group->repeats );
756+ web_command_t cmd = {};
757+ cmd.type = web_cmd_t ::group_repeat;
758+ cmd.groupId = groupId;
759+ cmd.command = command;
760+ cmd.repeat = repeat;
761+ cmd.stepSize = stepSize;
762+ this ->queueCommand (cmd);
640763 AsyncJsonResp resp;
641764 resp.beginResponse (request, g_async_content, sizeof (g_async_content));
642765 resp.beginObject ();
@@ -722,11 +845,15 @@ void Web::handleSetPositions(AsyncWebServerRequest *request, JsonVariant &json)
722845 if (!obj[" tiltPosition" ].isNull ()) tiltPos = obj[" tiltPosition" ];
723846 }
724847 if (shadeId != 255 ) {
848+ ESP_LOGI (TAG, " handleSetPositions shade=%u pos=%d tiltPos=%d" , shadeId, pos, tiltPos);
725849 SomfyShade *shade = somfy.getShadeById (shadeId);
726850 if (shade) {
727- if (pos >= 0 ) shade->target = shade->currentPos = pos;
728- if (tiltPos >= 0 && shade->tiltType != tilt_types::none) shade->tiltTarget = shade->currentTiltPos = tiltPos;
729- shade->emitState ();
851+ web_command_t cmd = {};
852+ cmd.type = web_cmd_t ::set_positions;
853+ cmd.shadeId = shadeId;
854+ cmd.position = pos;
855+ cmd.tiltPosition = tiltPos;
856+ this ->queueCommand (cmd);
730857 AsyncJsonResp resp;
731858 resp.beginResponse (request, g_async_content, sizeof (g_async_content));
732859 resp.beginObject ();
@@ -763,8 +890,13 @@ void Web::handleSetSensor(AsyncWebServerRequest *request, JsonVariant &json) {
763890 if (shadeId != 255 ) {
764891 SomfyShade *shade = somfy.getShadeById (shadeId);
765892 if (shade) {
766- shade->sendSensorCommand (windy, sunny, repeat >= 0 ? (uint8_t )repeat : shade->repeats );
767- shade->emitState ();
893+ web_command_t cmd = {};
894+ cmd.type = web_cmd_t ::shade_sensor;
895+ cmd.shadeId = shadeId;
896+ cmd.sunny = sunny;
897+ cmd.windy = windy;
898+ cmd.repeat = repeat;
899+ this ->queueCommand (cmd);
768900 AsyncJsonResp resp;
769901 resp.beginResponse (request, g_async_content, sizeof (g_async_content));
770902 resp.beginObject ();
@@ -777,8 +909,13 @@ void Web::handleSetSensor(AsyncWebServerRequest *request, JsonVariant &json) {
777909 else if (groupId != 255 ) {
778910 SomfyGroup *group = somfy.getGroupById (groupId);
779911 if (group) {
780- group->sendSensorCommand (windy, sunny, repeat >= 0 ? (uint8_t )repeat : group->repeats );
781- group->emitState ();
912+ web_command_t cmd = {};
913+ cmd.type = web_cmd_t ::group_sensor;
914+ cmd.groupId = groupId;
915+ cmd.sunny = sunny;
916+ cmd.windy = windy;
917+ cmd.repeat = repeat;
918+ this ->queueCommand (cmd);
782919 AsyncJsonResp resp;
783920 resp.beginResponse (request, g_async_content, sizeof (g_async_content));
784921 resp.beginObject ();
0 commit comments