Now available through pypi/pip!
To install:
pip install jellyfishlights-py
The snippets below show most of what this module can do and have important usage notes in the comments.
from jellyfishlightspy import JellyFishController, ScheduleEvent, ScheduleEventAction, ZoneConfig, PortMapping
import logging
# Debug logging exposes the JSON messages sent to and received from the controller
logging.basicConfig(level = logging.DEBUG)
# Create a controller object and connect
jfc = JellyFishController('192.168.0.245') # hostname also works
jfc.connect()
# Print the controller's name and hostname
print(f"Connected to JellyFish Lighting controller '{jfc.name}' ({jfc.hostname})")
# Print the controller's firmware version information
print(f"Firmware version: {jfc.firmware_version}")
# Print the controller's timezone configuration
print(f"Timezone configuration: {jfc.time_config}")
# Change the controller's user-defined name
jfc.set_name("My JellyFish Controller")
# Disconnect from the controller
jfc.disconnect()
# Create callbacks to respond to various events
def on_open()
print("Connected!")
def on_close(status, message)
print("Disconnected (status: %s, message: %s)", status, message)
def on_message(data)
print("Recieved push data: %s", data)
def on_error(error)
print("Error encountered: %s", error)
# Register your callbacks
jfc.add_listener(on_open, on_close, on_message, on_error)
# Print the currently configured zones
# NOTE: all attributes on the controller will return cached data when available.
# Cached data is automatically updated via push events, but if you want to ensure
# you are retrieving the latest information from the controller, use the corresponding
# get_* function (jfc.get_zone_names() in this case)
print(f"Zones: {jfc.zone_names}")
# Print the current state of all zones
for name, state in jfc.get_zone_states().items():
pattern = state.file
brightness = state.data.runData.brightness
colors = state.data.colors
print(f"Zone '{name}' is {'on' if state.is_on else 'off'} (pattern: '{pattern}', colors: {colors}, brightness: {brightness})")
# Turn off all zones
# NOTE: Many commands have an optional zones parameter. If not filled, it defaults to all zones
# NOTE: Many of the commands have an optional sync parameter.
# If sync=False (default is True) it sends the command and does not wait for a response (returns immediately)
jfc.turn_off(sync=False)
# Turn on the 'front-zone' zone - the lights will be in the same state as when they were last on
# NOTE: Many of the commands have an optional timeout parameter.
# If sync=True (it is by default), the command is synchronous and will raise a JellyFishException
# if a response isn't received within the timeout period (default is 10 seconds).
jfc.turn_on(["front-zone"], timeout=5)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# !!ADVANCED!! - change zone configurations
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
orig_zones = jfc.zone_configs
# Print current zone configurations
for zone, config in orig_zones.items():
print(f"Zone '{zone}' config: {config}")
# Add a new zone
new_config = ZoneConfig([
# NOTE: the phyPort attribute maps as such to the ports on the controller (controller port->phyPort): 1->1, 2->2, 3->4, 4->8
# NOTE: zoneRGBStartIdx defaults to phyStartIdx. Setting it to the phyEndIdx value will reverse the direction
# NOTE: ctlrName defaults to the hostname of the controller you are currently connected to (jfc.hostname)
# NOTE: All of the *Idx values are one less than what is displayed in the app! (e.g. a "1" value in the app is a "0" value here)
PortMapping(phyPort=1, phyStartIdx=0, phyEndIdx=10, zoneRGBStartIdx=10, ctlrName="JellyFish-XXXX.local"),
# NOTE: this is the short version that sets only the required fields: phyPort, phyStartIdx, and phyEndIdx
PortMapping(2, 0, 99)
])
jfc.add_zone("My new zone", new_config)
# Delete the zone we just created
jfc.delete_zone("My new zone")
# Save the full set of all zone configurations at once
jfc.set_zone_configs({"My new zone": new_config}) # This would result in a single zone (any other zones would be deleted)
jfc.set_zone_configs({}) # This would delete all zone configurations
jfc.set_zone_configs(orig_zones) # This would restore the zone configurations to what we retrieved above (before we modified them)
# Print the list of currently configured patterns
print(f"Patterns: {jfc.pattern_names}")
# Run a preset pattern on all zones
jfc.apply_pattern("Special Effects/Red Waves")
# Retrieve a pattern configuration
config = jfc.get_pattern_config("Colors/Blue")
print(config)
# Customize the pattern configuration and run it on the 'front-zone' zone
# (this example shows just a few of the configurable pattern attributes)
config.colors.extend([0, 0, 0]) # Note that 'colors' is a list of ints, not a list of tuples! Be sure the list is divisible by 3
config.type = "Chase" # Valid values: ["Color", "Chase", "Paint", "Stacker", "Sequence", "Multi-Paint", "Soffit"]
config.direction = "Center" # Valid values: ["Left", "Center", "Right"]
config.spaceBetweenPixels = 8
config.effectBetweenPixels = "Progression" # Valid values: ["No Color Transform", "Repeat", "Progression", "Fade", "Fill with Black"]
config.runData.speed = 1
config.runData.effect = "No Effect" # Valid values: ["No Effect", "Twinkle", "Lightning"]
jfc.apply_pattern_config(config, ["front-zone"])
# Save your new pattern to a file to easily run later.
# The parent folder will be created if it doesn't exist.
# You can also update existing patterns this way (if they're editable).
jfc.save_pattern("Special Effects/Blue Waves", config)
# Delete the pattern
jfc.delete_pattern("Special Effects/Blue Waves")
# Set 'front-zone' and 'back-zone' to a solid color (white @ 100% brightness in this case)
jfc.apply_color((255, 255, 255), 100, ["front-zone", "back-zone"])
# Set individual lights on the 'porch-zone' zone
# This example sets only the first 3 lights in the zone, but you can set as many as you would like
lights = [
(255, 0, 0), # Red
(0, 255, 0), # Green
(0, 0, 255) # Blue
]
jfc.apply_light_string(lights, 75, ["porch-zone"]) # 75% brightness
# Retrieve the calendar schedule
orig_events = jfc.calendar_schedule # Use jfc.daily_schedule to get the daily schedule
for event in orig_events:
print(event)
# Add an event to the schedule
event = ScheduleEvent(
# Must be in YYYYMMDD format for calendar events
# Even though a year must be specified the event will run annually
# You must include each individual day if specifying a range
days = ["20231231", "20230101", "20230102"],
# days = ["M", "T", "W", "TH", "F", "SA", "S"], <-- Example for a daily schedule event
actions = [
ScheduleEventAction(
type = "RUN",
startFrom = "sunset",
hour = 0, # For 'sunrise' and 'sunset', hour must be 0...
minute = 30, # ...and the minute offset must be between -55 and 55 and divisible by 5
patternFile = "Special Effects/Rainbow Waves",
zones = jfc.zone_names # The list of zones for each RUN/STOP action must match!
),
# For 'time', the hour must be between 0 and 23, and minute between 0 and 59
ScheduleEventAction("STOP", "time", 5, 00, "", jfc.zone_names)
]
)
jfc.add_calendar_event(event)
# To remove events you must send the updated full schedule of events
jfc.set_calendar_schedule([event]) # This would delete all events other than what we just created
jfc.set_calendar_schedule([]) # This would delete all events
jfc.set_calendar_schedule(orig_events) # This would restore the schedule to what we retrieved above (before we modified it)
Contributions are welcome! To run the test suite, first set the JF_TEST_HOST
environment variable to your local JellyFish Lighting controller's address. Then run:
python -m pytest ./tests
If you don't have a local controller to test with you can skip the integration tests by running:
python -m pytest ./tests/unit