huawei_solar 0.0.1

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: 1e86d39ee331eb59ce2496335ab7c766dc19b3f2d37825c99fd798f30ea2b868
4
+ data.tar.gz: 3169ec089f148789c1ded10c3e5f3afb5a862c40821cd88039b2896ea5dbe102
5
+ SHA512:
6
+ metadata.gz: 76b42e417c4436caa6da377bc6912413f16beeb08a0dce86ec5db331763f369c437809aa7160fcdcd34e34b4f4e3f322bcd24c84910aff46cc20307e3b106899
7
+ data.tar.gz: 0f6e8b7fabe17a1851265337ea28ae15ed07facdd0f8763dadb109767d59a7a78ddcaf8eb7d8d24a0dbbdafc31e510f1576102db62ad3d40745f5dc1c73aca63
data/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ # This file is for unifying the coding style for different editors and IDEs
2
+ # editorconfig.org
3
+
4
+ root = true
5
+
6
+ [*]
7
+ charset = utf-8
8
+ indent_size = 2
9
+ indent_style = space
10
+ insert_final_newline = true
11
+ max_line_length = 120
12
+ trim_trailing_whitespace = true
data/.envrc ADDED
@@ -0,0 +1 @@
1
+ PATH_add bin
data/.pryrc ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ Pry.config.prompt_name = "[huawei_solar]"
data/.rubocop.yml ADDED
@@ -0,0 +1,54 @@
1
+ require:
2
+ - rubocop-minitest
3
+ - rubocop-rake
4
+
5
+ inherit_mode:
6
+ merge:
7
+ - Exclude
8
+ - Include
9
+ - Prefixes
10
+
11
+ AllCops:
12
+ DisplayCopNames: true
13
+ Exclude:
14
+ - "**/bin/**/*"
15
+ NewCops: enable
16
+ TargetRubyVersion: 3.1
17
+
18
+ Layout/SpaceAroundEqualsInParameterDefault:
19
+ EnforcedStyle: no_space
20
+
21
+ Metrics/ClassLength:
22
+ CountAsOne:
23
+ - array
24
+ - hash
25
+ - heredoc
26
+
27
+ Metrics/MethodLength:
28
+ CountAsOne:
29
+ - array
30
+ - hash
31
+ - heredoc
32
+
33
+ Metrics/ModuleLength:
34
+ CountAsOne:
35
+ - array
36
+ - hash
37
+ - heredoc
38
+
39
+ Style/ClassAndModuleChildren:
40
+ EnforcedStyle: compact
41
+
42
+ Style/Documentation:
43
+ Enabled: false
44
+
45
+ Style/StringLiterals:
46
+ Enabled: true
47
+ EnforcedStyle: double_quotes
48
+
49
+ Style/StringLiteralsInInterpolation:
50
+ Enabled: true
51
+ EnforcedStyle: double_quotes
52
+
53
+ Layout/LineLength:
54
+ Max: 120
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.1.2
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.0.1] - 2022-12-07
4
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "minitest", "~> 5.0"
8
+ gem "pry", "~> 0.14"
9
+ gem "rake", "~> 13.0"
10
+ gem "rubocop", "~> 1.21"
11
+ gem "rubocop-minitest", "~> 0.19"
12
+ gem "rubocop-rake", "~> 0.6"
data/Gemfile.lock ADDED
@@ -0,0 +1,57 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ huawei_solar (0.0.1)
5
+ rmodbus (~> 1.3.3)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ coderay (1.1.3)
12
+ method_source (1.0.0)
13
+ minitest (5.15.0)
14
+ parallel (1.22.1)
15
+ parser (3.1.1.0)
16
+ ast (~> 2.4.1)
17
+ pry (0.14.1)
18
+ coderay (~> 1.1)
19
+ method_source (~> 1.0)
20
+ rainbow (3.1.1)
21
+ rake (13.0.6)
22
+ regexp_parser (2.2.1)
23
+ rexml (3.2.5)
24
+ rmodbus (1.3.3)
25
+ rubocop (1.26.1)
26
+ parallel (~> 1.10)
27
+ parser (>= 3.1.0.0)
28
+ rainbow (>= 2.2.2, < 4.0)
29
+ regexp_parser (>= 1.8, < 3.0)
30
+ rexml
31
+ rubocop-ast (>= 1.16.0, < 2.0)
32
+ ruby-progressbar (~> 1.7)
33
+ unicode-display_width (>= 1.4.0, < 3.0)
34
+ rubocop-ast (1.16.0)
35
+ parser (>= 3.1.1.0)
36
+ rubocop-minitest (0.19.0)
37
+ rubocop (>= 0.90, < 2.0)
38
+ rubocop-rake (0.6.0)
39
+ rubocop (~> 1.0)
40
+ ruby-progressbar (1.11.0)
41
+ unicode-display_width (2.1.0)
42
+
43
+ PLATFORMS
44
+ arm64-darwin-21
45
+ x86_64-linux
46
+
47
+ DEPENDENCIES
48
+ huawei_solar!
49
+ minitest (~> 5.0)
50
+ pry (~> 0.14)
51
+ rake (~> 13.0)
52
+ rubocop (~> 1.21)
53
+ rubocop-minitest (~> 0.19)
54
+ rubocop-rake (~> 0.6)
55
+
56
+ BUNDLED WITH
57
+ 2.3.7
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Javier Aranda
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # Huawei Solar
2
+
3
+ ![CI](https://github.com/javierav/huawei_solar/workflows/CI/badge.svg)
4
+
5
+ A Ruby library for connect to Huawei Solar inverters using Modbus TCP.
6
+
7
+ ## Example
8
+
9
+ ```ruby
10
+ require "huawei_solar"
11
+
12
+ hs = HuaweiSolar.new("192.168.0.110", 502)
13
+
14
+ hs.read(:inverter_model) # => "SUN2000-3.68KTL-L1"
15
+ hs.read(:inverter_input_power) # => 234.0
16
+ hs.read(:inverter_input_power, with_unit: true) # => "234.0 W"
17
+
18
+ hs.read(%i[inverter_output_power inverter_daily_energy meter_grid_power])
19
+ # => [225.0, 5.82, -339.0]
20
+
21
+ hs.read(%i[inverter_output_power inverter_daily_energy meter_grid_power], with_unit: true)
22
+ # => ["225.0 W", "5.82 kWh", "-339.0 W"]
23
+ ```
24
+
25
+ ## License
26
+
27
+ Copyright © 2022 Javier Aranda. Released under the terms of the [MIT license](LICENSE).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
@@ -0,0 +1,167 @@
1
+ #
2
+ ## INVERTER
3
+ #
4
+
5
+ # Modelo del inversor
6
+ inverter_model:
7
+ type: string
8
+ address: 30000
9
+ quantity: 15
10
+
11
+ # Número de serie del inversor
12
+ inverter_serial_number:
13
+ type: string
14
+ address: 30015
15
+ quantity: 10
16
+
17
+ # Número de strings del inversor
18
+ inverter_pv_strings:
19
+ type: u16
20
+ address: 30071
21
+ quantity: 1
22
+
23
+ # Voltaje de entrada CC en el String 1 del inversor
24
+ inverter_pv1_voltage:
25
+ type: i16
26
+ address: 32016
27
+ quantity: 1
28
+ unit: V
29
+ gain: 10
30
+
31
+ # Intensidad de entrada CC en el String 1 del inversor
32
+ inverter_pv1_current:
33
+ type: i16
34
+ address: 32017
35
+ quantity: 1
36
+ unit: A
37
+ gain: 100
38
+
39
+ # Voltaje de entrada CC en el String 2 del inversor
40
+ inverter_pv2_voltage:
41
+ type: i16
42
+ address: 32018
43
+ quantity: 1
44
+ unit: V
45
+ gain: 10
46
+
47
+ # Intensidad de entrada CC en el String 2 del inversor
48
+ inverter_pv2_current:
49
+ type: i16
50
+ address: 32019
51
+ quantity: 1
52
+ unit: A
53
+ gain: 100
54
+
55
+ # Potencia de entrada CC en el inversor
56
+ inverter_input_power:
57
+ type: i32
58
+ address: 32064
59
+ quantity: 2
60
+ unit: W
61
+ gain: 1
62
+
63
+ # Voltaje de salida AC del inversor
64
+ inverter_output_voltage:
65
+ type: u16
66
+ address: 32066
67
+ quantity: 1
68
+ unit: V
69
+ gain: 10
70
+
71
+ # Intensidad de salida AC del inversor
72
+ inverter_output_current:
73
+ type: i32
74
+ address: 32072
75
+ quantity: 2
76
+ unit: A
77
+ gain: 1000
78
+
79
+ # Potencia de salida AC del inversor
80
+ inverter_output_power:
81
+ type: i32
82
+ address: 32080
83
+ quantity: 2
84
+ unit: W
85
+ gain: 1
86
+
87
+ # Factor de potencia del inversor
88
+ inverter_power_factor:
89
+ type: i16
90
+ address: 32084
91
+ quantity: 1
92
+ gain: 1000
93
+
94
+ # Eficiencia del inversor
95
+ inverter_efficiency:
96
+ type: u16
97
+ address: 32086
98
+ quantity: 1
99
+ unit: "%"
100
+ gain: 100
101
+
102
+ # Temperatura del inversor
103
+ inverter_temperature:
104
+ type: i16
105
+ address: 32087
106
+ quantity: 1
107
+ unit: "ºC"
108
+ gain: 10
109
+
110
+ # Producción diaria de energía
111
+ inverter_daily_energy:
112
+ type: u32
113
+ address: 32114
114
+ quantity: 2
115
+ unit: kWh
116
+ gain: 100
117
+
118
+ # Frecuencia de red del inversor
119
+ inverter_grid_frequency:
120
+ type: u16
121
+ address: 32085
122
+ quantity: 1
123
+ unit: Hz
124
+ gain: 100
125
+
126
+ #
127
+ ## METER
128
+ #
129
+
130
+ # Voltage de red
131
+ meter_grid_voltage:
132
+ type: i32
133
+ address: 37101
134
+ quantity: 2
135
+ unit: V
136
+ gain: 10
137
+
138
+ # Intensidad de red. > 0 vertiendo a red, < 0 obteniendo de red
139
+ meter_grid_current:
140
+ type: i32
141
+ address: 37107
142
+ quantity: 2
143
+ unit: A
144
+ gain: 100
145
+
146
+ # Potencia de red. > 0 vertiendo a red, < 0 obteniendo de red
147
+ meter_grid_power:
148
+ type: i32
149
+ address: 37113
150
+ quantity: 2
151
+ unit: W
152
+ gain: 1
153
+
154
+ # Factor de potencia de red.
155
+ meter_power_factor:
156
+ type: i16
157
+ address: 37117
158
+ quantity: 1
159
+ gain: 1000
160
+
161
+ # Frecuencia de red.
162
+ meter_grid_frequency:
163
+ type: i16
164
+ address: 37118
165
+ quantity: 1
166
+ unit: Hz
167
+ gain: 100
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://github.com/rmodbus/rmodbus/issues/57
4
+ class Array
5
+ def to_32u
6
+ raise "Array requires an even number of elements to pack to 32bits: was #{size}" unless size.even?
7
+
8
+ each_slice(2).map { |(msb, lsb)| [msb, lsb].pack("n*").unpack1("N") }
9
+ end
10
+
11
+ # Given an array of 16bit Fixnum, we turn it into 32bit unsigned int in little-endian order, halving the size
12
+ def to_32u_le
13
+ raise "Array requires an even number of elements to pack to 32bits: was #{size}" unless size.even?
14
+
15
+ each_slice(2).map { |(lsb, msb)| [msb, lsb].pack("n*").unpack1("N") }
16
+ end
17
+
18
+ def to_signed32(number)
19
+ number >= 2_147_483_648 ? number - 4_294_967_296 : number
20
+ end
21
+
22
+ # Given an array of 16bit Fixnum, we turn it into 32bit signed int in big-endian order, halving the size
23
+ def to_32i
24
+ raise "Array requires an even number of elements to pack to 32bits: was #{size}" unless size.even?
25
+
26
+ to_32u.map { |n| to_signed32(n) }
27
+ end
28
+
29
+ # Given an array of 16bit Fixnum, we turn it into 32bit signed int in little-endian order, halving the size
30
+ def to_32i_le
31
+ raise "Array requires an even number of elements to pack to 32bits: was #{size}" unless size.even?
32
+
33
+ to_32u_le.map { |n| to_signed32(n) }
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HuaweiSolar::I16
4
+ def self.call(value)
5
+ value
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HuaweiSolar::I32
4
+ def self.call(value)
5
+ value.to_32i.first
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HuaweiSolar::String
4
+ def self.call(value)
5
+ value.map(&:to_word).join.strip
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HuaweiSolar::U16
4
+ def self.call(value)
5
+ value
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HuaweiSolar::U32
4
+ def self.call(value)
5
+ value.to_32u.first
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HuaweiSolar
4
+ module VERSION
5
+ MAJOR = 0
6
+ MINOR = 0
7
+ TINY = 1
8
+ PRE = nil
9
+
10
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
11
+ end
12
+
13
+ def self.version
14
+ VERSION::STRING
15
+ end
16
+
17
+ def self.gem_version
18
+ Gem::Version.new VERSION::STRING
19
+ end
20
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "huawei_solar/string"
4
+ require_relative "huawei_solar/i16"
5
+ require_relative "huawei_solar/i32"
6
+ require_relative "huawei_solar/u16"
7
+ require_relative "huawei_solar/u32"
8
+ require_relative "huawei_solar/version"
9
+
10
+ require "rmodbus"
11
+ require_relative "huawei_solar/array_ext"
12
+ require "yaml"
13
+
14
+ class HuaweiSolar
15
+ def initialize(host, port=502, **opts)
16
+ @client = ModBus::TCPClient.connect(host, port, opts)
17
+ @client.debug = !ENV["DEBUG"].nil?
18
+ @slave = @client.with_slave(1)
19
+ @registers = YAML.load_file(File.expand_path("../data/registers.yml", __dir__))
20
+ end
21
+
22
+ def read(name, with_unit: false)
23
+ name.is_a?(Array) ? read_multiple(name, with_unit:) : read_single(name, with_unit:)
24
+ end
25
+
26
+ private
27
+
28
+ def read_single(name, with_unit: false) # rubocop:disable Metrics/AbcSize
29
+ register = register_by_name(name)
30
+ type = find_type(register["type"])
31
+ gain = register["gain"]
32
+ unit = register["unit"]
33
+
34
+ value = type.call(
35
+ @slave.read_holding_registers(register["address"], register["quantity"])
36
+ )
37
+
38
+ value = value.is_a?(Array) && value.size == 1 ? value.first : value
39
+ value = gain ? value / (gain * 1.0) : value
40
+
41
+ with_unit && !unit.nil? ? "#{value} #{unit}" : value
42
+ end
43
+
44
+ def read_multiple(names, with_unit: false)
45
+ names.map { |name| read_single(name, with_unit:) }
46
+ end
47
+
48
+ def register_by_name(name)
49
+ @registers[name.to_s]
50
+ end
51
+
52
+ def find_type(type)
53
+ Object.const_get("HuaweiSolar::#{type.capitalize}")
54
+ end
55
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: huawei_solar
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Javier Aranda
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-12-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rmodbus
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.3.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.3.3
27
+ description:
28
+ email:
29
+ - javier.aranda.varo@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files:
33
+ - LICENSE
34
+ - README.md
35
+ files:
36
+ - ".editorconfig"
37
+ - ".envrc"
38
+ - ".pryrc"
39
+ - ".rubocop.yml"
40
+ - ".tool-versions"
41
+ - CHANGELOG.md
42
+ - Gemfile
43
+ - Gemfile.lock
44
+ - LICENSE
45
+ - README.md
46
+ - Rakefile
47
+ - data/registers.yml
48
+ - lib/huawei_solar.rb
49
+ - lib/huawei_solar/array_ext.rb
50
+ - lib/huawei_solar/i16.rb
51
+ - lib/huawei_solar/i32.rb
52
+ - lib/huawei_solar/string.rb
53
+ - lib/huawei_solar/u16.rb
54
+ - lib/huawei_solar/u32.rb
55
+ - lib/huawei_solar/version.rb
56
+ homepage: https://github.com/javierav/huawei_solar
57
+ licenses:
58
+ - MIT
59
+ metadata:
60
+ homepage_uri: https://github.com/javierav/huawei_solar
61
+ source_code_uri: https://github.com/javierav/huawei_solar/tree/v0.0.1
62
+ changelog_uri: https://github.com/javierav/huawei_solar/blob/v0.0.1/CHANGELOG.md
63
+ rubygems_mfa_required: 'true'
64
+ post_install_message:
65
+ rdoc_options:
66
+ - "--charset=UTF-8"
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 3.1.0
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.3.7
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: A library for connect to Huawei Solar inverters using Modbus TCP
84
+ test_files: []