senec 0.18.0 → 0.20.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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.env +9 -0
  4. data/.env.test +1 -1
  5. data/.gitignore +13 -0
  6. data/.rspec_status +64 -0
  7. data/.rubocop.yml +1 -1
  8. data/.ruby-lsp/.gitignore +1 -0
  9. data/.ruby-lsp/Gemfile +6 -0
  10. data/.ruby-lsp/Gemfile.lock +191 -0
  11. data/.ruby-lsp/last_updated +1 -0
  12. data/.ruby-lsp/main_lockfile_hash +1 -0
  13. data/.ruby-lsp/needs_update +0 -0
  14. data/README.md +13 -85
  15. data/coverage/.last_run.json +5 -0
  16. data/coverage/.resultset.json +1376 -0
  17. data/coverage/.resultset.json.lock +0 -0
  18. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc.png +0 -0
  19. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
  20. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_both.png +0 -0
  21. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc.png +0 -0
  22. data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
  23. data/coverage/assets/0.13.2/application.css +1 -0
  24. data/coverage/assets/0.13.2/application.js +7 -0
  25. data/coverage/assets/0.13.2/colorbox/border.png +0 -0
  26. data/coverage/assets/0.13.2/colorbox/controls.png +0 -0
  27. data/coverage/assets/0.13.2/colorbox/loading.gif +0 -0
  28. data/coverage/assets/0.13.2/colorbox/loading_background.png +0 -0
  29. data/coverage/assets/0.13.2/favicon_green.png +0 -0
  30. data/coverage/assets/0.13.2/favicon_red.png +0 -0
  31. data/coverage/assets/0.13.2/favicon_yellow.png +0 -0
  32. data/coverage/assets/0.13.2/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  33. data/coverage/assets/0.13.2/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  34. data/coverage/assets/0.13.2/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  35. data/coverage/assets/0.13.2/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  36. data/coverage/assets/0.13.2/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  37. data/coverage/assets/0.13.2/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  38. data/coverage/assets/0.13.2/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  39. data/coverage/assets/0.13.2/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  40. data/coverage/assets/0.13.2/images/ui-icons_222222_256x240.png +0 -0
  41. data/coverage/assets/0.13.2/images/ui-icons_2e83ff_256x240.png +0 -0
  42. data/coverage/assets/0.13.2/images/ui-icons_454545_256x240.png +0 -0
  43. data/coverage/assets/0.13.2/images/ui-icons_888888_256x240.png +0 -0
  44. data/coverage/assets/0.13.2/images/ui-icons_cd0a0a_256x240.png +0 -0
  45. data/coverage/assets/0.13.2/loading.gif +0 -0
  46. data/coverage/assets/0.13.2/magnify.png +0 -0
  47. data/coverage/coverage.json +1377 -0
  48. data/coverage/index.html +14996 -0
  49. data/lib/.DS_Store +0 -0
  50. data/lib/senec/cloud/connection.rb +156 -36
  51. data/lib/senec/cloud/error.rb +0 -6
  52. data/lib/senec/local/connection.rb +1 -0
  53. data/lib/senec/local/request.rb +1 -1
  54. data/lib/senec/local/state.rb +1 -0
  55. data/lib/senec/version.rb +1 -1
  56. data/lib/senec.rb +1 -2
  57. data/pkg/senec-0.1.0.gem +0 -0
  58. data/pkg/senec-0.10.0.gem +0 -0
  59. data/pkg/senec-0.11.0.gem +0 -0
  60. data/pkg/senec-0.12.0.gem +0 -0
  61. data/pkg/senec-0.13.0.gem +0 -0
  62. data/pkg/senec-0.14.0.gem +0 -0
  63. data/pkg/senec-0.15.0.gem +0 -0
  64. data/pkg/senec-0.17.0.gem +0 -0
  65. data/pkg/senec-0.17.1.gem +0 -0
  66. data/pkg/senec-0.17.2.gem +0 -0
  67. data/pkg/senec-0.18.0.gem +0 -0
  68. data/pkg/senec-0.19.0.gem +0 -0
  69. data/pkg/senec-0.2.0.gem +0 -0
  70. data/pkg/senec-0.3.0.gem +0 -0
  71. data/pkg/senec-0.4.0.gem +0 -0
  72. data/pkg/senec-0.5.0.gem +0 -0
  73. data/pkg/senec-0.6.0.gem +0 -0
  74. data/pkg/senec-0.6.1.gem +0 -0
  75. data/pkg/senec-0.6.2.gem +0 -0
  76. data/pkg/senec-0.7.0.gem +0 -0
  77. data/pkg/senec-0.7.1.gem +0 -0
  78. data/pkg/senec-0.7.2.gem +0 -0
  79. data/pkg/senec-0.8.0.gem +0 -0
  80. data/pkg/senec-0.9.0.gem +0 -0
  81. metadata +86 -5
  82. data/lib/senec/cloud/dashboard.rb +0 -80
  83. data/lib/senec/cloud/technical_data.rb +0 -69
