Go Down

Topic: Setting Variable from MAC Address (Read 1 time) previous topic - next topic

cweinhofer

Oct 01, 2018, 02:44 pm Last Edit: Oct 01, 2018, 02:53 pm by cweinhofer Reason: removed extra linebreak
I am setting up a series of temperature sensors for my home, using the LOLIN (WEMOS) D1 mini pro (a ESP8266-based board). I have multiple sensors, with the code for each being identical except for the variables which tell the server which sensor it is. In this case there are four:
Code: [Select]
char sensorIDtemp[] = "livingroomT";
char sensorIDhumd[] = "livingroomH";

which are used to send the temperature and humidity to a MagicMirror as part of an HTTP POST.

Code: [Select]
unsigned long myChannelNumber = 123456;
const char * myWriteAPIKey = "A1B2C3D4E5F6G7H8";

which sends the same data to a ThingSpeak channel for logging purposes.

These work fine if they are hard-coded into the beginning of the sketch. But whenever I update the code, I have to remember to change each of each of the four variables as I am uploading the sketchto each board. To avoid doing that, I'm trying to work out a solution where each device would read some unique identifier and automatically adjust the variables.

I have three questions (in bold below):

1) What I have below is just my very un-knowledgable, hacked-up solution. If anyone has suggestions for a more elegant way of doing this, I'd be happy to consider it.

2) I decided to use the MAC address as the unique identifier. Because the MAC address -- as-is from WiFi.macAddress() -- can't be used with switch, I decided to derive a relatively unique integer for each MAC address. I turned each octet into an integer and then did a little math on the six numbers to produce a 4-digit integer. It's no where near as unique as the actual MAC address, but with 5,000+ possibilities, the chances of having duplicate values is quite small.

This works, but it feels like a lot of fiddling. Anyone know of a better way to do this?

Code: [Select]
byte mac[6];

// at the beginning of the code, and then in in void setup()

WiFi.macAddress(mac);
Serial.print("MAC Address: ");
Serial.println(WiFi.macAddress());

int mac0 = mac[0];
int mac1 = mac[1];
int mac2 = mac[2];
int mac3 = mac[3];
int mac4 = mac[4];
int mac5 = mac[5];
 int macIfM = ( ( mac0 * 1 ) + ( mac1 * 2 ) + ( mac2 * 3 ) + ( mac3 * 4 ) + ( mac4 * 5 ) + ( mac5 * 6 ) );

Serial.print("Integer from MAC: ");
Serial.println(macIfM);


