knx 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/acaprojects/ruby-knx.svg?branch=master)](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
|