lifx 0.0.1 → 0.4.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.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +1 -1
- data/README.md +71 -13
- data/Rakefile +12 -0
- data/bin/lifx-console +15 -0
- data/bin/lifx-snoop +50 -0
- data/examples/auto-off/Gemfile +3 -0
- data/examples/auto-off/auto-off.rb +35 -0
- data/examples/identify/Gemfile +3 -0
- data/examples/identify/identify.rb +70 -0
- data/examples/travis-build-light/Gemfile +4 -0
- data/examples/travis-build-light/build-light.rb +57 -0
- data/lib/bindata_ext/bool.rb +29 -0
- data/lib/bindata_ext/record.rb +11 -0
- data/lib/lifx/client.rb +136 -0
- data/lib/lifx/color.rb +190 -0
- data/lib/lifx/config.rb +12 -0
- data/lib/lifx/firmware.rb +55 -0
- data/lib/lifx/gateway_connection.rb +177 -0
- data/lib/lifx/light.rb +406 -0
- data/lib/lifx/light_collection.rb +105 -0
- data/lib/lifx/light_target.rb +189 -0
- data/lib/lifx/logging.rb +11 -0
- data/lib/lifx/message.rb +166 -0
- data/lib/lifx/network_context.rb +200 -0
- data/lib/lifx/observable.rb +46 -0
- data/lib/lifx/protocol/address.rb +21 -0
- data/lib/lifx/protocol/device.rb +225 -0
- data/lib/lifx/protocol/header.rb +24 -0
- data/lib/lifx/protocol/light.rb +110 -0
- data/lib/lifx/protocol/message.rb +17 -0
- data/lib/lifx/protocol/metadata.rb +21 -0
- data/lib/lifx/protocol/payload.rb +7 -0
- data/lib/lifx/protocol/sensor.rb +29 -0
- data/lib/lifx/protocol/type.rb +134 -0
- data/lib/lifx/protocol/wan.rb +50 -0
- data/lib/lifx/protocol/wifi.rb +76 -0
- data/lib/lifx/protocol_path.rb +84 -0
- data/lib/lifx/routing_manager.rb +110 -0
- data/lib/lifx/routing_table.rb +33 -0
- data/lib/lifx/seen.rb +15 -0
- data/lib/lifx/site.rb +89 -0
- data/lib/lifx/tag_manager.rb +105 -0
- data/lib/lifx/tag_table.rb +47 -0
- data/lib/lifx/target.rb +23 -0
- data/lib/lifx/timers.rb +18 -0
- data/lib/lifx/transport/tcp.rb +81 -0
- data/lib/lifx/transport/udp.rb +67 -0
- data/lib/lifx/transport.rb +41 -0
- data/lib/lifx/transport_manager/lan.rb +140 -0
- data/lib/lifx/transport_manager.rb +34 -0
- data/lib/lifx/utilities.rb +33 -0
- data/lib/lifx/version.rb +1 -1
- data/lib/lifx.rb +15 -1
- data/lifx.gemspec +11 -7
- data/spec/color_spec.rb +45 -0
- data/spec/gateway_connection_spec.rb +32 -0
- data/spec/integration/client_spec.rb +40 -0
- data/spec/integration/light_spec.rb +43 -0
- data/spec/integration/tags_spec.rb +31 -0
- data/spec/message_spec.rb +163 -0
- data/spec/protocol_path_spec.rb +109 -0
- data/spec/routing_manager_spec.rb +22 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/transport/udp_spec.rb +38 -0
- data/spec/transport_spec.rb +14 -0
- metadata +143 -26
data/lib/lifx/message.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'lifx/protocol_path'
|
3
|
+
|
4
|
+
module LIFX
|
5
|
+
# @api private
|
6
|
+
|
7
|
+
class Message
|
8
|
+
include Logging
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
class MessageError < StandardError; end
|
12
|
+
class UnpackError < MessageError; end
|
13
|
+
class PackError < MessageError; end
|
14
|
+
class NoPath < MessageError; end
|
15
|
+
|
16
|
+
class InvalidFrame < UnpackError; end
|
17
|
+
class UnsupportedProtocolVersion < UnpackError; end
|
18
|
+
class NotAddressableFrame < UnpackError; end
|
19
|
+
class NoPayload < PackError; end
|
20
|
+
class UnmappedPayload < MessageError; end
|
21
|
+
class InvalidFields < PackError; end
|
22
|
+
|
23
|
+
PROTOCOL_VERSION = 1024
|
24
|
+
|
25
|
+
class << self
|
26
|
+
attr_accessor :log_invalid_messages
|
27
|
+
|
28
|
+
def unpack(data)
|
29
|
+
raise InvalidFrame if data.length < 2
|
30
|
+
|
31
|
+
header = Protocol::Header.read(data)
|
32
|
+
raise UnsupportedProtocolVersion.new("Expected #{PROTOCOL_VERSION} but got #{header.protocol} instead") if header.protocol != PROTOCOL_VERSION
|
33
|
+
raise NotAddressableFrame if header.addressable == 0
|
34
|
+
|
35
|
+
message = Protocol::Message.read(data)
|
36
|
+
path = ProtocolPath.new(raw_site: message.raw_site, raw_target: message.raw_target, tagged: message.tagged)
|
37
|
+
payload_class = message_type_for_id(message.type.snapshot)
|
38
|
+
if payload_class.nil?
|
39
|
+
if self.log_invalid_messages
|
40
|
+
logger.error("Message.unpack: Unrecognised payload ID: #{message.type}")
|
41
|
+
logger.error("Message.unpack: Message: #{message}")
|
42
|
+
end
|
43
|
+
return nil # FIXME
|
44
|
+
raise UnmappedPayload.new("Unrecognised payload ID: #{message.type}")
|
45
|
+
end
|
46
|
+
begin
|
47
|
+
payload = payload_class.read(message.payload)
|
48
|
+
rescue => ex
|
49
|
+
if message.raw_site == "\x00" * 6
|
50
|
+
logger.info("Message.unpack: Ignoring malformed message from virgin bulb")
|
51
|
+
else
|
52
|
+
if self.log_invalid_messages
|
53
|
+
logger.error("Message.unpack: Exception while unpacking payload of type #{payload_class}: #{ex}")
|
54
|
+
logger.error("Message.unpack: Data: #{data.inspect}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
new(path, message, payload)
|
59
|
+
rescue => ex
|
60
|
+
if self.log_invalid_messages
|
61
|
+
logger.debug("Message.unpack: Exception while unpacking #{data.inspect}")
|
62
|
+
logger.debug("Message.unpack: #{ex} - #{ex.backtrace.join("\n")}")
|
63
|
+
end
|
64
|
+
raise ex
|
65
|
+
end
|
66
|
+
|
67
|
+
def message_type_for_id(type_id)
|
68
|
+
Protocol::TYPE_ID_TO_CLASS[type_id]
|
69
|
+
end
|
70
|
+
|
71
|
+
def type_id_for_message_class(klass)
|
72
|
+
Protocol::CLASS_TO_TYPE_ID[klass]
|
73
|
+
end
|
74
|
+
|
75
|
+
def valid_fields
|
76
|
+
@valid_fields ||= Protocol::Message.new.field_names.map(&:to_sym)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
LIFX::Protocol::Message.fields.each do |field|
|
81
|
+
define_method(field.name) do
|
82
|
+
@message.send(field.name).snapshot
|
83
|
+
end
|
84
|
+
|
85
|
+
define_method("#{field.name}=") do |value|
|
86
|
+
@message.send("#{field.name}=", value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
alias_method :tagged?, :tagged
|
91
|
+
alias_method :addressable?, :addressable
|
92
|
+
|
93
|
+
def_delegators :path, :device_id, :site_id, :tagged
|
94
|
+
|
95
|
+
attr_accessor :path, :payload
|
96
|
+
def initialize(*args)
|
97
|
+
if args.count == 3
|
98
|
+
@path, @message, @payload = args
|
99
|
+
elsif (hash = args.first).is_a?(Hash)
|
100
|
+
path = hash.delete(:path)
|
101
|
+
payload = hash.delete(:payload)
|
102
|
+
|
103
|
+
check_valid_fields!(hash)
|
104
|
+
|
105
|
+
@message = Protocol::Message.new(hash)
|
106
|
+
self.payload = payload
|
107
|
+
self.path = path
|
108
|
+
@message.tagged = path.tagged? if path
|
109
|
+
else
|
110
|
+
@message = Protocol::Message.new
|
111
|
+
end
|
112
|
+
@message.msg_size = @message.num_bytes
|
113
|
+
@message.protocol = PROTOCOL_VERSION
|
114
|
+
rescue => ex
|
115
|
+
raise MessageError.new("Unable to initialize message with args: #{args.inspect} - #{ex}")
|
116
|
+
end
|
117
|
+
|
118
|
+
def payload=(payload)
|
119
|
+
@payload = payload
|
120
|
+
type_id = self.class.type_id_for_message_class(payload.class)
|
121
|
+
if type_id.nil?
|
122
|
+
raise UnmappedPayload.new("Unmapped payload class #{payload.class}")
|
123
|
+
end
|
124
|
+
@message.type = type_id
|
125
|
+
@message.payload = payload.pack
|
126
|
+
end
|
127
|
+
|
128
|
+
def pack
|
129
|
+
raise NoPayload if !payload
|
130
|
+
raise NoPath if !path
|
131
|
+
@message.raw_site = path.raw_site
|
132
|
+
@message.raw_target = path.raw_target
|
133
|
+
@message.tagged = path.tagged?
|
134
|
+
@message.msg_size = @message.num_bytes
|
135
|
+
@message.pack
|
136
|
+
end
|
137
|
+
|
138
|
+
def to_s
|
139
|
+
hash = {site: path.site_id}
|
140
|
+
if path.tagged?
|
141
|
+
hash[:tags] = path.tag_ids
|
142
|
+
hash[:tags] = 'all' if hash[:tags].empty?
|
143
|
+
else
|
144
|
+
hash[:device] = path.device_id
|
145
|
+
end
|
146
|
+
hash[:type] = payload.class.to_s.sub('LIFX::Protocol::', '')
|
147
|
+
hash[:addressable] = addressable? ? 'true' : 'false'
|
148
|
+
hash[:tagged] = path.tagged? ? 'true' : 'false'
|
149
|
+
hash[:at_time] = @message.at_time if @message.at_time && @message.at_time > 0
|
150
|
+
hash[:protocol] = protocol
|
151
|
+
hash[:payload] = payload.snapshot if payload
|
152
|
+
attrs = hash.map { |k, v| "#{k}=#{v}" }.join(' ')
|
153
|
+
%Q{#<LIFX::Message #{attrs}>}
|
154
|
+
end
|
155
|
+
alias_method :inspect, :to_s
|
156
|
+
|
157
|
+
protected
|
158
|
+
|
159
|
+
def check_valid_fields!(hash)
|
160
|
+
invalid_fields = hash.keys - self.class.valid_fields
|
161
|
+
if invalid_fields.count > 0
|
162
|
+
raise InvalidFields.new("Invalid fields for Message: #{invalid_fields.join(', ')}")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'lifx/timers'
|
2
|
+
require 'lifx/transport_manager'
|
3
|
+
require 'lifx/routing_manager'
|
4
|
+
require 'lifx/tag_manager'
|
5
|
+
require 'lifx/light'
|
6
|
+
require 'lifx/protocol_path'
|
7
|
+
|
8
|
+
module LIFX
|
9
|
+
class NetworkContext
|
10
|
+
include Timers
|
11
|
+
include Logging
|
12
|
+
include Utilities
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
# NetworkContext stores lights and ties together TransportManager, TagManager and RoutingManager
|
16
|
+
attr_reader :transport_manager, :tag_manager, :routing_manager
|
17
|
+
|
18
|
+
def initialize(transport: :lan)
|
19
|
+
@devices = {}
|
20
|
+
|
21
|
+
@transport_manager = case transport
|
22
|
+
when :lan
|
23
|
+
TransportManager::LAN.new
|
24
|
+
else
|
25
|
+
raise ArgumentError.new("Unknown transport method: #{transport}")
|
26
|
+
end
|
27
|
+
@transport_manager.add_observer(self) do |message:, ip:, transport:|
|
28
|
+
handle_message(message, ip, transport)
|
29
|
+
end
|
30
|
+
|
31
|
+
reset!
|
32
|
+
|
33
|
+
@threads = []
|
34
|
+
@threads << initialize_timer_thread
|
35
|
+
initialize_periodic_refresh
|
36
|
+
initialize_message_rate_updater
|
37
|
+
end
|
38
|
+
|
39
|
+
def discover
|
40
|
+
@transport_manager.discover
|
41
|
+
end
|
42
|
+
|
43
|
+
def refresh
|
44
|
+
@routing_manager.refresh
|
45
|
+
end
|
46
|
+
|
47
|
+
def reset!
|
48
|
+
@routing_manager = RoutingManager.new(context: self)
|
49
|
+
@tag_manager = TagManager.new(context: self, tag_table: @routing_manager.tag_table)
|
50
|
+
end
|
51
|
+
|
52
|
+
def stop
|
53
|
+
@transport_manager.stop
|
54
|
+
@threads.each do |thread|
|
55
|
+
Thread.kill(thread)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Sends a message to their destination(s)
|
60
|
+
# @param target: [Target] Target of the message
|
61
|
+
# @param payload: [Protocol::Payload] Message payload
|
62
|
+
# @param acknowledge: [Boolean] If recipients must acknowledge with a response
|
63
|
+
def send_message(target:, payload:, acknowledge: false)
|
64
|
+
paths = @routing_manager.resolve_target(target)
|
65
|
+
|
66
|
+
messages = paths.map do |path|
|
67
|
+
Message.new(path: path, payload: payload, acknowledge: acknowledge)
|
68
|
+
end
|
69
|
+
|
70
|
+
if within_sync?
|
71
|
+
Thread.current[:sync_messages].push(*messages)
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
messages.each do |message|
|
76
|
+
@transport_manager.write(message)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
protected def within_sync?
|
81
|
+
!!Thread.current[:sync_enabled]
|
82
|
+
end
|
83
|
+
|
84
|
+
# Synchronize asynchronous set_color, set_waveform and set_power messages to multiple devices.
|
85
|
+
# You cannot use synchronous methods in the block
|
86
|
+
# @note This is alpha
|
87
|
+
# @yield Block to synchronize commands in
|
88
|
+
# @return [Float] Delay before messages are executed
|
89
|
+
def sync(&block)
|
90
|
+
if within_sync?
|
91
|
+
raise "You cannot nest sync"
|
92
|
+
end
|
93
|
+
messages = Thread.new do
|
94
|
+
Thread.current[:sync_enabled] = true
|
95
|
+
Thread.current[:sync_messages] = messages = []
|
96
|
+
block.call
|
97
|
+
Thread.current[:sync_enabled] = false
|
98
|
+
messages
|
99
|
+
end.join.value
|
100
|
+
|
101
|
+
time = nil
|
102
|
+
try_until -> { time } do
|
103
|
+
light = gateways.sample
|
104
|
+
time = light && light.time
|
105
|
+
end
|
106
|
+
|
107
|
+
delay = (messages.count + 1) * (1.0 / message_rate)
|
108
|
+
at_time = ((time.to_f + delay) * 1_000_000_000).to_i
|
109
|
+
messages.each do |m|
|
110
|
+
m.at_time = at_time
|
111
|
+
@transport_manager.write(m)
|
112
|
+
end
|
113
|
+
flush
|
114
|
+
delay
|
115
|
+
end
|
116
|
+
|
117
|
+
def flush(**options)
|
118
|
+
@transport_manager.flush(**options)
|
119
|
+
end
|
120
|
+
|
121
|
+
def register_device(device)
|
122
|
+
device_id = device.id
|
123
|
+
@devices[device_id] = device # What happens when there's already one registered?
|
124
|
+
end
|
125
|
+
|
126
|
+
def lights
|
127
|
+
LightCollection.new(context: self)
|
128
|
+
end
|
129
|
+
|
130
|
+
def all_lights
|
131
|
+
@devices.values
|
132
|
+
end
|
133
|
+
|
134
|
+
# Tags
|
135
|
+
|
136
|
+
def_delegators :@tag_manager, :tags,
|
137
|
+
:unused_tags,
|
138
|
+
:purge_unused_tags!,
|
139
|
+
:add_tag_to_device,
|
140
|
+
:remove_tag_from_device
|
141
|
+
|
142
|
+
def tags_for_device(device)
|
143
|
+
@routing_manager.tags_for_device_id(device.id)
|
144
|
+
end
|
145
|
+
|
146
|
+
def gateways
|
147
|
+
transport_manager.gateways.map(&:keys).flatten.map { |id| lights.with_id(id) }
|
148
|
+
end
|
149
|
+
|
150
|
+
def gateway_connections
|
151
|
+
transport_manager.gateways.map(&:values).flatten
|
152
|
+
end
|
153
|
+
|
154
|
+
protected
|
155
|
+
|
156
|
+
def handle_message(message, ip, transport)
|
157
|
+
logger.debug("<- #{self} #{transport}: #{message}")
|
158
|
+
|
159
|
+
@routing_manager.update_from_message(message)
|
160
|
+
if !message.tagged?
|
161
|
+
if @devices[message.device_id].nil?
|
162
|
+
device = Light.new(context: self, id: message.device_id, site_id: message.site_id)
|
163
|
+
register_device(device)
|
164
|
+
end
|
165
|
+
device = @devices[message.device_id]
|
166
|
+
device.handle_message(message, ip, transport)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def initialize_periodic_refresh
|
171
|
+
timers.every(10) do
|
172
|
+
refresh
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def initialize_message_rate_updater
|
177
|
+
timers.every(5) do
|
178
|
+
missing_mesh_firmware = lights.select { |l| l.mesh_firmware(fetch: false).nil? }
|
179
|
+
if missing_mesh_firmware.count > 10
|
180
|
+
send_message(target: Target.new(broadcast: true), payload: Protocol::Device::GetMeshFirmware.new)
|
181
|
+
elsif missing_mesh_firmware.count > 0
|
182
|
+
missing_mesh_firmware.each { |l| l.send_message(Protocol::Device::GetMeshFirmware.new) }
|
183
|
+
else
|
184
|
+
@message_rate = lights.all? do |light|
|
185
|
+
m = light.mesh_firmware(fetch: false)
|
186
|
+
m && m >= '1.2'
|
187
|
+
end ? 20 : 5
|
188
|
+
gateway_connections.each do |connection|
|
189
|
+
connection.set_message_rate(@message_rate)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
DEFAULT_MESSAGING_RATE = 5 # per second
|
196
|
+
def message_rate
|
197
|
+
@message_rate || 5
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module LIFX
|
2
|
+
module Observable
|
3
|
+
class ObserverCallbackMismatch < ArgumentError; end
|
4
|
+
def add_observer(obj, &callback)
|
5
|
+
if !callback_has_required_keys?(callback)
|
6
|
+
raise ObserverCallbackMismatch.new
|
7
|
+
end
|
8
|
+
observers[obj] = callback
|
9
|
+
end
|
10
|
+
|
11
|
+
def remove_observer(obj)
|
12
|
+
observers.delete(obj)
|
13
|
+
end
|
14
|
+
|
15
|
+
def notify_observers(**args)
|
16
|
+
observers.each do |_, callback|
|
17
|
+
callback.call(**args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def callback_has_required_keys?(callback)
|
22
|
+
(required_keys_for_callback - required_keys_in_proc(callback)).empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
def observer_callback_definition
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def required_keys_for_callback
|
30
|
+
@_required_keys_for_callback ||= begin
|
31
|
+
return [] if !observer_callback_definition
|
32
|
+
required_keys_in_proc(observer_callback_definition)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def required_keys_in_proc(proc)
|
37
|
+
proc.parameters.select do |type, _|
|
38
|
+
type == :keyreq
|
39
|
+
end.map(&:last)
|
40
|
+
end
|
41
|
+
|
42
|
+
def observers
|
43
|
+
@_observers ||= {}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module LIFX
|
2
|
+
module Protocol
|
3
|
+
module AddressFields
|
4
|
+
def AddressFields.included(mod)
|
5
|
+
mod.instance_eval do
|
6
|
+
hide :_reserved2
|
7
|
+
string :raw_target, length: 8
|
8
|
+
string :raw_site, length: 6
|
9
|
+
bool_bit1 :acknowledge
|
10
|
+
bit15le :_reserved2
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Address < BinData::Record
|
16
|
+
endian :little
|
17
|
+
|
18
|
+
include AddressFields
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
# Generated code ahoy!
|
2
|
+
module LIFX
|
3
|
+
module Protocol
|
4
|
+
module Device
|
5
|
+
module Service
|
6
|
+
UDP = 1
|
7
|
+
TCP = 2
|
8
|
+
end
|
9
|
+
|
10
|
+
class SetSite < Payload
|
11
|
+
endian :little
|
12
|
+
|
13
|
+
string :site, length: 6
|
14
|
+
end
|
15
|
+
|
16
|
+
class GetPanGateway < Payload
|
17
|
+
endian :little
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class StatePanGateway < Payload
|
22
|
+
endian :little
|
23
|
+
|
24
|
+
uint8 :service
|
25
|
+
uint32 :port
|
26
|
+
end
|
27
|
+
|
28
|
+
class GetTime < Payload
|
29
|
+
endian :little
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
class SetTime < Payload
|
34
|
+
endian :little
|
35
|
+
|
36
|
+
uint64 :time # Nanoseconds since epoch.
|
37
|
+
end
|
38
|
+
|
39
|
+
class StateTime < Payload
|
40
|
+
endian :little
|
41
|
+
|
42
|
+
uint64 :time # Nanoseconds since epoch.
|
43
|
+
end
|
44
|
+
|
45
|
+
class GetResetSwitch < Payload
|
46
|
+
endian :little
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
class StateResetSwitch < Payload
|
51
|
+
endian :little
|
52
|
+
|
53
|
+
uint8 :position
|
54
|
+
end
|
55
|
+
|
56
|
+
class GetMeshInfo < Payload
|
57
|
+
endian :little
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
class StateMeshInfo < Payload
|
62
|
+
endian :little
|
63
|
+
|
64
|
+
float :signal # Milliwatts.
|
65
|
+
uint32 :tx # Bytes.
|
66
|
+
uint32 :rx # Bytes.
|
67
|
+
int16 :mcu_temperature # Deci-celsius. 25.45 celsius is 2545
|
68
|
+
end
|
69
|
+
|
70
|
+
class GetMeshFirmware < Payload
|
71
|
+
endian :little
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
class StateMeshFirmware < Payload
|
76
|
+
endian :little
|
77
|
+
|
78
|
+
uint64 :build # Firmware build nanoseconds since epoch.
|
79
|
+
uint64 :install # Firmware install nanoseconds since epoch.
|
80
|
+
uint32 :version # Firmware human readable version.
|
81
|
+
end
|
82
|
+
|
83
|
+
class GetWifiInfo < Payload
|
84
|
+
endian :little
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class StateWifiInfo < Payload
|
89
|
+
endian :little
|
90
|
+
|
91
|
+
float :signal # Milliwatts.
|
92
|
+
uint32 :tx # Bytes.
|
93
|
+
uint32 :rx # Bytes.
|
94
|
+
int16 :mcu_temperature # Deci-celsius. 25.45 celsius is 2545
|
95
|
+
end
|
96
|
+
|
97
|
+
class GetWifiFirmware < Payload
|
98
|
+
endian :little
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
class StateWifiFirmware < Payload
|
103
|
+
endian :little
|
104
|
+
|
105
|
+
uint64 :build # Firmware build nanoseconds since epoch.
|
106
|
+
uint64 :install # Firmware install nanoseconds since epoch.
|
107
|
+
uint32 :version # Firmware human readable version.
|
108
|
+
end
|
109
|
+
|
110
|
+
class GetPower < Payload
|
111
|
+
endian :little
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
class SetPower < Payload
|
116
|
+
endian :little
|
117
|
+
|
118
|
+
uint16 :level # 0 Standby. > 0 On.
|
119
|
+
end
|
120
|
+
|
121
|
+
class StatePower < Payload
|
122
|
+
endian :little
|
123
|
+
|
124
|
+
uint16 :level # 0 Standby. > 0 On.
|
125
|
+
end
|
126
|
+
|
127
|
+
class GetLabel < Payload
|
128
|
+
endian :little
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
class SetLabel < Payload
|
133
|
+
endian :little
|
134
|
+
|
135
|
+
string :label, length: 32, trim_padding: true
|
136
|
+
end
|
137
|
+
|
138
|
+
class StateLabel < Payload
|
139
|
+
endian :little
|
140
|
+
|
141
|
+
string :label, length: 32, trim_padding: true
|
142
|
+
end
|
143
|
+
|
144
|
+
class GetTags < Payload
|
145
|
+
endian :little
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
class SetTags < Payload
|
150
|
+
endian :little
|
151
|
+
|
152
|
+
uint64 :tags
|
153
|
+
end
|
154
|
+
|
155
|
+
class StateTags < Payload
|
156
|
+
endian :little
|
157
|
+
|
158
|
+
uint64 :tags
|
159
|
+
end
|
160
|
+
|
161
|
+
class GetTagLabels < Payload
|
162
|
+
endian :little
|
163
|
+
|
164
|
+
uint64 :tags
|
165
|
+
end
|
166
|
+
|
167
|
+
class SetTagLabels < Payload
|
168
|
+
endian :little
|
169
|
+
|
170
|
+
uint64 :tags
|
171
|
+
string :label, length: 32, trim_padding: true
|
172
|
+
end
|
173
|
+
|
174
|
+
class StateTagLabels < Payload
|
175
|
+
endian :little
|
176
|
+
|
177
|
+
uint64 :tags
|
178
|
+
string :label, length: 32, trim_padding: true
|
179
|
+
end
|
180
|
+
|
181
|
+
class GetVersion < Payload
|
182
|
+
endian :little
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
class StateVersion < Payload
|
187
|
+
endian :little
|
188
|
+
|
189
|
+
uint32 :vendor
|
190
|
+
uint32 :product
|
191
|
+
uint32 :version
|
192
|
+
end
|
193
|
+
|
194
|
+
class GetInfo < Payload
|
195
|
+
endian :little
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
class StateInfo < Payload
|
200
|
+
endian :little
|
201
|
+
|
202
|
+
uint64 :time # Nanoseconds since epoch.
|
203
|
+
uint64 :uptime # Nanoseconds since boot.
|
204
|
+
uint64 :downtime # Nanoseconds off last power cycle.
|
205
|
+
end
|
206
|
+
|
207
|
+
class GetMcuRailVoltage < Payload
|
208
|
+
endian :little
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
class StateMcuRailVoltage < Payload
|
213
|
+
endian :little
|
214
|
+
|
215
|
+
uint32 :voltage
|
216
|
+
end
|
217
|
+
|
218
|
+
class Reboot < Payload
|
219
|
+
endian :little
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module LIFX
|
2
|
+
module Protocol
|
3
|
+
module HeaderFields
|
4
|
+
def HeaderFields.included(mod)
|
5
|
+
mod.instance_eval do
|
6
|
+
hide :_reserved, :_reserved1
|
7
|
+
|
8
|
+
uint16 :msg_size
|
9
|
+
bit12le :protocol
|
10
|
+
bool_bit1 :addressable, value: true
|
11
|
+
bool_bit1 :tagged
|
12
|
+
bit2le :_reserved
|
13
|
+
uint32 :_reserved1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Header < BinData::Record
|
19
|
+
endian :little
|
20
|
+
|
21
|
+
include HeaderFields
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|