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 +4 -4
- data/CHANGELOG.md +5 -1
- data/README.md +35 -4
- data/Rakefile +5 -2
- data/lib/growatt/api.rb +30 -6
- data/lib/growatt/authorization.rb +72 -17
- data/lib/growatt/client.rb +125 -66
- data/lib/growatt/connection.rb +21 -2
- data/lib/growatt/const.rb +26 -5
- data/lib/growatt/error.rb +16 -4
- data/lib/growatt/pagination.rb +17 -11
- data/lib/growatt/version.rb +1 -1
- data/lib/growatt.rb +40 -5
- metadata +3 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 77b696ab2333a1954a2e62dcacffea1cb9f1c4ba02f06e34001d90abc25ec4a2
|
4
|
+
data.tar.gz: ae2ce05b85d79cec90ca7beb35e820f0d2529b95c215fb2ebf88213c7c3d8b92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
[](https://rubygems.org/gems/growatt)
|
3
|
+
[](https://codeclimate.com/github/jancotanis/growatt/maintainability)
|
4
|
+
[](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
|
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
|
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
|
-
|
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
|
-
|
22
|
+
|
data/lib/growatt/api.rb
CHANGED
@@ -1,15 +1,31 @@
|
|
1
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
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'
|
34
|
+
response = post('newTwoLoginAPI.do', { 'userName' => self.username, 'password' => _password })
|
16
35
|
self.format = _format
|
17
|
-
|
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
|
data/lib/growatt/client.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
#
|
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
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
24
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
#
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
#
|
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
|
-
|
83
|
-
|
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
|
-
|
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
|
-
#
|
94
|
-
|
95
|
-
|
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
|
-
|
162
|
+
when Timespan::MONTH
|
98
163
|
date.strftime("%Y-%m")
|
99
|
-
|
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
|
-
#
|
106
|
-
#
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
data/lib/growatt/connection.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
data/lib/growatt/pagination.rb
CHANGED
@@ -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
|
-
#
|
22
|
+
# If the body is a Hash, return the 'back' key if it exists
|
13
23
|
if body.is_a? Hash
|
14
|
-
|
15
|
-
body['back']
|
16
|
-
else
|
17
|
-
body
|
18
|
-
end
|
24
|
+
body['back'] || body
|
19
25
|
else
|
20
|
-
#
|
26
|
+
# If the body is a String, parse it as JSON
|
21
27
|
JSON.parse(body)
|
22
28
|
end
|
23
29
|
end
|
data/lib/growatt/version.rb
CHANGED
data/lib/growatt.rb
CHANGED
@@ -1,22 +1,57 @@
|
|
1
|
-
|
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
|
-
|
12
|
-
|
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({
|
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
|
+
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:
|
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
|
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: []
|