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

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.


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.


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.

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.

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;
}
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.
Hi.
This Is awesome
Can you tell me what you are using for the graphs?
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.
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’….