growatt 0.1.4 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 690f64562aa9bb8165ed9ddd859d5858c39d4a347c9a428997969881cb0dcfa4
4
- data.tar.gz: 7a6236aa6d31d42ba90166dfbf3e64d5209e542eef2902186e394c7a708e41e4
3
+ metadata.gz: 77b696ab2333a1954a2e62dcacffea1cb9f1c4ba02f06e34001d90abc25ec4a2
4
+ data.tar.gz: ae2ce05b85d79cec90ca7beb35e820f0d2529b95c215fb2ebf88213c7c3d8b92
5
5
  SHA512:
6
- metadata.gz: b02ff867b268c0d4ea7697a53e76cf4a6afb9245afb8ad34e5982f03d4789c6a3effc8518cc19d707d9bdad8483d95192136e9b40f04489c50568c7142b8c2f3
7
- data.tar.gz: 4fd6b8ee8aafda44cf9ddada702f929a98c9a121ebaca71af70a786f6b8fb09fd884530a35542e1c1a2e4b1e6cff0178819656151449f6e017a117eaac2db018
6
+ metadata.gz: 433e4623b99df6514698821dbbd0ad319d736c47840b2c670f216b1bf9302f6cedede2c0f8ef9eb7bd02741f2d3ae0b8f179d72b14d69448852a7fc5eb6d219f
7
+ data.tar.gz: 2b6d7c7f0b69bace8f2b45ad838ff091e0af665ddc1dfae0c970a7bb0d692882428534eb706793102ce2524b00345f8eb02a9b771bb3dafc07dee2fa73c9c893
data/CHANGELOG.md CHANGED
@@ -9,4 +9,8 @@
9
9
  ## [0.1.3] - 2024-05-30
10
10
  - fix api issues to get inverter data
11
11
  ## [0.1.4] - 2024-05-30
12
- - fix api issues where some api require correct Accept header
12
+ - fix api issues where some api require correct Accept header
13
+ ## [0.2.0] - 2024-06-03
14
+ - rename method to reflect export limitations
15
+ ## [0.2.1] - 2025-03-19
16
+ - add dcumenttion
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Growatt API
2
2
  [![Version](https://img.shields.io/gem/v/growatt.svg)](https://rubygems.org/gems/growatt)
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/60e7b62db0513a99ae4a/maintainability)](https://codeclimate.com/github/jancotanis/growatt/maintainability)
4
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/60e7b62db0513a99ae4a/test_coverage)](https://codeclimate.com/github/jancotanis/growatt/test_coverage)
3
5
 
4
6
  This is a wrapper for the Growatt rest API. Main objective is to turn inverter on/off. This has been testen with MOD-9000TL-X.
5
7
 
@@ -22,12 +24,12 @@ Or install it yourself as:
22
24
 
23
25
  ## Usage
24
26
 
25
- Before you start making the requests to API provide the endpoint and api key using the configuration wrapping.
27
+ Before you start making the requests to API provide the username and password using with Shinephone app.
26
28
 
27
29
  ```ruby
28
30
  require 'growatt'
29
31
  require 'logger'
30
-
32
+ TEST_LOGGER = './test.log'
31
33
  # use do block
32
34
  Growatt.configure do |config|
33
35
  config.username = ENV['GROWATT_USERNAME']
@@ -42,7 +44,7 @@ client.login
42
44
  ```
43
45
 
44
46
  ## Resources
45
- ### Authentication
47
+ ### Authentication and configuration
46
48
  ```ruby
47
49
  # setup
48
50
  #
@@ -52,12 +54,40 @@ begin
52
54
  # turn invertor off
53
55
  client.turn_inverter('<serial_no>', false)
54
56
  rescue Growatt::AuthenticationError => e
55
- puts "Error logging in growatt api"
57
+ puts "Error login to growatt api"
56
58
  puts e
57
59
  end
58
60
  ```
61
+ ### Read data
62
+
63
+ ```ruby
64
+ # create client (don't forget to configure authentication)
65
+ # get data for first inverter for first defined plant
66
+ plants = client.plant_list
67
+ plant_id = plants.data.first.plantId
68
+ devices = client.inverter_list(plant_id)
69
+ inverter = devices.first
59
70
 
71
+ yymm = Time.now.strftime("%Y%m")
72
+ puts "- loading period #{yymm}"
73
+ data = client.inverter_data(inverter.deviceSn,Growatt::Timespan::MONTH,current_month)
60
74
 
75
+ ```
76
+
77
+ ### Control
78
+
79
+ ```ruby
80
+ # continu from read data example above
81
+ inverter = devices.first
82
+
83
+ # turn inverter on/of
84
+ client.turn_inverter(inverter.deviceSn, false)
85
+
86
+ # or limit energy export
87
+ client.export_limit(inverter.deviceSn,Growatt::ExportLimit::PERCENTAGE, 100)
88
+ # allow energy export to grid
89
+ client.export_limit(inverter.deviceSn,Growatt::ExportLimit::DISABLE)
90
+ ```
61
91
 
62
92
  ## Publishing
63
93
 
@@ -72,6 +102,7 @@ end
72
102
  5. Release
73
103
  ```
74
104
  > rake release
105
+ ```
75
106
 
76
107
  ## Contributing
77
108
 
data/Rakefile CHANGED
@@ -6,14 +6,17 @@ require 'rake/testtask'
6
6
 
7
7
  Dotenv.load
8
8
 
9
- #system './bin/cc-test-reporter before-build'
9
+ system './bin/cc-test-reporter before-build'
10
10
  Rake::TestTask.new(:test) do |t|
11
11
  t.libs << 'test'
12
12
  t.libs << 'lib'
13
13
  t.test_files = FileList['test/**/*_test.rb']
14
+ at_exit do
15
+ system './bin/cc-test-reporter after-build'
16
+ end
14
17
  end
15
18
 
16
19
  require 'rubocop/rake_task'
17
20
  RuboCop::RakeTask.new
18
21
  task default: %i[test rubocop]
19
- #system './bin/cc-test-reporter after-build'
22
+
data/lib/growatt/api.rb CHANGED
@@ -1,15 +1,31 @@
1
- require "wrapi"
1
+ # frozen_string_literal: true
2
+
3
+ require 'wrapi'
2
4
  require File.expand_path('authorization', __dir__)
3
5
  require File.expand_path('connection', __dir__)
4
6
 
5
7
  module Growatt
6
- # @private
8
+ # The `API` class is an internal component of the Growatt module.
9
+ # It is responsible for managing API configurations, connections, and authentication.
10
+ #
11
+ # This class should not be accessed directly. Instead, use `Growatt.client` to interact with the API.
12
+ #
7
13
  class API
8
-
9
- # @private
14
+ # Attribute accessors for all valid configuration options.
15
+ #
16
+ # These options are defined in `WrAPI::Configuration::VALID_OPTIONS_KEYS`.
10
17
  attr_accessor *WrAPI::Configuration::VALID_OPTIONS_KEYS
11
18
 
12
- # Creates a new API and copies settings from singleton
19
+ # Initializes a new `Growatt::API` instance.
20
+ #
21
+ # This method copies configuration settings from the Growatt module singleton and allows
22
+ # for optional overrides through the `options` parameter.
23
+ #
24
+ # @param options [Hash] Optional configuration overrides.
25
+ #
26
+ # @example Creating an API instance with custom options:
27
+ # api = Growatt::API.new(user_agent: "CustomClient/1.0")
28
+ #
13
29
  def initialize(options = {})
14
30
  options = Growatt.options.merge(options)
15
31
  WrAPI::Configuration::VALID_OPTIONS_KEYS.each do |key|
@@ -17,6 +33,13 @@ module Growatt
17
33
  end
18
34
  end
19
35
 
36
+ # Retrieves the current API configuration as a hash.
37
+ #
38
+ # @return [Hash] A hash containing the current API configuration.
39
+ #
40
+ # @example Getting the current configuration:
41
+ # api.config # => { endpoint: "https://server.growatt.com/", user_agent: "Ruby Growatt API client ..." }
42
+ #
20
43
  def config
21
44
  conf = {}
22
45
  WrAPI::Configuration::VALID_OPTIONS_KEYS.each do |key|
@@ -25,11 +48,12 @@ module Growatt
25
48
  conf
26
49
  end
27
50
 
51
+ # Includes required modules for making API requests, handling authentication,
52
+ # and establishing connections.
28
53
  include WrAPI::Connection
29
54
  include Connection
30
55
  include WrAPI::Request
31
56
  include WrAPI::Authentication
32
57
  include Authentication
33
-
34
58
  end
35
59
  end
@@ -1,36 +1,91 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'digest'
2
4
  require File.expand_path('error', __dir__)
3
5
 
4
6
  module Growatt
5
- # Deals with authentication flow and stores it within global configuration
7
+ # Handles authentication flow and stores session data in the global configuration.
8
+ #
9
+ # This module provides methods for logging into the Growatt portal, hashing passwords,
10
+ # and validating credentials.
11
+ #
6
12
  module Authentication
7
-
8
- # Authorize to the Growatt portal
9
- def login()
10
- raise ConfigurationError, "Username/password not set" unless username || password
11
- _password = hash_password(self.password) #unless is_password_hashed
13
+ # Logs in to the Growatt portal using the stored credentials.
14
+ #
15
+ # This method:
16
+ # - Validates that the username and password are set.
17
+ # - Hashes the password using MD5.
18
+ # - Sends a login request with the credentials.
19
+ # - Processes the server response.
20
+ #
21
+ # @raise [ConfigurationError] If username or password is missing.
22
+ # @raise [AuthenticationError] If authentication fails.
23
+ # @return [Hash] The login response data if successful.
24
+ #
25
+ # @example Logging in to Growatt:
26
+ # client.login
27
+ #
28
+ def login
29
+ validate_credentials
30
+ _password = hash_password(self.password) # Hash password before sending
12
31
 
13
32
  _format = self.format
14
33
  self.format = 'x-www-form-urlencoded'
15
- response = post('newTwoLoginAPI.do', {'userName': self.username, 'password': _password})
34
+ response = post('newTwoLoginAPI.do', { 'userName' => self.username, 'password' => _password })
16
35
  self.format = _format
17
- data = response.body['back']
18
- if data && data['success']
19
- @login_data = data
20
- data
21
- else
22
- raise AuthenticationError.new(data['error'])
23
- end
36
+ process_response(response.body['back'])
24
37
  end
38
+
25
39
  private
40
+
41
+ # Hashes the given password using MD5.
42
+ #
43
+ # This method generates an MD5 hash of the password and modifies it by replacing
44
+ # every occurrence of '0' at even indices with 'c'.
45
+ #
46
+ # @param password [String] The plain-text password.
47
+ # @return [String] The modified MD5-hashed password.
48
+ #
49
+ # @example Hashing a password:
50
+ # hash_password("mypassword") # => "5f4dcc3bcfcd204e074324a5e7565eaf"
51
+ #
26
52
  def hash_password(password)
27
53
  password_md5 = Digest::MD5.hexdigest(password.encode('utf-8'))
28
54
  (0...password_md5.length).step(2) do |i|
29
- if password_md5[i] == '0'
30
- password_md5[i] = 'c'
31
- end
55
+ password_md5[i] = 'c' if password_md5[i] == '0'
32
56
  end
33
57
  password_md5
34
58
  end
59
+
60
+ # Validates that the username and password are set.
61
+ #
62
+ # @raise [ConfigurationError] If either credential is missing.
63
+ #
64
+ # @example Checking credentials before login:
65
+ # validate_credentials # Raises ConfigurationError if missing
66
+ #
67
+ def validate_credentials
68
+ raise ConfigurationError, "Username/password not set" unless username && password
69
+ end
70
+
71
+ # Processes the authentication response.
72
+ #
73
+ # If authentication is successful, stores the login data. Otherwise, raises an error.
74
+ #
75
+ # @param data [Hash] The response data from the Growatt portal.
76
+ # @raise [AuthenticationError] If authentication fails.
77
+ # @return [Hash] The login data if authentication succeeds.
78
+ #
79
+ # @example Handling a successful login:
80
+ # process_response({ "success" => true }) # Returns login data
81
+ #
82
+ def process_response(data)
83
+ if data && data['success']
84
+ @login_data = data
85
+ data
86
+ else
87
+ raise AuthenticationError.new(data['error'])
88
+ end
89
+ end
35
90
  end
36
91
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path('api', __dir__)
2
4
  require File.expand_path('const', __dir__)
3
5
  require File.expand_path('error', __dir__)
@@ -5,28 +7,56 @@ require File.expand_path('error', __dir__)
5
7
  module Growatt
6
8
  # Wrapper for the Growatt REST API
7
9
  #
8
- # @see no API documentation, reverse engineered
10
+ # This class provides methods to interact with the Growatt API, including:
11
+ # - Retrieving plant and inverter data
12
+ # - Managing inverters (turning them on/off, updating settings)
13
+ # - Handling authentication and session data
14
+ #
15
+ # @note No official API documentation is available; this was reverse-engineered.
16
+ #
17
+ # @see Growatt::Authentication For authentication-related logic.
9
18
  class Client < API
10
-
19
+ # Initializes the Growatt API client.
20
+ #
21
+ # @param options [Hash] Additional options for the API client.
11
22
  def initialize(options = {})
12
23
  super(options)
13
24
  end
14
25
 
15
- # access data returned from login
26
+ # Retrieves login session data.
27
+ #
28
+ # @return [Hash, nil] The login data stored in @login_data.
16
29
  def login_info
17
30
  @login_data
18
31
  end
19
- def plant_list(user_id=nil)
20
- user_id = login_info['user']['id'] unless user_id
21
- _plant_list({'userId':user_id})
32
+
33
+ # Retrieves a list of plants associated with a user.
34
+ #
35
+ # @param user_id [String, nil] The user ID; if nil, it defaults to the logged-in user's ID.
36
+ # @return [Hash] The list of plants.
37
+ def plant_list(user_id = nil)
38
+ user_id ||= login_info['user']['id']
39
+ _plant_list({ 'userId': user_id })
22
40
  end
23
- def plant_detail(plant_id,type=Timespan::DAY,date=Time.now)
24
- _plant_detail( {
41
+
42
+ # Retrieves detailed information about a plant.
43
+ #
44
+ # @param plant_id [String] The plant ID.
45
+ # @param type [String] The timespan type (default: `Timespan::DAY`).
46
+ # @param date [Time] The date for the requested timespan (default: `Time.now`).
47
+ # @return [Hash] The plant details.
48
+ def plant_detail(plant_id, type = Timespan::DAY, date = Time.now)
49
+ _plant_detail({
25
50
  'plantId': plant_id,
26
51
  'type': type,
27
- 'date': timespan_date(type,date)
52
+ 'date': timespan_date(type, date)
28
53
  })
29
54
  end
55
+
56
+ # Retrieves plant information.
57
+ #
58
+ # @param plant_id [String] The plant ID.
59
+ # @return [Hash] Plant information, including available devices.
30
60
  def plant_info(plant_id)
31
61
  _plant_info({
32
62
  'op': 'getAllDeviceList',
@@ -35,16 +65,28 @@ module Growatt
35
65
  'pageSize': 1
36
66
  })
37
67
  end
68
+
69
+ # Retrieves a list of devices in a plant.
70
+ #
71
+ # @param plant_id [String] The plant ID.
72
+ # @return [Array] A list of devices.
38
73
  def device_list(plant_id)
39
74
  plant_info(plant_id).deviceList
40
75
  end
41
76
 
77
+ # Retrieves a list of inverters in a plant.
78
+ #
79
+ # @param plant_id [String] The plant ID.
80
+ # @return [Array] A list of inverters.
42
81
  def inverter_list(plant_id)
43
82
  devices = device_list(plant_id)
44
- devices.select { |device| 'inverter'.eql? device.deviceType }
83
+ devices.select { |device| 'inverter'.eql? device.deviceType }
45
84
  end
46
85
 
47
- # get data for invertor control
86
+ # Retrieves data for inverter control.
87
+ #
88
+ # @param inverter_id [String] The inverter's serial number.
89
+ # @return [Hash] The inverter's control data.
48
90
  def inverter_control_data(inverter_id)
49
91
  _inverter_api({
50
92
  'op': 'getMaxSetData',
@@ -52,109 +94,126 @@ module Growatt
52
94
  }).obj.maxSetBean
53
95
  end
54
96
 
55
- def update_inverter_setting(serial_number,command,param_id,parameters)
97
+ # Updates an inverter's setting.
98
+ #
99
+ # @param serial_number [String] The inverter's serial number.
100
+ # @param command [String] The command to execute.
101
+ # @param param_id [String] The parameter ID.
102
+ # @param parameters [Array, Hash] The parameters to send.
103
+ # @return [Boolean] `true` if the update was successful, `false` otherwise.
104
+ def update_inverter_setting(serial_number, command, param_id, parameters)
56
105
  command_parameters = {
57
106
  'op': command,
58
107
  'serialNum': serial_number,
59
108
  'paramId': param_id
60
109
  }
61
- # repeated values to hash { param1: value1 }
62
110
 
63
111
  parameters = parameters.map.with_index { |value, index| ["param#{index + 1}", value] }.to_h if parameters.is_a? Array
64
112
  self.format = 'x-www-form-urlencoded'
65
- data = JSON.parse(post('newTcpsetAPI.do',command_parameters.merge(parameters)).body)
113
+ data = JSON.parse(post('newTcpsetAPI.do', command_parameters.merge(parameters)).body)
66
114
  self.format = :json
67
115
  data['success']
68
116
  end
69
117
 
70
- # turn invertor on of off
71
- def turn_inverter(serial_number,on=true)
72
- onoff = (on ? Inverter::ON : Inverter::OFF )
73
- update_inverter_setting(serial_number,'maxSetApi','max_cmd_on_off',[onoff])
118
+ # Turns an inverter on or off.
119
+ #
120
+ # @param serial_number [String] The inverter's serial number.
121
+ # @param on [Boolean] `true` to turn on, `false` to turn off.
122
+ # @return [Boolean] `true` if the operation was successful.
123
+ def turn_inverter(serial_number, on = true)
124
+ onoff = (on ? Inverter::ON : Inverter::OFF)
125
+ update_inverter_setting(serial_number, 'maxSetApi', 'max_cmd_on_off', [onoff])
74
126
  end
75
127
 
76
- # check if invertor is turned on
128
+ # Checks if an inverter is turned on.
129
+ #
130
+ # @param serial_number [String] The inverter's serial number.
131
+ # @return [Boolean] `true` if the inverter is on, `false` otherwise.
77
132
  def inverter_on?(serial_number)
78
133
  status = inverter_control_data(serial_number)
79
134
  Inverter::ON.eql? status.max_cmd_on_off
80
135
  end
81
136
 
82
- def export_limitation(serial_number,enable,value=nil)
83
- if Inverter::DISABLE.eql? enable
137
+ # Sets export limit for an inverter.
138
+ #
139
+ # @param serial_number [String] The inverter's serial number.
140
+ # @param enable [String] `ExportLimit::DISABLE`, `ExportLimit::WATT`, or `ExportLimit::PERCENTAGE`.
141
+ # @param value [Numeric, nil] The export limit value (required unless disabled).
142
+ # @return [Boolean] `true` if the setting update was successful.
143
+ def export_limit(serial_number, enable, value = nil)
144
+ if ExportLimit::DISABLE.eql? enable
84
145
  params = [0]
85
146
  else
86
- raise ArgumentError, "exportlimitation enable should be Inverter::WATT or Inverter::PERCENTAGE" unless [Inverter::WATT,Inverter::PERCENTAGE].include? enable
87
- raise ArgumentError, "Value should be set for export limitation" unless value
147
+ validate_export_parameters(enable, value)
88
148
  params = [1, value, enable]
89
149
  end
90
- update_inverter_setting(serial_number,'maxSetApi','backflow_setting',params)
150
+ update_inverter_setting(serial_number, 'maxSetApi', 'backflow_setting', params)
91
151
  end
92
152
 
93
- # utility function to get date accordign timespan month/day
94
- def timespan_date(timespan=Timespan::DAY,date=Time.now)
95
- if Timespan::YEAR.eql? timespan
153
+ # Utility function to get a formatted date based on timespan.
154
+ #
155
+ # @param timespan [String] The timespan type (`Timespan::DAY`, `Timespan::MONTH`, `Timespan::YEAR`).
156
+ # @param date [Time] The date (default: `Time.now`).
157
+ # @return [String] The formatted date.
158
+ def timespan_date(timespan = Timespan::DAY, date = Time.now)
159
+ case timespan
160
+ when Timespan::YEAR
96
161
  date.strftime("%Y")
97
- elsif Timespan::MONTH.eql? timespan
162
+ when Timespan::MONTH
98
163
  date.strftime("%Y-%m")
99
- elsif Timespan::DAY.eql? timespan
164
+ when Timespan::DAY
100
165
  date.strftime("%Y-%m-%d")
101
166
  end
102
167
  end
103
168
 
169
+ # Retrieves inverter data based on timespan.
104
170
  #
105
- # functions below are copied from python example code but not sure if these work with MOD9000 inverters
106
- #
107
- def inverter_data(inverter_id,type=Timespan::DAY,date=Time.now)
108
- if Timespan::DAY.eql? type
109
- operator = 'getInverterData_max'
110
- elsif Timespan::MONTH.eql? type
111
- operator = 'getMaxMonthPac'
112
- elsif Timespan::YEAR.eql? type
113
- operator = 'getMaxYearPac'
114
- end
171
+ # @param inverter_id [String] The inverter's ID.
172
+ # @param type [String] The timespan type.
173
+ # @param date [Time] The date (default: `Time.now`).
174
+ # @return [Hash] The inverter data.
175
+ def inverter_data(inverter_id, type = Timespan::DAY, date = Time.now)
176
+ operator =
177
+ case type
178
+ when Timespan::DAY then 'getInverterData_max'
179
+ when Timespan::MONTH then 'getMaxMonthPac'
180
+ when Timespan::YEAR then 'getMaxYearPac'
181
+ end
182
+
115
183
  _inverter_api({
116
184
  'op': operator,
117
185
  'id': inverter_id,
118
186
  'type': 1,
119
- 'date': timespan_date(type,date)
120
- })
121
- end
122
- =begin
123
- def inverter_detail(inverter_id)
124
- _inverter_api({
125
- 'op': 'getInverterDetailData',
126
- 'inverterId': inverter_id
187
+ 'date': timespan_date(type, date)
127
188
  })
128
189
  end
129
- def inverter_detail_two(inverter_id)
130
- _inverter_api({
131
- 'op': 'getInverterDetailData_two',
132
- 'inverterId': inverter_id
133
- })
134
- end
135
- =end
136
- def update_mix_inverter_setting(serial_number, setting_type, parameters)
137
- update_inverter_setting(serial_number,'mixSetApiNew',setting_type,parameters)
138
- end
139
- def update_ac_inverter_setting(serial_number, setting_type, parameters)
140
- update_inverter_setting(serial_number,'spaSetApi',setting_type,parameters)
141
- end
142
190
 
191
+ private
143
192
 
144
- private
145
- def self.api_endpoint(method,path)
146
- # all records
147
- self.send(:define_method, method) do |params = {}|
148
- # return data result
149
- get(path,params) do |request|
193
+ # Defines API endpoints dynamically.
194
+ #
195
+ # @param method [Symbol] The method name.
196
+ # @param path [String] The API endpoint.
197
+ def self.api_endpoint(method, path)
198
+ define_method(method) do |params = {}|
199
+ get(path, params) do |request|
150
200
  request.headers['Accept'] = "application/#{format}"
151
201
  end
152
202
  end
153
203
  end
204
+
154
205
  api_endpoint :_plant_list, 'PlantListAPI.do'
155
206
  api_endpoint :_plant_detail, 'PlantDetailAPI.do'
156
207
  api_endpoint :_inverter_api, 'newInverterAPI.do'
157
208
  api_endpoint :_plant_info, 'newTwoPlantAPI.do'
158
209
 
210
+ # Validates export limitation parameters.
211
+ def validate_export_parameters(enable, value)
212
+ unless [ExportLimit::WATT, ExportLimit::PERCENTAGE].include?(enable)
213
+ raise ArgumentError, "Export limitation must be ExportLimit::WATT or ExportLimit::PERCENTAGE"
214
+ end
215
+ raise ArgumentError, "Value is required" unless value
216
+ raise ArgumentError, "Value must be numeric" unless value.is_a? Numeric
217
+ end
159
218
  end
160
219
  end
@@ -1,26 +1,45 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'faraday'
2
4
  require 'faraday-cookie_jar'
3
5
 
4
6
  module Growatt
5
- # Create connection and use cookies for authentication tokens
7
+ # Handles HTTP connection setup and authentication management.
8
+ #
9
+ # This module establishes a Faraday connection to interact with the Growatt API,
10
+ # ensuring proper authentication via cookies and setting up required headers.
6
11
  module Connection
12
+ # Establishes a Faraday connection with appropriate middleware and settings.
13
+ #
14
+ # @return [Faraday::Connection] The configured Faraday connection instance.
15
+ # @raise [ConfigurationError] If the API endpoint is not defined.
7
16
  def connection
8
17
  raise ConfigurationError, "Option for endpoint is not defined" unless endpoint
9
18
 
10
19
  options = setup_options
11
20
  @connection ||= Faraday::Connection.new(options) do |connection|
21
+ # Enable cookie-based authentication
12
22
  connection.use :cookie_jar
13
23
 
24
+ # Handle HTTP response errors
14
25
  connection.use Faraday::Response::RaiseError
26
+
27
+ # Set up default Faraday adapter
15
28
  connection.adapter Faraday.default_adapter
29
+
30
+ # Configure authentication and request headers
16
31
  setup_authorization(connection)
17
32
  setup_headers(connection)
33
+
34
+ # Parse JSON responses automatically
18
35
  connection.response :json, content_type: /\bjson$/
36
+
37
+ # Ensure requests are URL-encoded
19
38
  connection.use Faraday::Request::UrlEncoded
20
39
 
40
+ # Configure logging if a logger is present
21
41
  setup_logger_filtering(connection, logger) if logger
22
42
  end
23
43
  end
24
-
25
44
  end
26
45
  end
data/lib/growatt/const.rb CHANGED
@@ -1,26 +1,47 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Growatt
4
+ # A base class for defining enumerations using constants.
5
+ #
6
+ # This class dynamically defines constants from an array of strings.
3
7
  class Enum
4
- def self.enum(array)
5
- array.each do |c|
6
- const_set c,c
7
- end
8
+ # Defines constants based on the provided array.
9
+ #
10
+ # @param array [Array<String>] The array of strings to be converted into constants.
11
+ def self.enum(array)
12
+ array.each do |c|
13
+ const_set c, c
8
14
  end
15
+ end
9
16
  end
10
17
 
18
+ # Represents different timespan options for data retrieval.
11
19
  class Timespan
20
+ # Hourly data timespan
12
21
  HOUR = 0
22
+ # Daily data timespan
13
23
  DAY = 1
24
+ # Monthly data timespan
14
25
  MONTH = 2
26
+ # Yearly data timespan
15
27
  YEAR = 3
16
28
  end
17
29
 
30
+ # Represents possible states for an inverter.
18
31
  class Inverter
32
+ # Inverter is turned on
19
33
  ON = "0101"
34
+ # Inverter is turned off
20
35
  OFF = "0000"
36
+ end
37
+
38
+ # Represents export limit settings for an inverter.
39
+ class ExportLimit
40
+ # Disables export limitation
21
41
  DISABLE = -1
42
+ # Export limit is set in watts
22
43
  WATT = 1
44
+ # Export limit is set in percentage
23
45
  PERCENTAGE = 0
24
46
  end
25
-
26
47
  end
data/lib/growatt/error.rb CHANGED
@@ -1,12 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Growatt
2
4
 
3
- # Generic error to be able to rescue all Hudu errors
5
+ # Base error class for all Growatt-related exceptions.
6
+ # Allows rescuing all Growatt-specific errors with `Growatt::GrowattError`.
4
7
  class GrowattError < StandardError; end
5
8
 
6
- # Configuration returns error
9
+ # Raised when there is a configuration issue, such as missing or invalid settings.
10
+ #
11
+ # @example Raising a configuration error
12
+ # raise Growatt::ConfigurationError, "Invalid API endpoint"
7
13
  class ConfigurationError < GrowattError; end
8
14
 
9
- # Issue authenticting
15
+ # Raised when an authentication attempt fails.
16
+ #
17
+ # @example Handling authentication failure
18
+ # begin
19
+ # client.login
20
+ # rescue Growatt::AuthenticationError => e
21
+ # puts "Login failed: #{e.message}"
22
+ # end
10
23
  class AuthenticationError < GrowattError; end
11
-
12
24
  end
@@ -1,23 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require 'json'
3
5
 
4
6
  module Growatt
5
-
6
- # Defines HTTP request methods
7
+ # Provides pagination handling for API requests.
7
8
  module RequestPagination
8
-
9
+ # Custom data pager for processing API responses.
10
+ # Inherits from `WrAPI::RequestPagination::DefaultPager` and extracts relevant data from responses.
9
11
  class DataPager < WrAPI::RequestPagination::DefaultPager
10
-
12
+ # Extracts the relevant data from the API response body.
13
+ #
14
+ # @param body [String, Hash] The response body from an API request.
15
+ # @return [Hash] The parsed response data.
16
+ #
17
+ # @example Extracting data from a response
18
+ # response = { "back" => { "items" => [...] } }
19
+ # Growatt::RequestPagination::DataPager.data(response)
20
+ # # => { "items" => [...] }
11
21
  def self.data(body)
12
- # data is at 'back'
22
+ # If the body is a Hash, return the 'back' key if it exists
13
23
  if body.is_a? Hash
14
- if body['back']
15
- body['back']
16
- else
17
- body
18
- end
24
+ body['back'] || body
19
25
  else
20
- # in some cases wrong contenttype is returned instead of app/json
26
+ # If the body is a String, parse it as JSON
21
27
  JSON.parse(body)
22
28
  end
23
29
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Growatt
4
- VERSION = '0.1.4'
4
+ VERSION = '0.2.1'
5
5
  end
data/lib/growatt.rb CHANGED
@@ -1,22 +1,57 @@
1
- require "wrapi"
1
+ # frozen_string_literal: true
2
+
3
+ require 'wrapi'
2
4
  require File.expand_path('growatt/client', __dir__)
3
5
  require File.expand_path('growatt/version', __dir__)
4
6
  require File.expand_path('growatt/pagination', __dir__)
5
7
 
8
+ # The `Growatt` module provides a Ruby client for interacting with the Growatt API.
9
+ #
10
+ # It extends `WrAPI::Configuration` for managing API settings and `WrAPI::RespondTo`
11
+ # for handling API responses dynamically.
12
+ #
13
+ # The module allows you to create a client instance, configure API endpoints, and manage pagination settings.
14
+ #
15
+ # @example Creating a Growatt API client:
16
+ # client = Growatt.client(api_key: "your_api_key")
17
+ #
6
18
  module Growatt
7
19
  extend WrAPI::Configuration
8
20
  extend WrAPI::RespondTo
9
21
 
22
+ # Default User-Agent string for API requests.
10
23
  DEFAULT_UA = "Ruby Growatt API client #{Growatt::VERSION}".freeze
11
- # https://openapi.growatt.com/ is an option but does not work with my account
12
- DEFAULT_ENDPOINT = 'https://server.growatt.com/'.freeze
24
+
25
+ # Default API endpoint for Growatt services.
26
+ #
27
+ # Note: `https://openapi.growatt.com/` is an alternative, but it does not work with some accounts.
28
+ DEFAULT_ENDPOINT = 'https://server.growatt.com/'
29
+
30
+ # Default pagination class used for handling paginated API responses.
13
31
  DEFAULT_PAGINATION = RequestPagination::DataPager
32
+
33
+ # Initializes and returns a new `Growatt::Client` instance.
34
+ #
35
+ # @param options [Hash] Configuration options for the client.
36
+ # @option options [String] :user_agent Custom user agent string.
37
+ # @option options [String] :endpoint Custom API endpoint.
38
+ # @option options [Class] :pagination_class Custom pagination class.
14
39
  #
15
- # @return [Growatt::Client]
40
+ # @return [Growatt::Client] A new API client instance.
41
+ #
42
+ # @example Creating a client with custom options:
43
+ # client = Growatt.client(user_agent: "MyCustomClient/1.0")
16
44
  def self.client(options = {})
17
- Growatt::Client.new({ user_agent: DEFAULT_UA, endpoint: DEFAULT_ENDPOINT, pagination_class: DEFAULT_PAGINATION }.merge(options))
45
+ Growatt::Client.new({
46
+ user_agent: DEFAULT_UA,
47
+ endpoint: DEFAULT_ENDPOINT,
48
+ pagination_class: DEFAULT_PAGINATION
49
+ }.merge(options))
18
50
  end
19
51
 
52
+ # Resets the Growatt configuration to default values.
53
+ #
54
+ # This method restores the API endpoint, user agent, and pagination settings to their default values.
20
55
  def self.reset
21
56
  super
22
57
  self.endpoint = nil
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: growatt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janco Tanis
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-05-30 00:00:00.000000000 Z
10
+ date: 2025-03-19 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: wrapi
@@ -94,7 +93,6 @@ dependencies:
94
93
  - - ">="
95
94
  - !ruby/object:Gem::Version
96
95
  version: '0'
97
- description:
98
96
  email: gems@jancology.com
99
97
  executables: []
100
98
  extensions: []
@@ -122,7 +120,6 @@ licenses:
122
120
  metadata:
123
121
  homepage_uri: https://rubygems.org/gems/growatt
124
122
  source_code_uri: https://github.com/jancotanis/growatt
125
- post_install_message:
126
123
  rdoc_options: []
127
124
  require_paths:
128
125
  - lib
@@ -137,8 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
134
  - !ruby/object:Gem::Version
138
135
  version: '0'
139
136
  requirements: []
140
- rubygems_version: 3.2.3
141
- signing_key:
137
+ rubygems_version: 3.6.2
142
138
  specification_version: 4
143
139
  summary: A Ruby wrapper for the Growatt APIs (readonly)
144
140
  test_files: []