moving function map into library

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

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.

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:

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

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

Cpp

#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;
}

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

#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

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

cpp

#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) {
}
    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:

#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 )

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 )

#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;
}

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.

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.

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.

// 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:

struct {
  const char *name;
  void (*func)(char *);
} function_map [] = {
  { "status", processStatusLine },
  { "update", processUpdateLine },
};

and another class could have:

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. :slight_smile:

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.

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; 
};
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 ) ){

    }    
  }
};
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 );
}