This project details using the Unihiker as a scale along with a DFRobot Gravity series HX711 pressure sensor. I previously converted the library to work with circuit python for the scale's i2c interface so I opted to do the same here with the pinpong library for the unihiker. Overall the conversion process was straightforward and I was able to get the scale running without too much effort.
Note: the grams move a bit on a decimal level as the scale functions. I think it has to do with the hx711 needing an excitation voltage of around 5V or it will vary (from what I've seen folks discuss online). I may try level shifting in the future here to see if it improves but it works for my needs with the small variation.
The above steps cover the gist of the steps to get this project up and running. You'll need to upload both the unihiker scale code and the associate i2c library I've provided here with this project.
For my purposes I placed both files in the root directory and accessed it via the run programs flow. You can access the getting started documentation for the Unihiker to see more about that process.
For the stand there is a printables link with access to the model: https://www.printables.com/model/894852-unihiker-stand
I soldered a jst 3 pin connector to my LED ring to allow me to connect directly to the unihiker using the provided cables. As I'm only using 20 in each channel for the LED brightness the current draw should be minimal (the lights are plenty bright).
The LED ring is used as part of a night light feature I included to utilize the Unihiker's light sensor to determine whether or not to enable.
The scale provides two buttons to further utilize it:
The top button on the unihiker activates the calibration mode which allows a weight to be used to calibrate the scale (it can be configured in the script with an ENV variable)
The bottom button on the unihiker activates the peel functionality resetting the scale back to 0
# dfrobot_unihiker_scale.py
#!/usr/bin/python3
import time
from unihiker import GUI
from pinpong.board import Board, I2C, Pin, NeoPixel
from pinpong.extension.unihiker import *
from dfrobot_i2c_hx711 import DFRobot_HX711_I2C
import matplotlib.pyplot as plt
from datetime import datetime
import matplotlib.dates as mdates
# Weight value
CALIBRATION_WEIGHT_VALUE=50
# Arbitrary value for light sensor to trigger LED ring light
LIGHT_ENABLE_THRESHOLD = 300
Board("UNIHIKER").begin()
NEOPIXEL_PIN = Pin(22)
PIXELS_NUM = 8
neopixel = NeoPixel(NEOPIXEL_PIN, PIXELS_NUM)
# Initialize GUI and variables
gui = GUI()
lightEnabled = None
hx711_sensor = DFRobot_HX711_I2C()
# Initialize the HX711 sensor
hx711_sensor.begin()
# Page header
gui.draw_text(text="Weight Sensor", x=30, y=20, font_size=20, color="teal")
state = gui.draw_text(text="Current State: Weighing", x=30, y=80, font_size=10, color="blue")
# Weight display
weightText = gui.draw_text(text="0.00 g", x=20, y=180, font_size=34, color="teal")
def handleNightMode():
global lightEnabled
lightValue = light.read()
if 0 <= lightValue < LIGHT_ENABLE_THRESHOLD:
lightEnabled = True
for i in range(PIXELS_NUM):
neopixel[i] = (20, 20, 20)
elif lightEnabled:
lightEnabled = False
for i in range(PIXELS_NUM):
neopixel[i] = (0, 0, 0)
# Track the time when the state was last changed
last_state_change_time = time.time()
initial_state_set = False
while True:
handleNightMode()
# Check if button A is pressed for calibration
if button_a.is_pressed() == True:
state.config(text="Current State: Calibrating {}g".format(CALIBRATION_WEIGHT_VALUE))
last_state_change_time = time.time()
hx711_sensor.setTriggerWeight(CALIBRATION_WEIGHT_VALUE)
hx711_sensor.setCalThreshold(1)
hx711_sensor.enableCalibration()
i = 0
while hx711_sensor.peelFlag() != 2:
time.sleep(1)
i += 1
if i >= 7:
break
calibration = hx711_sensor.calibration()
state.config(text="Calibrated: {:.2f}".format(calibration))
hx711_sensor.setCalibration(calibration)
last_state_change_time = time.time()
initial_state_set = False
# Check if button B is pressed for tare (peel)
if button_b.is_pressed() == True:
state.config(text="Current State: Peeled")
last_state_change_time = time.time()
hx711_sensor.peel()
initial_state_set = False
# Update the weight display
weight = hx711_sensor.weight(10)
weightText.config(text="{:.2f} g".format(weight))
# Check and update the state to "Current State: Weighing" if needed
if not initial_state_set and (time.time() - last_state_change_time) >= 5:
state.config(text="Current State: Weighing")
initial_state_set = True
time.sleep(0.1)
# dfrobot_i2c_hx711.py
import time
import struct
from pinpong.board import gboard, I2C
# Define constants
I2C_ADDR = 0x64
REG_CLEAR_REG_STATE = 0x65
REG_DATA_GET_RAM_DATA = 0x66
REG_DATA_GET_CALIBRATION = 0x67
REG_DATA_GET_PEEL_FLAG = 0x69
REG_DATA_INIT_SENSOR = 0x70
REG_SET_CAL_THRESHOLD = 0x71
REG_SET_TRIGGER_WEIGHT = 0x72
REG_CLICK_RST = 0x73
REG_CLICK_CAL = 0x74
class DFRobot_HX711_I2C:
def __init__(self, board=None, i2c_addr=I2C_ADDR, bus_num=0):
if isinstance(board, int):
i2c_addr = board
board = gboard
elif board is None:
board = gboard
self.i2c_addr = i2c_addr
self._i2c = I2C(bus_num)
self.buffer = bytearray(3)
self.rxbuf = bytearray(16)
self._offset = 0
self._calibration = 1752.60
def begin(self):
time.sleep(0.03)
self.buffer[0] = REG_DATA_INIT_SENSOR
self._write_register()
time.sleep(0.03)
self.buffer[0] = REG_CLEAR_REG_STATE
self._write_register()
time.sleep(0.03)
self._offset = self.average(1)
self.setCalibration(self._calibration)
def average(self, times=1):
sum = 0
for _ in range(times):
sum += self.raw_weight()
return sum / times
def weight(self, times=1):
weight = self.average(times)
time.sleep(0.05)
peel_flag = self.peelFlag()
if peel_flag == 1:
self._offset = self.average(times)
elif peel_flag == 2:
self._calibration = self.calibration()
return (weight - self._offset) / self._calibration
def raw_weight(self):
self.buffer[0] = REG_DATA_GET_RAM_DATA
self._write_register()
time.sleep(0.03)
self._read_register(4)
return ((self.rxbuf[0] << 24) | (self.rxbuf[1] << 16) | (self.rxbuf[2] << 8) | self.rxbuf[3])
def peel(self):
self._offset = self.average(1)
self.buffer[0] = REG_CLICK_RST
self._write_register()
time.sleep(0.03)
def peelFlag(self):
self.buffer[0] = REG_DATA_GET_PEEL_FLAG
self._write_register()
time.sleep(0.03)
self._read_register(1)
return self.rxbuf[0]
def calibration(self):
self.buffer[0] = REG_DATA_GET_CALIBRATION
self._write_register()
time.sleep(0.03)
self._read_register(4)
value = (self.rxbuf[0] << 24) | (self.rxbuf[1] << 16) | (self.rxbuf[2] << 8) | self.rxbuf[3]
return struct.unpack('>f', struct.pack('>I', value))[0]
def enableCalibration(self):
self.buffer[0] = REG_CLICK_CAL
self._write_register()
time.sleep(0.03)
def setCalibration(self, cal):
self._calibration = cal
def setCalThreshold(self, cal):
self.buffer[0] = REG_SET_CAL_THRESHOLD
tx_data = struct.pack('>H', cal)
self.buffer[1:3] = tx_data
self._write_register()
time.sleep(0.03)
def setTriggerWeight(self, weight):
self.buffer[0] = REG_SET_TRIGGER_WEIGHT
tx_data = struct.pack('>H', weight)
self.buffer[1:3] = tx_data
self._write_register()
time.sleep(0.03)
def _write_register(self):
self._i2c.writeto(self.i2c_addr, self.buffer)
def _read_register(self, length):
self.rxbuf = self._i2c.readfrom(self.i2c_addr, length)