jfy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9cabb2fb58366f8b9a0c0ddc9be7222a2f751078
4
+ data.tar.gz: 89b61add016154cd474c9d6118c7d716a0d9a0b5
5
+ SHA512:
6
+ metadata.gz: eb01ce52d7186a9f3659b48e0eee847ed9bb1210e77e840a6ef4768e8eba1b32aa7cbdca76ef55ed37f1b2249e549af55cc3a1bdae69acbaec119b5f26547856
7
+ data.tar.gz: e6f2c985e116adfefc504f8845d42014576f430d9c2697f012930451e9037eb2ee70b55c267e7216dde3498fee912cbcac92e22dafd227ad8dffc72b5a9d3950
@@ -0,0 +1,6 @@
1
+ /.bundle/
2
+ /Gemfile.lock
3
+ /coverage/
4
+ /pkg/
5
+ /spec/reports/
6
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,25 @@
1
+ require: rubocop-rspec
2
+
3
+ HashSyntax:
4
+ EnforcedStyle: hash_rockets
5
+
6
+ Style/Documentation:
7
+ Enabled: false
8
+
9
+ Style/Lambda:
10
+ Enabled: false
11
+
12
+ Style/TrailingComma:
13
+ EnforcedStyleForMultiline: comma
14
+
15
+ Metrics/LineLength:
16
+ Max: 120
17
+
18
+ Metrics/ClassLength:
19
+ Max: 200
20
+
21
+ Style/ModuleFunction:
22
+ Enabled: false
23
+
24
+ AlignHash:
25
+ EnforcedHashRocketStyle: table
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behaviour by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behaviour may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 John Ferlito
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.
@@ -0,0 +1,46 @@
1
+ # Jfy
2
+
3
+ [![Circle CI](https://circleci.com/gh/johnf/jfy.svg?style=svg)](https://circleci.com/gh/johnf/jfy)
4
+ [![Coverage Status](https://coveralls.io/repos/johnf/jfy/badge.svg?branch=master&service=github)](https://coveralls.io/github/johnf/jfy?branch=master)
5
+
6
+ Ruby library for communicating with JFY Solar Inverters over a serial port
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'jfy'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install jfy
23
+
24
+ ## Usage
25
+
26
+ Do get a one off run output from your inverter run
27
+
28
+ $ jfy
29
+
30
+ It also has some sample code on how to use the API
31
+
32
+ ## Development
33
+
34
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
35
+
36
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
37
+
38
+ ## Contributing
39
+
40
+ Bug reports and pull requests are welcome on GitHub at https://github.com/johnf/jfy. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
41
+
42
+
43
+ ## License
44
+
45
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
46
+
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ RuboCop::RakeTask.new
7
+
8
+ task :default => :spec
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'jfy'
5
+
6
+ require 'pry'
7
+ Pry.start
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ set -euo pipefail
4
+ IFS=$'\n\t'
5
+
6
+ bundle install
@@ -0,0 +1,6 @@
1
+ test:
2
+ post:
3
+ - bundle exec rubocop
4
+ machine:
5
+ ruby:
6
+ version: 2.2.3
data/exe/jfy ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/lib')
4
+
5
+ require 'jfy/client'
6
+ require 'awesome_print'
7
+
8
+ client = Jfy::Client.new
9
+
10
+ retries = 0
11
+
12
+ begin
13
+ print 'Re Register all the arrays... '
14
+ client.re_register
15
+ puts 'Done.'
16
+
17
+ print 'Offine Query an the array... '
18
+ serial = client.offline_query
19
+ puts 'Done.'
20
+ rescue BadPacket, ReadTimeout => e
21
+ puts "Bad Packet #{e} ... Retrying" if @debug
22
+ puts if @debug
23
+ retries += 1
24
+ exit if retries > 5
25
+ retry
26
+ end
27
+
28
+ print 'Register the first array... '
29
+ serial_num = 0x02
30
+ client.register(serial, serial_num)
31
+ puts 'Done.'
32
+ puts
33
+
34
+ print 'Description: '
35
+ description = client.description(serial_num)
36
+ puts description
37
+ puts
38
+
39
+ print 'RW Description: '
40
+ rw_description = client.rw_description(serial_num)
41
+ puts rw_description
42
+ puts
43
+
44
+ puts 'Inverter Info:'
45
+ data = client.query_inverter_info(serial_num)
46
+ ap data
47
+ puts
48
+
49
+ puts 'Normal Info:'
50
+ data = client.query_normal_info(serial_num)
51
+ ap data
52
+ puts
53
+
54
+ puts 'Set Info:'
55
+ data = client.query_set_info(serial_num)
56
+ ap data
57
+ puts
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jfy/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'jfy'
8
+ spec.version = Jfy::VERSION
9
+ spec.authors = ['John Ferlito']
10
+ spec.email = ['johnf@inodes.org']
11
+
12
+ spec.summary = 'Library to speak to JFY Solar Inverters over a serial port'
13
+ spec.homepage = 'https://github.com/johnf/jfy'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = 'exe'
18
+ spec.require_paths = ['lib']
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+
21
+ spec.add_dependency 'serialport'
22
+ spec.add_dependency 'awesome_print'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.10'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'pry'
27
+ spec.add_development_dependency 'rspec'
28
+ spec.add_development_dependency 'rubocop'
29
+ spec.add_development_dependency 'rubocop-rspec'
30
+ spec.add_development_dependency 'rspec_junit_formatter'
31
+ spec.add_development_dependency 'coveralls'
32
+ end
@@ -0,0 +1,4 @@
1
+ require 'jfy/version'
2
+
3
+ module Jfy
4
+ end
@@ -0,0 +1,291 @@
1
+ require 'jfy/packet'
2
+ require 'jfy/codes'
3
+ require 'jfy/errors'
4
+
5
+ require 'serialport'
6
+
7
+ module Jfy
8
+ class Client
9
+ def initialize(options = {})
10
+ serial_port = options[:serial_port] || '/dev/ttyUSB0'
11
+ baud = options[:baud] || 9600
12
+ @debug = options[:debug] || false
13
+
14
+ @serial = SerialPort.new(serial_port, :baud => baud)
15
+
16
+ @serial.flush_output
17
+ @serial.flush_input
18
+ @serial.read_timeout = 1_000
19
+
20
+ @last_write = Time.now
21
+ end
22
+
23
+ def re_register
24
+ packet = Packet.new(Jfy::Codes::RE_REGISTER, [], :dst => 0x0)
25
+ write(packet, :read => false)
26
+ end
27
+
28
+ def offline_query
29
+ packet = Packet.new(Jfy::Codes::OFFLINE_QUERY, [], :dst => 0x0)
30
+ packet = write(packet)
31
+
32
+ fail(BadPacket, 'invalid offline response') unless packet.command == Jfy::Codes::REGISTER_REQUEST
33
+
34
+ packet.decode
35
+ end
36
+
37
+ def register(serial_num, address)
38
+ data = serial_num.unpack('c*') << address
39
+ packet = Jfy::Packet.new(Jfy::Codes::SEND_ADDRESS, data, :dst => 0x0)
40
+ packet = write(packet)
41
+
42
+ fail(BadPacket, 'invalid send address response') unless packet.command == Jfy::Codes::ADDRESS_CONFIRM
43
+ fail(BadPacket, 'No Ack') unless packet.ack?
44
+ end
45
+
46
+ def description(serial_num)
47
+ packet = Jfy::Packet.new(Jfy::Codes::READ_DESCRIPTION, [], :dst => serial_num)
48
+ packet = write(packet)
49
+
50
+ fail(BadPacket, 'invalid description response') unless packet.command == Jfy::Codes::READ_DESCRIPTION_RESP
51
+
52
+ packet.decode
53
+ end
54
+
55
+ def rw_description(serial_num)
56
+ packet = Jfy::Packet.new(Jfy::Codes::READ_RW_DESCRIPTION, [], :dst => serial_num)
57
+ packet = write(packet)
58
+
59
+ fail(BadPacket, 'invalid rw description response') unless packet.command == Jfy::Codes::READ_RW_DESCRIPTION_RESP
60
+
61
+ packet.decode
62
+ end
63
+
64
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
65
+ def query_normal_info(serial_num)
66
+ packet = Jfy::Packet.new(Jfy::Codes::QUERY_NORMAL_INFO, [], :dst => serial_num)
67
+ packet = write(packet)
68
+
69
+ fail(BadPacket, 'invalid query normal info response') unless packet.command == Jfy::Codes::QUERY_NORMAL_INFO_RESP
70
+
71
+ data = packet.data
72
+
73
+ modes = {
74
+ [0x0, 0x0] => :wait,
75
+ [0x0, 0x1] => :normal,
76
+ [0x0, 0x2] => :warning,
77
+ [0x0, 0x3] => :error,
78
+ }
79
+ mode = modes[data[24, 2]] || fail("Unkown mode #{data[24]} #{data[25]}")
80
+
81
+ metrics = {
82
+ :temperature => short(data, 0) / 10.0,
83
+ :mode => mode,
84
+ :voltage => [
85
+ short(data, 2) / 10.0,
86
+ short(data, 4) / 10.0,
87
+ short(data, 6) / 10.0,
88
+ # FIXME: Need to test on larger inverter
89
+ # short(data, 28) / 10.0,
90
+ # short(data, 30) / 10.0,
91
+ # short(data, 32) / 10.0,
92
+ # short(data, 40) / 10.0,
93
+ # short(data, 42) / 10.0,
94
+ # short(data, 44) / 10.0,
95
+ ],
96
+ :current => [
97
+ short(data, 8) / 10.0,
98
+ short(data, 10) / 10.0,
99
+ short(data, 12) / 10.0,
100
+ # FIXME: Need to test on larger inverter
101
+ # short(data, 34) / 10.0,
102
+ # short(data, 36) / 10.0,
103
+ # short(data, 38) / 10.0,
104
+ # short(data, 46) / 10.0,
105
+ # short(data, 48) / 10.0,
106
+ # short(data, 50) / 10.0,
107
+ ],
108
+ :hours => long(data, 18),
109
+ :power => {
110
+ :total => long(data, 14) / 10.0 * 1_000,
111
+ :today => short(data, 26) / 100.0 * 1_000,
112
+ :now => short(data, 22),
113
+ },
114
+ }
115
+
116
+ if data.size > 68
117
+ metrics.merge!(
118
+ :fault => {
119
+ :temperature => short(data, 114) / 10.0,
120
+ :voltage => [
121
+ short(data, 116) / 10.0,
122
+ short(data, 118) / 10.0,
123
+ short(data, 120) / 10.0,
124
+ ],
125
+ },
126
+ )
127
+ end
128
+
129
+ metrics
130
+ end
131
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
132
+
133
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
134
+ def query_inverter_info(serial_num)
135
+ packet = Jfy::Packet.new(Jfy::Codes::QUERY_INVERTER_INFO, [], :dst => serial_num)
136
+ packet = write(packet)
137
+
138
+ fail(BadPacket, 'invalid inverter info response') unless packet.command == Jfy::Codes::QUERY_INVERTER_INFO_RESP
139
+
140
+ data = packet.data
141
+
142
+ phase_types = {
143
+ 0x31 => 1,
144
+ 0x33 => 3,
145
+ }
146
+ phases = phase_types[data[0]] || fail("Unknown phase mode #{data[0]}")
147
+
148
+ rating = data[1, 6].pack('c*').to_i
149
+ version = data[7, 5].pack('c*')
150
+ model = data[12, 16].pack('c*').strip
151
+ manufacturer = data[28, 16].pack('c*').strip
152
+ serial = data[44, 16].pack('c*').strip
153
+ nominal_voltage = data[60, 4].pack('c*').to_i / 10.0
154
+
155
+ metrics = {
156
+ :phases => phases,
157
+ :rating => rating,
158
+ :version => version,
159
+ :model => model,
160
+ :manufacturer => manufacturer,
161
+ :serial => serial,
162
+ :nominal_voltage => nominal_voltage,
163
+ }
164
+
165
+ metrics
166
+ end
167
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
168
+
169
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
170
+ def query_set_info(serial_num)
171
+ packet = Jfy::Packet.new(Jfy::Codes::QUERY_SET_INFO, [], :dst => serial_num)
172
+ packet = write(packet)
173
+
174
+ fail(BadPacket, 'invalid set info response') unless packet.command == Jfy::Codes::QUERY_SET_INFO_RESP
175
+
176
+ data = packet.data
177
+
178
+ metrics = {
179
+ :pv_voltage => {
180
+ :startup => short(data, 0) / 10.0,
181
+ :high_stop => short(data, 4) / 10.0,
182
+ :low_stop => short(data, 6) / 10.0,
183
+ },
184
+ :grid => {
185
+ :voltage => {
186
+ :min => short(data, 8) / 10.0,
187
+ :max => short(data, 10) / 10.0,
188
+ },
189
+ :frequency => {
190
+ :min => short(data, 12) / 100.0,
191
+ :max => short(data, 14) / 100.0,
192
+ },
193
+ :impedance => {
194
+ :max => short(data, 16) / 1_000.0,
195
+ :delta => short(data, 18),
196
+ },
197
+ },
198
+ :power_max => short(data, 20),
199
+ :power_factor => short(data, 22) / 100.0,
200
+ :connect_time => short(data, 2),
201
+ }
202
+
203
+ metrics
204
+ end
205
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
206
+
207
+ private
208
+
209
+ def short(data, offset)
210
+ a, b = data[offset, 2]
211
+
212
+ ((a & 0x00ff) << 8) | (b & 0xff)
213
+ end
214
+
215
+ def long(data, offset)
216
+ a, b, c, d = data[offset, 4]
217
+
218
+ ((a & 0x00ff) << 24) |
219
+ ((b & 0x00ff) << 16) |
220
+ ((c & 0x00ff) << 8) |
221
+ (d & 0xff)
222
+ end
223
+
224
+ def write(packet, options = {})
225
+ p packet if @debug
226
+
227
+ wait
228
+ @serial.syswrite(packet.to_s)
229
+ @last_write = Time.now
230
+
231
+ read unless options[:read] == false
232
+ end
233
+
234
+ def wait
235
+ diff = Time.now - @last_write
236
+
237
+ return unless diff < 0.5
238
+
239
+ sleep(0.5 - diff)
240
+ end
241
+
242
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
243
+ def read
244
+ buffer = []
245
+
246
+ loop do
247
+ char = @serial.getbyte
248
+ fail(ReadTimeout) if char.nil?
249
+
250
+ buffer << char
251
+
252
+ fail(BadPacket, 'invalid header') if buffer.size == 2 && buffer != [0xA5, 0xA5]
253
+
254
+ break if buffer[-2, 2] == [0x0A, 0x0D]
255
+
256
+ fail(BadPacket, 'packet too big') if buffer.size > 256
257
+ end
258
+
259
+ header = [buffer.shift, buffer.shift]
260
+ fail(BadPacket, 'invalid header') unless header == [0xA5, 0xA5]
261
+
262
+ src = buffer.shift
263
+ dst = buffer.shift
264
+
265
+ ctrl = buffer.shift
266
+ func = buffer.shift
267
+
268
+ size = buffer.shift
269
+
270
+ # TODO: My unit seems to return the wrong size here (Maybe we should have an option if not all units do this)
271
+ size -= 2 if [ctrl, func] == Jfy::Codes::QUERY_SET_INFO_RESP
272
+
273
+ data = buffer.take(size)
274
+ buffer = buffer.drop(size)
275
+
276
+ checksum = [buffer.shift, buffer.shift]
277
+ ender = [buffer.shift, buffer.shift]
278
+
279
+ packet = Packet.new([ctrl, func], data, :src => src, :dst => dst)
280
+ p packet if @debug
281
+
282
+ fail(BadPacket, 'invalid checksum') if checksum != packet.checksum &&
283
+ [ctrl, func] != Jfy::Codes::QUERY_SET_INFO_RESP
284
+
285
+ fail(BadPacket, 'invalid ender') unless ender == [0x0A, 0x0D]
286
+
287
+ packet
288
+ end
289
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
290
+ end
291
+ end
@@ -0,0 +1,33 @@
1
+ module Jfy
2
+ module Codes
3
+ CTL_REGISTER = 0x30
4
+ CTL_READ = 0x31
5
+ CTL_WRITE = 0x32
6
+ CTL_EXECUTE = 0x33
7
+
8
+ # Register
9
+ OFFLINE_QUERY = [CTL_REGISTER, 0x40]
10
+ SEND_ADDRESS = [CTL_REGISTER, 0x41]
11
+ RE_REGISTER = [CTL_REGISTER, 0x44]
12
+
13
+ # Register Response
14
+ REGISTER_REQUEST = [CTL_REGISTER, 0xBF]
15
+ ADDRESS_CONFIRM = [CTL_REGISTER, 0xBE]
16
+
17
+ # Read
18
+ READ_DESCRIPTION = [CTL_READ, 0x40]
19
+ READ_RW_DESCRIPTION = [CTL_READ, 0x41]
20
+ QUERY_NORMAL_INFO = [CTL_READ, 0x42]
21
+ QUERY_INVERTER_INFO = [CTL_READ, 0x43]
22
+ QUERY_SET_INFO = [CTL_READ, 0x44]
23
+ QUERY_TIME = [CTL_READ, 0x45]
24
+
25
+ # Read
26
+ READ_DESCRIPTION_RESP = [CTL_READ, 0xBF]
27
+ READ_RW_DESCRIPTION_RESP = [CTL_READ, 0xBE]
28
+ QUERY_NORMAL_INFO_RESP = [CTL_READ, 0xBD]
29
+ QUERY_INVERTER_INFO_RESP = [CTL_READ, 0xBC]
30
+ QUERY_SET_INFO_RESP = [CTL_READ, 0xBB]
31
+ QUERY_TIME_RESP = [CTL_READ, 0xBA]
32
+ end
33
+ end
@@ -0,0 +1,4 @@
1
+ module Jfy
2
+ class BadPacket < StandardError; end
3
+ class ReadTimeout < StandardError; end
4
+ end
@@ -0,0 +1,59 @@
1
+ module Jfy
2
+ class Packet
3
+ attr_reader :data
4
+
5
+ def initialize(code, data = [], options = {})
6
+ @header = [0xA5, 0xA5]
7
+ @src = options[:src] || 0x01
8
+ @dst = options[:dst] || 0x01
9
+ @ctrl, @func = code
10
+ @data = data || []
11
+ @ender = [0x0A, 0x0D]
12
+ end
13
+
14
+ def command
15
+ [@ctrl, @func]
16
+ end
17
+
18
+ def ack?
19
+ @data == [0x06]
20
+ end
21
+
22
+ def sub_packet
23
+ @sub_packet ||= @header + [@src, @dst, @ctrl, @func, @data.size] + @data
24
+ end
25
+
26
+ def packet
27
+ @packet ||= sub_packet + checksum + @ender
28
+ end
29
+
30
+ def checksum
31
+ @checksum ||= begin
32
+ sum = sub_packet.inject(:+)
33
+ sum ^= 0xffff
34
+ sum += 1
35
+
36
+ left = (sum & 0xffff) >> 8
37
+ right = sum & 0x00ff
38
+
39
+ [left, right]
40
+ end
41
+ end
42
+
43
+ def decode
44
+ @data.pack('c*')
45
+ end
46
+
47
+ def to_s
48
+ packet.pack('c*')
49
+ end
50
+
51
+ def inspect
52
+ hex = @data.map { |d| "0x#{d.to_s(16)}" }.join(' ')
53
+ csum = checksum.map { |d| "0x#{d.to_s(16)}" }.join(' ')
54
+ data = decode.encode('ASCII', :invalid => :replace, :undef => :replace)
55
+ format('<Jfy::Packet:0x%02x @ctrl=0x%02X @func=0x%02X @data=[%s] @hex=[%s] @csum=[%s]>',
56
+ object_id, @ctrl, @func, data, hex, csum)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ module Jfy
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,204 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jfy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - John Ferlito
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-11-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: serialport
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: awesome_print
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: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.10'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.10'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
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
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop-rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec_junit_formatter
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: coveralls
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description:
154
+ email:
155
+ - johnf@inodes.org
156
+ executables:
157
+ - jfy
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - ".gitignore"
162
+ - ".rspec"
163
+ - ".rubocop.yml"
164
+ - CODE_OF_CONDUCT.md
165
+ - Gemfile
166
+ - LICENSE.txt
167
+ - README.md
168
+ - Rakefile
169
+ - bin/console
170
+ - bin/setup
171
+ - circle.yml
172
+ - exe/jfy
173
+ - jfy.gemspec
174
+ - lib/jfy.rb
175
+ - lib/jfy/client.rb
176
+ - lib/jfy/codes.rb
177
+ - lib/jfy/errors.rb
178
+ - lib/jfy/packet.rb
179
+ - lib/jfy/version.rb
180
+ homepage: https://github.com/johnf/jfy
181
+ licenses:
182
+ - MIT
183
+ metadata: {}
184
+ post_install_message:
185
+ rdoc_options: []
186
+ require_paths:
187
+ - lib
188
+ required_ruby_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ required_rubygems_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ requirements: []
199
+ rubyforge_project:
200
+ rubygems_version: 2.4.5.1
201
+ signing_key:
202
+ specification_version: 4
203
+ summary: Library to speak to JFY Solar Inverters over a serial port
204
+ test_files: []