Some more background on what is already achieved.
I use C++ classes of which I create global instances to avoid new and delete. Each of these classes has a void Setup(); and a void Loop(); (Notice the first letter is capital). The basic idea is to only do real actions in the Loop(). All getters and setters should only set the variables but not act upon it*.
This way I know when the action will be done and as such I avoid complex sequence problems.
The result is that I call all Setup() of all my classes in setup() of the sketch and the Loop() in the loop() of the sketch.
In the program I used to test the serial communication the setup and Loop looks like:
void setup()
{
wdt_reset();
wdt_disable();
Serial.begin(115200);
myStringSerial.Setup();
myDataDummyClass.Setup();
myMessageHandler.Setup(&myDataDummyClass);
}
void loop()
{
myStringSerial.Loop();
if (myStringSerial.MessageReceived())
{
myMessageHandler.SetReceivedMessage(myStringSerial.getMessage());
}
myMessageHandler.Loop();
myDataDummyClass.Loop();
}
wdt_reset(); wdt_disable(); are not really needed when you run the bootloader. If not you need them. Then there is Serial.Begin() and then all setups.
myStringSerial is a simple class that reads strings from the serial and returns it line by line.
myDataDummyClass is a class containing data that I use to modify and read.
myMessageHandler links the lines received from the serial ports via myStringSerial and executes the commands on myDataDummyClass. This is why myDataDummyClass is given as parameter to the Setup of myMessageHandler.
The commands I can set to Arduino are
Serial.println(F("Following commands are supported"));
Serial.println(F("? to show this info"));
Serial.println(F("DUMP to show all variables and their values"));
Serial.println(F("GET [FieldName] to show one variable value"));
Serial.println(F("SET [FieldName] [value] to set one variable value"));
Serial.println(F("ISDIRTY to see all the classes with the dirty flag set"));
Serial.println(F("LOG VALUE to LOG the values of the fields"));
Serial.println(F("LOG HEADER to LOG the names of the fields"));
Serial.println(F("RESET to reset the arduino"));
Most of them are self explanatory except for LOG VALUE; LOG HEADER; ISDIRTY.
Lets start with ISDIRTY
To save memory I use pointer to the actual variables. That means that when you do a SET the code does not know the variable has been changed. I have foreseen a overloadable method to know but I prefer not to do so.
If I do a SET I also set the ISDIRTY flag of the class. This is possible because each class that you want to change with the serial monitor must be derived from the class DataInterface wich holds the ISDIRTY flag.
The LOG VALUE dumps all values just like DUMP but only the values and with ';' in between the values.
The LOG HEADER dumps all names of the variable just like DUMP but only the names and with ';' in between the values.
So 1 LOG HEADER and many LOG VALUE's gives you a csv file with header 8)
What is the impact on your code?
Functionally you will need to be able to cope with someone changing your variables behind you back (This is done by the Setup Loop approach )
Secondly you will have to check and reset the ISDIRTY flag to process Serial monitor driven changes
Thirdly you will need to use classes
Fourthly you will need to derive the classes you want to control from the web from DataInterface
And last you have to fill in the data DataInterface needs in your setup.
That is all.
As an example I dumped the creator of MyDataDummyClass below
DataInterface * globalChildren[NUMCHILDREN];
MyDataDummyClass::MyDataDummyClass():myLeftDataClass(F("MyDataDummyClass.left")),myRightDataClass(F("MyDataDummyClass.right"))
{
myFields[0].ClassName=F("MyDataDummyClass");
myFields[0].FieldName=F("Field_1");
myFields[0].type=Uint16_t;
myFields[0].isWritable=false;
myFields[0].value.pUint16_t=&myField0;
myFields[1].ClassName=myFields[0].ClassName;
myFields[1].FieldName=F("Field_2");
myFields[1].type=Uint8_t;
myFields[1].isWritable=true;
myFields[1].value.pUint8_t=&myField1;
myFields[2].ClassName=myFields[0].ClassName;
myFields[2].FieldName=F("Field_3");
myFields[2].type=Uint16_t;
myFields[2].isWritable=true;
myFields[2].value.pUint16_t=&myField2;
DataInterface::myFields=myFields;
myNumFields=NUMFIELDS;
myNumChildren=NUMCHILDREN;
globalChildren[0]=&myLeftDataClass;
globalChildren[1]=&myRightDataClass;
myChildren= globalChildren;
myField0=10;
myField1=11;
myField2=12;
}
The class has 2 children which can be changed over the Serial monitor. myLeftDataClass and myRightDataClass
The class has 3 fields: Field_1 Field_2 Field_3
myFields[0].ClassName=F("MyDataDummyClass");
myFields[0].FieldName=F("Field_1");
myFields[0].type=Uint16_t;
myFields[0].isWritable=false;
myFields[0].value.pUint16_t=&myField0;
Field_1 is an Uint16_t and can not be changed using the Serial Monitor.
As I said I point directly to the memory. This can be seen at the last line.
For the children classes only the pointers to the classes need to be registered.
myNumChildren=NUMCHILDREN;
globalChildren[0]=&myLeftDataClass;
globalChildren[1]=&myRightDataClass;
myChildren= globalChildren;
I hope this background info makes my question a bit more clear.
Best regards
Jantje
*Note this is a strong rule as I break it in the example 8)