knx 1.0.0 → 1.0.1
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 +4 -4
- data/README.md +1 -1
- data/knx.gemspec +1 -1
- data/lib/knx.rb +3 -2
- data/lib/knx/address.rb +2 -1
- data/lib/knx/cemi.rb +137 -14
- data/lib/knx/datagram.rb +20 -23
- data/lib/knx/header.rb +10 -3
- data/lib/knx/object_server.rb +2 -1
- data/lib/knx/object_server/datagram.rb +6 -4
- data/lib/knx/object_server/object_header.rb +2 -1
- data/lib/knx/object_server/request_item.rb +2 -1
- data/lib/knx/object_server/status_item.rb +2 -1
- data/spec/knx_spec.rb +7 -2
- data/spec/object_server_spec.rb +13 -7
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b473b3ace06e05014aeebd7733497c6d81888edc
|
4
|
+
data.tar.gz: ac2690060bcb3352653117b86e97d693661f39bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1bc24fe48e8c95ec347067b8cd269ba2a0a772f1c19939d5546e1709c28c2312f58bae4df387f21a67f400d980278d1ddb81730f8fe85cdf64bcdadc67f9dd8c
|
7
|
+
data.tar.gz: 7d3762824d58560470b127dbfe3b533fccce6961c6d8c455099f5d8c2fe25f52de58493a75e3959f3f75578463cce644c46698b56bc3f7a5a2deee86cf563dfb
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Ruby KNX
|
2
2
|
|
3
3
|
Constructs [KNX standard](https://en.wikipedia.org/wiki/KNX_(standard)) datagrams that make it easy to communicate with devices on KNX networks.
|
4
|
-
It does not implement the transport layer so you can use it with
|
4
|
+
It does not implement the transport layer so you can use it with native ruby, eventmachine, celluloid or the like.
|
5
5
|
|
6
6
|
[](https://travis-ci.org/acaprojects/ruby-knx)
|
7
7
|
|
data/knx.gemspec
CHANGED
data/lib/knx.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#encoding: ASCII-8BIT
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'bindata'
|
4
5
|
|
@@ -14,7 +15,7 @@ class KNX
|
|
14
15
|
no_repeat: true,
|
15
16
|
broadcast: true,
|
16
17
|
hop_count: 6,
|
17
|
-
msg_code: :
|
18
|
+
msg_code: :data_indicator
|
18
19
|
}
|
19
20
|
|
20
21
|
def initialize(options = {})
|
data/lib/knx/address.rb
CHANGED
data/lib/knx/cemi.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#encoding: ASCII-8BIT
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
class KNX
|
4
5
|
# APCI type
|
@@ -7,25 +8,81 @@ class KNX
|
|
7
8
|
group_resp: 1,
|
8
9
|
group_write: 2,
|
9
10
|
|
10
|
-
individual_write:
|
11
|
-
individual_read:
|
12
|
-
individual_resp:
|
11
|
+
individual_write: 0x0C0,
|
12
|
+
individual_read: 0x100,
|
13
|
+
individual_resp: 0x140,
|
13
14
|
|
14
15
|
adc_read: 6,
|
15
|
-
adc_resp:
|
16
|
+
adc_resp: 0x1C0,
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
sys_net_param_read: 0x1C4,
|
19
|
+
sys_net_param_resp: 0x1C9,
|
20
|
+
sys_net_param_write: 0x1CA,
|
20
21
|
|
21
|
-
|
22
|
+
memory_read: 0x020,
|
23
|
+
memory_resp: 0x024,
|
24
|
+
memory_write: 0x028,
|
22
25
|
|
23
|
-
|
24
|
-
|
26
|
+
user_memory_read: 0x2C0,
|
27
|
+
user_memory_resp: 0x2C1,
|
28
|
+
user_memory_write: 0x2C2,
|
25
29
|
|
26
|
-
|
27
|
-
|
30
|
+
user_manufacturer_info_read: 0x2C5,
|
31
|
+
user_manufacturer_info_resp: 0x2C6,
|
32
|
+
|
33
|
+
function_property_command: 0x2C7,
|
34
|
+
function_property_state_read: 0x2C8,
|
35
|
+
function_property_state_resp: 0x2C9,
|
36
|
+
|
37
|
+
device_descriptor_read: 0x300,
|
38
|
+
device_descriptor_resp: 0x340,
|
39
|
+
|
40
|
+
restart: 0x380,
|
41
|
+
escape: 0x3C0, # Not sure about this one
|
42
|
+
|
43
|
+
authorize_request: 0x3D1,
|
44
|
+
authorize_resp: 0x3D2,
|
45
|
+
|
46
|
+
key_write: 0x3D3,
|
47
|
+
key_resp: 0x3D4,
|
48
|
+
|
49
|
+
property_value_read: 0x3D5,
|
50
|
+
property_value_resp: 0x3D6,
|
51
|
+
property_value_write: 0x3D7,
|
52
|
+
|
53
|
+
property_description_read: 0x3D8,
|
54
|
+
property_description_resp: 0x3D9,
|
55
|
+
|
56
|
+
network_param_read: 0x3DA,
|
57
|
+
network_param_resp: 0x3DB,
|
58
|
+
|
59
|
+
individual_serial_num_read: 0x3DC,
|
60
|
+
individual_serial_num_resp: 0x3DD,
|
61
|
+
individual_serial_num_write: 0x3DF,
|
62
|
+
|
63
|
+
domain_write: 0x3E0,
|
64
|
+
domain_read: 0x3E1,
|
65
|
+
domain_resp: 0x3E2,
|
66
|
+
domain_selective_read: 0x3E3,
|
67
|
+
|
68
|
+
network_param_write: 0x3E4,
|
69
|
+
|
70
|
+
link_read: 0x3E5,
|
71
|
+
link_resp: 0x3E6,
|
72
|
+
link_write: 0x3E7,
|
73
|
+
|
74
|
+
group_prop_value_read: 0x3E8,
|
75
|
+
group_prop_value_resp: 0x3E9,
|
76
|
+
group_prop_value_write: 0x3EA,
|
77
|
+
group_prop_value_info: 0x3EB,
|
78
|
+
|
79
|
+
domain_serial_num_read: 0x3EC,
|
80
|
+
domain_serial_num_resp: 0x3ED,
|
81
|
+
domain_serial_num_write: 0x3EE,
|
82
|
+
|
83
|
+
filesystem_info: 0x3F0
|
28
84
|
}
|
85
|
+
ActionType.merge!(ActionType.invert)
|
29
86
|
|
30
87
|
TpciType = {
|
31
88
|
unnumbered_data: 0b00,
|
@@ -35,7 +92,29 @@ class KNX
|
|
35
92
|
}
|
36
93
|
|
37
94
|
MsgCode = {
|
38
|
-
|
95
|
+
raw_request: 0x10,
|
96
|
+
data_request: 0x11,
|
97
|
+
poll_data_request: 0x13,
|
98
|
+
poll_data_connection: 0x25,
|
99
|
+
data_indicator: 0x29,
|
100
|
+
busmon_indicator: 0x2B,
|
101
|
+
raw_indicator: 0x2D,
|
102
|
+
data_connection: 0x2E,
|
103
|
+
raw_connection: 0x2F,
|
104
|
+
data_connection_request: 0x41,
|
105
|
+
data_individual_request: 0x4A,
|
106
|
+
data_connection_indicator: 0x89,
|
107
|
+
data_individual_indicator: 0x94,
|
108
|
+
reset_indicator: 0xF0,
|
109
|
+
reset_request: 0xF1,
|
110
|
+
propwrite_connection: 0xF5,
|
111
|
+
propwrite_request: 0xF6,
|
112
|
+
propinfo_indicator: 0xF7,
|
113
|
+
func_prop_com_request: 0xF8,
|
114
|
+
func_prop_stat_read_request: 0xF9,
|
115
|
+
func_prop_com_connection: 0xFA,
|
116
|
+
prop_read_connection: 0xFB,
|
117
|
+
prop_read_request: 0xFC
|
39
118
|
}
|
40
119
|
|
41
120
|
Priority = {
|
@@ -44,6 +123,20 @@ class KNX
|
|
44
123
|
high: 2,
|
45
124
|
low: 3
|
46
125
|
}
|
126
|
+
|
127
|
+
ErrorCodes = {
|
128
|
+
0x00 => "Unspecified Error",
|
129
|
+
0x01 => "Out of range",
|
130
|
+
0x02 => "Out of maxrange",
|
131
|
+
0x03 => "Out of minrange",
|
132
|
+
0x04 => "Memory Error",
|
133
|
+
0x05 => "Read only",
|
134
|
+
0x06 => "Illegal command",
|
135
|
+
0x07 => "Void DP",
|
136
|
+
0x08 => "Type conflict",
|
137
|
+
0x09 => "Prop. Index range error",
|
138
|
+
0x0A => "Value temporarily not writeable"
|
139
|
+
}
|
47
140
|
|
48
141
|
|
49
142
|
# CEMI == Common External Message Interface
|
@@ -216,5 +309,35 @@ class KNX
|
|
216
309
|
bit4 :tpci_seq_num # Sequence number when tpci is sequenced
|
217
310
|
bit4 :apci # application protocol control information (What we trying to do: Read, write, respond etc)
|
218
311
|
bit6 :data # Or the tail end of APCI depending on the message type
|
312
|
+
|
313
|
+
|
314
|
+
# Applies 2 byte APCI value where required
|
315
|
+
#
|
316
|
+
# @param val [Symbol, Fixnum, Integer] the value or symbol representing the APCI value
|
317
|
+
# @return [true, false] returns true if data is available for storage
|
318
|
+
def apply_apci(val, data = nil)
|
319
|
+
value = if val.is_a?(Symbol)
|
320
|
+
ActionType[val]
|
321
|
+
else
|
322
|
+
val
|
323
|
+
end
|
324
|
+
|
325
|
+
if value > 15
|
326
|
+
self.apci = (value >> 6) & 0b1111
|
327
|
+
self.data = value & 0b111111
|
328
|
+
false
|
329
|
+
else
|
330
|
+
self.apci = value
|
331
|
+
if data && data[0] && data[0] <= 0b111111
|
332
|
+
self.data = data[0]
|
333
|
+
true
|
334
|
+
else
|
335
|
+
self.data = 0
|
336
|
+
false
|
337
|
+
end
|
338
|
+
end
|
339
|
+
rescue => e
|
340
|
+
raise ArgumentError, "Bad apci value: #{data}"
|
341
|
+
end
|
219
342
|
end
|
220
343
|
end
|
data/lib/knx/datagram.rb
CHANGED
@@ -1,27 +1,22 @@
|
|
1
|
-
#encoding: ASCII-8BIT
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
class KNX
|
4
|
-
DatagramBuilder = Struct.new(:header, :cemi, :source_address, :destination_address, :data) do
|
5
|
+
DatagramBuilder = Struct.new(:header, :cemi, :source_address, :destination_address, :data, :action_type) do
|
5
6
|
|
6
7
|
def to_binary_s
|
7
|
-
|
8
|
+
resp = if @cemi.apply_apci(self.action_type, self.data) && self.data
|
9
|
+
@cemi.data_length = self.data.length
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
if data_array[0] <= 0b111111
|
13
|
-
@cemi.data = data_array[0]
|
14
|
-
if data_array.length > 1
|
15
|
-
data_array[1..-1].pack('C')
|
16
|
-
else
|
17
|
-
String.new
|
18
|
-
end
|
11
|
+
if self.data.length > 1
|
12
|
+
self.data[1..-1].pack('C')
|
19
13
|
else
|
20
|
-
|
21
|
-
data_array.pack('C')
|
14
|
+
String.new
|
22
15
|
end
|
16
|
+
elsif present?(self.data)
|
17
|
+
@cemi.data_length = self.data.length
|
18
|
+
self.data.pack('C')
|
23
19
|
else
|
24
|
-
@cemi.data = 0
|
25
20
|
@cemi.data_length = 0
|
26
21
|
String.new
|
27
22
|
end
|
@@ -59,6 +54,7 @@ class KNX
|
|
59
54
|
@cemi.hop_count = options[:hop_count]
|
60
55
|
|
61
56
|
@header = Header.new
|
57
|
+
@header.version = 0x10
|
62
58
|
if options[:request_type]
|
63
59
|
@header.request_type = RequestTypes[options[:request_type]]
|
64
60
|
else
|
@@ -93,15 +89,11 @@ class KNX
|
|
93
89
|
super(address, options)
|
94
90
|
|
95
91
|
# Set the protocol control information
|
96
|
-
|
92
|
+
self.action_type = @address.is_group? ? :group_write : :individual_write
|
93
|
+
@cemi.apply_apci(self.action_type, data_array)
|
97
94
|
@cemi.tpci = TpciType[:unnumbered_data]
|
98
95
|
|
99
|
-
# To attempt save a byte we try to cram the first byte into the APCI field
|
100
96
|
if present? data_array
|
101
|
-
if data_array[0] <= 0b111111
|
102
|
-
@cemi.data = data_array[0]
|
103
|
-
end
|
104
|
-
|
105
97
|
@cemi.data_length = data_array.length
|
106
98
|
self.data = data_array
|
107
99
|
end
|
@@ -113,7 +105,8 @@ class KNX
|
|
113
105
|
super(address, options)
|
114
106
|
|
115
107
|
# Set the protocol control information
|
116
|
-
|
108
|
+
self.action_type = @address.is_group? ? :group_read : :individual_read
|
109
|
+
@cemi.apply_apci(self.action_type)
|
117
110
|
@cemi.tpci = TpciType[:unnumbered_data]
|
118
111
|
end
|
119
112
|
end
|
@@ -134,6 +127,10 @@ class KNX
|
|
134
127
|
self.data = raw_data[17..(@header.request_length - 1)].bytes
|
135
128
|
if @cemi.data_length > self.data.length
|
136
129
|
self.data.unshift @cemi.data
|
130
|
+
self.action_type = ActionType[@cemi.apci]
|
131
|
+
else
|
132
|
+
acpi = @cemi.data | (@cemi.apci << 6)
|
133
|
+
self.action_type = ActionType[acpi] || ActionType[@cemi.apci]
|
137
134
|
end
|
138
135
|
|
139
136
|
self.source_address = IndividualAddress.parse(@cemi.source_address.to_i)
|
data/lib/knx/header.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#encoding: ASCII-8BIT
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
class KNX
|
4
5
|
RequestTypes = {
|
@@ -17,7 +18,13 @@ class KNX
|
|
17
18
|
tunnelling_request: 0x0420,
|
18
19
|
tunnelling_ack: 0x0421,
|
19
20
|
routing_indication: 0x0530,
|
20
|
-
routing_lost_message: 0x0531
|
21
|
+
routing_lost_message: 0x0531,
|
22
|
+
|
23
|
+
routing_busy: 0x0532,
|
24
|
+
remote_diagnostic_request: 0x0740,
|
25
|
+
remote_diagnostic_response: 0x0741,
|
26
|
+
remote_basic_config_request: 0x0742,
|
27
|
+
remote_reset_request: 0x0743
|
21
28
|
}
|
22
29
|
|
23
30
|
# http://www.openremote.org/display/forums/KNX+IP+Connection+Headers
|
@@ -25,7 +32,7 @@ class KNX
|
|
25
32
|
endian :big
|
26
33
|
|
27
34
|
uint8 :header_length, value: 0x06 # Length 6 (always for version 1)
|
28
|
-
uint8 :version
|
35
|
+
uint8 :version
|
29
36
|
uint16 :request_type
|
30
37
|
uint16 :request_length
|
31
38
|
end
|
data/lib/knx/object_server.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#encoding: ASCII-8BIT
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
class KNX
|
4
5
|
class ObjectServer
|
@@ -64,14 +65,15 @@ class KNX
|
|
64
65
|
|
65
66
|
def to_binary_s
|
66
67
|
self.header.item_count = @data.length if @data.length > 0
|
67
|
-
resp = "#{self.connection.to_binary_s}#{self.header.to_binary_s}"
|
68
|
+
resp = String.new "#{self.connection.to_binary_s}#{self.header.to_binary_s}"
|
68
69
|
|
69
70
|
@data.each do |item|
|
70
71
|
resp << item.to_binary_s
|
71
72
|
end
|
72
73
|
|
73
74
|
self.knx_header.request_length = resp.length + 6
|
74
|
-
|
75
|
+
resp.prepend self.knx_header.to_binary_s
|
76
|
+
resp
|
75
77
|
end
|
76
78
|
|
77
79
|
def add_action(index, data: nil, **options)
|
@@ -86,7 +88,7 @@ class KNX
|
|
86
88
|
if data.is_a? String
|
87
89
|
req.value = data
|
88
90
|
else
|
89
|
-
req.value =
|
91
|
+
req.value = String.new
|
90
92
|
req.value << data
|
91
93
|
end
|
92
94
|
end
|
data/spec/knx_spec.rb
CHANGED
@@ -30,9 +30,14 @@ describe "knx protocol helper" do
|
|
30
30
|
|
31
31
|
it "should generate byte action requests" do
|
32
32
|
datagram = @knx.action('1/2/0', 20)
|
33
|
-
expect(datagram.to_binary_s).to eq("\x06\x10\
|
33
|
+
expect(datagram.to_binary_s).to eq("\x06\x10\x05\x30\x00\x11\x29\x00\xBC\xE0\x00\x01\n\x00\x01\x00\x94")
|
34
34
|
|
35
35
|
datagram = @knx.action('1/2/0', 240)
|
36
|
-
expect(datagram.to_binary_s).to eq("\x06\x10\
|
36
|
+
expect(datagram.to_binary_s).to eq("\x06\x10\x05\x30\x00\x12\x29\x00\xBC\xE0\x00\x01\n\x00\x01\x00\x80\xF0")
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should generate status requests" do
|
40
|
+
datagram = @knx.status('1/2/1')
|
41
|
+
expect(datagram.to_binary_s).to eq("\x06\x10\x05\x30\x00\x11\x29\x00\xBC\xE0\x00\x01\n\x01\x00\x00\x00")
|
37
42
|
end
|
38
43
|
end
|
data/spec/object_server_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#encoding: ASCII-8BIT
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'knx/object_server'
|
4
5
|
|
@@ -9,8 +10,8 @@ describe "object server protocol helper" do
|
|
9
10
|
end
|
10
11
|
|
11
12
|
it "should parse and generate the same string" do
|
12
|
-
datagram = @knx.read("\x06\
|
13
|
-
expect(datagram.to_binary_s).to eq("\x06\
|
13
|
+
datagram = @knx.read("\x06\x20\xF0\x80\x00\x15\x04\x00\x00\x00\xF0\x06\x00\x02\x00\x01\x00\x02\x03\x01\x01")
|
14
|
+
expect(datagram.to_binary_s).to eq("\x06\x20\xF0\x80\x00\x15\x04\x00\x00\x00\xF0\x06\x00\x02\x00\x01\x00\x02\x03\x01\x01")
|
14
15
|
|
15
16
|
expect(datagram.data[0].id).to eq(2)
|
16
17
|
expect(datagram.data[0].value).to eq("\x01")
|
@@ -18,17 +19,22 @@ describe "object server protocol helper" do
|
|
18
19
|
|
19
20
|
it "should generate single bit action requests" do
|
20
21
|
datagram = @knx.action(1, false)
|
21
|
-
expect(datagram.to_binary_s).to eq("\x06\
|
22
|
+
expect(datagram.to_binary_s).to eq("\x06\x20\xF0\x80\x00\x15\x04\x00\x00\x00\xF0\x06\x00\x01\x00\x01\x00\x01\x03\x01\x00")
|
22
23
|
|
23
24
|
datagram = @knx.action(2, true)
|
24
|
-
expect(datagram.to_binary_s).to eq("\x06\
|
25
|
+
expect(datagram.to_binary_s).to eq("\x06\x20\xF0\x80\x00\x15\x04\x00\x00\x00\xF0\x06\x00\x02\x00\x01\x00\x02\x03\x01\x01")
|
25
26
|
end
|
26
27
|
|
27
28
|
it "should generate byte action requests" do
|
28
29
|
datagram = @knx.action(3, 20)
|
29
|
-
expect(datagram.to_binary_s).to eq("\x06\
|
30
|
+
expect(datagram.to_binary_s).to eq("\x06\x20\xF0\x80\x00\x15\x04\x00\x00\x00\xF0\x06\x00\x03\x00\x01\x00\x03\x03\x01\x14")
|
30
31
|
|
31
32
|
datagram = @knx.action(4, 240)
|
32
|
-
expect(datagram.to_binary_s).to eq("\x06\
|
33
|
+
expect(datagram.to_binary_s).to eq("\x06\x20\xF0\x80\x00\x15\x04\x00\x00\x00\xF0\x06\x00\x04\x00\x01\x00\x04\x03\x01\xF0")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should generate status requests" do
|
37
|
+
datagram = @knx.status(3)
|
38
|
+
expect(datagram.to_binary_s).to eq("\x06\x20\xF0\x80\x00\x11\x04\x00\x00\x00\xF0\x05\x00\x03\x00\x01\x01")
|
33
39
|
end
|
34
40
|
end
|