lxp-packet 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f690a3ff0e519ed9f0c68152117bfcd4ece48d2ffd67c32b83cd1b2bafc356b5
4
- data.tar.gz: 4f35b9e46c92659e7bd5c366e3cc6bcbe9c4646e17278b6a8df3f7c3edb220a5
3
+ metadata.gz: 4fc4ef9aa204639ee245d39bba281bb3ec005013a7a7d3f16f975d5e31a6eca6
4
+ data.tar.gz: 9cb28af9771bf98ec9f793bf77785611337529b3514d7b8061d8ee53e234620a
5
5
  SHA512:
6
- metadata.gz: d19df5ab0f0cbe0568c4f7277a1ea84c071f30a3c882cabc16464b0a6a1b2850d27deeb191e74920d5f6da7fb052fde8b89768a535117a369c1724b75cfc4ef4
7
- data.tar.gz: 418b00400f1c147d45dccb5f0d74660baccd9a5a5d88fb81cd0e25c86115550f1609f1482d9ea8fca9f4f9ec56d86b6bd5654138d7d2561d8a9595dc0fb4f466
6
+ metadata.gz: cb39026d5d05f414ddfae9d1087646310fb52c1d5a9b59752472eaa69046e793697165b88153e4a03bb9372c562a331e3db4284b312c0164b810b4508d6f052d
7
+ data.tar.gz: 6bf3e09ff9a48c98c884f6bb45fe776cf15ca80dd6cac1445889c9a9cbb89d3b1631f9df9c18210a1c100af404e0ffbea000af695a767196a6c99ca0824ee1e4
data/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - support 3 PV strings in `ReadInput1` and `ReadInput2`
13
+ - support 3 phase power in `ReadInput1`
14
+ - support `t_bat` key in `ReadInput2#to_h` (suspect this is Lead Acid only as mine is 0)
15
+ - support `uptime` key in `ReadInput2#to_h`
16
+
17
+ ### Fixed
18
+
19
+ - parsing of temperatures above 255C in ReadInput2
20
+
21
+
10
22
  ## [0.4.0] - 2020-04-07
11
23
 
12
24
  ### Added
@@ -8,44 +8,68 @@ class LXP
8
8
  # Decode the data and return a hash of values in this input packet
9
9
  def to_h
