Last active
December 5, 2020 11:07
-
-
Save omarmiatello/19d52a0a1e33e39b8475d53ec5f9d2fe to your computer and use it in GitHub Desktop.
Yeelight API in Kotlin. Add support for multiple light!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
New project: https://github.com/omarmiatello/yeelight