Best Way To Find Message by ID in PROGMEM? - c++

I am trying to determine what the best way would be to retrieve and display an error message from PROGMEM based on an integer return from an external device.
const prog_char error_1000[] PROGMEM = "No data provided.";
const prog_char error_1001[] PROGMEM = "device not activated";
const prog_char error_2000[] PROGMEM = "Machine ID invalid";
const prog_char error_3000[] PROGMEM = "Insufficient Balance";
void loop()
{
int result = device.GetStatus();
Serial.println(/*error by code here*/);
}
The errors are grouped together by the leading number (i.e. 1xxx are device errors, 2xxx are issues with another component, 3xxx are transaction errors). There are probably only 5-10 errors in each category though.
I am using a few memory heavy libraries and my memory is already almost exhausted on the Uno so I am trying to keep things small here.
Basically someway to lookup strings by an ID is what is required but I am not making much progress on the best way to do this.

instead of doing the following:
const prog_char error_1000[] PROGMEM = "No data provided.";
const prog_char error_1001[] PROGMEM = "device not activated";
const prog_char error_2000[] PROGMEM = "Machine ID invalid";
const prog_char error_3000[] PROGMEM = "Insufficient Balance";
I'd advise you to index the error category (1000s, 2000s etc..) as first index of a matrix, and the actual error as second index of the array:
Here's the idea:
const prog_char error_code[1][0] PROGMEM = "No data provided.";
const prog_char error_code[1][1] PROGMEM = "device not activated";
const prog_char error_code[2][0] PROGMEM = "Machine ID invalid";
const prog_char error_code[3][0] PROGMEM = "Insufficient Balance";
(edit) Here's a valid syntax:
const prog_char* error_code[][3] PROGMEM = {{"ERROR 1000", "ERROR 1001", "ERROR 1002"},
{"ERROR 2000", "ERROR 2001", "ERROR 2002"},
{"ERROR 3000", "ERROR 3001", "ERROR 3002"}};
the only drawback will be that you shall specify the length of the inner array, and thus need to have the same number of string in each inner array.
And then you could code a function that does status code conversion:
const prog_char* fmt_error(int code) {
return error_code[code/1000][code%1000];
}
void loop()
{
int result = device.GetStatus();
Serial.println(fmt_error(result));
}
That solution does not use more memory than using one array (just one more pointer). The only downside of that is if you need status code that are not contiguous, like 1000, 1010, 1042 and 1300. Then there is no cool solution I can think of, except using a good old switch/case:
const prog_char* fmt_error(int code) {
switch (code) {
case (1000): return F("Error XXX");
case (1042): return F("Error which code is not contiguous");
case (2042): return F("Another error");
}
}
(edit) I had a third idea on how to deal with your problem:
typedef struct
{
int code;
prog_char* message;
} error_code;
const error_type error_codes[] =
{
{0000, F("Unknown error code")},
{1000, F("Error foo")},
{1001, F("Error bar")},
{2000, F("Error foobar")}
...
};
const prog_char* fmt_error(int code) {
for (int i=0; i<sizeof(error_codes);++i)
if (error_codes[i].code == code)
return error_codes[i].message;
return error_codes[0];
}
But I think the solution that uses the less memory of all three solution is the second one using use cases. Because everything is done in program memory, and all the strings are in the flash thanks to the F() macro. To save a few extra bytes, you could even make the fmt_error() function inline, so it does not add to the function call stack.
HTH

Related

How to compare a char pointer with a string [duplicate]

This question already has answers here:
How do I properly compare strings in C?
(10 answers)
Closed last year.
Right now I need to create an if statement that can compare a char pointer with a string like the following statement:
if (Start == "on"){
Serial.println("virker");
}
The problem is that this simple sentence does not work. The variable Start is a string containing the word on that I get from a web page that sends a JSON object via a AJAX request. The object looks like this when I receive it:
{"start":"on","relay":"off","computer_alert":"off","esp_alert":"off","alarm1":{"tilstand":"off","tid":"null"},"alarm2":{"tilstand":"off","tid":"null"},"alarm3":{"tilstand":"off","tid":"null"}}
I've tried to give Start a value inside the program and that works. My entire code can be seen below:
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <Hash.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
const char* ssid = "ESP8266-Access-Point";
const char* password = "123456789";
const int buzzer = 0;
const int relay = 6;
const char* Start;
int d;
const char* test = "on";
const char* PARAM_INPUT_1 = "Json";
AsyncWebServer server(80);
void ekstern() {
const int buzzer = 0;
const int relay = 6;
pinMode(relay and buzzer, OUTPUT);
}
void setup() {
ESP.eraseConfig();
Serial.begin(9600);
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
if(!SPIFFS.begin()){
Serial.println("An Error has occurred while mounting SPIFFS");
return;
}
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/HTML.html");
});
server.on("/JQ", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/JQ.js");
});
server.on("/CSS", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/CSS.css");
});
server.on("/GET", HTTP_GET, [] (AsyncWebServerRequest *request) {
String json;
if (request->hasParam(PARAM_INPUT_1)) {
json = request->getParam(PARAM_INPUT_1)->value();
Serial.println(json);
}
request->send(200, "text/plain", "OK");
StaticJsonDocument<384> doc;
DeserializationError error = deserializeJson(doc, json);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
Start = doc["start"]; // "off"
const char* relay = doc["relay"]; // "off"
const char* computer_alert = doc["computer_alert"]; // "off"
const char* esp_alert = doc["esp_alert"]; // "off"
const char* alarm1_tilstand = doc["alarm1"]["tilstand"]; // "off"
long alarm1_tid = doc["alarm1"]["tid"]; // 3184358
const char* alarm2_tilstand = doc["alarm2"]["tilstand"]; // "off"
long alarm2_tid = doc["alarm2"]["tid"]; // 3184358
const char* alarm3_tilstand = doc["alarm3"]["tilstand"]; // "off"
long alarm3_tid = doc["alarm3"]["tid"]; // 3244358
Serial.println(alarm3_tid);
Serial.println(alarm3_tilstand);
});
server.begin();
}
void loop(){
if (Start == "on"){
Serial.println("virker");
}
Serial.println(Start);
Serial.println("hallo");
delay(5000);
}
I don't think it makes any difference, but I am using the ESP8266.
Your doc is a stack variable that will vanish as just you leave the request handler GET. This means that you will absolutely definitely access to already dangling pointer, which Start variable stores, in loop function because it points to the already not existing doc["start"]. You have to preserve data from doc["start"] rather than the pointer doc["start"] contains. For that you need to define Start as an array of chars and then use strncpy to copy characters from the doc["start"] into Start variable. Further, in loop function you need to use strcmp or strncmp to compare "on" with characters in the variable Start. As easy as it gets
UPD: Also GET request MUST NOT change the state of your server conventionally, therefore Start variable is not supposed to be altered
You can convert the value in the pointer to a String by useing the following command,
String();
For me it did not work when i used the following command,
strcmp();
The command just gave a exception (28) error. I feel a little bit stupid because i am pretty sure i used the String() before and it didnt work, but i must have done something else wrong because it wroks now. The solution was to write the if statement like this,
if(String(Start)=="on"){
Serial.println("virker");
}
UPD: The methode I described above does work, but stackoverflow user dpronin made me aware that the methode has some problems. The problem came from when the program received the JSON object because just saving the pointer made the program buggy. The solution was useing this command,
strncpy();
Which copies the string to another variable instead of just pointing to the memory addresse. When I changed to this methode my code worked. I will also say that it may be the reason why the following command didnt work,
strcmp();
I have tested it again after the changes.

Pointer to pointer comparision breaks when while(1) removed - why?

