class YeelightCli::Bulb

A bulb wrapper rubocop:disable ClassLength

Constants

BaseError
BlankIdError
BlankLocationError
ResponseError
UnsupportedActionError
WrongDataFormatError

Attributes

id[RW]
logger[RW]
name[R]
specifications[RW]
state[RW]
uri[RW]

Public Class Methods

initialize_from_package(package_body) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 453
def self.initialize_from_package(package_body)
  lines = package_body.lines
  _status_line = lines.shift

  data = lines
         .map { |line| line.strip.split(': ', 2) }
         .select { |processed_line| processed_line.count == 2 }
         .to_h
         .symbolize_keys

  new(data)
end
new( data, socket_client: nil, color_processor: ColorProcessor, logger: initialize_default_logger, state_caching: true, args_validator: Bulb::ArgsValidator ) click to toggle source

rubocop:disable ParameterLists

# File lib/yeelight_cli/bulb.rb, line 20
def initialize(
  data,
  socket_client: nil,
  color_processor: ColorProcessor,
  logger: initialize_default_logger,
  state_caching: true,
  args_validator: Bulb::ArgsValidator
)
  @logger = logger
  @logger.debug "Initializing new object with data=#{data}"

  @args_validator = args_validator
  @args_validator.check_initial_data!(data)

  initialize_variables_from(data)

  @state_caching = state_caching
  fill_state_with(data) if state_caching?

  @socket_client = socket_client ||
                   TCPSocketClient.new(@uri.host, @uri.port)

  @color_processor = color_processor
end

Public Instance Methods

<=>(other) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 57
def <=>(other)
  name <=> other.name
end
==(other) click to toggle source

rubocop:enable ParameterLists

# File lib/yeelight_cli/bulb.rb, line 46
def ==(other)
  return false unless other.is_a?(self.class)

  id == other.id
end
Also aliased as: eql?
adjust(action, prop) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 375
def adjust(action, prop)
  @args_validator.check_adjust_action!(action)
  @args_validator.check_adjust_prop!(prop)

  perform(:set_adjust, [action, prop])
  reload_state! if state_caching?

  [action, prop]
end
adjust_brightness(percentage, duration = 0) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 385
def adjust_brightness(percentage, duration = 0)
  @args_validator.check_percentage!(percentage)
  @args_validator.check_duration!(duration)

  perform(:adjust_bright, [percentage, duration])
  reload_state! if state_caching?

  off? ? 0 : brightness
end
adjust_color(percentage, duration = 0) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 405
def adjust_color(percentage, duration = 0)
  @args_validator.check_percentage!(percentage)
  @args_validator.check_duration!(duration)

  perform(:adjust_color, [percentage, duration])
  reload_state! if state_caching?

  rgb
end
adjust_ct(percentage, duration = 0) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 395
def adjust_ct(percentage, duration = 0)
  @args_validator.check_percentage!(percentage)
  @args_validator.check_duration!(duration)

  perform(:adjust_ct, [percentage, duration])
  reload_state! if state_caching?

  color_temperature
end
brightness() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 181
def brightness
  get_prop(:bright).to_i
end
brightness=(brightness = 100) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 174
def brightness=(brightness = 100)
  @args_validator.check_brightness!(brightness)

  perform(:set_bright, [brightness, :sudden, 0])
  set_prop(:bright, brightness)
end
brightness_character() click to toggle source

rubocop:disable MethodLength

# File lib/yeelight_cli/bulb.rb, line 107
def brightness_character
  return 'x' if off?

  case brightness
  when 100
    '●'
  when 40..99
    '◕'
  when 10..39
    '◑'
  else
    '○'
  end
end
cancel_delayed_shutdown!() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 364
def cancel_delayed_shutdown!
  perform(:cron_del, [0])
  return true if !state_caching? || @shutdown_thread.blank?

  @shutdown_thread.kill
  @shutdown_thread = nil
  @logger.info 'The thread to update the power state has been killed'

  true
end
color_mode() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 206
def color_mode
  get_prop(:color_mode).to_i
end
color_mode_name() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 210
def color_mode_name
  return :rgb if rgb_color_mode?
  return :temperature if temperature_color_mode?
  return :hsv if hsv_color_mode?
end
color_temperature() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 248
def color_temperature
  get_prop(:ct).to_i
end
color_temperature=(color_temperature = 6500) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 240
def color_temperature=(color_temperature = 6500)
  @args_validator.check_color_temperature!(color_temperature)

  perform(:set_ct_abx, [color_temperature, :sudden, 0])
  set_prop(:color_mode, 2)
  set_prop(:ct, color_temperature)
end
current_color_in_rgb() click to toggle source

rubocop:enable MethodLength

# File lib/yeelight_cli/bulb.rb, line 123
def current_color_in_rgb
  return 0x888888 if off?

  case color_mode_name
  when :hsv
    @color_processor.huesat_to_rgb(hue, sat)
  when :temperature
    @color_processor.color_temperature_to_rgb(color_temperature)
  else
    rgb
  end
end
default!() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 343
def default!
  perform :set_default
end
delayed_shutdown() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 360
def delayed_shutdown
  perform(:cron_get, [0]).try(:first).try(:[], 'delay')
end
delayed_shutdown=(minutes)
delayed_shutdown_after(minutes) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 347
def delayed_shutdown_after(minutes)
  @args_validator.check_timeout!(minutes)

  return cancel_delayed_shutdown! if minutes.zero?

  perform(:cron_add, [0, minutes])

  start_shutdown_thread_with(minutes * 60) if state_caching?

  minutes
end
Also aliased as: delayed_shutdown=
eql?(other)
Alias for: ==
get_prop(param) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 74
def get_prop(param)
  return @state[param] if state_caching? && @state[param]

  load_props(param)[param]
end
group_name(level = 1) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 85
def group_name(level = 1)
  return unless level.positive?

  chunks = name.split('/')
  return unless chunks.count > level

  chunks[level - 1]
end
hash() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 53
def hash
  id.hash
end
hsv_color_mode?() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 224
def hsv_color_mode?
  color_mode == 3
end
hue() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 283
def hue
  get_prop(:hue).to_i
end
hue=(hue = 359) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 265
def hue=(hue = 359)
  @args_validator.check_hue!(hue)

  sat = get_prop(:sat) || 100
  perform(:set_hsv, [hue, sat, :smooth, 0])
  set_prop(:color_mode, 3)
  set_prop(:hue, hue.to_i)
end
load_props(*params) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 292
def load_props(*params)
  props = perform(:get_prop, params)
  params.zip(props).to_h
end
name=(name) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 159
def name=(name)
  perform(:set_name, [name])
  @name = name
end
off?() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 144
def off?
  !on?
end
on?() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 140
def on?
  get_prop(:power) == 'on'
end
power() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 202
def power
  get_prop(:power)
end
power=(state = :on) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 195
def power=(state = :on)
  @args_validator.check_power_state!(state)

  perform(:set_power, [state, :sudden, 0])
  set_prop(:power, state.to_s)
end
random_color!(duration = 0) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 338
def random_color!(duration = 0)
  random_color = Random.new.rand(0xffffff)
  set_rgb(random_color, duration)
end
reload_state!() click to toggle source

rubocop:disable RescueModifier

# File lib/yeelight_cli/bulb.rb, line 298
def reload_state!
  return false unless state_caching?

  @logger.info 'Reloading state'

  actual_props = load_props(*@state.keys)

  actual_props.each do |prop_key, prop_value|
    casted_prop_value = Integer(prop_value) rescue nil
    value = casted_prop_value || prop_value

    @state[prop_key] = value if value.present?
  end

  @state
end
rgb() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 334
def rgb
  get_prop(:rgb).to_i
