Saturday, August 12, 2017

Thermostat based on Arduino

We will build a Thermostat based on Arduino, a temperature sensor, and a relay this time.
You can find it on GitHub

This Thermostat gives you the possibility to drive multiple devices in order to control temperature. In my case, I have installed two fans in my attic in order to cool it down in summer. There are no windows, so I had to force airflow. The first fan starts when the temperature reaches 35 degrees, and the second over 45.

You can control any reasonable amount of units, it's all configurable. You have also had access to basic statistics for the past two weeks:
Runtime of whole system

ON time for each relay

Statistics for 14 days

Statistics for 7th day

Configuration

The whole configuration is stored in Config.h. You can change PINs controlling relays, buttons, and input for reading temperature, thresholds, or timings.

Hardware


Buttons

Two buttons are dedicated for menu navigation and the third one resets statistics. They are stored in EEPROM (Storage.cpp) once a day. Pins assigned to buttons can be found in Config.h

Configuring Relays

Let's assume that we would like to have 3 relays:

  • ID:0, PIN: 1, Temperature setpoint: 20
  • ID:1, PIN: 10, Temperature setpoint: 30
  • ID:2, PIN: 11, Temperature setpoint: 40
  • First, you have to make sure that PIN of your choice is not already taken. All pins can be found in Config.h, they are defined by variables starting with DIG_PIN.

    You have to edit Config.h and configure PINs, thresholds and amount of relays. Obviously, some properties already exist, so you have to just edit them.

    const static uint8_t DIG_PIN_RELAY_0 = 1;
    const static uint8_t DIG_PIN_RELAY_1 = 10;
    const static uint8_t DIG_PIN_RELAY_2 = 11;
    
    const static uint8_t RELAYS_AMOUNT = 3;
    
    const static int16_t RELAY_TEMP_SET_POINT_0 = 20;
    const static int16_t RELAY_TEMP_SET_POINT_1 = 30;
    const static int16_t RELAY_TEMP_SET_POINT_2 = 40;
    

    Now we have to set up relays and controller, this happens in RelayDriver.cpp


        initRelayHysteresisController(0, DIG_PIN_RELAY_0, RELAY_TEMP_SET_POINT_0);
        initRelayHysteresisController(1, DIG_PIN_RELAY_1, RELAY_TEMP_SET_POINT_1);
        initRelayHysteresisController(2, DIG_PIN_RELAY_2, RELAY_TEMP_SET_POINT_2);


    Choosing Controller

    There two controllers available Hysteresis and PID

    Hysteresis Controller

    It's the one chosen in the example above, it has few additional configurations:

    const static uint32_t RELAY_DELAY_AFTER_SWITCH_MS = 300000; // 5 minutes
    const static uint32_t RHC_RELAY_MIN_SWITCH_MS = 3600000;
    

    RELAY_DELAY_AFTER_SWITCH_MS gives a wait time for switching to the next relay. Imagine that configuration from our example would start working in 40 degrees environment. This would result in enabling all three relays at the same time. This could eventually lead to high power consumption - depending on what you are controlling, an electric engine, for example, consumes more power during the start. In our case, switching relays have the following flow: the first relay goes, waits 5 minutes, the second goes on, waits 5 minutes, third goes on.

    RHC_RELAY_MIN_SWITCH_MS defines hysteresis, it's the minimum frequency for a particular relay to change its state. Once it's on, it will remain on for at least this period of time, ignoring temperature changes. This is quite useful if you control electric motors since each switch negatively impacts live time.

    PID Controller

    This is an advanced topic. Implementing such a controller is a simple task, finding the right amplitude settings is a different story. 

    To use the PID controller, you must change initRelayHysteresisController(.....) to initRelayPiDController(....) and find the right settings for it. You will find them in Config.h 

    I've implemented a simple simulator in Java so that it's possible to visualize the results. It can be found in the folder: pidsimulator.
    Below you can see simulations for two controllers PID, and  P. PID is not perfectly stable because I did not apply any sophisticated algorithm to find the right values.

    On both plots required temperature is set to 30 (blue). Current temperature indicates read line. The relay has two states ON and OFF. When it's enabled, temperature drops by 1.5, when it's disabled, it rises by 0.5.

    Software Design

    Message Bus

    Different software modules have to communicate with each other, hopefully not both ways ;) 

    For example: 
    • statistics module has to know when the particular relay goes on and off,
    • pressing a button has to change display content, and it also has to suspend services that would consume many CPU cycles, for example, temperature reading from the sensor,
    • after some time temperature reading has to be renewed, 
    • and so on...
    Every module is connected to Message Bus and can register for particular events and can produce any events.

    For example, pressing Next button results in the following flow:
    Some components have some tasks that need to be executed periodically. We could call their corresponding methods from the main loop since we have Message Bus it's only necessary to propagate the right event:

    LIBS

    The following libs are required to compile Thermostat:
    • https://github.com/maciejmiklas/Thermostat
    • https://github.com/milesburton/Arduino-Temperature-Control-Library
    • https://github.com/maciejmiklas/ArdLog.git

    Wednesday, March 22, 2017

    Weather Station based on Arduino and NodeMCU

    We are about to build a weather station with a forecast for three days, including a clock with local time and date.
    The whole project is based on Arduino, and NodeMCU provides access to the Internet over WiFi. The display is built from single LEDs. Since they can be really bright, it adopts illumination based on light conditions.


    Let's start from the beginning ;)
    I've found those 8x8 LED modules:

    So I've decided to combine a few to build a display. In my case, there are 3 lines, each consisting of 8 modules, 24 in total, this gives us 1532 single LEDs! 
    To drive a single module, I've chosen MAX72xx, I also wanted to improve my soldering skills, so I've decided to go for 24 PIN DIP chips and solder them to prototype boards:

    Well, that worked out pretty well when it comes to those skills, but I would recommend using LED modules combined with MAX Chip, this will save you at least a few hours, not to mention time spent afterward when a single cable gets loose ;) Such a combo module has only 3 wires instead of 16.

    So we have a hardware part for our display: 24 led modules with drivers. Now it's time to light them up! I've decided to go for Arduino Mega because it has two serial ports, so it's easier to debug things (one port will be used for communication with EPS8266). You could also use Uno, in this case, you would have to daisy chain MAX chips and change addressing in the software. I've used separate lines for each Max chip, but Uno just does not have enough digital output pins.

    I was looking for API that will join all led modules into one canvas, so you can print sprites without bothering with transitions between LED modules. I did not find anything that would make me happy, so I implemented one myself. It provides not only a simple canvas but fonts and a few animations. Basically, everything that will be needed to display time and weather.

    So ... we have a display and API to control it. Now we need to get the date and weather. Arduino does not support Internet connectivity and does not have enough resources to process incoming data. So I've decided to use NodeMCU. With a few Lua scripts, I could implement a simple API accessible over the serial port. Arduino connects over it with NodeMCU, obtains time, date, and weather, and displays it.

    To provide the date, NodeMCU connects with the NTP server and receives UTC time, afterwards it calculates the local date from it. I could use one of the Internet services to grab the date, but I was looking for a solution that would remain stable for a long time. Those services can change their API or just go offline, but the NTP protocol will remain unchanged. In the worst case, you will have to change the server name. The current implementation also supports failover through many different servers, so you will probably not have to bother with it soon ;)

    Getting the weather was a bit tricky because I had to find an API that would return a response small enough so it could be parsed by NodeMCU. I've decided to use Yahoo Weather. They provide a nice REST API with a small and simple response. Hopefully, they will keep interfaces stable for a long time.....


    Putting things together

    Hardware

    First, you have to build the display, I've already described it in this post: Arduino LED Display . You can use the same pin numbers on Mega to not have to alter the software.

    After the display is ready, you should connect ESP8266 and the photoresistor. The Schematic below contains all hardware elements together:

    The area on the top right corner is the display itself - 8x3 LED modules. Every single module exposes 3 PINs, those are MAX 72xxx PINs responsible for communication over SPI.
    Here you will find an exact schematic of the single module, including SPI PINs:


    The next one is just another representation:
    Those last two schematics can be found here: https://github.com/maciejmiklas/LEDDisplay/tree/master/doc/fritzing

    Now it's time to build software.

    Software for Arduino Mega

    You need to compile this project https://github.com/maciejmiklas/LEDClock and upload into Arduino.
    In order to compile it, you will need to include SPI module and two other libraries: https://github.com/maciejmiklas/LEDDisplay and https://github.com/maciejmiklas/LEDDisplay

    Here is a compiled software for those with the same hardware setup.

    You can use Sloeber for compilation, just follow the steps:
    1. Install Sloeber from http://eclipse.baeyens.it
    2. Create a new Arduino sketch and name it: LEDClock 
    3. Confirm the next screens until it asks you about code, select cpp
    4. Finish it, and you should get something like that:
    5. Clone the LEDClock project from https://github.com/maciejmiklas/LEDDisplay and move its content into the Sloeber project folder. This operation should replace two files: LEDClock.h and LEDClock.cpp. Now we have the Sloeber cpp project with the right main files. Those wired steps were necessary because I did not want to check into GitHub IDE-specific files. This is our structure now:
    6. There are still compilation errors, we will now add missing libraries
    7. Clone LED Display API: https://github.com/maciejmiklas/LEDDisplay and import into the project:
    8. Repeat this procedure for Logger: https://github.com/maciejmiklas/ArdLog
    9. Imported project LEDDisplay has a subfolder: examples. We must exclude it from compilation because it contains files with the main-loop, which will disturb Sloeber. Select Properties on folder examples and check: Exclude resource from the build:
    10. Now you should have the following structure:
    11. You can upload it!

    Software for NodeMCU/ESP8266

    I am using EPS8266 with a Lua interpreter, this combination is called NodeMCU.

    To provide weather and time to Arduino, you must clone NodeMCUUtil, modify a few scripts and finally upload those into NodeMCU. Below you will find exact instructions: 
    1. Compile firmware for NodeMCU so that it has all required modules. Here you will find instructions on it, which are the required modules: file, gpio, net, node, tmr, uart, wifi and cjson.
    2. Clone project containing Lua scripts: https://github.com/maciejmiklas/NodeMCUUtils
    3. Edit serialAPIClock.lua and set UTC offset for your location. This will be required to calculate local date from UTC time. For most European countries it's already set to correct value. For US you will have to replace require "dateformatEurope" with require "dateformatAmerica" and rename all method calls from setEuropeTime to setAmericaTime
    4. Edit yahooWeather.lua and provide city and country that you would like to have weather for.
    5. Create a new file called: credentials.lua and specify login data for WiFi connection, it's just one line, for example cred = {ssid = 'openwifi', password = '123456789'}
    6. Upload all Lua scirpts from main project's folder into NodeMCU:
      • credentials.lua
      • dateformat.lua
      • dateformatAmerica.lua
      • dateformatEurope.lua
      • ntp.lua
      • ntpClock.lua
      • serialAPI.lua
      • serialAPIClock.lua
      • serialAPIYahooWeather.lua
      • wlan.lua
      • yahooWeather.lua
    7. Now for the final touch, we need the init-file that will be executed right after NodeMCU boots up. In our case, we use only the Serial Port to expose weather and clock API. This also means that once our API is registered, it's impossible to execute standard NodeMCU commands, like file upload. For this reason init-script has two seconds delay, during this time you can still upload files, or just remove current init.lua file. Init-files are there: NodeMCUUtils/init/serialInit
      • init.lua
      • serialInit.lua

    Github repos used in this project