The Greenfinger Project
First off, You will need to connect the TFT screen to the Wemos D1 Mini.
Instructions for this can be found at:
www.so-now.com/tft.php
When your TFT and Wemos are up and running, we need to connect the DHT 11.
DHT 11 Sensor
As we mentioned earlier, we have a spare DHT 11 temperature and humidity sensor for this project.
It needs to be connected to the Wemos D1 Mini in the following way:
(pins are numbered 1 to 4, left to right).
DHT 11 Pin | DHT Pin | Wemos D1 |
---|---|---|
Number | Function | Pin Name |
1 | VCC | 3.3v |
2 | Data | D4 |
3 | NC | Not Connected |
4 | GND | Circuit Ground |
When that is all connected up, you will need to use the library manager in the Arduin0 IDE to make sure you have the DHT libraries installed.
Then, it's all down to the code.....
Time to code
OK. Example code below.
Click here to download
Note this is the code we use for testing.
We have a MQTTT server running.
You may want to remove the MQTT/Wifi parts.
Use the serial monitor for some debugging info.
/*
greenhouse sensor/control.
Target: Wemos D1 mini.
Support: 320 X 240 tft screen.
So-now.com v1.0, 19-04-2019
Updated by Bodmer for variable meter size
*/
// Define meter size as 1 for tft.rotation(0) or 1.3333 for tft.rotation(1)
//#define M_SIZE 1.3333
#define M_SIZE 1
#include <TFT_eSPI.h> // Hardware-specific library
#include <SPI.h>>
#include <DHTesp.h>>
#include <ESP8266WiFi.h>>
#include <PubSubClient.h>>
#ifdef ESP32
#pragma message(THIS EXAMPLE IS FOR ESP8266 ONLY!)
#error Select ESP8266 board.
#endif
DHTesp dht;
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library
WiFiClient espClient;
PubSubClient client(espClient);
#define TFT_GREY 0x5AEB
uint8_t gTemperature = 10;
float ltx = 0; // Saved x coord of bottom of needle
uint16_t osx = M_SIZE*120, osy = M_SIZE*120; // Saved x & y coords
uint32_t updateScreenTime = 0; // time for next update
uint32_t updateMQTTTime = 0; // time for next update
int old_analog = -999; // Value last displayed
uint8_t old humidity -999;
uint8_t old temperature = 0;
uint8_t giSW1 = 0b00000001;
uint8_t giSW2 = 0b00000010;
//SSID of your network
const char* ssid = "looking"; //SSID of your Wi-Fi router
const char* pass = "sexy"; //Password of your Wi-Fi router
const char* MQTTserver = "192.168.0.51";
const char* MQTTClientName = "greenhouse";
const char* MQTTuid = "imtoosexy";
const char* MQTTpwd = "formyboots";
char charBuffer[128];
uint8_t iBufferCount = 0;
void setup(void) {
tft.init();
tft.setRotation(1);
Serial.begin(9600); // For debug
tft.fillScreen(TFT_BLACK);
drawHumidityMeter(); // Draw analogue meter
drawThermometer();
updateScreenTime = millis(); // Next update time
updateMQTTTime = millis(); // Next update time
dht.setup(D4, DHTesp::DHT11);
//set up wifi connection
// Connect to Wi-Fi network
Serial.println();
Serial.println();
Serial.print("Connecting to...");
Serial.println(ssid);
WiFi.mode(WIFI_STA); //We don't want the ESP to act as an AP
WiFi.begin(ssid, pass);
uint8_t trycount = 0;
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
trycount ++;
if(trycount > 9)
{
trycount = 0;
Serial.println("");
}
}
Serial.println("");
Serial.println("Wi-Fi connected.");
Serial.print("ip_address : ");
Serial.println(WiFi.localIP());
Serial.print("MQTT Client Name : ");
Serial.println(MQTTClientName);
//connect to MQTT server
client.setServer(MQTTserver, 1883);
//attempt initial connection
mqtt_connect();
}
void loop() {
uint8_t humidity;
uint8_t temperature;
if (!client.connected())
{
Serial.println("[Disconnected]");
mqtt_connect();
}
client.loop();
if (updateScreenTime <= millis())
{
updateScreenTime = millis() + 10000; // Update screen values every 10 seconds
humidity = dht.getHumidity();
temperature = dht.getTemperature();
plotHumidity((int)humidity, 0); // It takes between 2 and 12ms to replot the needle with zero delay
plotTemperature(temperature);
}
if (updateMQTTTime <= millis())
{
updateMQTTTime = millis() + 600000; // Update MQTT values every 10 minutes
humidity = dht.getHumidity();
temperature = dht.getTemperature();
String line = "{\"iUID\":77, ";
line = line + "\"fTemp\":";
line = line + temperature + ", \"fHum\":";
line = line + humidity + ", \"SW1\":";
line = line + giSW1 + ", \"SW2\":";
line = line + giSW2 + "}";
client.publish("response", (char*)line.c_str());
Serial.println((char*)line.c_str());
}
delay(2000);
}
// #########################################################################
// Draw the Humidity meter
// #########################################################################
void drawHumidityMeter()
{
// Meter outline
tft.fillRect(0, 0, M_SIZE*239, M_SIZE*126, TFT_GREY);
tft.fillRect(5, 3, M_SIZE*230, M_SIZE*119, TFT_WHITE);
tft.setTextColor(TFT_BLACK); // Text colour
// Draw ticks every 5 degrees from -50 to +50 degrees (100 deg. FSD swing)
for (int i = -50; i < 51; i += 5) {
// Long scale tick length
int tl = 15;
// Coodinates of tick to draw
float sx = cos((i - 90) * 0.0174532925);
float sy = sin((i - 90) * 0.0174532925);
uint16_t x0 = sx * (M_SIZE*100 + tl) + M_SIZE*120;
uint16_t y0 = sy * (M_SIZE*100 + tl) + M_SIZE*140;
uint16_t x1 = sx * M_SIZE*100 + M_SIZE*120;
uint16_t y1 = sy * M_SIZE*100 + M_SIZE*140;
// Coordinates of next tick for zone fill
float sx2 = cos((i + 5 - 90) * 0.0174532925);
float sy2 = sin((i + 5 - 90) * 0.0174532925);
int x2 = sx2 * (M_SIZE*100 + tl) + M_SIZE*120;
int y2 = sy2 * (M_SIZE*100 + tl) + M_SIZE*140;
int x3 = sx2 * M_SIZE*100 + M_SIZE*120;
int y3 = sy2 * M_SIZE*100 + M_SIZE*140;
// Cold zone limits
if (i >= -50 && i < -5) {
tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_CYAN);
tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_CYAN);
}
// warning zone limits
if (i >= -5 && i < 5) {
tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_ORANGE);
tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_ORANGE);
}
// Green zone limits
if (i >= 5 && i < 15) {
tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_GREEN);
tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_GREEN);
}
// warning zone limits
if (i >= 15 && i < 25) {
tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_ORANGE);
tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_ORANGE);
}
// Hot zone limits
if (i >= 25 && i < 50) {
tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_RED);
tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_RED);
}
// Short scale tick length
if (i % 25 != 0) tl = 8;
// Recalculate coords incase tick length changed
x0 = sx * (M_SIZE*100 + tl) + M_SIZE*120;
y0 = sy * (M_SIZE*100 + tl) + M_SIZE*140;
x1 = sx * M_SIZE*100 + M_SIZE*120;
y1 = sy * M_SIZE*100 + M_SIZE*140;
// Draw tick
tft.drawLine(x0, y0, x1, y1, TFT_BLACK);
// Check if labels should be drawn, with position tweaks
if (i % 25 == 0) {
// Calculate label positions
x0 = sx * (M_SIZE*100 + tl + 10) + M_SIZE*120;
y0 = sy * (M_SIZE*100 + tl + 10) + M_SIZE*140;
switch (i / 25) {
case -2: tft.drawCentreString("0", x0, y0 - 12, 2); break;
case -1: tft.drawCentreString("25", x0, y0 - 9, 2); break;
case 0: tft.drawCentreString("50", x0, y0 - 7, 2); break;
case 1: tft.drawCentreString("75", x0, y0 - 9, 2); break;
case 2: tft.drawCentreString("100", x0, y0 - 12, 2); break;
}
}
// Now draw the arc of the scale
sx = cos((i + 5 - 90) * 0.0174532925);
sy = sin((i + 5 - 90) * 0.0174532925);
x0 = sx * M_SIZE*100 + M_SIZE*120;
y0 = sy * M_SIZE*100 + M_SIZE*140;
// Draw scale arc, don't draw the last part
if (i < 50) tft.drawLine(x0, y0, x1, y1, TFT_BLACK);
}
tft.drawString("%RH", M_SIZE*(5 + 230 - 40), M_SIZE*(119 - 20), 2); // Units at bottom right
tft.drawCentreString("%RH", M_SIZE*120, M_SIZE*70, 4); // Comment out to avoid font 4
tft.drawRect(5, 3, M_SIZE*230, M_SIZE*119, TFT_BLACK); // Draw bezel line
plotHumidity(0, 0); // Put meter needle at 0
}
// #########################################################################
// Update needle position
// This function is blocking while needle moves, time depends on ms_delay
// 10ms minimises needle flicker if text is drawn within needle sweep area
// Smaller values OK if text not in sweep area, zero for instant movement but
// does not look realistic... (note: 100 increments for full scale deflection)
// #########################################################################
void plotHumidity(int value, byte ms_delay)
{
tft.setTextColor(TFT_BLACK, TFT_WHITE);
char buf[8]; dtostrf(value, 4, 0, buf);
tft.drawRightString(buf, M_SIZE*40, M_SIZE*(119 - 20), 2);
if (value < -10) value = -10; // Limit value to emulate needle end stops
if (value > 110) value = 110;
// Move the needle until new value reached
while (!(value == old_analog)) {
if (old_analog < value) old_analog++;
else old_analog--;
if (ms_delay == 0) old_analog = value; // Update immediately if delay is 0
float sdeg = map(old_analog, -10, 110, -150, -30); // Map value to angle
// Calcualte tip of needle coords
float sx = cos(sdeg * 0.0174532925);
float sy = sin(sdeg * 0.0174532925);
// Calculate x delta of needle start (does not start at pivot point)
float tx = tan((sdeg + 90) * 0.0174532925);
// Erase old needle image
tft.drawLine(M_SIZE*(120 + 20 * ltx - 1), M_SIZE*(140 - 20), osx - 1, osy, TFT_WHITE);
tft.drawLine(M_SIZE*(120 + 20 * ltx), M_SIZE*(140 - 20), osx, osy, TFT_WHITE);
tft.drawLine(M_SIZE*(120 + 20 * ltx + 1), M_SIZE*(140 - 20), osx + 1, osy, TFT_WHITE);
// Re-plot text under needle
tft.setTextColor(TFT_BLACK);
tft.drawCentreString("%RH", M_SIZE*120, M_SIZE*70, 4); // // Comment out to avoid font 4
// Store new needle end coords for next erase
ltx = tx;
osx = M_SIZE*(sx * 98 + 120);
osy = M_SIZE*(sy * 98 + 140);
// Draw the needle in the new postion, magenta makes needle a bit bolder
// draws 3 lines to thicken needle
tft.drawLine(M_SIZE*(120 + 20 * ltx - 1), M_SIZE*(140 - 20), osx - 1, osy, TFT_RED);
tft.drawLine(M_SIZE*(120 + 20 * ltx), M_SIZE*(140 - 20), osx, osy, TFT_MAGENTA);
tft.drawLine(M_SIZE*(120 + 20 * ltx + 1), M_SIZE*(140 - 20), osx + 1, osy, TFT_RED);
// Slow needle down slightly as it approaches new postion
if (abs(old_analog - value) < 10) ms_delay += ms_delay / 5;
// Wait before next update
delay(ms_delay);
}
}
//void drawThermometer(int xPos, int yPos)
void drawThermometer()
{
//force position for testing
int xPos = 240;
int yPos = 0;
// Thermometer outline
tft.fillRect(xPos, yPos, 80, 240, TFT_GREY);
tft.fillRect(xPos + 5, yPos + 3, 70, 232, TFT_WHITE);
//colour scale
tft.fillRect(xPos + 10, yPos + 30, 35, 40, TFT_RED);
tft.fillRect(xPos + 10, yPos + 70, 35, 10, TFT_ORANGE);
tft.fillRect(xPos + 10, yPos + 80, 35, 30, TFT_GREEN);
tft.fillRect(xPos + 10, yPos + 110, 35, 10, TFT_ORANGE);
tft.fillRect(xPos + 10, yPos + 120, 35, 30, TFT_CYAN);
// Thermometer body
tft.fillCircle(xPos + 28, yPos + 20, 8, TFT_BLACK); //top dome outline
tft.fillRect(xPos + 20, yPos + 20, 17, 140, TFT_BLACK); //neck outline
tft.fillCircle(xPos + 28, yPos + 170, 18, TFT_BLACK); //bottom bulb outline
tft.fillRect(xPos + 20, yPos + 20, 17, 140, TFT_BLACK); //neck outline
//Thermometer scale
tft.drawLine(xPos + 10, yPos + 30, xPos + 45, yPos + 30, TFT_BLACK); //
tft.drawLine(xPos + 30, yPos + 40, xPos + 50, yPos + 40, TFT_BLACK); // 50
tft.drawLine(xPos + 30, yPos + 50, xPos + 45, yPos + 50, TFT_BLACK); //
tft.drawLine(xPos + 30, yPos + 60, xPos + 50, yPos + 60, TFT_BLACK); // 40
tft.drawLine(xPos + 30, yPos + 70, xPos + 45, yPos + 70, TFT_BLACK); //
tft.drawLine(xPos + 10, yPos + 80, xPos + 50, yPos + 80, TFT_BLACK); // 30
tft.drawLine(xPos + 30, yPos + 90, xPos + 45, yPos + 90, TFT_BLACK); //
tft.drawLine(xPos + 30, yPos + 100, xPos + 50, yPos + 100, TFT_BLACK); // 20
tft.drawLine(xPos + 10, yPos + 110, xPos + 45, yPos + 110, TFT_BLACK); //
tft.drawLine(xPos + 10, yPos + 120, xPos + 50, yPos + 120, TFT_BLACK); // 10
tft.drawLine(xPos + 30, yPos + 130, xPos + 45, yPos + 130, TFT_BLACK); //
tft.drawLine(xPos + 30, yPos + 140, xPos + 50, yPos + 140, TFT_BLACK); // 0
tft.drawLine(xPos + 10, yPos + 150, xPos + 45, yPos + 150, TFT_BLACK); //
//draw graticule text
tft.drawCentreString("50", xPos + 60, yPos + 35, 2);
tft.drawCentreString("40", xPos + 60, yPos + 55, 2);
tft.drawCentreString("30", xPos + 60, yPos + 75, 2);
tft.drawCentreString("20", xPos + 60, yPos + 95, 2);
tft.drawCentreString("10", xPos + 60, yPos + 115, 2);
tft.drawCentreString("0", xPos + 60, yPos + 135, 2);
tft.fillCircle(xPos + 28, yPos + 20, 6, TFT_WHITE); //hollow top dome
tft.fillRect(xPos + 22, yPos + 20, 13, 150, TFT_WHITE); //hollow neck
tft.fillCircle(xPos + 28, 170, 16, TFT_WHITE); //hollow bulb
tft.fillCircle(xPos + 28, 170, 14, TFT_BLUE); //full bulb
// tft.fillRect(xPos + 25, yPos + 140, 7, 30, TFT_BLUE); //semi filled
tft.setTextColor(TFT_BLACK);
tft.drawCircle(xPos + 30, 210, 4, TFT_BLACK); //degree symbol
tft.drawCircle(xPos + 30, 210, 3, TFT_BLACK); //degree symbol
tft.drawCentreString("C", xPos + 50, yPos + 205, 4); // // Comment out to avoid font 4
}
void plotTemperature(int value)
{
//screen position
int xPos = 240;
int yPos = 0;
int iNewValue = value;
iNewValue = (value * 2);
tft.fillRect(xPos + 25, yPos + 20, 7, yPos + 120 - iNewValue, TFT_WHITE);
tft.fillRect(xPos + 25, yPos + 140 - iNewValue, 7, iNewValue + 30, TFT_BLUE);
tft.setTextColor(TFT_WHITE, TFT_BLACK); // Text colour
char buf[8]; dtostrf(value, 4, 0, buf);
tft.drawCentreString(buf,120,180,4);
}
void mqtt_connect()
{
// Loop until we're reconnected
while (!client.connected())
{
// Attempt to connect
if(client.connect(MQTTClientName, MQTTuid, MQTTpwd))
{
Serial.println("[Connected]");
}
else
{
Serial.print("[failed, rc=");
Serial.print(client.state());
Serial.println("]");
// // Wait 5 seconds before retrying
delay(5000);
}
}
}
Enjoy