Changes to support keeping multiple Browser Clients in sync

This commit is contained in:
MartinMueller2003 2023-11-09 11:06:44 -05:00
parent 85ccff0ee0
commit 00841ce32d
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.");
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;
}
@ -854,11 +857,21 @@ void ESPUIClass::updateControl(Control* control, int)
{
return;
}
// tel the control it has been updated
control->HasBeenUpdated();
// tell the control it has been updated
control->SetControlChangedId(ESPUI.GetNextControlChangeId());
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)
{
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()
{
for (auto& CurrentClient : MapOfClients)

View File

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

View File

@ -100,7 +100,7 @@ bool ESPUIclient::SendClientNotification(ClientUpdateType_t value)
{
if(!CanSend())
{
// Serial.println(F("ESPUIclient::NotifyClient"));
// Serial.println(F("ESPUIclient::SendClientNotification:CannotSend"));
break;
}
@ -124,42 +124,12 @@ void ESPUIclient::NotifyClient(ClientUpdateType_t newState)
{
SetState(newState);
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
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));
switch (type)
@ -228,21 +198,23 @@ 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"));
// Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:ProcessAck:")) + pCurrentFsmState->GetStateName());
pCurrentFsmState->ProcessAck(id, emptyString);
break;
}
if (cmd.equals(F("uifragmentok")))
{
// Serial.println(String(F("ESPUIclient::OnWsEvent:WS_EVT_DATA:uiok:uifragmentok:")) + pCurrentFsmState->GetStateName() + ":ProcessAck");
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);
}
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;
}
@ -253,6 +225,7 @@ void ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t
break;
}
// Serial.println(F("WS_EVT_DATA:Process Control"));
Control* control = ESPUI.getControl(id);
if (nullptr == control)
{
@ -265,6 +238,8 @@ void ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t
break;
}
control->onWsEvent(cmd, value);
// notify other clients of change
Response = true;
break;
}
@ -274,6 +249,8 @@ void ESPUIclient::onWsEvent(AwsEventType type, void* arg, uint8_t* data, size_t
break;
}
} // end switch
return Response;
}
/*
@ -361,7 +338,7 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
if(InUpdateMode)
{
// In update mode we only count the controls that have been updated.
if(control->IsUpdated())
if(control->NeedsSync(CurrentSyncID))
{
++currentIndex;
}
@ -397,7 +374,7 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
if(InUpdateMode && !SingleControl)
{
if(control->IsUpdated())
if(control->NeedsSync(CurrentSyncID))
{
// 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.");
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"]));

View File

@ -49,14 +49,19 @@ protected:
bool SendClientNotification(ClientUpdateType_t value);
private:
uint32_t CurrentSyncID = 0;
uint32_t NextSyncID = 0;
public:
ESPUIclient(AsyncWebSocketClient * _client);
ESPUIclient(const ESPUIclient & source);
virtual ~ESPUIclient();
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();
uint32_t id() { return client->id(); }
void SetState(ClientUpdateType_t value);
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
// 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);
}
}
@ -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()
{
// 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)
{
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);
}
}
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() {}
virtual ~fsm_EspuiClient_state_Rebuilding() {}
void Init();
virtual bool NotifyClient();
virtual void ProcessAck(uint16_t id, String FragmentRequest);
String GetStateName() { return String(F("Sending Rebuild")); }
@ -71,7 +72,8 @@ public:
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);
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)
{
id = ++idCounter;
ControlChangeID = 1;
}
Control::Control(const Control& Control)
@ -29,7 +30,8 @@ Control::Control(const Control& Control)
color(Control.color),
visible(Control.visible),
parentControl(Control.parentControl),
next(Control.next)
next(Control.next),
ControlChangeID(Control.ControlChangeID)
{ }
void Control::SendCallback(int type)
@ -42,7 +44,7 @@ void Control::SendCallback(int type)
void Control::DeleteControl()
{
ControlSyncState = ControlSyncState_t::deleted;
_ToBeDeleted = true;
callback = nullptr;
}
@ -150,6 +152,8 @@ void Control::onWsEvent(String & cmd, String& data)
{
do // once
{
// Serial.println(String(F("Control::onWsEvent")));
SetControlChangedId(ESPUI.GetNextControlChangeId());
if (!HasCallback())
{
#if defined(DEBUG_ESPUI)

View File

@ -84,21 +84,15 @@ public:
bool HasCallback() { return (nullptr != callback); }
void MarshalControl(ArduinoJson::JsonObject& item, bool refresh, uint32_t DataOffset);
void MarshalErrorMessage(ArduinoJson::JsonObject& item);
bool ToBeDeleted() { return (ControlSyncState_t::deleted == ControlSyncState); }
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);
inline bool ToBeDeleted() { return _ToBeDeleted; }
inline bool NeedsSync(uint32_t lastControlChangeID) {return (false == _ToBeDeleted) && (lastControlChangeID < ControlChangeID);}
void SetControlChangedId(uint32_t value) {ControlChangeID = value;}
private:
enum ControlSyncState_t
{
synchronized = 0,
updated,
deleted,
};
ControlSyncState_t ControlSyncState = ControlSyncState_t::synchronized;
bool _ToBeDeleted = false;
uint32_t ControlChangeID = 0;
};
#define UI_TITLE ControlType::Title