-1

I have a relaisboard connected to an arduino via firmata protocol using Python bindings. The communication works without problems using pyfirmata (https://github.com/tino/pyFirmata).

The relaisboard has 16 relais. Every group of 3 relais is a channel. Every channel is connected to a Device Under Test input or output. This just to have a rough description of the purpose of the releboard.

Below you can find a skeleton of the code.

#!/usr/bin/env python

__version__ = '0.1'

# Fault Injection Unit

# Power is connected to Fault Bus 1
# Ground is connected to Fault Bus 2

from pyfirmata import Arduino

class FaultInsertionBoard(object):

    def __init__ (self, comPort = 'COM3'):
        """Initalize the Fault insertion Board

        Open communication with host via serial port

        Arguments:
        comPort -- The serial port used to connect the board to the host.
        """
        self.board = Arduino(comPort)

    class Channel(object):

        def __init__ (self, aChannel):
            """ Create a Channel"""
            pass

        def NoFault():
            """ Set the channel to the "No fault" condition

            No Fault condition is:
            -- DUT channel connected to the testing sistem
            -- DUT channel disconnected from the Fault bus 1  
            -- DUT channel disconnected from the Fault bus 2
            """
            pass


        def OpenCircuit():
            """ Set the channel to the "Open Circuit fault" condition

            Open Circuit fault condition is:
            -- DUT channel disconnected from the testing sistem
            -- DUT channel disconnected from the Fault bus 1  
            -- DUT channel disconnected from the Fault bus 2
            """
            pass

        def ShortToGround():
            """ Set the channel to the "Short to Ground fault" condition

            Open Circuit fault condition is:
            -- DUT channel disconnected from the testing sistem
            -- DUT channel disconnected from the Fault bus 1  
            -- DUT channel connected to the Fault bus 2
            """
            pass

        def ShortToPower():
            """ Set the channel to the "Short to Ground fault" condition

            Open Circuit fault condition is:
            -- DUT channel disconnected from the testing sistem: channel relay is open
            -- DUT channel connected to the Fault bus 1: Fault Bus 1 relay is closed 
            -- DUT channel disconnected from the Fault bus 2: Fault Bus 1 relay is open
            """
            pass

def main():

    FaultBoard = FaultInsertionBoard('COM3')
    VoutSensor = FaultBoard.Channel(0)  
    IOutSensor = FaultBoard.Channel(1)    
    VoutSensor.NoFault()
    IOutSensor.NoFault()
    VoutSensor.ShortToGround()
    IOutSensor.ShortToPower()

if __name__ == "__main__":
    main()

Where:

  • FaultInsertionBoard is a simple wrapper of the Arduino class in Firmata.
  • Channel(n) identifies the n-th group of three relais
  • NoFault, ShortToPower, ShortToGround are various configurations of the three relais of each channel (it does not matter the actual configuration).

Now the question: I have a very good experience with embedded firmware written in C, far less in Python. Obviously the code above is not correct.

Can someone suggest me a class framework in order to get the above functionality? In other words, how can I write the Python code in order to drive the relais as described above?

PS: alternatively I could write something like this:

FaultBoard = FaultInsertionBoard('COM3')
FaultBoard.Channel(0).NoFault()

but I think it is less elegant and clear.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
A.Giordano
  • 13
  • 1
  • 5

1 Answers1

1

On the one hand, your actual question is pretty general, and you should try to be more specific in the future. On the other hand, it is often difficult for a beginner to know where to start, so I will provide you with some design tips that should help you get through this challenge.

No nested classes

Nested classes are pretty much useless in Python. Perfectly legal, but pointless. They do not give you magical access to the containing class, and are not present in any of the instances (as they might be in Java). All that nesting does is to make the namespace more complex.

The first thing I would do is to move Channel out of FaultInsertionBoard. A simple unindent will suffice. I will show you how to use it a bit further down.

Naming convention

Another thing to keep in mind is Python naming convention. While not a requirement, it is common to capitalize only class names, while everything else is lowercase with underscores between words (instead of camelCase).

It is also conventional not to put spaces around the = in the definition of a default value for a function parameter.

I will follow these conventions throughout this answer.

Inheritance vs containment

You should probably use inheritance rather than containment for FaultInsertionBoard:

class FaultInsertionBoard(Arduino):
    pass

This will make FaultInsertionBoard have all the methods and attributes of Arduino. You can now do fault_board.method() instead of fault_board.board.method(), where method is some method of the Arduino class.

You will probably need to define some additional initialization steps, like setting a default value for the com_port, and later setting up the channels. You can define your own version of __init__ and call the parent class's implementation whenever you want:

class FaultInsertionBoard(Arduino):
    def __init__(self, com_port='COM3'):
        super().__init__(com_port)

If you use Python 2.x, use super(FaultInsertionBoard, self).__init__.

Adding channels

To be able to actually access channel instances, you need to define some data structure to hold them, and initialize some channels up front. The data structure can be accessible directly as an attribute, or through a method that does some additional checking of the parameters.

As I mentioned earlier, nesting the Channel class will not move you in this direction at all. In fact, since your Channel class probably needs access to its parent board, we will add a new initialization parameter to its constructor:

class Channel:
    def __init__(self, channel_id, parent):
        self.id = channel_id
        self.parent = parent

You have a couple of options available to you. The simplest is to initialize a sequence of Channels in FaultInsertionBoard, which you can access via [] rather than ():

class FaultInsertionBoard(Arduino):
    def __init__(self, com_port='COM3'):
        super().__init__(com_port)
        self.channels = []
        self.channels.append(Channel(0, self))
        self.channels.append(Channel(1, self))
        ...

Now main will look like this:

def main():
    fault_board = FaultInsertionBoard('COM3')
    v_out_sensor = fault_board.channels[0]  
    i_out_sensor = fault_board.channel[1]
    v_out_sensor.no_fault()
    v_out_sensor.short_to_ground()
    i_out_sensor.no_fault()
    i_out_sensor.short_to_ground()

If you absolutely want to use parentheses to access the channels as channel(0), etc., you can define a method in FaultInsertionBoard. Keeping the __init__ method the same, you can add another method:

def channel(self, index):
    # Check index if you want to, possibly raise an error if invalid
    return self.channels[index]

In this case main will look like this:

def main():
    fault_board = FaultInsertionBoard('COM3')
    v_out_sensor = fault_board.channel(0)  
    i_out_sensor = fault_board.channel(1)
    v_out_sensor.no_fault()
    v_out_sensor.short_to_ground()
    i_out_sensor.no_fault()
    i_out_sensor.short_to_ground()

The first method has the advantage of allowing you direct access to the sequence of Channel objects. Since you are applying the same operation to the channels, you can iterate over all of them for an even simpler interface:

def main():
    fault_board = FaultInsertionBoard('COM3')
    for channel in fault_board.channels:
        channel.no_fault()
        channel.short_to_ground()

Convenience methods

It appears that you use the operation x.no_fault(); x.short_to_ground() multiple times in your code. It is often helpful to create what is called a convenience method in that case. You could add the following to Channel:

def reset(self):
    self.no_fault()
    self.short_to_ground()

And main could then look like this:

def main():
    fault_board = FaultInsertionBoard('COM3')
    for channel in fault_board.channels:
        channel.reset()
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264