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.
Related
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.
,,I need to provide an AT command to a modem which looks like this: AT^SRPN=1,99991,"Download_URL","Image";^SMSO
How can I insert the variable download_url and the variable image into the commands string array? Is the right way to declare the commands array not as const and to use strcpy() to insert the two variables into the commands list?
The function at_send_commands() needs the commands list as const.
Function proto: at_resp_t at_send_commands(TickType ticks_to_wait, const char *commands[]);
at_resp_t at_send_download_url_and_image(const char *download_url, const char *image)
{
static const char *commands[] =
{
"AT^SRPN=1,99991,",
download_url,
",",
image,
";^SMSO\r",
NULL
};
at_resp_t err = at_send_commands(AT_TIMEOUT, commands);
if (err)
return err;
}
Try this:
at_resp_t at_send_download_url_and_image(const char *download_url, const char *image)
{
std::string str("AT^SRPN=1,99991,");
str += download_url;
str += ",";
str += image;
str += ";^SMSO\r";
const char* command = str.c_str();
const char* commands[] =
{
command,
NULL
};
at_resp_t err = at_send_commands(AT_TIMEOUT, commands);
if (err)
return err;
}
In C the simplest way is IMO
void send_command(const char *download_url, const char *image) {
char buf[1000];
sprintf(buf, "AT^SRPN=1,99991,\"%s\",\"%s\";^SMSO",
download_url, image);
...
}
in buf you will end up having the final command to send to the modem.
If this code can be used in an hostile environment then you should also pay attention that no overflow can happen when passed large strings as url/image (e.g. add a check on strlen first or use snprintf instead).
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.
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
I have a sample project here on github where I created a c++ wrapper class for an external C++ library that I want to use in Objective-C.
I don't understand why my returned pointers are sometimes correct and sometimes wrong. Here's sample output:
Test Data = 43343008
In Compress 43343008
Returned Value = 43343008
Casted Value = 43343008
Test Data = 2239023
In Compress 2239023
Returned Value = 2239023
Casted Value = 2239023
Test Data = 29459973
In Compress 29459973
Returned Value = 29459973
Casted Value = l.remote
Test Data = 64019670
In Compress 64019670
Returned Value =
Casted Value = stem.syslog.master
In the above output you can see that the 1st and 2nd click of the button outputs the results I was expecting. In each of the other clicks either the returned value or casted value are invalid. I'm assuming this is because my pointer is pointing to an address I wasn't expecting. when running the app multiple times, any button click could be right or wrong.
I also tried with a single thread but experienced similar results.
The complete code is on github but here are the important bits.
ViewController.m
#import "ViewController.h"
extern const char * CompressCodeData(const char * strToCompress);
#implementation ViewController
...
// IBAction on the button
- (IBAction)testNow:(id)sender
{
[self performSelectorInBackground:#selector(analyze) withObject:nil];
}
- (void)analyze
{
#synchronized(self) {
const char *testData = [[NSString stringWithFormat:#"%d",
(int)(arc4random() % 100000000)] UTF8String];
NSLog(#"Test Data = %s", testData);
const char *compressed = CompressCodeData(testData);
NSLog(#"Returned Value = %s", compressed);
NSString *casted = [NSString stringWithCString:compressed
encoding:NSASCIIStringEncoding];
NSLog(#"Casted Value = %#\n\n", casted);
}
}
#end
SampleWrapper.cpp
#include <iostream>
#include <string.h>
#include <CoreFoundation/CoreFoundation.h>
using namespace std;
extern "C"
{
extern void NSLog(CFStringRef format, ...);
/**
* This function simply wraps a library function so that
* it can be used in objective-c.
*/
const char * CompressCodeData(const char * strToCompress)
{
const string s(strToCompress);
// Omitted call to static method in c++ library
// to simplify this test case.
//const char *result = SomeStaticLibraryFunction(s);
const char *result = s.c_str();
NSLog(CFSTR("In Compress %s"), result);
return result;
}
}
You are returning a pointer to at object that has been deallocated.
const string s(strToCompress);
…
const char *result = s.c_str();
NSLog(CFSTR("In Compress %s"), result);
return result;
s does not exist after CompressCodeData() function is over, so the pointer to it's internal memory is invalid.
You could allocate a chunk of memory to hold the response, but it would be up to the caller to release it.
char *compressed = CompressCodeData(testData);
NSLog(#"Returned Value = %s", compressed);
NSString *casted = [NSString stringWithCString:compressed
encoding:NSASCIIStringEncoding];
free(compressed);
NSLog(#"Casted Value = %#\n\n", casted);
…
const char * CompressCodeData(const char * strToCompress)
…
char *result = strdup(s.c_str());
Another solution is to pass in the memory to store the data into.
char compressed[2048]; // Or whatever!
CompressCodeData(testData, compressed, sizeof(compressed));
NSLog(#"Returned Value = %s", compressed);
NSString *casted = [NSString stringWithCString:compressed
encoding:NSASCIIStringEncoding];
NSLog(#"Casted Value = %#\n\n", casted);
…
void CompressCodeData(const char * strToCompress, char *result, size_t size)
…
s.copy(result, size - 1);
result[s.length() < size ? s.length() : size-1] = '\0';