[ale] Anyone familiar with Python?

Pete Hardie pete.hardie at gmail.com
Tue Mar 8 22:48:33 EST 2016


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


On Tue, Mar 8, 2016 at 10:34 PM, Alex Carver <agcarver+ale at acarver.net>
wrote:

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



-- 
Pete Hardie
--------
Better Living Through Bitmaps
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.ale.org/pipermail/ale/attachments/20160308/df6f9143/attachment.html>


More information about the Ale mailing list