//Z21 LAN and WiFi 
//Kommunikation mit der Z21 Library!
//
//Copyright (c) by Philipp Gahtow, year 2021
/*
 * Changes:
 * - Add change für WLAN für multi Paket - meherer Abfragen vom Client
 * - Add change via LAN für multi Paket
 * - Add store for source IP
 * - Change State Report of Volt, mA and temp
 * - Change Ethernet Big UDP handel at packet length = 0
 * - Update ESP Wifi setup
 * - add new function to request WiFi conf data
 * - remove while in Serial read for WiFi
 */
//----------------------------------------------
#if defined(LAN) || defined(WIFI) || defined(ESP_WIFI)

extern void Z21LANreceive();

// buffers for receiving and sending data
#define Z21_UDP_TX_MAX_SIZE 20  //--> Z21 LocoNet tunnel DATA has 20 Byte!
#define Z21_BIG_UDP_MIN_SIZE 4    //--> smallest packet length in a BIG UDP Packet that we can handle

//----------------------------------------------
#if defined(Z21DISPLAY) && defined(BOOSTER_INT_MAINCURRENT)  
extern void DisplayUpdateRailData(uint16_t inAm, float volt, float temp);
#endif
#if defined(Z21DISPLAY)
  extern boolean DisplayReady;
  extern void DisplayConfigData();
  extern uint8_t DisplayCounter;  //for animation
  extern void DisplayDataTransmission(bool active);
  #if defined(ESP_WIFI)
  uint8_t ESPLastConnetionState;
  #endif
#endif

//----------------------------------------------
//Wenn zwei Kommunikationsschnittstellen aktiv sind wird LAN hinten angestellt:
#if defined(LAN) && defined(WIFI) 
#define Z21_Eth_offset WLANmaxIP   //shift LAN client behind WiFi client
#else
#define Z21_Eth_offset 0    
#endif

//----------------------------------------------
#if defined(BOOSTER_INT_MAINCURRENT)  
float temp = 0;
#ifdef DALLASTEMPSENSE
#define DALLAS_Temp_Int 10000   //Intervall to request the temperatur
unsigned long temp_timer = 0;
#endif
#endif

//----------------------------------------------
#if defined(ESP_WIFI)
/*
IPAddress Ip(192,168,0,111);
IPAddress Gw(192,168,0,111);
IPAddress Sb(255,255,255,0);
*/
//client ssid and pw stored in EEPROM!
String ssid = "";  
String pass = "";
//AP ssid and pw read out from EEPROM:
String ssidAP = "";
String passAP = "";
byte kanalAP = 3;
#endif

//----------------------------------------------
#if defined(LAN) || defined(ESP_WIFI)
typedef struct    //Rückmeldung des Status der Programmierung
{
  byte IP0; //client IP-Adresse
  byte IP1;
  byte IP2;
  byte IP3;
  byte time;  //aktive Zeit
  unsigned int port;    //source Port
} listofIP;
listofIP mem[LANmaxIP];
byte storedIP = 0;  //speicher für IPs
#endif

//----------------------------------------------
unsigned long IPpreviousMillis = 0;       // will store last time of IP decount updated and SystemInfo update

//--------------------------------------------------------------------------------------------
#if defined(WIFI)
byte outData[Z21_UDP_TX_MAX_SIZE];    //store received Serial to send via UDP
byte outDcount = 0;  //position writing out data
byte sendTOO = 0xFF;  //memIP to whom to send the data
byte listofWLANHash[WLANmaxIP+LANmaxIP];

#define WLAN_Config_Size 10         //num of config data that we can request from the ESP Modul
#if defined(Z21DISPLAY)
  extern void DisplayUpdateRailPower(bool clear);
  IPAddress WLANlocalIP;              //0xE4 = Client IP
  String WLANssid = "[not defined]";  //0xE5 = Client SSID Name
  float ESPSwVer = 0.0;               //0xE8 = ESP Sw Version
  uint8_t ESPsoftAPStationNum = 0;    //0xE9 = AP Client Connected
  int WLAN_Signal = 0xFF;             //0xEA = WLAN client Signal - 31 = "not connected"
#endif
//--------------------------------------------------------------------------------------------
#if defined(DEBUG_WLAN_CONFIG) || defined(Z21DATADEBUG)
void WLANConfDebug(uint8_t id, String s) {
  //Report ESP config data: ---------------------------------------------------
  String list[WLAN_Config_Size] = {F("WiFi-AP"),F("SSIP-AP"),F("PASS-AP"),F("CHAN-AP"),F("WiFi-CL"),F("SSIP-CL"),F("PASS-CL"),F("ESP-S88"),F("ESP-Ver"),F("AP-CLNum")};
  //request each singel value:
  if (id < WLAN_Config_Size) {
    Debug.print(list[id]);
    Debug.print(": ");
    Debug.println(s);
  }
}
#endif

//--------------------------------------------------------------------------------------------
//Request init data from ESP Modul
void WLANRequest(uint8_t id) {
  WLAN.write(id);
  delay(5); //wait for answer...
  while (WLAN.available() > 0) {
    Z21LANreceive();
  }
}

//--------------------------------------------------------------------------------------------
//init ESP8266 serial interface
void WLANSetup() {
    WLAN.begin(WIFISerialBaud);  //UDP to Serial Kommunikation
    WLAN.setTimeout(3);   //sets the maximum milliseconds to wait for serial data
    byte b1 = 0;
    #if defined(DEBUG)
    Debug.print(F("WiFi "));
    Debug.print(WIFISerialBaud);
    Debug.print(F("b start.."));
    #endif
    byte timeout = 80;  //try to init at least
    do {
      #if defined(DEBUG)
      Debug.print(".");
      #endif
      WLAN.println(); //let device Flush old Data!
      delay(50);   //wait for ESP8266 to answer a receive!
      while (WLAN.available() > 0) {
         b1 = WLAN.read();
         #if defined(DEBUG)
         if (b1 == 0xFA)
          Debug.print("OK");
         #endif
      }
      timeout--;
    }  
    while (b1 != 0xFA && timeout > 0);
    if (timeout == 0 || b1 != 0xFA) {   //Error - no ESP module found!
      #if defined(DEBUG)
      Debug.println("FAIL");
      #endif
    }
    else {
       //Continue config setting for ESP
      #if defined(DEBUG)
      Debug.println();
      #endif
      //Setting S88:
      #if defined(DEBUG)
      Debug.print(F("Set ESP S88..."));
      #endif
      WLAN.write(0xFE);   //Send start Bit for Initial Settings
      #if defined(S88N)
      WLAN.write(S88Module);  
      #else 
      WLAN.write(0xFE);
      #endif
      delay(5); //wait for answer...
      if (WLAN.available() > 0) {
          Z21LANreceive();
      }
      #if defined(DEBUG_WLAN_CONFIG)  
      //request all values:
      for (byte counter = 0; counter < WLAN_Config_Size; counter++) {
        delay(10); //wait for answer...
        WLANRequest(0xE0 | (counter & 0x0F));
      }
      #else 
      //request only used ones:
      #if defined(Z21DISPLAY)
      #if defined(DEBUG)
      Debug.println(F("Request..."));
      #endif
        WLANRequest(0xE8); //ESP Sw Version
        WLANRequest(0xE5); //Client SSID Name
        WLANRequest(0xE4); //Client IP
      #endif
      WLANRequest(0xEB);    //Read all client Hash
      #endif
    }
}
#endif

