Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using belay in a class #51

Closed
roaldarbol opened this issue Nov 21, 2022 · 10 comments
Closed

Using belay in a class #51

roaldarbol opened this issue Nov 21, 2022 · 10 comments

Comments

@roaldarbol
Copy link
Contributor

I'm now trying to create a class that takes a port as input, creates the Belay.device in __init__ and has belay-decorated methods. However, I can't do that currently as the decorator depends on device. I also tried initiating the device outside the class and using it as an argument, but that doesn't help either. Any ideas on how to go about this?

import belay

class BelayDevice():
    def __init__(self, port, **kwargs):
        device = belay.Device(port, 115200) #'/dev/cu.usbmodem141201'

    @device.task
    def led_loop(self, period):
        from neopixel import NeoPixel
        np = NeoPixel(Pin(17), 8)
        x = 0
        state = False
        light = (0,0,0)
        while x < 6:
            # np.value(state)
            if state is False:
                light = (255, 255, 255)
            else:
                light = (0,0,0)
            np.fill(light)
            state = not state
            sleep(period)
            x += 1

bel = BelayDevice('/dev/tty.SLAB_USBtoUART')
bel.led_loop(1)

# Traceback (most recent call last):
#  File "/Users/roaldarbol/Filen/git-projects/belay/mikkel-test.py", line 3, in <module>
#   class BelayDevice():
#  File "/Users/roaldarbol/Filen/git-projects/belay/mikkel-test.py", line 7, in BelayDevice
#    @device.task
# NameError: name 'device' is not defined

For background, I'd like to be able to connect to multiple controllers and run code separately. I'll use multiprocessing for that, but I've already got that side of things down - I just need a way that I can easily instantiate a class multiple times.

@BrianPugh
Copy link
Owner

BrianPugh commented Nov 21, 2022

so its not perfect, but currently the best available solution is probably something like:

class BelayDevice():
    def __init__(self, port, **kwargs):
        device = belay.Device(port, 115200) #'/dev/cu.usbmodem141201'
        @device.task
        def led_loop(period):
            from neopixel import NeoPixel
            np = NeoPixel(Pin(17), 8)
            x = 0
            state = False
            light = (0,0,0)
            while x < 6:
                # np.value(state)
                if state is False:
                    light = (255, 255, 255)
                else:
                    light = (0,0,0)
                np.fill(light)
                state = not state
                sleep(period)
                x += 1
        self.led_loop = led_loop

There is two issues with decorating methods in the way you posted:

  1. led_loop is defined before device is defined.
  2. If the decorated method somewhere were sent to the device, the device doesn't know about self.

(1) Can't really be solved since this is outside of Belay's control. If device was defined outside of __init__, it could work, but this doesn't really solve your problem.
(2) Could be solved if we made it a staticmethod. I haven't really tested this.

If you are trying to control multiple boards, Belay supports multiple device chaining. Note that multiple device chaining is currently not supported for generators. Also, honestly, this isn't as well tested as other features.

@roaldarbol
Copy link
Contributor Author

roaldarbol commented Nov 21, 2022

As I'd like to dynamically use the method I can't decorate it manually which leaves out chaining multiple devices. I think (2) might be a good solution - found a similar suggestion in a discussion. Would then look something like (untested):

from belay import Device

class BelayDevice(Device):
    def __init__(self, port, **kwargs):
        super().__init__(port, 115200) #'/dev/cu.usbmodem141201'

    @Device.task
    def led_loop(self, period):
        from neopixel import NeoPixel
        np = NeoPixel(Pin(17), 8)
        x = 0
        state = False
        light = (0,0,0)
        while x < 6:
            # np.value(state)
            if state is False:
                light = (255, 255, 255)
            else:
                light = (0,0,0)
            np.fill(light)
            state = not state
            sleep(period)
            x += 1

bel = BelayDevice('/dev/tty.SLAB_USBtoUART')
bel.led_loop(1)

With no modifications to the source code I get AttributeError: type object 'Device' has no attribute 'task'.

EDIT: Updated the import and decorator name.

@BrianPugh
Copy link
Owner

something like

    @Device.task
    @staticmethod
    def led_loop(period):

might be doable; since it would always have to be a staticmethod. Maybe I should just integrate staticmethod into the decorator since it has to be a staticmethod. Currently device.task is actually a belay.device. _Executer object that actually gets initialized during __init__, so defining a staticmethod task wouldn't actually collide with existing functionality, since it gets overwritten on construction. I'll investigate more later this week.

@roaldarbol
Copy link
Contributor Author

Ah sweet! I'll wait for you to have a jab at it then! 😃 And BTW thanks for mentioning me in the micropython PR - really appreciate it.

@roaldarbol
Copy link
Contributor Author

Just some additional use case that would be possible with a class. It would allow importing the class from a different script, thus keeping the methods that are inherent to the piece of hardware you're working with in a separate place. I think it would be a great way to have a focused script. For me it could look like:

import time
import belay
from experiments import BelayDevice

# Initiate device
beehive = BelayDevice('/dev/tty.SLAB_USBtoUART')

# Experiment
beehive.led_toggle()
time.sleep(5)
beehive.led_toggle()

# Finish experiment
beehive.close()

@BrianPugh
Copy link
Owner

I agree that looks much nicer; i'll see what i can do.

@BrianPugh
Copy link
Owner

BrianPugh commented Nov 25, 2022

@roaldarbol See #52, I got it working. See if you like the interface or if you would change anything; please leave feedback in the PR. I still need to write more documentation for it, but see example 11 08.

EDIT: In this PR, the previous method for multiple devices (multiple decorating) has been removed. I think the class approach is much more elegant for the user, and significantly simplifies Belay's code.

@BrianPugh
Copy link
Owner

Between #52 and #54 , I'm going to close this issue. #54 will be merged in shortly. If you think something isn't addressed in these PRs, we can either re-open this issue or create a new one. Thanks!

@BrianPugh
Copy link
Owner

by the way, I made Device a subclass of autoregistry.Registry in v0.13.0, so if you are making a cli that controls multiple hardware, you could easily do something like this:

import argparse
from belay import Device
from time import sleep


class ModelA(Device):
    @Device.setup
    def setup():
        led = Pin(24)

    @Device.task
    def set_led(value):
        led.value(value)


class ModelB(Device):
    @Device.setup
    def setup():
        led = Pin(25)

    @Device.task
    def set_led(value):
        led.value(value)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("model", choices=["ModelA", "ModelB"])
    parser.add_argument("--port", default="/dev/ttyUSB0")
    args = parser.parse_args()

    device = Device[args.model](args.port)
    device.setup()

    while True:
        device.set_led(True)
        sleep(0.5)
        device.set_led(False)
        sleep(0.5)


if __name__ == "__main__":
    main()

@roaldarbol
Copy link
Contributor Author

Wohooooo! 🥳 I'm actually building a GUI for something similar, so the example is really useful! Really nice work Brian - I'll test in the afternoon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants