growatt 0.1.1 → 0.1.2

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: 2791314d4017b5a2938b28f53a622bb065c7a7bfe7bcf2f40f11dcd933fc55a0
4
- data.tar.gz: f4c17815a0e1181e85dc6fa427c8e6ea0bbf9fc7a52df71f0b2a42545e111357
3
+ metadata.gz: 901c3b86599ff30ac6afa1514954736acd92800cc7c969ab0bb185ed1285472a
4
+ data.tar.gz: a3a2d6a8ed6dcde5a5187b8133a05d15e003b38495afa456de28bb6be556f46f
5
5
  SHA512:
6
- metadata.gz: b9675bf16f2c91a6dadfce6c8a6db8ba4cf8d353bc1523cb9e17e934c711f070230354b00c34253466b604b841caaa1d515cee1729a42523017d0d4a94d1362d
7
- data.tar.gz: '02828c3dd7555d0eff9964adf3669ac93d7f55aaf5c7b65daa7e181be8cdaeaae506899c2fdba8f130006a3cb4a348820c8ea57b0c2c86f8514e864bc2c78a33'
6
+ metadata.gz: 84fd0f8eab0c9a2bd1f325a04b8d514140e81ccebcb20749b9bdb11161f78ff13a5f40ad871f47f01eb277d2345d3945b086567c61476de5ac4207e72ad3aee1
7
+ data.tar.gz: e00dfb6ac885c733d24be4d8170727f2cb5ae87693fbf9bb19e2f12deedcb1e2a9872c194a1f0598f8644aca294e094d187f443c12e898a53309416059641e5f
data/.env.template CHANGED
@@ -1,4 +1,4 @@
1
- # demo token used
2
- GROWATT_USERNAME=username
3
- GROWATT_PASSWORD=password
4
-
1
+ # demo token used
2
+ GROWATT_USERNAME=username
3
+ GROWATT_PASSWORD=password
4
+
data/.gitignore CHANGED
@@ -1,47 +1,47 @@
1
- *.gem
2
- *.rbc
3
- /.config
4
- /coverage/
5
- /InstalledFiles
6
- /pkg/
7
- /spec/reports/
8
- /spec/examples.txt
9
- /test/tmp/
10
- /test/version_tmp/
11
- /tmp/
12
- /data/
13
- *.log
14
- *.txt
15
- *.json
16
- *.yml
17
- .DS_Store
18
- __pycache__
19
- *.py
20
-
21
- # Used by dotenv library to load environment variables.
22
- .env
23
-
24
-
25
- ## Documentation cache and generated files:
26
- /.yardoc/
27
- /_yardoc/
28
- /doc/
29
- /rdoc/
30
-
31
- ## Environment normalization:
32
- /.bundle/
33
- /vendor/bundle
34
- /lib/bundler/man/
35
-
36
- # for a library or gem, you might want to ignore these files since the code is
37
- # intended to run in multiple environments; otherwise, check them in:
38
- # Gemfile.lock
39
- # .ruby-version
40
- # .ruby-gemset
41
-
42
- # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
43
- .rvmrc
44
-
45
- # Used by RuboCop. Remote config files pulled in from inherit_from directive.
46
- # .rubocop-https?--*
47
- Gemfile.lock
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+ /data/
13
+ *.log
14
+ *.txt
15
+ *.json
16
+ *.yml
17
+ .DS_Store
18
+ __pycache__
19
+ *.py
20
+
21
+ # Used by dotenv library to load environment variables.
22
+ .env
23
+
24
+
25
+ ## Documentation cache and generated files:
26
+ /.yardoc/
27
+ /_yardoc/
28
+ /doc/
29
+ /rdoc/
30
+
31
+ ## Environment normalization:
32
+ /.bundle/
33
+ /vendor/bundle
34
+ /lib/bundler/man/
35
+
36
+ # for a library or gem, you might want to ignore these files since the code is
37
+ # intended to run in multiple environments; otherwise, check them in:
38
+ # Gemfile.lock
39
+ # .ruby-version
40
+ # .ruby-gemset
41
+
42
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
43
+ .rvmrc
44
+
45
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
46
+ # .rubocop-https?--*
47
+ Gemfile.lock
data/CHANGELOG.md CHANGED
@@ -1,6 +1,8 @@
1
- ## [Unreleased]
2
-
3
- ## [0.1.0] - 2024-05-28
4
- - Initial release
5
- ## [0.1.1] - 2024-05-28
6
- - remove unused require
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-05-28
4
+ - Initial release
5
+ ## [0.1.1] - 2024-05-28
6
+ - remove unused require
7
+ ## [0.1.2] - 2024-05-29
8
+ - add missing dependency
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- # Specify your gem's dependencies in growatt.gemspec
6
- gemspec
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in growatt.gemspec
6
+ gemspec
data/README.md CHANGED
@@ -1,83 +1,83 @@
1
- # Growatt API
2
- [![Version](https://img.shields.io/gem/v/growatt.svg)](https://rubygems.org/gems/growatt)
3
-
4
- 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
-
6
-
7
- ## Installation
8
-
9
- Add this line to your application's Gemfile:
10
-
11
- ```ruby
12
- gem 'growatt'
13
- ```
14
-
15
- And then execute:
16
-
17
- $ bundle install
18
-
19
- Or install it yourself as:
20
-
21
- $ gem install growatt
22
-
23
- ## Usage
24
-
25
- Before you start making the requests to API provide the endpoint and api key using the configuration wrapping.
26
-
27
- ```ruby
28
- require 'growatt'
29
- require 'logger'
30
-
31
- # use do block
32
- Growatt.configure do |config|
33
- config.username = ENV['GROWATT_USERNAME']
34
- config.password = ENV['GROWATT_PASSWORD']
35
- config.logger = Logger.new(TEST_LOGGER)
36
- end
37
-
38
- # or configure with options hash
39
- client = Growatt.client
40
- client.login
41
-
42
- ```
43
-
44
- ## Resources
45
- ### Authentication
46
- ```ruby
47
- # setup
48
- #
49
- begin
50
- client = Growatt.client
51
- client.login
52
- # turn invertor off
53
- client.turn_inverter('<serial_no>', false)
54
- rescue Growatt::AuthenticationError => e
55
- puts "Error logging in growatt api"
56
- puts e
57
- end
58
- ```
59
-
60
-
61
-
62
- ## Publishing
63
-
64
- 1. Update version in [version.rb](lib/growatt/version.rb).
65
- 2. Add release to [CHANGELOG.md](CHANGELOG.md)
66
- 3. Commit.
67
- 4. Test build.
68
- ```
69
- > rake build
70
-
71
- ```
72
- 5. Release
73
- ```
74
- > rake release
75
-
76
- ## Contributing
77
-
78
- Bug reports and pull requests are welcome on GitHub at https://github.com/jancotanis/growatt.
79
-
80
- ## License
81
-
82
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
83
- "# growatt"
1
+ # Growatt API
2
+ [![Version](https://img.shields.io/gem/v/growatt.svg)](https://rubygems.org/gems/growatt)
3
+
4
+ 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
+
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'growatt'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install growatt
22
+
23
+ ## Usage
24
+
25
+ Before you start making the requests to API provide the endpoint and api key using the configuration wrapping.
26
+
27
+ ```ruby
28
+ require 'growatt'
29
+ require 'logger'
30
+
31
+ # use do block
32
+ Growatt.configure do |config|
33
+ config.username = ENV['GROWATT_USERNAME']
34
+ config.password = ENV['GROWATT_PASSWORD']
35
+ config.logger = Logger.new(TEST_LOGGER)
36
+ end
37
+
38
+ # or configure with options hash
39
+ client = Growatt.client
40
+ client.login
41
+
42
+ ```
43
+
44
+ ## Resources
45
+ ### Authentication
46
+ ```ruby
47
+ # setup
48
+ #
49
+ begin
50
+ client = Growatt.client
51
+ client.login
52
+ # turn invertor off
53
+ client.turn_inverter('<serial_no>', false)
54
+ rescue Growatt::AuthenticationError => e
55
+ puts "Error logging in growatt api"
56
+ puts e
57
+ end
58
+ ```
59
+
60
+
61
+
62
+ ## Publishing
63
+
64
+ 1. Update version in [version.rb](lib/growatt/version.rb).
65
+ 2. Add release to [CHANGELOG.md](CHANGELOG.md)
66
+ 3. Commit.
67
+ 4. Test build.
68
+ ```
69
+ > rake build
70
+
71
+ ```
72
+ 5. Release
73
+ ```
74
+ > rake release
75
+
76
+ ## Contributing
77
+
78
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jancotanis/growatt.
79
+
80
+ ## License
81
+
82
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
83
+ "# growatt"
data/Rakefile CHANGED
@@ -1,19 +1,19 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'dotenv'
5
- require 'rake/testtask'
6
-
7
- Dotenv.load
8
-
9
- #system './bin/cc-test-reporter before-build'
10
- Rake::TestTask.new(:test) do |t|
11
- t.libs << 'test'
12
- t.libs << 'lib'
13
- t.test_files = FileList['test/**/*_test.rb']
14
- end
15
-
16
- require 'rubocop/rake_task'
17
- RuboCop::RakeTask.new
18
- task default: %i[test rubocop]
19
- #system './bin/cc-test-reporter after-build'
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'dotenv'
5
+ require 'rake/testtask'
6
+
7
+ Dotenv.load
8
+
9
+ #system './bin/cc-test-reporter before-build'
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << 'test'
12
+ t.libs << 'lib'
13
+ t.test_files = FileList['test/**/*_test.rb']
14
+ end
15
+
16
+ require 'rubocop/rake_task'
17
+ RuboCop::RakeTask.new
18
+ task default: %i[test rubocop]
19
+ #system './bin/cc-test-reporter after-build'
data/growatt.gemspec CHANGED
@@ -1,36 +1,37 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'lib/growatt/version'
4
-
5
- Gem::Specification.new do |s|
6
- s.name = 'growatt'
7
- s.version = Growatt::VERSION
8
- s.authors = ['Janco Tanis']
9
- s.email = 'gems@jancology.com'
10
- s.license = 'MIT'
11
-
12
- s.summary = 'A Ruby wrapper for the Growatt APIs (readonly)'
13
- s.homepage = 'https://rubygems.org/gems/growatt'
14
-
15
- s.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
16
-
17
- s.metadata['homepage_uri'] = s.homepage
18
- s.metadata['source_code_uri'] = 'https://github.com/jancotanis/growatt'
19
-
20
- # Specify which files should be added to the gem when it is released.
21
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
- s.files = Dir.chdir(File.expand_path(__dir__)) do
23
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
24
- end
25
- s.bindir = 'exe'
26
- s.executables = s.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
27
- s.require_paths = ['lib']
28
-
29
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
30
- s.platform = Gem::Platform::RUBY
31
- s.add_runtime_dependency 'wrapi', ">= 0.3.0"
32
- s.add_development_dependency 'dotenv'
33
- s.add_development_dependency 'minitest'
34
- s.add_development_dependency 'simplecov'
35
- s.add_development_dependency 'rubocop'
36
- end
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/growatt/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'growatt'
7
+ s.version = Growatt::VERSION
8
+ s.authors = ['Janco Tanis']
9
+ s.email = 'gems@jancology.com'
10
+ s.license = 'MIT'
11
+
12
+ s.summary = 'A Ruby wrapper for the Growatt APIs (readonly)'
13
+ s.homepage = 'https://rubygems.org/gems/growatt'
14
+
15
+ s.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
16
+
17
+ s.metadata['homepage_uri'] = s.homepage
18
+ s.metadata['source_code_uri'] = 'https://github.com/jancotanis/growatt'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ s.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
24
+ end
25
+ s.bindir = 'exe'
26
+ s.executables = s.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
27
+ s.require_paths = ['lib']
28
+
29
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
30
+ s.platform = Gem::Platform::RUBY
31
+ s.add_runtime_dependency 'wrapi', ">= 0.3.0"
32
+ s.add_runtime_dependency 'faraday-cookie_jar'
33
+ s.add_development_dependency 'dotenv'
34
+ s.add_development_dependency 'minitest'
35
+ s.add_development_dependency 'simplecov'
36
+ s.add_development_dependency 'rubocop'
37
+ end
data/lib/growatt/api.rb CHANGED
@@ -1,35 +1,35 @@
1
- require "wrapi"
2
- require File.expand_path('authorization', __dir__)
3
- require File.expand_path('connection', __dir__)
4
-
5
- module Growatt
6
- # @private
7
- class API
8
-
9
- # @private
10
- attr_accessor *WrAPI::Configuration::VALID_OPTIONS_KEYS
11
-
12
- # Creates a new API and copies settings from singleton
13
- def initialize(options = {})
14
- options = Growatt.options.merge(options)
15
- WrAPI::Configuration::VALID_OPTIONS_KEYS.each do |key|
16
- send("#{key}=", options[key])
17
- end
18
- end
19
-
20
- def config
21
- conf = {}
22
- WrAPI::Configuration::VALID_OPTIONS_KEYS.each do |key|
23
- conf[key] = send key
24
- end
25
- conf
26
- end
27
-
28
- include WrAPI::Connection
29
- include Connection
30
- include WrAPI::Request
31
- include WrAPI::Authentication
32
- include Authentication
33
-
34
- end
35
- end
1
+ require "wrapi"
2
+ require File.expand_path('authorization', __dir__)
3
+ require File.expand_path('connection', __dir__)
4
+
5
+ module Growatt
6
+ # @private
7
+ class API
8
+
9
+ # @private
10
+ attr_accessor *WrAPI::Configuration::VALID_OPTIONS_KEYS
11
+
12
+ # Creates a new API and copies settings from singleton
13
+ def initialize(options = {})
14
+ options = Growatt.options.merge(options)
15
+ WrAPI::Configuration::VALID_OPTIONS_KEYS.each do |key|
16
+ send("#{key}=", options[key])
17
+ end
18
+ end
19
+
20
+ def config
21
+ conf = {}
22
+ WrAPI::Configuration::VALID_OPTIONS_KEYS.each do |key|
23
+ conf[key] = send key
24
+ end
25
+ conf
26
+ end
27
+
28
+ include WrAPI::Connection
29
+ include Connection
30
+ include WrAPI::Request
31
+ include WrAPI::Authentication
32
+ include Authentication
33
+
34
+ end
35
+ end
@@ -1,36 +1,36 @@
1
- require 'digest'
2
- require File.expand_path('error', __dir__)
3
-
4
- module Growatt
5
- # Deals with authentication flow and stores it within global configuration
6
- 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
12
-
13
- _format = self.format
14
- self.format = 'x-www-form-urlencoded'
15
- response = post('newTwoLoginAPI.do', {'userName': self.username, 'password': _password})
16
- 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
24
- end
25
- private
26
- def hash_password(password)
27
- password_md5 = Digest::MD5.hexdigest(password.encode('utf-8'))
28
- (0...password_md5.length).step(2) do |i|
29
- if password_md5[i] == '0'
30
- password_md5[i] = 'c'
31
- end
32
- end
33
- password_md5
34
- end
35
- end
36
- end
1
+ require 'digest'
2
+ require File.expand_path('error', __dir__)
3
+
4
+ module Growatt
5
+ # Deals with authentication flow and stores it within global configuration
6
+ 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
12
+
13
+ _format = self.format
14
+ self.format = 'x-www-form-urlencoded'
15
+ response = post('newTwoLoginAPI.do', {'userName': self.username, 'password': _password})
16
+ 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
24
+ end
25
+ private
26
+ def hash_password(password)
27
+ password_md5 = Digest::MD5.hexdigest(password.encode('utf-8'))
28
+ (0...password_md5.length).step(2) do |i|
29
+ if password_md5[i] == '0'
30
+ password_md5[i] = 'c'
31
+ end
32
+ end
33
+ password_md5
34
+ end
35
+ end
36
+ end
@@ -1,136 +1,136 @@
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,setting_type,parameters)
56
- command_parameters = {
57
- 'op': command,
58
- 'serialNum': serial_number,
59
- 'type': setting_type
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',nil,{'paramId':'max_cmd_on_off','param1':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
- # utility function to get date accordign timespan month/day
83
- def timespan_date(timespan=Timespan::DAY,date=Time.now)
84
- if Timespan::MONTH.eql? timespan
85
- date.strftime("%Y-%m")
86
- else
87
- date.strftime("%Y-%m-%d")
88
- end
89
- end
90
-
91
- #
92
- # functions below are copied from python example code but not sure if these work with MOD9000 inverters
93
- #
94
- def inverter_data(inverter_id,type=Timespan::DAY,date=Time.now)
95
- _inverter_api({
96
- 'op': 'getInverterData',
97
- 'inverterId': inverter_id,
98
- 'type': type,
99
- 'date': timespan_date(type,date)
100
- })
101
- end
102
- def inverter_detail(inverter_id)
103
- _inverter_api({
104
- 'op': 'getInverterDetailData',
105
- 'inverterId': inverter_id
106
- })
107
- end
108
- def inverter_detail_two(inverter_id)
109
- _inverter_api({
110
- 'op': 'getInverterDetailData_two',
111
- 'inverterId': inverter_id
112
- })
113
- end
114
- def update_mix_inverter_setting(serial_number, setting_type, parameters)
115
- update_inverter_setting(serial_number,'mixSetApiNew',setting_type,parameters)
116
- end
117
- def update_ac_inverter_setting(serial_number, setting_type, parameters)
118
- update_inverter_setting(serial_number,'spaSetApi',setting_type,parameters)
119
- end
120
-
121
-
122
- private
123
- def self.api_endpoint(method,path)
124
- # all records
125
- self.send(:define_method, method) do |params = {}|
126
- # return data result
127
- get(path,params)
128
- end
129
- end
130
- api_endpoint :_plant_list, 'PlantListAPI.do'
131
- api_endpoint :_plant_detail, 'PlantDetailAPI.do'
132
- api_endpoint :_inverter_api, 'newInverterAPI.do'
133
- api_endpoint :_plant_info, 'newTwoPlantAPI.do'
134
-
135
- end
136
- end
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,setting_type,parameters)
56
+ command_parameters = {
57
+ 'op': command,
58
+ 'serialNum': serial_number,
59
+ 'type': setting_type
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',nil,{'paramId':'max_cmd_on_off','param1':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
+ # utility function to get date accordign timespan month/day
83
+ def timespan_date(timespan=Timespan::DAY,date=Time.now)
84
+ if Timespan::MONTH.eql? timespan
85
+ date.strftime("%Y-%m")
86
+ else
87
+ date.strftime("%Y-%m-%d")
88
+ end
89
+ end
90
+
91
+ #
92
+ # functions below are copied from python example code but not sure if these work with MOD9000 inverters
93
+ #
94
+ def inverter_data(inverter_id,type=Timespan::DAY,date=Time.now)
95
+ _inverter_api({
96
+ 'op': 'getInverterData',
97
+ 'inverterId': inverter_id,
98
+ 'type': type,
99
+ 'date': timespan_date(type,date)
100
+ })
101
+ end
102
+ def inverter_detail(inverter_id)
103
+ _inverter_api({
104
+ 'op': 'getInverterDetailData',
105
+ 'inverterId': inverter_id
106
+ })
107
+ end
108
+ def inverter_detail_two(inverter_id)
109
+ _inverter_api({
110
+ 'op': 'getInverterDetailData_two',
111
+ 'inverterId': inverter_id
112
+ })
113
+ end
114
+ def update_mix_inverter_setting(serial_number, setting_type, parameters)
115
+ update_inverter_setting(serial_number,'mixSetApiNew',setting_type,parameters)
116
+ end
117
+ def update_ac_inverter_setting(serial_number, setting_type, parameters)
118
+ update_inverter_setting(serial_number,'spaSetApi',setting_type,parameters)
119
+ end
120
+
121
+
122
+ private
123
+ def self.api_endpoint(method,path)
124
+ # all records
125
+ self.send(:define_method, method) do |params = {}|
126
+ # return data result
127
+ get(path,params)
128
+ end
129
+ end
130
+ api_endpoint :_plant_list, 'PlantListAPI.do'
131
+ api_endpoint :_plant_detail, 'PlantDetailAPI.do'
132
+ api_endpoint :_inverter_api, 'newInverterAPI.do'
133
+ api_endpoint :_plant_info, 'newTwoPlantAPI.do'
134
+
135
+ end
136
+ end
@@ -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,19 +1,19 @@
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
- class Timespan
11
- HOUR = 0
12
- DAY = 1
13
- MONTH = 2
14
- end
15
- class Inverter
16
- ON = "0101"
17
- OFF = "0000"
18
- end
19
- end
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
+ class Timespan
11
+ HOUR = 0
12
+ DAY = 1
13
+ MONTH = 2
14
+ end
15
+ class Inverter
16
+ ON = "0101"
17
+ OFF = "0000"
18
+ end
19
+ 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
@@ -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
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- module Growatt
4
- VERSION = '0.1.1'
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Growatt
4
+ VERSION = '0.1.2'
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
@@ -0,0 +1,44 @@
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
+ # turn device on
37
+ result = @client.turn_inverter(inverter.deviceSn,true)
38
+ p "\n* turnon result:", result
39
+ is_on = @client.inverter_on?(inverter.deviceSn)
40
+
41
+ assert is_on, "Inverter should be on"
42
+ assert result, "inverter on should be success"
43
+ end
44
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+
5
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
6
+ require 'minitest/autorun'
7
+ require 'minitest/spec'
8
+
9
+ require 'dotenv'
10
+ require 'growatt'
11
+
12
+ Dotenv.load
13
+
14
+ TEST_LOGGER = 'test.log'
15
+
16
+ File.delete(TEST_LOGGER) if File.exist?(TEST_LOGGER)
17
+
18
+ Growatt.reset
19
+ Growatt.configure do |config|
20
+ config.username = ENV['GROWATT_USERNAME']
21
+ config.password = ENV['GROWATT_PASSWORD']
22
+ config.logger = Logger.new(TEST_LOGGER)
23
+ end
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: growatt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janco Tanis
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
11
  date: 2024-05-29 00:00:00.000000000 Z
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.3.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday-cookie_jar
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: dotenv
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,7 +94,7 @@ dependencies:
80
94
  - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
83
- description:
97
+ description:
84
98
  email: gems@jancology.com
85
99
  executables: []
86
100
  extensions: []
@@ -102,13 +116,16 @@ files:
102
116
  - lib/growatt/error.rb
103
117
  - lib/growatt/pagination.rb
104
118
  - lib/growatt/version.rb
119
+ - test/auth_test.rb
120
+ - test/client_test.rb
121
+ - test/test_helper.rb
105
122
  homepage: https://rubygems.org/gems/growatt
106
123
  licenses:
107
124
  - MIT
108
125
  metadata:
109
126
  homepage_uri: https://rubygems.org/gems/growatt
110
127
  source_code_uri: https://github.com/jancotanis/growatt
111
- post_install_message:
128
+ post_install_message:
112
129
  rdoc_options: []
113
130
  require_paths:
114
131
  - lib
@@ -123,8 +140,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
140
  - !ruby/object:Gem::Version
124
141
  version: '0'
125
142
  requirements: []
126
- rubygems_version: 3.2.3
127
- signing_key:
143
+ rubygems_version: 3.0.3.1
144
+ signing_key:
128
145
  specification_version: 4
129
146
  summary: A Ruby wrapper for the Growatt APIs (readonly)
130
- test_files: []
147
+ test_files:
148
+ - test/auth_test.rb
149
+ - test/client_test.rb
150
+ - test/test_helper.rb