Raspberry PI mini server with UPS

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))

Could be replaced with this:

     aReceiveBuf = bus.read_i2c_block_data(DEVICE_ADDR, 0, 32)

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.

3 Likes

As said , duno much about python. :disappointed_relieved:

As such can not judge whether (line 119) aReceiveBuf = [] clears the buffer: other wise in the while true loop it will over flow at some point. :question:

Never the less (it seems to me) you are on the right track. :grinning:
@python experts, who can chip in…

2 Likes

Note the above :crazy_face:

what about ?

   aReceiveBuf = bus.read_i2c_block_data(DEVICE_ADDR, 1, 32)

EDIT for possible educational use only

(aReceiveBuf[10] << 8 | aReceiveBuf[9])

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)

3 Likes

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…

3 Likes

Going to repeat myself for the last time : duno much about python. :disappointed_relieved:

instigating the i2c instance bus = smbus2.SMBus(DEVICE_BUS) (line 117) in the while true loop does not seems to be oke… shouldn’t this be done once?

Do not see a destructor somewhere …

That’s addressed above:

2 Likes

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 can you buy me one of those and mail it to Kenya. would love to have one.

where’d you get the case from ?

@danb35, took the liberty to split this off (Also to get a bit off practice with moderating :wink:)

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.

Edit: especially as INA219 does not use the smbus2 library ( !!!Deprecation Warning!!!)

2 Likes

Bought the UPS board and display from AliExpress (hope they are the right ones)

how reliable are they to buy from

1 Like

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
1 Like

my next years big purchase (wife permitting :wink:) will be a 3d printer setup i mean ill need one for homeschooling as there essential now :slightly_smiling_face:

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:

1 Like

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:

0x0 0xf2 0xc 0x2 0x14 0x37 0x10 0xc9 0x14 0x3 0x0 0x36 0x0 0xce 0x10 0x74 0xe 0x74 0xe 0x5e 0x0 0x2 0x0 0x1 0x7a 0x1 0xdb 0x0 0xad 0x2f 0x0 0x0
0x1 0xf2 0xc 0x2 0x14 0x37 0x10 0xc9 0x14 0x3 0x0 0x36 0x0 0xce 0x10 0x74 0xe 0x74 0xe 0x5e 0x0 0x2 0x0 0x1 0x7a 0x1 0xdb 0x0 0xad 0x2f 0x0 0x0

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.

Edit:

3 Likes

Probably are going to be tempted to write it in C ;
Though one of the objectives is actually to have a fun project and learn some python on the go.

@danb35, without having a board to test this what i’d would try first:

3 Likes

Haven’t tried your suggestions yet, but there’s also discussion of this script on the Raspberry Pi forums:
https://www.raspberrypi.org/forums/viewtopic.php?f=28&t=316171

Sounds like they’re echoing some of your comments about opening resources excessively.

3 Likes

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. :sob:
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. :slight_smile:

EDIT: experimented with exception handling ans introduced a bug of my own making, catch it and don’t die… :rofl:

2 Likes