Cold frame monitoring with an ESP8266 WebServer

In this post I will describe a monitor for a Coldframe (that is sort of a small unheated greenhouse).

It has the following functions/uses the following sensors:

  • A HTU21D to measure humidity and temperature inside the coldframe. The HTU21D is a very stable easy to use sensor….but after it has been exposed to 100% humidity for a while, it starts to behave funny, it may give you readings of over 100% or even negative readings. Not sure if this will leave longterm damage but I understand it can be ‘cured’ by  putting it in a 120 degree celsius oven for about an hour.
  • Four DS18B20’s to check temperature in the soil, the air outside the coldframe, the electronics as well as a reservoir.
  • A soil moisture sensor, consisting of  2 carbon rods (those are resistant against electrolysis). Though I could have used the A0 pin of the ESP8266 I am using, I found that one to be unreliable, so I am using a PCF8591 ADC for that. The PCF8591 is a reltively slow 8 bit ADC, but it suffices for the goal. If you want a fast one, consider the ADS1015.
  • A BH1750 to measure the incoming light.
  • A leaf wetness sensor (actually it is cheap chinese webshop ‘rainsensor‘). When used upside down it measures condensation, which is a measure for leaf wetness. I use the pcf8591 ADC to read it.
  • A solar panel as a backup power source. I use one channel of the pcf8591 to measure the voltage.
  • A rain meter (outside the cold frame of course) that I read with yet another channel of the pcf8591.
  • Time is set by a time server and after that kept by the ESP8266
  • A Switch, checking if the lid is open.

There are also three outputs:

  • A fan for ventilation. I have the fan blowing out air from the coldframe -in case it got too hot- but I probably will just put it in another position and use it simply for circulation. The fan is driven through a transistor driver.
  • A fan circulating hot air through an underground heat sink. This fan is autonomous, so there is no switch on the webserver-interface. It does two things: if the coldframe is getting too hot, it will blow that air through the heatsink, thus storing it. If the coldframe gets too cold and the  temperature of the heatsink is higher, it will push the warmth of the heatsink into the coldframe. The heatsink is an underground space filled with stones, throughwhich I have led a pipe.
  • A motor to open the lid of the cold frame. The motor is driven through a MOSFET
    Credit where credit is due, the code around the outputs ‘I stole’ from randomnerdtutorials and modified it for my own use

The reporting and control is done as follows:

  • A webserver connected to the home network (ESP8266 in STA mode)
  • A stand-alone webserver (ESP8266 in AP mode)
  • Thingspeak
  • A MySQL/MariaDB database on a separate computer (e.g. a raspberry). This is also  based on some articles by randomerdtutorials.
  • Googlesheet.
  • Telegram messages: I use Telegram to report the min and max temperature every day at 6 a.m.  It also sends me a message once the temperature goes over a set value. In future I may start to use it to control the Outputs described above

The processor used is a Wemos D1 mini, It might be wise though to get a Wemos D1 mini Pro or an ESP8266-07 board as that has an antenna. Could come in handy, depending where in the garden you place the coldframe.
One more remark about the HTU21D, both the sparkfun library and the adafruit library are rather ‘spartan’ in the use of the HTU21D sensor. It has the possibility of heating up the sensor to prevent condensation. I have added some code to switch that on when the sensor is close to 100% humidity, but only during a few hours in the night. When that internal heating is switched on the temperature is shown in red, like shown below

HTU21D Heated
HTU21D Heated

Construction.
it is not my intention to fully describe how to make a coldframe (I may in future on ‘instructables’), as I presume everybody can make a box with a lid from glass. The one I made is not perfect as it only lets in light from above. I built it during the corona lockdown and had to make do with what I had. But i do want to point out a few details: I put all the electronics in a wooden box with plastic lid, that I just hung in the Coldframe. It has the BH1750 (cover temporarily removed for the picture) on top of it

For the back and one side I used polystyrene, covered with duct tape. In my experience that holds up quite long against the elements.
The construction to open the lid took a lot of pondering. Most of the linear actuator solutions required me to attach the push rod  to the lid…which would impede manual opening. In the end I decide for an egg shaped disc that is eccentrically connected to a motor so when it turns it opens and lowers the lid. make sure to apply some grease on the turning part.

For those not familiar with cold frames: The main ‘danger’  of a cold frame is that it gets too hot during the day. Opening the lid is essential then. It is easy to automate that: insert a line that checks the temperature against a set value and start the motor. As I have chosen to let the motor turn one direction that would mean let it turn for a few seconds and then stop it. I could fine-tune that by adding a position switch,  or even use an H-bridge (requiring an extra pin), but as I have not been on holiday much during the lockdown, I have chosen to keep it manual.

To measure the outside temperature I put a DS18B20 in a piece of conduit pipe sprayed black  on the top half. the idea behind that is that the top will heat more than the bottom, causing an updraft, so the DS18B20 that is located in the bottom will always get fresh outside air

The code:
The core of the code is an Asynchronous webserver that presents the sensordata via server events, every 10 seconds. To prevent the ‘on-start’ values being empty I do an initial replace of the placeholders with the freshly read sensordata. and to make sure I also start with a negative number for the timer so the first 10sec update comes almost immediately.

If one wants to fill a webpage with data there are several approaches: 1. build the webpage, then read the sensors and fill them in; 2. Read sensors while building the webpage and fill them in step by step; 3 read all the sensors first.
I have chosen for the latter.

The webserver contains several icons. I have used 3 ways to put those icons there: 1. from the fontawesome library; or of not available there: 2. directly inserted with base64 codes; or 3. from SPIFFS.
I did that mainly to play around with different techniques, but to use SPIFFS just for 1 picture might be a bit silly and you may consider to not do that. (Edit: I did away with the SPIFFS picture, just picked another one from fontawesome)

