Merge branch 'feature-led'

This commit is contained in:
Yohan Boujon 2023-12-22 09:13:50 +01:00
commit a9b456cef7
18 changed files with 413 additions and 52 deletions

View file

@ -5,9 +5,7 @@
enum class ComponentType {
Digital,
Analog,
I2C,
Serial
Analog
};
class Component{

View file

@ -0,0 +1,28 @@
#include "DHTComponent.hpp"
DHTComponent::DHTComponent(uint8_t type, byte pin)
: _pin(pin), _type(type), _dht(nullptr)
{}
DHTComponent::~DHTComponent()
{
delete _dht;
}
void DHTComponent::setup()
{
_dht = new DHT_Unified(_pin, _type);
_dht->begin();
}
float DHTComponent::getHumidity()
{
_dht->humidity().getEvent(&_event);
return _event.relative_humidity;
}
float DHTComponent::getTemperature()
{
_dht->temperature().getEvent(&_event);
return _event.temperature;
}

View file

@ -0,0 +1,23 @@
#ifndef _HEADER_COMPONENT_DHT
#define _HEADER_COMPONENT_DHT
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
class DHTComponent
{
public:
DHTComponent(uint8_t type, byte pin);
~DHTComponent();
void setup();
float getHumidity();
float getTemperature();
private:
byte _pin;
uint8_t _type;
DHT_Unified* _dht;
sensors_event_t _event;
};
#endif // _HEADER_COMPONENT_LED

View file

@ -0,0 +1,42 @@
#include "LedComponent.hpp"
Color Color::operator-(byte value)
{
byte nullbyte(0);
return {(this->red - value) < 0 ? nullbyte : static_cast<byte>(this->red - value),
(this->blue - value) < 0 ? nullbyte : static_cast<byte>(this->blue - value),
(this->green - value) < 0 ? nullbyte : static_cast<byte>(this->green - value)};
}
LedComponent::LedComponent(byte pin, byte pin_clock, uint8_t led_number)
: _pin(pin), _pinClock(pin_clock), _ledNumber(led_number), _led(nullptr)
{
}
LedComponent::~LedComponent()
{
delete _led;
}
void LedComponent::setup()
{
_led = new ChainableLED(_pinClock, _pin, _ledNumber);
}
void LedComponent::setColor(LedNumber led_number, Color color)
{
_led->setColorRGB(static_cast<byte>(led_number), color.red, color.green, color.blue);
}
void LedComponent::setColor(LedNumber led_number, Color color, uint16_t fading_time)
{
const auto redFade = color.red / (static_cast<float>(fading_time));
const auto greenFade = color.green / (static_cast<float>(fading_time));
const auto blueFade = color.blue / (static_cast<float>(fading_time));
for (uint16_t time(0); time < fading_time; time++)
{
_led->setColorRGB(static_cast<byte>(led_number), static_cast<byte>(redFade * time), static_cast<byte>(greenFade * time), static_cast<byte>(blueFade * time));
delay(1);
}
}

View file

@ -0,0 +1,45 @@
#ifndef _HEADER_COMPONENT_LED
#define _HEADER_COMPONENT_LED
#include "Component.hpp"
#include <ChainableLED.h>
struct Color
{
byte red;
byte green;
byte blue;
Color operator-(byte value);
};
namespace LedColors
{
constexpr Color LED_OFF = {0,0,0};
constexpr Color WIFI_ON = {0x18,0x28,0x36};
constexpr Color NO_WIFI = {0x64,0x04,0x0B};
constexpr Color TOO_DRY = {0xB3,0x58,0x1B};
constexpr Color TOO_WET = {0x1B,0x09,0x3F};
}
enum class LedNumber {
LED_HARDWARE = 0,
LED_PLANT = 1
};
class LedComponent
{
public:
LedComponent(byte pin, byte pin_clock, uint8_t led_number);
~LedComponent();
void setup();
void setColor(LedNumber led_number, Color color);
void setColor(LedNumber led_number, Color color, uint16_t fading_time);
private:
byte _pin;
byte _pinClock;
uint8_t _ledNumber;
ChainableLED* _led;
};
#endif // _HEADER_COMPONENT_LED

View file

@ -0,0 +1,22 @@
#include "MainComponent.hpp"
MainComponent::MainComponent()
: _humidity(ComponentType::Analog, PIN_A0), _led(D8, D7, 2), _dht(DHT11, D3)
{
}
MainComponent::~MainComponent()
{}
void MainComponent::setup()
{
_led.setup();
_dht.setup();
// Lights are off when powered
_led.setColor(LedNumber::LED_HARDWARE,{0,0,0});
_led.setColor(LedNumber::LED_PLANT,{0,0,0});
}
Component& MainComponent::getHumidity() { return _humidity; }
LedComponent& MainComponent::getLed() { return _led; }
DHTComponent& MainComponent::getDHT() { return _dht; }

View file

@ -0,0 +1,34 @@
#ifndef _HEADER_COMPONENT_MAIN
#define _HEADER_COMPONENT_MAIN
#include "Component.hpp"
#include "LedComponent.hpp"
#include "DHTComponent.hpp"
class MainComponent {
public:
// Singleton
static MainComponent& GetInstance()
{
static MainComponent instance;
return instance;
}
// Public functions
void setup();
Component& getHumidity();
LedComponent& getLed();
DHTComponent& getDHT();
private:
// Singleton
MainComponent();
~MainComponent();
MainComponent(const MainComponent&) = delete;
MainComponent& operator=(const MainComponent&) = delete;
// Components
Component _humidity;
LedComponent _led;
DHTComponent _dht;
};
#endif //

View file

@ -1,12 +1,11 @@
#include "Screen.hpp"
#include "MainComponent.hpp"
#include <vector>
#include <memory>
// XBM Files
#include "../Pictures/failed.xbm"
#include "../Pictures/humidity.xbm"
#include "../Pictures/thermometer.xbm"
#include "../Pictures/air_humidity.xbm"
using namespace Display;
@ -33,9 +32,9 @@ void Screen::Setup(uint8_t *font)
auto plantHumidity = TextBox("plantHumidity", StyleWidth::LEFT, StyleHeight::TOP, U8G2_BTN_BW0, 0, 0);
auto airTemperature = TextBox("airTemperature", StyleWidth::LEFT, StyleHeight::CENTERED, U8G2_BTN_BW0, 0, 0);
auto airHumidity = TextBox("airHumidity", StyleWidth::LEFT, StyleHeight::BOTTOM, U8G2_BTN_BW0, 0, 6);
auto humidityPicture = SpriteBox(humidity_bits,humidity_width,humidity_height,StyleWidth::LEFT,StyleHeight::CENTERED);
auto thermometerPicture = SpriteBox(thermometer_bits,thermometer_width,thermometer_height,StyleWidth::LEFT,StyleHeight::CENTERED);
auto airHumidityPicture = SpriteBox(air_humidity_bits,air_humidity_width,air_humidity_height,StyleWidth::LEFT,StyleHeight::CENTERED);
auto humidityPicture = SpriteBox(ICONS[0].data,ICONS[0].width,ICONS[0].height,StyleWidth::LEFT,StyleHeight::CENTERED);
auto thermometerPicture = SpriteBox(ICONS[1].data,ICONS[1].width,ICONS[1].height,StyleWidth::LEFT,StyleHeight::CENTERED);
auto airHumidityPicture = SpriteBox(ICONS[2].data,ICONS[2].width,ICONS[2].height,StyleWidth::LEFT,StyleHeight::CENTERED);
// Config Boxes
plantHumidity.SetOffset(OFFSET_TEXT,12);
@ -126,16 +125,19 @@ void Screen::boot()
_bootFrame++;
bootWindow.Update(0,CLOVER_FRAMES[(_bootFrame >= 10 ? 10 : _bootFrame)]);
_screen->sendBuffer();
// Shutting down led when finished booting
if(_bootFrame == MAX_BOOT_FRAMES)
MainComponent::GetInstance().getLed().setColor(LedNumber::LED_HARDWARE,LedColors::LED_OFF);
}
void Screen::loop(const float plantHumidity, const float airTemperature, const float airHumidity, const float light)
void Screen::loop(const float plantHumidity, const float airTemperature, const float airHumidity)
{
_screen->clearBuffer();
// Updating with values
loopWindow.Update(0,String("Hum: ")+String(plantHumidity,1)+String("%"));
loopWindow.Update(1,String("Tem: ")+String(airTemperature,1)+String("°C"));
loopWindow.Update(2,String("Hum: ")+String(airHumidity,1)+String("%"));
//loopWindow.Update(3,String("Light: ")+String(light,1)+String("%"));
// Component
loopWindow.Display();
iconWindow.Display();
@ -143,6 +145,15 @@ void Screen::loop(const float plantHumidity, const float airTemperature, const f
_screen->sendBuffer();
}
void Screen::setWarningIcon(Sensors sensorId, bool warning)
{
const auto realId = static_cast<size_t>(sensorId);
if(warning)
iconWindow.Update(realId,ICONS_WARNING[realId]);
else
iconWindow.Update(realId,ICONS[realId]);
}
uint16_t Screen::getHeight() { return _height; }
uint16_t Screen::getWidth() { return _width; }
U8G2_SSD1306_128X64_NONAME_F_HW_I2C &Screen::getScreen() { return *_screen; }

View file

@ -20,6 +20,14 @@
#include "../Pictures/clover10.xbm"
#include "../Pictures/clover11.xbm"
// Icons
#include "../Pictures/humidity.xbm"
#include "../Pictures/thermometer.xbm"
#include "../Pictures/air_humidity.xbm"
#include "../Pictures/humidity-warning.xbm"
#include "../Pictures/thermometer-warning.xbm"
#include "../Pictures/air_humidity-warning.xbm"
namespace Display
{
constexpr Picture CLOVER_FRAMES[] = {
@ -35,10 +43,26 @@ namespace Display
{clover10_bits, clover10_width, clover10_height},
{clover11_bits, clover11_width, clover11_height},
};
constexpr Picture ICONS[] = {
{humidity_bits,humidity_width,humidity_height},
{thermometer_bits,thermometer_width,thermometer_height},
{air_humidity_bits,air_humidity_width,air_humidity_height},
};
constexpr Picture ICONS_WARNING[] = {
{humidity_warning_bits,humidity_warning_width,humidity_warning_height},
{thermometer_warning_bits,thermometer_warning_width,thermometer_warning_height},
{air_humidity_warning_bits,air_humidity_warning_width,air_humidity_warning_height},
};
constexpr uint8_t MAX_BOOT_FRAMES = 25;
constexpr uint8_t OFFSET_ICONS = 55;
constexpr uint8_t OFFSET_TEXT = 75;
enum class Sensors {
SOIL_MOISTURE = 0,
THERMOMETER,
AIR_HUMIDITY
};
class Screen
{
public:
@ -54,7 +78,8 @@ namespace Display
void notConnected();
void connected(const char *ipaddress, uint8_t timing);
void boot();
void loop(const float plantHumidity, const float airTemperature, const float airHumidity, const float light);
void loop(const float plantHumidity, const float airTemperature, const float airHumidity);
void setWarningIcon(Sensors sensorId, bool warning=true);
// Getters
uint16_t getHeight();
uint16_t getWidth();

View file

@ -0,0 +1,6 @@
#define air_humidity_warning_width 16
#define air_humidity_warning_height 16
static unsigned char air_humidity_warning_bits[] = {
0x60, 0x00, 0x90, 0x00, 0x18, 0x01, 0x24, 0x02, 0x42, 0x0a, 0x42, 0x0a,
0x04, 0x19, 0x90, 0x3c, 0x38, 0x3e, 0x28, 0x7f, 0x6c, 0x7f, 0x6c, 0x7e,
0xfe, 0x3e, 0xee, 0x1e, 0xfe, 0x0e, 0x00, 0x00 };

View file

@ -0,0 +1,6 @@
#define humidity_warning_width 16
#define humidity_warning_height 16
static unsigned char humidity_warning_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x40, 0x03, 0x40, 0x03,
0x80, 0x07, 0x90, 0x0f, 0xb8, 0x0f, 0x28, 0x1f, 0x6c, 0x1f, 0x6c, 0x1e,
0xfe, 0x0e, 0xee, 0x06, 0xfe, 0x02, 0x00, 0x00 };

View file

@ -0,0 +1,6 @@
#define thermometer_warning_width 16
#define thermometer_warning_height 16
static unsigned char thermometer_warning_bits[] = {
0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0xc0, 0x02, 0x40, 0x02, 0xc0, 0x02,
0x40, 0x02, 0x90, 0x02, 0x38, 0x02, 0x28, 0x04, 0x6c, 0x08, 0x6c, 0x0e,
0xfe, 0x0e, 0xee, 0x06, 0xfe, 0x02, 0x00, 0x00 };

View file

@ -0,0 +1,22 @@
#ifndef _HEADER_SERVER_EXCEPTION
#define _HEADER_SERVER_EXCEPTION
#include <Arduino.h>
#include <exception>
#include "ServerException.hpp"
class ServerException : public std::exception
{
public:
ServerException(const char* msg, uint8_t code)
: _msg(msg), _code(code)
{
}
uint8_t code() { return _code; }
const char* what() { return _msg; }
private:
const char* _msg;
uint8_t _code;
};
#endif // _HEADER_SERVER_EXCEPTION

View file

@ -1,6 +1,15 @@
#include "ServerHandler.hpp"
#include "MainComponent.hpp"
#include "../Display/Screen.hpp"
inline void led_blink(LedComponent& led)
{
led.setColor(LedNumber::LED_HARDWARE,LedColors::WIFI_ON);
delay(50);
led.setColor(LedNumber::LED_HARDWARE,LedColors::LED_OFF);
}
ServerHandler::ServerHandler() : server(80), display_time(0), _connected(false)
{
}
@ -11,13 +20,17 @@ void ServerHandler::setup(const char *ssid, const char *password)
{ // On utilise les scope resolution operator pour définir les méthodes la classe ServerHandle qui elle est dans hpp
uint8_t state(0);
uint16_t tryConnection(0);
auto& led = MainComponent::GetInstance().getLed();
Serial.begin(9600);
WiFi.begin(ssid, password);
// Testing connection
while ((WiFi.status() != WL_CONNECTED) && (tryConnection < MAX_CONNECT_TRIES))
{
delay(500);
led_blink(led);
delay(50);
led_blink(led);
delay(350);
Display::Screen::GetInstance().connecting(state);
state >= 3 ? state = 0 : state++;
tryConnection++;
@ -26,10 +39,15 @@ void ServerHandler::setup(const char *ssid, const char *password)
if (tryConnection < MAX_CONNECT_TRIES)
{
_connected = true;
auto color = LedColors::WIFI_ON;
led.setColor(LedNumber::LED_HARDWARE,color-15,200);
server.begin();
server.on("/", [this]()
{ this->handleRoot(); }); // fonction lamda pour gérer les requettes get
}
else {
led.setColor(LedNumber::LED_HARDWARE,LedColors::NO_WIFI,200);
}
}
void ServerHandler::loop()

View file

@ -11,12 +11,15 @@
[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
build_flags = -fexceptions
build_flags =
-std=c++11
framework = arduino
lib_deps =
tzapu/WiFiManager@^0.16.0
bbx10/DNSServer@^1.1.0
ArduinoJson
olikraus/U8g2@^2.35.7
extra_scripts =
pre:scripts/dotenv-var.py
seeed-studio/Grove - Chainable RGB LED@^1.0.0
adafruit/DHT sensor library@^1.4.6
extra_scripts =
pre:scripts/dotenv-var.py

View file

@ -3,47 +3,48 @@
#include <ESP8266WebServer.h>
#include "ServerHandler.hpp"
#include "Component.hpp"
#include "MainComponent.hpp"
#include "Screen.hpp"
#include "warning.hpp"
#ifdef SSID_CLOVER
const char* ssid = SSID_CLOVER;
const char *ssid = SSID_CLOVER;
#endif
#ifdef PSWD_CLOVER
const char* pswd = PSWD_CLOVER;
const char *pswd = PSWD_CLOVER;
#endif
Component humidity(ComponentType::Analog, PIN_A0);
void setup()
{
// Sensors/Acuators setup
MainComponent::GetInstance().setup();
// Setup for screen and server
Serial.begin(9600);
Display::Screen::GetInstance().Setup(const_cast<uint8_t*>(u8g2_font_busdisplay8x5_tr));
Display::Screen::GetInstance().Setup(const_cast<uint8_t *>(u8g2_font_busdisplay8x5_tr));
ServerHandler::GetInstance().setup(ssid, pswd);
// Printing server data
Serial.print("Connected to WiFi. IP address: ");
Serial.println(WiFi.localIP());
pinMode(D5, OUTPUT);
digitalWrite(D5, LOW);
}
void loop()
{
// Creating variables to access singletons
auto& serverHandler = ServerHandler::GetInstance();
auto& dataHandler = DataHandler::GetInstance();
auto& screen = Display::Screen::GetInstance();
auto &serverHandler = ServerHandler::GetInstance();
auto &dataHandler = DataHandler::GetInstance();
auto &screen = Display::Screen::GetInstance();
// Could not connect after setup: Showing screen failure
if(!serverHandler.isConnected())
if (!serverHandler.isConnected())
{
screen.notConnected();
return;
}
// Server showing IP
if(!serverHandler.showBoot())
if (!serverHandler.showBoot())
{
serverHandler.showIp();
delay(250);
@ -51,7 +52,7 @@ void loop()
}
// When Screen can boot (isBooting) and Server finished showing IP (showBoot)
if(screen.isBooting() && serverHandler.showBoot())
if (screen.isBooting() && serverHandler.showBoot())
{
screen.boot();
delay(100);
@ -60,33 +61,19 @@ void loop()
// Data gathered from various sensors
// 0 -> air(0), 0-300 -> dry(20), 300-700 -> humid (580), 700-950 -> water(940)
auto soilHumidityData = static_cast<float>(std::any_cast<int>(humidity.getValue()));
auto airTemperatureData = random(150, 300) / 10.0;
auto airHumidityData = random(0, 1000) / 10.0;
auto lightData = random(0, 1000) / 10.0;
auto soilHumidityData = static_cast<float>(std::any_cast<int>(MainComponent::GetInstance().getHumidity().getValue()));
auto airTemperatureData = MainComponent::GetInstance().getDHT().getTemperature();
auto airHumidityData = MainComponent::GetInstance().getDHT().getHumidity();
// Updating the data handler
dataHandler.updateSoilMoistureData(soilHumidityData);
dataHandler.updateAirTemperatureData(airTemperatureData);
dataHandler.updateAirHumidityData(airHumidityData);
dataHandler.updateLightData(lightData);
// Screen showing
screen.loop(soilHumidityData,airTemperatureData,airHumidityData,lightData);
if (soilHumidityData < 550) {
Serial.println("Soil humidity low. Please water the plant.");
digitalWrite(D5, HIGH);
} else if (soilHumidityData >= 550 && soilHumidityData <= 680) {
Serial.println("Idle...");
digitalWrite(D5, LOW);
} else {
Serial.println("Soil too wet.");
digitalWrite(D5, LOW);
delay(400);
digitalWrite(D5, HIGH);
delay(400);
}
// Showing screen
screen.loop((soilHumidityData / 950.0f) * 100.0f, airTemperatureData, airHumidityData);
Warning::warningLedLoop(soilHumidityData);
Warning::warningScreenLoop(soilHumidityData,airTemperatureData,airHumidityData);
serverHandler.loop();
}

52
embedded/src/warning.cpp Normal file
View file

@ -0,0 +1,52 @@
#include "warning.hpp"
#include "Screen.hpp"
#include "MainComponent.hpp"
using namespace Warning;
LedMoistureStatus moisture_status(LedMoistureStatus::IDLE);
void Warning::warningLedLoop(const float soilHumidity)
{
auto& led = MainComponent::GetInstance().getLed();
if ((soilHumidity < MoistureLevel::DRY) && (moisture_status != LedMoistureStatus::DRY))
{
moisture_status = LedMoistureStatus::DRY;
led.setColor(LedNumber::LED_PLANT,LedColors::TOO_DRY,200);
}
else if (soilHumidity >= MoistureLevel::DRY && soilHumidity < MoistureLevel::HUMID)
{
led.setColor(LedNumber::LED_PLANT,LedColors::LED_OFF);
moisture_status = LedMoistureStatus::IDLE;
}
else if ((soilHumidity >= MoistureLevel::HUMID) && (moisture_status != LedMoistureStatus::WET))
{
moisture_status = LedMoistureStatus::WET;
led.setColor(LedNumber::LED_PLANT,LedColors::TOO_WET,200);
}
}
void Warning::warningScreenLoop(const float plantMoisture, const float airTemperature, const float airHumidity)
{
auto& display = Display::Screen::GetInstance();
// Plant Moisture Warning
if(plantMoisture < MoistureLevel::DRY || plantMoisture > MoistureLevel::HUMID)
display.setWarningIcon(Display::Sensors::SOIL_MOISTURE);
else
display.setWarningIcon(Display::Sensors::SOIL_MOISTURE,false);
// Temperature Warning
if(airTemperature >= AIR_TEMPERATURE_TOO_HOT || airTemperature <= AIR_TEMPERATURE_TOO_COLD)
display.setWarningIcon(Display::Sensors::THERMOMETER);
else
display.setWarningIcon(Display::Sensors::THERMOMETER,false);
// Humidity Warning
if(airHumidity >= AIR_HUMIDITY_SATURATED)
display.setWarningIcon(Display::Sensors::AIR_HUMIDITY);
else
display.setWarningIcon(Display::Sensors::AIR_HUMIDITY,false);
}

33
embedded/src/warning.hpp Normal file
View file

@ -0,0 +1,33 @@
#ifndef _HEADER_WARNING
#define _HEADER_WARNING
#include <stdint.h>
namespace Warning {
/**
* @brief See the documentation on 'https://www.mouser.com/datasheet/2/744/Seeed_101020008-1217463.pdf'
*/
namespace MoistureLevel {
constexpr uint16_t AIR = 0;
constexpr uint16_t DRY = 300;
constexpr uint16_t HUMID = 700;
constexpr uint16_t WATER = 950;
}
constexpr float AIR_HUMIDITY_SATURATED = 95.0f;
/**
* @brief Source : 'https://extension.umd.edu/resource/temperature-and-humidity-indoor-plants/'
*/
constexpr float AIR_TEMPERATURE_TOO_HOT = 29.0f;
constexpr float AIR_TEMPERATURE_TOO_COLD = 14.0f;
enum class LedMoistureStatus {
IDLE,
DRY,
WET
};
void warningLedLoop(const float soilHumidity);
void warningScreenLoop(const float plantMoisture, const float airTemperature, const float airHumidity);
}
#endif // _HEADER_WARNING