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 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.