The code keeps track of max and min temperature during a 6 a.m to 6 a.m. 24 hr timeslot. Should you lose power or update the code in between via OTA, or if for whatever reason the processor resets, the kept min and mac temperatures are lost. It is easy to prevent that by storing them in SPIFFS (but you will rapidly wear  out the EEPROM), or RTC memory (in a 4 byte per value block). The RTC memory survives reset but not a power outage. I just did not find it important enough to bother with and it introduces some other problems. Mind you that rtcData stored in the first 32 blocks will be lost after performing an OTA update, because they are used by the core internals.

Thingspeak
The thingspeak code is very straightforward. You will need to open a Thingspeak account though and note the channel number and write API code to insert in the program.

coldframe-thingspeak

When using Thingspeak, one can install one of the many Thingspeak apps (such as Thingshow, Thingview or Thingviewer)on one’s phone for easy acces to the data.

Thingshow

MySQL
In order to use this you will need to setup MySQL or MariaDB on a server somewhere. A raspberry Pi would be suitable for that. Earlier I referenced to two articles on randomnerd tutorials that describe how to set up a MySQL  database, no need to reinvent the wheel here, although I did adapt the code a bit.
coldframe-mysql

Googlesheet
You wil need to setup Googlesheet for that as I have described here. You will find an example here too.

Telegram
You will need to setup a Telegram account and create a bot. I describe the process here.

InfluxDB
I did not put my data in influxDB, but if you want it is rather easy to do that with a POST statement.
Add code like this:

#include <ESP8266HTTPClient.h>
HTTPClient ifhttp;
const char* influxUrl = "http://localhost:8086/write?db=mydb";
const char* influxUser = "username";
const char* influxPassword = "password";
String influxData = "";