end
rgb=(rgb_value = 0xffffff) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 326
def rgb=(rgb_value = 0xffffff)
  @args_validator.check_rgb!(rgb_value)

  perform(:set_rgb, [rgb_value, :smooth, 0])
  set_prop(:color_mode, 1)
  set_prop(:rgb, rgb_value)
end
rgb_color_mode?() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 216
def rgb_color_mode?
  color_mode == 1
end
room() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 94
def room
  group_name(1)
end
sat() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 287
def sat
  get_prop(:sat).to_i
  @state[:sat] ||= get_prop(:sat).to_i
end
sat=(sat = 100) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 274
def sat=(sat = 100)
  @args_validator.check_sat!(sat)

  hue = get_prop(:hue) || 359
  perform(:set_hsv, [hue, sat, :smooth, 0])
  set_prop(:color_mode, 3)
  set_prop(:sat, sat.to_i)
end
set_brightness(brightness = 100, duration = 0) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 164
def set_brightness(brightness = 100, duration = 0)
  @args_validator.check_brightness!(brightness)
  @args_validator.check_duration!(duration)

  effect = duration.positive? ? :smooth : :sudden
  perform(:set_bright, [brightness, effect, duration])

  set_prop(:bright, brightness)
end
set_color_temperature(color_temperature = 6500, duration = 0) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 228
def set_color_temperature(color_temperature = 6500, duration = 0)
  color_temperature = color_temperature.to_i

  @args_validator.check_color_temperature!(color_temperature)
  @args_validator.check_duration!(duration)

  effect = duration.positive? ? :smooth : :sudden
  perform(:set_ct_abx, [color_temperature, effect, duration])
  set_prop(:color_mode, 2)
  set_prop(:ct, color_temperature)
end
set_huesat(hue = 359, sat = 100, duration = 0) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 252
def set_huesat(hue = 359, sat = 100, duration = 0)
  @args_validator.check_hue!(hue)
  @args_validator.check_sat!(sat)
  @args_validator.check_duration!(duration)

  effect = duration.positive? ? :smooth : :sudden
  perform(:set_hsv, [hue, sat, effect, duration])

  set_prop(:color_mode, 3)

  [set_prop(:hue, hue.to_i), set_prop(:sat, sat.to_i)]
end
set_music(action, host, port) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 442
def set_music(action, host, port)
  @args_validator.check_music_action!(action)
  @args_validator.check_host!(host)
  @args_validator.check_port!(port)

  action_code = action == :on ? 1 : 0
  perform(:set_music, [action_code, host, port])

  action == :on
end
set_power(state = :on, duration = 0) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 185
def set_power(state = :on, duration = 0)
  @args_validator.check_duration!(duration)
  @args_validator.check_power_state!(state)

  effect = duration.positive? ? :smooth : :sudden
  perform(:set_power, [state, effect, duration])

  set_prop(:power, state.to_s)
end
set_prop(param, value) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 80
def set_prop(param, value)
  @state[param] = value if state_caching?
  value
end
set_rgb(rgb_value = 0xffffff, duration = 0) click to toggle source

rubocop:enable RescueModifier

# File lib/yeelight_cli/bulb.rb, line 316
def set_rgb(rgb_value = 0xffffff, duration = 0)
  @args_validator.check_rgb!(rgb_value)
  @args_validator.check_duration!(rgb_value)

  effect = duration.positive? ? :smooth : :sudden
  perform(:set_rgb, [rgb_value, effect, duration])
  set_prop(:color_mode, 1)
  set_prop(:rgb, rgb_value)
end
start_cf(count, action, expression) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 415
def start_cf(count, action, expression)
  @args_validator.check_cf_count!(count)
  @args_validator.check_cf_action!(action)
  @args_validator.check_cf_expression!(expression)

  exp_array = expression.is_a?(Array) ? expression : expression.split(',')

  cancel_cf_thread! if state_caching?

  perform(:start_cf, [count, action, expression])

  start_cf_thread(count, exp_array) if state_caching? && count != 0

  true
end
state_caching?() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 102
def state_caching?
  @state_caching == true
end
stop_cf() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 431
def stop_cf
  perform :stop_cf

  return true unless state_caching?

  reload_state!
  cancel_cf_thread!

  true
end
support?(method) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 136
def support?(method)
  @specifications.include?(method.to_s) || method.to_s == 'set_name'
end
temperature_color_mode?() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 220
def temperature_color_mode?
  color_mode == 2
end
to_icon() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 98
def to_icon
  Paint[brightness_character, current_color_in_rgb.to_s(16)]
end
to_s() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 61
def to_s
  shutdown_timer = delayed_shutdown

  shutdown_string = if shutdown_timer
                      Paint[" shutdown_after=#{delayed_shutdown}", 'ff4444']
                    else
                      ''
                    end

  "<YeelightCli::Bulb id=#{id} name=#{name} "\
    "icon=#{to_icon}#{shutdown_string}>"
end
toggle!() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 148
def toggle!
  perform :toggle

  return get_prop(:power) unless state_caching?

  new_power_state = on? ? 'off' : 'on'
  set_prop(:power, new_power_state)

  new_power_state
end

Private Instance Methods

calculate_general_duration_from(count, exp_array) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 508
def calculate_general_duration_from(count, exp_array)
  res = Array
        .new(count)
        .map.with_index { |_, i| exp_array[(i * 4) % exp_array.count].to_i }
        .sum

  @logger.debug "The calculated duraion for cf with count=#{count}, "\
    "exp_array=#{exp_array} is #{res}"

  res
end
cancel_cf_thread!() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 534
def cancel_cf_thread!
  return false if !state_caching? || @cf_thread.blank?

  @cf_thread.kill
  @cf_thread = nil

  @logger.info 'The cf thread to actualize the state has been killed'
end
fill_state_with(data) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 476
def fill_state_with(data)
  return false unless state_caching?

  new_state = {}

  @logger.debug "Filling state with data=#{data}"

  %i[bright color_mode ct rgb hue sat].each do |prop|
    new_state[prop] = data.delete(prop).to_i
  end

  # the state includes power, bright, color_mode, ct, rgb, hue, sat, name
  @state = data.merge(new_state)
  @logger.debug "The state now is #{@state}"

  @state
end
initialize_default_logger() click to toggle source
# File lib/yeelight_cli/bulb.rb, line 558
def initialize_default_logger
  logger = Logger.new(STDOUT)
  logger.level = Logger::WARN
  logger
end
initialize_variables_from(data) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 468
def initialize_variables_from(data)
  @id = data.delete(:id).to_i(16)
  @uri = URI(data.delete(:Location))
  @model = data.delete(:model)
  @name = data.delete(:name)
  @specifications = data.delete(:support)
end
perform(method, params = []) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 494
def perform(method, params = [])
  raise UnsupportedActionError unless support?(method)

  json = JSON.generate(id: 1, method: method, params: params) + "\r\n"
  @logger.debug "Socket request: #{json}"
  response = @socket_client.request(json)
  @logger.debug "Socket response: #{response}"

  result = response['result']
  raise ResponseError unless result

  result
end
start_cf_thread(count, exp_array) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 520
def start_cf_thread(count, exp_array)
  return false unless state_caching?

  timeout = calculate_general_duration_from(count, exp_array).to_f / 1000

  @cf_thread = Thread.new do
    sleep timeout
    reload_state!
  end

  @logger.info 'The cf thread to actualize the state with '\
    "timeout=#{timeout} has been created"
end
start_shutdown_thread_with(timeout) click to toggle source
# File lib/yeelight_cli/bulb.rb, line 543
def start_shutdown_thread_with(timeout)
  return false unless state_caching?

  @shutdown_thread = Thread.new do
    sleep timeout
    @state[:power] = 'off'
    @logger.info 'The power state has been changed to "off"'
  end

  @logger.info 'A thread to update the power state with '\
    "timeout=#{timeout} has been created"

  timeout
end