<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">&lt;<a href="mailto:agcarver+ale@acarver.net" target="_blank">agcarver+ale@acarver.net</a>&gt;</span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Thank&#39;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&#39;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&#39;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&#39;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&#39;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&#39;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&#39;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&#39;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&#39;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&#39;s very hard to maintain.<br>
<br>
<br>
The way you wrote your code shows that my choice of calling it<br>
&quot;data_collector&quot; is probably not the best because it&#39;s not stand-alone<br>
code that runs in the background.  It&#39;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&#39;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 &gt; 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 &quot;Am I sure<br>
I&#39;m not trying to read a voltage from a relay module?&quot;<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=&quot;/dev/ttyS7&quot;)<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&#39;s close.  I&#39;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&#39;s enough to<br>
make documenting a test module a little easier.  I just have to say<br>
&quot;remember to do this and be sure serial_bus is the first parameter when<br>
setting up the modules&quot;.<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&#39;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>
&gt; Hey Ales,<br>
&gt;<br>
&gt; I&#39;m no expert in Python by any means, so if anyone else here sees a<br>
&gt; problem with my approach, please speak up. But it seems to me that what<br>
&gt; you would want to be doing is creating a single class, and then running<br>
&gt; the different functions within it.<br>
&gt;<br>
&gt; In code:<br>
&gt; &lt;file is my_data_collector_file&gt;<br>
&gt; class data_collector(object):<br>
&gt;<br>
&gt;     serial_port = None<br>
&gt;<br>
&gt;     def open_port(self, tty=&#39;&#39;):<br>
&gt;         self.serial_port = serial.open(tty)<br>
&gt;<br>
&gt;     def write_port(self):<br>
&gt;         pass<br>
&gt;<br>
&gt;     def read_port(self):<br>
&gt;         pass<br>
&gt;<br>
&gt;     def get_module_name(self):<br>
&gt;         self.write_port()<br>
&gt;         self.read_port()<br>
&gt;<br>
&gt;     def voltage_input(self, channel):<br>
&gt;         # This would perform whatever tasks you planned on running for<br>
&gt;         # get_voltage. You could even define whatever functions you<br>
&gt;         # needed in here, and then call them and return their values<br>
&gt;         def get_voltage(channel):<br>
&gt;             # Do what you need to here<br>
&gt;             return self.write_port(channel)<br>
&gt;<br>
&gt;         def other_function(channel)<br>
&gt;             # Do what you need to here<br>
&gt;             return self.read_port(channel)<br>
&gt;<br>
&gt;         # Used like this, you get back what you return from your<br>
&gt;         # functions in an array<br>
&gt;         return [get_voltage(channel), other_function(channel)]<br>
&gt;<br>
&gt;         # Any variables you may need can be set from here as well, but<br>
&gt;         # it&#39;s only necessary to do so if you want to set default values<br>
&gt;         my_variable = 0<br>
&gt;<br>
&gt;     def relay_output(self, channel):<br>
&gt;         # Other stuff here<br>
&gt;<br>
&gt; So then when you go to import your module, you would do something like<br>
&gt; this:<br>
&gt;<br>
&gt; import my_data_collector_file as my_dcf<br>
&gt;<br>
&gt; dc = my_dcf.data_collector()<br>
&gt;<br>
&gt;<br>
&gt; The problem with this bit of code:<br>
&gt;<br>
&gt; gauges = dcf.voltage_input(module_address=1)<br>
&gt; valves = dcf.relay_output(module_address=2)<br>
&gt;<br>
&gt; is that you&#39;re creating two different<br>
&gt; objects, so any variables stored within one of those objects aren&#39;t shared<br>
&gt; with the other.<br>
&gt;<br>
&gt; By using a single object with those functions within it, you would use<br>
&gt; that object to perform the actions you needed, like so:<br>
&gt;<br>
&gt; dc.voltage_input(1)<br>
&gt;<br>
&gt; The variable you set in the class can be accessed here as well as<br>
&gt; modified, so you can do things like<br>
&gt;<br>
&gt; dc.voltage_input(dc.my_variable) # my_variable was set to 0 in the class<br>
&gt;<br>
&gt; dc.my_variable = 2<br>
&gt;<br>
&gt; dc.voltage_input(dc.my_variable) # my_variable is now 2<br>
&gt; dc.relay_output(dc.my_variable) # my_variable is still 2<br>
&gt;<br>
&gt; If you did something like having voltage_input return values, you can<br>
&gt; access those by storing them in a variable either in or outside of the<br>
&gt; scope of the object<br>
&gt;<br>
&gt; my_array = dc.voltage_input(123)<br>
&gt;<br>
&gt; dc.new_array = dc.voltage_input<br>
&gt;<br>
&gt; Like I said, I&#39;m no expert, so if anyone sees any errors with what I&#39;ve<br>
&gt; written, please correct them.<br>
&gt;<br>
&gt; I hope this helps though!<br>
&gt;<br>
&gt; On Tue, Mar 08, 2016 at 02:31:36PM -0800, Alex Carver wrote:<br>
&gt;&gt; No, there&#39;s no module for these devices.  The company supports their own<br>
&gt;&gt; programming environment (very similar to LabView) and provides the<br>
&gt;&gt; protocol reference so that the devices can be used elsewhere but no code<br>
&gt;&gt; is offered for anything outside their program.<br>
&gt;&gt;<br>
&gt;&gt; This is Python 2.7.  I can&#39;t use 3.0 or above because I&#39;m having to<br>
&gt;&gt; integrate this hardware into an existing code base that communicates<br>
&gt;&gt; with even more specialized hardware.  I know there&#39;s a few things in the<br>
&gt;&gt; existing code base that won&#39;t migrate easily and would require a lot of<br>
&gt;&gt; work to rewrite.<br>
&gt;&gt;<br>
&gt;&gt; On 2016-03-08 14:23, Jay Lozier wrote:<br>
&gt;&gt;&gt; Just curious, but is it possible that there might be Python module<br>
&gt;&gt;&gt; available to do what you want?<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt; Also, which flavor of Python are you using?<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt; Jay<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt;<br>
&gt;&gt;&gt; On 03/08/2016 04:12 PM, Alex Carver wrote:<br>
&gt;&gt;&gt;&gt; Ok, so this should be fun since I&#39;ve spent the past hour and a half on<br>
&gt;&gt;&gt;&gt; Google and haven&#39;t really gotten anywhere.<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; I&#39;m writing some code to control data collection modules.  Physically,<br>
&gt;&gt;&gt;&gt; all the modules are hanging off an RS485 bus with a single wire to the<br>
&gt;&gt;&gt;&gt; computer.  Each of the modules has a set of very core commands<br>
&gt;&gt;&gt;&gt; (specifically for things like fetching the module name, firmware number,<br>
&gt;&gt;&gt;&gt; and base configuration) and then it has module specific commands that<br>
&gt;&gt;&gt;&gt; vary depending on the type of module (read voltage inputs, set relays on<br>
&gt;&gt;&gt;&gt; outputs, etc.)  All of the modules share the same protocol for comms.<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; So my original thought was to create a class which contained the very<br>
&gt;&gt;&gt;&gt; bottom stuff, serial I/O/ that would handle the basic serial functions<br>
&gt;&gt;&gt;&gt; and the protocol.  Let&#39;s call this data_collectors.<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; I then thought to make a subclass of data_collectors that contains the<br>
&gt;&gt;&gt;&gt; more specific functions mapped to the protocol commands (e.g. I&#39;d have a<br>
&gt;&gt;&gt;&gt; get_voltage() function for the specific voltage input module and a<br>
&gt;&gt;&gt;&gt; set_relay() function for a relay output module).  The modules each have<br>
&gt;&gt;&gt;&gt; their own address.<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; So in code form (some pseudo code in here, too for brevity):<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; &lt;file is my_data_collector_file&gt;<br>
&gt;&gt;&gt;&gt; class data_collector(object):<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt;     serial_port = None<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt;     def open_port(self, tty=&#39;&#39;):<br>
&gt;&gt;&gt;&gt;         self.serial_port = serial.open(tty)<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt;     def write_port(...):<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt;     def read_port(...):<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt;     def get_module_name(...):<br>
&gt;&gt;&gt;&gt;         write_port()<br>
&gt;&gt;&gt;&gt;         read_port()<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; class voltage_input(data_collector):<br>
&gt;&gt;&gt;&gt;     __init__(self, module_address):<br>
&gt;&gt;&gt;&gt;         self.module_address  = module_address<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt;     get_voltage(self, channel):<br>
&gt;&gt;&gt;&gt;         &lt;stuff here including write_port() read_port()&gt;<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; class relay_output(data_collector):<br>
&gt;&gt;&gt;&gt;     __init__(self, module_address):<br>
&gt;&gt;&gt;&gt;         self.module_address = module_address<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt;     set_relay(self, channel, value):<br>
&gt;&gt;&gt;&gt;         &lt;stuff here including value validation, write_port(), read_port()<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; The goal was to be able to import this block of code and then do the<br>
&gt;&gt;&gt;&gt; following (somehow):<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; import my_data_collector_file as my_dcf<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; dcf.open_port(tty=...0)  #somehow do this, not sure how<br>
&gt;&gt;&gt;&gt; gauges = dcf.voltage_input(module_address=1)<br>
&gt;&gt;&gt;&gt; valves = dcf.relay_output(module_address=2)<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; Right now, with the same structure I can execute the gauges = and valves<br>
&gt;&gt;&gt;&gt; = lines but they get independent copies of the base class.  I want to<br>
&gt;&gt;&gt;&gt; share the base class (so the port opens and stays open) among all of the<br>
&gt;&gt;&gt;&gt; subclasses but have the subclasses behave individually (since they each<br>
&gt;&gt;&gt;&gt; have their own set of commands that don&#39;t overlap).<br>
&gt;&gt;&gt;&gt;<br>
&gt;&gt;&gt;&gt; I haven&#39;t figured out if there&#39;s a way to accomplish this even if<br>
&gt;&gt;&gt;&gt; there&#39;s a third call (as above with the dcf.open_port) to just get<br>
&gt;&gt;&gt;&gt; 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>