Arduino, ESP8266 и MQTT

melandr
Offline
Зарегистрирован: 21.07.2020

Добрый день!

Подскажите пожалуйста, как правильно нужно разбить скетч, чтобы было в стиле C, основной файл и файлы .h .c? Так как довольно трудно понять, как Arduino IDE собирает и компилирует исходник. Используется ESP8266 с прошивкой ESP-link, датчик BME280 и Arduino UNO с библиотекой ELclient. Два примеры объединил в один скетч. Один пример вывод данных с датчика на web-страницу и второй передача данных датчика MQTT-брокеру

// подключим необходимые библиотеки
#include <ELClient.h>
#include <ELClientWebServer.h>
#include <ELClientCmd.h>
#include <ELClientMqtt.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

//void bmeLoop();
//void bmeInit();

#define SEALEVELPRESSURE_HPA (1013.25)

//// создадим объект для работы с библиотекой BME280
Adafruit_BME280 bme; // I2C

float temperature = 0;     // измеренная температура
float pressure = 0;  //измеренное давление
float humidity = 0;  //измеренная влажность

bool connected;

static int count;
static uint32_t last;

// Инициализация подключения к esp-link с использованием обычного последовательного порта
// DEBUG отключен как
// - ведение журнала пакетов происходит медленно, и буфер приема UART может переполняться (отправка HTML-формы)
ELClient esp(&Serial, &Serial);

// Инициализируем клиента Web-сервера
ELClientWebServer webServer(&esp);

// Инициализация клиента CMD (для GetTime)
ELClientCmd cmd(&esp);

// Инициализация клиента MQTT
ELClientMqtt mqtt(&esp);

void ftoa(float f, char *str, uint8_t precision) {
  uint8_t i, j, divisor = 1;
  int8_t log_f;
  int32_t int_digits = (int)f;             //store the integer digits
  float decimals;
  char s1[12];

  memset(str, 0, sizeof(s1));  
  memset(s1, 0, 10);

  if (f < 0) {                             //if a negative number 
    str[0] = '-';                          //start the char array with '-'
    f = abs(f);                            //store its positive absolute value
  }
  log_f = ceil(log10(f));                  //get number of digits before the decimal
  if (log_f > 0) {                         //log value > 0 indicates a number > 1
    if (log_f == precision) {              //if number of digits = significant figures
      f += 0.5;                            //add 0.5 to round up decimals >= 0.5
      itoa(f, s1, 10);                     //itoa converts the number to a char array
      strcat(str, s1);                     //add to the number string
    }
    else if ((log_f - precision) > 0) {    //if more integer digits than significant digits
      i = log_f - precision;               //count digits to discard
      divisor = 10;
      for (j = 0; j < i; j++) divisor *= 10;    //divisor isolates our desired integer digits 
      f /= divisor;                             //divide
      f += 0.5;                            //round when converting to int
      int_digits = (int)f;
      int_digits *= divisor;               //and multiply back to the adjusted value
      itoa(int_digits, s1, 10);
      strcat(str, s1);
    }
    else {                                 //if more precision specified than integer digits,
      itoa(int_digits, s1, 10);            //convert
      strcat(str, s1);                     //and append
    }
  }

  else {                                   //decimal fractions between 0 and 1: leading 0
    s1[0] = '0';
    strcat(str, s1);
  }

  if (log_f < precision) {                 //if precision exceeds number of integer digits,
    decimals = f - (int)f;                 //get decimal value as float
    strcat(str, ".");                      //append decimal point to char array

    i = precision - log_f;                 //number of decimals to read
    for (j = 0; j < i; j++) {              //for each,
      decimals *= 10;                      //multiply decimals by 10
      if (j == (i-1)) decimals += 0.5;     //and if it's the last, add 0.5 to round it
      itoa((int)decimals, s1, 10);         //convert as integer to character array
      strcat(str, s1);                     //append to string
      decimals -= (int)decimals;           //and remove, moving to the next
    }
  }
}

// Обратный вызов из esp-link для уведомления об изменениях статуса wifi
// Здесь мы просто что-то распечатываем
void wifiCb(void* response) {
  ELClientResponse *res = (ELClientResponse*)response;
  if (res->argc() == 1) {
    uint8_t status;
    res->popArg(&status, 1);

    if (status == STATION_GOT_IP) {
      Serial.println("WIFI CONNECTED");
    } else {
      Serial.print("WIFI NOT READY: ");
      Serial.println(status);
    }
  }
}

// Обратный вызов при подключении MQTT
void mqttConnected(void* response) {
  Serial.println("MQTT connected!");
  mqtt.subscribe("/bme280/#");
  connected = true;
}

// обратный вызов, когда MQTT отключен
void mqttDisconnected(void* response) {
  Serial.println("MQTT disconnected");
  connected = false;
}

// Обратный вызов при получении сообщения MQTT для одной из наших подписок
void mqttData(void* response) {
  ELClientResponse *res = (ELClientResponse *)response;

  Serial.print("Received: topic=");
  String topic = res->popString();
  Serial.println(topic);

  Serial.print("data=");
  String data = res->popString();
  Serial.println(data);
}

