[TempWebServer] Устройство формирует HTTP страницу (адреса датчиков + температура)

MrFlanker
Offline
Зарегистрирован: 16.01.2018

Разработал недорогое решение на ардуино для получения температуры с удаленных участков дома.
Протестировал на 10 датчиках, полет нормальный.

Что для этого нужно
- Контроллер ARDUINO 1шт 100 руб (uno, mini)
- Расширение для Ethernet ENC28J60 1шт 165 руб
- Датчик температуры DS18B20 (10 шт по 90 руб)

Неохота тащить 1-wire, для получения данных температуры с удаленных участков дома, таких как возможно чердак или удаленные комнаты. Я решил протащить по всему дому ethrnet и разместить дешевые помощники в нужных местах. Предосмотрев легкую замену в случае выхода из строя.

Суть разработки: (Функция аналогично Mega-2561)
Ардуина ищет темп.датчики шиной, и формирует HTTP страницу с адресами и температурами
адрес датчика;температура;временная метка#..... и т.д.

Новая временная метка присваивается каждый раз когда произошло считывание конкретного датчика. Анализируя метку вы всегда поймете поступает ли свежая информация.

Скетч не требует правки, IP адрес определяется по DHCP, адреса датчиков считываются функцией поиск, залил и работает.

 

 

MrFlanker
Offline
Зарегистрирован: 16.01.2018

Исходный код скетча, без класса для удобства просмотра:

#include <EtherCard.h>
#include <OneWire.h>

// Какой нормальный человек будет статику в ардуинно прописывать ??  
static byte mymac[] = { 0x74,0x69,0x69,0x2D,0x30,0x31 };
// static byte myip[] = { 192,168,1,203 };

byte Ethernet::buffer[500]; // 150 + 35 байт для каждого датчика температуры (но не меньше 300)
BufferFiller bfill;

CLASS_OneWireSensorsSet oneWireSensorsSet(2, 10);  // pin=2; макс. количество датчиков=5


void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);

  oneWireSensorsSet.SearchSensors();   // Поиск датчиков на шине
  oneWireSensorsSet.StartMeasureAll(); // Измерить температуру
  Serial.print(F("Sensor count:"));
  Serial.println(oneWireSensorsSet.sensorCount);

  // Инициализация Web-сервера
   if (ether.begin(sizeof Ethernet::buffer, mymac) == 0) Serial.println(F("Failed to access Ethernet controller"));

  // Определяем IP
  if (ether.dhcpSetup()) { Serial.println(F("Found DHCP"));  } else { Serial.println(F("DHCP not found")); } //ether.staticSetup(myip);  // Какой нормальный человек будет статику в ардуинно прописывать ??  

   // Данная функция выводит в Serial "IP адрес" разделенный точками. Может принимать 1 или 2 параметра
   ether.printIp(F("MY IP: "),      ether.myip);
   ether.printIp(F("NETMASK: "),    ether.netmask);
   ether.printIp(F("GATEWAY IP: "), ether.gwip);
   ether.printIp(F("DNS IP: "),     ether.dnsip);  

}


