mirror of
https://github.com/s00500/ESPUI.git
synced 2025-07-05 02:40:18 +00:00
Merge branch 'master' into lambda-with-examples
This commit is contained in:
15
src/ESPUI.h
15
src/ESPUI.h
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
// comment out to turn off debug output
|
||||
#define DEBUG_ESPUI true
|
||||
// #define DEBUG_ESPUI true
|
||||
#define WS_AUTHENTICATION false
|
||||
|
||||
#include <Arduino.h>
|
||||
@ -87,16 +87,13 @@ enum Verbosity : uint8_t
|
||||
class ESPUIClass
|
||||
{
|
||||
public:
|
||||
|
||||
#ifdef ESP32
|
||||
ESPUIClass()
|
||||
{
|
||||
#ifdef ESP32
|
||||
ControlsSemaphore = xSemaphoreCreateMutex();
|
||||
xSemaphoreGive(ControlsSemaphore);
|
||||
}
|
||||
SemaphoreHandle_t ControlsSemaphore = NULL;
|
||||
#endif // def ESP32
|
||||
|
||||
}
|
||||
unsigned int jsonUpdateDocumentSize = 2000;
|
||||
#ifdef ESP8266
|
||||
unsigned int jsonInitialDocumentSize = 2000;
|
||||
@ -196,7 +193,6 @@ public:
|
||||
void jsonDom(uint16_t startidx, AsyncWebSocketClient* client = nullptr, bool Updating = false);
|
||||
|
||||
Verbosity verbosity = Verbosity::Quiet;
|
||||
AsyncWebServer* server;
|
||||
|
||||
// emulate former extended callback API by using an intermediate lambda (no deprecation)
|
||||
uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, std::function<void(Control*, int, void*)> callback, void* userData)
|
||||
@ -240,8 +236,13 @@ protected:
|
||||
friend class ESPUIclient;
|
||||
friend class ESPUIcontrol;
|
||||
|
||||
#ifdef ESP32
|
||||
SemaphoreHandle_t ControlsSemaphore = NULL;
|
||||
#endif // def ESP32
|
||||
|
||||
void RemoveToBeDeletedControls();
|
||||
|
||||
AsyncWebServer* server;
|
||||
AsyncWebSocket* ws;
|
||||
|
||||
const char* basicAuthUsername = nullptr;
|
||||
|
@ -229,7 +229,21 @@ void ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t
|
||||
if (cmd.equals(F("uiok")))
|
||||
{
|
||||
// Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:ProcessAck"));
|
||||
pCurrentFsmState->ProcessAck(id);
|
||||
pCurrentFsmState->ProcessAck(id, emptyString);
|
||||
break;
|
||||
}
|
||||
|
||||
if (cmd.equals(F("uifragmentok")))
|
||||
{
|
||||
if(!emptyString.equals(value))
|
||||
{
|
||||
// Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uifragmentok:ProcessAck:value: '")) + value + "'");
|
||||
pCurrentFsmState->ProcessAck(uint16_t(-1), value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Serial.println(F("ERROR:ESPUIclient::OnWsEvent:WS_EVT_DATA:uifragmentok:ProcessAck:Fragment Header is missing"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -269,7 +283,8 @@ client will acknowledge receipt by requesting the next chunk.
|
||||
*/
|
||||
uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
|
||||
DynamicJsonDocument & rootDoc,
|
||||
bool InUpdateMode)
|
||||
bool InUpdateMode,
|
||||
String FragmentRequestString)
|
||||
{
|
||||
#ifdef ESP32
|
||||
xSemaphoreTake(ESPUI.ControlsSemaphore, portMAX_DELAY);
|
||||
@ -283,8 +298,61 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
|
||||
// Follow the list until control points to the startindex'th node
|
||||
Control* control = ESPUI.controls;
|
||||
uint32_t currentIndex = 0;
|
||||
uint32_t DataOffset = 0;
|
||||
JsonArray items = rootDoc[F("controls")];
|
||||
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.
|
||||
DynamicJsonDocument FragmentRequest(FragmentRequestString.length() * 3);
|
||||
if(0 >= FragmentRequest.capacity())
|
||||
{
|
||||
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Could not allocate memory for a fragmentation request. Skipping Response"));
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if(!FragmentRequest.containsKey(F("id")))
|
||||
{
|
||||
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Request does not contain a control ID"));
|
||||
break;
|
||||
}
|
||||
uint16_t ControlId = uint16_t(FragmentRequest[F("id")]);
|
||||
|
||||
if(!FragmentRequest.containsKey(F("offset")))
|
||||
{
|
||||
Serial.println(F("ERROR:prepareJSONChunk:Fragmentation:Request does not contain a starting offset"));
|
||||
break;
|
||||
}
|
||||
DataOffset = uint16_t(FragmentRequest[F("offset")]);
|
||||
control = ESPUI.getControlNoLock(ControlId);
|
||||
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
|
||||
while ((startindex > currentIndex) && (nullptr != control))
|
||||
{
|
||||
// only count active controls
|
||||
@ -320,14 +388,14 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
|
||||
while (nullptr != control)
|
||||
{
|
||||
// skip deleted controls or controls that have not been updated
|
||||
if (control->ToBeDeleted())
|
||||
if (control->ToBeDeleted() && !SingleControl)
|
||||
{
|
||||
// Serial.println(String("prepareJSONChunk: Ignoring Deleted control: ") + String(control->id));
|
||||
control = control->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(InUpdateMode)
|
||||
if(InUpdateMode && !SingleControl)
|
||||
{
|
||||
if(control->IsUpdated())
|
||||
{
|
||||
@ -343,7 +411,7 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
|
||||
|
||||
JsonObject item = items.createNestedObject();
|
||||
elementcount++;
|
||||
control->MarshalControl(item, InUpdateMode);
|
||||
control->MarshalControl(item, InUpdateMode, DataOffset);
|
||||
|
||||
if (rootDoc.overflowed() || (ESPUI.jsonChunkNumberMax > 0 && (elementcount % ESPUI.jsonChunkNumberMax) == 0))
|
||||
{
|
||||
@ -351,6 +419,7 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
|
||||
if (1 == elementcount)
|
||||
{
|
||||
Serial.println(String(F("ERROR: prepareJSONChunk: Control ")) + String(control->id) + F(" is too large to be sent to the browser."));
|
||||
// Serial.println(String(F("ERROR: prepareJSONChunk: value: ")) + control->value);
|
||||
rootDoc.clear();
|
||||
item = items.createNestedObject();
|
||||
control->MarshalErrorMessage(item);
|
||||
@ -366,7 +435,11 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
|
||||
}
|
||||
// exit the loop
|
||||
control = nullptr;
|
||||
|
||||
}
|
||||
else if (SingleControl)
|
||||
{
|
||||
// Serial.println("prepareJSONChunk: exit loop");
|
||||
control = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -404,8 +477,7 @@ CLIENT: controls.js:handleEvent()
|
||||
etc.
|
||||
Returns true if all controls have been sent (aka: Done)
|
||||
*/
|
||||
bool ESPUIclient::SendControlsToClient(uint16_t startidx,
|
||||
ClientUpdateType_t TransferMode)
|
||||
bool ESPUIclient::SendControlsToClient(uint16_t startidx, ClientUpdateType_t TransferMode, String FragmentRequest)
|
||||
{
|
||||
bool Response = false;
|
||||
// Serial.println(String("ESPUIclient:SendControlsToClient:startidx: ") + String(startidx));
|
||||
@ -417,9 +489,9 @@ bool ESPUIclient::SendControlsToClient(uint16_t startidx,
|
||||
break;
|
||||
}
|
||||
|
||||
if (startidx >= ESPUI.controlCount)
|
||||
else if ((startidx >= ESPUI.controlCount) && (emptyString.equals(FragmentRequest)))
|
||||
{
|
||||
// Serial.println("ESPUIclient:SendControlsToClient: No more controls to send.");
|
||||
// Serial.println(F("ERROR:ESPUIclient:SendControlsToClient: No more controls to send."));
|
||||
Response = true;
|
||||
break;
|
||||
}
|
||||
@ -427,7 +499,7 @@ bool ESPUIclient::SendControlsToClient(uint16_t startidx,
|
||||
DynamicJsonDocument document(ESPUI.jsonInitialDocumentSize);
|
||||
FillInHeader(document);
|
||||
document[F("startindex")] = startidx;
|
||||
document[F("totalcontrols")] = 65534; // ESPUI.controlCount;
|
||||
document[F("totalcontrols")] = uint16_t(-1); // ESPUI.controlCount;
|
||||
|
||||
if(0 == startidx)
|
||||
{
|
||||
@ -437,7 +509,7 @@ bool ESPUIclient::SendControlsToClient(uint16_t startidx,
|
||||
// Serial.println(String("ESPUIclient:SendControlsToClient:type: ") + String((uint32_t)document["type"]));
|
||||
|
||||
// Serial.println("ESPUIclient:SendControlsToClient: Build Controls.");
|
||||
if(prepareJSONChunk(startidx, document, ClientUpdateType_t::UpdateNeeded == TransferMode))
|
||||
if(prepareJSONChunk(startidx, document, ClientUpdateType_t::UpdateNeeded == TransferMode, FragmentRequest))
|
||||
{
|
||||
#if defined(DEBUG_ESPUI)
|
||||
if (ESPUI.verbosity >= Verbosity::VerboseJSON)
|
||||
|
@ -44,8 +44,8 @@ protected:
|
||||
|
||||
bool CanSend();
|
||||
void FillInHeader(ArduinoJson::DynamicJsonDocument& document);
|
||||
uint32_t prepareJSONChunk(uint16_t startindex, DynamicJsonDocument& rootDoc, bool InUpdateMode);
|
||||
bool SendControlsToClient(uint16_t startidx, ClientUpdateType_t TransferMode);
|
||||
uint32_t prepareJSONChunk(uint16_t startindex, DynamicJsonDocument& rootDoc, bool InUpdateMode, String value);
|
||||
bool SendControlsToClient(uint16_t startidx, ClientUpdateType_t TransferMode, String FragmentRequest);
|
||||
|
||||
bool SendClientNotification(ClientUpdateType_t value);
|
||||
|
||||
|
@ -58,12 +58,20 @@ bool fsm_EspuiClient_state_Idle::NotifyClient()
|
||||
return Response;
|
||||
}
|
||||
|
||||
void fsm_EspuiClient_state_Idle::ProcessAck(uint16_t)
|
||||
void fsm_EspuiClient_state_Idle::ProcessAck(uint16_t ControlIndex, String FragmentRequestString)
|
||||
{
|
||||
// This is an unexpected request for control data from the browser
|
||||
// treat it as if it was a rebuild operation
|
||||
// Serial.println(F("fsm_EspuiClient_state_Idle: ProcessAck"));
|
||||
Parent->NotifyClient(ClientUpdateType_t::RebuildNeeded);
|
||||
if(!emptyString.equals(FragmentRequestString))
|
||||
{
|
||||
// Serial.println(F("fsm_EspuiClient_state_Idle::ProcessAck:Fragmentation:Got fragment Header"));
|
||||
Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::UpdateNeeded, FragmentRequestString);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is an unexpected request for control data from the browser
|
||||
// treat it as if it was a rebuild operation
|
||||
// Serial.println(F("fsm_EspuiClient_state_Idle: ProcessAck"));
|
||||
Parent->NotifyClient(ClientUpdateType_t::RebuildNeeded);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------
|
||||
@ -75,10 +83,10 @@ bool fsm_EspuiClient_state_SendingUpdate::NotifyClient()
|
||||
return true; /* Ignore request */
|
||||
}
|
||||
|
||||
void fsm_EspuiClient_state_SendingUpdate::ProcessAck(uint16_t ControlIndex)
|
||||
void fsm_EspuiClient_state_SendingUpdate::ProcessAck(uint16_t ControlIndex, String FragmentRequest)
|
||||
{
|
||||
// Serial.println(F("fsm_EspuiClient_state_SendingUpdate: ProcessAck"));
|
||||
if(Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::UpdateNeeded))
|
||||
if(Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::UpdateNeeded, FragmentRequest))
|
||||
{
|
||||
// No more data to send. Go back to idle or start next request
|
||||
Parent->fsm_EspuiClient_state_Idle_imp.Init();
|
||||
@ -95,13 +103,25 @@ bool fsm_EspuiClient_state_Rebuilding::NotifyClient()
|
||||
return true; /* Ignore request */
|
||||
}
|
||||
|
||||
void fsm_EspuiClient_state_Rebuilding::ProcessAck(uint16_t ControlIndex)
|
||||
void fsm_EspuiClient_state_Rebuilding::ProcessAck(uint16_t ControlIndex, String FragmentRequest)
|
||||
{
|
||||
// Serial.println(F("fsm_EspuiClient_state_Rebuilding: ProcessAck"));
|
||||
if(Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::RebuildNeeded))
|
||||
if(Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::RebuildNeeded, FragmentRequest))
|
||||
{
|
||||
// No more data to send. Go back to idle or start next request
|
||||
Parent->fsm_EspuiClient_state_Idle_imp.Init();
|
||||
Parent->fsm_EspuiClient_state_Idle_imp.NotifyClient();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------
|
||||
//----------------------------------------------
|
||||
//----------------------------------------------
|
||||
void fsm_EspuiClient_state_Reloading::ProcessAck(uint16_t ControlIndex, String FragmentRequestString)
|
||||
{
|
||||
if(!emptyString.equals(FragmentRequestString))
|
||||
{
|
||||
// Serial.println(F("fsm_EspuiClient_state_Reloading::ProcessAck:Fragmentation:Got fragment Header"));
|
||||
Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::UpdateNeeded, FragmentRequestString);
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ public:
|
||||
|
||||
void Init();
|
||||
virtual bool NotifyClient() = 0;
|
||||
virtual void ProcessAck(uint16_t id) = 0;
|
||||
virtual void ProcessAck(uint16_t id, String FragmentRequest) = 0;
|
||||
virtual String GetStateName () = 0;
|
||||
void SetParent(ESPUIclient * value) { Parent = value; }
|
||||
|
||||
@ -36,7 +36,7 @@ public:
|
||||
virtual ~fsm_EspuiClient_state_Idle() {}
|
||||
|
||||
virtual bool NotifyClient();
|
||||
virtual void ProcessAck(uint16_t id);
|
||||
virtual void ProcessAck(uint16_t id, String FragmentRequest);
|
||||
String GetStateName() { return String(F("Idle")); }
|
||||
|
||||
}; // fsm_EspuiClient_state_Idle
|
||||
@ -48,7 +48,7 @@ public:
|
||||
virtual ~fsm_EspuiClient_state_SendingUpdate() {}
|
||||
|
||||
virtual bool NotifyClient();
|
||||
virtual void ProcessAck(uint16_t id);
|
||||
virtual void ProcessAck(uint16_t id, String FragmentRequest);
|
||||
String GetStateName() { return String(F("Sending Update")); }
|
||||
|
||||
}; // fsm_EspuiClient_state_SendingUpdate
|
||||
@ -60,7 +60,7 @@ public:
|
||||
virtual ~fsm_EspuiClient_state_Rebuilding() {}
|
||||
|
||||
virtual bool NotifyClient();
|
||||
virtual void ProcessAck(uint16_t id);
|
||||
virtual void ProcessAck(uint16_t id, String FragmentRequest);
|
||||
String GetStateName() { return String(F("Sending Rebuild")); }
|
||||
|
||||
}; // fsm_EspuiClient_state_Rebuilding
|
||||
@ -72,7 +72,7 @@ public:
|
||||
virtual ~fsm_EspuiClient_state_Reloading() {}
|
||||
|
||||
virtual bool NotifyClient() { return false; }
|
||||
virtual void ProcessAck(uint16_t) {}
|
||||
virtual void ProcessAck(uint16_t id, String FragmentRequest);
|
||||
String GetStateName() { return String(F("Reloading")); }
|
||||
|
||||
}; // fsm_EspuiClient_state_Reloading
|
||||
|
@ -46,8 +46,43 @@ void Control::DeleteControl()
|
||||
callback = nullptr;
|
||||
}
|
||||
|
||||
void Control::MarshalControl(JsonObject & item, bool refresh)
|
||||
void Control::MarshalControl(JsonObject & _item, bool refresh, uint32_t StartingOffset)
|
||||
{
|
||||
JsonObject & item = _item;
|
||||
uint32_t length = value.length();
|
||||
uint32_t maxLength = uint32_t(double(ESPUI.jsonInitialDocumentSize) * 0.75);
|
||||
if((length > maxLength) || StartingOffset)
|
||||
{
|
||||
/*
|
||||
Serial.println(String("MarshalControl:Start Fragment Processing"));
|
||||
Serial.println(String("MarshalControl:id: ") + String(id));
|
||||
Serial.println(String("MarshalControl:length: ") + String(length));
|
||||
Serial.println(String("MarshalControl:StartingOffset: ") + String(StartingOffset));
|
||||
Serial.println(String("MarshalControl:maxLength: ") + String(maxLength));
|
||||
|
||||
if(0 == StartingOffset)
|
||||
{
|
||||
Serial.println(String("MarshalControl: New control to fragement. ID: ") + String(id));
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println(String("MarshalControl: Next fragement. ID: ") + String(id));
|
||||
}
|
||||
*/
|
||||
|
||||
// indicate that no additional controls should be sent
|
||||
_item[F("type")] = uint32_t(ControlType::Fragment);
|
||||
_item[F("id")] = id;
|
||||
|
||||
length = min((length - StartingOffset), maxLength);
|
||||
// Serial.println(String("MarshalControl:Final length: ") + String(length));
|
||||
|
||||
_item[F("offset")] = StartingOffset;
|
||||
_item[F("length")] = length;
|
||||
_item[F("total")] = value.length();
|
||||
item = _item.createNestedObject(F("control"));
|
||||
}
|
||||
|
||||
item[F("id")] = id;
|
||||
ControlType TempType = (ControlType::Password == type) ? ControlType::Text : type;
|
||||
if(refresh)
|
||||
@ -58,8 +93,9 @@ void Control::MarshalControl(JsonObject & item, bool refresh)
|
||||
{
|
||||
item[F("type")] = uint32_t(TempType);
|
||||
}
|
||||
|
||||
item[F("label")] = label;
|
||||
item[F ("value")] = (ControlType::Password == type) ? F ("--------") : value;
|
||||
item[F ("value")] = (ControlType::Password == type) ? F ("--------") : value.substring(StartingOffset, length + StartingOffset);
|
||||
item[F("visible")] = visible;
|
||||
item[F("color")] = (int)color;
|
||||
item[F("enabled")] = enabled;
|
||||
|
@ -31,6 +31,7 @@ enum ControlType : uint8_t
|
||||
Separator,
|
||||
Time,
|
||||
|
||||
Fragment,
|
||||
Password = 99,
|
||||
UpdateOffset = 100,
|
||||
};
|
||||
@ -81,7 +82,7 @@ public:
|
||||
|
||||
void SendCallback(int type);
|
||||
bool HasCallback() { return (nullptr != callback); }
|
||||
void MarshalControl(ArduinoJson::JsonObject& item, bool refresh);
|
||||
void MarshalControl(ArduinoJson::JsonObject& item, bool refresh, uint32_t DataOffset);
|
||||
void MarshalErrorMessage(ArduinoJson::JsonObject& item);
|
||||
bool ToBeDeleted() { return (ControlSyncState_t::deleted == ControlSyncState); }
|
||||
void DeleteControl();
|
||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user