/*
    busware.de - Network Module Connector NMC

    To set WiFi credentials use: i.e. "esptouch" on yr mobile - see Appstore

    USB @ 115200 fixed baudrate
    all interfaces @ DHCP

    Press button:

    >1 sec to Bootload PIM
    >5 sec to delete WiFi credentials
*/

#include <ETH.h>
#include <EEPROM.h>
#include <WiFi.h>
#include <Wire.h>
#include <HardwareSerial.h>

#define PIM_MODULE "RS485"

HardwareSerial Serial2(2);

/* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */
char ssid[32] = "";
char password[32] = "";

/* 
  ETHERNET PHY CONFIG
*/
#define ETH_CLK_MODE    ETH_CLOCK_GPIO17_OUT
#define ETH_POWER_PIN   23
#define ETH_TYPE        ETH_PHY_LAN8720
#define ETH_ADDR        1
#define ETH_MDC_PIN     15
#define ETH_MDIO_PIN    2
#define LED             32

static bool eth_connected = false;
static bool wifi_connected = false;
uint8_t pim_found = 0;

uint16_t ledPattern = 0;            // ledPattern used to set the LED
uint8_t  ledPos = 0;
unsigned long previousMillis = 0;   // will store last time LED was updated
const    long interval = 100;       // interval at which to blink (milliseconds)
unsigned long startSmartConfig = 0;

#define MAX_SRV_CLIENTS 10

WiFiServer server(2323);
WiFiClient serverClients[MAX_SRV_CLIENTS];

/** Load WLAN credentials from EEPROM */
void loadCredentials() {
  EEPROM.begin(512);
  EEPROM.get(0, ssid);
  EEPROM.get(0+sizeof(ssid), password);
  char ok[2+1];
  EEPROM.get(0+sizeof(ssid)+sizeof(password), ok);
  EEPROM.end();
  if ((String(ok) != String("OK")) || !strlen(ssid)) {
    ssid[0] = 0;
    password[0] = 0;
    Serial.println("no recovered WiFi-credentials found");
  } else {
    Serial.println("Recovered WiFi-credentials:");
    Serial.println(ssid);
    Serial.println(strlen(password)>0?"********":"<no password>");
  }
}

/** Store WLAN credentials to EEPROM */
void saveCredentials() {
  EEPROM.begin(512);
  EEPROM.put(0, ssid);
  EEPROM.put(0+sizeof(ssid), password);
  char ok[2+1] = "OK";
  EEPROM.put(0+sizeof(ssid)+sizeof(password), ok);
  EEPROM.commit();
  EEPROM.end();
}

void WiFiEvent(WiFiEvent_t event) {
  switch (event) {
    case SYSTEM_EVENT_ETH_START:
      Serial.println("ETH Started");
      //set eth hostname here
      ETH.setHostname("NMC-ethernet");
      break;
    case SYSTEM_EVENT_ETH_CONNECTED:
      Serial.println("ETH Connected");
      break;
    case SYSTEM_EVENT_ETH_GOT_IP:
      Serial.print("ETH MAC: ");
      Serial.print(ETH.macAddress());
      Serial.print(", IPv4: ");
      Serial.print(ETH.localIP());
      if (ETH.fullDuplex()) {
        Serial.print(", FULL_DUPLEX");
      }
      Serial.print(", ");
      Serial.print(ETH.linkSpeed());
      Serial.println("Mbps");
      eth_connected = true;
      server.begin();
      server.setNoDelay(true);
      break;
    case SYSTEM_EVENT_ETH_DISCONNECTED:
      Serial.println("ETH Disconnected");
      eth_connected = false;
      break;
    case SYSTEM_EVENT_ETH_STOP:
      Serial.println("ETH Stopped");
      eth_connected = false;
      break;
      
    case SYSTEM_EVENT_STA_START:
      Serial.println("STA Started");
      //set eth hostname here
      WiFi.setHostname("NMC-wifi");
      break;
    case SYSTEM_EVENT_STA_CONNECTED:
      Serial.println("WiFi Connected");
      if (strlen(ssid) == 0) {
        strcpy (ssid, WiFi.SSID().c_str());
        strcpy (password, WiFi.psk().c_str());
        saveCredentials();
      }
      break;
    case SYSTEM_EVENT_STA_GOT_IP:
      Serial.print("WiFi-IP address: ");
      Serial.println(WiFi.localIP());
      wifi_connected = true;
      server.begin();
      server.setNoDelay(true);
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      Serial.println("WiFi Disconnected");
      wifi_connected = false;
      break;
    default:
//      Serial.printf("Unhandled Network event:%d\r\n", event);
      break;
  }
}

int i2c_eeprom_read_buffer(byte deviceaddr, unsigned eeaddr, byte * buffer, byte length) {
    // Three lsb of Device address byte are bits 8-10 of eeaddress
    byte devaddr = deviceaddr | ((eeaddr >> 8) & 0x07);
    byte addr    = eeaddr;
   
    Wire.beginTransmission(devaddr);
    Wire.write(int(addr));
    Wire.endTransmission();

    Wire.requestFrom(devaddr, length);
    int i;
    for (i = 0; i < length && Wire.available(); i++) {
        buffer[i] = Wire.read();
    }

    return i;
}

void setup() {
  pinMode( 0,INPUT); // EXT CFG BUTTON
  pinMode(32,INPUT); // MODULE BOOT_SELECT
  pinMode(33,INPUT); // MODULE RESET

  
  Serial.begin(115200);

#ifdef PIM_MODULE
  char magic[11] = PIM_MODULE;
  Serial.print("FIXED");
#else
  char magic[11];
  
  // identify PIM 
  Wire.begin(ETH_MDIO_PIN,ETH_MDC_PIN); 
  memset( magic, 0, sizeof(magic));
  int res = i2c_eeprom_read_buffer(0x50, 0, (byte *)magic, sizeof(magic)-1);
  Wire.end();
  Serial.print("DYNAMIC");

#endif

  Serial.print(" PIM MAGIC: ");
  Serial.print(magic);
  Serial.println(" ");

  ledPattern = 0xffff;

  // initialize PIM 
  if (!strncmp(magic, "CSM", 3)) {
    Serial.print("FOUND CSM @ 38400 8N1");
    pinMode(33,OUTPUT);
    digitalWrite(33, HIGH);
    Serial2.begin(38400, SERIAL_8N1, 4, 16);
    pim_found = 1;
    
  } else if (!strncmp(magic, "TPUART", 6)) {
    Serial.print("FOUND TPUART @ 19200 8E1");
    Serial2.begin(19200, SERIAL_8E1, 4, 16);
    pim_found = 2;
    
  } else if (!strncmp(magic, "TCM310", 6)) {
    Serial.print("FOUND TCM310 @ 57600 8N1");
    pinMode(33,OUTPUT);
    digitalWrite(33, LOW);
    Serial2.begin(57600, SERIAL_8N1, 4, 16);
    pim_found = 3;

  } else if (!strncmp(magic, "RS485", 5)) {
    Serial.print("FOUND RS485 @ 9600 8N1");
    pinMode(5,OUTPUT);  // SEND ENABLE == HIGH
    digitalWrite(5, LOW);
    Serial2.begin(9600, SERIAL_8N1, 4, 16);
    pim_found = 4;
    
  } else {
    Serial.print("PIM module NOT SUPPORTED (yet)");
    ledPattern = 0xaaaa;
    pim_found = 0;
  }

  pinMode(LED,OUTPUT); 
  digitalWrite(LED, LOW);

  WiFi.onEvent(WiFiEvent);
  ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE, ETH_CLK_MODE);

  loadCredentials(); // Load WLAN credentials
  
  if (strlen(ssid) > 0) {
    WiFi.begin(ssid, password);
/* PRESS BUTTON less than a second instead
  } else {

    Serial.println("staring SmartConfig on WiFi interface ...");
    
    WiFi.mode(WIFI_AP_STA);
    WiFi.beginSmartConfig();
    startSmartConfig = millis();

*/
  }

}

#define MAXBUF 128

