import ArgumentParser import CliClient import Dependencies import Foundation import Logging import Models import MQTTConnectionService import MQTTManager import MQTTNIO import NIO import PsychrometricClientLive import SensorsService import ServiceLifecycle extension Application { /// Run the controller. /// struct Run: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "run", abstract: "Run the controller." ) @OptionGroup var options: SharedOptions mutating func run() async throws { @Dependency(\.cliClient) var cliClient let (mqtt, logger) = try await cliClient.setupRun(options: options) logger.info("Setting up environment...") do { try await withDependencies { $0.psychrometricClient = .liveValue $0.mqtt = .live(client: mqtt, logger: logger) } operation: { let mqttConnection = MQTTConnectionService(logger: logger) let sensors = SensorsService(sensors: .live, logger: logger) var serviceGroupConfiguration = ServiceGroupConfiguration( services: [ mqttConnection, sensors ], gracefulShutdownSignals: [.sigterm, .sigint], logger: logger ) // These settings prevent services from running forever after we've // received a shutdown signal. In general it should not needed unless the // services don't shutdown their async streams properly. serviceGroupConfiguration.maximumCancellationDuration = .seconds(5) serviceGroupConfiguration.maximumGracefulShutdownDuration = .seconds(10) let serviceGroup = ServiceGroup(configuration: serviceGroupConfiguration) logger.info("Starting dewpoint-controller!") try await serviceGroup.run() } // Here we've received a shutdown signal and shutdown all the // services. try await mqtt.shutdown() } catch { // If something fails, shutdown the mqtt client. try await mqtt.shutdown() } } } } private extension CliClient { func setupRun( eventLoopGroup: MultiThreadedEventLoopGroup = .init(numberOfThreads: 1), loggerLabel: String = "dewpoint-controller", options: Application.SharedOptions ) async throws -> (MQTTClient, Logger) { var logger = Logger(label: loggerLabel) let environment = try await makeEnvVars(options.envVarsRequest(logger: logger)) logger.logLevel = logLevel(environment) let client = try makeClient(.init( environment: environment, eventLoopGroup: eventLoopGroup, logger: logger )) return (client, logger) } }