growatt 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d8f7a2c9e157fa1d945fdb7f29b9196ebe9104ebb84bcd348382e6b63c462aef
4
+ data.tar.gz: c748548639e53602a547b20ae2ba87edd70b8e9ce56b8a14db92f077c43b1643
5
+ SHA512:
6
+ metadata.gz: b732fd4efb07fd62e4e9a3a080b32da09bc45aff31804ee9b39f7dc74194e24eea975c58bcbc49e9ed7619533126cca6ecfe21a51cf4297caaaedf97d71e8dfa
7
+ data.tar.gz: 625b1e596b341b080e41d90d70aed14c42e1178171f8544f89a7f7fdacb4dfd10f8e18664c47507f1c98aa1cf720fae31eb7728f049a707c9ba06468a582f87a
data/.env.template ADDED
@@ -0,0 +1,4 @@
1
+ # demo token used
2
+ GROWATT_USERNAME=username
3
+ GROWATT_PASSWORD=password
4
+
data/.gitignore ADDED
@@ -0,0 +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
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-05-22
4
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +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
data/README.md ADDED
@@ -0,0 +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"
data/Rakefile ADDED
@@ -0,0 +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'
data/growatt.gemspec ADDED
@@ -0,0 +1,36 @@
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
@@ -0,0 +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
@@ -0,0 +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
@@ -0,0 +1,138 @@
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
+ r = _inverter_api({
50
+ 'op': 'getMaxSetData',
51
+ 'serialNum': inverter_id
52
+ })
53
+ puts ">>>> #{r.to_json}"
54
+ r.obj.maxSetBean
55
+ end
56
+
57
+ def update_inverter_setting(serial_number,command,setting_type,parameters)
58
+ command_parameters = {
59
+ 'op': command,
60
+ 'serialNum': serial_number,
61
+ 'type': setting_type
62
+ }
63
+ # repeated values to hash { param1: value1 }
64
+
65
+ parameters = parameters.map.with_index { |value, index| ["param#{index + 1}", value] }.to_h if parameters.is_a? Array
66
+ self.format = 'x-www-form-urlencoded'
67
+ data = JSON.parse(post('newTcpsetAPI.do',command_parameters.merge(parameters)).body)
68
+ self.format = :json
69
+ data['success']
70
+ end
71
+
72
+ # turn invertor on of off
73
+ def turn_inverter(serial_number,on=true)
74
+ onoff = (on ? Inverter::ON : Inverter::OFF )
75
+ update_inverter_setting(serial_number,'maxSetApi',nil,{'paramId':'max_cmd_on_off','param1':onoff})
76
+ end
77
+
78
+ # check if invertor is turned on
79
+ def inverter_on?(serial_number)
80
+ status = inverter_control_data(serial_number)
81
+ Inverter::ON.eql? status.max_cmd_on_off
82
+ end
83
+
84
+ # utility function to get date accordign timespan month/day
85
+ def timespan_date(timespan=Timespan::DAY,date=Time.now)
86
+ if Timespan::MONTH.eql? timespan
87
+ date.strftime("%Y-%m")
88
+ else
89
+ date.strftime("%Y-%m-%d")
90
+ end
91
+ end
92
+
93
+ #
94
+ # functions below are copied from python example code but not sure if these work with MOD9000 inverters
95
+ #
96
+ def inverter_data(inverter_id,type=Timespan::DAY,date=Time.now)
97
+ _inverter_api({
98
+ 'op': 'getInverterData',
99
+ 'inverterId': inverter_id,
100
+ 'type': type,
101
+ 'date': timespan_date(type,date)
102
+ })
103
+ end
104
+ def inverter_detail(inverter_id)
105
+ _inverter_api({
106
+ 'op': 'getInverterDetailData',
107
+ 'inverterId': inverter_id
108
+ })
109
+ end
110
+ def inverter_detail_two(inverter_id)
111
+ _inverter_api({
112
+ 'op': 'getInverterDetailData_two',
113
+ 'inverterId': inverter_id
114
+ })
115
+ end
116
+ def update_mix_inverter_setting(serial_number, setting_type, parameters)
117
+ update_inverter_setting(serial_number,'mixSetApiNew',setting_type,parameters)
118
+ end
119
+ def update_ac_inverter_setting(serial_number, setting_type, parameters)
120
+ update_inverter_setting(serial_number,'spaSetApi',setting_type,parameters)
121
+ end
122
+
123
+
124
+ private
125
+ def self.api_endpoint(method,path)
126
+ # all records
127
+ self.send(:define_method, method) do |params = {}|
128
+ # return data result
129
+ get(path,params)
130
+ end
131
+ end
132
+ api_endpoint :_plant_list, 'PlantListAPI.do'
133
+ api_endpoint :_plant_detail, 'PlantDetailAPI.do'
134
+ api_endpoint :_inverter_api, 'newInverterAPI.do'
135
+ api_endpoint :_plant_info, 'newTwoPlantAPI.do'
136
+
137
+ end
138
+ end
@@ -0,0 +1,28 @@
1
+ require 'faraday'
2
+ require 'faraday/follow_redirects'
3
+ require 'faraday-cookie_jar'
4
+
5
+ module Growatt
6
+ # Create connection and use cookies for authentication tokens
7
+ module Connection
8
+ def connection
9
+ raise ConfigurationError, "Option for endpoint is not defined" unless endpoint
10
+
11
+ options = setup_options
12
+ @connection ||= Faraday::Connection.new(options) do |connection|
13
+ # connection.use Faraday::FollowRedirects::Middleware, limit: 10
14
+ connection.use :cookie_jar
15
+
16
+ connection.use Faraday::Response::RaiseError
17
+ connection.adapter Faraday.default_adapter
18
+ setup_authorization(connection)
19
+ setup_headers(connection)
20
+ connection.response :json, content_type: /\bjson$/
21
+ connection.use Faraday::Request::UrlEncoded
22
+
23
+ setup_logger_filtering(connection, logger) if logger
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +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
@@ -0,0 +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
@@ -0,0 +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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Growatt
4
+ VERSION = '0.1.0'
5
+ end
data/lib/growatt.rb ADDED
@@ -0,0 +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
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 result, "inverter on should be success"
42
+ assert is_on, "Inverter should be on"
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 ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: growatt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Janco Tanis
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-05-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: wrapi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: dotenv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email: gems@jancology.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - ".env.template"
90
+ - ".gitignore"
91
+ - CHANGELOG.md
92
+ - Gemfile
93
+ - README.md
94
+ - Rakefile
95
+ - growatt.gemspec
96
+ - lib/growatt.rb
97
+ - lib/growatt/api.rb
98
+ - lib/growatt/authorization.rb
99
+ - lib/growatt/client.rb
100
+ - lib/growatt/connection.rb
101
+ - lib/growatt/const.rb
102
+ - lib/growatt/error.rb
103
+ - lib/growatt/pagination.rb
104
+ - lib/growatt/version.rb
105
+ - test/auth_test.rb
106
+ - test/client_test.rb
107
+ - test/test_helper.rb
108
+ homepage: https://rubygems.org/gems/growatt
109
+ licenses:
110
+ - MIT
111
+ metadata:
112
+ homepage_uri: https://rubygems.org/gems/growatt
113
+ source_code_uri: https://github.com/jancotanis/growatt
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 2.4.0
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubygems_version: 3.0.3.1
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: A Ruby wrapper for the Growatt APIs (readonly)
133
+ test_files:
134
+ - test/auth_test.rb
135
+ - test/client_test.rb
136
+ - test/test_helper.rb