RS485 przez siec Wi-FI

Miejsce dobre do dyskusji nad własnymi projektami - pochwal się wszystkim co samodzielnie stworzyłeś.
ODPOWIEDZ
es2
-
Posty: 70
Rejestracja: 13 mar 2018, 9:47

RS485 przez siec Wi-FI

Post autor: es2 » 20 paź 2019, 17:52

Rozwiązań wirtualnych portów COM (RS232C, UART) używających sieci Wi-Fi można zleźć w internecie wiele. Ich jakość jest lepsza lub gorsza. Z RS485 jest już dużo gorzej bo konieczne jest przełączanie kierunku transmisji. Dobrych rozwiązań nie znalazłem, aby zaoszczędzić czas przeznaczony na szukanie, analizę kodu i testy postanowiłem program napisać samemu. Posiłkowałem się rozwiązaniem znalezionym w internecie, z oryginału pozostało niewiele a kod nie był napisany zbyt dobrze dlatego nie ma sensu podawanie źródła, poza tym autor w kodzie nie chwalił się informacjami o sobie. Kod

Kod: Zaznacz cały

#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   "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, timeout485rx;
bool rdONROfull;
int8_t static pytanie = -1;


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</H3><P>" );
  sprintf( txt, "SysTick: %d sekund<P>", millis() / 1000 ); strcat( buf, txt );

#define TIM_OFLINE_ORNO   15000UL
  if ( millis() < timeout485rx + TIM_OFLINE_ORNO ) {  // Timeout 15 sekund
    if ( rdONROfull ) {
      sprintf( txt, "<font color='GREEN'><B> ON-Line </B> Read record %d", pytanie ); strcat( buf, txt );
    }
    else {
      sprintf( txt, "<font color='ORANGE'><B> ON-Line </B> Read record %d", pytanie ); strcat( buf, txt );
    }
  }
  else {
    rdONROfull = false;
    int tim = millis() - ( timeout485rx + TIM_OFLINE_ORNO);
    if ( tim < 0 ) tim = 0;
    sprintf( txt, "<font color='RED'> <B>OFF-Line </B> Time %d", tim / 1000 ); strcat( buf, txt );
  }
  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 + 1847900; // 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() {
  ESP.wdtEnable(1000);

  
  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() + 1 * 60 * 60 * 1000; // 24h   //CzasDoResetu = millis() + 24 * 60 * 60 * 1000; // 24h


  ESP.wdtEnable(100);
}


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

  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
    Serial.print(".");
  }
  
  Serial.print("\n\rPolaczony z siecia '");
  Serial.print( WIFI_SSID );
  Serial.print("' IP ");
  Serial.print(WiFi.localIP());
  Serial.print(". Tryb UART BRIDGE ");
  
  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);
  Serial.println();
}


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

  
  if ( millis() > CzasDoResetu ) {
    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 -----//
#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 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);
      }
    }
  }


}
działa, co można zobaczyć klikając na http://es2.noip.pl:83/, ale wymaga dopracowania i rozbudowy. Strona sprzetowa jest banalna
Obrazek
Jak podłączyć RX i TX do MAX485 czy lepiej MAX3458 albo do odpowiednika nie trzeba się rozpisywać. Linia D2 płytki Wemos steruje wyprowadzeniami 2 i 3 drivera (wyprowadzenia DE i /RE). Linie A i B warto spolaryzować.

ODPOWIEDZ