void (influxDB()
{
influxData ="........" ---> add your fields and data here


// Create POST message
ifhttp.begin(influxUrl);
ifhttp.addHeader("Content-Type", "application/x-www-form-urlencoded");
ifhttp.setAuthorization(influxUser, influxPassword);

// The first attempt to send returns -1
int httpCode = -1;
while(httpCode == -1){
httpCode = ifhttp.POST(influxData);
http.writeToStream(&Serial);
}
ifhttp.end();
}

There is also a library for InfluxDB. The program below fills a database ‘garden’ and a set called ‘bed’ with (random) values for moisture, temperature, light and barometric pressure

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <InfluxDb.h>
#define INFLUXDB_HOST "YourInfluxdbIP" //Enter IP of device running Influx Database
#define WIFI_SSID "YourSSID" //Enter SSID of your WIFI Access Point
#define WIFI_PASS "YourPW" //Enter Password of your WIFI Access PointESP8266WiFiMulti WiFiMulti;
Influxdb influx(INFLUXDB_HOST);

void setup() {
Serial.begin(115200);
WiFiMulti.addAP(WIFI_SSID, WIFI_PASS);
Serial.print("Connecting to WIFI");
while (WiFiMulti.run() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
influx.setDb("garden");
Serial.println("Setup Complete.");
}
void loop() {
InfluxData row("beds");// measurement name
row.addTag("bedname", "bed1");//tag
row.addValue("moisture", random(500,800));
row.addValue("temperature", random(10, 40));
row.addValue("light", random(1100,1400));
row.addValue("pressure", random(980, 1023));
influx.write(row);
delay(20000);
}

These can be viewed in Grafana (full explanation on how to do this here)

grafanadas

Carriots
Carriots, now called Altair Smartworks is an IoT data collection and processing site. It allows you to store your data and have it processed and attach specific actions to it. It might be useful and is fairly easy to add. Check here.

The code can be updated via website OTA. For development serial OTA is the easiest, so if that is what you prefer: I left the original serial OTA statements in so it is easy to convert back.

Download
You can download the code here. A remark though: it is my ‘as is’ working code and while developing it, I may have put in some unnecessary things and comments and also my variables might not have been grouped logically. Normally i clean up code before publishing it….but that means I have to retest it again and i just did not have the time for that right now. I may in future. But as said, it is a working code. You do need to add your own data such as passwords and API’s though. I am in the process of rewriting the code from the ground up,based on experience gained with the using the  ColdFrame for some time

————————————————————

 

Experience after a few weeks:
In general the system functions well. The cooling/heating system was able to keep the temperature above 0 degrees celsius when the temperature outside dropped to minus 6 at night for a few hours. It had a bit more trouble when it was freezing minus 6 the entire day. Mind you though that a relatively small object is harder to keep warm because of the unfavorable volume vs. surface ratio.

The HTU21D was a bit of a disappointment. According to the datasheet, the humidity operating range is 0-100% and I have no reason to doubt that. However, after continued exposure to 100% RH, condensation within the sensor can play havoc on the results. RH values above 100% or below 0% are possible then. Robert Smith in fact mentions this in his excellent comparison of various humidity sensors. He though sees it as a possibility to “do your own calibration”. I think though that if the HTU21D jumps from say 114% to -3% within 10 secs, there is more at large than the calibration not being fine tuned.

The question though is whether the condensation also influences the temperature readings. I do not think so, but I might add another DS18B20 just to make sure. I doubt if another humidity sensor would do better. Measuring near the upper limit of the range for a prolonged period seems to be problematic for all. The problem is the condensation in the sensor and no doubt other sensors, like the BME280 would suffer the same fate (eventhough the BME280 seems to use different type of sensor (resistive vs capacitive)). Also here, the relatively small size of the box may play a role as a relatively low amount of water will easily raise the potential humidity.
Ventilation can help, but it is a 2 edged sword: ventilation will generally also drop the temperature, which will make the relative humidity go up. Also the 100% humidity would normally occur when the temperature would go down to 6-7 degrees or lower, i.e. usually at night, and that is the moment you do not want the temperature to drop any lower.
Alternatives: BME280, Sensirion SHT4x or SHT71, HDC1080, AHT20 (datasheet mentions in fact a drift if subject to prolonged >80%RH). The Si7021 is practically identical to the HTU21D (in fact you may get the one if you order the other at less than reputable shops). They are only distinguishable by the serialnumber that supposedly is 60 for the HTU21D and 57 for the Si7021.

The BH1750 functioned well, it provided good information on what hours were most sunny on the spot the coldframe was standing.. It had one quirk though, occasionally (though very rarely), it would display the default value of 56412 Lux for a while and then it would be OK again ( did not find any fault in the wiring). A full reset would normally ‘cure’ it as well.

Obviously not a matter of just ‘finetuning’

The reporting through Telegram worked fine. Only 2 little ‘annoyances’ When the temperature ‘hoovers around 0 degrees or 25 degrees celcius (the lower and upper limit warning levels), I could get a couple of warnings in a row: I get a warning when the temparature dropped below zero, then when it would briefly rise above zero again the warning flag would be reset and subsequently if the temperature would drop below half a minute later I would get a warning again. It is basically the system doing what it was told to do. I could ‘solve’ that but not allowing a second warning within say 5 minutes, but I really did not find it important enough.

The leaf wetness sensor functioned ‘moderately’. Somehow the surface was not as appealing to condensation as the glass top (or the HTU21D sensor for that matter). As I still consider it important info, I will try to find another solution for it.

The combination of the webserver info that is basically à la minute (every 10 secs to be precise) and Thingspeak/MySQL/GoogleSheet that gives an historic overview worked very well.

Improvements
Apart from the HTU21D and leaf wetness sensor I have to look at/reconsider, there are a few things I may add: currently experimenting with a TCS34725 sensor to see if I can monitor ‘green development’.
I am considering if I should add a moveable setpoint for the cooling fan to set in, but it may not be important enough.
Currently I have 4 ADC channels through the PCF8591. I use one of those channels for soil moisture. If I want to use the system in a larger coldframe, or a greenhouse, I may need to add some ADC’s.
The current soil sensor I use (carbon rods) works fine, albeit that the thickness and bluntness of the rods can make it hard to stick it in a pot, so I may decide for a slightly leaner and sharper capacitive sensor, or just use metal nails. The latter though would require a transistor to switch off the current when not used (in order to minimize electrolyses), which takes an extra pin of the ESP8266.
I may expand the function of the lid opener to automatically stop when the lid is open or the lid is closed.
Also considering to add Tweets for notification next to Telegram


If you have a Thingspeak account, tweets can be sent either from your ESP via “thingtweet” or directly from Thingspeak.

I did add an emergency heating in the mean time. It is set to kick in if the temperature drops below 0 degrees, in spite of the underground heatsink providing heat. It is basically a relay that can switch on a heater. In my cold frame it is a resistor heater, if used in a greenhouse it could be a dedicated electrical heater. With seemingly only 2 projected frost days remaining till mid May, I will need to wait with proper testing.

Coldframe Aircon

When the Coldframe is getting too hot, the fan will pull hot air through the underground heatsink that has a rather steady temperature 0f 8-12 degrees Centigrade. When it is getting too cold, the same fan will pull cold air through the underground heatsink which then will warm the air. If that is not enough to prevent coldframe temperature to stay above zero, a heater above the fan wil kick in. I made the heater from 24 half-Watt resistors that are pushed to their limit, producing 12 Watt in heat. The main aim is to keep the temperature above freezing, not to heat the coldframe to say 20 degrees. As said, this is still experimental stage.
It happened to me once that the 6 a.m. telegram message was not received. Checking my SQL database and Thingspeak showed the connection was briefly dropped around 6 a.m. As I do not do any error checking on the connection with Telegram (or MySQL for that matter), the program is not aware of the message not being received.
If it is important to you, the result of bot.sendMessage(chat_id, bottext, "HTML"); is “0” if the transmission failed and “1” if it succeeded. You could do a check on that and resend if necessary. I am using an insecure connection with Telegram by specifying “setInsecure” in the setup. That’s easier and I am not sending top secret info. If you want the connection to be secure, you can do that by stating:

X509List cert(TELEGRAM_CERTIFICATE_ROOT);
WiFiClientSecure botclient;
UniversalTelegramBot bot(BOT_TOKEN, botclient);

Experience after a few months
When summer came, I had trouble loading the coldframe’s website on my network. As I still could load the the AP website when I was close to the coldframe, I was suspecting something was blocking the connection to the router. The most likely suspect was a row of potato filled containers between the coldframe and my house. When those grew into firm plants it isn’t unlikely to think these walls of moisture blocked the signal. Yet the connection to Thingspeak as well as my mySQL server seemed not to suffer.

Granted, I do not really use the coldframe in the summer, yet it might be wise to use a Wemos Pro (or ESP8266-7) with external antenna and place a router or WiFi extender in a room directly facing the garden (currently it connects to a router on the opposite side of the house, 1 floor up).

Also, when I will build another coldframe, I will try to find a way to remove the electronics from the box in an easier way, e.g.by making external plug connections with the ventilators, motors and some of the sensors. and I will start streamlining the code a bit more.

Connect ESP8266 with Thingspeak through LUA: Analog pin and Internal ReferenceVoltage

esp8266-thingspeakluaConnecting your ESP to Thingspeak with a program written in your Arduino IDE is quite easy and those programs come a dime a dozen.
However, when I went to look for a program that did the same in LUA I came a cross a number of programs that simply didn’t work.
So I had to give it a go myself.
The below program is more or less a skeleton for thingspeak connection. It reads two values and posts those on Thingspeak. As it was just for testing I didnt want to add a lot of sensors, so th eonly two values read are the Internal reference voltage and the analog value from ADC0.
To make the program easily adaptable, I readthe sensors in a seperate function.
If you want to add a variable resistor to test the ADC, mind you that it can only take 1 Volt input so you need a voltage divider that limits it to 1 volt instead of 3.3 Volt.
When using the program dont forget your network credentials and  Thingspeak API Key

WRITEKEY="T3I6T9YF67JE527" -- set your thingspeak.com key
wifi.setmode(wifi.STATION)
wifi.sta.config("YourSSID","YourPW")
wifi.sta.connect()
tmr.delay(1000000)
volt=0 --Internal reference voltage
ana=0 -- analog port A0 Mind you, this one has a 1 Volt max

--read sensor
function ReadSensor()
volt = node.readvdd33()
ana=adc.read(0) -- 1V=1023
print("Internal Ref.Voltage: " ..(volt/1000).."."..(volt%1000).." Volt")
print("Analoog: "..ana)
end 

-- send to https://api.thingspeak.com 
function sendTS() 
conn = nil
conn = net.createConnection(net.TCP, 0)
conn:on("receive", function(conn, payload)success = true print(payload)end)
conn:on("connection",
   function(conn, payload)
   print("Connected")
   conn:send('GET /update?key='..WRITEKEY..'&field1='..(volt/1000)..'.'..(volt%1000)..'&field2='..ana..'HTTP/1.1\r\n\
   Host: api.thingspeak.com\r\nAccept: */*\r\nUser-Agent: Mozilla/4.0 (compatible; esp8266 Lua; Windows NT 5.1)\r\n\r\n')end)
conn:on("disconnection", function(conn, payload) print('Disconnected') end)
conn:connect(80,'184.106.153.149')
end

ReadSensor()
sendTS()
tmr.alarm(1,6000,1,function()ReadSensor()sendTS()end)

Now I haven’t been completely honest here, as it isnt really good practice to read the internal Voltage and the A0 port as it reads either the one or the other.
When using the  A0 to read internal voltage  in the Arduino IDE rather than in LUA ADC_MODE(ADC_VCC); in combination with ESP.getVcc() is now the correct way to do it. Using readvdd33 causes problems

Adding a PCF 8591 ADC/DAC to ESP8266-01

ad-da-converter-module
YL-40 module

The ESP8266-01 is a great WiFi enabled  microcontroller but it only has 4 I/O pins broken out. Fortunately it does support I2C protocol so in spite of  the low number of pins, there still is a lot of hardware that can be added.
As the ESP8266-01 has no analog inputs at all, adding an ADC to it is something I wanted to do. After all,  sensors as LDR or NTC are still analog.
The PCF8591 is such an ADC: it is  a single-chip, single‑supply low‑power 8‑bit CMOS data acquisition device with four analog inputs, one analog output and a serial I²C‑bus interface. Three address pins A0, A1 and A2 are used for programming the hardware address, allowing the use of up to eight devices connected to the I²C‑bus without additional hardware. I doubt whether I need more than 8 of those chips. Lets just start with one. Although one can get the individual chip, I have chosen for a module that actually already has some sensors on it:

  • AIN0 – Jumper P5 – Light Dependent Resistor (LDR)
  • AIN1 – Jumper P4 – Thermistor
  • AIN2 – Not connected
  • AIN3 – Jumper P6 – Potentiometer

pcf8591-circ
YL-40 circuit

The I2C address of the PCF8591 is determined by the pins A0-A2. As they are close to the Ground pin, let’s start with grounding them (as they are on the module).
pcf8591adresThe address is 1001A2A1A0. With A2-A0 being LOW, that is 1001000=0x48.

base A2A1A0 Hex Dec
1001 000 48 72
1001 001 49 73
1001 010 4A 74
1001 011 4B 75
1001 100 4C 76
1001 101 4D 77
1001 110 4E 78
1001 111 4F 79

In some programs you will see the address as “0x90>>1” Which is 48 as well. The “0x90” counts the LSB of the 8 bit address, which is the R/W bit. With the Write Bit Low (=active) the full address is 10010000=0x90, but the rightshift 1 removes the LSB again, making it 0x48.
The module is hardwired to 0x48 as the three address lines are soldered to ground. So if you would want to use more than one module on the same I2C port you would need to do some de-soldering (or use   bare PCF8591 chips ofcourse).

Sunfounder board (expensive)

A similar but needlessly more expensive, non configurable board without the sensors is the sunfounder board.

Mini PCF8591 AD DA Shell Module

There is a (more expensive)  fully configurable module, that allows to set the  I2C address with jumpers. That module is daisy chainable with other I2C modules in the same range (there is for example a PCF8547 digital I/O module with similar connections). Making a module yrself is not hard either:

PCF8591
PCF8591 configured as 0x48

The control byte sets the operating mode of the PCF8591 and is described in section 7.2 of the datasheet, The upper nibble of the control register is used for enabling the analog output, and for programming the analog inputs as single-ended or differential inputs.
The lower nibble selects one of the analog input channels defined by the upper nibble. If the auto-increment flag is set the channel number is incremented automatically after each A/D conversion.
If the auto-increment mode is desired in applications where the internal oscillator is used, the analog output enable flag in the control byte (bit 6) should be set. This allows the internal oscillator to run continuously, thereby preventing conversion errors resulting from oscillator start-up delay. The analog output enable flag may be reset at other times to reduce quiescent power consumption.

As it is not my intention to explain the full innerworkings of the PCF8591, but just to show it is working with the ESP8266-01, I will skip a full technical discussion. For now it is enough to know that the PCF8591 can be read byte for byte, but it can also be read in ‘burst mode’, in which we read the 4 analog values all at once. The program I present is burstmode with autoincrement of the address. The reason we read 5 bytes instead of 4 is because the first byte contains old data. As the datasheet states in paragraph 8.4: “The first byte transmitted in a read cycle contains the conversion result code of the previous read cycle.”

#include "Wire.h"
int PCF8591=0x48; // I2C bus address
byte ana0, ana1, ana2, ana3;
void setup()
{
 Wire.pins(0,2);// just to make sure
 Wire.begin(0,2);// the SDA and SCL
}
void loop()
{
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(0x04); // control byte: reads ADC0 then auto-increment
 Wire.endTransmission(); // end tranmission
 Wire.requestFrom(PCF8591, 5);
 ana0=Wire.read();// throw this one away
 ana0=Wire.read();
 ana1=Wire.read();
 ana2=Wire.read();
 ana3=Wire.read();
}

Obviously when you have these values read you will need to do something with them: print them out, put them on yr own webpage or upload them to e.g. Thingspeak. As printing from the ESP8266-01  is not always easy, I will show you how to upload the values to Thingspeak:

#include  // ESP8266WiFi.h library
#include "Wire.h"
int PCF8591=0x48; // I2C bus address
byte ana0, ana1, ana2, ana3;

const char* ssid     = "YourNetworkSSID";
const char* password = "YourPassword";
const char* host = "api.thingspeak.com";
const char* writeAPIKey = "YourWriteAPI";

void setup() {
  // Initialize sensor
 Wire.pins(0,2);// just to make sure
 Wire.begin(0,2);// the SDA and SCL

//  Connect to WiFi network
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}

void loop() {
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(0x04); // control byte: reads ADC0 then auto-increment
 Wire.endTransmission(); // end tranmission
 Wire.requestFrom(PCF8591, 5);
 ana0=Wire.read();
 ana0=Wire.read();
 ana1=Wire.read();
 ana2=Wire.read();
 ana3=Wire.read();

// make TCP connections
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    return;
  }

  String url = "/update?key=";
  url+=writeAPIKey;
  url+="&field1=";
  url+=String(ana0);
  url+="&field2=";
  url+=String(ana1);
  url+="&field3=";
  url+=String(ana2);
  url+="&field3=";
  url+=String(ana3);
  url+="\r\n";
  
  // Request to the server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" + 
               "Connection: close\r\n\r\n");
    delay(1000);
}

