nradulovic / pyeds

Python Event Driven System
GNU Lesser General Public License v3.0
11 stars 4 forks source link

Help with do_some_stuff() #13

Closed dclementson closed 3 years ago

dclementson commented 3 years ago

Hi @nradulovic:

I'm wondering if you could please provide an example of how a pyeds event handler can accesses external functions. For example, this Ttk example transitions states upon button presses and prints the current state, but I haven't been able to figure out how to get the event handler to access the gui to update the Ttk label text. Any advice is welcome, thank you. Best, DC

''' FSM Demo ''' from tkinter import (Tk, StringVar) from tkinter import ttk from pyeds import fsm

class gui: def init(self, master): self.master = master master.title("FSM Example") self.state_label_text = StringVar() self.state_label = ttk.Label( self.master, textvariable=self.state_label_text) self.state_label.pack(pady=10)

    self.a_button = ttk.Button(master, text="State A", command=self.set_a)
    self.a_button.pack()

    self.b_button = ttk.Button(master, text="State B", command=self.set_b)
    self.b_button.pack()

    class myFsm(fsm.StateMachine, gui):
        pass

    @fsm.DeclareState(myFsm)
    class Initialization(fsm.State):
        def on_init(self):
            print('FSM Init')
    #                fsm.Every(2, 'poll')
            return AState

    @fsm.DeclareState(myFsm)
    class AState(fsm.State):
        def on_entry(self):
            print('A State')
            # Update Ttk State Label Text, but how?

        def on_BButton(self, event):
            return BState

    @fsm.DeclareState(myFsm)
    class BState(fsm.State):
        def on_entry(self):
            print('B State')
            # Update Ttk State Label Text, but how?

        def on_AButton(self, event):
            return AState

    self.myfsm = myFsm()

def set_b(self):
    ''' Enable B Mode '''
    self.myfsm.send(fsm.Event('BButton'))

self.set_state_label('Set B State')

def set_a(self):
    ''' Enable A Mode '''
    self.myfsm.send(fsm.Event('AButton'))

self.set_state_label('Set A State')

def set_state_label(self, mode_text):
    self.state_label_text.set(mode_text)

def main(): ''' Main loop for FSM Demo ''' root = Tk() root.geometry("200x100") gui(root) root.mainloop()

if name == 'main': main()

nradulovic commented 3 years ago

Hello,

You can always access FSM class instance from any state of that FSM through sm property.

For example, let's say you have FSM with the following definition:

class MyFsm(fsm.StateMachine):
    A_VARIABLE = 13

You can access A_VARIABLE from any state of the state machine with:

@fsm.DeclareState(MyFsm)
class MyState(fsm.State):
    def on_entry(self):
        print(self.sm.A_VARIABLE)
nradulovic commented 3 years ago

Regarding your example here is what I did:

'''
FSM Demo
'''
from tkinter import (Tk, StringVar)
from tkinter import ttk
from pyeds import fsm

class myFsm(fsm.StateMachine):
    # should_autostart = False
    def __init__(self, master):
        # Setup your own stuff
        # All members are accessible via 'sm' property from any FSM state
        self.master = master
        master.title("FSM Example")
        self.state_label_text = StringVar()
        self.state_label = ttk.Label(
        self.master, textvariable=self.state_label_text)
        self.state_label.pack(pady=10)

        self.a_button = ttk.Button(master, text="State A", command=self.set_a)
        self.a_button.pack()

        self.b_button = ttk.Button(master, text="State B", command=self.set_b)
        self.b_button.pack()
        # You have to put __init__ as the last statement since it will start
        # the machine right after initialization
        # If you want to avoid this set 'should_autostart' to False
        fsm.StateMachine.__init__(self)

    def set_b(self):
        ''' Enable B Mode '''
        self.send(fsm.Event('BButton'))

    def set_a(self):
        ''' Enable A Mode '''
        self.send(fsm.Event('AButton'))

    def set_state_label(self, mode_text):
        self.state_label_text.set(mode_text)

@fsm.DeclareState(myFsm)
class Initialization(fsm.State):
    def on_init(self):
        print('FSM Init')
        self.sm.a_button.config(text = "State A")
        self.sm.b_button.config(text = "State B")

    def on_AButton(self, event):
        return AState

    def on_BButton(self, event):
        return BState

@fsm.DeclareState(myFsm)
class AState(fsm.State):

    def on_entry(self):
        print('A State')
        self.sm.set_state_label('Set A State')

    def on_BButton(self, event):
        return BState

@fsm.DeclareState(myFsm)
class BState(fsm.State):

    def on_entry(self):
        print('B State')
        self.sm.set_state_label('Set B State')

    def on_AButton(self, event):
        return AState

def main():
    ''' Main loop for FSM Demo '''
    root = Tk()
    root.geometry("200x100")
    my_fsm = myFsm(root)
    root.mainloop()

if __name__ == '__main__':
    main()
nradulovic commented 3 years ago

You can use inheritance instead of composition (which I did) and still use the sm property of State class to access the state machine instance.

If you still need help, don't hesitate to ask. I the next few days I'll update the documentation to describe this use-case as well.

dclementson commented 3 years ago

Thank you very much for the help, Nenard. Your example will hopefully get my project (a CNC machine controller) unstuck. I am new to python, so it is extremely helpful to be able to see an example of a properly structured program. For a much larger program (many buttons and readouts, RS-232 communications, G-Code interpreter) would still make the FSM the top-level object and do all of the setup init its init method?

Dave Clementson

On Sat, Nov 28, 2020 at 5:45 AM Nenad Radulović notifications@github.com wrote:

You can use inheritance instead of composition (which I did) and still use the sm property of State class to access the state machine instance.

If you still need help, don't hesitate to ask. I the next few days I'll update the documentation to describe this use-case as well.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/nradulovic/pyeds/issues/13#issuecomment-735232929, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGL6X6N6J52YATEEC5R3EVDSSD5HPANCNFSM4UFMNR7A .

dclementson commented 3 years ago

Hi Nenad:

One more question: is it possible to add a parameter to an event in addition to its name? This would allow the event handler to do different things depending on the parameter. For example, my CNC controller might have an event handler to move an axis in a certain direction based on a button press. The parameter would indicate which axis to move.

Thanks again for your advice! Best regards,

DC

On Sat, Nov 28, 2020 at 5:45 AM Nenad Radulović notifications@github.com wrote:

You can use inheritance instead of composition (which I did) and still use the sm property of State class to access the state machine instance.

If you still need help, don't hesitate to ask. I the next few days I'll update the documentation to describe this use-case as well.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/nradulovic/pyeds/issues/13#issuecomment-735232929, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGL6X6N6J52YATEEC5R3EVDSSD5HPANCNFSM4UFMNR7A .

nradulovic commented 3 years ago

Thank you very much for the help, Nenard. Your example will hopefully get my project (a CNC machine controller) unstuck. I am new to python, so it is extremely helpful to be able to see an example of a properly structured program. For a much larger program (many buttons and readouts, RS-232 communications, G-Code interpreter) would still make the FSM the top-level object and do all of the setup init its init method? Dave Clementson On Sat, Nov 28, 2020 at 5:45 AM Nenad Radulović @.***> wrote: You can use inheritance instead of composition (which I did) and still use the sm property of State class to access the state machine instance. If you still need help, don't hesitate to ask. I the next few days I'll update the documentation to describe this use-case as well. — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub <#13 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGL6X6N6J52YATEEC5R3EVDSSD5HPANCNFSM4UFMNR7A .

Hi,

It is not so easy to answer the question. You can continue using this single FSM, but that leads to obfuscated program as more features are added, or you can have multiple FSM-s in your application. You can create one controller FSM which issues commands to other FSM with a specific jobs (divide and conquer methodology).

nradulovic commented 3 years ago

Hi Nenad: One more question: is it possible to add a parameter to an event in addition to its name? This would allow the event handler to do different things depending on the parameter. For example, my CNC controller might have an event handler to move an axis in a certain direction based on a button press. The parameter would indicate which axis to move. Thanks again for your advice! Best regards, DC On Sat, Nov 28, 2020 at 5:45 AM Nenad Radulović @.***> wrote: You can use inheritance instead of composition (which I did) and still use the sm property of State class to access the state machine instance. If you still need help, don't hesitate to ask. I the next few days I'll update the documentation to describe this use-case as well. — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub <#13 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGL6X6N6J52YATEEC5R3EVDSSD5HPANCNFSM4UFMNR7A .

You can create event classes that suit your needs, for example:

class AxisButtonPress(fsm.Event):
    def set_param_direction(self, direction):
        self.direction = direction

then in some FSM state:

@fsm.DeclareState(MyFsm)
class Initialization(fsm.State):
    def on_axis_button_press(self, event):
        print(event.direction)
dclementson commented 3 years ago

Thanks again for the help, Nenard. It looks like separate event classes for each axis and direction will do the trick. As for multiple FSMs, I will indeed use separate FSMs for different tasks: one controlling the overall machine state, one to handle sending serial port messages, etc.

Best, DC

On Sun, Nov 29, 2020 at 6:59 AM Nenad Radulović notifications@github.com wrote:

Hi Nenad: One more question: is it possible to add a parameter to an event in addition to its name? This would allow the event handler to do different things depending on the parameter. For example, my CNC controller might have an event handler to move an axis in a certain direction based on a button press. The parameter would indicate which axis to move. Thanks again for your advice! Best regards, DC … <#m-5173858036344176135> On Sat, Nov 28, 2020 at 5:45 AM Nenad Radulović @.***> wrote: You can use inheritance instead of composition (which I did) and still use the sm property of State class to access the state machine instance. If you still need help, don't hesitate to ask. I the next few days I'll update the documentation to describe this use-case as well. — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub <#13 (comment) https://github.com/nradulovic/pyeds/issues/13#issuecomment-735232929>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGL6X6N6J52YATEEC5R3EVDSSD5HPANCNFSM4UFMNR7A .

You can create event classes that suit your needs, for example:

class AxisButtonPress(fsm.Event):

def set_param_direction(self, direction):

    self.direction = direction

then in some FSM state:

@fsm.DeclareState(MyFsm)

class Initialization(fsm.State):

def on_axis_button_press(self, event):

    print(event.direction)

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/nradulovic/pyeds/issues/13#issuecomment-735407368, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGL6X6J7DKH6TPM76ZZ2PHDSSJOTPANCNFSM4UFMNR7A .

nradulovic commented 3 years ago

I'm glad I can help. If you have more questions feel free to open new issue. I'll close this one for now.

Best regards, Nenad

dclementson commented 3 years ago

Hi Nenad: I hope this email finds you well. My project is coming along nicely thanks to a careful study of your device controller project. So I have another question: does PYEDS have any way to return to the prior state from which it just transitioned? The use case would be for an autonomic function like polling a hardware device. A timer event would cause a transition to a state where the polling is done, then return to its prior state. Thanks in advance for any info.

DC

P.S., I'm enjoying your audio amplifier designs; that is what I do for my "day job."

On Sun, Nov 29, 2020 at 10:44 AM Nenad Radulović notifications@github.com wrote:

I'm glad I can help. If you have more questions feel free to open new issue. I'll close this one for now.

Best regards, Nenad

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/nradulovic/pyeds/issues/13#issuecomment-735437212, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGL6X6LAIGCJDV3IFP57TRDSSKI7PANCNFSM4UFMNR7A .

nradulovic commented 3 years ago

Hello,

It looks like that you don't need to transit to a new state at all, but just receive a time event (from timer) and do the processing there. After processing just return from function and don't return the next state.

For example, the state State_A is sensitive to 2 events:

When event_1 is received, after processing of the event the function returns new state class State_B which will tell to state machine dispatcher that it needs to transition to State_B state. When event_2 is received, the event will be processed and the function will return None which tells to the state machine dispatcher that state machine will remain in the current state.

@fsm.DeclareState(BlinkyFsm)
class State_A(fsm.State):
    def on_event_1(self, event):
        print('event 1')
        return State_B

    def on_event_2(self, event):
        print('event 2')

If you omit the return, then the state machine will not transition to a new state but it will remain in the current state.

This is the pattern how you usually deal with the use-case you just described.

P.S. I would like to have your input on my amplifier designs, like component choices (SMD, through hole, availability...), possible improvements and so on. :-)

dclementson commented 3 years ago

Hi Nenad:

Thank you again for the reply. Yes, I had just figured that out on my own. I did what you suggested and it works perfectly. I also discovered that the FSM runs in its own thread, so if both the FSM and another process share a resource (a UART TX in my case), there will be thread collisions. So I implemented a queue to make the UART communications thread safe. The FSM timer event dequeues and sends the UART commands in a single thread. It is all working perfectly.

I also copied your technique from the Device Controller to use a shell script to set the PYTHONPATH and launch the main module. This works fine, but it is not compatible with the VSCode debugger, which needs to launch the python code itself. I can't figure out how to get VSCode to set the PYTHONPATH , so I get 'module not found' errors. I may have to set PYTHONPATH at the OS level. Do you have any advice on setting up VSCode for debugging?

About audio, I can tell from your schematics that you already know what you're doing, so there is probably not much I can tell you. But here are a few tricks you may find useful:

  1. avoid thick film SMT resistors in the audio path. They have a non-linear voltage coefficient that causes significant distortion, particularly for values in the 3k to 5k range. And the distortion vaires by batch, something you would only learn about when you build in production quantities. And it is not a subtle "golden ears" thing, but a very measureable phenomenon. Use thin film 1% resistors. MELF resistors are an excellent choice but are expensive and hard to find. They are also not great for automated assembly because they can move during soldering and cause low yield. Susumu has their "RS" line of "audio" resistors that are very good, but also harder to find and expensive. Of course thru-hole 1% metal film resistors are great and probably easier to get for now, but maybe not in the future.

  2. try to avoid any capacitors in the audio path for critical designs. If you must have them, make sure they are a good film type like Wima or Panasonic. Some designers use dc-biased capacitors to avoid polarity reversal nonlinearity: use two capacitors in series and tie the midpoint node to a clean DC supply via a high value (5-10 Meg) resistor. Also, use a DC servo to remove DC offset at the output of a line stage. This line amp has both a DC servo and a mute relay (see below.) Each board has a 555 timer for power-on delay. The relay controls are "wire-or" connected so that if there are multiple boards in a chassis, the relays all stay closed until the last 555 times out, then all the relays switch together. BTW this design was done in EasyEDA, (easyeda.com) which is great for small boards. Their parts partner LCSC is also great and the common parts library is very handy. Look up my user name "DaveDC" there and have a look at my designs. They should all be public. If you can't get access, let me know and I will fix it. You can also order boards if you want.

[image: image.png]

  1. for multi-channel op amp circuits, instead of tying all of the op amp power supplies directly to the same rail, isolate them with transistors in an emitter-follower configuration. Each op amp gets its own pair of transistors, NPN for the Vcc and PNP for the VDD. Bias the transistor bases through a large low-pass RC filter. This helps prevent the different channels from 'talking' to each other via the power supply. And yes the 5532 is still a good audio amp! Also notice that I use a separate schematic symbol for the op amp power pins. That way the power connections don't get mixed up with the audio connections, making the schematic easier to read.

[image: image.png]

  1. Speaking of schematic symbols, notice that my resistor and capacitor symbols have pin1 indicator dots (the black squares are a CAD graphic artifact). The PCB footprints do as well. This is very handy for debugging and writing rework instructions. Also it is a good idea to make the package size, capacitor type and voltage, etc. visible on the schematic. It helps other designers do a design review. Like coding, style is important.

  2. For monitor (speaker) controllers, it is often a good idea to pass the signal through a high-quality mute relay (mercury-wetted if possible) at the very output. The relay grounds the outputs on power-up, then unmutes after a time delay to let the power supplies stabilize. This prevents speaker damage if you accidentally power up the controller after the power amps are powered. Speaker power amps should turn on last and turn off first.

  3. for ESD protection, avoid back-biased diodes to the power supply if the signal node is high impedance. They are helpful for minor overvoltage, but a direct 8kV ESD spark will destroy them. Diodes generally fail to a short circuit, so now the spark can kill the power supply. Instead, I generally use back-to-back Zeners to ground after a series resistor and a ferrite bead "T" filter, then a small capacitor to ground. Because the majority of the energy of an ESD spark is high frequency, a low-pass filter does a very good job of protecting the downstream device. BTW, output connections need ESD protection too. Also notice the small "+" symbol on the schematic near the input jack. I use this to indicate the signal polarity. If you use any inverting amplifiers, you need to keep track of the overall signal polarity throughout the signal path. This little trick helps ensure that the overall device maintains true polarity.

[image: image.png]

  1. I see you are keen on discrete transistor circuits, so try the "Diamond Buffer" when you need a unity gain buffer: [image: image.png] Another variant uses a J507 current regulator diode for biasing, but that part is now pretty much obsolete and unobtainable. Notice the DC-biased capacitors C20 and C21.

[image: image.png]

  1. don't be afraid of using a switching power supply for audio designs. Because of the rectifier diodes, even traditional "linear" supplies are switching, only at 50-60 Hz. But as we know, the switching artifacts contain the same high frequency energy regardless of the switching frequency. And you can always follow the switcher with a linear regulator. But do still watch out for magnetic interference from the PSU, particularly radiating from the common-mode choke at the AC inlet. You may need to reorient the PSU inside the enclosure to avoid it.

Feel free to send along any specific designs you would like a design review for.

Have a great holiday season and stay safe! Cheers!

DC

On Sun, Dec 13, 2020 at 6:12 AM Nenad Radulović notifications@github.com wrote:

Hello,

It looks like that you don't need to transit to a new state at all, but just receive a time event (from timer) and do the processing there. After processing just return from function and don't return the next state.

For example, the state State_A is sensitive to 2 events:

  • event_1
  • event_2

When event_1 is received, after processing of the event the function returns new state class State_B which will tell to state machine dispatcher that it needs to transition to State_B state. When event_2 is received, the event will be processed and the function will return None which tells to the state machine dispatcher that state machine will remain in the current state.

@fsm.DeclareState(BlinkyFsm) class State_A(fsm.State): def on_event_1(self, event): print('event 1') return State_B

def on_event_2(self, event):
    print('event 2')

If you omit the return, then the state machine will not transition to a new state but it will remain in the current state.

This is the pattern how you usually deal with the use-case you just described.

P.S. I would like to have your input on my amplifier designs, like component choices (SMD, through hole, availability...), possible improvements and so on. :-)

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/nradulovic/pyeds/issues/13#issuecomment-744013647, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGL6X6PGTPICN7LRIGNFQWLSUTDTTANCNFSM4UFMNR7A .