функция-член нескольких отдельных объектов, передаваемых в качестве обработчика другой функции-члену - PullRequest
0 голосов
/ 16 октября 2018

Мой поток похож на мой предыдущий , который мне удалось обработать с помощью лямбда-функции.Но опять же у меня есть проблема, и я думаю, что лямбда-функции не являются решением проблемы, которую я на самом деле еще не полностью понял.

Что мне требовалось: в моих проектах arduino мне часто приходилось нуждатьсяизменить значение переменной вручную во время выполнения (например, рабочий цикл, состояние светодиода или что-либо еще).Поэтому я реализовал класс, который обеспечивает эту функциональность простым в использовании способом, то есть я могу получить доступ к глобальным переменным путем анализа входной строки (через serial, tcp, telnet, we), отформатированной в моем собственном протоколе.Еще одно требование состояло в том, чтобы было возможно связать событие с такой переменной, которая выполняется, когда к переменной обращаются через мой интерфейс.

Что я сделал: в упрощенной версии мой «параметр класса» имеетатрибуты для адреса моей переменной, имя, которое я выбираю для доступа к нему, и указатель на функцию, которую я хочу связать.Соответствующие методы read и write обрабатывают для доступа к фактическому значению по заданному адресу.Оригинальная версия также имеет атрибуты с информацией об уровне доступа и типе данных.Кроме того, отдельные события связаны с событиями чтения и записи, но я опущу эту часть, а также тот факт, что все соответствующие функции перегружены для доступа к переменным различных типов данных.Поэтому я буду придерживаться упрощенной версии, поскольку она достаточно короткая, чтобы продемонстрировать мою проблему.

Чтобы мой класс работал, мне нужна функция 'new_parameter (,)' для хранения моих параметров в векторе и функциях 'read_param () '/' write_param () 'для фактического доступа к определенному параметру через строку.Эти последние функции представляют мой синтаксический анализатор, но я также опущу это.

Ниже приведен код, который, кажется, работает хорошо, хотя я уверен, что есть более эффективные способы сделать это.Для MCVE он скомпилирован с g ++ 7.3.0, но, наконец, он должен скомпилироваться с avr-g ++.

//parameter.h
using namespace std;

class Parameter {
  public:
    Parameter(int *p, std::string n, void (*e)());
    long int address;
    std::string name;
    void (*event)();

    int read();
    void write(int v);
};
Parameter::Parameter (int *p, std::string n, void (*e)()) {
  address = (long int) p;
  name=n;
  event=e;
}

int Parameter::read () {
  if(event!=NULL){
    event();
  }
  int value = *reinterpret_cast<int*>(address);
  return value;
}

void Parameter::write (int v) {
  if(event!=NULL){
    event();
  }
  *(int*)(address)=v;
}

std::vector<Parameter> parameters;

void new_parameter (int *p, std::string n="defaultname", void (*e)()=NULL) {
  parameters.push_back(Parameter(p,n,e));
}

int read_param(std::string n) {
  for (int i=0;i<parameters.size();i++) {
    if (parameters[i].name == n) {
      int v = parameters[i].read();
      return v;
    }
  }
  return -1;
}

void write_param(std::string n, int v) {
  for (int i=0;i<parameters.size();i++) {
    if (parameters[i].name == n) {
      parameters[i].write(v);
      break;
    }
  }
}


//simple_main.cpp
#include <vector>
#include <string>
#include <locale>
#include <functional>
#include "device.h"

//a global variable
int variable1=-1;
//an event executed when global variable is accessed
void variable1_event () {printf("variable1 event\n");}

int main () {
  //create parameter object for variable
    new_parameter(&variable1,"variable1",variable1_event);

  //read parameter
  printf("1: %i\n",variable1);
  printf("2: %i\n",read_param("variable1"));

  //change value
  write_param("variable1",10);

  //read parameter
  printf("3: %i\n",variable1);
  printf("4: %i\n",read_param("variable1"));
}

При выполнении main () имеет следующий вывод:

1: -1
variable1 event
2: -1
variable1 event
3: 10
variable1 event
4: 10

Это отвечало моим требованиям в прошлом.Пока все хорошо!

В моем текущем проекте у меня есть переменное количество подчиненных устройств, подключенных к моему mcu (ESP32) с I2C, каждое из которых имеет идентичный набор параметров (например, заданная температура для контроля температуры)что я теперь хочу получить доступ через мой ранее продемонстрированный «параметр класса».Поскольку ведомые устройства одного типа, создание «класса Device» является очевидным решением.Затем я создаю любое количество объектов, в зависимости от того, сколько i2c-ведомых подключено.Использование «класса Device» означает, что мой параметр теперь будет указывать на атрибут, а соответствующая функция события теперь является методом события.Дело в том, что этот event-метод должен передавать данные определенному ведомому устройству и поэтому не может быть статичным, так как он должен вызываться с разными i2c-адресами (верно?).Я попробовал все, что было в моих силах, но пока не заставил его работать.

Это моя упрощенная версия «класса Device»:

//parameter.h
#define MAX_DEVICES 4

int device_count=0;

class Device {
    public:
    Device();
        Device(uint8_t i2c_address);

        bool is_default;
        uint8_t i2c_address;
        int data;

        void i2c_write();
};
Device::Device () {
  is_default=true;
}
Device::Device (uint8_t i2c) {
    is_default=false;
    i2c_address=i2c;
}

Device devices [MAX_DEVICES];

void Device::i2c_write () {
    printf("call to i2c_write (address %i, data %i)\n",i2c_address,data);
}