10
10
  {
11
- status: @data[15],
11
+ # 0 = static 1
12
+ # 1 = R_INPUT
13
+ # 2..12 = serial
14
+ # 13/14 = length
12
15
 
13
- v_bat: Utils.int(@data[23, 2], :lsb) / 10.0, # V
16
+ status: Utils.int(@data[15, 2]),
17
+
18
+ # 17-22 all observed zeroes (v_pv, v_pv_2, v_pv_3 ?)
19
+
20
+ v_bat: Utils.int(@data[23, 2]) / 10.0, # V
14
21
  soc: @data[25], # %
22
+ # 26 used for anything?
23
+
24
+ # observed [0, 47] => 12032 or 0x2F00
25
+ _unknown_i1_28: @data[28],
26
+ _unknown_i1_27_28: Utils.int(@data[27, 2]),
15
27
 
16
- # 26..28 ?
28
+ p_pv: Utils.int(@data[29, 2]) +
29
+ Utils.int(@data[31, 2]) +
30
+ Utils.int(@data[33, 2]), # W
31
+ p_pv_1: Utils.int(@data[29, 2]), # W
32
+ p_pv_2: Utils.int(@data[31, 2]), # W
33
+ p_pv_3: Utils.int(@data[33, 2]), # W
34
+ p_charge: Utils.int(@data[35, 2]), # W
35
+ p_discharge: Utils.int(@data[37, 2]), # W
36
+ v_ac_r: Utils.int(@data[39, 2]) / 10.0, # V
37
+ v_ac_s: Utils.int(@data[41, 2]) / 10.0, # V
38
+ v_ac_t: Utils.int(@data[43, 2]) / 10.0, # V
39
+ f_ac: Utils.int(@data[45, 2]) / 100.0, # Hz
17
40
 
18
- p_pv: Utils.int(@data[29, 2], :lsb), # W
19
- p_charge: Utils.int(@data[35, 2], :lsb), # W
20
- p_discharge: Utils.int(@data[37, 2], :lsb), # W
21
- v_acr: Utils.int(@data[39, 2], :lsb) / 10.0, # V
22
- f_ac: Utils.int(@data[45, 2], :lsb) / 100.0, # Hz
41
+ p_inv: Utils.int(@data[47, 2]), # W
42
+ p_rec: Utils.int(@data[49, 2]), # W
23
43
 
24
- p_inv: Utils.int(@data[47, 2], :lsb), # W
25
- p_rec: Utils.int(@data[49, 2], :lsb), # W
44
+ _unknown_i1_51_52: Utils.int(@data[51, 2]),
26
45
 
27
- # 51..54 ?
46
+ pf: Utils.int(@data[53, 2]) / 1000.0, # Hz
28
47
 
29
- v_eps: Utils.int(@data[55, 2], :lsb) / 10.0, # V
30
- f_eps: Utils.int(@data[61, 2], :lsb) / 100.0, # Hz
48
+ v_eps_r: Utils.int(@data[55, 2]) / 10.0, # V
49
+ v_eps_s: Utils.int(@data[57, 2]) / 10.0, # V
50
+ v_eps_t: Utils.int(@data[59, 2]) / 10.0, # V
51
+ f_eps: Utils.int(@data[61, 2]) / 100.0, # Hz
31
52
 
32
53
  # peps and seps in 63..66?
33
54
 
34
- p_to_grid: Utils.int(@data[67, 2], :lsb), # W
35
- p_to_user: Utils.int(@data[69, 2], :lsb), # W
36
-
37
- e_pv_day: Utils.int(@data[71, 2], :lsb) / 10.0, # kWh
38
- # 73..76 ?
39
- e_inv_day: Utils.int(@data[77, 2], :lsb) / 10.0, # kWh
40
- e_rec_day: Utils.int(@data[79, 2], :lsb) / 10.0, # kWh
41
- e_chg_day: Utils.int(@data[81, 2], :lsb) / 10.0, # kWh
42
- e_dischg_day: Utils.int(@data[83, 2], :lsb) / 10.0, # kWh
43
- e_eps_day: Utils.int(@data[85, 2], :lsb) / 10.0, # kWh
44
- e_to_grid_day: Utils.int(@data[87, 2], :lsb) / 10.0, # kWh
45
- e_to_user_day: Utils.int(@data[89, 2], :lsb) / 10.0, # kWh
46
-
47
- v_bus_1: Utils.int(@data[91, 2], :lsb) / 10.0, # V
48
- v_bus_2: Utils.int(@data[93, 2], :lsb) / 10.0 # V
55
+ p_to_grid: Utils.int(@data[67, 2]), # W
56
+ p_to_user: Utils.int(@data[69, 2]), # W
57
+
58
+ e_pv_day: (Utils.int(@data[71, 2]) +
59
+ Utils.int(@data[73, 2]) + Utils.int(@data[75, 2])) / 10.0, # kWh
60
+ e_pv_1_day: Utils.int(@data[71, 2]) / 10.0, # kWh
61
+ e_pv_2_day: Utils.int(@data[73, 2]) / 10.0, # kWh
62
+ e_pv_3_day: Utils.int(@data[75, 2]) / 10.0, # kWh
63
+ e_inv_day: Utils.int(@data[77, 2]) / 10.0, # kWh
64
+ e_rec_day: Utils.int(@data[79, 2]) / 10.0, # kWh
65
+ e_chg_day: Utils.int(@data[81, 2]) / 10.0, # kWh
66
+ e_dischg_day: Utils.int(@data[83, 2]) / 10.0, # kWh
67
+ e_eps_day: Utils.int(@data[85, 2]) / 10.0, # kWh
68
+ e_to_grid_day: Utils.int(@data[87, 2]) / 10.0, # kWh
69
+ e_to_user_day: Utils.int(@data[89, 2]) / 10.0, # kWh
70
+
71
+ v_bus_1: Utils.int(@data[91, 2]) / 10.0, # V
72
+ v_bus_2: Utils.int(@data[93, 2]) / 10.0 # V
49
73
  }
50
74
  end
51
75
  end
@@ -8,18 +8,37 @@ class LXP
8
8
  # Decode the data and return a hash of values in this input packet
9
9
  def to_h
