Ha 0.3.1

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.
data/bin/pry ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'pry' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("pry", "pry")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rdebug-ide' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("ruby-debug-ide", "rdebug-ide")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'thor' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("thor", "thor")
data/exe/ha ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/ha'
4
+ Ha::CLI.start(ARGV)
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require "ha/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "Ha"
9
+ spec.version = Ha::VERSION
10
+ spec.authors = ["Pito Salas"]
11
+ spec.email = ["pitosalas@gmail.com"]
12
+
13
+ spec.summary = %q{Cli to control Home Automation}
14
+ spec.description = %q{A well behaved CLI for controlling Philips Hue and other home automation devices.}
15
+ spec.homepage = "http://www.salas.com"
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "pry-byebug"
24
+ spec.add_development_dependency "rake", '~> 0'
25
+ spec.add_development_dependency "minitest"
26
+ spec.add_development_dependency 'ruby-debug-ide', '~> 0'
27
+
28
+ spec.add_runtime_dependency "thor", '~> 0'
29
+ spec.add_runtime_dependency "faraday", '~> 0'
30
+ end
@@ -0,0 +1,22 @@
1
+ require "thor"
2
+ require 'pry-byebug'
3
+ require_relative "ha/version"
4
+ require_relative "ha/hue_command"
5
+ require_relative "ha/context"
6
+
7
+ module Ha
8
+ class CLI < Thor
9
+ desc "hello NAME", "say hello to NAME"
10
+
11
+ def hello(name)
12
+ puts "Hello #{name}"
13
+ end
14
+
15
+ desc "hue SUBCOMMAND", "commands for working with Philips Hue"
16
+ subcommand "hue", HueCommand
17
+
18
+ def self.exit_on_failure?
19
+ true
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,49 @@
1
+ class CliTable
2
+ attr_accessor :headers, :rows, :column_widths
3
+
4
+ def add(headers, rows)
5
+ @headers = headers
6
+ @rows = rows
7
+ end
8
+
9
+ def rows_count
10
+ @rows.length
11
+ end
12
+
13
+
14
+ def render
15
+ result = header_render + "\n"
16
+ @rows.each do
17
+ |row|
18
+ result << row_render(row) + "\n"
19
+ end
20
+ result
21
+ end
22
+
23
+ def header_render
24
+ result = ""
25
+ @headers.each_index { |i| result << (" %#{column_widths[i]}s" % headers[i]) }
26
+ result
27
+ end
28
+
29
+ def row_render row
30
+ result = ""
31
+ @headers.each_index { |i| result << (" %#{column_widths[i]}s" % format_value(row[i])) }
32
+ result
33
+ end
34
+
35
+ def reset
36
+ @rows = []
37
+ end
38
+
39
+ private
40
+
41
+ def format_value(val)
42
+ if [true, false].include? val
43
+ val ? "Yes" : "No"
44
+ elsif val.nil?
45
+ val = "nil"
46
+ end
47
+ val
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ require "yaml"
2
+
3
+ class Context
4
+ FILE_SPEC = "#{Dir.home}/.ha"
5
+ attr_accessor :context
6
+
7
+ def initialize
8
+ if File.exist? FILE_SPEC
9
+ myfile = File.open(FILE_SPEC, "r")
10
+ @context = YAML.load(myfile.read)
11
+ end
12
+ @context ||= {}
13
+ end
14
+
15
+ def save
16
+ puts "saving context"
17
+ myfile = File.open(FILE_SPEC, "w")
18
+ myfile.write(@context.to_yaml)
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ class Super
2
+ def initialize(number)
3
+ puts "Super #{number}"
4
+ end
5
+ end
6
+
7
+ class Sub < Super
8
+ def initialize(number)
9
+ super
10
+ puts "Sub #{number}"
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "hue_resource"
2
+ class Group < HueResource
3
+ attr_reader :detail, :on, :name, :lights
4
+ def initialize(key, hashvalue)
5
+ super
6
+ @lights = hashvalue["lights"]
7
+ @lights_s = hashvalue["lights"].join(",")
8
+ gen_reskey("g")
9
+ @state.merge! ({"on" =>@lights_s, "name" => @name, "detail" => @detail})
10
+ end
11
+
12
+ def array(selectors)
13
+ selectors.map { |key| @state[key] }
14
+ end
15
+
16
+ def self.owning(number, grouparray)
17
+ grouparray.select { |group| group.lights.include? number}
18
+ end
19
+ end
@@ -0,0 +1,78 @@
1
+ require 'json'
2
+ require 'faraday'
3
+ require_relative 'sensor'
4
+ require_relative 'light'
5
+ require_relative 'group'
6
+ require_relative 'rule'
7
+
8
+ BRIDGE_IP = "10.0.0.89"
9
+ USERNAME = "78UEGUotX3otmWxbhiucELCLiiKmaD9E2O5YW-d1"
10
+
11
+
12
+ class Hue
13
+
14
+ def initialize(context, bridge_state)
15
+ @context = context
16
+ @bridge_state = bridge_state
17
+ @groups = groups
18
+ @sensors = sensors
19
+ @lights = lights
20
+ end
21
+
22
+ def self.bridge_state
23
+ unless defined? @bridge_state
24
+ conn = Faraday.new(url: "http://#{BRIDGE_IP}")
25
+ get = conn.get("/api/#{USERNAME}/")
26
+ @bridge_state = JSON.parse(get.body)
27
+ end
28
+ @bridge_state
29
+ end
30
+
31
+ def pair
32
+ @context[:useraccount] = "12345"
33
+ @context.save
34
+ end
35
+
36
+ def sensors
37
+ parsed = @bridge_state["sensors"]
38
+ parsed.to_a.map { |sensor_pair| Sensor.new(*sensor_pair)}
39
+ end
40
+
41
+ def lights
42
+ parsed = @bridge_state["lights"]
43
+ parsed.to_a.map { |light_pair| Light.new(*light_pair, @groups)}
44
+ end
45
+
46
+ def groups
47
+ parsed = @bridge_state["groups"]
48
+ parsed.to_a.map { |group_pair| Group.new(*group_pair)}
49
+ end
50
+
51
+ def rules
52
+ parsed = @bridge_state["rules"]
53
+ parsed.to_a.map { |rule_pair| Rule.new(*rule_pair)}
54
+ end
55
+
56
+ def all_a selector
57
+ items = sensors_a selector
58
+ items += lights_a selector
59
+ items += groups_a selector
60
+ items += rules_a selector
61
+ end
62
+
63
+ def sensors_a selector
64
+ sensors.map { |sensor| sensor.array(selector)}
65
+ end
66
+
67
+ def lights_a selector
68
+ lights.map { |light| light.array(selector)}
69
+ end
70
+
71
+ def groups_a selector
72
+ groups.map { |group| group.array(selector)}
73
+ end
74
+
75
+ def rules_a selector
76
+ rules.map { |rule| rule.array(selector)}
77
+ end
78
+ end
@@ -0,0 +1,34 @@
1
+ require_relative 'cli_table'
2
+ require_relative 'hue'
3
+
4
+ # The 'hue' commmands
5
+ class HueCommand < Thor
6
+ desc "list", "list Hue items"
7
+ def list
8
+ hue = Hue.new(context, Hue.bridge_state)
9
+ table = CliTable.new
10
+ table.headers = list_headers
11
+ table.rows = hue.all_a list_headers
12
+ table.column_widths = column_widths
13
+ puts table.render
14
+ end
15
+
16
+ desc "pair", "run pairing process to allow you to access the hue bridge"
17
+ def pair
18
+ Hue.new(context).pair
19
+ end
20
+
21
+ private
22
+
23
+ def context
24
+ Context.new
25
+ end
26
+
27
+ def list_headers
28
+ ["id", "name", "on", "detail"]
29
+ end
30
+
31
+ def column_widths
32
+ ["5", "22", "12", "-30"]
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ class HueResource
2
+ def initialize(key, hash_value)
3
+ @hash_value = hash_value
4
+ @lastupdated = @hash_value.dig("state", "lastupdated")
5
+ @detail = hash_value["type"]
6
+ @name = hash_value["name"]
7
+ @state = { "key" => key, "lastupdated" => @lastupdated, "detail" => @detail, "name" => @name}
8
+ end
9
+
10
+ def gen_reskey(detail)
11
+ @state.merge!({ "id" => detail + @state["key"]})
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ require_relative "hue_resource"
2
+ class Light < HueResource
3
+
4
+ def initialize(key, hashvalue, grouparray)
5
+ super(key, hashvalue)
6
+ @grouparray = grouparray
7
+ @onstate = hashvalue.dig("state", "on")
8
+ @brightstate = hashvalue.dig("state", "bri")
9
+ @combinedsate = "#{@onstate} (#{@brightstate})"
10
+ @number = key
11
+ @group = Group.owning(key, grouparray)
12
+ gen_reskey("l")
13
+ build_name
14
+ @state.merge! ({"on" => @combinedsate})
15
+ end
16
+
17
+ def build_name
18
+ @state["detail"] += " in " + Group.owning(@number, @grouparray).first.name
19
+ end
20
+
21
+ def array(selectors)
22
+ selectors.map { |key| @state[key] }
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "hue_resource"
2
+ class Rule < HueResource
3
+
4
+ attr_reader :detail, :on, :name, :lights
5
+ def initialize(key, hashvalue)
6
+ super(key, hashvalue)
7
+ @detail = "#{hashvalue["conditions"].length} conds => #{hashvalue["actions"].length} acts"
8
+ @state.merge! ({"on" => @hash_value["status"], "name" => @name, "detail" => @detail})
9
+ gen_reskey("r")
10
+ end
11
+
12
+ def array(selectors)
13
+ selectors.map { |key| @state[key] }
14
+ end
15
+
16
+ def self.owning(number, grouparray)
17
+ grouparray.select { |group| group.lights.include? number}
18
+ end
19
+ end