Working on tests.

This commit is contained in:
2021-10-22 08:01:18 -04:00
parent 6ec6ce34d9
commit f45f667af6
11 changed files with 208 additions and 35 deletions

View File

@@ -120,7 +120,7 @@ extension EventLoopFuture where Value == (EnvVars, Topics) {
map { envVars, topics in
let nioClient = MQTTClient(envVars: envVars, eventLoopGroup: eventLoopGroup, logger: logger)
return DewPointEnvironment.init(
mqttClient: .live(client: nioClient),
mqttClient: .live(client: nioClient, topics: topics),
envVars: envVars,
nioClient: nioClient,
topics: topics
@@ -148,7 +148,7 @@ extension EventLoopFuture where Value == DewPointEnvironment {
}
}
extension MQTTClient {
extension MQTTNIO.MQTTClient {
fileprivate convenience init(envVars: EnvVars, eventLoopGroup: EventLoopGroup, logger: Logger?) {
self.init(

View File

@@ -9,15 +9,18 @@ import Psychrometrics
///
/// This is an abstraction around the ``MQTTNIO.MQTTClient``.
public struct MQTTClient {
/// Retrieve the humidity from the MQTT Broker.
public var fetchHumidity: (Sensor<RelativeHumidity>) -> EventLoopFuture<RelativeHumidity>
/// Retrieve a set point from the MQTT Broker.
public var fetchSetPoint: (KeyPath<Topics.SetPoints, String>) -> EventLoopFuture<Double>
/// Retrieve the temperature from the MQTT Broker.
public var fetchTemperature: (Sensor<Temperature>, PsychrometricEnvironment.Units?) -> EventLoopFuture<Temperature>
/// Publish a change of state message for a relay.
public var setRelay: (Relay, Relay.State) -> EventLoopFuture<Void>
public var setRelay: (KeyPath<Topics.Commands.Relays, String>, Relay.State) -> EventLoopFuture<Void>
/// Disconnect and close the connection to the MQTT Broker.
public var shutdown: () -> EventLoopFuture<Void>
@@ -27,12 +30,14 @@ public struct MQTTClient {
public init(
fetchHumidity: @escaping (Sensor<RelativeHumidity>) -> EventLoopFuture<RelativeHumidity>,
fetchSetPoint: @escaping (KeyPath<Topics.SetPoints, String>) -> EventLoopFuture<Double>,
fetchTemperature: @escaping (Sensor<Temperature>, PsychrometricEnvironment.Units?) -> EventLoopFuture<Temperature>,
setRelay: @escaping (Relay, Relay.State) -> EventLoopFuture<Void>,
setRelay: @escaping (KeyPath<Topics.Commands.Relays, String>, Relay.State) -> EventLoopFuture<Void>,
shutdown: @escaping () -> EventLoopFuture<Void>,
publishDewPoint: @escaping (DewPoint, String) -> EventLoopFuture<Void>
) {
self.fetchHumidity = fetchHumidity
self.fetchSetPoint = fetchSetPoint
self.fetchTemperature = fetchTemperature
self.setRelay = setRelay
self.shutdown = shutdown
@@ -60,7 +65,7 @@ public struct MQTTClient {
/// - Parameters:
/// - relay: The relay to send the message to.
/// - state: The state to change the relay to.
public func `set`(relay: Relay, to state: Relay.State) -> EventLoopFuture<Void> {
public func `set`(relay: KeyPath<Topics.Commands.Relays, String>, to state: Relay.State) -> EventLoopFuture<Void> {
setRelay(relay, state)
}

View File

@@ -43,8 +43,55 @@ enum MQTTError: Error {
case relay(reason: String, error: Error?)
}
protocol FetchableTopic {
associatedtype Value: BufferInitalizable
var topic: String { get }
}
extension Double: BufferInitalizable {
init?(buffer: inout ByteBuffer) {
guard let string = buffer.readString(length: buffer.readableBytes) else {
return nil
}
self.init(string)
}
}
//extension SetPoint: FetchableTopic {
// typealias Value = Double
//}
extension Sensor: FetchableTopic where Reading: BufferInitalizable {
typealias Value = Reading
}
extension MQTTNIO.MQTTClient {
func mqttSubscription(topic: String, qos: MQTTQoS = .atLeastOnce, retainAsPublished: Bool = true, retainHandling: MQTTSubscribeInfoV5.RetainHandling = .sendAlways) -> MQTTSubscribeInfoV5 {
.init(topicFilter: topic, qos: qos, retainAsPublished: retainAsPublished, retainHandling: retainHandling)
}
func fetch<Value>(
_ subscription: MQTTSubscribeInfoV5
) -> EventLoopFuture<Value> where Value: BufferInitalizable {
logger.debug("Fetching data for: \(subscription.topicFilter)")
return v5.subscribe(to: [subscription])
.flatMap { _ in
let promise = self.eventLoopGroup.next().makePromise(of: Value.self)
self.addPublishListener(named: subscription.topicFilter + "-listener") { result in
result.mapBuffer(to: Value.self)
.unwrap(or: MQTTError.sensor(reason: "Invalid sensor reading", error: nil))
.fullfill(promise: promise)
self.logger.debug("Done fetching data for: \(subscription.topicFilter)")
}
return promise.futureResult
}
}
/// Fetch a sensor state and convert it appropriately, when the sensor type is ``BufferInitializable``.
///
/// - Parameters:
@@ -52,32 +99,17 @@ extension MQTTNIO.MQTTClient {
func fetch<S>(
sensor: Sensor<S>
) -> EventLoopFuture<S> where S: BufferInitalizable {
logger.debug("Fetching data for sensor: \(sensor.topic)")
let subscription = MQTTSubscribeInfoV5.init(
topicFilter: sensor.topic,
qos: .atLeastOnce,
retainAsPublished: false,
retainHandling: .sendAlways
)
return v5.subscribe(to: [subscription])
.flatMap { _ in
let promise = self.eventLoopGroup.next().makePromise(of: S.self)
self.addPublishListener(named: sensor.topic) { result in
result.mapBuffer(to: S.self)
.unwrap(or: MQTTError.sensor(reason: "Invalid sensor reading", error: nil))
.fullfill(promise: promise)
self.logger.debug("Done fetching data for sensor: \(sensor.topic)")
}
return promise.futureResult
}
return fetch(mqttSubscription(topic: sensor.topic))
}
func `set`(relay: Relay, to state: Relay.State, qos: MQTTQoS = .atLeastOnce) -> EventLoopFuture<Void> {
func fetch(setPoint: KeyPath<Topics.SetPoints, String>, setPoints: Topics.SetPoints) -> EventLoopFuture<Double> {
// logger.debug("Fetching data for set point: \(setPoint.topic)")
return fetch(mqttSubscription(topic: setPoints[keyPath: setPoint]))
}
func `set`(relay relayTopic: String, to state: Relay.State, qos: MQTTQoS = .atLeastOnce) -> EventLoopFuture<Void> {
publish(
to: relay.topic,
to: relayTopic,
payload: ByteBufferAllocator().buffer(string: state.rawValue),
qos: qos
)

View File

@@ -1,5 +1,5 @@
import Foundation
import Client
@_exported import Client
import CoreUnitTypes
import Models
import MQTTNIO
@@ -11,20 +11,24 @@ extension Client.MQTTClient {
///
/// - Parameters:
/// - client: The ``MQTTNIO.MQTTClient`` used to send and recieve messages from the MQTT Broker.
public static func live(client: MQTTNIO.MQTTClient) -> Self {
public static func live(client: MQTTNIO.MQTTClient, topics: Topics) -> Self {
.init(
fetchHumidity: { sensor in
client.fetch(sensor: sensor)
.debug(logger: client.logger)
},
fetchSetPoint: { setPointKeyPath in
client.fetch(client.mqttSubscription(topic: topics.setPoints[keyPath: setPointKeyPath]))
.debug(logger: client.logger)
},
fetchTemperature: { sensor, units in
client.fetch(sensor: sensor)
.debug(logger: client.logger)
.convertIfNeeded(to: units)
.debug(logger: client.logger)
},
setRelay: { relay, state in
client.set(relay: relay, to: state)
setRelay: { relayKeyPath, state in
client.set(relay: topics.commands.relays[keyPath: relayKeyPath], to: state)
},
shutdown: {
client.disconnect()

View File

@@ -5,7 +5,7 @@ public struct Topics: Codable, Equatable {
/// The command topics the application can publish to.
public var commands: Commands
/// The sensor topics the application can read sensor values from.
/// The sensor topics the application can read from / write to.
public var sensors: Sensors
/// The set point topics the application can read set point values from.

View File

@@ -34,7 +34,11 @@ defer {
while true {
// let temp = try environment.mqttClient.fetchTemperature(tempSensor, .imperial).wait()
// logger.debug("Temp: \(temp.rawValue)")
//
// logger.debug("Fetching set-point...")
// let sp = try environment.mqttClient.fetchSetPoint(\.dehumidify.highDewPoint).wait()
// logger.debug("Set point: \(sp)")
//
logger.debug("Fetching dew point...")
let dp = try environment.mqttClient.currentDewPoint(