mirror of
				https://github.com/s00500/ESPUI.git
				synced 2025-11-04 04:13:23 +00:00 
			
		
		
		
	Changes to support keeping multiple Browser Clients in sync
This commit is contained in:
		@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user