Skip to content

Instantly share code, notes, and snippets.

@omarmiatello
Last active December 5, 2020 11:07
Show Gist options
  • Save omarmiatello/19d52a0a1e33e39b8475d53ec5f9d2fe to your computer and use it in GitHub Desktop.
Save omarmiatello/19d52a0a1e33e39b8475d53ec5f9d2fe to your computer and use it in GitHub Desktop.
Yeelight API in Kotlin. Add support for multiple light!
package gdax.notification
import gdax.conf.App
import gdax.utils.logV
import kotlinx.coroutines.experimental.*
import java.io.BufferedOutputStream
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
import java.net.Socket
object Yeelight {
val devices = mutableMapOf<String, Pair<String, Int>>()
init {
searchYeelight()
}
fun easyFlow(countFull: Int, action: FlowEndAction, flowTuples: List<String>) = API.startColorFlow(countFull * flowTuples.size, action, flowTuples.joinToString(","))
fun flowTuple(duration: Long, mode: FlowMode, value: Int, brightness: Int) = "$duration,${mode.id},$value,$brightness"
fun startFlowGreen() = easyFlow(1, FlowEndAction.stay, listOf(
flowTuple(2000, FlowMode.colorRgb, 0x00ff00, 100),
flowTuple(1000, FlowMode.colorRgb, 0x00ff00, 30)
))
fun startFlowRed() = easyFlow(1, FlowEndAction.stay, listOf(
flowTuple(2000, FlowMode.colorRgb, 0xff0000, 100),
flowTuple(1000, FlowMode.colorRgb, 0xff0000, 30)
))
fun startFlowPolice() = easyFlow(2, FlowEndAction.recover, listOf(
flowTuple(500, FlowMode.colorRgb, 0xff0000, 100),
flowTuple(200, FlowMode.sleep, 0, 0),
flowTuple(500, FlowMode.colorRgb, 0x0000ff, 100),
flowTuple(200, FlowMode.sleep, 0, 0)
))
fun startFlowSun() = easyFlow(2, FlowEndAction.recover, listOf(
flowTuple(2000, FlowMode.colorRgb, 0xff0000, 50),
flowTuple(2000, FlowMode.colorRgb, 0xff0000, 10),
flowTuple(1000, FlowMode.sleep, 0, 0),
flowTuple(2000, FlowMode.colorRgb, 0xffff00, 50),
flowTuple(2000, FlowMode.colorRgb, 0xffff00, 10),
flowTuple(1000, FlowMode.sleep, 0, 0),
flowTuple(2000, FlowMode.colorRgb, 0x00ff00, 50),
flowTuple(1000, FlowMode.sleep, 0, 0),
flowTuple(2000, FlowMode.colorRgb, 0x00ffff, 50),
flowTuple(1000, FlowMode.sleep, 0, 0),
flowTuple(2000, FlowMode.colorRgb, 0x0000ff, 50),
flowTuple(1000, FlowMode.sleep, 0, 0),
flowTuple(2000, FlowMode.colorRgb, 0xff00ff, 50),
flowTuple(1000, FlowMode.sleep, 0, 0)
))
private fun searchYeelight(searchTime: Long = 1000) = runBlocking {
launch(CommonPool) {
DatagramSocket().use { s ->
val msg = "M-SEARCH * HTTP/1.1\r\nMAN:\"ssdp:discover\"\r\nST:wifi_bulb\r\n".toByteArray()
s.send(DatagramPacket(msg, msg.size, InetAddress.getByName("239.255.255.250"), 1982))
while (true) {
val response = ByteArray(1024).let { DatagramPacket(it, it.size) }.also { s.receive(it) }.data.filter { it.toInt() != 13 }.let { String(it.toByteArray()) }
val info = response.lines().map { it.split(":", limit = 2) }.filter { it.size == 2 }.map { it[0] to it[1] }.toMap()
val address = info["Location"]!!.split("//")[1].split(":").let { it[0] to it[1].toInt() }
devices.put(info["id"]!!, address)
}
}
}.also {
delay(searchTime)
it.cancel()
}
}
fun sendCmdAll(cmd: String) = runBlocking {
devices.values.map { (ip, port) ->
async { sendCmd(cmd, ip, port) }
}.map { it.await() }
}
fun sendCmd(cmd: String, ip: String, port: Int) = Socket(ip, port).use { s ->
// s.setKeepAlive(true)
BufferedOutputStream(s.getOutputStream()).use {
logV("$ip:$port --> $cmd")
it.write("$cmd\r\n".toByteArray())
it.flush()
BufferedReader(InputStreamReader(s.getInputStream())).readLine().also { logV("$ip:$port <-- $it") }
}
}
enum class SpeedEffect { sudden, smooth }
enum class FlowEndAction(val id: Int) { recover(0), stay(1), off(2) }
enum class FlowMode(val id: Int) { colorRgb(1), colorTemperature(2), sleep(7) }
data class Cmd(val method: String, val params: List<Any> = emptyList(), val id: Int = 1, @Transient private val dry: Boolean = false) {
var firstResponse: List<String>? = null
private set
init {
if (!dry) firstResponse = sendCmdAll(toCmdString())
}
fun toCmdString() = App.gson.toJson(this)
}
// Docs: http://www.yeelight.com/download/Yeelight_Inter-Operation_Spec.pdf
object API {
fun getProperties(propertiesNames: List<String>) = Cmd("get_prop", propertiesNames)
fun setCurrentAsDefault() = Cmd("set_default")
fun setPower(isOn: Boolean = true, effect: SpeedEffect = SpeedEffect.smooth, duration: Long = 500) = Cmd("set_power", listOf(if (isOn) "on" else "off", effect, duration))
fun toggle() = Cmd("toggle")
/**
* brightness: 1 - 100
*/
fun setBrightness(brightness: Int, effect: SpeedEffect = SpeedEffect.smooth, duration: Long = 500) = Cmd("set_bright", listOf(brightness.coerceIn(1..100), effect, duration))
fun startColorFlow(count: Int, action: FlowEndAction, flowExpression: String) = Cmd("start_cf", listOf(count, action.id, flowExpression))
fun stopColorFlow() = Cmd("stop_cf")
fun _setScene(): Yeelight.Cmd = TODO("Missing app prarams") // Cmd("set_scene")
fun _cronAdd(): Yeelight.Cmd = TODO("Missing app prarams") // Cmd("cron_add")
fun _cronGet(): Yeelight.Cmd = TODO("Missing app prarams") // Cmd("cron_get")
fun _cronDel(): Yeelight.Cmd = TODO("Missing app prarams") // Cmd("cron_del")
/**
* colorTemperature: 1700 ~ 6500
*/
fun setColorTemperature(colorTemperature: Int, effect: SpeedEffect = SpeedEffect.smooth, duration: Long = 500) = Cmd("set_ct_abx", listOf(colorTemperature.coerceIn(1700..6500), effect, duration))
fun setColorRgb(color: Int, effect: SpeedEffect = SpeedEffect.smooth, duration: Long = 500) = Cmd("set_rgb", listOf(color.coerceIn(0..0xffffff), effect, duration))
}
}
@omarmiatello
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment