mirror of
https://github.com/s00500/ESPUI.git
synced 2024-11-25 14:10:54 +00:00
Made Type and ID private and added accessors for them.
This commit is contained in:
parent
610ed1275e
commit
bddcde4e57
@ -7,8 +7,8 @@
|
||||
class Control
|
||||
{
|
||||
public:
|
||||
enum Type : uint8_t
|
||||
{
|
||||
enum Type : uint8_t
|
||||
{
|
||||
// fixed Controls
|
||||
Title = 0,
|
||||
|
||||
@ -38,10 +38,10 @@ enum Type : uint8_t
|
||||
Fragment = 98,
|
||||
Password = 99,
|
||||
UpdateOffset = 100,
|
||||
};
|
||||
};
|
||||
|
||||
enum Color : uint8_t
|
||||
{
|
||||
enum Color : uint8_t
|
||||
{
|
||||
Turquoise,
|
||||
Emerald,
|
||||
Peterriver,
|
||||
@ -51,10 +51,10 @@ enum Color : uint8_t
|
||||
Alizarin,
|
||||
Dark,
|
||||
None = 0xFF
|
||||
};
|
||||
};
|
||||
|
||||
typedef uint16_t ControlId_t;
|
||||
|
||||
Type type;
|
||||
uint16_t id; // just mirroring the id here for practical reasons
|
||||
const char* label;
|
||||
std::function<void(Control*, int)> callback;
|
||||
String value;
|
||||
@ -63,15 +63,15 @@ enum Color : uint8_t
|
||||
bool wide;
|
||||
bool vertical;
|
||||
bool enabled;
|
||||
uint16_t parentControl;
|
||||
ControlId_t parentControl;
|
||||
String panelStyle;
|
||||
String elementStyle;
|
||||
String inputType;
|
||||
Control* next;
|
||||
|
||||
static constexpr uint16_t noParent = 0xffff;
|
||||
|
||||
Control(Type type,
|
||||
Control(ControlId_t id,
|
||||
Type type,
|
||||
const char* label,
|
||||
std::function<void(Control*, int)> callback,
|
||||
const String& value,
|
||||
@ -90,7 +90,8 @@ enum Color : uint8_t
|
||||
inline bool ToBeDeleted() { return _ToBeDeleted; }
|
||||
inline bool NeedsSync(uint32_t lastControlChangeID) {return (false == _ToBeDeleted) && (lastControlChangeID < ControlChangeID);}
|
||||
void SetControlChangedId(uint32_t value) {ControlChangeID = value;}
|
||||
|
||||
inline ControlId_t GetId() {return id;}
|
||||
inline Type GetType() {return type;}
|
||||
|
||||
#define UI_TITLE Control::Type::Title
|
||||
#define UI_LABEL Control::Type::Label
|
||||
@ -123,6 +124,9 @@ enum Color : uint8_t
|
||||
#define COLOR_NONE Control::Color::None
|
||||
|
||||
private:
|
||||
Type type = Type::Title;
|
||||
ControlId_t id = Control::noParent;
|
||||
|
||||
bool _ToBeDeleted = false;
|
||||
uint32_t ControlChangeID = 0;
|
||||
String OldValue = emptyString;
|
||||
|
371
src/ESPUIcontrolMgr.cpp
Normal file
371
src/ESPUIcontrolMgr.cpp
Normal file
@ -0,0 +1,371 @@
|
||||
|
||||
#include "ESPUIControlMgr.h"
|
||||
|
||||
static Control::ControlId_t idCounter = 0;
|
||||
|
||||
_ESPUIcontrolMgr::_ESPUIcontrolMgr()
|
||||
{
|
||||
#ifdef ESP32
|
||||
ControlsSemaphore = xSemaphoreCreateMutex();
|
||||
xSemaphoreGive(ControlsSemaphore);
|
||||
#endif // def ESP32
|
||||
}
|
||||
|
||||
void _ESPUIcontrolMgr::RemoveToBeDeletedControls()
|
||||
{
|
||||
#ifdef ESP32
|
||||
xSemaphoreTake(ControlsSemaphore, portMAX_DELAY);
|
||||
#endif // def ESP32
|
||||
|
||||
ControlObject_t * PreviousControl = nullptr;
|
||||
ControlObject_t * CurrentControl = controls;
|
||||
|
||||
while (nullptr != CurrentControl)
|
||||
{
|
||||
ControlObject_t * NextControl = CurrentControl->next;
|
||||
if (CurrentControl->ToBeDeleted())
|
||||
{
|
||||
if (CurrentControl == controls)
|
||||
{
|
||||
// this is the root control
|
||||
controls = NextControl;
|
||||
}
|
||||
else
|
||||
{
|
||||
PreviousControl->next = NextControl;
|
||||
}
|
||||
delete CurrentControl;
|
||||
CurrentControl = NextControl;
|
||||
}
|
||||
else
|
||||
{
|
||||
PreviousControl = CurrentControl;
|
||||
CurrentControl = NextControl;
|
||||
}
|
||||
}
|
||||
#ifdef ESP32
|
||||
xSemaphoreGive(ControlsSemaphore);
|
||||
#endif // def ESP32
|
||||
}
|
||||
|
||||
Control* _ESPUIcontrolMgr::getControl(Control::ControlId_t id)
|
||||
{
|
||||
#ifdef ESP32
|
||||
xSemaphoreTake(ControlsSemaphore, portMAX_DELAY);
|
||||
#endif // !def ESP32
|
||||
Control* Response = getControlNoLock(id);
|
||||
#ifdef ESP32
|
||||
xSemaphoreGive(ControlsSemaphore);
|
||||
#endif // !def ESP32
|
||||
return Response;
|
||||
}
|
||||
|
||||
// WARNING: Anytime you walk the chain of controllers, the protection semaphore
|
||||
// MUST be locked. This function assumes that the semaphore is locked
|
||||
// at the time it is called. Make sure YOU locked it :)
|
||||
Control* _ESPUIcontrolMgr::getControlNoLock(Control::ControlId_t id)
|
||||
{
|
||||
return getControlObjectNoLock(id);
|
||||
} // getControlNoLock
|
||||
|
||||
// WARNING: Anytime you walk the chain of controllers, the protection semaphore
|
||||
// MUST be locked. This function assumes that the semaphore is locked
|
||||
// at the time it is called. Make sure YOU locked it :)
|
||||
_ESPUIcontrolMgr::ControlObject_t * _ESPUIcontrolMgr::getControlObjectNoLock(Control::ControlId_t id)
|
||||
{
|
||||
ControlObject_t * Response = nullptr;
|
||||
ControlObject_t * CurrentControl = controls;
|
||||
|
||||
while (nullptr != CurrentControl)
|
||||
{
|
||||
if (CurrentControl->GetId() == id)
|
||||
{
|
||||
if (!CurrentControl->ToBeDeleted())
|
||||
{
|
||||
Response = CurrentControl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
CurrentControl = CurrentControl->next;
|
||||
}
|
||||
|
||||
return Response;
|
||||
} // getControlObjectNoLock
|
||||
|
||||
bool _ESPUIcontrolMgr::removeControl(Control::ControlId_t id)
|
||||
{
|
||||
bool Response = false;
|
||||
|
||||
Control* control = getControl(id);
|
||||
if (control)
|
||||
{
|
||||
Response = true;
|
||||
control->DeleteControl();
|
||||
controlCount--;
|
||||
}
|
||||
#ifdef DEBUG_ESPUI
|
||||
else
|
||||
{
|
||||
// Serial.println(String("Could not Remove Control ") + String(id));
|
||||
}
|
||||
#endif // def DEBUG_ESPUI
|
||||
|
||||
return Response;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Prepare a chunk of elements as a single JSON string. If the allowed number of elements is greater than the total
|
||||
number this will represent the entire UI. More likely, it will represent a small section of the UI to be sent. The
|
||||
client will acknowledge receipt by requesting the next chunk.
|
||||
*/
|
||||
uint32_t _ESPUIcontrolMgr::prepareJSONChunk(uint16_t startindex,
|
||||
JsonDocument & rootDoc,
|
||||
bool InUpdateMode,
|
||||
String FragmentRequestString,
|
||||
uint32_t CurrentSyncID)
|
||||
{
|
||||
#ifdef ESP32
|
||||
xSemaphoreTake(ControlsSemaphore, portMAX_DELAY);
|
||||
#endif // def ESP32
|
||||
|
||||
// Serial.println(String("prepareJSONChunk: Start. InUpdateMode: ") + String(InUpdateMode));
|
||||
// Serial.println(String("prepareJSONChunk: Start. startindex: ") + String(startindex));
|
||||
// Serial.println(String("prepareJSONChunk: Start. FragmentRequestString: '") + FragmentRequestString + "'");
|
||||
int elementcount = 0;
|
||||
uint32_t MaxMarshaledJsonSize = (!InUpdateMode) ? ESPUI.jsonInitialDocumentSize: ESPUI.jsonUpdateDocumentSize;
|
||||
uint32_t EstimatedUsedMarshaledJsonSize = 0;
|
||||
|
||||
do // once
|
||||
{
|
||||
// Follow the list until control points to the startindex'th node
|
||||
ControlObject_t * CurrentControlObject = 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.
|
||||
AllocateJsonDocument(FragmentRequest, FragmentRequestString.length() * 3);
|
||||
/*
|
||||
ArduinoJson::detail::sizeofObject(N);
|
||||
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")]);
|
||||
CurrentControlObject = getControlObjectNoLock(ControlId);
|
||||
if(nullptr == CurrentControlObject)
|
||||
{
|
||||
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 != CurrentControlObject))
|
||||
{
|
||||
// only count active controls
|
||||
if (!CurrentControlObject->ToBeDeleted())
|
||||
{
|
||||
if(InUpdateMode)
|
||||
{
|
||||
// In update mode we only count the controls that have been updated.
|
||||
if(CurrentControlObject->NeedsSync(CurrentSyncID))
|
||||
{
|
||||
++currentIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not in update mode. Count all active controls
|
||||
++currentIndex;
|
||||
}
|
||||
}
|
||||
CurrentControlObject = CurrentControlObject->next;
|
||||
}
|
||||
|
||||
// any controls left to be processed?
|
||||
if(nullptr == CurrentControlObject)
|
||||
{
|
||||
// Serial.println("prepareJSONChunk: No controls to process");
|
||||
break;
|
||||
}
|
||||
|
||||
// keep track of the number of elements we have serialised into this
|
||||
// message. Overflow is detected and handled later in this loop
|
||||
// and needs an index to the last item added.
|
||||
while (nullptr != CurrentControlObject)
|
||||
{
|
||||
// skip deleted controls or controls that have not been updated
|
||||
if (CurrentControlObject->ToBeDeleted() && !SingleControl)
|
||||
{
|
||||
// Serial.println(String("prepareJSONChunk: Ignoring Deleted control: ") + String(control->id));
|
||||
CurrentControlObject = CurrentControlObject->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(InUpdateMode && !SingleControl)
|
||||
{
|
||||
if(CurrentControlObject->NeedsSync(CurrentSyncID))
|
||||
{
|
||||
// dont skip this control
|
||||
}
|
||||
else
|
||||
{
|
||||
// control has not been updated. Skip it
|
||||
CurrentControlObject = CurrentControlObject->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Serial.println(String(F("prepareJSONChunk: MaxMarshaledJsonSize: ")) + String(MaxMarshaledJsonSize));
|
||||
// Serial.println(String(F("prepareJSONChunk: Cur EstimatedUsedMarshaledJsonSize: ")) + String(EstimatedUsedMarshaledJsonSize));
|
||||
|
||||
JsonObject item = AllocateJsonObject(items);
|
||||
elementcount++;
|
||||
uint32_t RemainingSpace = (MaxMarshaledJsonSize - EstimatedUsedMarshaledJsonSize) - 100;
|
||||
// Serial.println(String(F("prepareJSONChunk: RemainingSpace: ")) + String(RemainingSpace));
|
||||
uint32_t SpaceUsedByMarshaledControl = 0;
|
||||
bool ControlIsFragmented = CurrentControlObject->MarshalControl(item,
|
||||
InUpdateMode,
|
||||
DataOffset,
|
||||
RemainingSpace,
|
||||
SpaceUsedByMarshaledControl);
|
||||
// Serial.println(String(F("prepareJSONChunk: SpaceUsedByMarshaledControl: ")) + String(SpaceUsedByMarshaledControl));
|
||||
EstimatedUsedMarshaledJsonSize += SpaceUsedByMarshaledControl;
|
||||
// Serial.println(String(F("prepareJSONChunk: New EstimatedUsedMarshaledJsonSize: ")) + String(EstimatedUsedMarshaledJsonSize));
|
||||
// Serial.println(String(F("prepareJSONChunk: ControlIsFragmented: ")) + String(ControlIsFragmented));
|
||||
|
||||
// did the control get added to the doc?
|
||||
if (0 == SpaceUsedByMarshaledControl ||
|
||||
(ESPUI.jsonChunkNumberMax > 0 && (elementcount % ESPUI.jsonChunkNumberMax) == 0))
|
||||
{
|
||||
// Serial.println( String("prepareJSONChunk: too much data in the message. Remove the last entry"));
|
||||
if (1 == elementcount)
|
||||
{
|
||||
// Serial.println(String(F("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 = AllocateJsonObject(items);
|
||||
CurrentControlObject->MarshalErrorMessage(item);
|
||||
elementcount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Serial.println(String("prepareJSONChunk: Defering control: ") + String(control->id));
|
||||
// Serial.println(String("prepareJSONChunk: elementcount: ") + String(elementcount));
|
||||
|
||||
items.remove(elementcount);
|
||||
--elementcount;
|
||||
}
|
||||
// exit the loop
|
||||
CurrentControlObject = nullptr;
|
||||
}
|
||||
else if ((SingleControl) ||
|
||||
(ControlIsFragmented) ||
|
||||
(MaxMarshaledJsonSize < (EstimatedUsedMarshaledJsonSize + 100)))
|
||||
{
|
||||
// Serial.println("prepareJSONChunk: Doc is Full, Fragmented Control or Single Control. exit loop");
|
||||
CurrentControlObject = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Serial.println("prepareJSONChunk: Next Control");
|
||||
CurrentControlObject = CurrentControlObject->next;
|
||||
}
|
||||
} // end while (control != nullptr)
|
||||
|
||||
} while (false);
|
||||
|
||||
#ifdef ESP32
|
||||
xSemaphoreGive(ControlsSemaphore);
|
||||
#endif // def ESP32
|
||||
|
||||
// Serial.println(String("prepareJSONChunk: END: elementcount: ") + String(elementcount));
|
||||
return elementcount;
|
||||
}
|
||||
|
||||
|
||||
Control::ControlId_t _ESPUIcontrolMgr::addControl(Control::Type type,
|
||||
const char* label,
|
||||
const String& value,
|
||||
Control::Color color,
|
||||
Control::ControlId_t parentControl,
|
||||
bool visible,
|
||||
std::function<void(Control*, int)> callback)
|
||||
{
|
||||
// Create a Wrapper and a control
|
||||
ControlObject_t * NewControlObject = new ControlObject_t(++idCounter, type, label, callback, value, color, visible, parentControl);
|
||||
NewControlObject->next = nullptr;
|
||||
|
||||
#ifdef ESP32
|
||||
xSemaphoreTake(ControlsSemaphore, portMAX_DELAY);
|
||||
#endif // def ESP32
|
||||
|
||||
if (controls == nullptr)
|
||||
{
|
||||
controls = NewControlObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
ControlObject_t * iterator = controls;
|
||||
while (iterator->next != nullptr)
|
||||
{
|
||||
iterator = iterator->next;
|
||||
}
|
||||
iterator->next = NewControlObject;
|
||||
}
|
||||
|
||||
controlCount++;
|
||||
|
||||
#ifdef ESP32
|
||||
xSemaphoreGive(ControlsSemaphore);
|
||||
#endif // def ESP32
|
||||
|
||||
return NewControlObject->GetId();
|
||||
}
|
||||
|
||||
_ESPUIcontrolMgr::ControlObject_t::ControlObject_t(Control::ControlId_t id, Control::Type type, const char* label, std::function<void(Control*, int)> callback,
|
||||
const String& value, Control::Color color, bool visible, ControlId_t parentControl)
|
||||
: Control(id, type, label, callback, value, color, visible, parentControl)
|
||||
{}
|
||||
|
||||
// Instantiate the singleton
|
||||
_ESPUIcontrolMgr ESPUIcontrolMgr;
|
62
src/ESPUIcontrolMgr.h
Normal file
62
src/ESPUIcontrolMgr.h
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ESPUI.h>
|
||||
#include <ESPUIControl.h>
|
||||
|
||||
// implemented as a singleton
|
||||
class _ESPUIcontrolMgr
|
||||
{
|
||||
public:
|
||||
_ESPUIcontrolMgr();
|
||||
~_ESPUIcontrolMgr() {}
|
||||
_ESPUIcontrolMgr (_ESPUIcontrolMgr const&) = delete;
|
||||
void operator=(_ESPUIcontrolMgr const&) = delete;
|
||||
|
||||
Control::ControlId_t addControl(Control::Type type,
|
||||
const char* label,
|
||||
const String& value,
|
||||
Control::Color color,
|
||||
Control::ControlId_t parentControl,
|
||||
bool visible,
|
||||
std::function<void(Control*, int)> callback);
|
||||
bool removeControl(Control::ControlId_t id);
|
||||
void RemoveToBeDeletedControls();
|
||||
|
||||
Control* getControl(Control::ControlId_t id);
|
||||
Control* getControlNoLock(Control::ControlId_t id);
|
||||
Control::ControlId_t GetControlCount() {return controlCount;}
|
||||
|
||||
uint32_t prepareJSONChunk(uint16_t startindex,
|
||||
JsonDocument & rootDoc,
|
||||
bool InUpdateMode,
|
||||
String FragmentRequestString,
|
||||
uint32_t CurrentSyncID);
|
||||
private:
|
||||
class ControlObject_t : public Control
|
||||
{
|
||||
public:
|
||||
ControlObject_t(Control::ControlId_t id,
|
||||
Type type,
|
||||
const char* label,
|
||||
std::function<void(Control*, int)> callback,
|
||||
const String& value,
|
||||
Color color,
|
||||
bool visible,
|
||||
uint16_t parentControl);
|
||||
|
||||
ControlObject_t * next = nullptr;
|
||||
}; // ControlObject_t
|
||||
|
||||
ControlObject_t * controls = nullptr;
|
||||
Control::ControlId_t controlCount = 0;
|
||||
Control::ControlId_t idCounter = 0;
|
||||
ControlObject_t * getControlObjectNoLock(Control::ControlId_t id);
|
||||
|
||||
#ifdef ESP32
|
||||
SemaphoreHandle_t ControlsSemaphore = NULL;
|
||||
#endif // def ESP32
|
||||
|
||||
};
|
||||
|
||||
extern _ESPUIcontrolMgr ESPUIcontrolMgr;
|
Loading…
Reference in New Issue
Block a user