1
\$\begingroup\$

Context:

I have to create an interface between devices and a client. My package is composed of:

  • Device.py
  • Error.py
  • DeviceA.py (for example)
  • DeviceB.py
  • ...

Basically each DeviceA, DeviceB, ... inherit Device class. Device is composed of one FSM (state machine: module Fysom) and does several thing about communication, parsing, ... The purpose is to have one common FSM (same rules) for all devices but each device follow his own FSM. That's why there is one thread per device (Device inherit a Threading module). A code simplified:

# One common state rule
TableFSM = {
'initial' : 'none',
'events': [
    {'name': 'do_start', 'src': ['OFF', 'none'], 'dst': 'LOADED'},
    {'name': 'do_stop', 'src': ['LOADED'], 'dst': 'OFF'},
    # same for resume, pause, ...
]

class Device(MyThread):
    def __init__(self):
        # FSM is instantiate here so ONE FSM per device with same rule
        self.fsm = Fysom(TableFSM)
        self.mode = "operation" #or simulation
    def start(self):
        self.fsm.do_start()
        # Real common Device parsing stuff
    def stop(self):
        self.fsm.do_stop()
        # Real common Device parsing stuff
    def pause(self):
        self.fsm.do_pause()
        # Real common Device parsing stuff
    def resume(self):
        self.fsm.do_resume()
        # Real common Device parsing stuff


class DeviceA(Device):
    def __init__(self):
        Device.__init__(self)
    def start(self):
        #Specific Real Device stuff
        super(DeviceA, self).start()
    def stop(self):            
        #Specific Real Device stuff
        super(DeviceA, self).start()
    #def ...

class DeviceB(Device):
    def __init__(self):
        Device.__init__(self)
    def start(self):
        #Specific Real Device stuff
        super(DeviceB, self).start()
    def stop(self):            
        #Specific Real Device stuff
        super(DeviceB, self).start()
    #def ...

Here methods called will be DeviceA().start() or DeviceB().stop() (bad represention) for example.

Issue:

I have to implement for each device an operation and simulation mode For example I could add in each method of each device class the following statement:

if self.mode == "operation":
    #some stuff
elif self.mode == "simulated":
    #almost nothing

I want to write it as generic as possible. From a previous post I could derivate SimuDevice from Device with the same methods. Basically SimuDevice will only trigger the FSM :

class DeviceSimu(Device):
    def __init__(self):
        Device.__init__(self)
    def start(self):
        self.fsm.do_start()
    def stop(self):
        self.fsm.do_stop()
    def pause(self):
        self.fsm.do_pause()
    def resume(self):
        self.fsm.do_resume()

Question:

My issue is about instanciation and object. I want to be able to switch between operation and simulation mode without deleting and reinstantiate an object.

  1. I can't understand how DeviceA could access to DeviceSimu method?
  2. Is it a good way (conceptually) to do simulation and operation mode? Maybe I have to change the link between class.
  3. Any other advice ?
\$\endgroup\$
2
  • \$\begingroup\$ You mention "each device follow his own FSM", but the code you posted does not include that. Could you expand the code to better illustrate what goes on in there? \$\endgroup\$ May 22, 2014 at 6:34
  • \$\begingroup\$ @JanneKarila: I am actually using Fysom module and inheriting a MyThread class. Updated post \$\endgroup\$
    – Katsu
    May 22, 2014 at 7:47

2 Answers 2

3
\$\begingroup\$

You could simply have if-statements in each method of your class. This would probably result in (slightly) lesser lines of code than my suggestions. However, I think that solution has a lot of repeated code and is quite static. What if you wanted a device that simply simulates? Or one that simply operates?

My suggestion is to abstract the classes a bit more and have seperate classes for each mode. Then have a final DualModeDevice class that inherits from both of them:

class OperationDevice(Device):
    def __init__(self):
        super(OperationDevice, self).__init__()

    def operation_start(self):
        # Do operation specific code
        self.start()

    def operation_stop(self):
        # Do operation specific code
        self.stop()

    def operation_pause(self):
        # Do operation specific code
        self.pause()

    def operation_resume(self):
        # Do operation specific code
        self.resume()


class SimulationDevice(Device):
    def __init__(self):
        super(SimulationDevice, self).__init__()

    def simulation_start(self):
        # Do simulation specific code
        self.start()

    def simulation_stop(self):
        # Do simulation specific code
        self.stop()

    def simulation_pause(self):
        # Do simulation specific code
        self.pause()

    def simulation_resume(self):
        # Do simulation specific code
        self.resume()


class DualModeDevice(OperationDevice, SimulationDevice):
    def __init__(self, mode='simulation'):
        super(DualModeDevice, self).__init__()
        self._mode = mode

        # To be continued 

Each function in SimulationDevice and OperationDevice needs to be named differently because of how Python interprets and resolves inheritence. If there is a identically named function from multiple inheritances, it will use the first available reference from the MRO (method resolution order).

Now with that being said, you could implement dual mode in two ways:

  1. Function mapping

    One way is to simply have a dict that holds a mapping from mode to function:

    class DualModeDevice(OperationDevice, SimulationDevice):
        def __init__(self, mode='simulation'):
            super(DualModeDevice, self).__init__()
            self._mode = mode
            self._start_map = {
                'simulation': self.simulation_start,
                'operation': self.operation_start
            }
            # Repeat the above line for `stop`, `pause`, and `resume`.
            # Or you could combine them into a single `dict`.
    
        def stop(self):
            self._start_map[self._mode]()
    
        def stop(self):
            self._stop_map[self._mode]()
    
  2. getattr

    If you felt like being sneaky, you could use the getattr function (assuming consistent function names):

    class DualModeDevice(OperationDevice, SimulationDevice):
        def __init__(self, mode='simulation'):
            self._mode = mode
    
        def run_command(self, command, mode=self.mode):
            getattr(self, '{}_{}'.format(mode, command))()
    

    Since functions boil down to callable properties in classes, we can simply use getattr to get the correct function, then call it.

\$\endgroup\$
7
  • \$\begingroup\$ Ty for help. Solution 1 is pretty nice. I think now I can remove Device class (abstract class with almost nothing) and inheritate to MyThread instead. Question: Now DeviceA, DeviceB, ... should derivate from DualModeDevice I guess. So I need to had self.operation_start = OperationDevice.__init__(self) and self.simulation_start = SimulationDevice.__init__(self) in init of DualModeDevice? \$\endgroup\$
    – Katsu
    May 22, 2014 at 8:24
  • 1
    \$\begingroup\$ No. What you would be doing with those lines is storing the return value of the __init__ functions to a variable. __init__ in this solution doesn't return anything (i.e. will 'return' None). When structuring your classes, start with the basics: what do ALL devices do? Put those funtions into your base class. Then specialize: what do simulation devices need to do? Operation devices? Those are new classes that inherit from your base. Then: do you need a single device to do both? Have another class that inherits from the simulation and operation devices. \$\endgroup\$ May 22, 2014 at 11:53
  • \$\begingroup\$ Ok ty, actually all device need both Operation and Simulation and are able to switch so DualModeDevice answer the problem. But I don't see what self.simulation_start and self.operation_start is refering. To my mind it should be respectively object of SimulationDevice and OperationDevice ? How could I specify it? \$\endgroup\$
    – Katsu
    May 22, 2014 at 12:11
  • \$\begingroup\$ Gotcha, I see. self.simulation_start and self.operation_start are function references from the classes DualModeOperation inherits from. By inheriting from SimuationDevice and OperationDevice, DualModeDevice has access to their methods; all we need to do is reference them at the right times. \$\endgroup\$ May 22, 2014 at 12:16
  • \$\begingroup\$ Well ok sorry now I really see what you mean. better with super(DualModeDevice, self).__init__() \$\endgroup\$
    – Katsu
    May 22, 2014 at 12:41
0
\$\begingroup\$

How about this: split DeviceA into two classes. DeviceA could manage the state machine and DeviceAConnection could handle the communication with the physical device. Then create a DeviceASimulation class that implements the same interface as DeviceAConnection but simulates the device. Now DeviceA would be able to switch between the real and simulated connections without restarting the state machine.

\$\endgroup\$
1
  • \$\begingroup\$ DeviceA, DeviceB, ... (at least 8 device classes). It could be pretty long code. The purpose is to have something almost well designed and avoid repetition. \$\endgroup\$
    – Katsu
    May 23, 2014 at 7:25

Not the answer you're looking for? Browse other questions tagged or ask your own question.