DAC
The PCF8591 does not only have  4 ADC channels but also 1 DAC channel. Writing to the DAC is as follows:

#define PCF8591 (0x48) // I2C bus address
void setup()
{
 Wire.pins(0,2);// just to make sure
 Wire.begin(0,2);
}
void loop()
{
 for (int i=0; i<255; ++i)
 {
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(0x40); // turn on DAC b1000000
 Wire.write(i); 
 Wire.endTransmission();
 }
}

Calculations
A the board normally uses the 3.3v supply as the reference voltage:
The input voltage is determined with:
vIn = value * (3.3 / 255)
and the output voltage is:
vOut = (value / 255) * 3.3
or to find the value for a given voltage:
value = (vOut / 3.3) * 255

So if for instance I would write  the value of 50 to the DAC. The voltage would be: 0.64V
In order to test that, I hooked up Aout to the A0 of an ESP 8266-12 and found a value of 192.
To calculate that to a voltage that is  (192/1023)=0.18V.  (Remember, the ESP gives readings from 0-1023, whereas 1023 being 1 Volt.)  So that is not particularly close.
Oddly though when I hooked up the Aout to AIN2 or AIN1 that didn seem to give reliable readings. A value of 50 written to Aout should give a value of 50 on AIN, but oddly it didnt