10
10
  {
11
- e_pv_all: Utils.int(@data[15, 4], :lsb) / 10.0, # kWh
12
- e_inv_all: Utils.int(@data[27, 4], :lsb) / 10.0, # kWh
13
- e_rec_all: Utils.int(@data[31, 4], :lsb) / 10.0, # kWh
14
- e_chg_all: Utils.int(@data[35, 4], :lsb) / 10.0, # kWh
15
- e_dischg_all: Utils.int(@data[39, 4], :lsb) / 10.0, # kWh
16
- e_eps_all: Utils.int(@data[43, 4], :lsb) / 10.0, # kWh
17
- e_to_grid_all: Utils.int(@data[47, 4], :lsb) / 10.0, # kWh
18
- e_to_user_all: Utils.int(@data[51, 4], :lsb) / 10.0, # kWh
11
+ # 0 = static 1
12
+ # 1 = R_INPUT
13
+ # 2..12 = serial
14
+ # 13/14 = length
19
15
 
20
- t_inner: Utils.int(@data[62, 2], :msb),
21
- t_rad_1: Utils.int(@data[64, 2], :msb),
22
- t_rad_2: Utils.int(@data[66, 2], :msb)
16
+ e_pv_all: (Utils.int(@data[15, 4]) +
17
+ Utils.int(@data[19, 4]) +
18
+ Utils.int(@data[23, 4])) / 10.0, # kWh
19
+ e_pv_1_all: Utils.int(@data[15, 4]) / 10.0, # kWh
20
+ e_pv_2_all: Utils.int(@data[19, 4]) / 10.0, # kWh
21
+ e_pv_3_all: Utils.int(@data[23, 4]) / 10.0, # kWh
22
+ e_inv_all: Utils.int(@data[27, 4]) / 10.0, # kWh
23
+ e_rec_all: Utils.int(@data[31, 4]) / 10.0, # kWh
24
+ e_chg_all: Utils.int(@data[35, 4]) / 10.0, # kWh
25
+ e_dischg_all: Utils.int(@data[39, 4]) / 10.0, # kWh
26
+ e_eps_all: Utils.int(@data[43, 4]) / 10.0, # kWh
27
+ e_to_grid_all: Utils.int(@data[47, 4]) / 10.0, # kWh
28
+ e_to_user_all: Utils.int(@data[51, 4]) / 10.0, # kWh
29
+
30
+ # 55 .. 62?
31
+ # fault code? 4 bytes?
32
+ # warning code? 4 bytes?
33
+
34
+ t_inner: Utils.int(@data[63, 2]),
35
+ t_rad_1: Utils.int(@data[65, 2]),
36
+ t_rad_2: Utils.int(@data[67, 2]),
37
+ t_bat: Utils.int(@data[69, 2]),
38
+
39
+ # 71..72 ?
40
+
41
+ uptime: Utils.int(@data[73, 4]) # seconds
23
42
  }
24
43
  end
25
44
  end
@@ -8,11 +8,20 @@ class LXP
8
8
  # Decode the data and return a hash of values in this input packet
9
9
  def to_h
10
10
  {
11
- max_chg_curr: Utils.int(@data[17, 2], :lsb) / 100.0, # A
12
- max_dischg_curr: Utils.int(@data[19, 2], :lsb) / 100.0, # A
13
- charge_volt_ref: Utils.int(@data[21, 2], :lsb) / 10.0, # V
14
- dischg_cut_volt: Utils.int(@data[23, 2], :lsb) / 10.0, # V
11
+ # 0 = static 1
12
+ # 1 = R_INPUT
13
+ # 2..12 = serial
14
+ # 13/14 = length
15
15
 
16
+ # 15..16? .. (observed: 10)
17
+
18
+ max_chg_curr: Utils.int(@data[17, 2]) / 100.0, # A
19
+ max_dischg_curr: Utils.int(@data[19, 2]) / 100.0, # A
20
+ charge_volt_ref: Utils.int(@data[21, 2]) / 10.0, # V
21
+ dischg_cut_volt: Utils.int(@data[23, 2]) / 10.0, # V
22
+
23
+ # are these actually 2 bytes as well?
24
+ # never seen data in them so its hard to tell.
16
25
  bat_status_0: @data[25],
17
26
  bat_status_1: @data[27],
18
27
  bat_status_2: @data[29],
@@ -24,6 +33,9 @@ class LXP
24
33
  bat_status_8: @data[41],
25
34
  bat_status_9: @data[43],
26
35
  bat_status_inv: @data[45]
36
+
37
+ # 47/48 = battery count? seen a 6 but unconfirmed it changes
38
+ # when battery count does.. TODO..
27
39
  }
28
40
  end
29
41
  end
data/lib/lxp/utils.rb CHANGED
@@ -4,16 +4,14 @@ class LXP
4
4
  module Utils
5
5
  module_function
6
6
 
7
- def int(bytes, order = :lsb)
8
- bytes = bytes.reverse if order == :msb
9
-
7
+ def int(bytes)
10
8
  bytes.each_with_index.map do |b, idx|
11
9
  b << (idx * 8)
12
10
  end.inject(:|)
13
11
  end
14
12
 
15
- def int_complement(bytes, order = :lsb)
16
- r = int(bytes, order)
13
+ def int_complement(bytes)
14
+ r = int(bytes)
17
15
  r -= 0x10000 if r & 0x8000 == 0x8000
18
16
  r
19
17
  end
data/lib/lxp/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class LXP
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
data/spec/parser_spec.rb CHANGED
@@ -74,7 +74,30 @@ RSpec.describe LXP::Packet::Parser do
74
74
 
75
75
  describe '#to_h' do
76
76
  subject { packet.to_h }
77
- it { is_expected.to eq status: 16, v_bat: 49.7, soc: 95, p_pv: 0, p_charge: 0, p_discharge: 633, v_acr: 247.6, f_ac: 50.1, p_inv: 569, p_rec: 0, v_eps: 247.6, f_eps: 50.1, p_to_grid: 0, p_to_user: 0, e_pv_day: 22.1, e_inv_day: 4.2, e_rec_day: 7.3, e_chg_day: 8.6, e_dischg_day: 5.2, e_eps_day: 0.0, e_to_grid_day: 3.6, e_to_user_day: 0.2, v_bus_1: 375.6, v_bus_2: 299.7 }
77
+ it { is_expected.to eq _unknown_i1_28: 47, _unknown_i1_27_28: 12_032, _unknown_i1_51_52: 227, status: 16, v_bat: 49.7, soc: 95, p_pv: 0, p_pv_1: 0, p_pv_2: 0, p_pv_3: 0, p_charge: 0, p_discharge: 633, v_ac_r: 247.6, v_ac_s: 0.1, v_ac_t: 0.0, f_ac: 50.1, p_inv: 569, p_rec: 0, pf: 1.0, v_eps_r: 247.6, v_eps_s: 281.6, v_eps_t: 2875.2, f_eps: 50.1, p_to_grid: 0, p_to_user: 0, e_pv_day: 22.1, e_pv_1_day: 22.1, e_pv_2_day: 0.0, e_pv_3_day: 0.0, e_inv_day: 4.2, e_rec_day: 7.3, e_chg_day: 8.6, e_dischg_day: 5.2, e_eps_day: 0.0, e_to_grid_day: 3.6, e_to_user_day: 0.2, v_bus_1: 375.6, v_bus_2: 299.7 }
78
+ end
79
+ end
80
+
81
+ context 'ReadInput2 data' do
82
+ let(:input) { [161, 26, 2, 0, 111, 0, 1, 194, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 97, 0, 1, 4, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 40, 0, 80, 83, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 179, 38, 0, 0, 43, 45, 0, 0, 105, 51, 0, 0, 44, 47, 0, 0, 0, 0, 0, 0, 95, 5, 0, 0, 63, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 0, 36, 0, 37, 0, 0, 0, 0, 0, 35, 136, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 188, 199] }
83
+
84
+ it { is_expected.to be_a LXP::Packet::ReadInput2 }
85
+
86
+ it 'has the correct attributes' do
87
+ expect(packet).to have_attributes register: 40,
88
+ values: [83, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 179, 38, 0, 0, 43, 45, 0, 0, 105, 51, 0, 0, 44, 47, 0, 0, 0, 0, 0, 0, 95, 5, 0, 0, 63, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 0, 36, 0, 37, 0, 0, 0, 0, 0, 35, 136, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
89
+ protocol: 2,
90
+ packet_length: 111, data_length: 97,
91
+ tcp_function: LXP::Packet::TcpFunctions::TRANSLATED_DATA,
92
+ device_function: LXP::Packet::DeviceFunctions::READ_INPUT,
93
+ inverter_serial: 'XXXXXXXXXX',
94
+ datalog_serial: 'cccccccccc',
95
+ bytes: input
96
+ end
97
+
98
+ describe '#to_h' do
99
+ subject { packet.to_h }
100
+ it { is_expected.to eq e_chg_all: 1316.1, e_dischg_all: 1207.6, e_eps_all: 0.0, e_inv_all: 990.7, e_pv_all: 1774.7, e_pv_1_all: 1774.7, e_pv_2_all: 0.0, e_pv_3_all: 0.0, e_rec_all: 1156.3, e_to_grid_all: 137.5, e_to_user_all: 1183.9, t_bat: 0, t_inner: 48, t_rad_1: 36, t_rad_2: 37, uptime: 14_714_915 }
78
101
  end
79
102
  end
80
103
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lxp-packet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Elsworth
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-07 00:00:00.000000000 Z
11
+ date: 2020-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler