I got myself a FlowHive recently in the hope of keeping a colony of bees and producing honey in my village in South Lantau, Hong Kong. While i’m sorting out how to get a colony of local bees (Apis Cerana), i’ve added a few sensors to my hive. Counting bees going in/out the hive definitely looks cool, but those PCBs seem much too exposed for the hot humid climate here. The most interesting parameter for keeping an electronic eye on what’s going on inside the beehive seems to be its weight, as in this commercial system, and the open-source HiveEyes project. However, this last one seemed a bit over-engineered for my case, so i simplified it to this setup, using components that i already had:

  • ESP8266 in NodeMCUv1 package
  • DHT11 temperature and humidity sensor
  • DS18B20 waterproof temperature sensor
  • HX711 with 4x 5kg load cells
  • 2x 18650 Lithium cells battery
  • TP4056 battery charger, with solar panel
graph showing the weight of the hive, temperatures and humidity

This system measure the weight of my entire hive, the ambient temperature and humidity, the inside temperature, and the battery voltage. It connects to my home wifi to send the data to my favourite IoT cloud server emoncms.org which allows me create an interactive dashboard as above.

my ‘smart’ FlowHive (without super)
battery cells, TP4056 charger, perf board with LDO, ESP8266 NodeMCUv1, DHT11 and HX711

I will discuss the components of the system one by one, and include the full Arduino code below.

LOAD CELL

The HiveEyes project suggests a 100kg single point load cell, mounted between 2 H-frames to support the hive box. As my FlowHive base uses 4 legs, i decided to put a simple load cell under each leg, similar to a bathroom scale. Unfortunately i only had 5kg load cells available, so assuming my hive’s weight is equally distributed over the 4 legs, i can weigh up to 20kg only. I will probably need to upgrade this later as i think a full hive (brood box + super) will weigh more than 40kg.

5kg load cell – 3 wires
4 load cells wired up, note the rectangle: black wires on the left/right, brown-white on top/bottom, red wires going to HX711

I attached the load cells to a wooden circle with 2 support beams, and wired it up as in the above bathroom scale example. I am using this popular HX711 library and spent a while calibrating my 4-cell setup. As i am literally over-stretching my cells, i decided to use the HX711’s B channel that has a gain of 32, instead of the default 128 gain on the A channel. If you get negative raw values and clipping/overflow, try swapping the B+/B- wires, that solved it for me.

I included a calibrateScale() function in my code below, to find 2 parameters of my system: the offset and the scale factor. Then i will hard code these 2 parameters in my sketch. For the calibration, the trick is to first reset the scale:

scale.set_offset(0);  // no offset, raw value
scale.set_scale(1);   // reset scale factor to 1

Then a raw readings give you the offset, but the first few readings are not accurate, so take 10 readings and use the last one. This value is used with the scale.set_offset() function, so now the raw reading should be close to 0.

scale.set_offset(offset_val);

Then add a known weight on the scale, and take a reading. At this point, the scale.get_value() and scale.get_units() still return similar numbers, as the scale factor is still 1. You may see small differences as the first returns a long (integer) and the second a float. To find the scale factor, divide this number by the known weight. I used 2x 3kg fitness weights, so i divide the value by 6000 to set the scale to grams.

scale.set_scale(w_units / 6000.0); // w_units is float of w_val, as scale was 1 until now -- use 6KG weight

I record these 2 parameters, and use them in my sketch:

const long LOADCELL_OFFSET = 244000;  // by calibration
const float LOADCELL_DIVIDER = 66;     // by calibration

After installing the system, it was reporting weight values between 13 and 22kg, in a daily pattern, obviously incorrect values. After some experimenting, my conclusion is that the HX711 needs a lot of time to stabilise its output, so to be on the safe side, i take 10 readings of 100 samples, and only use the last one. This seems to return constant values as expected (see graph above). Below the earlier incorrect results with shorter sampling times.

SENSORS

The DHT11 and DS18B20 are easy to connect, i added a 10K pull-up resistor to 3.3V on their data pins as usual, and used the Adafruit DHT library.

The DHT11 is on the perf board, so it’s not really inside the beehive, it measures the outside temperature and humidity. The DS18B20 i used is waterproof, so i can put that in the brood box.

perf board with LDO plus input/output caps, NodeMCUv1, DHT11, HX711, pull-up resistors and voltage divider with ceramic cap

BATTERY + SOLAR

I’ve been running solar+battery powered IoT nodes for several years, and my conclusion is that it is much safer to over dimension the battery and solar panel than to try to estimate the required battery and panel sizes. So for this system, i am using a pair of 18650 Lithium cells recovered from an old laptop battery pack (in parallel), with a TP4056 charger/protection board. In the past, i have used dedicated solar chargers such as the expensive Seeedstudio’s Lipo Rider Pro, and cheaper boards with the CN3065 chip, but as this video shows, the common TP4056 charger boards work just as well, if not better, as solar chagers, provided the input voltage remains below 8V. Most common DIY solar panels seem to have an open circuit voltage of 6-7V, so that seems to be fine. I’ve been running 2 nodes for a few months and the TP4056 is doing great. In case they do get fried, i will add a bypass diode to clamp to voltage to e.g. max 6V. The common blue TP4056 provides +/- pads next to the microUSB connector, i connect these to my solar panel via a connector.

waterproof DS18B20 temp sensor on left, 2x 18650 Lithium cells on top, connected to TP4056 charger/protection board, HX711 on perf board with connectors for 4 wires to load cell array

The only problem with my setup is that the hive box is not exposed to the sun all day, so the solar panel will only receive a fraction of the available sunshine. I am measuring the battery voltage with a voltage divider of 10K/10K, and a 0.1uF cap on the ESP8266’s only ADC pin, so i can keep an eye on the solar charging.

DEEP SLEEP

The ESP8266 has an ESP.deepSleep() function that shuts down most of its parts to drops the current draw to less than 1mA. However, the NodeMCUv1 package that i’m using draws around 8mA in deep sleep, because of it’s voltage regulator and USB-UART converter. This blog post explains how to hack the NodeMCUv1 to remove the AMS1117 voltage regulator (easy), and disconnect power from the USB-UART chip (hard). I unsoldered the AMS1117 but i could hardly see a change in deep sleep current draw. The other mod seemed to difficult without microscope so i didn’t bother. Without the AMS1117 i measured 7mA in deep sleep, when powered by 3.3V. The Lithium cells have a nominal voltage of 3.7V (4.2V fully charged) so i had to add a Low Drop Out voltage regulator to reduce the battery’s voltage to 3.3V: i used the MCP1826S (rated up to 1A) with 10uF filtering caps on input and output.

The code has nothing in the loop() as it just executes the setup() and then goes into deep sleep. For this timed deep sleep, we need to connect D0=GPIO16 to the RST pin. This is slightly annoying because we cannot upload new code when this connection is made, so i had to unplug the NodeMCUv1 from its headers each time i uploaded a new version.

The setup starts with initiating the DHT as that requires a bit of warm up, then sets the 2 calibration parameters of the scale (see above). I use a boolean to enable/disable the calibrate_scale() function, false in normal operation.

The sendDataEmoncms() function checks if the readings are within normal range, and sends the acceptable data points to my cloud server as a HTTP GET request with a json-like URL. You can easily modify the function to send to any other cloud server, or use e.g. MQTT.

INSTALL

I found a plastic box that could hold the battery, charger, and perf board, so i screwed that into my FlowHive base.

I used some cable connectors for the 4 load cell wires and the solar panel. The 3 wires of each load cell are very thin and fragile, i’m not sure they will survive long.

Below if the code, for Arduino IDE, with NodeMCU 1.0 selected as board.

/*
   BuffaloLabs BEEHIVE monitoring node
   > ESP8266 in NodeMCUv1 package
   > deep sleep with D0=GPIO16 connected to RST
   > battery voltage with 10K/10K voltage divider and 104 cap on A0/GND
   > DHT11 with 10k pullup to D2=GPIO4
   > DS18B20 with 10k pullup to D1=GPIO5
   > HX711 on 3V3, GND, SCK=D6=GPIO12 , DT=D7=GPIO13
   > solar+battery powered

   libraries:
   DHT    https://github.com/adafruit/DHT-sensor-library
   HX711  https://github.com/bogde/HX711

   LOAD CELL
   single point load cell 100kg Bosche H30A
   H frame as in https://hiveeyes.org/docs/system/vendor/beutenkarl/index.html#hiveeyes-scale-beutenkarl
   http://karstenharazim.de/bienenmonitoring-hiveeyes-ping/
   cell+frame on taobao? https://item.taobao.com/item.htm?spm=a1z10.3-c.w4002-6686997183.44.872512737yvWa9&id=36064913547
   OR
   4 small load cells 5kg each, connected as in https://www.instructables.com/id/Arduino-Bathroom-Scale-With-50-Kg-Load-Cells-and-H/
   connect in a black/white circle, reds go to hx711 but careful with polarity of second pair, in wrong case i got negative readings and overflow

   LOW POWER
   low power NodeMCU: https://tinker.yeoman.com.au/2016/05/29/running-nodemcu-on-a-battery-esp8266-low-power-consumption-revisited/
   on 3.3V PSU
   standard NodeMCU: deepsleep 8mA
   remove AMS1117: deepsleep 7mA
   cut CP2102: not tried
   on 3.7V PSU with MCP1826S and 10uF on input and output
   remove AMS1117: deepsleep 8mA

   Tom Tobback - June 2020
*/

#define LED_PIN 2  // D4 = GPIO2 LED on ESP8266
const int sleep_interval_sec = 10 * 60;  // 10min
const boolean calibrate_scale = false;
const float known_weight = 21000;
float battery_voltage = 0;

// DHT
#include "DHT.h"
#define DHTPIN 4     // this is D2 on NodeMCU
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
DHT dht(DHTPIN, DHTTYPE);
float hum = 0;      // humidity
float temp = 0;     // temperature

// DS18B20
#include <OneWire.h>
OneWire  ds(5);      // on GPIO5 = D1
float ds_temp = 0;   // temperature

// HX711
#include "HX711.h"
#define LOADCELL_DOUT_PIN 13 // D7
#define LOADCELL_SCK_PIN 12  // D6
HX711 scale;
const long LOADCELL_OFFSET = 244000;  // by calibration
const float LOADCELL_DIVIDER = 66;     // by calibration
int scale_reading = 0;

// WIFI
#include <ESP8266WiFi.h>
const char* ssid = "xxx";
const char* password = "xxx";
WiFiClient espClient;

// EMONCMS
#define DATA_SERVER "emoncms.org"
#define APIKEY "xxx"
#define NODE 41   // TomT


/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

void setup() {

  WiFi.mode(WIFI_OFF);

  // start sensors
  dht.begin();                                    // needs to warm up
  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN, 32);  // default gain=128 on channel A, use 32 for channel B
  scale.set_scale(LOADCELL_DIVIDER);
  scale.set_offset(LOADCELL_OFFSET);

  pinMode(LED_PIN, OUTPUT);     // Initialize the onboard LED, inverted!
  /*
    for (int i = 0; i < 10; i++) {
    digitalWrite(LED_PIN, HIGH); // off
    delay(100);
    digitalWrite(LED_PIN, LOW);  // on
    delay(100);
    }
  */

  Serial.begin(115200);
  Serial.println();
  Serial.println();
  Serial.println("===================================");
  Serial.println("BuffaloLabs BEEHIVE monitoring node");
  Serial.println("===================================");

  if (calibrate_scale) calibrateScale();

  Serial.print(">>> read DHT sensor..");
  if (!readDHT()) {
    Serial.println(" failed to read from sensor!");
    //goSleep();
  } else {
    Serial.print(" OK \t temp:");
    Serial.print(temp, 2);
    Serial.print("\t hum:");
    Serial.println(hum, 1);
  }

  Serial.print(">>> read DS18B20 sensor..");
  if (!readDS()) {
    Serial.println(" failed to read from sensor!");
    //goSleep();
  } else {
    Serial.print(" OK \t ds_temp:");
    Serial.println(ds_temp, 2);
  }

  Serial.println(">>> read HX711 scale..");
  if (scale.is_ready()) {
    Serial.print("scale offset: ");
    Serial.println(scale.get_offset());
    Serial.print("scale scale: ");
    Serial.println(scale.get_scale());
    for (int i = 0; i < 10; i++) { // take 10 readings, keep last one
      scale_reading = scale.get_units(100);
      Serial.print("scale reading: ");
      Serial.print(scale_reading);
      Serial.println("gr");
      delay(100);
    }
  } else {
    Serial.println(" failed to read from scale!");
  }
  scale.power_down();              // put the ADC in sleep mode

  Serial.print(">>> read battery voltage..");
  battery_voltage = ((float)analogRead(A0)) * 3.3 / 1023.0 * 2.0;  // calibration factor     voltage divider 10k/10k
  Serial.print(battery_voltage, 2);
  Serial.println("V");

  Serial.print(">>> init WIFI..");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  byte counter = 0;
  while (WiFi.status() != WL_CONNECTED && counter < 30) {  //15 sec max
    delay(500);
    Serial.print(".");
    counter++;
  }
  //if (counter == 10) ESP.restart();   // this should be go to sleep!!! otherwise keeps trying to connect
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print(" connected as ");
    Serial.println(WiFi.localIP());
    sendDataEmoncms();
  } else {
    Serial.println("wifi failed, not sent");
  }

  goSleep();

}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

void loop() {
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

void sendDataEmoncms() {

  Serial.print(">>> sending to emoncms.org.. ");
  WiFiClient client;
  if (!client.connect(DATA_SERVER, 80)) {
    Serial.println("connection failed");
    return;
  }

  // construct URL and include data that are within range
  String url = "/input/post.json?node=";
  url += NODE;
  url += "&apikey=";
  url += APIKEY;
  url += "&json={";
  if (ds_temp > 0 && ds_temp < 100) {   // temp range
    url += "ds_temp:";
    url += String(ds_temp, 2);
    url += ",";
  }
  if (temp > 0 && temp < 100) {   // temp range
    url += "temp:";
    url += String(temp, 2);
    url += ",";
  }
  if (hum > 0 && hum < 100) {     // humidity range
    url += "hum:";
    url += String(hum, 1);
    url += ",";
  }
  if (scale_reading > 0) {        // weight range
    url += "weight:";
    url += String(scale_reading);
    url += ",";
  }
  url += "bat:";
  url += String(battery_voltage, 2);
  url += "}";

  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: emoncms.org\r\n" +
               "Connection: close\r\n\r\n");
  // delay(10);
  // Serial.println(url);

  /*
    unsigned long send_timeout = millis();
    while (client.available() == 0) {
      if (millis() - send_timeout > 5000) {
        Serial.println(">>> Client Timeout !");
        client.stop();
        return;
      }
    }
  */
  // if server replies OK, show
  if (client.find("200 OK")) {
    Serial.println("\t sent OK");
  } else {
    Serial.println("\t failed");
  }
  // flush rest of server reply
  while (client.available()) {                  // read the remaining text from serial otherwise connection cannot be closed
    client.read();
  }
  client.stop();

}
/////////////////////////////////////////////////////////////////////////////////////////

boolean readDHT() {
  hum = dht.readHumidity();
  temp = dht.readTemperature();
  if (isnan(hum) || isnan(temp) || hum > 100) return false;
  else return true;
}

/////////////////////////////////////////////////////////////////////////////////////////

void goSleep() {
  digitalWrite(LED_PIN, HIGH);  // off
  Serial.println(">>> going into deep sleep...");
  Serial.println();
  Serial.flush();
  ESP.deepSleep(sleep_interval_sec * 1e6);   // 10e6 is 10sec

}

/////////////////////////////////////////////////////////////////////////////////////////

