Class: LIFX::Light

Inherits:
Object
  • Object
show all
Includes:
LightTarget, Seen
Defined in:
lib/lifx/light.rb

Overview

LIFX::Light represents a Light device

Defined Under Namespace

Classes: LabelTooLong

Constant Summary collapse

MAX_LABEL_LENGTH =
32

Constants included from LightTarget

LIFX::LightTarget::MSEC_PER_SEC, LIFX::LightTarget::NSEC_IN_SEC

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from LightTarget

#half_sine, #pulse, #reboot!, #refresh, #saw, #set_color, #set_power, #set_site_id, #set_time, #set_waveform, #sine, #triangle, #turn_off, #turn_on

Methods included from Seen

#last_seen, #seconds_since_seen

Constructor Details

#initialize(context:, id:, site_id: nil, label: nil) ⇒ Light

Returns a new instance of Light.

Parameters:

  • context: (NetworkContext)

    NetworkContext the Light belongs to

  • id: (String)

    Device ID of the Light

  • site_id: (String) (defaults to: nil)

    Site ID of the Light. Avoid using when possible.

  • label: (String) (defaults to: nil)

    Label of Light to prepopulate



25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/lifx/light.rb', line 25

def initialize(context:, id:, site_id: nil, label: nil)
  @context = context
  @id = id
  @site_id = site_id
  @label = label
  @power = nil
  @message_hooks = Hash.new { |h, k| h[k] = [] }
  @context.register_device(self)
  @message_signal = ConditionVariable.new

  add_hooks
end

Instance Attribute Details

#contextNetworkContext (readonly)

Returns NetworkContext the Light belongs to.

Returns:



16
17
18
# File 'lib/lifx/light.rb', line 16

def context
  @context
end

#idString (readonly)

Returns Device ID.

Returns:

  • (String)

    Device ID



19
20
21
# File 'lib/lifx/light.rb', line 19

def id
  @id
end

Instance Method Details

#<=>(other) ⇒ -1, ...

Compare current Light to another light

Parameters:

Returns:

  • (-1, 0, 1)

    Comparison value

Raises:

  • (ArgumentError)


338
339
340
341
# File 'lib/lifx/light.rb', line 338

def <=>(other)
  raise ArgumentError.new("Comparison of #{self} with #{other} failed") unless other.is_a?(LIFX::Light)
  [label, id, 0] <=> [other.label, other.id, 0]
end

#add_hook(payload_class, hook_arg = nil, &hook_block) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Adds a block to be run when a payload of class payload_class is received

Parameters:

  • payload_class (Class)

    Payload type to execute block on

  • &hook (Proc)

    Hook to run



56
57
58
59
60
61
62
# File 'lib/lifx/light.rb', line 56

def add_hook(payload_class, hook_arg = nil, &hook_block)
  hook = block_given? ? hook_block : hook_arg
  if !hook || !hook.is_a?(Proc)
    raise "MUst pass a proc either as an argument or a block"
  end
  @message_hooks[payload_class] << hook
end

#add_tag(tag) ⇒ Light

Add tag to the Light

Parameters:

  • tag (String)

    The tag to add

Returns:



309
310
311
312
# File 'lib/lifx/light.rb', line 309

def add_tag(tag)
  context.add_tag_to_device(tag: tag, device: self)
  self
end

#color(refresh: false, fetch: true) ⇒ Color

Returns the color of the device.

Parameters:

  • refresh: (Boolean) (defaults to: false)

    If true, will request for current color

  • fetch: (Boolean) (defaults to: true)

    If false, it will not request current color if it's not cached

Returns:



77
78
79
80
81
# File 'lib/lifx/light.rb', line 77

def color(refresh: false, fetch: true)
  @color = nil if refresh
  send_message!(Protocol::Light::Get.new, wait_for: Protocol::Light::Get) if fetch && !@color
  @color
end

#handle_message(message, ip, transport) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Handles updating the internal state of the Light from incoming protocol messages.



41
42
43
44
45
46
47
48
49
# File 'lib/lifx/light.rb', line 41

def handle_message(message, ip, transport)
  payload = message.payload

  @message_hooks[payload.class].each do |hook|
    hook.call(payload)
  end
  @message_signal.broadcast
  seen!
end

#label(refresh: false, fetch: true) ⇒ String

Returns the label of the light

Parameters:

  • refresh: (Boolean) (defaults to: false)

    If true, will request for current label

  • fetch: (Boolean) (defaults to: true)

    If false, it will not request current label if it's not cached

Returns:

  • (String)

    Label



87
88
89
90
91
# File 'lib/lifx/light.rb', line 87

def label(refresh: false, fetch: true)
  @label = nil if refresh
  send_message!(Protocol::Light::Get.new, wait_for: Protocol::Light::Get) if fetch && !@label
  @label
end

#last_downtimeFloat

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return device last downtime

Returns:

  • (Float)

    Device's last downtime in secodns



277
278
279
280
281
282
# File 'lib/lifx/light.rb', line 277

def last_downtime
  send_message!(Protocol::Device::GetInfo.new,
     wait_for: Protocol::Device::StateInfo) do |payload|
    payload.downtime.to_f / NSEC_IN_SEC
  end
end

#latencyFloat

Pings the device and measures response time.

Returns:

  • (Float)

    Latency from sending a message to receiving a response.



183
184
185
186
187
# File 'lib/lifx/light.rb', line 183

def latency
  start = Time.now.to_f
  send_message!(Protocol::Device::GetTime.new, wait_for: Protocol::Device::StateTime)
  Time.now.to_f - start
end

#mesh_firmware(fetch: true) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the mesh firmware details

Returns:

  • (Hash)

    firmware details



192
193
194
195
196
197
198
199
# File 'lib/lifx/light.rb', line 192

def mesh_firmware(fetch: true)
  @mesh_firmware ||= begin
    send_message!(Protocol::Device::GetMeshFirmware.new,
      wait_for: Protocol::Device::StateMeshFirmware) do |payload|
      Firmware.new(payload)
    end if fetch
  end
end

#mesh_infoHash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns mesh network info

Returns:

  • (Hash)

    Mesh network info



225
226
227
228
229
230
231
232
233
234
# File 'lib/lifx/light.rb', line 225

def mesh_info
  send_message!(Protocol::Device::GetMeshInfo.new,
      wait_for: Protocol::Device::StateMeshInfo) do |payload|
    {
      signal: payload.signal, # This is in Milliwatts
      tx: payload.tx,
      rx: payload.rx
    }
  end
end

#off?(refresh: false, fetch: true) ⇒ Boolean

Returns true if device is off

Returns:

  • (Boolean)

    Returns true if device is off

See Also:



145
146
147
# File 'lib/lifx/light.rb', line 145

def off?(refresh: false, fetch: true)
  power(refresh: refresh, fetch: fetch) == :off
end

#on?(refresh: false, fetch: true) ⇒ Boolean

Returns true if device is on

Returns:

  • (Boolean)

    Returns true if device is on

See Also:



139
140
141
# File 'lib/lifx/light.rb', line 139

def on?(refresh: false, fetch: true)
  power(refresh: refresh, fetch: fetch) == :on
end

#power(refresh: false, fetch: true) ⇒ :unknown, ...

Returns Light power state.

Parameters:

  • refresh: (defaults to: false)

    see #label

  • fetch: (defaults to: true)

    see #label

Returns:

  • (:unknown, :off, :on)

    Light power state



152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/lifx/light.rb', line 152

def power(refresh: false, fetch: true)
  @power = nil if refresh
  send_message!(Protocol::Device::GetPower.new, wait_for: Protocol::Device::StatePower) if !@power && fetch
  case @power
  when nil
    :unknown
  when 0
    :off
  else
    :on
  end
end

#remove_hook(payload_class, hook) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Removes a hook added by #add_hook

Parameters:

  • payload_class (Class)

    Payload type to delete hook from

  • hook (Proc)

    The original hook passed into #add_hook



69
70
71
# File 'lib/lifx/light.rb', line 69

def remove_hook(payload_class, hook)
  @message_hooks[payload_class].delete(hook)
end

#remove_tag(tag) ⇒ Light

Remove tag from the Light

Parameters:

  • tag (String)

    The tag to remove

Returns:



317
318
319
320
# File 'lib/lifx/light.rb', line 317

def remove_tag(tag)
  context.remove_tag_from_device(tag: tag, device: self)
  self
end

#send_message(payload, acknowledge: true) ⇒ Light

Queues a message to be sent the Light

Parameters:

  • payload (Protocol::Payload)

    the payload to send

  • acknowledge: (Boolean) (defaults to: true)

    whether the device should respond

Returns:

  • (Light)

    returns self for chaining



347
348
349
350
# File 'lib/lifx/light.rb', line 347

def send_message(payload, acknowledge: true)
  context.send_message(target: Target.new(device_id: id, site_id: @site_id), payload: payload, acknowledge: acknowledge)
  self
end

#send_message!(payload, wait_for:, wait_timeout: 3, timeout_exception: Timeout::Error, &block) ⇒ Object

Queues a message to be sent to the Light and waits for a response

Parameters:

  • payload (Protocol::Payload)

    the payload to send

  • wait_for: (Class)

    the payload class to wait for

  • wait_timeout: (Numeric) (defaults to: 3)

    wait timeout

  • block: (Proc)

    the block that is executed when the expected wait_for payload comes back. If the return value is false or nil, it will try to send the message again.

Returns:

  • (Object)

    the truthy result of block is returned.

Raises:

  • (Timeout::Error)


359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/lifx/light.rb', line 359

def send_message!(payload, wait_for:, wait_timeout: 3, timeout_exception: Timeout::Error, &block)
  if Thread.current[:sync_enabled]
    raise "Cannot use synchronous methods inside a sync block"
  end

  result = nil
  begin
    block ||= Proc.new { |msg| true }
    proc = -> (payload) {
      result = block.call(payload)
    }
    add_hook(wait_for, proc)
    try_until -> { result }, signal: @message_signal, timeout_exception: timeout_exception do
      send_message(payload)
    end
    result
  ensure
    remove_hook(wait_for, proc)
  end
end

#set_label(label) ⇒ Light

Sets the label of the light

Parameters:

  • label (String)

    Desired label

Returns:

Raises:



100
101
102
103
104
105
106
107
108
# File 'lib/lifx/light.rb', line 100

def set_label(label)
  if label.length > MAX_LABEL_LENGTH
    raise LabelTooLong.new("Label length must be below or equal to #{MAX_LABEL_LENGTH}")
  end
  while self.label != label
    send_message!(Protocol::Device::SetLabel.new(label: label), wait_for: Protocol::Device::StateLabel)
  end
  self
end

#set_power!(level) ⇒ Light, LightCollection

Set the power state to level synchronously. This method cannot guarantee the message was received.

Parameters:

  • level (0, 1)

    0 for off, 1 for on

Returns:



114
115
116
117
118
119
120
121
122
123
# File 'lib/lifx/light.rb', line 114

def set_power!(level)
  send_message!(Protocol::Device::SetPower.new(level: level), wait_for: Protocol::Device::StatePower) do |payload|
    if level.zero?
      payload.level == 0
    else
      payload.level > 0
    end
  end
  self
end

#site_idString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the site_id the Light belongs to.

Returns:

  • (String)


287
288
289
290
291
292
293
294
# File 'lib/lifx/light.rb', line 287

def site_id
  if @site_id.nil?
    # FIXME: This is ugly.
    context.routing_manager.routing_table.site_id_for_device_id(id)
  else
    @site_id
  end
end

#tagsArray<String>

Returns the tags that are associated with the Light

Returns:

  • (Array<String>)

    tags



324
325
326
# File 'lib/lifx/light.rb', line 324

def tags
  context.tags_for_device(self)
end

#tags_fieldInteger

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the tags uint64 bitfield for protocol use.

Returns:

  • (Integer)


299
300
301
302
303
304
# File 'lib/lifx/light.rb', line 299

def tags_field
  try_until -> { @tags_field } do
    send_message(Protocol::Device::GetTags.new)
  end
  @tags_field
end

#temperatureFloat

Returns the temperature of the device

Returns:

  • (Float)

    Temperature in Celcius



215
216
217
218
219
220
# File 'lib/lifx/light.rb', line 215

def temperature
  send_message!(Protocol::Light::GetTemperature.new,
      wait_for: Protocol::Light::StateTemperature) do |payload|
    payload.temperature / 100.0
  end
end

#timeTime

Returns the local time of the light

Returns:

  • (Time)


167
168
169
170
171
# File 'lib/lifx/light.rb', line 167

def time
  send_message!(Protocol::Device::GetTime.new, wait_for: Protocol::Device::StateTime) do |payload|
    Time.at(payload.time.to_f / NSEC_IN_SEC)
  end
end

#time_deltaFloat

Returns the difference between the device time and time on the current machine Positive values means device time is further in the future.

Returns:

  • (Float)


176
177
178
179
# File 'lib/lifx/light.rb', line 176

def time_delta
  device_time = time
  delta = device_time - Time.now
end

#to_sString Also known as: inspect

Returns a nice string representation of the Light

Returns:

  • (String)


330
331
332
# File 'lib/lifx/light.rb', line 330

def to_s
  %Q{#<LIFX::Light id=#{id} label=#{label(fetch: false)} power=#{power(fetch: false)}>}.force_encoding(Encoding.default_external)
end

#turn_off!Light, LightCollection

Turns the light(s) off synchronously

Returns:



133
134
135
# File 'lib/lifx/light.rb', line 133

def turn_off!
  set_power!(0)
end

#turn_on!Light, LightCollection

Turns the light(s) on synchronously

Returns:



127
128
129
# File 'lib/lifx/light.rb', line 127

def turn_on!
  set_power!(1)
end

#uptimeFloat

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return device uptime

Returns:

  • (Float)

    Device uptime in seconds



267
268
269
270
271
272
# File 'lib/lifx/light.rb', line 267

def uptime
  send_message!(Protocol::Device::GetInfo.new,
     wait_for: Protocol::Device::StateInfo) do |payload|
    payload.uptime.to_f / NSEC_IN_SEC
  end
end

#versionHash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns version info

Returns:

  • (Hash)

    version info



253
254
255
256
257
258
259
260
261
262
# File 'lib/lifx/light.rb', line 253

def version
  send_message!(Protocol::Device::GetVersion.new,
     wait_for: Protocol::Device::StateVersion) do |payload|
    {
      vendor: payload.vendor,
      product: payload.product,
      version: payload.version
    }
  end
end

#wifi_firmware(fetch: true) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the wifi firmware details

Returns:

  • (Hash)

    firmware details



204
205
206
207
208
209
210
211
# File 'lib/lifx/light.rb', line 204

def wifi_firmware(fetch: true)
  @wifi_firmware ||= begin
    send_message!(Protocol::Device::GetWifiFirmware.new,
      wait_for: Protocol::Device::StateWifiFirmware) do |payload|
      Firmware.new(payload)
    end if fetch
  end
end

#wifi_infoHash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns wifi network info

Returns:

  • (Hash)

    wifi network info



239
240
241
242
243
244
245
246
247
248
# File 'lib/lifx/light.rb', line 239

def wifi_info
  send_message!(Protocol::Device::GetWifiInfo.new,
      wait_for: Protocol::Device::StateWifiInfo) do |payload|
    {
      signal: payload.signal, # This is in Milliwatts
      tx: payload.tx,
      rx: payload.rx
    }
  end
end