I am receiving MQTT message in following format :
["006","106","1","2","3","4","HTM","PASSWORD"]
Now I want to save first 6 items in uint8_t array and last 2 for wifi login SSID and password.
What is the best way to achieve that.
Thanks.
I am receiving MQTT message in following format :
["006","106","1","2","3","4","HTM","PASSWORD"]
Now I want to save first 6 items in uint8_t array and last 2 for wifi login SSID and password.
What is the best way to achieve that.
Thanks.
Is this a hidden suggestion for the Arduino project?
I moved your topic to a more appropriate forum category @happytm.
The Community > Suggestions for the Arduino Project category you chose is only to be used to submit suggestions for changes to the Arduino company's software and hardware projects. It is not an appropriate place to publish topics like this that are requesting assistance with your own project.
In the future, when creating a topic please take the time to pick the forum category that best suits the subject of your question. There is an "About the _____ category" topic at the top of each category that explains its purpose.
As you can see from @ledsyn's reply, selecting an inappropriate category can cause confusion or otherwise derail the discussion, and thus make it less likely for you to receive effective assistance.
Thanks in advance for your cooperation.
Yes this is for Arduino using ESP32.
If you're already using JSON (like with the ArduinoJSON library), it could be deserialized as a JSON array. Otherwise, sscanf is kinda prickly, but would work
void setup() {
Serial.begin(115200);
const char *q = R"(["009","106","1","2","3","4","HTM","PASSWORD"])";
uint8_t n[6];
const char ssid[6];
const char password[6];
auto result = sscanf(q, R"(["%d","%d","%d","%d","%d","%d","%5[^"]","%5[^"]"])",
n, n+1, n+2, n+3, n+4, n+5, ssid, password
);
Serial.println(result);
Serial.println(n[0]);
Serial.println(n[1]);
Serial.println(n[2]);
Serial.println(n[3]);
Serial.println(n[4]);
Serial.println(n[5]);
Serial.println(ssid);
Serial.println(password);
}
void loop() {}
006 with 009 to see what happens)[^"]: anything except a quoteprints
8
9
106
1
2
3
4
HTM
PASSW
Always check the result to make sure it filled in all of them.
Thank you for your solution.
It works standalone but when I try with live mqtt data it does not print anything except last Serial.printf statement with correct real data from mqtt.
The code is below:
{
if (payload && strlen(payload)) {
const char *q = R"(payload)";
uint8_t n[6];
auto result = sscanf(q, R"(["%d","%d","%d","%d","%d","%d","%5[^"]","%5[^"]"])",
n, n+1, n+2, n+3, n+4, n+5, ssid, password
);
Serial.println(result);
Serial.println(n[0]);
Serial.println(n[1]);
Serial.println(n[2]);
Serial.println(n[3]);
Serial.println(n[4]);
Serial.println(n[5]);
Serial.println(ssid);
Serial.println(password);
Serial.printf("Received message in topic 'command' & message is:- %s\n", payload);
}
});
The R"()" is a raw string literal, useful for all those double-quotes in the strings.
Creates a string with the value "payload". Since you've already got the payload in a variable, you don't need a new variable at all. Use the existing one as the first argument
auto result = sscanf(payload, // etc
Thank you. It works perfectly.
When I use following code I get result that shows correct data:
["136","106","2","6","112","32","HTM","password"]
Received message in topic 'command' & message is:- ["136","106","2","6","112","32","HTM","password"]
136
106
2
6
112
32
HTM
password
String ssid, password;
uint8_t receivedCommand[6];
mqtt.subscribe("command", [](const char * payload)
{
if (payload && strlen(payload)) {
Serial.println(payload);
Serial.printf("Received message in topic 'command' & message is:- %s\n", payload);
auto result = sscanf(payload, R"(["%u","%u","%u","%u","%u","%u","%5[^"]","%5[^"]"])",
&receivedCommand[0], &receivedCommand[1], &receivedCommand[2],
&receivedCommand[3], &receivedCommand[4], &receivedCommand[5], &ssid,
&password);
Serial.println(receivedCommand[0]);
Serial.println(receivedCommand[1]);
Serial.println(receivedCommand[2]);
Serial.println(receivedCommand[3]);
Serial.println(receivedCommand[4]);
Serial.println(receivedCommand[5]);
Serial.println(ssid.c_str());
Serial.println(password.c_str());
}
}
You can use pointers to those Strings only because on that platform -- ESP32 I'm guessing because you're using Serial.printf -- String implements SSO, Small String Optimization: storing the chars at the start of the String object if they are short enough, no more than 14 bytes. This is a non-portable implementation detail.
In that case, String::c_str returns the address of the String itself. In normal operation, once the string is too long, then it allocates memory on the heap; returning that address with c_str
Directly writing to the memory does not update the String length, so if you use the String in other ways, it will be empty. For these reasons, you should use a char[] instead.
Separately, the format string still has %5[^"] in the format string, which means that should yield no more than 5 characters. But you're getting "password", which is 8. There may be something else with the code you omitted. I could not reproduce your behavior with this sketch
String ssid, password;
uint8_t receivedCommand[6];
void mqtt_recv(const char * payload)
{
if (payload && strlen(payload)) {
Serial.println(payload);
Serial.printf("Received message in topic 'command' & message is:- %s\n", payload);
auto result = sscanf(payload, R"(["%u","%u","%u","%u","%u","%u","%5[^"]","%5[^"]"])",
&receivedCommand[0], &receivedCommand[1], &receivedCommand[2],
&receivedCommand[3], &receivedCommand[4], &receivedCommand[5], &ssid,
&password);
Serial.println(receivedCommand[0]);
Serial.println(receivedCommand[1]);
Serial.println(receivedCommand[2]);
Serial.println(receivedCommand[3]);
Serial.println(receivedCommand[4]);
Serial.println(receivedCommand[5]);
Serial.println(ssid.c_str());
Serial.println(password.c_str());
Serial.println(password.length()); // added this
}
}
void setup() {
Serial.begin(115200);
mqtt_recv(R"(["136","106","2","6","112","32","HTM","password"])");
String x;
const char *p = x.c_str();
do {
x += static_cast<char>('a' + x.length());
Serial.printf("%2d %x %s\n", x.length(), x.c_str(), x.c_str());
} while (x.c_str() == p);
Serial.printf("sizeof(String): %d\n", sizeof(String));
}
void loop() {}
I got instead
["136","106","2","6","112","32","HTM","password"]
Received message in topic 'command' & message is:- ["136","106","2","6","112","32","HTM","password"]
136
106
2
6
112
32
HTM
passw
0
1 3ffb223c a
2 3ffb223c ab
3 3ffb223c abc
4 3ffb223c abcd
5 3ffb223c abcde
6 3ffb223c abcdef
7 3ffb223c abcdefg
8 3ffb223c abcdefgh
9 3ffb223c abcdefghi
10 3ffb223c abcdefghij
11 3ffb223c abcdefghijk
12 3ffb223c abcdefghijkl
13 3ffb223c abcdefghijklm
14 3ffb223c abcdefghijklmn
15 3ffb8998 abcdefghijklmno
sizeof(String): 16
The code is part of larger project so I hesitated to post full code but link below has full code:
The code in question starts at line 379.
Thanks.
Ah: instead of the format string allowing 5 bytes each, that code allows 20
And the relevant portion of the variable declarations is
As shown previously, a String object occupies 16 bytes. The way the code works, the layout with SSO
struct _sso {
char buff[SSOSIZE];
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
unsigned char isSSO : 1;
} __attribute__((packed)); // Ensure that GCC doesn't expand the flag byte to a 32-bit word for alignment issues
has the char buffer, which must include the terminating NUL, followed by a bit field: 7 bits for the length (more than enough) and a single bit indicating that it is SSO mode. It's actually part of a union
union {
struct _ptr ptr;
struct _sso sso;
};
which when not in SSO mode, is
struct _ptr {
char *buff;
uint32_t cap;
uint32_t len;
};
SSOSIZE is defined according to that latter struct
// This allows strings up up to 11 (10 + \0 termination) without any extra space.
enum {
SSOSIZE = sizeof(struct _ptr) + 4 - 1
};
The comment is incorrect (it's old and has not been updated as the code has been ported, if it was ever correct)
struct _ptr is 12 bytesSSOSIZE is 15So TL;DR: you can't store 20 bytes there. The memory layout for those String variables is "backwards", with the earlier variables later in memory; with 12-, 13-, and 14-byte strings it would be
0123456789abcdef z=NUL b=bitfield
----------------
graphData 123456789 14zb
password 123456789 13z b
ssid 123456789 12z b
The test reads HTM and password, which results in
0123456789abcdef z=NUL b=bitfield
----------------
graphData 123456789 14zb
password passwordz 13z b
ssid HTMz56789 12z b
So luckily, the code still works if you stick with c_str(). (The length() has not been updated.) But if you read a long password like TWENTY-BYTE-PASSWORD
0123456789abcdef z=NUL b=bitfield
----------------
graphData 123456789 14zb
password TWENTY-BYTE-PASS
ssid WORDz6789 12z b
This is a classic "why C is an unsafe language" buffer overflow.
ssid.c_str() points to NUL-terminated WORD, which has overwritten HTM12password has been obliterated
isSSO flag falseNow the memory layout is that of struct _ptr. If you print password.c_str() and password.length() in hex (the capacity is not immediately accessible)
4E455754
2D455459
Those numbers should look familiar (when read backwards, or little-endian)
$ echo -n "TWENTY-BYTE-PASS" | hexdump -C
00000000 54 57 45 4e 54 59 2d 42 59 54 45 2d 50 41 53 53 |TWENTY-BYTE-PASS|
00000010
That means you definitely not try to use password as a String, since the pointer and length are invalid. Here's a demo sketch
String ssid, password, graphData, Hour, Minute;
void setup() {
Serial.begin(115200);
ssid = "123456789 12";
password = "123456789 13";
graphData = "123456789 14";
Serial.println(reinterpret_cast<uintptr_t>(&ssid), HEX);
Serial.println(reinterpret_cast<uintptr_t>(&password), HEX);
Serial.println(reinterpret_cast<uintptr_t>(&graphData), HEX);
sscanf("HTM TWENTY-BYTE-PASSWORD", "%20s %20s", &ssid, &password);
Serial.println(ssid.c_str());
Serial.println(ssid.length());
Serial.println(reinterpret_cast<uintptr_t>(password.c_str()), HEX);
Serial.println(password.length(), HEX);
// don't uncomment this
// Serial.println(password);
}
void loop() {}
Use an appropriately-sized plain char array
```cpp
char ssid[21], password[21];
String graphData, Hour, Minute;
with those array names/pointers as arguments to sscanf: ssid, password);
Thank you for explaining in all these detail.