"""
Turn the lights on in my office when it gets dim
"""
# Explicitly bring in the prelude
# This is done for you but for examples exlicit helps.
load("pkg.flowmetal.io/prelude/v1",
"cron_trigger",
"current_flow",
"flow",
"resource",
"retry",
)
# Bring in
load("pkg.flowmetal.io/home-assistant/v1",
"home_assistant_resource",
)
load("pkg.flowmetal.io/one-password/v1",
"op_resource",
)
# Wrap the hass client to use a secret from 1Password.
hass = resource(
open = lambda ctx: home_assistant_resource(
url = ctx.args.url,
token = ctx.resources.op.item(
ctx.args.vault_id,
ctx.args.secret_id,
).field("api-key").value,
),
# This is the default close, but spell it out
close = lambda self: self.close(),
args = {
"url": args.string(),
"vault_id": args.string(),
"secret_id": args.string(),
# This means "an instace of op" not "the op"
"op": args.resource(op),
}
)
# A JSON schema describing allowed data
SCHEMA = {"properties": {
"brightness_pct": {"type": "int"}
}}
# Our workflow
auto_lights = flow(
# Arguments the workflow caller will provide
args = {
"hass_url": args.string(),
"hass_secret_id": args.string(),
"luminocity_sensor_id": args.string(),
"luminocity_threshold": args.int(
min=0,
default=100,
),
"occupancy_sensor_id": args.string(),
"light_id": args.string(),
"light_on_data": args.json(
schema=SCHEMA,
default={"brightness_pct": 100},
),
},
# Arguments we read as eponymous secrets.
# Secrets come from the user and from Flowmetal.
secrets = [
"op_token",
"op_url",
"op_vault",
],
# Recipes for building resources used in this flow.
# Resource recipes can consume other resources.
resources = {
"op": lambda fctx: op_resource(
url = fctx.secrets.op_url,
token = fctx.secrets.op_token,
),
"hass": lambda fctx: hass(
url = fctx.args.hass_url,
vault_id = fctx.secrets.op_vault,
secret_id = fctx.args.hass_secret_id,
op = ctx.resources.op,
),
},
# And this is the code to run
implementation = _flow,
)
def _flow(fctx: FlowContext):
# Read the sensor state
occ_state = fctx.resources.hass.states(
fctx.args.occupancy_id,
)
lux_state = fctx.resources.hass.states(
fctx.args.luminocity_sensor_id,
)
# If the room is occupied and the room is
# below the lux threshold, turn the light on
if unwrap(occ_state) \
and fctx.args.luminocity_threshold < unwrap(lux_state):
fctx.resources.hass.service(
"light/turn_on",
fctx.args.light_id,
data = fctx.args.light_on_data,
)
if __name__ == "__flow__":
# This body itself happens as a flow. We could
# just call `auto_lights`. That would be a
# one-shot job. Instead we want to create a
# daemon with a time trigger.
l = lambda *_, **__: auto_lights(
hass_url = "https://hass.tirefireind.us",
hass_secret_id = "<redacted>",
occupancy_sensor_id = "<redacted>",
luminocity_sensor_id = "<redacted>",
luminocity_threshold = 100,
light_id = "<redacted>",
# Prefer mood lighting in the office
light_on_data = dict(brightness_pct = 35),
)
# One-shot call the lights flow in the current context
l()
# We can wrap up the lights flow into something
# that'll run on a schedule and spawn that off
# in order to create a service.
spawn_res = current_flow().spawn(
l,
task_id = "lights",
on_conflict = "replace",
triggers = [
cron_trigger(
pattern = "*/5 * * * *",
),
]
)