stekker_easee 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b5592a4edc9d574ac3d942adea5527716bb97f15090f33e6829a2e002f5fee5c
4
+ data.tar.gz: 9e26e2712745636dac113c1abc3282ea705a60f9598998e3d829dfd10222110a
5
+ SHA512:
6
+ metadata.gz: 836a495d985e421acf2508c2618de4034fb3bae460d1184eb40a09f2127875f6654bfd8a49c7986b7962623ec360addf5fd872d8b6ce54b525fd0f2683bfbe8c
7
+ data.tar.gz: '06358a19035ef71ea2d7d6a7a071a832b888963feb26a81b8802b595c4ad630afa3947cbdabbbdeab5bfc4d39827d800a7d1db4b680adcefe3caa5e023422bfb'
@@ -0,0 +1,23 @@
1
+ name: "Ruby"
2
+ on:
3
+ push:
4
+ branches: [ "main" ]
5
+ pull_request:
6
+ branches: [ "main" ]
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+
11
+ env:
12
+ RAILS_ENV: test
13
+ steps:
14
+ - name: Checkout code
15
+ uses: actions/checkout@v3
16
+
17
+ - name: Install Ruby and gems
18
+ uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4
19
+ with:
20
+ bundler-cache: true
21
+
22
+ - name: Run tests and Rubocop
23
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1 @@
1
+ inherit_from: ../../.rubocop.yml
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.1
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in easee.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 13.0"
7
+ gem "rspec", "~> 3.0"
8
+ gem "rubocop", "~> 1.7"
9
+ gem "rubocop-rails", "~> 2.17"
10
+ gem "rubocop-rspec", "~> 2.10"
11
+ gem "timecop", "~> 0.9.6"
12
+ gem "webmock", "~> 3.18"
data/Gemfile.lock ADDED
@@ -0,0 +1,126 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ stekker_easee (0.1.0)
5
+ activemodel
6
+ activesupport
7
+ faraday
8
+ faraday_middleware
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ activemodel (7.0.4.3)
14
+ activesupport (= 7.0.4.3)
15
+ activesupport (7.0.4.3)
16
+ concurrent-ruby (~> 1.0, >= 1.0.2)
17
+ i18n (>= 1.6, < 2)
18
+ minitest (>= 5.1)
19
+ tzinfo (~> 2.0)
20
+ addressable (2.8.1)
21
+ public_suffix (>= 2.0.2, < 6.0)
22
+ ast (2.4.2)
23
+ concurrent-ruby (1.2.2)
24
+ crack (0.4.5)
25
+ rexml
26
+ diff-lcs (1.5.0)
27
+ faraday (1.10.3)
28
+ faraday-em_http (~> 1.0)
29
+ faraday-em_synchrony (~> 1.0)
30
+ faraday-excon (~> 1.1)
31
+ faraday-httpclient (~> 1.0)
32
+ faraday-multipart (~> 1.0)
33
+ faraday-net_http (~> 1.0)
34
+ faraday-net_http_persistent (~> 1.0)
35
+ faraday-patron (~> 1.0)
36
+ faraday-rack (~> 1.0)
37
+ faraday-retry (~> 1.0)
38
+ ruby2_keywords (>= 0.0.4)
39
+ faraday-em_http (1.0.0)
40
+ faraday-em_synchrony (1.0.0)
41
+ faraday-excon (1.1.0)
42
+ faraday-httpclient (1.0.1)
43
+ faraday-multipart (1.0.4)
44
+ multipart-post (~> 2)
45
+ faraday-net_http (1.0.1)
46
+ faraday-net_http_persistent (1.2.0)
47
+ faraday-patron (1.0.0)
48
+ faraday-rack (1.0.0)
49
+ faraday-retry (1.0.3)
50
+ faraday_middleware (1.2.0)
51
+ faraday (~> 1.0)
52
+ hashdiff (1.0.1)
53
+ i18n (1.12.0)
54
+ concurrent-ruby (~> 1.0)
55
+ json (2.6.3)
56
+ minitest (5.18.0)
57
+ multipart-post (2.3.0)
58
+ parallel (1.22.1)
59
+ parser (3.2.1.1)
60
+ ast (~> 2.4.1)
61
+ public_suffix (5.0.1)
62
+ rack (3.0.7)
63
+ rainbow (3.1.1)
64
+ rake (13.0.6)
65
+ regexp_parser (2.7.0)
66
+ rexml (3.2.5)
67
+ rspec (3.12.0)
68
+ rspec-core (~> 3.12.0)
69
+ rspec-expectations (~> 3.12.0)
70
+ rspec-mocks (~> 3.12.0)
71
+ rspec-core (3.12.1)
72
+ rspec-support (~> 3.12.0)
73
+ rspec-expectations (3.12.2)
74
+ diff-lcs (>= 1.2.0, < 2.0)
75
+ rspec-support (~> 3.12.0)
76
+ rspec-mocks (3.12.4)
77
+ diff-lcs (>= 1.2.0, < 2.0)
78
+ rspec-support (~> 3.12.0)
79
+ rspec-support (3.12.0)
80
+ rubocop (1.48.1)
81
+ json (~> 2.3)
82
+ parallel (~> 1.10)
83
+ parser (>= 3.2.0.0)
84
+ rainbow (>= 2.2.2, < 4.0)
85
+ regexp_parser (>= 1.8, < 3.0)
86
+ rexml (>= 3.2.5, < 4.0)
87
+ rubocop-ast (>= 1.26.0, < 2.0)
88
+ ruby-progressbar (~> 1.7)
89
+ unicode-display_width (>= 2.4.0, < 3.0)
90
+ rubocop-ast (1.28.0)
91
+ parser (>= 3.2.1.0)
92
+ rubocop-capybara (2.17.1)
93
+ rubocop (~> 1.41)
94
+ rubocop-rails (2.18.0)
95
+ activesupport (>= 4.2.0)
96
+ rack (>= 1.1)
97
+ rubocop (>= 1.33.0, < 2.0)
98
+ rubocop-rspec (2.19.0)
99
+ rubocop (~> 1.33)
100
+ rubocop-capybara (~> 2.17)
101
+ ruby-progressbar (1.13.0)
102
+ ruby2_keywords (0.0.5)
103
+ timecop (0.9.6)
104
+ tzinfo (2.0.6)
105
+ concurrent-ruby (~> 1.0)
106
+ unicode-display_width (2.4.2)
107
+ webmock (3.18.1)
108
+ addressable (>= 2.8.0)
109
+ crack (>= 0.3.2)
110
+ hashdiff (>= 0.4.0, < 2.0.0)
111
+
112
+ PLATFORMS
113
+ arm64-darwin-22
114
+
115
+ DEPENDENCIES
116
+ rake (~> 13.0)
117
+ rspec (~> 3.0)
118
+ rubocop (~> 1.7)
119
+ rubocop-rails (~> 2.17)
120
+ rubocop-rspec (~> 2.10)
121
+ stekker_easee!
122
+ timecop (~> 0.9.6)
123
+ webmock (~> 3.18)
124
+
125
+ BUNDLED WITH
126
+ 2.4.8
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ require "rubocop/rake_task"
7
+
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: [:spec, :rubocop]
data/bin/console ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ require "bundler/setup"
3
+ require "easee"
4
+
5
+ # You can add fixtures and/or initialization code here to make experimenting
6
+ # with your gem easier. You can also use a different console, if you like.
7
+
8
+ # (If you use this, don't forget to add pry to your Gemfile!)
9
+ # require "pry"
10
+ # Pry.start
11
+
12
+ require "irb"
13
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/easee.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ require_relative "lib/easee/version"
2
+ ruby_version = File.read(".ruby-version").strip
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "stekker_easee"
6
+ spec.version = Easee::VERSION
7
+ spec.authors = ["Team Stekker"]
8
+ spec.email = ["support@stekker.com"]
9
+
10
+ spec.summary = "Connect to your Easee charger"
11
+ spec.description = "Easee connector"
12
+ spec.homepage = "https://stekker.com"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= #{ruby_version}")
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/stekker/easee"
17
+ spec.metadata["changelog_uri"] = "https://github.com/stekker/easee"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_runtime_dependency "activemodel"
29
+ spec.add_runtime_dependency "activesupport"
30
+ spec.add_runtime_dependency "faraday"
31
+ spec.add_runtime_dependency "faraday_middleware"
32
+
33
+ spec.metadata["rubygems_mfa_required"] = "true"
34
+ end
@@ -0,0 +1,158 @@
1
+ require "active_support/notifications"
2
+ require "active_support/cache"
3
+
4
+ require_relative "null_encryptor"
5
+
6
+ module Easee
7
+ class Client
8
+ BASE_URL = "https://api.easee.cloud".freeze
9
+ TOKENS_CACHE_KEY = "easee.auth.tokens".freeze
10
+
11
+ def initialize(
12
+ user_name:,
13
+ password:,
14
+ token_cache: ActiveSupport::Cache::MemoryStore.new,
15
+ encryptor: NullEncryptor.new
16
+ )
17
+ @user_name = user_name
18
+ @password = password
19
+ @token_cache = token_cache
20
+ @encryptor = encryptor
21
+ end
22
+
23
+ # https://developer.easee.cloud/reference/post_api-chargers-id-unpair
24
+ def unpair(charger_id:, pin_code:)
25
+ post("/api/chargers/#{charger_id}/unpair", query: { pinCode: pin_code })
26
+ end
27
+
28
+ # https://developer.easee.cloud/reference/post_api-chargers-id-pair
29
+ def pair(charger_id:, pin_code:)
30
+ post("/api/chargers/#{charger_id}/pair", query: { pinCode: pin_code })
31
+ end
32
+
33
+ # https://developer.easee.cloud/reference/get_api-chargers-id-state
34
+ def state(charger_id)
35
+ get("/api/chargers/#{charger_id}/state")
36
+ .then { |response| State.new(response.body) }
37
+ end
38
+
39
+ # https://developer.easee.cloud/reference/post_api-chargers-id-commands-pause-charging
40
+ def pause_charging(charger_id)
41
+ post("/api/chargers/#{charger_id}/commands/pause_charging")
42
+ end
43
+
44
+ # https://developer.easee.cloud/reference/post_api-chargers-id-commands-resume-charging
45
+ def resume_charging(charger_id)
46
+ post("/api/chargers/#{charger_id}/commands/resume_charging")
47
+ end
48
+
49
+ def configuration(charger_id)
50
+ get("/api/chargers/#{charger_id}/config")
51
+ .then { |response| Configuration.new(response.body) }
52
+ end
53
+
54
+ def site(charger_id)
55
+ get("/api/chargers/#{charger_id}/site")
56
+ .then { |response| Site.new(response.body) }
57
+ end
58
+
59
+ def inspect
60
+ <<~INSPECT
61
+ #<#{self.class.name} @user_name="[FILTERED]", @password="[FILTERED]", @token_cache=#{@token_cache.inspect}, @encryptor=#{@encryptor.inspect}>
62
+ INSPECT
63
+ end
64
+
65
+ private
66
+
67
+ attr_reader :user_name, :password
68
+
69
+ def connection
70
+ Faraday.new(url: BASE_URL) do |conn|
71
+ conn.request :json
72
+ conn.response :raise_error
73
+ end
74
+ end
75
+
76
+ def authenticated_connection
77
+ connection.tap do |conn|
78
+ conn.request :authorization, "Bearer", access_token
79
+ conn.response :json, content_type: /\bjson$/
80
+ end
81
+ end
82
+
83
+ def get(endpoint, query = {})
84
+ with_error_handling do
85
+ authenticated_connection.get("#{BASE_URL}#{endpoint}", query)
86
+ end
87
+ end
88
+
89
+ def post(endpoint, body: nil, query: nil)
90
+ with_error_handling do
91
+ authenticated_connection.post("#{BASE_URL}#{endpoint}", body) do |req|
92
+ req.params = query unless query.nil?
93
+ end
94
+ end
95
+ end
96
+
97
+ def with_error_handling
98
+ token_refreshed ||= false
99
+
100
+ yield
101
+ rescue Faraday::UnauthorizedError => e
102
+ if token_refreshed
103
+ raise Errors::RequestFailed.new("Request returned status #{e.response_status}", e.response)
104
+ else
105
+ refresh_access_token!
106
+ token_refreshed = true
107
+
108
+ retry
109
+ end
110
+ rescue Faraday::Error => e
111
+ raise Errors::RequestFailed.new("Request returned status #{e.response_status}", e.response)
112
+ end
113
+
114
+ def access_token
115
+ encrypted_tokens = @token_cache.fetch(TOKENS_CACHE_KEY) do
116
+ @encryptor.encrypt(request_access_token, cipher_options: { deterministic: true })
117
+ end
118
+
119
+ plain_text_tokens = @encryptor.decrypt(encrypted_tokens)
120
+
121
+ JSON.parse(plain_text_tokens).fetch("accessToken")
122
+ end
123
+
124
+ def refresh_access_token!
125
+ @token_cache.write(
126
+ TOKENS_CACHE_KEY,
127
+ @encryptor.encrypt(refresh_access_token, cipher_options: { deterministic: true }),
128
+ expires_in: 1.day,
129
+ )
130
+ rescue Faraday::Error => e
131
+ raise Errors::RequestFailed.new("Request returned status #{e.response_status}", e.response)
132
+ end
133
+
134
+ # https://developer.easee.cloud/reference/post_api-accounts-login
135
+ def request_access_token
136
+ connection
137
+ .post("/api/accounts/login", userName: user_name, password:)
138
+ .then(&:body)
139
+ end
140
+
141
+ # https://developer.easee.cloud/reference/post_api-accounts-refresh-token
142
+ def refresh_access_token
143
+ tokens = JSON.parse(
144
+ @encryptor.decrypt(
145
+ @token_cache.fetch(TOKENS_CACHE_KEY),
146
+ ),
147
+ )
148
+
149
+ connection
150
+ .post(
151
+ "/api/accounts/refresh_token",
152
+ accessToken: tokens.fetch("accessToken"),
153
+ refreshToken: tokens.fetch("refreshToken"),
154
+ )
155
+ .then(&:body)
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,10 @@
1
+ module Easee
2
+ class Configuration
3
+ def initialize(data)
4
+ @data = data.symbolize_keys
5
+ end
6
+
7
+ def phase_mode = @data.fetch(:phaseMode)
8
+ def max_charger_current = @data.fetch(:maxChargerCurrent)
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ module Easee
2
+ module Errors
3
+ class Base < ::StandardError; end
4
+
5
+ class RequestFailed < Base
6
+ attr_reader :response
7
+
8
+ def initialize(message, response = nil)
9
+ @response = response
10
+ super(message)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ module Easee
2
+ class MeterReading
3
+ attr_reader :reading_kwh, :timestamp
4
+
5
+ def initialize(reading_kwh:, timestamp:)
6
+ @reading_kwh = reading_kwh
7
+ @timestamp = timestamp
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ module Easee
2
+ # rubocop:disable Lint/UnusedMethodArgument
3
+ class NullEncryptor
4
+ def encrypt(clear_text, key_provider: nil, cipher_options: {})
5
+ clear_text
6
+ end
7
+
8
+ def decrypt(encrypted_text, key_provider: nil, cipher_options: {})
9
+ encrypted_text
10
+ end
11
+
12
+ def encrypted?(text)
13
+ false
14
+ end
15
+ end
16
+ # rubocop:enable Lint/UnusedMethodArgument
17
+ end
data/lib/easee/site.rb ADDED
@@ -0,0 +1,22 @@
1
+ module Easee
2
+ class Site
3
+ def initialize(data)
4
+ @data = data.deep_symbolize_keys
5
+ end
6
+
7
+ def name = @data.fetch(:name)
8
+ def street = address.fetch(:street)
9
+ def building_number = address.fetch(:buildingNumber)
10
+ def zip = address.fetch(:zip)
11
+ def area = address.fetch(:area)
12
+ def country_id = address.fetch(:country).fetch(:id)
13
+ def latitude = address.fetch(:latitude)
14
+ def longitude = address.fetch(:longitude)
15
+
16
+ private
17
+
18
+ def address
19
+ @address ||= @data.fetch(:address)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ module Easee
2
+ class State
3
+ CHARGER_OP_MODES = {
4
+ 0 => :offline,
5
+ 1 => :disconnected,
6
+ 2 => :awaiting_start,
7
+ 3 => :charging,
8
+ 4 => :completed,
9
+ 5 => :error,
10
+ 6 => :ready_to_charge,
11
+ }.freeze
12
+
13
+ def initialize(data)
14
+ @data = data.symbolize_keys
15
+ end
16
+
17
+ def charging? = charger_op_mode == :charging
18
+ def disconnected? = charger_op_mode == :disconnected
19
+ def online? = @data.fetch(:isOnline)
20
+
21
+ def meter_reading
22
+ MeterReading.new(
23
+ reading_kwh: @data.fetch(:lifetimeEnergy),
24
+ timestamp: Time.current,
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ def charger_op_mode = CHARGER_OP_MODES.fetch(@data.fetch(:chargerOpMode))
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Easee
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -0,0 +1,15 @@
1
+ require "faraday"
2
+ require "active_support/core_ext/time/calculations"
3
+ require "active_support/isolated_execution_state"
4
+ require "active_support/core_ext/hash"
5
+
6
+ require_relative "easee/version"
7
+ require_relative "easee/client"
8
+ require_relative "easee/configuration"
9
+ require_relative "easee/errors"
10
+ require_relative "easee/meter_reading"
11
+ require_relative "easee/site"
12
+ require_relative "easee/state"
13
+
14
+ module Easee
15
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stekker_easee
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Team Stekker
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-04-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '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'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
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'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
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: faraday_middleware
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Easee connector
70
+ email:
71
+ - support@stekker.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".github/workflows/ruby.yml"
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".rubocop.yml"
80
+ - ".ruby-version"
81
+ - Gemfile
82
+ - Gemfile.lock
83
+ - Rakefile
84
+ - bin/console
85
+ - bin/setup
86
+ - easee.gemspec
87
+ - lib/easee/client.rb
88
+ - lib/easee/configuration.rb
89
+ - lib/easee/errors.rb
90
+ - lib/easee/meter_reading.rb
91
+ - lib/easee/null_encryptor.rb
92
+ - lib/easee/site.rb
93
+ - lib/easee/state.rb
94
+ - lib/easee/version.rb
95
+ - lib/stekker_easee.rb
96
+ homepage: https://stekker.com
97
+ licenses: []
98
+ metadata:
99
+ homepage_uri: https://stekker.com
100
+ source_code_uri: https://github.com/stekker/easee
101
+ changelog_uri: https://github.com/stekker/easee
102
+ rubygems_mfa_required: 'true'
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 3.2.1
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubygems_version: 3.4.6
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Connect to your Easee charger
122
+ test_files: []