Last active
December 9, 2023 01:57
-
-
Save ccutrer/8894f62eadeaae145eebbab0a49adb1f to your computer and use it in GitHub Desktop.
EverLights API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The app always sends a nocache param, which appears to be time since the app launched. It's ignored by the control box. | |
Color is just RGB. 255,255,255 = 0xffffff. 0,255,0 = 0x00ff00 | |
Modes: | |
all modes can take a value between 0-255. only one of tw, cf, cr, wi, ra can be set at once | |
tw: twinkle | |
fa: fade | |
bl: blink | |
st: strobe | |
cf: chase forward | |
cr: chase reverse | |
wi: wipe | |
ra: random | |
rb: ??? | |
For pattern names, spaces are saved as underscores (you can't save underscores) | |
User-Agent: everlights2/7 CFNetwork/978.0.7 Darwin/18.6.0 | |
GET /status/get?nocache=534.999015 | |
{"stsCnt":20,"ch1Cnt":402,"ch2Cnt":201,"version":"2.3.0","mac":"2C:3A:E8:FF:FF:FF","autopilot":0,"epoch":1560017691,"tzOffset":-7,"stsActive":1,"ch1Active":0,"ch2Active":0,"stsInd":1,"lightingStatus":0,"ssid":"Cutrer","rssi":-58,"ip":"192.168.10.10","mode":1,"status":"connected","rebootCnt":21,"loopsPerSecond":13422,"writeDuration":19327} | |
GET /epoch/set?epoch=1560017696&nocache=567.472399 | |
{"success":true} | |
GET /ptrn/all | |
{"patterns":["NEW_YEAR'S","VALENTINE'S_DAY","ST_PATRICK'S_DAY","EASTER","MOTHER'S_DAY","FATHER'S_DAY","HALLOWEEN","VETERANS_DAY","THANKSGIVING","CHRISTMAS","Color_splat","My_Christmas","INDEPENDENCE_DAY"],"cnt":13} | |
GET /ptrn/get?id=NEW_YEAR'S | |
{"len":3,"ptrn":[16733952,16777215,16733952],"modes":{"fa":0,"bl":0,"st":0,"cf":0,"cr":0,"wi":0,"ra":0,"tw":0,"rb":0},"name":"NEW_YEAR'S"} | |
GET /sch/getchannel?channel=1 | |
{"schedules":[]} | |
GET /ptrn/set?channel=3&data=%7B%22len%22:4,%22ptrn%22:[16777215,16711680,16777215,45056],%22modes%22:%7B%22fa%22:0,%22bl%22:0,%22st%22:0,%22cf%22:0,%22cr%22:0,%22wi%22:0,%22ra%22:0,%22tw%22:0,%22rb%22:0%7D,%22name%22:%22%22%7D | |
{"success":true} | |
GET /status/get | |
{"stsCnt":20,"ch1Cnt":402,"ch2Cnt":201,"version":"2.3.0","mac":"2C:3A:E8:FF:FF:FF","autopilot":0,"epoch":1560018587,"tzOffset":-7,"stsActive":1,"ch1Active":1,"ch2Active":1,"stsInd":1,"lightingStatus":0,"ssid":"Cutrer","rssi":-58,"ip":"192.168.10.10","mode":1,"status":"connected","rebootCnt":21,"loopsPerSecond":13099,"writeDuration":19660} | |
turn lights off | |
GET /ptrn/clear?channel=0 | |
GET /ptrn/clear?channel=1 | |
GET /ptrn/clear?channel=2 | |
GET /ptrn/clear?channel=3 | |
save pattern | |
GET /ptrn/sv?id=My_Christmas2&data=%7B%22len%22:4,%22ptrn%22:[16777215,16711680,16777215,45056],%22modes%22:%7B%22fa%22:0,%22bl%22:0,%22st%22:0,%22cf%22:0,%22cr%22:0,%22wi%22:0,%22ra%22:154,%22tw%22:0,%22rb%22:0%7D,%22name%22:%22My_Christmas2%22%7D | |
delete a pattern | |
GET /ptrn/del?id=MY_CHRUSTMAS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
rule "Any rule that runs daily at sunset or whenever you want the lights to come on" | |
when | |
... | |
then | |
// Calculate if Everlights should be on | |
var holiday = executeCommandLine("/var/lib/openhab2/holidays.rb", 500) | |
if (holiday != "") { | |
Everlights_Preset.sendCommand(holiday) | |
Everlights_Switch.sendCommand(ON) | |
} | |
end | |
// don't forget a rule to turn them off |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Switch Everlights_Switch "Everlights" <light> (gAllOff) | |
String EverlightsZone1_Data | |
String EverlightsZone2_Data | |
Group gEverlights | |
Group gEverlightsExclusive | |
String Everlights_Preset "Pattern" | |
Switch Everlights_PatternChanging | |
Number Everlights_Count "Pattern Length [%s]" (gEverlights) | |
Color Everlights1_RGB "Color 1" (gEverlights) | |
Color Everlights2_RGB "Color 2" (gEverlights) | |
Color Everlights3_RGB "Color 3" (gEverlights) | |
Color Everlights4_RGB "Color 4" (gEverlights) | |
Color Everlights5_RGB "Color 5" (gEverlights) | |
Color Everlights6_RGB "Color 6" (gEverlights) | |
Color Everlights7_RGB "Color 7" (gEverlights) | |
Color Everlights8_RGB "Color 8" (gEverlights) | |
Color Everlights9_RGB "Color 9" (gEverlights) | |
Color Everlights10_RGB "Color 10" (gEverlights) | |
Number Everlights_Twinkle "Twinkle" (gEverlights, gEverlightsExclusive) | |
Number Everlights_Fade "Fade" (gEverlights) | |
Number Everlights_Blink "Blink" (gEverlights) | |
Number Everlights_Strobe "Strobe" (gEverlights) | |
Number Everlights_Chase "Chase" (gEverlights, gEverlightsExclusive) | |
Number Everlights_ChaseReverse "Reverse Chase" (gEverlights, gEverlightsExclusive) | |
Number Everlights_Wipe "Wipe" (gEverlights, gEverlightsExclusive) | |
Number Everlights_Random "Random" (gEverlights, gEverlightsExclusive) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
require "cgi" | |
require "json" | |
require "net/http" | |
gemfile do | |
source "https://rubygems.org/" | |
gem "activesupport", "~> 7.0", require: false | |
end | |
require "active_support/core_ext/enumerable" | |
require "active_support/core_ext/object/blank" | |
require "active_support/core_ext/object/try" | |
EVERLIGHTS_HOST = "everlights.cutrer.network" | |
def normalize_pattern(pattern, reverse: false) | |
modes = { fa: 0, bl: 0, st: 0, cf: 0, cr: 0, wi: 0, ra: 0, tw: 0, rb: 0 }.merge(pattern[:modes] || {}) | |
pattern = pattern[:ptrn] | |
if reverse | |
temp = modes[:cf] | |
modes[:cf] = modes[:cr] | |
modes[:cr] = temp | |
pattern = pattern.reverse | |
end | |
CGI.escape({ len: pattern.length, ptrn: pattern, modes: }.to_json) | |
end | |
MODES = { | |
tw: "Twinkle", | |
fa: "Fade", | |
blink: "Blink", | |
st: "Strobe", | |
cf: "Chase", | |
cr: "ChaseReverse", | |
wi: "Wipe", | |
ra: "Random" | |
}.freeze | |
def calculate_data(reverse: false) | |
pattern = {} | |
modes = pattern[:modes] = {} | |
pattern[:ptrn] = Array.new(Everlights_Count.state.to_i) do |i| | |
item = items["Everlights#{i + 1}_RGB"] | |
next 0xffffff unless item.state? | |
item.state.rgb | |
end | |
MODES.each do |(key, name)| | |
modes[key] = items["Everlights_#{name}"].state.to_i * 255 / 100 | |
end | |
normalize_pattern(pattern, reverse:) | |
end | |
def update_everlights(zone) | |
return if items["EverlightsZone#{zone}_Data"].state.to_s.empty? | |
Net::HTTP.get(EVERLIGHTS_HOST, | |
"/ptrn/set?channel=#{zone}&data=#{items["EverlightsZone#{zone}_Data"].state}") | |
end | |
received_command Everlights_Switch, command: ON do | |
2.times { |zone| update_everlights(zone + 1) } | |
end | |
received_command Everlights_Switch, command: OFF do | |
Net::HTTP.get(EVERLIGHTS_HOST, "/ptrn/clear?channel=3") | |
end | |
2.times do |zone| | |
zone += 1 | |
changed items["EverlightsZone#{zone}_Data"] do |_event| | |
next unless Everlights_Switch.on? | |
update_everlights(zone) | |
end | |
end | |
changed gEverlights.members do | |
next unless Everlights_Count.state? | |
unless Everlights_PatternChanging.on? | |
EverlightsZone1_Data.update(calculate_data) | |
EverlightsZone2_Data.update(calculate_data(reverse: true)) | |
Everlights_Preset.update(NULL) | |
end | |
end | |
changed gEverlightsExclusive.members do |event| | |
gEverlightsExclusive.members.each do |item| | |
item.update(0) unless item == event.item | |
end | |
end | |
PATTERNS = { | |
"Valentine's Day" => { | |
colors: %w[#ff0000 #ffffff #ff0000 #fc00ff], | |
chase: 40 | |
}, | |
"St. Patrick's Day" => { | |
colors: %w[#00ff41 #00ff41 #00ff00], | |
chase: 40 | |
}, | |
"Easter" => { | |
colors: %w[#fd13ff #f9ff20 #ff0141 #5aff12 #2949ff] | |
}, | |
"Patriotic" => { | |
colors: %w[#0000ff #ff0000 #ffffff #fc0000 #0000ff #000000], | |
chase: 40 | |
}, | |
"Halloween" => { | |
colors: %w[#ff2900 #ff2900 #7300ff #004f00] | |
}, | |
"Thanksgiving" => { | |
colors: %w[#ff8721 #ff4b00 #990000 #ff4b00] | |
}, | |
"Christmas" => { | |
colors: %w[#ffd6aa #ff0000 #ffd6aa #00b000] | |
}, | |
"Olympics" => { | |
colors: %w[#0020c8 #ffa000 #303030 #005000 #800000 #000000 #000000 #000000] | |
}, | |
"Rainbow" => { | |
colors: %w[#ff0000 #ff2900 #ffc000 #00ff00 #0000ff #3200ff] | |
} | |
}.freeze | |
STYLES = %i[fade blink strobe].freeze | |
EXCLUSIVE_STYLES = %i[twinkle chase chase_reverse wipe random].freeze | |
rule "Everlights Preset" do | |
changed Everlights_Preset | |
received_command Everlights_Preset | |
run do |event| | |
pattern_name = event.try(:state) || event.try(:command) | |
pattern = PATTERNS[pattern_name.to_s] | |
next unless pattern | |
Everlights_PatternChanging.update(ON) | |
Everlights_Count.update(pattern[:colors].length) | |
pattern[:colors].each_with_index do |color, i| | |
items["Everlights#{i + 1}_RGB"].update(color) | |
STYLES.each do |style| | |
items["Everlights_#{style.capitalize}"].update(pattern[style] || 0) | |
end | |
exclusive_styles = pattern.slice(*EXCLUSIVE_STYLES) | |
exclusive_styles.each do |(style, value)| | |
items["Everlights_#{style.capitalize}"].update(value) | |
end | |
gEverlightsExclusive << 0 if exclusive_styles.empty? | |
end | |
EverlightsZone1_Data.update(calculate_data) | |
EverlightsZone2_Data.update(calculate_data(reverse: true)) | |
2.times { |zone| update_everlights(zone + 1) } if Everlights_Switch.on? | |
Everlights_PatternChanging.update(OFF) | |
end | |
end | |
def save_pattern(_name) | |
pattern = {} | |
pattern[:colors] = Array.new(Everlights_Count.state.to_i) do |i| | |
items["Everlights#{i + 1}_RGB"].state.to_hex | |
end | |
(STYLES + EXCLUSIVE_STYLES).each do |style| | |
item = items["Everlights_#{style.capitalize}"] | |
pattern[style] = item.state.to_i if item.state.to_i != 0 | |
end | |
logger.info("pattern: #{pattern.to_json}") | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Switch item=Everlights_Switch | |
Text label="Everlights Pattern" { | |
Frame label="Presets" { | |
Selection item=Everlights_Preset mappings=[ | |
""="---", | |
"Valentine's Day"="Valentine's Day", | |
"St. Patrick's Day"="St. Patrick's Day", | |
"Easter"="Easter", | |
"Patriotic"="Patriotic", | |
"Halloween"="Halloween", | |
"Thanksgiving"="Thanksgiving", | |
"Christmas"="Christmas"] | |
} | |
Frame label="Colors" { | |
Selection item=Everlights_Count mappings=[1="1", 2="2", 3="3", 4="4", 5="5", 6="6", 7="7", 8="8", 9="9", 10="10"] | |
Colorpicker item=Everlights1_RGB visibility=[Everlights_Count>=1] | |
Colorpicker item=Everlights2_RGB visibility=[Everlights_Count>=2] | |
Colorpicker item=Everlights3_RGB visibility=[Everlights_Count>=3] | |
Colorpicker item=Everlights4_RGB visibility=[Everlights_Count>=4] | |
Colorpicker item=Everlights5_RGB visibility=[Everlights_Count>=5] | |
Colorpicker item=Everlights6_RGB visibility=[Everlights_Count>=6] | |
Colorpicker item=Everlights7_RGB visibility=[Everlights_Count>=7] | |
Colorpicker item=Everlights8_RGB visibility=[Everlights_Count>=8] | |
Colorpicker item=Everlights9_RGB visibility=[Everlights_Count>=9] | |
Colorpicker item=Everlights10_RGB visibility=[Everlights_Count>=10] | |
} | |
Frame label="Mode" { | |
Slider item=Everlights_Twinkle | |
Slider item=Everlights_Fade | |
Slider item=Everlights_Blink | |
Slider item=Everlights_Strobe | |
Slider item=Everlights_Chase | |
Slider item=Everlights_ChaseReverse | |
Slider item=Everlights_Wipe | |
Slider item=Everlights_Random | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'cgi' | |
require 'holidays' | |
require 'json' | |
# keys are either a single value, or an array that's a range of two date_specs | |
# for values, it's the pattern name. if it's nil, match the holiday's name | |
# for date_specs, it can be a month and day, or it can be a holiday name. | |
# you can append + or - a number to the end of the string to do lights "around" | |
# a date | |
SCHEDULE = { | |
"Valentine's Day" => nil, | |
"St. Patrick's Day" => nil, | |
["Easter Sunday-1", "Easter Sunday"] => "Easter", | |
"Memorial Day" => "Patriotic", | |
["Independence Day-3", "Independence Day"] => "Patriotic", | |
["Jul 24-1", "Jul 24"] => "Patriotic", | |
["Halloween-7", "Halloween"] => nil, | |
["Thanksgiving-4", "Thanksgiving"] => nil, | |
["Thanksgiving+1", "Dec 31"] => "Christmas", | |
# can't go over the year boundary | |
"Jan 1" => "Christmas", | |
} | |
HOLIDAYS = Holidays.year_holidays([:us, :informal], Date.parse('Jan 1')).map { |x| [x[:name], x[:date]] }.to_h | |
def normalize_schedule(schedule) | |
schedule.map do |(dates, pattern)| | |
dates = [dates, dates] unless dates.is_a?(Array) | |
pattern ||= dates.last | |
[dates, pattern] | |
end.to_h | |
end | |
def identify_date(date_spec) | |
offset = 0 | |
if date_spec =~ /^(.+)([+-]\d+)$/ | |
offset = $2.to_i | |
date_spec = $1 | |
end | |
date = HOLIDAYS[date_spec] || Date.parse(date_spec) | |
date + offset | |
end | |
def dates_range(date_specs) | |
identify_date(date_specs.first)..identify_date(date_specs.last) | |
end | |
def search_schedule(date) | |
normalize_schedule(SCHEDULE).find { |(date_specs, pattern)| dates_range(date_specs).include?(date) }&.last | |
end | |
date = ARGV[0] ? Date.parse(ARGV[0]) : Date.today | |
puts search_schedule(date) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment