defmodule Yeelight.Command do
@moduledoc """
create command and send to device
```
Yeelight.Command.set_scene_auto_delay_off(1, 5)
|> Yeelight.Command.send_to(device)
```
"""
alias Yeelight.Message
@type duration :: non_neg_integer() | nil
@power_on_mode %{normal: 0, ct: 1, rgb: 2, hsv: 3, cf: 4, night: 5}
def send_to(command, device) do
json =
command
|> Map.from_struct()
|> Map.put(:id, device.command_id)
|> Jason.encode!()
|> Kernel.<>("\r\n")
{:ok, socket} =
:gen_tcp.connect(
String.to_charlist(device.host),
device.port,
[:binary, {:active, false}]
)
:ok = :gen_tcp.send(socket, json)
result = :gen_tcp.recv(socket, 0)
:ok = :gen_tcp.close(socket)
Yeelight.Result.parse_result(result)
end
@spec build_command(binary(), list()) :: Message.t()
def build_command(method, params)
when is_binary(method) and is_list(params) do
%Message{method: method, params: params}
end
@spec get_properties(list()) :: Message.t()
def get_properties(properties) do
build_command("get_prop", properties)
end
@spec set_temperature(1700..6500, duration()) :: Message.t()
def set_temperature(temperature, duration) do
build_command("set_ct_abx", [temperature | get_effect(duration)])
end
@spec set_rgb(0..255, 0..255, 0..255, duration()) :: Message.t()
def set_rgb(r, g, b, duration \\ nil) do
calculate_rgb(r, g, b)
|> set_rgb(duration)
end
def set_rgb(rgb, duration \\ nil)
@spec set_rgb(list(0..255), duration()) :: Message.t()
def set_rgb([r, g, b], duration) do
set_rgb(r, g, b, duration)
end
@spec set_rgb(0..0xFFFFFF, duration()) :: Message.t()
def set_rgb(rgb, duration) do
build_command("set_rgb", [rgb | get_effect(duration)])
end
@spec set_hsv(0..359, 0..100, duration()) :: Message.t()
def set_hsv(hue, saturation, duration \\ nil) do
build_command("set_hsv", [hue, saturation | get_effect(duration)])
end
@spec set_bright(1..100, duration()) :: Message.t()
def set_bright(brightness, duration \\ nil) do
build_command("set_bright", [brightness | get_effect(duration)])
end
@spec set_power_on(atom(), duration()) :: Message.t()
def set_power_on(mode, duration \\ nil) do
build_command("set_power", ["on"] ++ get_effect(duration) ++ [Map.get(@power_on_mode, mode)])
end
@spec set_power_off(duration()) :: Message.t()
def set_power_off(duration \\ nil) do
build_command("set_power", ["off" | get_effect(duration)])
end
@spec toggle() :: Message.t()
def toggle(), do: build_command("toggle", [])
@spec set_default() :: Message.t()
def set_default(), do: build_command("set_default", [])
@spec start_color_flow(non_neg_integer(), 0..2, list(map())) :: Message.t()
def start_color_flow(count, action, flow_expressions) do
build_command("start_cf", [count, action, get_flow_expression_string(flow_expressions)])
end
@spec stop_color_flow() :: Message.t()
def stop_color_flow(), do: build_command("stop_cf", [])
def set_scene_rgb(r, g, b, brightness) do
set_scene_rgb(calculate_rgb(r, g, b), brightness)
end
def set_scene_rgb(rgb, brightness) do
build_command("set_scene", ["color", rgb, brightness])
end
def set_scene_temperature(temperature, brightness) do
build_command("set_scene", ["ct", temperature, brightness])
end
def set_scene_auto_delay_off(time, brightness) do
build_command("set_scene", ["auto_delay_off", brightness, time])
end
def set_scene_color_flow(count, action, flow_expressions) do
build_command("set_scene", ["cf", count, action, get_flow_expression_string(flow_expressions)])
end
def set_scene_hsv(hue, saturation, brightness) do
build_command("set_scene", ["hsv", hue, saturation, brightness])
end
@spec start_timer_job(integer()) :: Message.t()
def start_timer_job(minutes) do
build_command("cron_add", [0, minutes])
end
@spec get_timer_job() :: Message.t()
def get_timer_job() do
build_command("cron_get", [0])
end
@spec stop_timer_job() :: Message.t()
def stop_timer_job() do
build_command("cron_del", [0])
end
@spec set_name(binary()) :: Message.t()
def set_name(name), do: build_command("set_name", [name])
@spec set_adjust(atom, atom) :: Message.t()
def set_adjust(action, prop) when action == :circle and prop == :color do
build_command("set_adjust", [action, prop])
end
def set_adjust(action, prop)
when action in [:increase, :decrease, :circle] and prop in [:bright, :ct] do
build_command("set_adjust", [action, prop])
end
def set_adjust(action, prop) do
IO.puts("unsupported property: #{prop} for action: #{action}")
end
@spec adjust_brightness(-100..100, non_neg_integer()) :: Message.t()
def adjust_brightness(percentage, duration) do
build_command("adjust_bright", [percentage, duration])
end
@spec adjust_color_temperature(-100..100, non_neg_integer()) :: Message.t()
def adjust_color_temperature(percentage, duration) do
build_command("adjust_ct", [percentage, duration])
end
@spec adjust_color(-100..100, non_neg_integer()) :: Message.t()
def adjust_color(percentage, duration) do
build_command("adjust_color", [percentage, duration])
end
@spec set_music_on(String.t(), integer) :: Message.t()
def set_music_on(host, port) do
build_command("set_music", [1, host, port])
end
@spec set_music_off :: Message.t()
def set_music_off do
build_command("set_music", [0])
end
def calculate_rgb(r, g, b) do
r * (256 * 256) + g * 256 + b
end
defp get_flow_expression_string(flow_expressions) do
Enum.map_join(flow_expressions, ", ", &to_string/1)
end
defp get_effect(duration) do
if duration, do: [:smooth, duration], else: [:sudden, 0]
end
end