void loop() {
  uint8_t i;
  unsigned long currentMillis = millis();
  uint8_t sbuf[MAXBUF];

  // make LED blink 
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    digitalWrite(LED, ((ledPattern >> (ledPos++)) & 1) ? HIGH : LOW);
    ledPos = ledPos & 15;

  }

  if (startSmartConfig && (currentMillis - startSmartConfig >= 60000)) {
    WiFi.stopSmartConfig();
    Serial.print("WiFi SmartConfig timing out ...");
    WiFi.disconnect( true );
    startSmartConfig = 0;
  }
  
  if (wifi_connected || eth_connected) {

    if (pim_found)
      ledPattern = 0x0101;

    if (server.hasClient()){
      for(i = 0; i < MAX_SRV_CLIENTS; i++){
        //find free/disconnected spot
        if (!serverClients[i] || !serverClients[i].connected()){
          if(serverClients[i]) serverClients[i].stop();
          serverClients[i] = server.available();
          if (!serverClients[i]) Serial.println("available broken");
          Serial.print("New client: ");
          Serial.print(i); Serial.print(' ');
          Serial.println(serverClients[i].remoteIP());
          break;
        }
      }
      if (i >= MAX_SRV_CLIENTS) {
        //no free/disconnected spot so reject
        server.available().stop();
      }
    }
    
    //check WAN clients for data
    for(i = 0; i < MAX_SRV_CLIENTS; i++){
      if (serverClients[i] && serverClients[i].connected()){
        if (pim_found)
          ledPattern = 0x0505;

        if(serverClients[i].available()){
          //get data from the telnet client and push it to the UART
          if (pim_found == 4) digitalWrite(5, HIGH); // RS485 - SEND ENABLE
          while(serverClients[i].available()) Serial2.write(serverClients[i].read());
          if (pim_found == 4) {
            delay(200);
            digitalWrite(5, LOW); // RS485 - SEND ENABLE
          }
        }
      } else {
        if (serverClients[i])
          serverClients[i].stop();
      }
    }
  } else {
    for(i = 0; i < MAX_SRV_CLIENTS; i++)
      if (serverClients[i]) serverClients[i].stop();
      ledPattern = pim_found ? 0xffff : 0xaaaa;
  }


  //check UART for data
  if (Serial2.available()){
    size_t len = Serial2.available();
    if (len > sizeof(sbuf))
      len = sizeof(sbuf);
    memset( sbuf, 0, sizeof(sbuf) );   
    Serial2.readBytes(sbuf, len);
    //push UART data to all connected telnet clients
    for(i = 0; i < MAX_SRV_CLIENTS; i++){
      if (serverClients[i] && serverClients[i].connected())
        serverClients[i].write(sbuf, len);
    }

#ifdef USB2PIM    
    Serial.write(sbuf, len);
  }

  //check USB port 
  if (Serial.available() > 0) {
    Serial2.write(Serial.read());
#endif
    
  }

  //check CFG button pressed
  if (digitalRead(0) == LOW) {
    Serial.println("CONFIG BUTTON PRESSED! - waiting ...");
    currentMillis = millis();
    while (digitalRead(0) == LOW) yield();

    if (millis()-currentMillis>5000) {
      Serial.println("CONFIG BUTTON RELEASED! - deleting WiFi credentials ...");
      // clear old credentials
      ssid[0] = 0;
      password[0] = 0;
      saveCredentials();
    } else if (millis()-currentMillis>1000) {
      Serial.println("CONFIG BUTTON RELEASED! - bootloading PIM ...");
      
      if (pim_found == 1) {
//        pinMode(32,OUTPUT);
        digitalWrite(LED, LOW);

        pinMode(33,OUTPUT);
        digitalWrite(33, LOW);
        delay(100);
        digitalWrite(33, HIGH);
/*
        delay(500);
        digitalWrite(32, HIGH);
        pinMode(32,INPUT);
*/

      } else {
        Serial.println("PIM does not support bootloading...");
      }
    } else if (millis()-currentMillis>100) {
      Serial.print("WiFi-IP address: ");
      Serial.print(WiFi.localIP());
      Serial.print(" SSID: ");
      Serial.println(WiFi.SSID());
      Serial.print("Ethernet-IP address: ");
      Serial.println(ETH.localIP());
      Serial.print("PIM: ");
      Serial.println(pim_found);

      if (!wifi_connected && !startSmartConfig) { 
        Serial.println("staring SmartConfig on WiFi interface ...");
        WiFi.mode(WIFI_AP_STA);
        WiFi.beginSmartConfig();
        startSmartConfig = millis();
      }
    }
  }
}