//--------------------------------------------------------------------------------------------
#if defined(ESP_WIFI)
/**********************************************************************************/
//s = String that will be saved, 
//laenge = Index to length byte, 
//start = index to the first letter
void EEPROMwrite (String s, uint16_t laenge, uint16_t start) {
  byte len = s.length();
  FIXSTORAGE.FIXMODE(laenge,len);
  for (uint16_t i = start; i < (start+len); i++) {
     FIXSTORAGE.FIXMODE(i,s[i-start]);
  }

  FIXSTORAGE.commit();
}
/**********************************************************************************/
//laenge = Index to length byte, 
//start = index to the first letter
String EEPROMread (uint16_t laenge, uint16_t start) {
  String s = "";
  byte len = FIXSTORAGE.read(laenge);
  if (len < EEStringMaxSize) {
    for (uint16_t i = start; i < (start+len); i++) {
      s += char(FIXSTORAGE.read(i));
    }
  }
  return s;
}
/**********************************************************************************/
boolean tryWifiClient() {
  WiFi.disconnect();
  if ( (ssid.length() > 0) && (pass.length() > 7) ) {
    //check first if we see this SSID
    byte available_networks = WiFi.scanNetworks();
    for (int network = 0; network < available_networks; network++) {
      if (WiFi.SSID(network) == ssid.c_str()) {

        WiFi.begin(ssid.c_str(), pass.c_str());

        return true;
      }
    }
  }
  return false;
}
/**********************************************************************************/
void ESPSetup() {
  #if defined(ESP32) 
  WiFi.mode(WIFI_MODE_APSTA); 
  #else
  WiFi.mode(WIFI_AP_STA);  //AP & client
  #endif

  // read eeprom for ssid and pass:
 
  //--------------WIFI CLIENT---------------
  ssid = EEPROMread(EEssidLength, EEssidBegin);
  pass = EEPROMread(EEpassLength, EEpassBegin);
  //--------------ACCESS POINT------------- 
  ssidAP = EEPROMread(EEssidAPLength, EEssidAPBegin);
  passAP = EEPROMread(EEpassAPLength, EEpassAPBegin);
  
  if ((ssidAP.length() > 32) || (ssidAP.length() == 0) || (passAP.length() > 32) || (passAP.length() == 0) ) { //request is OK?
    #if defined(DEBUG)
    Debug.println(F("Reset to default!"));
    #endif

    ssidAP = SssidAP;
    passAP = SpassAP;
    kanalAP = SkanalAP;
/*
    #if defined(ESP32)
    portMUX_TYPE myMutex = portMUX_INITIALIZER_UNLOCKED;
    portENTER_CRITICAL(&myMutex);
    #endif
*/
    EEPROMwrite (ssidAP, EEssidAPLength, EEssidAPBegin);
    EEPROMwrite (passAP, EEpassAPLength, EEpassAPBegin);
    FIXSTORAGE.FIXMODE(EEkanalAP, kanalAP);

    FIXSTORAGE.commit();
/*
    #if defined(ESP32)  
    portEXIT_CRITICAL(&myMutex);
    #endif  
*/
  }
  else {    
      kanalAP = FIXSTORAGE.read(EEkanalAP);
      if ((kanalAP < 1) || (kanalAP > 13))
        kanalAP = SkanalAP;
  }

  WiFi.softAPConfig(AP_ip, AP_ip, AP_sb);  //set the default IP for Z21
  
  #if defined(ESP32_MCU)
  WiFi.softAP(ssidAP.c_str(), passAP.c_str() );  //Start AcessPoint, ignore Channel parameter!
  #else
  WiFi.softAP(ssidAP.c_str(), passAP.c_str(), kanalAP, false, 8);    //Start AcessPoint with max 8 number of clients
  #endif
  //don't hide SSID and max simultaneous connected stations set to eight!

  #if defined(DEBUG)
    Debug.print(F("AP Name: "));
    Debug.println(ssidAP);
    Debug.print(F("Pw: "));
    Debug.println(passAP);
    Debug.print(F("IP: "));
    Debug.println(WiFi.softAPIP());
  #endif

  #if defined(DEBUG)
    Debug.print("Client: ");
    Debug.println(ssid.c_str());
    Debug.print("PW: ");
    Debug.println(pass.c_str());
  #endif
        
  if (tryWifiClient()) {  //Try to connect to WiFi  
  	#if defined(DEBUG)
    Debug.print(ssid);
    Debug.println(F(" gefunden!"));
    //Debug.print(F("IP: "));
    //Debug.println(WiFi.localIP());
	  #endif
  }
  else {
    //Change WiFi Mode, to remove blocking OTA handel, when not connected:
    WiFi.mode(WIFI_AP);  //AP & client
    #if defined(DEBUG)
    Debug.print(F("Connect fail to "));
    Debug.println(ssid);
    #endif
  }
  
  #if defined(Z21DISPLAY)
  if (Railpower == csNormal) {
    DisplayCounter = 0xFF;
    DisplayConfigData(); //rewrite config data to Display
  }
  #endif  
}
#endif

//-------------------------------------------------------------------------------------------
#if defined(WIFI)
void WLANEvent() {
  //receive WLAN data:
  if (WLAN.available() > 0) {  //Empfang Z21 UDP Packet over Serial from ESP
      outData[outDcount] = WLAN.read(); //read
		  
      if (sendTOO == 0xFF) {
        sendTOO = outData[outDcount];  //Ziel IP/client read out
	    }
      else {
        outDcount++;
		
        if (outData[0] <= outDcount) {  //1. Byte gibt länge der Daten an!
          if (sendTOO <= WLANmaxIP) {     //Ziel in range?
              //read Data:
            #if defined(Z21DATADEBUG)  
            Debug.print(sendTOO);
            Debug.print(F(" Z21 RX: "));
            for (byte i = 0; i < outData[0]; i++) {
              Debug.print(outData[i], HEX);
              Debug.print(" ");
            }
            Debug.println(F("WLAN ok"));  //Accept
            #endif
            if (outData[0] >= Z21_BIG_UDP_MIN_SIZE) { //Check if there is really Data in?
              z21.receive(sendTOO, outData);
            }
            else if (outData[0] == 0x02) {   //length == 2, then ESP report client IP-Hash
              listofWLANHash[sendTOO] = outData[1];
              #if defined(Z21DATADEBUG)
              Debug.print(sendTOO + 1);
              Debug.print(F(" Z21 Client Hash: "));
              Debug.println(outData[1], HEX);
              #endif
            }
            else {
              #if defined(Z21DATADEBUG)
              Debug.println("Length ERROR!");
			  WLAN.println(); //new sync!
              #endif
            }
            outData[1] = sendTOO;  //Store Client that send the DATA
          }
          #if defined(Z21DATADEBUG)
          else {
            Debug.print(F("Z21 EE "));  //Fail
            Debug.println(sendTOO, HEX);
            WLAN.println(); //new sync!
          }
          #endif
          //Reset to read next data:
          outDcount = 0;
          sendTOO = 0xFF;
        } //END of Data received "normal"!
        else if ((outDcount >= Z21_UDP_TX_MAX_SIZE) || (sendTOO == 'E' && outData[0] == 'E') || (outData[outDcount-2] == 'E' && outData[outDcount-1] == 'E')) { 
          #if defined(Z21DATADEBUG)
            Debug.write(sendTOO);  //Fail?
            for (byte i = 0; i < outDcount; i++)
                Debug.write(outData[i]);
            Debug.print(F(" FLUSH "));
            Debug.print(outDcount);
            Debug.println(" Data!");
          #endif
          WLAN.println(); //new sync!
          //WLAN.flush();   //clear
          outDcount = 0;  //reset read buffer
          sendTOO = 0xFF;
        }
        else if ((sendTOO == 'O' && outData[0] == 'K') || (outData[outDcount-2] == 'O' && outData[outDcount-1] == 'K')) {  //keine valied Data!
          #if defined(Z21DATADEBUG)
          Debug.println(F("Z21 OK"));
          #endif
          outDcount = 0;  //reset read buffer
          sendTOO = 0xFF;
        }
		else if (sendTOO > WLANmaxIP) {
		  #if defined(Z21DATADEBUG)
          Debug.print(F("Z21 Error: 0x"));
		  Debug.println(sendTOO, HEX);
          #endif
			outDcount = 0;  //reset read buffer
      sendTOO = 0xFF;
		}
		return;	//just count the Data byte that we had read!
    } // ENDE Z21 Data
     //----------------------------------------------
      /*ESP System Config: 
     ** 0xE0 = AP IP
     ** 0xE1 = AP SSID Name 
     ** 0xE2 = AP Password
     ** 0xE3 = AP Channel
     ** 0xE4 = Client IP
     ** 0xE5 = Client SSID Name
     ** 0xE6 = Client Password
     ** 0xE7 = S88 Module
     ** 0xE8 = ESP Sw Version
     ** 0xE9 = AP Client Connected
     * 0xEA = WLAN RSSI 
     * 0xEB = IP-HASH clients
     * 0xEC = reserved
     * 0xED = reserved
     * 0xEF = reserved
     * 0xFE = Set S88 Module
     * 0xFA = Z21 "OK"! 
     * 0xFB = ERROR "EE" Fail
     */ 
      //receive config of Serial WLAN ESP8266:
     if ((sendTOO & 0xF0) == 0xE0) {
        if ((sendTOO & 0x0F) < WLAN_Config_Size) {
          String data = WLAN.readStringUntil('\n'); 
          #if defined(DEBUG_WLAN_CONFIG) || defined(Z21DATADEBUG)
            WLANConfDebug(sendTOO & 0x0F, data);
          #endif
          #if defined(Z21DISPLAY)  
          switch (sendTOO) {
            case 0xE4: WLANlocalIP.fromString(data);   //Client IP
                       if (Railpower ==csNormal) {
                          DisplayCounter = 0xFF;
                          DisplayConfigData();
                       }
                       break;
            case 0xE5: WLANssid = data;   //Client SSID Name
                       if (Railpower ==csNormal) {
                          DisplayCounter = 0xFF;
                          DisplayConfigData();
                       } 
                       break; 
            case 0xE8: ESPSwVer = data.toFloat(); break; //ESP Sw Version
            case 0xE9: ESPsoftAPStationNum = data.toInt(); //AP Client Connected
          }
          #endif
        }
        else if (sendTOO == 0xEA) {         //WLAN RSSI 
          if (WLAN.available() > 0) {
            #if defined(Z21DISPLAY)
               //WLAN Signal RSSI is reported:
              WLAN_Signal = WLAN.read();
              if ((WLAN_Signal >> 7) == 1) {  // >= 128
                WLAN_Signal = (256 - WLAN_Signal) * -1;  //make it negativ (db)
			  }
              #if defined(Z21SYSTEMDATADEBUG)
                Debug.print(F(" WLAN-Signal: "));
                Debug.println(WLAN_Signal); //read
              #endif
              IPpreviousMillis = 0; //reset counter to update display now!
            #else
              WLAN.read();  //empty buffer, data not in use!
            #endif
          }
        }
        else if (sendTOO == 0xEB) {         //IP-HASH clients
          byte HashData = 0x00;
          byte ClientCount = 0;
          #if defined(Z21DEBUG)  
          Debug.print(F("Report Clients: "));
          #endif    
          while (WLAN.available() > 0) {
            HashData = WLAN.read();
            if (HashData != 0x00) {
              listofWLANHash[ClientCount] = HashData;
              ClientCount++;
              #if defined(Z21DEBUG)  
              Debug.print(ClientCount);
              Debug.print(F(" IPHash: "));
              Debug.print(HashData, HEX);
              Debug.print(F("; "));
              #endif
            }
          }
          #if defined(Z21DEBUG)  
          if (ClientCount == 0)
              Debug.print(F(" none!"));
          Debug.println();
          #endif
        } 
        outDcount = 0;  //reset read buffer
        sendTOO = 0xFF;  
      } //ENDE WLAN Config
      else if (sendTOO == 0xFA) { //Quittung!
        #if defined(Z21DATADEBUG)
        Debug.println(F("Z21 OK"));
        #endif
        outDcount = 0;  //reset read buffer
        sendTOO = 0xFF;
      }
      else if (sendTOO == 0xFB) { //ERROR!
        #if defined(Z21DATADEBUG)
        Debug.println(F("Z21 ERROR"));
        #endif
        outDcount = 0;  //reset read buffer
        sendTOO = 0xFF;
      }
      //Set S88 Module
      else if (sendTOO == 0xFE) {  
         if (WLAN.available() > 0) {
           uint8_t Module = WLAN.read();   
           #if defined(S88N)
            if (Module <= S88MAXMODULE) {  // && sendTOO >= 0
              S88Module = Module;
              #if defined(REPORT)
              Debug.print(F("Set S88 Module: "));
              Debug.println(S88Module);
              #endif
              if (FIXSTORAGE.read(EES88Moduls) != S88Module) {
                FIXSTORAGE.FIXMODE(EES88Moduls, S88Module);
                SetupS88();
                WLANSetup();
              }
            }
            #else
              WLAN.write(0xFE);  
              WLAN.write(0xFE);  //kein S88 aktiv!
            #endif
            outDcount = 0;   //reset read buffer
            sendTOO = 0xFF;
        }
      }
  }  //ENDE if wlan available
  else {
    //no data - stream is over!
    outDcount = 0;   //reset read buffer
    sendTOO = 0xFF;
  }
}
#endif

//--------------------------------------------------------------------------------------------
#if defined(LAN) || defined(ESP_WIFI)
byte Z21addIP (byte ip0, byte ip1, byte ip2, byte ip3, unsigned int port) {
  //suche ob IP schon vorhanden?
  for (byte i = 0; i < storedIP; i++) {
    if (mem[i].IP0 == ip0 && mem[i].IP1 == ip1 && mem[i].IP2 == ip2 && mem[i].IP3 == ip3) {
      mem[i].time = ActTimeIP; //setzte Zeit
      mem[i].port = port; //update port!
      return i+1;
    }
  }
  if (storedIP >= LANmaxIP) {
    for (byte i = 0; i < storedIP; i++) {
      if (mem[i].time == 0) { //Abgelaufende IP, dort eintragen!
         mem[i].IP0 = ip0;
         mem[i].IP1 = ip1;
         mem[i].IP2 = ip2;
         mem[i].IP3 = ip3;
         mem[i].port = port;
         mem[i].time = ActTimeIP; //setzte Zeit
         return i+1;
      }
    }
    return 0; //keine freien IPs (never reach here!)
  }
  mem[storedIP].IP0 = ip0;
  mem[storedIP].IP1 = ip1;
  mem[storedIP].IP2 = ip2;
  mem[storedIP].IP3 = ip3;
  mem[storedIP].port = port;
  mem[storedIP].time = ActTimeIP; //setzte Zeit
  storedIP++;
  return storedIP;  
}
#endif

//--------------------------------------------------------------------------------------------
void Z21LANreceive() {
  //-------------------------------------------------------------------------------------------
  //receive LAN data or we run on ESP MCU:
  #if defined(LAN) || defined(ESP_WIFI)
  byte packetSize = Udp.parsePacket();
  if(packetSize) {  //packetSize
    IPAddress remote = Udp.remoteIP();
    byte packetBuffer[packetSize];
    Udp.read(packetBuffer,packetSize);  // read the packet into packetBufffer
    if (packetSize == packetBuffer[0]) { //normal:
      #if defined(Z21DATADEBUG)
      Debug.print(Z21addIP(remote[0], remote[1], remote[2], remote[3], Udp.remotePort()) + Z21_Eth_offset);
      Debug.print(" Z21 RX: ");
      for (byte i = 0; i < packetBuffer[0]; i++) {
        Debug.print(packetBuffer[i], HEX);
        Debug.print(" ");
      }
      Debug.println();
      #endif
      z21.receive(Z21addIP(remote[0], remote[1], remote[2], remote[3], Udp.remotePort()) + Z21_Eth_offset, packetBuffer);
    }
    else {  //kombiniertes UDP Paket:
      #if defined(Z21DATADEBUG)
      Debug.print(packetSize);
      Debug.print(F(" BIG Z21 RX: "));
      for (byte i = 0; i < packetSize; i++) {
        Debug.print(packetBuffer[i], HEX);
        Debug.print(" ");
      }
      Debug.println();
      #endif
      
      byte readpos = 0;   //data length position des aktuellen Paket
      //durchlaufe alle Pakete:
      do {    
        //check if packet length is not empty and okay?
        if (packetBuffer[readpos] >= Z21_BIG_UDP_MIN_SIZE) {
          byte pBuffer[packetBuffer[readpos]];   //make a array of packet length
          for (byte i = 0; i < packetBuffer[readpos]; i++)    //fill up array with packet
            pBuffer[i] = packetBuffer[readpos + i];
          #if defined(Z21DATADEBUG)
          Debug.print("#-- "); //Big UDP = in one Packet!
          Debug.print(Z21addIP(remote[0], remote[1], remote[2], remote[3], Udp.remotePort()) + Z21_Eth_offset);
          Debug.print(F(" Z21 RX: "));
          for (byte i = 0; i < pBuffer[0]; i++) {
            Debug.print(pBuffer[i], HEX);
            Debug.print(" ");
          }
          Debug.println();
          #endif
          z21.receive(Z21addIP(remote[0], remote[1], remote[2], remote[3], Udp.remotePort()) + Z21_Eth_offset, pBuffer);
          readpos = packetBuffer[readpos] + readpos;  //bestimme position des nächsten Paket
          }
        else {
          #if defined(Z21DATADEBUG)
          Debug.print("Length ERROR! ");
          Debug.println(packetBuffer[readpos]);
          #endif
          break; //Stop here!
        }
      }
      while(readpos < packetSize);
    }
  }
  #endif
  
  #if defined(WIFI)
  WLANEvent();
  #endif	

  //----------------------------------------------
  //Nutzungszeit IP's bestimmen und update SystemInfo
  unsigned long currentMillis = millis();
  if((currentMillis - IPpreviousMillis) >= IPinterval) {
    IPpreviousMillis = currentMillis;   
    #if defined(LAN) || defined(ESP_WIFI)
    for (byte i = 0; i < storedIP; i++) {
        if (mem[i].time > 0) {
          mem[i].time--;    //Zeit herrunterrechnen
          #if defined(Z21DATADEBUG)
          if (mem[i].time == 0) {
            Debug.print(i + Z21_Eth_offset + 1);
            Debug.println(F(" LAN client timed out"));
          }
          #endif
        }
    }
    #endif
    //interval:
    #if defined(BOOSTER_INT_MAINCURRENT)
    notifyz21getSystemInfo(0); //SysInfo an alle BC Clients senden!
    #endif
  }
  //----------------------------------------------
  #if defined(Z21DISPLAY) && defined(ESP_WIFI)
  if (ESPLastConnetionState != WiFi.status()) {
    ESPLastConnetionState = WiFi.status();
    if (Railpower ==csNormal) { //Update data on Display
      DisplayCounter = 0xFF;
      DisplayConfigData();
    } 
  }
  #endif
  //----------------------------------------------
}

//--------------------------------------------------------------------------------------------
#if defined(S88N)
//Ask for Feedback data of group index:
void notifyz21S88Data(uint8_t gIndex) {
  //S88sendon = 'i';
  reportS88Data(gIndex);
}
#endif

//--------------------------------------------------------------------------------------------
void notifyz21RailPower(uint8_t State)
{
  if (Railpower != State) {
    #if defined(Z21DEBUG)  
    Debug.print(F("z21 "));
    #endif          

    globalPower(State);
  }
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoState(uint16_t Adr, uint8_t data[])
{
  #if defined(DCC) 
  AllLocoData(Adr, data);
  #endif

  #if defined(LOCONET) && !defined(LnSLOTSRV)
  LNGetSetLocoSlot(Adr, true);  //send status to LocoNet
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoFkt(uint16_t Adr, uint8_t state, uint8_t fkt)
{
  #if defined(DCC) 
  dcc.setLocoFunc(Adr, state, fkt); 
  #endif
  
  #if defined(LOCONET) && defined(DCC) 
  if (fkt <= 4) {
    byte DIRF = dcc.getFunktion0to4(Adr) | (dcc.getLocoDir(Adr) << 5);
    sendLNDIRF(Adr,  DIRF);
  }
  else if (fkt >= 5 && fkt <= 8)
    sendLNSND(Adr, dcc.getFunktion5to8(Adr));
  else if (fkt >= 9 && fkt <= 12)
    sendLNF3 (Adr, dcc.getFunktion9to12(Adr)); 
  else if (fkt >= 13 && fkt <= 20)
    sendLNF4 (Adr, dcc.getFunktion13to20(Adr));   
  else if (fkt >= 21 && fkt <= 28)
    sendLNF5 (Adr, dcc.getFunktion21to28(Adr));   
  #endif

  #if defined(XPRESSNET) && defined(DCC) 
  if (fkt <= 4) 
    XpressNet.setFunc0to4(Adr, dcc.getFunktion0to4(Adr));
  else if (fkt >= 5 && fkt <= 8) 
    XpressNet.setFunc5to8(Adr, dcc.getFunktion5to8(Adr));
  else if (fkt >= 9 && fkt <= 12)
    XpressNet.setFunc9to12(Adr, dcc.getFunktion9to12(Adr));
  else if (fkt >= 13 && fkt <= 20)
    XpressNet.setFunc13to20(Adr, dcc.getFunktion13to20(Adr));
  else if (fkt >= 21 && fkt <= 28)
    XpressNet.setFunc21to28(Adr, dcc.getFunktion21to28(Adr));
  XpressNet.ReqLocoBusy(Adr);   //Lok wird nicht von LokMaus gesteuert!  
  #endif
  
  #if defined(Z21DEBUG) && defined(DCC) 
  Debug.print("Z21 A:");
  Debug.print(Adr);
  Debug.print(" Dir: ");
  Debug.print(dcc.getLocoDir(Adr), BIN);
  if (fkt <= 4) {
    Debug.print(", F1:");
    Debug.println(dcc.getFunktion0to4(Adr), BIN);
  }  
  else if (fkt >= 5 && fkt <= 8) {
    Debug.print(", F2:");
    Debug.println(dcc.getFunktion5to8(Adr), BIN);
  }
  else if (fkt >= 9 && fkt <= 12) {
    Debug.print(", F3:");
    Debug.println(dcc.getFunktion9to12(Adr), BIN);
  }
  else if (fkt >= 13 && fkt <= 20) {
    Debug.print(", F4:");
    Debug.println(dcc.getFunktion13to20(Adr), BIN);
  }
  else if (fkt >= 21 && fkt <= 28) {
    Debug.print(", F5:");
    Debug.println(dcc.getFunktion21to28(Adr), BIN);
  }
  else if (fkt >= 29 && fkt <= 31) {
    Debug.print(", F6:");
    Debug.println(dcc.getFunktion29to31(Adr), BIN);
  }
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoFkt0to4(uint16_t Adr, uint8_t fkt)
{
  #if defined(DCC) 
  dcc.setFunctions0to4(Adr, fkt); 
  #endif
  
  #if defined(LOCONET) && defined(DCC) 
  sendLNDIRF(Adr,  fkt | (dcc.getLocoDir(Adr) << 5));
  #endif

  #if defined(XPRESSNET)
  XpressNet.setFunc0to4(Adr, fkt);
  XpressNet.ReqLocoBusy(Adr);   //Lok wird nicht von LokMaus gesteuert!  
  #endif

  #if defined(Z21DEBUG) && defined(DCC) 
  Debug.print("Z21 A:");
  Debug.print(Adr);
  Debug.print(", F0-4:");
  Debug.println(dcc.getFunktion0to4(Adr), BIN);
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoFkt5to8(uint16_t Adr, uint8_t fkt)
{
  #if defined(DCC) 
  dcc.setFunctions5to8(Adr, fkt); 
  #endif
  
  #if defined(LOCONET)
  sendLNSND(Adr, fkt);
  #endif

  #if defined(XPRESSNET)
  XpressNet.setFunc5to8(Adr, fkt);
  XpressNet.ReqLocoBusy(Adr);   //Lok wird nicht von LokMaus gesteuert!  
  #endif

  #if defined(Z21DEBUG) && defined(DCC) 
  Debug.print("Z21 A:");
  Debug.print(Adr);
  Debug.print(", F5-8:");
  Debug.println(dcc.getFunktion5to8(Adr), BIN);
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoFkt9to12(uint16_t Adr, uint8_t fkt)
{
  #if defined(DCC) 
  dcc.setFunctions9to12(Adr, fkt); 
  #endif
  
  #if defined(LOCONET)
  sendLNF3 (Adr, fkt); 
  #endif

  #if defined(XPRESSNET)
  XpressNet.setFunc9to12(Adr, fkt);
  XpressNet.ReqLocoBusy(Adr);   //Lok wird nicht von LokMaus gesteuert!  
  #endif

  #if defined(Z21DEBUG) && defined(DCC) 
  Debug.print("Z21 A:");
  Debug.print(Adr);
  Debug.print(", F9-12:");
  Debug.println(dcc.getFunktion9to12(Adr), BIN);
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoFkt13to20(uint16_t Adr, uint8_t fkt)
{
  #if defined(DCC) 
  dcc.setFunctions13to20(Adr, fkt); 
  #endif

  #if defined(LOCONET)
  sendLNF4 (Adr, fkt);   
  #endif

  #if defined(XPRESSNET)
  XpressNet.setFunc13to20(Adr, fkt);
  XpressNet.ReqLocoBusy(Adr);   //Lok wird nicht von LokMaus gesteuert!  
  #endif

  #if defined(Z21DEBUG) && defined(DCC) 
  Debug.print("Z21 A:");
  Debug.print(Adr);
  Debug.print(", F13-20:");
  Debug.println(dcc.getFunktion13to20(Adr), BIN);
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoFkt21to28(uint16_t Adr, uint8_t fkt)
{
  #if defined(DCC) 
  dcc.setFunctions21to28(Adr, fkt); 
  #endif

  #if defined(LOCONET)
  sendLNF5 (Adr, fkt);   
  #endif

  #if defined(XPRESSNET)
  XpressNet.setFunc21to28(Adr, fkt);
  XpressNet.ReqLocoBusy(Adr);   //Lok wird nicht von LokMaus gesteuert!  
  #endif

  #if defined(Z21DEBUG) && defined(DCC) 
  Debug.print("Z21 A:");
  Debug.print(Adr);
  Debug.print(", F21-28:");
  Debug.println(dcc.getFunktion21to28(Adr), BIN);
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoFkt29to36(uint16_t Adr, uint8_t fkt)
{
  #if defined(DCC) 
  dcc.setFunctions29to36(Adr, fkt); 
  #endif

  #if defined(Z21DEBUG) 
  Debug.print("Z21 A:");
  Debug.print(Adr);
  Debug.print(", F29-36:");
  Debug.println(fkt, BIN);
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoFkt37to44(uint16_t Adr, uint8_t fkt) 
{
  #if defined(DCC) 
  dcc.setFunctions37to44(Adr, fkt); 
  #endif

  #if defined(Z21DEBUG) 
  Debug.print("Z21 A:");
  Debug.print(Adr);
  Debug.print(", F37-44:");
  Debug.println(fkt, BIN);
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoFkt45to52(uint16_t Adr, uint8_t fkt)
{
  #if defined(DCC) 
  dcc.setFunctions45to52(Adr, fkt); 
  #endif

  #if defined(Z21DEBUG) 
  Debug.print("Z21 A:");
  Debug.print(Adr);
  Debug.print(", F45-52:");
  Debug.println(fkt, BIN);
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoFkt53to60(uint16_t Adr, uint8_t fkt) 
{
  #if defined(DCC) 
  dcc.setFunctions53to60(Adr, fkt); 
  #endif

  #if defined(Z21DEBUG) 
  Debug.print("Z21 A:");
  Debug.print(Adr);
  Debug.print(", F53-60:");
  Debug.println(fkt, BIN);
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoFkt61to68(uint16_t Adr, uint8_t fkt)
{
  #if defined(DCC) 
  dcc.setFunctions61to68(Adr, fkt); 
  #endif

  #if defined(Z21DEBUG) 
  Debug.print("Z21 A:");
  Debug.print(Adr);
  Debug.print(", F61-68:");
  Debug.println(fkt, BIN);
  #endif
}

//--------------------------------------------------------------------------------------------
//Binärzustandsadressen von 29 bis 32767
void notifyz21LocoFktExt(uint16_t Adr, uint8_t low, uint8_t high)
{
  #if defined(DCC) 
  dcc.setLocoFuncBinary(Adr, low, high); 
  #endif

  #if defined(Z21DEBUG) 
  Debug.print("Z21 A:");
  Debug.print(Adr);
  Debug.print(", F");
  Debug.print((high << 7) + (low & 0x7F));
  Debug.print(": ");
  Debug.println(low >> 7);  //Zustand
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21LocoSpeed(uint16_t Adr, uint8_t speed, uint8_t steps)
{
  if (Adr == 0) {
    #if defined(Z21DEBUG)
    Debug.print(F("Z21 A:"));
    Debug.print(Adr);
    Debug.println(F(" ERROR not a loco!"));
    #endif
    return;
  }
  #if defined(LOCONET)
  switch (steps) {
    case 14: sendLNSPD(Adr, map(speed, 0, 14, 0, 128)); break;
    case 28: sendLNSPD(Adr, map(speed, 0, 28, 0, 128)); break;
    default: sendLNSPD(Adr, speed); 
  }
  #endif
  #if defined(XPRESSNET)
  XpressNet.setSpeed(Adr, steps, speed);
  XpressNet.ReqLocoBusy(Adr);   //Lok wird nicht von LokMaus gesteuert!
  #endif
  switch (steps) {
    case 14: dcc.setSpeed14(Adr, speed); break;
    case 28: dcc.setSpeed28(Adr, speed); break;
    default: dcc.setSpeed128(Adr, speed); 
  }
  #if defined(Z21DEBUG)
  Debug.print(F("Z21 A:"));
  Debug.print(Adr);
  Debug.print(", S");
  Debug.print(steps);
  Debug.print(":");
  Debug.println(speed, BIN);
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21Accessory(uint16_t Adr, bool state, bool active)
{
  #if defined(DCC) 
  dcc.setBasicAccessoryPos(Adr, state, active);
  #endif
}

//--------------------------------------------------------------------------------------------
uint8_t notifyz21AccessoryInfo(uint16_t Adr)
//return state of the Address (left/right = true/false)
{
  #if defined(DCC) 
    #if defined(Z21DEBUG)
    Debug.print("Z21 GetAccInfo: ");
    Debug.print(Adr);
    Debug.print("-");
    Debug.println(dcc.getBasicAccessoryInfo(Adr));
    #endif
    
    return dcc.getBasicAccessoryInfo(Adr);
  #else
    return 0x00;
  #endif    
}

//--------------------------------------------------------------------------------------------
void notifyz21ExtAccessory(uint16_t Adr, byte state)
{
  #if defined(DCC) 
  dcc.setExtAccessoryPos(Adr, state);
  #endif
  
  #if defined(Z21DEBUG)
  Debug.print("Z21 SetExtAcc: ");
  Debug.print(Adr);
  Debug.print("-");
  Debug.println(state);
  #endif
}

#if defined(LOCONET)
//--------------------------------------------------------------------------------------------
uint8_t notifyz21LNdispatch(uint16_t Adr)
//return the Slot that was dispatched, 0xFF at error!
{
  #if defined(Z21DEBUG)
  Debug.print("Z21 LNdispatch: ");
  Debug.println(Adr);
  #endif
  return LNdispatch(Adr);
}
//--------------------------------------------------------------------------------------------
void notifyz21LNSendPacket(uint8_t *data, uint8_t length)
{
  #if defined(LnDEB)
    Debug.print(F("LOCONET_FROM_LAN ")); 
  #endif
  LNSendPacket (data, length, Z21bcLocoNet_s, false);   //Report nicht an Z21 Library!
  LNdecode(false); //verarbeite empfangene Daten, aber nicht als Z21 RX! 
}

//--------------------------------------------------------------------------------------------
void notifyz21LNdetector(uint8_t client, uint8_t typ, uint16_t Adr) {
  #if defined(Z21DEBUG)
  Debug.print(F("LAN_LOCONET_DETECTOR 0x")); 
  Debug.print(Adr);
  Debug.print("-");
  Debug.print(typ, HEX);
  #endif
  if (typ == 0x80) { //SIC Abfrage
    byte data[4];
    data[0] = 0x01; //Typ
    data[1] = Adr & 0xFF;
    data[2] = Adr >> 8;
    data[3] = dcc.getBasicAccessoryInfo(Adr); //Zustand Rückmelder
    #if defined(Z21DEBUG)
    Debug.print(F(" State: "));
    Debug.println(data[3], BIN);
    #endif
    z21.setLNDetector(client, data, 4);
  }
  #if defined(Z21DEBUG)
  else Debug.println();
  #endif
}

#endif  //LOCONET

/*
//--------------------------------------------------------------------------------------------
void notifyz21CANdetector(uint8_t client, uint8_t typ, uint16_t ID) {
  #if defined(Z21DEBUG)
  Debug.print(F("CAN_DETECTOR ")); 
  Debug.print(typ);
  Debug.print("=");
  Debug.println(ID);
  #endif
}

//z21.setCANDetector(uint16_t NID, uint16_t Adr, uint8_t port, uint8_t typ, uint16_t v1, uint16_t v2);
*/

//--------------------------------------------------------------------------------------------
//read out the current of the booster main
void notifyz21getSystemInfo(uint8_t client) {


#if defined(BOOSTER_INT_MAINCURRENT)  
  uint16_t inAm = getRailmA(); 
  uint16_t volt = getRailVolt();

 
  #ifdef DALLASTEMPSENSE
    if((millis() - temp_timer) >= DALLAS_Temp_Int) {  //á 10 sec.
      temp = sensors.getTempCByIndex(0);  
      sensors.requestTemperatures(); // Send the command to get temperatures
      temp_timer = millis();
    }
    
  #elif defined(MEGA_MCU)
    temp = analogRead(TempPin);
    #if defined(AREF_1V1)
    temp = (1024-temp) / (temp / 34.5);
    #else
    temp = (1024-temp) / (temp / 11.5);
    #endif
  #endif
  
  #if defined(Z21SYSTEMDATADEBUG)
    Debug.print("mA: ");
    Debug.print(inAm);
    Debug.print("(");
    Debug.print(VAmpINT); //Volt on VAmpIntPin
    Debug.print(")_V");
    
    #if defined(VoltIntPin) //ESP8266 has only one ADC for current sense!
      Debug.print(analogRead(VoltIntPin));
      Debug.print(":");
    #endif
    Debug.print(volt);  //Rail Voltage: Rail:100k - Sense - 4,7k - GND
    Debug.print("_T:");
    Debug.println(temp);
  #endif

  #if defined(Z21DISPLAY)
    DisplayUpdateRailData(inAm,volt,temp);
  #endif
  
  z21.sendSystemInfo(client, inAm, volt, temp);  //report System State to z21 client
  //(12-22V): 20V=0x4e20, 21V=0x5208, 22V=0x55F0
#else
  z21.sendSystemInfo(client, 0, 0, 0);  //report zero System State to z21 client
#endif  
}


//--------------------------------------------------------------------------------------------
void notifyz21CVREAD(uint8_t cvAdrMSB, uint8_t cvAdrLSB)
{
  unsigned int cvAdr = cvAdrMSB << 8 | cvAdrLSB;
   #if defined(Z21DEBUG)
      Debug.print(F("Z21 read CV#"));
      Debug.println(cvAdr+1);
    #endif

  #if defined(DCC)  
  dcc.opsReadDirectCV(cvAdr);  //read cv
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21CVWRITE(uint8_t cvAdrMSB, uint8_t cvAdrLSB, uint8_t value)
{
  unsigned int cvAdr = cvAdrMSB << 8 | cvAdrLSB;
   #if defined(Z21DEBUG)
      Debug.print(F("Z21 set CV#"));
      Debug.print(cvAdr+1);
      Debug.print(" - ");
      Debug.println(value);
    #endif

   #if defined(DCC) 
   dcc.opsProgDirectCV(cvAdr,value);  //return value from DCC via 'notifyCVVerify'
   #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21CVPOMWRITEBYTE(uint16_t Adr, uint16_t cvAdr, uint8_t value)
{
  #if defined(RCDEB)
  Debug.print(F("Z21 POM Byte A"));
  Debug.print(Adr);
  Debug.print(F(" set CV#"));
  Debug.print(cvAdr+1);
  Debug.print(" - ");
  Debug.println(value);
  #endif
  
  #if defined(DCC)
  dcc.opsProgramCV(Adr, cvAdr, value);  //set decoder byte
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21CVPOMWRITEBIT(uint16_t Adr, uint16_t cvAdr, uint8_t value)
{
  #if defined(RCDEB)
  Debug.print(F("Z21 POM Bit A"));
  Debug.print(Adr);
  Debug.print(F(" set CV#"));
  Debug.print(cvAdr+1);
  Debug.print(" - ");
  Debug.println(value);
  #endif
  
  #if defined(DCC)
  dcc.opsPOMwriteBit(Adr, cvAdr, value);  //set decoder bit
  #endif
}

//--------------------------------------------------------------------------------------------
void notifyz21CVPOMREADBYTE (uint16_t Adr, uint16_t cvAdr)
{
  #if defined(DCCGLOBALDETECTOR) && defined(DCC)

    #if defined(RCDEB)
    Debug.print(F("Z21 POM A"));
    Debug.print(Adr);
    Debug.print(" read CV#");
    Debug.println(cvAdr+1);
    #endif
  
    RailcomCVAdr = cvAdr;
      
    #if defined(MEGA_MCU)
    RailComStart(); //Start Reading Data!
    #endif

  #else
    z21.setCVNack();  //Antwort: LAN_X_CV_NACK
  #endif

  #if defined(DCC)
    dcc.opsPOMreadCV(Adr, cvAdr);  //get decoder value
  #endif
}

//--------------------------------------------------------------------------------------------
//Information to DCC Libray via EEPROM (50 to 75) over RailCom, ProgModus, etc.
void notifyz21UpdateConf() {
  FIXSTORAGE.FIXMODE(EEPROMDCCPROPERTY, (FIXSTORAGE.read(EEPROMDCCPROPERTY) & 0xFC) | 0x02);   //Ausgabeformat nur DCC (Ausgabeformat (0x00) DCC+MM, (0x02) nur DCC, (0x03) nur MM)
  #if defined(ESP_WIFI)
  FIXSTORAGE.commit();
  #endif
  
  #if defined(Z21DEBUG)
  Debug.println("Z21 Conf: ");
  Debug.print(F("RailCom: ")); //RailCom: 0=aus/off, 1=ein/on
  Debug.print(FIXSTORAGE.read(EEPROMRailCom), HEX);
  Debug.print(F(", PWR-B: ")); //Power-Button: 0=Gleisspannung aus, 1=Nothalt  
  Debug.print(FIXSTORAGE.read(52), HEX);
  Debug.print(F(", Prog-R: ")); //Auslese-Modus: 0=Nichts, 1=Bit, 2=Byte, 3=Beides
  Debug.print(FIXSTORAGE.read(EEPROMProgReadMode), HEX);
  Debug.print(F(", RstP(s): "));
  Debug.print(FIXSTORAGE.read(EEPROMRSTsRepeat));
  Debug.print(F(", RstP(f): "));
  Debug.print(FIXSTORAGE.read(EEPROMRSTcRepeat));
  Debug.print(F(", ProgP: "));
  Debug.print(FIXSTORAGE.read(EEPROMProgRepeat));
  Debug.print(F(", MainV: "));
  Debug.print(word(FIXSTORAGE.read(73),FIXSTORAGE.read(72)));
  Debug.print(F(", ProgV: "));
  Debug.println(word(FIXSTORAGE.read(75),FIXSTORAGE.read(74)));
  #endif

  #if defined(DCC)
  dcc.loadEEPROMconfig(); //Update values!
  #endif
}

//--------------------------------------------------------------------------------------------
//Information Abfrage Z21 Library
uint8_t notifyz21ClientHash(uint8_t client) {

  byte HashIP = 0x00;
  
  #if defined(LAN) || defined(ESP_WIFI)
  if (client < Z21_Eth_offset) {  //Prüfe über Offset ob WiFi or LAN
  #endif  
    #if defined(WIFI)
    //get Client IP-Hash from WiFi
    HashIP = listofWLANHash[client - 1];
    #endif
  #if defined(LAN) || defined(ESP_WIFI)  
  }
  else {
    //get Client IP-Hash from Ethernet Interface
    byte cl = client - Z21_Eth_offset - 1;  //senden ohne Offset!  
    HashIP = mem[cl].IP0 ^ mem[cl].IP1 ^ mem[cl].IP2 ^ mem[cl].IP3; //make Hash from IP
  }
  #endif
  
  #if defined(Z21DEBUG)
  Debug.print(client);
  Debug.print(F(" IPHash: "));
  Debug.print(HashIP, HEX);
  Debug.print(F(" BC: "));
  Debug.println(FIXSTORAGE.read(0x200 | HashIP), BIN);
  #endif

  return HashIP;  //not found!
}

//--------------------------------------------------------------------------------------------
//bug fix for enc28 Ethernet shield with UIP Library - need to do this after each packet!
#if defined (LAN) && defined (ENC28)
void restartENC28() {
  Udp.stop(); // added for enc28
  #if defined(Z21SYSTEMDATADEBUG)
    Debug.print("restart connection: ");// added for enc28
    Debug.println(Udp.begin(z21Port) ? "success" : "failed");// added for enc28
  #else
    Udp.begin(z21Port);
  #endif
}
#endif

//--------------------------------------------------------------------------------------------
void notifyz21EthSend(uint8_t client, uint8_t *data) 
{
  #if defined(Z21DATADEBUG)
  Debug.print(client);
  Debug.print(F(" Z21 TX: "));
  for (byte i = 0; i < data[0]; i++) {
    Debug.print(data[i], HEX);
    Debug.print(" ");
  }
  Debug.println();
  #endif
  if (client == 0) { //all stored 
    #if defined(LAN) || defined(ESP_WIFI)
    for (byte i = 0; i < storedIP; i++) {
      if (mem[i].time > 0) {  //noch aktiv?
        IPAddress ip(mem[i].IP0, mem[i].IP1, mem[i].IP2, mem[i].IP3);
        Udp.beginPacket(ip, mem[i].port);    //Broadcast
        Udp.write(data, data[0]);
        Udp.endPacket();
        #if defined (LAN) && defined (ENC28)
        restartENC28(); //fix bug!
        #endif
      }
    }
    #endif
    
    #if defined(WIFI)
    WLAN.write(client);
    WLAN.write(data, data[0]);
    #endif
  }
  else {
    #if defined(LAN) || defined(ESP_WIFI)
    if (client < Z21_Eth_offset) {  //Prüfe über Offset ob WiFi or LAN
    #endif  
      #if defined(WIFI)
      WLAN.write(client);
      WLAN.write(data, data[0]);
      #endif
    #if defined(LAN) || defined(ESP_WIFI)
    }
    else {
      byte cl = client - Z21_Eth_offset - 1;  //senden ohne Offset!
      IPAddress ip(mem[cl].IP0, mem[cl].IP1, mem[cl].IP2, mem[cl].IP3);
      Udp.beginPacket(ip, mem[cl].port);    //no Broadcast
      Udp.write(data, data[0]);
      Udp.endPacket();
    }
    #endif
  }
  #if defined (LAN) && defined (ENC28)
  restartENC28();  //fix bug!
  #endif
}

//---------------------------------
#endif