growatt 0.1.2 → 0.1.3

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: 901c3b86599ff30ac6afa1514954736acd92800cc7c969ab0bb185ed1285472a
4
- data.tar.gz: a3a2d6a8ed6dcde5a5187b8133a05d15e003b38495afa456de28bb6be556f46f
3
+ metadata.gz: c4ad3dfd2cb69b9089ded3f89572c129c6681e927ffbbf85c3b95e5257758db3
4
+ data.tar.gz: 266c26a95fa3835e52a3783a669ac10084420be4748d0942ee7af8d3f0ecf19e
5
5
  SHA512:
6
- metadata.gz: 84fd0f8eab0c9a2bd1f325a04b8d514140e81ccebcb20749b9bdb11161f78ff13a5f40ad871f47f01eb277d2345d3945b086567c61476de5ac4207e72ad3aee1
7
- data.tar.gz: e00dfb6ac885c733d24be4d8170727f2cb5ae87693fbf9bb19e2f12deedcb1e2a9872c194a1f0598f8644aca294e094d187f443c12e898a53309416059641e5f
6
+ metadata.gz: ca2a1abfd1d5df708ae36b60abe0b6d5312ba345431672d92a091db417336421cd004a549ac48c1cc900f4bfcfb3a657978a54d34e68877ebe9ebbc54d125aa9
7
+ data.tar.gz: 8a46f8d312bb5bcd46e27e15c290b31bf8c8270d7583b159d57e0aea0fc5770f194cc36f3a69495fe4a25df34f05d1872b572d4c43fab1c178945de91ba2f177
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,8 +1,10 @@
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
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
9
+ ## [0.1.3] - 2024-05-30
10
+ - fix api issues to get inverter data
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,37 +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_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
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,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,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,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_limitation(serial_number,enable,value=nil)
83
+ if Inverter::DISABLE.eql? enable
84
+ params = [0]
85
+ 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
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
+ end
151
+ end
152
+ api_endpoint :_plant_list, 'PlantListAPI.do'
153
+ api_endpoint :_plant_detail, 'PlantDetailAPI.do'
154
+ api_endpoint :_inverter_api, 'newInverterAPI.do'
155
+ api_endpoint :_plant_info, 'newTwoPlantAPI.do'
156
+
157
+ end
158
+ 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,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
- 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
+
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
+ DISABLE = -1
22
+ WATT = 1
23
+ PERCENTAGE = 0
24
+ end
25
+
26
+ 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.2'
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Growatt
4
+ VERSION = '0.1.3'
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
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: growatt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janco Tanis
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-29 00:00:00.000000000 Z
11
+ date: 2024-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: wrapi
@@ -94,7 +94,7 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
- description:
97
+ description:
98
98
  email: gems@jancology.com
99
99
  executables: []
100
100
  extensions: []
@@ -116,16 +116,13 @@ files:
116
116
  - lib/growatt/error.rb
117
117
  - lib/growatt/pagination.rb
118
118
  - lib/growatt/version.rb
119
- - test/auth_test.rb
120
- - test/client_test.rb
121
- - test/test_helper.rb
122
119
  homepage: https://rubygems.org/gems/growatt
123
120
  licenses:
124
121
  - MIT
125
122
  metadata:
126
123
  homepage_uri: https://rubygems.org/gems/growatt
127
124
  source_code_uri: https://github.com/jancotanis/growatt
128
- post_install_message:
125
+ post_install_message:
129
126
  rdoc_options: []
130
127
  require_paths:
131
128
  - lib
@@ -140,11 +137,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
137
  - !ruby/object:Gem::Version
141
138
  version: '0'
142
139
  requirements: []
143
- rubygems_version: 3.0.3.1
144
- signing_key:
140
+ rubygems_version: 3.2.3
141
+ signing_key:
145
142
  specification_version: 4
146
143
  summary: A Ruby wrapper for the Growatt APIs (readonly)
147
- test_files:
148
- - test/auth_test.rb
149
- - test/client_test.rb
150
- - test/test_helper.rb
144
+ test_files: []
data/test/auth_test.rb DELETED
@@ -1,20 +0,0 @@
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 DELETED
@@ -1,44 +0,0 @@
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
data/test/test_helper.rb DELETED
@@ -1,23 +0,0 @@
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