mirror of
https://github.com/s00500/ESPUI.git
synced 2024-12-22 19:30:50 +00:00
Merge pull request #264 from MartinMueller2003/master
Changes to support keeping multiple Browser Clients in sync
This commit is contained in:
commit
8b64b185a4
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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"]));
|
||||
|
||||
|
@ -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);
|
||||
|
||||
};
|
||||
|
@ -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 */
|
||||
}
|
||||
|
@ -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")); }
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user