senec 0.22.1 → 0.24.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 +4 -4
- data/.rspec_status +71 -62
- data/.ruby-lsp/Gemfile +1 -1
- data/.ruby-lsp/Gemfile.lock +80 -77
- data/.ruby-lsp/freshness_hash +1 -0
- data/.ruby-lsp/last_updated +1 -1
- data/.ruby-lsp/main_lockfile_hash +1 -1
- data/coverage/.last_run.json +1 -1
- data/coverage/.resultset.json +1 -1542
- data/coverage/coverage.json +870 -942
- data/coverage/index.html +68 -16793
- data/lib/senec/cloud/connection.rb +65 -22
- data/lib/senec/version.rb +1 -1
- data/pkg/senec-0.22.1.gem +0 -0
- data/pkg/senec-0.23.0.gem +0 -0
- metadata +6 -3
- /data/{.ruby-lsp/needs_update → coverage/.report_stamp} +0 -0
data/coverage/coverage.json
CHANGED
|
@@ -1,9 +1,40 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/simplecov-ruby/simplecov/main/schemas/coverage-v1.0.schema.json",
|
|
2
3
|
"meta": {
|
|
3
|
-
"
|
|
4
|
+
"schema_version": "1.0",
|
|
5
|
+
"simplecov_version": "1.1.1",
|
|
6
|
+
"command_name": "RSpec",
|
|
7
|
+
"project_name": "Senec",
|
|
8
|
+
"timestamp": "2026-09-03T17:12:12.173+02:00",
|
|
9
|
+
"root": "/Users/ledermann/Projects/solectrus/senec",
|
|
10
|
+
"commit": "15f873f607ff3a55406b4b0ded204567970eb10f",
|
|
11
|
+
"primary_coverage": "line",
|
|
12
|
+
"line_coverage": true,
|
|
13
|
+
"branch_coverage": false,
|
|
14
|
+
"method_coverage": false
|
|
15
|
+
},
|
|
16
|
+
"total": {
|
|
17
|
+
"lines": {
|
|
18
|
+
"covered": 297,
|
|
19
|
+
"missed": 2,
|
|
20
|
+
"omitted": 361,
|
|
21
|
+
"total": 299,
|
|
22
|
+
"percent": 99.33110367892976,
|
|
23
|
+
"strength": 13.494983277591974
|
|
24
|
+
}
|
|
4
25
|
},
|
|
5
26
|
"coverage": {
|
|
6
|
-
"
|
|
27
|
+
"lib/senec.rb": {
|
|
28
|
+
"source": [
|
|
29
|
+
"require 'senec/version'",
|
|
30
|
+
"require 'senec/local/connection'",
|
|
31
|
+
"require 'senec/local/state'",
|
|
32
|
+
"require 'senec/local/request'",
|
|
33
|
+
"require 'senec/local/error'",
|
|
34
|
+
"",
|
|
35
|
+
"require 'senec/cloud/connection'",
|
|
36
|
+
"require 'senec/cloud/error'"
|
|
37
|
+
],
|
|
7
38
|
"lines": [
|
|
8
39
|
1,
|
|
9
40
|
1,
|
|
@@ -13,9 +44,322 @@
|
|
|
13
44
|
null,
|
|
14
45
|
1,
|
|
15
46
|
1
|
|
16
|
-
]
|
|
47
|
+
],
|
|
48
|
+
"lines_covered_percent": 100.0,
|
|
49
|
+
"covered_lines": 7,
|
|
50
|
+
"missed_lines": 0,
|
|
51
|
+
"omitted_lines": 1,
|
|
52
|
+
"total_lines": 7
|
|
17
53
|
},
|
|
18
|
-
"
|
|
54
|
+
"lib/senec/cloud/connection.rb": {
|
|
55
|
+
"source": [
|
|
56
|
+
"require 'oauth2'",
|
|
57
|
+
"require 'rotp'",
|
|
58
|
+
"",
|
|
59
|
+
"module Senec",
|
|
60
|
+
" module Cloud",
|
|
61
|
+
" CONFIG_URL =",
|
|
62
|
+
" 'https://sso.senec.com/realms/senec/.well-known/openid-configuration'.freeze",
|
|
63
|
+
"",
|
|
64
|
+
" CLIENT_ID = 'endcustomer-app-frontend'.freeze",
|
|
65
|
+
" REDIRECT_URI = 'senec-app-auth://keycloak.prod'.freeze",
|
|
66
|
+
" SCOPE = 'roles profile meinsenec'.freeze",
|
|
67
|
+
"",
|
|
68
|
+
" SYSTEMS_HOST = 'https://senec-app-systems-proxy.prod.senec.dev'.freeze",
|
|
69
|
+
" MEASUREMENTS_HOST = 'https://senec-app-measurements-proxy.prod.senec.dev'.freeze",
|
|
70
|
+
" WALLBOX_HOST = 'https://senec-app-wallbox-proxy.prod.senec.dev'.freeze",
|
|
71
|
+
"",
|
|
72
|
+
" class Connection",
|
|
73
|
+
" DEFAULT_USER_AGENT = \"ruby-senec/#{Senec::VERSION} (+https://github.com/solectrus/senec)\".freeze",
|
|
74
|
+
"",
|
|
75
|
+
" def initialize(username:, password:, user_agent: DEFAULT_USER_AGENT, totp_uri: nil)",
|
|
76
|
+
" @username = username",
|
|
77
|
+
" @password = password",
|
|
78
|
+
" @user_agent = user_agent",
|
|
79
|
+
" @totp_uri = totp_uri",
|
|
80
|
+
" end",
|
|
81
|
+
"",
|
|
82
|
+
" attr_reader :username, :password, :user_agent, :totp_uri",
|
|
83
|
+
"",
|
|
84
|
+
" def authenticate!",
|
|
85
|
+
" code_verifier = SecureRandom.alphanumeric(43)",
|
|
86
|
+
" digest = Digest::SHA256.digest(code_verifier)",
|
|
87
|
+
" code_challenge = Base64.urlsafe_encode64(digest).delete('=')",
|
|
88
|
+
"",
|
|
89
|
+
" auth_url =",
|
|
90
|
+
" oauth_client.auth_code.authorize_url(",
|
|
91
|
+
" redirect_uri: REDIRECT_URI,",
|
|
92
|
+
" scope: SCOPE,",
|
|
93
|
+
" code_challenge:,",
|
|
94
|
+
" code_challenge_method: 'S256',",
|
|
95
|
+
" )",
|
|
96
|
+
"",
|
|
97
|
+
" # Manual HTTP needed for Keycloak cross-domain form handling",
|
|
98
|
+
" form_type, form_url = fetch_login_form(auth_url)",
|
|
99
|
+
" redirect_url = submit_credentials(form_type, form_url)",
|
|
100
|
+
" authorization_code = extract_authorization_code(redirect_url)",
|
|
101
|
+
"",
|
|
102
|
+
" self.oauth_token =",
|
|
103
|
+
" oauth_client.auth_code.get_token(",
|
|
104
|
+
" authorization_code,",
|
|
105
|
+
" redirect_uri: REDIRECT_URI,",
|
|
106
|
+
" code_verifier:,",
|
|
107
|
+
" )",
|
|
108
|
+
" end",
|
|
109
|
+
"",
|
|
110
|
+
" def authenticated?",
|
|
111
|
+
" !!oauth_token",
|
|
112
|
+
" end",
|
|
113
|
+
"",
|
|
114
|
+
" def systems",
|
|
115
|
+
" get \"#{SYSTEMS_HOST}/systems/api/v1\"",
|
|
116
|
+
" end",
|
|
117
|
+
"",
|
|
118
|
+
" def system_details(system_id)",
|
|
119
|
+
" get \"#{SYSTEMS_HOST}/systems/api/v1/#{system_id}/details\"",
|
|
120
|
+
" end",
|
|
121
|
+
"",
|
|
122
|
+
" def dashboard(system_id)",
|
|
123
|
+
" get \"#{MEASUREMENTS_HOST}/measurements/api/v1/systems/#{system_id}/dashboard\"",
|
|
124
|
+
" end",
|
|
125
|
+
"",
|
|
126
|
+
" def wallbox(system_id, wallbox_id)",
|
|
127
|
+
" get \"#{WALLBOX_HOST}/wallbox/api/v1/systems/#{system_id}/wallboxes/#{wallbox_id}\"",
|
|
128
|
+
" end",
|
|
129
|
+
"",
|
|
130
|
+
" def wallbox_search(system_id)",
|
|
131
|
+
" post \"#{WALLBOX_HOST}/wallbox/api/v1/systems/wallboxes/search\", { systemIds: [system_id] }",
|
|
132
|
+
" end",
|
|
133
|
+
"",
|
|
134
|
+
" def measurements(system_id, resolution:, from:, to:)",
|
|
135
|
+
" params = URI.encode_www_form(",
|
|
136
|
+
" resolution:,",
|
|
137
|
+
" from: from.strftime('%Y-%m-%dT%H:%M:%SZ'),",
|
|
138
|
+
" to: to.strftime('%Y-%m-%dT%H:%M:%SZ'),",
|
|
139
|
+
" )",
|
|
140
|
+
"",
|
|
141
|
+
" get \"#{MEASUREMENTS_HOST}/measurements/api/v1/systems/#{system_id}/measurements?#{params}\"",
|
|
142
|
+
" end",
|
|
143
|
+
"",
|
|
144
|
+
" def data_availability(system_id, timezone:)",
|
|
145
|
+
" params = URI.encode_www_form(timezone:)",
|
|
146
|
+
"",
|
|
147
|
+
" get \"#{MEASUREMENTS_HOST}/measurements/api/v1/systems/#{system_id}/data-availability/timespan?#{params}\"",
|
|
148
|
+
" end",
|
|
149
|
+
"",
|
|
150
|
+
" private",
|
|
151
|
+
"",
|
|
152
|
+
" attr_accessor :oauth_token",
|
|
153
|
+
"",
|
|
154
|
+
" def fetch_login_form(auth_url)",
|
|
155
|
+
" response = http_request(:get, auth_url)",
|
|
156
|
+
" store_cookies(response) # Required for Keycloak CSRF protection",
|
|
157
|
+
" detect_login_form(response.body) || raise('Login form not found')",
|
|
158
|
+
" end",
|
|
159
|
+
"",
|
|
160
|
+
" def submit_credentials(form_type, form_url)",
|
|
161
|
+
" case form_type",
|
|
162
|
+
" when :username_and_password",
|
|
163
|
+
" response = http_request(:post, form_url, data: { username:, password: })",
|
|
164
|
+
" when :username_only",
|
|
165
|
+
" response = submit_username_then_password(form_url)",
|
|
166
|
+
" end",
|
|
167
|
+
"",
|
|
168
|
+
" if response.status == 200",
|
|
169
|
+
" # Check if MFA is required",
|
|
170
|
+
" if (totp_form_url = extract_totp_form_action_url(response.body))",
|
|
171
|
+
" unless totp_uri",
|
|
172
|
+
" raise 'MFA required but no TOTP URI provided'",
|
|
173
|
+
" end",
|
|
174
|
+
"",
|
|
175
|
+
" response = submit_totp_form(totp_form_url)",
|
|
176
|
+
" else",
|
|
177
|
+
" # No OTP form found, assume login failed",
|
|
178
|
+
" raise 'Login failed - invalid credentials or unexpected response'",
|
|
179
|
+
" end",
|
|
180
|
+
" end",
|
|
181
|
+
"",
|
|
182
|
+
" # Handle final redirect (same for both MFA and non-MFA flows)",
|
|
183
|
+
" handle_redirect_response(response)",
|
|
184
|
+
" end",
|
|
185
|
+
"",
|
|
186
|
+
" def submit_username_then_password(form_url)",
|
|
187
|
+
" response = http_request(:post, form_url, data: { username: })",
|
|
188
|
+
" store_cookies(response)",
|
|
189
|
+
"",
|
|
190
|
+
" raise 'Password form not found' unless response.status == 200",
|
|
191
|
+
"",
|
|
192
|
+
" password_form_url = find_form_action_url(response.body) do |f|",
|
|
193
|
+
" f.match(/name=[\"']?password[\"']?/i)",
|
|
194
|
+
" end",
|
|
195
|
+
" raise 'Password form not found' unless password_form_url",
|
|
196
|
+
"",
|
|
197
|
+
" http_request(:post, password_form_url, data: { username:, password: })",
|
|
198
|
+
" end",
|
|
199
|
+
"",
|
|
200
|
+
" def submit_totp_form(form_url)",
|
|
201
|
+
" totp = build_totp_from_uri(totp_uri)",
|
|
202
|
+
"",
|
|
203
|
+
" totp_data = { otp: totp.now }",
|
|
204
|
+
" http_request(:post, form_url, data: totp_data)",
|
|
205
|
+
" end",
|
|
206
|
+
"",
|
|
207
|
+
" def extract_authorization_code(redirect_url)",
|
|
208
|
+
" raise 'Invalid redirect URL' unless redirect_url&.start_with?(REDIRECT_URI)",
|
|
209
|
+
"",
|
|
210
|
+
" uri = URI(redirect_url)",
|
|
211
|
+
" params = URI.decode_www_form(uri.query).to_h",
|
|
212
|
+
"",
|
|
213
|
+
" params['code'] || raise('No authorization code found')",
|
|
214
|
+
" end",
|
|
215
|
+
"",
|
|
216
|
+
" def detect_login_form(html)",
|
|
217
|
+
" # Check for combined form first (backward compatible with old SENEC flow)",
|
|
218
|
+
" url = find_form_action_url(html) do |form|",
|
|
219
|
+
" form.match(/name=[\"']?username[\"']?/i) &&",
|
|
220
|
+
" form.match(/name=[\"']?password[\"']?/i)",
|
|
221
|
+
" end",
|
|
222
|
+
" return [:username_and_password, url] if url",
|
|
223
|
+
"",
|
|
224
|
+
" # Check for username-only form (new two-step SENEC flow)",
|
|
225
|
+
" url = find_form_action_url(html) do |form|",
|
|
226
|
+
" form.match(/name=[\"']?username[\"']?/i)",
|
|
227
|
+
" end",
|
|
228
|
+
" return [:username_only, url] if url",
|
|
229
|
+
"",
|
|
230
|
+
" nil",
|
|
231
|
+
" end",
|
|
232
|
+
"",
|
|
233
|
+
" def extract_totp_form_action_url(html)",
|
|
234
|
+
" find_form_action_url(html) do |form|",
|
|
235
|
+
" # Look for a form with an OTP field",
|
|
236
|
+
" form.match(/name=[\"']?otp[\"']?/i)",
|
|
237
|
+
" end",
|
|
238
|
+
" end",
|
|
239
|
+
"",
|
|
240
|
+
" def find_form_action_url(html)",
|
|
241
|
+
" forms = html.scan(%r{<form[^>]*action=\"([^\"]+)\"[^>]*>(.*?)</form>}mi)",
|
|
242
|
+
"",
|
|
243
|
+
" forms.each do |action_url, form_content|",
|
|
244
|
+
" return CGI.unescapeHTML(action_url) if yield(form_content)",
|
|
245
|
+
" end",
|
|
246
|
+
"",
|
|
247
|
+
" nil",
|
|
248
|
+
" end",
|
|
249
|
+
"",
|
|
250
|
+
" def build_totp_from_uri(uri_string)",
|
|
251
|
+
" params = URI.decode_www_form(URI.parse(uri_string).query).to_h",
|
|
252
|
+
" secret = params['secret'] || raise('Missing secret in TOTP URI')",
|
|
253
|
+
"",
|
|
254
|
+
" ROTP::TOTP.new(",
|
|
255
|
+
" secret,",
|
|
256
|
+
" digits: params['digits']&.to_i,",
|
|
257
|
+
" period: params['period']&.to_i,",
|
|
258
|
+
" algorithm: params['algorithm'],",
|
|
259
|
+
" )",
|
|
260
|
+
" end",
|
|
261
|
+
"",
|
|
262
|
+
" def handle_redirect_response(response)",
|
|
263
|
+
" unless response.status == 302",
|
|
264
|
+
" raise \"Authentication failed, got unexpected response: #{response.status} #{response.body}\"",
|
|
265
|
+
" end",
|
|
266
|
+
"",
|
|
267
|
+
" response.headers['location'] || raise('No redirect location')",
|
|
268
|
+
" end",
|
|
269
|
+
"",
|
|
270
|
+
" def ensure_token_valid!",
|
|
271
|
+
" authenticate! unless authenticated?",
|
|
272
|
+
" return unless oauth_token.expired?",
|
|
273
|
+
"",
|
|
274
|
+
" self.oauth_token = oauth_token.refresh!",
|
|
275
|
+
" rescue StandardError => e",
|
|
276
|
+
" warn \"Token refresh failed: #{e.message}, trying to re-authenticate...\"",
|
|
277
|
+
"",
|
|
278
|
+
" authenticate!",
|
|
279
|
+
" end",
|
|
280
|
+
"",
|
|
281
|
+
" def get(url)",
|
|
282
|
+
" ensure_token_valid!",
|
|
283
|
+
"",
|
|
284
|
+
" response = oauth_token.get(url)",
|
|
285
|
+
" JSON.parse(response.body)",
|
|
286
|
+
" rescue StandardError => e",
|
|
287
|
+
" # simplecov:disable",
|
|
288
|
+
" warn \"API error: #{e.message}\"",
|
|
289
|
+
" nil",
|
|
290
|
+
" # simplecov:enable",
|
|
291
|
+
" end",
|
|
292
|
+
"",
|
|
293
|
+
" def post(url, data)",
|
|
294
|
+
" ensure_token_valid!",
|
|
295
|
+
"",
|
|
296
|
+
" response = oauth_token.post(",
|
|
297
|
+
" url,",
|
|
298
|
+
" body: data.to_json,",
|
|
299
|
+
" headers: { 'Content-Type' => 'application/json' },",
|
|
300
|
+
" )",
|
|
301
|
+
"",
|
|
302
|
+
" JSON.parse(response.body)",
|
|
303
|
+
" rescue StandardError => e",
|
|
304
|
+
" # simplecov:disable",
|
|
305
|
+
" warn \"API error: #{e.message}\"",
|
|
306
|
+
" nil",
|
|
307
|
+
" # simplecov:enable",
|
|
308
|
+
" end",
|
|
309
|
+
"",
|
|
310
|
+
" def http_request(method, url, data: nil)",
|
|
311
|
+
" Faraday",
|
|
312
|
+
" .new",
|
|
313
|
+
" .send(method, url) do |req|",
|
|
314
|
+
" req.headers['user-agent'] = user_agent",
|
|
315
|
+
" req.headers['connection'] = 'keep-alive'",
|
|
316
|
+
" req.headers['cookie'] = cookie_string if cookies.any?",
|
|
317
|
+
" req.body = URI.encode_www_form(data) if data",
|
|
318
|
+
" end",
|
|
319
|
+
" end",
|
|
320
|
+
"",
|
|
321
|
+
" def oauth_client",
|
|
322
|
+
" @oauth_client ||=",
|
|
323
|
+
" OAuth2::Client.new(",
|
|
324
|
+
" CLIENT_ID,",
|
|
325
|
+
" nil,",
|
|
326
|
+
" site: openid_config['issuer'],",
|
|
327
|
+
" authorize_url: openid_config['authorization_endpoint'],",
|
|
328
|
+
" token_url: openid_config['token_endpoint'],",
|
|
329
|
+
" )",
|
|
330
|
+
" end",
|
|
331
|
+
"",
|
|
332
|
+
" def openid_config",
|
|
333
|
+
" @openid_config ||= JSON.parse(http_request(:get, CONFIG_URL).body)",
|
|
334
|
+
" rescue StandardError => e",
|
|
335
|
+
" # simplecov:disable",
|
|
336
|
+
" raise \"Failed to load OpenID configuration: #{e.message}\"",
|
|
337
|
+
" # simplecov:enable",
|
|
338
|
+
" end",
|
|
339
|
+
"",
|
|
340
|
+
" def cookies",
|
|
341
|
+
" @cookies ||= {}",
|
|
342
|
+
" end",
|
|
343
|
+
"",
|
|
344
|
+
" def cookie_string",
|
|
345
|
+
" cookies.map { |k, v| \"#{k}=#{v}\" }.join('; ')",
|
|
346
|
+
" end",
|
|
347
|
+
"",
|
|
348
|
+
" def store_cookies(response)",
|
|
349
|
+
" set_cookie = response.headers['set-cookie']",
|
|
350
|
+
" return unless set_cookie",
|
|
351
|
+
"",
|
|
352
|
+
" set_cookie",
|
|
353
|
+
" .split(', ')",
|
|
354
|
+
" .each do |cookie_header|",
|
|
355
|
+
" name, value = cookie_header.split(';').first.split('=', 2)",
|
|
356
|
+
" cookies[name] = value if name && value",
|
|
357
|
+
" end",
|
|
358
|
+
" end",
|
|
359
|
+
" end",
|
|
360
|
+
" end",
|
|
361
|
+
"end"
|
|
362
|
+
],
|
|
19
363
|
"lines": [
|
|
20
364
|
1,
|
|
21
365
|
1,
|
|
@@ -37,21 +381,21 @@
|
|
|
37
381
|
1,
|
|
38
382
|
null,
|
|
39
383
|
1,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
384
|
+
31,
|
|
385
|
+
31,
|
|
386
|
+
31,
|
|
387
|
+
31,
|
|
44
388
|
null,
|
|
45
389
|
null,
|
|
46
390
|
1,
|
|
47
391
|
null,
|
|
48
392
|
1,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
393
|
+
28,
|
|
394
|
+
28,
|
|
395
|
+
28,
|
|
52
396
|
null,
|
|
53
397
|
null,
|
|
54
|
-
|
|
398
|
+
28,
|
|
55
399
|
null,
|
|
56
400
|
null,
|
|
57
401
|
null,
|
|
@@ -59,11 +403,11 @@
|
|
|
59
403
|
null,
|
|
60
404
|
null,
|
|
61
405
|
null,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
406
|
+
28,
|
|
407
|
+
28,
|
|
408
|
+
26,
|
|
65
409
|
null,
|
|
66
|
-
|
|
410
|
+
26,
|
|
67
411
|
null,
|
|
68
412
|
null,
|
|
69
413
|
null,
|
|
@@ -72,7 +416,7 @@
|
|
|
72
416
|
null,
|
|
73
417
|
null,
|
|
74
418
|
1,
|
|
75
|
-
|
|
419
|
+
31,
|
|
76
420
|
null,
|
|
77
421
|
null,
|
|
78
422
|
1,
|
|
@@ -96,20 +440,40 @@
|
|
|
96
440
|
null,
|
|
97
441
|
null,
|
|
98
442
|
1,
|
|
443
|
+
5,
|
|
444
|
+
null,
|
|
445
|
+
null,
|
|
446
|
+
null,
|
|
447
|
+
null,
|
|
448
|
+
null,
|
|
449
|
+
5,
|
|
450
|
+
null,
|
|
99
451
|
null,
|
|
100
452
|
1,
|
|
453
|
+
4,
|
|
454
|
+
null,
|
|
455
|
+
4,
|
|
456
|
+
null,
|
|
101
457
|
null,
|
|
102
458
|
1,
|
|
103
|
-
19,
|
|
104
|
-
19,
|
|
105
|
-
19,
|
|
106
459
|
null,
|
|
460
|
+
1,
|
|
107
461
|
null,
|
|
108
462
|
1,
|
|
109
|
-
|
|
110
|
-
|
|
463
|
+
28,
|
|
464
|
+
28,
|
|
465
|
+
28,
|
|
111
466
|
null,
|
|
112
|
-
|
|
467
|
+
null,
|
|
468
|
+
1,
|
|
469
|
+
28,
|
|
470
|
+
null,
|
|
471
|
+
2,
|
|
472
|
+
null,
|
|
473
|
+
26,
|
|
474
|
+
null,
|
|
475
|
+
null,
|
|
476
|
+
28,
|
|
113
477
|
null,
|
|
114
478
|
3,
|
|
115
479
|
2,
|
|
@@ -124,7 +488,21 @@
|
|
|
124
488
|
null,
|
|
125
489
|
null,
|
|
126
490
|
null,
|
|
127
|
-
|
|
491
|
+
26,
|
|
492
|
+
null,
|
|
493
|
+
null,
|
|
494
|
+
1,
|
|
495
|
+
26,
|
|
496
|
+
26,
|
|
497
|
+
null,
|
|
498
|
+
26,
|
|
499
|
+
null,
|
|
500
|
+
26,
|
|
501
|
+
26,
|
|
502
|
+
null,
|
|
503
|
+
26,
|
|
504
|
+
null,
|
|
505
|
+
26,
|
|
128
506
|
null,
|
|
129
507
|
null,
|
|
130
508
|
1,
|
|
@@ -135,20 +513,29 @@
|
|
|
135
513
|
null,
|
|
136
514
|
null,
|
|
137
515
|
1,
|
|
138
|
-
|
|
516
|
+
26,
|
|
139
517
|
null,
|
|
140
|
-
|
|
141
|
-
|
|
518
|
+
26,
|
|
519
|
+
26,
|
|
142
520
|
null,
|
|
143
|
-
|
|
521
|
+
26,
|
|
144
522
|
null,
|
|
145
523
|
null,
|
|
146
524
|
1,
|
|
147
|
-
19,
|
|
148
525
|
null,
|
|
149
|
-
|
|
526
|
+
28,
|
|
527
|
+
54,
|
|
528
|
+
null,
|
|
529
|
+
null,
|
|
530
|
+
28,
|
|
150
531
|
null,
|
|
151
532
|
null,
|
|
533
|
+
26,
|
|
534
|
+
26,
|
|
535
|
+
null,
|
|
536
|
+
26,
|
|
537
|
+
null,
|
|
538
|
+
0,
|
|
152
539
|
null,
|
|
153
540
|
null,
|
|
154
541
|
1,
|
|
@@ -159,13 +546,13 @@
|
|
|
159
546
|
null,
|
|
160
547
|
null,
|
|
161
548
|
1,
|
|
162
|
-
|
|
549
|
+
83,
|
|
163
550
|
null,
|
|
164
|
-
|
|
165
|
-
|
|
551
|
+
83,
|
|
552
|
+
109,
|
|
166
553
|
null,
|
|
167
554
|
null,
|
|
168
|
-
|
|
555
|
+
27,
|
|
169
556
|
null,
|
|
170
557
|
null,
|
|
171
558
|
1,
|
|
@@ -181,16 +568,16 @@
|
|
|
181
568
|
null,
|
|
182
569
|
null,
|
|
183
570
|
1,
|
|
184
|
-
|
|
571
|
+
26,
|
|
185
572
|
0,
|
|
186
573
|
null,
|
|
187
574
|
null,
|
|
188
|
-
|
|
575
|
+
26,
|
|
189
576
|
null,
|
|
190
577
|
null,
|
|
191
578
|
1,
|
|
192
|
-
|
|
193
|
-
|
|
579
|
+
27,
|
|
580
|
+
27,
|
|
194
581
|
null,
|
|
195
582
|
2,
|
|
196
583
|
null,
|
|
@@ -200,10 +587,10 @@
|
|
|
200
587
|
null,
|
|
201
588
|
null,
|
|
202
589
|
1,
|
|
203
|
-
|
|
590
|
+
24,
|
|
204
591
|
null,
|
|
205
|
-
|
|
206
|
-
|
|
592
|
+
24,
|
|
593
|
+
19,
|
|
207
594
|
null,
|
|
208
595
|
"ignored",
|
|
209
596
|
"ignored",
|
|
@@ -229,18 +616,18 @@
|
|
|
229
616
|
null,
|
|
230
617
|
null,
|
|
231
618
|
1,
|
|
232
|
-
|
|
619
|
+
111,
|
|
233
620
|
null,
|
|
234
621
|
null,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
622
|
+
111,
|
|
623
|
+
111,
|
|
624
|
+
111,
|
|
625
|
+
111,
|
|
239
626
|
null,
|
|
240
627
|
null,
|
|
241
628
|
null,
|
|
242
629
|
1,
|
|
243
|
-
|
|
630
|
+
54,
|
|
244
631
|
null,
|
|
245
632
|
null,
|
|
246
633
|
null,
|
|
@@ -251,7 +638,7 @@
|
|
|
251
638
|
null,
|
|
252
639
|
null,
|
|
253
640
|
1,
|
|
254
|
-
|
|
641
|
+
84,
|
|
255
642
|
null,
|
|
256
643
|
"ignored",
|
|
257
644
|
"ignored",
|
|
@@ -259,30 +646,43 @@
|
|
|
259
646
|
null,
|
|
260
647
|
null,
|
|
261
648
|
1,
|
|
262
|
-
|
|
649
|
+
250,
|
|
263
650
|
null,
|
|
264
651
|
null,
|
|
265
652
|
1,
|
|
266
|
-
|
|
653
|
+
220,
|
|
267
654
|
null,
|
|
268
655
|
null,
|
|
269
656
|
1,
|
|
270
|
-
|
|
271
|
-
|
|
657
|
+
54,
|
|
658
|
+
54,
|
|
272
659
|
null,
|
|
273
|
-
|
|
660
|
+
28,
|
|
274
661
|
null,
|
|
275
662
|
null,
|
|
276
|
-
|
|
277
|
-
|
|
663
|
+
84,
|
|
664
|
+
84,
|
|
278
665
|
null,
|
|
279
666
|
null,
|
|
280
667
|
null,
|
|
281
668
|
null,
|
|
282
669
|
null
|
|
283
|
-
]
|
|
670
|
+
],
|
|
671
|
+
"lines_covered_percent": 98.55072463768116,
|
|
672
|
+
"covered_lines": 136,
|
|
673
|
+
"missed_lines": 2,
|
|
674
|
+
"omitted_lines": 157,
|
|
675
|
+
"total_lines": 138
|
|
284
676
|
},
|
|
285
|
-
"
|
|
677
|
+
"lib/senec/cloud/error.rb": {
|
|
678
|
+
"source": [
|
|
679
|
+
"module Senec",
|
|
680
|
+
" module Cloud",
|
|
681
|
+
" class Error < StandardError",
|
|
682
|
+
" end",
|
|
683
|
+
" end",
|
|
684
|
+
"end"
|
|
685
|
+
],
|
|
286
686
|
"lines": [
|
|
287
687
|
1,
|
|
288
688
|
1,
|
|
@@ -290,9 +690,51 @@
|
|
|
290
690
|
null,
|
|
291
691
|
null,
|
|
292
692
|
null
|
|
293
|
-
]
|
|
693
|
+
],
|
|
694
|
+
"lines_covered_percent": 100.0,
|
|
695
|
+
"covered_lines": 3,
|
|
696
|
+
"missed_lines": 0,
|
|
697
|
+
"omitted_lines": 3,
|
|
698
|
+
"total_lines": 3
|
|
294
699
|
},
|
|
295
|
-
"
|
|
700
|
+
"lib/senec/local/connection.rb": {
|
|
701
|
+
"source": [
|
|
702
|
+
"require 'faraday'",
|
|
703
|
+
"require 'faraday/net_http_persistent'",
|
|
704
|
+
"require 'faraday-request-timer'",
|
|
705
|
+
"require 'forwardable'",
|
|
706
|
+
"",
|
|
707
|
+
"module Senec",
|
|
708
|
+
" module Local",
|
|
709
|
+
" class Connection",
|
|
710
|
+
" def initialize(host:, schema: 'http')",
|
|
711
|
+
" @url = \"#{schema}://#{host}\"",
|
|
712
|
+
" end",
|
|
713
|
+
"",
|
|
714
|
+
" attr_reader :url",
|
|
715
|
+
"",
|
|
716
|
+
" extend Forwardable",
|
|
717
|
+
"",
|
|
718
|
+
" def_delegators :faraday, :get, :post",
|
|
719
|
+
"",
|
|
720
|
+
" private",
|
|
721
|
+
"",
|
|
722
|
+
" def faraday",
|
|
723
|
+
" @faraday ||= Faraday.new @url,",
|
|
724
|
+
" ssl: { verify: false },",
|
|
725
|
+
" headers: {",
|
|
726
|
+
" 'Connection' => 'keep-alive'",
|
|
727
|
+
" } do |f|",
|
|
728
|
+
" f.request :timer",
|
|
729
|
+
" f.adapter :net_http_persistent, pool_size: 5 do |http|",
|
|
730
|
+
" http.idle_timeout = 30",
|
|
731
|
+
" end",
|
|
732
|
+
" end",
|
|
733
|
+
" end",
|
|
734
|
+
" end",
|
|
735
|
+
" end",
|
|
736
|
+
"end"
|
|
737
|
+
],
|
|
296
738
|
"lines": [
|
|
297
739
|
1,
|
|
298
740
|
1,
|
|
@@ -329,9 +771,75 @@
|
|
|
329
771
|
null,
|
|
330
772
|
null,
|
|
331
773
|
null
|
|
332
|
-
]
|
|
774
|
+
],
|
|
775
|
+
"lines_covered_percent": 100.0,
|
|
776
|
+
"covered_lines": 18,
|
|
777
|
+
"missed_lines": 0,
|
|
778
|
+
"omitted_lines": 17,
|
|
779
|
+
"total_lines": 18
|
|
333
780
|
},
|
|
334
|
-
"
|
|
781
|
+
"lib/senec/local/constants.rb": {
|
|
782
|
+
"source": [
|
|
783
|
+
"module Senec",
|
|
784
|
+
" module Local",
|
|
785
|
+
" # For a full list of available vars, see https://[IP-of-your-SENEC]//Vars.html",
|
|
786
|
+
" # Comments taken from https://gist.github.com/smashnet/82ad0b9d7f0ba2e5098e6649ba08f88a",
|
|
787
|
+
" BASIC_REQUEST = {",
|
|
788
|
+
" ENERGY: {",
|
|
789
|
+
" STAT_STATE: '', # Current state of the system (int, see SYSTEM_STATE_NAME)",
|
|
790
|
+
" GUI_BAT_DATA_CURRENT: '', # Battery charge current: negative if discharging, positiv if charging (A)",
|
|
791
|
+
" GUI_BAT_DATA_FUEL_CHARGE: '', # Remaining battery (percent)",
|
|
792
|
+
" GUI_BAT_DATA_POWER: '', # Battery charge power: negative if discharging, positiv if charging (W)",
|
|
793
|
+
" GUI_BAT_DATA_VOLTAGE: '', # Battery voltage (V)",
|
|
794
|
+
" GUI_GRID_POW: '', # Grid power: negative if exporting, positiv if importing (W)",
|
|
795
|
+
" GUI_HOUSE_POW: '', # House power consumption (W)",
|
|
796
|
+
" GUI_INVERTER_POWER: '', # PV production (W)",
|
|
797
|
+
" STAT_HOURS_OF_OPERATION: '' # Appliance hours of operation",
|
|
798
|
+
" },",
|
|
799
|
+
" WIZARD: {",
|
|
800
|
+
" APPLICATION_VERSION: ''",
|
|
801
|
+
" },",
|
|
802
|
+
" RTC: {",
|
|
803
|
+
" UTC_OFFSET: '',",
|
|
804
|
+
" WEB_TIME: ''",
|
|
805
|
+
" },",
|
|
806
|
+
" PV1: {",
|
|
807
|
+
" MPP_POWER: '', # List: MPP power (W)",
|
|
808
|
+
" POWER_RATIO: '' # Grid export limit (percent)",
|
|
809
|
+
" },",
|
|
810
|
+
" TEMPMEASURE: {",
|
|
811
|
+
" CASE_TEMP: ''",
|
|
812
|
+
" },",
|
|
813
|
+
" WALLBOX: {",
|
|
814
|
+
" APPARENT_CHARGING_POWER: '',",
|
|
815
|
+
" EV_CONNECTED: ''",
|
|
816
|
+
" }",
|
|
817
|
+
" }.freeze",
|
|
818
|
+
"",
|
|
819
|
+
" SAFETY_CHARGE = {",
|
|
820
|
+
" ENERGY: {",
|
|
821
|
+
" SAFE_CHARGE_FORCE: 'u8_01',",
|
|
822
|
+
" SAFE_CHARGE_PROHIBIT: '',",
|
|
823
|
+
" SAFE_CHARGE_RUNNING: '',",
|
|
824
|
+
" LI_STORAGE_MODE_START: '',",
|
|
825
|
+
" LI_STORAGE_MODE_STOP: '',",
|
|
826
|
+
" LI_STORAGE_MODE_RUNNING: ''",
|
|
827
|
+
" }",
|
|
828
|
+
" }.freeze",
|
|
829
|
+
"",
|
|
830
|
+
" ALLOW_DISCHARGE = {",
|
|
831
|
+
" ENERGY: {",
|
|
832
|
+
" SAFE_CHARGE_FORCE: '',",
|
|
833
|
+
" SAFE_CHARGE_PROHIBIT: 'u8_01',",
|
|
834
|
+
" SAFE_CHARGE_RUNNING: '',",
|
|
835
|
+
" LI_STORAGE_MODE_START: '',",
|
|
836
|
+
" LI_STORAGE_MODE_STOP: '',",
|
|
837
|
+
" LI_STORAGE_MODE_RUNNING: ''",
|
|
838
|
+
" }",
|
|
839
|
+
" }.freeze",
|
|
840
|
+
" end",
|
|
841
|
+
"end"
|
|
842
|
+
],
|
|
335
843
|
"lines": [
|
|
336
844
|
1,
|
|
337
845
|
1,
|
|
@@ -392,9 +900,22 @@
|
|
|
392
900
|
null,
|
|
393
901
|
null,
|
|
394
902
|
null
|
|
395
|
-
]
|
|
903
|
+
],
|
|
904
|
+
"lines_covered_percent": 100.0,
|
|
905
|
+
"covered_lines": 5,
|
|
906
|
+
"missed_lines": 0,
|
|
907
|
+
"omitted_lines": 54,
|
|
908
|
+
"total_lines": 5
|
|
396
909
|
},
|
|
397
|
-
"
|
|
910
|
+
"lib/senec/local/error.rb": {
|
|
911
|
+
"source": [
|
|
912
|
+
"module Senec",
|
|
913
|
+
" module Local",
|
|
914
|
+
" class Error < StandardError; end",
|
|
915
|
+
" class DecodingError < StandardError; end",
|
|
916
|
+
" end",
|
|
917
|
+
"end"
|
|
918
|
+
],
|
|
398
919
|
"lines": [
|
|
399
920
|
1,
|
|
400
921
|
1,
|
|
@@ -402,9 +923,158 @@
|
|
|
402
923
|
1,
|
|
403
924
|
null,
|
|
404
925
|
null
|
|
405
|
-
]
|
|
926
|
+
],
|
|
927
|
+
"lines_covered_percent": 100.0,
|
|
928
|
+
"covered_lines": 4,
|
|
929
|
+
"missed_lines": 0,
|
|
930
|
+
"omitted_lines": 2,
|
|
931
|
+
"total_lines": 4
|
|
406
932
|
},
|
|
407
|
-
"
|
|
933
|
+
"lib/senec/local/request.rb": {
|
|
934
|
+
"source": [
|
|
935
|
+
"require_relative 'value'",
|
|
936
|
+
"require_relative 'constants'",
|
|
937
|
+
"",
|
|
938
|
+
"module Senec",
|
|
939
|
+
" module Local",
|
|
940
|
+
" class Request",
|
|
941
|
+
" def initialize(connection:, body: BASIC_REQUEST, state_names: nil)",
|
|
942
|
+
" @connection = connection",
|
|
943
|
+
" @body = body",
|
|
944
|
+
" @state_names = state_names",
|
|
945
|
+
" end",
|
|
946
|
+
"",
|
|
947
|
+
" attr_reader :connection, :body, :state_names",
|
|
948
|
+
"",
|
|
949
|
+
" def perform! # rubocop:disable Naming/PredicateMethod",
|
|
950
|
+
" parsed_response",
|
|
951
|
+
" true",
|
|
952
|
+
" end",
|
|
953
|
+
"",
|
|
954
|
+
" def house_power",
|
|
955
|
+
" get('ENERGY', 'GUI_HOUSE_POW')",
|
|
956
|
+
" end",
|
|
957
|
+
"",
|
|
958
|
+
" def inverter_power",
|
|
959
|
+
" get('ENERGY', 'GUI_INVERTER_POWER')",
|
|
960
|
+
" end",
|
|
961
|
+
"",
|
|
962
|
+
" def mpp_power",
|
|
963
|
+
" get('PV1', 'MPP_POWER')",
|
|
964
|
+
" end",
|
|
965
|
+
"",
|
|
966
|
+
" def power_ratio",
|
|
967
|
+
" get('PV1', 'POWER_RATIO')",
|
|
968
|
+
" end",
|
|
969
|
+
"",
|
|
970
|
+
" def bat_power",
|
|
971
|
+
" get('ENERGY', 'GUI_BAT_DATA_POWER')",
|
|
972
|
+
" end",
|
|
973
|
+
"",
|
|
974
|
+
" def bat_fuel_charge",
|
|
975
|
+
" get('ENERGY', 'GUI_BAT_DATA_FUEL_CHARGE')",
|
|
976
|
+
" end",
|
|
977
|
+
"",
|
|
978
|
+
" def bat_charge_current",
|
|
979
|
+
" get('ENERGY', 'GUI_BAT_DATA_CURRENT')",
|
|
980
|
+
" end",
|
|
981
|
+
"",
|
|
982
|
+
" def bat_voltage",
|
|
983
|
+
" get('ENERGY', 'GUI_BAT_DATA_VOLTAGE')",
|
|
984
|
+
" end",
|
|
985
|
+
"",
|
|
986
|
+
" def grid_power",
|
|
987
|
+
" get('ENERGY', 'GUI_GRID_POW')",
|
|
988
|
+
" end",
|
|
989
|
+
"",
|
|
990
|
+
" def ev_connected",
|
|
991
|
+
" get('WALLBOX', 'EV_CONNECTED')",
|
|
992
|
+
" end",
|
|
993
|
+
"",
|
|
994
|
+
" def wallbox_charge_power",
|
|
995
|
+
" get('WALLBOX', 'APPARENT_CHARGING_POWER')",
|
|
996
|
+
" end",
|
|
997
|
+
"",
|
|
998
|
+
" def case_temp",
|
|
999
|
+
" get('TEMPMEASURE', 'CASE_TEMP')",
|
|
1000
|
+
" end",
|
|
1001
|
+
"",
|
|
1002
|
+
" def application_version",
|
|
1003
|
+
" get('WIZARD', 'APPLICATION_VERSION')",
|
|
1004
|
+
" end",
|
|
1005
|
+
"",
|
|
1006
|
+
" def current_state_code",
|
|
1007
|
+
" get('ENERGY', 'STAT_STATE')",
|
|
1008
|
+
" end",
|
|
1009
|
+
"",
|
|
1010
|
+
" def current_state_name",
|
|
1011
|
+
" throw RuntimeError, 'No state names provided!' unless state_names",
|
|
1012
|
+
"",
|
|
1013
|
+
" state_names[current_state_code]",
|
|
1014
|
+
" end",
|
|
1015
|
+
"",
|
|
1016
|
+
" def measure_time",
|
|
1017
|
+
" web_time = get('RTC', 'WEB_TIME')",
|
|
1018
|
+
" utc_offset = get('RTC', 'UTC_OFFSET')",
|
|
1019
|
+
"",
|
|
1020
|
+
" web_time - (utc_offset * 60)",
|
|
1021
|
+
" end",
|
|
1022
|
+
"",
|
|
1023
|
+
" def response_duration",
|
|
1024
|
+
" raw_response.env[:duration]",
|
|
1025
|
+
" end",
|
|
1026
|
+
"",
|
|
1027
|
+
" def get(*keys)",
|
|
1028
|
+
" return unless parsed_response",
|
|
1029
|
+
"",
|
|
1030
|
+
" value = parsed_response.dig(*keys)",
|
|
1031
|
+
"",
|
|
1032
|
+
" if value.is_a?(Array)",
|
|
1033
|
+
" value.map do |v|",
|
|
1034
|
+
" Value.new(v).decoded",
|
|
1035
|
+
" end",
|
|
1036
|
+
" elsif value",
|
|
1037
|
+
" Value.new(value).decoded",
|
|
1038
|
+
" else",
|
|
1039
|
+
" raise Error, \"Value missing for #{keys.join('.')}\"",
|
|
1040
|
+
" end",
|
|
1041
|
+
" rescue Senec::Local::DecodingError => e",
|
|
1042
|
+
" raise Error, \"Decoding failed for #{keys.join('.')}: #{e.message}\"",
|
|
1043
|
+
" end",
|
|
1044
|
+
"",
|
|
1045
|
+
" def path",
|
|
1046
|
+
" '/lala.cgi'",
|
|
1047
|
+
" end",
|
|
1048
|
+
"",
|
|
1049
|
+
" private",
|
|
1050
|
+
"",
|
|
1051
|
+
" def parsed_response",
|
|
1052
|
+
" @parsed_response ||= JSON.parse(raw_response.body)",
|
|
1053
|
+
" end",
|
|
1054
|
+
"",
|
|
1055
|
+
" def raw_response",
|
|
1056
|
+
" @raw_response ||= begin",
|
|
1057
|
+
" response = connection.post(path, request_body, request_header)",
|
|
1058
|
+
" raise Error, response.status unless response.success?",
|
|
1059
|
+
"",
|
|
1060
|
+
" response",
|
|
1061
|
+
" end",
|
|
1062
|
+
" end",
|
|
1063
|
+
"",
|
|
1064
|
+
" def request_body",
|
|
1065
|
+
" JSON.generate(body)",
|
|
1066
|
+
" end",
|
|
1067
|
+
"",
|
|
1068
|
+
" def request_header",
|
|
1069
|
+
" {",
|
|
1070
|
+
" 'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8',",
|
|
1071
|
+
" 'Accept' => 'application/json, text/javascript, */*; q=0.01'",
|
|
1072
|
+
" }",
|
|
1073
|
+
" end",
|
|
1074
|
+
" end",
|
|
1075
|
+
" end",
|
|
1076
|
+
"end"
|
|
1077
|
+
],
|
|
408
1078
|
"lines": [
|
|
409
1079
|
1,
|
|
410
1080
|
1,
|
|
@@ -548,9 +1218,82 @@
|
|
|
548
1218
|
null,
|
|
549
1219
|
null,
|
|
550
1220
|
null
|
|
551
|
-
]
|
|
1221
|
+
],
|
|
1222
|
+
"lines_covered_percent": 100.0,
|
|
1223
|
+
"covered_lines": 74,
|
|
1224
|
+
"missed_lines": 0,
|
|
1225
|
+
"omitted_lines": 68,
|
|
1226
|
+
"total_lines": 74
|
|
552
1227
|
},
|
|
553
|
-
"
|
|
1228
|
+
"lib/senec/local/state.rb": {
|
|
1229
|
+
"source": [
|
|
1230
|
+
"module Senec",
|
|
1231
|
+
" module Local",
|
|
1232
|
+
" class State",
|
|
1233
|
+
" def initialize(connection:)",
|
|
1234
|
+
" @connection = connection",
|
|
1235
|
+
" end",
|
|
1236
|
+
"",
|
|
1237
|
+
" attr_reader :connection",
|
|
1238
|
+
"",
|
|
1239
|
+
" # Extract state names from JavaScript file, which is formatted like this:",
|
|
1240
|
+
" #",
|
|
1241
|
+
" # var system_state_name = {",
|
|
1242
|
+
" # 0: \"FIRST STATE\",",
|
|
1243
|
+
" # 1: \"SECOND STATE\",",
|
|
1244
|
+
" # ...",
|
|
1245
|
+
" # };",
|
|
1246
|
+
" def names(language: :de)",
|
|
1247
|
+
" js_content = response(language:)",
|
|
1248
|
+
"",
|
|
1249
|
+
" # Extract the matched content",
|
|
1250
|
+
" match = js_content.match(FILE_REGEX)",
|
|
1251
|
+
" return unless match",
|
|
1252
|
+
"",
|
|
1253
|
+
" # Extract the JSON-like part",
|
|
1254
|
+
" json_like = \"{#{match[1]}}\"",
|
|
1255
|
+
"",
|
|
1256
|
+
" # The keys are numbers, which is not valid JSON, so we need to convert them to strings",
|
|
1257
|
+
" json = json_like.gsub(/(\\d+)\\s*:/, '\"\\1\":')",
|
|
1258
|
+
"",
|
|
1259
|
+
" # Convert the JSON string to a Ruby hash",
|
|
1260
|
+
" hash = JSON.parse(json)",
|
|
1261
|
+
"",
|
|
1262
|
+
" # Convert keys from strings to integers",
|
|
1263
|
+
" hash.transform_keys(&:to_i)",
|
|
1264
|
+
" end",
|
|
1265
|
+
"",
|
|
1266
|
+
" private",
|
|
1267
|
+
"",
|
|
1268
|
+
" # Regex pattern to match the system_state_name definition in the JavaScript file",
|
|
1269
|
+
" # The file may be minimized, so we need to be flexible with whitespace and line breaks",
|
|
1270
|
+
" FILE_REGEX = /system_state_name\\s*=\\s*{\\s*([^}]*)\\s*}/m",
|
|
1271
|
+
" private_constant :FILE_REGEX",
|
|
1272
|
+
"",
|
|
1273
|
+
" def response(language:)",
|
|
1274
|
+
" res = connection.get url(language:)",
|
|
1275
|
+
" raise Error, res.message unless res.success?",
|
|
1276
|
+
"",
|
|
1277
|
+
" res.body",
|
|
1278
|
+
" end",
|
|
1279
|
+
"",
|
|
1280
|
+
" # Use the JavaScript file containing English/German/Italian names from the SENEC web interface",
|
|
1281
|
+
" def url(language:)",
|
|
1282
|
+
" case language",
|
|
1283
|
+
" when :en",
|
|
1284
|
+
" '/js/EN-en.js'",
|
|
1285
|
+
" when :de",
|
|
1286
|
+
" '/js/DE-de.js'",
|
|
1287
|
+
" when :it",
|
|
1288
|
+
" '/js/IT-it.js'",
|
|
1289
|
+
" else",
|
|
1290
|
+
" raise Error, \"Language #{language} not supported\"",
|
|
1291
|
+
" end",
|
|
1292
|
+
" end",
|
|
1293
|
+
" end",
|
|
1294
|
+
" end",
|
|
1295
|
+
"end"
|
|
1296
|
+
],
|
|
554
1297
|
"lines": [
|
|
555
1298
|
1,
|
|
556
1299
|
1,
|
|
@@ -618,9 +1361,59 @@
|
|
|
618
1361
|
null,
|
|
619
1362
|
null,
|
|
620
1363
|
null
|
|
621
|
-
]
|
|
1364
|
+
],
|
|
1365
|
+
"lines_covered_percent": 100.0,
|
|
1366
|
+
"covered_lines": 27,
|
|
1367
|
+
"missed_lines": 0,
|
|
1368
|
+
"omitted_lines": 39,
|
|
1369
|
+
"total_lines": 27
|
|
622
1370
|
},
|
|
623
|
-
"
|
|
1371
|
+
"lib/senec/local/value.rb": {
|
|
1372
|
+
"source": [
|
|
1373
|
+
"module Senec",
|
|
1374
|
+
" module Local",
|
|
1375
|
+
" class Value",
|
|
1376
|
+
" def initialize(raw)",
|
|
1377
|
+
" @raw = raw",
|
|
1378
|
+
" @prefix, @value = raw&.split('_')",
|
|
1379
|
+
" end",
|
|
1380
|
+
"",
|
|
1381
|
+
" attr_reader :prefix, :value, :raw",
|
|
1382
|
+
"",
|
|
1383
|
+
" def decoded",
|
|
1384
|
+
" case prefix",
|
|
1385
|
+
" when 'fl'",
|
|
1386
|
+
" decoded_float(value)",
|
|
1387
|
+
" when 'i3', 'u1', 'u3', 'u6', 'u8'",
|
|
1388
|
+
" decoded_int(value)",
|
|
1389
|
+
" when 'st'",
|
|
1390
|
+
" value",
|
|
1391
|
+
" # TODO: There are some more prefixes to handle",
|
|
1392
|
+
" else",
|
|
1393
|
+
" raise DecodingError, \"Unknown value '#{@raw}'\"",
|
|
1394
|
+
" end",
|
|
1395
|
+
" end",
|
|
1396
|
+
"",
|
|
1397
|
+
" alias to_i decoded",
|
|
1398
|
+
" alias to_f decoded",
|
|
1399
|
+
" alias to_s decoded",
|
|
1400
|
+
"",
|
|
1401
|
+
" private",
|
|
1402
|
+
"",
|
|
1403
|
+
" PREFIXES = %w[fl i3 u1 u3 u6 u8 st].freeze",
|
|
1404
|
+
" private_constant :PREFIXES",
|
|
1405
|
+
"",
|
|
1406
|
+
" def decoded_float(hex)",
|
|
1407
|
+
" [\"0x#{hex}\".to_i(16)].pack('L').unpack1('F').round(1)",
|
|
1408
|
+
" end",
|
|
1409
|
+
"",
|
|
1410
|
+
" def decoded_int(hex)",
|
|
1411
|
+
" \"0x#{hex}\".to_i(16)",
|
|
1412
|
+
" end",
|
|
1413
|
+
" end",
|
|
1414
|
+
" end",
|
|
1415
|
+
"end"
|
|
1416
|
+
],
|
|
624
1417
|
"lines": [
|
|
625
1418
|
1,
|
|
626
1419
|
1,
|
|
@@ -665,879 +1458,14 @@
|
|
|
665
1458
|
null,
|
|
666
1459
|
null,
|
|
667
1460
|
null
|
|
668
|
-
]
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
"
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
null,
|
|
675
|
-
23,
|
|
676
|
-
22,
|
|
677
|
-
22,
|
|
678
|
-
null,
|
|
679
|
-
1,
|
|
680
|
-
5,
|
|
681
|
-
null,
|
|
682
|
-
1,
|
|
683
|
-
3,
|
|
684
|
-
null,
|
|
685
|
-
null,
|
|
686
|
-
1,
|
|
687
|
-
2,
|
|
688
|
-
null,
|
|
689
|
-
3,
|
|
690
|
-
null,
|
|
691
|
-
null,
|
|
692
|
-
1,
|
|
693
|
-
3,
|
|
694
|
-
null,
|
|
695
|
-
null,
|
|
696
|
-
1,
|
|
697
|
-
2,
|
|
698
|
-
null,
|
|
699
|
-
3,
|
|
700
|
-
null,
|
|
701
|
-
null,
|
|
702
|
-
null,
|
|
703
|
-
1,
|
|
704
|
-
4,
|
|
705
|
-
null,
|
|
706
|
-
4,
|
|
707
|
-
null,
|
|
708
|
-
2,
|
|
709
|
-
2,
|
|
710
|
-
null,
|
|
711
|
-
1,
|
|
712
|
-
1,
|
|
713
|
-
null,
|
|
714
|
-
null,
|
|
715
|
-
null,
|
|
716
|
-
null,
|
|
717
|
-
null,
|
|
718
|
-
null,
|
|
719
|
-
null,
|
|
720
|
-
null,
|
|
721
|
-
null,
|
|
722
|
-
null,
|
|
723
|
-
null,
|
|
724
|
-
null,
|
|
725
|
-
null,
|
|
726
|
-
null,
|
|
727
|
-
null,
|
|
728
|
-
1,
|
|
729
|
-
4,
|
|
730
|
-
null,
|
|
731
|
-
4,
|
|
732
|
-
null,
|
|
733
|
-
1,
|
|
734
|
-
3,
|
|
735
|
-
null,
|
|
736
|
-
2,
|
|
737
|
-
null,
|
|
738
|
-
1,
|
|
739
|
-
1,
|
|
740
|
-
null,
|
|
741
|
-
null,
|
|
742
|
-
null,
|
|
743
|
-
null,
|
|
744
|
-
null,
|
|
745
|
-
null,
|
|
746
|
-
null,
|
|
747
|
-
null,
|
|
748
|
-
null,
|
|
749
|
-
null,
|
|
750
|
-
null,
|
|
751
|
-
1,
|
|
752
|
-
2,
|
|
753
|
-
null,
|
|
754
|
-
1,
|
|
755
|
-
1,
|
|
756
|
-
null,
|
|
757
|
-
null,
|
|
758
|
-
null,
|
|
759
|
-
null,
|
|
760
|
-
1,
|
|
761
|
-
4,
|
|
762
|
-
null,
|
|
763
|
-
4,
|
|
764
|
-
null,
|
|
765
|
-
1,
|
|
766
|
-
3,
|
|
767
|
-
null,
|
|
768
|
-
2,
|
|
769
|
-
null,
|
|
770
|
-
1,
|
|
771
|
-
1,
|
|
772
|
-
null,
|
|
773
|
-
null,
|
|
774
|
-
null,
|
|
775
|
-
null,
|
|
776
|
-
null,
|
|
777
|
-
null,
|
|
778
|
-
null,
|
|
779
|
-
null,
|
|
780
|
-
null,
|
|
781
|
-
null,
|
|
782
|
-
null,
|
|
783
|
-
null,
|
|
784
|
-
1,
|
|
785
|
-
2,
|
|
786
|
-
null,
|
|
787
|
-
1,
|
|
788
|
-
1,
|
|
789
|
-
null,
|
|
790
|
-
null,
|
|
791
|
-
null,
|
|
792
|
-
null,
|
|
793
|
-
1,
|
|
794
|
-
4,
|
|
795
|
-
null,
|
|
796
|
-
4,
|
|
797
|
-
null,
|
|
798
|
-
1,
|
|
799
|
-
3,
|
|
800
|
-
null,
|
|
801
|
-
2,
|
|
802
|
-
null,
|
|
803
|
-
1,
|
|
804
|
-
1,
|
|
805
|
-
null,
|
|
806
|
-
null,
|
|
807
|
-
null,
|
|
808
|
-
null,
|
|
809
|
-
null,
|
|
810
|
-
null,
|
|
811
|
-
null,
|
|
812
|
-
null,
|
|
813
|
-
null,
|
|
814
|
-
null,
|
|
815
|
-
null,
|
|
816
|
-
null,
|
|
817
|
-
null,
|
|
818
|
-
null,
|
|
819
|
-
null,
|
|
820
|
-
null,
|
|
821
|
-
null,
|
|
822
|
-
1,
|
|
823
|
-
2,
|
|
824
|
-
null,
|
|
825
|
-
1,
|
|
826
|
-
1,
|
|
827
|
-
null,
|
|
828
|
-
null,
|
|
829
|
-
null,
|
|
830
|
-
null,
|
|
831
|
-
1,
|
|
832
|
-
4,
|
|
833
|
-
null,
|
|
834
|
-
4,
|
|
835
|
-
null,
|
|
836
|
-
1,
|
|
837
|
-
3,
|
|
838
|
-
null,
|
|
839
|
-
2,
|
|
840
|
-
null,
|
|
841
|
-
1,
|
|
842
|
-
1,
|
|
843
|
-
null,
|
|
844
|
-
null,
|
|
845
|
-
null,
|
|
846
|
-
null,
|
|
847
|
-
null,
|
|
848
|
-
null,
|
|
849
|
-
null,
|
|
850
|
-
null,
|
|
851
|
-
null,
|
|
852
|
-
null,
|
|
853
|
-
null,
|
|
854
|
-
null,
|
|
855
|
-
null,
|
|
856
|
-
null,
|
|
857
|
-
null,
|
|
858
|
-
null,
|
|
859
|
-
null,
|
|
860
|
-
1,
|
|
861
|
-
2,
|
|
862
|
-
null,
|
|
863
|
-
1,
|
|
864
|
-
1,
|
|
865
|
-
null,
|
|
866
|
-
null,
|
|
867
|
-
null,
|
|
868
|
-
null,
|
|
869
|
-
1,
|
|
870
|
-
4,
|
|
871
|
-
null,
|
|
872
|
-
4,
|
|
873
|
-
null,
|
|
874
|
-
4,
|
|
875
|
-
null,
|
|
876
|
-
1,
|
|
877
|
-
1,
|
|
878
|
-
1,
|
|
879
|
-
1,
|
|
880
|
-
null,
|
|
881
|
-
1,
|
|
882
|
-
1,
|
|
883
|
-
null,
|
|
884
|
-
null,
|
|
885
|
-
1,
|
|
886
|
-
1,
|
|
887
|
-
1,
|
|
888
|
-
1,
|
|
889
|
-
1,
|
|
890
|
-
1,
|
|
891
|
-
null,
|
|
892
|
-
1,
|
|
893
|
-
1,
|
|
894
|
-
null,
|
|
895
|
-
null,
|
|
896
|
-
1,
|
|
897
|
-
1,
|
|
898
|
-
1,
|
|
899
|
-
1,
|
|
900
|
-
2,
|
|
901
|
-
1,
|
|
902
|
-
1,
|
|
903
|
-
null,
|
|
904
|
-
1,
|
|
905
|
-
1,
|
|
906
|
-
null,
|
|
907
|
-
null,
|
|
908
|
-
null
|
|
909
|
-
]
|
|
910
|
-
},
|
|
911
|
-
"/Users/ledermann/Projects/solectrus/senec/spec/lib/senec/local/request_spec.rb": {
|
|
912
|
-
"lines": [
|
|
913
|
-
1,
|
|
914
|
-
23,
|
|
915
|
-
1,
|
|
916
|
-
23,
|
|
917
|
-
null,
|
|
918
|
-
null,
|
|
919
|
-
1,
|
|
920
|
-
19,
|
|
921
|
-
null,
|
|
922
|
-
1,
|
|
923
|
-
2,
|
|
924
|
-
null,
|
|
925
|
-
2,
|
|
926
|
-
null,
|
|
927
|
-
null,
|
|
928
|
-
1,
|
|
929
|
-
2,
|
|
930
|
-
null,
|
|
931
|
-
2,
|
|
932
|
-
null,
|
|
933
|
-
null,
|
|
934
|
-
1,
|
|
935
|
-
2,
|
|
936
|
-
null,
|
|
937
|
-
2,
|
|
938
|
-
null,
|
|
939
|
-
null,
|
|
940
|
-
1,
|
|
941
|
-
2,
|
|
942
|
-
null,
|
|
943
|
-
2,
|
|
944
|
-
null,
|
|
945
|
-
null,
|
|
946
|
-
1,
|
|
947
|
-
2,
|
|
948
|
-
null,
|
|
949
|
-
2,
|
|
950
|
-
null,
|
|
951
|
-
null,
|
|
952
|
-
1,
|
|
953
|
-
2,
|
|
954
|
-
null,
|
|
955
|
-
2,
|
|
956
|
-
null,
|
|
957
|
-
null,
|
|
958
|
-
1,
|
|
959
|
-
2,
|
|
960
|
-
null,
|
|
961
|
-
2,
|
|
962
|
-
null,
|
|
963
|
-
null,
|
|
964
|
-
1,
|
|
965
|
-
2,
|
|
966
|
-
null,
|
|
967
|
-
2,
|
|
968
|
-
null,
|
|
969
|
-
null,
|
|
970
|
-
1,
|
|
971
|
-
2,
|
|
972
|
-
null,
|
|
973
|
-
2,
|
|
974
|
-
null,
|
|
975
|
-
null,
|
|
976
|
-
1,
|
|
977
|
-
2,
|
|
978
|
-
null,
|
|
979
|
-
2,
|
|
980
|
-
null,
|
|
981
|
-
null,
|
|
982
|
-
1,
|
|
983
|
-
2,
|
|
984
|
-
null,
|
|
985
|
-
2,
|
|
986
|
-
null,
|
|
987
|
-
null,
|
|
988
|
-
1,
|
|
989
|
-
2,
|
|
990
|
-
null,
|
|
991
|
-
2,
|
|
992
|
-
null,
|
|
993
|
-
null,
|
|
994
|
-
1,
|
|
995
|
-
2,
|
|
996
|
-
null,
|
|
997
|
-
2,
|
|
998
|
-
null,
|
|
999
|
-
null,
|
|
1000
|
-
1,
|
|
1001
|
-
3,
|
|
1002
|
-
null,
|
|
1003
|
-
2,
|
|
1004
|
-
2,
|
|
1005
|
-
null,
|
|
1006
|
-
null,
|
|
1007
|
-
1,
|
|
1008
|
-
2,
|
|
1009
|
-
null,
|
|
1010
|
-
2,
|
|
1011
|
-
null,
|
|
1012
|
-
null,
|
|
1013
|
-
1,
|
|
1014
|
-
2,
|
|
1015
|
-
null,
|
|
1016
|
-
2,
|
|
1017
|
-
null,
|
|
1018
|
-
null,
|
|
1019
|
-
1,
|
|
1020
|
-
2,
|
|
1021
|
-
null,
|
|
1022
|
-
2,
|
|
1023
|
-
null,
|
|
1024
|
-
null,
|
|
1025
|
-
null,
|
|
1026
|
-
1,
|
|
1027
|
-
2,
|
|
1028
|
-
null,
|
|
1029
|
-
2,
|
|
1030
|
-
null,
|
|
1031
|
-
1,
|
|
1032
|
-
2,
|
|
1033
|
-
null,
|
|
1034
|
-
null,
|
|
1035
|
-
null,
|
|
1036
|
-
1,
|
|
1037
|
-
2,
|
|
1038
|
-
null,
|
|
1039
|
-
1,
|
|
1040
|
-
1,
|
|
1041
|
-
null,
|
|
1042
|
-
null,
|
|
1043
|
-
1,
|
|
1044
|
-
2,
|
|
1045
|
-
null,
|
|
1046
|
-
null,
|
|
1047
|
-
null,
|
|
1048
|
-
1,
|
|
1049
|
-
2,
|
|
1050
|
-
null,
|
|
1051
|
-
2,
|
|
1052
|
-
null,
|
|
1053
|
-
1,
|
|
1054
|
-
2,
|
|
1055
|
-
null,
|
|
1056
|
-
null,
|
|
1057
|
-
null,
|
|
1058
|
-
1,
|
|
1059
|
-
1,
|
|
1060
|
-
1,
|
|
1061
|
-
null,
|
|
1062
|
-
null,
|
|
1063
|
-
null,
|
|
1064
|
-
null,
|
|
1065
|
-
null,
|
|
1066
|
-
1,
|
|
1067
|
-
2,
|
|
1068
|
-
null,
|
|
1069
|
-
2,
|
|
1070
|
-
null,
|
|
1071
|
-
null,
|
|
1072
|
-
null,
|
|
1073
|
-
1,
|
|
1074
|
-
1,
|
|
1075
|
-
1,
|
|
1076
|
-
null,
|
|
1077
|
-
null,
|
|
1078
|
-
null,
|
|
1079
|
-
null,
|
|
1080
|
-
null,
|
|
1081
|
-
1,
|
|
1082
|
-
2,
|
|
1083
|
-
null,
|
|
1084
|
-
2,
|
|
1085
|
-
null,
|
|
1086
|
-
null,
|
|
1087
|
-
null,
|
|
1088
|
-
1,
|
|
1089
|
-
1,
|
|
1090
|
-
3,
|
|
1091
|
-
null,
|
|
1092
|
-
null,
|
|
1093
|
-
null,
|
|
1094
|
-
null,
|
|
1095
|
-
null,
|
|
1096
|
-
null,
|
|
1097
|
-
null,
|
|
1098
|
-
null,
|
|
1099
|
-
null,
|
|
1100
|
-
1,
|
|
1101
|
-
1,
|
|
1102
|
-
2,
|
|
1103
|
-
null,
|
|
1104
|
-
2,
|
|
1105
|
-
null,
|
|
1106
|
-
null,
|
|
1107
|
-
1,
|
|
1108
|
-
2,
|
|
1109
|
-
null,
|
|
1110
|
-
1,
|
|
1111
|
-
2,
|
|
1112
|
-
null,
|
|
1113
|
-
null,
|
|
1114
|
-
null,
|
|
1115
|
-
1,
|
|
1116
|
-
2,
|
|
1117
|
-
null,
|
|
1118
|
-
1,
|
|
1119
|
-
1,
|
|
1120
|
-
null,
|
|
1121
|
-
null,
|
|
1122
|
-
null,
|
|
1123
|
-
null,
|
|
1124
|
-
null,
|
|
1125
|
-
null,
|
|
1126
|
-
1,
|
|
1127
|
-
2,
|
|
1128
|
-
null,
|
|
1129
|
-
null,
|
|
1130
|
-
null,
|
|
1131
|
-
null,
|
|
1132
|
-
null,
|
|
1133
|
-
null
|
|
1134
|
-
]
|
|
1135
|
-
},
|
|
1136
|
-
"/Users/ledermann/Projects/solectrus/senec/spec/lib/senec/local/state_spec.rb": {
|
|
1137
|
-
"lines": [
|
|
1138
|
-
1,
|
|
1139
|
-
6,
|
|
1140
|
-
null,
|
|
1141
|
-
6,
|
|
1142
|
-
null,
|
|
1143
|
-
1,
|
|
1144
|
-
5,
|
|
1145
|
-
null,
|
|
1146
|
-
null,
|
|
1147
|
-
null,
|
|
1148
|
-
1,
|
|
1149
|
-
3,
|
|
1150
|
-
null,
|
|
1151
|
-
null,
|
|
1152
|
-
null,
|
|
1153
|
-
null,
|
|
1154
|
-
null,
|
|
1155
|
-
null,
|
|
1156
|
-
null,
|
|
1157
|
-
null,
|
|
1158
|
-
null,
|
|
1159
|
-
null,
|
|
1160
|
-
null,
|
|
1161
|
-
null,
|
|
1162
|
-
null,
|
|
1163
|
-
null,
|
|
1164
|
-
null,
|
|
1165
|
-
1,
|
|
1166
|
-
1,
|
|
1167
|
-
null,
|
|
1168
|
-
null,
|
|
1169
|
-
null,
|
|
1170
|
-
null,
|
|
1171
|
-
1,
|
|
1172
|
-
6,
|
|
1173
|
-
null,
|
|
1174
|
-
1,
|
|
1175
|
-
null,
|
|
1176
|
-
4,
|
|
1177
|
-
null,
|
|
1178
|
-
null,
|
|
1179
|
-
null,
|
|
1180
|
-
null,
|
|
1181
|
-
null,
|
|
1182
|
-
null,
|
|
1183
|
-
null,
|
|
1184
|
-
null,
|
|
1185
|
-
1,
|
|
1186
|
-
2,
|
|
1187
|
-
null,
|
|
1188
|
-
1,
|
|
1189
|
-
1,
|
|
1190
|
-
null,
|
|
1191
|
-
null,
|
|
1192
|
-
2,
|
|
1193
|
-
null,
|
|
1194
|
-
null,
|
|
1195
|
-
1,
|
|
1196
|
-
2,
|
|
1197
|
-
null,
|
|
1198
|
-
1,
|
|
1199
|
-
1,
|
|
1200
|
-
null,
|
|
1201
|
-
null,
|
|
1202
|
-
2,
|
|
1203
|
-
null,
|
|
1204
|
-
null,
|
|
1205
|
-
1,
|
|
1206
|
-
2,
|
|
1207
|
-
null,
|
|
1208
|
-
1,
|
|
1209
|
-
1,
|
|
1210
|
-
null,
|
|
1211
|
-
null,
|
|
1212
|
-
2,
|
|
1213
|
-
null,
|
|
1214
|
-
null,
|
|
1215
|
-
1,
|
|
1216
|
-
2,
|
|
1217
|
-
null,
|
|
1218
|
-
1,
|
|
1219
|
-
1,
|
|
1220
|
-
null,
|
|
1221
|
-
null,
|
|
1222
|
-
2,
|
|
1223
|
-
null,
|
|
1224
|
-
null,
|
|
1225
|
-
1,
|
|
1226
|
-
2,
|
|
1227
|
-
null,
|
|
1228
|
-
1,
|
|
1229
|
-
2,
|
|
1230
|
-
null,
|
|
1231
|
-
null,
|
|
1232
|
-
null,
|
|
1233
|
-
null
|
|
1234
|
-
]
|
|
1235
|
-
},
|
|
1236
|
-
"/Users/ledermann/Projects/solectrus/senec/spec/lib/senec/local/value_spec.rb": {
|
|
1237
|
-
"lines": [
|
|
1238
|
-
1,
|
|
1239
|
-
17,
|
|
1240
|
-
null,
|
|
1241
|
-
1,
|
|
1242
|
-
17,
|
|
1243
|
-
null,
|
|
1244
|
-
1,
|
|
1245
|
-
3,
|
|
1246
|
-
null,
|
|
1247
|
-
2,
|
|
1248
|
-
2,
|
|
1249
|
-
null,
|
|
1250
|
-
null,
|
|
1251
|
-
1,
|
|
1252
|
-
3,
|
|
1253
|
-
null,
|
|
1254
|
-
2,
|
|
1255
|
-
2,
|
|
1256
|
-
null,
|
|
1257
|
-
null,
|
|
1258
|
-
1,
|
|
1259
|
-
3,
|
|
1260
|
-
null,
|
|
1261
|
-
2,
|
|
1262
|
-
2,
|
|
1263
|
-
null,
|
|
1264
|
-
null,
|
|
1265
|
-
1,
|
|
1266
|
-
3,
|
|
1267
|
-
null,
|
|
1268
|
-
2,
|
|
1269
|
-
2,
|
|
1270
|
-
null,
|
|
1271
|
-
null,
|
|
1272
|
-
1,
|
|
1273
|
-
3,
|
|
1274
|
-
null,
|
|
1275
|
-
2,
|
|
1276
|
-
2,
|
|
1277
|
-
null,
|
|
1278
|
-
null,
|
|
1279
|
-
1,
|
|
1280
|
-
3,
|
|
1281
|
-
null,
|
|
1282
|
-
2,
|
|
1283
|
-
2,
|
|
1284
|
-
null,
|
|
1285
|
-
null,
|
|
1286
|
-
1,
|
|
1287
|
-
3,
|
|
1288
|
-
null,
|
|
1289
|
-
2,
|
|
1290
|
-
2,
|
|
1291
|
-
null,
|
|
1292
|
-
null,
|
|
1293
|
-
1,
|
|
1294
|
-
2,
|
|
1295
|
-
null,
|
|
1296
|
-
3,
|
|
1297
|
-
null,
|
|
1298
|
-
null,
|
|
1299
|
-
1,
|
|
1300
|
-
2,
|
|
1301
|
-
null,
|
|
1302
|
-
1,
|
|
1303
|
-
2,
|
|
1304
|
-
null,
|
|
1305
|
-
null,
|
|
1306
|
-
null,
|
|
1307
|
-
null
|
|
1308
|
-
]
|
|
1309
|
-
},
|
|
1310
|
-
"/Users/ledermann/Projects/solectrus/senec/spec/senec_spec.rb": {
|
|
1311
|
-
"lines": [
|
|
1312
|
-
1,
|
|
1313
|
-
1,
|
|
1314
|
-
1,
|
|
1315
|
-
null,
|
|
1316
|
-
null
|
|
1317
|
-
]
|
|
1318
|
-
},
|
|
1319
|
-
"/Users/ledermann/Projects/solectrus/senec/spec/support/vcr.rb": {
|
|
1320
|
-
"lines": [
|
|
1321
|
-
1,
|
|
1322
|
-
null,
|
|
1323
|
-
1,
|
|
1324
|
-
1,
|
|
1325
|
-
9,
|
|
1326
|
-
null,
|
|
1327
|
-
null,
|
|
1328
|
-
null,
|
|
1329
|
-
1,
|
|
1330
|
-
1,
|
|
1331
|
-
6,
|
|
1332
|
-
null,
|
|
1333
|
-
null,
|
|
1334
|
-
null,
|
|
1335
|
-
1,
|
|
1336
|
-
2,
|
|
1337
|
-
23,
|
|
1338
|
-
null,
|
|
1339
|
-
null,
|
|
1340
|
-
null,
|
|
1341
|
-
1,
|
|
1342
|
-
4,
|
|
1343
|
-
null,
|
|
1344
|
-
null,
|
|
1345
|
-
null,
|
|
1346
|
-
1,
|
|
1347
|
-
null,
|
|
1348
|
-
null,
|
|
1349
|
-
null,
|
|
1350
|
-
null,
|
|
1351
|
-
null,
|
|
1352
|
-
null,
|
|
1353
|
-
null,
|
|
1354
|
-
null,
|
|
1355
|
-
null,
|
|
1356
|
-
null,
|
|
1357
|
-
null,
|
|
1358
|
-
null,
|
|
1359
|
-
null,
|
|
1360
|
-
null,
|
|
1361
|
-
null,
|
|
1362
|
-
null,
|
|
1363
|
-
null,
|
|
1364
|
-
null,
|
|
1365
|
-
null,
|
|
1366
|
-
null,
|
|
1367
|
-
null,
|
|
1368
|
-
null,
|
|
1369
|
-
null,
|
|
1370
|
-
null,
|
|
1371
|
-
null,
|
|
1372
|
-
null,
|
|
1373
|
-
null,
|
|
1374
|
-
null,
|
|
1375
|
-
null,
|
|
1376
|
-
null,
|
|
1377
|
-
null,
|
|
1378
|
-
null,
|
|
1379
|
-
null,
|
|
1380
|
-
null,
|
|
1381
|
-
null,
|
|
1382
|
-
null,
|
|
1383
|
-
null,
|
|
1384
|
-
null,
|
|
1385
|
-
null,
|
|
1386
|
-
null,
|
|
1387
|
-
null,
|
|
1388
|
-
null,
|
|
1389
|
-
null,
|
|
1390
|
-
null,
|
|
1391
|
-
null,
|
|
1392
|
-
null,
|
|
1393
|
-
null,
|
|
1394
|
-
null,
|
|
1395
|
-
null,
|
|
1396
|
-
null,
|
|
1397
|
-
null,
|
|
1398
|
-
null,
|
|
1399
|
-
null,
|
|
1400
|
-
null,
|
|
1401
|
-
null,
|
|
1402
|
-
null,
|
|
1403
|
-
null,
|
|
1404
|
-
null,
|
|
1405
|
-
null,
|
|
1406
|
-
null,
|
|
1407
|
-
null,
|
|
1408
|
-
null,
|
|
1409
|
-
1,
|
|
1410
|
-
226,
|
|
1411
|
-
null,
|
|
1412
|
-
null,
|
|
1413
|
-
null,
|
|
1414
|
-
null,
|
|
1415
|
-
null,
|
|
1416
|
-
null,
|
|
1417
|
-
null,
|
|
1418
|
-
null,
|
|
1419
|
-
null,
|
|
1420
|
-
null,
|
|
1421
|
-
1,
|
|
1422
|
-
226,
|
|
1423
|
-
null,
|
|
1424
|
-
null,
|
|
1425
|
-
1,
|
|
1426
|
-
1,
|
|
1427
|
-
1,
|
|
1428
|
-
1,
|
|
1429
|
-
1,
|
|
1430
|
-
null,
|
|
1431
|
-
1,
|
|
1432
|
-
1,
|
|
1433
|
-
null,
|
|
1434
|
-
null,
|
|
1435
|
-
null,
|
|
1436
|
-
null,
|
|
1437
|
-
null,
|
|
1438
|
-
null,
|
|
1439
|
-
null,
|
|
1440
|
-
1,
|
|
1441
|
-
113,
|
|
1442
|
-
113,
|
|
1443
|
-
113,
|
|
1444
|
-
null,
|
|
1445
|
-
null,
|
|
1446
|
-
null,
|
|
1447
|
-
1,
|
|
1448
|
-
115,
|
|
1449
|
-
null,
|
|
1450
|
-
1,
|
|
1451
|
-
115,
|
|
1452
|
-
null,
|
|
1453
|
-
1,
|
|
1454
|
-
115,
|
|
1455
|
-
null,
|
|
1456
|
-
1,
|
|
1457
|
-
115,
|
|
1458
|
-
115,
|
|
1459
|
-
null,
|
|
1460
|
-
1,
|
|
1461
|
-
115,
|
|
1462
|
-
115,
|
|
1463
|
-
null,
|
|
1464
|
-
null,
|
|
1465
|
-
null,
|
|
1466
|
-
"ignored",
|
|
1467
|
-
"ignored",
|
|
1468
|
-
"ignored",
|
|
1469
|
-
"ignored",
|
|
1470
|
-
"ignored",
|
|
1471
|
-
"ignored",
|
|
1472
|
-
"ignored",
|
|
1473
|
-
"ignored",
|
|
1474
|
-
"ignored",
|
|
1475
|
-
"ignored",
|
|
1476
|
-
"ignored",
|
|
1477
|
-
"ignored",
|
|
1478
|
-
"ignored",
|
|
1479
|
-
"ignored",
|
|
1480
|
-
"ignored",
|
|
1481
|
-
"ignored",
|
|
1482
|
-
"ignored",
|
|
1483
|
-
"ignored",
|
|
1484
|
-
"ignored",
|
|
1485
|
-
"ignored",
|
|
1486
|
-
"ignored",
|
|
1487
|
-
"ignored",
|
|
1488
|
-
"ignored",
|
|
1489
|
-
"ignored",
|
|
1490
|
-
"ignored",
|
|
1491
|
-
"ignored",
|
|
1492
|
-
"ignored",
|
|
1493
|
-
"ignored",
|
|
1494
|
-
"ignored",
|
|
1495
|
-
"ignored",
|
|
1496
|
-
"ignored",
|
|
1497
|
-
"ignored",
|
|
1498
|
-
"ignored",
|
|
1499
|
-
"ignored",
|
|
1500
|
-
"ignored",
|
|
1501
|
-
"ignored",
|
|
1502
|
-
"ignored",
|
|
1503
|
-
"ignored",
|
|
1504
|
-
"ignored",
|
|
1505
|
-
"ignored",
|
|
1506
|
-
"ignored",
|
|
1507
|
-
"ignored",
|
|
1508
|
-
"ignored",
|
|
1509
|
-
"ignored",
|
|
1510
|
-
"ignored",
|
|
1511
|
-
"ignored",
|
|
1512
|
-
"ignored",
|
|
1513
|
-
"ignored",
|
|
1514
|
-
"ignored",
|
|
1515
|
-
"ignored",
|
|
1516
|
-
"ignored",
|
|
1517
|
-
"ignored",
|
|
1518
|
-
"ignored",
|
|
1519
|
-
null,
|
|
1520
|
-
null,
|
|
1521
|
-
null,
|
|
1522
|
-
null,
|
|
1523
|
-
1,
|
|
1524
|
-
1,
|
|
1525
|
-
null,
|
|
1526
|
-
1,
|
|
1527
|
-
null,
|
|
1528
|
-
null,
|
|
1529
|
-
null,
|
|
1530
|
-
1,
|
|
1531
|
-
5,
|
|
1532
|
-
5,
|
|
1533
|
-
null,
|
|
1534
|
-
null,
|
|
1535
|
-
null,
|
|
1536
|
-
null,
|
|
1537
|
-
71,
|
|
1538
|
-
null
|
|
1539
|
-
]
|
|
1461
|
+
],
|
|
1462
|
+
"lines_covered_percent": 100.0,
|
|
1463
|
+
"covered_lines": 23,
|
|
1464
|
+
"missed_lines": 0,
|
|
1465
|
+
"omitted_lines": 20,
|
|
1466
|
+
"total_lines": 23
|
|
1540
1467
|
}
|
|
1541
1468
|
},
|
|
1542
|
-
"groups": {}
|
|
1469
|
+
"groups": {},
|
|
1470
|
+
"errors": {}
|
|
1543
1471
|
}
|