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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c759d2c4fc062deccebc4190aa531af5f388cdd1
4
- data.tar.gz: 0d471ef1d1f9b3988187f26e3efa57283df1731b
2
+ SHA256:
3
+ metadata.gz: 7c88abb859296341e8d84b4f1bb31bbf5932abf0daed9439e2d22640de25fab0
4
+ data.tar.gz: ba45253224b6b2e523e774255a8f56ab6aaf1d892ed4d805859e22a295961e05
5
5
  SHA512:
6
- metadata.gz: 5c643c0a4992cb7310469cc5c7070d791906836267b0b24d1f2b3b5e1269902f6e626d4c687ad0a5cf0d0f05241504df7a8ad7872cb1cce5283fcb30dba71062
7
- data.tar.gz: 78f81988a236233f7e64ad735f61a6edba4d9ddf952b2e564f3904bc9103ae910db20788acd12bee5aa2d9c22a921f7d9b23b18ec31637769ba0abb0fd047deb
6
+ metadata.gz: bccde7663cde60bcc102898e005e077e93b8507fe3b6b9ab247b055273dfa8c81c102a8a9f1b15aa1838326a6d7183417f4f7af487e2907d662fef18a4252001
7
+ data.tar.gz: 3efedea3cffa086835d1cbe30a01ced9505c94549fe107dc77abdcd9b8d3148218b2be0cf8228a184b32f169bd1cc63716a0b88905ca77ddb723c37c1f68981d
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- DEBUG = true
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
- PIN_RIGHT = 23
28
- PIN_LEFT = 24
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 = DEBUG
32
- logger.filename = '/var/log/motion.log'
36
+ logger.debug = false
37
+ logger.filename = config['log_file']
33
38
  logger.log 'Starting up'
34
39
 
35
- def unregister_pins
36
- [PIN_RIGHT, PIN_LEFT].each do |pin|
37
- File.open('/sys/class/gpio/unexport', 'w') { |f| f.write("#{pin}") }
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 = 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 :pin => PIN_RIGHT, :goes => :high do
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 :pin => PIN_LEFT, :goes => :high do
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
-
@@ -10,7 +10,7 @@ module WSLight
10
10
  end
11
11
 
12
12
  def frame_data(count)
13
- frame(count).collect{|color| color.to_a}.flatten
13
+ frame(count).collect(&:to_a).flatten
14
14
  end
15
15
 
16
16
  def frames_per_second
@@ -18,4 +18,4 @@ module WSLight
18
18
  end
19
19
  end
20
20
  end
21
- end
21
+ end
@@ -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,6 @@ module WSLight
4
4
  module Animation
5
5
  # Slides from one set to another from right to left (obviously depending on the hardware setup)
6
6
  class SlideRightAnimation < BaseAnimation
7
-
8
7
  def frames
9
8
  @set_from.length + 1 # one for each led plus one for all zero
10
9
  end
@@ -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."
@@ -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
- (@r * (1-ratio) + other.r * ratio).to_i,
32
- (@g * (1-ratio) + other.g * ratio).to_i,
33
- (@b * (1-ratio) + other.b * ratio).to_i
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
- fail "Cannot find color #{name}"
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
@@ -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, :entries, :debug, :filename
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
- file = File.open(@filename, File.exists?(@filename) ? 'a' : 'w')
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
@@ -19,7 +19,7 @@ module WSLight
19
19
  end
20
20
 
21
21
  def frame_data
22
- frame.collect{|color| color.to_a}.flatten
22
+ frame.collect(&:to_a).flatten
23
23
  end
24
24
 
25
25
  def frame
@@ -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+@frame_count)
30
+ x = @frequency * (number + @frame_count)
31
31
  Color.new(
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)
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 do
15
+ while @raspberries.size < RASPBERRY_COUNT
16
16
  position = rand(@full_length)
17
- @raspberries << position unless (at_end?(position) || between_strips?(position) || is_raspberry?(position))
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 << (is_raspberry?(i) ? COLOR_RASPBERRY : COLOR_SEMOLINA)
37
+ set << (raspberry?(i) ? COLOR_RASPBERRY : COLOR_SEMOLINA)
39
38
  end
40
39
 
41
40
  set
42
41
  end
43
42
 
44
- def is_raspberry?(pixel)
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
- draw_star(@stars[start + i], @frame_count - ((start - VISIBLE_STARS + 1 + i) * (FRAMES_PER_STAR.to_f / VISIBLE_STARS.to_f)).to_i)
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
- #(-3..3).each do |i|
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(led_distance, frame_distance)
54
- #return 0 if led_distance > 3
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) == 0
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 += 15 + rand(5)
37
+ distance += rand(5..20)
39
38
  set[distance] = COLOR_NUT
40
39
  end
41
40
 
@@ -27,7 +27,7 @@ module WSLight
27
27
  end
28
28
  end
29
29
 
30
- set.reverse! if rand(2) == 0
30
+ set.reverse! if rand(2).zero?
31
31
 
32
32
  type == :double ? set + set.reverse : set
33
33
  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 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
@@ -1,4 +1,4 @@
1
- require 'ws2801'
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
- WS2801.length(Strip::TYPE == :double ? Strip::LENGTH * 2 : Strip::LENGTH)
43
- WS2801.autowrite(true)
63
+ @spi = SPI.new(device: '/dev/spidev0.0')
64
+ @spi.speed = 500_000
44
65
  self_test
45
- @listen_thread = Thread.new { while true do check_timer; sleep 0.5; end }
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
- case rand(100)
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
- WS2801.strip(animation.frame_data(current_frame = i))
136
- WS2801.write
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
- WS2801.strip(animation.frame_data(current_frame - i - 1))
145
- WS2801.write
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
- WS2801.strip(set.frame_data)
159
- WS2801.write
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
- WS2801.set(r: 0, g: 0, b: 0)
183
+ write([0, 0, 0] * FULL_LENGTH)
172
184
  end
173
185
 
174
186
  def self_test
175
- WS2801.set(r: 0, g: 0, b: 255)
187
+ write([0, 0, 255] * FULL_LENGTH)
176
188
  sleep 1
177
- WS2801.set(r: 0, g: 255, b: 0)
189
+ write([0, 255, 0] * FULL_LENGTH)
178
190
  sleep 1
179
- WS2801.set(r: 255, g: 0, b: 0)
191
+ write([255, 0, 0] * FULL_LENGTH)
180
192
  sleep 1
181
- WS2801.set(r: 0, g: 0, b: 0)
193
+ write([0, 0, 0] * FULL_LENGTH)
182
194
  end
183
195
 
184
196
  def check_timer
185
- WS2801.set(r: 0, g: 0, b: 0) if @state == :state_off
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
@@ -1,4 +1,4 @@
1
1
  # Provides a global version number
2
2
  module WSLight
3
- VERSION = '0.3.0'
4
- end
3
+ VERSION = '0.4.2'.freeze
4
+ end
@@ -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 'ws2801', '~>1'
23
+ spec.add_runtime_dependency 'spi', '~>0.1'
25
24
  end
@@ -0,0 +1,9 @@
1
+ [Unit]
2
+ Description=WS Light
3
+
4
+ [Service]
5
+ Type=simple
6
+ ExecStart=/usr/local/bin/ws_light
7
+
8
+ [Install]
9
+ WantedBy=multi-user.target
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.3.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: 2015-12-29 00:00:00.000000000 Z
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: ws2801
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
- rubyforge_project:
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.