lxp-packet 0.4.0 → 0.5.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 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