Go Down

Topic: moving function map into library (Read 619 times) previous topic - next topic

KRavEN

I'm trying to write library for interfacing with some hardware via serial.  I've got things working well like this in a sketch:

Code: [Select]
const static struct {
  const char *name;
  void (*func)(char *);
} function_map [] = {
  { "status", processStatusLine },
};

void processStatusLine(char * buffer) {
  Serial.println((char*)buffer);
}

int call_function(const char *name, char * var) {
  int i;
  for (i = 0; i < (sizeof(function_map) / sizeof(function_map[0])); i++) {
    if (!strcmp(function_map[i].name, name) && function_map[i].func) {
      function_map[i].func(var);
      return 0;
    }
  }
  return -1;
}

void setup () {
  Serial.begin(9600);
  char * buffer;
  call_function("status", buffer);
}

void loop() {
}

How do I move the function_map and call_function into my HardwareClass.h and HardwareClass.cpp?  I've tried quite a few things and couldn't get it working.

pYro_65

#1
Feb 25, 2013, 06:21 am Last Edit: Feb 25, 2013, 06:25 am by pYro_65 Reason: 1
This kind of layout below should work, ( untested ) and function_map is not available outside of the Cpp, if it needs to be usable in the sketch file, you'll need a variable marked extern.

Edit: or you will have to make processStatusLine visible to the cpp, by placing its declaration in the header ( can leave the definition in the sketch )

Header:
Code: [Select]

struct function_map_type{
 const char *name;
 void (*func)(char *);
};

int call_function(const char *name, char * var);


Cpp
Code: [Select]
#include "Arduino.h"

static const function_map_type function_map[] = { { "status", processStatusLine }, };

int call_function(const char *name, char * var) {
 int i;
 for (i = 0; i < (sizeof(function_map) / sizeof(function_map[0])); i++) {
   if (!strcmp(function_map[i].name, name) && function_map[i].func) {
     function_map[i].func(var);
     return 0;
   }
 }
 return -1;
}

KRavEN

That's not working either.  I also think I need the struct to be private to the object:

Here's what I was trying that's not working

header
Code: [Select]
#ifndef HwClassSerial_h
#define HwClassSerial_h

#include "Arduino.h"

class HwClassSerial
{
  public:
    HwClassSerial(HardwareSerial &_hwClassSerial);
   
  private:
    HardwareSerial* hwClassSerial;   
    void init();
   
    struct _function_map{
      char *name;
      void (*func)(char *);
    };
   
    int _call_function(const char *name, char * var);
    void _processStatusLine(char * buffer);


Also tried this under private
Code: [Select]
    typedef struct {
      char *name;
      void (HwClassSerial::*func)(char *);
    } function_map[];
   
    static function_map _function_map;


cpp
Code: [Select]
#include "Arduino.h"
#include "HwClassSerial.h"

static const _function_map function_map[] = { { "status", _processStatusLine }, };

HwClassSerial::HwClassSerial(HardwareSerial &_hwClassSerial) {
  hwClassSerial = &_hwClassSerial;
  init(); 
}

void HwClassSerial::init()

  hwClassSerial->begin(9600);
  static function_map _function_map  = {
    { "status", _processStatusLine },
  };
}

int HwClassSerial::_call_function(const char * name, char * var) {
  int i;

  for (i = 0; i < (sizeof(_function_map) / sizeof(_function_map[0])); i++) {
    if (!strcmp(_function_map[i].name, name) && _function_map[i].func) {
      //_function_map[i].func(var);
      return 0;
    }
  }

  return -1;
}

void HwClassSerial::_processStatusLine(char * buffer) {
}

pYro_65

Code: [Select]
    typedef struct {
      char *name;
      void (HwClassSerial::*func)(char *);
    } function_map[];
   
    static function_map _function_map;


static function_map _function_map; is invalid, function_map is declared as an array of 'anonymous structs'. Don't worry about typedefs on structs, they are not necessary for this usage in C++.

What I had does work, here is a working example you can modify.

Sketch file:
Code: [Select]