In my version of the board the NTC channel only varied between 255 and 254, changing the temperatuur didnt seem to have any influence, but removing jumper P4 made the value go all over the place so i presume the channel is ok, and only the NTC might not be OK.

Differential Input
The PCF8591 is capable of more, it is  for instance possible to do differential measurements.
Suppose you want to  measure the difference between AIN0 and AIN1.
You do that as follows:

#include "Wire.h"
int PCF8591=0x48; // I2C bus address 
int Raw = 0;
float Voltage = 0.0;

void setup()
{
 Wire.pins(0,2);// just to make sure
 Wire.begin(0,2);
  Serial.begin(9600); //Not on an ESP8266-01 
  Wire.beginTransmission(PCF8591); // Wake up PCF8591
  Wire.write(0x11); // control Byte for differential input mode 
  Wire.endTransmission(); //
}
void loop()
{
  Wire.requestFrom(PCF8591, 1); // Get Data from channel 1

 RawValue=Wire.read();
 Voltage = (Raw * 3.3 )/ 255.0;
 Serial.print("Raw ADC = ");
 Serial.print(Raw);
 Serial.print(" Voltage = ");
 Serial.println(Voltage,3);
 delay(1000);
}

If you want to hook up this chip to a Raspbery Pi, have a look here.
There are other ways of expanding the ADC capabilities of the ESP8266:

 

Using the 4 pins of the ESP8266-01

esp8266_henhouseThe limited number (4) of GPIO pins on the ESP8266-01 may seem like an obstacle, for any serious application.

Yet if one uses the pins in a smart way it is very well possible to do a lot with only those 4 pins.
In some of my recent postings, I have shown the use of a DHT11 a DS18B20, an OLED, an RTC and a BMP180 with the ESP8266-01.
In this posting I set out to use 4 sensors and a display, while also uploading the acquired data to Thingspeak. It actually is expanding on a project of monitoring the atmosphere in and around my chicken coop. Yes, you could call this a weatherstation, but it is just to illustrate the use of the 4 pins, you could easily make something else this way

I will be using 2 pins for I2C (BMP180 and OLED)
1 pin for 2 DS18B20 sensors via the OneWire protocol
1 pin for the DHT11
Although the ESP8266-01 now has all its pins used, I can still add more sensors (or actuators) through the OneWire protocol and/or via the I2C protocol.

So, what do we need:
BOM

  • ESP8266-01
  • 2x DS18B20
  • 1x DHT11
  • 1x BMP180
  • OLED (optional)

and ofcourse a breadboard, a 3.3 V PSU and some breadboard wires and a Thingspeak acount

Just some remarks regarding the BOM:

  • ESP8266-01
    Obviously the project is about utilizing the limited pins of the ESP8266-01, but if you still need to buy one, you could consider an ESP8266-12 that has more pins
  • DHT11
    A cheap all purpose humidity and temperature sensor. It is not hugely accurate but it will do. If you still need to buy one, you could opt for the DHT22 that is supposedly more accurate, but you could also opt for the AM2321. That is a sort of DHT22 that is suitable for I2C, thus freeing another pin
  • BMP180
    measures temperature and Airpressure. It is the successor of the BMP085, but it also now has some successors itself. There is the (cheaper) BMP280, but you could also opt for the BME280 that measures temperature, airpresure AND humidity. That way you can save on the DHT/AMS sensor
  • OLED
    I just used that so I quickly could see whether the sensors were read, but you could just as well check it on Thingspeak. The OLED is too small anyway to print all the read values

The circuit

weerstationThe 4 pins of the ESP8266 are not indicated as such on the PCB, and most images only clearly state GPIO0 and GPIO2.
However the ESP826-01 has a a GPIO1 pin (the Tx pin) and a GPIO3 pin (the Rx pin).
i will be using those pins as follows

  • GPIO0 -> SDA pin of the I2C port
  • GPIO1 ->DHT11
  • GPIO2-> SCL pin of the I2C port
  • GPIO3-> OneWire Bus

As my I2C modules already have pull up resistors, I will not add any I2C pullup resistors there. The DS18B20 still needs a pull up resistor for which I used a 4k7, but it is really not that critical, a 10k is also good. The DHT11 supposedly also needs a pull-up resistor but I found it to work without one as well. adding a 4k7 resistor didnt change any of the readings, so I left it out. Many of the 3 pin DHT11 modules, already have a 10 k soldered onto the module.

I just realized that I didnt draw the connections for the OLED. That is because I only hooked it up for a quick check, but should you want to add it, it is just a matter of connecting SDA to SDA and SCL to SCL… and ofcourse the ground and Vcc pins to their counterparts

The program is quite straightforward. First it sets up the libraries and the sensors.
It attaches the DHT11 to pin 1 (Tx) and the OnWire bus for the DS18B20 to pin 3 (Rx). In order to use more than 1 DS18B20 sensor on the OneWire bus, you need to know their ‘unique adress’. If you do not have that then you need a program to read those addresses. Do that on an arduino for ease.

In the program you still have to provide your WiFi credentials as well as the write API for your Thingspeak Channel

 

/*
   Field 1 temp roost (DHT11)
   Field 2 humidity roost (DHT11)
   field 3 Coop temperature (DS18B20)
   field 4 soil temperature (DS18B20)
   field 5 Airpressure (bmp180)
   field 6 Outside temperature (bmp180)
 * */
#include <DHT.h>
#include  <OneWire.h>//  http://www.pjrc.com/teensy/td_libs_OneWire.html
#include <DallasTemperature.h> //   http://milesburton.com/Main_Page?title=Dallas_Tem...
#include <Adafruit_BMP085.h>
#include <ESP8266WiFi.h>
#include "SSD1306.h"
SSD1306  display(0x3c, 0, 2);

#define DHTPIN 1  //GPIO1 (Tx)
#define DHTTYPE  DHT11
#define ONE_WIRE_BUS 3 // GPIO3=Rx

const char* ssid     = "YourSSID";
const char* password = "YourPassword";
const char* host = "api.thingspeak.com";
const char* writeAPIKey = "W367812985"; //use YOUR writeApi

//DHT11 stuff
float temperature_buiten;
float temperature_buiten2;
DHT dht(DHTPIN, DHTTYPE, 15);

//DS18b20 stuff
OneWire oneWire(ONE_WIRE_BUS); //oneWire instance to communicate with any OneWire devices
DallasTemperature sensors(&oneWire);// Pass address of our oneWire instance to Dallas Temperature.
DeviceAddress Probe01 = { 0x28, 0x0F, 0x2A, 0x28, 0x00, 0x00, 0x80, 0x9F};
DeviceAddress Probe02 = { 0x28, 0x10, 0xA4, 0x57, 0x04, 0x00, 0x00, 0xA9};

// bmp180 stuff
Adafruit_BMP085 bmp;

void setup() {
  //I2C stuff
  Wire.pins(0, 2);
  Wire.begin(0, 2);
  // Initialize sensors
  //dht 11 stuff
  dht.begin();
  //ds18b20 stuff
  sensors.begin();//ds18b20
  // set the resolution to 10 bit (Can be 9 to 12 bits .. lower is faster)
  sensors.setResolution(Probe01, 10);
  sensors.setResolution(Probe02, 10);
  //bmp180 stuff
  if (!bmp.begin()) {
    //   Serial.println("No BMP180 / BMP085");
    //   while (1) {}
  }

  //OLED stuff
  display.init();
  display.flipScreenVertically();
  display.setFont(ArialMT_Plain_10);
  delay(1000);

  //  Connect to WiFi network
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}

void loop() {
  //ds18b20stuff-------------------
  sensors.requestTemperatures(); // Send the command to get temperatures
  temperature_buiten = sensors.getTempC(Probe01);//
  temperature_buiten2 = sensors.getTempC(Probe02);//
  //dht11 stuff--------------------
  float humidity = dht.readHumidity();
  float temperature = dht.readTemperature();
  if (isnan(humidity) || isnan(temperature)) {
    return;
  }
  //bmp stuff-------------------------
    String t= String(bmp.readTemperature());
    String p=String(bmp.readPressure());
  //OLED stuff--------------------------
  display.clear();
  display.drawString(0,10,p);//bmp pressure
  display.drawString(0,24,String(temperature_buiten));//ds18b20
  display.drawString(0,38,String(humidity));//dht11
  display.display();

  // make TCP connections
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    return;
  }

  String url = "/update?key=";
  url += writeAPIKey;
  url += "&field1=";
  url += String(temperature);// roost (DHT1)
  url += "&field2=";
  url += String(humidity);// roost (DHT11)
  url += "&field3=";
  url += String(temperature_buiten);//coop temperature (DS18B20 nr 1)
  url += "&field4=";
  url += String(temperature_buiten2); //soil temperature (DS18B29 nr 2)
  url +="&field5=";
  url +=String(bmp.readTemperature());//Outside temperature (BMP180)
  url +="&field6=";
  url +=String(bmp.readPressure());// Airpressure (BMP180)
  url += "\r\n";

  // Send request to the server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Connection: close\r\n\r\n");

  delay(1000);

}

Currently this program only monitors, but what is to stop you from adding a BH1750 I2C light sensor to measure if it is evening or morning or an RTC to know the time of day and to open and close the door of the coop automatically with aid of a PCF8574 I2C I/O expansion card, or as it is already in the garden, add a PCF8591 or ADS1115 AD converter to measure soil humidity and activate a pump when necessary. Or maybe switching on the water basin heater when the temperature falls below zero
if there is an I2 C chip for it, the ESP8266 can probably use it.

NOTE: the Adafruit DHT library contains an error that may show up in bigger programs on an 8266. If the majority of readings result in “failed to read”, it is time to comment out two erroneous lines in the DHT.cpp file as in the picture below:
adafruiterror

Note: The Rx and Tx pins sometimes can be a bit unruly when used as GPIO. If you want to use them e.g. as an output, you may want to check here.

A capacitive soil humidity sensor: Part 3

Initially I imagined the probe as a sleek device with a wire coming out that one would stick in the soil, if necessary completely. This would mean that the circuit pcb would be an integral part of the PCB that  formed the plates, but as always, things go different in practice. For one thing , the piece of PCB I intended to use just wasnt long enough. Second, my PCB etching possibilites are temporarily impeded. Third, I wanted to add an LDR, meaning that I had to have some sort of  translucent cover.

I constructed the circuit on a small piece of stripboard:

IMG_20160225_163125 i2cprobe

With regard to the LDR, sure that is nonsense. If I want to add an LDR I  could just as well add it to my base station. But I am not adding it because I have to, I am adding it because I can and because I wanted to get some experience in programming an I2C slave. An LDR might not be so usefull but in future I may want another sensor, e.g.a sensor that reads if there is really water flowing from the irrigation tube.

Also I had decided on putting the capacitor plates back to back, but as I did not have double sided PCB I just used two pieces glued and  soldered(!) together.
So My BOM was rather simple:

  • 2 equal size pieces of PCB  Size depends on what you have, but do not make them too small. I used 12×3 cm.
  • 1 piece of 0.5-1.0 cm plastic for a baseboard. I used an old cutting board.
  • 1 clear/translucent cover, I used the lid of a whipped cream spray can.
  • 1 piece of  thin 4 wire cable, length depending on your need

pcbprobe2I glued the two piecesof pcb back to back. drilled a hole in all 4 corners and through soldered a piece of wire through each hole, thus anchoring the plates together. I removed some copper around the solder so it would become an island isolated from the rest of the plate. (See picture).

Soldering the plates together in the corners may not be necessary if you decide to electrically isolate them from the soil with e.g. shrink tube.

Eventually I will place an NTC on the plate as well after it is covered with Shrink tube. Esthetically it might be better to put the NTC under the shrink tube, but that could create an air pocket.

 

baseplateI made the base plate from a o.5 cm thick piece of soft plastic. Cut out a round shape  with a 5.5 cm diameter to fit the base of my  clear dome and  made a slit 3 x0.3 cm into that in which the PCB  fits snug. made a round hole for the connecting cable.

IMG_20160226_133820

 

 

I soldered two wires on the top of the PCB, one on each side. Soldered wires on an NTC, insulated those, attached the NTC to the bottom of the PCB with the wires leading to the top and then covered the PCB with shrink wrap.

probeEventually the probe looks like this (picture)probe3

Andreas Spiess, discusses Capacitive sensors vs Resistive sensors in a youtube video.

The DS3231 RTC temperature sensor

