Skip to content

Commit

Permalink
updates to readme
Browse files Browse the repository at this point in the history
added raw data outputs to sample script for easier access when debugging
  • Loading branch information
james-e-morris committed Nov 30, 2021
1 parent 877b914 commit bd7a62e
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 60 deletions.
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ HX711 class to sample a 24-bit ADC (or multiple) with Python 3 on a Raspberry Pi

Description
-----------
This library allows to configure and read from one or multiple HX711 ADCs with a Raspberry Pi. It was developed and tested in Python 3.8 on Raspberry Pi 4B with Raspberry Pi OS Lite v5.10.
This library allows the user to configure and read from one or multiple HX711 ADCs with a Raspberry Pi. It was developed and tested in Python 3.8 on Raspberry Pi 4B with Raspberry Pi OS Lite v5.10.

Capabilities:

Expand Down Expand Up @@ -57,6 +57,10 @@ try:
hx711.zero(readings_to_average=30)
except Exception as e:
print(e)
# uncomment below loop to see raw 2's complement and read integers
# for adc in hx711._adcs:
# print(adc.raw_reads) # these are the 2's complemented values read bitwise from the hx711
# print(adc.reads) # these are the raw values after being converted to signed integers
hx711.set_weight_multiples(weight_multiples=weight_multiples)

# read until keyboard interrupt
Expand All @@ -65,7 +69,7 @@ try:
start = perf_counter()

# perform read operation, returns signed integer values as delta from zero()
# readings aare filtered for bad data and then averaged
# readings are filtered for bad data and then averaged
raw_vals = hx711.read_raw(readings_to_average=10)

# request weights using multiples set previously with set_weight_multiples()
Expand All @@ -74,11 +78,12 @@ try:

read_duration = perf_counter() - start
print('\nread duration: {:.3f} seconds'.format(read_duration))
print(
'raw',
['{:.3f}'.format(x) if x is not None else None for x in raw_vals])
print(' wt',
['{:.3f}'.format(x) if x is not None else None for x in weights])
print('raw', ['{:.3f}'.format(x) if x is not None else None for x in raw_vals])
print(' wt', ['{:.3f}'.format(x) if x is not None else None for x in weights])
# uncomment below loop to see raw 2's complement and read integers
# for adc in hx711._adcs:
# print(adc.raw_reads) # these are the 2's complemented values read bitwise from the hx711
# print(adc.reads) # these are the raw values after being converted to signed integers
except KeyboardInterrupt:
print('Keyboard interrupt..')
except Exception as e:
Expand All @@ -90,12 +95,12 @@ GPIO.cleanup()

Author
-------
* [James Morris](https://morrisjam.es)
* James Morris (https://james.pizza)

License
-------
* Free software: MIT license

Credits
---------
* https://github.com/gandalf15/HX711/ as base starting point
* Starting point: https://github.com/gandalf15/HX711/
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[metadata]
name = hx711_multi
version = 1.1.2
version = 1.1.3
author = James Morris
author_email = [email protected]
description = HX711 class to sample 24-bit ADCs with Python 3 on a Raspberry Pi Rasperry Pi Zero, 2 or 3
Expand Down
112 changes: 67 additions & 45 deletions src/hx711_multi/hx711.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

#!/usr/bin/env python3
"""
This file holds HX711 class and ADC class which is used within HX711 in order to track multiple ADCs
Expand Down Expand Up @@ -35,15 +34,15 @@ class HX711:
if dout_pins not an int or list of ints
if gain_channel_A or select_channel not match required values
"""

def __init__(self,
dout_pins,
sck_pin: int,
channel_A_gain: int = 128,
channel_select: str = 'A',
all_or_nothing: bool = True,
log_level: str = 'WARN',
):
def __init__(
self,
dout_pins,
sck_pin: int,
channel_A_gain: int = 128,
channel_select: str = 'A',
all_or_nothing: bool = True,
log_level: str = 'WARN',
):
self._logger: Logger = getLogger('hx711-multi')
self._logger.setLevel(log_level)
consoleLogHandler = StreamHandler()
Expand All @@ -69,12 +68,14 @@ def _dout_pins(self):
def _dout_pins(self, dout_pins):
""" set dout_pins as array of ints. If just an int input, turn it into a single array of int """
self._single_adc = (type(dout_pins) is int)
_dout_pins_temp = convert_to_list(
dout_pins, _type=int, _default_output=None)
_dout_pins_temp = convert_to_list(dout_pins,
_type=int,
_default_output=None)
if _dout_pins_temp is None:
# raise error if pins not set properly
raise TypeError(
f'dout_pins must be type int or array of int.\nReceived dout_pins: {dout_pins}')
f'dout_pins must be type int or array of int.\nReceived dout_pins: {dout_pins}'
)
self.__dout_pins = _dout_pins_temp

@property
Expand All @@ -98,7 +99,8 @@ def _channel_A_gain(self, channel_A_gain):
if channel_A_gain not in [128, 64]:
# raise error if channel not 128 or 64
raise TypeError(
f'channel_A_gain must be A or B.\nReceived channel_A_gain: {channel_A_gain}')
f'channel_A_gain must be A or B.\nReceived channel_A_gain: {channel_A_gain}'
)
self.__channel_A_gain = channel_A_gain

@property
Expand All @@ -111,7 +113,8 @@ def _channel_select(self, channel_select):
if channel_select not in ['A', 'B']:
# raise error if channel not A or B
raise TypeError(
f'channel_select must be A or B.\nReceived channel_select: {channel_select}')
f'channel_select must be A or B.\nReceived channel_select: {channel_select}'
)
self.__channel_select = channel_select

def _init_gpio(self):
Expand Down Expand Up @@ -171,7 +174,8 @@ def _pulse_sck_high(self):
if pulse_end - pulse_start >= 0.00006:
# if pd_sck pin is HIGH for 60 us and more than the HX 711 enters power down mode.
self._logger.warn(
f'sck pulse lasted for longer than 60us\nTime elapsed: {pulse_end - pulse_start}')
f'sck pulse lasted for longer than 60us\nTime elapsed: {pulse_end - pulse_start}'
)
return False
return True

Expand All @@ -185,7 +189,7 @@ def _write_channel_gain(self):
B, 32 : total pulses = 26 (24 read data, 2 extra to set dout back to high)
Returns:
bool: True if pulsees were all successful
bool: True if pulses were all successful
"""

# get number of pulses based on channel configuration
Expand Down Expand Up @@ -255,7 +259,8 @@ def read_raw(self, readings_to_average: int = 10):

if not (1 <= readings_to_average <= 10000):
raise ValueError(
f'Parameter "readings_to_average" input to read_raw() is way too high... Received: {readings_to_average}')
f'Parameter "readings_to_average" input to read_raw() is way too high... Received: {readings_to_average}'
)

adc: ADC
# init each adc for a set of reads
Expand All @@ -277,17 +282,21 @@ def read_raw(self, readings_to_average: int = 10):
adc_measurements = [adc.measurement_from_zero for adc in self._adcs]

if not adc_measurements or all(x is None for x in adc_measurements):
self._logger.warning(f'All ADC measurements failed. '
'This is either due to all ADCs actually failing, '
'or if you have set all_or_nothing=True and 1 or more ADCs failed')
self._logger.warning(
f'All ADC measurements failed. '
'This is either due to all ADCs actually failing, '
'or if you have set all_or_nothing=True and 1 or more ADCs failed'
)

# return a single value if there was only a single dout pin set during initialization
if self._single_adc:
return adc_measurements[0]
else:
return adc_measurements

def read_weight(self, readings_to_average: int = 10, use_prev_read: bool = False):
def read_weight(self,
readings_to_average: int = 10,
use_prev_read: bool = False):
""" read raw data for all ADCs and then return with weight conversion
Args:
Expand All @@ -301,7 +310,8 @@ def read_weight(self, readings_to_average: int = 10, use_prev_read: bool = False

if not (1 <= readings_to_average <= 10000):
raise ValueError(
f'Parameter "readings_to_average" input to read_raw() is way too high... Received: {readings_to_average}')
f'Parameter "readings_to_average" input to read_raw() is way too high... Received: {readings_to_average}'
)

if not use_prev_read:
# perform raw read operation to get means and then offset and divide by weight multiple
Expand Down Expand Up @@ -350,8 +360,10 @@ def zero(self, readings_to_average: int = 30, retry_limit: int = 10):
for _ in range(retry_limit):
readings_new = self.read_raw(readings_to_average)
if readings is not None:
readings = [r_new if r_new is not None else r_old for r_new, r_old in zip(
readings_new, readings)]
readings = [
r_new if r_new is not None else r_old
for r_new, r_old in zip(readings_new, readings)
]
else:
readings = readings_new
if None not in readings:
Expand All @@ -368,9 +380,13 @@ def zero(self, readings_to_average: int = 30, retry_limit: int = 10):
zeroing_errors.append(e)
continue
if zeroing_errors:
raise Exception(f'Failed to zero all ADCs. Errors: {str(zeroing_errors)}')
raise Exception(
f'Failed to zero all ADCs. Errors: {str(zeroing_errors)}')

def set_weight_multiples(self, weight_multiples, adc_indices=None, dout_pins=None):
def set_weight_multiples(self,
weight_multiples,
adc_indices=None,
dout_pins=None):
"""
Sets the weight mutliples for ADCs to be used when calculating weight
Example: scale indicates value of 5000 for 1 gram on scale, weight_multiple = 5000 (to convert to weight in grams)
Expand All @@ -394,8 +410,9 @@ def set_weight_multiples(self, weight_multiples, adc_indices=None, dout_pins=Non
adcs = [adc for adc in self._adcs if adc._dout_pin in dout_pins]
else:
adc_indices = convert_to_list(adc_indices, _type=int)
adcs = [adc for (i, adc) in enumerate(
self._adcs) if i in adc_indices]
adcs = [
adc for (i, adc) in enumerate(self._adcs) if i in adc_indices
]

# set weight multiples to ADCs
for adc, weight_multiple in zip(adcs, weight_multiples):
Expand Down Expand Up @@ -430,11 +447,11 @@ class ADC:
measurement_from_zero (float): measurement minus offset
weight (float): measurement_from_zero divided by weight_multiple
"""

