atrea_control 1.2.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile.lock +22 -15
- data/README.md +50 -16
- data/atrea_control.gemspec +16 -12
- data/config/locales/cs.yml +269 -0
- data/config/locales/de.yml +269 -0
- data/config/locales/en.yml +269 -0
- data/lib/atrea_control/duplex/login.rb +107 -0
- data/lib/atrea_control/duplex/request.rb +29 -0
- data/lib/atrea_control/duplex/unit.rb +93 -0
- data/lib/atrea_control/duplex/user_ctrl.rb +138 -0
- data/lib/atrea_control/duplex.rb +6 -197
- data/lib/atrea_control/logger.rb +11 -0
- data/lib/atrea_control/sensor_parser.rb +63 -0
- data/lib/atrea_control/version.rb +3 -1
- data/lib/atrea_control.rb +6 -11
- metadata +28 -8
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtreaControl
|
4
|
+
module Duplex
|
5
|
+
# Communication with RD5 unit (read/write)
|
6
|
+
class Unit
|
7
|
+
include AtreaControl::Logger
|
8
|
+
|
9
|
+
attr_reader :current_mode, :current_power, :outdoor_temperature
|
10
|
+
# @return [DateTime] store time of last update
|
11
|
+
attr_reader :valid_for
|
12
|
+
# @return [UserCtrl]
|
13
|
+
attr_reader :user_ctrl
|
14
|
+
|
15
|
+
# @param [String, Integer] user_id
|
16
|
+
# @param [String, Integer] unit_id
|
17
|
+
# @param [String, Integer] sid
|
18
|
+
# @param [AtreaControl::Duplex::UserCtrl] user_ctrl
|
19
|
+
def initialize(user_id:, unit_id:, sid:, user_ctrl: nil)
|
20
|
+
@user_id = user_id
|
21
|
+
@unit_id = unit_id
|
22
|
+
@sid = sid
|
23
|
+
@user_ctrl = user_ctrl || UserCtrl.new(user_id: user_id, unit_id: unit_id, sid: sid)
|
24
|
+
end
|
25
|
+
|
26
|
+
def name
|
27
|
+
@user_ctrl.name
|
28
|
+
end
|
29
|
+
|
30
|
+
def mode
|
31
|
+
current_mode || values[:current_mode]
|
32
|
+
end
|
33
|
+
|
34
|
+
def power
|
35
|
+
current_power || values[:current_power]
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param [String] value 0 - power-off; 1 - automat
|
39
|
+
def mode=(value)
|
40
|
+
v = parser.input(@user_ctrl.sensors["mode_input"], value.to_s)
|
41
|
+
write(v)
|
42
|
+
end
|
43
|
+
|
44
|
+
def power=(value)
|
45
|
+
v = [parser.input(@user_ctrl.sensors["power_input"], value.to_s)]
|
46
|
+
v << parser.input(@user_ctrl.sensors["mode_switch"], "2")
|
47
|
+
write(v)
|
48
|
+
end
|
49
|
+
|
50
|
+
def values
|
51
|
+
parser.values(read.body).each do |name, value|
|
52
|
+
instance_variable_set :"@#{name}", value
|
53
|
+
end
|
54
|
+
as_json
|
55
|
+
end
|
56
|
+
|
57
|
+
def as_json(_options = nil)
|
58
|
+
{
|
59
|
+
current_mode: current_mode,
|
60
|
+
current_power: current_power,
|
61
|
+
outdoor_temperature: outdoor_temperature,
|
62
|
+
valid_for: valid_for,
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_json(*args)
|
67
|
+
values.to_json(*args)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def parser
|
73
|
+
@parser ||= ::AtreaControl::SensorParser.new(@user_ctrl)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Request to RD5
|
77
|
+
def request
|
78
|
+
@request ||= Request.new(user_id: @user_id, unit_id: @unit_id, sid: @sid)
|
79
|
+
end
|
80
|
+
|
81
|
+
def read
|
82
|
+
request.call(_t: "config/xml.xml")
|
83
|
+
end
|
84
|
+
|
85
|
+
# @param [Array<String>] values in format SENSOR0000VALUE
|
86
|
+
def write(*values)
|
87
|
+
inputs = values.to_h { |i| [i, nil] }
|
88
|
+
logger.debug("set RD5 #{inputs.keys}")
|
89
|
+
request.call({ _t: "config/xml.cgi", _w: 1 }.merge(inputs))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require "nokogiri"
|
2
|
+
|
3
|
+
module AtreaControl
|
4
|
+
module Duplex
|
5
|
+
# Parse `userCtrl.xml`
|
6
|
+
class UserCtrl
|
7
|
+
# @param (see #initialize)
|
8
|
+
def self.data(user_id:, unit_id:, sid:)
|
9
|
+
user_ctrl = new(user_id: user_id, unit_id: unit_id, sid: sid)
|
10
|
+
{
|
11
|
+
name: user_ctrl.name,
|
12
|
+
sensors: user_ctrl.sensors,
|
13
|
+
modes: user_ctrl.modes,
|
14
|
+
user_modes: user_ctrl.user_modes,
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [String, Integer] user_id
|
19
|
+
# @param [String, Integer] unit_id
|
20
|
+
# @param [String, Integer] sid
|
21
|
+
def initialize(user_id:, unit_id:, sid:)
|
22
|
+
@user_id = user_id
|
23
|
+
@unit_id = unit_id
|
24
|
+
@sid = sid
|
25
|
+
end
|
26
|
+
|
27
|
+
# Request to RD5
|
28
|
+
def request
|
29
|
+
@request ||= Request.new(user_id: @user_id, unit_id: @unit_id, sid: @sid)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get and parse XML with user/unit configuration source
|
33
|
+
def user_ctrl
|
34
|
+
response = request.call(_t: "lang/userCtrl.xml")
|
35
|
+
Nokogiri::XML response.body
|
36
|
+
end
|
37
|
+
|
38
|
+
# User-defined name of home (RD5 unit)
|
39
|
+
# @return [String]
|
40
|
+
def name
|
41
|
+
@name ||= user_texts["UnitName"]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Gather ID of sensors based on userCtrl config file
|
45
|
+
def sensors
|
46
|
+
return @sensors if @sensors
|
47
|
+
|
48
|
+
power = user_ctrl.xpath("//body/content/i[@title='$currentPower']")
|
49
|
+
mode = user_ctrl.xpath("//body/content/i[@title='$currentMode']")
|
50
|
+
outdoor_temp = user_ctrl.xpath("//body/content/i[@title='$outdoorTemp']")
|
51
|
+
|
52
|
+
switch_mode = mode.xpath("displayval").text[/values\.(\w+)/, 1]
|
53
|
+
|
54
|
+
power_input = power.xpath("onchange").text[/getUrlPar\('(\w+)',val\)/, 1]
|
55
|
+
m = mode.xpath("onchange").text.match(/getUrlPar\('(\w+)',val\).*getUrlPar\(.(\w+).,2\)/)
|
56
|
+
|
57
|
+
@sensors = {
|
58
|
+
"outdoor_temperature" => outdoor_temp.attribute("id").value, # "I10208"
|
59
|
+
"current_power" => power.attribute("id").value, # "H10704"
|
60
|
+
"current_mode" => mode.attribute("id").value, # "H10705"
|
61
|
+
"current_mode_switch" => switch_mode, # "H10712"
|
62
|
+
"power_input" => power_input, # "H10708"
|
63
|
+
"mode_input" => m[1], # "H10709"
|
64
|
+
"mode_switch" => m[2], # "H10701"
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
# Generic modes
|
69
|
+
# @return [Hash]
|
70
|
+
def modes
|
71
|
+
return @modes if @modes
|
72
|
+
|
73
|
+
@modes = {}
|
74
|
+
user_ctrl.xpath("//op[@id='Mode']/i").each do |mode|
|
75
|
+
m = translate_mode(mode)
|
76
|
+
@modes[m[:id]] = m[:value]
|
77
|
+
end
|
78
|
+
@modes
|
79
|
+
end
|
80
|
+
|
81
|
+
# Modes by inputs - switches - named by user custom texts
|
82
|
+
# @return [Hash]
|
83
|
+
def user_modes
|
84
|
+
return @user_modes if @user_modes
|
85
|
+
|
86
|
+
@user_modes = {}
|
87
|
+
user_ctrl.xpath("//op[@id='ModeText']/i").each do |mode|
|
88
|
+
m = translate_mode(mode)
|
89
|
+
@user_modes[m[:id]] = m[:value]
|
90
|
+
end
|
91
|
+
@user_modes
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# User defined texts in RD5 unit
|
97
|
+
# @return [Hash]
|
98
|
+
def user_texts
|
99
|
+
return @user_texts if @user_texts
|
100
|
+
|
101
|
+
response = request.call(_t: "config/texts.xml")
|
102
|
+
xml = Nokogiri::XML response.body
|
103
|
+
@user_texts = xml.xpath("//i").to_h do |node|
|
104
|
+
value = node.attributes["value"].value
|
105
|
+
id = node.attributes["id"].value
|
106
|
+
[id, value.gsub(/%u([\dA-Z]{4})/) { |i| +"" << i[Regexp.last_match(1)].to_i(16) }]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# @param [Nokogiri::XML::Element] mode
|
111
|
+
# @return [Hash{Symbol->String}]
|
112
|
+
def translate_mode(mode)
|
113
|
+
id = mode.attributes["id"].value
|
114
|
+
title = mode.attributes["title"].value
|
115
|
+
title = if title.start_with?("$")
|
116
|
+
I18n.t(title[/\w+/])
|
117
|
+
else
|
118
|
+
user_texts[title]
|
119
|
+
end
|
120
|
+
{ id: id, value: title }
|
121
|
+
end
|
122
|
+
|
123
|
+
# Re-generate copy of locale files
|
124
|
+
# @note internal use only
|
125
|
+
def update_locales_files!
|
126
|
+
{ cs: "0", de: "1", en: "2" }.each do |name, atrea_id|
|
127
|
+
response = request.call(_t: "lang/texts_#{atrea_id}.xml")
|
128
|
+
|
129
|
+
xml = Nokogiri::XML response.body
|
130
|
+
# `eval` JS object (= hash)
|
131
|
+
locale = eval xml.xpath("//words/text()")[0].text
|
132
|
+
yaml = { name.to_s => JSON.parse(locale.to_json) }.to_yaml
|
133
|
+
File.write(File.expand_path("../../../config/locales/#{name}.yml", __dir__), yaml)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/atrea_control/duplex.rb
CHANGED
@@ -1,206 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "logger"
|
4
|
-
require "nokogiri"
|
5
|
-
require "rest-client"
|
6
|
-
require "selenium-webdriver"
|
7
|
-
|
8
3
|
module AtreaControl
|
9
4
|
# Controller for +control.atrea.eu+
|
10
|
-
|
5
|
+
module Duplex
|
11
6
|
CONTROL_URI = "https://control.atrea.eu/"
|
7
|
+
CONTROL_VERSION = "003001009"
|
12
8
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# @param [String] login
|
18
|
-
# @param [String] password
|
19
|
-
# @param [Hash] sensors_map which box is related to sensor ID
|
20
|
-
# @option sensors_map [String] :outdoor_temperature
|
21
|
-
# @option sensors_map [String] :current_power
|
22
|
-
# @option sensors_map [String] :current_mode
|
23
|
-
def initialize(login:, password:, sensors_map: Config.default_sensors_map)
|
24
|
-
@login = login
|
25
|
-
@password = password
|
26
|
-
@sensors = sensors_map
|
27
|
-
end
|
28
|
-
|
29
|
-
# @return [Selenium::WebDriver::Firefox::Driver]
|
30
|
-
def driver
|
31
|
-
return @driver if defined?(@driver)
|
32
|
-
|
33
|
-
options = Selenium::WebDriver::Firefox::Options.new
|
34
|
-
options.headless! unless ENV["NO_HEADLESS"]
|
35
|
-
@driver ||= Selenium::WebDriver.for :firefox, options: options
|
36
|
-
end
|
37
|
-
|
38
|
-
def logged?
|
39
|
-
user&.[] "loged"
|
40
|
-
end
|
41
|
-
|
42
|
-
def login_in_progress?
|
43
|
-
@login_in_progress
|
44
|
-
end
|
45
|
-
|
46
|
-
# Login into control
|
47
|
-
def login
|
48
|
-
@login_in_progress = true
|
49
|
-
logger.debug "start new login"
|
50
|
-
driver.get CONTROL_URI
|
51
|
-
submit_login_form if user.nil? || !logged?
|
52
|
-
finish_login
|
53
|
-
@login_in_progress = false
|
54
|
-
inspect
|
55
|
-
end
|
56
|
-
|
57
|
-
# Submit given credentials and proceed login
|
58
|
-
def submit_login_form
|
59
|
-
form = driver.find_element(id: "loginFrm")
|
60
|
-
username = form.find_element(name: "username")
|
61
|
-
username.send_keys @login
|
62
|
-
password = form.find_element(name: "password")
|
63
|
-
password.send_keys @password
|
64
|
-
|
65
|
-
submit = form.find_element(css: "input[type=submit]")
|
66
|
-
submit.click
|
67
|
-
end
|
68
|
-
|
69
|
-
# Retrieve dashboard URI from object tag and open it again
|
70
|
-
def open_dashboard
|
71
|
-
uri = driver.find_element(tag_name: "object").attribute "data"
|
72
|
-
driver.get uri
|
73
|
-
logger.debug "#{name} login success"
|
74
|
-
end
|
75
|
-
|
76
|
-
# @return [String]
|
77
|
-
def name
|
78
|
-
return unless logged?
|
79
|
-
|
80
|
-
container = driver.find_element css: "div#pageTitle > h2"
|
81
|
-
container.text
|
82
|
-
end
|
83
|
-
|
84
|
-
# @return [String] ID of logged user
|
85
|
-
def user_id
|
86
|
-
@user_id ||= driver.execute_script("return window._user")
|
87
|
-
end
|
88
|
-
|
89
|
-
# @return [String] ID of recuperation unit
|
90
|
-
def unit_id
|
91
|
-
@unit_id ||= driver.execute_script("return window._unit")
|
92
|
-
end
|
93
|
-
|
94
|
-
# Window.user object from atrea
|
95
|
-
# @return [Hash, nil]
|
96
|
-
def user
|
97
|
-
driver.execute_script("return window.user")
|
98
|
-
end
|
99
|
-
|
100
|
-
# @return [String]
|
101
|
-
def current_mode_name
|
102
|
-
return current_mode unless logged?
|
103
|
-
|
104
|
-
element = sensor_element(@sensors[:current_mode])
|
105
|
-
element.find_element(css: "div:first-child").text
|
106
|
-
end
|
107
|
-
|
108
|
-
# quit selenium browser
|
109
|
-
def close
|
110
|
-
@user_auth = nil
|
111
|
-
driver.quit
|
112
|
-
remove_instance_variable :@driver
|
113
|
-
end
|
114
|
-
|
115
|
-
alias logout! close
|
116
|
-
|
117
|
-
def as_json(_options = nil)
|
118
|
-
{
|
119
|
-
logged: logged?,
|
120
|
-
current_mode: current_mode_name,
|
121
|
-
current_power: current_power,
|
122
|
-
outdoor_temperature: outdoor_temperature,
|
123
|
-
valid_for: valid_for,
|
124
|
-
}
|
125
|
-
end
|
126
|
-
|
127
|
-
alias values as_json
|
128
|
-
|
129
|
-
def to_json(*args)
|
130
|
-
as_json.to_json(*args)
|
131
|
-
end
|
132
|
-
|
133
|
-
def inspect
|
134
|
-
"<AtreaControl name: '#{name}' outdoor_temperature: '#{outdoor_temperature}°C' current_power: '#{current_power}%' current_mode: '#{current_mode_name}' valid_for: '#{valid_for}'>"
|
135
|
-
end
|
136
|
-
|
137
|
-
def call_unit!
|
138
|
-
return false if @login_in_progress
|
139
|
-
|
140
|
-
logger.debug "call_unit!"
|
141
|
-
parse_response(response_comm_unit)
|
142
|
-
@valid_for = Time.now
|
143
|
-
as_json
|
144
|
-
rescue RestClient::Forbidden
|
145
|
-
logger.debug "session expired..."
|
146
|
-
close if @logged
|
147
|
-
login && call_unit!
|
148
|
-
end
|
149
|
-
|
150
|
-
private
|
151
|
-
|
152
|
-
def parse_response(response)
|
153
|
-
xml = Nokogiri::XML response.body
|
154
|
-
sensors_values = @sensors.transform_values do |id|
|
155
|
-
xml.xpath("//O[@I=\"#{id}\"]/@V").last&.value
|
156
|
-
end
|
157
|
-
refresh_data(sensors_values)
|
158
|
-
end
|
159
|
-
|
160
|
-
# @param [Hash] values
|
161
|
-
# @return [Hash]
|
162
|
-
def refresh_data(values)
|
163
|
-
@outdoor_temperature = values[:outdoor_temperature].to_f / 10
|
164
|
-
@current_power = values[:current_power].to_f
|
165
|
-
@current_mode = mode_map[values[:current_mode]]
|
166
|
-
|
167
|
-
as_json
|
168
|
-
end
|
169
|
-
|
170
|
-
# ? I10204 ?
|
171
|
-
def mode_map
|
172
|
-
{ "0" => "Vypnuto", "1" => "Automat", "2" => "Větrání", "6" => "Rozvážení" }
|
173
|
-
end
|
174
|
-
|
175
|
-
def logger
|
176
|
-
@logger ||= ::Logger.new($stdout)
|
177
|
-
end
|
178
|
-
|
179
|
-
def finish_login
|
180
|
-
13.times do |i|
|
181
|
-
return true if open_dashboard
|
182
|
-
rescue Selenium::WebDriver::Error::NoSuchElementError => _e
|
183
|
-
t = [3 * (1 + i), 25].min
|
184
|
-
logger.debug "waiting #{t}s for login..."
|
185
|
-
sleep t
|
186
|
-
end
|
187
|
-
raise Error, "unable to login"
|
188
|
-
end
|
189
|
-
|
190
|
-
def sensor_element(sensor_id)
|
191
|
-
driver.find_element id: "contentBox#{sensor_id}"
|
192
|
-
end
|
9
|
+
autoload :Login, "atrea_control/duplex/login"
|
10
|
+
autoload :Request, "atrea_control/duplex/request"
|
11
|
+
autoload :Unit, "atrea_control/duplex/unit"
|
12
|
+
autoload :UserCtrl, "atrea_control/duplex/user_ctrl"
|
193
13
|
|
194
|
-
# @return [RestClient::Response]
|
195
|
-
def response_comm_unit
|
196
|
-
params = {
|
197
|
-
_user: user_id,
|
198
|
-
_unit: unit_id,
|
199
|
-
auth: user&.[]("auth"),
|
200
|
-
_t: "config/xml.xml",
|
201
|
-
}
|
202
|
-
autologin_token = CGI.escape([@login, @password].join("\b"))
|
203
|
-
RestClient.get "https://control.atrea.eu/comm/sw/unit.php", { Cookie: "autoLogin=#{autologin_token}", params: params }
|
204
|
-
end
|
205
14
|
end
|
206
15
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
module AtreaControl
|
6
|
+
# Call RD5 unit ang get current sensors values
|
7
|
+
# parse it and return Hash
|
8
|
+
class SensorParser
|
9
|
+
# @param [AtreaControl::Duplex::UserCtrl] user_ctrl
|
10
|
+
def initialize(user_ctrl)
|
11
|
+
@user_ctrl = user_ctrl
|
12
|
+
end
|
13
|
+
|
14
|
+
def values(xml)
|
15
|
+
format_data(parse(xml))
|
16
|
+
end
|
17
|
+
|
18
|
+
def input(sensor, value)
|
19
|
+
sensor + value.to_s.rjust(5, "0")
|
20
|
+
end
|
21
|
+
|
22
|
+
# @see scripts.php -> loadRD5Values(node, init)
|
23
|
+
# @note
|
24
|
+
# if(values[key]>32767) values[key]-=65536;
|
25
|
+
# if(params[key] && params[key].offset)
|
26
|
+
# values[key]=values[key]-params[key].offset;
|
27
|
+
# if(params[key] && params[key].coef)
|
28
|
+
# values[key]=values[key]/params[key].coef;
|
29
|
+
def parse(xml)
|
30
|
+
xml = Nokogiri::XML xml
|
31
|
+
@user_ctrl.sensors.transform_values do |id|
|
32
|
+
value = xml.xpath("//O[@I=\"#{id}\"]/@V").last&.value.to_i
|
33
|
+
value -= 65_536 if value > 32_767
|
34
|
+
# value -= 0 if "offset"
|
35
|
+
# value -= 0 if "coef"
|
36
|
+
value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [Hash] values
|
41
|
+
# @return [Hash]
|
42
|
+
def format_data(values)
|
43
|
+
{
|
44
|
+
"current_mode" => parse_current_mode(values),
|
45
|
+
"current_power" => values["current_power"].to_f,
|
46
|
+
"outdoor_temperature" => values["outdoor_temperature"].to_f / 10.0,
|
47
|
+
"valid_for" => Time.now,
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# `current_mode_switch` = mode trigger by wall switch or something similar
|
54
|
+
# `current_mode` = is common "builtin" mode
|
55
|
+
def parse_current_mode(values)
|
56
|
+
if values['current_mode_switch'].positive?
|
57
|
+
@user_ctrl.user_modes[values['current_mode_switch'].to_s]
|
58
|
+
else
|
59
|
+
@user_ctrl.modes[values['current_mode'].to_s]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/atrea_control.rb
CHANGED
@@ -1,22 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "atrea_control/version"
|
4
|
+
require "i18n"
|
5
|
+
require "yaml"
|
4
6
|
|
5
7
|
module AtreaControl
|
6
8
|
class Error < StandardError; end
|
7
9
|
|
8
10
|
autoload :Duplex, "atrea_control/duplex"
|
11
|
+
autoload :Logger, "atrea_control/logger"
|
12
|
+
autoload :SensorParser, "atrea_control/sensor_parser"
|
9
13
|
|
10
|
-
#
|
11
|
-
|
12
|
-
module_function
|
14
|
+
I18n.load_path.concat Dir["#{File.expand_path("../config/locales", __dir__)}/*.yml"]
|
15
|
+
I18n.default_locale = :cs
|
13
16
|
|
14
|
-
def default_sensors_map
|
15
|
-
{
|
16
|
-
outdoor_temperature: "I10208",
|
17
|
-
current_power: "H10704",
|
18
|
-
current_mode: "H10705",
|
19
|
-
}
|
20
|
-
end
|
21
|
-
end
|
22
17
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: atrea_control
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lukáš Pokorný
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: i18n
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: nokogiri
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,14 +58,14 @@ dependencies:
|
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
61
|
+
version: '4.1'
|
48
62
|
type: :runtime
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
68
|
+
version: '4.1'
|
55
69
|
description: Read data from web controller of RD5 duplex by Atrea.
|
56
70
|
email:
|
57
71
|
- pokorny@luk4s.cz
|
@@ -73,16 +87,22 @@ files:
|
|
73
87
|
- atrea_control.gemspec
|
74
88
|
- bin/console
|
75
89
|
- bin/setup
|
90
|
+
- config/locales/cs.yml
|
91
|
+
- config/locales/de.yml
|
92
|
+
- config/locales/en.yml
|
76
93
|
- lib/atrea_control.rb
|
77
94
|
- lib/atrea_control/duplex.rb
|
95
|
+
- lib/atrea_control/duplex/login.rb
|
96
|
+
- lib/atrea_control/duplex/request.rb
|
97
|
+
- lib/atrea_control/duplex/unit.rb
|
98
|
+
- lib/atrea_control/duplex/user_ctrl.rb
|
99
|
+
- lib/atrea_control/logger.rb
|
100
|
+
- lib/atrea_control/sensor_parser.rb
|
78
101
|
- lib/atrea_control/version.rb
|
79
102
|
homepage: https://github.com/luk4s/atrea_control
|
80
103
|
licenses: []
|
81
104
|
metadata:
|
82
|
-
|
83
|
-
homepage_uri: https://github.com/luk4s/atrea_control
|
84
|
-
source_code_uri: https://github.com/luk4s/atrea_control
|
85
|
-
changelog_uri: https://github.com/luk4s/atrea_control/CHANGELOG.md
|
105
|
+
rubygems_mfa_required: 'true'
|
86
106
|
post_install_message:
|
87
107
|
rdoc_options: []
|
88
108
|
require_paths:
|