JMAlego / PyDMX

Python module for sending DMX512 data.
https://pypi.org/project/PyDMX/
BSD 3-Clause "New" or "Revised" License
47 stars 10 forks source link

FEATURE IDEA: Customisable DMXLight class #1

Closed HJ959 closed 3 years ago

HJ959 commented 3 years ago

Hey DMAlego! First let me say thank you so much for this code! It's given me some hope with a project I am currently working on and works with my cheap DMX interface. Brilliant.

My feature idea is to create a DMXLight class that can be customised to fit the fixtures you may come across. For example I have some cheap U'King DMX par cans that have 7 channels, Brightness, RGB, Strobe, presets, presets. Also another spot light that has 6 channels.

I must admit I'm not the best at OOP and am struggling to adapt your code. I can't seem to find where the value of the channels are actually set as well. I'm thinking it's the Colour class that's responsible.

I wonder if you could give me a steer and to whether or not you'd like this implemented in the repo.

Maybe another class called channels or something that lets you access whatever channel within the light object.

Thanks!

JMAlego commented 3 years ago

Hi there @HJ959, it's nice to see someone else using this code.

There currently exists a DMXLight abstract class which all other lights need to inherit from and implement some methods for: https://github.com/JMAlego/PyDMX/blob/846a50a46df7cd82920b053532ef9e75dc50a1fc/dmx/light.py#L11-L38

Two examples of this can be seen in DMXLight3Slot and DMXLight7Slot, here's what DMXLight3Slot looks like: https://github.com/JMAlego/PyDMX/blob/846a50a46df7cd82920b053532ef9e75dc50a1fc/dmx/light.py#L41-L60

You can see there are 2 main methods that need to be overridden in subclasses:

For basically all use cases you'll want to also add other methods for interacting with the light (for instance set_colour or similar) and also want to override the initialiser to setup relevant internal state for the light.

The current way that the raw data for channels travels is as follows:

  1. When serialise is called on a DMXUniverse class, each light in that universe has it's serialise method called.
  2. Each light runs it's serialise logic (which can include calling serialise on a Colour class as you noticed) and returns it's channel data.
  3. Each light's data is taken by the universe and combined to fill out all the channels (using the channel each light says it's on).
  4. serialise on the universe returns a complete picture of what all 512 channels are set to as a list of integers from 0-255 inclusive (e.g. basically bytes).

As can be seen in the example code from the readme (pasted below) this channel data is then typically passed to the interface by calling set_frame which updates the frame to be sent out next time the send_update method on the interface is called.

from dmx import Colour, DMXInterface, DMXLight3Slot, DMXUniverse

PURPLE = Colour(255, 0, 255)

# Open an interface
with DMXInterface("FT232R") as interface:
    # Create a universe
    universe = DMXUniverse()

    # Define a light
    light = DMXLight3Slot(address=8)

    # Add the light to a universe
    universe.add_light(light)

    # Update the interface's frame to be the universe's current state
    interface.set_frame(universe.serialise())

    # Send an update to the DMX network
    interface.send_update()

    # Set light to purple
    light.set_colour(PURPLE)

    # Update the interface's frame to be the universe's current state
    interface.set_frame(universe.serialise())

    # Send an update to the DMX network
    interface.send_update()

The idea behind this architecture is that once a light has a class implemented then it can simply be initialised (with an address specified) and then added to the DMX universe, after that all further interactions can be with this light class which can provide a nicer interface than directly settings channel data (this can be seen in the above example). The only assumption made by the architecture is that each light will always use contiguous addresses e.g. a single light won't listen to 2 entirely separate address ranges, say 2,3, and 4 for RGB data and 20 for dimmer data.

Not sure what data the lights you describe actually want but here's an example of implementing something that looks like the first light you described:

class DMXLightExample(DMXLight3Slot):
    """Represents an DMX light with brightness, RGB, Strobe and presets."""

    def __init__(self, address: int = 1):
        """Initialise the light."""
        super().__init__(address=address)
        self._brightness = 255
        self._strobe = 0
        self._presets = (0,0)

    def set_brightness(self, value: int):
        """Set the brightness of the light between 0 and 255 (inclusive)."""
        self._brightness = int(max(0, min(value, 255)))

    def set_strobe(self, value: int):
        """Set the strobe of the light between 0 and 255 (inclusive)."""
        self._strobe = int(max(0, min(value, 255)))

    def set_presets(self, values: List[int]):
        """Set the presets of the light."""
        # NB: I have no idea how these work on the light so just left it as two arbitrary bytes.
        self._presets = tuple(values[:2])

    @property
    def slot_count(self) -> int:
        """Get the number of slots used by this light."""
        return 7

    def serialise(self) -> List[int]:
        """Serialise the DMX light to a sequence of bytes."""
        # Colour is handled by the superclass `DMXLight3Slot`.
        return [self._brightness] + super().serialise() + [self._strobe] + list(self._presets)

I have no way of testing it (as I don't have your lights) so I'm not sure if it works (there may even be a syntax error hiding in there) but hopefully that gives you an idea of what you can do to implement a light using it.

Hope all of that helps you with using this library!

HJ959 commented 3 years ago

Thanks for your reply! Give me a couple of days to digest your post and to do some tests! Cheers dude.

HJ959 commented 3 years ago

Got it working! Many many many many thanks for your help and great explanation. I also feel like I understand classes in python a lot better now.