Exercise #1 :
 
  • Here is a sketch that gives the same behavior as asked by the subject, but modified to use interrupts to manage the switch changes instead of an active scan during loop() execution.
  • As we can see, with interrupts, there is nothing to do within loop() except waiting for the interrupt to trigger.
#include <ESP8266WiFi.h>

volatile byte oldState;
volatile byte state;

 void ICACHE_RAM_ATTR getSwitch() {
  oldState = state; // copy the old state
  state = digitalRead(D2);
  if (state == 0) {    
    digitalWrite(D1,LOW);        
  }
  else {    
    digitalWrite(D1,HIGH);        
  }  
}

void setup() {
 
  Serial.begin(115200);
  delay(10);

  // power off Wifi to save energy
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
  delay(10); 
 
  pinMode(D1,OUTPUT); // LED
  pinMode(D2,INPUT); // switch
  state = -1;
  // get initial switch & led state
  getSwitch();    
  
  // create interrupt
  attachInterrupt(digitalPinToInterrupt(D2), getSwitch, CHANGE);  
}
 
void loop() {    
  delay(1000); // wait 1s
}

 

Exercise #2 :

  • This time, we do not use an interrupt to check if the switch changed of state. Indeed, it is not possible to send an http request from within an interrupt callback (processings must be very short).
  • Instead, we use an active scanning of the switch state by calling getSwitch() directly, at each loop() execution.
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
WiFiClient clientForHTTP;
HTTPClient http;
 
volatile byte oldState;
volatile byte state;

// NB: in this sketch, we do not attach this function to an interrupt
// because we want to send an HTTP request when oldState != state, which
// is possible only in loop() and not in a interrupt handler.
void ICACHE_RAM_ATTR getSwitch() {
  oldState = state; // copy the old state
  state = digitalRead(D2);
  if (state == 0) {    
    digitalWrite(D1,LOW);        
  }
  else {    
    digitalWrite(D1,HIGH);        
  }  
}

void setupWifi() {
     
  const char *ssid = "testingiot"; // the ssid of the AP        
  const char *password = "testingiot"; // the password of the AP  
  
  WiFi.setAutoConnect(false);  // do not reconnect automatically to the last known AP
  WiFi.setSleepMode(WIFI_NONE_SLEEP); // stays alive and never use the default modem sleep mode
  WiFi.mode (WIFI_STA); // setup as a wifi client
  WiFi.begin(ssid,password); // try to connect
  while (WiFi.status() != WL_CONNECTED) { // check connection
    delay(500); 
    Serial.print(".");
  } 
  // debug messages
  Serial.print("Connected, IP address: ");
  Serial.println(WiFi.localIP());
}

void sendHTTP(int value) {
  Serial.print("[HTTP] begin...\n");
 
  http.begin(clientForHTTP, "http://192.168.0.2/set_state.php?state="+String(value)); 
  Serial.print("[HTTP] GET...\n");
  // start connection and send HTTP header
  int httpCode = http.GET();
 
  // httpCode will be negative on error
  if(httpCode > 0) {
    // HTTP header has been send and Server response header has been handled
    Serial.printf("[HTTP] GET... code: %d\n", httpCode);
 
    // file found at server
    if(httpCode == HTTP_CODE_OK) {
      String payload = http.getString();
      Serial.println(payload);
    }
  } 
  else {
    Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
  } 
  http.end();
}

void setup() {
 
  Serial.begin(115200);
  delay(10);

  pinMode(D1,OUTPUT); // LED
  pinMode(D2,INPUT); // switch
  state = -1;
  getSwitch();  

  // starts wifi connection
  setupWifi();    
  
}
 
void loop() {
  // active scanning of switch state.
  getSwitch();
  if (oldState != state) {      
    sendHTTP(state); // do the http request to send the state  
  }
  delay(100);
}

 

Exercises #3, #4 and #5: 

  • This solution presents a common "tips and tricks" in arduino programming : after TCP connection is established, we never use delay() to wait but a timer to measure elapsed time.
  • Indeed, using delay() may lead to efficiency issues, and even may lead to a crash when some background tasks cannot be executed because delay is blocking. Furthermore, delay relies itself on an interrupt to work, and in few circumstances, it can lead to an infinite wait.
  • This is why, it is better to use a timer, based on the function millis() :
    • the start time of the timer is measured one time, by calling millis(). It gets the number of millisecondes elapsed since booting.
    • at each loop() execution, a second call to millis() is done. If the gap between both measures is sufficient, we do a processing.
  • This is illustrated at the end of loop(), to wait for 500ms between two measurements of the distance.
  • For PWM, we use a fixed duty cycle of 50% when the LED is on. Only the frequency is changed according to the distance (see changeBlink() )
#include <ESP8266WiFi.h>
#include <Ultrasonic.h>
#include <TM1637.h>

WiFiClient client;
const char* server = "192.168.0.2";
const int port = 12345;
int stateConn = 0; // state of connection to server
String idClient; // my id given by server.

#define TRIGGER_PIN D5
#define ECHO_PIN D6
Ultrasonic ultrasonic(TRIGGER_PIN, ECHO_PIN);

#define CLK_PIN D3
#define DIO_PIN D4
TM1637 tm1637(CLK_PIN, DIO_PIN);
int8_t dispIndexes[] = { 0,0,0,0}; // display 4 zeroes
 
volatile byte oldState;
volatile byte state;
float oldDist;
float dist;
long timerDist;
long timerReq;


void ICACHE_RAM_ATTR getSwitch() {
  oldState = state; // copy the old state
  state = digitalRead(D2);
  if (state == 0) {    
    // set duty cycle to 0%
    analogWrite(D1,0);        
  }
  else {    
    // set duty cycle to 50%
    analogWrite(D1,50);        
  }  
}

void changeBlink() {
  if (dist < 20) {
    analogWriteFreq(1);
  }
  else if (dist > 100) {
    analogWriteFreq(50);
  }
  else {
    // slope is (50-1)/(100-20) = (49/80)
    // b = 1 - 20*slope = -11.25
    int freq = (int)((49.0*dist/80.0)-11.25);
    analogWriteFreq(freq);
  }
  if (state == 0) {
    analogWrite(D1,0);
  }
  else {
    analogWrite(D1,50); // led off in other cases
  }
    
}
void displayDistAndState() {
  if (state == 0) dispIndexes[0] = 0;
  else dispIndexes[0] = 1;
  // convert distance
  dispIndexes[1] = dist/100;
  dispIndexes[2] = ((int)dist%100)/10;
  dispIndexes[3] = (int)dist % 10;
  tm1637.display(dispIndexes);
}

void setupWifi() {
     
  const char *ssid = "testingiot"; // the ssid of the AP        
  const char *password = "testingiot"; // the password of the AP  
  
  WiFi.setAutoConnect(false);  // do not reconnect automatically to the last known AP
  WiFi.setSleepMode(WIFI_NONE_SLEEP); // stays alive and never use the default modem sleep mode
  WiFi.mode (WIFI_STA); // setup as a wifi client
  WiFi.begin(ssid,password); // try to connect
  while (WiFi.status() != WL_CONNECTED) { // check connection
    delay(500); 
    Serial.print(".");
  } 
  // debug messages
  Serial.print("Connected, IP address: ");
  Serial.println(WiFi.localIP());
}

// readLineServer() blocks until a line is read, or 1023 char have been read.
String readLineServer() {
  char buffer[1024];
  memset(buffer,0,1024);    // reset buffer
  int index = 0; 
  char c;
  while(true) {
    while (!client.available()) {} // wait for smthg to read
    while ((index < 1023) && (client.available())) {
      c = client.read();
      if (c == '\n') return String(buffer); // end-of-line reached => return
      buffer[index++] = c; // store the char
    }
    if (index == 1023) return String(buffer); 
  }  
}

void serverConnection() {

  Serial.print("connecting to ");
  Serial.println(server);  
  // Use WiFiClient class to create TCP connections

  if (!client.connect(server, port)) {
    Serial.println("connection failed");
    stateConn = 0;
  }
  else {   
    stateConn = 1;  
    Serial.println("connected to server. Asking my id");    
    client.println("0"); // envoi requête GETID
    idClient = readLineServer();
    Serial.println("my id is "+idClient);    
  }
}

void requestStore() {
  client.println("1");
  client.println(idClient);
  String req = String(dist)+",0,0";
  client.println(req);
  Serial.println(readLineServer);  
}

void setup() {
 
  Serial.begin(115200);
  delay(10);

  dist = 50.0;
  oldDist = 50.0;

  pinMode(D1,OUTPUT); // LED  
  pinMode(D2,INPUT); // switch
  
  state = -1;
  getSwitch();
  // switch odd the LED whatever the switch state
  // it will be overridden in loop() by calling changeBlink()
  digitalWrite(D1,LOW); 
  analogWriteRange(100);
  analogWriteFreq(1);
  delay(10);  
  
  // starts wifi connection
  setupWifi();
  
  // set 4-digits display
  tm1637.set(BRIGHT_TYPICAL);//BRIGHT_TYPICAL = 2,BRIGHT_DARKEST = 0,BRIGHTEST = 7;
  tm1637.init();
  tm1637.display(dispIndexes);  

  // init timers to measure elapsed time within loop()
  timerDist = millis();
  timerReq = timerDist;
}
 
void loop() {
  
  if (stateConn == 0) {    
    serverConnection();
    if (stateConn == 0) delay(2000);    
  }
  else {
    // NB: no use of delay() to wait in this case. This is generally the best to do !

    // check the state of the switch
    getSwitch();
    // get new distance, every 500ms => using timer to measure elasped time instead of delay()
    long t = millis();
    if (t-timerDist > 500) {
      //  reinit timer to wait again 500ms
      timerDist = t;
      oldDist = dist;  
      long microsec = ultrasonic.timing();
      dist = ultrasonic.convert(microsec, Ultrasonic::CM);      
      displayDistAndState();      
      changeBlink();
      if ( ((oldDist < 80.0) && (dist >= 80.0)) || ((oldDist > 80.0) && (dist <= 80.0)) ) { // send the request    
        requestStore();
      }
    }
  }
}