ruby-tado 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3e1847ff3773eb48da88d21baa51c630b8f535ffb04e6be296c6c44589f1d564
4
+ data.tar.gz: 2d1b7f5a83730ceeb3439b9fa0ecff22391bff8425ba88369b48468f8d373eb4
5
+ SHA512:
6
+ metadata.gz: e68e08a5fe1f854670e25309099cc7d459f57eab2fbec5ac5d7eeb967c29f1cb612e86112a483074dc5ae39a9daa90d58d0eaddbb5ad51643d7ade27d3193cd7
7
+ data.tar.gz: 6332814e981ecf1c9c6ce0630a52eab195393bbfb8a9fe0a1c8c3bd56aaa99dc90eb12f5955dd30c40af7eb764e7909a2366c961f5defd12e7dbc344b209aa9f
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # -*- ruby -*-
2
+ source 'https://rubygems.org'
3
+
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 sdalu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,122 @@
1
+ require 'json'
2
+ require 'date'
3
+ require 'faraday'
4
+ require 'oauth2'
5
+ require 'faraday_middleware/oauth2_refresh'
6
+
7
+
8
+
9
+ #
10
+ # Access to the Tado API
11
+ #
12
+ # Unoffical API documentation is provided by:
13
+ # * https://shkspr.mobi/blog/2019/02/tado-api-guide-updated-for-2019/
14
+ # * http://blog.scphillips.com/posts/2017/01/the-tado-api-v2/
15
+ #
16
+ # Web app client id/secret are retrieved from:
17
+ # * https://my.tado.com/webapp/env.js
18
+ #
19
+ # Example:
20
+ #
21
+ # require 'tado'
22
+ #
23
+ # $t = Tado.new('login', 'password')
24
+ # puts $t.getHomes.first.getZones.first.getHistory.inspect
25
+ #
26
+ class Tado
27
+ # Client ID used the the Tado web application
28
+ CLIENT_ID = 'tado-web-app'
29
+ # Client secret used by the Tado web application
30
+ CLIENT_SECRET = 'wZaRN7rpjn3FoNyF5IFuxg9uMzYJcvOo' \
31
+ 'Q8QWiIqS3hfk6gLhVlG57j5YNoZL2Rtc'
32
+ # Site used sefor authentication
33
+ AUTH_SITE = 'https://auth.tado.com'.freeze
34
+ # Site used for API requests
35
+ API_SITE = 'https://my.tado.com'.freeze
36
+
37
+
38
+ # Name of Tado owner
39
+ #
40
+ # @return [String]
41
+ attr_reader :name
42
+
43
+
44
+ # Email of Tado owner
45
+ #
46
+ # @return [String]
47
+ attr_reader :email
48
+
49
+
50
+ # Username of Tado owner
51
+ #
52
+ # @return [String]
53
+ attr_reader :username
54
+
55
+
56
+ # Create a new instance of Tado
57
+ #
58
+ # @param [String] username account username
59
+ # @param [String] password account password
60
+ # @param [String] client_id client id used for oauth2
61
+ # @param [String] client_secret client secret used for oauth2
62
+ def initialize(username, password,
63
+ client_id: CLIENT_ID, client_secret: CLIENT_SECRET)
64
+ tclient = OAuth2::Client.new(client_id, client_secret, site: AUTH_SITE)
65
+ @token = tclient.password.get_token(username, password)
66
+
67
+ @client = Faraday.new(url: API_SITE) do |f|
68
+ f.request :oauth2_refresh, @token
69
+ f.request :url_encoded
70
+ f.adapter :net_http
71
+ end
72
+
73
+ refresh!
74
+ end
75
+
76
+
77
+ # Retrieve list of homes associated with the tado account
78
+ #
79
+ # @return [Array<Home>] list of homes
80
+ def getHomes
81
+ v2_get('me').dig('homes').map {|h|
82
+ Home.new( h.dig('id'), tado: self )
83
+ }
84
+ end
85
+
86
+ # Shortcut to get zone history from home/zone identifiers.
87
+ # @see Zone#getHistory
88
+ #
89
+ # @param home [Integer] home identifier
90
+ # @param zone [Integer] zone identifier
91
+ # @return [Hash{Symbol => Object}] history of the given day.
92
+ def getHistory(date = Date.today, home:, zone:)
93
+ History.get(date, home: home, zone: zone, tado: self)
94
+ end
95
+
96
+ # Force a refresh of tado attributs
97
+ #
98
+ # @return [self]
99
+ def refresh!
100
+ data = v2_get('me')
101
+ @name = data.dig('name')
102
+ @email = data.dig('email')
103
+ @username = data.dig('username')
104
+ self
105
+ end
106
+
107
+
108
+ # Retrieve a resource as a JSON
109
+ #
110
+ # @param [String] resource relative to the v2 API
111
+ # @return a JSON structure
112
+ def v2_get(resource, **opts)
113
+ JSON.parse(@client.get("/api/v2/#{resource}", **opts).body)
114
+ end
115
+ end
116
+
117
+ require_relative 'tado/version'
118
+ require_relative 'tado/errors'
119
+ require_relative 'tado/home'
120
+ require_relative 'tado/zone'
121
+ require_relative 'tado/device'
122
+ require_relative 'tado/history'
@@ -0,0 +1,107 @@
1
+ class Tado
2
+
3
+ #
4
+ # Class wrapping interaction with the Tado Device
5
+ #
6
+ class Device
7
+ # Create a device instance associated to the zone from
8
+ # the JSON returned by the Tado API
9
+ #
10
+ # @param json [Object] json returned by the Tado API
11
+ # @param zone [Zone ] zone to which this device belongs
12
+ def self.from_json(json, zone:)
13
+ self.new(json.dig('serialNo'),
14
+ data: self.parse_from_json(json),
15
+ zone: zone)
16
+ end
17
+
18
+
19
+ # Serial number of device
20
+ #
21
+ # @return [String] serial number
22
+ attr_reader :serial
23
+
24
+
25
+ # Type of device (heating, airconditioning)
26
+ #
27
+ # @return [:HEATING] heating device
28
+ # @return [Symbol]
29
+ attr_reader :type
30
+
31
+
32
+ # Current firmware version
33
+ #
34
+ # @return [String] firmware version
35
+ attr_reader :firmware
36
+
37
+
38
+ # Duties performed by the device
39
+ #
40
+ # @return [Array<Symbol>] list of duties
41
+ # * [:ZONE_UI ] user interface
42
+ # * [:ZONE_DRIVER] drive the heating/aircon
43
+ # * [:ZONE_LEADER] used as reference for temperature
44
+ attr_reader :duties
45
+
46
+
47
+ # Battery status
48
+ #
49
+ # @return [:NORMAL]
50
+ attr_reader :battery
51
+
52
+
53
+ # Capabilities
54
+ #
55
+ # @return [Array<Symbol>] list of capabilities
56
+ # * [:INSIDE_TEMPERATURE_MEASUREMENT] temperature
57
+ # * [:IDENTIFY ] identify
58
+ attr_reader :capabilities
59
+
60
+
61
+ # Time of device calilbration
62
+ #
63
+ # @return [nil ] device was not calibrated
64
+ # @return [Time] timestamp of device calibration
65
+ attr_reader :calibrated
66
+
67
+
68
+ # Time of last connection
69
+ #
70
+ # @return [nil ] device was not connected
71
+ # @return [Time] timestamp of last device connection
72
+ attr_reader :connection
73
+
74
+
75
+ # Create a device instance associated to the zone
76
+ #
77
+ # @param serial [String] device serial number
78
+ # @param zone [Zone ] zone to which this device belongs
79
+ # @param data [Hash{Symbol => Object}] initialising data
80
+ def initialize(serial, data: nil, zone:)
81
+ @zone = zone
82
+ @serial = serial
83
+
84
+ data&.each {|k, v| instance_variable_set("@#{k}", v) }
85
+ end
86
+
87
+ private
88
+
89
+ def self.parse_from_json(json)
90
+ {
91
+ :type => json.dig('deviceType'),
92
+ :firmware => json.dig('currentFwVersion'),
93
+ :duties => json.dig('duties').map(&:to_sym),
94
+ :battery => json.dig('batteryState').to_sym,
95
+ :capabilities => json.dig('characteristics', 'capabilities')
96
+ .map(&:to_sym),
97
+ :calibrated => if json.dig('mountingState', 'value') == 'CALIBRATED'
98
+ Time.parse(json.dig('mountingState', 'timestamp'))
99
+ end,
100
+ :connection => if json.dig('connectionState', 'value')
101
+ Time.parse(json.dig('connectionState', 'timestamp'))
102
+ end,
103
+ }
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,12 @@
1
+ class Tado
2
+
3
+
4
+ # Standard Tado error
5
+ class Error < StandardError
6
+ end
7
+
8
+ # Error related to the parsing/processing of Tado data
9
+ class ParsingError < Error
10
+ end
11
+
12
+ end
@@ -0,0 +1,92 @@
1
+ class Tado
2
+ module History
3
+ def self.get(date = Date.today, home: nil, zone: nil, tado: nil)
4
+ date = case date
5
+ when Date then date.strftime('%Y-%m-%d')
6
+ when /^\d{4}-\d{2}-\d{2}$/ then date
7
+ else raise ArgumentError
8
+ end
9
+
10
+ report = if Zone === zone
11
+ zone.v2_get("dayReport", date: date)
12
+ elsif Tado === tado && Integer === home && Integer === zone
13
+ tado.v2_get("homes/#{home}/zones/#{zone}/dayReport", date: date)
14
+ else
15
+ raise ArgumentError
16
+ end
17
+ self.parse_from_json(report)
18
+ end
19
+
20
+
21
+ private
22
+
23
+ MAPPING = [ {
24
+ name: [ :weather, :condition ],
25
+ series: [ 'weather', 'condition' ],
26
+ fields: [ 'state',
27
+ [ 'temperature', 'celsius' ] ] },
28
+ { name: [ :weather, :sunny ],
29
+ series: [ 'weather', 'sunny' ] },
30
+ { name: [ :weather, :slots ],
31
+ series: [ 'weather', 'slots' ],
32
+ fields: [ 'state', [ 'temperature', 'celsius' ] ] },
33
+ { name: :settings,
34
+ series: [ 'settings' ],
35
+ fields: [ 'type', 'power',
36
+ [ 'temperature', 'celsius' ] ] },
37
+ { name: [ :measured, :temperature ],
38
+ series: [ 'measuredData', 'insideTemperature' ],
39
+ fields: [ 'celsius' ] },
40
+ { name: [ :measured, :humidity ],
41
+ series: [ 'measuredData', 'humidity' ] },
42
+ { name: [ :measured, :device_connected ],
43
+ series: [ 'measuredData', 'measuringDeviceConnected' ] },
44
+ { name: :stripes,
45
+ series: [ 'stripes' ],
46
+ fields: [ 'stripeType',
47
+ [ 'setting', 'type' ],
48
+ [ 'setting', 'power' ],
49
+ [ 'setting', 'temperature', 'celsius' ] ] },
50
+ { name: :heating,
51
+ series: [ 'callForHeat' ] }
52
+ ]
53
+
54
+
55
+ def self.parse_from_json(json)
56
+ h = {}
57
+ MAPPING.each {|name:, series:, fields: nil|
58
+ e = h
59
+ fields = [ nil ] if fields.nil? || fields.empty?
60
+ name = Array(name)
61
+ name[0..-2].each {|n| e = e[n] ||= {} }
62
+ e[name.last] = History.timeSeries(json.dig(*series), fields)
63
+ }
64
+ h
65
+ end
66
+
67
+
68
+ def self.timeSeries(json, template)
69
+ case type = json.dig('timeSeriesType')
70
+ when 'slots'
71
+ json.dig('slots').transform_values {|v|
72
+ template.map {|fields| v.dig(*fields) }
73
+ }
74
+
75
+ when 'dataIntervals'
76
+ json.dig('dataIntervals').map {|v|
77
+ [ Time.parse(v.dig('from')) .. Time.parse(v.dig('to')) ] +
78
+ template.map {|fields| v.dig('value', *fields) }
79
+ }
80
+
81
+ when 'dataPoints'
82
+ json.dig('dataPoints').map {|v|
83
+ [ Time.parse(v.dig('timestamp')) ] +
84
+ template.map {|fields| v.dig('value', *fields) }
85
+ }
86
+
87
+ else raise ParsingError
88
+ end
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,142 @@
1
+ require 'time'
2
+ require 'tzinfo'
3
+
4
+ class Tado
5
+
6
+ #
7
+ # Class wrapping interaction with the Tado Home
8
+ #
9
+ class Home
10
+ # Create a new Home associated to the Tado account
11
+ #
12
+ # @param id [Integer]
13
+ # @param tado [Tado]
14
+ def initialize(id, tado:)
15
+ @tado = tado
16
+ @id = id
17
+
18
+ refresh!
19
+ end
20
+
21
+
22
+ # Retrieve weather information associated with this home
23
+ #
24
+ # @return [Hash<Symbol,Object>] weather information
25
+ # * :solar_intensity [Float]
26
+ # * :temperature [Float]
27
+ # * :state [Symbol]
28
+ # * :timestamp [Time]
29
+ def getWeather
30
+ w = v2_get('weather')
31
+
32
+ ts = [ 'solarIntensity', 'outsideTemperature', 'weatherState' ]
33
+ .map {|k| w.dig(k, 'timestamp') }.uniq
34
+ raise ParsingError if ts.size != 1
35
+
36
+ { :solar_intensity => w.dig('solarIntensity', 'percentage'),
37
+ :temperature => w.dig('outsideTemperature', 'celsius'),
38
+ :state => w.dig('weatherState', 'value').to_sym,
39
+ :timestamp => Time.parse(ts.first)
40
+ }
41
+ end
42
+
43
+
44
+ # Retrieve zones associated with this home
45
+ #
46
+ # @return [Array<Zone>] list of zones
47
+ def getZones
48
+ v2_get('zones').map{|data|
49
+ Zone.from_json(data, home: self)
50
+ }
51
+ end
52
+
53
+
54
+ # Home identifier
55
+ #
56
+ # @return [Integer] identifier
57
+ attr_reader :id
58
+
59
+
60
+ # Timezone
61
+ #
62
+ # @return [TZInfo::Timezone] timezone
63
+ attr_reader :tz
64
+
65
+
66
+ # Creation time
67
+ #
68
+ # @return [Time] timestamp
69
+ attr_reader :created
70
+
71
+
72
+ # Name of home
73
+ #
74
+ # @return [String] name
75
+ attr_reader :name
76
+
77
+
78
+ # Geolocalisation (GPS coordinate)
79
+ #
80
+ # @return [Array<Numeric>] latitude/longitude
81
+ attr_reader :geoloc
82
+
83
+
84
+ # Distance to consider user away from home
85
+ #
86
+ # @return [Float] distance in meters
87
+ attr_reader :away_radius
88
+
89
+
90
+ # Home address
91
+ #
92
+ # @return [Hash{Symbol => String,Array<String>}] home address
93
+ # * :address [Array<String>] address
94
+ # * :zipcode [String] zip code
95
+ # * :city [String] city
96
+ # * :state [String] state
97
+ # * :country [String] country (iso3)
98
+ attr_reader :address
99
+
100
+
101
+ # Home contact
102
+ #
103
+ # @return [Hash{Symbol => String}] contact information
104
+ # * :name [String] contact name
105
+ # * :email [String] email address
106
+ # * :phone [String] phone number
107
+ attr_reader :contact
108
+
109
+
110
+ # Force a refresh of home attributs
111
+ #
112
+ # @return [self]
113
+ def refresh!
114
+ data = @tado.v2_get("homes/#{@id}")
115
+ @tz = TZInfo::Timezone.get(data.dig('dateTimeZone'))
116
+ @created = Time.parse(data.dig('dateCreated'))
117
+ @name = data.dig('name')
118
+ @geoloc = [ data.dig('geolocation', 'latitude'),
119
+ data.dig('geolocation', 'longitude') ]
120
+ @away_radius = data.dig('awayRadiusInMeters')
121
+ @address = {
122
+ :address => [ data.dig('address', 'addressLine1'),
123
+ data.dig('address', 'addressLine2') ].compact,
124
+ :zipcode => data.dig('address', 'zipCode'),
125
+ :city => data.dig('address', 'city'),
126
+ :state => data.dig('address', 'state'),
127
+ :country => data.dig('address', 'country')
128
+ }.compact
129
+ @contact = data.dig('contactDetails').transform_keys(&:to_sym)
130
+ self
131
+ end
132
+
133
+
134
+ # Retrieve a resource as a JSON
135
+ #
136
+ # @param [String] resource relative to the home
137
+ # @return a JSON structure
138
+ def v2_get(resource, **opts)
139
+ @tado.v2_get("homes/#{@id}/#{resource}", **opts)
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,4 @@
1
+ class Tado
2
+ # Version of Tado ruby implementation
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,126 @@
1
+ class Tado
2
+ #
3
+ # Class wrapping interaction with the Tado Zone
4
+ #
5
+ class Zone
6
+ # Create a zone instance associated to the home from
7
+ # the JSON returned by the Tado API
8
+ #
9
+ # @param json [Object] json returned by the Tado API
10
+ # @param home [Home ] home to which this zone belongs
11
+ def self.from_json(json, home:)
12
+ self.new(json.dig('id'),
13
+ data: self.parse_from_json(json),
14
+ home: home)
15
+ end
16
+
17
+
18
+ # Zone identifier
19
+ #
20
+ # @return [Integer] identifier
21
+ attr_reader :id
22
+
23
+
24
+ # Name of zone
25
+ #
26
+ # @return [String] name
27
+ attr_reader :name
28
+
29
+
30
+ # Type of zone (heating, airconditioning)
31
+ #
32
+ # @return [:HEATING] heating zone
33
+ # @return [Symbol]
34
+ attr_reader :type
35
+
36
+
37
+ # Devices associated with this zone
38
+ #
39
+ # @return [Array<Device>] list of devices
40
+ attr_reader :devices
41
+
42
+
43
+ # Dazze mode
44
+ #
45
+ # @return [nil] not supported
46
+ # @return [Boolean] is dazzle mode enabled
47
+ attr_reader :dazzle
48
+
49
+
50
+ # Open window detection
51
+ #
52
+ # @return [nil] not supported
53
+ # @return [Boolean] is window detection enabled
54
+ attr_reader :open_window
55
+
56
+
57
+ # Open window timeout
58
+ #
59
+ # @return [nil] not supported
60
+ # @return [Integer] is window detection enabled
61
+ attr_reader :open_window_timeout
62
+
63
+
64
+ # Create a zone instance associated to the home
65
+ #
66
+ # @param id [String] home identifier
67
+ # @param home [Home ] home to which this zone belongs
68
+ # @param data [Hash{Symbol => Object}] initialising data
69
+ def initialize(id, data:, home:)
70
+ @home = home
71
+ @id = id
72
+
73
+ data&.each {|k, v| instance_variable_set("@#{k}", v) }
74
+ end
75
+
76
+
77
+ # Retrieve history (temperature, humidity, ...) for this zone
78
+ #
79
+ # @param date [String] date in yyyy-mm-dd format
80
+ # @return [Hash{Symbol => Object}] history of the given day.
81
+ # For example:
82
+ # { :wheather => {
83
+ # :condition => [ [ timeFrom..timeTo, state, temperature ]... ],
84
+ # :sunny => [ [ timeFrom..timeTo, is_sunny ]... ],
85
+ # :slots => { 'hh:mm' => [ state, temperature ] } },
86
+ # :settings => [ [ timeFrom..timeTo, type, powered, temperature ]... ],
87
+ # :measure => {
88
+ # :temperature => [ [ time, temperature ]... ],
89
+ # :humidity => [ [ time, humidity ]... ],
90
+ # :device_connected => [ [ timeFrom..timeTo, is_connected ]... ] },
91
+ # :stripes => [ [ timeFrom..timeTo, mode, type, is_powerd, temperature ]... ]
92
+ # }
93
+ #
94
+ def getHistory(date = Date.today)
95
+ History.get(date, zone: self)
96
+ end
97
+
98
+
99
+ # Retrieve a resource as a JSON
100
+ #
101
+ # @param [String] resource relative to the zone
102
+ # @return a JSON structure
103
+ def v2_get(resource, **opts)
104
+ @home.v2_get("zones/#{@id}/#{resource}", **opts)
105
+ end
106
+
107
+
108
+ private
109
+
110
+ def self.parse_from_json(json)
111
+ supported_enabled = ->(d) {
112
+ d.dig('supported') ? d.dig('enabled') : nil }
113
+ {
114
+ :name => json.dig('name'),
115
+ :type => json.dig('type'),
116
+ :created => Time.parse(json.dig('dateCreated')),
117
+ :devices => json.dig('devices').map {|data|
118
+ Device.from_json(data, zone: self) },
119
+ :dazzle => supported_enabled.(json.dig('dazzleMode')),
120
+ :open_window => supported_enabled.(json.dig('openWindowDetection')),
121
+ :open_window_timeout => json.dig('openWindowDetection', 'timeoutInSeconds'),
122
+ }
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.unshift File.expand_path("../lib", __FILE__)
3
+ require "tado/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "ruby-tado"
7
+ s.version = Tado::VERSION
8
+ s.authors = [ "Stephane D'Alu" ]
9
+ s.email = [ "sdalu@sdalu.com" ]
10
+ s.homepage = "http://gitlab.com/sdalu/ruby-tado"
11
+ s.summary = "Access to Tado API"
12
+
13
+ s.add_dependency 'faraday'
14
+ s.add_dependency 'oauth2'
15
+ s.add_dependency 'faraday_middleware-oauth2_refresh'
16
+ s.add_dependency 'tzinfo'
17
+ s.add_dependency 'tzinfo-data'
18
+
19
+
20
+ s.add_development_dependency "yard"
21
+ s.add_development_dependency "rake"
22
+ s.add_development_dependency "redcarpet"
23
+
24
+ s.license = 'MIT'
25
+
26
+ s.files = %w[ LICENSE Gemfile ruby-tado.gemspec ] +
27
+ Dir['lib/**/*.rb']
28
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-tado
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Stephane D'Alu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: oauth2
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday_middleware-oauth2_refresh
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tzinfo
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: tzinfo-data
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: yard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: redcarpet
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description:
126
+ email:
127
+ - sdalu@sdalu.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - Gemfile
133
+ - LICENSE
134
+ - lib/tado.rb
135
+ - lib/tado/device.rb
136
+ - lib/tado/errors.rb
137
+ - lib/tado/history.rb
138
+ - lib/tado/home.rb
139
+ - lib/tado/version.rb
140
+ - lib/tado/zone.rb
141
+ - ruby-tado.gemspec
142
+ homepage: http://gitlab.com/sdalu/ruby-tado
143
+ licenses:
144
+ - MIT
145
+ metadata: {}
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubygems_version: 3.0.4
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: Access to Tado API
165
+ test_files: []