growatt 0.1.3 → 0.2.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/.env.template +4 -4
- data/.gitignore +47 -47
- data/CHANGELOG.md +14 -10
- data/Gemfile +6 -6
- data/README.md +83 -83
- data/Rakefile +19 -19
- data/growatt.gemspec +37 -37
- data/lib/growatt/api.rb +35 -35
- data/lib/growatt/authorization.rb +36 -36
- data/lib/growatt/client.rb +160 -158
- data/lib/growatt/connection.rb +26 -26
- data/lib/growatt/const.rb +29 -26
- data/lib/growatt/error.rb +12 -12
- data/lib/growatt/pagination.rb +26 -26
- data/lib/growatt/version.rb +5 -5
- data/lib/growatt.rb +27 -27
- data/test/auth_test.rb +20 -0
- data/test/client_test.rb +69 -0
- data/test/test_helper.rb +23 -0
- metadata +14 -8
data/lib/growatt/client.rb
CHANGED
@@ -1,158 +1,160 @@
|
|
1
|
-
require File.expand_path('api', __dir__)
|
2
|
-
require File.expand_path('const', __dir__)
|
3
|
-
require File.expand_path('error', __dir__)
|
4
|
-
|
5
|
-
module Growatt
|
6
|
-
# Wrapper for the Growatt REST API
|
7
|
-
#
|
8
|
-
# @see no API documentation, reverse engineered
|
9
|
-
class Client < API
|
10
|
-
|
11
|
-
def initialize(options = {})
|
12
|
-
super(options)
|
13
|
-
end
|
14
|
-
|
15
|
-
# access data returned from login
|
16
|
-
def login_info
|
17
|
-
@login_data
|
18
|
-
end
|
19
|
-
def plant_list(user_id=nil)
|
20
|
-
user_id = login_info['user']['id'] unless user_id
|
21
|
-
_plant_list({'userId':user_id})
|
22
|
-
end
|
23
|
-
def plant_detail(plant_id,type=Timespan::DAY,date=Time.now)
|
24
|
-
_plant_detail( {
|
25
|
-
'plantId': plant_id,
|
26
|
-
'type': type,
|
27
|
-
'date': timespan_date(type,date)
|
28
|
-
})
|
29
|
-
end
|
30
|
-
def plant_info(plant_id)
|
31
|
-
_plant_info({
|
32
|
-
'op': 'getAllDeviceList',
|
33
|
-
'plantId': plant_id,
|
34
|
-
'pageNum': 1,
|
35
|
-
'pageSize': 1
|
36
|
-
})
|
37
|
-
end
|
38
|
-
def device_list(plant_id)
|
39
|
-
plant_info(plant_id).deviceList
|
40
|
-
end
|
41
|
-
|
42
|
-
def inverter_list(plant_id)
|
43
|
-
devices = device_list(plant_id)
|
44
|
-
devices.select { |device| 'inverter'.eql? device.deviceType }
|
45
|
-
end
|
46
|
-
|
47
|
-
# get data for invertor control
|
48
|
-
def inverter_control_data(inverter_id)
|
49
|
-
_inverter_api({
|
50
|
-
'op': 'getMaxSetData',
|
51
|
-
'serialNum': inverter_id
|
52
|
-
}).obj.maxSetBean
|
53
|
-
end
|
54
|
-
|
55
|
-
def update_inverter_setting(serial_number,command,param_id,parameters)
|
56
|
-
command_parameters = {
|
57
|
-
'op': command,
|
58
|
-
'serialNum': serial_number,
|
59
|
-
'paramId': param_id
|
60
|
-
}
|
61
|
-
# repeated values to hash { param1: value1 }
|
62
|
-
|
63
|
-
parameters = parameters.map.with_index { |value, index| ["param#{index + 1}", value] }.to_h if parameters.is_a? Array
|
64
|
-
self.format = 'x-www-form-urlencoded'
|
65
|
-
data = JSON.parse(post('newTcpsetAPI.do',command_parameters.merge(parameters)).body)
|
66
|
-
self.format = :json
|
67
|
-
data['success']
|
68
|
-
end
|
69
|
-
|
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])
|
74
|
-
end
|
75
|
-
|
76
|
-
# check if invertor is turned on
|
77
|
-
def inverter_on?(serial_number)
|
78
|
-
status = inverter_control_data(serial_number)
|
79
|
-
Inverter::ON.eql? status.max_cmd_on_off
|
80
|
-
end
|
81
|
-
|
82
|
-
def
|
83
|
-
if
|
84
|
-
params = [0]
|
85
|
-
else
|
86
|
-
raise ArgumentError, "exportlimitation enable should be
|
87
|
-
raise ArgumentError, "Value should be set for export limitation" unless value
|
88
|
-
params = [1, value, enable]
|
89
|
-
end
|
90
|
-
update_inverter_setting(serial_number,'maxSetApi','backflow_setting',params)
|
91
|
-
end
|
92
|
-
|
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
|
96
|
-
date.strftime("%Y")
|
97
|
-
elsif Timespan::MONTH.eql? timespan
|
98
|
-
date.strftime("%Y-%m")
|
99
|
-
elsif Timespan::DAY.eql? timespan
|
100
|
-
date.strftime("%Y-%m-%d")
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
#
|
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
|
115
|
-
_inverter_api({
|
116
|
-
'op': operator,
|
117
|
-
'id': inverter_id,
|
118
|
-
'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
|
127
|
-
})
|
128
|
-
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
|
-
|
143
|
-
|
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)
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
api_endpoint :
|
155
|
-
api_endpoint :
|
156
|
-
|
157
|
-
|
158
|
-
|
1
|
+
require File.expand_path('api', __dir__)
|
2
|
+
require File.expand_path('const', __dir__)
|
3
|
+
require File.expand_path('error', __dir__)
|
4
|
+
|
5
|
+
module Growatt
|
6
|
+
# Wrapper for the Growatt REST API
|
7
|
+
#
|
8
|
+
# @see no API documentation, reverse engineered
|
9
|
+
class Client < API
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
super(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
# access data returned from login
|
16
|
+
def login_info
|
17
|
+
@login_data
|
18
|
+
end
|
19
|
+
def plant_list(user_id=nil)
|
20
|
+
user_id = login_info['user']['id'] unless user_id
|
21
|
+
_plant_list({'userId':user_id})
|
22
|
+
end
|
23
|
+
def plant_detail(plant_id,type=Timespan::DAY,date=Time.now)
|
24
|
+
_plant_detail( {
|
25
|
+
'plantId': plant_id,
|
26
|
+
'type': type,
|
27
|
+
'date': timespan_date(type,date)
|
28
|
+
})
|
29
|
+
end
|
30
|
+
def plant_info(plant_id)
|
31
|
+
_plant_info({
|
32
|
+
'op': 'getAllDeviceList',
|
33
|
+
'plantId': plant_id,
|
34
|
+
'pageNum': 1,
|
35
|
+
'pageSize': 1
|
36
|
+
})
|
37
|
+
end
|
38
|
+
def device_list(plant_id)
|
39
|
+
plant_info(plant_id).deviceList
|
40
|
+
end
|
41
|
+
|
42
|
+
def inverter_list(plant_id)
|
43
|
+
devices = device_list(plant_id)
|
44
|
+
devices.select { |device| 'inverter'.eql? device.deviceType }
|
45
|
+
end
|
46
|
+
|
47
|
+
# get data for invertor control
|
48
|
+
def inverter_control_data(inverter_id)
|
49
|
+
_inverter_api({
|
50
|
+
'op': 'getMaxSetData',
|
51
|
+
'serialNum': inverter_id
|
52
|
+
}).obj.maxSetBean
|
53
|
+
end
|
54
|
+
|
55
|
+
def update_inverter_setting(serial_number,command,param_id,parameters)
|
56
|
+
command_parameters = {
|
57
|
+
'op': command,
|
58
|
+
'serialNum': serial_number,
|
59
|
+
'paramId': param_id
|
60
|
+
}
|
61
|
+
# repeated values to hash { param1: value1 }
|
62
|
+
|
63
|
+
parameters = parameters.map.with_index { |value, index| ["param#{index + 1}", value] }.to_h if parameters.is_a? Array
|
64
|
+
self.format = 'x-www-form-urlencoded'
|
65
|
+
data = JSON.parse(post('newTcpsetAPI.do',command_parameters.merge(parameters)).body)
|
66
|
+
self.format = :json
|
67
|
+
data['success']
|
68
|
+
end
|
69
|
+
|
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])
|
74
|
+
end
|
75
|
+
|
76
|
+
# check if invertor is turned on
|
77
|
+
def inverter_on?(serial_number)
|
78
|
+
status = inverter_control_data(serial_number)
|
79
|
+
Inverter::ON.eql? status.max_cmd_on_off
|
80
|
+
end
|
81
|
+
|
82
|
+
def export_limit(serial_number,enable,value=nil)
|
83
|
+
if ExportLimit::DISABLE.eql? enable
|
84
|
+
params = [0]
|
85
|
+
else
|
86
|
+
raise ArgumentError, "exportlimitation enable should be ExportLimit::WATT or ExportLimit::PERCENTAGE" unless [ExportLimit::WATT,ExportLimit::PERCENTAGE].include? enable
|
87
|
+
raise ArgumentError, "Value should be set for export limitation" unless value
|
88
|
+
params = [1, value, enable]
|
89
|
+
end
|
90
|
+
update_inverter_setting(serial_number,'maxSetApi','backflow_setting',params)
|
91
|
+
end
|
92
|
+
|
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
|
96
|
+
date.strftime("%Y")
|
97
|
+
elsif Timespan::MONTH.eql? timespan
|
98
|
+
date.strftime("%Y-%m")
|
99
|
+
elsif Timespan::DAY.eql? timespan
|
100
|
+
date.strftime("%Y-%m-%d")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
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
|
115
|
+
_inverter_api({
|
116
|
+
'op': operator,
|
117
|
+
'id': inverter_id,
|
118
|
+
'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
|
127
|
+
})
|
128
|
+
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
|
+
|
143
|
+
|
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|
|
150
|
+
request.headers['Accept'] = "application/#{format}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
api_endpoint :_plant_list, 'PlantListAPI.do'
|
155
|
+
api_endpoint :_plant_detail, 'PlantDetailAPI.do'
|
156
|
+
api_endpoint :_inverter_api, 'newInverterAPI.do'
|
157
|
+
api_endpoint :_plant_info, 'newTwoPlantAPI.do'
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
data/lib/growatt/connection.rb
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
-
require 'faraday'
|
2
|
-
require 'faraday-cookie_jar'
|
3
|
-
|
4
|
-
module Growatt
|
5
|
-
# Create connection and use cookies for authentication tokens
|
6
|
-
module Connection
|
7
|
-
def connection
|
8
|
-
raise ConfigurationError, "Option for endpoint is not defined" unless endpoint
|
9
|
-
|
10
|
-
options = setup_options
|
11
|
-
@connection ||= Faraday::Connection.new(options) do |connection|
|
12
|
-
connection.use :cookie_jar
|
13
|
-
|
14
|
-
connection.use Faraday::Response::RaiseError
|
15
|
-
connection.adapter Faraday.default_adapter
|
16
|
-
setup_authorization(connection)
|
17
|
-
setup_headers(connection)
|
18
|
-
connection.response :json, content_type: /\bjson$/
|
19
|
-
connection.use Faraday::Request::UrlEncoded
|
20
|
-
|
21
|
-
setup_logger_filtering(connection, logger) if logger
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
end
|
26
|
-
end
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday-cookie_jar'
|
3
|
+
|
4
|
+
module Growatt
|
5
|
+
# Create connection and use cookies for authentication tokens
|
6
|
+
module Connection
|
7
|
+
def connection
|
8
|
+
raise ConfigurationError, "Option for endpoint is not defined" unless endpoint
|
9
|
+
|
10
|
+
options = setup_options
|
11
|
+
@connection ||= Faraday::Connection.new(options) do |connection|
|
12
|
+
connection.use :cookie_jar
|
13
|
+
|
14
|
+
connection.use Faraday::Response::RaiseError
|
15
|
+
connection.adapter Faraday.default_adapter
|
16
|
+
setup_authorization(connection)
|
17
|
+
setup_headers(connection)
|
18
|
+
connection.response :json, content_type: /\bjson$/
|
19
|
+
connection.use Faraday::Request::UrlEncoded
|
20
|
+
|
21
|
+
setup_logger_filtering(connection, logger) if logger
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/growatt/const.rb
CHANGED
@@ -1,26 +1,29 @@
|
|
1
|
-
|
2
|
-
module Growatt
|
3
|
-
class Enum
|
4
|
-
def self.enum(array)
|
5
|
-
array.each do |c|
|
6
|
-
const_set c,c
|
7
|
-
end
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
class Timespan
|
12
|
-
HOUR = 0
|
13
|
-
DAY = 1
|
14
|
-
MONTH = 2
|
15
|
-
YEAR = 3
|
16
|
-
end
|
17
|
-
|
18
|
-
class Inverter
|
19
|
-
ON = "0101"
|
20
|
-
OFF = "0000"
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
1
|
+
|
2
|
+
module Growatt
|
3
|
+
class Enum
|
4
|
+
def self.enum(array)
|
5
|
+
array.each do |c|
|
6
|
+
const_set c,c
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Timespan
|
12
|
+
HOUR = 0
|
13
|
+
DAY = 1
|
14
|
+
MONTH = 2
|
15
|
+
YEAR = 3
|
16
|
+
end
|
17
|
+
|
18
|
+
class Inverter
|
19
|
+
ON = "0101"
|
20
|
+
OFF = "0000"
|
21
|
+
end
|
22
|
+
|
23
|
+
class ExportLimit
|
24
|
+
DISABLE = -1
|
25
|
+
WATT = 1
|
26
|
+
PERCENTAGE = 0
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/lib/growatt/error.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
module Growatt
|
2
|
-
|
3
|
-
# Generic error to be able to rescue all Hudu errors
|
4
|
-
class GrowattError < StandardError; end
|
5
|
-
|
6
|
-
# Configuration returns error
|
7
|
-
class ConfigurationError < GrowattError; end
|
8
|
-
|
9
|
-
# Issue authenticting
|
10
|
-
class AuthenticationError < GrowattError; end
|
11
|
-
|
12
|
-
end
|
1
|
+
module Growatt
|
2
|
+
|
3
|
+
# Generic error to be able to rescue all Hudu errors
|
4
|
+
class GrowattError < StandardError; end
|
5
|
+
|
6
|
+
# Configuration returns error
|
7
|
+
class ConfigurationError < GrowattError; end
|
8
|
+
|
9
|
+
# Issue authenticting
|
10
|
+
class AuthenticationError < GrowattError; end
|
11
|
+
|
12
|
+
end
|
data/lib/growatt/pagination.rb
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
-
require 'uri'
|
2
|
-
require 'json'
|
3
|
-
|
4
|
-
module Growatt
|
5
|
-
|
6
|
-
# Defines HTTP request methods
|
7
|
-
module RequestPagination
|
8
|
-
|
9
|
-
class DataPager < WrAPI::RequestPagination::DefaultPager
|
10
|
-
|
11
|
-
def self.data(body)
|
12
|
-
# data is at 'back'
|
13
|
-
if body.is_a? Hash
|
14
|
-
if body['back']
|
15
|
-
body['back']
|
16
|
-
else
|
17
|
-
body
|
18
|
-
end
|
19
|
-
else
|
20
|
-
# in some cases wrong contenttype is returned instead of app/json
|
21
|
-
JSON.parse(body)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
1
|
+
require 'uri'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Growatt
|
5
|
+
|
6
|
+
# Defines HTTP request methods
|
7
|
+
module RequestPagination
|
8
|
+
|
9
|
+
class DataPager < WrAPI::RequestPagination::DefaultPager
|
10
|
+
|
11
|
+
def self.data(body)
|
12
|
+
# data is at 'back'
|
13
|
+
if body.is_a? Hash
|
14
|
+
if body['back']
|
15
|
+
body['back']
|
16
|
+
else
|
17
|
+
body
|
18
|
+
end
|
19
|
+
else
|
20
|
+
# in some cases wrong contenttype is returned instead of app/json
|
21
|
+
JSON.parse(body)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/growatt/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Growatt
|
4
|
-
VERSION = '0.
|
5
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Growatt
|
4
|
+
VERSION = '0.2.0'
|
5
|
+
end
|
data/lib/growatt.rb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
|
-
require "wrapi"
|
2
|
-
require File.expand_path('growatt/client', __dir__)
|
3
|
-
require File.expand_path('growatt/version', __dir__)
|
4
|
-
require File.expand_path('growatt/pagination', __dir__)
|
5
|
-
|
6
|
-
module Growatt
|
7
|
-
extend WrAPI::Configuration
|
8
|
-
extend WrAPI::RespondTo
|
9
|
-
|
10
|
-
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
|
13
|
-
DEFAULT_PAGINATION = RequestPagination::DataPager
|
14
|
-
#
|
15
|
-
# @return [Growatt::Client]
|
16
|
-
def self.client(options = {})
|
17
|
-
Growatt::Client.new({ user_agent: DEFAULT_UA, endpoint: DEFAULT_ENDPOINT, pagination_class: DEFAULT_PAGINATION }.merge(options))
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.reset
|
21
|
-
super
|
22
|
-
self.endpoint = nil
|
23
|
-
self.user_agent = DEFAULT_UA
|
24
|
-
self.endpoint = DEFAULT_ENDPOINT
|
25
|
-
self.pagination_class = DEFAULT_PAGINATION
|
26
|
-
end
|
27
|
-
end
|
1
|
+
require "wrapi"
|
2
|
+
require File.expand_path('growatt/client', __dir__)
|
3
|
+
require File.expand_path('growatt/version', __dir__)
|
4
|
+
require File.expand_path('growatt/pagination', __dir__)
|
5
|
+
|
6
|
+
module Growatt
|
7
|
+
extend WrAPI::Configuration
|
8
|
+
extend WrAPI::RespondTo
|
9
|
+
|
10
|
+
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
|
13
|
+
DEFAULT_PAGINATION = RequestPagination::DataPager
|
14
|
+
#
|
15
|
+
# @return [Growatt::Client]
|
16
|
+
def self.client(options = {})
|
17
|
+
Growatt::Client.new({ user_agent: DEFAULT_UA, endpoint: DEFAULT_ENDPOINT, pagination_class: DEFAULT_PAGINATION }.merge(options))
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.reset
|
21
|
+
super
|
22
|
+
self.endpoint = nil
|
23
|
+
self.user_agent = DEFAULT_UA
|
24
|
+
self.endpoint = DEFAULT_ENDPOINT
|
25
|
+
self.pagination_class = DEFAULT_PAGINATION
|
26
|
+
end
|
27
|
+
end
|
data/test/auth_test.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'dotenv'
|
2
|
+
require 'logger'
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
|
6
|
+
describe 'authentication' do
|
7
|
+
|
8
|
+
it '#1 use wrong username/password' do
|
9
|
+
assert_raises Growatt::AuthenticationError do
|
10
|
+
client = Growatt.client( { username: "xxx"+Growatt.username, password: Growatt.password } )
|
11
|
+
client.login
|
12
|
+
flunk( 'AuthenticationError expected' )
|
13
|
+
end
|
14
|
+
end
|
15
|
+
it '#2 use correct username/password' do
|
16
|
+
client = Growatt.client( { username: Growatt.username, password:Growatt.password } )
|
17
|
+
client.login
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/test/client_test.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'dotenv'
|
2
|
+
require 'logger'
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
def p m, o
|
6
|
+
# puts "#{m}: #{o.inspect}"
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'client' do
|
10
|
+
before do
|
11
|
+
@client = Growatt.client
|
12
|
+
@client.login
|
13
|
+
end
|
14
|
+
|
15
|
+
it '#1 GET info' do
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
it "#2 plant/device list" do
|
20
|
+
plants = @client.plant_list
|
21
|
+
|
22
|
+
p "\n* plants", plants
|
23
|
+
plant_id = plants.data.first.plantId
|
24
|
+
assert plant_id, "plant_id should not be nil"
|
25
|
+
|
26
|
+
detail = @client.plant_detail(plant_id)
|
27
|
+
p "\n* plant detail", detail
|
28
|
+
assert value(detail.plantData.plantId).must_equal(plant_id), 'correct plantId/structure'
|
29
|
+
|
30
|
+
plant_info = @client.plant_info(plant_id)
|
31
|
+
p "\n* plant info:", plant_info
|
32
|
+
|
33
|
+
devices = @client.device_list(plant_id)
|
34
|
+
p "\n* devices:", plant_info.deviceList
|
35
|
+
inverter = devices.first
|
36
|
+
# get data
|
37
|
+
data = @client.inverter_data(inverter.deviceSn,Growatt::Timespan::DAY,Time.now)
|
38
|
+
assert data, "Get day data by hour"
|
39
|
+
data = @client.inverter_data(inverter.deviceSn,Growatt::Timespan::MONTH,Time.now)
|
40
|
+
assert data, "Get month data by day"
|
41
|
+
data = @client.inverter_data(inverter.deviceSn,Growatt::Timespan::YEAR,Time.now)
|
42
|
+
assert data, "Get year data by month"
|
43
|
+
puts "\n* Inverter data:"
|
44
|
+
puts data.to_json
|
45
|
+
|
46
|
+
# turn device on
|
47
|
+
result = @client.turn_inverter(inverter.deviceSn,true)
|
48
|
+
p "\n* turnon result:", result
|
49
|
+
is_on = @client.inverter_on?(inverter.deviceSn)
|
50
|
+
|
51
|
+
assert is_on, "Inverter should be on"
|
52
|
+
assert result, "inverter on should be success"
|
53
|
+
end
|
54
|
+
it "#3 export limitation parameters" do
|
55
|
+
|
56
|
+
assert_raises ArgumentError do
|
57
|
+
@client.export_limit('xxxx', 4)
|
58
|
+
flunk( 'ArgumentError expected, invalid limtation' )
|
59
|
+
end
|
60
|
+
assert_raises ArgumentError do
|
61
|
+
@client.export_limit('xxxx', Growatt::ExportLimit::WATT)
|
62
|
+
flunk( 'ArgumentError expected, no value given for WATTs' )
|
63
|
+
end
|
64
|
+
assert_raises ArgumentError do
|
65
|
+
@client.export_limit('xxxx',Growatt::ExportLimit::PERCENTAGE)
|
66
|
+
flunk( 'ArgumentError expected, no value given for PERCENTAGEs' )
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|