ws_light 0.3.0 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/bin/ws_light +35 -24
- data/lib/ws_light/animation/base_animation.rb +2 -2
- data/lib/ws_light/animation/fade_animation.rb +1 -1
- data/lib/ws_light/animation/slide_right_animation.rb +0 -1
- data/lib/ws_light/benchmark/set_benchmark.rb +3 -1
- data/lib/ws_light/color.rb +17 -16
- data/lib/ws_light/config.rb +79 -0
- data/lib/ws_light/hass.rb +30 -0
- data/lib/ws_light/sd_logger.rb +3 -3
- data/lib/ws_light/set/color_set.rb +1 -1
- data/lib/ws_light/set/flowerbed_set.rb +28 -0
- data/lib/ws_light/set/gradient_set.rb +3 -3
- data/lib/ws_light/set/rainbow_set.rb +4 -5
- data/lib/ws_light/set/semolina_set.rb +5 -6
- data/lib/ws_light/set/star_set.rb +8 -15
- data/lib/ws_light/set/strawberry_set.rb +2 -3
- data/lib/ws_light/set/watermelon_set.rb +1 -1
- data/lib/ws_light/set/weather/cloudy_set.rb +24 -0
- data/lib/ws_light/set/weather/fair_set.rb +24 -0
- data/lib/ws_light/set/weather/rain_set.rb +53 -0
- data/lib/ws_light/set/weather/sunny_set.rb +42 -0
- data/lib/ws_light/strip.rb +57 -41
- data/lib/ws_light/version.rb +2 -2
- data/ws_light.gemspec +1 -2
- data/ws_light.service +9 -0
- metadata +14 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7c88abb859296341e8d84b4f1bb31bbf5932abf0daed9439e2d22640de25fab0
|
4
|
+
data.tar.gz: ba45253224b6b2e523e774255a8f56ab6aaf1d892ed4d805859e22a295961e05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bccde7663cde60bcc102898e005e077e93b8507fe3b6b9ab247b055273dfa8c81c102a8a9f1b15aa1838326a6d7183417f4f7af487e2907d662fef18a4252001
|
7
|
+
data.tar.gz: 3efedea3cffa086835d1cbe30a01ced9505c94549fe107dc77abdcd9b8d3148218b2be0cf8228a184b32f169bd1cc63716a0b88905ca77ddb723c37c1f68981d
|
data/bin/ws_light
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
if ARGV[0] && ARGV[0]=='-b'
|
3
|
+
if ARGV[0] && ARGV[0] == '-b'
|
6
4
|
case ARGV[1]
|
7
5
|
when 'set'
|
8
6
|
require 'ws_light/benchmark/set_benchmark'
|
@@ -20,52 +18,65 @@ require 'pi_piper'
|
|
20
18
|
|
21
19
|
include PiPiper
|
22
20
|
|
21
|
+
require 'socket'
|
23
22
|
require 'bundler'
|
24
23
|
require 'ws_light/strip'
|
25
24
|
require 'ws_light/sd_logger'
|
25
|
+
require 'ws_light/hass'
|
26
|
+
|
27
|
+
require 'ws_light/config'
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
+
TELEGRAF_IP = '127.0.0.1'.freeze
|
30
|
+
TELEGRAF_PORT = 8094
|
31
|
+
|
32
|
+
# Load config to overwrite default values
|
33
|
+
config = WSLight::Config.new.parse
|
29
34
|
|
30
35
|
logger = WSLight::SDLogger.new
|
31
|
-
logger.debug =
|
32
|
-
logger.filename = '
|
36
|
+
logger.debug = false
|
37
|
+
logger.filename = config['log_file']
|
33
38
|
logger.log 'Starting up'
|
34
39
|
|
35
|
-
def unregister_pins
|
36
|
-
|
37
|
-
File.open('/sys/class/gpio/unexport', 'w') { |f| f.write(
|
40
|
+
def unregister_pins(pins)
|
41
|
+
pins.each do |pin|
|
42
|
+
File.open('/sys/class/gpio/unexport', 'w') { |f| f.write(pin.to_s) }
|
38
43
|
end
|
39
44
|
end
|
40
45
|
|
41
|
-
# Trap ^C
|
42
|
-
Signal.trap('INT')
|
46
|
+
# Trap ^C
|
47
|
+
Signal.trap('INT') do
|
43
48
|
logger.log 'Shutting down'
|
44
49
|
logger.write_log
|
45
|
-
unregister_pins
|
50
|
+
unregister_pins([config['pin_right'], config['pin_left']])
|
46
51
|
exit
|
47
|
-
|
48
|
-
|
52
|
+
end
|
53
|
+
|
49
54
|
# Trap `kill `
|
50
|
-
Signal.trap('TERM')
|
55
|
+
Signal.trap('TERM') do
|
51
56
|
logger.log 'Shutting down'
|
52
57
|
logger.write_log
|
53
|
-
unregister_pins
|
58
|
+
unregister_pins([config['pin_right'], config['pin_left']])
|
54
59
|
exit
|
55
|
-
|
60
|
+
end
|
56
61
|
|
57
62
|
strip = WSLight::Strip.new
|
58
|
-
strip.debug =
|
63
|
+
strip.debug = config['debug']
|
64
|
+
hass = Hass.new(config['hass_url'], config['hass_api_password'], logger)
|
65
|
+
|
66
|
+
socket = UDPSocket.new
|
59
67
|
|
60
|
-
after :
|
61
|
-
logger.log('Motion detected: RIGHT')
|
68
|
+
after pin: config['pin_right'], goes: :high do
|
69
|
+
logger.log('Motion detected: RIGHT') if config['track_motion_in_log']
|
70
|
+
socket.send 'motion,room=hallway,sensor=right value=1', 0, TELEGRAF_IP, TELEGRAF_PORT
|
62
71
|
strip.on(:direction_right)
|
72
|
+
hass.notify(true, config['sensor_right_name'], config['sensor_right_description']) if config['hass_integration']
|
63
73
|
end
|
64
74
|
|
65
|
-
after :
|
66
|
-
logger.log('Motion detected: LEFT')
|
75
|
+
after pin: config['pin_left'], goes: :high do
|
76
|
+
logger.log('Motion detected: LEFT') if config['track_motion_in_log']
|
77
|
+
socket.send 'motion,room=hallway,sensor=left value=1', 0, TELEGRAF_IP, TELEGRAF_PORT
|
67
78
|
strip.on(:direction_left)
|
79
|
+
hass.notify(true, config['sensor_left_name'], config['sensor_left_description']) if config['hass_integration']
|
68
80
|
end
|
69
81
|
|
70
82
|
PiPiper.wait
|
71
|
-
|
@@ -17,7 +17,7 @@ module WSLight
|
|
17
17
|
@set_to.next_frame
|
18
18
|
|
19
19
|
@set_from.full_length.times do |i|
|
20
|
-
set << @set_from.pixel(i).mix(@set_to.pixel(i), count.to_f/FADE_DURATION.to_f)
|
20
|
+
set << @set_from.pixel(i).mix(@set_to.pixel(i), count.to_f / FADE_DURATION.to_f)
|
21
21
|
end
|
22
22
|
|
23
23
|
set
|
@@ -4,7 +4,7 @@ require 'ws_light/set/semolina_set'
|
|
4
4
|
require 'ws_light/set/random_set'
|
5
5
|
require 'ws_light/set/strawberry_set'
|
6
6
|
require 'ws_light/set/watermelon_set'
|
7
|
-
|
7
|
+
require 'ws_light/set/star_set'
|
8
8
|
|
9
9
|
require 'benchmark'
|
10
10
|
include Benchmark
|
@@ -15,6 +15,7 @@ include Benchmark
|
|
15
15
|
@random_set = WSLight::Set::RandomSet.new
|
16
16
|
@strawberry_set = WSLight::Set::StrawberrySet.new
|
17
17
|
@watermelon_set = WSLight::Set::WatermelonSet.new
|
18
|
+
@star_set = WSLight::Set::StarSet.new
|
18
19
|
|
19
20
|
@gradient_set.color_from = WSLight::Color.random_from_set
|
20
21
|
@gradient_set.color_to = WSLight::Color.random_from_set
|
@@ -30,6 +31,7 @@ Benchmark.bm(15) do |x|
|
|
30
31
|
x.report('RandomSet:') { n.times do @random_set.frame end }
|
31
32
|
x.report('StrawberrySet:') { n.times do @strawberry_set.frame end }
|
32
33
|
x.report('WatermelonSet:') { n.times do @watermelon_set.frame end }
|
34
|
+
x.report('StarSet:') { n.times do @star_set.frame end }
|
33
35
|
end
|
34
36
|
|
35
37
|
puts "To run smoothly, the benchmarks should all be lower than #{n/50.0} seconds."
|
data/lib/ws_light/color.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
module WSLight
|
2
|
+
# Handles the red/green/blue value of a color
|
2
3
|
class Color
|
3
4
|
attr_accessor :r, :g, :b
|
4
5
|
|
5
6
|
COLORS = {
|
6
|
-
pink: { r: 255, g: 16, b:32 },
|
7
|
-
red: { r: 255, g: 0, b: 0},
|
8
|
-
blue: { r: 0, g: 0, b: 255},
|
9
|
-
green: { r: 0, g: 255, b: 0},
|
10
|
-
cyan: { r: 0, g: 127, b: 127},
|
11
|
-
orange: {r: 255, g:70, b: 0},
|
12
|
-
yellow: {r: 255, g:220, b: 0},
|
13
|
-
purple: {r: 80, g:0, b: 180}
|
14
|
-
}
|
7
|
+
pink: { r: 255, g: 16, b: 32 },
|
8
|
+
red: { r: 255, g: 0, b: 0 },
|
9
|
+
blue: { r: 0, g: 0, b: 255 },
|
10
|
+
green: { r: 0, g: 255, b: 0 },
|
11
|
+
cyan: { r: 0, g: 127, b: 127 },
|
12
|
+
orange: { r: 255, g: 70, b: 0 },
|
13
|
+
yellow: { r: 255, g: 220, b: 0 },
|
14
|
+
purple: { r: 80, g: 0, b: 180 }
|
15
|
+
}.freeze
|
15
16
|
|
16
17
|
def initialize(r=0, g=0, b=0)
|
17
18
|
@r = r > 255 ? 255 : r.to_i
|
@@ -28,16 +29,16 @@ module WSLight
|
|
28
29
|
|
29
30
|
def mix(other, ratio)
|
30
31
|
Color.new(
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
(@r * (1 - ratio) + other.r * ratio).to_i,
|
33
|
+
(@g * (1 - ratio) + other.g * ratio).to_i,
|
34
|
+
(@b * (1 - ratio) + other.b * ratio).to_i
|
34
35
|
)
|
35
36
|
end
|
36
37
|
|
37
38
|
def mix!(other, ratio)
|
38
|
-
@r = (@r * (1-ratio) + other.r * ratio).to_i
|
39
|
-
@g = (@g * (1-ratio) + other.g * ratio).to_i
|
40
|
-
@b = (@b * (1-ratio) + other.b * ratio).to_i
|
39
|
+
@r = (@r * (1 - ratio) + other.r * ratio).to_i
|
40
|
+
@g = (@g * (1 - ratio) + other.g * ratio).to_i
|
41
|
+
@b = (@b * (1 - ratio) + other.b * ratio).to_i
|
41
42
|
self
|
42
43
|
end
|
43
44
|
|
@@ -52,7 +53,7 @@ module WSLight
|
|
52
53
|
elsif name == :black
|
53
54
|
Color.new(0, 0, 0)
|
54
55
|
else
|
55
|
-
|
56
|
+
raise "Cannot find color #{name}"
|
56
57
|
end
|
57
58
|
end
|
58
59
|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module WSLight
|
5
|
+
# Reads config file and parses command parameters
|
6
|
+
class Config
|
7
|
+
CONFIG_FILE = '/etc/ws_light.conf'.freeze
|
8
|
+
|
9
|
+
DEFAULT_OPTIONS = {
|
10
|
+
'pin_right' => 23,
|
11
|
+
'pin_left' => 24,
|
12
|
+
'log_file' => '/var/log/motion.log',
|
13
|
+
'track_motion_in_log' => true,
|
14
|
+
'debug' => false,
|
15
|
+
'sensor_right_name' => 'motion_right',
|
16
|
+
'sensor_left_name' => 'motion_left',
|
17
|
+
'sensor_right_description' => 'Motion sensor right',
|
18
|
+
'sensor_left_description' => 'Motion sensor left',
|
19
|
+
'hass_integration' => false,
|
20
|
+
'hass_url' => '',
|
21
|
+
'hass_api_password' => ''
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@config = DEFAULT_OPTIONS.merge(yaml_options).merge(command_line_options)
|
26
|
+
store_options
|
27
|
+
end
|
28
|
+
|
29
|
+
def store_options
|
30
|
+
File.open(CONFIG_FILE, 'w') do |file|
|
31
|
+
file.puts @config.to_yaml
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse
|
36
|
+
@config
|
37
|
+
end
|
38
|
+
|
39
|
+
def yaml_options
|
40
|
+
if File.exist? CONFIG_FILE
|
41
|
+
::YAML.safe_load(File.read(CONFIG_FILE))
|
42
|
+
else
|
43
|
+
{}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def command_line_options
|
48
|
+
options = {}
|
49
|
+
OptionParser.new do |opts|
|
50
|
+
opts.banner = 'Usage: ws_light [options]'
|
51
|
+
|
52
|
+
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
|
53
|
+
options['verbose'] = v
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on('-l NUMBER', '--left-pin NUMBER', 'Pin to which the left motion detector is connected') do |number|
|
57
|
+
options['pin_left'] = number
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on('-r NUMBER', '--right-pin NUMBER', 'Pin to which the right motion detector is connected') do |number|
|
61
|
+
options['pin_right'] = number
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on('-o PATH', '--log PATH', 'path to the log file') do |log_file|
|
65
|
+
options['log_file'] = log_file
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on('--quiet-log', 'do not log detected motions') do
|
69
|
+
options['track_motion_in_log'] = false
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on('--debug', 'output all log messages to stdout, too') do
|
73
|
+
options['debug'] = true
|
74
|
+
end
|
75
|
+
end.parse!
|
76
|
+
options
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# Home Assistant notifier
|
5
|
+
class Hass
|
6
|
+
def initialize(url, password, logger)
|
7
|
+
@url = url
|
8
|
+
@password = password
|
9
|
+
@logger = logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def notify(state = true, sensor_name = 'motion', friendly_name = 'Motion Sensor')
|
13
|
+
url = "#{@url}/api/states/sensor.#{sensor_name}"
|
14
|
+
data = { 'state' => (state ? 'on' : 'off'), 'attributes' => { 'friendly_name' => friendly_name } }.to_json
|
15
|
+
Thread.new { send_data(url, data) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def send_data(url, data)
|
19
|
+
uri = URI(url)
|
20
|
+
puts "Starting request to #{url} with data #{data}"
|
21
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
22
|
+
request = Net::HTTP::Post.new(uri.path)
|
23
|
+
request['Authorization'] = "Bearer #{@password}"
|
24
|
+
request['Accept-Encoding'] = 'deflate'
|
25
|
+
request.body = data
|
26
|
+
https.use_ssl = true if uri.scheme == 'https'
|
27
|
+
response = https.request request
|
28
|
+
@logger.log("Request to Home Assistant failed (#{response.code}): #{response.body}") # if response.code > 299
|
29
|
+
end
|
30
|
+
end
|
data/lib/ws_light/sd_logger.rb
CHANGED
@@ -2,7 +2,7 @@ module WSLight
|
|
2
2
|
# Provides a logger which writes only in long intervals, thus reducing write access to the cd card
|
3
3
|
# (out data is not that crucial)
|
4
4
|
class SDLogger
|
5
|
-
attr_accessor :entries, :interval, :
|
5
|
+
attr_accessor :entries, :interval, :debug, :filename
|
6
6
|
|
7
7
|
def initialize
|
8
8
|
@filename = '/var/log/motion.log'
|
@@ -23,7 +23,8 @@ module WSLight
|
|
23
23
|
|
24
24
|
def write_log
|
25
25
|
return if @entries.empty?
|
26
|
-
|
26
|
+
|
27
|
+
file = File.open(@filename, File.exist?(@filename) ? 'a' : 'w')
|
27
28
|
@entries.each do |entry|
|
28
29
|
file.puts(entry[:time].to_s + ', ' + entry[:text])
|
29
30
|
end
|
@@ -35,6 +36,5 @@ module WSLight
|
|
35
36
|
def timeout?
|
36
37
|
(Time.now - @last_write) > @interval
|
37
38
|
end
|
38
|
-
|
39
39
|
end
|
40
40
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'ws_light/set/color_set'
|
2
|
+
|
3
|
+
module WSLight
|
4
|
+
module Set
|
5
|
+
# Creates a green set with some random colors
|
6
|
+
class FlowerbedSet < ColorSet
|
7
|
+
def frame
|
8
|
+
@set ||= generate_set
|
9
|
+
end
|
10
|
+
|
11
|
+
def pixel(number, _frame = 0)
|
12
|
+
frame[number]
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_set
|
16
|
+
set = []
|
17
|
+
@full_length.times do
|
18
|
+
set << if rand(8).zero?
|
19
|
+
Color.random_from_set
|
20
|
+
else
|
21
|
+
Color.by_name(:green)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
set
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -7,8 +7,8 @@ module WSLight
|
|
7
7
|
attr_accessor :color_from, :color_to
|
8
8
|
|
9
9
|
def init
|
10
|
-
@color_from = Color.new(0,0,0)
|
11
|
-
@color_to = Color.new(255,255,255)
|
10
|
+
@color_from = Color.new(0, 0, 0)
|
11
|
+
@color_to = Color.new(255, 255, 255)
|
12
12
|
end
|
13
13
|
|
14
14
|
def frame
|
@@ -28,7 +28,7 @@ module WSLight
|
|
28
28
|
|
29
29
|
def pixel(number)
|
30
30
|
number = @full_length - 1 - number if number >= @length
|
31
|
-
@color_from.mix(@color_to, number.to_f/(@length-1))
|
31
|
+
@color_from.mix(@color_to, number.to_f / (@length - 1))
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -27,14 +27,13 @@ module WSLight
|
|
27
27
|
|
28
28
|
def pixel(number)
|
29
29
|
number = @full_length - 1 - number if number >= @length
|
30
|
-
x = @frequency*(number
|
30
|
+
x = @frequency * (number + @frame_count)
|
31
31
|
Color.new(
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
(Math.sin(x)**2 * 127),
|
33
|
+
(Math.sin(x + 2.0 * Math::PI / 3.0)**2 * 127),
|
34
|
+
(Math.sin(x + 4.0 * Math::PI / 3.0)**2 * 127)
|
35
35
|
)
|
36
36
|
end
|
37
|
-
|
38
37
|
end
|
39
38
|
end
|
40
39
|
end
|
@@ -12,15 +12,14 @@ module WSLight
|
|
12
12
|
def init
|
13
13
|
@raspberries = []
|
14
14
|
|
15
|
-
while @raspberries.size < RASPBERRY_COUNT
|
15
|
+
while @raspberries.size < RASPBERRY_COUNT
|
16
16
|
position = rand(@full_length)
|
17
|
-
@raspberries << position unless
|
17
|
+
@raspberries << position unless at_end?(position) || between_strips?(position) || raspberry?(position)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
def between_strips?(position)
|
22
|
-
|
23
|
-
@type == :double && ((@full_length/2 - 1 - RASPBERRY_SIZE)..(@full_length/2)).include?(position)
|
22
|
+
@type == :double && ((@full_length / 2 - 1 - RASPBERRY_SIZE)..(@full_length / 2)).cover?(position)
|
24
23
|
end
|
25
24
|
|
26
25
|
def at_end?(position)
|
@@ -35,13 +34,13 @@ module WSLight
|
|
35
34
|
set = []
|
36
35
|
|
37
36
|
@full_length.times do |i|
|
38
|
-
set << (
|
37
|
+
set << (raspberry?(i) ? COLOR_RASPBERRY : COLOR_SEMOLINA)
|
39
38
|
end
|
40
39
|
|
41
40
|
set
|
42
41
|
end
|
43
42
|
|
44
|
-
def
|
43
|
+
def raspberry?(pixel)
|
45
44
|
@raspberries.each do |raspberry|
|
46
45
|
return true if pixel > raspberry && pixel < (raspberry + RASPBERRY_SIZE)
|
47
46
|
end
|
@@ -6,10 +6,10 @@ module WSLight
|
|
6
6
|
class StarSet < ColorSet
|
7
7
|
FRAMES_PER_STAR = 75
|
8
8
|
VISIBLE_STARS = 7
|
9
|
-
BLACK = Color.new(0,0,0)
|
9
|
+
BLACK = Color.new(0, 0, 0)
|
10
10
|
|
11
11
|
def init
|
12
|
-
@stars = (0..(@full_length-4)).to_a.shuffle
|
12
|
+
@stars = (0..(@full_length - 4)).to_a.shuffle
|
13
13
|
@max = @stars.size
|
14
14
|
@stars += @stars[@stars.size - VISIBLE_STARS, VISIBLE_STARS]
|
15
15
|
generate_frame
|
@@ -28,21 +28,16 @@ module WSLight
|
|
28
28
|
|
29
29
|
def generate_frame
|
30
30
|
@set = []
|
31
|
-
@full_length.times{ @set << BLACK }
|
31
|
+
@full_length.times { @set << BLACK }
|
32
32
|
start = (@frame_count * VISIBLE_STARS / FRAMES_PER_STAR) % @max
|
33
33
|
VISIBLE_STARS.times do |i|
|
34
|
-
|
34
|
+
star_ratio = FRAMES_PER_STAR.to_f / VISIBLE_STARS.to_f
|
35
|
+
draw_star(@stars[start + i], @frame_count - ((start - VISIBLE_STARS + 1 + i) * star_ratio).to_i)
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
38
39
|
def draw_star(position, star_frame)
|
39
|
-
|
40
|
-
# white = brightness(i.abs, (star_frame - (FRAMES_PER_STAR/2)).abs)
|
41
|
-
# @set[position + i] = Color.new(white, white, white)
|
42
|
-
#end
|
43
|
-
#return if star_frame < 0
|
44
|
-
white = brightness(0, (star_frame.to_f - (FRAMES_PER_STAR.to_f/2.0)).abs)
|
45
|
-
#puts "#{position}, #{star_frame}, #{(star_frame.to_f - (FRAMES_PER_STAR.to_f/2.0)).abs}, #{white}"
|
40
|
+
white = brightness((star_frame.to_f - (FRAMES_PER_STAR.to_f / 2.0)).abs)
|
46
41
|
@set[position] = Color.new(white, white, white)
|
47
42
|
end
|
48
43
|
|
@@ -50,10 +45,8 @@ module WSLight
|
|
50
45
|
@set[number]
|
51
46
|
end
|
52
47
|
|
53
|
-
def brightness(
|
54
|
-
|
55
|
-
#(2 ** ((3 - led_distance) * 3 - 1) - 1) * (1 - (frame_distance/(FRAMES_PER_STAR/2)))
|
56
|
-
(255 * (1.0 - (frame_distance.to_f/(FRAMES_PER_STAR.to_f/2.0)))).to_i
|
48
|
+
def brightness(frame_distance)
|
49
|
+
(255 * (1.0 - (frame_distance.to_f / (FRAMES_PER_STAR.to_f / 2.0)))).to_i
|
57
50
|
end
|
58
51
|
end
|
59
52
|
end
|
@@ -16,7 +16,6 @@ module WSLight
|
|
16
16
|
|
17
17
|
length_red = (LENGTH_RED * @length).to_i
|
18
18
|
|
19
|
-
|
20
19
|
color_strawberry = Color.new(255, 7, 15)
|
21
20
|
color_leaves = Color.new(15, 191, 15)
|
22
21
|
|
@@ -26,7 +25,7 @@ module WSLight
|
|
26
25
|
|
27
26
|
set = sprinkle_nuts(set)
|
28
27
|
|
29
|
-
set.reverse! if rand(2)
|
28
|
+
set.reverse! if rand(2).zero?
|
30
29
|
|
31
30
|
type == :double ? set + set.reverse : set
|
32
31
|
end
|
@@ -35,7 +34,7 @@ module WSLight
|
|
35
34
|
length_red = (LENGTH_RED * @length).to_i
|
36
35
|
distance = 0
|
37
36
|
while distance < length_red - 21
|
38
|
-
distance +=
|
37
|
+
distance += rand(5..20)
|
39
38
|
set[distance] = COLOR_NUT
|
40
39
|
end
|
41
40
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'ws_light/set/color_set'
|
2
|
+
|
3
|
+
module WSLight
|
4
|
+
module Set
|
5
|
+
# Creates a set with all random colors
|
6
|
+
class CloudySet < ColorSet
|
7
|
+
def frame
|
8
|
+
@set ||= generate_set
|
9
|
+
end
|
10
|
+
|
11
|
+
def pixel(number, _frame = 0)
|
12
|
+
frame[number]
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_set
|
16
|
+
set = []
|
17
|
+
@full_length.times do
|
18
|
+
set << Color.new(8, 8, 8)
|
19
|
+
end
|
20
|
+
set
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'ws_light/set/color_set'
|
2
|
+
|
3
|
+
module WSLight
|
4
|
+
module Set
|
5
|
+
# Creates a set with all random colors
|
6
|
+
class FairSet < ColorSet
|
7
|
+
def frame
|
8
|
+
@set ||= generate_set
|
9
|
+
end
|
10
|
+
|
11
|
+
def pixel(number, _frame = 0)
|
12
|
+
frame[number]
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_set
|
16
|
+
set = []
|
17
|
+
@full_length.times do
|
18
|
+
set << Color.new(8, 8, 8)
|
19
|
+
end
|
20
|
+
set
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'ws_light/set/color_set'
|
2
|
+
|
3
|
+
module WSLight
|
4
|
+
module Set
|
5
|
+
# Creates a set with all random colors
|
6
|
+
class RainSet < ColorSet
|
7
|
+
FRAMES_PER_DROP = 20
|
8
|
+
VISIBLE_DROPS = 35
|
9
|
+
CLOUD = Color.new(4, 4, 4)
|
10
|
+
|
11
|
+
def init
|
12
|
+
@drops = (0..(@full_length - 1)).to_a.shuffle
|
13
|
+
@max = @drops.size
|
14
|
+
# append the start at the end to ensure same result window when near the % @max
|
15
|
+
@drops += @drops[0, VISIBLE_DROPS]
|
16
|
+
generate_frame
|
17
|
+
end
|
18
|
+
|
19
|
+
def next_frame
|
20
|
+
@frame_count += 1
|
21
|
+
@frame_count = @frame_count % (@max * FRAMES_PER_DROP)
|
22
|
+
generate_frame
|
23
|
+
end
|
24
|
+
|
25
|
+
def frame
|
26
|
+
next_frame
|
27
|
+
@set
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate_frame
|
31
|
+
@set = [CLOUD] * @full_length
|
32
|
+
start = (@frame_count * VISIBLE_DROPS / FRAMES_PER_DROP) % @max
|
33
|
+
VISIBLE_DROPS.times do |i|
|
34
|
+
drop_ratio = (FRAMES_PER_DROP.to_f / VISIBLE_DROPS.to_f)
|
35
|
+
draw_drop(@drops[start + i], @frame_count - ((start - VISIBLE_DROPS + 1 + i) * drop_ratio).to_i)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def draw_drop(position, drop_frame)
|
40
|
+
blue = brightness(drop_frame.to_f) % 250
|
41
|
+
@set[position] = Color.new(4, 4, 4 + blue)
|
42
|
+
end
|
43
|
+
|
44
|
+
def pixel(number)
|
45
|
+
@set[number]
|
46
|
+
end
|
47
|
+
|
48
|
+
def brightness(frame_distance)
|
49
|
+
(250 * (1.0 - (frame_distance.to_f / FRAMES_PER_DROP.to_f))).to_i
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'ws_light/set/color_set'
|
2
|
+
|
3
|
+
module WSLight
|
4
|
+
module Set
|
5
|
+
# Creates a set with all random colors
|
6
|
+
class SunnySet < ColorSet
|
7
|
+
def frame
|
8
|
+
@set ||= generate_set
|
9
|
+
end
|
10
|
+
|
11
|
+
def pixel(number, _frame = 0)
|
12
|
+
frame[number]
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_set
|
16
|
+
set = []
|
17
|
+
position = sun_position
|
18
|
+
@full_length.times do |i|
|
19
|
+
set << if position.include?(i)
|
20
|
+
Color.by_name(:yellow)
|
21
|
+
else
|
22
|
+
Color.by_name(:blue)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
set
|
26
|
+
end
|
27
|
+
|
28
|
+
def sun_position
|
29
|
+
case rand(4)
|
30
|
+
when 0
|
31
|
+
10..40
|
32
|
+
when 1
|
33
|
+
140..170
|
34
|
+
when 2
|
35
|
+
190..220
|
36
|
+
else
|
37
|
+
300..330
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/ws_light/strip.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'spi'
|
2
2
|
require 'ws_light/color'
|
3
3
|
require 'pp'
|
4
4
|
require 'date'
|
@@ -11,6 +11,7 @@ require 'ws_light/animation/fade_animation'
|
|
11
11
|
|
12
12
|
require 'ws_light/set/color_set'
|
13
13
|
require 'ws_light/set/gradient_set'
|
14
|
+
require 'ws_light/set/flowerbed_set'
|
14
15
|
require 'ws_light/set/random_set'
|
15
16
|
require 'ws_light/set/rainbow_set'
|
16
17
|
require 'ws_light/set/strawberry_set'
|
@@ -18,7 +19,10 @@ require 'ws_light/set/watermelon_set'
|
|
18
19
|
require 'ws_light/set/semolina_set'
|
19
20
|
require 'ws_light/set/star_set'
|
20
21
|
|
21
|
-
|
22
|
+
require 'ws_light/set/weather/cloudy_set'
|
23
|
+
require 'ws_light/set/weather/fair_set'
|
24
|
+
require 'ws_light/set/weather/rain_set'
|
25
|
+
require 'ws_light/set/weather/sunny_set'
|
22
26
|
|
23
27
|
# Ideas
|
24
28
|
# - Fire?
|
@@ -31,23 +35,40 @@ module WSLight
|
|
31
35
|
|
32
36
|
LENGTH = 160
|
33
37
|
TYPE = :double
|
38
|
+
FULL_LENGTH = 320
|
39
|
+
|
40
|
+
SPECIAL_SETS = [
|
41
|
+
Set::RainbowSet,
|
42
|
+
Set::RandomSet,
|
43
|
+
Set::StrawberrySet,
|
44
|
+
Set::WatermelonSet,
|
45
|
+
Set::SemolinaSet,
|
46
|
+
Set::FlowerbedSet
|
47
|
+
].freeze
|
48
|
+
|
49
|
+
# SPECIAL_SETS = [
|
50
|
+
# Set::RainSet,
|
51
|
+
# Set::FairSet,
|
52
|
+
# Set::SunnySet,
|
53
|
+
# Set::CloudySet
|
54
|
+
# ].freeze
|
34
55
|
|
35
56
|
TIMEOUT = 12
|
36
|
-
|
37
|
-
WEATHER_URL = 'http://api.openweathermap.org/data/2.5/weather?q=Hannover,de'
|
57
|
+
|
58
|
+
WEATHER_URL = 'http://api.openweathermap.org/data/2.5/weather?q=Hannover,de'.freeze
|
38
59
|
|
39
60
|
FRAMES_PER_SECOND = 25
|
40
61
|
|
41
62
|
def initialize
|
42
|
-
|
43
|
-
|
63
|
+
@spi = SPI.new(device: '/dev/spidev0.0')
|
64
|
+
@spi.speed = 500_000
|
44
65
|
self_test
|
45
|
-
@listen_thread = Thread.new {
|
66
|
+
@listen_thread = Thread.new { loop { check_timer; sleep 0.5; } }
|
46
67
|
@last_event = Time.now - 3600 # set last event to a longer time ago
|
47
68
|
@state = :state_off
|
48
69
|
@debug = false
|
49
70
|
@current_set = Set::ColorSet.new
|
50
|
-
@current_set.color = Color.new(0,0,0)
|
71
|
+
@current_set.color = Color.new(0, 0, 0)
|
51
72
|
end
|
52
73
|
|
53
74
|
def on(direction)
|
@@ -61,24 +82,7 @@ module WSLight
|
|
61
82
|
@direction = direction
|
62
83
|
@state = :state_starting_up
|
63
84
|
|
64
|
-
|
65
|
-
when 0..3
|
66
|
-
set = Set::RainbowSet.new
|
67
|
-
when 4..6
|
68
|
-
set = Set::RandomSet.new
|
69
|
-
when 7..9
|
70
|
-
set = Set::StrawberrySet.new
|
71
|
-
when 10..12
|
72
|
-
set = Set::WatermelonSet.new
|
73
|
-
when 13..15
|
74
|
-
set = Set::SemolinaSet.new
|
75
|
-
else
|
76
|
-
set = Set::GradientSet.new
|
77
|
-
set.color_from = Color.random_from_set
|
78
|
-
set.color_to = Color.random_from_set
|
79
|
-
end
|
80
|
-
|
81
|
-
set = Set::StarSet.new if night?
|
85
|
+
set = choose_set
|
82
86
|
|
83
87
|
puts "Set #{set.class}" if @debug
|
84
88
|
|
@@ -117,6 +121,17 @@ module WSLight
|
|
117
121
|
puts "finished shutting off: #{Time.now.to_f}" if @debug
|
118
122
|
end
|
119
123
|
|
124
|
+
def choose_set
|
125
|
+
return Set::StarSet.new if night?
|
126
|
+
|
127
|
+
return SPECIAL_SETS.sample.new if rand(8).zero?
|
128
|
+
|
129
|
+
set = Set::GradientSet.new
|
130
|
+
set.color_from = Color.random_from_set
|
131
|
+
set.color_to = Color.random_from_set
|
132
|
+
set
|
133
|
+
end
|
134
|
+
|
120
135
|
def animation_for(direction)
|
121
136
|
return Animation::FadeAnimation if night?
|
122
137
|
|
@@ -132,18 +147,16 @@ module WSLight
|
|
132
147
|
beginning_state = @state
|
133
148
|
|
134
149
|
animation.frames.times do |i|
|
135
|
-
|
136
|
-
|
137
|
-
sleep (1.0/animation.frames_per_second) if animation.frames_per_second
|
150
|
+
write(animation.frame_data(current_frame = i))
|
151
|
+
sleep(1.0 / animation.frames_per_second) if animation.frames_per_second
|
138
152
|
break if @state != beginning_state # Reverse shutting off when a new event is triggered
|
139
153
|
end
|
140
154
|
|
141
155
|
# This is run when the animation is reversed
|
142
156
|
if (current_frame + 1) < animation.frames
|
143
157
|
current_frame.times do |i|
|
144
|
-
|
145
|
-
|
146
|
-
sleep (1.0/animation.frames_per_second) if animation.frames_per_second
|
158
|
+
write(animation.frame_data(current_frame - i - 1))
|
159
|
+
sleep(1.0 / animation.frames_per_second) if animation.frames_per_second
|
147
160
|
end
|
148
161
|
false
|
149
162
|
else
|
@@ -155,9 +168,8 @@ module WSLight
|
|
155
168
|
current_state = @state
|
156
169
|
i = start_frame
|
157
170
|
while @state == current_state
|
158
|
-
|
159
|
-
|
160
|
-
sleep 1.0/FRAMES_PER_SECOND.to_f
|
171
|
+
write(set.frame_data)
|
172
|
+
sleep 1.0 / FRAMES_PER_SECOND.to_f
|
161
173
|
i += 1
|
162
174
|
end
|
163
175
|
end
|
@@ -168,26 +180,30 @@ module WSLight
|
|
168
180
|
end
|
169
181
|
|
170
182
|
def shutdown
|
171
|
-
|
183
|
+
write([0, 0, 0] * FULL_LENGTH)
|
172
184
|
end
|
173
185
|
|
174
186
|
def self_test
|
175
|
-
|
187
|
+
write([0, 0, 255] * FULL_LENGTH)
|
176
188
|
sleep 1
|
177
|
-
|
189
|
+
write([0, 255, 0] * FULL_LENGTH)
|
178
190
|
sleep 1
|
179
|
-
|
191
|
+
write([255, 0, 0] * FULL_LENGTH)
|
180
192
|
sleep 1
|
181
|
-
|
193
|
+
write([0, 0, 0] * FULL_LENGTH)
|
182
194
|
end
|
183
195
|
|
184
196
|
def check_timer
|
185
|
-
|
197
|
+
write([0, 0, 0] * FULL_LENGTH) if @state == :state_off
|
186
198
|
off if timeout?
|
187
199
|
end
|
188
200
|
|
189
201
|
def timeout?
|
190
202
|
@last_event < (Time.now - TIMEOUT)
|
191
203
|
end
|
204
|
+
|
205
|
+
def write(data)
|
206
|
+
@spi.xfer(txdata: data)
|
207
|
+
end
|
192
208
|
end
|
193
209
|
end
|
data/lib/ws_light/version.rb
CHANGED
data/ws_light.gemspec
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
lib = File.expand_path('../lib', __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'ws_light/version'
|
@@ -21,5 +20,5 @@ Gem::Specification.new do |spec|
|
|
21
20
|
spec.add_development_dependency 'bundler', '~> 1.7'
|
22
21
|
|
23
22
|
spec.add_runtime_dependency 'pi_piper', '~>1'
|
24
|
-
spec.add_runtime_dependency '
|
23
|
+
spec.add_runtime_dependency 'spi', '~>0.1'
|
25
24
|
end
|
data/ws_light.service
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ws_light
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerrit Visscher
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -39,19 +39,19 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: spi
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1'
|
47
|
+
version: '0.1'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1'
|
54
|
+
version: '0.1'
|
55
55
|
description: Controls one or two WS2801 led strips with a Raspberry Pi or another
|
56
56
|
computer with an SPI interface.
|
57
57
|
email:
|
@@ -75,8 +75,11 @@ files:
|
|
75
75
|
- lib/ws_light/benchmark/set_benchmark.rb
|
76
76
|
- lib/ws_light/benchmark/ws2801_benchmark.rb
|
77
77
|
- lib/ws_light/color.rb
|
78
|
+
- lib/ws_light/config.rb
|
79
|
+
- lib/ws_light/hass.rb
|
78
80
|
- lib/ws_light/sd_logger.rb
|
79
81
|
- lib/ws_light/set/color_set.rb
|
82
|
+
- lib/ws_light/set/flowerbed_set.rb
|
80
83
|
- lib/ws_light/set/gradient_set.rb
|
81
84
|
- lib/ws_light/set/rainbow_set.rb
|
82
85
|
- lib/ws_light/set/random_set.rb
|
@@ -84,9 +87,14 @@ files:
|
|
84
87
|
- lib/ws_light/set/star_set.rb
|
85
88
|
- lib/ws_light/set/strawberry_set.rb
|
86
89
|
- lib/ws_light/set/watermelon_set.rb
|
90
|
+
- lib/ws_light/set/weather/cloudy_set.rb
|
91
|
+
- lib/ws_light/set/weather/fair_set.rb
|
92
|
+
- lib/ws_light/set/weather/rain_set.rb
|
93
|
+
- lib/ws_light/set/weather/sunny_set.rb
|
87
94
|
- lib/ws_light/strip.rb
|
88
95
|
- lib/ws_light/version.rb
|
89
96
|
- ws_light.gemspec
|
97
|
+
- ws_light.service
|
90
98
|
homepage: https://github.com/kayssun/ws_light
|
91
99
|
licenses:
|
92
100
|
- MIT
|
@@ -106,8 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
114
|
- !ruby/object:Gem::Version
|
107
115
|
version: '0'
|
108
116
|
requirements: []
|
109
|
-
|
110
|
-
rubygems_version: 2.4.5.1
|
117
|
+
rubygems_version: 3.0.6
|
111
118
|
signing_key:
|
112
119
|
specification_version: 4
|
113
120
|
summary: A lighting gem for WS2801 led strips.
|