def __init__(self,
dout_pin: int,
logger: Logger,
):
def __init__(
self,
dout_pin: int,
logger: Logger,
):
self._dout_pin = dout_pin
self._logger = logger
self._zero_offset = 0.
Expand All @@ -459,8 +476,9 @@ def zero_from_last_measurement(self):
if self.measurement:
self.zero(self.measurement)
else:
raise ValueError(f'Trying to zero ADC (dout={self._dout_pin}) with a bad mean value. '
f'Value of measurement: {self.measurement}')
raise ValueError(
f'Trying to zero ADC (dout={self._dout_pin}) with a bad mean value. '
f'Value of measurement: {self.measurement}')

def zero(self, offset: float = None):
""" sets offset based on current value for measurement """
Expand Down Expand Up @@ -502,8 +520,8 @@ def _is_ready(self):

def _shift_and_read(self):
""" left shift by one bit then bitwise OR with the new bit """
self._current_raw_read = (
self._current_raw_read << 1) | GPIO.input(self._dout_pin)
self._current_raw_read = (self._current_raw_read << 1) | GPIO.input(
self._dout_pin)

def _finish_raw_read(self):
""" append current raw read value and signed value to raw_reads list and reads list """
Expand All @@ -514,14 +532,15 @@ def _finish_raw_read(self):
self.reads.append(self._current_signed_value)
# log 2's complement value and signed value
self._logger.debug(
f'Binary value: {bin(self._current_raw_read)} -> Signed: {self._current_signed_value}')
f'Binary value: {bin(self._current_raw_read)} -> Signed: {self._current_signed_value}'
)

def convert_to_signed_value(self, raw_value):
# convert to signed value after verifying value is valid
# raise error if value is exactly the min or max value, or a value of all 1's
if raw_value in [0x800000, 0x7FFFFF, 0xFFFFFF]:
self._logger.debug(
'Invalid raw value detected: {}'.format(hex(raw_value)))
self._logger.debug('Invalid raw value detected: {}'.format(
hex(raw_value)))
return None # return None because the data is invalid
# calculate int from 2's complement
# check if the sign bit is 1, indicating a negative number
Expand All @@ -545,8 +564,9 @@ def _calculate_measurement(self):
"""

# filter reads to valid data only
self._reads_filtered = [r for r in self.reads if (
(r is not None) and (type(r) is int))]
self._reads_filtered = [
r for r in self.reads if ((r is not None) and (type(r) is int))
]
if not len(self._reads_filtered):
# no values after filter, so return False to indicate no read value
return False
Expand All @@ -566,7 +586,8 @@ def _calculate_measurement(self):
# sometimes with a bad scale connection, the bit will come back ready out of chance and the binary values are garbage data
self._ready = False
self._logger.warn(
f'ADC (dout {self._dout_pin}) not ready, stdev from median was over {self._max_stdev}: {self._read_stdev}')
f'ADC (dout {self._dout_pin}) not ready, stdev from median was over {self._max_stdev}: {self._read_stdev}'
)
elif self._read_stdev:
self._ratios_to_stdev = [(dev / self._read_stdev)
for dev in self._devs_from_med]
Expand All @@ -575,7 +596,8 @@ def _calculate_measurement(self):
self.measurement = self._read_med
return True
_new_reads_filtered = []
for (read_val, ratio) in zip(self._reads_filtered, self._ratios_to_stdev):
for (read_val, ratio) in zip(self._reads_filtered,
self._ratios_to_stdev):
if ratio <= self._max_number_of_stdev_from_med:
_new_reads_filtered.append(read_val)
self._reads_filtered = _new_reads_filtered
Expand Down
15 changes: 10 additions & 5 deletions tests/simple_read_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
hx711.zero(readings_to_average=30)
except Exception as e:
print(e)
# uncomment below loop to see raw 2's complement and read integers
# for adc in hx711._adcs:
# print(adc.raw_reads) # these are the 2's complemented values read bitwise from the hx711
# print(adc.reads) # these are the raw values after being converted to signed integers
hx711.set_weight_multiples(weight_multiples=weight_multiples)

# read until keyboard interrupt
Expand All @@ -48,11 +52,12 @@

read_duration = perf_counter() - start
print('\nread duration: {:.3f} seconds'.format(read_duration))
print(
'raw',
['{:.3f}'.format(x) if x is not None else None for x in raw_vals])
print(' wt',
['{:.3f}'.format(x) if x is not None else None for x in weights])
print('raw', ['{:.3f}'.format(x) if x is not None else None for x in raw_vals])
print(' wt', ['{:.3f}'.format(x) if x is not None else None for x in weights])
# uncomment below loop to see raw 2's complement and read integers
# for adc in hx711._adcs:
# print(adc.raw_reads) # these are the 2's complemented values read bitwise from the hx711
# print(adc.reads) # these are the raw values after being converted to signed integers
except KeyboardInterrupt:
print('Keyboard interrupt..')
except Exception as e:
Expand Down

0 comments on commit bd7a62e

Please sign in to comment.