<div dir="ltr"><div class="gmail_default" style="font-family:arial,helvetica,sans-serif;font-size:small">I think the @classmethod decorator will do what you want - you could make bus initialization in a class method with guards to perform it only once (and on the single set of values), and then call the subclasses fo the reading methods<br><br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Tue, Mar 8, 2016 at 10:34 PM, Alex Carver <span dir="ltr"><<a href="mailto:agcarver+ale@acarver.net" target="_blank">agcarver+ale@acarver.net</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Thank's Billy,<br>
<br>
The problem with this approach is that not every module works with every<br>
function. The voltage input module, for example, expects certain<br>
commands to read the inputs. However, the relay module doesn't<br>
understand that command, it has a different command for setting one of<br>
the relays.<br>
<br>
The idea for the class was to ensure that only functions related to a<br>
particular type of module were exposed to the final program because, in<br>
the future, I may not be the only one using this code. So I'm trying to<br>
make it reasonably easy to use once loaded as a module/library.<br>
<br>
Using the instantation examples for my original class:<br>
<span class=""><br>
gauges = dcf.voltage_input(module_address=1)<br>
valves = dcf.relay_output(module_address=2)<br>
<br>
</span>This would ensure that a user of the library couldn't do something dumb<br>
like say valves.get_voltage() because it makes no sense. The module<br>
might ignore the command (or maybe answer with an error message) but<br>
it's entirely possible that the command is reused in another module and<br>
does something different. [1]<br>
<br>
<br>
The other problem is the nested defs like your voltage_input example.<br>
I don't necessarily want to execute all the commands in a module every<br>
single time because that would tie up the bus and prevent me from<br>
reaching other modules while I wait. I'd only want to poll a value from<br>
the module on a regular basis and ask for other things once or twice in<br>
a session.<br>
<br>
For example, when everything starts up I'll request the configuration of<br>
the module so I know things like input voltage range (this is<br>
adjustable), data format (ASCII text or hex encoded), module name, etc.<br>
I only need those once. Then after that I would poll for the voltages<br>
in a loop (along with other polling of other modules). Using your<br>
example I could break that out as an independent function but that hits<br>
the first problem above. If it's a nested function then I have to run a<br>
control structure to decide what I want to do and how to route the code<br>
to the right location. I end up repeating a lot of code. I did this<br>
for the first brute force code to make sure the modules were functional<br>
but it's very hard to maintain.<br>
<br>
<br>
The way you wrote your code shows that my choice of calling it<br>
"data_collector" is probably not the best because it's not stand-alone<br>
code that runs in the background. It's really a driver library so that<br>
a custom test bench program can get data or twiddle outputs as needed<br>
for a particular experimental setup. It doesn't really run in a loop on<br>
its own. One day the test bench might do:<br>
<br>
while True:<br>
v1 = get_voltage(module=1, channel=2)<br>
set_relay(module=3, channel=4, True)<br>
v2 = get_voltage(module=2, channel=5)<br>
set_relay(module=3, channel=4, False)<br>
<br>
And another day it might be:<br>
<br>
for i in range(0,20):<br>
v1 = get_voltage(module=1, channel=6)<br>
if v1 > 5:<br>
set_relay(module=7, channel=2, True)<br>
else:<br>
set_relay(module=4, channel=4, True)<br>
<br>
<br>
It has to be easy for someone to write up a test module that reads and<br>
writes what it is supposed to do without them having to think "Am I sure<br>
I'm not trying to read a voltage from a relay module?"<br>
<br>
I rewrote my class by breaking out the serial portion as its own top<br>
level class and leave the rest as classes and subclasses of a generic<br>
module within the module file and that seems to work well enough.<br>
<br>
Now I run this:<br>
<br>
import my_class_file<br>
<br>
serial_bus = my_class_file.setup_bus(tty="/dev/ttyS7")<br>
<br>
valve_relay_module = my_class_file.relay_module_typeA(serial_bus,<br>
module_address=5)<br>
<br>
gauge_voltage_module =<br>
my_class_file.voltage_input_module_typeB(serial_bus, module_address=3)<br>
<br>
It's close. I'd eventually like to be able to ditch passing the<br>
serial_bus parameter so I still do the setup_bus function but then the<br>
rest of the classes know about it automatically. For now it's enough to<br>
make documenting a test module a little easier. I just have to say<br>
"remember to do this and be sure serial_bus is the first parameter when<br>
setting up the modules".<br>
<br>
<br>
[1] The reasoning here is that the base command structure is:<br>
<br>
$AACDDDD....<br>
<br>
Where:<br>
AA is the hex address (transmitted as two ASCII characters) of the<br>
module (so 01, 01, 0E, 0F, etc.),<br>
C is the single command as an ASCII character 0-9 or A-Z, and<br>
DDDD.... is the variable length data payload for a command (could be<br>
zero or more data bytes).<br>
<br>
The single command byte only allows for 36 characters. The company<br>
makes over 20 different types of modules so command reuse is very<br>
likely. Some commands are the same across all modules. For example<br>
$01M asks for the name of the module (command M) with address 01.<br>
Others won't have the same data expectations, especially for write<br>
commands versus read queries.<br>
<div class="HOEnZb"><div class="h5"><br>
<br>
On 2016-03-08 18:35, Billy wrote:<br>
> Hey Ales,<br>
><br>
> I'm no expert in Python by any means, so if anyone else here sees a<br>
> problem with my approach, please speak up. But it seems to me that what<br>
> you would want to be doing is creating a single class, and then running<br>
> the different functions within it.<br>
><br>
> In code:<br>
> <file is my_data_collector_file><br>
> class data_collector(object):<br>
><br>
> serial_port = None<br>
><br>
> def open_port(self, tty=''):<br>
> self.serial_port = serial.open(tty)<br>
><br>
> def write_port(self):<br>
> pass<br>
><br>
> def read_port(self):<br>
> pass<br>
><br>
> def get_module_name(self):<br>
> self.write_port()<br>
> self.read_port()<br>
><br>
> def voltage_input(self, channel):<br>
> # This would perform whatever tasks you planned on running for<br>
> # get_voltage. You could even define whatever functions you<br>
> # needed in here, and then call them and return their values<br>
> def get_voltage(channel):<br>
> # Do what you need to here<br>
> return self.write_port(channel)<br>
><br>
> def other_function(channel)<br>
> # Do what you need to here<br>
> return self.read_port(channel)<br>
><br>
> # Used like this, you get back what you return from your<br>
> # functions in an array<br>
> return [get_voltage(channel), other_function(channel)]<br>
><br>
> # Any variables you may need can be set from here as well, but<br>
> # it's only necessary to do so if you want to set default values<br>
> my_variable = 0<br>
><br>
> def relay_output(self, channel):<br>
> # Other stuff here<br>
><br>
> So then when you go to import your module, you would do something like<br>
> this:<br>
><br>
> import my_data_collector_file as my_dcf<br>
><br>
> dc = my_dcf.data_collector()<br>
><br>
><br>
> The problem with this bit of code:<br>
><br>
> gauges = dcf.voltage_input(module_address=1)<br>
> valves = dcf.relay_output(module_address=2)<br>
><br>
> is that you're creating two different<br>
> objects, so any variables stored within one of those objects aren't shared<br>
> with the other.<br>
><br>
> By using a single object with those functions within it, you would use<br>
> that object to perform the actions you needed, like so:<br>
><br>
> dc.voltage_input(1)<br>
><br>
> The variable you set in the class can be accessed here as well as<br>
> modified, so you can do things like<br>
><br>
> dc.voltage_input(dc.my_variable) # my_variable was set to 0 in the class<br>
><br>
> dc.my_variable = 2<br>
><br>
> dc.voltage_input(dc.my_variable) # my_variable is now 2<br>
> dc.relay_output(dc.my_variable) # my_variable is still 2<br>
><br>
> If you did something like having voltage_input return values, you can<br>
> access those by storing them in a variable either in or outside of the<br>
> scope of the object<br>
><br>
> my_array = dc.voltage_input(123)<br>
><br>
> dc.new_array = dc.voltage_input<br>
><br>
> Like I said, I'm no expert, so if anyone sees any errors with what I've<br>
> written, please correct them.<br>
><br>
> I hope this helps though!<br>
><br>
> On Tue, Mar 08, 2016 at 02:31:36PM -0800, Alex Carver wrote:<br>
>> No, there's no module for these devices. The company supports their own<br>
>> programming environment (very similar to LabView) and provides the<br>
>> protocol reference so that the devices can be used elsewhere but no code<br>
>> is offered for anything outside their program.<br>
>><br>
>> This is Python 2.7. I can't use 3.0 or above because I'm having to<br>
>> integrate this hardware into an existing code base that communicates<br>
>> with even more specialized hardware. I know there's a few things in the<br>
>> existing code base that won't migrate easily and would require a lot of<br>
>> work to rewrite.<br>
>><br>
>> On 2016-03-08 14:23, Jay Lozier wrote:<br>
>>> Just curious, but is it possible that there might be Python module<br>
>>> available to do what you want?<br>
>>><br>
>>> Also, which flavor of Python are you using?<br>
>>><br>
>>> Jay<br>
>>><br>
>>><br>
>>> On 03/08/2016 04:12 PM, Alex Carver wrote:<br>
>>>> Ok, so this should be fun since I've spent the past hour and a half on<br>
>>>> Google and haven't really gotten anywhere.<br>
>>>><br>
>>>> I'm writing some code to control data collection modules. Physically,<br>
>>>> all the modules are hanging off an RS485 bus with a single wire to the<br>
>>>> computer. Each of the modules has a set of very core commands<br>
>>>> (specifically for things like fetching the module name, firmware number,<br>
>>>> and base configuration) and then it has module specific commands that<br>
>>>> vary depending on the type of module (read voltage inputs, set relays on<br>
>>>> outputs, etc.) All of the modules share the same protocol for comms.<br>
>>>><br>
>>>> So my original thought was to create a class which contained the very<br>
>>>> bottom stuff, serial I/O/ that would handle the basic serial functions<br>
>>>> and the protocol. Let's call this data_collectors.<br>
>>>><br>
>>>> I then thought to make a subclass of data_collectors that contains the<br>
>>>> more specific functions mapped to the protocol commands (e.g. I'd have a<br>
>>>> get_voltage() function for the specific voltage input module and a<br>
>>>> set_relay() function for a relay output module). The modules each have<br>
>>>> their own address.<br>
>>>><br>
>>>> So in code form (some pseudo code in here, too for brevity):<br>
>>>><br>
>>>> <file is my_data_collector_file><br>
>>>> class data_collector(object):<br>
>>>><br>
>>>> serial_port = None<br>
>>>><br>
>>>> def open_port(self, tty=''):<br>
>>>> self.serial_port = serial.open(tty)<br>
>>>><br>
>>>> def write_port(...):<br>
>>>><br>
>>>> def read_port(...):<br>
>>>><br>
>>>> def get_module_name(...):<br>
>>>> write_port()<br>
>>>> read_port()<br>
>>>><br>
>>>> class voltage_input(data_collector):<br>
>>>> __init__(self, module_address):<br>
>>>> self.module_address = module_address<br>
>>>><br>
>>>> get_voltage(self, channel):<br>
>>>> <stuff here including write_port() read_port()><br>
>>>><br>
>>>> class relay_output(data_collector):<br>
>>>> __init__(self, module_address):<br>
>>>> self.module_address = module_address<br>
>>>><br>
>>>><br>
>>>> set_relay(self, channel, value):<br>
>>>> <stuff here including value validation, write_port(), read_port()<br>
>>>><br>
>>>><br>
>>>> The goal was to be able to import this block of code and then do the<br>
>>>> following (somehow):<br>
>>>><br>
>>>> import my_data_collector_file as my_dcf<br>
>>>><br>
>>>> dcf.open_port(tty=...0) #somehow do this, not sure how<br>
>>>> gauges = dcf.voltage_input(module_address=1)<br>
>>>> valves = dcf.relay_output(module_address=2)<br>
>>>><br>
>>>><br>
>>>> Right now, with the same structure I can execute the gauges = and valves<br>
>>>> = lines but they get independent copies of the base class. I want to<br>
>>>> share the base class (so the port opens and stays open) among all of the<br>
>>>> subclasses but have the subclasses behave individually (since they each<br>
>>>> have their own set of commands that don't overlap).<br>
>>>><br>
>>>> I haven't figured out if there's a way to accomplish this even if<br>
>>>> there's a third call (as above with the dcf.open_port) to just get<br>
>>>> everything started.<br>
<br>
<br>
_______________________________________________<br>
Ale mailing list<br>
<a href="mailto:Ale@ale.org">Ale@ale.org</a><br>
<a href="http://mail.ale.org/mailman/listinfo/ale" rel="noreferrer" target="_blank">http://mail.ale.org/mailman/listinfo/ale</a><br>
See JOBS, ANNOUNCE and SCHOOLS lists at<br>
<a href="http://mail.ale.org/mailman/listinfo" rel="noreferrer" target="_blank">http://mail.ale.org/mailman/listinfo</a><br>
</div></div></blockquote></div><br><br clear="all"><br>-- <br><div class="gmail_signature">Pete Hardie<br>--------<br>Better Living Through Bitmaps</div>
</div>