Project tutorial

Herb Box Eco System © GPL3+

Log humidity, automatic irrigate, switch plant growing lamp on and voice control everything with Amazon Alexa and Arduino.

  • 32,263 views
  • 18 comments
  • 204 respects

Components and supplies

Apps and online services

About this project

Since my plants do always suffer from too much or less water and I like to put a lot of herbs into my dishes, I decided to create a custom irrigation system. The box for my herbs should be configurable and work automatically or manually. Therefore an interface to a website exists to enable a setup and show the humidity in a nice chart. The last step was the integration of voice control to ask Amazon Alexa for humidity, switch a plant growing lamp on/off, and start the irrigation, if automation is deactivated. Click here for find the result.

I started with the technical part of the project and bought an Arduino. After some tutorials, i was firm with the software and controlling the Arduino. I ordered a wifi controller, some moisture sensors, pumps, a plant growing lamp and additional required hardware (relais shield to separate the circuts for the lamp and pumps from the Arduino, some wires and beech wood for the frame). The Arduino code of the result is provided in this tutorial, beside some information of how to use the components in your projects. The website/api code is not provided (unless the demand is very high ;) ).

Step One: Moisture Sensor

The first milestone was to read the humidity with my Arduino. The moisture sensor YL-69 was easy to connect with the Arduino. You need to connect he VCC pin to a GPIO pin (in my example pin 06), ground to ground and the A0 to an analog pin (in my example pin A1) of the Arduino.

Tutorial: Soil Humidity Sensor

byte vccPin = 6;
byte dataPin = A1;
void setup() {
 pinMode(vccPin, OUTPUT);
 digitalWrite(vccPin, LOW);
 Serial.begin(9600);
 while (!Serial);
}
int readHumidity() {
 digitalWrite(vccPin, HIGH);
 delay(500); // you need to test how long you pre-power before measurement
 int value = analogRead(dataPin);
 digitalWrite(vccPin, LOW);
 return 1023 - value;
}
void loop() {
 Serial.print("HumidityLevel (0-1023): ");
 Serial.println(readHumidity()); 
 delay(10000);
}

Step Two: Relay for Pumps and Lamp

