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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: da3886255a22b8e72fe4843c9d572b8f55d0f24f
4
- data.tar.gz: f13f7c99aa45592be334abe8d5fda818ed4a03a5
3
+ metadata.gz: b473b3ace06e05014aeebd7733497c6d81888edc
4
+ data.tar.gz: ac2690060bcb3352653117b86e97d693661f39bc
5
5
  SHA512:
6
- metadata.gz: 1edd50db73644cc37edb56cdf2ac87375f39af1945df6ad957eb5dea19976a5c4d1545297fc754b51d66bc5e5a8c19544a4fe35645d1565e9b203a95e30df39f
7
- data.tar.gz: 9146bc461808c8f06c56a5b3e256052ff95cb0ef0bb62d7eeac31e1b76e188f6f1ea560962a5a9a4efacab2b9ac4543ddcdc8bf6438c833894941cb0cc5e2ad7
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 naitive ruby, eventmachine, celluloid or the like.
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
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "knx"
5
- s.version = '1.0.0'
5
+ s.version = '1.0.1'
6
6
  s.authors = ["Stephen von Takach"]
7
7
  s.email = ["steve@cotag.me"]
8
8
  s.licenses = ["MIT"]
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: :send_datagram
18
+ msg_code: :data_indicator
18
19
  }
19
20
 
20
21
  def initialize(options = {})
@@ -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
  # ------------------------
@@ -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: 3,
11
- individual_read: 4,
12
- individual_resp: 5,
11
+ individual_write: 0x0C0,
12
+ individual_read: 0x100,
13
+ individual_resp: 0x140,
13
14
 
14
15
  adc_read: 6,
15
- adc_resp: 7,
16
+ adc_resp: 0x1C0,
16
17
 
17
- memory_read: 8,
18
- memory_resp: 9,
19
- memory_write: 10,
18
+ sys_net_param_read: 0x1C4,
19
+ sys_net_param_resp: 0x1C9,
20
+ sys_net_param_write: 0x1CA,
20
21
 
21
- user_msg: 11,
22
+ memory_read: 0x020,
23
+ memory_resp: 0x024,
24
+ memory_write: 0x028,
22
25
 
23
- descriptor_read: 12,
24
- descriptor_resp: 13,
26
+ user_memory_read: 0x2C0,
27
+ user_memory_resp: 0x2C1,
28
+ user_memory_write: 0x2C2,
25
29
 
26
- restart: 14,
27
- escape: 15
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
- send_datagram: 0x29
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
@@ -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
- data_array = self.data
8
+ resp = if @cemi.apply_apci(self.action_type, self.data) && self.data
9
+ @cemi.data_length = self.data.length
8
10
 
9
- resp = if present? data_array
10
- @cemi.data_length = data_array.length
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
- @cemi.data = 0
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
- @cemi.apci = @address.is_group? ? ActionType[:group_write] : ActionType[:individual_write]
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
- @cemi.apci = @address.is_group? ? ActionType[:group_read] : ActionType[:individual_read]
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)
@@ -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, value: 0x10 # Version 1
35
+ uint8 :version
29
36
  uint16 :request_type
30
37
  uint16 :request_length
31
38
  end
@@ -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
 
@@ -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
- "#{self.knx_header.to_binary_s}#{resp}"
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
@@ -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
@@ -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
@@ -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
@@ -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\x050\x00\x11)\x00\xBC\xE0\x00\x01\n\x00\x01\x00\x94")
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\x050\x00\x12)\x00\xBC\xE0\x00\x01\n\x00\x01\x00\x80\xF0")
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
@@ -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\x10\xF0\x80\x00\x15\x04\x00\x00\x00\xF0\x06\x00\x02\x00\x01\x00\x02\x03\x01\x01")
13
- expect(datagram.to_binary_s).to eq("\x06\x10\xF0\x80\x00\x15\x04\x00\x00\x00\xF0\x06\x00\x02\x00\x01\x00\x02\x03\x01\x01")
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\x10\xF0\x80\x00\x15\x04\x00\x00\x00\xF0\x06\x00\x01\x00\x01\x00\x01\x03\x01\x00")
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\x10\xF0\x80\x00\x15\x04\x00\x00\x00\xF0\x06\x00\x02\x00\x01\x00\x02\x03\x01\x01")
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\x10\xF0\x80\x00\x15\x04\x00\x00\x00\xF0\x06\x00\x03\x00\x01\x00\x03\x03\x01\x14")
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\x10\xF0\x80\x00\x15\x04\x00\x00\x00\xF0\x06\x00\x04\x00\x01\x00\x04\x03\x01\xF0")
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knx
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen von Takach