senec 0.23.0 → 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 +77 -68
- data/.ruby-lsp/Gemfile +1 -1
- data/.ruby-lsp/Gemfile.lock +50 -58
- data/.ruby-lsp/freshness_hash +1 -0
- data/.ruby-lsp/last_updated +1 -1
- data/coverage/.last_run.json +1 -1
- data/coverage/.report_stamp +0 -0
- data/coverage/.resultset.json +1 -1571
- data/coverage/coverage.json +854 -955
- data/coverage/index.html +68 -17109
- data/lib/senec/cloud/connection.rb +28 -12
- data/lib/senec/version.rb +1 -1
- data/pkg/senec-0.23.0.gem +0 -0
- metadata +5 -2
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,24 +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
|
-
|
|
463
|
+
28,
|
|
464
|
+
28,
|
|
465
|
+
28,
|
|
466
|
+
null,
|
|
467
|
+
null,
|
|
468
|
+
1,
|
|
469
|
+
28,
|
|
110
470
|
null,
|
|
111
471
|
2,
|
|
112
472
|
null,
|
|
113
|
-
|
|
473
|
+
26,
|
|
114
474
|
null,
|
|
115
475
|
null,
|
|
116
|
-
|
|
476
|
+
28,
|
|
117
477
|
null,
|
|
118
478
|
3,
|
|
119
479
|
2,
|
|
@@ -128,21 +488,21 @@
|
|
|
128
488
|
null,
|
|
129
489
|
null,
|
|
130
490
|
null,
|
|
131
|
-
|
|
491
|
+
26,
|
|
132
492
|
null,
|
|
133
493
|
null,
|
|
134
494
|
1,
|
|
135
|
-
|
|
136
|
-
|
|
495
|
+
26,
|
|
496
|
+
26,
|
|
137
497
|
null,
|
|
138
|
-
|
|
498
|
+
26,
|
|
139
499
|
null,
|
|
140
|
-
|
|
141
|
-
|
|
500
|
+
26,
|
|
501
|
+
26,
|
|
142
502
|
null,
|
|
143
|
-
|
|
503
|
+
26,
|
|
144
504
|
null,
|
|
145
|
-
|
|
505
|
+
26,
|
|
146
506
|
null,
|
|
147
507
|
null,
|
|
148
508
|
1,
|
|
@@ -153,27 +513,27 @@
|
|
|
153
513
|
null,
|
|
154
514
|
null,
|
|
155
515
|
1,
|
|
156
|
-
|
|
516
|
+
26,
|
|
157
517
|
null,
|
|
158
|
-
|
|
159
|
-
|
|
518
|
+
26,
|
|
519
|
+
26,
|
|
160
520
|
null,
|
|
161
|
-
|
|
521
|
+
26,
|
|
162
522
|
null,
|
|
163
523
|
null,
|
|
164
524
|
1,
|
|
165
525
|
null,
|
|
166
|
-
|
|
167
|
-
|
|
526
|
+
28,
|
|
527
|
+
54,
|
|
168
528
|
null,
|
|
169
529
|
null,
|
|
170
|
-
|
|
530
|
+
28,
|
|
171
531
|
null,
|
|
172
532
|
null,
|
|
173
|
-
|
|
174
|
-
|
|
533
|
+
26,
|
|
534
|
+
26,
|
|
175
535
|
null,
|
|
176
|
-
|
|
536
|
+
26,
|
|
177
537
|
null,
|
|
178
538
|
0,
|
|
179
539
|
null,
|
|
@@ -186,13 +546,13 @@
|
|
|
186
546
|
null,
|
|
187
547
|
null,
|
|
188
548
|
1,
|
|
189
|
-
|
|
549
|
+
83,
|
|
190
550
|
null,
|
|
191
|
-
|
|
192
|
-
|
|
551
|
+
83,
|
|
552
|
+
109,
|
|
193
553
|
null,
|
|
194
554
|
null,
|
|
195
|
-
|
|
555
|
+
27,
|
|
196
556
|
null,
|
|
197
557
|
null,
|
|
198
558
|
1,
|
|
@@ -208,16 +568,16 @@
|
|
|
208
568
|
null,
|
|
209
569
|
null,
|
|
210
570
|
1,
|
|
211
|
-
|
|
571
|
+
26,
|
|
212
572
|
0,
|
|
213
573
|
null,
|
|
214
574
|
null,
|
|
215
|
-
|
|
575
|
+
26,
|
|
216
576
|
null,
|
|
217
577
|
null,
|
|
218
578
|
1,
|
|
219
|
-
|
|
220
|
-
|
|
579
|
+
27,
|
|
580
|
+
27,
|
|
221
581
|
null,
|
|
222
582
|
2,
|
|
223
583
|
null,
|
|
@@ -227,10 +587,10 @@
|
|
|
227
587
|
null,
|
|
228
588
|
null,
|
|
229
589
|
1,
|
|
230
|
-
|
|
590
|
+
24,
|
|
231
591
|
null,
|
|
232
|
-
|
|
233
|
-
|
|
592
|
+
24,
|
|
593
|
+
19,
|
|
234
594
|
null,
|
|
235
595
|
"ignored",
|
|
236
596
|
"ignored",
|
|
@@ -256,18 +616,18 @@
|
|
|
256
616
|
null,
|
|
257
617
|
null,
|
|
258
618
|
1,
|
|
259
|
-
|
|
619
|
+
111,
|
|
260
620
|
null,
|
|
261
621
|
null,
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
622
|
+
111,
|
|
623
|
+
111,
|
|
624
|
+
111,
|
|
625
|
+
111,
|
|
266
626
|
null,
|
|
267
627
|
null,
|
|
268
628
|
null,
|
|
269
629
|
1,
|
|
270
|
-
|
|
630
|
+
54,
|
|
271
631
|
null,
|
|
272
632
|
null,
|
|
273
633
|
null,
|
|
@@ -278,7 +638,7 @@
|
|
|
278
638
|
null,
|
|
279
639
|
null,
|
|
280
640
|
1,
|
|
281
|
-
|
|
641
|
+
84,
|
|
282
642
|
null,
|
|
283
643
|
"ignored",
|
|
284
644
|
"ignored",
|
|
@@ -286,30 +646,43 @@
|
|
|
286
646
|
null,
|
|
287
647
|
null,
|
|
288
648
|
1,
|
|
289
|
-
|
|
649
|
+
250,
|
|
290
650
|
null,
|
|
291
651
|
null,
|
|
292
652
|
1,
|
|
293
|
-
|
|
653
|
+
220,
|
|
294
654
|
null,
|
|
295
655
|
null,
|
|
296
656
|
1,
|
|
297
|
-
|
|
298
|
-
|
|
657
|
+
54,
|
|
658
|
+
54,
|
|
299
659
|
null,
|
|
300
|
-
|
|
660
|
+
28,
|
|
301
661
|
null,
|
|
302
662
|
null,
|
|
303
|
-
|
|
304
|
-
|
|
663
|
+
84,
|
|
664
|
+
84,
|
|
305
665
|
null,
|
|
306
666
|
null,
|
|
307
667
|
null,
|
|
308
668
|
null,
|
|
309
669
|
null
|
|
310
|
-
]
|
|
670
|
+
],
|
|
671
|
+
"lines_covered_percent": 98.55072463768116,
|
|
672
|
+
"covered_lines": 136,
|
|
673
|
+
"missed_lines": 2,
|
|
674
|
+
"omitted_lines": 157,
|
|
675
|
+
"total_lines": 138
|
|
311
676
|
},
|
|
312
|
-
"
|
|
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
|
+
],
|
|
313
686
|
"lines": [
|
|
314
687
|
1,
|
|
315
688
|
1,
|
|
@@ -317,9 +690,51 @@
|
|
|
317
690
|
null,
|
|
318
691
|
null,
|
|
319
692
|
null
|
|
320
|
-
]
|
|
693
|
+
],
|
|
694
|
+
"lines_covered_percent": 100.0,
|
|
695
|
+
"covered_lines": 3,
|
|
696
|
+
"missed_lines": 0,
|
|
697
|
+
"omitted_lines": 3,
|
|
698
|
+
"total_lines": 3
|
|
321
699
|
},
|
|
322
|
-
"
|
|
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
|
+
],
|
|
323
738
|
"lines": [
|
|
324
739
|
1,
|
|
325
740
|
1,
|
|
@@ -356,9 +771,75 @@
|
|
|
356
771
|
null,
|
|
357
772
|
null,
|
|
358
773
|
null
|
|
359
|
-
]
|
|
774
|
+
],
|
|
775
|
+
"lines_covered_percent": 100.0,
|
|
776
|
+
"covered_lines": 18,
|
|
777
|
+
"missed_lines": 0,
|
|
778
|
+
"omitted_lines": 17,
|
|
779
|
+
"total_lines": 18
|
|
360
780
|
},
|
|
361
|
-
"
|
|
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
|
+
],
|
|
362
843
|
"lines": [
|
|
363
844
|
1,
|
|
364
845
|
1,
|
|
@@ -419,9 +900,22 @@
|
|
|
419
900
|
null,
|
|
420
901
|
null,
|
|
421
902
|
null
|
|
422
|
-
]
|
|
903
|
+
],
|
|
904
|
+
"lines_covered_percent": 100.0,
|
|
905
|
+
"covered_lines": 5,
|
|
906
|
+
"missed_lines": 0,
|
|
907
|
+
"omitted_lines": 54,
|
|
908
|
+
"total_lines": 5
|
|
423
909
|
},
|
|
424
|
-
"
|
|
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
|
+
],
|
|
425
919
|
"lines": [
|
|
426
920
|
1,
|
|
427
921
|
1,
|
|
@@ -429,9 +923,158 @@
|
|
|
429
923
|
1,
|
|
430
924
|
null,
|
|
431
925
|
null
|
|
432
|
-
]
|
|
926
|
+
],
|
|
927
|
+
"lines_covered_percent": 100.0,
|
|
928
|
+
"covered_lines": 4,
|
|
929
|
+
"missed_lines": 0,
|
|
930
|
+
"omitted_lines": 2,
|
|
931
|
+
"total_lines": 4
|
|
433
932
|
},
|
|
434
|
-
"
|
|
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
|
+
],
|
|
435
1078
|
"lines": [
|
|
436
1079
|
1,
|
|
437
1080
|
1,
|
|
@@ -575,9 +1218,82 @@
|
|
|
575
1218
|
null,
|
|
576
1219
|
null,
|
|
577
1220
|
null
|
|
578
|
-
]
|
|
1221
|
+
],
|
|
1222
|
+
"lines_covered_percent": 100.0,
|
|
1223
|
+
"covered_lines": 74,
|
|
1224
|
+
"missed_lines": 0,
|
|
1225
|
+
"omitted_lines": 68,
|
|
1226
|
+
"total_lines": 74
|
|
579
1227
|
},
|
|
580
|
-
"
|
|
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
|
+
],
|
|
581
1297
|
"lines": [
|
|
582
1298
|
1,
|
|
583
1299
|
1,
|
|
@@ -645,9 +1361,59 @@
|
|
|
645
1361
|
null,
|
|
646
1362
|
null,
|
|
647
1363
|
null
|
|
648
|
-
]
|
|
1364
|
+
],
|
|
1365
|
+
"lines_covered_percent": 100.0,
|
|
1366
|
+
"covered_lines": 27,
|
|
1367
|
+
"missed_lines": 0,
|
|
1368
|
+
"omitted_lines": 39,
|
|
1369
|
+
"total_lines": 27
|
|
649
1370
|
},
|
|
650
|
-
"
|
|
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
|
+
],
|
|
651
1417
|
"lines": [
|
|
652
1418
|
1,
|
|
653
1419
|
1,
|
|
@@ -692,881 +1458,14 @@
|
|
|
692
1458
|
null,
|
|
693
1459
|
null,
|
|
694
1460
|
null
|
|
695
|
-
]
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
"
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
null,
|
|
702
|
-
23,
|
|
703
|
-
22,
|
|
704
|
-
22,
|
|
705
|
-
null,
|
|
706
|
-
1,
|
|
707
|
-
5,
|
|
708
|
-
null,
|
|
709
|
-
1,
|
|
710
|
-
3,
|
|
711
|
-
null,
|
|
712
|
-
null,
|
|
713
|
-
1,
|
|
714
|
-
2,
|
|
715
|
-
null,
|
|
716
|
-
3,
|
|
717
|
-
null,
|
|
718
|
-
null,
|
|
719
|
-
1,
|
|
720
|
-
3,
|
|
721
|
-
null,
|
|
722
|
-
null,
|
|
723
|
-
1,
|
|
724
|
-
2,
|
|
725
|
-
null,
|
|
726
|
-
3,
|
|
727
|
-
null,
|
|
728
|
-
null,
|
|
729
|
-
null,
|
|
730
|
-
1,
|
|
731
|
-
4,
|
|
732
|
-
null,
|
|
733
|
-
4,
|
|
734
|
-
null,
|
|
735
|
-
2,
|
|
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
|
-
null,
|
|
752
|
-
null,
|
|
753
|
-
null,
|
|
754
|
-
null,
|
|
755
|
-
1,
|
|
756
|
-
4,
|
|
757
|
-
null,
|
|
758
|
-
4,
|
|
759
|
-
null,
|
|
760
|
-
1,
|
|
761
|
-
3,
|
|
762
|
-
null,
|
|
763
|
-
2,
|
|
764
|
-
null,
|
|
765
|
-
1,
|
|
766
|
-
1,
|
|
767
|
-
null,
|
|
768
|
-
null,
|
|
769
|
-
null,
|
|
770
|
-
null,
|
|
771
|
-
null,
|
|
772
|
-
null,
|
|
773
|
-
null,
|
|
774
|
-
null,
|
|
775
|
-
null,
|
|
776
|
-
null,
|
|
777
|
-
null,
|
|
778
|
-
1,
|
|
779
|
-
2,
|
|
780
|
-
null,
|
|
781
|
-
1,
|
|
782
|
-
1,
|
|
783
|
-
null,
|
|
784
|
-
null,
|
|
785
|
-
null,
|
|
786
|
-
null,
|
|
787
|
-
1,
|
|
788
|
-
4,
|
|
789
|
-
null,
|
|
790
|
-
4,
|
|
791
|
-
null,
|
|
792
|
-
1,
|
|
793
|
-
3,
|
|
794
|
-
null,
|
|
795
|
-
2,
|
|
796
|
-
null,
|
|
797
|
-
1,
|
|
798
|
-
1,
|
|
799
|
-
null,
|
|
800
|
-
null,
|
|
801
|
-
null,
|
|
802
|
-
null,
|
|
803
|
-
null,
|
|
804
|
-
null,
|
|
805
|
-
null,
|
|
806
|
-
null,
|
|
807
|
-
null,
|
|
808
|
-
null,
|
|
809
|
-
null,
|
|
810
|
-
null,
|
|
811
|
-
1,
|
|
812
|
-
2,
|
|
813
|
-
null,
|
|
814
|
-
1,
|
|
815
|
-
1,
|
|
816
|
-
null,
|
|
817
|
-
null,
|
|
818
|
-
null,
|
|
819
|
-
null,
|
|
820
|
-
1,
|
|
821
|
-
4,
|
|
822
|
-
null,
|
|
823
|
-
4,
|
|
824
|
-
null,
|
|
825
|
-
1,
|
|
826
|
-
3,
|
|
827
|
-
null,
|
|
828
|
-
2,
|
|
829
|
-
null,
|
|
830
|
-
1,
|
|
831
|
-
1,
|
|
832
|
-
null,
|
|
833
|
-
null,
|
|
834
|
-
null,
|
|
835
|
-
null,
|
|
836
|
-
null,
|
|
837
|
-
null,
|
|
838
|
-
null,
|
|
839
|
-
null,
|
|
840
|
-
null,
|
|
841
|
-
null,
|
|
842
|
-
null,
|
|
843
|
-
null,
|
|
844
|
-
null,
|
|
845
|
-
null,
|
|
846
|
-
null,
|
|
847
|
-
null,
|
|
848
|
-
null,
|
|
849
|
-
1,
|
|
850
|
-
2,
|
|
851
|
-
null,
|
|
852
|
-
1,
|
|
853
|
-
1,
|
|
854
|
-
null,
|
|
855
|
-
null,
|
|
856
|
-
null,
|
|
857
|
-
null,
|
|
858
|
-
1,
|
|
859
|
-
4,
|
|
860
|
-
null,
|
|
861
|
-
4,
|
|
862
|
-
null,
|
|
863
|
-
1,
|
|
864
|
-
3,
|
|
865
|
-
null,
|
|
866
|
-
2,
|
|
867
|
-
null,
|
|
868
|
-
1,
|
|
869
|
-
1,
|
|
870
|
-
null,
|
|
871
|
-
null,
|
|
872
|
-
null,
|
|
873
|
-
null,
|
|
874
|
-
null,
|
|
875
|
-
null,
|
|
876
|
-
null,
|
|
877
|
-
null,
|
|
878
|
-
null,
|
|
879
|
-
null,
|
|
880
|
-
null,
|
|
881
|
-
null,
|
|
882
|
-
null,
|
|
883
|
-
null,
|
|
884
|
-
null,
|
|
885
|
-
null,
|
|
886
|
-
null,
|
|
887
|
-
1,
|
|
888
|
-
2,
|
|
889
|
-
null,
|
|
890
|
-
1,
|
|
891
|
-
1,
|
|
892
|
-
null,
|
|
893
|
-
null,
|
|
894
|
-
null,
|
|
895
|
-
null,
|
|
896
|
-
1,
|
|
897
|
-
4,
|
|
898
|
-
null,
|
|
899
|
-
4,
|
|
900
|
-
null,
|
|
901
|
-
4,
|
|
902
|
-
null,
|
|
903
|
-
1,
|
|
904
|
-
1,
|
|
905
|
-
1,
|
|
906
|
-
1,
|
|
907
|
-
null,
|
|
908
|
-
1,
|
|
909
|
-
1,
|
|
910
|
-
null,
|
|
911
|
-
null,
|
|
912
|
-
1,
|
|
913
|
-
1,
|
|
914
|
-
1,
|
|
915
|
-
1,
|
|
916
|
-
1,
|
|
917
|
-
1,
|
|
918
|
-
null,
|
|
919
|
-
1,
|
|
920
|
-
1,
|
|
921
|
-
null,
|
|
922
|
-
null,
|
|
923
|
-
1,
|
|
924
|
-
1,
|
|
925
|
-
1,
|
|
926
|
-
1,
|
|
927
|
-
2,
|
|
928
|
-
1,
|
|
929
|
-
1,
|
|
930
|
-
null,
|
|
931
|
-
1,
|
|
932
|
-
1,
|
|
933
|
-
null,
|
|
934
|
-
null,
|
|
935
|
-
null
|
|
936
|
-
]
|
|
937
|
-
},
|
|
938
|
-
"/Users/ledermann/Projects/solectrus/senec/spec/lib/senec/local/request_spec.rb": {
|
|
939
|
-
"lines": [
|
|
940
|
-
1,
|
|
941
|
-
23,
|
|
942
|
-
1,
|
|
943
|
-
23,
|
|
944
|
-
null,
|
|
945
|
-
null,
|
|
946
|
-
1,
|
|
947
|
-
19,
|
|
948
|
-
null,
|
|
949
|
-
1,
|
|
950
|
-
2,
|
|
951
|
-
null,
|
|
952
|
-
2,
|
|
953
|
-
null,
|
|
954
|
-
null,
|
|
955
|
-
1,
|
|
956
|
-
2,
|
|
957
|
-
null,
|
|
958
|
-
2,
|
|
959
|
-
null,
|
|
960
|
-
null,
|
|
961
|
-
1,
|
|
962
|
-
2,
|
|
963
|
-
null,
|
|
964
|
-
2,
|
|
965
|
-
null,
|
|
966
|
-
null,
|
|
967
|
-
1,
|
|
968
|
-
2,
|
|
969
|
-
null,
|
|
970
|
-
2,
|
|
971
|
-
null,
|
|
972
|
-
null,
|
|
973
|
-
1,
|
|
974
|
-
2,
|
|
975
|
-
null,
|
|
976
|
-
2,
|
|
977
|
-
null,
|
|
978
|
-
null,
|
|
979
|
-
1,
|
|
980
|
-
2,
|
|
981
|
-
null,
|
|
982
|
-
2,
|
|
983
|
-
null,
|
|
984
|
-
null,
|
|
985
|
-
1,
|
|
986
|
-
2,
|
|
987
|
-
null,
|
|
988
|
-
2,
|
|
989
|
-
null,
|
|
990
|
-
null,
|
|
991
|
-
1,
|
|
992
|
-
2,
|
|
993
|
-
null,
|
|
994
|
-
2,
|
|
995
|
-
null,
|
|
996
|
-
null,
|
|
997
|
-
1,
|
|
998
|
-
2,
|
|
999
|
-
null,
|
|
1000
|
-
2,
|
|
1001
|
-
null,
|
|
1002
|
-
null,
|
|
1003
|
-
1,
|
|
1004
|
-
2,
|
|
1005
|
-
null,
|
|
1006
|
-
2,
|
|
1007
|
-
null,
|
|
1008
|
-
null,
|
|
1009
|
-
1,
|
|
1010
|
-
2,
|
|
1011
|
-
null,
|
|
1012
|
-
2,
|
|
1013
|
-
null,
|
|
1014
|
-
null,
|
|
1015
|
-
1,
|
|
1016
|
-
2,
|
|
1017
|
-
null,
|
|
1018
|
-
2,
|
|
1019
|
-
null,
|
|
1020
|
-
null,
|
|
1021
|
-
1,
|
|
1022
|
-
2,
|
|
1023
|
-
null,
|
|
1024
|
-
2,
|
|
1025
|
-
null,
|
|
1026
|
-
null,
|
|
1027
|
-
1,
|
|
1028
|
-
3,
|
|
1029
|
-
null,
|
|
1030
|
-
2,
|
|
1031
|
-
2,
|
|
1032
|
-
null,
|
|
1033
|
-
null,
|
|
1034
|
-
1,
|
|
1035
|
-
2,
|
|
1036
|
-
null,
|
|
1037
|
-
2,
|
|
1038
|
-
null,
|
|
1039
|
-
null,
|
|
1040
|
-
1,
|
|
1041
|
-
2,
|
|
1042
|
-
null,
|
|
1043
|
-
2,
|
|
1044
|
-
null,
|
|
1045
|
-
null,
|
|
1046
|
-
1,
|
|
1047
|
-
2,
|
|
1048
|
-
null,
|
|
1049
|
-
2,
|
|
1050
|
-
null,
|
|
1051
|
-
null,
|
|
1052
|
-
null,
|
|
1053
|
-
1,
|
|
1054
|
-
2,
|
|
1055
|
-
null,
|
|
1056
|
-
2,
|
|
1057
|
-
null,
|
|
1058
|
-
1,
|
|
1059
|
-
2,
|
|
1060
|
-
null,
|
|
1061
|
-
null,
|
|
1062
|
-
null,
|
|
1063
|
-
1,
|
|
1064
|
-
2,
|
|
1065
|
-
null,
|
|
1066
|
-
1,
|
|
1067
|
-
1,
|
|
1068
|
-
null,
|
|
1069
|
-
null,
|
|
1070
|
-
1,
|
|
1071
|
-
2,
|
|
1072
|
-
null,
|
|
1073
|
-
null,
|
|
1074
|
-
null,
|
|
1075
|
-
1,
|
|
1076
|
-
2,
|
|
1077
|
-
null,
|
|
1078
|
-
2,
|
|
1079
|
-
null,
|
|
1080
|
-
1,
|
|
1081
|
-
2,
|
|
1082
|
-
null,
|
|
1083
|
-
null,
|
|
1084
|
-
null,
|
|
1085
|
-
1,
|
|
1086
|
-
1,
|
|
1087
|
-
1,
|
|
1088
|
-
null,
|
|
1089
|
-
null,
|
|
1090
|
-
null,
|
|
1091
|
-
null,
|
|
1092
|
-
null,
|
|
1093
|
-
1,
|
|
1094
|
-
2,
|
|
1095
|
-
null,
|
|
1096
|
-
2,
|
|
1097
|
-
null,
|
|
1098
|
-
null,
|
|
1099
|
-
null,
|
|
1100
|
-
1,
|
|
1101
|
-
1,
|
|
1102
|
-
1,
|
|
1103
|
-
null,
|
|
1104
|
-
null,
|
|
1105
|
-
null,
|
|
1106
|
-
null,
|
|
1107
|
-
null,
|
|
1108
|
-
1,
|
|
1109
|
-
2,
|
|
1110
|
-
null,
|
|
1111
|
-
2,
|
|
1112
|
-
null,
|
|
1113
|
-
null,
|
|
1114
|
-
null,
|
|
1115
|
-
1,
|
|
1116
|
-
1,
|
|
1117
|
-
3,
|
|
1118
|
-
null,
|
|
1119
|
-
null,
|
|
1120
|
-
null,
|
|
1121
|
-
null,
|
|
1122
|
-
null,
|
|
1123
|
-
null,
|
|
1124
|
-
null,
|
|
1125
|
-
null,
|
|
1126
|
-
null,
|
|
1127
|
-
1,
|
|
1128
|
-
1,
|
|
1129
|
-
2,
|
|
1130
|
-
null,
|
|
1131
|
-
2,
|
|
1132
|
-
null,
|
|
1133
|
-
null,
|
|
1134
|
-
1,
|
|
1135
|
-
2,
|
|
1136
|
-
null,
|
|
1137
|
-
1,
|
|
1138
|
-
2,
|
|
1139
|
-
null,
|
|
1140
|
-
null,
|
|
1141
|
-
null,
|
|
1142
|
-
1,
|
|
1143
|
-
2,
|
|
1144
|
-
null,
|
|
1145
|
-
1,
|
|
1146
|
-
1,
|
|
1147
|
-
null,
|
|
1148
|
-
null,
|
|
1149
|
-
null,
|
|
1150
|
-
null,
|
|
1151
|
-
null,
|
|
1152
|
-
null,
|
|
1153
|
-
1,
|
|
1154
|
-
2,
|
|
1155
|
-
null,
|
|
1156
|
-
null,
|
|
1157
|
-
null,
|
|
1158
|
-
null,
|
|
1159
|
-
null,
|
|
1160
|
-
null
|
|
1161
|
-
]
|
|
1162
|
-
},
|
|
1163
|
-
"/Users/ledermann/Projects/solectrus/senec/spec/lib/senec/local/state_spec.rb": {
|
|
1164
|
-
"lines": [
|
|
1165
|
-
1,
|
|
1166
|
-
6,
|
|
1167
|
-
null,
|
|
1168
|
-
6,
|
|
1169
|
-
null,
|
|
1170
|
-
1,
|
|
1171
|
-
5,
|
|
1172
|
-
null,
|
|
1173
|
-
null,
|
|
1174
|
-
null,
|
|
1175
|
-
1,
|
|
1176
|
-
3,
|
|
1177
|
-
null,
|
|
1178
|
-
null,
|
|
1179
|
-
null,
|
|
1180
|
-
null,
|
|
1181
|
-
null,
|
|
1182
|
-
null,
|
|
1183
|
-
null,
|
|
1184
|
-
null,
|
|
1185
|
-
null,
|
|
1186
|
-
null,
|
|
1187
|
-
null,
|
|
1188
|
-
null,
|
|
1189
|
-
null,
|
|
1190
|
-
null,
|
|
1191
|
-
null,
|
|
1192
|
-
1,
|
|
1193
|
-
1,
|
|
1194
|
-
null,
|
|
1195
|
-
null,
|
|
1196
|
-
null,
|
|
1197
|
-
null,
|
|
1198
|
-
1,
|
|
1199
|
-
6,
|
|
1200
|
-
null,
|
|
1201
|
-
1,
|
|
1202
|
-
null,
|
|
1203
|
-
4,
|
|
1204
|
-
null,
|
|
1205
|
-
null,
|
|
1206
|
-
null,
|
|
1207
|
-
null,
|
|
1208
|
-
null,
|
|
1209
|
-
null,
|
|
1210
|
-
null,
|
|
1211
|
-
null,
|
|
1212
|
-
1,
|
|
1213
|
-
2,
|
|
1214
|
-
null,
|
|
1215
|
-
1,
|
|
1216
|
-
1,
|
|
1217
|
-
null,
|
|
1218
|
-
null,
|
|
1219
|
-
2,
|
|
1220
|
-
null,
|
|
1221
|
-
null,
|
|
1222
|
-
1,
|
|
1223
|
-
2,
|
|
1224
|
-
null,
|
|
1225
|
-
1,
|
|
1226
|
-
1,
|
|
1227
|
-
null,
|
|
1228
|
-
null,
|
|
1229
|
-
2,
|
|
1230
|
-
null,
|
|
1231
|
-
null,
|
|
1232
|
-
1,
|
|
1233
|
-
2,
|
|
1234
|
-
null,
|
|
1235
|
-
1,
|
|
1236
|
-
1,
|
|
1237
|
-
null,
|
|
1238
|
-
null,
|
|
1239
|
-
2,
|
|
1240
|
-
null,
|
|
1241
|
-
null,
|
|
1242
|
-
1,
|
|
1243
|
-
2,
|
|
1244
|
-
null,
|
|
1245
|
-
1,
|
|
1246
|
-
1,
|
|
1247
|
-
null,
|
|
1248
|
-
null,
|
|
1249
|
-
2,
|
|
1250
|
-
null,
|
|
1251
|
-
null,
|
|
1252
|
-
1,
|
|
1253
|
-
2,
|
|
1254
|
-
null,
|
|
1255
|
-
1,
|
|
1256
|
-
2,
|
|
1257
|
-
null,
|
|
1258
|
-
null,
|
|
1259
|
-
null,
|
|
1260
|
-
null
|
|
1261
|
-
]
|
|
1262
|
-
},
|
|
1263
|
-
"/Users/ledermann/Projects/solectrus/senec/spec/lib/senec/local/value_spec.rb": {
|
|
1264
|
-
"lines": [
|
|
1265
|
-
1,
|
|
1266
|
-
17,
|
|
1267
|
-
null,
|
|
1268
|
-
1,
|
|
1269
|
-
17,
|
|
1270
|
-
null,
|
|
1271
|
-
1,
|
|
1272
|
-
3,
|
|
1273
|
-
null,
|
|
1274
|
-
2,
|
|
1275
|
-
2,
|
|
1276
|
-
null,
|
|
1277
|
-
null,
|
|
1278
|
-
1,
|
|
1279
|
-
3,
|
|
1280
|
-
null,
|
|
1281
|
-
2,
|
|
1282
|
-
2,
|
|
1283
|
-
null,
|
|
1284
|
-
null,
|
|
1285
|
-
1,
|
|
1286
|
-
3,
|
|
1287
|
-
null,
|
|
1288
|
-
2,
|
|
1289
|
-
2,
|
|
1290
|
-
null,
|
|
1291
|
-
null,
|
|
1292
|
-
1,
|
|
1293
|
-
3,
|
|
1294
|
-
null,
|
|
1295
|
-
2,
|
|
1296
|
-
2,
|
|
1297
|
-
null,
|
|
1298
|
-
null,
|
|
1299
|
-
1,
|
|
1300
|
-
3,
|
|
1301
|
-
null,
|
|
1302
|
-
2,
|
|
1303
|
-
2,
|
|
1304
|
-
null,
|
|
1305
|
-
null,
|
|
1306
|
-
1,
|
|
1307
|
-
3,
|
|
1308
|
-
null,
|
|
1309
|
-
2,
|
|
1310
|
-
2,
|
|
1311
|
-
null,
|
|
1312
|
-
null,
|
|
1313
|
-
1,
|
|
1314
|
-
3,
|
|
1315
|
-
null,
|
|
1316
|
-
2,
|
|
1317
|
-
2,
|
|
1318
|
-
null,
|
|
1319
|
-
null,
|
|
1320
|
-
1,
|
|
1321
|
-
2,
|
|
1322
|
-
null,
|
|
1323
|
-
3,
|
|
1324
|
-
null,
|
|
1325
|
-
null,
|
|
1326
|
-
1,
|
|
1327
|
-
2,
|
|
1328
|
-
null,
|
|
1329
|
-
1,
|
|
1330
|
-
2,
|
|
1331
|
-
null,
|
|
1332
|
-
null,
|
|
1333
|
-
null,
|
|
1334
|
-
null
|
|
1335
|
-
]
|
|
1336
|
-
},
|
|
1337
|
-
"/Users/ledermann/Projects/solectrus/senec/spec/senec_spec.rb": {
|
|
1338
|
-
"lines": [
|
|
1339
|
-
1,
|
|
1340
|
-
1,
|
|
1341
|
-
1,
|
|
1342
|
-
null,
|
|
1343
|
-
null
|
|
1344
|
-
]
|
|
1345
|
-
},
|
|
1346
|
-
"/Users/ledermann/Projects/solectrus/senec/spec/support/vcr.rb": {
|
|
1347
|
-
"lines": [
|
|
1348
|
-
1,
|
|
1349
|
-
null,
|
|
1350
|
-
1,
|
|
1351
|
-
1,
|
|
1352
|
-
9,
|
|
1353
|
-
null,
|
|
1354
|
-
null,
|
|
1355
|
-
null,
|
|
1356
|
-
1,
|
|
1357
|
-
1,
|
|
1358
|
-
6,
|
|
1359
|
-
null,
|
|
1360
|
-
null,
|
|
1361
|
-
null,
|
|
1362
|
-
1,
|
|
1363
|
-
2,
|
|
1364
|
-
23,
|
|
1365
|
-
null,
|
|
1366
|
-
null,
|
|
1367
|
-
null,
|
|
1368
|
-
1,
|
|
1369
|
-
4,
|
|
1370
|
-
null,
|
|
1371
|
-
null,
|
|
1372
|
-
null,
|
|
1373
|
-
1,
|
|
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
|
-
null,
|
|
1410
|
-
null,
|
|
1411
|
-
null,
|
|
1412
|
-
null,
|
|
1413
|
-
null,
|
|
1414
|
-
null,
|
|
1415
|
-
null,
|
|
1416
|
-
null,
|
|
1417
|
-
null,
|
|
1418
|
-
null,
|
|
1419
|
-
null,
|
|
1420
|
-
null,
|
|
1421
|
-
null,
|
|
1422
|
-
null,
|
|
1423
|
-
null,
|
|
1424
|
-
null,
|
|
1425
|
-
null,
|
|
1426
|
-
null,
|
|
1427
|
-
null,
|
|
1428
|
-
null,
|
|
1429
|
-
null,
|
|
1430
|
-
null,
|
|
1431
|
-
null,
|
|
1432
|
-
null,
|
|
1433
|
-
null,
|
|
1434
|
-
null,
|
|
1435
|
-
null,
|
|
1436
|
-
null,
|
|
1437
|
-
null,
|
|
1438
|
-
1,
|
|
1439
|
-
260,
|
|
1440
|
-
null,
|
|
1441
|
-
null,
|
|
1442
|
-
null,
|
|
1443
|
-
null,
|
|
1444
|
-
null,
|
|
1445
|
-
null,
|
|
1446
|
-
null,
|
|
1447
|
-
null,
|
|
1448
|
-
null,
|
|
1449
|
-
null,
|
|
1450
|
-
1,
|
|
1451
|
-
260,
|
|
1452
|
-
null,
|
|
1453
|
-
null,
|
|
1454
|
-
1,
|
|
1455
|
-
1,
|
|
1456
|
-
1,
|
|
1457
|
-
1,
|
|
1458
|
-
1,
|
|
1459
|
-
null,
|
|
1460
|
-
1,
|
|
1461
|
-
1,
|
|
1462
|
-
null,
|
|
1463
|
-
null,
|
|
1464
|
-
null,
|
|
1465
|
-
null,
|
|
1466
|
-
null,
|
|
1467
|
-
null,
|
|
1468
|
-
null,
|
|
1469
|
-
1,
|
|
1470
|
-
130,
|
|
1471
|
-
130,
|
|
1472
|
-
130,
|
|
1473
|
-
null,
|
|
1474
|
-
null,
|
|
1475
|
-
null,
|
|
1476
|
-
1,
|
|
1477
|
-
132,
|
|
1478
|
-
null,
|
|
1479
|
-
1,
|
|
1480
|
-
132,
|
|
1481
|
-
null,
|
|
1482
|
-
1,
|
|
1483
|
-
132,
|
|
1484
|
-
null,
|
|
1485
|
-
1,
|
|
1486
|
-
132,
|
|
1487
|
-
132,
|
|
1488
|
-
null,
|
|
1489
|
-
1,
|
|
1490
|
-
132,
|
|
1491
|
-
132,
|
|
1492
|
-
null,
|
|
1493
|
-
null,
|
|
1494
|
-
null,
|
|
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
|
-
"ignored",
|
|
1520
|
-
"ignored",
|
|
1521
|
-
"ignored",
|
|
1522
|
-
"ignored",
|
|
1523
|
-
"ignored",
|
|
1524
|
-
"ignored",
|
|
1525
|
-
"ignored",
|
|
1526
|
-
"ignored",
|
|
1527
|
-
"ignored",
|
|
1528
|
-
"ignored",
|
|
1529
|
-
"ignored",
|
|
1530
|
-
"ignored",
|
|
1531
|
-
"ignored",
|
|
1532
|
-
"ignored",
|
|
1533
|
-
"ignored",
|
|
1534
|
-
"ignored",
|
|
1535
|
-
"ignored",
|
|
1536
|
-
"ignored",
|
|
1537
|
-
"ignored",
|
|
1538
|
-
"ignored",
|
|
1539
|
-
"ignored",
|
|
1540
|
-
"ignored",
|
|
1541
|
-
"ignored",
|
|
1542
|
-
"ignored",
|
|
1543
|
-
"ignored",
|
|
1544
|
-
"ignored",
|
|
1545
|
-
"ignored",
|
|
1546
|
-
"ignored",
|
|
1547
|
-
"ignored",
|
|
1548
|
-
null,
|
|
1549
|
-
null,
|
|
1550
|
-
null,
|
|
1551
|
-
null,
|
|
1552
|
-
1,
|
|
1553
|
-
1,
|
|
1554
|
-
null,
|
|
1555
|
-
1,
|
|
1556
|
-
null,
|
|
1557
|
-
null,
|
|
1558
|
-
null,
|
|
1559
|
-
1,
|
|
1560
|
-
5,
|
|
1561
|
-
5,
|
|
1562
|
-
null,
|
|
1563
|
-
null,
|
|
1564
|
-
null,
|
|
1565
|
-
null,
|
|
1566
|
-
71,
|
|
1567
|
-
null
|
|
1568
|
-
]
|
|
1461
|
+
],
|
|
1462
|
+
"lines_covered_percent": 100.0,
|
|
1463
|
+
"covered_lines": 23,
|
|
1464
|
+
"missed_lines": 0,
|
|
1465
|
+
"omitted_lines": 20,
|
|
1466
|
+
"total_lines": 23
|
|
1569
1467
|
}
|
|
1570
1468
|
},
|
|
1571
|
-
"groups": {}
|
|
1469
|
+
"groups": {},
|
|
1470
|
+
"errors": {}
|
|
1572
1471
|
}
|