1
0
mirror of https://github.com/s00500/ESPUI.git synced 2024-11-25 19:20:54 +00:00

Made Type and ID private and added accessors for them.

This commit is contained in:
Martin 2024-07-18 11:42:37 -04:00
parent 610ed1275e
commit bddcde4e57
3 changed files with 485 additions and 48 deletions

View File

@ -7,54 +7,54 @@
class Control class Control
{ {
public: public:
enum Type : uint8_t enum Type : uint8_t
{ {
// fixed Controls // fixed Controls
Title = 0, Title = 0,
// updatable Controls // updatable Controls
Pad, Pad,
PadWithCenter, PadWithCenter,
Button, Button,
Label, Label,
Switcher, Switcher,
Slider, Slider,
Number, Number,
Text, Text,
Graph, Graph,
GraphPoint, GraphPoint,
Tab, Tab,
Select, Select,
Option, Option,
Min, Min,
Max, Max,
Step, Step,
Gauge, Gauge,
Accel, Accel,
Separator, Separator,
Time, Time,
FileDisplay, FileDisplay,
Fragment = 98, Fragment = 98,
Password = 99, Password = 99,
UpdateOffset = 100, UpdateOffset = 100,
}; };
enum Color : uint8_t enum Color : uint8_t
{ {
Turquoise, Turquoise,
Emerald, Emerald,
Peterriver, Peterriver,
Wetasphalt, Wetasphalt,
Sunflower, Sunflower,
Carrot, Carrot,
Alizarin, Alizarin,
Dark, Dark,
None = 0xFF None = 0xFF
}; };
typedef uint16_t ControlId_t;
Type type;
uint16_t id; // just mirroring the id here for practical reasons
const char* label; const char* label;
std::function<void(Control*, int)> callback; std::function<void(Control*, int)> callback;
String value; String value;
@ -63,15 +63,15 @@ enum Color : uint8_t
bool wide; bool wide;
bool vertical; bool vertical;
bool enabled; bool enabled;
uint16_t parentControl; ControlId_t parentControl;
String panelStyle; String panelStyle;
String elementStyle; String elementStyle;
String inputType; String inputType;
Control* next;
static constexpr uint16_t noParent = 0xffff; static constexpr uint16_t noParent = 0xffff;
Control(Type type, Control(ControlId_t id,
Type type,
const char* label, const char* label,
std::function<void(Control*, int)> callback, std::function<void(Control*, int)> callback,
const String& value, const String& value,
@ -90,7 +90,8 @@ enum Color : uint8_t
inline bool ToBeDeleted() { return _ToBeDeleted; } inline bool ToBeDeleted() { return _ToBeDeleted; }
inline bool NeedsSync(uint32_t lastControlChangeID) {return (false == _ToBeDeleted) && (lastControlChangeID < ControlChangeID);} inline bool NeedsSync(uint32_t lastControlChangeID) {return (false == _ToBeDeleted) && (lastControlChangeID < ControlChangeID);}
void SetControlChangedId(uint32_t value) {ControlChangeID = value;} 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_TITLE Control::Type::Title
#define UI_LABEL Control::Type::Label #define UI_LABEL Control::Type::Label
@ -123,6 +124,9 @@ enum Color : uint8_t
#define COLOR_NONE Control::Color::None #define COLOR_NONE Control::Color::None
private: private:
Type type = Type::Title;
ControlId_t id = Control::noParent;
bool _ToBeDeleted = false; bool _ToBeDeleted = false;
uint32_t ControlChangeID = 0; uint32_t ControlChangeID = 0;
String OldValue = emptyString; String OldValue = emptyString;

371
src/ESPUIcontrolMgr.cpp Normal file
View 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
View 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;