Skocz do zawartości
RFM

Zdalny odczyt licznika ORNO: tunel VCOM przez Wi-Fi), prezentowanie wyników na WWW.

Pomocna odpowiedź

Taka nocna dłubanina. Główna rola urządzenia

DSCN1134.thumb.JPG.94040bf4b81df23390ef87acee172d9f.JPG

to tunel VCOM <-> RS485 przez Wi-Fi ale dodałem dodatkowa funkcje prezentacji wyników na WWW co można zobaczyć klikając na http://es2.noip.pl:83/. Kod w wersji rozwojowej:

#include <ESP8266WiFi.h>


/*
TODO:
- WDG
- Timeout po kazdej poprawnej odpowiedzi z ORN i prezentowanie CONNECT / NOconnect na WWW
- Forularz do wysylania ramki do ORNO i okno prezentacji wyniku (wszystko w HEX)
- chckbox do on/off zapytań do orno
- Forularz do prametrĂłw transmisji  formatu ramki
*/


//#define DEBUG


//----- Ustawienia UART -----//
#define DIR485    D0    // D2 na PCB
#define BAUD    9600
#define FORMAT_UART  SERIAL_8E1


//----- Ustawienia sieci -----//
#define DHCP    // uzycie definicji wlacza DHCP
#define WIFI_SSID   "ssid"
#define WIFI_PASS   "pass"


#ifndef DHCP        // Gdy nie wlaczone DHCP
IPAddress staticIP(192, 168, 2, 101);
IPAddress gateway(192, 168, 2, 1);
IPAddress subnet(255, 255, 255, 0);
#endif


// #define DELAY_DIR         // Deklaracja wlacza pauze przed po nadaniu ramki (przydatne gdy nie ma terminowalinia linii z polaryzacja)
#define TIM_SEND_BYTE_UART    1200    // Czas nadawania bajtu przez UART
#define DEF_RESTART_WIFI    (5 * 60 * 10000)  // 5 minut


WiFiServer localServer(8888);
WiFiClient localClient;


uint16_t Napiecie, WspolczynnikMocy, Czestotliwosc;
uint32_t Prad, MocCzynna, MocBierna, MocPozorna, EnergiaCzynna, EnergiaBierna;
uint32_t CzasDoResetu, CzasDoRstWifi;


char static buf[10000];
//------------------------------------------------------------
#include "ESP8266WebServer.h"
ESP8266WebServer  webServer(80);
//===========================================================
void IndexHandler()
{
  //  webServer.send("Location", "/index.html",true );  // przekierowanie na plik w SPIFFS
  //  webServer.send( 302,"text/plane" );    //EP 2018-12 str 31

  //  webServer.send( 200, "text/plain", "Czas uruchomienia " + String( millis()) + "ms" );

  char txt[100];  //1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000


  strcpy( buf, "<head><title>Licznik ORNO</title></head>" );


  strcat( buf, "<H3>Licznik ORNO</H3><P>" );
  sprintf( txt, "SysTick: %d sekund<P>", millis() / 1000 ); strcat( buf, txt );

  sprintf( txt, "<BR>Napiecie = %d,%02dV", Napiecie / 100, Napiecie % 100  ); strcat( buf, txt );
  sprintf( txt, "<BR>Prad = %d,%03dA", Prad / 1000, Prad % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>Czestotliwosc=%d,%02dHz", Czestotliwosc / 100, Czestotliwosc % 100 ); strcat( buf, txt );
  
  sprintf( txt, "<P>MocCzynna = %d,%03dkW", MocCzynna / 1000, MocCzynna % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>MocBierna = %d,%03dkWAr", MocBierna / 1000, MocBierna % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>MocPozorna = %d,%03dkWA", MocPozorna / 1000, MocPozorna % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>WspolczynnikMocy = %d,%03d", WspolczynnikMocy / 1000, WspolczynnikMocy % 1000  ); strcat( buf, txt );
  
  sprintf( txt, "<P>EnergiaCzynnaa = %d,%02dkWh", EnergiaCzynna / 100, EnergiaCzynna % 100 ); strcat( buf, txt );
  sprintf( txt, "<BR>EnergiaBierna = %d,%02dkVArh", EnergiaBierna / 100, EnergiaBierna % 100 ); strcat( buf, txt );

  //strcat( buf, "<P><A HREF='/s'>Skanuj I2C</A>" );

  //  webServer.send( 200, "text / plain", buf );
  webServer.send( 200, "text/html; charset=iso-8859-2", buf );
  /*
    void  send (int code, const String &content_type, const String &content)
    void  sendHeader (const String &name, const String &value, bool first=false)
    void  sendContent (const String &content)
  */
}


void notFoundHandler()
{
  webServer.send( 404, "text/plain", "Strona nie istnieje." );
}
//w loop():
//  webServer.handleClient();
//w setup():
//  webServer.on( " / ", IndexHandler );    // tych deklaracji moze byc wiele,co by to nie znaczylo
//  webServer.onNotFound( notFoundHandler );
//  webServer.begin();

/*
  boolean syncEventTriggered = false; // True if a time even has been triggered
  NTPSyncEvent_t ntpEvent; // Last triggered event
  int8_t timeZone = 1;
  int8_t minutesTimeZone = 0;
  bool wifiFirstConnected = false;


  // Start NTP only after IP network is connected
  void onSTAGotIP (WiFiEventStationModeGotIP ipInfo) {
  Serial.printf ("***NTP*** Got IP: %s\r\n", ipInfo.ip.toString ().c_str ());
  Serial.printf ("Connected: %s\r\n", WiFi.status () == WL_CONNECTED ? "yes" : "no");
  wifiFirstConnected = true;
  }


  void onSTAConnected (WiFiEventStationModeConnected ipInfo) {
  Serial.printf ("***NTP*** Connected to %s\r\n", ipInfo.ssid.c_str ());
  }


  // Manage network disconnection
  void onSTADisconnected (WiFiEventStationModeDisconnected event_info) {
  Serial.printf ("***NTP*** Disconnected from SSID: %s\n", event_info.ssid.c_str ());
  Serial.printf ("Reason: %d\n", event_info.reason);
  //NTP.stop(); // NTP sync can be disabled to avoid sync errors
  }


  void processSyncEvent(NTPSyncEvent_t ntpEvent) {
  if (ntpEvent) {
    Serial.print ("***NTP*** Time Sync error: ");
    if (ntpEvent == noResponse)
      Serial.println ("NTP server not reachable");
    else if (ntpEvent == invalidAddress)
      Serial.println ("Invalid NTP server address");
  } else {
    Serial.print ("***NTP*** Got NTP time: ");
    Serial.println (NTP.getTimeDateString (NTP.getLastNTPSync ()));
  }
  }
*/


//===========================================================
uint16_t lenBufSerTx;

void setup() {
  Serial.begin(BAUD, FORMAT_UART);

  WiFi.begin(WIFI_SSID, WIFI_PASS);
#ifndef DHCP
  WiFi.mode(WIFI_STA);
  WiFi.config(staticIP, gateway, subnet);
#endif
  pinMode(DIR485, OUTPUT);
  digitalWrite(DIR485, LOW);

  Find_WiFi();
  localServer.begin();
  localServer.setNoDelay(true);


  delay(100); // Czas na oproznienie bufora nadawczego UART
  lenBufSerTx = Serial.availableForWrite();
#ifdef DEBUG
  Serial.print( "\n\rBufor nadawczy UART "); Serial.print( lenBufSerTx ); Serial.println( " bajtow" );
#endif


  webServer.on( "/", IndexHandler );
  webServer.on( "/i", IndexHandler );
  //  webServer.on( "/s", IndexHandlerScan ); // podstrona WWW HTTP
  webServer.onNotFound( notFoundHandler );
  webServer.begin();


  CzasDoRstWifi = millis() + DEF_RESTART_WIFI;
  CzasDoResetu = millis() + 24 * 60 * 60 * 1000; // 24h
}


//------------------------------------------------------------
void Find_WiFi() {
  Serial.print("\n\rRozlaczony, szukam sieci...");
  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
    Serial.print(".");
  }
  Serial.print("\n\rPolaczony z siecia, przechodze w tryb UART BRIDGE ");
  Serial.print(WiFi.localIP());
  Serial.flush();
  Serial.println();
}


//------------------------------------------------------------
void wyslij_na_RS485( uint8_t *sbuf, size_t len) {
  digitalWrite(DIR485, HIGH);
#ifdef DELAY_DIR
  delay(1);
#endif
  Serial.write(sbuf, len);
  //delay(15);  // Niestety trzeba dobrac doswiadczalnie bo zalezy od dlugosci ramki
  //  delay( len * 1.2 );//mozna tez czekac liczba bajtow x 1.2ms, bo wyslanie 1 bajtu przy 9600 8E1 trwa ok 0,0011458ms: 1/(9600/11)
  while ( Serial.availableForWrite() != lenBufSerTx ) ; // Get the number of bytes (characters) available for writing in the serial buffer without blocking the write operation.
  delayMicroseconds( TIM_SEND_BYTE_UART ); // Konieczne, bo uzywajac "Serial.availableForWrite()" stwierdzimy kiedy bufor jest pusty a nie kiedy znak wyslano z UART
#ifdef DELAY_DIR
  delay(1);
#endif
  digitalWrite(DIR485, LOW);
}


//===========================================================
void loop() {
  if ( millis() > CzasDoResetu ) {
    Serial.print("\n\r*** Abort ***");
    while ( Serial.availableForWrite() );
    abort();
  }
  if ( millis() > CzasDoRstWifi ) {
    millis() + DEF_RESTART_WIFI;

    Serial.println("***** Restart Wi-Fi *****");
    WiFi.begin();
  }


  if (WiFi.status() != WL_CONNECTED) Find_WiFi();
  if (localServer.hasClient()) {
    if (!localClient.connected()) {
      if (localClient) localClient.stop();
      localClient = localServer.available();
    }
  }
  
  
  //----- HTTP -----//
  webServer.handleClient();


  //----- Cykliczne odpytywanie licznika -----//
#define CO_ILE_PYTAC_ORNO    5000
  const byte zapytanie[][8] =
  {
    { 0x00, 0x03, 0x01, 0x31, 0x00, 0x01, 0xD5, 0xE8 }, // Pytanie o napiecie
    { 0x00, 0x03, 0x01, 0x39, 0x00, 0x02, 0x14, 0x2B }, // Pytanie o prad
    { 0x00, 0x03, 0x01, 0x40, 0x00, 0x02, 0xC5, 0xF2 }, // Pytanie o moc czynna
    { 0x00, 0x03, 0x01, 0x48, 0x00, 0x02, 0x44, 0x30 }, // Pytanie o moc bierna
    { 0x00, 0x03, 0x01, 0x58, 0x00, 0x01, 0x05, 0xF4 }, // Pytanie o PF
    { 0x00, 0x03, 0xA0, 0x00, 0x00, 0x0A, 0xE6, 0x1C }, // Pytanie o zuzyta energie czynna
    { 0x00, 0x03, 0xA0, 0x1E, 0x00, 0x0A, 0x86, 0x1A }, // Pytanie o zuzyta energie bierna
    { 0x00, 0x03, 0x01, 0x50, 0x00, 0x02, 0xC4, 0x37 }, // Pytanie o moc pozorna
    { 0x00, 0x03, 0x01, 0x30, 0x00, 0x01, 0x84, 0x28 }, // Pytanie o czestotliwosc
    { 0xFF }
  };
  int8_t static pytanie = -1, fl_Pytanie;
  unsigned long static timeoutOdpytanie;
  if ( millis() >= timeoutOdpytanie ) {
    timeoutOdpytanie = millis() + CO_ILE_PYTAC_ORNO;

    fl_Pytanie = true;

    ++pytanie;
    if ( zapytanie[pytanie][0] != 0xFF ) wyslij_na_RS485( (uint8_t*)zapytanie[pytanie], 8 );
    else pytanie = -1;
  }


  //----- WiFi->UART -----//
  /*
    if (localClient && localClient.connected()) {
      if (localClient.available()) {
        size_t len = localClient.available();
        uint8_t sbuf[len];
        localClient.readBytes(sbuf, len);
        digitalWrite(DIR485, HIGH);
        delay(2);
        Serial.write(sbuf, len);
        delay(15);  // Niestety trzeba dobrac doswiadczalnie bo zalezy od dlugosci ramki
        //mozna tez czekac liczba bajtow x 1.2ms, bo wyslanie 1 bajtu przy 9600 8E1 trwa ok 0,0011458ms: 1/(9600/11)
        digitalWrite(DIR485, LOW);
      }
    }
  */
  if (localClient && localClient.connected()) {
    if (localClient.available()) {
      timeoutOdpytanie = millis() + CO_ILE_PYTAC_ORNO * 2;

      size_t len = localClient.available();
      uint8_t sbuf[len];
      localClient.readBytes(sbuf, len);
      wyslij_na_RS485(sbuf, len);
    }
  }


  //----- UART->WiFi -----//
  /*
    if (Serial.available()) {
      delay(20); // Czekamy az wiecej znakow bedzie w buforze (przy 9600 ok 10)

      size_t len = Serial.available();
      uint8_t sbuf[len];
      Serial.readBytes(sbuf, len);
      if (localClient && localClient.connected()) {
        localClient.write(sbuf, len);
      }
    }
  */
  uint32_t static timeoutWr;
  size_t static prevLen;
  if ( prevLen != Serial.available() ) {
    prevLen = Serial.available();
    timeoutWr = millis() + 5; // Przychodzacy znak ustawia timeout
  }
  if ( millis() >= timeoutWr ) { // Gdy timeout minie
    timeoutWr = 0xFFFFFFFF;   // Zatrymujemy liczenie

    size_t len = Serial.available();
    uint8_t sbuf[len];
    Serial.readBytes(sbuf, len);

    if ( fl_Pytanie ) { // Jesli zapytanie lokalne (przez ESP)
      fl_Pytanie = false;

#ifdef DEBUG
      char txt[50];
      Serial.println( );
      for ( byte x = 0; x < len; x++) {
        sprintf( txt, " %02x", sbuf[x] );
        Serial.print( txt );
      }
#endif

      // todo: sprawdzenie CRC
      switch ( pytanie ) {
        case 0:   // Pytanie o napiecie
          Napiecie = sbuf[3] << 8 | sbuf[4];
#ifdef DEBUG
          sprintf( txt, "\n\rNapiecie=%d,%02d", Napiecie / 100, Napiecie % 100  ); Serial.println( txt );
#endif
          break;
        case 1:   // Pytanie o prad
          Prad = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
#ifdef DEBUG
          sprintf( txt, "\n\rPrad=%d,%03d", Prad / 1000, Prad % 1000 ); Serial.println( txt );
#endif
          break;
        case 2:   // Pytanie o moc czynna
          MocCzynna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
#ifdef DEBUG
          sprintf( txt, "\n\rMocCzynna=%d,%03d", MocCzynna / 1000, MocCzynna % 1000 ); Serial.println( txt );
#endif
          break;
        case 3:   // Pytanie o moc bierna
          MocBierna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
#ifdef DEBUG
          sprintf( txt, "\n\rMocBierna=%d,%03d", MocBierna / 1000, MocBierna % 1000 ); Serial.println( txt );
#endif
          break;
        case 4:   // Pytanie o PF
          WspolczynnikMocy = sbuf[3] << 8 | sbuf[4];
#ifdef DEBUG
          sprintf( txt, "\n\rWspolczynnikMocy=%d,%03d", WspolczynnikMocy / 1000, WspolczynnikMocy % 1000  ); Serial.println( txt );
#endif
          break;
        case 5:   // Pytanie o zuzyta energie czynna
          EnergiaCzynna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6]; // Total, kolejne UINT32 to taryfy T1, T2, T3 i T4
#ifdef DEBUG
          sprintf( txt, "\n\rEnergiaCzynnaa=%d,%02d", EnergiaCzynna / 100, EnergiaCzynna % 100 ); Serial.println( txt );
#endif
          break;
        case 6:   // Pytanie o zuzyta energie bierna
          EnergiaBierna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6]; // Total, kolejne UINT32 to taryfy T1, T2, T3 i T4
#ifdef DEBUG
          sprintf( txt, "\n\rEnergiaBierna=%d,%02d", EnergiaBierna / 100, EnergiaBierna % 100 ); Serial.println( txt );
#endif
          break;
        case 7:   // Pytanie o moc pozorna
          MocPozorna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
#ifdef DEBUG
          sprintf( txt, "\n\rMocPozorna=%d,%03d", MocPozorna / 1000, MocPozorna % 1000 ); Serial.println( txt );
#endif
          break;
        case 8:   // Pytanie o czestotliwosc
          Czestotliwosc = sbuf[3] << 8 | sbuf[4];
#ifdef DEBUG
          sprintf( txt, "\n\rCzestotliwosc=%d,%02d", Czestotliwosc / 100, Czestotliwosc % 100 ); Serial.println( txt );
#endif
          break;
      }
    }
    else { // Zapytanie z Wi-Fi
      if (localClient && localClient.connected()) {
        localClient.write(sbuf, len);
      }
    }
  }



}

Jest jeszcze dużo do zrobienia ale działa i może komuś się przyda.

  • Lubię! 2

Udostępnij ten post


Link to post
Share on other sites

Kilka usprawnień jeśli ktokolwiek jest zainteresowany

#include <ESP8266WiFi.h>


/*
  TODO:
  - Forularz do wysylania ramki do ORNO i okno prezentacji wyniku (wszystko w HEX)
  - chckbox do on/off zapytań do orno
  - Forularz do prametrów transmisji formatu ramki
*/


//#define DEBUG


//----- Ustawienia UART -----//
#define DIR485    D0    // D2 na PCB
#define BAUD    9600
#define FORMAT_UART  SERIAL_8E1


//----- Ustawienia sieci -----//
#define DHCP    // uzycie definicji wlacza DHCP
#define WIFI_SSID   "SaS"
#define WIFI_PASS   "janek007"


#ifndef DHCP        // Gdy nie wlaczone DHCP
IPAddress staticIP(192, 168, 2, 101);
IPAddress gateway(192, 168, 2, 1);
IPAddress subnet(255, 255, 255, 0);
#endif


// #define DELAY_DIR         // Deklaracja wlacza pauze przed po nadaniu ramki (przydatne gdy nie ma terminowalinia linii z polaryzacja)
#define TIM_SEND_BYTE_UART    1200    // Czas nadawania bajtu przez UART
#define DEF_RESTART_WIFI    (5 * 60 * 10000)  // 5 minut


WiFiServer localServer(8888);
WiFiClient localClient;


uint16_t Napiecie, WspolczynnikMocy, Czestotliwosc;
uint32_t Prad, MocCzynna, MocBierna, MocPozorna, EnergiaCzynna, EnergiaBierna;
uint32_t CzasDoResetu, CzasDoRstWifi, timeout485rx;
bool rdONROfull;

char static buf[10000];
//------------------------------------------------------------
#include "ESP8266WebServer.h"
ESP8266WebServer  webServer(80);
//===========================================================
void IndexHandler()
{
  //  webServer.send("Location", "/index.html",true );  // przekierowanie na plik w SPIFFS
  //  webServer.send( 302,"text/plane" );    //EP 2018-12 str 31

  //  webServer.send( 200, "text/plain", "Czas uruchomienia " + String( millis()) + "ms" );


  char txt[100];//1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000


  strcpy( buf, "<head><title>Licznik ORNO</title></head>" );


  strcat( buf, "<H3>Licznik ORNO</H3><P>" );
  sprintf( txt, "SysTick: %d sekund<P>", millis() / 1000 ); strcat( buf, txt );

  if ( millis() < timeout485rx + 15000UL ) {  // Timeout 15 sekund
    if( rdONROfull ) strcat( buf, "<font color='GREEN'><B> ON-Line </B>" );
    else strcat( buf, "<font color='ORANGE'><B> ON-Line (read data...)</B>" );
  }
  else {
    rdONROfull = false;
    strcat( buf, "<font color='RED'> <B>OFF-Line </B>" );
  }
  sprintf( txt, "<P>Napiecie = %d,%02dV", Napiecie / 100, Napiecie % 100  ); strcat( buf, txt );
  sprintf( txt, "<BR>Prad = %d,%03dA", Prad / 1000, Prad % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>Czestotliwosc=%d,%02dHz", Czestotliwosc / 100, Czestotliwosc % 100 ); strcat( buf, txt );

  uint32_t MocSynchronizowana = MocCzynna + 184760; // Dodajemy wskazanie licznie ZE
  // MocSynchronizowana -= todo: odejmujemy bierzace (od resetu) z ORNO 
  sprintf( txt, "<P>MocCzynna = %d,%03dkW, ZE = %d,%03dkW ", MocCzynna / 1000, MocCzynna % 1000, MocSynchronizowana / 1000, MocSynchronizowana % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>MocBierna = %d,%03dkWAr", MocBierna / 1000, MocBierna % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>MocPozorna = %d,%03dkWA", MocPozorna / 1000, MocPozorna % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>WspolczynnikMocy = %d,%03d", WspolczynnikMocy / 1000, WspolczynnikMocy % 1000  ); strcat( buf, txt );

  sprintf( txt, "<P>EnergiaCzynnaa = %d,%02dkWh", EnergiaCzynna / 100, EnergiaCzynna % 100 ); strcat( buf, txt );
  sprintf( txt, "<BR>EnergiaBierna = %d,%02dkVArh", EnergiaBierna / 100, EnergiaBierna % 100 ); strcat( buf, txt );

  strcat( buf, "</font>" );

  //strcat( buf, "<P><A HREF='/s'>Skanuj I2C</A>" );


  //  webServer.send( 200, "text / plain", buf );
  webServer.send( 200, "text/html; charset=iso-8859-2", buf );
  /*
    void  send (int code, const String &content_type, const String &content)
    void  sendHeader (const String &name, const String &value, bool first=false)
    void  sendContent (const String &content)
  */
}


void notFoundHandler()
{
  webServer.send( 404, "text/plain", "Strona nie istnieje." );
}
//w loop():
//  webServer.handleClient();
//w setup():
//  webServer.on( " / ", IndexHandler );    // tych deklaracji moze byc wiele,co by to nie znaczylo
//  webServer.onNotFound( notFoundHandler );
//  webServer.begin();


/*
  boolean syncEventTriggered = false; // True if a time even has been triggered
  NTPSyncEvent_t ntpEvent; // Last triggered event
  int8_t timeZone = 1;
  int8_t minutesTimeZone = 0;
  bool wifiFirstConnected = false;


  // Start NTP only after IP network is connected
  void onSTAGotIP (WiFiEventStationModeGotIP ipInfo) {
  Serial.printf ("***NTP*** Got IP: %s\r\n", ipInfo.ip.toString ().c_str ());
  Serial.printf ("Connected: %s\r\n", WiFi.status () == WL_CONNECTED ? "yes" : "no");
  wifiFirstConnected = true;
  }


  void onSTAConnected (WiFiEventStationModeConnected ipInfo) {
  Serial.printf ("***NTP*** Connected to %s\r\n", ipInfo.ssid.c_str ());
  }


  // Manage network disconnection
  void onSTADisconnected (WiFiEventStationModeDisconnected event_info) {
  Serial.printf ("***NTP*** Disconnected from SSID: %s\n", event_info.ssid.c_str ());
  Serial.printf ("Reason: %d\n", event_info.reason);
  //NTP.stop(); // NTP sync can be disabled to avoid sync errors
  }


  void processSyncEvent(NTPSyncEvent_t ntpEvent) {
  if (ntpEvent) {
    Serial.print ("***NTP*** Time Sync error: ");
    if (ntpEvent == noResponse)
      Serial.println ("NTP server not reachable");
    else if (ntpEvent == invalidAddress)
      Serial.println ("Invalid NTP server address");
  } else {
    Serial.print ("***NTP*** Got NTP time: ");
    Serial.println (NTP.getTimeDateString (NTP.getLastNTPSync ()));
  }
  }
*/


//===========================================================
uint16_t lenBufSerTx;

void setup() {
  Serial.begin(BAUD, FORMAT_UART);

  WiFi.begin(WIFI_SSID, WIFI_PASS);
#ifndef DHCP
  WiFi.mode(WIFI_STA);
  WiFi.config(staticIP, gateway, subnet);
#endif
  pinMode(DIR485, OUTPUT);
  digitalWrite(DIR485, LOW);

  Find_WiFi();
  localServer.begin();
  localServer.setNoDelay(true);


  delay(100); // Czas na oproznienie bufora nadawczego UART
  lenBufSerTx = Serial.availableForWrite();
#ifdef DEBUG
  Serial.print( "\n\rBufor nadawczy UART "); Serial.print( lenBufSerTx ); Serial.println( " bajtow" );
#endif


  webServer.on( "/", IndexHandler );
  webServer.on( "/i", IndexHandler );
  //  webServer.on( "/s", IndexHandlerScan ); // podstrona WWW HTTP
  webServer.onNotFound( notFoundHandler );
  webServer.begin();


  CzasDoRstWifi = millis() + DEF_RESTART_WIFI;
  CzasDoResetu = millis() + 24 * 60 * 60 * 1000; // 24h
}


//------------------------------------------------------------
void Find_WiFi() {
  Serial.print("\n\rRozlaczony, szukam sieci...");
  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
    Serial.print(".");
  }
  Serial.print("\n\rPolaczony z siecia, przechodze w tryb UART BRIDGE ");
  Serial.print(WiFi.localIP());
  Serial.flush();
  Serial.println();
}


//------------------------------------------------------------
void wyslij_na_RS485( uint8_t *sbuf, size_t len) {
  digitalWrite(DIR485, HIGH);
#ifdef DELAY_DIR
  delay(1);
#endif
  Serial.write(sbuf, len);
  //delay(15);  // Niestety trzeba dobrac doswiadczalnie bo zalezy od dlugosci ramki
  //  delay( len * 1.2 );//mozna tez czekac liczba bajtow x 1.2ms, bo wyslanie 1 bajtu przy 9600 8E1 trwa ok 0,0011458ms: 1/(9600/11)
  while ( Serial.availableForWrite() != lenBufSerTx ) ; // Get the number of bytes (characters) available for writing in the serial buffer without blocking the write operation.
  delayMicroseconds( TIM_SEND_BYTE_UART ); // Konieczne, bo uzywajac "Serial.availableForWrite()" stwierdzimy kiedy bufor jest pusty a nie kiedy znak wyslano z UART
#ifdef DELAY_DIR
  delay(1);
#endif
  digitalWrite(DIR485, LOW);
}


//===========================================================
void loop() {
  if ( millis() > CzasDoResetu ) {
    Serial.print("\n\r*** Abort ***");
    while ( Serial.availableForWrite() );
    abort();
  }
  /*
    if ( millis() > CzasDoRstWifi ) {
      millis() + DEF_RESTART_WIFI;

      Serial.println("***** Restart Wi-Fi *****");
      WiFi.begin();
    }
  */


  if (WiFi.status() != WL_CONNECTED) Find_WiFi();
  if (localServer.hasClient()) {
    if (!localClient.connected()) {
      if (localClient) localClient.stop();
      localClient = localServer.available();
    }
  }


  //----- HTTP -----//
  webServer.handleClient();


  //----- Cykliczne odpytywanie licznika -----//
#define CO_ILE_PYTAC_ORNO    5000
  const byte zapytanie[][8] =
  {
    { 0x00, 0x03, 0x01, 0x31, 0x00, 0x01, 0xD5, 0xE8 }, // Pytanie o napiecie
    { 0x00, 0x03, 0x01, 0x39, 0x00, 0x02, 0x14, 0x2B }, // Pytanie o prad
    { 0x00, 0x03, 0x01, 0x40, 0x00, 0x02, 0xC5, 0xF2 }, // Pytanie o moc czynna
    { 0x00, 0x03, 0x01, 0x48, 0x00, 0x02, 0x44, 0x30 }, // Pytanie o moc bierna
    { 0x00, 0x03, 0x01, 0x58, 0x00, 0x01, 0x05, 0xF4 }, // Pytanie o PF
    { 0x00, 0x03, 0xA0, 0x00, 0x00, 0x0A, 0xE6, 0x1C }, // Pytanie o zuzyta energie czynna
    { 0x00, 0x03, 0xA0, 0x1E, 0x00, 0x0A, 0x86, 0x1A }, // Pytanie o zuzyta energie bierna
    { 0x00, 0x03, 0x01, 0x50, 0x00, 0x02, 0xC4, 0x37 }, // Pytanie o moc pozorna
    { 0x00, 0x03, 0x01, 0x30, 0x00, 0x01, 0x84, 0x28 }, // Pytanie o czestotliwosc
    { 0xFF }
  };
  int8_t static pytanie = -1, fl_Pytanie;
  unsigned long static timeoutOdpytanie;
  if ( millis() >= timeoutOdpytanie ) {
    timeoutOdpytanie = millis() + CO_ILE_PYTAC_ORNO;

    fl_Pytanie = true;

    ++pytanie;
    if ( zapytanie[pytanie][0] != 0xFF ) wyslij_na_RS485( (uint8_t*)zapytanie[pytanie], 8 );
    else pytanie = -1;
  }


  //----- WiFi->UART -----//
  /*
    if (localClient && localClient.connected()) {
      if (localClient.available()) {
        size_t len = localClient.available();
        uint8_t sbuf[len];
        localClient.readBytes(sbuf, len);
        digitalWrite(DIR485, HIGH);
        delay(2);
        Serial.write(sbuf, len);
        delay(15);  // Niestety trzeba dobrac doswiadczalnie bo zalezy od dlugosci ramki
        //mozna tez czekac liczba bajtow x 1.2ms, bo wyslanie 1 bajtu przy 9600 8E1 trwa ok 0,0011458ms: 1/(9600/11)
        digitalWrite(DIR485, LOW);
      }
    }
  */
  if (localClient && localClient.connected()) {
    if (localClient.available()) {
      timeoutOdpytanie = millis() + CO_ILE_PYTAC_ORNO * 2;

      size_t len = localClient.available();
      uint8_t sbuf[len];
      localClient.readBytes(sbuf, len);
      wyslij_na_RS485(sbuf, len);
    }
  }


  //----- UART->WiFi -----//
  /*
    if (Serial.available()) {
      delay(20); // Czekamy az wiecej znakow bedzie w buforze (przy 9600 ok 10)

      size_t len = Serial.available();
      uint8_t sbuf[len];
      Serial.readBytes(sbuf, len);
      if (localClient && localClient.connected()) {
        localClient.write(sbuf, len);
      }
    }
  */
  uint32_t static timeoutWr;
  size_t static prevLen;
  if ( prevLen != Serial.available() ) {
    prevLen = Serial.available();
    timeoutWr = millis() + 5; // Przychodzacy znak ustawia timeout
  }
  if ( millis() >= timeoutWr ) { // Gdy timeout minie
    timeoutWr = 0xFFFFFFFF;   // Zatrymujemy liczenie

    size_t len = Serial.available();
    uint8_t sbuf[len];
    Serial.readBytes(sbuf, len);

    if ( fl_Pytanie ) { // Jesli zapytanie lokalne (przez ESP)
      fl_Pytanie = false;
      timeout485rx = millis();

#ifdef DEBUG
      char txt[50];
      Serial.println( );
      for ( byte x = 0; x < len; x++) {
        sprintf( txt, " %02x", sbuf[x] );
        Serial.print( txt );
      }
#endif

      // todo: sprawdzenie CRC
      switch ( pytanie ) {
        case 0:   // Pytanie o napiecie
          Napiecie = sbuf[3] << 8 | sbuf[4];
#ifdef DEBUG
          sprintf( txt, "\n\rNapiecie=%d,%02d", Napiecie / 100, Napiecie % 100  ); Serial.println( txt );
#endif
          break;
        case 1:   // Pytanie o prad
          Prad = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
#ifdef DEBUG
          sprintf( txt, "\n\rPrad=%d,%03d", Prad / 1000, Prad % 1000 ); Serial.println( txt );
#endif
          break;
        case 2:   // Pytanie o moc czynna
          MocCzynna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
#ifdef DEBUG
          sprintf( txt, "\n\rMocCzynna=%d,%03d", MocCzynna / 1000, MocCzynna % 1000 ); Serial.println( txt );
#endif
          break;
        case 3:   // Pytanie o moc bierna
          MocBierna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
#ifdef DEBUG
          sprintf( txt, "\n\rMocBierna=%d,%03d", MocBierna / 1000, MocBierna % 1000 ); Serial.println( txt );
#endif
          break;
        case 4:   // Pytanie o PF
          WspolczynnikMocy = sbuf[3] << 8 | sbuf[4];
#ifdef DEBUG
          sprintf( txt, "\n\rWspolczynnikMocy=%d,%03d", WspolczynnikMocy / 1000, WspolczynnikMocy % 1000  ); Serial.println( txt );
#endif
          break;
        case 5:   // Pytanie o zuzyta energie czynna
          EnergiaCzynna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6]; // Total, kolejne UINT32 to taryfy T1, T2, T3 i T4
#ifdef DEBUG
          sprintf( txt, "\n\rEnergiaCzynnaa=%d,%02d", EnergiaCzynna / 100, EnergiaCzynna % 100 ); Serial.println( txt );
#endif
          break;
        case 6:   // Pytanie o zuzyta energie bierna
          EnergiaBierna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6]; // Total, kolejne UINT32 to taryfy T1, T2, T3 i T4
#ifdef DEBUG
          sprintf( txt, "\n\rEnergiaBierna=%d,%02d", EnergiaBierna / 100, EnergiaBierna % 100 ); Serial.println( txt );
#endif
          break;
        case 7:   // Pytanie o moc pozorna
          MocPozorna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
#ifdef DEBUG
          sprintf( txt, "\n\rMocPozorna=%d,%03d", MocPozorna / 1000, MocPozorna % 1000 ); Serial.println( txt );
#endif
          break;
        case 8:   // Pytanie o czestotliwosc
          Czestotliwosc = sbuf[3] << 8 | sbuf[4];
          rdONROfull = true;
#ifdef DEBUG
          sprintf( txt, "\n\rCzestotliwosc=%d,%02d", Czestotliwosc / 100, Czestotliwosc % 100 ); Serial.println( txt );
#endif
          break;
      }
    }
    else { // Zapytanie z Wi-Fi
      if (localClient && localClient.connected()) {
        localClient.write(sbuf, len);
      }
    }
  }



}

 

Udostępnij ten post


Link to post
Share on other sites

Program się trochę rozbudował. Dodałem miedzy innymi wysyłanie danych na serwer. Pierwotnie wykorzystałem https://thingspeak.com/ Wiedziałem, ze demon szybkości to nie jest ale jakież było moje zdziwienie gdy dane wysłałem na serwer lokalny i w Internecie.

Czastransmisji.thumb.gif.54cba64b90753cd64741bb95ad2304de.gif

Tak, tylko 480 razy szybciej! Dane można zobaczyć na RPi http://es2.noip.pl/automatyka/data/

Dla zainteresowanych kod programu


#define DEBUG           // Informacje na software serial
#define DEBUG_UART0     // Czesc informacji na serial (USB) wie takze na RS485 (ale DIR nieaktywny)


#include <ESP8266WiFi.h>
#include <ThingSpeak.h>

#include <SoftwareSerial.h>
SoftwareSerial mySerial( D3, D4 ); // RX, TX - GPIO0 GPIO2

#include <Crc16.h> //Crc 16 library (XModem)
Crc16 crc16;

/*
  todo:
  - CRC
  - NTP: po resecie czas z ORNO do ESP. Gdy NTP działa co kilka minut, czas z ESp do ORNO

  - określenie typu licznika, 514 czy 504? jek się nie da wybór przyciskiem radiowym

  - chckbox do on/off zapytań do orno
  - checkbox do wysyłki na thingspeak (wysylka blokuje loop na 5 sekun!)
  - Forularz do wysylania ramki do ORNO i okno prezentacji wyniku (wszystko w HEX)
  - Forularz do prametrów transmisji formatu ramki

  - rejestr 110. Ustawienie stałej licznika (LED). Domyslne 1000, akceptowane 100, 1000, 2000. ustawic na 100
*/


//----- Ustawienia UART -----//
#define DIR485    D2
#define BAUD    9600
#define FORMAT_UART  SERIAL_8E1


//----- Ustawienia sieci -----//
#define DHCP    // uzycie definicji wlacza DHCP
#define WIFI_SSID   "xxx"
#define WIFI_PASS   "xxx"


#ifndef DHCP        // Gdy nie wlaczone DHCP
IPAddress staticIP(192, 168, 2, 101);
IPAddress gateway(192, 168, 2, 1);
IPAddress subnet(255, 255, 255, 0);
#endif


//dane konta na thingspeak
const char * thingspeak_apiKey_sasedw = "xxxx"; 
const char * thingspeak_apiKey_rmikliczniki = "P2DTxxxx"; 
unsigned long thingspeak_myChannelNumber = 1;
// "184.106.153.149" lub api.thingspeak.com
const char* server = "api.thingspeak.com";
WiFiClient client_thingspeak;


// #define DELAY_DIR         // Deklaracja wlacza pauze przed i po nadaniu ramki (przydatne gdy nie ma terminowalinia linii z polaryzacją)
#define TIM_SEND_BYTE_UART    1200    // Czas [us] nadawania bajtu przez UART
#define DEF_RESTART_WIFI    (5 * 60 * 10000)  // 5 minut
#define TIM_OFLINE_ORNO   	15000UL
#define CO_ILE_PYTAC_ORNO    2000UL
#define Co_ILE_NA_THINGSPEAK    60000UL


WiFiServer localServer(8888);
WiFiClient localClient;
byte ip_4;


uint16_t Napiecie, WspolczynnikMocy, Czestotliwosc;
uint32_t Prad, MocCzynna, MocBierna, MocPozorna, EnergiaCzynna, EnergiaBierna;
uint32_t CzasDoResetu, CzasDoRstWifi, timeout485rx;
bool rdONROfull, flCrcModbus;
int8_t static pytanie = -1;
uint32_t static CzasDoTHINGSPEAK;


char static buf[10000];
//------------------------------------------------------------
#include "ESP8266WebServer.h"
ESP8266WebServer  webServer(80);
//===========================================================
void IndexHandler()
{
  //  webServer.send("Location", "/index.html",true );  // przekierowanie na plik w SPIFFS
  //  webServer.send( 302,"text/plane" );    //EP 2018-12 str 31

  //  webServer.send( 200, "text/plain", "Czas uruchomienia " + String( millis()) + "ms" );


  char txt[100];//1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000


  //  strcpy( buf, "<head> <title>Licznik ORNO</title> <meta http-equiv='refresh' content='5' /> </head>" );  // Firefox burzy sie na 'refresh'
  strcpy( buf, "<head> <title>Licznik ORNO</title> </head>" );


  strcat( buf, "<H3>Licznik ORNO " );
  if ( ip_4 == 52 ) strcat( buf, "<font color='BLUE'> (Glowny) </font>" );
  else if ( ip_4 == 53 ) strcat( buf, "<font color='BLUE'> (Warsztat) </font>" );
  char kolor[30] = "<font color='BLACK'>";
  if ( millis() < timeout485rx + TIM_OFLINE_ORNO ) {  // Timeout 15 sekund
    if ( rdONROfull ) {
      strcpy( kolor, "<font color='GREEN'>" );
      sprintf( txt, "<font color='GREEN'><B>ON-Line </B>Read record %d</font>", pytanie ); strcat( buf, txt );
    }
    else {
      strcpy( kolor, "<font color='ORANGE'>" );
      sprintf( txt, "<font color='ORANGE'><B> ON-Line </B>Read record %d</font>", pytanie ); strcat( buf, txt );
    }
  }
  else {
    rdONROfull = false;
    int tim = millis() - ( timeout485rx + TIM_OFLINE_ORNO);
    if ( tim < 0 ) tim = 0;
    strcpy( kolor, "<font color='RED'>" );
    sprintf( txt, "<font color='RED'> <B>OFF-Line </B> Time %d sekund </font>", tim / 1000 ); strcat( buf, txt );
  }
  strcat( buf, "</H3>\r" );
  // sprintf( txt, "SysTick: %d sekund<BR>", millis() / 1000 ); strcat( buf, txt );

  uint32_t EnergiaCzynnaSyncZE = EnergiaCzynna + 1867.7 * 1000; // Dodajemy wskazanie licznika ZE // todo: zrobic w formularzu, zapis do EEPROM
  EnergiaCzynnaSyncZE -= 3820; // Wskazanie ORNO // todo: forularz
  strcat( buf, "<TABLE cellspacing='10' cellpadding='5'> <tr style='vertical-align: top; '><TD>\r" );
  strcat( buf, kolor );
  sprintf( txt, "MocCzynna = %d,%03dkW", MocCzynna / 1000, MocCzynna % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>MocBierna = %d,%03dkWAr", MocBierna / 1000, MocBierna % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>MocPozorna = %d,%03dkWA", MocPozorna / 1000, MocPozorna % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>WspolczynnikMocy = %d,%03d", WspolczynnikMocy / 1000, WspolczynnikMocy % 1000  ); strcat( buf, txt );
  strcat( buf, "</TD><TD>\r" );
  strcat( buf, kolor );
  sprintf( txt, "Napiecie = %d,%02dV", Napiecie / 100, Napiecie % 100  ); strcat( buf, txt );
  sprintf( txt, "<BR>Prad = %d,%03dA", Prad / 1000, Prad % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>Czestotliwosc=%d,%02dHz", Czestotliwosc / 100, Czestotliwosc % 100 ); strcat( buf, txt );
  strcat( buf, "</TD><TD>\r" );
  strcat( buf, kolor );
  sprintf( txt, "<font color='BLUE'>EnergiaCzynna = %d,%03dkWh</font>", EnergiaCzynna / 1000, EnergiaCzynna % 1000 ); strcat( buf, txt );
  if ( ip_4 == 52 ) {
    sprintf( txt, "<font color='BLUE'></BR>ZE = %d,%03dkWh</B></font> ",  EnergiaCzynnaSyncZE / 1000, EnergiaCzynnaSyncZE % 1000 ); strcat( buf, txt );
  }
  sprintf( txt, "<BR>EnergiaBierna = %d,%03dkVArh</TD>", EnergiaBierna / 1000, EnergiaBierna % 1000 ); strcat( buf, txt );
  strcat( buf, "</TD></tr></TABLE>\r" );



  // todo: mozliwosc wpisania w formularzu "<iframe width = .........
  if ( ip_4 == 52 ) {
    strcat( buf, "\r<P><A HREF='https://thingspeak.com/channels/890858' TARGET='_blank' >Rejestrator</A>" );
    strcat( buf, "<P><iframe width='450' height='260' style='border: 1px solid #cccccc;' src='https://thingspeak.com/channels/890858/charts/2?bgcolor=%23ffffff&color=%23d62020&dynamic=true&results=60&title=Moc+G%C5%82%C3%B3wny&type=line'>< / iframe >" );
  }
  else if ( ip_4 == 53 ) {
    strcat( buf, "\r<P><A HREF='https://thingspeak.com/channels/893181' TARGET='_blank' >Rejestrator</A>" );
    strcat( buf, "<P><iframe width='450' height='260' style='border: 1px solid #cccccc;' src='https://thingspeak.com/channels/893181/charts/2?bgcolor=%23ffffff&color=%23d62020&dynamic=true&results=60&type=line&update=15'>< / iframe > ");
  }


  //strcat( buf, "<P><A HREF='/s'>Skanuj I2C</A>" );


  //  webServer.send( 200, "text / plain", buf );
  webServer.send( 200, "text/html; charset=iso-8859-2", buf );
  /*
    void  send (int code, const String &content_type, const String &content)
    void  sendHeader (const String &name, const String &value, bool first=false)
    void  sendContent (const String &content)
  */
}


void notFoundHandler()
{
  webServer.send( 404, "text/plain", "Strona nie istnieje." );
}
//w loop():
//  webServer.handleClient();
//w setup():
//  webServer.on( " / ", IndexHandler );    // tych deklaracji moze byc wiele,co by to nie znaczylo
//  webServer.onNotFound( notFoundHandler );
//  webServer.begin();


/*
  boolean syncEventTriggered = false; // True if a time even has been triggered
  NTPSyncEvent_t ntpEvent; // Last triggered event
  int8_t timeZone = 1;
  int8_t minutesTimeZone = 0;
  bool wifiFirstConnected = false;


  // Start NTP only after IP network is connected
  void onSTAGotIP (WiFiEventStationModeGotIP ipInfo) {
  Serial.printf ("***NTP*** Got IP: %s\r\n", ipInfo.ip.toString ().c_str ());
  Serial.printf ("Connected: %s\r\n", WiFi.status () == WL_CONNECTED ? "yes" : "no");
  wifiFirstConnected = true;
  }


  void onSTAConnected (WiFiEventStationModeConnected ipInfo) {
  Serial.printf ("***NTP*** Connected to %s\r\n", ipInfo.ssid.c_str ());
  }


  // Manage network disconnection
  void onSTADisconnected (WiFiEventStationModeDisconnected event_info) {
  Serial.printf ("***NTP*** Disconnected from SSID: %s\n", event_info.ssid.c_str ());
  Serial.printf ("Reason: %d\n", event_info.reason);
  //NTP.stop(); // NTP sync can be disabled to avoid sync errors
  }


  void processSyncEvent(NTPSyncEvent_t ntpEvent) {
  if (ntpEvent) {
    Serial.print ("***NTP*** Time Sync error: ");
    if (ntpEvent == noResponse)
      Serial.println ("NTP server not reachable");
    else if (ntpEvent == invalidAddress)
      Serial.println ("Invalid NTP server address");
  } else {
    Serial.print ("***NTP*** Got NTP time: ");
    Serial.println (NTP.getTimeDateString (NTP.getLastNTPSync ()));
  }
  }
*/


//===========================================================
uint16_t lenBufSerTx;

void setup() {
  ESP.wdtEnable(1000);


  Serial.begin(BAUD, FORMAT_UART);
  Serial1.begin(BAUD, FORMAT_UART);
  mySerial.begin(9600);


  WiFi.begin(WIFI_SSID, WIFI_PASS);
#ifndef DHCP
  WiFi.mode(WIFI_STA);
  WiFi.config(staticIP, gateway, subnet);
#endif
  pinMode(DIR485, OUTPUT);
  digitalWrite(DIR485, LOW);

  Find_WiFi();
  localServer.begin();
  localServer.setNoDelay(true);


  delay(100); // Czas na oproznienie bufora nadawczego UART
  lenBufSerTx = Serial.availableForWrite();
#ifdef DEBUG
  mySerial.print( "\n\rBufor nadawczy UART "); mySerial.print( lenBufSerTx ); mySerial.println( " bajtow" );
#endif


  webServer.on( "/", IndexHandler );
  webServer.on( "/i", IndexHandler );
  //  webServer.on( "/s", IndexHandlerScan ); // podstrona WWW HTTP
  webServer.onNotFound( notFoundHandler );
  webServer.begin();


  ThingSpeak.begin(client_thingspeak);


  CzasDoRstWifi = millis() + DEF_RESTART_WIFI;
  CzasDoResetu = millis() + 1 * 60 * 60 * 1000; // 24h   //CzasDoResetu = millis() + 24 * 60 * 60 * 1000; // 24h
  ESP.wdtEnable(100);
}


//------------------------------------------------------------
void Find_WiFi() {
  mySerial.print("\n\rRozlaczony, Lącze z sieca Wi-Fi '");
  mySerial.print( WIFI_SSID );
  mySerial.print("'...");
#ifdef DEBUG_UART0
  Serial.print("\n\rRozlaczony, Lącze z sieca Wi-Fi '");
  Serial.print( WIFI_SSID );
  Serial.print("'...");
#endif

  while (WiFi.status() != WL_CONNECTED) {
    ESP.wdtFeed();
    delay(200);
    mySerial.print(".");
#ifdef DEBUG_UART0
    Serial.print(".");
#endif
  }
  mySerial.print("\n\rPolaczony z siecia '");
  mySerial.print( WIFI_SSID );
  mySerial.print("' IP ");
  mySerial.print( WiFi.localIP() );
  mySerial.println(". Tryb UART BRIDGE ");
#ifdef DEBUG_UART0
  Serial.print("\n\rPolaczony z siecia '");
  Serial.print( WIFI_SSID );
  Serial.print("' IP ");
  Serial.print( WiFi.localIP() );
  Serial.println(". Tryb UART BRIDGE ");
#endif
  Serial.flush();


  // todo: do poprawki albo przejscie na formularz i eeprom
  String ip = WiFi.localIP().toString();
  if ( ip == "192.168.2.52" ) {
    ip_4 = 52;
  }
  else if ( ip == "192.168.2.53" ) {
    ip_4 = 53;
  }
}


//------------------------------------------------------------
void wyslij_na_RS485( uint8_t *sbuf, size_t len) {
  /*
    #ifdef DEBUG
    char txt[10];
    myserial.print( "->485: " );
    for (byte x = 0; x < len; x++) {
    sprintf( txt, "%0xd ", sbuf[x]); myserial.print( txt );
    }
    myserial.println();
    #endif
  */


  digitalWrite(DIR485, HIGH);
#ifdef DELAY_DIR
  delay(1);
#endif
  Serial.write(sbuf, len);
  //  delay( len * 1.2 );//mozna tez czekac liczba bajtow x 1.2ms, bo wyslanie 1 bajtu przy 9600 8E1 trwa ok 0,0011458ms: 1/(9600/11)
  while ( Serial.availableForWrite() != lenBufSerTx ) ; // Get the number of bytes (characters) available for writing in the serial buffer without blocking the write operation.
  //extern inline size_t uart_tx_fifo_available(const int uart_nr);
  //while ( uart_tx_fifo_available( UART0 ) ) ;
  //while ( (USS0 >> USTXC) & 0xff ) ;
  delayMicroseconds( TIM_SEND_BYTE_UART ); // Konieczne, bo uzywajac "Serial.availableForWrite()" stwierdzimy kiedy bufor jest pusty a nie kiedy znak wyslano z UART
#ifdef DELAY_DIR
  delay(1);
#endif
  digitalWrite(DIR485, LOW);
  Serial.println();

  /*
    void uart_wait_tx_empty(uart_t* uart) {
    if(uart == NULL || !uart->tx_enabled)
        return;

    while(uart_tx_fifo_available(uart->uart_nr) > 0)
        delay(0);

    }
    exp:
    uart_wait_tx_empty( USS1 )


    inline size_t uart_tx_fifo_available(const int uart_nr) {
    return (USS(uart_nr) >> USTXC) & 0xff;
    }
    exp: uart_tx_fifo_available( 0 )
    uart_tx_fifo_available( UART0 )
    uart_tx_fifo_available( UART1 )


    size_t uart_tx_free(uart_t* uart) {
    if(uart == NULL || !uart->tx_enabled)
        return 0;

    return UART_TX_FIFO_SIZE - uart_tx_fifo_available(uart->uart_nr);
    }
    exp: uart_tx_free( USS0 );


    https://github.com/esp8266/Arduino/blob/master/cores/esp8266/uart.h
    https://github.com/esp8266/Arduino/blob/master/cores/esp8266/uart.cpp
  */
}


//===========================================================
void loop() {
  ESP.wdtFeed();

  //todo: test:
  uint32_t static timSerTx;
  if ( millis() >= timSerTx ) {
    timSerTx = millis() + 3000;

    Serial1.println("test");
    //mySerial.println("Hello, world?");
  }
  //if (mySerial.available())Serial.write(mySerial.read());
  /*
    if ( Serial1.available() ) {
    size_t len = Serial1.available();
    uint8_t sbuf[len];
    Serial1.readBytes(sbuf, len);
    Serial.print(sbuf);
    }
  */

  if ( millis() > CzasDoResetu ) {
    mySerial.print("\n\r*** Abort ***");
    Serial.print("\n\r*** Abort ***");
    while ( Serial.availableForWrite() );
    abort();
  }
  /*
    if ( millis() > CzasDoRstWifi ) {
      millis() + DEF_RESTART_WIFI;

      Serial.println("***** Restart Wi-Fi *****");
      disconnect
      po kilkun sekundach connect
      WiFi.begin();
    }
  */


  if (WiFi.status() != WL_CONNECTED) Find_WiFi();
  if (localServer.hasClient()) {
    if (!localClient.connected()) {
      if (localClient) localClient.stop();
      localClient = localServer.available();
    }
  }


  //----- HTTP -----//
  webServer.handleClient();


  //----- Cykliczne odpytywanie licznika -----//
  const byte zapytanie[][8] =
  {
    { 0x00, 0x03, 0x01, 0x31, 0x00, 0x01, 0xD5, 0xE8 }, // Pytanie o napiecie
    { 0x00, 0x03, 0x01, 0x39, 0x00, 0x02, 0x14, 0x2B }, // Pytanie o prad
    { 0x00, 0x03, 0x01, 0x40, 0x00, 0x02, 0xC5, 0xF2 }, // Pytanie o moc czynna
    { 0x00, 0x03, 0x01, 0x48, 0x00, 0x02, 0x44, 0x30 }, // Pytanie o moc bierna
    { 0x00, 0x03, 0x01, 0x58, 0x00, 0x01, 0x05, 0xF4 }, // Pytanie o PF
    { 0x00, 0x03, 0xA0, 0x00, 0x00, 0x0A, 0xE6, 0x1C }, // Pytanie o zuzyta energie czynna
    { 0x00, 0x03, 0xA0, 0x1E, 0x00, 0x0A, 0x86, 0x1A }, // Pytanie o zuzyta energie bierna
    { 0x00, 0x03, 0x01, 0x50, 0x00, 0x02, 0xC4, 0x37 }, // Pytanie o moc pozorna
    { 0x00, 0x03, 0x01, 0x30, 0x00, 0x01, 0x84, 0x28 }, // Pytanie o czestotliwosc
    { 0xFF }
  };
  int8_t static fl_Pytanie;
  unsigned long static timeoutOdpytanie;
  if ( millis() >= timeoutOdpytanie ) {
    timeoutOdpytanie = millis() + CO_ILE_PYTAC_ORNO;		// przychodzace dane blokuja odpytywanie

    fl_Pytanie = true;

    ++pytanie;
    if ( zapytanie[pytanie][0] != 0xFF ) wyslij_na_RS485( (uint8_t*)zapytanie[pytanie], 8 );
    else pytanie = -1;

    /*
        uint16_t c = crc16.Modbus( (uint8_t*)zapytanie[pytanie], 0, 6 );
        uint16_t crc = zapytanie[pytanie][6] | zapytanie[pytanie][7] << 8;
        char txt[50];
        sprintf( txt, "\n\rCRC wyliczone %x otrzymane %x", c, crc ); Serial.println( txt );
    */
  }


  //----- WiFi->UART -----//
  /*
    if (localClient && localClient.connected()) {
      if (localClient.available()) {
        size_t len = localClient.available();
        uint8_t sbuf[len];
        localClient.readBytes(sbuf, len);
        digitalWrite(DIR485, HIGH);
        delay(2);
        Serial.write(sbuf, len);
        delay(15);  // Niestety trzeba dobrac doswiadczalnie bo zalezy od dlugosci ramki
        //mozna tez czekac liczba bajtow x 1.2ms, bo wyslanie 1 bajtu przy 9600 8E1 trwa ok 0,0011458ms: 1/(9600/11)
        digitalWrite(DIR485, LOW);
      }
    }
  */
  if (localClient && localClient.connected()) {
    if (localClient.available()) {
      timeoutOdpytanie = millis() + CO_ILE_PYTAC_ORNO * 2;
      CzasDoTHINGSPEAK = millis() + Co_ILE_NA_THINGSPEAK;    // i wysylke


      size_t len = localClient.available();
      uint8_t sbuf[len];
      localClient.readBytes(sbuf, len);
      wyslij_na_RS485(sbuf, len);
    }
  }


  //----- UART->WiFi -----//
  /*
    if (Serial.available()) {
      delay(20); // Czekamy az wiecej znakow bedzie w buforze (przy 9600 ok 10)

      size_t len = Serial.available();
      uint8_t sbuf[len];
      Serial.readBytes(sbuf, len);
      if (localClient && localClient.connected()) {
        localClient.write(sbuf, len);
      }
    }
  */
  uint32_t static timeoutWr;
  size_t static prevLen;
  if ( prevLen != Serial.available() ) {
    prevLen = Serial.available();
    timeoutWr = millis() + 5; // Przychodzacy znak ustawia timeout
  }
  if ( millis() >= timeoutWr ) { // Gdy timeout minie
    timeoutWr = 0xFFFFFFFF;   // Zatrymujemy liczenie

    size_t len = Serial.available();
    uint8_t sbuf[len];
    Serial.readBytes(sbuf, len);

    if ( fl_Pytanie ) { // Jesli zapytanie lokalne (przez ESP)
      fl_Pytanie = false;
      timeout485rx = millis();

#ifdef DEBUG
      char txt[50];
      mySerial.println( );
      for ( byte x = 0; x < len; x++) {
        sprintf( txt, " %02x", sbuf[x] );
        mySerial.print( txt );
      }
#endif

      // todo: sprawdzenie CRC
      uint16_t crcCalculate = crc16.Modbus( sbuf, 0, len - 2 );
      uint16_t crcRX = sbuf[len - 2] | sbuf[len - 1] << 8;
      //if ( crcRX != crcCalculate ) {
      if ( false ) {
        flCrcModbus = false;
        //Serial.println( "\n\rCRC Err" );
      }
      else {
        flCrcModbus = true;
        //Serial.println( "\n\rCR COK" );


        switch ( pytanie ) {
          case 0:   // Pytanie o napiecie
            Napiecie = sbuf[3] << 8 | sbuf[4];
#ifdef DEBUG
            sprintf( txt, "\n\rNapiecie=%d,%02d", Napiecie / 100, Napiecie % 100  ); mySerial.println( txt );
#endif
            break;
          case 1:   // Pytanie o prad
            Prad = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
#ifdef DEBUG
            sprintf( txt, "\n\rPrad=%d,%03d", Prad / 1000, Prad % 1000 ); mySerial.println( txt );
#endif
            break;
          case 2:   // Pytanie o moc czynna
            MocCzynna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
#ifdef DEBUG
            sprintf( txt, "\n\rMocCzynna=%d,%03d", MocCzynna / 1000, MocCzynna % 1000 ); mySerial.println( txt );
#endif
            break;
          case 3:   // Pytanie o moc bierna
            MocBierna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
#ifdef DEBUG
            sprintf( txt, "\n\rMocBierna=%d,%03d", MocBierna / 1000, MocBierna % 1000 ); mySerial.println( txt );
#endif
            break;
          case 4:   // Pytanie o PF
            WspolczynnikMocy = sbuf[3] << 8 | sbuf[4];
#ifdef DEBUG
            sprintf( txt, "\n\rWspolczynnikMocy=%d,%03d", WspolczynnikMocy / 1000, WspolczynnikMocy % 1000  ); mySerial.println( txt );
#endif
            break;
          case 5:   // Pytanie o zuzyta energie czynna
            EnergiaCzynna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6]; // Total, kolejne UINT32 to taryfy T1, T2, T3 i T4
#ifdef DEBUG
            sprintf( txt, "\n\rEnergiaCzynnaa=%d,%02d", EnergiaCzynna / 100, EnergiaCzynna % 100 ); mySerial.println( txt );
#endif
            break;
          case 6:   // Pytanie o zuzyta energie bierna
            EnergiaBierna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6]; // Total, kolejne UINT32 to taryfy T1, T2, T3 i T4
#ifdef DEBUG
            sprintf( txt, "\n\rEnergiaBierna=%d,%02d", EnergiaBierna / 100, EnergiaBierna % 100 ); mySerial.println( txt );
#endif
            break;
          case 7:   // Pytanie o moc pozorna
            MocPozorna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
#ifdef DEBUG
            sprintf( txt, "\n\rMocPozorna=%d,%03d", MocPozorna / 1000, MocPozorna % 1000 ); mySerial.println( txt );
#endif
            break;
          case 8:   // Pytanie o czestotliwosc
            Czestotliwosc = sbuf[3] << 8 | sbuf[4];
            rdONROfull = true;
#ifdef DEBUG
            sprintf( txt, "\n\rCzestotliwosc=%d,%02d", Czestotliwosc / 100, Czestotliwosc % 100 ); mySerial.println( txt );
#endif
            break;
        }
      }
    }
    else { // Zapytanie z Wi-Fi
      if (localClient && localClient.connected()) {
        localClient.write(sbuf, len);
      }
    }
  }


  if (WiFi.status() == WL_CONNECTED)  {
    if ( millis() > CzasDoTHINGSPEAK && rdONROfull ) {
      CzasDoTHINGSPEAK = millis() + Co_ILE_NA_THINGSPEAK + random( 0, 1000 );

#ifdef DEBUG
      mySerial.print( "send Thingspeak..." );
#endif
#ifdef DEBUG_UART0
      Serial.print( "send Thingspeak..." );
#endif
      //-----
      uint32_t tim = millis();
      ThingSpeak.setField(1, ((float)(WspolczynnikMocy) / 1000) );
      ThingSpeak.setField(2, (float)MocCzynna );
      if ( ip_4 == 52 ) {
        ThingSpeak.writeFields(thingspeak_myChannelNumber, thingspeak_apiKey_sasedw);
      }
      else if ( ip_4 == 53 ) {
        ThingSpeak.writeFields(thingspeak_myChannelNumber, thingspeak_apiKey_rmikliczniki);
      }
#ifdef DEBUG
      mySerial.print( millis() - tim );
      mySerial.println(" ms");
#endif
#ifdef DEBUG_UART0
      Serial.print( millis() - tim );
      Serial.println(" ms");
#endif


      //----- WWW (RPi)
      for (byte www = 0; www < 2; www++) {
        WiFiClient client; //wysylanie do PHP i MySQL
        const int httpPort = 80;
        const char* host;
        if ( !www ) host = "192.168.2.12"; //IP lokalnego serwera
        else host = "37.59.49.187"; //IP serwera, nie może byc nazwa domenowa
        if (!client.connect(host, httpPort)) {
#ifdef DEBUG
          mySerial.println("connection failed");
#endif
#ifdef DEBUG_UART0
          Serial.println("connection failed");
#endif
        }
        else {
          String url = "/automatyka/data_logger.php?";
          char txt[20];
          sprintf( txt, "%d", (float)MocCzynna);
          url += "moc=";
          url += txt;
          sprintf( txt, "%d,%03d", WspolczynnikMocy / 1000, WspolczynnikMocy % 1000  );
          url += "&pf=";
          url += txt;
#ifdef DEBUG
          if ( !www ) mySerial.print( "Send Local host..." );
          else mySerial.print( "Send Remote host..." );
#endif
#ifdef DEBUG_UART0
          if ( !www ) Serial.print( "Send Local host..." );
          else Serial.print( "Send Remote host..." );
#endif
          uint32_t tim = millis();
          client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
#ifdef DEBUG
          mySerial.print( millis() - tim );
          mySerial.println(" ms");
#endif
#ifdef DEBUG_UART0
          Serial.print( millis() - tim );
          Serial.println(" ms");
#endif
        }
      }
    }
  }


}

 

Udostępnij ten post


Link to post
Share on other sites

Zaintrygował mnie czas komunikacji z serwerem zdalnym i lokalnym. Teraz mierzę go z większą rozdzielczością

1140430641_Czastransmisjinaserwerzdalnyilokalny.gif.37da09c2e2bb5c898714f124215dedc3.gif

Wynik zaskakujący, w sieci lokalnej operacja wykonuje się dłużej niż zdalnej! Można pobawić się WireShark'em aby dokładnej zdiagnozować dlaczego tak jest ale powodem jest pewnie stosunkowo mała prędkość pracy RPi w stosunku do serwera w internecie. W takiej sytuacji czas transmisji pakietu ma drugorzędne znaczenie. Dodam w skryptach PHP pomiar czasu to wszystko się wyjaśni.

W każdym razie, widać, że warto używać własnych rozwiązań. Niekoniecznie musi to być serwer lokalny, doskonale sprawuje się także serwer zdalny. Przyrost prędkości o 64700% w najgorszym przypadku o 46400% daje do myślenia. Napisanie skryptu PHP, łącznie z testowaniem, zajęło mi ok 2 godzin (sam skrypt napisałem szybciej ale wychodziły błędy w programie dla PHP i trzeba było diagnozować problem i robić poprawki). Własne rozwiązanie nie ma ograniczenia liczby danych jakie mogę wysłać, liczby kanałów jakie można obsłużyć (w thingspeak 8). Za pełna wersję nie trzeba płacić ( thingspeak $650 na rok, co daje ok 2`508,025 zł, pewnie do tego VAT co daje 3`084zł / rok) a i tak są ograniczenia

thingspeak_koszty.thumb.gif.eaeb3e5e59cdd2e66222e50a0d84a880.gif

Udostępnij ten post


Link to post
Share on other sites

Małe sprostowanie. Wyniki pomiarów nie są wiarygodne bo

tracert.thumb.gif.b79c7ed52618a2a660ad199593445fc9.gif

Dla ArduinoUNO wyniki byłyby wiarygodne natomiast ESP ma dużo RAM i buforuje dane. Aby wyniki były wiarygodne trzeba by sprawdzać odpowiedź skryptu (skrypt odpowiada). Sam skrypt, czy na RPi czy w internecie, wykonuje się w czasie 800..900us, czasem trochę ponad ms.

Przypomniała mi się taka dewiza "Nie wystarczy posiadać przyrząd pomiarowy, trzeba się jeszcze umieć nim posługiwać".

Udostępnij ten post


Link to post
Share on other sites

Z ciekawości, zmodyfikowałem kod

 uint32_t timE = 0, tim = micros();
          client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
          sprintf( txt, "\n\r Send GET %d.%02dms", (micros() - tim) / 1000, (micros() - tim) % 1000 ); //Czas na jaki angarzowany jest uC
