numat / tripplite Goto Github PK
View Code? Open in Web Editor NEWPython USB HID interface to Tripplite UPS battery backups.
License: GNU General Public License v2.0
Python USB HID interface to Tripplite UPS battery backups.
License: GNU General Public License v2.0
Right now, this driver can handle reading multiple tripplites with unique product IDs but not multiple of the same product.
To do this, follow this advice and use hid.enumerate
to grab all unique paths.
The path could then be passed to each TrippLite
object.
First of all, thanks for this great little project!
I have a Tripp Lite ECO850LCD, and all of the "status" fields show false
:
$ tripplite
{
"config": {
"frequency": 60,
"power": 850,
"voltage": 120
},
"health": 90,
"input": {
"frequency": 59.9,
"voltage": 121.8
},
"output": {
"power": 47,
"voltage": 122.9
},
"status": {
"ac present": false,
"below remaining capacity": false,
"charging": false,
"discharging": false,
"fully charged": false,
"fully discharged": false,
"needs replacement": false,
"shutdown imminent": false
},
"time to empty": 3049
}
I wrote this little tool for debugging:
#!/usr/bin/env python3
from tripplite.driver import Battery
from pprint import pprint
b = Battery()
with b:
# https://trezor.github.io/cython-hidapi/api.html?highlight=get_feature_report#hid.device.get_feature_report
# Parameters:
# report_num (int)
# max_length (int)
# Returns: Incoming feature report
def test(report_num, max_length):
r = b.device.get_feature_report(report_num, max_length+1)
if r:
addr = r[0]
r = r[1:]
assert addr == report_num
print(f"get_feature_report(report_num={report_num}, max_length={max_length}+1) => {r}")
return r
for rptnum in range(0, 256):
test(rptnum, 100)
Here's the output (while connected to AC):
get_feature_report(report_num=0, max_length=100+1) => []
get_feature_report(report_num=1, max_length=100+1) => [120]
get_feature_report(report_num=2, max_length=100+1) => [60]
get_feature_report(report_num=3, max_length=100+1) => [82, 3]
get_feature_report(report_num=4, max_length=100+1) => [12, 0]
get_feature_report(report_num=6, max_length=100+1) => [95, 0]
get_feature_report(report_num=9, max_length=100+1) => [145, 0]
get_feature_report(report_num=13, max_length=100+1) => [6]
get_feature_report(report_num=14, max_length=100+1) => [7]
get_feature_report(report_num=15, max_length=100+1) => [177]
get_feature_report(report_num=16, max_length=100+1) => [6]
get_feature_report(report_num=17, max_length=100+1) => [3]
get_feature_report(report_num=21, max_length=100+1) => [255, 255]
get_feature_report(report_num=22, max_length=100+1) => [255, 255]
get_feature_report(report_num=23, max_length=100+1) => [255, 255]
get_feature_report(report_num=24, max_length=100+1) => [205, 4]
get_feature_report(report_num=25, max_length=100+1) => [87, 2]
get_feature_report(report_num=27, max_length=100+1) => [205, 4]
get_feature_report(report_num=28, max_length=100+1) => [87, 2]
get_feature_report(report_num=30, max_length=100+1) => [10]
get_feature_report(report_num=32, max_length=100+1) => [137, 0]
get_feature_report(report_num=33, max_length=100+1) => [90]
get_feature_report(report_num=34, max_length=100+1) => [32, 0]
get_feature_report(report_num=35, max_length=100+1) => [16]
get_feature_report(report_num=40, max_length=100+1) => [1]
get_feature_report(report_num=41, max_length=100+1) => [5]
get_feature_report(report_num=42, max_length=100+1) => [4]
get_feature_report(report_num=43, max_length=100+1) => [3]
get_feature_report(report_num=44, max_length=100+1) => [1]
get_feature_report(report_num=48, max_length=100+1) => [120]
get_feature_report(report_num=49, max_length=100+1) => [205, 4]
get_feature_report(report_num=50, max_length=100+1) => [0, 0, 17]
get_feature_report(report_num=51, max_length=100+1) => [2]
get_feature_report(report_num=52, max_length=100+1) => [90]
get_feature_report(report_num=53, max_length=100+1) => [233, 11]
get_feature_report(report_num=54, max_length=100+1) => [100]
get_feature_report(report_num=55, max_length=100+1) => [100]
get_feature_report(report_num=56, max_length=100+1) => [30]
get_feature_report(report_num=58, max_length=100+1) => [10]
get_feature_report(report_num=65, max_length=100+1) => [0]
get_feature_report(report_num=67, max_length=100+1) => [1]
get_feature_report(report_num=70, max_length=100+1) => [4, 0]
get_feature_report(report_num=71, max_length=100+1) => [42, 0]
get_feature_report(report_num=81, max_length=100+1) => [0]
get_feature_report(report_num=82, max_length=100+1) => [0]
get_feature_report(report_num=85, max_length=100+1) => [2]
get_feature_report(report_num=87, max_length=100+1) => [0]
get_feature_report(report_num=88, max_length=100+1) => [0]
get_feature_report(report_num=93, max_length=100+1) => [1]
get_feature_report(report_num=97, max_length=100+1) => [255, 255]
get_feature_report(report_num=98, max_length=100+1) => [2]
get_feature_report(report_num=108, max_length=100+1) => [36, 48]
get_feature_report(report_num=194, max_length=100+1) => [11, 107, 42, 105]
get_feature_report(report_num=195, max_length=100+1) => [2, 10, 0, 0]
get_feature_report(report_num=227, max_length=100+1) => [0, 0, 0, 0, 0, 0, 0]
And while disconnected:
get_feature_report(report_num=0, max_length=100+1) => []
get_feature_report(report_num=1, max_length=100+1) => [120]
get_feature_report(report_num=2, max_length=100+1) => [60]
get_feature_report(report_num=3, max_length=100+1) => [82, 3]
get_feature_report(report_num=4, max_length=100+1) => [12, 0]
get_feature_report(report_num=6, max_length=100+1) => [95, 0]
get_feature_report(report_num=9, max_length=100+1) => [145, 0]
get_feature_report(report_num=13, max_length=100+1) => [6]
get_feature_report(report_num=14, max_length=100+1) => [7]
get_feature_report(report_num=15, max_length=100+1) => [177]
get_feature_report(report_num=16, max_length=100+1) => [6]
get_feature_report(report_num=17, max_length=100+1) => [3]
get_feature_report(report_num=21, max_length=100+1) => [255, 255]
get_feature_report(report_num=22, max_length=100+1) => [255, 255]
get_feature_report(report_num=23, max_length=100+1) => [255, 255]
get_feature_report(report_num=24, max_length=100+1) => [0, 0]
get_feature_report(report_num=25, max_length=100+1) => [0, 0]
get_feature_report(report_num=27, max_length=100+1) => [194, 4]
get_feature_report(report_num=28, max_length=100+1) => [88, 2]
get_feature_report(report_num=30, max_length=100+1) => [10]
get_feature_report(report_num=32, max_length=100+1) => [125, 0]
get_feature_report(report_num=33, max_length=100+1) => [91]
get_feature_report(report_num=34, max_length=100+1) => [33, 0]
get_feature_report(report_num=35, max_length=100+1) => [32]
get_feature_report(report_num=40, max_length=100+1) => [1]
get_feature_report(report_num=41, max_length=100+1) => [5]
get_feature_report(report_num=42, max_length=100+1) => [4]
get_feature_report(report_num=43, max_length=100+1) => [3]
get_feature_report(report_num=44, max_length=100+1) => [1]
get_feature_report(report_num=48, max_length=100+1) => [120]
get_feature_report(report_num=49, max_length=100+1) => [0, 0]
get_feature_report(report_num=50, max_length=100+1) => [0, 0, 32]
get_feature_report(report_num=51, max_length=100+1) => [2]
get_feature_report(report_num=52, max_length=100+1) => [91]
get_feature_report(report_num=53, max_length=100+1) => [218, 11]
get_feature_report(report_num=54, max_length=100+1) => [100]
get_feature_report(report_num=55, max_length=100+1) => [100]
get_feature_report(report_num=56, max_length=100+1) => [30]
get_feature_report(report_num=58, max_length=100+1) => [10]
get_feature_report(report_num=65, max_length=100+1) => [0]
get_feature_report(report_num=67, max_length=100+1) => [1]
get_feature_report(report_num=70, max_length=100+1) => [4, 0]
get_feature_report(report_num=71, max_length=100+1) => [42, 0]
get_feature_report(report_num=81, max_length=100+1) => [1]
get_feature_report(report_num=82, max_length=100+1) => [0]
get_feature_report(report_num=85, max_length=100+1) => [2]
get_feature_report(report_num=87, max_length=100+1) => [0]
get_feature_report(report_num=88, max_length=100+1) => [0]
get_feature_report(report_num=93, max_length=100+1) => [1]
get_feature_report(report_num=97, max_length=100+1) => [255, 255]
get_feature_report(report_num=98, max_length=100+1) => [2]
get_feature_report(report_num=108, max_length=100+1) => [36, 48]
get_feature_report(report_num=194, max_length=100+1) => [11, 107, 42, 105]
get_feature_report(report_num=195, max_length=100+1) => [2, 10, 0, 0]
get_feature_report(report_num=227, max_length=100+1) => [0, 0, 0, 0, 0, 0, 0]
Note this in the diff:
-get_feature_report(report_num=50, max_length=100+1) => [0, 0, 17]
+get_feature_report(report_num=50, max_length=100+1) => [0, 0, 32]
Sorry, not sure if this project is still maintained, maybe you could just give me some pointers and I could patch it. I'm running into this error pretty frequently which requires a restart to mitigate, but then will happen again.
Traceback (most recent call last):
File "/usr/local/lib/python3.9/wsgiref/handlers.py", line 137, in run
self.result = application(self.environ, self.start_response)
File "/usr/local/lib/python3.9/site-packages/prometheus_client/exposition.py", line 123, in prometheus_app
status, header, output = _bake_output(registry, accept_header, params)
File "/usr/local/lib/python3.9/site-packages/prometheus_client/exposition.py", line 105, in _bake_output
output = encoder(registry)
File "/usr/local/lib/python3.9/site-packages/prometheus_client/openmetrics/exposition.py", line 14, in generate_latest
for metric in registry.collect():
File "/usr/local/lib/python3.9/site-packages/prometheus_client/registry.py", line 83, in collect
for metric in collector.collect():
File "/usr/local/lib/python3.9/site-packages/tripplite/prometheus.py", line 57, in collect
ups_data = self.get_data()
File "/usr/local/lib/python3.9/site-packages/tripplite/collectors.py", line 38, in get_data
ups_data = self.battery.get()
File "/usr/local/lib/python3.9/site-packages/tripplite/driver.py", line 127, in get
output[category][subcategory] = self._read(options)
File "/usr/local/lib/python3.9/site-packages/tripplite/driver.py", line 137, in _read
report = self.device.get_feature_report(options['address'],
File "hid.pyx", line 191, in hid.device.get_feature_report
ValueError: not open
I am not sure how to request this as I am n00b. But in using this WONDERFUL code for my personal use, I needed to include the device_id so I could create a URL parameter when building a python cgi-bin response webserver for the json. This allowed me to query the json regardless of what order the json response was in.
This is the hack I used in the driver.py. I am positive there is a better and more elegant way to do this. I am just sharing as it was really useful to know which device was which as I have 4 devices to query.
def get(self):
"""Return an object containing all available data."""
output = {}
for category, data in structure.items():
if 'address' in data:
output[category] = self._read(data)
else:
output[category] = {}
for subcategory, options in data.items():
output[category][subcategory] = self._read(options)
output['device_id'] = self.path
return output
#!/usr/bin/python3
import json
import cgi
from tripplite import Battery, battery_paths
link = cgi.FieldStorage()
params = link.getvalue('path')
params = params.replace("'", "")
print("Content-Type: text/html")
print()
with Battery(params) as battery:
print(json.dumps(battery.get(), indent=1))
addgroup tripplite
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="09ae", GROUP="tripplite"' > /etc/udev/rules.d/tripplite.rules
udevadm control --reload-rules
usermod -a -G tripplite www-data
%www-data ALL = (root) NOPASSWD: /usr/lib/cgi-bin/api
From here, it looks like UPSs with product code 3016 are temperamental. May even have a different API not supported by this library.
We should specify what versions we support in the README and warn/error in the code if used otherwise.
The Communication Protocol allows items I didn't think were useful on the first pass. Now, using one device to read multiple batteries, I've reconsidered.
On init, we could pull the fixed data (serial, model, etc) and store within the object. This could then be used to uniquely ID each connected device without worrying about udev rules or other UNIX config.
I should more precisely describe the list of Python versions this runs on.
@cooperlees In 2020 you were running on 3.8
. Is that still the case? What version works for you now?
HI there,
I am trying to get this to work on a Raspberry Pi... I have followed the instructions to add tripplite to the pi (all done as root) and when I run it, I get the following error:
sudo tripplite
Traceback (most recent call last):
File "/usr/local/bin/tripplite", line 8, in <module>
sys.exit(command_line())
File "/usr/local/lib/python3.7/dist-packages/tripplite/__init__.py", line 20, in command_line
with Battery(args.product_id) as battery:
File "/usr/local/lib/python3.7/dist-packages/tripplite/driver.py", line 86, in __init__
self.device = hid.device()
AttributeError: module 'hid' has no attribute 'device'
I run the prometheus exporter 24/7 and it has been running flawlessly until a recent hardware change. On my Protectli FW1 hardware I had no issues with how code is, but I recently upgraded to a Supermicro 1 RU server and the USB device ID seems to periodically change. That is, when I run lsusb
the Device int changes:
Bus 003 Device 098: ID 09ae:2012 Tripp Lite Tripp Lite UPS
This can take sometimes minutes, sometimes hours after starting the collector.
I've made some attempts on a fork to handle this, but my hardware / USB n00b-ness has resulted in a lot of guessing. My attempt can be seen here: cooperlees@master
Any tips would be appreciated. The reason I think this is doable is as soon as I systemctl restart tripplite_exporter
everything works again for a random amount of time.
Lets get some very basic unittests so we can see that we can install + run said tests in different supported python versions.
I see we have pre-commit in use to run ruff etc. so I'll also add some CI to run that on PRs and merges to main (probably using the pre-commit ci and github actions to run some unittests)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.