Автоматизация для всех. Производитель программного обеспечения и электроники.

Измеритель потребляемой мощности на 16 каналов с передачей данных по WIFI

 

Прибор был сделан для контроля потребляемой мощности различных нагрузок внутри помещения.
Задач получения высокой точности измерений не стояло. Устройство скорее, для того чтобы,  видеть работу тех или иных приборов  внутри помещения, и видеть режим их работы.
Также, стояла задача, иметь возможность в дальнейшем  увеличить, количество точек измерения без ущерба для уже работающей части.
Предполагалась передача всех полученных данных по локальной сети WIFI на некий выделенный ПК или сервер.
В качестве основы  измерителя,  был выбран чип ESP8266, в форм факторе 07. Он имеет разъем под внешнюю антенну. Предполагается установка прибора  внутри электрощита на дин рейку, который может быт металлическим, что сделает невозможным работу WIFI на встроенную антенну ESP8266.
Для  считывания показаний датчиков,  применены не дорогие,   внешние АЦП,  PCF8591 с шиной I2C.

 

Датчики, трансформаторного типа, китайского производства, на различные номиналы токов, со встроенными резисторами типа SCT-013.

 

Схема

.

На схеме показан только один АЦП, все  остальные подключены аналогично,  но с другими адресами.  Так как АЦП  измеряет только положительные напряжения,  все датчики тока одним концом подключены к искусственной средней точке.
Для упрощения программирования, чтобы не лезть в дебри  ESP слишком глубоко,  программная часть написана на  Arduino IDE. 

 


/*Программа измерения тока по WIFI 16 каналов
 * Релиз 03 от 18/02/2023
 * Сброс настроек по двойному ресету
 *
*/

//Библиотеки и определения к ним
  #include <ESP8266WiFi.h>
  #include <WiFiClient.h>
    //Создаем клиента TCP
      WiFiClient client;     

  #include <DoubleResetDetector.h> 
      #define DRD_TIMEOUT 1   // Количество секунд после сброса, в течение которых последующий сброс будет считаться двойным сбросом.
      #define DRD_ADDRESS 0    // Адрес памяти RTC для использования DoubleResetDetector
      DoubleResetDetector drd(DRD_TIMEOUT, DRD_ADDRESS);  //инициализация детектора длинного сброса

  #include <WiFiManager.h> // https://github.com/tzapu/WiFiManager библиотека первичной конфигурации подключения
    //добавим параметры для заполнения при конфигурации
      byte bIp[] = { 192, 168, 1, 101 };                                      //адрес сервера для подключения по умолчанию
      int uiPort = 713;                                                       //порт сервера для подключения  по умолчанию
      char server[15] = "192.168.1.101";
      char port[6]  = "713";
      char IDKey[8]  = "DT16_1";                                              //идентификатор блока для сервера
      WiFiManagerParameter custom_server("server", "server", server, 15);    //перед цифрой server это переменная по умолчанию
      WiFiManagerParameter custom_port("port", "port", port, 5);             //перед цифрой port это переменная по умолчанию
      WiFiManagerParameter custom_IDKey("IDKey", "IDKey", IDKey, 7);         //идентификатор блока для сервера

  #include <EEPROM.h>

  #include "Wire.h"
    //адреса устройств
      int PCF8591_0=0x48; // I2C bus address для 7 бит (H91 >> 1)
      int PCF8591_1=0x49; // I2C bus address
      int PCF8591_2=0x4B; // I2C bus address
      int PCF8591_3=0x4F; // I2C bus address

      byte AV[4];         //массив для измеренного
      byte AMIN[4];       //массив минимальных значений
      byte AMAX[4];       //массив максимальных значений
      byte VD[16];        //массив всех измеренных размахов

 

void setup()
{

  WiFi.mode(WIFI_STA); // явно укажем режим запуска   
  Serial.begin(115200);
  delay(10);
 
  WiFiManager wifiManager;  //создаем объект конфигурации
  //настраиваем меню конфигурации
      //уберем из меню то что не нужно
        std::vector<const char *> wifiManager_menu  = {"wifi", "exit"};
        wifiManager.setShowInfoUpdate(false);
        wifiManager.setShowInfoErase(false);
        wifiManager.setMenu(wifiManager_menu);
      //добавим параметры настройки 
        wifiManager.addParameter(&custom_server);
        wifiManager.addParameter(&custom_port);
        wifiManager.addParameter(&custom_IDKey);
      //укажем обработчик события сохранения конфигурации событие сохранения конфигурации
      // сохранять надо только то что добавили, имя сети и пароль сохранится сам
        wifiManager.setSaveConfigCallback(saveConfigCallback);
        wifiManager.setConfigPortalTimeout(60);     //при рестарте открываем портал на минуту, если сеть не доступна
        wifiManager.setClass("invert");             //темная тема
       // wifiManager.setDebugOutput(false);          //отключает вывод отладки

  //проверим, был ли двойной сброс
    if (drd.detectDoubleReset()) {
       wifiManager.resetSettings(); //Был двойной сброс. Очистим конфигурацию
    }

 

  //зажигаем светодиод чтобы сообщить что в режиме станции
  pinMode(LED_BUILTIN, OUTPUT);     // Initialize the LED_BUILTIN pin as an output
  digitalWrite(LED_BUILTIN, LOW);   //вкл
  wifiManager.autoConnect(); 
  digitalWrite(LED_BUILTIN, HIGH);

 

  Serial.println("Connected! IP address: ");
  Serial.println(WiFi.localIP());

 

  //Читаем настройки из EEPROM
    LoadSroperties ();

  //установки шины I2C для опроса АЦП библиотеку поправлял, штатная может подвисать, сам видел
    Wire.setClock(100000);  //стандартная скорость   
    Wire.pins(4,5);         // just to make sure
    Wire.begin(4,5);        // the SDA and SCL
 
 
}//end setuo

void loop(){
    drd.loop();     //необходимо периодически вызывать, для распознавания двойного сброса
    delay(3000);

   //считываем 0 АЦП   
    GetVAalog(PCF8591_0);   
    //переносим в массив для передачи размах амплитуды
    VD[0] = AMAX[0] - AMIN[0];    
    VD[1] = AMAX[1] - AMIN[1];
    VD[2] = AMAX[2] - AMIN[2];
    VD[3] = AMAX[3] - AMIN[3];   
      
     //считываем 1 АЦП   
    GetVAalog(PCF8591_1);   
    //переносим в массив для передачи размах амплитуды
    VD[4] = AMAX[0] - AMIN[0];    
    VD[5] = AMAX[1] - AMIN[1];
    VD[6] = AMAX[2] - AMIN[2];
    VD[7] = AMAX[3] - AMIN[3];   

    //считываем 2 АЦП   
    GetVAalog(PCF8591_2);   
    //переносим в массив для передачи размах амплитуды
    VD[8] = AMAX[0] - AMIN[0];    
    VD[9] = AMAX[1] - AMIN[1];
    VD[10] = AMAX[2] - AMIN[2];
    VD[11] = AMAX[3] - AMIN[3];   
  
    //считываем 3 АЦП   
    GetVAalog(PCF8591_3);   
    //переносим в массив для передачи размах амплитуды
    VD[12] = AMAX[0] - AMIN[0];    
    VD[13] = AMAX[1] - AMIN[1];
    VD[14] = AMAX[2] - AMIN[2];
    VD[15] = AMAX[3] - AMIN[3];   
     
    Serial.println(" ");
    Serial.print("Data ADC: ");
    for(byte i = 0; i < 16; i++){ Serial.print(String(VD[i],DEC) + ".");}
    Serial.println(" ");

 

  //пробуем подключиться к серверу 
    if (!client.connect(bIp, uiPort)) {
      Serial.println("Not connect server");
      return;
    }

     //строка запрос 
     String url = "/update?key=";
     url+= String(IDKey);  //"DT16_1"; //apikey;
     url+="&field0=";
     url+=String(VD[0]);
     url+="&field1=";
     url+=String(VD[1]);
     url+="&field2=";
     url+=String(VD[2]);
     url+="&field3=";
     url+=String(VD[3]);
 
     url+="&field4=";
     url+=String(VD[4]);
     url+="&field5=";
     url+=String(VD[5]);
     url+="&field6=";
     url+=String(VD[6]);
     url+="&field7=";
     url+=String(VD[7]);
 
     url+="&field8=";
     url+=String(VD[8]);
     url+="&field9=";
     url+=String(VD[9]);
     url+="&field10=";
     url+=String(VD[10]);
     url+="&field11=";
     url+=String(VD[11]);

     url+="&field12=";
     url+=String(VD[12]);
     url+="&field13=";
     url+=String(VD[13]);
     url+="&field14=";
     url+=String(VD[14]);
     url+="&field15=";
     url+=String(VD[15]);

     url+="&Err=";
     url+=String(Wire.status());
 
     url+="\r\n";

   Serial.println(url);

   // запрос на сервер
   client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                "Host: "  + "\r\n" +
                "Connection: close\r\n\r\n");

 

}//end loop

 

 

 

//------------------------Подпрограммы для работы с I2C--------------------------------------------------------------------------
void GetVAalog(int DIVACE)          //подпрограмма получает напряжения по входам указанного АЦП
{
      //задаем исходные нулевые значения,  средняя точка измерителя на делителе 1/2                  
      AMIN[0] = 128;
      AMAX[0] = 128;
      AMIN[1] = 128;
      AMAX[1] = 128;
      AMIN[2] = 128;
      AMAX[2] = 128;
      AMIN[3] = 128;
      AMAX[3] = 128;

      Wire.beginTransmission(DIVACE); // wake up PCF8591
      Wire.write(0x44); // control byte: reads ADC0 then auto-increment гдето прочитал, что если авто инскримент, то обязательно включить выход аналоговый
      Wire.write(0x00);
      Wire.endTransmission(); // end tranmission

      Wire.requestFrom(DIVACE, 1);               // тут предыдущее измеренное, оно нам не надо. Особенности микросхемы
      AV[0]=Wire.read();

      for (int I = 0; I < 100; I++) {             //делаем 100 измерений, ищем максимальное и и минимальное амплитудное значение, размах
          Wire.requestFrom(DIVACE, 1);            // принять 1 байт из устройства
          AV[0] = Wire.read();

          Wire.requestFrom(DIVACE, 1);            // принять 1 байт из устройства        
          AV[1] = Wire.read();

          Wire.requestFrom(DIVACE, 1);            // принять 1 байт из устройства         
          AV[2] = Wire.read();

          Wire.requestFrom(DIVACE, 1);            // принять 1 байт из устройства         
          AV[3] = Wire.read();

          //минимальные и максимальные значения
          AMIN[0] = min(AV[0], AMIN[0]);
          AMAX[0] = max(AV[0], AMAX[0]);
          AMIN[1] = min(AV[1], AMIN[1]);
          AMAX[1] = max(AV[1], AMAX[1]);
          AMIN[2] = min(AV[2], AMIN[2]);
          AMAX[2] = max(AV[2], AMAX[2]);
          AMIN[3] = min(AV[3], AMIN[3]);
          AMAX[3] = max(AV[3], AMAX[3]);
      }

      delay(10);
}//end GetVAalog
//-------------------------------------------------------------------------------------------------------------------------------------------

//------------------------Подпрограммы настройки конфигурации---------------------------------------------------------------------
void saveConfigCallback () {
  //обработчик события сохранения конфигурации WiFiManager
   //shouldSaveConfig = true;    //событие устанавливает флаг необходимости сохранить параметры
       Serial.println("saving config");
      //читаем адрес сервера и порт, ключа из WiFiManager
      Serial.println(custom_server.getValue());
      Serial.println(custom_port.getValue());
      Serial.println(custom_IDKey.getValue());

      //преобразуем  custom_server в массив байт, custom_port в int, пока все во временные  
      byte bT[4];       // IP
      parseBytes(custom_server.getValue(), '.', bT, 4, 10);  //IP адрес удаленного сервера

      int uiP;          // порт
      String str = String(custom_port.getValue());   //установки порта в строку
      uiP = str.toInt();
     
      char IDK[8];      // массив для ключа
      str = custom_IDKey.getValue();
      str.toCharArray(IDK, 8);

 

      byte bBUF [16];   //буфер для записи в eeprom
      byte bSUM;        //контрольная сумма

       for(byte i = 0; i < 4; i++){                      //IP адрес удаленного сервера
         bBUF[i] =  bT[i];
       }   
       bBUF[4] =  highByte(uiP);                //старший байт номера порта удаленного сервера
       bBUF[5] =  lowByte(uiP);                 //младший байт адреса порта удаленного сервера

       for(byte i = 0; i < 8; i++){                      //
         bBUF[i + 6] =  IDK[i];
       }   

      //считаем контрольную сумму
      bSUM= 0;
      for(byte i = 0; i < 14; i++){
        bSUM += bBUF[i];                   
      }   
      bBUF[14] = (bSUM ^ 0xe5);
 
     //Сохраняет в EEPROM
      EEPROM.begin(16);  //4 + 2 + 8 байт  = 14 +  1 контрольная сумма = 16

        for(byte i = 0; i < 16; i++){                    
         EEPROM.put(i, bBUF[i]);
        }   

      EEPROM.commit();
      EEPROM.end();
      delay(100);      //чтобы все записалось
}//end saveConfigCallback

void LoadSroperties (){
  //подпрограмма читает и устанавливает настройки из EEPROM
   byte bBUF [16];   //буфер для чтения  eeprom
   byte bSUM;        //контрольная сумма
   EEPROM.begin(16);
    for(byte i = 0; i < 16; i++){                       //читаем в буфер
       bBUF[i] = EEPROM.read(i)  ;
    }      
   EEPROM.end();

  //проверим контрольную сумму
    bSUM = 0;
    for(byte i = 0; i < 14; i++){                       //читаем в буфер
       bSUM += bBUF[i];
    }      

      if ((bSUM ^ 0xe5) != bBUF[14] ) {
         Serial.print ("CRC no correct");       
         return; 
      }
 
  //все верно, разбираем
    for(byte i = 0; i < 4; i++){                       //IP адрес удаленного сервера
       bIp[i] = bBUF[i];
    }      
                                         
    uiPort = 256 * bBUF[4] + bBUF[5];                  //номера порта удаленного сервера, два байта

    for(byte i = 0; i < 8; i++){                       //Id устройства
       IDKey[i] = bBUF[i + 6];
    }      
}//end LoadSroperties

void parseBytes(const char* str, char sep, byte* bytes, int maxBytes, int base) {
  //подпрограмма для выделения IP в байтовый массив
    for (int i = 0; i < maxBytes; i++) {
        bytes[i] = strtoul(str, NULL, base);  // Convert byte
        str = strchr(str, sep);               // Find next separator
        if (str == NULL || *str == '\0') {
            break;                            // No more separators, exit
        }
        str++;                                // Point to next character after separator
    }
}//end parseBytes

//--------------------------------------------------------------------------------------------------------------------------------

 

В скетче много отладочного вывода информации в порт, его можно сократить при желании.
Программа измеряет размах амплитуды с датчика тока и передает его на сервер, для дальнейшей обработки.
Небольшой пример  приема данных  и их обработки, написанных на VB6, можно скачать тут
Все материалы в данной статье, выложены исключительно в ознакомительных целях.

 


Видео обзор.

 

 С уважением,
  Алексей Петрушев.
   г. Кемерово


 

Еще статьи.

 

Учет антикафе
Учет антикафе

Тарификация бильярда
Тарификация бильярда

Учет общей баниУчет общей бани

Тарификация
саун

Тарификация саун

Тарификация автомойкиТарификация автомойки

“Модуль «Отель».
Бронирование контроль”
“Модуль «Отель».
Бронирование контроль”

Еще
Еще

 



Кемерово 2006 - 2023