diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7cfbe01 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*.swift] +indent_style = space +indent_size = 2 +tab_width = 2 +trim_trailing_whitespace = true diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..ebad891 --- /dev/null +++ b/.swiftformat @@ -0,0 +1 @@ +--self init-only diff --git a/Sources/Client/Interface.swift b/Sources/Client/Interface.swift index def1cf3..8bdfa76 100755 --- a/Sources/Client/Interface.swift +++ b/Sources/Client/Interface.swift @@ -6,21 +6,21 @@ import NIO import Psychrometrics public struct Client { - + /// Add the publish listeners to the MQTT Broker, to be notified of published changes. public var addListeners: () -> Void - + /// Connect to the MQTT Broker. public var connect: () -> EventLoopFuture - + public var publishSensor: (SensorPublishRequest) -> EventLoopFuture - + /// Subscribe to appropriate topics / events. public var subscribe: () -> EventLoopFuture - + /// Disconnect and close the connection to the MQTT Broker. public var shutdown: () -> EventLoopFuture - + public init( addListeners: @escaping () -> Void, connect: @escaping () -> EventLoopFuture, @@ -34,9 +34,9 @@ public struct Client { self.shutdown = shutdown self.subscribe = subscribe } - + public enum SensorPublishRequest { - case mixed(State.Sensors.TemperatureHumiditySensor) + case mixed(State.Sensors.TemperatureHumiditySensor) case postCoil(State.Sensors.TemperatureHumiditySensor) case `return`(State.Sensors.TemperatureHumiditySensor) case supply(State.Sensors.TemperatureHumiditySensor) diff --git a/Sources/ClientLive/Live.swift b/Sources/ClientLive/Live.swift index 5e8b698..0a80053 100755 --- a/Sources/ClientLive/Live.swift +++ b/Sources/ClientLive/Live.swift @@ -61,7 +61,7 @@ public class AsyncClient { tlsConfiguration: nil, webSocketURLPath: nil ) - self.client = .init( + self.client = MQTTClient( host: envVars.host, identifier: envVars.identifier, eventLoopGroupProvider: .shared(Self.eventLoopGroup), diff --git a/Sources/Models/State.swift b/Sources/Models/State.swift index 06fe870..58fc585 100755 --- a/Sources/Models/State.swift +++ b/Sources/Models/State.swift @@ -3,7 +3,7 @@ import Psychrometrics // TODO: Make this a struct, then create a Store class that holds the state?? public final class State { - + public var altitude: Length public var sensors: Sensors public var units: PsychrometricEnvironment.Units { @@ -11,7 +11,7 @@ public final class State { PsychrometricEnvironment.shared.units = units } } - + public init( altitude: Length = .seaLevel, sensors: Sensors = .init(), @@ -21,16 +21,16 @@ public final class State { self.sensors = sensors self.units = units } - + public struct Sensors: Equatable { - - public var mixedAirSensor: TemperatureHumiditySensor + + public var mixedAirSensor: TemperatureHumiditySensor public var postCoilSensor: TemperatureHumiditySensor public var returnAirSensor: TemperatureHumiditySensor public var supplyAirSensor: TemperatureHumiditySensor - + public init( - mixedAirSensor: TemperatureHumiditySensor = .init(), + mixedAirSensor: TemperatureHumiditySensor = .init(), postCoilSensor: TemperatureHumiditySensor = .init(), returnAirSensor: TemperatureHumiditySensor = .init(), supplyAirSensor: TemperatureHumiditySensor = .init() @@ -40,7 +40,7 @@ public final class State { self.returnAirSensor = returnAirSensor self.supplyAirSensor = supplyAirSensor } - + public var needsProcessed: Bool { mixedAirSensor.needsProcessed || postCoilSensor.needsProcessed @@ -51,15 +51,15 @@ public final class State { } extension State.Sensors { - + public struct TemperatureHumiditySensor: Equatable { - + @TrackedChanges public var temperature: Temperature? - + @TrackedChanges public var humidity: RelativeHumidity? - + public var needsProcessed: Bool { get { $temperature.needsProcessed || $humidity.needsProcessed } set { @@ -67,7 +67,7 @@ extension State.Sensors { $humidity.needsProcessed = newValue } } - + public func dewPoint(units: PsychrometricEnvironment.Units? = nil) -> DewPoint? { guard let temperature = temperature, let humidity = humidity, @@ -76,7 +76,7 @@ extension State.Sensors { else { return nil } return .init(dryBulb: temperature, humidity: humidity, units: units) } - + public func enthalpy(altitude: Length, units: PsychrometricEnvironment.Units? = nil) -> EnthalpyOf? { guard let temperature = temperature, let humidity = humidity, @@ -85,7 +85,7 @@ extension State.Sensors { else { return nil } return .init(dryBulb: temperature, humidity: humidity, altitude: altitude, units: units) } - + public init( temperature: Temperature? = nil, humidity: RelativeHumidity? = nil, @@ -95,9 +95,9 @@ extension State.Sensors { self._humidity = .init(wrappedValue: humidity, needsProcessed: needsProcessed) } } - + // MARK: - Temperature / Humidity Sensor Location Namespaces - public enum Mixed { } + public enum MixedAir { } public enum PostCoil { } public enum Return { } public enum Supply { } diff --git a/Sources/Models/TemperatureAndHumiditySensor.swift b/Sources/Models/TemperatureAndHumiditySensor.swift new file mode 100644 index 0000000..d27343c --- /dev/null +++ b/Sources/Models/TemperatureAndHumiditySensor.swift @@ -0,0 +1,65 @@ +import Psychrometrics + +public struct TemperatureAndHumiditySensor: Equatable, Identifiable { + /// The identifier of the sensor, same as the location. + public var id: Location { location } + + public let altitude: Length + + /// The location identifier of the sensor + public let location: Location + + /// The current temperature value of the sensor. + @TrackedChanges + public var temperature: Temperature? + + /// The current humidity value of the sensor. + @TrackedChanges + public var humidity: RelativeHumidity? + + /// The psychrometric units of the sensor. + public let units: PsychrometricEnvironment.Units + + public init( + location: Location, + altitude: Length = .feet(800.0), + temperature: Temperature? = nil, + humidity: RelativeHumidity? = nil, + needsProcessed: Bool = false, + units: PsychrometricEnvironment.Units = .imperial + ) { + self.altitude = altitude + self.location = location + self._temperature = .init(wrappedValue: temperature, needsProcessed: needsProcessed) + self._humidity = .init(wrappedValue: humidity, needsProcessed: needsProcessed) + self.units = units + } + + /// The calculated dew-point temperature of the sensor. + public var dewPoint: DewPoint? { + guard let temperature = temperature, + let humidity = humidity, + !temperature.rawValue.isNaN, + !humidity.rawValue.isNaN + else { return nil } + return .init(dryBulb: temperature, humidity: humidity, units: units) + } + + /// Check whether any of the sensor values have changed and need processed. + public var needsProcessed: Bool { + get { $temperature.needsProcessed || $humidity.needsProcessed } + set { + $temperature.needsProcessed = newValue + $humidity.needsProcessed = newValue + } + } + + /// Represents the different locations of a temperature and humidity sensor, which can + /// be used to derive the topic to both listen and publish new values to. + public enum Location: String, Equatable, Hashable { + case mixedAir = "mixed-air" + case postCoil = "post-coil" + case `return` + case supply + } +} diff --git a/Sources/Models/Topics.swift b/Sources/Models/Topics.swift index e5efc09..e17fd54 100755 --- a/Sources/Models/Topics.swift +++ b/Sources/Models/Topics.swift @@ -1,19 +1,19 @@ -/// A container for all the different topics that are needed by the application. +/// A container for all the different MQTT topics that are needed by the application. public struct Topics: Codable, Equatable { - + /// The command topics the application can publish to. public var commands: Commands - + /// 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. public var setPoints: SetPoints - + /// The state topics the application can read state values from. public var states: States - + /// Create the topics required by the application. /// /// - Parameters: @@ -32,17 +32,17 @@ public struct Topics: Codable, Equatable { self.setPoints = setPoints self.states = states } - + /// Represents the sensor topics. public struct Sensors: Codable, Equatable { - - public var mixedAirSensor: TemperatureAndHumiditySensor + + public var mixedAirSensor: TemperatureAndHumiditySensor public var postCoilSensor: TemperatureAndHumiditySensor public var returnAirSensor: TemperatureAndHumiditySensor public var supplyAirSensor: TemperatureAndHumiditySensor - + public init( - mixedAirSensor: TemperatureAndHumiditySensor = .default(location: "mixed=air"), + mixedAirSensor: TemperatureAndHumiditySensor = .default(location: "mixed-air"), postCoilSensor: TemperatureAndHumiditySensor = .default(location: "post-coil"), returnAirSensor: TemperatureAndHumiditySensor = .default(location: "return"), supplyAirSensor: TemperatureAndHumiditySensor = .default(location: "supply") @@ -52,13 +52,13 @@ public struct Topics: Codable, Equatable { self.returnAirSensor = returnAirSensor self.supplyAirSensor = supplyAirSensor } - + public struct TemperatureAndHumiditySensor: Codable, Equatable { public var temperature: String public var humidity: String public var dewPoint: String public var enthalpy: String - + /// Create a new sensor topic container. /// /// - Parameters: @@ -78,16 +78,16 @@ public struct Topics: Codable, Equatable { } } } - + /// A container for set point related topics used by the application. public struct SetPoints: Codable, Equatable { - + /// The topic for the humidify set point. public var humidify: Humidify - + /// The topics for dehumidification set points. public var dehumidify: Dehumidify - + /// Create a new set point topic container. /// /// - Parameters: @@ -100,16 +100,16 @@ public struct Topics: Codable, Equatable { self.humidify = humidify self.dehumidify = dehumidify } - + /// A container for the humidification set point topics used by the application. public struct Humidify: Codable, Equatable { - + /// The topic for dew point control mode set point. public var dewPoint: String - + /// The topic for relative humidity control mode set point. public var relativeHumidity: String - + /// Create a new container for the humidification set point topics. /// /// - Parameters: @@ -123,22 +123,22 @@ public struct Topics: Codable, Equatable { self.relativeHumidity = relativeHumidity } } - + /// A container for dehumidifcation set point topics. public struct Dehumidify: Codable, Equatable { - + /// A low setting for dew point control modes. public var lowDewPoint: String - + /// A high setting for dew point control modes. public var highDewPoint: String - + /// A low setting for relative humidity control modes. public var lowRelativeHumidity: String - + /// A high setting for relative humidity control modes. public var highRelativeHumidity: String - + /// Create a new container for dehumidification set point topics. /// /// - Parameters: @@ -159,16 +159,16 @@ public struct Topics: Codable, Equatable { } } } - + /// A container for control state topics used by the application. public struct States: Codable, Equatable { - + /// The topic for the control mode. public var mode: String - + /// The relay state topics. public var relays: Relays - + /// Create a new container for control state topics. /// /// - Parameters: @@ -180,19 +180,19 @@ public struct Topics: Codable, Equatable { self.mode = mode self.relays = relays } - + /// A container for reading the current state of a relay. public struct Relays: Codable, Equatable { - + /// The dehumidification stage-1 relay topic. public var dehumdification1: String - + /// The dehumidification stage-2 relay topic. public var dehumidification2: String - + /// The humidification relay topic. public var humdification: String - + /// Create a new container for relay state topics. /// /// - Parameters: @@ -210,13 +210,13 @@ public struct Topics: Codable, Equatable { } } } - + /// A container for commands topics that the application can publish to. public struct Commands: Codable, Equatable { - + /// The relay command topics. public var relays: Relays - + /// Create a new command topics container. /// /// - Parameters: @@ -224,19 +224,19 @@ public struct Topics: Codable, Equatable { public init(relays: Relays = .init()) { self.relays = relays } - + /// A container for relay command topics used by the application. public struct Relays: Codable, Equatable { - + /// The dehumidification stage-1 relay topic. public var dehumidification1: String - + /// The dehumidification stage-2 relay topic. public var dehumidification2: String - + /// The humidification relay topic. public var humidification: String - + /// Create a new container for commanding relays. /// /// - Parameters: diff --git a/Sources/Models/TrackedChanges.swift b/Sources/Models/TrackedChanges.swift index ac132da..77f3ca8 100755 --- a/Sources/Models/TrackedChanges.swift +++ b/Sources/Models/TrackedChanges.swift @@ -1,11 +1,20 @@ - +/// A property wrapper that tracks changes of a property. +/// +/// This allows values to only publish changes if they have changed since the +/// last time they were recieved. @propertyWrapper public struct TrackedChanges { - + + /// The current tracking state. private var tracking: TrackingState + + /// The current wrapped value. private var value: Value + + /// Used to check if a new value is equal to an old value. private var isEqual: (Value, Value) -> Bool - + + /// Access to the underlying property that we are wrapping. public var wrappedValue: Value { get { value } set { @@ -16,7 +25,13 @@ public struct TrackedChanges { tracking = .needsProcessed } } - + + /// Create a new property that tracks it's changes. + /// + /// - Parameters: + /// - wrappedValue: The value that we are wrapping. + /// - needsProcessed: Whether this value needs processed (default = false). + /// - isEqual: Method to compare old values against new values. public init( wrappedValue: Value, needsProcessed: Bool = false, @@ -26,12 +41,18 @@ public struct TrackedChanges { self.tracking = needsProcessed ? .needsProcessed : .hasProcessed self.isEqual = isEqual } - + + /// Represents whether a wrapped value has changed and needs processed or not. enum TrackingState { + + /// The state when nothing has changed and we've already processed the current value. case hasProcessed + + /// The state when the value has changed and has not been processed yet. case needsProcessed } - + + /// Check whether the value needs processed. public var needsProcessed: Bool { get { tracking == .needsProcessed } set { @@ -42,7 +63,7 @@ public struct TrackedChanges { } } } - + public var projectedValue: Self { get { self } set { self = newValue } @@ -54,7 +75,12 @@ extension TrackedChanges: Equatable where Value: Equatable { lhs.wrappedValue == rhs.wrappedValue && lhs.needsProcessed == rhs.needsProcessed } - + + /// Create a new property that tracks it's changes, using the default equality check. + /// + /// - Parameters: + /// - wrappedValue: The value that we are wrapping. + /// - needsProcessed: Whether this value needs processed (default = false). public init( wrappedValue: Value, needsProcessed: Bool = false diff --git a/buildServer.json b/buildServer.json deleted file mode 100755 index 1e44c41..0000000 --- a/buildServer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "xcode build server", - "version": "0.2", - "bspVersion": "2.0", - "languages": [ - "c", - "cpp", - "objective-c", - "objective-cpp", - "swift" - ], - "argv": [ - "/opt/homebrew/bin/xcode-build-server" - ], - "workspace": "/Volumes/michael/Repos/github.com/hvac-iot/swift-mqtt-dewPoint/.swiftpm/xcode/package.xcworkspace", - "build_root": "/Volumes/michael/Repos/github.com", - "scheme": "dewPoint-controller-Package", - "kind": "xcode" -} \ No newline at end of file