Merge pull request #264 from MartinMueller2003/master

Changes to support keeping multiple Browser Clients in sync
This commit is contained in:
Lukas Bachschwell 2023-11-09 18:15:04 +01:00 committed by GitHub
commit 8b64b185a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 81 additions and 86 deletions

View File

@ -601,10 +601,13 @@ void ESPUIClass::onWsEvent(
// Serial.println("ESPUIClass::OnWsEvent:Create new client."); // Serial.println("ESPUIClass::OnWsEvent:Create new client.");
MapOfClients[client->id()] = new ESPUIclient(client); MapOfClients[client->id()] = new ESPUIclient(client);
} }
MapOfClients[client->id()]->onWsEvent(type, arg, data, len);
}
ClearControlUpdateFlags(); if(MapOfClients[client->id()]->onWsEvent(type, arg, data, len))
{
// Serial.println("ESPUIClass::OnWsEvent:notify the clients that they need to be updated.");
NotifyClients(ESPUIclient::UpdateNeeded);
}
}
return; return;
} }
@ -854,11 +857,21 @@ void ESPUIClass::updateControl(Control* control, int)
{ {
return; return;
} }
// tel the control it has been updated // tell the control it has been updated
control->HasBeenUpdated(); control->SetControlChangedId(ESPUI.GetNextControlChangeId());
NotifyClients(ClientUpdateType_t::UpdateNeeded); NotifyClients(ClientUpdateType_t::UpdateNeeded);
} }
uint32_t ESPUIClass::GetNextControlChangeId()
{
if(uint32_t(-1) == ControlChangeID)
{
// force a reload which resets the counters
jsonReload();
}
return ++ControlChangeID;
}
void ESPUIClass::setPanelStyle(uint16_t id, const String& style, int clientId) void ESPUIClass::setPanelStyle(uint16_t id, const String& style, int clientId)
{ {
Control* control = getControl(id); Control* control = getControl(id);
@ -1126,30 +1139,6 @@ void ESPUIClass::NotifyClients(ClientUpdateType_t newState)
} }
} }
void ESPUIClass::ClearControlUpdateFlags()
{
bool CanClearUpdateFlags = true;
for (auto& CurrentClient : MapOfClients)
{
if (!CurrentClient.second->IsSyncronized())
{
CanClearUpdateFlags = false;
break;
}
}
if (CanClearUpdateFlags)
{
Control* control = controls;
while (nullptr != control)
{
control->HasBeenSynchronized();
control = control->next;
}
}
}
void ESPUIClass::jsonReload() void ESPUIClass::jsonReload()
{ {
for (auto& CurrentClient : MapOfClients) for (auto& CurrentClient : MapOfClients)

View File

@ -193,7 +193,7 @@ public:
void jsonDom(uint16_t startidx, AsyncWebSocketClient* client = nullptr, bool Updating = false); void jsonDom(uint16_t startidx, AsyncWebSocketClient* client = nullptr, bool Updating = false);
Verbosity verbosity = Verbosity::Quiet; Verbosity verbosity = Verbosity::Quiet;
uint32_t GetNextControlChangeId();
// emulate former extended callback API by using an intermediate lambda (no deprecation) // 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) 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)
{ {
@ -255,12 +255,12 @@ protected:
#define ClientUpdateType_t ESPUIclient::ClientUpdateType_t #define ClientUpdateType_t ESPUIclient::ClientUpdateType_t
void NotifyClients(ClientUpdateType_t newState); void NotifyClients(ClientUpdateType_t newState);
void NotifyClient(uint32_t WsClientId, ClientUpdateType_t newState); void NotifyClient(uint32_t WsClientId, ClientUpdateType_t newState);
void ClearControlUpdateFlags();
bool SendJsonDocToWebSocket(ArduinoJson::DynamicJsonDocument& document, uint16_t clientId); bool SendJsonDocToWebSocket(ArduinoJson::DynamicJsonDocument& document, uint16_t clientId);
std::map<uint32_t, ESPUIclient*> MapOfClients; std::map<uint32_t, ESPUIclient*> MapOfClients;
uint32_t ControlChangeID = 0;
}; };
extern ESPUIClass ESPUI; extern ESPUIClass ESPUI;

View File

@ -100,7 +100,7 @@ bool ESPUIclient::SendClientNotification(ClientUpdateType_t value)
{ {
if(!CanSend()) if(!CanSend())
{ {
// Serial.println(F("ESPUIclient::NotifyClient")); // Serial.println(F("ESPUIclient::SendClientNotification:CannotSend"));
break; break;
} }
@ -124,42 +124,12 @@ void ESPUIclient::NotifyClient(ClientUpdateType_t newState)
{ {
SetState(newState); SetState(newState);
pCurrentFsmState->NotifyClient(); pCurrentFsmState->NotifyClient();
#ifdef OldWay
do // once
{
// Serial.println(String("ESPUIclient::NotifyClient: State: ") + String(int(newState)));
SetState(newState);
if (HasBeenNotified)
{
// do not need to do anything
break;
}
if(TransferIsInprogress)
{
// record that a notification was needed while we were transfering data to the client
DelayedNotification = true;
break;
}
DelayedNotification = false;
if (SendJsonDocToWebSocket(document))
{
HasBeenNotified = true;
}
} while (false);
return HasBeenNotified;
#endif // def OldWay
} }
// Handle Websockets Communication // Handle Websockets Communication
void ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t len) bool ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t len)
{ {
bool Response = false;
// Serial.println(String("ESPUIclient::OnWsEvent: type: ") + String(type)); // Serial.println(String("ESPUIclient::OnWsEvent: type: ") + String(type));
switch (type) switch (type)
@ -228,21 +198,23 @@ void ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t
if (cmd.equals(F("uiok"))) if (cmd.equals(F("uiok")))
{ {
// Serial.println(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:ProcessAck"));
// Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:ProcessAck:")) + pCurrentFsmState->GetStateName());
pCurrentFsmState->ProcessAck(id, emptyString); pCurrentFsmState->ProcessAck(id, emptyString);
break; break;
} }
if (cmd.equals(F("uifragmentok"))) if (cmd.equals(F("uifragmentok")))
{ {
// Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:uifragmentok:")) + pCurrentFsmState->GetStateName() + ":ProcessAck");
if(!emptyString.equals(value)) if(!emptyString.equals(value))
{ {
// Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uifragmentok:ProcessAck:value: '")) + value + "'"); // Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:uifragmentok:")) + pCurrentFsmState->GetStateName() + ":ProcessAck:value:'" + value + "'");
pCurrentFsmState->ProcessAck(uint16_t(-1), value); pCurrentFsmState->ProcessAck(uint16_t(-1), value);
} }
else else
{ {
// Serial.println(F("ERROR:ESPUIclient::OnWsEvent:WS_EVT_DATA:uifragmentok:ProcessAck:Fragment Header is missing")); Serial.println(F("ERROR:ESPUIclient::OnWsEvent:WS_EVT_DATA:uifragmentok:ProcessAck:Fragment Header is missing"));
} }
break; break;
} }
@ -253,6 +225,7 @@ void ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t
break; break;
} }
// Serial.println(F("WS_EVT_DATA:Process Control"));
Control* control = ESPUI.getControl(id); Control* control = ESPUI.getControl(id);
if (nullptr == control) if (nullptr == control)
{ {
@ -265,6 +238,8 @@ void ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t
break; break;
} }
control->onWsEvent(cmd, value); control->onWsEvent(cmd, value);
// notify other clients of change
Response = true;
break; break;
} }
@ -274,6 +249,8 @@ void ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t
break; break;
} }
} // end switch } // end switch
return Response;
} }
/* /*
@ -361,7 +338,7 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
if(InUpdateMode) if(InUpdateMode)
{ {
// In update mode we only count the controls that have been updated. // In update mode we only count the controls that have been updated.
if(control->IsUpdated()) if(control->NeedsSync(CurrentSyncID))
{ {
++currentIndex; ++currentIndex;
} }
@ -397,7 +374,7 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
if(InUpdateMode && !SingleControl) if(InUpdateMode && !SingleControl)
{ {
if(control->IsUpdated()) if(control->NeedsSync(CurrentSyncID))
{ {
// dont skip this control // dont skip this control
} }
@ -505,6 +482,8 @@ bool ESPUIclient::SendControlsToClient(uint16_t startidx, ClientUpdateType_t Tra
{ {
// Serial.println("ESPUIclient:SendControlsToClient: Tell client we are starting a transfer of controls."); // 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; document["type"] = (ClientUpdateType_t::RebuildNeeded == TransferMode) ? UI_INITIAL_GUI : UI_EXTEND_GUI;
CurrentSyncID = NextSyncID;
NextSyncID = ESPUI.GetNextControlChangeId();
} }
// Serial.println(String("ESPUIclient:SendControlsToClient:type: ") + String((uint32_t)document["type"])); // Serial.println(String("ESPUIclient:SendControlsToClient:type: ") + String((uint32_t)document["type"]));

View File

@ -49,14 +49,19 @@ protected:
bool SendClientNotification(ClientUpdateType_t value); bool SendClientNotification(ClientUpdateType_t value);
private:
uint32_t CurrentSyncID = 0;
uint32_t NextSyncID = 0;
public: public:
ESPUIclient(AsyncWebSocketClient * _client); ESPUIclient(AsyncWebSocketClient * _client);
ESPUIclient(const ESPUIclient & source); ESPUIclient(const ESPUIclient & source);
virtual ~ESPUIclient(); virtual ~ESPUIclient();
void NotifyClient(ClientUpdateType_t value); void NotifyClient(ClientUpdateType_t value);
void onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t len); bool onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t len);
bool IsSyncronized(); bool IsSyncronized();
uint32_t id() { return client->id(); } uint32_t id() { return client->id(); }
void SetState(ClientUpdateType_t value); void SetState(ClientUpdateType_t value);
bool SendJsonDocToWebSocket(ArduinoJson::DynamicJsonDocument& document); bool SendJsonDocToWebSocket(ArduinoJson::DynamicJsonDocument& document);
}; };

View File

@ -69,7 +69,7 @@ void fsm_EspuiClient_state_Idle::ProcessAck(uint16_t ControlIndex, String Fragme
{ {
// This is an unexpected request for control data from the browser // This is an unexpected request for control data from the browser
// treat it as if it was a rebuild operation // treat it as if it was a rebuild operation
// Serial.println(F("fsm_EspuiClient_state_Idle: ProcessAck")); // Serial.println(F("fsm_EspuiClient_state_Idle: ProcessAck:Error: Rebuild"));
Parent->NotifyClient(ClientUpdateType_t::RebuildNeeded); Parent->NotifyClient(ClientUpdateType_t::RebuildNeeded);
} }
} }
@ -97,6 +97,14 @@ void fsm_EspuiClient_state_SendingUpdate::ProcessAck(uint16_t ControlIndex, Stri
//---------------------------------------------- //----------------------------------------------
//---------------------------------------------- //----------------------------------------------
//---------------------------------------------- //----------------------------------------------
void fsm_EspuiClient_state_Rebuilding::Init()
{
// Serial.println(String("fsm_EspuiClient_state:Init: ") + GetStateName());
Parent->CurrentSyncID = 0;
Parent->NextSyncID = 0;
Parent->pCurrentFsmState = this;
}
bool fsm_EspuiClient_state_Rebuilding::NotifyClient() bool fsm_EspuiClient_state_Rebuilding::NotifyClient()
{ {
// Serial.println(F("fsm_EspuiClient_state_Rebuilding: NotifyClient")); // Serial.println(F("fsm_EspuiClient_state_Rebuilding: NotifyClient"));
@ -117,6 +125,14 @@ void fsm_EspuiClient_state_Rebuilding::ProcessAck(uint16_t ControlIndex, String
//---------------------------------------------- //----------------------------------------------
//---------------------------------------------- //----------------------------------------------
//---------------------------------------------- //----------------------------------------------
void fsm_EspuiClient_state_Reloading::Init()
{
// Serial.println(String("fsm_EspuiClient_state:Init: ") + GetStateName());
Parent->CurrentSyncID = 0;
Parent->NextSyncID = 0;
Parent->pCurrentFsmState = this;
}
void fsm_EspuiClient_state_Reloading::ProcessAck(uint16_t ControlIndex, String FragmentRequestString) void fsm_EspuiClient_state_Reloading::ProcessAck(uint16_t ControlIndex, String FragmentRequestString)
{ {
if(!emptyString.equals(FragmentRequestString)) if(!emptyString.equals(FragmentRequestString))
@ -125,3 +141,9 @@ void fsm_EspuiClient_state_Reloading::ProcessAck(uint16_t ControlIndex, String F
Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::UpdateNeeded, FragmentRequestString); Parent->SendControlsToClient(ControlIndex, ClientUpdateType_t::UpdateNeeded, FragmentRequestString);
} }
} }
bool fsm_EspuiClient_state_Reloading::NotifyClient()
{
// Serial.println(F("fsm_EspuiClient_state_Reloading: NotifyClient"));
return true; /* Ignore request */
}

View File

@ -59,6 +59,7 @@ public:
fsm_EspuiClient_state_Rebuilding() {} fsm_EspuiClient_state_Rebuilding() {}
virtual ~fsm_EspuiClient_state_Rebuilding() {} virtual ~fsm_EspuiClient_state_Rebuilding() {}
void Init();
virtual bool NotifyClient(); virtual bool NotifyClient();
virtual void ProcessAck(uint16_t id, String FragmentRequest); virtual void ProcessAck(uint16_t id, String FragmentRequest);
String GetStateName() { return String(F("Sending Rebuild")); } String GetStateName() { return String(F("Sending Rebuild")); }
@ -71,7 +72,8 @@ public:
fsm_EspuiClient_state_Reloading() {} fsm_EspuiClient_state_Reloading() {}
virtual ~fsm_EspuiClient_state_Reloading() {} virtual ~fsm_EspuiClient_state_Reloading() {}
virtual bool NotifyClient() { return false; } void Init();
virtual bool NotifyClient();
virtual void ProcessAck(uint16_t id, String FragmentRequest); virtual void ProcessAck(uint16_t id, String FragmentRequest);
String GetStateName() { return String(F("Reloading")); } String GetStateName() { return String(F("Reloading")); }

View File

@ -18,6 +18,7 @@ Control::Control(ControlType type, const char* label, std::function<void(Control
next(nullptr) next(nullptr)
{ {
id = ++idCounter; id = ++idCounter;
ControlChangeID = 1;
} }
Control::Control(const Control& Control) Control::Control(const Control& Control)
@ -29,7 +30,8 @@ Control::Control(const Control& Control)
color(Control.color), color(Control.color),
visible(Control.visible), visible(Control.visible),
parentControl(Control.parentControl), parentControl(Control.parentControl),
next(Control.next) next(Control.next),
ControlChangeID(Control.ControlChangeID)
{ } { }
void Control::SendCallback(int type) void Control::SendCallback(int type)
@ -42,7 +44,7 @@ void Control::SendCallback(int type)
void Control::DeleteControl() void Control::DeleteControl()
{ {
ControlSyncState = ControlSyncState_t::deleted; _ToBeDeleted = true;
callback = nullptr; callback = nullptr;
} }
@ -150,6 +152,8 @@ void Control::onWsEvent(String & cmd, String& data)
{ {
do // once do // once
{ {
// Serial.println(String(F("Control::onWsEvent")));
SetControlChangedId(ESPUI.GetNextControlChangeId());
if (!HasCallback()) if (!HasCallback())
{ {
#if defined(DEBUG_ESPUI) #if defined(DEBUG_ESPUI)

View File

@ -84,21 +84,15 @@ public:
bool HasCallback() { return (nullptr != callback); } bool HasCallback() { return (nullptr != callback); }
void MarshalControl(ArduinoJson::JsonObject& item, bool refresh, uint32_t DataOffset); void MarshalControl(ArduinoJson::JsonObject& item, bool refresh, uint32_t DataOffset);
void MarshalErrorMessage(ArduinoJson::JsonObject& item); void MarshalErrorMessage(ArduinoJson::JsonObject& item);
bool ToBeDeleted() { return (ControlSyncState_t::deleted == ControlSyncState); }
void DeleteControl(); void DeleteControl();
bool IsUpdated() { return ControlSyncState_t::synchronized != ControlSyncState; }
void HasBeenUpdated() { ControlSyncState = ControlSyncState_t::updated; }
void HasBeenSynchronized() {ControlSyncState = ControlSyncState_t::synchronized;}
void onWsEvent(String& cmd, String& data); void onWsEvent(String& cmd, String& data);
inline bool ToBeDeleted() { return _ToBeDeleted; }
inline bool NeedsSync(uint32_t lastControlChangeID) {return (false == _ToBeDeleted) && (lastControlChangeID < ControlChangeID);}
void SetControlChangedId(uint32_t value) {ControlChangeID = value;}
private: private:
enum ControlSyncState_t bool _ToBeDeleted = false;
{ uint32_t ControlChangeID = 0;
synchronized = 0,
updated,
deleted,
};
ControlSyncState_t ControlSyncState = ControlSyncState_t::synchronized;
}; };
#define UI_TITLE ControlType::Title #define UI_TITLE ControlType::Title