ws_light 0.3.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|