void calibrateScale() {
  Serial.println("===============");
  Serial.println("CALIBRATE SCALE");
  Serial.println("===============");
  Serial.println("empty scale to calculate offset");
  scale.set_offset(0);
  scale.set_scale(1);
  delay(1000);
  long offset_val;
  for (int i = 0; i < 10; i++) {
    offset_val = scale.get_value(10);
    Serial.print("reading OFFSET value: ");
    Serial.println(offset_val);
    Serial.print("reading OFFSET units: ");
    Serial.println(scale.get_units(10), 2);
  }
  Serial.print("setting OFFSET: ");
  scale.set_offset(offset_val);
  Serial.println(scale.get_offset());
  //scale.tare();
  delay(1000);
  Serial.print("reading empty value, should be close to zero: ");
  Serial.println(scale.get_value(10));
  Serial.print("reading empty units, should be close to zero: ");
  Serial.println(scale.get_units(10), 2);

  Serial.println("CALIBRATION: place known weight on scale and press any key to continue");
  while (!Serial.available()) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();
  delay(1000);
  long w_val = scale.get_value(10);
  float w_units = scale.get_units(10);
  for (int i = 0; i < 10; i++) {
    w_val = scale.get_value(10);
    w_units = scale.get_units(10);
    Serial.print("reading scale (divide by the known weight to get DIVIDER): ");
    Serial.print("value: ");
    Serial.print(w_val);
    Serial.print("\t units: ");
    Serial.println(w_units, 2);
  }
  Serial.print("setting SCALE: ");
  scale.set_scale(w_units / known_weight); // w_units is float of w_val, as scale was 1 until now -- use 6KG weight
  Serial.println(scale.get_scale());
  Serial.println("FINISHED calibration");

  while (1) {
    Serial.print("value: ");
    Serial.print(scale.get_value(10));
    Serial.print("\t units: ");
    Serial.println(scale.get_units(10), 2);

    delay(500);
  }
}


/////////////////////////////////////////////////////////////////////////////////////////

float readDS(void) {
  byte i;
  byte present = 0;
  byte data[12];
  byte addr[8];
  int HighByte, LowByte, TReading, SignBit;
  float Tc;

  ds.reset_search();
  if ( !ds.search(addr)) {
    //      Serial.print("No more addresses.\n");
    ds.reset_search();
    return false;
  }

  if ( OneWire::crc8( addr, 7) != addr[7]) {
    //      Serial.print("CRC is not valid!\n");
    return false;
  }

  if ( addr[0] == 0x10) {
    //      Serial.print("DS18S20: ");
  }
  else if ( addr[0] == 0x28) {
    //      Serial.print("DS18B20: ");
  }
  else {
    //      Serial.print("Device family is not recognized: 0x");
    //      Serial.println(addr[0],HEX);
    return false;
  }

  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);        // start conversion, with parasite power on at the end

  delay(1000);     // maybe 750ms is enough, maybe not
  // we might do a ds.depower() here, but the reset will take care of it.

  present = ds.reset();
  ds.select(addr);
  ds.write(0xBE);         // Read Scratchpad

  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
  }

  LowByte = data[0];
  HighByte = data[1];
  TReading = (HighByte << 8) + LowByte;
  SignBit = TReading & 0x8000;  // test most sig bit
  if (SignBit) // negative
  {
    TReading = (TReading ^ 0xffff) + 1; // 2's comp
  }
  Tc = TReading * 0.0625;    // multiply by (100 * 0.0625) or 6.25

  if (SignBit) // If its negative
  {
    Tc = -Tc;
  }

  ds_temp = Tc;

  return true;
}
Beehive monitor, with load cells and temperature sensors

4 thoughts on “Beehive monitor, with load cells and temperature sensors

  • 27/08/2020 at 17:05
    Permalink

    Iam making weighing machine using 50kg load cell as similar one in your project with esp8266. i want to get my output on local server.
    so please help me regarding codes and circuit diagram .
    hoping for yours great help from yours end.

    Reply
  • 06/06/2021 at 18:34
    Permalink

    Hi.
    This Is awesome
    Can you tell me what you are using for the graphs?

    Reply
    • 07/06/2021 at 15:23
      Permalink

      i used https://emoncms.org/ which has been around for many years, designed mainly for energy monitoring: data storage and visualisation. these days grafana seems to be popular for fancy graphs.

      Reply
  • 04/08/2021 at 22:18
    Permalink

    complimenti Tom…
    ho eseguito i tuoi suggerimenti, riesco a fare la calibrazione dell’hx711 ma quando metto calibrate false e riavvio mi da sempre hx failed to read… non capisco il perche’….

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *