HAKit

Documentation codecov License Apache 2.0

该库允许您连接到Home Assistant WebSocket API来发出命令和订阅事件,以及发出REST API请求。未来的计划包括提供身份验证支持。

API 参考

您可以查看完整的文档。最重要的可用方法集位于HAConnection协议上。此协议是与 Home Assistant 实例通信的主要入口点。

创建和连接

创建连接需要两个信息:服务器 URL 和访问令牌。两者都在尝试建立连接时检索。由于 Home Assistant 实例的连通性可能因当前网络而异,因此每次尝试连接时都会重新查询这些值。

您可以使用暴露的初始化器来获取连接实例

let connection = HAKit.connection(configuration: .init(
  connectionInfo: {
    // Connection is required to be returned synchronously.
    // In a real implementation, handle both URL/connection info without crashing.
    try! .init(url: URL(string: "http://homeassistant.local:8123")!)
  },
  fetchAuthToken: { completion in
    // Access tokens are retrieved asynchronously, but be aware that Home Assistant
    // has a timeout of 10 seconds for sending your access token.
    completion(.success("API_Token_Here"))
  }
))

您可以进一步配置此连接的其他属性,例如 callbackQueue(将调用您的处理程序的位置),以及触发手动连接尝试。有关更多信息,请参见协议。

一旦您调用.connect()(或自动调用它),并且在您调用.disconnect()之前,连接将尝试保持连接状态,方法是在网络状态更改时以及断开连接后的重试期后尝试重新连接。

日志记录

您可以通过配置 HAGlobal 来启用库的日志记录

HAGlobal.log = { text in
  // just straight to the console
  print($0)
  // or something like XCGLogger
  log.info("WebSocket: \(text)")
}

这将包括诸如正在发送的命令以及连接的生命周期之类的内容。

发送和订阅

有两种类型的请求:一种是具有即时结果的请求,另一种是触发事件直到取消的请求。 这些操作分别是“发送”和“订阅”。 例如,您可能会发送服务调用,但订阅模板的渲染。

发出的请求将继续跨重连重试,直到执行一次,并且订阅将在必要时自动重新注册,直到取消为止。 每个sendsubscribe都会返回一个HACancellable令牌,您可以取消该令牌,并且每个订阅处理程序也包括一个令牌。

例如,像HATypedRequestHATypedSubscription中的其他调用一样,检索当前用户具有提供强类型值的辅助方法。 例如,您可以采用以下两种方式之一来编写它

// with the CurrentUser convenience helper
connection.send(.currentUser()) { result in
  switch result {
  case let .success(user):
    // e.g. user.id or user.name are available on the result object
  case let .failure(error):
    // an error occurred with the request
  }
}

// or issued directly, and getting the raw response
connection.send(.init(type: .currentUser, data: [:])) { result in
  switch result {
  case let .success(data):
    // data is an `HAData` which wraps possible responses and provides decoding
  case let .failure(error):
    // an error occurred with the request
  }
}

// you can also issue REST calls
Current.apiConnection.send(.init(
  // this will POST to `/api/template` with the data as the body
  type: .rest(.post, "template"),
  data: ["template": "{{ now() }}"]
)) { result in
  // same result type as sending a WebSocket request
}

同样,可以使用便捷助手或直接方式来订阅事件。

// with the RenderTemplate convenience helper
connection.subscribe(
  to: .renderTemplate("{{ states('sun.sun') }} {{ states.device_tracker | count }}"),
  initiated: { result in
    // when the initiated method is provided, this is the result of the subscription
  },
  handler: { [textView] cancelToken, response in
    // the overall response is parsed for type, but native template rendering means
    // the rendered type will be a Dictionary, Array, etc.
    textView.text = String(describing: response.result)
    // you could call `cancelToken.cancel()` to end the subscription here if desired
  }
)

