Skip to content

Commit 4a2afd5

Browse files
authored
Merge pull request #4 from cjkas/scz/123
Added command execution queue. Fixes crash when commands are invoked simultaneously
2 parents 6679d59 + 9c17aa4 commit 4a2afd5

5 files changed

Lines changed: 210 additions & 22 deletions

File tree

platformio.ini

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extra_scripts =
2525
post:archive_elf.py
2626
board_build.partitions = huge_app.csv
2727
board_build.filesystem = littlefs
28-
build_flags =
28+
build_flags =
2929
-DCORE_DEBUG_LEVEL=3
3030
-DCONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=1
3131
-DCONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=1
@@ -44,3 +44,9 @@ board = esp32dev
4444
[env:esp32devdbg]
4545
board = esp32dev
4646
build_type = debug
47+
48+
[env:esp32c3]
49+
board = esp32-c3-devkitm-1
50+
51+
[env:esp32s3]
52+
board = esp32-s3-devkitm-1

src/MQTT.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,10 @@ void MQTTClass::receive(const char *topic, byte*payload, uint32_t length) {
101101
if (shade) {
102102
int val = atoi(value);
103103
if(strncmp(command, "target", sizeof(command)) == 0) {
104-
if(val >= 0 && val <= 100)
104+
if(val >= 0 && val <= 100) {
105+
ESP_LOGI(TAG, "MQTT shade %s target=%d", entityId, val);
105106
shade->moveToTarget(shade->transformPosition(atoi(value)));
107+
}
106108
}
107109
if(strncmp(command, "tiltTarget", sizeof(command)) == 0) {
108110
if(val >= 0 && val <= 100)
@@ -152,6 +154,7 @@ void MQTTClass::receive(const char *topic, byte*payload, uint32_t length) {
152154
SomfyGroup* group = somfy.getGroupById(atoi(entityId));
153155
if (group) {
154156
int val = atoi(value);
157+
ESP_LOGI(TAG, "MQTT group %s command=%s value=%d", entityId, command, val);
155158
if(strncmp(command, "direction", sizeof(command)) == 0) {
156159
if(val < 0)
157160
group->sendCommand(somfy_commands::Up);

src/Somfy.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2926,11 +2926,12 @@ void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSiz
29262926
}
29272927
void SomfyGroup::sendCommand(somfy_commands cmd) { this->sendCommand(cmd, this->repeats); }
29282928
void SomfyGroup::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) {
2929+
ESP_LOGI(TAG, "[Group %u] sendCommand cmd=%s repeat=%u", this->getGroupId(), translateSomfyCommand(cmd).c_str(), repeat);
29292930
// This sendCommand function will always be called externally. sendCommand at the remote level
29302931
// is expected to be called internally when the motor needs commanded.
29312932
if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type;
29322933
SomfyRemote::sendCommand(cmd, repeat, stepSize);
2933-
2934+
29342935
switch(cmd) {
29352936
case somfy_commands::My:
29362937
this->p_direction(0);
@@ -2949,6 +2950,7 @@ void SomfyGroup::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSiz
29492950
if(this->linkedShades[i] != 0) {
29502951
SomfyShade *shade = somfy.getShadeById(this->linkedShades[i]);
29512952
if(shade) {
2953+
ESP_LOGI(TAG, "[Group %u] processInternalCommand on shade %u cmd=%s", this->getGroupId(), shade->getShadeId(), translateSomfyCommand(cmd).c_str());
29522954
shade->processInternalCommand(cmd, repeat);
29532955
shade->emitCommand(cmd, "group", this->getRemoteAddress());
29542956
}
@@ -2994,6 +2996,8 @@ void SomfyShade::moveToTiltTarget(float target) {
29942996
if(cmd != somfy_commands::My) this->settingTiltPos = true;
29952997
}
29962998
void SomfyShade::moveToTarget(float pos, float tilt) {
2999+
ESP_LOGI(TAG, "[Shade %u] moveToTarget(pos=%.2f, tilt=%.2f) settingPos=%d direction=%d currentTarget=%.2f currentPos=%.2f",
3000+
this->getShadeId(), pos, tilt, this->settingPos, this->direction, this->target, this->currentPos);
29973001
somfy_commands cmd = somfy_commands::My;
29983002
if(this->isToggle()) {
29993003
// Overload this as we cannot seek a position on a garage door or single button device.
@@ -3042,7 +3046,9 @@ bool SomfyShade::save() {
30423046
if(somfy.useNVS()) {
30433047
char shadeKey[15];
30443048
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId());
3045-
pref.begin(shadeKey);
3049+
if(!pref.begin(shadeKey)) {
3050+
ESP_LOGE(TAG, "[Shade %u] save() pref.begin(%s) FAILED", this->getShadeId(), shadeKey);
3051+
}
30463052
pref.clear();
30473053
pref.putChar("shadeType", static_cast<uint8_t>(this->shadeType));
30483054
pref.putUInt("remoteAddress", this->getRemoteAddress());
@@ -3915,7 +3921,9 @@ bool SomfyShadeController::deleteGroup(uint8_t groupId) {
39153921

39163922
bool SomfyShadeController::loadShadesFile(const char *filename) { return ShadeConfigFile::load(this, filename); }
39173923
uint16_t SomfyRemote::getNextRollingCode() {
3918-
pref.begin("ShadeCodes");
3924+
if(!pref.begin("ShadeCodes")) {
3925+
ESP_LOGE(TAG, "getNextRollingCode() pref.begin(ShadeCodes) FAILED");
3926+
}
39193927
uint16_t code = pref.getUShort(this->m_remotePrefId, 0);
39203928
code++;
39213929
pref.putUShort(this->m_remotePrefId, code);

src/Web.cpp

Lines changed: 154 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,15 @@ static const char _encoding_json[] = "application/json";
4343

4444
static const char *TAG = "Web";
4545

46+
static QueueHandle_t webCmdQueue = nullptr;
47+
static SemaphoreHandle_t webCmdDone = nullptr;
48+
4649
AsyncWebServer asyncServer(80);
4750
AsyncWebServer asyncApiServer(8081);
4851
void 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
}
6671
void 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+
}
69162
bool 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();

src/Web.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,39 @@
11
#include <ESPAsyncWebServer.h>
22
#include <AsyncJson.h>
3+
#include <freertos/queue.h>
4+
#include <freertos/semphr.h>
35
#include "Somfy.h"
46
#ifndef webserver_h
57
#define webserver_h
8+
9+
#define WEB_CMD_QUEUE_SIZE 8
10+
#define WEB_CMD_TIMEOUT_MS 3000
11+
12+
enum class web_cmd_t : uint8_t {
13+
shade_command, // moveToTarget or sendCommand
14+
group_command, // group sendCommand
15+
tilt_command, // moveToTiltTarget or sendTiltCommand
16+
shade_repeat, // shade sendCommand/repeatFrame
17+
group_repeat, // group sendCommand/repeatFrame
18+
set_positions, // set shade position directly
19+
shade_sensor, // shade sensor command
20+
group_sensor, // group sensor command
21+
};
22+
23+
struct web_command_t {
24+
web_cmd_t type;
25+
uint8_t shadeId;
26+
uint8_t groupId;
27+
uint8_t target; // 0-100 or 255 (none)
28+
somfy_commands command;
29+
int8_t repeat;
30+
uint8_t stepSize;
31+
int8_t position; // for setPositions
32+
int8_t tiltPosition; // for setPositions/tilt
33+
int8_t sunny; // for sensor
34+
int8_t windy; // for sensor
35+
};
36+
637
class Web {
738
public:
839
bool uploadSuccess = false;
@@ -36,5 +67,8 @@ class Web {
3667
void handleBackup(AsyncWebServerRequest *request);
3768
void handleReboot(AsyncWebServerRequest *request);
3869
void handleNotFound(AsyncWebServerRequest *request);
70+
private:
71+
void processQueue();
72+
bool queueCommand(const web_command_t &cmd);
3973
};
4074
#endif

0 commit comments

Comments
 (0)