lxp-packet 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +40 -4
- data/doc/LXP_PACKET.txt +8 -13
- data/doc/LXP_REGISTERS.txt +10 -1
- data/lib/lxp/packet/base.rb +3 -3
- data/lib/lxp/packet/device_functions.rb +6 -0
- data/lib/lxp/packet/heartbeat.rb +8 -0
- data/lib/lxp/packet/read_hold.rb +3 -3
- data/lib/lxp/packet/read_input.rb +9 -0
- data/lib/lxp/packet/read_input1.rb +7 -0
- data/lib/lxp/packet/read_input2.rb +9 -0
- data/lib/lxp/packet/read_input3.rb +7 -0
- data/lib/lxp/version.rb +1 -1
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddb052890f90bbf20bf9ff7fd399dcb1f2a7211612c53b7cc2e98a57cf5c69a1
|
4
|
+
data.tar.gz: 7234113e5decb4dad0581089b72277b138c7140af0105bd4231284342f187997
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 824355382e38cc49be2c1de0a9a7d875d8975bd4993156565d7dfa8b0cfa24c042deed6d6d4ab87d5a6c42b1e88f52ad7c61eaaa4c912a6c18f7a1ccb1eb0519
|
7
|
+
data.tar.gz: 11d143b9f00cce2cb821216e2f64d961e595d8b52451321cf2ce2073f8d4666f33c6e0d2c36cc21761c4e849a4ca0ff3dfd192ef0f08f15cc0beb1ce5a4c1e35
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [0.7.0] - 2020-05-08
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- support for requesting input registers on demand
|
13
|
+
- support for sending heartbeats
|
14
|
+
|
8
15
|
## [0.6.0] - 2020-04-10
|
9
16
|
|
10
17
|
### Added
|
data/README.md
CHANGED
@@ -44,6 +44,14 @@ It can optionally be configured with a second network endpoint; I set this to TC
|
|
44
44
|
|
45
45
|
Alternatively you can probably just change the first setting if you don't care about the official Lux portal or mobile app being updated, though I found it useful to verify I was setting the right values at first. This would also prevent LuxPower sending you firmware updates (for better or for worse), not that I've had any so far.
|
46
46
|
|
47
|
+
## Inverter Fundamentals
|
48
|
+
|
49
|
+
The inverter has two basic sets of information.
|
50
|
+
|
51
|
+
There are 114 registers (0-113), which are also referred to as "holdings". See [doc/LXP_REGISTERS.txt](doc/LXP_REGISTERS.txt) for a list of them. Most of these you can write, and they affect inverter operation. Some pieces of information span several registers, for example the serial number is in registers 2 through 8.
|
52
|
+
|
53
|
+
Additionally there are input registers. These are transient information which the inverter broadcasts to any connected client every 2 minutes. These are sent as sets of 3 packets. `ReadInput1` / `ReadInput2` / `ReadInput3` are used to parse these.
|
54
|
+
|
47
55
|
## Examples
|
48
56
|
|
49
57
|
The inverter requires that your datalog serial and inverter serial are in the packets you send to it for it to respond.
|
@@ -73,9 +81,9 @@ end
|
|
73
81
|
|
74
82
|
This is necessary because occasionally the inverter will send us state data and heartbeats, as well as replies for other clients (see above) which we need to either process (if you're interested in them) or ignore (which is easier, and done here).
|
75
83
|
|
76
|
-
### Reading
|
84
|
+
### Reading Holding Registers
|
77
85
|
|
78
|
-
This is the simplest use-case; read the value of
|
86
|
+
This is the simplest use-case; read the value of a holding register from the inverter.
|
79
87
|
|
80
88
|
```ruby
|
81
89
|
pkt = LXP::Packet::ReadHold.new
|
@@ -96,9 +104,17 @@ r = read_reply(sock, pkt)
|
|
96
104
|
puts "Received: #{r.value}" # should be discharge cut-off value
|
97
105
|
```
|
98
106
|
|
99
|
-
Usually, `ReadHold` instances contain the details of just one register. However, it is possible they can contain multiple. Pressing "Read" on the LuxPower Web Portal provokes the inverter into sending out 5 packets that each contain multiple registers, for example.
|
107
|
+
Usually, `ReadHold` instances contain the details of just one register. However, it is possible they can contain multiple. Pressing "Read" on the LuxPower Web Portal provokes the inverter into sending out 5 packets that each contain multiple registers, for example.
|
100
108
|
|
101
|
-
To
|
109
|
+
To do this yourself, set `#value` in a `ReadHold` you're going to send to the inverter. This tells it how many registers you want in the reply, and they'll start from the number set in `#register`:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
# get registers 0 through 22 inclusive:
|
113
|
+
pkt.register = 0
|
114
|
+
pkt.value = 23
|
115
|
+
```
|
116
|
+
|
117
|
+
To access these in the reply, you can use subscript notation to get a register directly, or call `#to_h` to get a hash of registers/values. For convenience this also works with single register packets, though obviously only one subscript will ever return data, and `to_h` will only have one key.
|
102
118
|
|
103
119
|
```ruby
|
104
120
|
# assuming pkt is a parsed packet with multiple registers/values:
|
@@ -111,6 +127,26 @@ pkt[22] # => nil
|
|
111
127
|
pkt.to_h # { 21 => 62292 }
|
112
128
|
```
|
113
129
|
|
130
|
+
### Reading Input Registers
|
131
|
+
|
132
|
+
This is similar to reading holdings. The inverter should send these packets every 2 minutes anyway, but if you want them on demand, you can create a `ReadInput1` (or 2, or 3) and send it.
|
133
|
+
|
134
|
+
The response packet contains a bunch of data, the simplest way to get at these is to call `to_h` on the packet, which returns a Hash of data:
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
pkt = LXP::Packet::ReadInput1.new
|
138
|
+
pkt.datalog_serial = 'AB12345678'
|
139
|
+
pkt.inverter_serial = '1234567890'
|
140
|
+
|
141
|
+
# assuming your inverter is at 192.168.0.30
|
142
|
+
sock = TCPSocket.new('192.168.0.30', 4346)
|
143
|
+
sock.write(out)
|
144
|
+
|
145
|
+
r = read_reply(sock, pkt)
|
146
|
+
# r is a populated ReadInput1, which responds to #to_h:
|
147
|
+
r.to_h # => {:status=>16, :soc=>79, ... }
|
148
|
+
```
|
149
|
+
|
114
150
|
|
115
151
|
### Writing
|
116
152
|
|
data/doc/LXP_PACKET.txt
CHANGED
@@ -28,15 +28,13 @@ Then there's a header:
|
|
28
28
|
194=data to translate
|
29
29
|
195=read param 196=write param
|
30
30
|
8 10 datalog serial number
|
31
|
-
18 2 data frame length
|
31
|
+
18 2 data frame length
|
32
32
|
|
33
|
-
Mostly the packets I've been dealing with have TCP_FUNCTION 194. I ignore
|
34
|
-
heartbeats, and I've not seen 195 or 196 I don't think.
|
35
33
|
|
36
|
-
For 194, bytes 18/19 are the data frame length which should match
|
37
|
-
of bytes that follows. It's sometimes easier to think of the
|
38
|
-
its own structure so here are byte offsets in both the dataframe
|
39
|
-
packet:
|
34
|
+
For TCP_FUNCTION 194, bytes 18/19 are the data frame length which should match
|
35
|
+
the number of bytes that follows. It's sometimes easier to think of the
|
36
|
+
dataframe as its own structure so here are byte offsets in both the dataframe
|
37
|
+
and the entire packet:
|
40
38
|
|
41
39
|
BYTE BYTES VALUE MEANING
|
42
40
|
0/20 1 address? 0 when writing to inverter, 1 when reading?
|
@@ -56,9 +54,6 @@ between R_HOLD and R_INPUT yet. W_SINGLE writes a single register; not used
|
|
56
54
|
W_MULTI.
|
57
55
|
|
58
56
|
To get the inverter to populate the response value for an R_HOLD, the value
|
59
|
-
you send
|
60
|
-
|
61
|
-
|
62
|
-
this seems to be for the regular stats the inverter sends out every 2 minutes;
|
63
|
-
seems to send 3 packets, the first one with address 0, then 40, then 80.
|
64
|
-
Not really done much with this yet.
|
57
|
+
you send needs to be above 0. This tells the inverter how many registers you
|
58
|
+
want to read. Normally this is 1, if its higher than you'll get seqeuential
|
59
|
+
registers
|
data/doc/LXP_REGISTERS.txt
CHANGED
@@ -8,7 +8,16 @@ testing in case I got anything wrong!
|
|
8
8
|
Registers 21 and 110 are bitmasks; normally you fetch the previous
|
9
9
|
settings, apply your changes, and set the new value back.
|
10
10
|
|
11
|
-
0 MODEL
|
11
|
+
0-1 MODEL
|
12
|
+
contains (not sure how to decode these yet):
|
13
|
+
lithiumType
|
14
|
+
powerRating
|
15
|
+
leadAcidType
|
16
|
+
ruleMask
|
17
|
+
batteryType
|
18
|
+
meterBrand
|
19
|
+
measurement
|
20
|
+
rule
|
12
21
|
2-8 SERIAL_NUM
|
13
22
|
7 FW_CODE ?
|
14
23
|
9-10 ?
|
data/lib/lxp/packet/base.rb
CHANGED
data/lib/lxp/packet/heartbeat.rb
CHANGED
@@ -17,10 +17,18 @@ class LXP
|
|
17
17
|
#
|
18
18
|
class Heartbeat < Base
|
19
19
|
def initialize
|
20
|
+
@header = [0] * 19
|
21
|
+
|
20
22
|
super
|
21
23
|
|
24
|
+
self.packet_length = 13
|
25
|
+
self.protocol = 2
|
22
26
|
self.tcp_function = TcpFunctions::HEARTBEAT
|
23
27
|
end
|
28
|
+
|
29
|
+
def bytes
|
30
|
+
header
|
31
|
+
end
|
24
32
|
end
|
25
33
|
end
|
26
34
|
end
|
data/lib/lxp/packet/read_hold.rb
CHANGED
@@ -12,9 +12,9 @@ class LXP
|
|
12
12
|
self.device_function = DeviceFunctions::READ_HOLD
|
13
13
|
|
14
14
|
self.data_length = 18
|
15
|
-
|
16
|
-
#
|
17
|
-
#
|
15
|
+
|
16
|
+
# in ReadHold packets, the value is the number of registers
|
17
|
+
# we want to read. Default to 1.
|
18
18
|
self.value = 1
|
19
19
|
end
|
20
20
|
|
@@ -6,6 +6,15 @@ class LXP
|
|
6
6
|
class Packet
|
7
7
|
# Input packets are a stream of values related to energy flows (inputs?)
|
8
8
|
class ReadInput < Base
|
9
|
+
def initialize
|
10
|
+
super
|
11
|
+
|
12
|
+
self.tcp_function = TcpFunctions::TRANSLATED_DATA
|
13
|
+
self.device_function = DeviceFunctions::READ_INPUT
|
14
|
+
|
15
|
+
self.data_length = 18
|
16
|
+
end
|
17
|
+
|
9
18
|
def self.parse(ascii)
|
10
19
|
i = super
|
11
20
|
|
@@ -5,6 +5,13 @@ require_relative 'read_input'
|
|
5
5
|
class LXP
|
6
6
|
class Packet
|
7
7
|
class ReadInput2 < ReadInput
|
8
|
+
def initialize
|
9
|
+
super
|
10
|
+
|
11
|
+
self.register = 40
|
12
|
+
self.value = 40
|
13
|
+
end
|
14
|
+
|
8
15
|
# Decode the data and return a hash of values in this input packet
|
9
16
|
def to_h
|
10
17
|
{
|
@@ -38,6 +45,8 @@ class LXP
|
|
38
45
|
|
39
46
|
# 71..72 ?
|
40
47
|
|
48
|
+
# this actually seems to be cumulative runtime.
|
49
|
+
# not found an uptime since reboot yet.
|
41
50
|
uptime: Utils.int(@data[73, 4]) # seconds
|
42
51
|
}
|
43
52
|
end
|
data/lib/lxp/version.rb
CHANGED
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
|
+
version: 0.7.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-
|
11
|
+
date: 2020-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -96,8 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '0'
|
98
98
|
requirements: []
|
99
|
-
|
100
|
-
rubygems_version: 2.7.6.2
|
99
|
+
rubygems_version: 3.0.3
|
101
100
|
signing_key:
|
102
101
|
specification_version: 4
|
103
102
|
summary: Library to generate and parse LuxPower inverter packets
|