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 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