domotics-core 0.0.1 → 0.1.0

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
2
  SHA1:
3
- metadata.gz: 9d968d8308c24d08e2171654bc6845b5b76104fc
4
- data.tar.gz: b2538a84593856978c5945464fb7efe00a2895d9
3
+ metadata.gz: 096e15387741c6806173272f0da0754df91249d7
4
+ data.tar.gz: 2555b1a6fd43f0d236edd53d94508072af6c6f10
5
5
  SHA512:
6
- metadata.gz: a54f487cd28ee6cdf719838ac721b8c3064e8d71748b68dd6104d5c14fbe7bd6c9ea60dcecc9fdda253c3219effa0f391c272671e61a4604a71e745754c86522
7
- data.tar.gz: ed4c64c3b9b1b2611445105f229c17d4a62d34cb84c9b8c13931bd22bad2cb407087484bbaf51468900588fb03731e4207b2a0d12ee80f034c06d94ac9915702
6
+ metadata.gz: 52e29d3445656f7a3b447687986a93e524652e1fa2439e875c36d741095d5863f81e91d1bd1a4ab9ff9333d10545e70bfb22864cdac3e5d9233147c5855cbc57
7
+ data.tar.gz: d390c82c1906c07a3744288481a2b18b6a8fc72219f05f3bad21968b9e236cc60cba0956a797fc0daf80866c38622244c1d49162849392a30626e4021bc804a4
data/Rakefile CHANGED
@@ -36,7 +36,7 @@ end
36
36
 
37
37
  def update(msg)
38
38
  # Update version
39
- File.open "./lib/domotics/arduino/version.rb", "r+" do |f|
39
+ File.open "./lib/domotics/core/version.rb", "r+" do |f|
40
40
  up = f.read.sub(/\d+.\d+.\d+/) { |ver| ver.split('.').map.with_index{ |sv, i| yield sv,i }.join('.') }
41
41
  f.seek 0
42
42
  f.write up
@@ -52,4 +52,4 @@ def update(msg)
52
52
  puts "Pushed to github."
53
53
  Dir["./*.gemspec"].each { |spec| puts %x(gem build #{spec}) }
54
54
  Dir["./*.gem"].each { |gem| puts %x(gem push #{gem}) }
55
- end
55
+ end
@@ -1,7 +1,47 @@
1
- require "domotics/core/version"
1
+ # From data_mongo
2
+ require 'mongo'
3
+ # From data_redis
4
+ require 'redis'
5
+ require 'hiredis'
6
+ # From arduino_board
7
+ require 'domotics/arduino'
8
+ # From server
9
+ require 'json'
2
10
 
3
11
  module Domotics
4
12
  module Core
5
- # Your code goes here...
13
+ # Map config names to real classes
14
+ CLASS_MAP = {}
15
+ # Scan file for class name and add to CLASS_MAP
16
+ def self.add_map(args = {})
17
+ realm = args[:realm] || self
18
+ if args[:file]
19
+ class_name = nil
20
+ index = nil
21
+ require args[:file]
22
+ IO.read(args[:file]).each_line do |line|
23
+ if line =~ /class\s*([A-Z]\w*)[\s\w<]*(#__as__ :(\w*))?/
24
+ class_name, index = $1, $3 && $3.to_sym
25
+ break
26
+ end
27
+ end
28
+ return unless class_name
29
+ end
30
+ class_name ||= args[:class_name]
31
+ index ||= class_name.split(/(?=[A-Z])/).map{ |cnp| cnp.downcase }.join('_').to_sym
32
+ klass = realm.const_get(class_name)
33
+ CLASS_MAP[index] = [args[:type], klass]
34
+ end
35
+ end
36
+ end
37
+
38
+ gem_path = File.dirname(__FILE__)
39
+ #require all
40
+ Dir["#{gem_path}/core/data/*.rb"].each {|file| require file}
41
+ Dir["#{gem_path}/core/*.rb"].each {|file| require file}
42
+ # scan all devices and elements and populate class map
43
+ [:device, :room, :element].each do |type|
44
+ Dir["#{gem_path}/core/#{type}/*.rb"].each do |file|
45
+ Domotics::Core.add_map type: type, file: file
6
46
  end
7
47
  end
@@ -0,0 +1,38 @@
1
+ module Domotics::Core
2
+ class DataHash
3
+ def initialize
4
+ @store = {}
5
+ end
6
+
7
+ def [](obj)
8
+ case obj
9
+ when Element
10
+ DataHashOperator.new self, "#{obj.room.name}:#{obj.name}"
11
+ end
12
+ end
13
+
14
+ def set(key, value)
15
+ @store[key] = value
16
+ end
17
+ def get(key)
18
+ @store[key]
19
+ end
20
+ end
21
+
22
+ class DataHashOperator < BasicObject
23
+ def initialize(hash, key)
24
+ @hash, @key = hash, key
25
+ end
26
+
27
+ def method_missing(symbol, *args)
28
+ # Setter method [*=(value)]
29
+ if symbol.to_s =~ /.*=\Z/ and args.size == 1
30
+ @hash.set "#{@key}:#{symbol.to_s[0..-2]}", args[0]
31
+ elsif args.size == 0
32
+ @hash.get "#{@key}:#{symbol}"
33
+ else
34
+ nil
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ module Domotics::Core
2
+ class DataMongo
3
+ def initialize(args = {})
4
+ @logger = args[:logger] || Logger.new(STDERR)
5
+ @mongo = Mongo::MongoClient.new(args[:host] || "127.0.0.1", args[:port] || 27017).db("domotics")
6
+ end
7
+
8
+ def [](obj)
9
+ case obj
10
+ when Element
11
+ DataMongoOperator.new @mongo.collection(obj.room.name.to_s), obj.name.to_s
12
+ end
13
+ end
14
+ end
15
+
16
+ class DataMongoOperator < BasicObject
17
+ def initialize(coll, element)
18
+ @coll = coll
19
+ @element = element
20
+ end
21
+
22
+ def method_missing(symbol, *args)
23
+ # Setter method [*=(value)]
24
+ if symbol.to_s =~ /.*=\Z/ and args.size == 1
25
+ if el = @coll.find_one("element" => @element)
26
+ @coll.update({ "_id" => el["_id"] }, { "element" => @element, symbol.to_s[0..-2] => args[0] })
27
+ else
28
+ @coll.insert("element" => @element, symbol.to_s[0..-2] => args[0])
29
+ end
30
+ # Getter method (no arguments allowed)
31
+ elsif args.size == 0
32
+ result = @coll.find_one("element" => @element)
33
+ result && result[symbol.to_s]
34
+ else
35
+ nil
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,56 @@
1
+ module Domotics::Core
2
+ class DataRedis
3
+ def initialize(args = {})
4
+ @logger = args[:logger] || Logger.new(STDERR)
5
+ @args = Hash.new
6
+ @args[:host] = args[:host] || "127.0.0.1"
7
+ @args[:port] = args[:port] || 6379
8
+ @args[:driver] = :hiredis
9
+ connect
10
+ end
11
+
12
+ def [](obj)
13
+ case obj
14
+ when Element
15
+ DataRedisOperator.new self, "#{obj.room.name}:#{obj.name}"
16
+ end
17
+ end
18
+
19
+ def connect(args = {})
20
+ @logger.debug "Broken connection to redis host [#{@args[:host]}:#{@args[:port]}] detected. Reconnect." if args[:broken]
21
+ @redis.quit if @redis
22
+ @redis = Redis.new @args
23
+ end
24
+
25
+ def get(*args)
26
+ @redis.get *args
27
+ end
28
+ def set(*args)
29
+ @redis.set *args
30
+ end
31
+ end
32
+
33
+ class DataRedisOperator < BasicObject
34
+ def initialize(redis, key)
35
+ @redis = redis
36
+ @key = key
37
+ end
38
+
39
+ def method_missing(symbol, *args)
40
+ # Setter method [*=(value)]
41
+ if symbol.to_s =~ /.*=\Z/ and args.size == 1
42
+ @redis.set "#{@key}:#{symbol.to_s[0..-2]}", args[0].to_s
43
+ # Getter method (no arguments allowed)
44
+ elsif args.size == 0
45
+ result = @redis.get "#{@key}:#{symbol}"
46
+ while result =~ /\AOK\Z/
47
+ @redis.connect broken: true
48
+ result = @redis.get "#{@key}:#{symbol}"
49
+ end
50
+ result && result.to_isym
51
+ else
52
+ nil
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,22 @@
1
+ module Domotics::Core
2
+ class Device
3
+ @@devices = {}
4
+ attr_reader :name, :type
5
+ def initialize(args = {})
6
+ @@devices[@name = args[:name]] = self
7
+ @type = args[:type]
8
+ end
9
+
10
+ def self.[](symbol = nil)
11
+ return @@devices[symbol] if symbol
12
+ @@devices
13
+ end
14
+
15
+ def destroy
16
+ @@devices[@name] = nil
17
+ end
18
+ def to_s
19
+ "Room[#{@name}](id:#{__id__})"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ module Domotics::Core
2
+ class ArduinoBoard < Device #__as__ :arduino
3
+ include Domotics::Arduino::ArduinoBase
4
+
5
+ def initialize(args_hash = {})
6
+ @pins = Hash.new
7
+ super
8
+ end
9
+
10
+ # Register pin for watch events
11
+ def register_pin(pin_object, number)
12
+ @pins[number] = pin_object
13
+ end
14
+
15
+ # Return pin object
16
+ def [](number = nil)
17
+ return @pins[number] if number
18
+ @pins
19
+ end
20
+
21
+ private
22
+
23
+ # Override default handler
24
+ def event_handler(hash)
25
+ case hash[:event]
26
+ # Tell element to change state
27
+ when :pin_state_changed
28
+ element = @pins[hash[:pin]]
29
+ element.state_changed element.to_hls(hash[:state])
30
+ when :malfunction
31
+ nil
32
+ else
33
+ nil
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,47 @@
1
+ module Domotics::Core
2
+ class Element
3
+ @@data = DataHash.new
4
+ attr_reader :name, :type, :room
5
+
6
+ def initialize(args = {})
7
+ @room = args[:room]
8
+ @room.register_element self, @name = args[:name]
9
+ @type ||= :element
10
+ set_state(self.state || :off)
11
+ end
12
+
13
+ def state
14
+ @@data[self].state
15
+ end
16
+
17
+ def verbose_state
18
+ { @room.name =>
19
+ { :elements =>
20
+ { @name =>
21
+ { :state => state,
22
+ :info => (info if respond_to? :info)
23
+ }
24
+ }
25
+ }
26
+ }
27
+ end
28
+
29
+ def set_state(value)
30
+ @@data[self].state = value
31
+ @room.notify({ event: :state_set, element: self }) unless @type == :dimmer
32
+ end
33
+
34
+ def state_changed(value)
35
+ @@data[self].state = value
36
+ @room.notify event: :state_changed, element: self
37
+ end
38
+
39
+ def self.data=(value)
40
+ @@data = value
41
+ end
42
+
43
+ def to_s
44
+ "Element[#{@room.name}@#{@name}](id:#{__id__})"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,41 @@
1
+ module Domotics::Core
2
+ class Button < Element
3
+ def initialize(args = {})
4
+ @type = args[:type] || :button
5
+ @touch = args[:touch]
6
+ @taped = true
7
+ #@tap_lock = Mutex.new
8
+ if args[:device_type]
9
+ eval_str = %(include Domotics::#{args[:device_type].capitalize}::#{@touch ? 'DigitalSensor' : 'NOSensor'})
10
+ self.class.class_eval(eval_str, __FILE__, __LINE__)
11
+ end
12
+ super
13
+ end
14
+ def set_state(*args)
15
+ nil
16
+ end
17
+
18
+ def state_changed(value)
19
+ case value
20
+ when :on
21
+ (@last_on = Time.now; @taped = false) if @taped
22
+ when :off
23
+ case Time.now - (@last_on || Time.now)
24
+ when 0...0.01 then return # debounce
25
+ when 0.01...0.3 then super :tap; @taped = true
26
+ when 0.3...1 then super :long_tap; @taped = true
27
+ #@tap_lock.synchronize do
28
+ # if @tap and @tap.alive?
29
+ # @tap.kill
30
+ # @tap = nil
31
+ # super :double_tap
32
+ # else
33
+ # @tap = Thread.new { sleep 0.25; super :tap }
34
+ # end
35
+ #end
36
+ else super :long_tap_x2; @taped = true
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,71 @@
1
+ module Domotics::Core
2
+ class Dimmer < Element
3
+
4
+ DEFAULT_LEVEL = 0
5
+ MIN_LEVEL = 0
6
+ MAX_LEVEL = 255
7
+ MAX_STEPS = 128
8
+ STEP_DELAY = 1.0 / MAX_STEPS
9
+ STEP_SIZE = ((MAX_LEVEL + 1) / MAX_STEPS.to_f).round
10
+
11
+ def initialize(args = {})
12
+ @type = args[:type] || :dimmer
13
+ @fade_lock = Mutex.new
14
+ @fade_thread = nil
15
+ if args[:device_type]
16
+ eval_str = %(include Domotics::#{args[:device_type].capitalize}::PWMPin)
17
+ self.class.class_eval(eval_str, __FILE__, __LINE__)
18
+ end
19
+ super
20
+ end
21
+
22
+ def set_state(value = DEFAULT_LEVEL, opt = {})
23
+ unless opt[:kill_fader] == :no
24
+ @fade_lock.synchronize do
25
+ @fade_thread.kill if @fade_thread and @fade_thread.alive?
26
+ end
27
+ end
28
+ if value.is_a? Integer
29
+ value = MIN_LEVEL if value < MIN_LEVEL
30
+ value = MAX_LEVEL if value > MAX_LEVEL
31
+ end
32
+ super value
33
+ end
34
+ # Decrease brightness level (value 0-100%)
35
+ def dim(value = nil)
36
+ if value
37
+ set_state value * MAX_LEVEL / 100
38
+ else
39
+ set_state state - STEP_SIZE
40
+ end
41
+ end
42
+ # Increase brightness level (value 0-100%)
43
+ def bright(value = nil)
44
+ if value
45
+ set_state value * MAX_LEVEL / 100
46
+ else
47
+ set_state state + STEP_SIZE
48
+ end
49
+ end
50
+
51
+ def off
52
+ set_state MIN_LEVEL unless state == MIN_LEVEL
53
+ end
54
+
55
+ def fade_to(value = DEFAULT_LEVEL, speed_divisor = 1)
56
+ @fade_lock.synchronize do
57
+ @fade_thread.kill if @fade_thread and @fade_thread.alive?
58
+ @fade_thread = Thread.new do
59
+ op = (value - state) >= 0 ? :+ : :-
60
+ steps = ((value - state).abs / STEP_SIZE.to_f).round
61
+ steps.times do
62
+ set_state(state.public_send(op, STEP_SIZE), kill_fader: :no)
63
+ sleep speed_divisor * STEP_DELAY
64
+ end
65
+ @fade_lock.synchronize { @fade_thread = nil }
66
+ end
67
+ end
68
+ @fade_thread
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,18 @@
1
+ module Domotics::Core
2
+ class MotionSensor < Element
3
+ def initialize(args = {})
4
+ @type = args[:type] || :motion_sensor
5
+ if args[:device_type]
6
+ eval_str = %(include Domotics::#{args[:device_type].capitalize}::DigitalSensor)
7
+ self.class.class_eval(eval_str, __FILE__, __LINE__)
8
+ end
9
+ super
10
+ end
11
+ #def to_hls(state)
12
+ # super == :on ? :move : :no_move
13
+ #end
14
+ def set_state(*args)
15
+ nil
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module Domotics::Core
2
+ class ReedSwitch < Element
3
+ def initialize(args = {})
4
+ @type = args[:type] || :reed_switch
5
+ if args[:device_type]
6
+ eval_str = %(include Domotics::#{args[:device_type].capitalize}::NCSensor)
7
+ self.class.class_eval(eval_str, __FILE__, __LINE__)
8
+ end
9
+ super
10
+ end
11
+ #def to_hls(state)
12
+ # super == :on ? :open : :close
13
+ #end
14
+ def set_state(*args)
15
+ nil
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,94 @@
1
+ module Domotics::Core
2
+ class RgbStrip < Element
3
+ def initialize(args = {})
4
+ @type = args[:type] || :rgb_strip
5
+ @strips = Hash.new
6
+ @crazy_lock = Mutex.new
7
+ @crazy_thread = nil
8
+ super
9
+ sub_args = args.dup
10
+ %w(r g b).each do |x|
11
+ sub_args[:name] = (args[:name].to_s+"_#{x}_strip").to_sym
12
+ sub_args[:pin] = args[x.to_sym]
13
+ @strips[x.to_sym] = Dimmer.new(sub_args)
14
+ end
15
+ end
16
+
17
+ def red
18
+ @strips[:r]
19
+ end
20
+ def green
21
+ @strips[:g]
22
+ end
23
+ def blue
24
+ @strips[:b]
25
+ end
26
+
27
+ def off
28
+ kill_crazy
29
+ @strips.values.each { |strip| strip.off } if on?
30
+ set_state :off
31
+ end
32
+
33
+ def color
34
+ @strips.values.map { |strip| strip.state }
35
+ end
36
+
37
+ def on?
38
+ color.reduce(:+) != 0
39
+ end
40
+
41
+ def on
42
+ set_color 255, 255, 255
43
+ end
44
+
45
+ def set_color(*args)
46
+ kill_crazy
47
+ args=args[0] if args.size == 1 and args[0].is_a? Array
48
+ if args.size == 3
49
+ @strips[:r].fade_to args[0]
50
+ @strips[:g].fade_to args[1]
51
+ @strips[:b].fade_to args[2]
52
+ set_state args.reduce(:+) == 0 ? :off : :on
53
+ end
54
+ end
55
+
56
+ def set_power(value=50)
57
+ return unless value.is_a? Integer
58
+ value=100 if value>100
59
+ value=0 if value<0
60
+ if state == :on
61
+ set_color color.map { |c| c * Dimmer::MAX_LEVEL * value / color.max / 100 }
62
+ else
63
+ set_color 3.times.map { Dimmer::MAX_LEVEL * value / 100 }
64
+ end
65
+ end
66
+
67
+ def random
68
+ set_color 3.times.map { rand Dimmer::MAX_LEVEL }
69
+ end
70
+
71
+ def crazy
72
+ @crazy_lock.synchronize do
73
+ @crazy_thread.kill if @crazy_thread
74
+ @crazy_thread = Thread.new do
75
+ loop do
76
+ @fade_threads = @strips.values.map { |strip| strip.fade_to(rand(Dimmer::MAX_LEVEL), 1) }
77
+ @fade_threads.each { |thread| thread.join }
78
+ end
79
+ end
80
+ end
81
+ set_state :on
82
+ end
83
+
84
+ def kill_crazy
85
+ @crazy_lock.synchronize do
86
+ if @crazy_thread
87
+ @crazy_thread.kill
88
+ @crazy_thread = nil
89
+ end
90
+ end
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,67 @@
1
+ module Domotics::Core
2
+ class Switch < Element
3
+ MINIMUM_LAG = 1
4
+ def initialize(args = {})
5
+ @type = args[:type] || :switch
6
+ @lag = nil
7
+ @lag_lock = Mutex.new
8
+ if args[:device_type]
9
+ eval_str = %(include Domotics::#{args[:device_type].capitalize}::DigitalPin)
10
+ self.class.class_eval(eval_str, __FILE__, __LINE__)
11
+ end
12
+ @initialized = false
13
+ super
14
+ @initialized = true
15
+ end
16
+ def set_state(value)
17
+ @initialized ? (super unless state == value) : super
18
+ end
19
+ def on(timer = nil)
20
+ set_state :on
21
+ lag(:off, timer)
22
+ end
23
+ def on?
24
+ state == :on
25
+ end
26
+ def delay_on(timer)
27
+ lag(:on, timer)
28
+ end
29
+ def off(timer = nil)
30
+ set_state :off
31
+ lag(:on, timer)
32
+ end
33
+ def off?
34
+ state == :off
35
+ end
36
+ def delay_off(timer)
37
+ lag(:off, timer)
38
+ end
39
+ def toggle(timer = nil)
40
+ set_state state == :off ? :on : :off
41
+ lag(:toggle, timer)
42
+ end
43
+ def delay_toggle(timer)
44
+ lag(:toggle, timer)
45
+ end
46
+
47
+ private
48
+
49
+ def lag(action = nil, timer = nil)
50
+ # Kill previous action -> out of date
51
+ @lag_lock.synchronize do
52
+ if @lag and @lag.alive?
53
+ @lag.kill
54
+ @lag = nil
55
+ end
56
+ raise ArgumentError unless (timer.is_a?(Integer) and timer >= MINIMUM_LAG)
57
+ # Delayed action
58
+ @lag = Thread.new do
59
+ sleep timer
60
+ public_send action
61
+ end
62
+ end
63
+ rescue ArgumentError
64
+ nil
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,38 @@
1
+ module Domotics::Core
2
+ class ElementGroup < BasicObject
3
+ attr_reader :name, :type, :room, :elements
4
+ def initialize(args = {})
5
+ #::Object.instance_method(:is_a?).bind(self)
6
+ @name = args[:name] || :undefined
7
+ @room = Room[args[:room]]
8
+ @room.register_element self, @name
9
+ @elements = []
10
+ @type = :group
11
+ end
12
+ def add_element(element)
13
+ @elements << element
14
+ end
15
+ def verbose_state
16
+ { @room.name =>
17
+ { :elements =>
18
+ { @name =>
19
+ { :state => nil }
20
+ }
21
+ }
22
+ }
23
+ end
24
+ def method_missing(method, *args, &block)
25
+ if @elements.map{ |el| el.respond_to? method }.reduce{ |res, n| res && n }
26
+ @elements.map{ |el| el.public_send method, *args, &block }.reduce{ |res, n| res && n }
27
+ else
28
+ super
29
+ end
30
+ end
31
+ def respond_to?(method)
32
+ @elements.map{ |el| el.respond_to? method }.reduce{ |res, n| res && n } || super
33
+ end
34
+ def to_s
35
+ "Group :#{@name} -> #{@elements}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,73 @@
1
+ # Debug - Show exception in threads
2
+ Thread.class_eval do
3
+ alias_method :initialize_without_exception_show, :initialize
4
+ def initialize(*args, &block)
5
+ initialize_without_exception_show(*args) do
6
+ begin
7
+ block.call
8
+ rescue Exception => e
9
+ $logger.error { e.message }
10
+ $logger.debug { e.inspect }
11
+ raise e
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ class String
18
+ # Return integer (if can convert) or symbol
19
+ def to_isym
20
+ begin
21
+ Integer(self)
22
+ rescue ArgumentError
23
+ self.to_sym
24
+ end
25
+ end
26
+ end
27
+
28
+ module Domotics::Core
29
+ # Suppress no method errors
30
+ class BlackHole
31
+ def method_missing(*args)
32
+ self
33
+ end
34
+ end
35
+ end
36
+
37
+ module Domotics::Core
38
+ class TestRoom < Room
39
+ def initialize(args = {})
40
+ super
41
+ @events = {}
42
+ end
43
+ def event_handler(msg = {})
44
+ event, element = msg[:event], msg[:element]
45
+ if element
46
+ @events[element.name] ||= []
47
+ @events[element.name].push event => element.state
48
+ end
49
+ super
50
+ end
51
+ def last_event(element_name)
52
+ @events[element_name].pop if @events[element_name].respond_to? :pop
53
+ end
54
+ end
55
+ end
56
+
57
+ module Domotics::Core
58
+ class TestHelper
59
+ def self.init
60
+ $emul = Domotics::Arduino::BoardEmulator.new
61
+ Domotics::Core.add_map type: :room, class_name: "TestRoom"
62
+ Domotics::Core::Setup.new IO.read File.expand_path("../../../test/config.test.rb", File.dirname(__FILE__))
63
+ end
64
+ end
65
+ end
66
+
67
+ class Object
68
+ def eigenclass
69
+ class << self
70
+ self
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,86 @@
1
+ module Domotics::Core
2
+ class Room
3
+ # All rooms
4
+ @@rooms = Hash.new
5
+ attr_reader :name, :type, :elements
6
+ def initialize(args = {})
7
+ @logger = args[:logger] || Logger.new(STDERR)
8
+ # Save self
9
+ @@rooms[@name = args[:name]] = self
10
+ @type = args[:type]
11
+ # Hash of elements
12
+ @elements = {}
13
+ class << @elements
14
+ def light
15
+ select { |name, element| (element.is_a? Element) and (element.type == :switch) and (name =~ /light/) }
16
+ end
17
+ end
18
+ # New queue thread
19
+ @room_queue = Queue.new
20
+ @queue_thread = Thread.new { loop { event_handler @room_queue.pop } }
21
+ end
22
+ # Register element
23
+ def register_element(element, name)
24
+ @elements[name] = element
25
+ # define accessor method (singleton)
26
+ instance_eval(%(def #{name}; @elements[:#{name}]; end;), __FILE__, __LINE__) unless respond_to? name
27
+ end
28
+ # Return state of all elements
29
+ def verbose_state
30
+ { @name =>
31
+ { :elements =>
32
+ @elements.inject(Hash.new) { |hash, element| hash.merge element[1].verbose_state[@name][:elements] },
33
+ :state => state,
34
+ :info => (info if respond_to? :info)
35
+ }
36
+ }
37
+ end
38
+ def state
39
+ nil
40
+ end
41
+ # Perform action with light
42
+ def light(action = :toggle)
43
+ case action
44
+ when :on, :off
45
+ @elements.light.values.each { |element| element.public_send(action) if element.respond_to? action }
46
+ when :toggle
47
+ light_off? ? light(:on) : light(:off)
48
+ end
49
+ end
50
+ def light_off?
51
+ @elements.light.values.reduce(true) { |res, el| res && el.off? }
52
+ end
53
+ # Method for pushing into queue
54
+ def notify(msg)
55
+ @room_queue.push(msg)
56
+ end
57
+ # Default - simple prints event
58
+ def event_handler(msg = {})
59
+ event, element = msg[:event], msg[:element]
60
+ @logger.info { "Event message :#{event} from #{element} with state [#{element.state}]" }
61
+ end
62
+
63
+ def destroy
64
+ @queue_thread.exit
65
+ end
66
+
67
+ # Return element object
68
+ def [](symbol = nil)
69
+ return @elements[symbol] if symbol
70
+ @elements
71
+ end
72
+ # Return requested room like element of array
73
+ def self.[](symbol = nil)
74
+ return @@rooms[symbol] if symbol
75
+ @@rooms
76
+ end
77
+ # Return requested room like variable
78
+ def method_missing(symbol, *args)
79
+ super unless args.size == 0
80
+ @@rooms[symbol] || BlackHole.new
81
+ end
82
+ def to_s
83
+ "Room[#{@name}](id:#{__id__})"
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,39 @@
1
+ module Domotics::Core
2
+ class Server
3
+ def initialize(args = {})
4
+ @logger = $logger || Logger.new(STDERR)
5
+ end
6
+ def call(env)
7
+ # [object]/[action]/[params]
8
+ request = env['PATH_INFO'][1..-1].split('/')
9
+ object = request.shift
10
+ return invalid 'room' unless object and object = Room[object.to_sym]
11
+ return invalid 'element or action' unless object_action = request.shift
12
+ if sub_object = object[object_action.to_isym]
13
+ room, object = object, sub_object
14
+ action = request.shift
15
+ else
16
+ room = object
17
+ action = object_action
18
+ end
19
+ return invalid 'action' unless action and object.respond_to? action
20
+ begin
21
+ object.public_send(action, *request.map { |param| param.to_isym })
22
+ rescue Exception => e
23
+ @logger.error { e.message }
24
+ @logger.debug { e }
25
+ return invalid 'request'
26
+ end
27
+ return ok object.verbose_state.to_json
28
+ end
29
+
30
+ private
31
+
32
+ def invalid(param)
33
+ [400, {"Content-Type" => "text/html"}, ["Processing error: invalid #{param}."]]
34
+ end
35
+ def ok(param)
36
+ [200, {"Content-Type" => "text/html"}, [param]]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,74 @@
1
+ # coding: utf-8
2
+ module Domotics::Core
3
+ CLASS_MAP[:group] = :element_group
4
+ class Setup < BasicObject
5
+ @@logger = ::Logger.new(::STDERR)
6
+ @@logger.level = ::Logger::FATAL
7
+ $logger = @@logger
8
+ def self.logger
9
+ @@logger
10
+ end
11
+ def self.logger=(logger)
12
+ @@logger = logger
13
+ $logger = @@logger
14
+ end
15
+
16
+ def initialize(conf)
17
+ @current_room = {}
18
+ @current_device = {}
19
+ @groups = []
20
+ instance_eval conf, __FILE__, __LINE__
21
+ end
22
+
23
+ def element_group(args = {})
24
+ raise "Element group must have room" unless @current_room.any?
25
+ args[:room] = @current_room[:name]
26
+ unless gr = Room[@current_room[:name]][args[:name]]
27
+ gr = ElementGroup.new args
28
+ @groups[-1].add_element gr if @groups[-1]
29
+ end
30
+ @groups.push(gr)
31
+ yield if ::Kernel.block_given?
32
+ @groups.pop
33
+ end
34
+
35
+ def room(klass, args = {})
36
+ @current_room[:name] = args[:name]
37
+ @current_room[:type] = args[:type]
38
+ klass.new(args) unless Room[args[:name]]
39
+ yield if ::Kernel.block_given?
40
+ @current_room.clear
41
+ end
42
+
43
+ def device(klass, args = {})
44
+ @current_device[:name] = args[:name]
45
+ @current_device[:type] = args[:type]
46
+ klass.new(args) unless Device[args[:name]]
47
+ yield if ::Kernel.block_given?
48
+ @current_device.clear
49
+ end
50
+
51
+ def element(klass, args = {})
52
+ raise "Element must have room" unless @current_room.any?
53
+ args[:room] = Room[@current_room[:name]]
54
+ args[:room_type] = @current_room[:type]
55
+ args[:device] = Device[@current_device[:name]]
56
+ args[:device_type] = @current_device[:type]
57
+ klass = klass.dup if args[:device_type]
58
+ el = klass.new(args) unless Room[@current_room[:name]][args[:name]]
59
+ @groups[-1].add_element el if @groups[-1]
60
+ end
61
+
62
+ def method_missing(symbol, *args, &block)
63
+ if CLASS_MAP[symbol] and name = args.shift
64
+ args_hash = args.shift || {}
65
+ args_hash[:name] = name
66
+ args_hash[:type] = symbol if CLASS_MAP[symbol][0] != :element
67
+ args_hash[:logger] = @@logger
68
+ __send__(*CLASS_MAP[symbol], args_hash, &block)
69
+ else
70
+ super
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,5 @@
1
1
  module Domotics
2
2
  module Core
3
- VERSION = "0.0.1"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
@@ -0,0 +1,16 @@
1
+ test_room :test do
2
+ button :button_no_dev
3
+ dimmer :dimmer_no_dev
4
+ motion_sensor :ms_no_dev
5
+ reed_switch :rs_no_dev
6
+ rgb_strip :rgb_no_dev
7
+ switch :light_no_dev
8
+ arduino :nano, board: :nano, port: $emul.port do
9
+ button :button, pin: 6
10
+ dimmer :dimmer, pin: 3
11
+ motion_sensor :ms, pin: 7
12
+ reed_switch :rs, pin: 8
13
+ rgb_strip :rgb, r: 9, g: 10, b: 11
14
+ switch :light_1, pin: 13
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require "test/unit"
4
+ require "domotics/core"
5
+
6
+ Domotics::Core::TestHelper.init
7
+
8
+ class DomoticsDevicesTestCase < Test::Unit::TestCase
9
+ end
@@ -0,0 +1,63 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require "test/unit"
4
+ require "domotics/core"
5
+
6
+ class DomoticsElementsTestCase < Test::Unit::TestCase
7
+ def test_dimmer
8
+ dimmer = Domotics::Core::Room[:test].dimmer
9
+ # Should turn on max and convert state to int
10
+ dimmer.set_state :on
11
+ assert_equal 255, dimmer.state
12
+ # Should turn off and convert state to int
13
+ dimmer.set_state :off
14
+ assert_equal 0, dimmer.state
15
+ # Dim
16
+ [0,3,24,127,237,255].each do |val|
17
+ dimmer.set_state val
18
+ assert_equal val, dimmer.state
19
+ end
20
+ # Off
21
+ dimmer.off
22
+ assert_equal 0, dimmer.state
23
+ # Fade to
24
+ dimmer.fade_to 255
25
+ sleep 1.6
26
+ assert_equal 255, dimmer.state
27
+ dimmer.fade_to 127
28
+ sleep 0.8
29
+ assert_equal 127, dimmer.state
30
+ dimmer.fade_to 0
31
+ sleep 0.8
32
+ assert_equal 0, dimmer.state
33
+ end
34
+
35
+ def test_rgb_strip
36
+ rgb = Domotics::Core::Room[:test].rgb
37
+ rgb.on
38
+ sleep 1.6
39
+ assert_equal 255, rgb.red.state
40
+ rgb.off
41
+ assert_equal 0, rgb.red.state
42
+ assert_equal :dimmer, rgb.red.type
43
+ assert_equal :dimmer, rgb.green.type
44
+ assert_equal :dimmer, rgb.blue.type
45
+ end
46
+
47
+ def test_button
48
+ room = Domotics::Core::Room[:test]
49
+ btn = room.button
50
+
51
+ $emul.set_internal_state 6, 1
52
+ $emul.toggle_pin 6
53
+ sleep 0.1
54
+ $emul.toggle_pin 6
55
+ sleep 0.05
56
+ assert_equal room.last_event(btn.name), :state_changed => :tap
57
+ $emul.toggle_pin 6
58
+ sleep 0.6
59
+ $emul.toggle_pin 6
60
+ sleep 0.01
61
+ assert_equal room.last_event(btn.name), :state_changed => :long_tap
62
+ end
63
+ end
@@ -0,0 +1,17 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require "test/unit"
4
+ require "domotics/core"
5
+
6
+ class DomoticsRoomsTestCase < Test::Unit::TestCase
7
+ def test_room
8
+ tr = Domotics::Core::Room.new name: :tr
9
+ assert_raise NoMethodError do
10
+ tr.instance_eval { no_method :arg1 }
11
+ end
12
+ assert_nothing_raised do
13
+ tr.instance_eval { nothing.off }
14
+ tr.instance_eval { nothing.light :off }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,57 @@
1
+ #ENV['RACK_ENV'] = 'test'
2
+
3
+ require "domotics/core"
4
+ require 'test/unit'
5
+ require 'rack/test'
6
+
7
+ class DomoticsTest < Test::Unit::TestCase
8
+ include Rack::Test::Methods
9
+
10
+ def app
11
+ Domotics::Core::Server.new
12
+ end
13
+
14
+ def test_valid_object
15
+ get '/to_s'
16
+ assert last_response.bad_request?
17
+ get '/test/to_s'
18
+ assert last_response.ok?
19
+ get '/test/light_1/to_s'
20
+ assert last_response.ok?
21
+ get '/invalid_object/to_s'
22
+ assert last_response.bad_request?
23
+ get '/test/invalid_object/to_s'
24
+ assert last_response.bad_request?
25
+ end
26
+
27
+ def test_query_action
28
+ get '/test/light_1/state'
29
+ assert last_response.ok?
30
+ get '/test/light_1/abracadabra'
31
+ assert last_response.bad_request?
32
+ get '/test/light_1/state/abracadabra'
33
+ assert last_response.bad_request?
34
+ end
35
+
36
+ def test_switch_element
37
+ get '/test/light_1/on'
38
+ assert last_response.ok?
39
+ get '/test/light_1/delay_off/1'
40
+ assert last_response.ok?
41
+ sleep 1.2
42
+ get '/test/light_1/delay_on/1'
43
+ assert last_response.ok?
44
+ sleep 1.2
45
+ 3.times do
46
+ sleep 1
47
+ get '/test/light_1/toggle'
48
+ assert last_response.ok?
49
+ end
50
+ get '/test/light_1/delay_toggle/1'
51
+ assert last_response.ok?
52
+ sleep 2
53
+ get '/test/light_1/off'
54
+ assert last_response.ok?
55
+ end
56
+
57
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: domotics-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - goredar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-28 00:00:00.000000000 Z
11
+ date: 2014-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,7 +52,29 @@ files:
52
52
  - Rakefile
53
53
  - domotics-core.gemspec
54
54
  - lib/domotics/core.rb
55
+ - lib/domotics/core/data/data_hash.rb
56
+ - lib/domotics/core/data/data_mongo.rb
57
+ - lib/domotics/core/data/data_redis.rb
58
+ - lib/domotics/core/device.rb
59
+ - lib/domotics/core/device/arduino_board.rb
60
+ - lib/domotics/core/element.rb
61
+ - lib/domotics/core/element/button.rb
62
+ - lib/domotics/core/element/dimmer.rb
63
+ - lib/domotics/core/element/motion_sensor.rb
64
+ - lib/domotics/core/element/reed_switch.rb
65
+ - lib/domotics/core/element/rgb_strip.rb
66
+ - lib/domotics/core/element/switch.rb
67
+ - lib/domotics/core/element_group.rb
68
+ - lib/domotics/core/helper.rb
69
+ - lib/domotics/core/room.rb
70
+ - lib/domotics/core/server.rb
71
+ - lib/domotics/core/setup.rb
55
72
  - lib/domotics/core/version.rb
73
+ - test/config.test.rb
74
+ - test/test_devices.rb
75
+ - test/test_elements.rb
76
+ - test/test_rooms.rb
77
+ - test/test_server.rb
56
78
  homepage: https://goredar.it
57
79
  licenses:
58
80
  - MIT
@@ -73,8 +95,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
95
  version: '0'
74
96
  requirements: []
75
97
  rubyforge_project:
76
- rubygems_version: 2.2.0
98
+ rubygems_version: 2.2.2
77
99
  signing_key:
78
100
  specification_version: 4
79
101
  summary: Home automation system.
80
- test_files: []
102
+ test_files:
103
+ - test/config.test.rb
104
+ - test/test_devices.rb
105
+ - test/test_elements.rb
106
+ - test/test_rooms.rb
107
+ - test/test_server.rb