Some of my recent rules:
# I have an item I turn ON when I'm waiting for someone, and want to be notified immediately based on my cameras. I have too many cameras and too many notifications to get this all the time
changed Driveway_Cars, Driveway_Persons do |event|
next unless ExpectingSomeone_Switch.on?
next unless event.state?
if event.state.positive?
only_every(:minute) do
notify("Someone is in the driveway")
end
end
end
# frozen_string_literal: true
# this rule saves a snapshot from my outdoor cameras every day at solar noon, so I can build a Timelapse without shadows shifting every day
require "cgi"
require "fileutils"
require "open-uri"
require "uri"
require "yaml"
CAMS_TO_SNAPSHOT = %w[
back_yard_south
fire_pit
...
].freeze
OUTPUT_DIR = "/home/cody/docker/frigate/storage/snapshots"
# astro.items:
# DateTime Sun_Noon { channel="astro:sun:home:noon#start" }
every :day, at: Sun_Noon do
frigate_config = YAML.load_file("/home/cody/docker/frigate/config.yml")
snapshot_urls = frigate_config.dig("go2rtc", "streams").to_h do |cam, url|
url = url.first if url.is_a?(Array)
url = URI.parse(url)
url.scheme = "http"
url.path = "/Streaming/channels/1/picture"
# have to re-parse so it will be the correct type since the scheme changed
[cam, URI.parse(url.to_s)]
end
today = Date.today
CAMS_TO_SNAPSHOT.each do |cam|
snapshot_url = snapshot_urls[cam]
FileUtils.mkdir_p(File.join(OUTPUT_DIR, cam))
# credentials have to be passed to #open separately
creds = [CGI.unescape(snapshot_url.user), CGI.unescape(snapshot_url.password)]
snapshot_url.userinfo = ""
logger.info("Downloading snapshot from camera #{cam}")
snapshot_url.open(http_basic_authentication: creds) do |snapshot|
IO.copy_stream(snapshot, File.join(OUTPUT_DIR, cam, "#{today}.jpg"))
end
rescue => e
logger.warn("Failed to fetch snapshot from #{cam}: #{e}")
notify("Failed to fetch snapshot from #{cam} camera")
end
rescue
notify("Failed to fetch snapshots from cameras")
raise
end
This sets up a series of rules for exterior light. The lights come on in the evening to a relatively low level, then even lower at night. But if the cameras detect a person in the vicinity, they’ll immediately brighten. When the cameras no longer detect a person, it takes 15 seconds before they go back to their prior level. It’s a good demonstration of using a method to set up variations on a base rule.
def setup_person_light(dimmer, person_items, evening_level:, night_level: nil, respect_halloween: false)
night_level ||= evening_level
OpenHAB::DSL.rule "Set #{dimmer.name} based on occupancy and house mode" do
on_load
changed HouseMode_String, *person_items
run do
halloween = respect_halloween && Date.today == MonthDay.parse("10-31")
OpenHAB::DSL.timers.schedule([dimmer, :state_based_on_occupancy_and_mode]) do |timer|
timer&.cancel
occupied = person_items.map(&:state).compact.sum.positive?
case HouseMode_String.state
when "Day"
dimmer.ensure.off
when "Evening", "LateEvening"
if occupied && !halloween
dimmer.ensure << 100
elsif !dimmer.state || dimmer.state < evening_level
dimmer << evening_level
else
next after(15.seconds) { dimmer.ensure << evening_level }
end
when "Night"
if occupied
dimmer.ensure << 100
elsif !dimmer.state || dimmer.state < night_level
dimmer << night_level
else
next after(15.seconds) { dimmer.ensure << night_level }
end
end
nil
end
end
end
end
setup_person_light(PorchCans_Dimmer,
[Porch_Persons],
evening_level: 20,
night_level: 0,
respect_halloween: true)
setup_person_light(PorchLights_Dimmer,
[Porch_Persons],
evening_level: 20,
respect_halloween: true)
setup_person_light(CoachLights_Dimmer,
[Porch_Persons, Driveway_Persons, Basketball_Persons],
evening_level: 50,
night_level: 25,
respect_halloween: true)
setup_person_light(BasketballCoachLights_Dimmer,
[Basketball_Persons],
evening_level: 100,
night_level: 25)