#include "a.h"

void processStatusLine(char * buffer) {
  Serial.println((char*)buffer);
}

void setup()
  {
    Serial.begin( 9600 );
    call_function( "status", "test string" );
    return;
  }

void loop(){ return; }


Header ( a.h )
Code: [Select]
struct function_map_type{
  const char *name;
  void (*func)(char *);
};

int call_function(const char *name, char * var);
void processStatusLine(char * buffer);


Source ( a.cpp )
Code: [Select]
#include "a.h"
#include "Arduino.h"

static const function_map_type function_map[] = { { "status", processStatusLine }, };

int call_function(const char *name, char * var) {
  int i;
  for (i = 0; i < (sizeof(function_map) / sizeof(function_map[0])); i++) {
    if (!strcmp(function_map[i].name, name) && function_map[i].func) {
      function_map[i].func(var);
      return 0;
    }
  }
  return -1;
}

KRavEN

Yes, that does work that way. 

Is it possible for the processStatusLine, function_map_struct, and call_function to all be within the scope of a HwClassSerial class instance instead of in the global scope?  The reason for this is I have several pieces of gear all controlled by serial so I'm trying to setup a base class for each to extend.  Each child class would define it's own function_map_type and there could be several child classes instantiated simultaneously.

pYro_65

Quote
Is it possible for the processStatusLine, function_map_struct, and call_function to all be within the scope of a HwClassSerial class instance instead of in the global scope?


Yes, using the correct working code I provided, you should be able to put them in. However not for the way you want.
To keep with ease of use, processStatusLine and other functions to be called via a pointer need to be non-member functions( static or friends ).

But really, you are mixing two different systems when one will do. call_function is a lookup method, whereas using an inherited/base type should imply that the derived type provides the missing information, which completes the base type.

KRavEN

The problem I'm trying to solve is that I will have several device classes.  For code re-usability I want to have a base class that has a few private methods.
Code: [Select]

// sends a command to a serial device and sends each received byte from the output to _processIncomingByte
void _processCommand(const char * commandName, const int longestLine, int linesToRead);

// this takes each received byte and constructs character arrays to be processed.  The method to process the
// character array is determined by the mapping of the commandName
void _processIncomingByte(const byte c, char * buffer, const char * commandName, const int lineLength);


So each class that extends the base class will have a different set of commands to run and methods to process those commands.  Once class could have:
Code: [Select]
struct {
  const char *name;
  void (*func)(char *);
} function_map [] = {
  { "status", processStatusLine },
  { "update", processUpdateLine },
};


and another class could have:
Code: [Select]
struct {
  const char *name;
  void (*func)(char *);
} function_map [] = {
  { "config", processConfigLine },
  { "reset", processRestLine },
};


Each process function should also be private to each class.  I hope that makes sense. :)

pYro_65

#7
Feb 25, 2013, 11:56 pm Last Edit: Feb 25, 2013, 11:57 pm by pYro_65 Reason: 1
If you want the idea encapsulated in a class, and only one serial device is communicating at once, then things are easy using classes.

Imagine your base class is something like this.
Code: [Select]
struct BaseType{
 /*
   Pure virtual as you will not be creating instances of this class, only instances of
   classes that derive this base class.
 */
 virtual void RunCommand( const char *c_Data ) = 0;
};


Code: [Select]
struct DerivedA : BaseType{

 void RunCommand( const char *c_Data ){

   if( strcmp( "status", c_Data ) ){

   }else if( strcmp( "update", c_Data ) ){

   }
 }
};

struct DerivedB : BaseType{

 void RunCommand( const char *c_Data ){

   if( strcmp( "config", c_Data ) ){

   }else if( strcmp( "reset", c_Data ) ){

   }    
 }
};



Code: [Select]
DerivedA objA;
DerivedB objB;

BaseType *currentObj = &objA;

void loop(){

char c_Buffer[ x ];
//...  fill c_buffer with command.

//Run command based on current object.
currentObj->RunCommand( c_Buffer );
}

Go Up