Minimal working version publishing dew-points and enthalpies.

This commit is contained in:
2021-11-05 16:59:25 -04:00
parent 31eaa6ade1
commit 2c255b6510
7 changed files with 128 additions and 46 deletions

View File

@@ -4,6 +4,8 @@ bootstrap-env:
bootstrap-topics:
@cp Bootstrap/topics-example .topics
bootstrap: bootstrap-env bootstrap-topics
build:
@swift build

View File

@@ -26,6 +26,8 @@ let package = Package(
name: "dewPoint-controller",
dependencies: [
"Bootstrap",
"ClientLive",
"TopicsLive",
.product(name: "MQTTNIO", package: "mqtt-nio"),
.product(name: "NIO", package: "swift-nio")
]
@@ -87,5 +89,11 @@ let package = Package(
"ClientLive"
]
),
.target(
name: "TopicsLive",
dependencies: [
"Models"
]
),
]
)

View File

@@ -10,32 +10,34 @@ protocol BufferInitalizable {
init?(buffer: inout ByteBuffer)
}
extension Temperature: BufferInitalizable {
extension Double: BufferInitalizable {
/// Attempt to create / parse a double from a byte buffer.
init?(buffer: inout ByteBuffer) {
guard let string = buffer.readString(length: buffer.readableBytes, encoding: .utf8),
let value = Double(string)
else {
return nil
}
guard let string = buffer.readString(length: buffer.readableBytes, encoding: .utf8)
else { return nil }
self.init(string)
}
}
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)
}
}
extension RelativeHumidity: BufferInitalizable {
/// Attempt to create / parse a relative humidity from a byte buffer.
init?(buffer: inout ByteBuffer) {
guard let string = buffer.readString(length: buffer.readableBytes, encoding: .utf8),
let value = Double(string)
else {
return nil
}
guard let value = Double(buffer: &buffer) else { return nil }
self.init(value)
}
}
extension MQTTNIO.MQTTClient {
/// Logs a failure for a given topic and error.
func logFailure(topic: String, error: Error) {
logger.error("\(topic): \(error)")
}
@@ -214,10 +216,11 @@ extension MQTTNIO.MQTTClient {
logger.trace("No dew point for sensor.")
return eventLoopGroup.next().makeSucceededFuture((self, request, state, topics))
}
let roundedDewPoint = round(dewPoint.rawValue * 100) / 100
logger.debug("Publishing dew-point: \(dewPoint), to: \(topic)")
return publish(
to: topic,
payload: ByteBufferAllocator().buffer(string: "\(dewPoint.rawValue)"),
payload: ByteBufferAllocator().buffer(string: "\(roundedDewPoint)"),
qos: .atLeastOnce
)
.map { (self, request, state, topics) }
@@ -240,10 +243,11 @@ extension EventLoopFuture where Value == (MQTTNIO.MQTTClient, Client.SensorPubli
client.logger.trace("No enthalpy for sensor.")
return client.eventLoopGroup.next().makeSucceededFuture((request, state))
}
let roundedEnthalpy = round(enthalpy.rawValue * 100) / 100
client.logger.debug("Publishing enthalpy: \(enthalpy), to: \(topic)")
return client.publish(
to: topic,
payload: ByteBufferAllocator().buffer(string: "\(enthalpy.rawValue)"),
payload: ByteBufferAllocator().buffer(string: "\(roundedEnthalpy)"),
qos: .atLeastOnce
)
.map { (request, state) }

View File

@@ -70,14 +70,18 @@ extension State.Sensors {
public func dewPoint(units: PsychrometricEnvironment.Units? = nil) -> DewPoint? {
guard let temperature = temperature,
let humidity = humidity
let humidity = humidity,
!temperature.rawValue.isNaN,
!humidity.rawValue.isNaN
else { return nil }
return .init(dryBulb: temperature, humidity: humidity, units: units)
}
public func enthalpy(altitude: Length, units: PsychrometricEnvironment.Units? = nil) -> EnthalpyOf<MoistAir>? {
guard let temperature = temperature,
let humidity = humidity
let humidity = humidity,
!temperature.rawValue.isNaN,
!humidity.rawValue.isNaN
else { return nil }
return .init(dryBulb: temperature, humidity: humidity, altitude: altitude, units: units)
}

View 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"
)
}
}

View File

@@ -1,60 +1,73 @@
import Bootstrap
import ClientLive
import CoreUnitTypes
import Logging
import Models
import MQTTNIO
import NIO
import TopicsLive
import Foundation
var logger: Logger = {
var logger = Logger(label: "dewPoint-logger")
logger.logLevel = .info
logger.logLevel = .debug
return logger
}()
logger.info("Starting Swift Dew Point Controller!")
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.
if environment.envVars.appEnv == .production {
logger.logLevel = .info
}
//let relay = Relay(topic: environment.topics.commands.relays.dehumidification1)
//let tempSensor = Sensor<Temperature>(topic: environment.topics.sensors.returnAirSensor.temperature)
//let humiditySensor = Sensor<RelativeHumidity>(topic: environment.topics.sensors.returnAirSensor.humidity)
// Set up the client, topics and state.
environment.topics = .live
let state = State()
let client = Client.live(client: environment.mqttClient, state: state, topics: environment.topics)
defer {
logger.debug("Disconnecting")
// try? environment.mqttClient.shutdown().wait()
}
// Add topic listeners.
client.addListeners()
while true {
// let temp = try environment.mqttClient.fetchTemperature(tempSensor, .imperial).wait()
// logger.debug("Temp: \(temp.rawValue)")
if !environment.mqttClient.isActive() {
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...")
// 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...")
// logger.debug("Published dew point...")
Thread.sleep(forTimeInterval: 5)
}

View File

@@ -9,7 +9,6 @@ import NIO
import NIOConcurrencyHelpers
import XCTest
// Can't seem to get tests to work, although we get values when ran from command line.
final class ClientLiveTests: XCTestCase {
static let hostname = ProcessInfo.processInfo.environment["MOSQUITTO_SERVER"] ?? "localhost"
let topics = Topics()