data/lib/.DS_Store ADDED
Binary file
@@ -1,72 +1,192 @@
1
+ require 'oauth2'
2
+
1
3
  module Senec
2
4
  module Cloud
5
+ CONFIG_URL =
6
+ 'https://sso.senec.com/realms/senec/.well-known/openid-configuration'.freeze
7
+
8
+ CLIENT_ID = 'endcustomer-app-frontend'.freeze
9
+ REDIRECT_URI = 'senec-app-auth://keycloak.prod'.freeze
10
+ SCOPE = 'roles meinsenec'.freeze
11
+
12
+ SYSTEMS_HOST = 'https://senec-app-systems-proxy.prod.senec.dev'.freeze
13
+ MEASUREMENTS_HOST = 'https://senec-app-measurements-proxy.prod.senec.dev'.freeze
14
+ WALLBOX_HOST = 'https://senec-app-wallbox-proxy.prod.senec.dev'.freeze
15
+
3
16
  class Connection
4
- def initialize(username:, password:, token: nil)
17
+ DEFAULT_USER_AGENT = "ruby-senec/#{Senec::VERSION} (+https://github.com/solectrus/senec)".freeze
18
+
19
+ def initialize(username:, password:, user_agent: DEFAULT_USER_AGENT)
5
20
  @username = username
6
21
  @password = password
7
- @token = token
22
+ @user_agent = user_agent
8
23
  end
9
24
 
10
- attr_reader :username, :password
25
+ attr_reader :username, :password, :user_agent
26
+
27
+ def authenticate!
28
+ code_verifier = SecureRandom.alphanumeric(43)
29
+ digest = Digest::SHA256.digest(code_verifier)
30
+ code_challenge = Base64.urlsafe_encode64(digest).delete('=')
31
+
32
+ auth_url =
33
+ oauth_client.auth_code.authorize_url(
34
+ redirect_uri: REDIRECT_URI,
35
+ scope: SCOPE,
36
+ code_challenge:,
37
+ code_challenge_method: 'S256',
38
+ )
39
+
40
+ # Manual HTTP needed for Keycloak cross-domain form handling
41
+ login_form_url = fetch_login_form_url(auth_url)
42
+ redirect_url = submit_credentials(login_form_url)
43
+ authorization_code = extract_authorization_code(redirect_url)
44
+
45
+ self.oauth_token =
46
+ oauth_client.auth_code.get_token(
47
+ authorization_code,
48
+ redirect_uri: REDIRECT_URI,
49
+ code_verifier:,
50
+ )
51
+ end
11
52
 
12
53
  def authenticated?
13
- !token.nil?
54
+ !!oauth_token
14
55
  end
15
56
 
16
57
  def systems
17
- get('/v1/senec/systems')
58
+ fetch_payload "#{SYSTEMS_HOST}/v1/systems"
18
59
  end
19
60
 
20
- def default_system_id
21
- raise Error.new('No systems found!', :not_found) if systems.nil?
61
+ def system_details(system_id)
62
+ fetch_payload "#{SYSTEMS_HOST}/systems/#{system_id}/details"
63
+ end
22
64
 
23
- systems[0]['id']
65
+ def dashboard(system_id)
66
+ fetch_payload "#{MEASUREMENTS_HOST}/v1/systems/#{system_id}/dashboard"
24
67
  end
25
68
 
