lifx 0.0.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|