rdserialtool
rdserialtool copied to clipboard
Questions: Why no IP support? / how to use as a library
BT & Serial is ok, but with all those RD6006W around, an option for Wifi would be great. Also: could you please provide an example on how rdserialtool can be used as a library, having an python application using it to write/poll data when needed (not just running it as a subprocess, then reading the output)
thank you.
This is of interest to me as well.
Currently, using this as a library is.... ugly.
Working backwards from rdserialtool/tool.py
, you need to create a custom class with a number of specific properties (socket
, args
).
First you open the port as either a instance of rdserial.device.Bluetooth()
or rdserial.device.Serial()
, and assign it to a member of your custom class. You also need to create a (undocumented) argparse.Namespace()
object (and it has to be a namespace, it can't be a dict) which contains the operation you want the power supply to do.
You then pass that created class to rdserial.dps.tool.Tool()
(or rdserial.um.tool.Tool()
), and then invoke main()
on the created tool class.
The architecture seems like it was written as a single script, and then broken up into classes at a later date without any re-architecting. The fact that rdserial.dps.tool.Tool()
depends on the internals of how the command-line parsing is implemented makes using this as a library much more irritating. The bizarre passing of the class self into another class which then access properties of that class directly is also very strange.
Ideally, rdserial.dps.tool.Tool()
should take both the socket
and the contents of args
as parameters.
You can make it work as-is, but it's fiddly.
Edit: There's more fiddly bits. If you don't invoke Tool.main()
, you cannot use the more script-worthy interface calls:
File "/usr/local/lib/python3.8/dist-packages/rdserialtool-0.2.1-py3.8.egg/rdserial/dps/tool.py", line 231, in assemble_device_state
AttributeError: 'Tool' object has no attribute 'device_state_class'
Some of the member properties of the Tool() class are set up in the main()
invocation. I can't think of any reasons this is where it is, it should be in the Tool() class __init__()
. Arrrgh.
Additionally, printing the device state is annoying. dps.DPSDeviceState()
has a load()
member, but no save()
property, or any way to extract the device state without knowing a lot about the internals of the class. Additionally, all the conversion functions for these classes are implemented in dps.Tool()
for some reason (why aren't they member functions of dps.DPSDeviceState()
!!!). There's a call to convert the DPSDeviceState()
to json, but it only prints, it cannot be used to extract the data that is then passed to json.dumps()
and subsequently to print()
.
Overall, while this is a super handy tool, a bunch of the design decisions confuse me, particularly about how responsibilities are divided between the various components.
Here's a hacked up script that steps through what's needed to poll a DPS5005 programatically:
import logging
import argparse
import time
import rdserial.tool
import rdserial.device
import rdserial.dps.tool
import rdserial.dps
DPS_ADDRESS = "xx:xx:xx:xx:xx:xx"
DPS_TYPE = "dps5005"
ARGS_DEFAULT = {
"bluetooth_address" : DPS_ADDRESS,
"device" : DPS_TYPE,
"all_groups" : False,
"baud" : 9600,
"bluetooth_port" : 1,
"clear_data_group" : False,
"connect_delay" : 0.3,
"debug" : False,
"group" : None,
"json" : False,
"load_group" : None,
"modbus_unit" : 1,
"next_data_group" : False,
"next_screen" : False,
"previous_screen" : False,
"quiet" : False,
"rotate_screen" : False,
"serial_device" : None,
"set_amps" : None,
"set_brightness" : None,
"set_clock" : False,
"set_data_group" : None,
"set_group_amps" : None,
"set_group_brightness" : None,
"set_group_cutoff_amps" : None,
"set_group_cutoff_volts" : None,
"set_group_cutoff_watts" : None,
"set_group_maintain_output" : None,
"set_group_poweron_output" : None,
"set_group_volts" : None,
"set_key_lock" : None,
"set_output_state" : None,
"set_record_threshold" : None,
"set_screen_brightness" : None,
"set_screen_timeout" : None,
"set_volts" : None,
"trend_points" : 5,
"watch" : False,
"watch_seconds" : 2.0
}
class Wrapper():
def go(self):
self.args = argparse.Namespace()
for k, v in ARGS_DEFAULT.items():
setattr(self.args, k, v)
self.socket = rdserial.device.Bluetooth(
DPS_ADDRESS,
port=1,
)
self.socket.connect()
time.sleep(self.args.connect_delay)
tool = rdserial.dps.tool.Tool(self)
tool.device_mode = 'dps'
tool.device_state_class = rdserial.dps.DPSDeviceState
tool.device_group_state_class = rdserial.dps.DPSGroupState
tool.modbus_client = rdserial.modbus.RTUClient(
tool.socket,
baudrate=tool.args.baud,
)
dstate = tool.assemble_device_state()
# Correct for https://github.com/rfinnie/rdserialtool/issues/8
dstate.amps = dstate.amps / 10.0
print("Current status:")
print(' amps', dstate.amps / 10.0)
print(' volts', dstate.volts)
print(' watts', dstate.watts)
print(' constant_current', dstate.constant_current)
print(' output_state', dstate.output_state)
print(' input_volts', dstate.input_volts)
print("Settings")
print(' setting_amps', dstate.setting_amps)
print(" Settings")
print(' setting_volts', dstate.setting_volts)
print(' key_lock', dstate.key_lock)
print(' brightness', dstate.brightness)
print("State")
print(' group_loader', dstate.group_loader)
print(' groups', dstate.groups)
print(' protection', dstate.protection)
print(' firmware', dstate.firmware)
print(' model', dstate.model)
logging.info('Closing socket')
self.socket.close()
def go():
intf = Wrapper()
intf.go()
def arg_test():
ret = rdserial.tool.parse_args()
print(ret)
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
go()
# arg_test()
arg_test()
can be useful for figuring out how to map CLI options to the actual internal arguments. And if you don't have a DPS power supply, you'll likely have to change things.