Introduction: Connected Letterbox Solar Powered

About: My name is Denis, 42 years old. My goal thru Instructables, is to share my little experience, my works and hacks : "Knowledge increases when we share it" !

For my second Ible, I'll describe you my works about my connected letterbox.

After reading this Instructable (+ many other one), and as my letterbox isn't near my house, I wanted to inspire me of Open Green Energy's works to connect my letterbox to my Domoticz server.

Goals

  • Be advised by Telegram when letters are coming;
  • Be advised by Telegram when a parcel are coming;
  • Check if the letters / parcels have been picked up.

My main constraint

The mailbox is relatively far from the house, and it was impossible to pull an electric cable up to it to power anything.

I had to find another solution: solar power was a good solution!

The BOM

Step 1: Global Scheme

Beautiful drawings are always better than long speeches ;-)

But a few explantations about MQTT, Domoticz and Telegram are always welcome!

MQTT (Message Queuing Telemetry Transport), is a messaging protocol, used to send datas between devices and other systems in the world of the IoT (Internet of things).

Without going into too much details, its operation is based on the principle of clients connecting to a server. In MQTT, clients are called Subscriber or Publisher, and the server is called Broker.

In this Instructable, I use only one publisher, the Lolin wired to my letterbox: when letters or parcel are detected via the reed contacts installed in the letterbox (Step 1 in shematic), its send MQTT message over WIFI to the Broker (Step 2).

The Broker part is done by Mosquitto, which is installed on the Raspberry Pi (Step 3).

About Domoticz :

As described on the source page, Domoticz is a "home automation system", allowing you to control various devices and receive input from various protocols : MQTT is one of the supported protocols …

As soon as information reaches him (Step 4), you can define events: In the case of the letterbox, I chose to send a Telegram notification (Step 5).

Finally, Telegram client is configured on my phone (and my wife's too! - Step 6): the end goal is reached...

Step 2: Shematic / Wiring

One word about the analog read :

First of all, I noticed after some researchs that Lolin mini D1 (as the old Wemos), hasbuilt in voltage divider for pin A0 (considering 220KΩ for R1 and 100KΩ for R2 - see on the right of the datasheet linked), allowing 3.2 Volts as maximum analog input voltage.

Considering max output voltage from the battery is 4,2v (limited by the charging board), and theorically, you only need to add an external resitor (in series with R1) to increase max input voltage range. Then, if you add 100K in series with R1, you'll have this result :

Vin * R1/(R1+R2) = Vout

4,2 * 320K/(320K+100K) = 3,2

In my circuit, I chose to be able to adjust its value, that's why I have preferred to use an adjustable resistor in my circuit : maybe it will be useless for you, but in my situation, I set its value to about 10KΩ to have a coherent value in Domoticz ...

Note that the A0 pin has 10 bit resolution : this means that in your sketch, your analog reading will return a value between 0 to 1024.

As I want to send a percentage value to Domoticz, I have to divide analog read result by 10,24.

Step 3: Power Managment

Of course, I want the letterbox to be autonomous. To reach my goal, I use these elements :

  • a Li-Ion 18650 battery of 4000mAh;
  • a solar panel wich can deliver 6V / 2W;
  • a TP4056 Lithium Battery charging board.

To choose the most suitable solar panel, I took a look at some examples, including this one : in this example, a 5.5V / 0.66W solar panel is used, and is probably sufficient for the purpose. In my case, and as the ESP8266 must stay ON during the day and must be able to run a servo motor to keep the house face to the sun, I chose a more powerful solar panel model (6V / 2W) - It also allows me to anticipate dark winter periods and cloudy days ;-)

Also, and in order to reduce energy expenditure to the maximum, I have selected the following scenarios:

  • knowing that the postman past only between 7am and 8pm, ESP is placed in DeepSleep the rest of the night;

  • The factor does not pass between Saturday noon and Monday morning: the ESP is also placed in DeepSleep mode during this period.

  • For the period between 7 am and 8 pm, and in order to reduce power consumption, I simply disable the network interface of the ESP : network is restarted only on the arrival of a parcel or a letter, just enough time to send the information to Domoticz.
    I do not need to be warned immediately and the few additional seconds necessary to restart the network interface are not harmful!

Some value about consumption in differents modes that I use for the Lolin - look at the datasheet, p18 :

  • In normal mode (with RF working), the power consumption can increase to 170mA ! As my letterbox is about 50 meters from my house (and at the limit of the WIFI signal ...) I suppose the power used to maintain connection is at his max ...
  • In Modem-sleep, the power consumption drop to 15mA. But as you can see in datasheet, it did not completely stop the modem, as the ESP is "maintaining a Wi-Fi connection with no data transmission".
  • In Deep-sleep, the power drop to 20uA.

To be certain that the wifi does not remain active unnecessarily, I preferred to disable it with the following commands. Note the many delay() call ... Without them, the ESP crash :


            WiFi.disconnect();
            delay(1000);
            WiFi.mode( WIFI_OFF );
            delay(1000);
            WiFi.forceSleepBegin();
            delay(1);

Overall, after several days of operation, it seems to work and especially to load correctly:

  • this allows me to run the servomotor every hour to position the house towards the sun;
  • I can also allow myself to reactivate the network interface every hour too to send to Domoticz the battery charge level.

Step 4: Installing Magnets and Reeds Contacts

As usual, I used my Proxxon to shape the place of the Reed in a piece of wood.

To fix the reed contact in its hole, I used a little bit of J-B weld.

For the parcel and the output, a little piece of tape, a little bit of hacksaw, and the goal is reached !

The advantage of my letterbox is that it is metal, which facilitates the positioning of magnets so that it interacts properly with reeds contacts.

Step 5: Connect to My Little House

To be able to easily connect and disconnect the cable that goes to the reed contacts from the letterbox to the house, I chose to use an Ethernet connector.

You can use this model or, like me, use an old Arduino Ethernet shield that hangs in my drawers:He did not suffer, he was brave in front of the saw, his death was fast ^^

Just a word about this Arduino Ethernet shield: do not expect to have 8 separate drivers ... Cables are paired by 2 inside the shield ... It drove me crazy for too long !!!

Step 6: In the House ...

Just enough place to fix the battery holder, setting the servo, and the RJ45 femal connector.

Step 7: Let It Turntable ...

The objective is to keep it face to the sun ...

To let the ability of being turnable, I used a long screw as an axle, with some nuts and two ball bearings ...

Until now, I used SG90 servo (torque: 1.8kg/cm at 4.8v).

To turn the house (and its few grams) is enough. On the other hand, I am not sure that its plastic gears resist for a long time the frequent gusts of wind that there are in my region.

I ordered another one (MG995 torque: 9.4kg/cm at 4.8v), not very expensive either, but with metal gears.

It will be the next thing to do when I have received it: I rely on my connected letterbox to notify me of his arrival!

Step 8: Some Tests

A few notes :

This sketch is only to emulate changes of hours during the day to allow me to control the position of the servo.

  • With SG90 : no extra needs, it can work with the OUT voltage coming from the battery controller.
  • But, with MG 995 :
    • The total angle of rotation is not the same (wider): I had to use an extra function to reduce it (Servo_Delta()).

    • Need a DC/DC Step up to provide enough voltage to the servo ... to be continued ...
/*
- TEST with SG90 : no extra needs, it can work with the OUT voltage comming from the battery controler 
- FOR MG 995 : 
	- use Servo_Delta() function ...
	- Need a DC/DC Step up to provide enough voltage to the servo ... to be continued :
*/

#include <Servo.h>

    bool Logs = true;

Servo myservo;
#define PIN_SERVO D2

    // servo position for     :  7h ,8h ,9h ,10h,11h,12h,13h,14h,15h,16h,17h,18h,19h,20h,21h
    //int Arr_Servo_Pos[]       = {177,173,163,148,133,118,100, 80, 61, 41, 28, 15,  2,  2,  2};
    int Arr_Servo_Pos[]       = {180,175,165,150,135,120,102, 82, 63, 43, 30, 15,  0,  0,  0};
    
int old;
int pos;
int i;

void setup() {
  Serial.begin(115200);
}

void loop() {

for(i = 7; i <= 22; i++){
    old = i;
          if (i == 7){
              if (Logs) Serial.println ("Positionne le servo pour 7 Heure");
              myservo.attach(PIN_SERVO);
                  for(int index = Arr_Servo_Pos[(sizeof(Arr_Servo_Pos) / sizeof(Arr_Servo_Pos[0])) -1]; index <= Arr_Servo_Pos[i-7] ; index++){
                      if (Logs) Serial.println (index);
                      if (Logs) Serial.print ("Adjusted value : ");
                      if (Logs) Serial.println(Servo_Delta(index)); 
                      delay(50);
                      //myservo.write(Servo_Delta(index));
                      myservo.write(index);
                  }
              myservo.detach();
          }
          else{
              if (i > 7 && i < 22){
                  if (Logs) Serial.println ("Positionne le servo pour " + String(i) + " Heures");
                  if (Arr_Servo_Pos[i-8] != Arr_Servo_Pos[i-7]){
                          myservo.write(Arr_Servo_Pos[i-8]); // write again the previous value to avoid jerky movements befor atach
			  myservo.attach(PIN_SERVO);
                          for(int index = Arr_Servo_Pos[i-8]; index >= Arr_Servo_Pos[i-7] ; index--){
                              if (Logs) Serial.println (index);
                              if (Logs) Serial.print ("Adjusted value : ");
                              if (Logs) Serial.println(Servo_Delta(index)); 
                              delay(200);
                              //myservo.write(Servo_Delta(index));
                              myservo.write(index);
                          }
                      delay(15);
                      myservo.write(Arr_Servo_Pos[i-7]); // write again the last value to avoid jerky movements when datach
                      myservo.detach();
                  }
              }
          }
      delay(2000);
    }
}

int Servo_Delta(int value){
  int Temp_val;
  Temp_val = (value*0.80)+9;
  return Temp_val;
}

Step 9: The Little House

As I told before, I didn't get any 3D printer. So I decide to use old vegetable crate ...

Maybe it will not last long weather, but by then, I would have time to consider another solution (or a friend who owns a 3D printer) : to protect the wood, I added a lot of varnish everywhere ...

You can see the "pretty curtains" ... That's what happens when you ask your wife to do the job ^^

Step 10: The Sketch

In progress ... But seems to be stable !

I'm still working on the code : as this isn't a definitive version, your comments / advices are welcome ;-)

Some remarks:

  • They are many delays() in the code : this is to avoid a lot of crash of the Lolin, especially while stoping an starting network ...

  • I didn't find an easy and reliable way to get the sun azimuth : that's why I fixed the servo value in function of what I observed ... I you have a good (and simple) way to get it, I'm interested !
    Maybe a track to study here,even if I prefer an online API gives me the azimuth directly according to the date, the hour, and the geographical position ...

  • About the sleep technic : as the Lolin is a 32-bit Tensilica Processor, its maximum value for a 32-bit unsigned integer is 4294967295 ... then, it give about max 71 minutes for the deep sleep interval. That's why I make sleeping l'ESP many times for about 60 minutes ...

EDIT - 08/10/2018 :

I discovered the servo has a lot of jerky movements, especially before the attachment (), detach() and each time the Lolin wake from deepSleep().

While studying a bit more datasheets, I realized two things:

  • On the Lolin datasheet, the D4 output is already connected with the BUILTIN_LED ...
  • On the ESP8266ex datasheet, we learn the D4 output is used as UART 1/U 1 TXD (Universal Asynchronous Receiver Transmitter). It's also specified that this UART1 is used for printing log.

By reading these informations, I realized that the D4 output was not a good idea, especially to manage a servo motor!

So, now the output used to control the servomotor is D2, the code below has been updated accordingly.

//****************************************
Date création             : 08/10/2018
Date mise en prod         : 08/10/2018
Version                   : 0.9.4
Version IDE Arduino       : 1.8.6
Upload speed              : 921600
Type de carte dans l'IDE  : "LOLIN(WEMOS) D1 R2 & mini"
Carte physique employée   : LOLIN(WEMOS) D1 R2 & mini (https://www.amazon.fr/gp/product/B01ELFAF1S/ref=oh_aui_detailpage_o00_s00?ie=UTF8&psc=1)

Pin   Function                      ESP-8266 Pin      Utilisation locale
---------------------------------------------------------------------------------------------
TX    TXD                           TXD
RX    RXD                           RXD
A0    Analog input, max 3.3V input  A0                Tension d'alimentaion
D0    IO                            GPIO16            Connecté à RST (pour le deep.sleep)
D1    IO, SCL                       GPIO5             
D2    IO, SDA                       GPIO4             Servo moteur             
D3    IO, 10k Pull-up               GPIO0             
D4    IO, 10k pull-up, BUILTIN_LED  GPIO2
D5    IO, SCK                       GPIO14            Reed relève
D6    IO, MISO                      GPIO12            Reed lettre
D7    IO, MOSI                      GPIO13            Reed colis
D8    IO, 10k pull-down, SS         GPIO15            
G     Ground                        GND
5V    5V                            –
3V3   3.3V                          3.3V
RST   Reset                         RST               Connecté à D0  (pour le deep.sleep)
****************************************/

#include <ESP8266WiFi.h>

    bool Logs = true;

// wifi
    const char* ssid     = "LOL";
    const char* password = "LOL";
    IPAddress ip(192, 168, 000, 000);
    IPAddress dns(192, 168, 000, 000);
    IPAddress gateway(192, 168, 000, 000);
    IPAddress subnet(255, 255, 000, 000);
    WiFiClient client;

// Servo
    #include <Servo.h>
    #define PIN_SERVO D2
    Servo myservo;
    // servo position for     :  7h ,8h ,9h ,10h,11h,12h,13h,14h,15h,16h,17h,18h,19h,20h,21h
    int Arr_Servo_Pos[]       = {179,175,165,150,135,120,102, 82, 63, 43, 30, 15,  1,  1,  1};

// Reeds
    #define PIN_SWITCH_OUT D5
    byte Old_Switch_State_OUT;
    byte Switch_State_OUT;
    #define PIN_SWITCH_IN_PARCEL D6
    byte Old_Switch_State_IN_PARCEL;
    byte Switch_State_IN_PARCEL;
    #define PIN_SWITCH_IN_LETTER D7
    byte Old_Switch_State_IN_LETTER;
    byte Switch_State_IN_LETTER;
    unsigned long switchPressTime;
    const unsigned long DEBOUCE_TIME = 200;
    
//  Analog
    #define PIN_ANALOG A0

// MQTT
    #include <PubSubClient.h>
    const char* MQTT_Server_IP  = "Your MQTT address";
    const int MQTT_Server_Port  = <your MQTT port>;
    int IDX_Letter_Box          = <your IDX>;
    int IDX_Parcel_Box          = <your IDX>;
    int IDX_Letter_Box_Battery  = <your IDX>;
    
    PubSubClient ClientMQTT(client);
    char MQTT_Message_Buff[70];
    String MQTT_Pub_String;

// Tension
    float vcc;

// NTP
    #include <time.h>
    time_t tnow;
    int Old_Time            = 0;
    int Int_Heures          = 0;
    int Int_Minutes         = 0;
    int Int_Sleep_Duration  = 63;

void setup(){
    Serial.begin(115200);
    network(true);
        pinMode(PIN_SWITCH_OUT, INPUT_PULLUP); 
        Old_Switch_State_OUT = digitalRead (PIN_SWITCH_OUT);
        pinMode(PIN_SWITCH_IN_LETTER, INPUT_PULLUP); 
        Old_Switch_State_IN_LETTER = digitalRead (PIN_SWITCH_IN_LETTER);
        pinMode(PIN_SWITCH_IN_PARCEL, INPUT_PULLUP); 
        Old_Switch_State_IN_PARCEL = digitalRead (PIN_SWITCH_IN_PARCEL);
        SendBatteryLevel(); 
    network(false);

  // NTP set
      tnow = time(nullptr);
      Int_Heures    = String(ctime(&tnow)).substring(11,13).toInt();
      Int_Minutes   = String(ctime(&tnow)).substring(14,16).toInt();

  // Deepsleep for the night
      if(!((Int_Heures >= 7) && (Int_Heures <= 20))){
          Serial.print("Sleep pour la nuit (");
          Serial.print(Int_Sleep_Duration - Int_Minutes);
          Serial.println(" minutes)");
          sleep(Int_Sleep_Duration - Int_Minutes);
      }
}

void loop() {

  // NTP set
      tnow = time(nullptr);
      Int_Heures    = String(ctime(&tnow)).substring(11,13).toInt();
      Int_Minutes   = String(ctime(&tnow)).substring(14,16).toInt();
      //Serial.println(String(ctime(&tnow)));
      //Serial.println ("Heure : " + String(ctime(&tnow)).substring(11,13));
      //Serial.println (String(ctime(&tnow)).substring(11,13).toInt());

  // Servo management
      if (Old_Time != Int_Heures){
          Old_Time = Int_Heures;

          if (Int_Heures == 7){
              if (Logs) Serial.println ("Positionne le servo pour 7 Heure");
              myservo.attach(PIN_SERVO);
                  for(int index = Arr_Servo_Pos[(sizeof(Arr_Servo_Pos) / sizeof(Arr_Servo_Pos[0])) -1]; index <= Arr_Servo_Pos[Int_Heures-7] ; index++){
                      if (Logs) Serial.println (index);
                      delay(50);
                      myservo.write(index);
                  }
              myservo.detach();
              network(true);
                  SendBatteryLevel();
              network(false);
          }
          else{
              if (Int_Heures > 7 && Int_Heures < 22){
                  if (Logs) Serial.println ("Positionne le servo pour " + String(Int_Heures) + " Heures");
                  if (Arr_Servo_Pos[Int_Heures-8] != Arr_Servo_Pos[Int_Heures-7]){
                      myservo.write(Arr_Servo_Pos[Int_Heures-8]); // write again the previous value to avoid jerky movements befor attach
                      myservo.attach(PIN_SERVO);
                          for(int index = Arr_Servo_Pos[Int_Heures-8]; index >= Arr_Servo_Pos[Int_Heures-7] ; index--){
                              if (Logs) Serial.println (index);
                              delay(200);
                              myservo.write(index);
                          }
                      delay(15);
                      myservo.write(Arr_Servo_Pos[Int_Heures-7]); // write again the last value to avoid jerky movements when detach
                      myservo.detach();
                  }
                  network(true);
                      SendBatteryLevel();
                  network(false);
              }
          }
       }

  // Deepsleep if saturday after 13h
      if((String(ctime(&tnow)).substring(0,3) == "Sat") && (Int_Heures >= 13)){
          if (Logs) Serial.print("Sleep pour le samedi aprés midi (");
          if (Logs) Serial.print(Int_Sleep_Duration - Int_Minutes);
          if (Logs) Serial.println(" minutes)");
          sleep(Int_Sleep_Duration - Int_Minutes); 
      }
  
  // Deepsleep if sunday
      if(String(ctime(&tnow)).substring(0,3) == "Sun"){
          if (Logs) Serial.print("Sleep pour le dimanche (");
          if (Logs) Serial.print(Int_Sleep_Duration - Int_Minutes);
          if (Logs) Serial.println(" minutes)");
          sleep(Int_Sleep_Duration - Int_Minutes); 
      }
  
  // Reeds managment 
      Switch_State_OUT = digitalRead (PIN_SWITCH_OUT);
      if (Switch_State_OUT != Old_Switch_State_OUT){
          if (millis () - switchPressTime >= DEBOUCE_TIME){
              switchPressTime = millis ();
              if (Switch_State_OUT == HIGH){
                  Serial.println ("courrier relevé !");
                  network(true);
                      delay(5000);
                      MQTT_Pubilsh(IDX_Letter_Box, 0, "0");
                      delay(5000);
                      MQTT_Pubilsh(IDX_Parcel_Box, 0, "0");
                      delay(5000);
                  network(false);
              }   
          }
          Old_Switch_State_OUT = Switch_State_OUT;
      }

      Switch_State_IN_LETTER = digitalRead (PIN_SWITCH_IN_LETTER);
      if (Switch_State_IN_LETTER != Old_Switch_State_IN_LETTER){
          if (millis () - switchPressTime >= DEBOUCE_TIME){
              switchPressTime = millis ();
              if (Switch_State_IN_LETTER == HIGH){
                  Serial.println ("courrier arrivé !");
                  network(true);
                      delay(5000);
                      MQTT_Pubilsh(IDX_Letter_Box, 1, "Courrier");
                      delay(5000);
                  network(false);
              }
          }
          Old_Switch_State_IN_LETTER = Switch_State_IN_LETTER;
      }

      Switch_State_IN_PARCEL = digitalRead (PIN_SWITCH_IN_PARCEL);
      if (Switch_State_IN_PARCEL != Old_Switch_State_IN_PARCEL){
          if (millis () - switchPressTime >= DEBOUCE_TIME){
              switchPressTime = millis ();
              if (Switch_State_IN_PARCEL == HIGH){
                  Serial.println ("colis arrivé !");
                  network(true);
                      delay(5000);
                      MQTT_Pubilsh(IDX_Parcel_Box, 1, "Colis");
                      delay(5000);
                  network(false);
              } 
          }
          Old_Switch_State_IN_PARCEL = Switch_State_IN_PARCEL;
      }
}

      void SendBatteryLevel(){
          delay(5000);
          vcc = analogRead(PIN_ANALOG)/10.24;
          if (Logs) Serial.println ("\tTension relevée: " + String(vcc,0));
          MQTT_Pubilsh(IDX_Letter_Box_Battery, 0, String(vcc,0));
          delay(5000);
      }

      void sleep(int Min_Duration){
        ESP.deepSleep(Min_Duration * 60e6);
      }

      void network(bool UpDown){
        if (UpDown){
              Serial.print("Network start ");
              WiFi.forceSleepWake();
              delay(1);
              
           // init WIFI
              WiFi.config(ip, dns, gateway, subnet);
              WiFi.begin(ssid, password);
              while (WiFi.status() != WL_CONNECTED) {
                  delay(500);
                  Serial.print(".");
              }
              delay(5000);
              Serial.println(".");
              Serial.print("\tConnected - IP Address : ");
              Serial.println(WiFi.localIP());
        
           // init MQTT
              ClientMQTT.setServer(MQTT_Server_IP, MQTT_Server_Port);
        
           // Init NTP
              Serial.print("\tTime Synch. ");
              configTime(0, 0, "fr.pool.ntp.org");  
              setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 0);
              while(time(nullptr) <= 100000) {
                Serial.print(".");
                delay(100);
              }
              Serial.println(".");
        }
        else{
            Serial.println("Network stop.");
            WiFi.disconnect();
            delay(1000);
            WiFi.mode( WIFI_OFF );
            delay(1000);
            WiFi.forceSleepBegin();
            delay(1);
        }
      }

      void reconnect() {
          while (!ClientMQTT.connected()) {
              Serial.print("\tAttempting MQTT connection...");
              // Attempt to connect
              if (ClientMQTT.connect("ESP8266ClientBAL")) {
                  Serial.println("connected");
              } else {
                  Serial.print("failed, rc=");
                  Serial.print(ClientMQTT.state());
                  Serial.println(" try again in 5 seconds");
                  // Wait 5 seconds before retrying
                  delay(5000);
              }
          }
      }

      void MQTT_Pubilsh(int Int_IDX, int N_Value, String S_Value) {
         if (!ClientMQTT.connected()) reconnect();
            vcc = analogRead(PIN_ANALOG)/10.24;
            Serial.println("\tSend info to MQTT ...");
            MQTT_Pub_String = "{ \"idx\" : " + String(Int_IDX) + ", \"Battery\" : " + String(vcc,0) + ", \"nvalue\" : " + N_Value + ", \"svalue\" : \"" + S_Value + "\"}";
            MQTT_Pub_String.toCharArray(MQTT_Message_Buff, MQTT_Pub_String.length()+1);
            ClientMQTT.publish("domoticz/in", MQTT_Message_Buff);
          ClientMQTT.disconnect();
      }

Step 11: Domoticz

In Domoticz:

For general use :

  • Create two "Dummy (Does nothing, use for virtual switches)":
    1. The first for letters ...
    2. The second for parcel ...
  • For each of them, personalize notifications;
  • Of course, you need to setup your Tegegram token.

Optionally:

You can add an "Utility sensor" to supervise your battery charge level.

Tips : here you can find a lot of free custom icons ...

Step 12: Conclusion

Hope this Instructable will help you :

  • whether to make your own connected letterlbox;

  • or just to give you some ideas for your projects!

If you have ideas for improvements, I'm listening!

PS : sorry for my english, Google translation helps me a lot but is probably not perfect ;-)

Electronics Tips & Tricks Challenge

Participated in the
Electronics Tips & Tricks Challenge