lifx-lan 0.1.0

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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +8 -0
  4. data/.yardopts +3 -0
  5. data/CHANGES.md +45 -0
  6. data/Gemfile +19 -0
  7. data/LICENSE.txt +23 -0
  8. data/README.md +15 -0
  9. data/Rakefile +20 -0
  10. data/bin/lifx-snoop +50 -0
  11. data/examples/auto-off/auto-off.rb +34 -0
  12. data/examples/blink/blink.rb +19 -0
  13. data/examples/identify/identify.rb +69 -0
  14. data/examples/travis-build-light/build-light.rb +57 -0
  15. data/lib/bindata_ext/bool.rb +30 -0
  16. data/lib/bindata_ext/record.rb +11 -0
  17. data/lib/lifx-lan.rb +27 -0
  18. data/lib/lifx/lan/client.rb +149 -0
  19. data/lib/lifx/lan/color.rb +199 -0
  20. data/lib/lifx/lan/config.rb +17 -0
  21. data/lib/lifx/lan/firmware.rb +60 -0
  22. data/lib/lifx/lan/gateway_connection.rb +185 -0
  23. data/lib/lifx/lan/light.rb +440 -0
  24. data/lib/lifx/lan/light_collection.rb +111 -0
  25. data/lib/lifx/lan/light_target.rb +185 -0
  26. data/lib/lifx/lan/logging.rb +14 -0
  27. data/lib/lifx/lan/message.rb +168 -0
  28. data/lib/lifx/lan/network_context.rb +188 -0
  29. data/lib/lifx/lan/observable.rb +66 -0
  30. data/lib/lifx/lan/protocol/address.rb +25 -0
  31. data/lib/lifx/lan/protocol/device.rb +387 -0
  32. data/lib/lifx/lan/protocol/header.rb +24 -0
  33. data/lib/lifx/lan/protocol/light.rb +142 -0
  34. data/lib/lifx/lan/protocol/message.rb +19 -0
  35. data/lib/lifx/lan/protocol/metadata.rb +23 -0
  36. data/lib/lifx/lan/protocol/payload.rb +12 -0
  37. data/lib/lifx/lan/protocol/sensor.rb +31 -0
  38. data/lib/lifx/lan/protocol/type.rb +204 -0
  39. data/lib/lifx/lan/protocol/wan.rb +51 -0
  40. data/lib/lifx/lan/protocol/wifi.rb +102 -0
  41. data/lib/lifx/lan/protocol_path.rb +85 -0
  42. data/lib/lifx/lan/required_keyword_arguments.rb +12 -0
  43. data/lib/lifx/lan/routing_manager.rb +114 -0
  44. data/lib/lifx/lan/routing_table.rb +48 -0
  45. data/lib/lifx/lan/seen.rb +25 -0
  46. data/lib/lifx/lan/site.rb +97 -0
  47. data/lib/lifx/lan/tag_manager.rb +111 -0
  48. data/lib/lifx/lan/tag_table.rb +49 -0
  49. data/lib/lifx/lan/target.rb +24 -0
  50. data/lib/lifx/lan/thread.rb +13 -0
  51. data/lib/lifx/lan/timers.rb +29 -0
  52. data/lib/lifx/lan/transport.rb +46 -0
  53. data/lib/lifx/lan/transport/tcp.rb +91 -0
  54. data/lib/lifx/lan/transport/udp.rb +87 -0
  55. data/lib/lifx/lan/transport_manager.rb +43 -0
  56. data/lib/lifx/lan/transport_manager/lan.rb +169 -0
  57. data/lib/lifx/lan/utilities.rb +36 -0
  58. data/lib/lifx/lan/version.rb +5 -0
  59. data/lifx-lan.gemspec +26 -0
  60. data/spec/color_spec.rb +43 -0
  61. data/spec/gateway_connection_spec.rb +30 -0
  62. data/spec/integration/client_spec.rb +42 -0
  63. data/spec/integration/light_spec.rb +56 -0
  64. data/spec/integration/tags_spec.rb +42 -0
  65. data/spec/light_collection_spec.rb +37 -0
  66. data/spec/message_spec.rb +183 -0
  67. data/spec/protocol_path_spec.rb +109 -0
  68. data/spec/routing_manager_spec.rb +25 -0
  69. data/spec/routing_table_spec.rb +23 -0
  70. data/spec/spec_helper.rb +56 -0
  71. data/spec/transport/udp_spec.rb +44 -0
  72. data/spec/transport_spec.rb +14 -0
  73. metadata +187 -0
@@ -0,0 +1,188 @@
1
+ require 'lifx/lan/timers'
2
+ require 'lifx/lan/transport_manager'
3
+ require 'lifx/lan/routing_manager'
4
+ require 'lifx/lan/tag_manager'
5
+ require 'lifx/lan/light'
6
+ require 'lifx/lan/protocol_path'
7
+ require 'lifx/lan/timers'
8
+
9
+ require 'weakref'
10
+
11
+ module LIFX
12
+ module LAN
13
+ class NetworkContext
14
+ include Logging
15
+ include Utilities
16
+ include RequiredKeywordArguments
17
+ include Timers
18
+ extend Forwardable
19
+
20
+ # NetworkContext stores lights and ties together TransportManager, TagManager and RoutingManager
21
+ attr_reader :transport_manager, :tag_manager, :routing_manager
22
+
23
+ def initialize(transport_manager: required!('transport_manager'))
24
+ @devices = {}
25
+
26
+ @transport_manager = transport_manager
27
+ @transport_manager.context = WeakRef.new(self)
28
+ @transport_manager.add_observer(self, :message_received) do |message: nil, ip: nil, transport: nil|
29
+ handle_message(message, ip, transport)
30
+ end
31
+
32
+ reset!
33
+
34
+ @threads = []
35
+ @threads << initialize_timer_thread
36
+ end
37
+
38
+ def discover
39
+ @transport_manager.discover
40
+ end
41
+
42
+ def refresh(force: true)
43
+ @routing_manager.refresh(force: force)
44
+ end
45
+
46
+ def reset!
47
+ @routing_manager = RoutingManager.new(context: self)
48
+ @tag_manager = TagManager.new(context: self, tag_table: @routing_manager.tag_table)
49
+ end
50
+
51
+ def stop_discovery
52
+ @transport_manager.stop_discovery
53
+ end
54
+
55
+ def stop
56
+ @transport_manager.stop
57
+ stop_timers
58
+ @threads.each do |thread|
59
+ thread.abort
60
+ thread.join
61
+ end
62
+ @threads = nil
63
+ end
64
+
65
+ # Sends a message to their destination(s)
66
+ # @param target: [Target] Target of the message
67
+ # @param payload: [Protocol::Payload] Message payload
68
+ # @param acknowledge: [Boolean] If recipients must acknowledge with a response
69
+ # @param at_time: [Integer] Unix epoch in milliseconds to run the payload. Only applicable to certain payload types.
70
+ def send_message(target: required!(:target), payload: required!(:payload), acknowledge: false, at_time: nil)
71
+ paths = @routing_manager.resolve_target(target)
72
+
73
+ messages = paths.map do |path|
74
+ Message.new(path: path, payload: payload, acknowledge: acknowledge, at_time: at_time)
75
+ end
76
+
77
+ if within_sync?
78
+ Thread.current[:sync_messages].push(*messages)
79
+ return
80
+ end
81
+
82
+ messages.each do |message|
83
+ @transport_manager.write(message)
84
+ end
85
+ end
86
+
87
+ def within_sync?
88
+ !!Thread.current[:sync_enabled]
89
+ end
90
+ protected :within_sync?
91
+
92
+ # Synchronize asynchronous set_color, set_waveform and set_power messages to multiple devices.
93
+ # You cannot use synchronous methods in the block
94
+ # @note This is alpha
95
+ # @param delay: [Float] The delay to add to sync commands when dealing with latency.
96
+ # @yield Block to synchronize commands in
97
+ # @return [Float] Delay before messages are executed
98
+ NSEC_PER_SEC = 1_000_000_000
99
+ AT_TIME_DELTA = 0.002
100
+ def sync(delay: 0, &block)
101
+ if within_sync?
102
+ raise "You cannot nest sync"
103
+ end
104
+ messages = Thread.start do
105
+ Thread.current[:sync_enabled] = true
106
+ Thread.current[:sync_messages] = messages = []
107
+ block.call
108
+ Thread.current[:sync_enabled] = false
109
+ messages
110
+ end.join.value
111
+
112
+ time = nil
113
+ failed_lights = []
114
+ try_until -> { time }, timeout: 5, action_interval: 1 do
115
+ light = (lights.alive - failed_lights).sample
116
+ begin
117
+ time = light && light.send_message!(Protocol::Device::GetTime.new, wait_for: Protocol::Device::StateTime, wait_timeout: 1, retry_interval: 0.5) do |payload|
118
+ Time.at(payload.time.to_f / NSEC_PER_SEC)
119
+ end
120
+ rescue => ex
121
+ logger.debug("!!! Add light failed")
122
+ failed_lights << light
123
+ end
124
+ end
125
+
126
+ delay += (messages.count + 1) * (1.0 / @transport_manager.message_rate)
127
+ at_time = ((time.to_f + delay) * NSEC_PER_SEC).to_i
128
+ messages.each_with_index do |m, i|
129
+ m.at_time = at_time + (i * AT_TIME_DELTA * NSEC_PER_SEC).to_i
130
+ @transport_manager.write(m)
131
+ end
132
+ flush
133
+ delay
134
+ end
135
+
136
+ def flush(**options)
137
+ @transport_manager.flush(**options)
138
+ end
139
+
140
+ def register_device(device)
141
+ return if device.site_id == NULL_SITE_ID
142
+ device_id = device.id
143
+ @devices[device_id] = device # What happens when there's already one registered?
144
+ end
145
+
146
+ def lights
147
+ LightCollection.new(context: self)
148
+ end
149
+
150
+ def all_lights
151
+ @devices.values
152
+ end
153
+
154
+ # Tags
155
+
156
+ def_delegators :@tag_manager, :tags,
157
+ :unused_tags,
158
+ :purge_unused_tags!,
159
+ :add_tag_to_device,
160
+ :remove_tag_from_device
161
+
162
+ def tags_for_device(device)
163
+ @routing_manager.tags_for_device_id(device.id)
164
+ end
165
+
166
+ def to_s
167
+ %Q{#<LIFX::LAN::NetworkContext transport_manager=#{transport_manager}>}
168
+ end
169
+ alias_method :inspect, :to_s
170
+
171
+ protected
172
+
173
+ def handle_message(message, ip, transport)
174
+ logger.debug("<- #{self} #{transport}: #{message}")
175
+
176
+ @routing_manager.update_from_message(message)
177
+ if !message.tagged?
178
+ if @devices[message.device_id].nil? && message.payload.is_a?(Protocol::Light::State)
179
+ device = Light.new(context: self, id: message.device_id, site_id: message.site_id)
180
+ end
181
+ device = @devices[message.device_id]
182
+ return if !device # Virgin bulb
183
+ device.handle_message(message, ip, transport)
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,66 @@
1
+ module LIFX
2
+ module LAN
3
+ # @private
4
+ module Observable
5
+ class ObserverCallbackMismatch < ArgumentError; end
6
+ class ObserverCallbackNotFound < ArgumentError; end
7
+
8
+ def add_observer(obj, type, &callback)
9
+ if !callback_type_exists?(type)
10
+ raise ObserverCallbackNotFound.new("Callback #{type} not found in #{observer_callback_definition.keys}")
11
+ end
12
+ if !callback_has_required_keys?(type, callback)
13
+ raise ObserverCallbackMismatch.new
14
+ end
15
+ observers[type][obj] = callback
16
+ end
17
+
18
+ def remove_observer(obj, type)
19
+ observers[type].delete(obj)
20
+ end
21
+
22
+ def remove_observers
23
+ observers.clear
24
+ end
25
+
26
+ def notify_observers(type, **args)
27
+ if !callback_type_exists?(type)
28
+ raise ObserverCallbackNotFound.new("Callback #{type} not found in #{observer_callback_definition.keys}")
29
+ end
30
+ observers[type].each do |_, callback|
31
+ callback.call(**args)
32
+ end
33
+ end
34
+
35
+ def callback_type_exists?(type)
36
+ !!observer_callback_definition[type]
37
+ end
38
+
39
+ def callback_has_required_keys?(type, callback)
40
+ (required_keys_for_callback(type) - required_keys_in_proc(callback)).empty?
41
+ end
42
+
43
+ def observer_callback_definition
44
+ {}
45
+ end
46
+
47
+ def required_keys_for_callback(type)
48
+ @_required_keys_for_callback ||= {}
49
+ @_required_keys_for_callback[type] ||= begin
50
+ return [] if !observer_callback_definition[type]
51
+ required_keys_in_proc(observer_callback_definition[type])
52
+ end
53
+ end
54
+
55
+ def required_keys_in_proc(proc)
56
+ proc.parameters.select do |type, _|
57
+ type == :keyreq
58
+ end.map(&:last)
59
+ end
60
+
61
+ def observers
62
+ @_observers ||= Hash.new { |h, k| h[k] = {} }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,25 @@
1
+ module LIFX
2
+ module LAN
3
+ module Protocol
4
+ module AddressFields
5
+ def AddressFields.included(mod)
6
+ mod.instance_eval do
7
+ hide :_reserved2
8
+ string :raw_target, length: 8
9
+ string :raw_site, length: 6 # Deprecated, should be zeros or "LIFXV2"
10
+ bit6le :_reserved2
11
+ bool_bit1 :acknowledge # Acknowledgement required
12
+ bool_bit1 :res_required # Response required
13
+ uint8 :sequence # Wrap around message sequence number
14
+ end
15
+ end
16
+ end
17
+
18
+ class Address < BinData::Record
19
+ endian :little
20
+
21
+ include AddressFields
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,387 @@
1
+ module LIFX
2
+ module LAN
3
+ module Protocol
4
+ # @api private
5
+ module Device
6
+
7
+ # Describes the services exposed by the device
8
+ module Service
9
+ UDP = 1 # Datagram transport on specified port
10
+ TCP = 2 # Reliable transport on specified port
11
+ ONBOARDING = 3 # Onboarding service on specified port
12
+ OTA = 4 # Active over-the-air update service on specified port. This is only exposed when the device is in active OTA mode with degraded service
13
+ end
14
+
15
+ # class SetSite < Payload
16
+ # endian :little
17
+
18
+ # string :site, length: 6
19
+ # end
20
+
21
+
22
+ # class GetPanGateway < Payload
23
+ # endian :little
24
+
25
+ # end
26
+
27
+ # class StatePanGateway < Payload
28
+ # endian :little
29
+
30
+ # uint8 :service
31
+ # uint32 :port
32
+ # end
33
+
34
+ # Discover devices and services exposed
35
+ class GetService < Payload
36
+ endian :little
37
+ end
38
+
39
+ # Response to GetService. Specifies service and port. Service temporarily unavailable if port is 0
40
+ class StateService < Payload
41
+ endian :little
42
+
43
+ uint8 :service # maps to Service enumerated type above
44
+ uint32 :port
45
+ end
46
+
47
+ # Gets device current time
48
+ class GetTime < Payload
49
+ endian :little
50
+ end
51
+
52
+ # Set the device time
53
+ class SetTime < Payload
54
+ endian :little
55
+
56
+ uint64 :time # Nanoseconds since epoch.
57
+ end
58
+
59
+ # Returns the time at device when message was sent
60
+ class StateTime < Payload
61
+ endian :little
62
+
63
+ uint64 :time # Nanoseconds since epoch.
64
+ end
65
+
66
+ # class GetResetSwitch < Payload
67
+ # endian :little
68
+
69
+ # end
70
+
71
+ # class StateResetSwitch < Payload
72
+ # endian :little
73
+
74
+ # uint8 :position
75
+ # end
76
+
77
+ # class GetMeshInfo < Payload
78
+ # endian :little
79
+
80
+ # end
81
+
82
+ # class StateMeshInfo < Payload
83
+ # endian :little
84
+
85
+ # float :signal # Milliwatts.
86
+ # uint32 :tx # Bytes.
87
+ # uint32 :rx # Bytes.
88
+ # int16 :mcu_temperature # Deci-celsius. 25.45 celsius is 2545
89
+ # end
90
+
91
+ # class GetMeshFirmware < Payload
92
+ # endian :little
93
+
94
+ # end
95
+
96
+ # class StateMeshFirmware < Payload
97
+ # endian :little
98
+
99
+ # uint64 :build # Firmware build nanoseconds since epoch.
100
+ # uint64 :install # Firmware install nanoseconds since epoch.
101
+ # uint32 :version # Firmware human readable version.
102
+ # end
103
+
104
+ # Get Host MCU information
105
+ class GetHostInfo < Payload
106
+ endian :little
107
+ end
108
+
109
+ # Host MCU information
110
+ class StateHostInfo < Payload
111
+ endian :little
112
+
113
+ float :signal # radio receive signal strength in Milliwatts (mw)
114
+ uint32 :tx # bytes transmitted since power on
115
+ uint32 :rx # bytes received since power on
116
+ int16 :mcu_temperature # internal temperature (deci-celsius). 25.45 celsius is 2545
117
+ end
118
+
119
+ # Gets Host MCU firmware
120
+ class GetHostFirmware < Payload
121
+ endian :little
122
+ end
123
+
124
+ class StateHostFirmware < Payload
125
+ endian :little
126
+
127
+ uint64 :build # build time in nanonseconds since epoch
128
+ uint64 :install # install time, depricated
129
+ uint32 :version # firmware version
130
+ end
131
+
132
+ # Get Wifi subsystem information
133
+ class GetWifiInfo < Payload
134
+ endian :little
135
+ end
136
+
137
+ # Wifi subsystem information
138
+ class StateWifiInfo < Payload
139
+ endian :little
140
+
141
+ float :signal # radio receive signal strength in Milliwatts (mw)
142
+ uint32 :tx # bytes transmitted since power on
143
+ uint32 :rx # bytes received since power on
144
+ int16 :mcu_temperature # internal temperature (deci-celsius). 25.45 celsius is 2545
145
+ end
146
+
147
+ # Get Wifi subsystem firmware
148
+ class GetWifiFirmware < Payload
149
+ endian :little
150
+ end
151
+
152
+ class StateWifiFirmware < Payload
153
+ endian :little
154
+
155
+ uint64 :build # build time in nanonseconds since epoch
156
+ uint64 :install # install time, depricated
157
+ uint32 :version # firmware version
158
+ end
159
+
160
+ # Get device power level
161
+ class GetPower < Payload
162
+ endian :little
163
+ end
164
+
165
+ # Set device power level
166
+ class SetPower < Payload
167
+ endian :little
168
+
169
+ uint16 :level # Zero implies standby and non-zero sets a corresponding power draw level on device. Currently only 0 and 65535 are supported
170
+ end
171
+
172
+ # Device power level
173
+ class StatePower < Payload
174
+ endian :little
175
+
176
+ uint16 :level # 0 Standby. > 0 On.
177
+ end
178
+
179
+ # Introspect the device label. Returns StateLabel
180
+ class GetLabel < Payload
181
+ endian :little
182
+ end
183
+
184
+ # Set the device label text
185
+ class SetLabel < Payload
186
+ endian :little
187
+
188
+ string :label, length: 32, trim_padding: true
189
+ end
190
+
191
+ # Device label
192
+ class StateLabel < Payload
193
+ endian :little
194
+
195
+ string :label, length: 32, trim_padding: true
196
+ end
197
+
198
+ # Get the device tags. Returns StateTags
199
+ class GetTags < Payload
200
+ endian :little
201
+ end
202
+
203
+ # Set the device tags
204
+ class SetTags < Payload
205
+ endian :little
206
+
207
+ uint64 :tags # Bitfield, allows 64 tags. A device can be tagged with one or more of them
208
+ end
209
+
210
+ # Device tags
211
+ class StateTags < Payload
212
+ endian :little
213
+
214
+ uint64 :tags
215
+ end
216
+
217
+ # Get the text labels describing the tags. Returns StateTagLabels
218
+ class GetTagLabels < Payload
219
+ endian :little
220
+
221
+ uint64 :tags # Retrieve the labels for tags selected in the bitfield
222
+ end
223
+
224
+ # Sets the text label for the corresponding tags. Note you can label or reset multiple labels at the same time
225
+ class SetTagLabels < Payload
226
+ endian :little
227
+
228
+ uint64 :tags # Set labels for tags selected in the bitfield
229
+ string :label, length: 32, trim_padding: true
230
+ end
231
+
232
+ # The tag label for tags in the bitfield
233
+ class StateTagLabels < Payload
234
+ endian :little
235
+
236
+ uint64 :tags
237
+ string :label, length: 32, trim_padding: true
238
+ end
239
+
240
+ # Get the hardware version. Returns StateVersion
241
+ class GetVersion < Payload
242
+ endian :little
243
+ end
244
+
245
+ # The hardware version of the device
246
+ class StateVersion < Payload
247
+ endian :little
248
+
249
+ uint32 :vendor # Vendor ID
250
+ uint32 :product # Product ID
251
+ uint32 :version # Hardware version
252
+ end
253
+
254
+ # Get runtime information. Returns StateInfo
255
+ class GetInfo < Payload
256
+ endian :little
257
+ end
258
+
259
+ # Runtime information of device
260
+ class StateInfo < Payload
261
+ endian :little
262
+
263
+ uint64 :time # wallclock time on device, nanoseconds since epoch
264
+ uint64 :uptime # device uptime, nanoseconds since boot
265
+ uint64 :downtime # last measured downtime, accurate to 5s, nanoseconds off last power cycle
266
+ end
267
+
268
+ # class GetMcuRailVoltage < Payload
269
+ # endian :little
270
+ # end
271
+
272
+ # class StateMcuRailVoltage < Payload
273
+ # endian :little
274
+
275
+ # uint32 :voltage
276
+ # end
277
+
278
+ # class Reboot < Payload
279
+ # endian :little
280
+ # end
281
+
282
+ # Soft reboot the device in 1000ms from time received
283
+ class SetReboot < Payload
284
+ endian :little
285
+ end
286
+
287
+ # Sent back if reboot is pending on SetReboot
288
+ class StateReboot < Payload
289
+ endian :little
290
+ end
291
+
292
+ # Sent back from device on receipt of a message with ack_required set to 1
293
+ class Acknowledgement < Payload
294
+ endian :little
295
+ end
296
+
297
+ # Clear all settings from the device
298
+ class SetFactoryReset < Payload
299
+ endian :little
300
+ end
301
+
302
+ # Signifies pending reset
303
+ class StateFactoryReset < Payload
304
+ endian :little
305
+ end
306
+
307
+ # Get the device location
308
+ class GetLocation < Payload
309
+ endian :little
310
+ end
311
+
312
+ # Set the device location
313
+ class SetLocation < Payload
314
+ endian :little
315
+
316
+ string :location, length: 16 # guid byte array
317
+ string :label, length: 32, trim_padding: true # text label for location
318
+ end
319
+
320
+ # Device location
321
+ class StateLocation < Payload
322
+ endian :little
323
+
324
+ string :location, length: 16 # guid byte array
325
+ string :label, length: 32, trim_padding: true # text label for location
326
+ end
327
+
328
+ # Get the device group. A device can belong to a single logical group
329
+ class GetGroup < Payload
330
+ endian :little
331
+ end
332
+
333
+ # Set the device group
334
+ class SetGroup < Payload
335
+ endian :little
336
+
337
+ string :group, length: 16 # guid byte array
338
+ string :label, length: 32, trim_padding: true # text label for group
339
+ end
340
+
341
+ # Set the device group
342
+ class StateGroup < Payload
343
+ endian :little
344
+
345
+ string :group, length: 16 # guid byte array
346
+ string :label, length: 32, trim_padding: true # text label for group
347
+ end
348
+
349
+ # Introspect the device owner. Returns StateOwner
350
+ class GetOwner < Payload
351
+ endian :little
352
+ end
353
+
354
+ # Set the device owner
355
+ class SetOwner < Payload
356
+ endian :little
357
+
358
+ string :owner, length: 16 # guid byte array
359
+ string :label, length: 32, trim_padding: true # text label of owner details
360
+ end
361
+
362
+ # Return the device owner information
363
+ class StateOwner < Payload
364
+ endian :little
365
+
366
+ string :owner, length: 16 # guid byte array
367
+ string :label, length: 32, trim_padding: true # text label of owner details
368
+ end
369
+
370
+ # Request an arbitrary payload be echoed back
371
+ class EchoRequest < Payload
372
+ endian :little
373
+
374
+ string :payload, length: 64 # byte array
375
+ end
376
+
377
+ # Echo response with payload sent in request
378
+ class EchoResponse < Payload
379
+ endian :little
380
+
381
+ string :payload, length: 64 # byte array
382
+ end
383
+
384
+ end
385
+ end
386
+ end
387
+ end