// or issued directly, and getting the raw response
connection.subscribe(
  to: .init(type: .renderTemplate, data: [
    "template": "{{ states('sun.sun') }} {{ states.device_tracker | count }}"
  ]),
  initiated: { result in
    // when the initiated method is provided, this is the result of the subscription
  },
  handler: { [textView] cancelToken, data in
    // data is an `HAData` which wraps possible responses and provides decoding
    // the decode methods infer which type you're asking for and attempt to convert
    textView.text = try? data.decode("result")
    // you could call `cancelToken.cancel()` to end the subscription here if desired
  }
)  

您还可以调用任何请求或订阅,即使是那些没有围绕其名称的便捷访问器。 事件名称和请求类型符合ExpressibleByStringLiteral,或者您可以使用原始值来初始化它们。

解码

当不使用便捷包装器时,许多方法会将结果作为 HAData 传递。 这是代替底层字典或数组响应传递的,它具有将字典中的键解码为特定类型的便捷方法。 这与Decodable相似但不完全相同-许多Home Assistant调用将返回必须保留为Any的结果,而Swift的JSON编码对此处理不佳。

有关可用方法,请参见HADataDecodable

缓存结果

HACache 允许您缓存请求的结果并订阅事件以更新它们。

该库包含一些内置缓存,您可以通过HACachesContainer通过connection.caches进行访问。 有关可用缓存的信息,请参见文档。

填充

使用HACachePopulateInfo完成缓存的填充,其中包含

  1. 要发送的请求
  2. 将请求的结果转换为缓存值的转换

例如,如果您想发出请求.getStates()并跟踪所有entityId

let populate = HACachePopulateInfo<Set<String>>(
  request: .getStates(),
  transform: { info in
    return Set(info.incoming.map(\.entityId))
  }
)

订阅

使用一个或多个HACacheSubscribeInfo完成缓存值的更新,每个订阅包含

  1. 要订阅的事件
  2. 如何将事件转换为缓存应执行的操作:重新发出填充请求,将值更新为新版本,以及完全忽略该事件。

例如,如果您想观看状态更改以更新entityId

let subscribe = HACacheSubscribeInfo<Set<String>>(
  subscription: .stateChanged(),
  transform: { info in
    var entityIds = info.current
    if info.incoming.newState == nil {
      entityIds.remove(info.incoming.entityId)
    } else {
      entityIds.insert(info.incoming.entityId)
    }
    return .replace(entityIds)
  }
)

将它们放在一起

将这两个放在一起,我们可以创建一个缓存,以维护当前已知的entityId

let entityIds = HACache(connection: connection, populate: populate, subscribe: subscribe)

您现在可以订阅缓存中的更改

entityIds.subscribe { token, value in
  print("current entity ids are: \(value)")
}

缓存将推迟执行其填充,直到连接已连接并且至少有一个订阅者。 您可以通过shouldResetWithoutSubscribers属性来控制它是否在没有订阅者的情况下断开与订阅的连接。

模拟

此库包含用于编写测试的可选添加项。 有关更多信息,请参见源代码

PromiseKit

此库包含与PromiseKit一起使用的可选添加项。 有关更多信息,请参见源代码

安装

Swift Package Manager

要安装该库,请将其作为依赖项添加到 Package.swift 中,例如

.package(url: "https://github.com/home-assistant/HAKit", from: Version(0, 4, 2))

要将其添加到 Xcode 项目,您可以通过将 URL 添加到 File > Swift Packages > Add Package Dependency 来完成。 您会发现一些可用的目标

CocoaPods

将以下行添加到您的 Podfile

pod "HAKit", "~> 0.4.4"
# We are working from a fork of Starscream due to a necessary fix, please specify in your podfile
pod 'Starscream', git: 'https://github.com/bgoncal/starscream', branch: 'ha-URLSession-fix'
# pod "HAKit/PromiseKit" # optional, for PromiseKit support
# pod "HAKit/Mocks" # optional, for tests

贡献

有关如何构建和修改此库的更多信息,请参见CONTRIBUTING.md

许可证

该库根据Apache 2.0 许可证提供。它还依赖于Starscream,用于在旧版本的iOS上进行WebSocket连接。 Starscream也根据Apache 2.0许可提供。