#ifdef DEBUG
          mySerial.println(txt);
#endif
#ifdef DEBUG_UART0
          Serial.println(txt);
#endif
          //----- Odpowiedz z WWW
          uint32_t timeout = millis() + 500; //todo: #define
          while ( client.connected() || client.available() ) {
            if ( millis() >= timeout ) break;
            if ( client.available() ) {
              if ( ! timE ) timE = micros();
              String line = client.readStringUntil('\n');
              /*
                #ifdef DEBUG
                            mySerial.println(line);
                #endif
                #ifdef DEBUG_UART0
                            Serial.println(line);
                #endif
              */
            }
          }
          sprintf( txt, " Read HTML %d.%02dms", (timE - tim) / 1000, (timE - tim) % 1000 ); // Czas odpowiedzi z WWW

oto wyniki

Send Thingspeak...5574 ms
Send Local host.../automatyka/Logger.php?moc=0,214&pf=0,986&id=warsztat

 Send GET 1.34ms
 Read HTML 25.237ms
Send Remote host.../automatyka/Logger.php?moc=0,214&pf=0,986&id=warsztat

 Send GET 0.983ms
 Read HTML 66.360ms

Jeśli nie ma potrzeby czekania na odpowiedź serwera to 1ms wystarcza na zainicjalizowanie wysyłki danych. Ponad 5 sekund a 1ms to przepaść. Jeśli na odpowiedź trzeba czekać, to najlepiej zrobić to w taki sposób aby nie blokować CPU. Nawet, jeśli zablokujemy, to serwer w sieci lokalnej daje przyspieszenie ponad 200 razy, w zewnętrznej 84 razy.

Od teraz, z thingspeak, będę korzystał tylko do testów! Jak to mówią, chcesz zrobić dobrze, zrób sam. Pozostaje mi znaleźć jakiś soft do wizualizacji wyników. Na razie, mam to w formie CSV http://es2.noip.pl/automatyka/data/

 

 

Udostępnij ten post


Link to post
Share on other sites

Soft po porządkowaniu i małych poprawkach oraz obsłudze serwera na OrangePi-Zero

wygląda tak:

//todo: odpytywanie co 3 sekundy:
// moc
// cos fi
// inne, gdzie inne kolejno
//  prad
//  napiecie
//  ...
//  id licznika (dodac odczyt)
//
//todo: zapis na thing spoeak i lokalny (co 10 sek) z usrednonych wynikow


#define DEBUG           // Informacje na software serial
#define DEBUG_UART0     // Czesc informacji na serial (USB) wie takze na RS485 (ale DIR nieaktywny)


#include <ESP8266WiFi.h>
#include <ThingSpeak.h>

#include <SoftwareSerial.h>
SoftwareSerial mySerial( D3, D4 ); // RX, TX - GPIO0 GPIO2

#include <NtpClientLib.h>     // NTP
#include <TimeLib.h>          // NTP

//#include <Crc16.h> //Crc 16 library (XModem)    // INFO: ta biblioteka jest trefna, wywoluje blad stosu. Dalej funkcja liczacza CRC "na piechote"
//Crc16 crc16;


/*
  todo:
  - określenie typu licznika, 514 czy 504? jek się nie da wybór przyciskiem radiowym

  - chckbox do on/off zapytań do orno
  - checkbox do wysyłki na thingspeak (wysylka blokuje loop na 5 sekun!)
  - Forularz do wysylania ramki do ORNO i okno prezentacji wyniku (wszystko w HEX)
  - Forularz do prametrów transmisji formatu ramki

  - rejestr 110. Ustawienie stałej licznika (LED). Domyslne 1000, akceptowane 100, 1000, 2000. ustawic na 100
*/


//----- Ustawienia UART -----//
#define DIR485    D2
#define BAUD    9600
#define FORMAT_UART  SERIAL_8E1


//----- Ustawienia sieci -----//
#define DHCP    // uzycie definicji wlacza DHCP
#define WIFI_SSID   "xxx"
#define WIFI_PASS   "xxx"


#ifndef DHCP        // Gdy nie wlaczone DHCP
IPAddress staticIP(192, 168, 2, 101);
IPAddress gateway(192, 168, 2, 1);
IPAddress subnet(255, 255, 255, 0);
#endif


#define LICZBA_HOSTOW       3
#define DATA_HOST_LOCAL     "xxx"      //IP lokalnego serwera
#define DATA_HOST_LOCAL_2   "xxx"      //IP lokalnego serwera
#define DATA_HOST_REMOTE    "xxx"     //IP serwera, może byc nazwa domenowa
#define LOGGER_URL          "/automatyka/xxx.php?"     
#define REPLY_WWW         // definicja zalacza oczekiwanie na odpowiedz z serweraf
#define OVERTIME_REPLY_WWW   200   // max czas czekania na odpowiedz z WWW


//dane konta na thingspeak
const char * thingspeak_apiKey_sasedw = "xxx"; // sasedw
const char * thingspeak_apiKey_rmikliczniki = "xxx"; // rmikliczniki
unsigned long thingspeak_myChannelNumber = 1;
// "184.106.153.149" lub api.thingspeak.com
const char* server = "api.thingspeak.com";
WiFiClient client_thingspeak;


// #define DELAY_DIR         // Deklaracja wlacza pauze przed i po nadaniu ramki (przydatne gdy nie ma terminowalinia linii z polaryzacją)
#define TIM_SEND_BYTE_UART    1200    // Czas [us] nadawania bajtu przez UART
#define DEF_RESTART_WIFI    (5 * 60 * 10000)  // 5 minut
#define TIM_OFLINE_ORNO   	15000UL
#define CO_ILE_PYTAC_ORNO    1250UL   // Przy 8 licznikach faje 10 sekund (+1 na RTC)
#define DEF_BUSY_ACK_ORNO    100UL

#define Co_ILE_NA_THINGSPEAK    60000UL
#define Co_ILE_NA_URL         10000UL
#define Co_ILE_NA_URL_LOCAL_HOST  6

#define CO_ILE_NTP          123
#define CO_ILE_PRINT_CZAS   66000UL


WiFiServer localServer(8888);
WiFiClient localClient;
byte ip_4;


uint16_t Napiecie, WspolczynnikMocy, Czestotliwosc;
uint32_t Prad, MocCzynna, MocBierna, MocPozorna, EnergiaCzynna, EnergiaBierna;
uint32_t CzasDoResetu, CzasDoRstWifi, timeoutAskOrno, timeoutBusyOrno;
uint32_t TimLoop, TimLoopMax;
bool rdORNOfull;
uint8_t rdOrnoCNT, flWriteRTC;
int8_t static nrPytaniaOrno = -1;
uint32_t CzasDoTHINGSPEAK, CzasDoURL;
uint32_t timOdpowiedziOrno, czasOdpowiedziOrno, timAktywnyTunel;


uint16_t dtYYYY;
uint8_t dtMIES, dtDD, dtGG, dtMM, dtSS;


char static buf[10000]; // todo: zrobic kontrole przekroczenia bufora
//------------------------------------------------------------
#include "ESP8266WebServer.h"
ESP8266WebServer  webServer(80);
//===========================================================
void IndexHandler()
{
  //  webServer.send("Location", "/index.html",true );  // przekierowanie na plik w SPIFFS
  //  webServer.send( 302,"text/plane" );    //EP 2018-12 str 31

  //  webServer.send( 200, "text/plain", "Czas uruchomienia " + String( millis()) + "ms" );


  char txt[200];//1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000


  //  strcpy( buf, "<head> <title>Licznik ORNO</title> <meta http-equiv='refresh' content='5' /> </head>" );  // Firefox burzy sie na 'refresh'
  strcpy( buf, "<head> <title>Licznik ORNO</title> </head>" );

  strcat( buf, "<H3>Licznik ORNO " );
  if ( ip_4 == 52 ) strcat( buf, "<font color='BLUE'> (Glowny) </font>" );
  else if ( ip_4 == 53 ) strcat( buf, "<font color='BLUE'> (Warsztat) </font>" );
  char kolor[30] = "<font color='BLACK'>";
  if ( millis() < timeoutAskOrno ) {  // Timeout 15 sekund
    if ( rdORNOfull ) {
      strcat( buf, "<font color='GREEN'> <B>RS485 ON-Line</B></font>" );
      strcpy( kolor, "<font color='GREEN'>" );
    }
    else {
      strcat( buf, "<font color='ORANGE'> <B>RS485 ON-Line</B></font>" );
      strcpy( kolor, "<font color='ORANGE'>" );
    }
  }
  else {
    rdORNOfull = false;
    strcat( buf, "<font color='RED'> <B>RS485 OFF-Line</B></font>" );
    strcpy( kolor, "<font color='RED'>" );
  }
  strcat( buf, " <A HREF='fw'>FW</A></H3>\r" );


  uint32_t EnergiaCzynnaSyncZE = EnergiaCzynna + 1867.7 * 1000; // Dodajemy wskazanie licznika ZE // todo: zrobic w formularzu, zapis do EEPROM
  EnergiaCzynnaSyncZE -= 3820; // Wskazanie ORNO // todo: forularz
  strcat( buf, "<TABLE cellspacing='10' cellpadding='5'> <tr style='vertical-align: top; '><TD>\r" );
  strcat( buf, kolor );
  sprintf( txt, "MocCzynna = %d,%03dkW", MocCzynna / 1000, MocCzynna % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>MocBierna = %d,%03dkWAr", MocBierna / 1000, MocBierna % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>MocPozorna = %d,%03dkWA", MocPozorna / 1000, MocPozorna % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>WspolczynnikMocy = %d,%03d", WspolczynnikMocy / 1000, WspolczynnikMocy % 1000  ); strcat( buf, txt );
  strcat( buf, "</TD><TD>\r" );
  strcat( buf, kolor );
  sprintf( txt, "Napiecie = %d,%02dV", Napiecie / 100, Napiecie % 100  ); strcat( buf, txt );
  sprintf( txt, "<BR>Prad = %d,%03dA", Prad / 1000, Prad % 1000 ); strcat( buf, txt );
  sprintf( txt, "<BR>Czestotliwosc = %d,%02dHz", Czestotliwosc / 100, Czestotliwosc % 100 ); strcat( buf, txt );
  strcat( buf, "</TD><TD>\r" );
  strcat( buf, kolor );
  sprintf( txt, "<font color='BLUE'>EnergiaCzynna = %d,%03dkWh</font>", EnergiaCzynna / 1000, EnergiaCzynna % 1000 ); strcat( buf, txt );
  if ( ip_4 == 52 ) {
    sprintf( txt, "<font color='BLUE'></BR>ZE = %d,%03dkWh</B></font> ",  EnergiaCzynnaSyncZE / 1000, EnergiaCzynnaSyncZE % 1000 ); strcat( buf, txt );
  }
  sprintf( txt, "<BR>EnergiaBierna = %d,%03dkVArh</TD>", EnergiaBierna / 1000, EnergiaBierna % 1000 ); strcat( buf, txt );
  strcat( buf, "</TD></tr></TABLE>\r" );


  strcat( buf, "<TABLE cellspacing='10' cellpadding='5'> <tr style='vertical-align: top; '><TD>\r" );
  String str = NTP.getTimeDateString(); strcpy( txt, str.c_str() ); strcat( buf, txt );
  if ( NTP.isSummerTime() ) strcpy( txt, "<BR>Czas letni. " ); else strcpy( txt, "<BR>Czas zimowy. " );
  strcat( buf, "</TD><TD>\r" );
  //Puste pole (kiedy LOOP=)
  strcat( buf, "</TD><TD>\r" );

  // todo: mozliwosc wpisania w formularzu "<iframe width = ......... i zapis do eeprom. Wtedy nie potrzeba if ( ip_4 == ....
  if ( ip_4 == 52 ) {
    strcat( buf, "<A HREF='https://thingspeak.com/channels/890858' TARGET='_blank' >Rejestrator</A>\r" );
    strcat( buf, "</TD></tr></TABLE>\r" );
    strcat( buf, "<P><iframe width='450' height='260' style='border: 1px solid #cccccc;' src='https://thingspeak.com/channels/890858/charts/2?bgcolor=%23ffffff&color=%23d62020&dynamic=true&results=60&title=Moc+G%C5%82%C3%B3wny&type=line'>< / iframe >" );
  }
  else if ( ip_4 == 53 ) {
    strcat( buf, "<A HREF='https://thingspeak.com/channels/893181' TARGET='_blank' >Rejestrator</A>\r" );
    strcat( buf, "</TD></tr></TABLE>\r" );
    strcat( buf, "<P><iframe width='450' height='260' style='border: 1px solid #cccccc;' src='https://thingspeak.com/channels/893181/charts/2?bgcolor=%23ffffff&color=%23d62020&dynamic=true&results=60&type=line&update=15'>< / iframe > ");
  }


  //strcat( buf, "<P><A HREF='/s'>Skanuj I2C</A>" );


  //  webServer.send( 200, "text / plain", buf );
  webServer.send( 200, "text/html; charset=iso-8859-2", buf );
  /*
    void  send (int code, const String &content_type, const String &content)
    void  sendHeader (const String &name, const String &value, bool first=false)
    void  sendContent (const String &content)
  */
}


void fwHandler() {
  char txt[200];//1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000


  //  strcpy( buf, "<head> <title>Licznik ORNO</title> <meta http-equiv='refresh' content='5' /> </head>" );  // Firefox burzy sie na 'refresh'
  strcpy( buf, "<head> <title>Licznik ORNO</title> </head>" );

  //todo: Licznik ORNO #define
  strcat( buf, "<H3>Licznik ORNO " );
  if ( millis() < timeoutAskOrno ) {  // Timeout 15 sekund
    if ( rdORNOfull ) {
      sprintf( txt, "<font color='GREEN'><B>RS485 ON-Line </B>Read record %d</font> (%dms)", nrPytaniaOrno, czasOdpowiedziOrno ); strcat( buf, txt );
    }
    else {
      sprintf( txt, "<font color='ORANGE'><B>RS485 ON-Line </B>Read record %d</font> (%dms)", nrPytaniaOrno, czasOdpowiedziOrno ); strcat( buf, txt );
    }
  }
  else {
    int tim = millis() - timeoutAskOrno; // czas przez jaki nieprzychodza odpowiedzi
    if ( tim < 0 ) tim = 0;
    sprintf( txt, "<font color='RED'> <B>RS485 OFF-Line </B> Time %d sekund </font>", tim / 1000 ); strcat( buf, txt );
  }
  strcat( buf, "</H3>\r" );


  sprintf( txt, "FW v1.0 %s %s", __DATE__, __TIME__ ); strcat( buf, txt );
  sprintf( txt, "<BR>SysTick: %d sekund", millis() / 1000 ); strcat( buf, txt );
  uint32_t tmax = TimLoopMax / 1000; // INFO: Na WWW czas jest wiekszy niz w serialmonitor bo dochodzi czas obslugi WWW
  sprintf( txt, "<BR>Loop=%dus, Max=%d.%03dsek", TimLoop, tmax / 1000, tmax % 1000 ); strcat( buf, txt );
  uint32_t t = ( millis() - timAktywnyTunel) / 100;
  sprintf( txt, "<BR>Tunel %d,%01dsek.", t / 10, t % 10 ); strcat( buf, txt );


  strcat( buf, "<P><P><P>WWW: <A HREF='http://sas.noip.pl'>http://sas.noip.pl</A>" );
  strcat( buf, "<BR>Autor: <A HREF='mailto:sas@elportal.pl?subject=Interfejs licznikow ORNO-5xx'>e-mail sas@elportal.pl</A>" );


  strcat( buf, "<P><P><P><button onclick='goBack()'>Go Back</button> ");
  strcat( buf, "<script> ");
  strcat( buf, "function goBack() { ");
  strcat( buf, "  window.history.back(); ");
  strcat( buf, "} ");
  strcat( buf, "</script> ");
  //strcat( buf, "<P><P><A HREF='/'>Wstecz</A>" );

  webServer.send( 200, "text/html; charset=iso-8859-2", buf );
}


void notFoundHandler()
{
  webServer.send( 404, "text/plain", "Strona nie istnieje." );
}
//w loop():
//  webServer.handleClient();
//w setup():
//  webServer.on( " / ", IndexHandler );    // tych deklaracji moze byc wiele,co by to nie znaczylo
//  webServer.onNotFound( notFoundHandler );
//  webServer.begin();



boolean syncEventTriggered = false; // True if a time even has been triggered
NTPSyncEvent_t ntpEvent; // Last triggered event
int8_t timeZone = 1;
int8_t minutesTimeZone = 0;
bool wifiFirstConnected = true; //false;


// Start NTP only after IP network is connected
void onSTAGotIP (WiFiEventStationModeGotIP ipInfo) {
  Serial.printf ("***NTP*** Got IP: %s\r\n", ipInfo.ip.toString ().c_str ());
  Serial.printf ("Connected: %s\r\n", WiFi.status () == WL_CONNECTED ? "yes" : "no");
  wifiFirstConnected = true;
}


void onSTAConnected (WiFiEventStationModeConnected ipInfo) {
  Serial.printf ("***NTP*** Connected to %s\r\n", ipInfo.ssid.c_str ());
}


// Manage network disconnection
void onSTADisconnected (WiFiEventStationModeDisconnected event_info) {
  Serial.printf ("***NTP*** Disconnected from SSID: %s\n", event_info.ssid.c_str ());
  Serial.printf ("Reason: %d\n", event_info.reason);
  //NTP.stop(); // NTP sync can be disabled to avoid sync errors
}


void processSyncEvent(NTPSyncEvent_t ntpEvent) {
  if (ntpEvent) {
    printDebug("***NTP*** Time Sync error: ");
    if (ntpEvent == noResponse)
      printDebug("NTP server not reachable");
    else if (ntpEvent == invalidAddress)
      printDebug("Invalid NTP server address");
  } else {
    printDebug("***NTP*** Got NTP time: ");
    Serial.println ( NTP.getTimeDateString(NTP.getLastNTPSync() ) );
  }
}



unsigned int CRC16(unsigned char val[] , unsigned char len)
{
  unsigned int crc = 0xffff;
  unsigned char i, k;


  for (k = 0; k < len; k++)
  {
    crc = crc ^ val[k];
    i = 8;
    while (i)
    {
      if (crc & 1)
      {
        crc = crc >> 1;
        crc = crc ^ 0xA001;
      }
      else
      {
        crc = crc >> 1;
      }
      i--;
    }
  }

  return crc;
}
/*
  const byte auchCRCHi[256] = {
  0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81,
  0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
  0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01,
  0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41,
  0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81,
  0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
  0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01,
  0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40,
  0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81,
  0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
  0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01,
  0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81,
  0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
  0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01,
  0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
  0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81,
  0x40
  };

  const byte auchCRCLo [256] = {
  0x00, 0xc0, 0xc1, 0x01, 0xc3, 0x03, 0x02, 0xc2, 0xc6, 0x06,
  0x07, 0xc7, 0x05, 0xc5, 0xc4, 0x04, 0xcc, 0x0c, 0x0d, 0xcd,
  0x0f, 0xcf, 0xce, 0x0e, 0x0a, 0xca, 0xcb, 0x0b, 0xc9, 0x09,
  0x08, 0xc8, 0xd8, 0x18, 0x19, 0xd9, 0x1b, 0xdb, 0xda, 0x1a,
  0x1e, 0xde, 0xdf, 0x1f, 0xdd, 0x1d, 0x1c, 0xdc, 0x14, 0xd4,
  0xd5, 0x15, 0xd7, 0x17, 0x16, 0xd6, 0xd2, 0x12, 0x13, 0xd3,
  0x11, 0xd1, 0xd0, 0x10, 0xf0, 0x30, 0x31, 0xf1, 0x33, 0xf3,
  0xf2, 0x32, 0x36, 0xf6, 0xf7, 0x37, 0xf5, 0x35, 0x34, 0xf4,
  0x3c, 0xfc, 0xfd, 0x3d, 0xff, 0x3f, 0x3e, 0xfe, 0xfa, 0x3a,
  0x3b, 0xfb, 0x39, 0xf9, 0xf8, 0x38, 0x28, 0xe8, 0xe9, 0x29,
  0xeb, 0x2b, 0x2a, 0xea, 0xee, 0x2e, 0x2f, 0xef, 0x2d, 0xed,
  0xec, 0x2c, 0xe4, 0x24, 0x25, 0xe5, 0x27, 0xe7, 0xe6, 0x26,
  0x22, 0xe2, 0xe3, 0x23, 0xe1, 0x21, 0x20, 0xe0, 0xa0, 0x60,
  0x61, 0xa1, 0x63, 0xa3, 0xa2, 0x62, 0x66, 0xa6, 0xa7, 0x67,
  0xa5, 0x65, 0x64, 0xa4, 0x6c, 0xac, 0xad, 0x6d, 0xaf, 0x6f,
  0x6e, 0xae, 0xaa, 0x6a, 0x6b, 0xab, 0x69, 0xa9, 0xa8, 0x68,
  0x78, 0xb8, 0xb9, 0x79, 0xbb, 0x7b, 0x7a, 0xba, 0xbe, 0x7e,
  0x7f, 0xbf, 0x7d, 0xbd, 0xbc, 0x7c, 0xb4, 0x74, 0x75, 0xb5,
  0x77, 0xb7, 0xb6, 0x76, 0x72, 0xb2, 0xb3, 0x73, 0xb1, 0x71,
  0x70, 0xb0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
  0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9c, 0x5c,
  0x5d, 0x9d, 0x5f, 0x9f, 0x9e, 0x5e, 0x5a, 0x9a, 0x9b, 0x5b,
  0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4b, 0x8b,
  0x8a, 0x4a, 0x4e, 0x8e, 0x8f, 0x4f, 0x8d, 0x4d, 0x4c, 0x8c,
  0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
  0x43, 0x83, 0x41, 0x81, 0x80, 0x40
  };


  uint16_t CRC16(uint8_t *puchMsg, uint8_t usDataLen)
  {
  static unsigned char uIndex = 0;
  static unsigned char uchCRCHi;
  static unsigned char uchCRCLo;
  uchCRCHi = 0xff;
  uchCRCLo = 0xff;

  while (usDataLen --)
  {
    uIndex = uchCRCHi ^ *puchMsg++;
    uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex];
    uchCRCLo = auchCRCLo[uIndex];
  }
  return (uchCRCHi << 8 | uchCRCLo);
  }
*/


//===========================================================
uint16_t lenBufSerTx;

void setup() {
  ESP.wdtEnable(1000);


  Serial.begin(BAUD, FORMAT_UART);
  Serial1.begin(BAUD, FORMAT_UART);
  mySerial.begin(9600);


  WiFi.begin(WIFI_SSID, WIFI_PASS);
#ifndef DHCP
  WiFi.mode(WIFI_STA);
  WiFi.config(staticIP, gateway, subnet);
#endif
  pinMode(DIR485, OUTPUT);
  digitalWrite(DIR485, LOW);

  Find_WiFi();
  localServer.begin();
  localServer.setNoDelay(true);


  delay(100); // Czas na oproznienie bufora nadawczego UART
  lenBufSerTx = Serial.availableForWrite();
#ifdef DEBUG
  mySerial.print( "\n\rBufor nadawczy UART "); mySerial.print( lenBufSerTx ); mySerial.println( " bajtow" );
#endif


  webServer.on( "/", IndexHandler );
  webServer.on( "/fw", fwHandler );
  //  webServer.on( "/s", IndexHandlerScan ); // podstrona WWW HTTP
  webServer.onNotFound( notFoundHandler );
  webServer.begin();


  ThingSpeak.begin(client_thingspeak);


  static WiFiEventHandler e1, e2, e3;

  NTP.onNTPSyncEvent ([](NTPSyncEvent_t event) {
    ntpEvent = event;
    syncEventTriggered = true;
  });
  // Deprecated
  /*WiFi.onEvent([](WiFiEvent_t e) {
      Serial.printf("Event wifi -----> %d\n", e);
    });*/
  e1 = WiFi.onStationModeGotIP (onSTAGotIP);// As soon WiFi is connected, start NTP Client
  e2 = WiFi.onStationModeDisconnected (onSTADisconnected);
  e3 = WiFi.onStationModeConnected (onSTAConnected);



  CzasDoRstWifi = millis() + DEF_RESTART_WIFI;
  CzasDoResetu = millis() + 1 * 60 * 60 * 1000; // 24h   //CzasDoResetu = millis() + 24 * 60 * 60 * 1000; // 24h
  ESP.wdtEnable(100);
}


//------------------------------------------------------------
void Find_WiFi() {
  mySerial.print("\n\rRozlaczony, Lącze z sieca Wi-Fi '");
  mySerial.print( WIFI_SSID );
  mySerial.print("'...");
#ifdef DEBUG_UART0
  Serial.print("\n\rRozlaczony, Lącze z sieca Wi-Fi '");
  Serial.print( WIFI_SSID );
  Serial.print("'...");
#endif

  while (WiFi.status() != WL_CONNECTED) {
    ESP.wdtFeed();
    delay(200);
    mySerial.print(".");
#ifdef DEBUG_UART0
    Serial.print(".");
#endif
  }
  mySerial.print("\n\rPolaczony z siecia '");
  mySerial.print( WIFI_SSID );
  mySerial.print("' IP ");
  mySerial.print( WiFi.localIP() );
  mySerial.println(". Tryb UART BRIDGE ");
#ifdef DEBUG_UART0
  Serial.print("\n\rPolaczony z siecia '");
  Serial.print( WIFI_SSID );
  Serial.print("' IP ");
  Serial.print( WiFi.localIP() );
  Serial.println(". Tryb UART BRIDGE ");
#endif
  Serial.flush();


  // todo: do poprawki albo przejscie na formularz i eeprom
  String ip = WiFi.localIP().toString();
  if ( ip == "192.168.2.52" ) {
    ip_4 = 52;
  }
  else if ( ip == "192.168.2.53" ) {
    ip_4 = 53;
  }
}


//------------------------------------------------------------
void wyslij_na_RS485( uint8_t *sbuf, size_t len) {
  /*
    #ifdef DEBUG
    char txt[10];
    myserial.print( "->485: " );
    for (byte x = 0; x < len; x++) {
    sprintf( txt, "%0xd ", sbuf[x]); myserial.print( txt );
    }
    myserial.println();
    #endif
  */


  digitalWrite(DIR485, HIGH);
#ifdef DELAY_DIR
  delay(1);
#endif
  Serial.write(sbuf, len);
  //  delay( len * 1.2 );//mozna tez czekac liczba bajtow x 1.2ms, bo wyslanie 1 bajtu przy 9600 8E1 trwa ok 0,0011458ms: 1/(9600/11)
  while ( Serial.availableForWrite() != lenBufSerTx ) ; // Get the number of bytes (characters) available for writing in the serial buffer without blocking the write operation.
  //extern inline size_t uart_tx_fifo_available(const int uart_nr);
  //while ( uart_tx_fifo_available( UART0 ) ) ;
  //while ( (USS0 >> USTXC) & 0xff ) ;
  delayMicroseconds( TIM_SEND_BYTE_UART ); // Konieczne, bo uzywajac "Serial.availableForWrite()" stwierdzimy kiedy bufor jest pusty a nie kiedy znak wyslano z UART
#ifdef DELAY_DIR
  delay(1);
#endif
  digitalWrite(DIR485, LOW);
  Serial.println();

  /*
    void uart_wait_tx_empty(uart_t* uart) {
    if(uart == NULL || !uart->tx_enabled)
        return;

    while(uart_tx_fifo_available(uart->uart_nr) > 0)
        delay(0);

    }
    exp:
    uart_wait_tx_empty( USS1 )


    inline size_t uart_tx_fifo_available(const int uart_nr) {
    return (USS(uart_nr) >> USTXC) & 0xff;
    }
    exp: uart_tx_fifo_available( 0 )
    uart_tx_fifo_available( UART0 )
    uart_tx_fifo_available( UART1 )


    size_t uart_tx_free(uart_t* uart) {
    if(uart == NULL || !uart->tx_enabled)
        return 0;

    return UART_TX_FIFO_SIZE - uart_tx_fifo_available(uart->uart_nr);
    }
    exp: uart_tx_free( USS0 );


    https://github.com/esp8266/Arduino/blob/master/cores/esp8266/uart.h
    https://github.com/esp8266/Arduino/blob/master/cores/esp8266/uart.cpp
  */
}


//------------------------------------------------------------
void printDebug( char * txt ) {
#ifdef DEBUG
  mySerial.print( txt );
#endif
#ifdef DEBUG_UART0
  Serial.print( txt );
#endif
}


//------------------------------------------------------------
int8_t static fl_PytanieOrno;
unsigned long static timeoutOdpytanie;
//------------------------------------------------------------
//----- Cykliczne odpytywanie licznika -----//
//------------------------------------------------------------
void Zapytana_Do_Orno() {
#define LICZBA_PYTAN_ORNO   8
  const byte zapytanie[][8] =
  {
    { 0x00, 0x03, 0x01, 0x31, 0x00, 0x01, 0xD5, 0xE8 }, // Pytanie o napiecie
    { 0x00, 0x03, 0x01, 0x39, 0x00, 0x02, 0x14, 0x2B }, // Pytanie o prad
    { 0x00, 0x03, 0x01, 0x40, 0x00, 0x02, 0xC5, 0xF2 }, // Pytanie o moc czynna
    { 0x00, 0x03, 0x01, 0x48, 0x00, 0x02, 0x44, 0x30 }, // Pytanie o moc bierna
    { 0x00, 0x03, 0x01, 0x58, 0x00, 0x01, 0x05, 0xF4 }, // Pytanie o PF
    { 0x00, 0x03, 0xA0, 0x00, 0x00, 0x0A, 0xE6, 0x1C }, // Pytanie o zuzyta energie czynna
    { 0x00, 0x03, 0xA0, 0x1E, 0x00, 0x0A, 0x86, 0x1A }, // Pytanie o zuzyta energie bierna
    { 0x00, 0x03, 0x01, 0x50, 0x00, 0x02, 0xC4, 0x37 }, // Pytanie o moc pozorna
    { 0x00, 0x03, 0x01, 0x30, 0x00, 0x01, 0x84, 0x28 }, // Pytanie o czestotliwosc
    { 0xFF }
  };


  if ( millis() >= timeoutOdpytanie ) {
    timeoutOdpytanie = millis() + CO_ILE_PYTAC_ORNO;
    timeoutBusyOrno = millis() + DEF_BUSY_ACK_ORNO;    // Blokada zapytan przez Wi-Fi
    timOdpowiedziOrno = millis();

    fl_PytanieOrno = true;

    ++nrPytaniaOrno;
    if ( zapytanie[nrPytaniaOrno][0] != 0xFF ) wyslij_na_RS485( (uint8_t*)zapytanie[nrPytaniaOrno], 8 );
    else {
      nrPytaniaOrno = -1;

      if ( flWriteRTC ) {
        flWriteRTC = false;

        // Zapis zegara
        //       ID CMD  AdrHL   NewAdr  Len  RR MM DD gg mm ss ?? ??
        //Write: 00 10 / 81 20 / 00 04 / 08 / 13 0A 1A 0C 1E 35 06 00 / E7 21
        //Read:  02 10 / 81 20 / 00 04 / E8 0F
        //
        //12:50:16:707  Write: 00 10 81 20 00 04 08 13 0A 1A 0C 32 0F 06 00 CF BC
        //12:50:17:108  Read:  02 10 81 20 00 04 E8 0F


        // Odczyt zegara
        //Write: 00 03 / 81 20 / 00 04 / 6C 2E
        //       ID CMD  Len  RR MM DD gg mm ss ?? ??
        //Read:  02 03 / 08 / 13 0A 1A 0C 1E 35 06 00 / 76 37

        byte dane[17] = { 0x00, 0x10, 0x81, 0x20, 0x00, 0x04, 0x08 }; // Stale elemety naglowka
        //todo: test:
        dtYYYY = 2073; dtMIES = 05; dtDD = 18; dtGG = 15; dtMM = 20; dtSS = 30; //todo: test:
        dtYYYY = 0x13; dtMIES = 0x0A; dtDD = 0x1A; dtGG = 0x0C; dtMM = 0x1E; dtSS = 0x35; //todo: test: powinno dać CRC E7 21

        dane[7] = dtYYYY;
        dane[8] = dtMIES;
        dane[9] = dtDD;
        dane[10] = dtGG;
        dane[11] = dtMM;
        dane[12] = dtSS;
        //
        dane[13] = 0x06;
        dane[14] = 0x00;
        uint16_t crc = CRC16( dane, 15 );
        dane[15] = crc;
        dane[16] = crc >> 8;

        //INFO: w ORNO-514 RTC to pic na wode (nie liczny czasu) ale zapisy cos nie tak. ORNO nie odpowiada choc wysyła ramke taka sama jak program testowy 9crc sie zgadza)

        char txt[50];
        printDebug( "\n\rZapis RTC ORNO: " );
        for ( byte x = 0; x < 17; x++) {
          sprintf( txt, "%02x ", dane[x] );
          printDebug( txt );
        }
        sprintf( txt, "[%d-%d-%d %d:%d:%d]\n\r", dtYYYY, dtMIES, dtDD, dtGG, dtMM, dtSS ); printDebug( txt );

        wyslij_na_RS485( dane, 17 );
      }
    }
    /*
      //test:
      uint16_t c = CRC16( (uint8_t*)zapytanie[pytanie], 6 );
      uint16_t crc = zapytanie[pytanie][6] | zapytanie[pytanie][7] << 8;
      char txt[50];
      sprintf( txt, "\n\rCRC wyliczone %x otrzymane %x", c, crc ); printDebug( txt );
    */
  }
}


//------------------------------------------------------------
void WiFi_do_Uart() {
  //----- WiFi->UART -----//
  /*
    if (localClient && localClient.connected()) {
      if (localClient.available()) {
        size_t len = localClient.available();
        uint8_t sbuf[len];
        localClient.readBytes(sbuf, len);
        digitalWrite(DIR485, HIGH);
        delay(2);
        Serial.write(sbuf, len);
        delay(15);  // Niestety trzeba dobrac doswiadczalnie bo zalezy od dlugosci ramki
        //mozna tez czekac liczba bajtow x 1.2ms, bo wyslanie 1 bajtu przy 9600 8E1 trwa ok 0,0011458ms: 1/(9600/11)
        digitalWrite(DIR485, LOW);
      }
    }
  */
  if (localClient && localClient.connected() && millis() > timeoutBusyOrno ) {
    if (localClient.available()) {
      timeoutOdpytanie = millis() + CO_ILE_PYTAC_ORNO;

      CzasDoTHINGSPEAK = millis() + Co_ILE_NA_THINGSPEAK;    // i wysylke
      CzasDoURL = millis() + Co_ILE_NA_URL;
      timAktywnyTunel = millis();


      size_t len = localClient.available();
      uint8_t sbuf[len];
      localClient.readBytes(sbuf, len);
      wyslij_na_RS485(sbuf, len);
    }
  }
}


//------------------------------------------------------------
void Uart_do_Wifi() {
  /*
    if (Serial.available()) {
      delay(20); // Czekamy az wiecej znakow bedzie w buforze (przy 9600 ok 10)

      size_t len = Serial.available();
      uint8_t sbuf[len];
      Serial.readBytes(sbuf, len);
      if (localClient && localClient.connected()) {
        localClient.write(sbuf, len);
      }
    }
  */
  uint32_t static timeoutWr;
  size_t static prevLen;
  if ( prevLen != Serial.available() ) {
    prevLen = Serial.available();
    timeoutWr = millis() + 5; // Przychodzacy znak ustawia timeout
  }
  if ( millis() >= timeoutWr ) { // Gdy timeout minie
    timeoutWr = 0xFFFFFFFF;   // Zatrymujemy liczenie

    size_t len = Serial.available();
    uint8_t sbuf[len];
    Serial.readBytes(sbuf, len);

    //todo: funkcja
    if ( fl_PytanieOrno ) { // Jesli zapytanie lokalne (przez ESP)
      fl_PytanieOrno = false;
      timeoutBusyOrno = 0;  // Zezwolenie na pytanie z WiFi
      czasOdpowiedziOrno = millis() - timOdpowiedziOrno;


      char txt[50];
      sprintf( txt, "\n\rRS485 rx (%dms): ", czasOdpowiedziOrno ); printDebug( txt );
      for ( byte x = 0; x < len; x++) {
        sprintf( txt, "%02x ", sbuf[x] );
        printDebug( txt );
      }
      printDebug( "\n\r" );


      uint16_t crcRX = sbuf[len - 2] | sbuf[len - 1] << 8;
      uint16_t crcCalculate = CRC16( sbuf, len - 2 );
      if ( crcRX != crcCalculate ) {
        rdORNOfull = false; // flaga kompletu danych
        rdOrnoCNT = 0;  // licznik odpowiedzi
        sprintf( txt, "\n\rCRC Error! Wyliczone %x otrzymane %x", crcCalculate, crcRX ); printDebug( txt );
      }
      else {
        timeoutAskOrno = millis() + TIM_OFLINE_ORNO;

        switch ( nrPytaniaOrno ) {
          case 0:   // Pytanie o napiecie
            Napiecie = sbuf[3] << 8 | sbuf[4];
            sprintf( txt, "Napiecie=%d,%02d", Napiecie / 100, Napiecie % 100  ); printDebug( txt );
            break;
          case 1:   // Pytanie o prad
            Prad = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
            sprintf( txt, "Prad=%d,%03d", Prad / 1000, Prad % 1000 ); printDebug( txt );
            break;
          case 2:   // Pytanie o moc czynna
            MocCzynna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
            sprintf( txt, "MocCzynna=%d,%03d", MocCzynna / 1000, MocCzynna % 1000 ); printDebug( txt );
            break;
          case 3:   // Pytanie o moc bierna
            MocBierna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
            sprintf( txt, "MocBierna=%d,%03d", MocBierna / 1000, MocBierna % 1000 ); printDebug( txt );
            break;
          case 4:   // Pytanie o PF
            WspolczynnikMocy = sbuf[3] << 8 | sbuf[4];
            sprintf( txt, "WspolczynnikMocy=%d,%03d", WspolczynnikMocy / 1000, WspolczynnikMocy % 1000  ); printDebug( txt );
            break;
          case 5:   // Pytanie o zuzyta energie czynna
            EnergiaCzynna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6]; // Total, kolejne UINT32 to taryfy T1, T2, T3 i T4
            sprintf( txt, "EnergiaCzynnaa=%d,%03d", EnergiaCzynna / 1000, EnergiaCzynna % 1000 ); printDebug( txt );
            break;
          case 6:   // Pytanie o zuzyta energie bierna
            EnergiaBierna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6]; // Total, kolejne UINT32 to taryfy T1, T2, T3 i T4
            sprintf( txt, "EnergiaBierna=%d,%03d", EnergiaBierna / 1000, EnergiaBierna % 1000 ); printDebug( txt );
            break;
          case 7:   // Pytanie o moc pozorna
            MocPozorna = sbuf[3] << 24 | sbuf[4] << 16 | sbuf[5] << 8 | sbuf[6];
            sprintf( txt, "MocPozorna=%d,%03d", MocPozorna / 1000, MocPozorna % 1000 ); printDebug( txt );
            break;
          case 8:   // Pytanie o czestotliwosc
            Czestotliwosc = sbuf[3] << 8 | sbuf[4];
            sprintf( txt, "Czestotliwosc=%d,%02d", Czestotliwosc / 100, Czestotliwosc % 100 ); printDebug( txt );
            break;
        }
        if ( rdOrnoCNT < LICZBA_PYTAN_ORNO ) rdOrnoCNT++; // Zwiekszamy licznik odpowiedzi
        else rdORNOfull = true;           // Jesli jest 8 to komplet danych
      }
    }
    else { // Zapytanie z Wi-Fi
      if (localClient && localClient.connected()) {
        localClient.write(sbuf, len);
      }
    }
  }
}


//------------------------------------------------------------
static int i_ntp = 0;
static int last_ntp = 0;
//------------------------------------------------------------
void obsluga_ntp() {

  if (wifiFirstConnected) {
    wifiFirstConnected = false;
    //     NTP.begin ("pool.ntp.org", timeZone, true, minutesTimeZone);
    NTP.begin ("ntp.itl.waw.pl", timeZone, true, minutesTimeZone);
    NTP.setInterval(CO_ILE_NTP);
    printDebug("*****NTP start ");
  }
  if (syncEventTriggered) {
    processSyncEvent(ntpEvent);
    syncEventTriggered = false;
    printDebug("*****NTP SYNC ");

    //----- Rozlozenie datay i czasu
    char txt[50];
    String str = NTP.getTimeDateString(); strcpy( txt, str.c_str() );
    // 0000000000111111111
    // 0123456789012345678
    // 10:11:07 26/10/2019
    //    sscanf( txt, "%d02:%d02:%d02 %d02/%d02/%d04", &dtGG, &dtMm, &dtSS, &dtDD, &dtMIES, &dtYYYY );
    //INFI: sscanf wywala program i to dziwnie. jak zmienne sa globalne zawsze problem. jak lokalne ok z czasem sscanf daty wysypuje
    //   sscanf( txt, "%d02", &dtGG );
    dtGG = (txt[0] - '0') * 10;
    dtGG += (txt[1] - '0');

    //   sscanf( txt + 3, "%d02", &dtMM );
    dtMM = (txt[3] - '0') * 10;
    dtMM += (txt[4] - '0');

    //   sscanf( txt + 6, "%d02", &dtSS );
    dtSS = (txt[6] - '0') * 10;
    dtSS += (txt[7] - '0');

    //   sscanf( txt + 6, "%d02", &dtDD );
    dtDD = (txt[9] - '0') * 10;
    dtDD += (txt[10] - '0');

    //   sscanf( txt + 12, "%d02", &dtMIES );
    dtMIES = (txt[12] - '0') * 10;
    dtMIES += (txt[13] - '0');

    //   sscanf( txt + 15, "%d02", &dtYYYY );
    dtYYYY = (txt[15] - '0') * 1000;
    dtYYYY += (txt[16] - '0') * 100;
    dtYYYY += (txt[17] - '0') * 10;
    dtYYYY += (txt[18] - '0');

    flWriteRTC = true; // zapis RTC do ORNO
    sprintf( txt, " %d-%d-%d% d:%d:%d\n\r", dtYYYY, dtMIES, dtDD, dtGG, dtMM, dtSS ); printDebug( txt );
  }


  //todo: zrobic funkcje
  if ((millis () - last_ntp) > CO_ILE_PRINT_CZAS && (WiFi.status() == WL_CONNECTED) ) {
    char txt[50];
    last_ntp = millis();
    printDebug("\n\r*****NTP "); // Serial.print(i_ntp); printDebug("  ");
    strcpy( txt, NTP.getTimeDateString().c_str() ); printDebug(txt);  printDebug(" ");
    if ( NTP.isSummerTime() ) printDebug( "Czas letni. " ); else printDebug( "Czas zimowy. " );
    printDebug( "WiFi is " );
    if ( WiFi.isConnected() ) printDebug( "connected" ); else printDebug( "not connected" );
    printDebug("\n\rUptime: ");
    strcpy( txt, NTP.getUptimeString().c_str() ); printDebug(txt);  printDebug(" since ");
    strcpy( txt, NTP.getTimeDateString( NTP.getFirstSync()).c_str() ); printDebug(txt);
    printDebug( "\n\r" );

    i_ntp++;
  }
}


//------------------------------------------------------------
uint32_t timWwwStat, timWwwEnd;
//------------------------------------------------------------
void obsluga_www() {
  webServer.handleClient();       //----- HTTP -----//


  if (WiFi.status() == WL_CONNECTED)  {

    if ( millis() > CzasDoURL && rdORNOfull ) {
      CzasDoURL = millis() + Co_ILE_NA_URL + random( 0, 500 );

      //----- WWW (RPi)
      byte static cnt;
      if ( ++cnt >= Co_ILE_NA_URL_LOCAL_HOST ) cnt = 0;
      for (byte www = 0; www < LICZBA_HOSTOW; www++) {
        WiFiClient client;
        const int httpPort = 80;
        const char* host;

        if ( !www ) host = DATA_HOST_REMOTE;
        else if ( www == 1 ) host = DATA_HOST_LOCAL_2;
        else host = DATA_HOST_LOCAL;
        if (!client.connect(host, httpPort)) {
          printDebug( "connection failed");
        }
        else {
          String url = LOGGER_URL;
          char txt[100];
          url += "moc=";
          sprintf( txt, "%d,%03d", MocCzynna / 1000, MocCzynna % 1000 );
          url += txt;

          url += "&pf=";
          sprintf( txt, "%d,%03d", WspolczynnikMocy / 1000, WspolczynnikMocy % 1000  );
          url += txt;

          if ( ip_4 == 52 ) url += "&id=glowny"; //sprintf( txt, "%d", ip_4 ); url += "&id=";
          else url += "&id=warsztat";


          uint32_t tim = timWwwStat = micros();
          timWwwEnd = 0;
          if ( !www || ( www && !cnt) ) {  // na zdalny (www==0) zawsze, lokalny (www<>0) tylko gdy cnt==0
            if ( www ) printDebug( "\n\rSend Local host..." );
            else printDebug( "\n\rSend Remote host..." );
            strcpy( txt, url.c_str() ); printDebug( txt );

            sprintf( txt, " [cnt=%d] ", cnt ); printDebug(txt);
            client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
            sprintf( txt, "\n\r Send GET %d.%02dms ", (micros() - tim) / 1000, (micros() - tim) % 1000 ); printDebug( txt ); //Czas na jaki angarzowany jest uC
#ifdef REPLY_WWW
            //----- Odpowiedz z WWW
            uint32_t timeout = millis() + OVERTIME_REPLY_WWW;
            while ( client.connected() || client.available() ) {
              if ( millis() >= timeout ) break;
              if ( client.available() ) {
                if ( ! timWwwEnd ) timWwwEnd = micros();
                String line = client.readStringUntil('\n');
#ifdef DEBUG
                //            mySerial.println(line);
#endif
#ifdef DEBUG_UART0
                //              Serial.println(line);
#endif
              }
            }
            sprintf( txt, "\n\r Read HTML %d.%02dms", (timWwwEnd - timWwwStat) / 1000, (timWwwEnd - timWwwStat) % 1000 ); printDebug( txt ); // Czas odpowiedzi z WWW
#endif
          }
        }
      }
    }
  } // END if ( WiFi.status() == WL_CONNECTED )


/*
  //todo: problem bardziej zlozony. Zapytania get trzeba wysylac pojedynczo a nie "hurtem"
  //trzeba trzymac otwarte polaczenie
#ifdef REPLY_WWW
  if (WiFi.status() == WL_CONNECTED)  {    //----- Odpowiedz z WWW
    if ( client.available() ) {
      if ( ! timWwwEnd ) timWwwEnd = micros();
      String line = client.readStringUntil('\n');

#ifdef DEBUG
      mySerial.println(line);
#endif
#ifdef DEBUG_UART0
      Serial.println(line);
#endif
      char txt[100];
      sprintf( txt, "\n\rRead HTML %d.%02dms", (timWwwEnd - timWwwStat) / 1000, (timWwwEnd - timWwwStat) % 1000 ); printDebug( txt ); // Czas odpowiedzi z WWW
    }
  }
#endif
*/
}


//------------------------------------------------------------
void obsluga_thingspeak() {
  if (WiFi.status() == WL_CONNECTED)  {
    //todo: zrobic funkcje
    if ( millis() > CzasDoTHINGSPEAK && rdORNOfull ) {
      CzasDoTHINGSPEAK = millis() + Co_ILE_NA_THINGSPEAK + random( 0, 1000 );

      printDebug( "Send Thingspeak..." );

      //-----
      uint32_t tim = millis();
      ThingSpeak.setField(1, ((float)(WspolczynnikMocy) / 1000) );
      ThingSpeak.setField(2, (float)MocCzynna );
      if ( ip_4 == 52 ) {
        ThingSpeak.writeFields(thingspeak_myChannelNumber, thingspeak_apiKey_sasedw);
      }
      else if ( ip_4 == 53 ) {
        ThingSpeak.writeFields(thingspeak_myChannelNumber, thingspeak_apiKey_rmikliczniki);
      }
      char txt[50];
      sprintf( txt, " %dms", millis() - tim ); printDebug( txt );
    }
  }
}


//===========================================================
void loop() {
  ESP.wdtFeed();


  //todo: test: serial1
  uint32_t static timSerTx;
  if ( millis() >= timSerTx ) {
    timSerTx = millis() + 3000;

    Serial1.println("test");
    //mySerial.println("Hello, world?");
  }
  //if (mySerial.available())Serial.write(mySerial.read());
  /*
    if ( Serial1.available() ) {
    size_t len = Serial1.available();
    uint8_t sbuf[len];
    Serial1.readBytes(sbuf, len);
    Serial.print(sbuf);
    }
  */


  //----- Wznawianie Wi-Fi gdy rozlaczone -----??
  if (WiFi.status() != WL_CONNECTED) Find_WiFi();
  if (localServer.hasClient()) {
    if (!localClient.connected()) {
      if (localClient) localClient.stop();
      localClient = localServer.available();
    }
  }


  Zapytana_Do_Orno();

  WiFi_do_Uart();
  Uart_do_Wifi();

  obsluga_ntp();

  obsluga_thingspeak();
  obsluga_www();


  //----- Pomiar czasu trwania petli glownej -----//
  uint32_t static tim, timprint;
  if ( ! tim ) tim = micros(); // Init przy pierwszym obiegu petli

  TimLoop = micros() - tim;
  tim = micros();
  if ( TimLoop > TimLoopMax ) TimLoopMax = TimLoop;

  if ( millis() >= timprint ) {
    timprint = millis() + 5000;
    char txt[100];
    uint32_t tmax = TimLoopMax / 1000;
    sprintf( txt, "\r\nLoop=%dus Max=%d.%03dsek", TimLoop, tmax / 1000, tmax % 1000 ); printDebug( txt );
    sprintf( txt, " rdORNOfull=%d\n\r", rdORNOfull ); printDebug( txt );
  }


  //----- Resetowanie modulu co 24h -----//
  if ( millis() > CzasDoResetu ) {
    mySerial.print("\n\r*** Abort ***");
    Serial.print("\n\r*** Abort ***");
    while ( Serial.availableForWrite() );
    abort();
  }
  /*
    if ( millis() > CzasDoRstWifi ) {
      millis() + DEF_RESTART_WIFI;

      Serial.println("***** Restart Wi-Fi *****");
      disconnect
      po kilkun sekundach connect
      WiFi.begin();
    }
  */


}//----- EMD loop()

 

Udostępnij ten post


Link to post
Share on other sites

Implementuję obsługę OR-WE-504, który jest tańszy od 514. Na samym początku w dokumentacji nieścisłość, w jednym pliku 48008N1 w innym 96008N1. Okazuje się, ze domyślnie 9600. Działa odczyt kilku czy nawet wszystkich rejestrów na raz, co do 514 nie sprawdziłem tego ale producent napisał, że działa tylko to co w programie testowym. Totalna bzdura, bo jest wiele funkcji w instrukcji, oznaczonych RW), których to w programie testowym nie ma. Zwięźle ujmę zebrane fakty, pomoc techniczna w praktyce nie istnieje, ogranicza się do przysyłania plików, które nie zawsze sa zgodne z rzeczywistością.

Zbadałem dokładniej czas odpowiedzi licznika. Wyniki na poniższych ekranach:

825137118_504odpowiedz95ms(16rejestrow).thumb.gif.7206a698cf812c883d9deb2ca6947d57.gif2014717948_504odpowiedz52ms(ID-rejnr15).thumb.gif.27a94bae8112caefd6c58d671fbe95cd.gif383200400_504odpowiedz36ms(1rejestr).thumb.gif.e465212dca16f1d9ffc06110d64a6eeb.gif98479380_504odpowiedz28ms(1rejestr).thumb.gif.fac8b0d2b3b3512b09779d580689c8eb.gif850630688_504odpowiedz28ms(1rejVoltage).thumb.gif.e7f1ee323056ef62bcb2da60ae7041cb.gif1024272980_504odpowiedz101ms(16rejestrow).thumb.gif.65c8d7a3bc8938281044ab518b310780.gif

Jak widać czas odpowiedzi losowy. Może autor programu chciał zbudować generator pseudolosu? Co program robi przez 30 czy ponad 100ms? Naturalnie od producenta nie mozna zdobyć informacji, po jakim czasie licznik musi odpowiedzieć. Ten czas chyba jest długi, bo soft czeka na odpowiedź ok 2 sekund przy czym nie ponawia wysyłania zapytań. Ciekawe te wszystkie niejasności w sytuacji gdy producent chwali się zgodnością z jakimiś tam normami. Nie wiem na czym licznik jest zrobiony ale na 8051 zrobiłbym szybsza odpowiedź.

Na szczęście do własnych zastosowań, nie jest się skazanym na ORNO. 2-3 razy taniej można kupić rozwiązania ze wschodu. Przy jednym liczniku może nie warto (u chińczyka też wsparcia nie ma ale coś za coś) ale przy kilku nabiera to sensu. Może zabiorę się za moduły PZEM. Niestety, nie znalazłem ich w ofercie krajowych sprzedawców, gdy ktokolwiek widział, ktokolwiek wie, proszę o namiary.

Z pewnością powstanie moduł pomiarowy zbudowany na przekładnikach. PCB juz mam, brakuje przekładników i .... chęci do pracy bo inne projekty są "fajniejsze" - większe wyzwania.

 

Udostępnij ten post


Link to post
Share on other sites

Oprogramowanie dla ORNO-504 czytać czyta, zapisy bez powodzenia. Soft nawet timeout nie wysyła a w praktyce nic do VCOM co widzę (nie widzę) na LA2016. Soft do 514 działa poprawnie. Inny problem, który widzę, nie wiem jeszcze czy softu testowego czy licznika, to często zwraca moc (czynna, bierną i pozorną = 0). Po kilku zapytaniach jest ok. Soft nie zwraca błędu wiec muszę zrobic kilka prób.

Nie jestem zadowolony z kompletu licznik 504 + soft testowy + dokumentacja. Jak chce się zaoszczędzić nerwów, lepiej kupić 514.

Wyraźnie widać, że licznik zwraca 0

108248143_504odczyt0ajest11W(testowy).thumb.gif.3cd86b0b7a3e9f2967d3f8006b9ca522.gif575250001_504odczyt0ajest11W(analizator).thumb.gif.eddca0a47a2c17e930f475685845c567.gif

Soft producenta twierdzi, że CRC ok. Po kilku próbach odczyt poprawny. Zauważyłem pewna prawidłowość, jak czytam tą sama grupę rejestrów po kilku odczytach moc = 0, gdy zmienię ową grupę jest ok ale przez chwilę. Gdy czytam pojedynczy rejestr z reguły 0. ORNO 514 w tym czasie wskazuje 12W.

Krótko, szmelc. Nie polecam Orno 504, wątpię abym dalej sie nim zajmował bo to nie ma sensu. Biorąc pod uwagę wcześniejsza współprace z producentem nie liczę na fachowa pomoc raczej na odpowiedź "coś u pana jest źle, proszę szukać błędu".

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

Ja w domu mam 1 ORNO 504, tylko że czytam go za pomocą panelu HMI, ale na przelotce USB<>RS485 też mi to bez problemu działa. Natomiast co do dokumentacji tego licznika i samego przedstawicielstwa ORNO Polska, to jest to istny kabaret, pomyłek, niekompetencji, i "dowcipów". Na pytanie czy mogą mi udostępnić DTRkę do Modbus RTU odpowiedzieli, najpierw, że nie, a po drugim zapytaniu, że to tajemnica handlowa i nie udostępniają takich informacji. Śmiech na sali. Tak naprawdę oni sami nie wiedzą co sprzedają, bo z kolegą drążyliśmy temat także innych ich produktów i wygląda to podobnie. Ogólnie też, nie polecam.
Z 504 na początku też miałem problem, ze źle wyświetlanym prądem poniżej 1A, ale potem to ogarnąłem. Czasami niektóre liczniki mają taka, durną cechę, jak zmienna precyzja wskazań, przesuwa się przecinek w zależności od mierzonej wartości, Jak DTRka o tym nie wspomina, lub jej nie ma, ciężko to wyczaić. Na HMI praktycznie nie do ogarnięcia, bez MAKR.

Co do liczników PZEM, mam 1szt., działa, dokumentacja jest OK (kolega zrobił nawet PL), na HMI luzik 15 minut roboty. Fajną funkcją jest alarm przekroczenia poziomu pobieranej mocy. Ogólnie rzadkość w licznikach. Wada to brak wyświetlacza, jak sobie coś poknocimy, to zostaje partyzantka. Jedynie kolega który kupił więcej sztuk, natknął się na problem, że jak jest więcej niż 2 liczniki na jednej linii RS485 to mu znika komunikacja, ale podejrzewamy konwerter LAN<>RS485, bo na innym modelu nie ma tego problemu?

Wracając jednak do tematu który poruszył tutaj kolega. Czy na ESP8266-01 da się zaimplementować soft który pozwoli z Domioticza odczytywać licznik ORNO 504, a właściwie dowolne urządzenie RS485 z protokołem MODBUS RTU? Na razie nie chciałbym od razu implementować serwera tak jak w przykładzie, bo obawiam się że mnie to przerośnie, na sam początek przygody z ESP..

Edytowano przez BlackJack
  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites
1 godzinę temu, BlackJack napisał:

Ja w domu mam 1 ORNO 504, tylko że czytam go za pomocą panelu HMI, ale na przelotce USB<>RS485 też mi to bez problemu działa

Może trafiłem na wadliwy egzemplarz? Okaże sie po dokładniejszych testach.Sprawdzę przy większych mocach, zweryfikuję, czy gdy mam odczyty 0 to na wyświetlaczu licznika też jest zero. Czas pokaże.

1 godzinę temu, BlackJack napisał:

Czy na ESP8266-01 da się zaimplementować soft który pozwoli z Domioticza odczytywać licznik ORNO 504, a właściwie dowolne urządzenie RS485 z protokołem MODBUS RTU?

Tak to u mnie działa. Odczyt ORNO to funkcja dodatkowa. Jest tylko mały haczyk. Aktualnie soft przyjmując znaki po UART gromadzi je w FIFO, wysyłka przez UDP następuje dopiero po pauzę pomiędzy znakami ponad 5ms. Ogranicza to liczbę danych do 128 bajtów. Naturalnie soft można zmodyfikować aby w przypadku odebrania określonej ilości danych (np 80 czy 100) wysłać je bez względu na czy była pauza 5ms czy nie. Inna opcja to zbierać dane w dodatkowym buforze.Wystarczy trochę ponad 256 bajtów bo modus ma ograniczenie do 254 (jak pamiętam) danych. Do tego adres slave, kod funkcji, liczba danych, 2 bajty crc (pisałem z pamięci), co daje  259 bajtów.

Udostępnij ten post


Link to post
Share on other sites

Skoro już poruszyliśmy temat nieszczęsnego ORNO 504, i MODBUS RTU to u mnie w konfiguracji HMI wygląda to tak.

ORNO504HMI.thumb.jpg.72267cd5914c9291a9e2e7ee78041160.jpg

Musiałem konwertować, zamieniać słowa miejscami przy odczycie danych 32 bitowych. Inaczej liczniki pokarzą bzdury. Ustawienia portu. Timeout - 1,0sek,  Opóźnienie zapytań 50ms, aby licznik miał czas przetrawić dane. Ogólnie liczniki nie należą do urządzeń szybkich co trzeba brać pod uwagę, niekiedy potrzebują 2 sek.I to działa, tak już ponad rok, ale licznik odczytują tylko raz w miesiącu, ostatecznie podpinam się jak chce sprawdzić co się dzieje na linii zasilającej piekarnik i indukcję pod obciążeniem.

Dla PZEMa 016 230V AC - 100A z przekładnikiem, mam zrobione coś takiego. Używam w symulacji ONLIne z oprogramowania HMI (Weintek) przez przelotkę USB<>RS485 która była w zestawie z licznikiem. Jakby taki licznik miał pracować w bardziej rozbudowanej sieci RS485, to niestety zamuli ją do 9600b/s, bo ma stalą szybkość transmisji. W sumie druga jego wada.

PZEM016HMI.thumb.jpg.f4b98d24c3b7d2cebe5673ee5127985c.jpg

Udostępnij ten post


Link to post
Share on other sites
4 minuty temu, BlackJack napisał:

Timeout - 1,0sek,  Opóźnienie zapytań 50ms, aby licznik miał czas przetrawić dane.

Co on tam tak miele? Dane powinny cyklicznie być uaktualniane w RAM. Gdy przychodzi zapytanie po RS485 wysyła się odpowiedź natychmiastowo,można to spokojnie zrobić w przerwaniach.Jak pisałem, na 8051 można to nawet tak zrobić i odpowiedź będzie wysyłana w 1..2ms po otrzymaniu zapytania.W ORNO widać, że jest to zrobione po amatorsku. Licznik dopiero po zapytaniu robi jakieś pomiary (dlaczego nie używa wyników pomiarów, które cyklicznie musi robić aby zliczać zużycie energii?) co trwa ok 30ms,w przypadku pytania o jeden parametr i 90 w przypadku odczytu 16 rejestrów. Zapytania na 99% są sprawdzane w pętli głównej, która może trwać ponad 10ms o czym świadczą różne czasy odpowiedzi na zapytanie wahające się właśnie o owe 10ms.

 

14 minut temu, BlackJack napisał:

ale licznik odczytują tylko raz w miesiącu

Mnie zależało na jak najczęstszym zapisie aby można tworzyć wykresy. Aktualnie robię to co 10 sekund ale docelowo będzie sekunda a może i  częściej jak w ORNO-514 zadziała odczyt kilku rejestrów w jednym zapytaniu.Niestety sam muszę sprawdzić czy to działa, bo producent nie wie! Sam doświadczyłeś wątpliwej pomocy technicznej ja natomiast wątpię w umiejętności programisty piszącego soft na licznik. Gdybym tak napisał soft do SAS, to dodając czasy transmisji danych przez ETH, reakcji programu na serwerze itp, nie byłoby szans wykonać operacji wpłaty i wypłaty, bo taką operację potwierdzało się w max 100ms (ramka operacji, odpowiedź z id, odesłanie id potwierdzającego operację. Nie miałem problemu aby na 8051 odpowiadać w mniej niż 1ms. W innym systemie (SALONET) gdybym odpowiadał po 100ms, to odpytanie 100slave trwałoby 10 sekund! Nawet 30ms, które uzyskał ORNO dałoby 3 sekundy! To było niedopuszczalne, max 1 sekunda, ale 8051 radził sobie z tym.

 

26 minut temu, BlackJack napisał:

Dla PZEMa

Niestety stoję z pracą. Padła mi płyta główna i robię teraz na kompie zapasowym, niestety mocno mnie ogranicza w działaniach więc prace prawie stoją. Oddaje się bardziej rozrywce i głównie oglądam filmy. Może zabiorę się za modyfikację softu tunelu VCOM aby na drugi serial wysyłać w kodach ANSI dane na terminal. Umożliwi to efektowne zaprezentowanie danych na ekranie terminala coś jak

1701606894_EkranTerminalaadrDS18B20.thumb.gif.4b53e3223f9cdefdcfde6e3aa71e1ba0.gif

Czy na wirtualnym LCD

269105352_Term218barw.thumb.gif.91d48777cbc9b5aa1e137b6baf067735.gif

Łatwo nie będzie, bo aby nie "zawieszać" programu, na raz nie mogę wysłać więcej niż 128 bajtów. Bardziej skomplikowaną treść trzeba będzie więc podzielić na kilka fragmentów i kolejne fragmenty wysyłać dopiero, gdy poprzednia zawartość został już wysłana.

Udostępnij ten post


Link to post
Share on other sites

Cóż miałem okazję, pobawić się różnymi licznikami energii, nie tylko elektrycznej, i ogólnie żaden, nie aktualizuje w swojej pamięci wyników częściej niż co 1 sek. A były to często przemysłowe urządzenia za 7-8 tyś zł. Przyczyny są rożne, czasami związane z samym pomiarem, medium które mierzymy itd.

Drugą sprawą jest to, że standardowo MAX czas odpowiedzi na zapytanie MODBUS to ok 100ms, to taki niepisany przyjęty standardowy czas, i jeżeli producent nie podaje inaczej, można go bezpiecznie przyjmować, choć trafiło mi si urządzenie które miało 125ms, ale było to w instrukcji. Trzeba też pamiętać, że liczniki energii, nie są analizatorami parametrów sieci, więc nie robią 100-200K pomiarów na sek. Doszukiwanie się tu jakiś, bugów, w oprogramowaniu, czy innych przyczyn nie ma większego sensu. Po prostu przyjęto takie standardy i tyle.

Ogólnie jakiś czas temu dosyć mocno drążyłem temat liczników energii dla siebie, i moim zdaniem najlepszy, taki zwykły licznik 1-fazowy to SDM-120. Nie kupiłem jeszcze, ale mam zamiar. Ma naprawdę konkretną dokumentację, jako jeden z niewielu, ale też nie robi więcej niż 1 pomiar na sek. Tzn. nie odświeża częściej rejestrów dostępnych przez MODBUS i RS485.

Natomiast z mojego doświadczenia wynika, że nie warto próbować po MODBASie, czytać nie wiadomo ile danych na sek. Wynika to z różnych przyczyn, ale uwierz mi że jeden góra dwa odczyty na sek z licznika energii w zupełności wystarcza do wykresów, i analizy danych. Nie chodzi tu nawet o to czy się da, wycisnąć więcej, bo da się spokojnie (w granicach rozsądku), tylko o to że nie zwróci ci to żadnych użytecznych danych, ze względu na właściwości urządzenia które czytasz.

Udostępnij ten post


Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Gość
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.


×
×
  • Utwórz nowe...