The DS3231 RTC is a rather accurate RTC that has an internal temperature sensor that is used to calibrate the oscillator. The sensor is however also readable from external software. It has a 10 bit solution and  uses two registers: 0x11 and 0x12. The information in the upper byte is stored in 2-complement notation. The lowerbyte is there for the fractional part and has a solution of 0.25 degrees Celsius.
Two’s complement notation means that with positive numbers  it just follows the regular binary storage, but with negative numbers it does it a bit different. Knowing whether a number is negative or positive is indicated by the MSB in the Upper byte. If that is  a ‘1’, then the number is negative.
ds3231 temp sensorAny reading of the registers therefore needs to include a check to see if the  number is positive or negative.  As the Lower byte only indicates the fraction with an accuracy of 0.25 degrees it only needs to count to 4 (0.0. 0.25, 0.50, 0.75), hence two bits are enough
So suppose we have  retrieved the number:
0b0001100101 => +25.25°C. We can easily see it is 25.25°C because the top 8 bits are 00011001, which is 25, while the lower two bits 0b01, mean 1×0.25.
As the  lower byte, only uses the top 2 bits, it may need to be rightshifted 6 positions for calculations. So how about negative numbers, say -18 degrees.
Well -18 is 0b11101110  (=238 in twos complement notatie).
We can see that the highest bit is a 1, indicating a negative number. In order to make a check, we do the following:
0b11101110 &   0b10000000 => 0b10000000  So we know it is negative
Then we need to convert the 2 complement notation
0b11101110 XOR 0b11111111 => 0b00010001 (=17) // first XOR it
17+1= 18   // and  add a ‘1’
18*-1 = -18 // and then we turn it negative
So, how does that look in a program?

float getTemperature()
{
    int   temperatureCelsius;
    float fTemperatureCelsius;

     uint8_t UBYTE  = readRegister(REG_TEMPM); //Two's complement form
     uint8_t LRBYTE = readRegister(REG_TEMPL); //Fractional part

	if (UBYTE & 0b10000000 !=0) //check if -ve number
	{
		UBYTE  ^= 0b11111111;
		UBYTE  += 0x1;
		fTemperatureCelsius = UBYTE + ((LRBYTE >> 6) * 0.25);
		fTemperatureCelsius = fTemperatureCelsius * -1;
	}
	else
	{
		fTemperatureCelsius = UBYTE + ((LRBYTE >> 6) * 0.25);
	}

	return (fTemperatureCelsius);

}

Obvously this isnt a full program but just a function. You still need to define REG_TEMPM (0h11) and REG_TEMPL (0x12), and ‘readRegister’ is another function that just reads the specified registers (using the ‘Wire’library)

Sending sensor data wireless (433MHz) with an Attiny85 or Attiny45 with Manchestercode

P1060016
instructables-transmitter

In an earlier article I described how to send RF/wireless data between two Attiny85 chips with a 433 MHz transmitter/Receiver pair.
Now I will  give a more practical program that sends  3 variables from an LDR and a DHT11 sensor.
The connections are simple
The transmitter is connected to D0 (pin 5). The DHT11 sensor is connected to D4 (pin 3) and an LDR connected to A1 (pin 7), with the other end connected to Vcc and a corresponding pull down resistor.
The big photo shows a test circuit on stripboard, The smaller picture shows the final PCB. The huge amount of  cables at the top doesn’t mean much, I just use a few

/*
Pin 2 =A3 D3
pin 3 = A2 D4
pin5 =D0
Pin 6=D1
Pin7= D2 A1

LDR=A1
PIR= D1
RF433=D0
DHT11=D4
LED=D3
*/
// libraries
#include <dht11.h> //Rob Tillaert
#include <Manchester.h>
dht11 DHT11;
#define DHT11PIN 4
#define TX_PIN 0  //pin where your transmitter is connected
//variables
float h=0;
float t=0;
int transmit_data = 2761;
int transmit_t = 0; // temperature
int transmit_h = 0; // humidity
int transmit_l=0; // lightlevel
int transmit_p = 0; //PIR
int transmit_s = 0; /Station identifier
byte ledPin=3;
int light=0;

void setup() {
pinMode(1, INPUT);
pinMode(ledPin,OUTPUT);
man.setupTransmit(TX_PIN, MAN_1200);
}

void loop() {
int chk = DHT11.read(DHT11PIN);
h=DHT11.humidity;
t=DHT11.temperature;
transmit_h=2000+h;
transmit_t=2100+t;
transmit_l=2200+(analogRead(A1)/10);
transmit_p=2300+digitalRead(1);
transmit_s=2500;
digitalWrite(ledPin,HIGH);
man.transmit(transmit_h);
delay(200);
man.transmit(transmit_t);
delay(200);
man.transmit(transmit_l);
delay(200);
digitalWrite(ledPin,LOW);
delay(1000);
}

The idea of adding a number, in this case in the 2000 range is to be able to identify what (which station) is sending the data: any 2000 number I know is humidity, any 2100 number I know is temperature, etc. Variables h and t are floats that can have decimals. As I am only interested in 2 digit accuracy, I just add them to an integer and thus turn them into integers. WIth regard to the lightlevel as it is an analog read it might be as high as 1023 (theoretically), so when it is divided by 10, the range will usuallu be 99 max a two digit number. If by any chance it would be 100, the transmitter will send a number as 24xx. The receiving software then knows it is a more than 2 digit value, but that will be very rare I then send 5  values: Humidity, temperature, light PIR and a station identifier ending the data. The receiving station then can identify where it came from and process the incoming signals (starting by dividing subtracting  the first two digits).
WIth regard to the lightlevel as it is an analog read it might be as high as 1023 (theoretically), so when it is divided by 10, the range will usually be 99 max a two digit number. If by any chance it would be 100, the transmitter will send a number as 24xx. The receiving software then knows it is a more than 2 digit value, but that will be very rare

It takes 3374 bytes so I could compile it in an Attiny45.

See also: DHT11 on Attiny85
Attiny DHT 11 & 433 MHz (with Oregon code)