3) The thing I can't get to work at all, however, is the switch itself. Here's the code I have currently. (I'll include just one case for brevity, but the actually code has many):
Code: [Select]
switch (macIfM) {
 case 3579: {
 char sensorReadName[] = "Living Room";   // for Serial.print
 char sensorIDtemp[] = "livingroomT";
 char sensorIDhumd[] = "livingroomH";
 unsigned long myChannelNumber = 162534;
 const char * myWriteAPIKey = "A1B2C3D4E5F6G7H8";;
 }
 break;
 default: {    // these are just dummy value to avoid "not declared" errors
 char sensorReadName[] = "Unknown";
 char sensorIDtemp[] = "unknownT";
 char sensorIDhumd[] = "unknownH";
 unsigned long myChannelNumber = 121212;
 const char * myWriteAPIKey = "XY12XY12XY12XY12";
 }
 break;
 }


I tried it without enclosing each case in curly brackets, but I got
Quote
error: jump to case label [-fpermissive]
I tried it with curly brackets and got
Quote
not declared in this scope
when I call the variable later in the sketch.

I tried declaring the variables at the beginning of the sketch (moving the default/dummy values to the beginning of the sketch), hoping the switch would overwrite them, but it didn't. The sketch just uses the dummy values throughout.

Can anyone figure out how to do this properly?

I'm still pretty green when it comes to coding, so you're welcome to include any extra detail you'd like with your answer.

Thanks!

J-M-L

#1
Oct 01, 2018, 02:58 pm Last Edit: Oct 01, 2018, 03:20 pm by J-M-L
when you are in a switch/case you can't declare local variable to one case without the Compound statements (or blocks which are brace-enclosed {} sequences of statements) , but then of course the variables are local to that statement and don't exist outside. (see variable scope)

what you want to have is a set of global variables defined at the top of the program
Code: [Select]
const uint8_t maxStr = 20;
char sensorReadName[maxStr];   // for Serial.print
char sensorIDtemp[maxStr] ;
char sensorIDhumd[maxStr]";


and use strcpy() to copy in the buffer the names you want in the switch case. (make sure your buffer is large enough for the longest name +1 byte)

alternatively you could declare everything at global level and just associate pointers at run time. that will be easier and you won't waste as much memory as each string will have the exact right number of bytes allocated.

Code: [Select]
const char * livingRoom =  "Living Room";
const char * livingRoomT =  "livingroomT";
const char * livingRoomH =  "livingroomH";

const char * bedRoom =  "Bed Room";
const char * bedRoomT =  "bedRoomT";
const char * bedRoomH =  "bedRoomH";

const char * sensorReadName;
const char * sensorIDtemp;
const char * sensorIDhumd;

byte macIfM;

void setup() {
  Serial.begin(115200);

  macIfM = 1; //  do your computation

  switch (macIfM) {
    case 1:
      sensorReadName = livingRoom;
      sensorIDtemp = livingRoomT;
      sensorIDhumd = livingRoomH;
      break;

    case 2:
      sensorReadName = bedRoom;
      sensorIDtemp = bedRoomT;
      sensorIDhumd = bedRoomH;
      break;

    default: // ERROR
      while (1); // die here
      break;
  }

}

void loop() {}


The challenge with this approach though is that your switch depends on the Mac address of your arduinos, so you need to modify the source code every time you add a new device...

Another (better) alternative would be to put your configuration information in SPIFFS as a text file and read that in the setup. this way you can really upload the very same program to all your units and only change the config file in the file system to define the names. No need to modify the code
Hello - Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums.
Bonjour Pas de messages privés SVP, postez dans le forum directement pour que ça profite à tous

cweinhofer

Thanks for the helpful reply. I appreciate knowing about various options.

A few follow up questions about the SPIFFS option:

1) As you mentioned, the non-SPIFFS option would require me to modify the code each time I add a new device. But (unless I'm missing something) I don't think I would have to upload the new code to each device each time. As long as a device has a set of config variables and a case in switch matching it's own MAC, it shouldn't matter if it knows about cases for subsequent deices, right?

Of course, I'd have to re-upload the code to every device if the core code changed, but that's a given. The thing I'm trying to avoid in all this is just the need to manually change the variables for each upload if I do have to do a all-device update.

2) You seem to indicate that modifying the config file and uploading it to SPIFFS for each new device is preferable to modifying and uploading new code. I'm curious why that would be?

3) In order to avoid modifying the code when adding a new device, I would need the config file in SPIFFS to have both the initial config options as well as at least some parts of the switch...case, right?

I'm guessing I'd keep the basic switch code in the main file, but have a reference(s) which would check for specific cases and resulting code in the config file. Any tips you can give for how to do that would certainly be appreciated.

Wawa

#3
Oct 03, 2018, 06:21 am Last Edit: Oct 03, 2018, 06:22 am by Wawa
I'm trying to work out a solution where each device would read some unique identifier and automatically adjust the variables.
Each ESP8266 seemst to have a unique ID number.
I print it in setup() with this line:

Serial.begin(74880); // this baudrate also shows ESP-12 startup data
Serial.printf("\r\nESP-12 chip ID: %d\r\n", ESP.getChipId());

I supposed it can be used to identify the board.
Leo..

J-M-L

#4
Oct 03, 2018, 07:03 am Last Edit: Oct 03, 2018, 07:04 am by J-M-L
You are making good points. - let me give you some perspective.

On 1, yes only new devices would need to new code (and the central device). If your desire is to simplify the upload only and you don't care having different codes on different devices as long as they deliver the same behaviour then that ticks that box. Personally I prefer having the same config exactly everywhere. That simplifies QA/testing

On 2 and 3 - the point is that once the information is dynamic but with permanent storage, the way you get the config file there can be dynamic too. For example First time your program starts and does not find the config file, could query the central hub for a config file. Then the only hardcoded information is your wireless network and how to reach out the central hub. Even more dynamic, you could think of offering a local web interface at first start (or admin mode) to enter this information, which the application will write into SPIFFS. You then have a totally flexible / configurable system. ( this is what "libraries" such as WiFi Manager or one of the multiple ESP WebConfig (here is one of those)

Hello - Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums.
Bonjour Pas de messages privés SVP, postez dans le forum directement pour que ça profite à tous

cweinhofer

Each ESP8266 seems to have a unique ID number.
The research I did seemed to indicate that the chip ID for these chips is identical to the NIC portion of the MAC address. So I guess I could have just done my math on the last three octets -- but either way you would need to do something to convert it to an integer.

For example First time your program starts and does not find the config file, could query the central hub for a config file. Then the only hard-coded information is your wireless network and how to reach out the central hub. Even more dynamic, you could think of offering a local web interface at first start (or admin mode) to enter this information, which the application will write into SPIFFS. You then have a totally flexible / configurable system.
This sounds great, but way beyond my skills at the moment. I think I'll just have to settle for hard-coding the config info into the beginning of the sketch for now.

J-M-L

Well that's what libraries do for you - abstract a bit that complexity

But hard coding does work too :)
Hello - Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums.
Bonjour Pas de messages privés SVP, postez dans le forum directement pour que ça profite à tous

gfvalvo

#7
Oct 03, 2018, 01:34 pm Last Edit: Oct 03, 2018, 01:35 pm by gfvalvo
The research I did seemed to indicate that the chip ID for these chips is identical to the NIC portion of the MAC address. So I guess I could have just done my math on the last three octets -- but either way you would need to do something to convert it to an integer.
It already is a uint32_t. Check out the function's prototype in Esp.h:
Code: [Select]
uint32_t getChipId();
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

gfvalvo

alternatively you could declare everything at global level and just associate pointers at run time. that will be easier and you won't waste as much memory as each string will have the exact right number of bytes allocated.
Code: [Select]
const char * livingRoom =  "Living Room";

This is safer as it prevents the pointer from being changed and stranding the string literal in memory, never to be heard from again:
Code: [Select]
const char * const livingRoom =  "Living Room";
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

J-M-L

#9
Oct 04, 2018, 03:26 am Last Edit: May 02, 2019, 12:19 pm by J-M-L
Yes this is indeed true (to avoid stupid mistakes which I do not have in the code above :), but does not hurt to inform the compiler)
Hello - Please do not PM me for help,  others will benefit as well if you post your question publicly on the forums.
Bonjour Pas de messages privés SVP, postez dans le forum directement pour que ça profite à tous

cweinhofer

#10
Oct 04, 2018, 03:41 pm Last Edit: Oct 04, 2018, 03:45 pm by cweinhofer Reason: added links
It already is a uint32_t. Check out the function's prototype in Esp.h:
Code: [Select]
uint32_t getChipId();
Glad you said something. I'm quite new to this and so still relying heavily on the Arduino Reference pages. The switch...case page (https://www.arduino.cc/reference/en/language/structure/control-structure/switchcase/) said that the allowed data types for the parameters were int & char. I took int to mean a 16-bit value as defined here: https://www.arduino.cc/reference/en/language/variables/data-types/int/

However, as per your tip, I just tried using ESP.getChipId() for the parameters and everything seems to work fine.

Thanks for helping me get rid of that clunky bit of code. :)

cweinhofer

A note for any other newbies like me who happen to be using ThingSpeak.

J-M-L's suggestion of using strcpy() works great for most of the variables, but the ThingSpeak library wants an unsigned long for "myChannelNumber".

I declared an empty unsigned long in the beginning of the sketch
Code: [Select]
unsigned long channelNumber;

and then assigned it a value in the switch...case
Code: [Select]
channelNumber = 567890;

cweinhofer

#12
May 02, 2019, 11:01 am Last Edit: May 02, 2019, 11:01 am by cweinhofer
Some updated information. If anyone is trying to use the ESP.getChipId() solution with ESP32s (which don't support it), you can check out what I posted here: http://forum.arduino.cc/index.php?topic=613549.0

Go Up