The next goal was to install a relay shield (4 relais) for separating the circuits of the lamp, pumps and Arduino. The Arduino runs on 5V, the pumps use 12V and the plant growing lamp 230V. The shield needs to be connected to the 5V and Ground pins on the Arduino. Each relay further needs a GPIO pin of your choice to switch on and off. Lastly, you can use a jumper for VCC JC to VCC on the shield, or use an extra battery (which would be best, but i don't have any battery inside my project, yet).

It is important to understand, that my shield switches "On" with "LOW" on the pin. As soon as my pin is defined as OUTPUT, it automatically switched to active. In the code you should always switch to INPUT and LOW, if you want the relay to be off. By default, the Arduino pins are INPUT and LOW.

Tutorial: Relay Shield

Information: Why relay OUTPUT + LOW = Active?

byte pump1 = 11;
byte pump2 = 10;
void setup() {
 Serial.begin(9600);
 while (!Serial);
 pinMode(pump1, OUTPUT); // variant low/high
 digitalWrite(pump2, LOW); // variant input/output
}
void loop() {
 digitalWrite(pump1, HIGH); // pump1 deactivated
 pinMode(pump2, INPUT); // pump2 deactivated
 delay(1000);
 digitalWrite(pump1, LOW); // pump1 activated
 pinMode(pump2, OUTPUT);  // pump2 activated
 delay(1000);
}

Step Three: WiFi with ESP-01

Connecting the espressif ESP8266 ESP-01 to the Arduino for WiFi was the most difficult part. It took me hours to get the wifi running in my script.

The ESP is connected to: VCC = 3.3V, GND = GND, CH_PD = 3.3V, TX = Pin 02, RX = Pin 03. For productive usage, you should use at least a level converter from 5V to 3.3V for the pin 02 and pin 03, too. In my case, it worked fine.

Similar to the Arduino, the ESP-01 is another microcontroller. If you want both controllers to communicate, you have to use serial communication. The Arduino UNO uses by default the pins 01 and 02 for RX and TX. But they are also used for USB debugging and therefore it's suggested to include SoftwareSerial.h and define custom pins.

#include <SoftwareSerial.h>
SoftwareSerial espSerial(3,2); // RX, TX
void setup() {
 Serial.begin(9600);
 espSerial.begin(115200); // switch to 9600 after AT+UART_DEF=9600,8,1,0,0
 while (!Serial);
}
void loop() {
 if (espSerial.available()) {
   Serial.write(espSerial.read());
 }
 if (Serial.available()) {
   espSerial.write(Serial.read());
 }
}

Running the script above, you can enter AT-commands into the serial monitor and see the results. Serial communication is prone to failure, therefore i decreased the communication baud rate used by the ESP from 115200 to 9600.

Tutorial: ESP8266 + Arduino | Tutorial: General ESP8266 (german)

The script uses HTTP 1.0, because with HTTP 1.1 the bytes are part of the response. It's important to take care about the line breaks for the command to be send after AT+CIPSEND. If they are wrong, you will receive a byte send error.

#include <SoftwareSerial.h>
SoftwareSerial espSerial(3,2); // RX, TX
const char* ssid = "<YOUR-WIFI-SSID>";
const char* pass = "<YOUR-WIFI-PASSWORD>";
void setup() {
 Serial.begin(9600);
 espSerial.begin(9600);
 while(!Serial);
 while(!connectToWiFi());
 // request website and print result
 if (httpRequest("my.server.com", "/site/subsite/index.php")) {
   while (espSerial.available()) { Serial.write(espSerial.read()); }
 }
}
void loop() { // run over and over
 if (espSerial.available()) {
   Serial.write(espSerial.read());
 }
 if (Serial.available()) {
   espSerial.write(Serial.read());
 }
}
bool connectToWiFi() {
 delay(2000;)
 espSerial.setTimeout(3000);
 while (espSerial.available()) Serial.write(espSerial.read());
 Serial.println(F("[ESP] Connecting to WiFi"));
 espSerial.println(F("AT+CIPSTATUS=2"));
 if (!espSerial.find("OK")) {
   espSerial.setTimeout(10000);
   Serial.println(F("[ESP] Reset Module"));
   espSerial.println(F("AT+RST")); if (!espSerial.find("ready")) { Serial.println(F("[ESP] Reset failed")); return false; }
   Serial.println(F("[ESP] Set CWMode"));
   espSerial.println(F("AT+CWMODE=1")); if (!espSerial.find("OK")) { Serial.println(F("[ESP] Mode failed")); return false; }
   Serial.println(F("[ESP] Connect to Router"));
   espSerial.print(F("AT+CWJAP=\""));
   espSerial.print(ssid);
   espSerial.print(F("\",\""));
   espSerial.print(pass);
   espSerial.println("\"");
   if (!espSerial.find("OK")) { Serial.println(F("[ESP] WiFi connection failed")); return false; }
 }
 espSerial.setTimeout(3000);
 Serial.println(F("[ESP] WiFi is connected"));
 return true;
}
bool httpRequest(String server, String site) {
 String cmd = "";
 cmd += "GET " + site + " HTTP/1.0\r\n";
 cmd += "Host: " + server + "\r\n";
 cmd += "Connection: close";
 int cmdLength = cmd.length() + 4;
 // Serial.println(cmd);
 espSerial.print(F("AT+CIPSTART=\"TCP\",\""));
 espSerial.print(server);
 espSerial.println(F("\",80"));
 if (!espSerial.find("OK")) { Serial.println(F("[ESP] TCP Connection Error")); return false; }
 espSerial.print(F("AT+CIPSEND="));
 espSerial.println(cmdLength);
 if (!espSerial.find(findGT)) { Serial.println(F("[ESP] Send State Error")); return false; }
 espSerial.print(F("GET "));
 espSerial.print(site);
 espSerial.print(F(" HTTP/1.0\r\n"));
 espSerial.print(F("Host: "));
 espSerial.print(server);
 espSerial.print(F("\r\n"));
 espSerial.print(F("Connection: close\r\n"));
 espSerial.println();
 if (!espSerial.find(":")) { Serial.println(F("Bytes not sent")); espSerial.print(F("AT+CIPCLOSE")); return false; }
 char status[32] = {0};
 espSerial.readBytesUntil('\r', status, sizeof(status));
 if (strcmp(status, "HTTP/1.1 200 OK") != 0) { Serial.print(F("[ESP] Unexpected response: ")); Serial.println(status); return false; }
 if (!espSerial.find("\r\n\r\n")) { Serial.println(F("[ESP] Invalid response")); return false; } // Skip HTTP headers
 // if (!espSerial.find(\r\n)) { Serial.println(F("[ESP] Bytes not found")); return; } // skip bytes (for http 1.1)
 return true;i
}

Step Four: The Wooden Box

The frame was planned to store all electronics and three herb pots from the supermarket. I measured the sizes of all components and structured the positions. Four moisture sensors, two pumps, the Arduino + shield, a 4x relay shield and an USB plug and some wires need to fit in the box. It was made out of beech wood, to make it strong and last waterdrops without additional glaze.

The circles were sawed out with jig saw on a self made jig saw table. The plant mounts are glued inside the circles with hot glue. The sides of the box are glued by wood glue (D3 for water resistance). Besides the electronic, I didn't use any screws or nails beside the lower panel fixing.

I put all the circuits, wires and water tubes inside the box, pulled the sensors out and the tubes for the additional water tank. Before closing the box, I added saucers to prevent water drowning inside the box to protect the electronic.

Step Five: The Website API

The API and website is based on jQuery, Bootstrap, X-editable (for inline ajax forms) and Chart.js (for the humidity chart), coded in php. On the website you can define the settings for the Arduino (e.g. sensor pins, humidity check interval, pumps per plant, pump VCC pins, light VCC pin) and find the current humidity + chart.

The configuration is provided by JSON for the Arduino. After starting and in a frequent interval, the herb box checks for new settings. For parsing the JSON with the Arduino, I used the library ArduinoJson. For polling interval I used StensTimer.

Library: ArduinoJson | Library: StensTimer

Step Six: Alexa Integration

The website provides an API for Alexa communication. It serves as a hub to receive the request JSON by Alexa and translates it into a custom JSON used by the Arduino (e.g. lamp on, irrigate plant 1, ...). The Arduino polls for new actions and executes them.

Because the voice requests are more than just on/off, I implemented an Alexa Skill and no Alexa Smart Home. The AWS Lampda forwards the request JSON to my API, which parses the intents.

var https = require('https');
exports.handler = (event, context, callback) => {
   var postData = JSON.stringify(event);
   var options = {
       host: '<MY-SERVER>',
       path: '<MY-API-SITE>',
       port: 443,
       method: 'POST',
       headers: {
           'Content-Type': 'application/json',
           'Content-Length': postData.length,
       }
   };
   // set up the request
   var postRequest = https.request(options, function(res) {
         res.setEncoding('utf8');
         res.on('data', function (chunk) {
             console.log('Response: ' + chunk);
             // console.log(chunk);
             callback(null, JSON.parse(chunk));
         });
   });
   // post the data
   postRequest.write(postData);
   postRequest.end();
};

An excerpt of the intents i used by my skill:

  • ReadHumidityIntent How are my plants
  • ReadHumidityIntent How is my {plantName}
  • IrrigatePlantIntent Irrigate my plants
  • IrrigatePlantIntent Irrigate my {plantName} for {durationSeconds} seconds
  • SwitchIntent Switch the lamp {switchState}
  • ReadIrrigateIntent Which plants need water
  • ReadLastIrrigationIntent When was the last irrigation of my {plantName}

Last, but not least, i added locale support for german and english usage.

The Result

As result, i do have a wooden box to put supermarket herb pots into, take water tubes and moisture sensors in the soil and the tubes in an external water tank. With Alexa intergration, i can say the following sentences:

  • "Alexa, ask herb box how my plants are" - Response: "Plant 1 is fine, Plant 2 is dry, ..."
  • "Alexa, tell herb box to irrigate my basil for 5 seconds" - Response: "Irrigating basil for 5 seconds"
  • "Alexa, ask herb box which plants need irrigation" - Reponse: "Plant 1 is dry, Plant 3 is dry, ..."
  • "Alexa, ask herb box when was the last irrigation of my basil" - Response: "Last irrigation of basil was 36h ago"
  • "Alexa, tell herb box to switch the lamp on" - Response: "Switched plant growing lamp on"

Asking Alexa for the humidity of my plant and irrigating it afterwards (german):

Asking Alexa to turn the plant growing lamp on:

GIFs showing the result without the videos:

Planned Features

The following features are not yet implemented but planned for future:

  • Power saving mode for the Arduino source code
  • Add external Arduino Nanos with wireless communication (2,4 GHz) for moisture measurement of other plants in the house (the box is the hub for WiFi) - using batteries only
  • Extend API for multiple instances of the herb box, for friends (and whomever, if you are interested?!)
  • Add a button to irrigate and switch lamp on the box without website or Alexa
  • Alexa images (card in skill response)

Update 23.03.2018

I have added two new intents. One of them is important for the planned feature of external Adruino Nanos who just log the humidity.

  • Which plants are dry
  • When was the last irrigation

Code

EcoActionBuffer.hArduino
#ifndef ECOACTIONBUFFER_H
#define ECOACTIONBUFFER_H

#include "Arduino.h"
#include "StensTimer.h"

struct EcoActionBuffer : public IStensTimerListener {
  long entryNo;
  int action;
  int pin;
  long duration;

  void timerCallback(Timer* timer);
  void switchPin(int pin, bool value);
  void readStack();
  void process();
  void toSerial();
  void reset();
};

#endif
EcoActionBuffer.cppArduino
#include "EcoActionBuffer.h"
#include "StensTimer.h"
#include "WhiteWalnutApi.h"

#define ACTION_ECOACTION_READ 1
#define ACTION_ECOACTION_HIGH 2
#define ACTION_ECOACTION_LOW 3

void EcoActionBuffer::readStack() {
  reset();
  WhiteWalnutApi::receiveActionFromStack(*this);
  if (entryNo != 0) {
    process();
    // WhiteWalnutApi::updateActionOnStack(*this); // deactivated for performance
  }
}

void EcoActionBuffer::process() {
  toSerial();
  pinMode(pin, OUTPUT);
  digitalWrite(pin, HIGH);
  
  switch (action) {
    case ACTION_ECOACTION_HIGH:
      switchPin(pin, true);
      break;
    case ACTION_ECOACTION_LOW:
      switchPin(pin, false);
      break;
  }

  if (duration != 0) {
    StensTimer::getInstance()->setTimer(this, -pin, duration);
  }
}

void EcoActionBuffer::timerCallback(Timer* timer) {
  switch (timer->getAction()) {
    case ACTION_ECOACTION_READ:
      readStack();
      break;
  }
  if (timer->getAction() < 0) {
    switchPin(abs(timer->getAction()), false);
  }
}

void EcoActionBuffer::switchPin(int pin, bool value) {
  switch (value) {
    case true:
      digitalWrite(pin, LOW);
      break;
    case false:
      digitalWrite(pin, HIGH);
      break;
  }
  WhiteWalnutApi::switchPin(pin, value);
}

void EcoActionBuffer::reset() {
  entryNo = 0;
  action = 0;
  pin = 0;
  duration = 0;
}

void EcoActionBuffer::toSerial() {
  Serial.print(entryNo);
  Serial.print(F(" - Action: "));
  Serial.print(action);
  Serial.print(F(", Pin: "));
  Serial.print(pin);
  Serial.print(F(", Duration: "));
  Serial.print(duration);
  Serial.println();
}
Plant.cppArduino
#include "Plant.h"
#include "StensTimer.h"
#include "WhiteWalnutApi.h"

#define ACTION_PLANT_CHECKHUMIDITY 2

#define PIN_HUMIDITY_VCC 12

void Plant::checkHumidity() {
  if (humidityDataPin != 0) {
    Serial.print(code);
    Serial.print(F(" - Check humidity..."));
    digitalWrite(PIN_HUMIDITY_VCC, HIGH);
    delay(200); // TODO
    int humidity = 1023 - analogRead(humidityDataPin);
    digitalWrite(PIN_HUMIDITY_VCC, LOW);

    Serial.println(humidity);
    WhiteWalnutApi::sendHumidity(*this, humidity);

    if (humidityCheckInterval == 0) humidityCheckInterval = 60000;
    StensTimer::getInstance()->setTimer(this, ACTION_PLANT_CHECKHUMIDITY, humidityCheckInterval);
  } else StensTimer::getInstance()->setTimer(this, ACTION_PLANT_CHECKHUMIDITY, 60000);
}

void Plant::updateApi() {
  WhiteWalnutApi::updatePlant(*this);
  // WhiteWalnutApi::sendHeartbeat(*this); // deactivated for performance
  pinMode(PIN_HUMIDITY_VCC, OUTPUT);
  toSerial();
}

void Plant::timerCallback(Timer* timer) {
  switch (timer->getAction()) {
    case ACTION_PLANT_CHECKHUMIDITY:
      checkHumidity();
      break;
  }
}

void Plant::toSerial() {
  Serial.print(code);
  Serial.print(F(" - DataPin: "));
  Serial.print(humidityDataPin);
  Serial.print(F(", Interval: "));
  Serial.print(humidityCheckInterval);
  Serial.println();
}
Plant.hArduino
#ifndef PLANT_H
#define PLANT_H

#include "Arduino.h"
#include "StensTimer.h"

struct Plant : public IStensTimerListener {
  const char* code;
  int humidityDataPin;
  long humidityCheckInterval;

  void checkHumidity();
  void timerCallback(Timer* timer);
  void toSerial();
  void updateApi();
};

#endif
WhiteWalnut.inoArduino
#include "EcoActionBuffer.h"
#include "Plant.h"
#include "StensTimer.h"
#include "WhiteWalnutApi.h"

struct TimerHelper : public IStensTimerListener {
  public:
    void updateApi();
    void timerCallback(Timer* timer);
};

StensTimer* stensTimer;
TimerHelper apiTimer;
Plant leftPlant;
Plant centerPlant;
Plant rightPlant;
Plant externalPlant;
EcoActionBuffer actionBuffer;

#define ACTION_PLANT_UPDATE 1
#define ACTION_ECOACTION_READ 1

void setup() {
  Serial.begin(9600);
  while (!Serial);

  stensTimer = StensTimer::getInstance();
  
  leftPlant.code = "LEFT";
  centerPlant.code = "CENTER";
  rightPlant.code = "RIGHT";
  externalPlant.code = "EXTERNAL";

  while(!WhiteWalnutApi::connectToWiFi()) delay(2000);

  WhiteWalnutApi::switchPin(0, false);
  apiTimer.updateApi();
  leftPlant.checkHumidity();
  centerPlant.checkHumidity();
  rightPlant.checkHumidity();
  externalPlant.checkHumidity();
  
  actionBuffer.readStack();
  StensTimer::getInstance()->setInterval(&apiTimer, ACTION_PLANT_UPDATE, 60000);
  StensTimer::getInstance()->setInterval(&actionBuffer, ACTION_ECOACTION_READ, 1000);
}

void loop() {
  stensTimer->run();
}

void TimerHelper::updateApi() {
  leftPlant.updateApi();
  centerPlant.updateApi();
  rightPlant.updateApi();
  externalPlant.updateApi();
}

void TimerHelper::timerCallback(Timer* timer){
  switch (timer->getAction()) {
    case ACTION_PLANT_UPDATE:
      updateApi();
      break;
  }
}
WhiteWalnutApi.cppArduino
you need to add your WiFi and API settings
#include "Arduino.h"
#include "ArduinoJson.h"
#include "EcoActionBuffer.h"
#include "MemoryFree.h"
#include "Plant.h"
#include "SoftwareSerial.h"
#include "WhiteWalnutApi.h"

SoftwareSerial espSerial(3, 2);

const char* ssid = "<MY-SSID>";
const char* pass = "<MY-PASSWORD>";
const char* API_SERVER = "<MY-SERVER>";
const char* API_PLANT  = "<MY-PLANT-API>";
const char* API_ACTION = "<MY-ACTION-API>";

char* findOK = "OK";
char* findRY = "ready";
char* findGT = ">";
char* findDP = ":";
char* findHD = "\r\n\r\n";
char* findBT = "\r\n";
  
bool WhiteWalnutApi::connectToWiFi() {
  espSerial.begin(9600);
  espSerial.setTimeout(3000);

  while (espSerial.available()) Serial.write(espSerial.read());
  Serial.println(F("[ESP] Connecting to WiFi"));
  espSerial.println(F("AT+CIPSTATUS=2"));
  if (!espSerial.find(findOK)) {
    espSerial.setTimeout(10000);
    Serial.println(F("[ESP] Reset Module"));
    espSerial.println(F("AT+RST")); if (!espSerial.find(findRY)) { Serial.println(F("[ESP] Reset failed")); return false; }
    Serial.println(F("[ESP] Set CWMode"));
    espSerial.println(F("AT+CWMODE=1")); if (!espSerial.find(findOK)) { Serial.println(F("[ESP] Mode failed")); return false; }
    Serial.println(F("[ESP] Connect to Router"));
    espSerial.print(F("AT+CWJAP=\""));
    espSerial.print(ssid);
    espSerial.print(F("\",\""));
    espSerial.print(pass);
    espSerial.println("\"");
    if (!espSerial.find(findOK)) { Serial.println(F("[ESP] WiFi connection failed")); return false; }
  }
  espSerial.setTimeout(3000);
  Serial.println(F("[ESP] WiFi is connected"));
  return true;
}

void WhiteWalnutApi::updatePlant(Plant& plant) {
  String site = String(API_PLANT) + "?action=get&code=" + String(plant.code);
  while (!httpRequest(site)) connectToWiFi();
  
  JsonObject& root = parseJson();
  if (root.success()) {
    plant.humidityDataPin = root["dataPin"].as<int>();
    plant.humidityCheckInterval = atol(root["interval"].as<char*>());
  }
}

void WhiteWalnutApi::sendHumidity(Plant& plant, int humidity) {
  String site = String(API_PLANT) + "?action=humidity&code=" + String(plant.code) + "&humidity=" + String(humidity);
  while (!httpRequest(site)) connectToWiFi(); // TODO: REMOVE RETURN
}

void WhiteWalnutApi::sendHeartbeat(Plant& plant) {
  String site = String(API_PLANT) + "?action=heartbeat&code=" + String(plant.code);
  while (!httpRequest(site)) connectToWiFi();
}

void WhiteWalnutApi::receiveActionFromStack(EcoActionBuffer& actionBuffer) {
  while (!httpRequest(String(API_ACTION))) connectToWiFi();

  JsonObject& root = parseJson();
  if (root.success()) {
    actionBuffer.entryNo = atol(root["entryNo"].as<char*>());
    actionBuffer.action = root["actionEnum"].as<int>();
    actionBuffer.pin = root["pin"].as<int>();
    actionBuffer.duration = atol(root["value"].as<char*>());
  }
}

void WhiteWalnutApi::updateActionOnStack(EcoActionBuffer& actionBuffer) {
  String site = String(API_ACTION) + "?action=processed&entryNo=" + String(actionBuffer.entryNo);
  while (!httpRequest(site)) connectToWiFi();
}

void WhiteWalnutApi::switchPin(int pin, bool value) {
  String site = String(API_ACTION) + "?action=switch&pin=" + String(pin) + "&value=" + String(value);
  while (!httpRequest(site)) connectToWiFi();
}

bool WhiteWalnutApi::httpRequest(String site) {

  // char* cmd;
  // sprintf(cmd, "GET %s HTTP/1.0\r\nHost: %s\r\nConnection: close", site, API_SERVER);

  /*
  String cmd = "";
  cmd += "GET " + site + " HTTP/1.0\r\n";
  cmd += "Host: " + String(API_SERVER) + "\r\n";
  cmd += "Connection: close";
  int cmdLength = cmd.length() + 4;
  Serial.println(cmd);
  */
  int cmdLength = 44 + site.length() + strlen(API_SERVER);
  
  // Serial.print(F("[MEMORY] "));
  // Serial.print(freeMemory());
  // Serial.print(F(" - "));
  // Serial.println(site);
  // -> 785 for external
      
  espSerial.print(F("AT+CIPSTART=\"TCP\",\""));
  espSerial.print(API_SERVER);
  espSerial.println(F("\",80"));
  if (!espSerial.find(findOK)) { Serial.println(F("[ESP] TCP Connection Error")); return false; }

  espSerial.print(F("AT+CIPSEND="));
  espSerial.println(cmdLength);
  // espSerial.println(strlen(cmd));
  if (!espSerial.find(findGT)) { Serial.println(F("[ESP] Send State Error")); return false; }
  
  espSerial.print(F("GET "));
  espSerial.print(site);
  espSerial.print(F(" HTTP/1.0\r\n"));
  espSerial.print(F("Host: "));
  espSerial.print(API_SERVER);
  espSerial.print(F("\r\n"));
  espSerial.print(F("Connection: close\r\n"));
  espSerial.println();
  // while (espSerial.available()) Serial.println(espSerial.readString()); return;
  if (!espSerial.find(findDP)) { Serial.println(F("Bytes not sent")); espSerial.print(F("AT+CIPCLOSE")); return false; }

  char status[32] = {0};
  espSerial.readBytesUntil('\r', status, sizeof(status));
  if (strcmp(status, "HTTP/1.1 200 OK") != 0) { Serial.print(F("[ESP] Unexpected response: ")); Serial.println(status); return false; } // Check HTTP status

  if (!espSerial.find(findHD)) { Serial.println(F("[ESP] Invalid response")); return false; } // Skip HTTP headers
  // if (!espSerial.find(findBT)) { Serial.println(F("[ESP] Bytes not found")); return; } // skip bytes (for http 1.1)

  return true;
}

JsonObject& WhiteWalnutApi::parseJson() {
  const size_t capacity = JSON_OBJECT_SIZE(3) + JSON_ARRAY_SIZE(2) + 60;
  DynamicJsonBuffer jsonBuffer(capacity);
  JsonObject& root = jsonBuffer.parseObject(espSerial);
  if (!root.success()) Serial.println(F("Parsing failed!"));
  return root;
}
WhiteWalnutApi.hArduino
#ifndef WHITEWALNUTAPI_H
#define WHITEWALNUTAPI_H

#include "Arduino.h"
#include "ArduinoJson.h"
#include "EcoActionBuffer.h"
#include "Plant.h"

class WhiteWalnutApi {
  public:
    static bool connectToWiFi();
    static void updatePlant(Plant& plant);
    static void sendHumidity(Plant& plant, int humidity);
    static void sendHeartbeat(Plant& plant);
    static void receiveActionFromStack(EcoActionBuffer& actionBuffer);
    static void updateActionOnStack(EcoActionBuffer& actionBuffer);
    static void switchPin(int pin, bool value);
    
  private:
    static bool httpRequest(String site);
    static JsonObject& parseJson();
};
#endif

Schematics

Communication Flow
Chart about the communication and interfaces
Alexaflow bsxlmk7zn9
Amazon Alexa Intents
All implemented intents you can ask Alexa, including the response.
(multilingual)
Alexaintents 22nhioja7k
Some Source Code of PHP integration
If you are interested in the entrance step of your alexa json parsing.
Alexaphp 9ydsy2fqpa

Comments

Similar projects you might like

PENXZYL: Arduino 3-Axis Brush Plotter

Project showcase by Guiye Perez Bongiovanni

  • 3,180 views
  • 1 comment
  • 13 respects

MyRiver

Project tutorial by Andrei Florian

  • 7,969 views
  • 2 comments
  • 33 respects

Detecting Heart Rate with a Photoresistor

Project tutorial by 3 developers

  • 6,405 views
  • 3 comments
  • 15 respects

Weather Forecast Clock Using Old Alarm and Arduino

Project showcase by LenkaDesign

  • 4,006 views
  • 10 comments
  • 21 respects

Photoresistor Characterization

by 3 developers

  • 2,123 views
  • 4 comments
  • 5 respects

u-blox LEA-6H 02 GPS Module with Arduino and Python

Project tutorial by Harshgosar

  • 1,312 views
  • 7 comments
  • 5 respects
Add projectSign up / Login