DIY Dry box — Part 8

So, the rust implementation was a failure, I’ll look at it later, maybe when the API is not in alpha state anymore.

New development environment

So, on the hardware side we have the components plugged together on a breadboard:

Notice that my soldering skills are still not great, but they are improving.

And on the software side, I’m now trying Arduino IDE. First thing to do is to follow the instructions to install the libraries to support ESP32 boards.

After having used it for a while I have to say that Arduino IDE is nice to start a project, you don’t need to know much because it makes it easy to get the board and libraries into the project. But apart from that it’s a major pain in the ass as it’s a very basic IDE. I think they should have written a plugin for a major IDE like IntelliJ IDEA or VS Code to leverage the power of real IDEs.

So Arduino IDE has “sketches” with a .ino file extension. They basically are written in an Arduino’s flavored C++ and contain 2 functions:

  • setup that is run when the microcontroller starts;
  • loop which is run after setup then again and again in a loop.

Code

Here is what I ended up writing:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#include <Adafruit_HTU31D.h>
#include <Adafruit_NeoPixel.h>
#include <MQTTPubSubClient_Generic.h>
#include <WiFiClientSecure.h>
#include <Wire.h>

#define NUMPIXELS 1 // Number of pixels present on the board.
#define SLEEP_TIME 60 // Number of seconds to sleep between 2 measures.
#define NB_RETRIES 10 // Number of retries to do something before giving up.

Adafruit_NeoPixel pixels(NUMPIXELS, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
Adafruit_HTU31D htu = Adafruit_HTU31D();

const char HOSTNAME[] PROGMEM = "drybox"; // Host name of this microcontroller.

const char WIFI_SSID[] PROGMEM = "wifi-ssid"; // Put here the SSID of your WiFi access point.
const char WIFI_PASSWORD[] PROGMEM = "wifi password"; // Put here the password of your WiFi access point.

const char MQTT_SERVER[] PROGMEM = "mqtt.example.com"; // Put here the address of your MQTT server.
const uint16_t MQTT_PORT = 8883; // Put here the port on which the mqtt server is listening.
const char MQTT_USERNAME[] PROGMEM = "mqttloginfordrybox"; // Put here the username to log into the MQTT server. 
const char MQTT_PASSWORD[] PROGMEM = "mqtt password"; // Put here the password to log into the MQTT server.

// Let's encrypt root certificate
const char ROOT_CA[] PROGMEM = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/\n" \
"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \
"DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow\n" \
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB\n" \
"AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC\n" \
"ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL\n" \
"wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D\n" \
"LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK\n" \
"4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5\n" \
"bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y\n" \
"sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ\n" \
"Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4\n" \
"FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc\n" \
"SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql\n" \
"PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND\n" \
"TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw\n" \
"SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1\n" \
"c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx\n" \
"+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB\n" \
"ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu\n" \
"b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E\n" \
"U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu\n" \
"MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC\n" \
"5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW\n" \
"9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG\n" \
"WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O\n" \
"he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC\n" \
"Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5\n" \
"-----END CERTIFICATE-----\n";

const uint8_t TCA_ADDRESS = 0x70; // Address of the multiplexer.
const uint8_t HTU_ADDRESS = 0x40; // Address of the sensors.

// Colors
const uint32_t RED = 0xFF0000;
const uint32_t BLUE = 0x0000FF;
const uint32_t GREEN = 0x00FF00;
const uint32_t OFF = 0x000000;

WiFiClientSecure secureClient;
MQTTPubSubClient mqttClient;

void setup() {
  // Nothing to do here.
}

void loop() {
  if (!startup(RED)) {
    stop(BLUE);
    deep_sleep(OFF);
  }

  measure_and_publish(GREEN);

  stop(BLUE);
  deep_sleep(OFF);
}

//// STARTUP PROCESS ////

bool startup(uint32_t color) {
  startPixel();
  setLed(color);
  startSerial();
  bool success = startWifi();
  if (success) {
    success = startMqtt();
  }
  if (success) {
    success = Wire.begin();
  }
  return success;
}

void startPixel() {
  pixels.begin();
  pixels.setBrightness(1); // Reduce brightness.
}

void setLed(uint32_t color) {
  pixels.fill(color);
  pixels.show();  
}

void startSerial() {
  Serial.begin(115200);
  for (uint8_t i = 0; i < NB_RETRIES; i++) {
    if (Serial) break;
    delay(100); // Wait until serial port opens
  }
}

bool startWifi() {
  print("Attempting to connect to SSID: ");
  print(WIFI_SSID);
  print("…");

  WiFi.setHostname(HOSTNAME);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  for (uint8_t i = 0; i < NB_RETRIES; i++) {
    if (WiFi.status() == WL_CONNECTED) {
      println(" connected!");
      printWifiStatus();
      return true;
    }
    delay(1000);
    print(".");
  }
  println(" failed!");
  return false;
}

void printWifiStatus() {
  print("SSID: ");
  println(WiFi.SSID());

  IPAddress ip = WiFi.localIP();
  print("IP Address: ");
  println(ip);

  long rssi = WiFi.RSSI();
  print("Signal strength (RSSI):");
  print(rssi);
  println(" dBm");
}

bool startMqtt() {
  secureClient.setCACert(ROOT_CA);
  secureClient.connect(MQTT_SERVER, MQTT_PORT);
  if (!secureClient.connected()) return false;

  mqttClient.begin(secureClient);
  print("Connecting to MQTT broker…");
  bool connected = false;
  for (uint8_t i = 0; i < NB_RETRIES; i++) {
    if (mqttClient.connect(MQTT_USERNAME, MQTT_USERNAME, MQTT_PASSWORD)) {
      println(" connected!");
      return true;
    } else {
      delay(1000);
      print(".");
    }
  }
  println(" failed!");
  return false;
}

//// STOP PROCESS ////

void stop(uint32_t color) {
  setLed(color);
  if (Wire.available()) Wire.end();
  if (mqttClient.isConnected()) mqttClient.disconnect();
  if (secureClient.connected()) secureClient.stop();
  if (WiFi.status() == WL_CONNECTED) WiFi.disconnect(true, true);
}

//// SLEEP PROCESS ////

void deep_sleep(uint32_t color) {
  setLed(color);
  esp_sleep_enable_timer_wakeup(SLEEP_TIME * 1000UL * 1000UL);
  esp_deep_sleep_start();
}

//// MAIN PROCESS ////

void measure_and_publish(uint32_t color) {
  setLed(color);

  for (uint8_t port = 0; port < 8; port++) {
    print("Port ");
    print(String(port, HEX));
    tcaSelectPort(TCA_ADDRESS, port);
    delay(100);

    // Check if there's an HTU sensor
    if (!htu.begin(HTU_ADDRESS)) {
      println("");
      continue;
    }

    print(" - Address " + String(HTU_ADDRESS, HEX));

    sensors_event_t humidity_event, temperature_event;
    htu.getEvent(&humidity_event, &temperature_event);
    print(" - " + String(humidity_event.relative_humidity, 2) + " \% RH");
    print(" - " + String(temperature_event.temperature, 2) + " °C");
    println("");

    String humidityUrl = "gladys/master/device/mqtt:drybox" + String(port) + "/feature/mqtt:drybox" + String(port) + "humidity/state";
    String tempratureUrl = "gladys/master/device/mqtt:drybox" + String(port) + "/feature/mqtt:drybox" + String(port) + "temperature/state";
    mqttClient.publish(humidityUrl, String(humidity_event.relative_humidity, 2));
    mqttClient.publish(tempratureUrl, String(temperature_event.temperature, 2));
  }
}

void tcaSelectPort(uint8_t tcaAddress, uint8_t port) {
  if (port > 7) return;
  Wire.beginTransmission(tcaAddress);
  Wire.write(1 << port);
  Wire.endTransmission();
}

//// UTILS ////

void print(const char str[]) {
  if (Serial) Serial.print(str);
}
void println(const char str[]) {
  if (Serial) Serial.println(str);
}

void print(const float n) {
  if (Serial) Serial.print(n);
}

void print(const String &s) {
  if (Serial) Serial.print(s);
}
void println(const String &s) {
  if (Serial) Serial.println(s);
}

void println(const Printable& x) {
  if (Serial) Serial.println(x);
}

//// END ////

The code is not great, it would require some refactoring and fixing some stuff, but as a first try it’s good enough.

Beginning is about declaring constants. Note that I had to put Let’s Encrypt root certificate (line 25) so that the TLS connection to the MQTT server is accepted.

The idea here is that the microcontroller will connect to Wi-Fi and MQTT server, send the sensor values, disconnect, sleep for 1 minute, then do it all over again. So in the loop function (line 74):

  • call the startup function that:
    • starts the NeoPixel LED on the board (line 102);
    • lights it up in red (line 107);
    • starts the serial port for debugging (line 112);
    • starts Wi-Fi (line 120);
    • starts MQTT (line 154);
    • starts I²C connection (line 97).
  • then call measure_and_publish function that:
    • lights the NeoPixel in green;
    • then for each of the 8 ports of the multiplexer (line 198) will:
      • select the port (line 225);
      • check if there’s an HTU sensor (line 205);
      • read humidity and temperature from the sensor (line 213);
      • send those values to the MQTT server (lines 220 and 221).
  • then call stop function that:
    • lights the NeoPixel in blue;
    • stops I²C connection (line 179);
    • disconnect from MQTT server (line 180);
    • disconnect the TLS connection (line 181);
    • disconnect the Wi-Fi (line 182).
  • then call deep_sleep function that:
    • turns off the NeoPixel;
    • tells the microcontroller to wake up in 1 minute (line 189);
    • puts the microcontroller in deep sleep (line 190).

So with the breadboard connected to the computer and one sensor, I can see the following logs:

Attempting to connect to SSID: wifi-ssid….. connected!
SSID: wifi-ssid
IP Address: 192.168.XXX.XXX
Signal strength (RSSI):-68.00 dBm
Connecting to MQTT broker… connected!
Port 0 - Address 40 - 47.99 % RH - 23.77 °C
Port 1
Port 2
Port 3
Port 4
Port 5
Port 6
Port 7

And the result in Gladys Assistant:

Comments Add one by sending me an email.