void mqttPublished(void* response) {
  Serial.println("MQTT published");
}

// Обратный вызов из esp-link для уведомления о том, что он только что вышел из сброса. Это означает, что мы
// должны инициализировать веб-сервер!
void resetCb(void) {
  Serial.println("EL-Client (re-)starting!");
  bool ok = false;
  do {
    ok = esp.Sync();      // синхронизировать с esp-link, блокировать до 2 секунд
    if (!ok) Serial.println("EL-Client sync failed!");
  } while (!ok);
  Serial.println("EL-Client synced!");

  webServer.setup();
}

// цикл измерения
void bmeLoop()
{
  // присваиваем переменной temperature текущее значение температуры в градусах Цельсия
  temperature = bme.readTemperature();

  // присваиваем переменной pressure текущее значение давления
  pressure = bme.readPressure()/133.3;

  // присваиваем переменной humidity текущее значение влажности
  humidity = bme.readHumidity();     
}

// sprintf %f не поддерживается в Arduino...
String floatToString(float f)
{
  char buf[20];
  char str_temp[7];
  dtostrf(f, 5, 2, str_temp);
  sprintf(buf, "%s", str_temp);
  
  return String(buf);
}

String arrayToString(byte array[])
{
    unsigned int len = 8;
    char buf[20];
    for (unsigned int i = 0; i < len; i++)
    {
        byte nib1 = (array[i] >> 4) & 0x0F;
        byte nib2 = (array[i] >> 0) & 0x0F;
        buf[i*2+0] = nib1  < 0xA ? '0' + nib1  : 'A' + nib1  - 0xA;
        buf[i*2+1] = nib2  < 0xA ? '0' + nib2  : 'A' + nib2  - 0xA;
    }
    buf[len*2] = '\0';
    return String(buf);
}

void bmeRefreshCb(char * url)
{
  // вычисляем значение температуры
  String t = floatToString(temperature);
  webServer.setArgString(F("temp"), t.begin());

  // вычисляем значение давления
  String p = floatToString(pressure);
  webServer.setArgString(F("press"), p.begin());
    
  // вычисляем значение влажности
  String h = floatToString(humidity);
  webServer.setArgString(F("humi"), h.begin());
}

// настройка страницы
void bmeInit()
{
  unsigned status;  
  // инициализируем работу с датчиком
  status = bme.begin();  
  if (!status) {
     // сделаем запрос на получение адреса датчика
 //    Serial.println(bme.sensorID(),16);
     while (1);
 }
 
  URLHandler *bmeHandler = webServer.createURLHandler(F("/bme280.html.json"));

  bmeHandler->loadCb.attach(bmeRefreshCb);
  bmeHandler->refreshCb.attach(bmeRefreshCb);
}

void setup()
{
  Serial.begin(115200);
  Serial.println("EL-Client starting!");
  esp.resetCb = resetCb;
  //инициализируем датчик bme280
  bmeInit();

  resetCb();
  // Синхронизация с esp-link, это требуется в начале любого скетча и инициализирует
  // обратные вызовы к обратному вызову изменения статуса Wi-Fi. Обратный вызов вызывается с начальным
  // статусом сразу после завершения Sync () ниже.
  esp.wifiCb.attach(wifiCb); // обратный вызов изменения статуса Wi-Fi, необязательно (удалить, если не требуется)
  bool ok;
  do {
    ok = esp.Sync();      // sync up with esp-link, blocks for up to 2 seconds
    if (!ok) Serial.println("EL-Client sync failed!");
  } while (!ok);
  Serial.println("EL-Client synced!");

  // Set-up callbacks for events and initialize with es-link.
  mqtt.connectedCb.attach(mqttConnected);
  mqtt.disconnectedCb.attach(mqttDisconnected);
  mqtt.publishedCb.attach(mqttPublished);
  mqtt.dataCb.attach(mqttData);
  mqtt.setup();

  Serial.println("EL-MQTT ready");
}

void loop()
{
  esp.Process();
  
  //функция измерения датчиком bme280
  bmeLoop();

  if (connected && (millis() - last) > 4000) {
    Serial.println("publishing");
    char buf[12];

    ftoa(temperature, buf, 4);
    mqtt.publish("/bme280/temp", buf);

    ftoa(pressure, buf, 4);
    mqtt.publish("/bme280/pressure", buf);

    ftoa(humidity, buf, 4);
    mqtt.publish("/bme280/humidity", buf);
    
    uint32_t t = cmd.GetTime();
    Serial.print("Time: "); Serial.println(t);

    last = millis();
  }
}

Хочется выделить функции работы с bme280 и mqtt в отдельные файлы. Также непонятно, в какую часть следует поместить функции преобразования переменных в другие типы. Так как получаю данные с датчика в виде float, вэб-странице следует передавать String, а MQTT-брокер должен получить данные в формате char[]

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