int get_free_index () {
    for (int i=0; i<MAX_DEVICES; i++) {
        if (devices[i].is_default) return i;
    }
    return -1;
}

void new_device (uint8_t i2c) {
    int new_index=get_free_index();
    if (new_index>=0) {
    devices[new_index]=Device(i2c);
//    new_parameter(&devices[new_index].data, "device"+std::to_string(new_index)+"data",  devices[new_index].i2c_transmit)
  }
    else printf("Error: exceeded maximum number of engines\n");
}

См. Мою расширенную основную функциюниже, чтобы увидеть, как я хотел бы обработать свои устройства.

//advanced_main.cpp
#include <vector>
#include <string>
#include <locale>
#include <functional>
#include "parameter2.h"
#include "device2.h"

int variable1=-1;
void variable1_event () {printf("variable1 event\n");}

int main () {
  //create parameter object for variable
    new_parameter(&variable1,"variable1",variable1_event);
  new_device(10);
  new_device(10);

  //read/write parameter
  printf("1: %i\n",read_param("variable1"));
  printf("2: %i\n",read_param("device0data"));
  printf("3: %i\n",read_param("device1data"));
  write_param("variable1",10);
  write_param("device0data",20);
  write_param("device1data",30);
  printf("4: %i\n",read_param("variable1"));
  printf("5: %i\n",read_param("device0data"));
  printf("6: %i\n",read_param("device1data"));
}

Результат, который я ожидал бы, если бы это работало:

variable1 event
1: -1
call to i2c_transmit (address 19, data 123)
2: 123
call to i2c_transmit (address 23, data 123)
3: 123
variable1 event
call to i2c_transmit (address 19, data 123)
call to i2c_transmit (address 23, data 123)
variable1 event
4: 10
call to i2c_transmit (address 19, data 20)
5: 20
call to i2c_transmit (address 23, data 30)
6: 30

, но на самом деле он даже не компилируетсяв этой версии:

device.h:40:120: error: invalid use of non-static member function ‘void Device::i2c_transmit()’
devices[new_index].data, "device"+std::to_string(new_index)+"data",  devices[new_index].i2c_transmit)

Все остальные способы, которыми я пытался передать функцию-член 'i2c_transmit ()' в конструктор 'параметра класса', тоже не работали, и хотя я часто понимаю, почему,я понятия не имею, КАК это работает ...

Тривиально ли создать локальный объект, сохранить копию этого объекта в глобальном массиве и работать только с этой копией?Я думаю, это то, что делает мой код выше.Я также пытался объявить 'Device devices [MAX_DEVICES];' как статический, но это не сработало.Я пытался использовать лямбда-функцию, но тоже не повезло ... Трудно сказать, что еще я пробовал, но я все равно думаю, что у меня есть проблемы с общей структурой.Я открыт для новых предложений, но так как «Параметр класса» является частью библиотеки, я бы хотел, чтобы этот класс не изменялся!

Ответы [ 2 ]

0 голосов
/ 17 октября 2018

Вы можете добавить параметр к обратному вызову, как указано в другом ответе.Другой вариант - полностью исключить обратные вызовы и использовать дизайн ООП.Создайте класс-оболочку вокруг int с виртуальными функциями для получения и установки.

class Parameter {
  public:
    Parameter(String n, int default_value = 0) :
        name(n), value(default_value) {}

    // Virtual functions that can be overridden but have default functionality
    virtual int read() {
        return value;
    }
    virtual int write(int new_value) {
        return value = new_value;
    }

    String name;
  protected:
    int value;

    // Below are static functions and variables
    // ----
    // std lib is not supported on Arduino, so use an array 
    // Arduino programs are small enough that you should be
    // able to make an educated guess at MAX_PARAMETERS
    // Also note it is an array of pointers for virtual functions to work
    static Parameter *all_values[MAX_PARAMETERS];
    static int parameter_count;
  public:
    static bool add_param(Parameter *p) {
        if (parameter_count < MAX_PARAMETERS) {
            all_values[parameter_count++] = p;
            return true;
        }
        return false;
    }
    static Parameter * find_param(String name) {
         for (int i = 0; i < parameter_count; i++) {
             if (all_values[i]->name == name) return all_values[i];
         }
         return nullptr;
    }
};
Parameter * Parameter::all_values[MAX_PARAMETERS];
int Parameter::parameter_count = 0;

Для параметров i2c вы можете расширить этот класс

class Device : public Parameter
{
  protected:
     uint8_t address;
  public:
    Device(std::string n, uint8_t i2c_address) :
        address(i2c_address), Parameter(n) {}

    // Override these
    int read() {
        // Add your code here to read the value from the bus....
        return value;
    }
    int write(int new_value) {
        Parameter::write(new_value);
        // Add your code here to write the value to the bus....
        return value;
    }
};

Чтобы использовать его, вы можете сделатьчто-то вроде:

// Create and add regular param
Parameter::add_param(new Parameter("test1"));
// Create and add i2c device param
Parameter::add_param(new Device("test2", 99));

Parameter::find_param("test1")->write(100);
int x = Parameter::find_param("test1")->read();

Parameter::find_param("test2")->write(123);
int y = Parameter::find_param("test2")->read();

// You can also use temp vars to simplify
Parameter *some_device = Parameter::find_param("test2");
some_device->write(100);
int z = some_device->read();

Я знаю, что Arduinos очень ограничен в ресурсах, поэтому мне было бы интересно посмотреть, как этот код работает на устройстве.

0 голосов
/ 16 октября 2018

void (*event)() - плохо спроектированный обратный вызов.Обратные вызовы в коде в стиле C имеют указатель на функцию и a void*, например:

void (*event)(void*);
void* state;
...