Raspberry PI mini server with UPS

It took a few adaptions (as the tiny CA uses Ubuntu, not Raspberry PiOS), but the UPS and stats display are working on my CA:

It’s not quite clear, though, how to start up the stats script at boot.

2 Likes

@danb35

Hi

A manual start works?

It does, though it’s not that stable–the script will die after a while with:

dan@tinyca:~/stats$ sudo python3 stats.py
[sudo] password for dan:
Traceback (most recent call last):
  File "stats.py", line 123, in <module>
    aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i))
  File "/usr/local/lib/python3.8/dist-packages/smbus2/smbus2.py", line 433, in read_byte_data
    ioctl(self.fd, I2C_SMBUS, msg)
OSError: [Errno 121] Remote I/O error

I’d expect I could prepare a systemd unit file that would start it on boot, and restart when it dies, but haven’t gotten that far yet.

2 Likes

You mean stats.py ?
EDIT yes you do, cross post

I’m working on wireguard and it has a python UI and I needed a systemd file. I think it could work in your case too…

Create file /etc/systemd/system/stats.service:

[Unit]
Description=Stats service
After=syslog.target
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/stats
ExecStart=python3 stats.py
User=statsuser
Restart=always

[Install]
WantedBy=multi-user.target
2 Likes

it’s an IO error from the i2c bus;

here (line 123) it reads 254 registers (variables) from the device attected to the i2c bus on address 0x17
You may want to try to add an delay for debugging to see if there are timing issues

    for i in range(1,255):
        aReceiveBuf.append(bus.read_byte_data(DEVICE_ADDR, i))
        sleep (0.01)

Note 0.01 = 10 ms and is quite long, as said for debuging

You also may want add a shebang on the first line #!/usr/bin/python3 of the script (amusing it’s python 3), chmod +x stats.py .
Now its a executable script and you can omit python3 to start it. :slight_smile:

EDIT: a bit of back ground
do not know the smb2 bus module /ibrary (whatever it is in python) do not know much python either.
the code above makes me feel very weary:
first: for i in range(1,255) is strange…hw does not work this way 0…254 ?? maybe python works different than C here.
second: the iteration is flawed, paraphrased it does this 254 times

hello 0x17 i want business with you : oke I’m open for businesses
i want var i from you : oke here it is
bye 0x17 : bye

should do

hello 0x17 i want business with you : oke I’m open for businesses
i want var 0 to 254 from you : oke here they are
bye 0x17 : bye

the python library seems to be able to do this:

from smbus2 import SMBus

with SMBus(1) as bus:
    # Read a block of 16 bytes from address 80, offset 0
    block = bus.read_i2c_block_data(80, 0, 16)
    # Returned value is a list of 16 bytes
    print(block)

EDIT2 (sorry get a bit upset when seeing stupid code :rage:)

if (aReceiveBuf[8] << 8 | aReceiveBuf[7]) > 4000:
        chargeStat = 'Charging USB C'
    elif (aReceiveBuf[10] << 8 | aReceiveBuf[9]) > 4000:
        chargeStat = 'Charging Micro USB.'
    else:
        chargeStat = 'Not Charging'

    battTemp = (aReceiveBuf[12] << 8 | aReceiveBuf[11])

    battCap = (aReceiveBuf[20] << 8 | aReceiveBuf[19])

We are only interested in register (variable) 7,8,9,10,11,12,19 and 20 why read (all?) 254 registers ?

4 Likes

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: