Pages: [1] 2   Go Down
Author Topic: String to function: any best practices?  (Read 1665 times)
0 Members and 1 Guest are viewing this topic.
Amsterdam, Netherlands
Offline Offline
Jr. Member
**
Karma: 0
Posts: 52
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi forum,
I need to execute functions from strings, so "somestring" should call function somestring();
It seems as though this is a classical 'problem', and it has a boatload of solutions. Some of them are cumbersome and ugly.
The problem would be easy if arduino could do a String Switch() but it can't, so i'm in doubt about the following solutions:

- Create a map of function pointers and Strings, and directly call the function mapped to a certain string.
(this seems like the nicest solution. but how do i create a 'map' in arduino? would this just be an array, and then call the function like array[0](); ? )

- Have a map of ENUM values and Strings. Create ENUMS for all functions. switch the ENUMS to execute a function with the same name.

- Just use a huge if/else block. This sounds ridiculous but it would be the fastest solution would it not?


Did anyone else out there had the same problem and how did you solve it?
Logged

Seattle, WA USA
Online Online
Brattain Member
*****
Karma: 601
Posts: 48556
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
This sounds ridiculous but it would be the fastest solution would it not?
Not to mention the most obvious and easiest to implement.

Of course, the statement:
Quote
I need to execute functions from strings, so "somestring" should call function somestring();
leaves one wondering why you need to do this. Having to know the name of the function to execute means that expanding the Arduino application can only happen in concert with changes to the application that is sending data to the Arduino.

A menu of options ("execute item #2") would require sending much less data, and make the mapping of data to function much simpler.
Logged

UK
Offline Offline
Faraday Member
**
Karma: 99
Posts: 4153
Where is your SSCCE?!?!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I usually use option 1 - create a map.

Here's a block of code from a digital emulator project I am working on:

Code:

struct opcode {
        unsigned short opcode;
        unsigned short mask;
        void (*function)(struct device *, unsigned short, char *);
};

struct opcode opcodes[] = {
        {0b000000000000, 0b111111111111, nop},
        {0b000001000000, 0b111111111111, clrw},
        {0b000000000100, 0b111111111111, clrwdt},
        {0b000000000010, 0b111111111111, option},
        {0b000000000011, 0b111111111111, _sleep},

        {0b000000000000, 0b111111111000, tris},
   ...
        {0,0,0}
};

The first two variables from each entry are what I use to select the entry.  This could be your string (or char* as it is far far more efficient).  The third is the function to call.

All these functions have to have the same prototype for it to work.

I then loop through looking for a match:

Code:
struct opcode *scan;

for(scan=opcodes; scan->function; scan++)
{
  if((instruction & scan->mask) == scan->opcode)
  {
    scan->function(dev,instruction,debugBuffer);
    break;
  }
}

So the whole thing consists of:

1. Define a structure to contain the data.
2. Populate a static array with the data and function pointers.
3. Iterate the array looking for a match.
4. If a match is found, call the function.
Logged

Get 10% off all 4D Systems TFT screens this month: use discount code MAJENKO10

nr Bundaberg, Australia
Offline Offline
Tesla Member
***
Karma: 126
Posts: 8474
Scattered showers my arse -- Noah, 2348BC.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
but how do i create a 'map' in arduino? would this just be an array,
Two parallel arrays, one of strings and the other of function pointers, or a single array of structs each with these elements.

Quote
Just use a huge if/else block. This sounds ridiculous but it would be the fastest solution would it not?
No faster than the above approach, either way you have compare strings and call functions. There may be a small difference either way but not enough IMO to justify a huge ugly if/else block.

______
Rob
« Last Edit: June 13, 2012, 09:01:41 am by Graynomad » Logged

Rob Gray aka the GRAYnomad www.robgray.com

Amsterdam, Netherlands
Offline Offline
Jr. Member
**
Karma: 0
Posts: 52
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Wow, thanks for all the superfast replies!

@PaulS Yes, this is completely true. however, i'm creating a proof of concept project, so all is in my own control (app-side and arduino-side coding).
Actually i'm trying to create a really badly written kind of RPC protocol (which contains functions i choose of course). Why you ask? well, because i cannot use a real RPC-like protocol, it's just plain text what gets sent from- and recieved on arduino, so i just have to work with Strings (or char* which apparently is more effective, will change that).
Oh bytheway - you reacted on a question i posted earlier about my project needing Wireless communication with an iPad app: My project now works with webSockets, using Per Ejeklint's arduino webSocketServer (he updated it to the latest spec and it now works in chrome, and hopefully also iPad, need to test that still. As webSockets just transfer plain text.. well, there you go :>)

@Majenko Yes! This is exactly what i'm looking for. I was hoping it would be less code, but this seems the least ugly solution!

@Graynomad Ah yes, that would be another way. I think one array with structs would be less error-prone and more clear. But i think i'll just copy paste Majenko's solution as my C++ experience is completely worthless. I understand the code when i see it, but i can't write it myself.
« Last Edit: June 13, 2012, 09:08:13 am by supermaggel » Logged

Global Moderator
UK
Offline Offline
Brattain Member
*****
Karma: 291
Posts: 25858
I don't think you connected the grounds, Dave.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
No faster than the above approach, either way you have compare strings and call functions.
A linear search will (on average, for random data) always be longer than a binary search - sort those keys!
Logged

"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

UK
Offline Offline
Faraday Member
**
Karma: 99
Posts: 4153
Where is your SSCCE?!?!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
No faster than the above approach, either way you have compare strings and call functions.
A linear search will (on average, for random data) always be longer than a binary search - sort those keys!

Absolutely!  Order matters.  For the stuff I posted there it has to be in "mask" order to work properly.  I would suggest you build your array in descending order of popularity of the commands sent - the most common ones at the top.
Logged

Get 10% off all 4D Systems TFT screens this month: use discount code MAJENKO10

Amsterdam, Netherlands
Offline Offline
Jr. Member
**
Karma: 0
Posts: 52
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Okay, i now have this:


// command structure (function name, parameter, actual function)
struct command {
        char* functionName;
        void (*function)(String, String);
};

// array of command structures
struct command commands[] = {
  {"GET_Speed", GET_Speed},
  {"SET_Mode", SET_Mode}
};


The rest now looks like this:

  // extracting function and parameters (yeah ugly i know).
  String data = String(dataString);
  String command;
  String param1;
  String param2;
 
  command = data.substring(0, data.indexOf(' '));
  param1 = data.substring(data.indexOf(' '));
  param2 = data.substring(data.lastIndexOf(' '));


struct command *scan;

  for(scan=commands; scan->function; scan++)
  {
    if(command == scan->functionName)
    {
      scan->function(param1, param2);
      break;
    }
  }

void GET_Speed(String param1, String param2) {
  Serial.println("function GET_Speed executing with params: ");
  Serial.println(param1);
  Serial.println("\t and ");
  Serial.println(param2);
  Serial.println("\n\n");
}

void SET_Mode(String param1, String param2) {
  Serial.println("function SET_Mode executing with params: ");
  Serial.println(param1);
  Serial.println("\t and ");
  Serial.println(param2);
  Serial.println("\n\n");
}

As you can see i still use String instead of char*, but that's because i don't know C++ and how to do subString(), indexOf() and lastIndexOf() on char (assuming that's even possible)

It compiles, let's see if it works. Thanks for all your help! smiley
« Last Edit: June 13, 2012, 09:54:31 am by supermaggel » Logged

UK
Offline Offline
Faraday Member
**
Karma: 99
Posts: 4153
Where is your SSCCE?!?!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

It won't work smiley-razz

First, you need to terminate your array.  Have an entry at the very end of all 0, then you look for that 0 to know when you have reached the end of your array.

Secondly, you're not comparing the string contents, but the string addresses.

You need to use
Code:
if(!strcmp(command, scan->functionName))
{
   ...
}

instead of if(command == scan->functionName)
Logged

Get 10% off all 4D Systems TFT screens this month: use discount code MAJENKO10

Amsterdam, Netherlands
Offline Offline
Jr. Member
**
Karma: 0
Posts: 52
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Ah thanks, i've added the {0,0} struct.

As everything is now a String, i'm using the arduino String compareTo function:

if(command.compareTo(scan->functionName))

shit yeah, it even works:

http://s17.postimage.org/vfiukqj7j/Screen_Shot_2012_06_13_at_17_17_36_PM.png
Logged

UK
Offline Offline
Faraday Member
**
Karma: 99
Posts: 4153
Where is your SSCCE?!?!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Ugh... String...  You may well end up with fragmented memory and an arduino that can no longer handle the incoming requests.  Much better to use statically allocated char *.
Logged

Get 10% off all 4D Systems TFT screens this month: use discount code MAJENKO10

Amsterdam, Netherlands
Offline Offline
Jr. Member
**
Karma: 0
Posts: 52
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Coming from higher level languages, i think a String object is way more nice to work with then a 'character array'.
But for lower level programming i can understand performance and memory saving means everything.
So, how would i approach a character array substring comparison? if you tell me, i can change everything
to char* :> anyway, it's all proof of concept.
Logged

UK
Offline Offline
Faraday Member
**
Karma: 99
Posts: 4153
Where is your SSCCE?!?!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

You have a number of tools at your disposal.

To match an entire string, you use strcmp():
Code:
match = strcmp(str1,str2)

match is <0 or >0 for a mismatch (str1 < str2, or str1 > str2), or 0 for a match, so you can use
Code:
if(!strcmp(command,"run"))
{
  ...
}

If you know the length of the substring, then you have the strncmp() function.

Code:
match = strncmp(str1, str2, size);

match is <0 or >0 for a mismatch (str1 < str2, or str1 > str2), or 0 for a match.

If you don't know where the substring is, then there is the strstr() function.

Code:
pos = strstr(str1,str2);

which returns the position of str2 within str1.

Then there is sscanf, which can be used to find parameters within a string:

Code:
int a,b,c;
if(sscanf(command,"set_coords %d %d %d",&a,&b,&c))
{
  ...
}

will take the string "set_coords 34 2 4983" and assign the numbers to the variables a b and c.  sscanf returns the number of parameters matched.

And there are loads more.
« Last Edit: June 13, 2012, 04:08:42 pm by majenko » Logged

Get 10% off all 4D Systems TFT screens this month: use discount code MAJENKO10

UK
Offline Offline
Faraday Member
**
Karma: 99
Posts: 4153
Where is your SSCCE?!?!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Accessing individual characters in a string can be done with the [] suffix:

Code:
char onechar = mystring[4];

An offset within a string can be created with simple maths:

Code:
char mystring[] = "This is my string";
char *offset;

offset = mystring + 7;
// offset points to "my string"
Logged

Get 10% off all 4D Systems TFT screens this month: use discount code MAJENKO10

Pittsburgh, PA, USA
Offline Offline
Faraday Member
**
Karma: 96
Posts: 4773
I learn a bit every time I visit the forum.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Coming from higher level languages, i think a String object is way more nice to work with then a 'character array'.
But for lower level programming i can understand performance and memory saving means everything.
So, how would i approach a character array substring comparison? if you tell me, i can change everything
to char* :> anyway, it's all proof of concept.

Coming from more ram than you need into ... well it depends on what AVR you use how tight the environment may be. Some have 16k or more on-chip and can address external ram while the UNO gives you 2k total. In high-level land you might use yacc and lex.

Performance is not worth ram on the small ram chips. Constants including text arrays can be stored in the more copious flash ram using PROGMEM and accessed directly or through buffer(s) for manipulation at a speed penalty.

I don't know how much text your proof of concept involves but perhaps an object class that has function pointer, PROGMEM link and _maybe_ the first 1 to 4 characters of the match string as data members (to speed up search) along with functions to find text matches would simplify your code? I would recommend making an array of such objects even if only to dispense with need of links that 'new' objects would require. In practice you lose a little flexibility but that's like saying you can't open an umbrella in a phone booth.

Really, so much depends on the number of words to match and act upon.
Logged

I find it harder to express logic in English than in Code.
Sometimes an example says more than many times as many words.

Pages: [1] 2   Go Up
Jump to: