I’d guess it’s in order to have them available for future use, in case someone else wanted to add a readout for the contents of some other register. It’s also shorter, in terms of lines of code, to just grab them all rather than eight individual ones, and it arranges them in such a way that when you use, e.g., aReceiveBuf[20] in the code, you’re actually dealing with register 20.
But it’s looking like this:
aReceiveBuf = []
aReceiveBuf.append(0x00) # Placeholder
for i in range(1,255):
aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i))
That obviously doesn’t read all the registers, but the read_i2c_block_data method apparently can only read 32 registers at once. But, as you note, the code doesn’t use anything past 20 anyway, so this really doesn’t hurt anything.
The script runs this way, and keeps running–it doesn’t die after a few minutes with the error I’d posted earlier. But the “Charging USB C” / “Charging Micro USB” / “Not Charging” doesn’t seem to be responding properly. So I figured it was an issue that computers start counting at 0, and tried to adjust by decrementing the indices in aReceiveBuf by 1. Still getting the same result–the charging indication doesn’t change, and the battery capacity’s reporting at 24000%. So something clearly isn’t working quite as expected with this change.
The above makes one 16 bit number from two (8 bit) bytes, << 8 means shift (move to left 8 places) so aReceiveBuf[10] becomes the first 8 most significant bits (MSB) | puts aReceiveBuf[9] on the last 8 bits (LSB). (again if python resembles C here)
With that change (DEVICE_ADDR, 1, 32), and decrementing each of the indices by 1, the battery capacity looks reasonable, but the charge source is stuck on USB-C. So I guess the next step is to compare the results of aReceiveBuf using the script’s method for populating that, and using read_i2c_block_data.
But the script would still run reliably until it reached this error:
dan@tinyca:/opt/stats$ sudo ./stats2.py
Traceback (most recent call last):
File "./stats2.py", line 92, in <module>
IP = subprocess.check_output(cmd, shell = True )
File "/usr/lib/python3.8/subprocess.py", line 415, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/usr/lib/python3.8/subprocess.py", line 493, in run
with Popen(*popenargs, **kwargs) as process:
File "/usr/lib/python3.8/subprocess.py", line 858, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "/usr/lib/python3.8/subprocess.py", line 1605, in _execute_child
errpipe_read, errpipe_write = os.pipe()
OSError: [Errno 24] Too many open files
From the comments on the blog post, the fix for this is to add a line saying bus.close() just before time.sleep(.1). With that added, the script keeps running for quite some time.
I’m thinking it’s about time to set up a github repo for this script…
yes it does… most likely I’m to much affected with getting stuff running in KB’s of ram and (flash) disk.
though even if SW developers largely disagree, object oriented programing is not the holy grail and can have unwanted side effects…
@danb35, took the liberty to split this off (Also to get a bit off practice with moderating )
Another cause off problems can be INA219(shunt_resistor, address) constructs the class for a device also using the i2c bus.
Maybe it is better to put those and bus = smbus2.SMBus(DEVICE_BUS) in 2 functions.
To my understanding than classes get destroyed when the function finishes.
3D-printed it from the files the author makes available. A little annoyed at the $5 charge for them, but it is only US$5. Warning, though, that you’ll need to flip the case design to use a Pi 3; the design in the file only fits a Pi 4.
Good call.
Yeah, apparently Adafruit prefers their CircuitPython libraries now. Not sure right now how important that is. But what happened was that Adafruit provided a sample system stats script, and the blog author then tweaked it to add UPS stats.
That looks like the same listing I bought the UPS boards from.
I’ve bought from them a number of times in the past. They’re usually slow (this order came quite a bit quicker than usual), but they get there.
The script did die overnight, once again an i/o error in the I2C read from the UPS board:
Traceback (most recent call last):
File "./stats2.py", line 121, in <module>
aReceiveBuf = bus.read_i2c_block_data(DEVICE_ADDR, 1, 32)
File "/usr/local/lib/python3.8/dist-packages/smbus2/smbus2.py", line 617, in read_i2c_block_data
ioctl(self.fd, I2C_SMBUS, msg)
OSError: [Errno 5] Input/output error
They can be pretty inexpensive, all things considered. The Ender 3, for example, is well under US$200, it’s pretty popular (I have an Ender 3 Pro, which adds a few upgrades), and you can do a lot with it. Be sure to set up OctoPi when you get it:
I probably used the wrong word “big purchase” i think it’s more along the lines of justifying tech to someone who doesn’t see a use as they haven’t used it although i have to give her credit when i met her about 10years ago they only thing she used her computer for was ms exel and solitaire.
To be quite honest it’s sometimes good to have that voice saying “and we need this why” it’s made me prioritise spending to projects i really like and have stopped me wasting time on some things which in hindsight turned out to be a money pit
So I did that, and the tl;dr seems to be that there’s a bug in how the UPS board’s reporting things. Here’s the script I wrote:
#!/usr/bin/python3
import smbus2
DEVICE_BUS = 1
DEVICE_ADDR = 0x17
bus = smbus2.SMBus(DEVICE_BUS)
aReceiveBuf = []
aReceiveBuf.append(0x00) # Placeholder
for i in range(1,32):
aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i))
print(" ".join(hex(n) for n in aReceiveBuf))
bReceiveBuf = []
bReceiveBuf = bus.read_i2c_block_data(DEVICE_ADDR, 0, 32)
print(" ".join(hex(n) for n in bReceiveBuf))
It reads aReceiveBuf byte-by-byte, just as the original script did (though only for 32 bytes), and reads bReceiveBuf as a block. Here’s the output, with the charger unplugged:
Note that, except for byte 0 (which is unused anyway), the output is identical.
According to the UPS-Plus wiki, and according to the original code, bytes 7 and 8 contain the USB-C charging port voltage, LSB first, in mV. Unless I can’t count any more, this reads 0x14c9 mV, or 5321 mV–an entirely reasonable reading for a 5V-nominal charger. Except that the charger is unplugged.
I’m thinking about to structure the code/script as I’m used to do for code running on mcu’s.
Which means two or more, old school, nested state machines. Probably mealy machines as they are easier to code.
Tried to do this in python and to my surprise python has no switch/case statement.
Here some demo-code (heavily influenced by my C habits) for two nested state machines:
#!/usr/bin/python3
"""
First go on programming two nested (moore) state machines in python.
"""
import sys
from enum import Enum
from time import sleep
# DEMOCODE
test_counter = 1
test_exception_counter = 13
class Main (Enum):
Init = 0
Running = 1
Powerdown = 2
Exception = 3
class Running (Enum):
Init = 0
Read = 1
UpdateDisplay = 2
def set_main_state(state):
global main_state
main_state = state
def set_running_state(state):
global running_state
running_state = state
main_state = Main.Init
running_state = Running.Init
# Main state machine
while True:
if (main_state == Main.Init):
try:
print("\nInitializing\n")
set_main_state(Main.Running)
# DEMOCODE
sleep(1)
if ((test_exception_counter % 13) == 0):
raise Exception("I do not know Python!")
except Exception as error:
print("Somthing went wrong while Initilazing ")
the_error = error
set_main_state(Main.Exception)
pass
elif (main_state == Main.Running):
# Nested Running state machine
while (main_state == Main.Running):
if (running_state == Running.Init):
print("Setting up running statemachine\n")
set_running_state(Running.Read)
elif (running_state == Running.Read):
try:
print("Reading Data")
# DEMOCODE
if (test_counter >= 9):
test_counter = 0
set_main_state(Main.Powerdown)
else:
set_running_state(Running.UpdateDisplay)
test_exception_counter += 1
if ((test_exception_counter % 13) == 0):
# Introduce a bug of our own making
something = unknown
except Exception as error:
print("\nERROR: Unable to Read")
the_error = error
set_main_state(Main.Exception)
pass
elif (running_state == Running.UpdateDisplay):
print("Updating Display")
# DEMOCODE
if ((test_counter % 3) == 0):
set_running_state(Running.Read)
else:
the_error = "PANIC: Unknown Running state"
set_main_state(Main.Exception)
# DEMOCODE
test_counter += 1
sleep(0.1)
# End Nested Running state machine
elif (main_state == Main.Powerdown):
print("\nGracefully powering down the system\n")
# DEMOCODE
set_main_state(Main.Running)
else: # catch all including Main.Exception
if (main_state != Main.Exception) :
the_error = "PANIC: Unknown Main state"
print(f"\nAn exception occurred : {the_error}")
print("We logging and trying to fix it\n")
set_main_state(Main.Init)
# DEMOCODE
test_exception_counter = 1
sleep(1)
# END Main state machine
Codding in state machines result’s in very ringed and reliably code, specially because you have a clear flow of your code. Hence it is used in the embedded space very often.
EDIT: experimented with exception handling ans introduced a bug of my own making, catch it and don’t die…