void loop() {

  // Запрос на измерение температуры
  oneWireSensorsSet.StartMeasureAll(); 

  // Получаем температуру с 1 датчика
  oneWireSensorsSet.GetNextTemp();

  //Serial.println("AllData:"+oneWireSensorsSet.GetAllDataString());  
  
  word len = ether.packetReceive();
  word pos = ether.packetLoop(len);

  // check if valid tcp data is received
  if (pos) { 
    String str = oneWireSensorsSet.GetAllDataString();
    bfill = ether.tcpOffset(); 
    bfill.emit_p(PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nPragma: no-cache\r\n\r\n"));  // print OK
    bfill.emit_p(PSTR("<title>Arduino Temp server </title>\n"));
    //bfill.emit_p(PSTR("Address: $S <br>\n"), str.c_str()); // $F $D $T $S - String
    bfill.emit_p(PSTR("$S"), str.c_str());  
    ether.httpServerReply(bfill.position()); 
  }  

}

Сам класс отдельно:

class CLASS_OneWireSensorsSet {

  struct STRUCT_TempSensor { // Структура для хранения данных с одного датчика температуры
    uint8_t address[8];                         // 8 байт на адрес датчика температуры
    int16_t rawValue;                           // Сырое значение температуры с датчика 2 байта (Для значения в градусах нужно разделить на 16)
    unsigned long time;                         // Момент получения температуры с датчика (чтобы отличать новые значения от старых)
  };
  
  private:
    uint8_t maxSensorCount;                     // Максимальное возможное количество датчиков, под которое выделяется память
    
  public:
    OneWire *oneWireObject;                     // Указатель на основной объект
    STRUCT_TempSensor *tempSensors;             // Указатель на массив структур
    uint8_t sensorCount=0;                      // количество найденных датчиков
    uint8_t curSensor=0;                        // Номер последнего сенсора с которого получена информация
    
    CLASS_OneWireSensorsSet(uint8_t pinOneWire, uint8_t maxSensors) { // конструктор
      // pinOneWire - пин ардуино на котором подключны датчики с подтяжкой резистором 4.7 кОм вверх +5 В
      // maxSensors - Максимально ожидаемое количество сенсоров, сколько выделять памяти
      
      oneWireObject = new OneWire(pinOneWire);              // выделяем память для объекта OneWire
      tempSensors   = new STRUCT_TempSensor[maxSensors];    // выделяем память для массива структур
      maxSensorCount = maxSensors;                          // запоминаем максимальную длину массива
    };  // КОНСТРУКТОР =============================================================================================
    
    ~CLASS_OneWireSensorsSet() { // деструктор
      delete oneWireObject;                     
      delete[] tempSensors;      
    }; // ДЕСТРУКТОР =============================================================================================


    String ByteArrayToStringHEX(uint8_t bytearray[], uint8_t count, String del=":") {    // Преобразуем массив байтов в строку HEX значений с разделителем или без
      String str = "";
      for(uint8_t i = 0; i < count; i++) {       // Определить размер массива "sizeof(address)" внутри функици невозможно, так как передается только указатель
        if (i>0) str += del;                 // добавляем разделитель
        if (bytearray[i] < 16) str += "0";   // Добавляем лидирующий "0" для малых значений
        str += String(bytearray[i], HEX);    // Добавляем HEX значеие
      }
      str.toUpperCase();
      return str;
    } // ByteArrayToStringHEX =============================================================================================

    void StartMeasureAll() { // Дает команду всем датчикам произвести измерение текущей температуры
      oneWireObject->reset();                     // Подготовка 1-wire шины
      oneWireObject->write(0xCC);                 // Посылаем команду "пропуск выбора датчика" OW_SKIP_ROM_SELECT
      //oneWireObject->select(address);           // Можно было выбрать конкретный датчик
      oneWireObject->write(0x44);                 // Посылаем команду "начать измерение температуры" OW_START_CONVERT
      //delay(750);                               // Задержка (датчикам нужно время на вычисление до 750мс в режиме максимальной точности)
    } // StartMeasureAll =============================================================================================

    void SearchSensors() {   // Определение адресов всех сенсоров
      uint8_t address[8];       // временный адрес
      sensorCount=0;            // Обнуляем количество найденных датчиков, перед поиском
      
      //Serial.println("Start SearchSensors");

      //oneWireObject->reset_search();  delay(250); // Это не обязательно первый раз
      while(oneWireObject->search(address) & sensorCount < maxSensorCount) { // ищем датчики

        // Проверяем CRC 
        if (OneWire::crc8(address, 7) != address[7]) { Serial.println(F("SearchSensors: CRC address is not valid!")); continue; }  

        // По первому байту определяем что найденное устройство датчик температуры
        switch (address[0]) {    // the first address byte indicates which chip
          case 0x28: break;        // Serial.print("Chip = DS18B20");
          case 0x22: break;        // Serial.print("Chip = DS1822");
          default: continue;       // Serial.println("Device is not a DS18x20 family device.");
        } 
      
        //Serial.println("Find address: "+ByteArrayToStringHEX(curAddress,8,":")+" ");   // Адрес устройства найден      
      
        for(uint8_t i = 0; i < 8; i++){ tempSensors[sensorCount].address[i] = address[i]; } // Копируем найденный адресс в структуру датчиков
        sensorCount++;
      }
      //Serial.println("End SearchSensors");
    } // SearchSensors =============================================================================================

  bool GetNextTemp() {  // Получаем температуру одного датчика (чтобы быстрее было, и контроллер мог сделать что то еще)
    
    uint8_t data[9];  // Буфер чтения

    if (sensorCount ==0) return false; // Если нет датчиков возврат

    curSensor++; if (curSensor >= sensorCount) curSensor = 0; // Определяем индекс следующего датчика
    
    oneWireObject->reset();                                   // Команда сброс  
    oneWireObject->select(tempSensors[curSensor].address);    // Выбор датчика
    oneWireObject->write(0xBE);                               // OW_READ_SCRATCH Команда на считывание температуры, датчик должен предварително измерить ее 
    
    for (uint8_t i = 0; i < 9; i++) data[i] = oneWireObject->read(); // Считываем данные 9 байт
  
    if (OneWire::crc8(data, 8) != data[8]) {Serial.println(F("GetNextTemp: CRC data is not valid!")); return false; } // Проверяем CRC
    if ( data[5] !=  0xFF) {Serial.println(F("GetNextTemp: No answer")); return false; } // Если датчик в норме 5 байт всегда возвращает FF
    
    // Convert the data to actual temperature
    int16_t raw = (data[1] << 8) | data[0];
    byte cfg = (data[4] & 0x60);
    // at lower res, the low bits are undefined, so let's zero them
    if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
    else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
    else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
    //// default is 12 bit resolution, 750 ms conversion time

    // В случае успешного считывания обновляем значение и метку времени
    tempSensors[curSensor].rawValue = raw;     
    tempSensors[curSensor].time = millis();   
    
    //Serial.print("N="+(String)curSensor+" ");
    //Serial.print("Data="+ByteArrayToStringHEX(data,9," ")+" ");
    //Serial.print("Prec="+String(data[4])+" ");  
    //Serial.print("Value="+String((float)raw / 16.0)+" ");  
    //Serial.println();

    return true;
  } // GetNextTemp =============================================================================================

  String GetAdressHEX(String del=":") {    // Преобразуем массив байтов в строку HEX значений с разделителем или без
    String str = "";
    for(uint8_t i = 0; i < 8; i++) {          
      if (i>0) str += del;                 // добавляем разделитель
      if (tempSensors[curSensor].address[i] < 16) str += "0";   // Добавляем лидирующий "0" для малых значений
      str += String(tempSensors[curSensor].address[i], HEX);    // Добавляем HEX значеие
    }
    str.toUpperCase();
    return str;
  } // GetAdressHEX =============================================================================================

  String GetAllDataString() {    // Формирует строку содержащую данные по всем
    String str = "";
    for(uint8_t i = 0; i < sensorCount; i++) {  // Перебираем все датчики
      if (i>0) str += "#"; // разделитель
      //Serial.print(ByteArrayToStringHEX(tempSensors[8].address, 8, ":"));
      str += ByteArrayToStringHEX(tempSensors[i].address, 8, "")+";";   // адрес
      str += (String)(tempSensors[i].rawValue/16.0)+";";                // температура
      str += (String)(tempSensors[i].time);                             // временная метка
    }
    //Serial.println();
    return str;
  } // GetAllDataString =============================================================================================
  

}; // Конец класса =============================================================================================

 

MrFlanker
Offline
Зарегистрирован: 16.01.2018

 

Результат работы устройства (ip адрес смотрим либо в выводе COM порта, либо в настройках вашего DHCP Server)

По возможности старался минимизировать размер скетча:
Global variables use 922 bytes (45%) of dynamic memory, leaving 1126 bytes for local variables.
Динамически выделяется еще примерно 20 байт + 15 байт на каждый датчик 
Итого: 922+20+150=1092 байта. (для 10 датчиков температуры)
 
CLASS_OneWireSensorsSet oneWireSensorsSet(2, 10);  
// Первый параметр: это номер пина к которому подключены датчики pin=2 
// Второй параметр: это максимальное количество датчиков 10 (память выделяется динамически)

 

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Может это лучше в "Проекты" перенести?

MrFlanker
Offline
Зарегистрирован: 16.01.2018

Можно, только я не знаю как это сделать :) По идее модератор может