Minimal working version publishing dew-points and enthalpies.
This commit is contained in:
2
Makefile
2
Makefile
@@ -5,6 +5,8 @@ bootstrap-env:
|
|||||||
bootstrap-topics:
|
bootstrap-topics:
|
||||||
@cp Bootstrap/topics-example .topics
|
@cp Bootstrap/topics-example .topics
|
||||||
|
|
||||||
|
bootstrap: bootstrap-env bootstrap-topics
|
||||||
|
|
||||||
build:
|
build:
|
||||||
@swift build
|
@swift build
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ let package = Package(
|
|||||||
name: "dewPoint-controller",
|
name: "dewPoint-controller",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"Bootstrap",
|
"Bootstrap",
|
||||||
|
"ClientLive",
|
||||||
|
"TopicsLive",
|
||||||
.product(name: "MQTTNIO", package: "mqtt-nio"),
|
.product(name: "MQTTNIO", package: "mqtt-nio"),
|
||||||
.product(name: "NIO", package: "swift-nio")
|
.product(name: "NIO", package: "swift-nio")
|
||||||
]
|
]
|
||||||
@@ -87,5 +89,11 @@ let package = Package(
|
|||||||
"ClientLive"
|
"ClientLive"
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "TopicsLive",
|
||||||
|
dependencies: [
|
||||||
|
"Models"
|
||||||
|
]
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,32 +10,34 @@ protocol BufferInitalizable {
|
|||||||
init?(buffer: inout ByteBuffer)
|
init?(buffer: inout ByteBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Temperature: BufferInitalizable {
|
extension Double: BufferInitalizable {
|
||||||
|
|
||||||
|
/// Attempt to create / parse a double from a byte buffer.
|
||||||
init?(buffer: inout ByteBuffer) {
|
init?(buffer: inout ByteBuffer) {
|
||||||
guard let string = buffer.readString(length: buffer.readableBytes, encoding: .utf8),
|
guard let string = buffer.readString(length: buffer.readableBytes, encoding: .utf8)
|
||||||
let value = Double(string)
|
else { return nil }
|
||||||
else {
|
self.init(string)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Temperature: BufferInitalizable {
|
||||||
|
/// Attempt to create / parse a temperature from a byte buffer.
|
||||||
|
init?(buffer: inout ByteBuffer) {
|
||||||
|
guard let value = Double(buffer: &buffer) else { return nil }
|
||||||
self.init(value, units: .celsius)
|
self.init(value, units: .celsius)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RelativeHumidity: BufferInitalizable {
|
extension RelativeHumidity: BufferInitalizable {
|
||||||
|
/// Attempt to create / parse a relative humidity from a byte buffer.
|
||||||
init?(buffer: inout ByteBuffer) {
|
init?(buffer: inout ByteBuffer) {
|
||||||
guard let string = buffer.readString(length: buffer.readableBytes, encoding: .utf8),
|
guard let value = Double(buffer: &buffer) else { return nil }
|
||||||
let value = Double(string)
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
self.init(value)
|
self.init(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MQTTNIO.MQTTClient {
|
extension MQTTNIO.MQTTClient {
|
||||||
|
/// Logs a failure for a given topic and error.
|
||||||
func logFailure(topic: String, error: Error) {
|
func logFailure(topic: String, error: Error) {
|
||||||
logger.error("\(topic): \(error)")
|
logger.error("\(topic): \(error)")
|
||||||
}
|
}
|
||||||
@@ -214,10 +216,11 @@ extension MQTTNIO.MQTTClient {
|
|||||||
logger.trace("No dew point for sensor.")
|
logger.trace("No dew point for sensor.")
|
||||||
return eventLoopGroup.next().makeSucceededFuture((self, request, state, topics))
|
return eventLoopGroup.next().makeSucceededFuture((self, request, state, topics))
|
||||||
}
|
}
|
||||||
|
let roundedDewPoint = round(dewPoint.rawValue * 100) / 100
|
||||||
logger.debug("Publishing dew-point: \(dewPoint), to: \(topic)")
|
logger.debug("Publishing dew-point: \(dewPoint), to: \(topic)")
|
||||||
return publish(
|
return publish(
|
||||||
to: topic,
|
to: topic,
|
||||||
payload: ByteBufferAllocator().buffer(string: "\(dewPoint.rawValue)"),
|
payload: ByteBufferAllocator().buffer(string: "\(roundedDewPoint)"),
|
||||||
qos: .atLeastOnce
|
qos: .atLeastOnce
|
||||||
)
|
)
|
||||||
.map { (self, request, state, topics) }
|
.map { (self, request, state, topics) }
|
||||||
@@ -240,10 +243,11 @@ extension EventLoopFuture where Value == (MQTTNIO.MQTTClient, Client.SensorPubli
|
|||||||
client.logger.trace("No enthalpy for sensor.")
|
client.logger.trace("No enthalpy for sensor.")
|
||||||
return client.eventLoopGroup.next().makeSucceededFuture((request, state))
|
return client.eventLoopGroup.next().makeSucceededFuture((request, state))
|
||||||
}
|
}
|
||||||
|
let roundedEnthalpy = round(enthalpy.rawValue * 100) / 100
|
||||||
client.logger.debug("Publishing enthalpy: \(enthalpy), to: \(topic)")
|
client.logger.debug("Publishing enthalpy: \(enthalpy), to: \(topic)")
|
||||||
return client.publish(
|
return client.publish(
|
||||||
to: topic,
|
to: topic,
|
||||||
payload: ByteBufferAllocator().buffer(string: "\(enthalpy.rawValue)"),
|
payload: ByteBufferAllocator().buffer(string: "\(roundedEnthalpy)"),
|
||||||
qos: .atLeastOnce
|
qos: .atLeastOnce
|
||||||
)
|
)
|
||||||
.map { (request, state) }
|
.map { (request, state) }
|
||||||
|
|||||||
@@ -70,14 +70,18 @@ extension State.Sensors {
|
|||||||
|
|
||||||
public func dewPoint(units: PsychrometricEnvironment.Units? = nil) -> DewPoint? {
|
public func dewPoint(units: PsychrometricEnvironment.Units? = nil) -> DewPoint? {
|
||||||
guard let temperature = temperature,
|
guard let temperature = temperature,
|
||||||
let humidity = humidity
|
let humidity = humidity,
|
||||||
|
!temperature.rawValue.isNaN,
|
||||||
|
!humidity.rawValue.isNaN
|
||||||
else { return nil }
|
else { return nil }
|
||||||
return .init(dryBulb: temperature, humidity: humidity, units: units)
|
return .init(dryBulb: temperature, humidity: humidity, units: units)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func enthalpy(altitude: Length, units: PsychrometricEnvironment.Units? = nil) -> EnthalpyOf<MoistAir>? {
|
public func enthalpy(altitude: Length, units: PsychrometricEnvironment.Units? = nil) -> EnthalpyOf<MoistAir>? {
|
||||||
guard let temperature = temperature,
|
guard let temperature = temperature,
|
||||||
let humidity = humidity
|
let humidity = humidity,
|
||||||
|
!temperature.rawValue.isNaN,
|
||||||
|
!humidity.rawValue.isNaN
|
||||||
else { return nil }
|
else { return nil }
|
||||||
return .init(dryBulb: temperature, humidity: humidity, altitude: altitude, units: units)
|
return .init(dryBulb: temperature, humidity: humidity, altitude: altitude, units: units)
|
||||||
}
|
}
|
||||||
|
|||||||
52
Sources/TopicsLive/Live.swift
Normal file
52
Sources/TopicsLive/Live.swift
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import Models
|
||||||
|
|
||||||
|
// TODO: Fix other live topics
|
||||||
|
extension Topics {
|
||||||
|
|
||||||
|
public static let live = Self.init(
|
||||||
|
commands: .init(),
|
||||||
|
sensors: .init(
|
||||||
|
mixedAirSensor: .live(location: .mixedAir),
|
||||||
|
postCoilSensor: .live(location: .postCoil),
|
||||||
|
returnAirSensor: .live(location: .return),
|
||||||
|
supplyAirSensor: .live(location: .supply)),
|
||||||
|
setPoints: .init(),
|
||||||
|
states: .init()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Topics.Sensors {
|
||||||
|
fileprivate enum Location: CustomStringConvertible {
|
||||||
|
case mixedAir
|
||||||
|
case postCoil
|
||||||
|
case `return`
|
||||||
|
case supply
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .mixedAir:
|
||||||
|
return "mixed_air"
|
||||||
|
case .postCoil:
|
||||||
|
return "post_coil"
|
||||||
|
case .return:
|
||||||
|
return "return"
|
||||||
|
case .supply:
|
||||||
|
return "supply"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Topics.Sensors.TemperatureAndHumiditySensor {
|
||||||
|
fileprivate static func live(
|
||||||
|
prefix: String = "frankensystem",
|
||||||
|
location: Topics.Sensors.Location
|
||||||
|
) -> Self {
|
||||||
|
.init(
|
||||||
|
temperature: "\(prefix)/sensor/\(location.description)_temperature/state",
|
||||||
|
humidity: "\(prefix)/sensor/\(location.description)_humidity/state",
|
||||||
|
dewPoint: "\(prefix)/sensor/\(location.description)_dew_point/state",
|
||||||
|
enthalpy: "\(prefix)/sensor/\(location.description)_enthalpy/state"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,60 +1,73 @@
|
|||||||
import Bootstrap
|
import Bootstrap
|
||||||
|
import ClientLive
|
||||||
import CoreUnitTypes
|
import CoreUnitTypes
|
||||||
import Logging
|
import Logging
|
||||||
import Models
|
import Models
|
||||||
import MQTTNIO
|
import MQTTNIO
|
||||||
import NIO
|
import NIO
|
||||||
|
import TopicsLive
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
var logger: Logger = {
|
var logger: Logger = {
|
||||||
var logger = Logger(label: "dewPoint-logger")
|
var logger = Logger(label: "dewPoint-logger")
|
||||||
logger.logLevel = .info
|
logger.logLevel = .debug
|
||||||
return logger
|
return logger
|
||||||
}()
|
}()
|
||||||
|
|
||||||
logger.info("Starting Swift Dew Point Controller!")
|
logger.info("Starting Swift Dew Point Controller!")
|
||||||
|
|
||||||
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||||
let environment = try bootstrap(eventLoopGroup: eventLoopGroup, logger: logger).wait()
|
var environment = try bootstrap(eventLoopGroup: eventLoopGroup, logger: logger, autoConnect: false).wait()
|
||||||
|
|
||||||
// Set the log level to info only in production mode.
|
// Set the log level to info only in production mode.
|
||||||
if environment.envVars.appEnv == .production {
|
if environment.envVars.appEnv == .production {
|
||||||
logger.logLevel = .info
|
logger.logLevel = .info
|
||||||
}
|
}
|
||||||
|
|
||||||
//let relay = Relay(topic: environment.topics.commands.relays.dehumidification1)
|
// Set up the client, topics and state.
|
||||||
//let tempSensor = Sensor<Temperature>(topic: environment.topics.sensors.returnAirSensor.temperature)
|
environment.topics = .live
|
||||||
//let humiditySensor = Sensor<RelativeHumidity>(topic: environment.topics.sensors.returnAirSensor.humidity)
|
let state = State()
|
||||||
|
let client = Client.live(client: environment.mqttClient, state: state, topics: environment.topics)
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
logger.debug("Disconnecting")
|
logger.debug("Disconnecting")
|
||||||
// try? environment.mqttClient.shutdown().wait()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add topic listeners.
|
||||||
|
client.addListeners()
|
||||||
|
|
||||||
while true {
|
while true {
|
||||||
// let temp = try environment.mqttClient.fetchTemperature(tempSensor, .imperial).wait()
|
if !environment.mqttClient.isActive() {
|
||||||
// logger.debug("Temp: \(temp.rawValue)")
|
logger.trace("Connecting to MQTT broker...")
|
||||||
|
try client.connect().wait()
|
||||||
|
try client.subscribe().wait()
|
||||||
|
Thread.sleep(forTimeInterval: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if sensors need processed.
|
||||||
|
if state.sensors.needsProcessed {
|
||||||
|
logger.debug("Sensor state has changed...")
|
||||||
|
if state.sensors.mixedAirSensor.needsProcessed {
|
||||||
|
logger.trace("Publishing mixed air sensor.")
|
||||||
|
try client.publishSensor(.mixed(state.sensors.mixedAirSensor)).wait()
|
||||||
|
}
|
||||||
|
if state.sensors.postCoilSensor.needsProcessed {
|
||||||
|
logger.trace("Publishing post coil sensor.")
|
||||||
|
try client.publishSensor(.postCoil(state.sensors.postCoilSensor)).wait()
|
||||||
|
}
|
||||||
|
if state.sensors.returnAirSensor.needsProcessed {
|
||||||
|
logger.trace("Publishing return air sensor.")
|
||||||
|
try client.publishSensor(.return(state.sensors.returnAirSensor)).wait()
|
||||||
|
}
|
||||||
|
if state.sensors.supplyAirSensor.needsProcessed {
|
||||||
|
logger.trace("Publishing supply air sensor.")
|
||||||
|
try client.publishSensor(.supply(state.sensors.supplyAirSensor)).wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// logger.debug("Fetching dew point...")
|
||||||
//
|
//
|
||||||
// logger.debug("Fetching set-point...")
|
// logger.debug("Published dew 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(
|
|
||||||
// temperature: tempSensor,
|
|
||||||
// humidity: humiditySensor,
|
|
||||||
// units: .imperial
|
|
||||||
// ).wait()
|
|
||||||
|
|
||||||
// logger.info("Dew Point: \(dp.rawValue) \(dp.units.symbol)")
|
|
||||||
|
|
||||||
// try environment.mqttClient.publish(
|
|
||||||
// dewPoint: dp,
|
|
||||||
// to: environment.topics.sensors.returnAirSensor.dewPoint
|
|
||||||
// ).wait()
|
|
||||||
|
|
||||||
logger.debug("Published dew point...")
|
|
||||||
|
|
||||||
Thread.sleep(forTimeInterval: 5)
|
Thread.sleep(forTimeInterval: 5)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import NIO
|
|||||||
import NIOConcurrencyHelpers
|
import NIOConcurrencyHelpers
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
// Can't seem to get tests to work, although we get values when ran from command line.
|
|
||||||
final class ClientLiveTests: XCTestCase {
|
final class ClientLiveTests: XCTestCase {
|
||||||
static let hostname = ProcessInfo.processInfo.environment["MOSQUITTO_SERVER"] ?? "localhost"
|
static let hostname = ProcessInfo.processInfo.environment["MOSQUITTO_SERVER"] ?? "localhost"
|
||||||
let topics = Topics()
|
let topics = Topics()
|
||||||
|
|||||||
Reference in New Issue
Block a user