Trying to build a menu system but running into some issues with pointers - which I don't have much experience with.
I don't understand why removing the while(1) makes the comparison fail between mainmenu_table[1][i] == &option6 but for some reason it does.
What am I doing wrong? Using visual studio and an atmega328p. Thanks
Serial output with original code:
Serial begins
MeNu6
MeNu6
Starting compare loop
it worked
Serial output with while(1) removed.
Serial begins
MeNu6
MeNu6
Starting compare loop
the end
Original code (with while(1) included)
const char option1[] PROGMEM = "Menu1";
const char option2[] PROGMEM = "MEnu2";
const char option3[] PROGMEM = "MeNu3";
const char option4[] PROGMEM = "Menu4";
const char option5[] PROGMEM = "MEnu5";
const char option6[] PROGMEM = "MeNu6";
const char option7[] PROGMEM = "menu7";
const char* const submenu1_table[] PROGMEM = { option1, option2, option3 }; // array of pointers to chars stored in flash
const char* const submenu2_table[] PROGMEM = { option4, option5, option6, option7 };
const char** const mainmenu_table[] PROGMEM = { submenu1_table, submenu2_table }; //array of pointers to pointers to chars in flash
// The setup() function runs once each time the micro-controller starts
void setup()
{
Serial.begin(9600);
delay(100);
Serial.println("Serial begins");
Serial.println((const __FlashStringHelper*)(mainmenu_table[1][2])); // prints "Menu6" as expected
Serial.println((const __FlashStringHelper*)option6); // also prints "Menu6"
Serial.println("Starting compare loop");
for (int i = 0; i < 4; i++) {
if ( mainmenu_table[1][i] == &option6 ) { //
Serial.println("it worked");
while (1); // COMMENTING THIS OUT MEANS DOESN'T COMPARE SUCCESSFULLY.
}
}
Serial.println("the end");
}
// Add the main program code into the continuous loop() function
void loop()
{
}
According to Arduino description of PROGMEM, you cannot access the data through pointers to it directly as with plain pointers. You need to use the proper macros/functions to access the data.
In your code, the pointer tables themselves are located in the PROGMEM, so, to extract the individual pointers, you are supposed to do something like:
const char** submenu = (const char**)pgm_read_word(&(mainmenu_table[1]));
const char* option = (const char*)pgm_read_word(&(submenu[i]));
if (option == option6) {
//...
This code is based on the string table example from the first link.

c++ protobuf: how to iterate through fields of message?

I'm new to protobuf and I'm stuck with simple task: I need to iterate through fields of message and check it's type. If type is message I will do same recursively for this message.
For example, I have such messages:
package MyTool;
message Configuration {
required GloablSettings globalSettings = 1;
optional string option1 = 2;
optional int32 option2 = 3;
optional bool option3 = 4;
}
message GloablSettings {
required bool option1 = 1;
required bool option2 = 2;
required bool option3 = 3;
}
Now, to explicitly access a field value in C++ I can do this:
MyTool::Configuration config;
fstream input("config", ios::in | ios::binary);
config.ParseFromIstream(&input);
bool option1val = config.globalSettings().option1();
bool option2val = config.globalSettings().option2();
and so on. This approach is not convenient in case when have big amount of fields.
Can I do this with iteration and get field's name and type? I know there are descriptors of type and somewhat called reflection, but I didn't have success in my attempts.
Can some one give me example of code if it's possible?
Thanks!
This is old but maybe someone will benefit. Here is a method that prints the contents of a protobuf message:
void Example::printMessageContents(std::shared_ptr<google::protobuf::Message> m)
{
const Descriptor *desc = m->GetDescriptor();
const Reflection *refl = m->GetReflection();
int fieldCount= desc->field_count();
fprintf(stderr, "The fullname of the message is %s \n", desc->full_name().c_str());
for(int i=0;i<fieldCount;i++)
{
const FieldDescriptor *field = desc->field(i);
fprintf(stderr, "The name of the %i th element is %s and the type is %s \n",i,field->name().c_str(),field->type_name());
}
}
You can find in FieldDescriptor Enum Values the possible values you get from field->type. For example for the message type you would have to check if type is equal to FieldDescriptor::TYPE_MESSAGE.
This function prints all the "metadata" of the protobuf message. However you need to check separately for each value what the type is and then call the corresponding getter function using Reflection.
So using this condition we could extract the strings :
if(field->type() == FieldDescriptor::TYPE_STRING && !field->is_repeated())
{
std::string g= refl->GetString(*m, field);
fprintf(stderr, "The value is %s ",g.c_str());
}
However fields can be either repeated or not repeated and different methods are used for both field types. So a check is used here to assure that we are using the right method. For repeated fields we have for example this method for strings :
GetRepeatedString(const Message & message, const FieldDescriptor * field, int index)
So it takes the index of the repeated field into consideration.
In the case of FieldDescriptor of type Message, the function provided will only print the name of the message, we better print its contents too.
if(field->type()==FieldDescriptor::TYPE_MESSAGE)
{
if(!field->is_repeated())
{
const Message &mfield = refl->GetMessage(*m, field);
Message *mcopy = mfield.New();
mcopy->CopyFrom(mfield);
void *ptr = new std::shared_ptr<Message>(mcopy);
std::shared_ptr<Message> *m =
static_cast<std::shared_ptr<Message> *>(ptr);
printMessageContents(*m);
}
}
And finally if the field is repeated you will have to call the FieldSize method on the reflection and iterate all repeated fields.
Take a look at how the Protobuf library implements the TextFormat::Printer class, which uses descriptors and reflection to iterate over fields and convert them to text:
https://github.com/google/protobuf/blob/master/src/google/protobuf/text_format.cc#L1473

Memcpy_p issues, how to resolve?

I'm puzzled by my own code :-) I'm trying to read data from PROGMEM. This works ok when I only have this array in PROGMEM. When adding an extra seperate array in PROGMEM it goes wrong. That is, when it's defined in a different piece of code and #included. When put together in 1 code it's fine. But I want these to arrays to live apart in different pieces of code.
I believe I have an error in the last function which I have include here (callMenuItemParaName).
It has to do with the way I'm reading out PROGMEM. I think it's best to use memcpy_P but cannot find any online explanation on how to use this exactly.
The code I have now works, but as long as I don't put another array in PROGMEM. (this routine is working correctly, with the memcpy_P function. But how do I implement memcpy_P in the function callMenuItemParaName?
Thanks for any advice you can give! (ofcourse pgmspace.h is included)
Working on AVR GCC, IDE is Eclipse, mcu = atmega644 # 20MHz
unsigned char (*adresParaName);
const uint8_t TEXT0[] PROGMEM = "TEXT0";
const uint8_t paraNameAtk[] PROGMEM = "Atk ";
const uint8_t paraNameDcy[] PROGMEM = "Dcy ";
...
const uint8_t paraNameTru[] PROGMEM = "Tru ";
const uint8_t paraNameLight[] PROGMEM = "Light";
typedef void (*pMenu)(void);
typedef struct
{
void (*pointer2MenuNumber)(void);
char VALUE;
const unsigned char *adresParaName;
} sel_item;
const sel_item menuNumber2ItemDbase[] PROGMEM=
{
{ itemA , 0x00 , TEXT0 },
{ itemB , 0x01 , paraNameAtk },
{ itemC , 0x02 , paraNameDcy },
...
{ itemM , 0x05 , paraNameTru },
{ itemN , 0x05 , paraNameLight }
};
//prototypes
void callMenuItem(const sel_item *item);
void callMenuItemValue(const sel_item *item);
void callMenuItemParaName(const sel_item *item);
// *************************************************
// callMenu
// Description:
//
// *************************************************
void callMenuItem(const sel_item *item)
{
pMenu function = (pMenu)pgm_read_word(&item->pointer2MenuNumber);
function();
}
void callMenuItemValue(const sel_item *item)
{
setCursor(1,4);
char VAL = (char)pgm_read_byte(&item->VALUE);
char2LCD('0'+VAL);
}
void callMenuItemParaName(const sel_item *item)
{
char tempText[5];
char *data = (char*)pgm_read_word(&item->adresParaName);
strcpy_P (tempText, data);
for (uint8_t x=0;x<5;x++)
{
char2LCD(tempText[x]);
}
}
I've tried adding this:
char* pstr = 0;
memcpy_P (&pstr, data, sizeof(char*));
But no luck. (can't find a good tutorial on memcpy_P either, btw)
Your strings are 6 bytes long (remember the terminating 0), which means that you're overflowing tempText when you strcpy_P into it. Use memcpy_P instead.
memcpy_P(tempText, data, sizeof tempText);
The way you use pgm_read_word is just fine.

validating structures - how to pass string to sizeof and offset methods?

My program contains auto-generated structures. When starting I need to validate them with "server" information - so I can be sure that my auto-generated structures are up to date. Server and local structures are valid if they are of the same size, contain fields with the same name and size (and type ideally should be validated too).
This is what I wrote so far:
void SchemeValidator::Validate(cg_scheme_desc_t* schemedesc, Logger& logger)
{
struct cg_message_desc_t* msgdesc = schemedesc->messages;
while (msgdesc)
{
struct cg_field_desc_t* fielddesc = msgdesc->fields;
char* structName = msgdesc->name;
size_t structSize = msgdesc->size;
logger.Debug("Message %s, block size = %d", structName, structSize);
if (strcmp(structName, "orders")) {
if (sizeof(orders) != structSize) {
printf("Validator error, structure 'orders', local size = %d server size = %d!", sizeof(orders), structSize);
throw std::exception("Validator error, structure 'orders' wrong size!");
}
while (fielddesc)
{
logger.Debug("\tField %s = %s [size=%d, offset=%d]", fielddesc->name, fielddesc->type, fielddesc->size, fielddesc->offset);
if (offsetof(struct orders, fielddesc->name) != fielddesc->offset) {
throw std::exception("orders structure offset wrong");
}
// TODO: validate fielddesc->size == sizeof corresponding field in structure
fielddesc = fielddesc->next;
}
} else {
throw std::exception("Validator error, validation not implemented!");
}
msgdesc = msgdesc->next;
}
}
There are a lot of problems:
I wrote if (strcmp(structName, "orders")) because later i need to use orders in several expressions, including sizeof(orders) and offsetof(struct orders, fielddesc->name). But I have a lot of structures and for each of them I have to copy-paste this block. Can I somehow pass string literal to sizeof and offsetof methods or have desired effect some another way?
offsetof(struct orders, fielddesc->name) doesn't work by same reason - second parameter can not be string literal, I receive error C2039: 'fielddesc' : is not a member of 'orders' error
By the same reason I can validate fielddesc->size.
How can I achieve desired validation without intensive copy-pasting and values hard-coding?
I believe you can accomplish what you want by defining structs with typedefs and using a templated function. The following should replace what you have (obviously not tested)
struct OrderType {
typedef cg_field_desc_t* ListItem;
typedef orders Class;
static const std::string Name;
};
std::string OrderType::Name = "orders";
template <class T>
checkStuff(T::ListItem fielddesc, const char* name, size_t structSize)
{
if (strcmp(name, T::Name.c_str())) {
if (sizeof(T::Class) != structSize) {
printf("Validator error, structure 'orders', local size = %d server size = %d!", sizeof(T::Class), structSize);
throw std::exception("Validator error, structure 'orders' wrong size!");
}
while (fielddesc)
{
logger.Debug("\tField %s = %s [size=%d, offset=%d]", fielddesc->name, fielddesc->type, fielddesc->size, fielddesc->offset);
if (offsetof(T::Class, fielddesc->name) != fielddesc->offset) {
throw std::exception("orders structure offset wrong");
}
// TODO: validate fielddesc->size == sizeof corresponding field in structure
fielddesc = fielddesc->next;
}
} else {
throw std::exception("Validator error, validation not implemented!");
}
}
You then replace your if statement with
checkStuff<OrderType>(fielddesc, name, structSize);
You can extend this to other types by defining new structs
struct OtherType {
typedef some_other_t* ListItem;
typedef bizbaz Class;
static const std::string Name;
};
std::string OtherType::Name = "foobar";