Initial Commit
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
/*.xcodeproj
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
|
.dewPoint-env
|
||||||
8
Bootstrap/dewPoint-env-example
Normal file
8
Bootstrap/dewPoint-env-example
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"APP_ENV": "development",
|
||||||
|
"MQTT_HOST": "127.0.0.1",
|
||||||
|
"MQTT_PORT": "1883",
|
||||||
|
"MQTT_IDENTIFIER": "dewPoint-controller",
|
||||||
|
"MQTT_USERNAME": "mqtt_user",
|
||||||
|
"MQTT_PASSWORD": "secret!"
|
||||||
|
}
|
||||||
9
Makefile
Normal file
9
Makefile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
bootstrap-env:
|
||||||
|
@cp Bootstrap/dewPoint-env-example .dewPoint-env
|
||||||
|
|
||||||
|
build:
|
||||||
|
@swift build
|
||||||
|
|
||||||
|
run:
|
||||||
|
@swift run dewPoint-controller
|
||||||
61
Package.resolved
Normal file
61
Package.resolved
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"object": {
|
||||||
|
"pins": [
|
||||||
|
{
|
||||||
|
"package": "mqtt-nio",
|
||||||
|
"repositoryURL": "https://github.com/adam-fowler/mqtt-nio.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "976a4b6019e1dd365a0da793babadd5cc958ce2b",
|
||||||
|
"version": "2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-log",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-log.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
|
||||||
|
"version": "1.4.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "6aa9347d9bc5bbfe6a84983aec955c17ffea96ef",
|
||||||
|
"version": "2.33.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio-ssl",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "5e68c1ded15619bb281b273fa8c2d8fd7f7b2b7d",
|
||||||
|
"version": "2.16.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio-transport-services",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio-transport-services.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "e7f5278a26442dc46783ba7e063643d524e414a0",
|
||||||
|
"version": "1.11.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-psychrometrics",
|
||||||
|
"repositoryURL": "https://github.com/swift-psychrometrics/swift-psychrometrics",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "03573545c3750b406921eb22a9575c8062beef88",
|
||||||
|
"version": "0.1.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
83
Package.swift
Normal file
83
Package.swift
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// swift-tools-version:5.5
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "dewPoint-controller",
|
||||||
|
platforms: [
|
||||||
|
.macOS(.v10_14)
|
||||||
|
],
|
||||||
|
products: [
|
||||||
|
.executable(name: "dewPoint-controller", targets: ["dewPoint-controller"]),
|
||||||
|
.library(name: "Bootstrap", targets: ["Bootstrap"]),
|
||||||
|
.library(name: "DewPointEnvironment", targets: ["DewPointEnvironment"]),
|
||||||
|
.library(name: "EnvVars", targets: ["EnvVars"]),
|
||||||
|
.library(name: "Models", targets: ["Models"]),
|
||||||
|
.library(name: "RelayClient", targets: ["RelayClient"]),
|
||||||
|
.library(name: "TemperatureSensorClient", targets: ["TemperatureSensorClient"]),
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
.package(url: "https://github.com/adam-fowler/mqtt-nio.git", from: "2.0.0"),
|
||||||
|
.package(url: "https://github.com/apple/swift-nio", from: "2.0.0"),
|
||||||
|
.package(url: "https://github.com/swift-psychrometrics/swift-psychrometrics", from: "0.1.0")
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.executableTarget(
|
||||||
|
name: "dewPoint-controller",
|
||||||
|
dependencies: [
|
||||||
|
"Bootstrap",
|
||||||
|
.product(name: "MQTTNIO", package: "mqtt-nio"),
|
||||||
|
.product(name: "NIO", package: "swift-nio")
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "dewPoint-controllerTests",
|
||||||
|
dependencies: ["dewPoint-controller"]
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "Bootstrap",
|
||||||
|
dependencies: [
|
||||||
|
"DewPointEnvironment",
|
||||||
|
"EnvVars",
|
||||||
|
"RelayClient",
|
||||||
|
"TemperatureSensorClient",
|
||||||
|
.product(name: "MQTTNIO", package: "mqtt-nio"),
|
||||||
|
.product(name: "NIO", package: "swift-nio")
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "DewPointEnvironment",
|
||||||
|
dependencies: [
|
||||||
|
"EnvVars",
|
||||||
|
"RelayClient",
|
||||||
|
"TemperatureSensorClient",
|
||||||
|
.product(name: "MQTTNIO", package: "mqtt-nio"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "EnvVars",
|
||||||
|
dependencies: []
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "Models",
|
||||||
|
dependencies: []
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "RelayClient",
|
||||||
|
dependencies: [
|
||||||
|
"Models",
|
||||||
|
.product(name: "NIO", package: "swift-nio"),
|
||||||
|
.product(name: "MQTTNIO", package: "mqtt-nio")
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "TemperatureSensorClient",
|
||||||
|
dependencies: [
|
||||||
|
"Models",
|
||||||
|
.product(name: "NIO", package: "swift-nio"),
|
||||||
|
.product(name: "MQTTNIO", package: "mqtt-nio"),
|
||||||
|
.product(name: "CoreUnitTypes", package: "swift-psychrometrics")
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# dewPoint-controller
|
||||||
|
|
||||||
|
A description of this package.
|
||||||
87
Sources/Bootstrap/Bootstrap.swift
Normal file
87
Sources/Bootstrap/Bootstrap.swift
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import DewPointEnvironment
|
||||||
|
import EnvVars
|
||||||
|
import Logging
|
||||||
|
import Foundation
|
||||||
|
import MQTTNIO
|
||||||
|
import NIO
|
||||||
|
import RelayClient
|
||||||
|
import TemperatureSensorClient
|
||||||
|
|
||||||
|
public func bootstrap(
|
||||||
|
eventLoopGroup: EventLoopGroup,
|
||||||
|
logger: Logger? = nil
|
||||||
|
) -> EventLoopFuture<DewPointEnvironment> {
|
||||||
|
logger?.debug("Bootstrapping Dew Point Controller...")
|
||||||
|
return loadEnvVars(eventLoopGroup: eventLoopGroup, logger: logger)
|
||||||
|
.map { (envVars) -> DewPointEnvironment in
|
||||||
|
let mqttClient = MQTTClient(envVars: envVars, eventLoopGroup: eventLoopGroup, logger: logger)
|
||||||
|
return DewPointEnvironment.init(
|
||||||
|
mqttClient: mqttClient,
|
||||||
|
envVars: envVars,
|
||||||
|
relayClient: .live(client: mqttClient),
|
||||||
|
temperatureSensorClient: .live(client: mqttClient)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.flatMap { environment in
|
||||||
|
environment.mqttClient.logger.debug("Connecting to MQTT broker...")
|
||||||
|
return environment.mqttClient.connect()
|
||||||
|
.map { _ in environment }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadEnvVars(eventLoopGroup: EventLoopGroup, logger: Logger?) -> EventLoopFuture<EnvVars> {
|
||||||
|
|
||||||
|
logger?.debug("Loading env vars...")
|
||||||
|
|
||||||
|
let envFilePath = URL(fileURLWithPath: #file)
|
||||||
|
.deletingLastPathComponent()
|
||||||
|
.deletingLastPathComponent()
|
||||||
|
.deletingLastPathComponent()
|
||||||
|
.appendingPathComponent(".dewPoint-env")
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
|
||||||
|
let defaultEnvVars = EnvVars()
|
||||||
|
|
||||||
|
let defaultEnvDict = (try? encoder.encode(defaultEnvVars))
|
||||||
|
.flatMap { try? decoder.decode([String: String].self, from: $0) }
|
||||||
|
?? [:]
|
||||||
|
|
||||||
|
// Read from file `.dewPoint-env` file if it exists.
|
||||||
|
let localEnvVarsDict = (try? Data(contentsOf: envFilePath))
|
||||||
|
.flatMap { try? decoder.decode([String: String].self, from: $0) }
|
||||||
|
?? [:]
|
||||||
|
|
||||||
|
// Merge with variables in the shell environment.
|
||||||
|
let envVarsDict = defaultEnvDict
|
||||||
|
.merging(localEnvVarsDict, uniquingKeysWith: { $1 })
|
||||||
|
.merging(ProcessInfo.processInfo.environment, uniquingKeysWith: { $1 })
|
||||||
|
|
||||||
|
// Produces the final env vars from the merged items or uses defaults if something
|
||||||
|
// went wrong.
|
||||||
|
let envVars = (try? JSONSerialization.data(withJSONObject: envVarsDict))
|
||||||
|
.flatMap { try? decoder.decode(EnvVars.self, from: $0) }
|
||||||
|
?? defaultEnvVars
|
||||||
|
|
||||||
|
logger?.debug("Done loading env vars...")
|
||||||
|
return eventLoopGroup.next().makeSucceededFuture(envVars)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MQTTClient {
|
||||||
|
|
||||||
|
convenience init(envVars: EnvVars, eventLoopGroup: EventLoopGroup, logger: Logger?) {
|
||||||
|
self.init(
|
||||||
|
host: envVars.host,
|
||||||
|
port: envVars.port != nil ? Int(envVars.port!) : nil,
|
||||||
|
identifier: envVars.identifier,
|
||||||
|
eventLoopGroupProvider: .shared(eventLoopGroup),
|
||||||
|
logger: logger,
|
||||||
|
configuration: .init(
|
||||||
|
version: .v5_0,
|
||||||
|
userName: envVars.userName,
|
||||||
|
password: envVars.password
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Sources/DewPointEnvironment/Environment.swift
Normal file
24
Sources/DewPointEnvironment/Environment.swift
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import EnvVars
|
||||||
|
import MQTTNIO
|
||||||
|
import RelayClient
|
||||||
|
import TemperatureSensorClient
|
||||||
|
|
||||||
|
public struct DewPointEnvironment {
|
||||||
|
|
||||||
|
public var mqttClient: MQTTClient
|
||||||
|
public var envVars: EnvVars
|
||||||
|
public var relayClient: RelayClient
|
||||||
|
public var temperatureSensorClient: TemperatureSensorClient
|
||||||
|
|
||||||
|
public init(
|
||||||
|
mqttClient: MQTTClient,
|
||||||
|
envVars: EnvVars,
|
||||||
|
relayClient: RelayClient,
|
||||||
|
temperatureSensorClient: TemperatureSensorClient
|
||||||
|
) {
|
||||||
|
self.mqttClient = mqttClient
|
||||||
|
self.envVars = envVars
|
||||||
|
self.relayClient = relayClient
|
||||||
|
self.temperatureSensorClient = temperatureSensorClient
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Sources/EnvVars/EnvVars.swift
Normal file
43
Sources/EnvVars/EnvVars.swift
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct EnvVars: Codable, Equatable {
|
||||||
|
|
||||||
|
public var appEnv: AppEnv
|
||||||
|
public var host: String
|
||||||
|
public var port: String?
|
||||||
|
public var identifier: String
|
||||||
|
public var userName: String?
|
||||||
|
public var password: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
appEnv: AppEnv = .development,
|
||||||
|
host: String = "127.0.0.1",
|
||||||
|
port: String? = "1883",
|
||||||
|
identifier: String = "dewPoint-controller",
|
||||||
|
userName: String? = "mqtt_user",
|
||||||
|
password: String? = "secret!"
|
||||||
|
){
|
||||||
|
self.appEnv = appEnv
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.identifier = identifier
|
||||||
|
self.userName = userName
|
||||||
|
self.password = password
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case appEnv = "APP_ENV"
|
||||||
|
case host = "MQTT_HOST"
|
||||||
|
case port = "MQTT_PORT"
|
||||||
|
case identifier = "MQTT_IDENTIFIER"
|
||||||
|
case userName = "MQTT_USERNAME"
|
||||||
|
case password = "MQTT_PASSWORD"
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AppEnv: String, Codable {
|
||||||
|
case development
|
||||||
|
case production
|
||||||
|
case staging
|
||||||
|
case testing
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Sources/Models/HumiditySensor.swift
Normal file
8
Sources/Models/HumiditySensor.swift
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
public struct HumiditySensor: Equatable {
|
||||||
|
public var topic: String
|
||||||
|
|
||||||
|
public init(topic: String) {
|
||||||
|
self.topic = topic
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Sources/Models/Relay.swift
Normal file
8
Sources/Models/Relay.swift
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
public struct Relay {
|
||||||
|
public var topic: String
|
||||||
|
|
||||||
|
public init(topic: String) {
|
||||||
|
self.topic = topic
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Sources/Models/TemperatureSensor.swift
Normal file
8
Sources/Models/TemperatureSensor.swift
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
public struct TemperatureSensor: Equatable {
|
||||||
|
public var topic: String
|
||||||
|
|
||||||
|
public init(topic: String) {
|
||||||
|
self.topic = topic
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Sources/RelayClient/Interface.swift
Normal file
20
Sources/RelayClient/Interface.swift
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import Foundation
|
||||||
|
import Models
|
||||||
|
import NIO
|
||||||
|
|
||||||
|
public struct RelayClient {
|
||||||
|
public var toggle: (Relay) -> EventLoopFuture<Void>
|
||||||
|
public var turnOn: (Relay) -> EventLoopFuture<Void>
|
||||||
|
public var turnOff: (Relay) -> EventLoopFuture<Void>
|
||||||
|
|
||||||
|
public init(
|
||||||
|
toggle: @escaping (Relay) -> EventLoopFuture<Void>,
|
||||||
|
turnOn: @escaping (Relay) -> EventLoopFuture<Void>,
|
||||||
|
turnOff: @escaping (Relay) -> EventLoopFuture<Void>
|
||||||
|
) {
|
||||||
|
self.toggle = toggle
|
||||||
|
self.turnOn = turnOn
|
||||||
|
self.turnOff = turnOff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
37
Sources/RelayClient/Live.swift
Normal file
37
Sources/RelayClient/Live.swift
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import Models
|
||||||
|
import MQTTNIO
|
||||||
|
import NIO
|
||||||
|
|
||||||
|
extension RelayClient {
|
||||||
|
|
||||||
|
public static func live(client: MQTTClient) -> RelayClient {
|
||||||
|
.init(
|
||||||
|
toggle: { relay in
|
||||||
|
client.publish(relay: relay, state: .toggle)
|
||||||
|
},
|
||||||
|
turnOn: { relay in
|
||||||
|
client.publish(relay: relay, state: .on)
|
||||||
|
},
|
||||||
|
turnOff: { relay in
|
||||||
|
client.publish(relay: relay, state: .off)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Relay {
|
||||||
|
enum State: String {
|
||||||
|
case toggle, on, off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MQTTClient {
|
||||||
|
|
||||||
|
func publish(relay: Relay, state: Relay.State, qos: MQTTQoS = .atLeastOnce) -> EventLoopFuture<Void> {
|
||||||
|
publish(
|
||||||
|
to: relay.topic,
|
||||||
|
payload: ByteBufferAllocator().buffer(string: state.rawValue),
|
||||||
|
qos: qos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Sources/TemperatureSensorClient/Interface.swift
Normal file
14
Sources/TemperatureSensorClient/Interface.swift
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import Foundation
|
||||||
|
import CoreUnitTypes
|
||||||
|
import Models
|
||||||
|
import NIO
|
||||||
|
|
||||||
|
public struct TemperatureSensorClient {
|
||||||
|
public var state: (TemperatureSensor, PsychrometricEnvironment.Units?) -> EventLoopFuture<Temperature>
|
||||||
|
|
||||||
|
public init(
|
||||||
|
state: @escaping (TemperatureSensor, PsychrometricEnvironment.Units?) -> EventLoopFuture<Temperature>
|
||||||
|
) {
|
||||||
|
self.state = state
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Sources/TemperatureSensorClient/Live.swift
Normal file
51
Sources/TemperatureSensorClient/Live.swift
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import CoreUnitTypes
|
||||||
|
import Foundation
|
||||||
|
import MQTTNIO
|
||||||
|
|
||||||
|
extension TemperatureSensorClient {
|
||||||
|
|
||||||
|
public static func live(client: MQTTClient) -> TemperatureSensorClient {
|
||||||
|
.init(
|
||||||
|
state: { sensor, units in
|
||||||
|
client.logger.debug("Adding listener for temperature sensor...")
|
||||||
|
let subscription = MQTTSubscribeInfoV5.init(topicFilter: sensor.topic, qos: .atLeastOnce)
|
||||||
|
return client.v5.subscribe(to: [subscription])
|
||||||
|
.flatMap { _ in
|
||||||
|
let promise = client.eventLoopGroup.next().makePromise(of: Temperature.self)
|
||||||
|
client.addPublishListener(named: "temperature-sensor", { result in
|
||||||
|
switch result.temperature() {
|
||||||
|
case let .success(celsius):
|
||||||
|
let userUnits = units ?? PsychrometricEnvironment.shared.units
|
||||||
|
let temperatureUnits = Temperature.Units.defaultFor(units: userUnits)
|
||||||
|
promise.succeed(.init(celsius[temperatureUnits], units: temperatureUnits))
|
||||||
|
case let .failure(error):
|
||||||
|
promise.fail(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return promise.futureResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TemperatureError: Error {
|
||||||
|
case invalidTemperature
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
extension Result where Success == MQTTPublishInfo, Failure == Error {
|
||||||
|
|
||||||
|
fileprivate func temperature() -> Result<Temperature, Error> {
|
||||||
|
flatMap { info in
|
||||||
|
var buffer = info.payload
|
||||||
|
guard let string = buffer.readString(length: buffer.readableBytes),
|
||||||
|
let temperatureValue = Double(string)
|
||||||
|
else {
|
||||||
|
return .failure(TemperatureError.invalidTemperature)
|
||||||
|
}
|
||||||
|
return .success(.celsius(temperatureValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Sources/dewPoint-controller/main.swift
Normal file
34
Sources/dewPoint-controller/main.swift
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import Bootstrap
|
||||||
|
import Logging
|
||||||
|
import Models
|
||||||
|
import MQTTNIO
|
||||||
|
import NIO
|
||||||
|
import RelayClient
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
var logger = Logger(label: "dewPoint-logger")
|
||||||
|
logger.logLevel = .debug
|
||||||
|
logger.debug("Swift Dew Point Controller!")
|
||||||
|
|
||||||
|
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||||
|
let environment = try bootstrap(eventLoopGroup: eventLoopGroup, logger: logger).wait()
|
||||||
|
let relayClient = environment.relayClient
|
||||||
|
let relay = Relay(topic: "frankensystem/relays/switch/relay_1/command")
|
||||||
|
let tempSensor = TemperatureSensor(topic: "frankensystem/relays/sensor/temperature_-_1/state")
|
||||||
|
|
||||||
|
defer {
|
||||||
|
logger.debug("Disconnecting")
|
||||||
|
_ = try? environment.mqttClient.disconnect().wait()
|
||||||
|
try? environment.mqttClient.syncShutdownGracefully()
|
||||||
|
}
|
||||||
|
|
||||||
|
while true {
|
||||||
|
logger.debug("Toggling relay.")
|
||||||
|
_ = try relayClient.toggle(relay).wait()
|
||||||
|
|
||||||
|
logger.debug("Reading temperature sensor.")
|
||||||
|
let temp = try environment.temperatureSensorClient.state(tempSensor, .imperial).wait()
|
||||||
|
logger.debug("Temperature: \(temp)")
|
||||||
|
|
||||||
|
Thread.sleep(forTimeInterval: 5)
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import XCTest
|
||||||
|
import class Foundation.Bundle
|
||||||
|
|
||||||
|
final class dewPoint_controllerTests: XCTestCase {
|
||||||
|
func testExample() throws {
|
||||||
|
// This is an example of a functional test case.
|
||||||
|
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||||
|
// results.
|
||||||
|
|
||||||
|
// Some of the APIs that we use below are available in macOS 10.13 and above.
|
||||||
|
guard #available(macOS 10.13, *) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mac Catalyst won't have `Process`, but it is supported for executables.
|
||||||
|
#if !targetEnvironment(macCatalyst)
|
||||||
|
|
||||||
|
let fooBinary = productsDirectory.appendingPathComponent("dewPoint-controller")
|
||||||
|
|
||||||
|
let process = Process()
|
||||||
|
process.executableURL = fooBinary
|
||||||
|
|
||||||
|
let pipe = Pipe()
|
||||||
|
process.standardOutput = pipe
|
||||||
|
|
||||||
|
try process.run()
|
||||||
|
process.waitUntilExit()
|
||||||
|
|
||||||
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||||
|
let output = String(data: data, encoding: .utf8)
|
||||||
|
|
||||||
|
XCTAssertEqual(output, "Hello, world!\n")
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns path to the built products directory.
|
||||||
|
var productsDirectory: URL {
|
||||||
|
#if os(macOS)
|
||||||
|
for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
|
||||||
|
return bundle.bundleURL.deletingLastPathComponent()
|
||||||
|
}
|
||||||
|
fatalError("couldn't find the products directory")
|
||||||
|
#else
|
||||||
|
return Bundle.main.bundleURL
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user