diff --git a/.gitignore b/.gitignore
index bb460e7..59e2947 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+Package.resolved
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/YeeLampa.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/YeeLampa.xcscheme
deleted file mode 100644
index cc6fb7a..0000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/YeeLampa.xcscheme
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Package.swift b/Package.swift
index 952edc3..84ef7ce 100644
--- a/Package.swift
+++ b/Package.swift
@@ -3,22 +3,22 @@
import PackageDescription
let package = Package(
- name: "YeeLampa",
+ name: "YeelightKit",
platforms: [
.iOS(.v13),
- .macOS(.v10_15)
+ .macOS(.v12)
],
products: [
.library(
- name: "YeeLampa",
- targets: ["YeeLampa"]),
+ name: "YeelightKit",
+ targets: ["YeelightKit"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.5.0"))
],
targets: [
.target(
- name: "YeeLampa",
+ name: "YeelightKit",
dependencies: ["Alamofire"],
resources: [
.copy("Resources/DeviceProps.plist")
diff --git a/README.md b/README.md
index 74e6dcc..709453e 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
-# YeeLampa
+# YeelightKit
-A Swift package to interact with Yeelight devices.
+A Swift package for type-safe interactions with Yeelight API.
diff --git a/Sources/Documentation.docc/Documentation.md b/Sources/Documentation.docc/Documentation.md
new file mode 100644
index 0000000..c972c91
--- /dev/null
+++ b/Sources/Documentation.docc/Documentation.md
@@ -0,0 +1,8 @@
+# ``YeelightKit``
+
+A Swift package for type-safe interactions with Yeelight API.
+
+## Overview
+
+yee
+
diff --git a/Sources/YeeLampa/ChangeEffect.swift b/Sources/YeeLampa/ChangeEffect.swift
deleted file mode 100644
index a167e52..0000000
--- a/Sources/YeeLampa/ChangeEffect.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Егор Яковенко on 18.12.2021.
-//
-
-public enum ChangeEffect {
- case sudden
- case smooth
-
- var rawValue: String {
- switch self {
- case .sudden:
- return "sudden"
- case .smooth:
- return "smooth"
- }
- }
-}
diff --git a/Sources/YeeLampa/QueryString.swift b/Sources/YeeLampa/QueryString.swift
deleted file mode 100644
index 6dddddd..0000000
--- a/Sources/YeeLampa/QueryString.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-//
-// QueryString.swift
-//
-//
-// Created by Егор Яковенко on 01.12.2021.
-//
-
-extension Dictionary {
- var queryString: String {
- var output: String = ""
- for (key,value) in self {
- output += "\(key)=\(value)&"
- }
- output = String(output.dropLast())
- return output
- }
-}
diff --git a/Sources/YeeLampa/Region.swift b/Sources/YeeLampa/Region.swift
deleted file mode 100644
index 3e8482d..0000000
--- a/Sources/YeeLampa/Region.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// Region.swift
-//
-//
-// Created by Егор Яковенко on 28.11.2021.
-//
-
-public enum Region {
- case Germany
- case Russia
-
- var urlFormat: String {
- switch self {
- case .Germany: return "de"
- case .Russia: return "ru"
- }
- }
-}
diff --git a/Sources/YeeLampa/YeeLampa.swift b/Sources/YeeLampa/YeeLampa.swift
deleted file mode 100644
index bbc7130..0000000
--- a/Sources/YeeLampa/YeeLampa.swift
+++ /dev/null
@@ -1,103 +0,0 @@
-import Foundation
-import Alamofire
-
-/// Main class of YeeLampa.
-public class YeeLampa {
- public static let clientId = 2882303761517308695
- public static let clientSecret = "OrwZHJ/drEXakH1LsfwwqQ=="
- private var region: Region
- private var accessToken: String
- private let deviceListUrl: URL
-
- /// Creates a YeeLampa instance with provided credentials.
- /// - Parameters:
- /// - accessToken: The token you acquired when logging in.
- /// - region: The region we will connect to.
- public init(accessToken: String, region: Region) {
- self.region = region
- self.accessToken = accessToken
- self.deviceListUrl = URL(string: "https://\(region.urlFormat).openapp.io.mi.com/openapp/user/device_list")!
- }
-
- private struct AuthResponse: Codable {
- let access_token: String
- }
-
- /// Converts an authorization grant to a usable access token.
- /// - Parameters:
- /// - grant: A grant to convert.
- public static func convertAuthGrantToToken(_ grant: String) async throws -> String {
- let url = URL(string: "https://account.xiaomi.com/oauth2/token")!
- let task = AF.request(
- url,
- method: .get,
- parameters: [
- "client_id": YeeLampa.clientId,
- "client_secret": YeeLampa.clientSecret,
- "grant_type": "authorization_code",
- "redirect_uri": "http://www.mi.com",
- "code": grant
- ],
- headers: [
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4495.0 Safari/537.36"
- ]
- ).serializingString()
- do {
- let response = try await task.value
- let validJson = response.replacingOccurrences(of: "&&&START&&&", with: "")
- return try! JSONDecoder().decode(AuthResponse.self, from: Data(validJson.utf8)).access_token
- } catch {
- throw LoginError.unknownError
- }
- }
-
- /// Returns the device list.
- /// This function does not use a cache, it returns new data from the server.
- public func getDeviceList() async throws -> [Device] {
- var request = URLRequest(url: self.deviceListUrl)
- request.httpMethod = "POST"
- request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
- request.httpBody = Data("clientId=\(YeeLampa.clientId)&accessToken=\(self.accessToken)".utf8)
-
- let task = AF.request(self.deviceListUrl, method: .post, parameters: [
- "clientId": YeeLampa.clientId,
- "accessToken": self.accessToken
- ], headers: [
- "Content-Type": "application/x-www-form-urlencoded"
- ]).serializingDecodable(BaseJsonModel.self)
- return try await task.value.result.list.map { device in
- return device.toPDevice()
- }
- }
-
- public func setPower(to on: Bool, for device: Device) async throws {
- try await self.sendDeviceRequest(
- url: URL(string: "https://\(self.region.urlFormat).openapp.io.mi.com/openapp/device/rpc/\(device.deviceId)")!,
- arguments: ["method": "set_power", "params": [on ? "on" : "off"]]
- )
- }
-
- /// An internal function to pass requests to devices.
- /// - Parameters:
- /// - url: A url to call when making a request.
- /// - arguments: JSON arguments that will be stuffid in the request.
- /// - Returns: A response in Data type.
- private func sendDeviceRequest(url: URL, arguments: [String: Any]) async throws -> Data {
- do {
- let payload = try JSONSerialization.data(withJSONObject: arguments)
- let task = AF.request(
- url,
- method: .post,
- parameters: [
- "clientId": YeeLampa.clientId,
- "accessToken": self.accessToken,
- "data": String(data: payload, encoding: .utf8)!.replacingOccurrences(of: "\n", with: "")
- ],
- headers: ["Content-Type": "application/x-www-form-urlencoded"]
- ).serializingData()
- return try await task.value
- } catch {
- throw RequestError.badToken
- }
- }
-}
diff --git a/Sources/YeelightKit/Enums/ChangeEffect.swift b/Sources/YeelightKit/Enums/ChangeEffect.swift
new file mode 100644
index 0000000..0a75f79
--- /dev/null
+++ b/Sources/YeelightKit/Enums/ChangeEffect.swift
@@ -0,0 +1,11 @@
+//
+// File.swift
+//
+//
+// Created by Егор Яковенко on 18.12.2021.
+//
+
+public enum ChangeEffect: String {
+ case sudden
+ case smooth
+}
diff --git a/Sources/YeelightKit/Enums/ColorFlowExpression.swift b/Sources/YeelightKit/Enums/ColorFlowExpression.swift
new file mode 100644
index 0000000..7c1ec79
--- /dev/null
+++ b/Sources/YeelightKit/Enums/ColorFlowExpression.swift
@@ -0,0 +1,12 @@
+//
+// ColorFlowExpression.swift
+//
+//
+// Created by Егор Яковенко on 19.12.2021.
+//
+
+public enum ColorFlowExpression {
+ case color(duration: Int, value: (red: Int, green: Int, blue: Int), brightness: Int?)
+ case colorTemperature(duration: Int, value: (red: Int, green: Int, blue: Int), brightness: Int?)
+ case sleep(duration: Int, value: (red: Int, green: Int, blue: Int))
+}
diff --git a/Sources/YeeLampa/DeviceModel.swift b/Sources/YeelightKit/Enums/DeviceModel.swift
similarity index 100%
rename from Sources/YeeLampa/DeviceModel.swift
rename to Sources/YeelightKit/Enums/DeviceModel.swift
diff --git a/Sources/YeelightKit/Enums/DeviceProperty.swift b/Sources/YeelightKit/Enums/DeviceProperty.swift
new file mode 100644
index 0000000..b125d23
--- /dev/null
+++ b/Sources/YeelightKit/Enums/DeviceProperty.swift
@@ -0,0 +1,33 @@
+//
+// DeviceProperty.swift
+//
+//
+// Created by Егор Яковенко on 22.12.2021.
+//
+
+/// An enum for representing device properties. Used in ``YeelightKit/YeelightKit/get(properties:of:)`` function, to get individual properties of a device.
+public enum DeviceProperty: String {
+ case isOn = "power"
+ case brightness = "bright"
+ case colorTemperature = "ct"
+ case rgbColor = "rgb"
+ case hue
+ case saturation = "sat"
+ case colorMode = "color_mode"
+ /// Is the flow mode running.
+ case isFlowing = "flowing"
+ case isMusicModeOn = "music_on"
+ case name
+ case isBackgroundOn = "bg_power"
+ /// Is the flow mode running on background light.
+ case isBackgroundFlowing = "bg_flowing"
+ case backgroundColorTemperature = "bg_ct"
+ case backgroundColorMode = "bg_lmode"
+ case backgroundBrightness = "bg_bright"
+ case backgroundRgbColor = "bg_rgb"
+ case backgroundHue = "bg_hue"
+ case backgroundSaturation = "bg_sat"
+ case nightLightBrightness = "nl_br"
+ /// Only applicable for the ceiling light.
+ case activeMode = "active_mode"
+}
diff --git a/Sources/YeelightKit/Enums/PowerState.swift b/Sources/YeelightKit/Enums/PowerState.swift
new file mode 100644
index 0000000..ab5f928
--- /dev/null
+++ b/Sources/YeelightKit/Enums/PowerState.swift
@@ -0,0 +1,11 @@
+//
+// PowerState.swift
+//
+//
+// Created by Егор Яковенко on 19.12.2021.
+//
+
+public enum PowerState: String {
+ case on
+ case off
+}
diff --git a/Sources/YeelightKit/Enums/Region.swift b/Sources/YeelightKit/Enums/Region.swift
new file mode 100644
index 0000000..774faba
--- /dev/null
+++ b/Sources/YeelightKit/Enums/Region.swift
@@ -0,0 +1,11 @@
+//
+// Region.swift
+//
+//
+// Created by Егор Яковенко on 28.11.2021.
+//
+
+public enum Region: String {
+ case Germany = "de"
+ case Russia = "ru"
+}
diff --git a/Sources/YeelightKit/Enums/TurnOnMode.swift b/Sources/YeelightKit/Enums/TurnOnMode.swift
new file mode 100644
index 0000000..1c62f50
--- /dev/null
+++ b/Sources/YeelightKit/Enums/TurnOnMode.swift
@@ -0,0 +1,22 @@
+//
+// TurnOnMode.swift
+//
+//
+// Created by Егор Яковенко on 19.12.2021.
+//
+
+/// Modes available when turning on a device.
+public enum TurnOnMode: Int {
+ /// Normal turn on operation (default value).
+ case normal = 0
+ /// Turn on and switch to CT mode.
+ case colorTemperature = 1
+ /// Turn on and switch to RGB mode.
+ case rgb = 2
+ /// Turn on and switch to HSV mode.
+ case hsv = 3
+ /// Turn on and switch to color flow mode.
+ case colorFlow = 4
+ /// Turn on and switch to Night light mode. (Ceiling light only).
+ case nightLight = 5
+}
diff --git a/Sources/YeeLampa/Errors/LoginError.swift b/Sources/YeelightKit/Errors/LoginError.swift
similarity index 100%
rename from Sources/YeeLampa/Errors/LoginError.swift
rename to Sources/YeelightKit/Errors/LoginError.swift
diff --git a/Sources/YeeLampa/Errors/RequestError.swift b/Sources/YeelightKit/Errors/RequestError.swift
similarity index 100%
rename from Sources/YeeLampa/Errors/RequestError.swift
rename to Sources/YeelightKit/Errors/RequestError.swift
diff --git a/Sources/YeeLampa/Models/BedsideLampDevice.swift b/Sources/YeelightKit/Models/BedsideLampDevice.swift
similarity index 100%
rename from Sources/YeeLampa/Models/BedsideLampDevice.swift
rename to Sources/YeelightKit/Models/BedsideLampDevice.swift
diff --git a/Sources/YeeLampa/Models/DeskLampDevice.swift b/Sources/YeelightKit/Models/DeskLampDevice.swift
similarity index 100%
rename from Sources/YeeLampa/Models/DeskLampDevice.swift
rename to Sources/YeelightKit/Models/DeskLampDevice.swift
diff --git a/Sources/YeeLampa/Models/Device.swift b/Sources/YeelightKit/Models/Device.swift
similarity index 72%
rename from Sources/YeeLampa/Models/Device.swift
rename to Sources/YeelightKit/Models/Device.swift
index 8bfbe3a..0693bb8 100644
--- a/Sources/YeeLampa/Models/Device.swift
+++ b/Sources/YeelightKit/Models/Device.swift
@@ -12,14 +12,14 @@ open class Device: Identifiable {
return lhs.deviceId == rhs.deviceId
}
- let deviceId: String
- let name: String
- let model: DeviceModel
- let macAddress: String
- let longitude: Double
- let latitude: Double
- let isOnline: Bool
- let isOn: Bool
+ public let deviceId: String
+ public let name: String
+ public let model: DeviceModel
+ public let macAddress: String
+ public let longitude: Double
+ public let latitude: Double
+ public let isOnline: Bool
+ public let isOn: Bool
init(deviceId: String, name: String, model: DeviceModel, macAddress: String, longitude: Double, latitude: Double, isOnline: Bool, isOn: Bool) {
self.deviceId = deviceId
diff --git a/Sources/YeeLampa/Models/Json/BaseJsonModel.swift b/Sources/YeelightKit/Models/Json/BaseJsonModel.swift
similarity index 100%
rename from Sources/YeeLampa/Models/Json/BaseJsonModel.swift
rename to Sources/YeelightKit/Models/Json/BaseJsonModel.swift
diff --git a/Sources/YeeLampa/Models/Json/BatchRpcJsonModel.swift b/Sources/YeelightKit/Models/Json/BatchRpcJsonModel.swift
similarity index 100%
rename from Sources/YeeLampa/Models/Json/BatchRpcJsonModel.swift
rename to Sources/YeelightKit/Models/Json/BatchRpcJsonModel.swift
diff --git a/Sources/YeeLampa/Models/Json/DeviceListJsonModel.swift b/Sources/YeelightKit/Models/Json/DeviceListJsonModel.swift
similarity index 100%
rename from Sources/YeeLampa/Models/Json/DeviceListJsonModel.swift
rename to Sources/YeelightKit/Models/Json/DeviceListJsonModel.swift
diff --git a/Sources/YeeLampa/Models/LightStripDevice.swift b/Sources/YeelightKit/Models/LightStripDevice.swift
similarity index 100%
rename from Sources/YeeLampa/Models/LightStripDevice.swift
rename to Sources/YeelightKit/Models/LightStripDevice.swift
diff --git a/Sources/YeeLampa/Resources/DeviceProps.plist b/Sources/YeelightKit/Resources/DeviceProps.plist
similarity index 100%
rename from Sources/YeeLampa/Resources/DeviceProps.plist
rename to Sources/YeelightKit/Resources/DeviceProps.plist
diff --git a/Sources/YeelightKit/YeeLampa.swift b/Sources/YeelightKit/YeeLampa.swift
new file mode 100644
index 0000000..afb0774
--- /dev/null
+++ b/Sources/YeelightKit/YeeLampa.swift
@@ -0,0 +1,271 @@
+import Foundation
+import Alamofire
+
+/// Main class of YeelightKit.
+public class YeelightKit {
+ public static let clientId = 2882303761517308695
+ public static let clientSecret = "OrwZHJ/drEXakH1LsfwwqQ=="
+ private var region: Region
+ private var accessToken: String
+ private let deviceListUrl: URL
+
+ /// Creates a YeelightKit instance with provided credentials.
+ /// - Parameters:
+ /// - accessToken: The token you acquired when logging in.
+ /// - region: The region we will connect to.
+ public init(accessToken: String, region: Region) {
+ self.region = region
+ self.accessToken = accessToken
+ self.deviceListUrl = URL(string: "https://\(region.rawValue).openapp.io.mi.com/openapp/user/device_list")!
+ }
+
+ private struct AuthResponse: Codable {
+ let access_token: String
+ }
+
+ /// Converts an authorization grant to a usable access token.
+ /// - Parameters:
+ /// - grant: A grant to convert.
+ public static func convertAuthGrantToToken(_ grant: String) async throws -> String {
+ let url = URL(string: "https://account.xiaomi.com/oauth2/token")!
+ let task = AF.request(
+ url,
+ method: .get,
+ parameters: [
+ "client_id": YeelightKit.clientId,
+ "client_secret": YeelightKit.clientSecret,
+ "grant_type": "authorization_code",
+ "redirect_uri": "http://www.mi.com",
+ "code": grant
+ ],
+ headers: [
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4495.0 Safari/537.36"
+ ]
+ ).serializingString()
+ do {
+ let response = try await task.value
+ let validJson = response.replacingOccurrences(of: "&&&START&&&", with: "")
+ return try! JSONDecoder().decode(AuthResponse.self, from: Data(validJson.utf8)).access_token
+ } catch {
+ throw LoginError.unknownError
+ }
+ }
+
+ /// Returns the device list.
+ /// This function does not use a cache, it returns new data from the server.
+ public func getDeviceList() async throws -> [Device] {
+ var request = URLRequest(url: self.deviceListUrl)
+ request.httpMethod = "POST"
+ request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
+ request.httpBody = Data("clientId=\(YeelightKit.clientId)&accessToken=\(self.accessToken)".utf8)
+
+ let task = AF.request(self.deviceListUrl, method: .post, parameters: [
+ "clientId": YeelightKit.clientId,
+ "accessToken": self.accessToken
+ ], headers: [
+ "Content-Type": "application/x-www-form-urlencoded"
+ ]).serializingDecodable(BaseJsonModel.self)
+ return try await task.value.result.list.map { device in
+ return device.toPDevice()
+ }
+ }
+
+ /// Set a color temperature for a device that supports it. Only accepted when a device is in `on` state.
+ /// - Parameters:
+ /// - temp: A target temperature.
+ /// - effect: An effect that wiil be used.
+ /// - duration: Time that it will take.
+ /// - device: A target device.
+ public func setColorTemperature(to temp: Int, withEffect effect: ChangeEffect = .smooth, withDuration duration: Int = 500, for device: Device) async throws {
+ let _ = try await runDeviceMethod(deviceId: device.deviceId, method: "set_ct_abx", params: [temp, effect.rawValue, duration])
+ }
+
+ /// Sets an RGB color. Only accepted when a device is in `on` state.
+ /// - Parameters:
+ /// - color: A RGB color you want to set.
+ /// - effect: An effect that wiil be used.
+ /// - duration: Time that it will take.
+ /// - device: A target device.
+ public func setRgbColor(
+ of color: (
+ red: Int,
+ green: Int,
+ blue: Int
+ ),
+ withEffect effect: ChangeEffect = .smooth,
+ withDuration duration: Int = 500,
+ for device: Device
+ ) async throws {
+ let _ = try await runDeviceMethod(deviceId: device.deviceId, method: "set_rgb", params: [
+ ((color.red << 16) + (color.green << 8) + color.blue),
+ effect.rawValue,
+ duration]
+ )
+ }
+
+ /// Sets an HSV color. Only accepted when a device is in `on` state.
+ /// - Parameters:
+ /// - color: A color you want to set. `hue`'s bounds are 0 to 359(yea, it's strange, but this is how the API works), `saturation` is from 0 to 100, and `brightness` is from 0 to 100. Note that if a `brightness` value is provided, then there will be 2 requests, one to set the `hue` and `saturation`, and one for `brightness`. Don't blame me on this design, this is how the API works! :D
+ /// - effect: An effect that wiil be used.
+ /// - duration: Time that it will take.
+ /// - device: A target device.
+ public func setHsvColor(
+ of color: (
+ hue: Int,
+ saturation: Int
+ ),
+ withEffect effect: ChangeEffect = .smooth,
+ withDuration duration: Int = 500,
+ for device: Device
+ ) async throws {
+ let _ = try await runDeviceMethod(deviceId: device.deviceId, method: "set_hsv", params: [color.hue, color.saturation, effect.rawValue, duration])
+ }
+
+ /// Sets the brightness of a device. Only accepted when a device is in `on` state.
+ /// - Parameters:
+ /// - value: A brightness value you want to set. Allowed values are from 0 to 100.
+ /// - effect: An effect that wiil be used.
+ /// - duration: Time that it will take.
+ /// - device: A target device.
+ public func setBrightness(to value: Int, withEffect effect: ChangeEffect = .smooth, withDuration duration: Int = 500, for device: Device) async throws {
+ let _ = try await runDeviceMethod(deviceId: device.deviceId, method: "set_bright", params: [value, effect.rawValue, duration])
+ }
+
+ /// Sets power state for a device.
+ /// - Parameters:
+ /// - on: The power state you want your device to change to.
+ /// - effect: An effect that wiil be used.
+ /// - duration: Time that it will take.
+ /// - device: A target device.
+ public func setPower(
+ to on: Bool,
+ withEffect effect: ChangeEffect = .smooth,
+ withDuration duration: Int = 500,
+ withMode mode: TurnOnMode = .normal,
+ for device: Device
+ ) async throws {
+ let _ = try await runDeviceMethod(deviceId: device.deviceId, method: "set_power", params: [(on ? "on" : "off"), effect.rawValue, duration, mode.rawValue])
+ }
+
+ /// Sets power state for a device.
+ /// - Parameters:
+ /// - state: The power state you want your device to change to.
+ /// - effect: An effect that wiil be used.
+ /// - duration: Time that it will take.
+ /// - device: A target device.
+ public func setPower(
+ to state: PowerState,
+ withEffect effect: ChangeEffect = .smooth,
+ withDuration duration: Int = 500,
+ withMode mode: TurnOnMode = .normal,
+ for device: Device
+ ) async throws {
+ let _ = try await runDeviceMethod(deviceId: device.deviceId, method: "set_power", params: [state.rawValue, effect.rawValue, duration, mode.rawValue])
+ }
+
+ /// Toggles an LED.
+ /// - Parameter device: A target device.
+ public func togglePower(of device: Device) async throws {
+ let _ = try await runDeviceMethod(deviceId: device.deviceId, method: "toggle", params: [])
+ }
+
+ public enum Action: Int {
+ case recoverToPreviousState = 0
+ case stayTheSame
+ case shutDown
+ }
+
+ /// This method is used to save current state of smart LED in persistent memory. So if user powers off and then powers on the smart LED again (hard power reset), the smart LED will show last saved state.
+ /// - Parameter device: Target device.
+ public func saveCurrentStateAsDefault(for device: Device) async throws {
+ let _ = try await runDeviceMethod(deviceId: device.deviceId, method: "set_default", params: [])
+ }
+
+ public func startColorFlow(of flow: [ColorFlowExpression], withCount count: Int, withAction action: Action, for device: Device) async throws {
+ var flowString: String = ""
+ for (index, element) in flow.enumerated() {
+ switch element {
+ case .color(let duration, let value, let brightness):
+ var computedBrightness: Int {
+ if brightness == nil {
+ return -1
+ } else {
+ return brightness!
+ }
+ }
+
+ flowString.append("\(duration), 1, \(((value.red << 16) + (value.green << 8) + value.blue)), \(computedBrightness)")
+ if index != flow.count {
+ flowString.append(",")
+ }
+ case .colorTemperature(let duration, let value, let brightness):
+ var computedBrightness: Int {
+ if brightness == nil {
+ return -1
+ } else {
+ return brightness!
+ }
+ }
+
+ flowString.append("\(duration), 2, \(((value.red << 16) + (value.green << 8) + value.blue)), \(computedBrightness)")
+ if index != flow.count {
+ flowString.append(",")
+ }
+ case .sleep(let duration, let value):
+ flowString.append("\(duration), 7, \(((value.red << 16) + (value.green << 8) + value.blue)), 0")
+ if index != flow.count {
+ flowString.append(",")
+ }
+ }
+ }
+
+ let _ = try await runDeviceMethod(deviceId: device.deviceId, method: "start_cf", params: [count, action.rawValue, flowString])
+ }
+
+ private func runDeviceMethod(deviceId: String, method: String, params: Array) async throws -> Data {
+ return try await self.sendDeviceRequest(
+ url: URL(string: "https://\(self.region.rawValue).openapp.io.mi.com/openapp/device/rpc/\(deviceId)")!,
+ arguments: ["method": method, "params": params]
+ )
+ }
+
+ public func get(properties: [DeviceProperty], of device: Device) async throws -> [String] {
+ var requestProps: [String] = []
+
+ for prop in properties {
+ requestProps.append(prop.rawValue)
+ }
+
+ let response = try await runDeviceMethod(deviceId: device.deviceId, method: "get_prop", params: requestProps)
+ return try JSONDecoder().decode(BaseJsonModel<[String]>.self, from: response).result
+ }
+
+ public func get(property: DeviceProperty, of device: Device) async throws -> String {
+ let response = try await runDeviceMethod(deviceId: device.deviceId, method: "get_prop", params: [property.rawValue])
+ return try JSONDecoder().decode(BaseJsonModel<[String]>.self, from: response).result[0]
+ }
+
+ /// An internal function to pass requests to devices.
+ /// - Parameters:
+ /// - url: A url to call when making a request.
+ /// - arguments: JSON arguments that will be stuffid in the request.
+ /// - Returns: A response in Data type.
+ private func sendDeviceRequest(url: URL, arguments: [String: Any]) async throws -> Data {
+ do {
+ let payload = try JSONSerialization.data(withJSONObject: arguments)
+ let task = AF.request(
+ url,
+ method: .post,
+ parameters: [
+ "clientId": YeelightKit.clientId,
+ "accessToken": self.accessToken,
+ "data": String(data: payload, encoding: .utf8)!.replacingOccurrences(of: "\n", with: "")
+ ],
+ headers: ["Content-Type": "application/x-www-form-urlencoded"]
+ ).serializingData()
+ return try await task.value
+ } catch {
+ throw RequestError.badToken
+ }
+ }
+}