hass-client 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 424e5b5945d9b3664fcd367db46bf2afe2f8473e4fa225cd0ee6824e7c2f0e76
4
+ data.tar.gz: 8b3191a2e3aaf64f253c0f957a01003a8258ac83bcae04320d31d58887d51d6c
5
+ SHA512:
6
+ metadata.gz: 8d8480e5f51bdc57622714871afca209e35943305e32015f7411b1d6305191ac3e1579065f534fda3c484142bc577d71ef6d5b6b4b505f86f778808514d74808
7
+ data.tar.gz: 8ea81f919350830449e0d4b6b4e71188cef3dde701f3a17730df3c777abcb2410123db9100917298b22faef273b8c8f8a928666c39d3a60dc399bf244c3dc09f
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # hass, Ruby client for HomeAssistant
2
+
3
+ hass is a simple Ruby client for the HomeAssistant API.
4
+
5
+ ## Installation
6
+
7
+ Install the gem via
8
+
9
+ ```bash
10
+ gem install hass
11
+ ```
12
+
13
+ or put it in your Gemfile
14
+
15
+ ```ruby
16
+ gem 'hass'
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Command line client
22
+
23
+ The command line client allows to send simple commands to your HomeAssistant instance.
24
+
25
+ You need to set the environment variables HASS_HOST, HASS_PORT, and HASS_TOKEN. To get your API token, log in to your HomeAssistant instance and go your profile (the initials in the sidebar). Then scroll down and ceate an authentication token.
26
+
27
+ To set these variables in the shell use:
28
+ ```
29
+ export HASS_HOST=your-hostname.example.com
30
+ export HASS_PORT=8123
31
+ export HASS_TOKEN=abcdef
32
+ export HASS_TLS=1
33
+ ```
34
+
35
+ HASS_SSL=1 ensures a TLS (SSL) connection to your server.
36
+
37
+ ```bash
38
+ hass-send <entity_id> <service>
39
+ ```
40
+
41
+ for example:
42
+
43
+ ```bash
44
+ hass-send light.living_room turn_on
45
+ hass-send light.living_room turn_off
46
+ hass-send light.living_room toggle
47
+ ```
48
+
49
+ Always use the full object name (with the dot) so the client can derive the object type.
50
+
51
+ ### Library
52
+
53
+ A simple script the toogle a light switch might look like this:
54
+
55
+ ```ruby
56
+ require 'hass/client'
57
+
58
+ client = Hass::Client.new('localhost', 8123, 'api_token')
59
+
60
+ light = client.light('light.living_room')
61
+ light.toggle
62
+ ```
data/bin/hass-send ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hass/client'
4
+
5
+ HOST = ENV['HASS_HOST'] ? ENV['HASS_HOST'].freeze : 'localhost'.freeze
6
+ PORT = ENV['HASS_PORT'] ? ENV['HASS_PORT'].to_i : 8123
7
+ TOKEN = ENV['HASS_TOKEN']
8
+
9
+ entity_id = ARGV[0]
10
+ domain = entity_id.split('.').first
11
+ service = ARGV[1]
12
+
13
+ hass = Hass::Client.new(HOST, PORT, TOKEN)
14
+ hass.send(domain.to_sym, entity_id).send(service.to_sym)
data/hass-ruby.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'hass-client'
6
+ spec.version = '0.1.0'
7
+ spec.authors = ['Gerrit Visscher']
8
+ spec.email = ['gerrit@visscher.de']
9
+ spec.summary = 'A small library to access Home Assistant.'
10
+ spec.description = 'Read and write to the HomeAssistant API. Control your smart home devices via Ruby/CLI.'
11
+ spec.homepage = 'https://github.com/kayssun/hass-ruby'
12
+ spec.license = 'MIT'
13
+
14
+ spec.files = `git ls-files -z`.split("\x0")
15
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(/^(test|spec|features)\//)
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_development_dependency 'bundler', '~> 1.7'
20
+ end
@@ -0,0 +1,98 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'json'
4
+ require 'hass/domain'
5
+
6
+ module Hass
7
+ # The HomeAssistant server
8
+ class Client
9
+ def initialize(host, port, token, base_path = '/api')
10
+ @host = host
11
+ @port = port
12
+ @token = token
13
+ @base_path = base_path
14
+ prepare_domains
15
+ prepare_methods
16
+ end
17
+
18
+ def get(path)
19
+ path = @base_path + path
20
+ request = Net::HTTP::Get.new path
21
+ header.each_pair { |field, content| request[field] = content }
22
+ response = send_http(request)
23
+ parse(response.body)
24
+ end
25
+
26
+ def post(path, data)
27
+ path = @base_path + path
28
+ request = Net::HTTP::Post.new path
29
+ request.body = data.to_json
30
+ header.each_pair { |field, content| request[field] = content }
31
+ response = send_http(request)
32
+ parse(response.body)
33
+ end
34
+
35
+ def send_http(request)
36
+ Net::HTTP.start(@host, @port, use_ssl: true) do |http|
37
+ response = http.request request
38
+ if response.code.to_i > 299
39
+ handle_http_error(response)
40
+ return {} # if handle does not throw an exception
41
+ end
42
+ return response
43
+ end
44
+ end
45
+
46
+ def handle_http_error(response)
47
+ raise "Server returned #{response.code}: #{response.body}"
48
+ end
49
+
50
+ def parse(response_text)
51
+ JSON.parse(response_text)
52
+ rescue JSON::ParserError => e
53
+ puts "Cannot parse JSON data: #{e}"
54
+ puts response_text
55
+ end
56
+
57
+ def header
58
+ {
59
+ 'Content-Type' => 'application/json',
60
+ 'Authorization' => "Bearer #{@token}",
61
+ 'Accept-Encoding' => 'identity'
62
+ }
63
+ end
64
+
65
+ def domains
66
+ @domains ||= get('/services')
67
+ end
68
+
69
+ def snake_to_camel(text)
70
+ text.split('_').collect(&:capitalize).join
71
+ end
72
+
73
+ def prepare_domains
74
+ domains.each do |domain|
75
+ domain_name = snake_to_camel(domain['domain'])
76
+ domain_class = Class.new(Domain)
77
+ domain_class.const_set('DATA', domain)
78
+ domain['services'].keys.each do |service|
79
+ domain_class.send(:define_method, service.to_sym) do |params = {}|
80
+ execute_service(service, params)
81
+ end
82
+ end
83
+ Hass.const_set(domain_name, domain_class)
84
+ end
85
+ end
86
+
87
+ def prepare_methods
88
+ domains.each do |domain|
89
+ domain_name = snake_to_camel(domain['domain'])
90
+ self.class.send(:define_method, domain['domain'].to_sym) do |entity_id|
91
+ domain_class = Hass.const_get(domain_name).new(entity_id)
92
+ domain_class.client = self
93
+ domain_class
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,47 @@
1
+ module Hass
2
+ # Base class for all domains (lights, switches, media_player...)
3
+ class Domain
4
+ attr_accessor :client
5
+ attr_reader :entity_id
6
+
7
+ # Just to make sure, the constant exists
8
+ DATA = {}.freeze
9
+
10
+ def initialize(entity_id)
11
+ @entity_id = entity_id
12
+ end
13
+
14
+ def required_fields(method_name)
15
+ data['services'][method_name]['fields'].keys.reject { |name| name == 'entity_id' }
16
+ end
17
+
18
+ def check_params(method_name, given_params)
19
+ required_fields(method_name).each do |required_field|
20
+ next if given_params.key?(required_field)
21
+
22
+ raise "Parameter #{required_field} might be missing. #{method_help(method_name)}"
23
+ end
24
+ end
25
+
26
+ # Returns a method description as a help text
27
+ def method_help(method_name)
28
+ param_help = required_fields(method_name).map { |name| "#{name}: #{name}_value" }
29
+ method_hint = "#{method_name}(#{param_help.join(', ')})"
30
+ fields = data['services'][method_name]['fields']
31
+ method_description = fields.keys.map { |field| "#{field}: #{fields[field]['description']}" }.join("\n")
32
+ "Hint: you can call this method with #{method_hint}\n#{method_description}"
33
+ end
34
+
35
+ def execute_service(service, params = {})
36
+ params['entity_id'] = @entity_id
37
+ @client.post("/services/#{data['domain']}/#{service}", params)
38
+ rescue RuntimeError => error
39
+ puts "Rescuing from #{error.class}: #{error}"
40
+ check_params(service, params)
41
+ end
42
+
43
+ def data
44
+ self.class.const_get('DATA')
45
+ end
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hass-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gerrit Visscher
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ description: Read and write to the HomeAssistant API. Control your smart home devices
28
+ via Ruby/CLI.
29
+ email:
30
+ - gerrit@visscher.de
31
+ executables:
32
+ - hass-send
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - Gemfile
37
+ - README.md
38
+ - bin/hass-send
39
+ - hass-ruby.gemspec
40
+ - lib/hass/client.rb
41
+ - lib/hass/domain.rb
42
+ homepage: https://github.com/kayssun/hass-ruby
43
+ licenses:
44
+ - MIT
45
+ metadata: {}
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.0.3
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: A small library to access Home Assistant.
65
+ test_files: []