diff --git a/.gitignore b/.gitignore index 71f55a2..0d58ddf 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ xcuserdata/ DerivedData/ .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .dewPoint-env +.topics diff --git a/Bootstrap/dewPoint-env-example b/Bootstrap/dewPoint-env-example index 6643493..48be2bc 100644 --- a/Bootstrap/dewPoint-env-example +++ b/Bootstrap/dewPoint-env-example @@ -4,11 +4,5 @@ "MQTT_PORT": "1883", "MQTT_IDENTIFIER": "dewPoint-controller", "MQTT_USERNAME": "mqtt_user", - "MQTT_PASSWORD": "secret!", - "DEHUMIDIFICATION_STAGE_1_RELAY": "relays/dehumidification_1", - "DEHUMIDIFICATION_STAGE_2_RELAY": "relays/dehumidification_2", - "DEW_POINT_TOPIC": "sensors/dew_point", - "HUMIDIFICATION_RELAY": "relays/humidification", - "HUMIDITY_SENSOR": "sensors/humidity", - "TEMPERATURE_SENSOR": "sensors/temperature" + "MQTT_PASSWORD": "secret!" } diff --git a/Bootstrap/topics-example b/Bootstrap/topics-example new file mode 100644 index 0000000..0b5009f --- /dev/null +++ b/Bootstrap/topics-example @@ -0,0 +1,34 @@ +{ + "sensors": { + "humidity": "sensors/humidity", + "temperature":"sensors/temperature", + "dewPoint":"sensors/dew_point" + }, + "setPoints": { + "dehumidify":{ + "lowDewPoint":"set_points/dehumidify/low_dew_point", + "highDewPoint":"set_points/dehumidify/high_dew_point", + "lowRelativeHumidity":"set_points/dehumidify/low_relative_humidity", + "highRelativeHumidity":"set_points/dehumidify/high_relative_humidity" + }, + "humidify":{ + "relativeHumidity":"set_points/humidify/relative_humidity", + "dewPoint":"set_points/humidify/dew_point" + } + }, + "states":{ + "mode":"states/mode", + "relays":{ + "dehumdification1":"states/relays/dehumidification_1", + "humdification":"states/relays/humidification", + "dehumidification2":"states/relays/dehumidification_2" + } + }, + "commands":{ + "relays":{ + "dehumidification2":"relays/dehumidification_2", + "humidification":"relays/humidification", + "dehumidification1":"relays/dehumidification_1" + } + } +} diff --git a/Makefile b/Makefile index a70a7b8..5be4655 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ bootstrap-env: @cp Bootstrap/dewPoint-env-example .dewPoint-env + +bootstrap-topics: + @cp Bootstrap/topics-example .topics build: @swift build diff --git a/Sources/Bootstrap/Bootstrap.swift b/Sources/Bootstrap/Bootstrap.swift index a33e285..593ecad 100644 --- a/Sources/Bootstrap/Bootstrap.swift +++ b/Sources/Bootstrap/Bootstrap.swift @@ -20,6 +20,7 @@ public func bootstrap( logger?.debug("Bootstrapping Dew Point Controller...") return loadEnvVars(eventLoopGroup: eventLoopGroup, logger: logger) + .and(loadTopics(eventLoopGroup: eventLoopGroup, logger: logger)) .makeDewPointEnvironment(eventLoopGroup: eventLoopGroup, logger: logger) .connectToMQTTBroker(logger: logger) } @@ -68,7 +69,42 @@ private func loadEnvVars(eventLoopGroup: EventLoopGroup, logger: Logger?) -> Eve return eventLoopGroup.next().makeSucceededFuture(envVars) } -extension EventLoopFuture where Value == EnvVars { +func loadTopics(eventLoopGroup: EventLoopGroup, logger: Logger?) -> EventLoopFuture { + + logger?.debug("Loading topics from file...") + + let topicsFilePath = URL(fileURLWithPath: #file) + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + .appendingPathComponent(".topics") + + let decoder = JSONDecoder() + + let data = try? Data.init(contentsOf: topicsFilePath) + logger?.debug("Data: \(data!)") + + let localTopics = data + .flatMap { try? decoder.decode(Topics.self, from: $0) } + +// .flatMap { try decoder.decode(Topics.self, from: $0) } + + logger?.debug( + localTopics == nil + ? "Failed to load topics from file, falling back to defaults." + : "Done loading topics from file." + ) + +// let defaultData = Topics() +// let jsonData = try! JSONEncoder().encode(defaultData) +// let string = String.init(data: jsonData, encoding: .utf8) ?? "Invalid data" +// logger?.debug("\(string)") +// try! string.write(to: topicsFilePath, atomically: true, encoding: .utf8) +// + return eventLoopGroup.next().makeSucceededFuture(localTopics ?? .init()) +} + +extension EventLoopFuture where Value == (EnvVars, Topics) { /// Creates the ``DewPointEnvironment`` for the application after the ``EnvVars`` have been loaded. /// @@ -79,13 +115,13 @@ extension EventLoopFuture where Value == EnvVars { eventLoopGroup: EventLoopGroup, logger: Logger? ) -> EventLoopFuture { - map { envVars in + map { envVars, topics in let nioClient = MQTTClient(envVars: envVars, eventLoopGroup: eventLoopGroup, logger: logger) return DewPointEnvironment.init( mqttClient: .live(client: nioClient), envVars: envVars, nioClient: nioClient, - topics: .init(envVars: envVars) + topics: topics ) } } @@ -128,22 +164,3 @@ extension MQTTClient { ) } } - -// MARK: - TODO Make topics loadable from a file in the root directory. -extension Topics { - - init(envVars: EnvVars) { - self.init( - sensors: .init( - temperature: envVars.temperatureSensor, - humidity: envVars.humiditySensor, - dewPoint: envVars.dewPointTopic - ), - relays: .init( - dehumidification1: envVars.dehumidificationStage1Relay, - dehumidification2: envVars.dehumidificationStage2Relay, - humidification: envVars.humidificationRelay - ) - ) - } -} diff --git a/Sources/EnvVars/EnvVars.swift b/Sources/EnvVars/EnvVars.swift index f8e558f..47f4a26 100644 --- a/Sources/EnvVars/EnvVars.swift +++ b/Sources/EnvVars/EnvVars.swift @@ -24,15 +24,6 @@ public struct EnvVars: Codable, Equatable { /// The MQTT user password. public var password: String? - // MARK: TODO Move Topics to their own file that can be loaded. - // Topics - public var dehumidificationStage1Relay: String - public var dehumidificationStage2Relay: String - public var dewPointTopic: String - public var humidificationRelay: String - public var humiditySensor: String - public var temperatureSensor: String - /// Create a new ``EnvVars`` /// /// - Parameters: @@ -48,13 +39,7 @@ public struct EnvVars: Codable, Equatable { port: String? = "1883", identifier: String = "dewPoint-controller", userName: String? = "mqtt_user", - password: String? = "secret!", - dehumidificationStage1Relay: String = "relays/dehumidification_1", - dehumidificationStage2Relay: String = "relays/dehumidification_2", - dewPointTopic: String = "sensors/dew_point", - humidificationRelay: String = "relays/humidification", - humiditySensor: String = "sensors/humidity", - temperatureSensor: String = "sensors/temperature" + password: String? = "secret!" ){ self.appEnv = appEnv self.host = host @@ -62,12 +47,6 @@ public struct EnvVars: Codable, Equatable { self.identifier = identifier self.userName = userName self.password = password - self.dehumidificationStage1Relay = dehumidificationStage1Relay - self.dehumidificationStage2Relay = dehumidificationStage2Relay - self.dewPointTopic = dewPointTopic - self.humidificationRelay = humidificationRelay - self.humiditySensor = humiditySensor - self.temperatureSensor = temperatureSensor } /// Custom coding keys. @@ -78,12 +57,6 @@ public struct EnvVars: Codable, Equatable { case identifier = "MQTT_IDENTIFIER" case userName = "MQTT_USERNAME" case password = "MQTT_PASSWORD" - case dehumidificationStage1Relay = "DEHUMIDIFICATION_STAGE_1_RELAY" - case dehumidificationStage2Relay = "DEHUMIDIFICATION_STAGE_2_RELAY" - case dewPointTopic = "DEW_POINT_TOPIC" - case humidificationRelay = "HUMIDIFICATION_RELAY" - case humiditySensor = "HUMIDITY_SENSOR" - case temperatureSensor = "TEMPERATURE_SENSOR" } /// Represents the different app environments. diff --git a/Sources/Models/Topics.swift b/Sources/Models/Topics.swift index b5959e9..88c8666 100644 --- a/Sources/Models/Topics.swift +++ b/Sources/Models/Topics.swift @@ -1,27 +1,56 @@ -public struct Topics { + +/// A container for all the different 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 sensor values from. public var sensors: Sensors - public var setPoints: SetPoints - public var states: States - public var relays: Relays + /// 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: + /// - sensors: The sensor topics. + /// - setPoints: The set point topics + /// - states: The states topics + /// - relays: The relay topics public init( + commands: Commands = .init(), sensors: Sensors = .init(), setPoints: SetPoints = .init(), - states: States = .init(), - relays: Relays = .init() + states: States = .init() ) { + self.commands = commands self.sensors = sensors self.setPoints = setPoints self.states = states - self.relays = relays } - public struct Sensors { + /// Represents the sensor topics. + public struct Sensors: Codable, Equatable { + + /// The temperature sensor topic. public var temperature: String + + /// The humidity sensor topic. public var humidity: String + + /// The dew point topic (we write / publish this data from the application). public var dewPoint: String + /// Create a new sensor topic container. + /// + /// - Parameters: + /// - temperature: The temperature sensor topic. + /// - humidity: The humidity sensor topic. + /// - dewPoint: The dew point sensor topic. public init( temperature: String = "sensors/temperature", humidity: String = "sensors/humidity", @@ -33,24 +62,73 @@ public struct Topics { } } - public struct SetPoints { - public var humidify: String + /// 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: + /// - humidify: The topic for humidification set points. + /// - dehumidify: The topics for dehumidification set points. public init( - humidify: String = "set_points/humidify", + humidify: Humidify = .init(), dehumidify: Dehumidify = .init() ) { self.humidify = humidify self.dehumidify = dehumidify } - public struct 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: + /// - dewPoint: The topic for dew point control mode set point. + /// - relativeHumidity: The topic for relative humidity control mode set point. + public init( + dewPoint: String = "set_points/humidify/dew_point", + relativeHumidity: String = "set_points/humidify/relative_humidity" + ) { + self.dewPoint = dewPoint + 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: + /// - lowDewPoint: A low setting for dew point control modes. + /// - highDewPoint: A high setting for dew point control modes. + /// - lowRelativeHumidity: A low setting for relative humidity control modes. + /// - highRelativeHumidity: A high setting for relative humidity control modes. public init( lowDewPoint: String = "set_points/dehumidify/low_dew_point", highDewPoint: String = "set_points/dehumidify/high_dew_point", @@ -65,27 +143,98 @@ public struct Topics { } } - public struct States { + /// 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 - public init(mode: String = "states/mode") { + /// The relay state topics. + public var relays: Relays + + /// Create a new container for control state topics. + /// + /// - Parameters: + /// - mode: The topic for the control mode. + public init( + mode: String = "states/mode", + relays: Relays = .init() + ) { 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: + /// - dehumidification1: The dehumidification stage-1 relay topic. + /// - dehumidification2: The dehumidification stage-2 relay topic. + /// - humidification: The humidification relay topic. + public init( + dehumidefication1: String = "states/relays/dehumidification_1", + dehumidification2: String = "states/relays/dehumidification_2", + humidification: String = "states/relays/humidification" + ) { + self.dehumdification1 = dehumidefication1 + self.dehumidification2 = dehumidification2 + self.humdification = humidification + } } } - public struct Relays { - public var dehumidification1: String - public var dehumidification2: String - public var humidification: String + /// A container for commands topics that the application can publish to. + public struct Commands: Codable, Equatable { - public init( - dehumidification1: String = "relays/dehumidification_1", - dehumidification2: String = "relays/dehumidification_2", - humidification: String = "relays/humidification" - ) { - self.dehumidification1 = dehumidification1 - self.dehumidification2 = dehumidification2 - self.humidification = humidification + /// The relay command topics. + public var relays: Relays + + /// Create a new command topics container. + /// + /// - Parameters: + /// - relays: The relay command topics. + 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: + /// - dehumidification1: The dehumidification stage-1 relay topic. + /// - dehumidification2: The dehumidification stage-2 relay topic. + /// - humidification: The humidification relay topic. + public init( + dehumidification1: String = "relays/dehumidification_1", + dehumidification2: String = "relays/dehumidification_2", + humidification: String = "relays/humidification" + ) { + self.dehumidification1 = dehumidification1 + self.dehumidification2 = dehumidification2 + self.humidification = humidification + } } } } diff --git a/Sources/dewPoint-controller/main.swift b/Sources/dewPoint-controller/main.swift index b87cbeb..a25d35c 100644 --- a/Sources/dewPoint-controller/main.swift +++ b/Sources/dewPoint-controller/main.swift @@ -22,7 +22,7 @@ if environment.envVars.appEnv == .production { logger.logLevel = .info } -let relay = Relay(topic: environment.topics.relays.dehumidification1) +let relay = Relay(topic: environment.topics.commands.relays.dehumidification1) let tempSensor = Sensor(topic: environment.topics.sensors.temperature) let humiditySensor = Sensor(topic: environment.topics.sensors.humidity)