https://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2...

http://cppstudio.com/cat/309/

http://it.mmcs.sfedu.ru/wiki/%D0%97%D0%B0%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%...

ну и пример

//dmfmod.h
#ifndef DMFMOD_H
#define DMFMOD_H

#define periodWaitModemBegin 3000UL
#define periodWaitShortCmdModem 4000UL

enum typeModemInitStep {modemBegin, modemPowerON, modemFirst, modemREG, modemRestart, modemStop, modemReady};
extern enum typeModemInitStep currentInitModem;

void processInitModem(unsigned char inByte, _Bool inExist);
void modemReset(void);

#endif  /* DMFMOD_H */

 

melandr
Offline
Зарегистрирован: 21.07.2020

А как правильно сделать, если в функции, которую я хочу вынести в другой файл используется метод экземпляра класса, который объявлен в основном файле? Пробовал использовать extern, но может из-за того, что делал это в проекте с несколькими файлами ino, компиляция не проходила.

Вот ниже указан код функции, в которой используется webServer.createURLHandler

void bmeInit()
{
  unsigned status;  
  // инициализируем работу с датчиком
  status = bme.begin();  
  if (!status) {
     // сделаем запрос на получение адреса датчика
 //    Serial.println(bme.sensorID(),16);
     while (1);
 }
 
  URLHandler *bmeHandler = webServer.createURLHandler(F("/bme280.html.json"));

  bmeHandler->loadCb.attach(bmeRefreshCb);
  bmeHandler->refreshCb.attach(bmeRefreshCb);
}

Саму функцию я хочу вынести в заголовочный файл, относящийся к bme 280. Но объект webServer создается в основном файле

// Инициализируем клиента Web-сервера
ELClientWebServer webServer(&esp);

Как это сделать правильно?

b707
Онлайн
Зарегистрирован: 26.05.2017

melandr пишет:

Как это сделать правильно?

например, передавать функции указатель на обьект webServer в качестве параметра

melandr
Offline
Зарегистрирован: 21.07.2020

Сделал файл bme280.h

#ifndef BME280_H
#define BME280_H
 
void bmeLoop(Adafruit_BME280 &, float &, float &, float &);

#endif /* BME280_H */

файл bme280.c

#include "bme280.h"

// цикл измерения
void bmeLoop(Adafruit_BME280 &bme280, float &tempOUT, float &pressOUT, float &humyOUT)
{
  // присваиваем переменной tempOUT текущее значение температуры в градусах Цельсия
  tempOUT = bme280.readTemperature();

  // присваиваем переменной pressOUT текущее значение давления
  pressOUT = bme280.readPressure()/133.3;

  // присваиваем переменной humyOUT текущее значение влажности
  humyOUT = bme280.readHumidity();     
}

Добавил в главный ino файл объявление

#include "bme280.h"

Выдает ошибку

In file included from sketch\bme280.c:1:0:
 
bme280.h:5:30: error: expected ')' before '&' token
 
 void bmeLoop(Adafruit_BME280 &, float &, float &, float &);
 
Что не так? если переношу реализацию функции обратно в главный ino файл, все компилируется.
sadman41
Offline
Зарегистрирован: 19.10.2016

Назовите файл bme280.cpp

melandr
Offline
Зарегистрирован: 21.07.2020

Спасибо, после изменения расширения дело сдвинулось с мертвой точки.

А не подскажите, почему ArduinoIDE не получалось с файлом СИ компилировать. Заголовочный файл в формате СИ среда разработки проглотила.

sadman41
Offline
Зарегистрирован: 19.10.2016

Это надо спросить у любителей покопаться в кишках Arduino IDE.

Я же только знаю, что .c передаются компилятору C, а .cpp - компилятору CPP. А так, как в pure-C вроде как нет функционала "передача аргумента по ссылке", то и понять запись Adafruit_BME280& компилятор не в состоянии.

rkit
Offline
Зарегистрирован: 23.11.2016

melandr пишет:

Заголовочный файл в формате СИ среда разработки проглотила.

Не смущает, что у тебя ошибка именно в заголовочном файле? Разберись что такое эти самые файлы и как они работают.

melandr
Offline
Зарегистрирован: 21.07.2020

Меня смущает, когда говорят ни о чем. Раз Вы все знаете, просветите что эта ошибка означает. В заголовочном файле описаны прототипы функций, что в нем не правильно? Можете продолжить свою мысль. А то как-то интересно получается, вроде человек знает и понимает, но сказать толком ничего не может. Компилятор может выдавать разные ошибки и не всегда эти ошибки указывают на проблему.

rkit
Offline
Зарегистрирован: 23.11.2016

Я сказал всё, что нужно знать. Я указал, где ошибка в твоем суждении. Я сказал в каком направлении нужно двигаться. Я дал понять, что в данном случае компилятор указывает на ошибку правильно. Если этого не достаточно чтобы исправиться, то это значит, что ты исправляться просто не хочешь.