In this project tutorial I will show you how to create:
- I2C Communication between two microcontrollers
- Websocket communication between microcontroller and ASP.NET Core server
- Android and Asp.Net Core server communication
I used two setups for this project, and I will cover both of them:
- ESP8266 and Arduino Uno ( my first project setup)
- ESP-01 and Arduino Nano (my second project setup)
The project was completed using the following technologies:
- Master/SlaveI2C connection between ESP8266 and Arduino Uno ( ESP-01 and Android Nano)
- Xamarin.Android for Android application
- Asp.Net Core Web APIwith WebSocket as middleware for real-time communication.
1. OPTION
- At first, I created direct communication between Android and ESP8266.
Pros: Easy to create.
Cons: Works only locally.
2. OPTION
- So, due that, I create another one, this time with the help of ThingSpeak. But I didn't like the 15-second Thingspeak period at all, because I didn't use it to collect data, but to control the actuators in real time.
Pros: Works globally (You can control it from anywhere in the world).
Cons: Not in real-time - ThingSpeak have 15 seconds period to collect and show data. Didn't like that.
3. OPTION
So, there was a third option - to create my own "middleware" server for using it between Android and Arduino. I created ASP.NET Core web app with websocket. I deployed server for free on SmarterASP.
Pros: Real-time communication from anywhere in the world.
Cons: I haven't found out yet.
FeaturesProject includes:
- Pet feeder
- Watering system
- Light
- Real-Time communication
- Reports and stats
I got idea for feeder from here. It uses just one servo motor so it's easy to build it.
Parts:
- S90G
- Case for food (something like Pringles tube)
- Stand for food case and S90G
- 5v power supply - I used L298N 5V output
The opening on both sides should be as large as possible, so that the food does not get stuck, so it is not the most reliable solution. But with all the possibility of monitoring the project, it is not a big problem. It will serve until the stepper motor arrives.
Operation mode:
- For now it's just manually.
NodeMcu receive a message, process it and sends to Arduino which controls the actuator.
#define SERVO_PIN 3
......
//in setup();
servo.attach(SERVO_PIN);
servo.write(4);
Value '4' here is initial value.
Feeding will be done with this code. I played with these values until I got the desired result.
servo.write(180);
delay(700);
servo.write(4);
Watering systemParts:
- 5V DC water pump
- L298N motor driver - connected to 12V power supply
- Old fish tank
- Water level detector (or soil moisture sensor)
Soil moisture sensors did not prove to be good - they corrode quickly. So I recommend real water sensor, or DIY version.
Operation mode:
- Auto - activates the pump when the water level is low, and stops it when it reaches a certain level - a full bowl.
- Manually - activate the pump with Android app, but it's going stop itself when it reaches a certain level - a full bowl.
- For emptying a tank, just remove sensor from bowl.
NodeMcu receive a message, process it and sends to Arduino which controls the actuator.
Water level is sended from Arduino to NodeMcu every 2 sec.
Define pins on top:
#define enA 9
#define in1 8
#define in2 7
And set all to outputs and LOW :
pinMode(enA, OUTPUT);
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
digitalWrite(in1, LOW);
digitalWrite(in2, LOW);
digitalWrite(LIGHT_PIN, LOW);
Code
// Set speed to max.
analogWrite(enA, 255);
// Start.
digitalWrite(in1, HIGH);
digitalWrite(in2, LOW);
int sensorLevel = analogRead(A3);
// Run it until it is full.
while(sensorLevel >= WATER_LEVEL_HIGH)
{
sensorLevel=analogRead(A3);
delay(150);
}
// Stop when it's full.
digitalWrite(in1, LOW);
digitalWrite(in2, LOW);
LightParts:
- 5v relay
- 220V Lamp
To be honest, this has nothing to do with pets, it is more to make my daily life easier - not to have to get up to turn off the light before going to bed.
NodeMcu receive a message, process it and sends to Arduino which controls the actuator.
I2C (Master/Slave connection)I2C is a serial bus interface connection protocol. It is also called as TWI (two-wire interface) since it uses only two wires for communication. Those two wires are SDA (serial data) and SCL (serial clock).
I2CAddressESPWifi is defined in both with the same value (8). It’s needed as part of the protocol ----> #define I2CAddressESPWifi 8
- Library used: "Wire.h"
Esp8266 part
- Setup() function:
Wire.begin(D1, D2); // Wire.begin(0,2) if it's ESP-01
- Send data to Arduino
Wire.beginTransmission(I2CAddressESPWifi);
Wire.write("Write something)";
Wire.endTransmission();
Arduinopart
- Setup() function:
Wire.begin(I2CAddressESPWifi);
Wire.onReceive(espWifiReceiveEvent);
Wire.onRequest(espWifiRequestEvent);
......
void espWifiReceiveEvent(int count)
{
// Do something.
}
void espWifiRequestEvent(){
Wire.write("Write something");
}
CommunicationThere are 3 parts of communication here:
1. Client 1 ( Android or PC) - Send commands to ESP8266 and Arduino trough middleware.
2. Middleware (Asp.Net Core app with websockets) - routes messages between clients in real-time and stores those messages in database.
3. Client 2 (ESP8266 and Arduino) - responds to Client 1 requests and periodically sends sensors reading.
First client and server are written in C#, third are Arduino sketches for ESP8266 and Arduino Uno.
Sketch for ESP8266 use this library. It includes good examples of how to use it.
In my project, I created one WebSocket object with servers IP and port. Servers websocket uri contains "ws://" before address, but in Arduino sketch, you enter address without it.
ESP8266 part
// Global variables
WebSocketsClient webSocket;
IPAddress serverIP(192, 168, 0, 1);
Websocket is started in setup function after connection to local WiFi network. Beside starting, it has options to add extra headers, set reconnect period, enable heartbeat, etc.
// I insert token in header
webSocket.setExtraHeaders(token.c_str());
// Starting
webSocket.begin(serverIP, 61955, "/");
//Main event
webSocket.onEvent(webSocketEvent);
// Reconnect after 15 sec. after closing connection
webSocket.setReconnectInterval(15000);
// Start heartbeat (optional)
// Ping server every 60000 ms
// expect pong from server within 10000 ms
// consider connection disconnected if pong is not received 2 times
webSocket.enableHeartbeat(60000, 10000, 2);
webSocketEvent is socket connection handler. Here we catch all necessary infos about our connection - read data, handle disconnection, connection etc..
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length){
switch (type){
case WStype_DISCONNECTED:
if (alreadyConnected)
{
Serial.println("Disconnected from websocket!");
alreadyConnected = false;
}
break;
case WStype_CONNECTED:
{
alreadyConnected = true;
Serial.print("Connected to websocket. ");
}
break;
case WStype_TEXT:
processPayload((char *) payload);
break;
case WStype_PING:
Serial.printf("[WSc] get ping\n");
break;
case WStype_PONG:
Serial.printf("[WSc] get pong\n");
break;
default:
break;
}
}
As you can see, I process payload in case WStype_TEXT.
Processing is done according to the type of message.
void processPayload(char * payload){
StaticJsonDocument<300> doc;
String jsonObject = String(payload);
auto error = deserializeJson(doc, jsonObject);
if (error) {
Serial.println("Error parsing object.");
return;
}
int messageType = doc["Type"];
writeToArduino(messageType);
readFromArduino(doc);
}
These two methods use Wire.h to send commands to Arduino and receive a response.
Arduino part
It's basic Arduino code for controlling motors and relay. Because I use Wire.h, it has 2 main events - Recieved and Request.
Recieve reads messages from NodeMCU, and Request send response after NodeMCU request.
In Recieved event, I set flags for starting motors and relay, and later check those flags in loop. If it's true - start it.
void espWifiReceiveEvent(int count){
byte value;
value = Wire.read();
process(value);
}
void process(int messageType){
if(messageType == (int)LIGHT_ON)
{
digitalWrite(LIGHT_PIN, HIGH);
waitingForResponse = true;
}
else if(messageType == (int)LIGHT_OFF)
{
digitalWrite(LIGHT_PIN, LOW);
waitingForResponse = true;
}
else if(messageType == (int)FEEDER_START)
{
servoState = true;
}
else if(messageType == (int)PUMP_START)
{
pumpState = true;
}
else if(messageType == (int)GET_VALUES)
{
requestType = (int)GET_VALUES;
}
}
Flags for request are setted after a task is finished, and when it's requested from Master, I send it based on those flags.
void espWifiRequestEvent(){
if(waitingForResponse)
{
Wire.write(DONE);
waitingForResponse = false;
}
else if(requestType == (int)GET_VALUES)
{
Wire.write(sensor); // send the lower 8 bits
Wire.write((sensor >> 8)); // send the upper 8 bits
}
else
{
-1;
}
}
Android and Web applicationYou can find code for android and web application in Code section. Code is explained with comments so you can create apps based on yours specific requests.
Comments
Please log in or sign up to comment.