This module provides an object for finding devices based on filters, which can also act as a daemon collecting information in the background.

The idea is that you can query the device_finder for information.

device_finder = DeviceFinder(lan_target)

serials = await device_finder.serials()
# serials will be of the form ["d073d5000001", "d073d5000002", "d073d5000003"]

info = await device_finder.info_for()
# info will be of the form:
# {
#      "d073d512c21d": {
#          "serial": "d073d512c21d",
#          "brightness": 0.29999237048905164,
#          "firmware_version": "1.21",
#          "group_id": "925fb774c42c11e799e580e650080d3e",
#          "group_name": "one",
#          "cap": ["color", "multizone", "variable_color_temp"]
#          "hue": 0.0,
#          "kelvin": 3500,
#          "label": "cupboard",
#          "location_id": "926647fec42c11e79fb080e650080d3e",
#          "location_name": "two",
#          "power": "off",
#          "product_id": 22,
#          "product_identifier": "lifx_a19_color",
#          "saturation": 0.0
#      },
#      "d073d514e733": {
#          "serial": "d073d514e733",
#          "brightness": 0.29999237048905164,
#          "firmware_version": "1.20",
#          "group_id": "925fb774c42c11e799e580e650080d3e",
#          "group_name": "one",
#          "cap": ["color", "multizone", "variable_color_temp"]
#          "hue": 0.0,
#          "kelvin": 3500,
#          "label": "tv",
#          "location_id": "926647fec42c11e79fb080e650080d3e",
#          "location_name": "two",
#          "power": "off",
#          "product_id": 31,
#          "product_identifier": "lifx_z",
#          "saturation": 0.0
#      }
# }

Or you can use it as a reference in a run_with/run_with_all call:

device_finder = DeviceFinder(lan_target)

reference = device_finder.find(group_name="one")
await lan_target.script(DeviceMessages.SetPower(level=0)).run_with_all(reference)

reference2 = device_finder.find(hue="0-20", product_identifier=["lifx_color_a19", "lifx_color_br30"])
for pkt, _, _ in lan_target.script(DeviceMessages.GetPower()).run_with(reference2):

Note that if you want the device_finder to update it’s idea of what devices are on the network and what properties those devices have, then you must await on it’s start method before using it:

device_finder = DeviceFinder(lan_target)
await device_finder.start()

# Use the device_finder as much as you want

# It's a good idea to finish it when you no longer want to use it
# Note that it cannot be started again after this.
await device_finder.finish()

The find, serials and info_for methods on DeviceFinder all take in keyword arguments to fill out the filter, or it takes in a filtr keyword that specifies a filter. Not providing any keyword arguments implies a match all.

Note that if you don’t call start on the device_finder is equivalent to always having force_refresh=True in your filter.

from photons_device_finder import Filter

# The following four lines are equivalent
filtr = Filter.from_options({"hue": ["0-10", "30-50"], "label": "one"})
filtr = Filter.from_kwargs(hue=["0-10", "30-50"], label="one")
filtr = Filter.from_json_str('{"hue": ["0-10", "30-50"], "label": "one"}')
filtr = Filter.from_key_value_str('hue=0-10,30-50 label=one')

# And these two lines are equivalent
device_finder.find(hue=["0-10", "30-50"], label="one")


When you use DeviceFinder.find you are creating an instance of photons_app.special.SpecialReference which means it will cache the result of finding devices for future calls. If you want to re-use one and have it search again, call reset on it.


See Tasks.

  • Needs a target to be specified

Find serials that match the provided filter

find_with_filter -- '{"label": ["kitchen", "loungeroom"], "product_identifier": "lifx_z"}'

Not providing options after the -- will find all devices on the network.

Valid Filters

The filter takes in:


Search for devices and their information. This will only refresh information required for the rest of the arguments to the Filter.

i.e. if the filter only matches against labels, then we will only refresh the labels on our devices.

The serial of the device
The label set on the device
Either “on” or “off” depending on whether the device is on or not.
The uuid of the group set on this device
The name of this group. Note that if you have several devices that have the same group, then this will be set to the label of the group with the newest updated_at option.
The uuid of the location set on this device
The name of this location. Note that if you have several devices that have the same location_id, then this will be set to the label of the location with the newest updated_at option.
hue, saturation, brightness, kelvin
The hsbk values of the device. You can specify a range by saying something like 10-30, which would match any device with a hsbk value between 10 and 30 (inclusive).
The version of the HostFirmware as a string of “{major}.{minor}”.
The product id of the device as an integer. You can see the hex product id of each device type in the photons_products module.
A string identifying the product type of the device. You can find these in the photons_products module.

A list of strings of capabilities this device has.

Capabilities include ir, color, chain, matrix, multizone, variable_color_temp and not_ir, not_color, not_chain, not_matrix, not_multizone, not_variable_color_temp

When a property in the filter is an array, it will match any device that matches against any of the items in the array.

And a filter with multiple properties will only match devices that match against all those properties.

Label properties (“product_identifier”, “label”, “location_name”, “group_name”) are matched with globs. So if you have device1 with product_identifier of lifx_a19_plus and device2 with a product_identifier of lifx_br30_plus you can filter them both by saying Filter.from_kwargs(product_identifier="*_plus")

class photons_device_finder.Filter(brightness, cap, firmware_version, force_refresh, group_id, group_name, hue, kelvin, label, location_id, location_name, power, product_id, product_identifier, saturation, serial)

The options for a filter. Usage looks like:

filtr = Filter.FieldSpec().empty_normalise(force_refresh=True, firmware_version="1.22")

# or
filtr = Filter.from_json_str('{"force_refresh": true, "firmware_version": "1.22"}')

# or
filtr = Filter.from_options({"force_refresh": True, "firmware_version": "1.22"})

# or
filtr = Filter.from_kwargs(force_refresh=True, firmware_version="1.22")

# or
filtr = Filter.from_key_value_str("force_refresh=true firmware_version=1.22")

# or
filtr = Filter.from_url_str("force_refresh=true&firmware_version=1.22")
classmethod from_options(options)

Create a Filter based on the provided dictionary

classmethod from_kwargs(**kwargs)

Create a Filter based on the provided kwarg arguments

classmethod empty(force_refresh=False)

Create an empty filter

classmethod from_json_str(s)

Interpret s as a json string and use it to create a Filter using from_options

classmethod from_key_value_str(s)

Create a Filter based on the key=value key2=value2 string provided.

Each key=value pair is separated by a space and arrays are formed by separating values by a comma.

Note that values may not have spaces in them because of how we split the key=value pairs. If you need values to have spaces use from_json_str or from_options.

classmethod from_url_str(s)

Create a Filter based on key=value&otherkey=value2 string provided

Where the string is url encoded.


True if this Filter matches against any device

matches(field_name, val)

Says whether this filter matches against provided filed_name/val pair

  • Always say False for force_refresh
  • Say False if the value on the filter for field_name is NotSpecified
  • Say True if a hsbk value and we are within the range specified in val
  • Say True if value on the filter is a list, and val exists in that list
  • Say True if value on the filter is not a list and matches val

Say whether the filter has an opinion on this field

Finally, we have has which takes in a field_name and says whether

class photons_device_finder.DeviceFinder(target, service_search_interval=20, information_search_interval=30, repeat_spread=1)

Used by users to find devices based on filters.

You can activate the daemon functionality by awaiting on start. Not doing this is equivalent to always using force_refresh with your filters.

When you are finished with this object, await on it’s finish method.

await serials(**kwargs)

Return a list of hexlified serials of the devices we can find that match the filter created from the passed in kwargs.

await info_for(**kwargs)

Return a dictionary of {serial: information} of the devices we can find that match the filter created from the passed in kwargs.


Return a SpecialReference object that may be used with run_with/run_with_all

It will tell the script to send messages to the devices it can find that match the filter we create from the passed in kwargs.

await start(quickstart=False)

Put the DeviceFinder into daemon mode and start the background threads.

By doing this, we will use cached information when using the device_finder unless the filter specifically sets force_refresh to True.

If quickstart is provided as True then we will gather information from devices on the network without spreading out the messages. This will allow us to gather information quicker at the cost of much more network traffic.

await finish()

Stop the background tasks

await args_for_run()

Return an args_for_run object from our target

Multiple calls to this will return the same object