2022-09-21 15:37:20 -04:00
|
|
|
#include "ESPUI.h"
|
|
|
|
#include "ESPUIclient.h"
|
|
|
|
#include "ESPUIcontrol.h"
|
|
|
|
|
2023-09-09 16:52:28 -04:00
|
|
|
// JSONSlave:
|
|
|
|
// helper to process exact JSON serialization size
|
|
|
|
// it takes ~2ms on esp8266 and avoid large String reallocation which is really worth the cost
|
|
|
|
class JSONSlave: public Print
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
size_t write (uint8_t c) override { counter++; return 1; }
|
|
|
|
size_t write (const uint8_t* buf, size_t count) override { counter += count; return count; }
|
|
|
|
size_t get_counter () { return counter; }
|
|
|
|
|
|
|
|
static size_t serializedSize (JsonDocument& doc)
|
|
|
|
{
|
|
|
|
JSONSlave counter;
|
|
|
|
serializeJson(doc, counter);
|
|
|
|
return counter.get_counter();
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t serialize (JsonDocument& doc, String& str)
|
|
|
|
{
|
|
|
|
size_t s = serializedSize(doc) + 10; // 10 is paranoid
|
|
|
|
str.reserve(s);
|
|
|
|
serializeJson(doc, str);
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
static String toString (JsonDocument& doc)
|
|
|
|
{
|
|
|
|
String str;
|
|
|
|
serialize(doc, str);
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
size_t counter = 0;
|
|
|
|
};
|
|
|
|
|
2022-09-21 15:37:20 -04:00
|
|
|
ESPUIclient::ESPUIclient(AsyncWebSocketClient * _client):
|
|
|
|
client(_client)
|
|
|
|
{
|
|
|
|
fsm_EspuiClient_state_Idle_imp.SetParent(this);
|
|
|
|
fsm_EspuiClient_state_SendingUpdate_imp.SetParent(this);
|
|
|
|
fsm_EspuiClient_state_Rebuilding_imp.SetParent(this);
|
|
|
|
fsm_EspuiClient_state_Reloading_imp.SetParent(this);
|
|
|
|
|
|
|
|
fsm_EspuiClient_state_Idle_imp.Init();
|
|
|
|
}
|
|
|
|
|
|
|
|
ESPUIclient::ESPUIclient(const ESPUIclient& source):
|
|
|
|
client(source.client)
|
|
|
|
{
|
|
|
|
fsm_EspuiClient_state_Idle_imp.SetParent(this);
|
|
|
|
fsm_EspuiClient_state_SendingUpdate_imp.SetParent(this);
|
|
|
|
fsm_EspuiClient_state_Rebuilding_imp.SetParent(this);
|
|
|
|
fsm_EspuiClient_state_Reloading_imp.SetParent(this);
|
|
|
|
|
|
|
|
fsm_EspuiClient_state_Idle_imp.Init();
|
|
|
|
}
|
|
|
|
|
|
|
|
ESPUIclient::~ESPUIclient()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ESPUIclient::CanSend()
|
|
|
|
{
|
|
|
|
bool Response = false;
|
|
|
|
if (nullptr != client)
|
|
|
|
{
|
|
|
|
Response = client->canSend();
|
|
|
|
}
|
|
|
|
return Response;
|
|
|
|
}
|
|
|
|
|
2024-03-26 16:06:23 -04:00
|
|
|
void ESPUIclient::FillInHeader(JsonDocument& document)
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
|
|
|
document[F("type")] = UI_EXTEND_GUI;
|
|
|
|
document[F("sliderContinuous")] = ESPUI.sliderContinuous;
|
|
|
|
document[F("startindex")] = 0;
|
|
|
|
document[F("totalcontrols")] = ESPUI.controlCount;
|
2024-03-26 16:06:23 -04:00
|
|
|
JsonArray items = AllocateJsonArray(document, F("controls"));
|
|
|
|
JsonObject titleItem = AllocateJsonObject(items);
|
2022-09-21 15:37:20 -04:00
|
|
|
titleItem[F("type")] = (int)UI_TITLE;
|
|
|
|
titleItem[F("label")] = ESPUI.ui_title;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ESPUIclient::IsSyncronized()
|
|
|
|
{
|
|
|
|
return ((ClientUpdateType_t::Synchronized == ClientUpdateType) &&
|
|
|
|
(&fsm_EspuiClient_state_Idle_imp == pCurrentFsmState));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ESPUIclient::SendClientNotification(ClientUpdateType_t value)
|
|
|
|
{
|
|
|
|
bool Response = false;
|
|
|
|
|
|
|
|
do // once
|
|
|
|
{
|
|
|
|
if(!CanSend())
|
|
|
|
{
|
2023-11-09 11:06:44 -05:00
|
|
|
// Serial.println(F("ESPUIclient::SendClientNotification:CannotSend"));
|
2022-09-21 15:37:20 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-03-26 16:06:23 -04:00
|
|
|
AllocateJsonDocument(document, ESPUI.jsonUpdateDocumentSize);
|
2022-09-21 15:37:20 -04:00
|
|
|
FillInHeader(document);
|
|
|
|
if(ClientUpdateType_t::ReloadNeeded == value)
|
|
|
|
{
|
|
|
|
// Serial.println(F("ESPUIclient::SendClientNotification:set type to reload"));
|
|
|
|
document["type"] = int(UI_RELOAD);
|
|
|
|
}
|
|
|
|
// dont send any controls
|
|
|
|
|
|
|
|
Response = SendJsonDocToWebSocket(document);
|
|
|
|
// Serial.println(String("ESPUIclient::SendClientNotification:NotificationSent:Response: ") + String(Response));
|
|
|
|
|
|
|
|
} while (false);
|
|
|
|
return Response;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ESPUIclient::NotifyClient(ClientUpdateType_t newState)
|
|
|
|
{
|
|
|
|
SetState(newState);
|
|
|
|
pCurrentFsmState->NotifyClient();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle Websockets Communication
|
2023-11-09 11:06:44 -05:00
|
|
|
bool ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t len)
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
2023-11-09 11:06:44 -05:00
|
|
|
bool Response = false;
|
2022-09-21 15:37:20 -04:00
|
|
|
// Serial.println(String("ESPUIclient::OnWsEvent: type: ") + String(type));
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
{
|
2022-09-22 09:34:11 -04:00
|
|
|
case WS_EVT_PONG:
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
|
|
|
#if defined(DEBUG_ESPUI)
|
|
|
|
if (ESPUI.verbosity)
|
|
|
|
{
|
|
|
|
Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_PONG"));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-09-22 09:34:11 -04:00
|
|
|
case WS_EVT_ERROR:
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
|
|
|
#if defined(DEBUG_ESPUI)
|
|
|
|
if (ESPUI.verbosity)
|
|
|
|
{
|
|
|
|
Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_ERROR"));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case WS_EVT_CONNECT:
|
|
|
|
{
|
|
|
|
#if defined(DEBUG_ESPUI)
|
|
|
|
if (ESPUI.verbosity)
|
|
|
|
{
|
|
|
|
Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_CONNECT"));
|
|
|
|
Serial.println(client->id());
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Serial.println("ESPUIclient:onWsEvent:WS_EVT_CONNECT: Call NotifyClient: RebuildNeeded");
|
|
|
|
NotifyClient(ClientUpdateType_t::RebuildNeeded);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-09-22 09:34:11 -04:00
|
|
|
case WS_EVT_DATA:
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
|
|
|
// Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_DATA"));
|
|
|
|
String msg = "";
|
|
|
|
msg.reserve(len + 1);
|
|
|
|
|
|
|
|
for (size_t i = 0; i < len; i++)
|
|
|
|
{
|
|
|
|
msg += (char)data[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
String cmd = msg.substring(0, msg.indexOf(":"));
|
|
|
|
String value = msg.substring(cmd.length() + 1, msg.lastIndexOf(':'));
|
|
|
|
uint16_t id = msg.substring(msg.lastIndexOf(':') + 1).toInt();
|
|
|
|
|
|
|
|
#if defined(DEBUG_ESPUI)
|
|
|
|
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
|
|
|
{
|
|
|
|
Serial.println(String(F(" WS msg: ")) + msg);
|
|
|
|
Serial.println(String(F(" WS cmd: ")) + cmd);
|
|
|
|
Serial.println(String(F(" WS id: ")) + String(id));
|
|
|
|
Serial.println(String(F("WS value: ")) + String(value));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (cmd.equals(F("uiok")))
|
|
|
|
{
|
2023-11-09 11:06:44 -05:00
|
|
|
|
|
|
|
// Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:ProcessAck:")) + pCurrentFsmState->GetStateName());
|
2023-09-09 15:58:12 -04:00
|
|
|
pCurrentFsmState->ProcessAck(id, emptyString);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmd.equals(F("uifragmentok")))
|
|
|
|
{
|
2023-11-09 11:06:44 -05:00
|
|
|
// Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:uifragmentok:")) + pCurrentFsmState->GetStateName() + ":ProcessAck");
|
2023-09-09 15:58:12 -04:00
|
|
|
if(!emptyString.equals(value))
|
|
|
|
{
|
2023-11-09 11:06:44 -05:00
|
|
|
// Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:uifragmentok:")) + pCurrentFsmState->GetStateName() + ":ProcessAck:value:'" + value + "'");
|
2023-09-09 15:58:12 -04:00
|
|
|
pCurrentFsmState->ProcessAck(uint16_t(-1), value);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-11-09 11:06:44 -05:00
|
|
|
Serial.println(F("ERROR:ESPUIclient::OnWsEvent:WS_EVT_DATA:uifragmentok:ProcessAck:Fragment Header is missing"));
|
2023-09-09 15:58:12 -04:00
|
|
|
}
|
2022-09-21 15:37:20 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmd.equals(F("uiuok")))
|
|
|
|
{
|
|
|
|
// Serial.println(F("WS_EVT_DATA: uiuok. Unlock new async notifications"));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2023-11-09 11:06:44 -05:00
|
|
|
// Serial.println(F("WS_EVT_DATA:Process Control"));
|
2022-09-21 15:37:20 -04:00
|
|
|
Control* control = ESPUI.getControl(id);
|
|
|
|
if (nullptr == control)
|
|
|
|
{
|
|
|
|
#if defined(DEBUG_ESPUI)
|
|
|
|
if (ESPUI.verbosity)
|
|
|
|
{
|
|
|
|
Serial.println(String(F("No control found for ID ")) + String(id));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
control->onWsEvent(cmd, value);
|
2023-11-09 11:06:44 -05:00
|
|
|
// notify other clients of change
|
|
|
|
Response = true;
|
2022-09-21 15:37:20 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
// Serial.println(F("ESPUIclient::OnWsEvent:default"));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} // end switch
|
2023-11-09 11:06:44 -05:00
|
|
|
|
|
|
|
return Response;
|
2022-09-21 15:37:20 -04:00
|
|
|
}
|
|
|
|
|
2022-09-22 09:34:11 -04:00
|
|
|
/*
|
2022-09-21 15:37:20 -04:00
|
|
|
Prepare a chunk of elements as a single JSON string. If the allowed number of elements is greater than the total
|
2022-09-22 09:34:11 -04:00
|
|
|
number this will represent the entire UI. More likely, it will represent a small section of the UI to be sent. The
|
2022-09-21 15:37:20 -04:00
|
|
|
client will acknowledge receipt by requesting the next chunk.
|
|
|
|
*/
|
2022-09-22 09:34:11 -04:00
|
|
|
uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
|
2024-03-26 16:06:23 -04:00
|
|
|
JsonDocument & rootDoc,
|
2023-09-09 15:58:12 -04:00
|
|
|
bool InUpdateMode,
|
|
|
|
String FragmentRequestString)
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
|
|
|
#ifdef ESP32
|
|
|
|
xSemaphoreTake(ESPUI.ControlsSemaphore, portMAX_DELAY);
|
|
|
|
#endif // def ESP32
|
|
|
|
|
2024-03-05 13:40:01 -05:00
|
|
|
// Serial.println(String("prepareJSONChunk: Start. InUpdateMode: ") + String(InUpdateMode));
|
|
|
|
// Serial.println(String("prepareJSONChunk: Start. startindex: ") + String(startindex));
|
|
|
|
// Serial.println(String("prepareJSONChunk: Start. FragmentRequestString: '") + FragmentRequestString + "'");
|
2022-09-21 15:37:20 -04:00
|
|
|
int elementcount = 0;
|
2024-03-05 13:40:01 -05:00
|
|
|
uint32_t MaxMarshaledJsonSize = (!InUpdateMode) ? ESPUI.jsonInitialDocumentSize: ESPUI.jsonUpdateDocumentSize;
|
|
|
|
uint32_t EstimatedUsedMarshaledJsonSize = 0;
|
2022-09-21 15:37:20 -04:00
|
|
|
|
|
|
|
do // once
|
|
|
|
{
|
|
|
|
// Follow the list until control points to the startindex'th node
|
|
|
|
Control* control = ESPUI.controls;
|
|
|
|
uint32_t currentIndex = 0;
|
2023-09-09 15:58:12 -04:00
|
|
|
uint32_t DataOffset = 0;
|
2022-09-21 15:37:20 -04:00
|
|
|
JsonArray items = rootDoc[F("controls")];
|
2023-09-09 15:58:12 -04:00
|
|
|
bool SingleControl = false;
|
|
|
|
|
|
|
|
if(!emptyString.equals(FragmentRequestString))
|
|
|
|
{
|
|
|
|
// Serial.println(F("prepareJSONChunk:Fragmentation:Got Header (1)"));
|
|
|
|
// Serial.println(String("prepareJSONChunk:startindex: ") + String(startindex));
|
|
|
|
// Serial.println(String("prepareJSONChunk:currentIndex: ") + String(currentIndex));
|
|
|
|
// Serial.println(String("prepareJSONChunk:FragmentRequestString: '") + FragmentRequestString + "'");
|
|
|
|
|
|
|
|
// this is actually a fragment or directed update request
|
|
|
|
// parse the string we got from the UI and try to update that specific
|
|
|
|
// control.
|
2024-03-26 16:06:23 -04:00
|
|
|
AllocateJsonDocument(FragmentRequest, FragmentRequestString.length() * 3);
|
|
|
|
/*
|
|
|
|
ArduinoJson::detail::sizeofObject(N);
|
2023-09-09 15:58:12 -04:00
|
|
|
if(0 >= FragmentRequest.capacity())
|
|
|
|
{
|
|
|
|
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Could not allocate memory for a fragmentation request. Skipping Response"));
|
|
|
|
break;
|
|
|
|
}
|
2024-03-26 16:06:23 -04:00
|
|
|
*/
|
2023-09-09 15:58:12 -04:00
|
|
|
size_t FragmentRequestStartOffset = FragmentRequestString.indexOf("{");
|
|
|
|
DeserializationError error = deserializeJson(FragmentRequest, FragmentRequestString.substring(FragmentRequestStartOffset));
|
|
|
|
if(DeserializationError::Ok != error)
|
|
|
|
{
|
|
|
|
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Could not extract json from the fragment request"));
|
|
|
|
break;
|
|
|
|
}
|
2022-09-21 15:37:20 -04:00
|
|
|
|
2024-10-04 16:14:41 -05:00
|
|
|
if(!FragmentRequest["id"].is<JsonVariant>())
|
2023-09-09 15:58:12 -04:00
|
|
|
{
|
|
|
|
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Request does not contain a control ID"));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
uint16_t ControlId = uint16_t(FragmentRequest[F("id")]);
|
|
|
|
|
2024-10-04 16:14:41 -05:00
|
|
|
if(!FragmentRequest["offset"].is<JsonVariant>())
|
2023-09-09 15:58:12 -04:00
|
|
|
{
|
|
|
|
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Request does not contain a starting offset"));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
DataOffset = uint16_t(FragmentRequest[F("offset")]);
|
2023-09-09 17:01:55 -04:00
|
|
|
control = ESPUI.getControlNoLock(ControlId);
|
2023-09-09 15:58:12 -04:00
|
|
|
if(nullptr == control)
|
|
|
|
{
|
|
|
|
Serial.println(String(F("ERROR:prepareJSONChunk:Fragmentation:Requested control: ")) + String(ControlId) + F(" does not exist"));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Serial.println(F("prepareJSONChunk:Fragmentation:disable the control search operation"));
|
|
|
|
currentIndex = 1;
|
|
|
|
startindex = 0;
|
|
|
|
SingleControl = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// find a control to send
|
2022-09-21 15:37:20 -04:00
|
|
|
while ((startindex > currentIndex) && (nullptr != control))
|
|
|
|
{
|
|
|
|
// only count active controls
|
|
|
|
if (!control->ToBeDeleted())
|
|
|
|
{
|
|
|
|
if(InUpdateMode)
|
|
|
|
{
|
|
|
|
// In update mode we only count the controls that have been updated.
|
2023-11-09 11:06:44 -05:00
|
|
|
if(control->NeedsSync(CurrentSyncID))
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
|
|
|
++currentIndex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// not in update mode. Count all active controls
|
|
|
|
++currentIndex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
control = control->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
// any controls left to be processed?
|
|
|
|
if(nullptr == control)
|
|
|
|
{
|
|
|
|
// Serial.println("prepareJSONChunk: No controls to process");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-09-22 09:34:11 -04:00
|
|
|
// keep track of the number of elements we have serialised into this
|
|
|
|
// message. Overflow is detected and handled later in this loop
|
2022-09-21 15:37:20 -04:00
|
|
|
// and needs an index to the last item added.
|
|
|
|
while (nullptr != control)
|
|
|
|
{
|
|
|
|
// skip deleted controls or controls that have not been updated
|
2023-09-09 15:58:12 -04:00
|
|
|
if (control->ToBeDeleted() && !SingleControl)
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
|
|
|
// Serial.println(String("prepareJSONChunk: Ignoring Deleted control: ") + String(control->id));
|
|
|
|
control = control->next;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-09-09 15:58:12 -04:00
|
|
|
if(InUpdateMode && !SingleControl)
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
2023-11-09 11:06:44 -05:00
|
|
|
if(control->NeedsSync(CurrentSyncID))
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
|
|
|
// dont skip this control
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// control has not been updated. Skip it
|
|
|
|
control = control->next;
|
|
|
|
continue;
|
|
|
|
}
|
2022-09-22 09:34:11 -04:00
|
|
|
}
|
2022-09-21 15:37:20 -04:00
|
|
|
|
2024-03-05 13:40:01 -05:00
|
|
|
// Serial.println(String(F("prepareJSONChunk: MaxMarshaledJsonSize: ")) + String(MaxMarshaledJsonSize));
|
|
|
|
// Serial.println(String(F("prepareJSONChunk: Cur EstimatedUsedMarshaledJsonSize: ")) + String(EstimatedUsedMarshaledJsonSize));
|
|
|
|
|
2024-03-26 16:06:23 -04:00
|
|
|
JsonObject item = AllocateJsonObject(items);
|
2022-09-21 15:37:20 -04:00
|
|
|
elementcount++;
|
2024-03-05 13:40:01 -05:00
|
|
|
uint32_t RemainingSpace = (MaxMarshaledJsonSize - EstimatedUsedMarshaledJsonSize) - 100;
|
|
|
|
// Serial.println(String(F("prepareJSONChunk: RemainingSpace: ")) + String(RemainingSpace));
|
|
|
|
uint32_t SpaceUsedByMarshaledControl = 0;
|
|
|
|
bool ControlIsFragmented = control->MarshalControl(item,
|
|
|
|
InUpdateMode,
|
|
|
|
DataOffset,
|
|
|
|
RemainingSpace,
|
|
|
|
SpaceUsedByMarshaledControl);
|
|
|
|
// Serial.println(String(F("prepareJSONChunk: SpaceUsedByMarshaledControl: ")) + String(SpaceUsedByMarshaledControl));
|
|
|
|
EstimatedUsedMarshaledJsonSize += SpaceUsedByMarshaledControl;
|
|
|
|
// Serial.println(String(F("prepareJSONChunk: New EstimatedUsedMarshaledJsonSize: ")) + String(EstimatedUsedMarshaledJsonSize));
|
|
|
|
// Serial.println(String(F("prepareJSONChunk: ControlIsFragmented: ")) + String(ControlIsFragmented));
|
|
|
|
|
|
|
|
// did the control get added to the doc?
|
|
|
|
if (0 == SpaceUsedByMarshaledControl ||
|
|
|
|
(ESPUI.jsonChunkNumberMax > 0 && (elementcount % ESPUI.jsonChunkNumberMax) == 0))
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
2024-03-05 13:40:01 -05:00
|
|
|
// Serial.println( String("prepareJSONChunk: too much data in the message. Remove the last entry"));
|
2022-09-21 15:37:20 -04:00
|
|
|
if (1 == elementcount)
|
|
|
|
{
|
2024-03-05 13:40:01 -05:00
|
|
|
// Serial.println(String(F("prepareJSONChunk: Control ")) + String(control->id) + F(" is too large to be sent to the browser."));
|
2023-09-03 09:26:36 -04:00
|
|
|
// Serial.println(String(F("ERROR: prepareJSONChunk: value: ")) + control->value);
|
2022-09-21 15:37:20 -04:00
|
|
|
rootDoc.clear();
|
2024-03-26 16:06:23 -04:00
|
|
|
item = AllocateJsonObject(items);
|
2022-09-21 15:37:20 -04:00
|
|
|
control->MarshalErrorMessage(item);
|
|
|
|
elementcount = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Serial.println(String("prepareJSONChunk: Defering control: ") + String(control->id));
|
|
|
|
// Serial.println(String("prepareJSONChunk: elementcount: ") + String(elementcount));
|
2023-09-09 16:52:28 -04:00
|
|
|
|
2022-09-21 15:37:20 -04:00
|
|
|
items.remove(elementcount);
|
|
|
|
--elementcount;
|
|
|
|
}
|
|
|
|
// exit the loop
|
|
|
|
control = nullptr;
|
|
|
|
}
|
2024-03-05 13:40:01 -05:00
|
|
|
else if ((SingleControl) ||
|
|
|
|
(ControlIsFragmented) ||
|
|
|
|
(MaxMarshaledJsonSize < (EstimatedUsedMarshaledJsonSize + 100)))
|
2023-09-09 15:58:12 -04:00
|
|
|
{
|
2024-03-05 13:40:01 -05:00
|
|
|
// Serial.println("prepareJSONChunk: Doc is Full, Fragmented Control or Single Control. exit loop");
|
2023-09-09 15:58:12 -04:00
|
|
|
control = nullptr;
|
|
|
|
}
|
2022-09-21 15:37:20 -04:00
|
|
|
else
|
|
|
|
{
|
2024-03-05 13:40:01 -05:00
|
|
|
// Serial.println("prepareJSONChunk: Next Control");
|
2022-09-21 15:37:20 -04:00
|
|
|
control = control->next;
|
|
|
|
}
|
|
|
|
} // end while (control != nullptr)
|
|
|
|
|
|
|
|
} while (false);
|
|
|
|
|
|
|
|
#ifdef ESP32
|
|
|
|
xSemaphoreGive(ESPUI.ControlsSemaphore);
|
|
|
|
#endif // def ESP32
|
|
|
|
|
2024-03-05 13:40:01 -05:00
|
|
|
// Serial.println(String("prepareJSONChunk: END: elementcount: ") + String(elementcount));
|
2022-09-21 15:37:20 -04:00
|
|
|
return elementcount;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-09-22 09:34:11 -04:00
|
|
|
Convert & Transfer Arduino elements to JSON elements. This function sends a chunk of
|
2022-09-21 15:37:20 -04:00
|
|
|
JSON describing the controls of the UI, starting from the control at index startidx.
|
|
|
|
If startidx is 0 then a UI_INITIAL_GUI message will be sent, else a UI_EXTEND_GUI.
|
|
|
|
Both message types contain a list of serialised UI elements. Only a portion of the UI
|
2022-09-22 09:34:11 -04:00
|
|
|
will be sent in order to avoid websocket buffer overflows. The client will acknowledge
|
2022-09-21 15:37:20 -04:00
|
|
|
receipt of a partial message by requesting the next chunk of UI.
|
|
|
|
|
|
|
|
The protocol is:
|
2022-09-22 09:34:11 -04:00
|
|
|
SERVER: SendControlsToClient(0):
|
2022-09-21 15:37:20 -04:00
|
|
|
"UI_INITIAL_GUI: n serialised UI elements"
|
|
|
|
CLIENT: controls.js:handleEvent()
|
|
|
|
"uiok:n"
|
|
|
|
SERVER: SendControlsToClient(n):
|
|
|
|
"UI_EXTEND_GUI: n serialised UI elements"
|
|
|
|
CLIENT: controls.js:handleEvent()
|
|
|
|
"uiok:2*n"
|
2022-09-22 09:34:11 -04:00
|
|
|
etc.
|
2022-09-21 15:37:20 -04:00
|
|
|
Returns true if all controls have been sent (aka: Done)
|
|
|
|
*/
|
2023-09-09 15:58:12 -04:00
|
|
|
bool ESPUIclient::SendControlsToClient(uint16_t startidx, ClientUpdateType_t TransferMode, String FragmentRequest)
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
|
|
|
bool Response = false;
|
|
|
|
// Serial.println(String("ESPUIclient:SendControlsToClient:startidx: ") + String(startidx));
|
|
|
|
do // once
|
|
|
|
{
|
|
|
|
if(!CanSend())
|
|
|
|
{
|
|
|
|
// Serial.println("ESPUIclient:SendControlsToClient: Cannot Send to clients.");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2023-09-09 15:58:12 -04:00
|
|
|
else if ((startidx >= ESPUI.controlCount) && (emptyString.equals(FragmentRequest)))
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
2023-09-09 15:58:12 -04:00
|
|
|
// Serial.println(F("ERROR:ESPUIclient:SendControlsToClient: No more controls to send."));
|
2022-09-21 15:37:20 -04:00
|
|
|
Response = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-03-26 16:06:23 -04:00
|
|
|
AllocateJsonDocument(document, ESPUI.jsonInitialDocumentSize);
|
2022-09-21 15:37:20 -04:00
|
|
|
FillInHeader(document);
|
|
|
|
document[F("startindex")] = startidx;
|
2023-09-09 15:58:12 -04:00
|
|
|
document[F("totalcontrols")] = uint16_t(-1); // ESPUI.controlCount;
|
2022-09-21 15:37:20 -04:00
|
|
|
|
|
|
|
if(0 == startidx)
|
|
|
|
{
|
|
|
|
// Serial.println("ESPUIclient:SendControlsToClient: Tell client we are starting a transfer of controls.");
|
|
|
|
document["type"] = (ClientUpdateType_t::RebuildNeeded == TransferMode) ? UI_INITIAL_GUI : UI_EXTEND_GUI;
|
2023-11-09 11:06:44 -05:00
|
|
|
CurrentSyncID = NextSyncID;
|
|
|
|
NextSyncID = ESPUI.GetNextControlChangeId();
|
2022-09-21 15:37:20 -04:00
|
|
|
}
|
|
|
|
// Serial.println(String("ESPUIclient:SendControlsToClient:type: ") + String((uint32_t)document["type"]));
|
|
|
|
|
|
|
|
// Serial.println("ESPUIclient:SendControlsToClient: Build Controls.");
|
2023-09-09 15:58:12 -04:00
|
|
|
if(prepareJSONChunk(startidx, document, ClientUpdateType_t::UpdateNeeded == TransferMode, FragmentRequest))
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
|
|
|
#if defined(DEBUG_ESPUI)
|
|
|
|
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
|
|
|
{
|
|
|
|
Serial.println(F("ESPUIclient:SendControlsToClient: Sending elements --------->"));
|
2023-09-09 16:52:28 -04:00
|
|
|
serializeJson(document, Serial);
|
|
|
|
Serial.println();
|
2022-09-21 15:37:20 -04:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Serial.println("ESPUIclient:SendControlsToClient: Send message.");
|
|
|
|
if(true == SendJsonDocToWebSocket(document))
|
|
|
|
{
|
|
|
|
// Serial.println("ESPUIclient:SendControlsToClient: Sent.");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Serial.println("ESPUIclient:SendControlsToClient: Send failed.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Serial.println("ESPUIclient:SendControlsToClient: No elements to send.");
|
|
|
|
Response = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
} while(false);
|
|
|
|
|
|
|
|
// Serial.println(String("ESPUIclient:SendControlsToClient:Response: ") + String(Response));
|
|
|
|
return Response;
|
|
|
|
}
|
|
|
|
|
2024-03-26 16:06:23 -04:00
|
|
|
bool ESPUIclient::SendJsonDocToWebSocket(JsonDocument& document)
|
2022-09-21 15:37:20 -04:00
|
|
|
{
|
|
|
|
bool Response = true;
|
|
|
|
|
|
|
|
do // once
|
|
|
|
{
|
|
|
|
if (!CanSend())
|
|
|
|
{
|
|
|
|
#if defined(DEBUG_ESPUI)
|
|
|
|
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
|
|
|
{
|
2022-09-22 09:34:11 -04:00
|
|
|
Serial.println(F("ESPUIclient::SendJsonDocToWebSocket: Cannot Send to client. Not sending websocket message"));
|
2022-09-21 15:37:20 -04:00
|
|
|
}
|
|
|
|
#endif
|
2022-09-22 09:34:11 -04:00
|
|
|
// Serial.println("ESPUIclient::SendJsonDocToWebSocket: Cannot Send to client. Not sending websocket message");
|
2022-09-21 15:37:20 -04:00
|
|
|
Response = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2023-09-09 16:52:28 -04:00
|
|
|
String json = JSONSlave::toString(document);
|
2022-09-21 15:37:20 -04:00
|
|
|
|
|
|
|
#if defined(DEBUG_ESPUI)
|
|
|
|
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
|
|
|
{
|
2022-09-22 09:34:11 -04:00
|
|
|
Serial.println(String(F("ESPUIclient::SendJsonDocToWebSocket: json: '")) + json + "'");
|
2022-09-21 15:37:20 -04:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(DEBUG_ESPUI)
|
|
|
|
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
|
|
|
{
|
2022-09-22 09:34:11 -04:00
|
|
|
Serial.println(F("ESPUIclient::SendJsonDocToWebSocket: client.text"));
|
2022-09-21 15:37:20 -04:00
|
|
|
}
|
|
|
|
#endif
|
2022-09-22 09:34:11 -04:00
|
|
|
// Serial.println(F("ESPUIclient::SendJsonDocToWebSocket: client.text"));
|
2022-09-21 15:37:20 -04:00
|
|
|
client->text(json);
|
|
|
|
|
|
|
|
} while (false);
|
|
|
|
|
|
|
|
return Response;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ESPUIclient::SetState(ClientUpdateType_t value)
|
|
|
|
{
|
|
|
|
// only a higher priority state request can replace the current state request
|
|
|
|
if(uint32_t(ClientUpdateType) < uint32_t(value))
|
|
|
|
{
|
|
|
|
ClientUpdateType = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|