26
- def get(path, params: nil)
27
- return_body do
28
- connection.get(path, params, { authorization: token })
29
- end
69
+ def wallbox(system_id, wallbox_id)
70
+ fetch_payload "#{WALLBOX_HOST}/v1/systems/#{system_id}/wallboxes/#{wallbox_id}"
30
71
  end
31
72
 
32
- def post(path, data)
33
- return_body do
34
- connection.post(path, data)
73
+ private
74
+
75
+ attr_accessor :oauth_token
76
+
77
+ def fetch_login_form_url(auth_url)
78
+ response = http_request(:get, auth_url)
79
+ store_cookies(response) # Required for Keycloak CSRF protection
80
+ extract_form_action_url(response.body)
81
+ end
82
+
83
+ def extract_form_action_url(html)
84
+ forms = html.scan(%r{<form[^>]*action="([^"]+)"[^>]*>(.*?)</form>}mi)
85
+
86
+ forms.each do |action_url, form_content|
87
+ has_username = form_content.match(/name=["']?username["']?/i)
88
+ has_password = form_content.match(/name=["']?password["']?/i)
89
+
90
+ return CGI.unescapeHTML(action_url) if has_username && has_password
35
91
  end
92
+
93
+ # :nocov:
94
+ raise 'Login form not found'
95
+ # :nocov:
36
96
  end
37
97
 
38
- def token
39
- @token ||= login['token']
98
+ def submit_credentials(form_url)
99
+ credentials = { username:, password: }
100
+ response = http_request(:post, form_url, data: credentials)
101
+ raise 'Login failed' unless response.status == 302
102
+
103
+ response.headers['location'] || raise('No redirect location')
40
104
  end
41
105
 
42
- private
106
+ def extract_authorization_code(redirect_url)
107
+ raise 'Invalid redirect URL' unless redirect_url&.start_with?(REDIRECT_URI)
108
+
109
+ uri = URI(redirect_url)
110
+ params = URI.decode_www_form(uri.query).to_h
111
+
112
+ params['code'] || raise('No authorization code found')
113
+ end
114
+
115
+ def ensure_token_valid
116
+ authenticate! unless authenticated?
117
+ return true unless oauth_token.expired?
43
118
 
44
- def login
45
- post('/v1/senec/login', { username:, password: })
119
+ self.oauth_token = oauth_token.refresh!
120
+ true
121
+ rescue StandardError => e
122
+ # :nocov:
123
+ warn "Token refresh failed: #{e.message}"
124
+ false
125
+ # :nocov:
46
126
  end
47
127
 
48
- def connection
49
- @connection ||=
50
- Faraday.new(url: 'https://app-gateway.prod.senec.dev') do |f|
51
- f.request :json
52
- f.response :json
128
+ def fetch_payload(url, default = nil)
129
+ return default unless ensure_token_valid
130
+
131
+ response = oauth_token.get(url)
132
+ return default unless response.status == 200
133
+
134
+ JSON.parse(response.body)
135
+ rescue StandardError => e
136
+ # :nocov:
137
+ warn "API error: #{e.message}"
138
+ default
139
+ # :nocov:
140
+ end
53
141
 
54
- f.adapter :net_http_persistent, pool_size: 5 do |http|
55
- # :nocov:
56
- http.idle_timeout = 120
57
- # :nocov:
58
- end
142
+ def http_request(method, url, data: nil)
143
+ Faraday
144
+ .new
145
+ .send(method, url) do |req|
146
+ req.headers['user-agent'] = user_agent
147
+ req.headers['connection'] = 'keep-alive'
148
+ req.headers['cookie'] = cookie_string if cookies.any?
149
+ req.body = URI.encode_www_form(data) if data
59
150
  end
60
151
  end
61
152
 
62
- def return_body(&)
63
- response = yield
153
+ def oauth_client
154
+ @oauth_client ||=
155
+ OAuth2::Client.new(
156
+ CLIENT_ID,
157
+ nil,
158
+ site: openid_config['issuer'],
159
+ authorize_url: openid_config['authorization_endpoint'],
160
+ token_url: openid_config['token_endpoint'],
161
+ )
162
+ end
64
163
 
65
- unless response.success?
66
- raise Error.new("Error #{response.status}", response.status)
67
- end
164
+ def openid_config
165
+ @openid_config ||= JSON.parse(http_request(:get, CONFIG_URL).body)
166
+ rescue StandardError => e
167
+ # :nocov:
168
+ raise "Failed to load OpenID configuration: #{e.message}"
169
+ # :nocov:
170
+ end
171
+
172
+ def cookies
173
+ @cookies ||= {}
174
+ end
68
175
 
69
- response.body
176
+ def cookie_string
177
+ cookies.map { |k, v| "#{k}=#{v}" }.join('; ')
178
+ end
179
+
180
+ def store_cookies(response)
181
+ set_cookie = response.headers['set-cookie']
182
+ return unless set_cookie
183
+
184
+ set_cookie
185
+ .split(', ')
186
+ .each do |cookie_header|
187
+ name, value = cookie_header.split(';').first.split('=', 2)
188
+ cookies[name] = value if name && value
189
+ end
70
190
  end
71
191
  end
72
192
  end
@@ -1,12 +1,6 @@
1
1
  module Senec
2
2
  module Cloud
3
3
  class Error < StandardError
4
- attr_reader :status
5
-
6
- def initialize(message, status)
7
- super(message)
8
- @status = status
9
- end
10
4
  end
11
5
  end
12
6
  end
@@ -13,6 +13,7 @@ module Senec
13
13
  attr_reader :url
14
14
 
15
15
  extend Forwardable
16
+
16
17
  def_delegators :faraday, :get, :post
17
18
 
18
19
  private
@@ -12,7 +12,7 @@ module Senec
12
12
 
13
13
  attr_reader :connection, :body, :state_names
14
14
 
15
- def perform!
15
+ def perform! # rubocop:disable Naming/PredicateMethod
16
16
  parsed_response
17
17
  true
18
18
  end
@@ -39,6 +39,7 @@ module Senec
39
39
  # Regex pattern to match the system_state_name definition in the JavaScript file
40
40
  # The file may be minimized, so we need to be flexible with whitespace and line breaks
41
41
  FILE_REGEX = /system_state_name\s*=\s*{\s*([^}]*)\s*}/m
42
+ private_constant :FILE_REGEX
42
43
 
43
44
  def response(language:)
44
45
  res = connection.get url(language:)
data/lib/senec/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Senec
2
- VERSION = '0.18.0'.freeze
2
+ VERSION = '0.20.0'.freeze
3
3
  end
data/lib/senec.rb CHANGED
@@ -4,6 +4,5 @@ require 'senec/local/state'
4
4
  require 'senec/local/request'
5
5
  require 'senec/local/error'
6
6
 
7
- require 'senec/cloud/dashboard'
8
- require 'senec/cloud/technical_data'
7
+ require 'senec/cloud/connection'
9
8
  require 'senec/cloud/error'
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: senec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Georg Ledermann
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-01-11 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: faraday
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: oauth2
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
54
68
  description: Access your local SENEC Solar Battery Storage System
55
69
  email:
56
70
  - georg@ledermann.dev
@@ -58,19 +72,62 @@ executables: []
58
72
  extensions: []
59
73
  extra_rdoc_files: []
60
74
  files:
75
+ - ".DS_Store"
76
+ - ".env"
61
77
  - ".env.test"
78
+ - ".gitignore"
62
79
  - ".rspec"
80
+ - ".rspec_status"
63
81
  - ".rubocop.yml"
82
+ - ".ruby-lsp/.gitignore"
83
+ - ".ruby-lsp/Gemfile"
84
+ - ".ruby-lsp/Gemfile.lock"
85
+ - ".ruby-lsp/last_updated"
86
+ - ".ruby-lsp/main_lockfile_hash"
87
+ - ".ruby-lsp/needs_update"
64
88
  - ".vscode/settings.json"
65
89
  - CODE_OF_CONDUCT.md
66
90
  - LICENSE
67
91
  - README.md
68
92
  - Rakefile
93
+ - coverage/.last_run.json
94
+ - coverage/.resultset.json
95
+ - coverage/.resultset.json.lock
96
+ - coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc.png
97
+ - coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc_disabled.png
98
+ - coverage/assets/0.13.2/DataTables-1.10.20/images/sort_both.png
99
+ - coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc.png
100
+ - coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc_disabled.png
101
+ - coverage/assets/0.13.2/application.css
102
+ - coverage/assets/0.13.2/application.js
103
+ - coverage/assets/0.13.2/colorbox/border.png
104
+ - coverage/assets/0.13.2/colorbox/controls.png
105
+ - coverage/assets/0.13.2/colorbox/loading.gif
106
+ - coverage/assets/0.13.2/colorbox/loading_background.png
107
+ - coverage/assets/0.13.2/favicon_green.png
108
+ - coverage/assets/0.13.2/favicon_red.png
109
+ - coverage/assets/0.13.2/favicon_yellow.png
110
+ - coverage/assets/0.13.2/images/ui-bg_flat_0_aaaaaa_40x100.png
111
+ - coverage/assets/0.13.2/images/ui-bg_flat_75_ffffff_40x100.png
112
+ - coverage/assets/0.13.2/images/ui-bg_glass_55_fbf9ee_1x400.png
113
+ - coverage/assets/0.13.2/images/ui-bg_glass_65_ffffff_1x400.png
114
+ - coverage/assets/0.13.2/images/ui-bg_glass_75_dadada_1x400.png
115
+ - coverage/assets/0.13.2/images/ui-bg_glass_75_e6e6e6_1x400.png
116
+ - coverage/assets/0.13.2/images/ui-bg_glass_95_fef1ec_1x400.png
117
+ - coverage/assets/0.13.2/images/ui-bg_highlight-soft_75_cccccc_1x100.png
118
+ - coverage/assets/0.13.2/images/ui-icons_222222_256x240.png
119
+ - coverage/assets/0.13.2/images/ui-icons_2e83ff_256x240.png
120
+ - coverage/assets/0.13.2/images/ui-icons_454545_256x240.png
121
+ - coverage/assets/0.13.2/images/ui-icons_888888_256x240.png
122
+ - coverage/assets/0.13.2/images/ui-icons_cd0a0a_256x240.png
123
+ - coverage/assets/0.13.2/loading.gif
124
+ - coverage/assets/0.13.2/magnify.png
125
+ - coverage/coverage.json
126
+ - coverage/index.html
127
+ - lib/.DS_Store
69
128
  - lib/senec.rb
70
129
  - lib/senec/cloud/connection.rb
71
- - lib/senec/cloud/dashboard.rb
72
130
  - lib/senec/cloud/error.rb
73
- - lib/senec/cloud/technical_data.rb
74
131
  - lib/senec/local/connection.rb
75
132
  - lib/senec/local/constants.rb
76
133
  - lib/senec/local/error.rb
@@ -78,6 +135,30 @@ files:
78
135
  - lib/senec/local/state.rb
79
136
  - lib/senec/local/value.rb
80
137
  - lib/senec/version.rb
138
+ - pkg/senec-0.1.0.gem
139
+ - pkg/senec-0.10.0.gem
140
+ - pkg/senec-0.11.0.gem
141
+ - pkg/senec-0.12.0.gem
142
+ - pkg/senec-0.13.0.gem
143
+ - pkg/senec-0.14.0.gem
144
+ - pkg/senec-0.15.0.gem
145
+ - pkg/senec-0.17.0.gem
146
+ - pkg/senec-0.17.1.gem
147
+ - pkg/senec-0.17.2.gem
148
+ - pkg/senec-0.18.0.gem
149
+ - pkg/senec-0.19.0.gem
150
+ - pkg/senec-0.2.0.gem
151
+ - pkg/senec-0.3.0.gem
152
+ - pkg/senec-0.4.0.gem
153
+ - pkg/senec-0.5.0.gem
154
+ - pkg/senec-0.6.0.gem
155
+ - pkg/senec-0.6.1.gem
156
+ - pkg/senec-0.6.2.gem
157
+ - pkg/senec-0.7.0.gem
158
+ - pkg/senec-0.7.1.gem
159
+ - pkg/senec-0.7.2.gem
160
+ - pkg/senec-0.8.0.gem
161
+ - pkg/senec-0.9.0.gem
81
162
  homepage: https://github.com/solectrus/senec
82
163
  licenses:
83
164
  - MIT
@@ -100,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
181
  - !ruby/object:Gem::Version
101
182
  version: '0'
102
183
  requirements: []
103
- rubygems_version: 3.6.2
184
+ rubygems_version: 3.7.1
104
185
  specification_version: 4
105
186
  summary: Unofficial Ruby Client for SENEC Home
106
187
  test_files: []
@@ -1,80 +0,0 @@
1
- require_relative 'connection'
2
-
3
- # Model for the Senec dashboard data.
4
- #
5
- # Example use:
6
- #
7
- # connection = Senec::Cloud::Connection.new(username: '...', password: '...')
8
- #
9
- # # Get the data of a specific system:
10
- # Dashboard[connection].find('123456').data
11
- #
12
- # # Get the data of the default system:
13
- # Dashboard[connection].first.data
14
- #
15
- # By default, it returns v1 data. To get v2 data, use:
16
- #
17
- # Dashboard[connection].find('123456').data(version: 'v2')
18
- # or
19
- # Dashboard[connection].first.data(version: 'v2')
20
- #
21
- module Senec
22
- module Cloud
23
- class Dashboard
24
- AVAILABLE_VERSIONS = %w[v1 v2].freeze
25
- DEFAULT_VERSION = 'v1'.freeze
26
-
27
- class Finder
28
- def initialize(connection)
29
- @connection = connection
30
- end
31
- attr_reader :connection
32
-
33
- def find(system_id)
34
- Dashboard.new(connection:, system_id:)
35
- end
36
-
37
- def first
38
- find(connection.default_system_id)
39
- end
40
- end
41
-
42
- def self.[](connection)
43
- Finder.new(connection)
44
- end
45
-
46
- def initialize(connection: nil, system_id: nil, data: nil)
47
- raise ArgumentError unless connection.nil? ^ data.nil?
48
-
49
- @connection = connection
50
- @system_id = system_id
51
-
52
- # Useful for testing only
53
- @data = {
54
- 'v1' => data,
55
- 'v2' => data
56
- }
57
- end
58
-
59
- def data(version: DEFAULT_VERSION)
60
- @data ||= {}
61
- @data[version] ||= fetch_data(version:)
62
- end
63
-
64
- attr_reader :system_id
65
-
66
- private
67
-
68
- def get(path, params: nil)
69
- @connection.get(path, params:)
70
- end
71
-
72
- def fetch_data(version:)
73
- raise ArgumentError unless AVAILABLE_VERSIONS.include?(version)
74
- return unless system_id
75
-
76
- get("/#{version}/senec/systems/#{system_id}/dashboard")
77
- end
78
- end
79
- end
80
- end
@@ -1,69 +0,0 @@
1
- require_relative 'connection'
2
-
3
- # Model for the Senec technical data.
4
- #
5
- # Example use:
6
- #
7
- # connection = Senec::Cloud::Connection.new(username: '...', password: '...')
8
- #
9
- # # Get the data of a specific system:
10
- # TechnicalData[connection].find('123456')
11
- #
12
- # # Get the data of the default system:
13
- # TechnicalData[connection].first
14
- #
15
- module Senec
16
- module Cloud
17
- class TechnicalData
18
- class Finder
19
- def initialize(connection)
20
- @connection = connection
21
- end
22
- attr_reader :connection
23
-
24
- def find(system_id)
25
- TechnicalData.new(connection:, system_id:).tap(&:load_data)
26
- end
27
-
28
- def first
29
- find(connection.default_system_id)
30
- end
31
- end
32
-
33
- def self.[](connection)
34
- Finder.new(connection)
35
- end
36
-
37
- def initialize(connection: nil, system_id: nil, data: nil)
38
- raise ArgumentError unless connection.nil? ^ data.nil?
39
-
40
- @connection = connection
41
- @system_id = system_id
42
-
43
- # Useful for testing only
44
- @data = data
45
- end
46
-
47
- def load_data
48
- raise 'Data already present!' if @data
49
-
50
- @system_id ||= connection.default_system_id
51
- @data = fetch_data
52
- end
53
-
54
- attr_reader :system_id, :data
55
-
56
- private
57
-
58
- def get(path, params: nil)
59
- @connection.get(path, params:)
60
- end
61
-
62
- def fetch_data
63
- return unless system_id
64
-
65
- get("/v1/senec/systems/#{system_id}/technical-data")
66
- end
67
- end
68
- end
69
- end