This is part ONE from a series of projects connected together.
In general the resulting solution consists of several parts which are:
- Sensors
- "Hubs"
- A webserver (including a database)
- Apps (universal Windows and Android)
The goal is to monitor my environment at home. For this series I cover 3 areas where I use different technologies for each single element.
Let's start with the conservatory (winter garden) and the garden next to it.
For this I use an ESP32 NodeMCU - a cheap device including
- Bluetooth (BLE is used in this case)
- WiFi (used to transfer the data to a different hub and act as web server)
- Several analog inputs (I use 5 of them)
- I2C - used to run a display
- Touch input - used as a kind of buttons
Approach one - get environmental data from the garden.
For this I use a sensor which is able to capture humidity (0-100%) and temperature (-40 to 60 °C). The sensor I use is an EE060 with analog output.
I choose this sensor because it is precise and robust, providing industrial grade operations even in harsh environments.
In my case it delivers 0-3V (different ranges are available). Although 0-10V is more common 0-3V is a good choice because the ESP32 analog inputs work for a range of 0-3.3V.
So the chosen sensor can be used without any extra wiring (e.g. voltage divider) by connecting it to the ESP32 analog inputs.
The ESP32 can be operated between 3.5 and 3.7V - or (using NodeMCU) at 5V.
There is also an EE060 Sensor which can operate at 3.6V but this model has an ouput of 0-1V. Later I will also add another sensor which needs at least 15V I decided to choose the 0-3V to have a wider analog range,
The modell I choose can operate at 8-30V DC - I use 18V because I have a matching power supply for this voltage.
I decided to supply my ESP32 via the USB port. This port is also used to load sketches tothe device and for serial (debug / informational) output.
As you know a project is never finished - so I thought it would be good to have easy access to the USB port with no need to open the case.
My solution looks like this:
This converter takes my 18V and provides 5V via a USB female port where I can plug in my ESP32 (normal operation mode). But I'm also able to use the plug and attach it to a development computer.
At the bottom of the case you can also see the back of display. I glued it to a little piece of plastic and screwed this to the case. Of course I could have used glue - but I use two of the screws (marked in the image above) as touch sensitive buttons.
The picture above shows the display with a protective foil on it. Before mounting to the case I removed this - instead I placed a piece of mobile phone screen protector on top of the blue plastic.
From the outside the case now looks like this:
The gray part on this picture show's a different sensor (EE800 - covered in the next part of this series). Inside the box is the ESP32 mounted on a breed board.
Let's have a look at this:
You can see the display connectors (4 pins); here the VCC (red) is moved a bit using the common plus rail to keep it seperate from the USB port.
You can also see the two wires connected to the screws (touch buttons - blue and green) and next (three pins left unconnected) are the analog inputs - two of them (green and yellow) are for the EE060 used in this post. Next to it are three analogs (whithe, gray, magenta) used by a sensor covered in part 2 of this series.
You can also see the USB cable connected to the box (DC / DC converter) which can be unplugged easily and connected to a PC.
On top of the box are the connections to the EE060 Sensor.
I used the same colors as in the EE060 datasheet.
Finally here is a simple overview of the wiring. The ESP32 on the image is not the used ESP32 NodeMCU - I placed the wires on the correct pin locations.
The used pins are:
- VCC 3.3V Red (display)
- ADC0 GPIO36 magenta (EE800)
- ADC3 GPIO39 brown (EE800)
- ADC6 GPIO34 white (EE800)
- ADC7 GPIO35 yellow (EE060)
- ADC4 GPIO32 green (EE060)
- TOUCH7 GPIO27 (screw - touch button)
- TOUCH6 GPIO14 (screw - touch button)
- Wire SCL GPIO22 orange (display)
- Wire SDA GPIO21 yellow (display)
- GND black
Using this library it was very easy to show some values on the screen. I decided to operate the screen in three modes.
- Off
- Weather display (showing the data from the sensor)
- Logging information
I'll switch between the modes by "pressing" my screws at the bottom of the screen.
To keep the programm logic simple I decided to build some kind of "virtual display" on top of the real display. This provides three methods:
- toggleDisplay (if on => off if off => on)
- printLogLine
- printData
With this approach I can just use the screen in my programm without the need to take care what actually should be displayed at the moment.
Touch buttonsThere are two ways to use the touch buttons. One is polling (read the values in a loop) the other is using an interrupt. I choose the second approach. To enable an interrupt for touch you must provide two paramters. The address of a method which is called when a touch event occures and the other is a threshold. Depending on the environment (case material, wiring,...) you'll get a value (for me about 70) when reading a touch input (not touched).
When you touch the pin (screw) you'll get a significant lower value. I simply run a little sample to check for the values. From that tests I choose a treshold of 30 to signal a good touch.
Interrupt routines generaly should return as fast as possible - so I just set a flag there. Which is checked in the main loop - simplyfied code:
void gotTouch1(){
flagOne=true;
}
void gotTouch2(){
flagTwo=true;
}
touchAttachInterrupt(T7, gotTouch1, 30);
touchAttachInterrupt(T6, gotTouch2, 30);
ESP32 and analog inputThe analog input of the ESP32 has some specialties - and one of the problems I noticed was about the same as describe in the first post here.
The values I read are far too low - at least in the lower voltage range. I found some narrowing solution for this. The good news - the deviation is almost the same on every pin and it is also relatively linear.
So using a voltage generator and a simple piece of code I figured out some formula which gives pretty good results for my needs here.
double DoCalc(int nVal) {
double dBase = 4100;
double dFactor = 0.00000004595;
double dCalcBase = 0.000801;
//calculate the offset to max
double dTmp = dBase - nVal;
//multiply with factor to compensate offset
dTmp *= dFactor;
//add the base (not used in offset calculation)
dTmp += dCalcBase;
return(nVal*dTmp);
}
Another fact is, that you can't use all ADC pins when using WiFi. Good luck here too - we need only 5 pins (2 at the moment - 3 in the second part) - they are available on ADC1.
BLE - GATTThere is are a lot of GATT specifications. One of the services is ESS (environmental sensing service) which is perfect for our needs.
The information provided on the linked pages sounds complicated? Let me explain how it works.
First I need a device (in the terms of hardware AND software) and a server which holds services. The hardware is our ESP32 board - and in the code I define a device and a server with 2 lines of code.
// Init the BLE Device
BLEDevice::init("ESP32-EE060");
// create the BLE Server
BLEServer *pServer = BLEDevice::createServer();
Now this server can (has to) implement services - I add a service with the ID 0x181A.
// create the BLE service
BLEService *pService = pServer->createService(BLEUUID((uint16_t)0x181A));
To that service I add two characteristics - temperature and humidity. Both can notify and are readable.
BLECharacteristic outHumidityCharacteristic(BLEUUID((uint16_t)0x2A6F),
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
BLECharacteristic outTemperatureCharacteristic(BLEUUID((uint16_t)0x2A6E),
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
To support notifications a characteristic must implement a special description (writeable) where a client can store the subscription state.
outHumidityCharacteristic.addDescriptor(new BLE2902());
outTemperatureCharacteristic.addDescriptor(new BLE2902());
You can also provide further descriptions (to the characteristic) like range, unit...
In this case I use one optional description - providing a "human readable" text so a common BLE client can display the value to the user.
BLEDescriptor outdoorHumidityDescriptor(BLEUUID((uint16_t)0x2901));
BLEDescriptor outdoorTemperatureDescriptor(BLEUUID((uint16_t)0x2901));
outdoorHumidityDescriptor.setValue("Humidity 0 to 100%");
outdoorTemperatureDescriptor.setValue("Temperature -40-60°C");
outHumidityCharacteristic.addDescriptor(&outdoorHumidityDescriptor);
outTemperatureCharacteristic.addDescriptor(&outdoorTemperatureDescriptor);
Last but not least I add the descriptors to the characteristics and attach the characteristics to the service. In the last step I start the service and advertising.
pService->addCharacteristic(&outHumidityCharacteristic);
pService->addCharacteristic(&outTemperatureCharacteristic);
pService->start(); //start service
// Start advertising
pServer->getAdvertising()->start();
Although the things may look complicated - they are easy to code.
After this setup if one of the values (temperature, humidity) changes you have to
- Set the value in the BLE characteristic
- Notfiy the client that the value changed
An easy job too - just format the data according to the specifications and provide it to BLE.
int16_t nTempOut... //value with 2 digits times 10 (15.32 becomes 1532)
//the function excepts a uint8_t pointer - we point to a int16_t so the size is 2
outHumidityCharacteristic.setValue((uint8_t*)&uHumOut, 2); //set new value
outHumidityCharacteristic.notify(); //notify client
Because I use "well known" BLE services / charateristics anyone can read the values using existing apps. One (linked in software section) is NRF Tool which shows:
And after connecting you can read values and description:
Another tool is BLE Tools which shows common GATT services / characteristics:
Because the ESP32 has WiFi I can provide the current values as a simple html page. I do this using a string with placeholders. When a client requests the page (surfs to the web server) I make a copy of the string, replace the placeholders with values and send the resulting string as response back to the client.
Data and dateWhen collecting environmental data it's important to know WHEN the measurement was taken. Thanks to WiFi this is just one extra line of code. The EPS32 can retrieve the current date/time from SNTP servers and provide it via the function "getLocalTime".
To start up this magic you just have to call a function telling the system the time zone (TZ and DST-offset) and which SNTP servers to use. On simple line of code.
configTime(_TZOffset * 3600, _DSTOffset * 3600, "pool.ntp.org");
To obtain the time I use a simple function:
tm TryToGetLocalDateTime() {
tm tmInfo;
if(!getLocalTime(&tmInfo)) { //on error provide valid defaults
tmInfo.tm_year = 100; //2000
tmInfo.tm_hour = 0;
tmInfo.tm_min = 0;
tmInfo.tm_sec = 0;
tmInfo.tm_mon = 1;
tmInfo.tm_mday = 1;
}
return(tmInfo);
}
ESP32 and a lot of things to doIn Arduino you normally use 2 functions - setup and loop.
In setup you prepare all the things (this functions runs once after startup) and in loop you do all your work.
To separate the things I decided to use tasks. This means a number of functions run "at the same time". ESP32 has two cores - so in theory 2 functions can run at the same time. Just don't care that much about what is really parallel and what is done sequentially. Assume everything is parallel and consider that when accessing common variables.
Let's separate the things we have to do:
- Read analog values
- Provide analog values via BLE
- Handle buttons
- Display data
- Display logging information
- Transfer data to "the cloud"
- Act as web server (serve a single page)
Let's start with the loop - I consider this as the main function. In the loop I'll check if a button was pressed - if so, I'll change the display (setting a flag). This can be done as (very simple) final state machine where pressing a button changes the state.
State==1 - display off, State==2 Display weather data, State==3 display log data.
Next is reading the analog values. This needs common data access – use a semaphore to control access. To simplify the things I use a method "readGardenValues" for reading / storing the values. I call this every 2 seconds while I check the buttons every 500ms (faster display switch).
The loop does:
- Check the buttons and switch the display if needed
- Display the data (or nothing if off)
- Every 4th cycle read the analog values (and update BLE)
- Sleep for 500ms
Next to the loop we have one thread to act as webserver, and 4 event / interrupt handlers which are:
- Button one pressed
- Button two pressed
- BLE client connected
- BLE client disconnected
For closer information check the (documented) source code. Comments / questions and suggestions are welcome.
Comments