Ardumower / ardumower

Develop an open source robotic lawn mower (HW+SW reference platform)
239 stars 135 forks source link

Support for ESP8266 WIFI module #58

Open FredericG-BE opened 9 years ago

FredericG-BE commented 9 years ago

Ardumower implements the fpod protocol to configure, diagnose and control the Ardumower via an application on your Android phone or tablet.

The Ardumower PCB allows to plug in a Bluetooth module or a ESP8266 WIFI module to make the wireless connection between your Android device and your Ardumower. Currently only Bluetooth is supported by the Ardumower software.

FredericG-BE commented 9 years ago

Work in progress: https://github.com/FredericG-BE/ardumower/wiki/Using-ESP8266-WIFI-module-on-Ardumower

greymfm commented 9 years ago

This should be added to the Ardumower main code very soon - also, it will allow us to send software commands to the mower (drive forward until obstacle at speed x, turn degree y, ...) and to get back sensor data (odometry, course, ...). This will allow us to more quickly develop SLAM algorithm code on the PC ('hardware-in-the-loop'), so to try the more advanced localization code on a PC first without having to re-flash the real hardware all the time...

greymfm commented 9 years ago

ESP8266SerialWifiBridge feature: bulk transfers work faster with this ...

 * HardwareSerial.h:
 *  #define SERIAL_TX_BUFFER_SIZE 4096
 *  #define SERIAL_RX_BUFFER_SIZE 4096
  //
  // What is received on the Serial Port is sent to the client
  //
  if (Serial.available()){
    size_t len = Serial.available();
    if (len != lenLastAvail) timeoutTime = millis() + 200;    
    lenLastAvail = len;
    if ((len >= 1500) || (millis() > timeoutTime)){
      uint8_t sbuf[len];
      Serial.readBytes(sbuf, len);      
      if (clientConnected){
        client.write(sbuf, len);
      }
      Serial.println(MSG_HEADER " OK");        
      lenLastAvail = 0;
    }
    // delay(1);    
  } 
greymfm commented 9 years ago

This code supports both normal AP client (channel=0), and soft-AP mode (if channel > 0, IP config is ignored):

example config:
   AP client:  config:0,SSID,PASSWD,192.168.2.14,192.168.2.1,255.255.255.0
   soft-AP mode: config:1,SSID,PASSWD,192.168.2.14,192.168.2.1,255.255.255.0
/*
 * HardwareSerial.h:
 *  #define SERIAL_TX_BUFFER_SIZE 4096
 *  #define SERIAL_RX_BUFFER_SIZE 4096
 * 
 */

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <Ticker.h>

#ifndef min
#define min(a,b) ((a)<(b)?(a):(b))
#endif // min

// When ECHOTEST is enabled, each received {xxx} will be echoed as [xxx]
// #define ECHOTEST

#define BAUDRATE 115200

#define MAX_CONFIG_LEN  100
#define MSG_HEADER "[WSB]"
#define VESRION "v0.2"
#define CONFIG_MSG_START "config:"

typedef struct {
  const char* name;
  const char* valueStr;
} param_t;

typedef struct ledSequence_t{
  uint8_t onTicks;
  uint8_t offTicks;
} ledSequence_t;

struct {
  boolean on;
  uint8_t counter;
  const ledSequence_t* currSequence;
} ledStatus;

char configMsg[MAX_CONFIG_LEN];
param_t params[] = { 
  {"Channel",""},  
  {"SSID",""},
  {"Password",""},
  {"IPAddress",""},
  {"Gateway",""},
  {"Subnet",""},
};
#define PARAMID_CHANNEL 0
#define PARAMID_SSID    1
#define PARAMID_PASSWD  2
#define PARAMID_LOCALIP 3
#define PARAMID_GATEWAY 4
#define PARAMID_SUBNET  5
#define NBPARAMS (sizeof(params)/sizeof(params[0]))

const ledSequence_t ledSeq_startup        =   {1,10};             
const ledSequence_t ledSeq_waitForConfig  =   {1,1};      
const ledSequence_t ledSeq_connecting  =      {3,3};      
const ledSequence_t ledSeq_connected  =       {1,0};      
const ledSequence_t ledSeq_clientConnected  = {10,1};      

bool wifiConnected = false;
WiFiServer server(80);
WiFiClient client;
bool clientConnected = false;
Ticker ledTicker;
uint16 connectCnt = 0;
size_t lenLastAvail = 0;
unsigned long timeoutTime = 0;
int channel = 0;
char sbuf[4096];

void setLed(boolean on) {
  ledStatus.on = on;
  digitalWrite(2, on?HIGH:LOW);
}

void setLedSequence(const struct ledSequence_t& ledSeq) {
  ledStatus.currSequence = &ledSeq;
  ledStatus.counter = 0;
  setLed(true);
}

void onLedTicker(void) {
  ledStatus.counter++;
  if (ledStatus.on) {
    if (ledStatus.counter >= ledStatus.currSequence->onTicks) {
      if (ledStatus.currSequence->offTicks) {
        setLed(false);
      } else {
        // Stay ON
      }
      ledStatus.counter = 0;
    }
  } else {
    if (ledStatus.counter >= ledStatus.currSequence->offTicks) {
      setLed(true);
      ledStatus.counter = 0;
    }
  } 
}

void flushInput(void) {
  while(Serial.available())
    Serial.read();
}

void waitForParams(void) {
  boolean done = false;

  // Loop until a valid config message is received  
  while (! done) {

    // Loop until a line starting with "config:" is received
    uint8_t configMsgLen = 0; 
    boolean msgComplete = false; 
    while (! msgComplete) {
      if (Serial.available()) {
        char ch = Serial.read();
        if (ch == '\n' || ch == '\r') {
          msgComplete = true;
          configMsg[configMsgLen] = 0;  // Zero terminate
        } else {
          configMsg[configMsgLen] = ch;
          configMsgLen++;
          if (configMsgLen > MAX_CONFIG_LEN) {
            Serial.println(MSG_HEADER " ERR: Config too long");
            configMsgLen = 0; // discard all we have got so far  
          }
          if (memcmp(configMsg, CONFIG_MSG_START, min(strlen(CONFIG_MSG_START), configMsgLen)) != 0 ) {
            configMsgLen = 0; // discard all we have got so far 
          }
        }
      }
      yield();    
    }

    // Analyze the message
    int i;
    if (strlen(configMsg) >= strlen(CONFIG_MSG_START)) {
      // Split message into parameters
      char* p = configMsg + sizeof(CONFIG_MSG_START)-1;  // start after the "config:"
      for (i=0; i<NBPARAMS && *p; i++) {
        params[i].valueStr = p;
        p = strchr(p, ',');
        if (p) {
          *p = 0;
          p++;
        } else {
          break;
        }
      }
      if (i==NBPARAMS-1) {
        // Correct number of parameters, done
        done = true;
        Serial.println(MSG_HEADER " OK");
      } else {
        Serial.println(MSG_HEADER " ERR: Not enough parameters");
        configMsgLen = 0;
      }
    } else {
      Serial.println(MSG_HEADER " ERR: Expected \"" CONFIG_MSG_START "...\"");
    }

    delay(100);
    flushInput();
  }

}

void printParams(void) {
  int i;
  Serial.println(MSG_HEADER " Configuration parameters:");
  for (i=0; i<NBPARAMS; i++) {
    Serial.print(MSG_HEADER "    ");
    Serial.print(params[i].name);
    Serial.print(" = \"");
    Serial.print(params[i].valueStr);
    Serial.println("\"");
  }
}

void str2IpAddr(const char* str, IPAddress* ip) {
  int i;
  for (i=0; i<4; i++) {
    (*ip)[i]=atoi(str);
    str=strchr(str,'.');
    if (str)
      str++;
    else
      break;
  }
}

#ifdef ECHOTEST
char echotestMsg[20];
uint8_t echotestMsgLen = 0;
void echotestProcess(char ch) {
  if (ch == '{') {
    echotestMsg[0] = '[';
    echotestMsgLen = 1;
  } else if (ch == '}') {
    echotestMsg[echotestMsgLen] = ']';
    echotestMsgLen++;
    Serial.println();
    serverClients[0].write((uint8_t*)echotestMsg, echotestMsgLen);
    echotestMsgLen = 0;
  } else {
    if (echotestMsgLen > 0) {
      echotestMsg[echotestMsgLen] = ch;
      echotestMsgLen++;
    }
  }
}
#endif

void setup() {
  // Configure Serial Port
  Serial.begin(BAUDRATE);

  // Configure LED
  pinMode(2, OUTPUT);
  setLedSequence(ledSeq_startup);
  ledTicker.attach(0.1, onLedTicker); 

  // Welcome message
  delay(500);
  Serial.println("\n\n");
  Serial.println(MSG_HEADER " ESP8266 Serial WIFI Bridge " VESRION);

  // Get configuration message
  setLedSequence(ledSeq_waitForConfig);
  waitForParams();
  printParams();

  // Configue the ESP8266
  setLedSequence(ledSeq_connecting);

  // AP client:  config:0,SSID,PASSWD,192.168.2.14,192.168.2.1,255.255.255.0
  // AP mode:    config:1,ESPap,thereisnospoon,192.168.2.14,192.168.2.1,255.255.255.0

  channel = atoi(params[PARAMID_CHANNEL].valueStr);
  if (channel == 0){
    // AP client mode
    WiFi.begin(params[PARAMID_SSID].valueStr, params[PARAMID_PASSWD].valueStr);       
    if (strlen(params[PARAMID_LOCALIP].valueStr) > 0) {
      IPAddress localIp;
      IPAddress gateway;
      IPAddress subnet;    
      str2IpAddr(params[PARAMID_LOCALIP].valueStr, &localIp);
      str2IpAddr(params[PARAMID_GATEWAY].valueStr, &gateway);
      str2IpAddr(params[PARAMID_SUBNET].valueStr, &subnet);   
      WiFi.config(localIp, gateway, subnet);    
    }
  } else { 
    // AP mode
    WiFi.softAP(params[PARAMID_SSID].valueStr, params[PARAMID_PASSWD].valueStr, channel);
    IPAddress myIP = WiFi.softAPIP();
    Serial.println(MSG_HEADER " AP: ");    
    Serial.println(myIP);             
  }  

  // Start server
  server.begin();
  server.setNoDelay(true);
}

void disconnectClient(void) {
  client.stop();
  clientConnected = false;  
}

void loop() {
  //
  // Handle AccessPoint connection
  //
  if (channel == 0){
    // AP client mode
    if (WiFi.status() == WL_CONNECTED) {
      // Connected to AP
      if (!wifiConnected) {
        // Transition Disconnected => Connected
        wifiConnected = true;
        setLedSequence(ledSeq_connected);
        Serial.print(MSG_HEADER " CONNECTED! ");
        Serial.print(" IP address: ");
        Serial.println(WiFi.localIP());
      }
    } else {
      // Disconnected from AP
      if (wifiConnected) {
        // Transition Connected => Disconnected
        wifiConnected = false;
        setLedSequence(ledSeq_connecting);
        Serial.print(MSG_HEADER " DISCONNECTED");
        disconnectClient();
        connectCnt = 0;
      }
      Serial.print(MSG_HEADER " Connecting ..."); 
      Serial.println(connectCnt); 
      connectCnt++;
      delay(250);
    }
  }

  // 
  // Handle Client connection
  //
  if (clientConnected) {
    if (!client.connected())  {
      // Client is disconnected
      disconnectClient();
      setLedSequence(ledSeq_connected);
      Serial.println(MSG_HEADER " Client Disconnected");
    }
  }

  if (server.hasClient()) {
    // A new client tries to connect
    if (!clientConnected) {
      // OK accept the client
      client = server.available();
      clientConnected = true;
      setLedSequence(ledSeq_clientConnected);
      Serial.println(MSG_HEADER " Client Connected");  
    } else {
      // A client is already connetced, refuse
      WiFiClient serverClient = server.available();
      serverClient.stop();
    }
  }

  //
  // Send all bytes received form the client to the Serial Port
  //
  if (clientConnected){
    if(client.available()){
      while(client.available()) {
        size_t len = min(4096, client.available());
        client.read( (uint8_t*)&sbuf, len);
        Serial.write(sbuf, len);
        #ifdef ECHOTEST
          echotestProcess(ch);
        #endif 
        delay(1);
      }
    }
  }

  //
  // What is received on the Serial Port is sent to the client
  //
  if (Serial.available()){
    size_t len = min(4096, Serial.available());
    if (len != lenLastAvail) timeoutTime = millis() + 100;    
    lenLastAvail = len;
    if ((len >= 1500) || (millis() > timeoutTime)){      
      Serial.readBytes(sbuf, len);      
      if (clientConnected){
        client.write((uint8_t*)&sbuf, len);
      }
      Serial.println(MSG_HEADER " OK");        
      lenLastAvail = 0;
    }
    delay(1);    
  } 
}
greymfm commented 9 years ago

Updated the code above: buffer is not allocated for each packet anymore. Seem to work more stable.

xjustbmw commented 5 years ago

any news regarding this code with the new esp library that does not support changing the buffer size anymore ?

for me it barely works on mega using esp-link esp-link works good on due only