import Vapor import SwiftyGPIO import APNSwift import NIO import Crypto private struct Payload: Codable {} private var apnsConfiguration: APNSClientConfiguration! private var apnsNotification: APNSAlertNotification! private let apnsEventGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) private let apnsRequestEncoder = JSONEncoder() private let apnsResponseDecoder = JSONDecoder() // configures your application public func configure(_ app: Application) throws { let resourcesFolderUrl = URL(fileURLWithPath: app.directory.resourcesDirectory) let configUrl = resourcesFolderUrl.appendingPathComponent("config.json") let config: ServerConfiguration do { let data = try Data(contentsOf: configUrl) config = try JSONDecoder().decode(from: data) } catch { print("Failed to load config: \(error)") return } guard configureAPNS(config) else { return } configureGPIO(config) // register routes try routes(app) serverStatus = .running } private extension JSONDecoder { func decode(from data: Data) throws -> T where T: Decodable { try self.decode(T.self, from: data) } } private func load(keyAtPath path: String) -> P256.Signing.PrivateKey? { do { guard let key = try P256.Signing.PrivateKey.loadFrom(filePath: path) else { print("Failed to read key \(path)") return nil } return key } catch { print("Failed to load key \(error)") return nil } } private func configureAPNS(_ config: ServerConfiguration) -> Bool { guard let key = load(keyAtPath: config.privateKeyFilePath) else { return false } let authentication = APNSClientConfiguration.AuthenticationMethod.jwt( privateKey: key, keyIdentifier: config.keyIdentifier, teamIdentifier: config.teamIdentifier) apnsConfiguration = APNSClientConfiguration( authenticationMethod: authentication, environment: .sandbox) let alert = APNSAlertNotificationContent( title: .raw(config.messageTitle), body: .raw(config.messageBody)) apnsNotification = APNSAlertNotification( alert: alert, expiration: .none, priority: .immediately, topic: config.topic, payload: Payload(), sound: .default) return true } private func configureGPIO(_ config: ServerConfiguration) { let gpio = RaspberryGPIO( name: "GPIO\(config.buttonPin)", id: config.buttonPin, baseAddr: 0x7E000000) gpio.direction = .IN gpio.pull = .down gpio.bounceTime = config.bounceTime gpio.onChange { _ in log(info: "Push detected") sendPush() } log(info: "GPIO \(config.buttonPin) configured") } private func sendPush() { Task(priority: .userInitiated) { let client = APNSClient( configuration: apnsConfiguration, eventLoopGroupProvider: .shared(apnsEventGroup), responseDecoder: apnsResponseDecoder, requestEncoder: apnsRequestEncoder) log(info: "Client created") do { for token in knownTokens { log(info: "Sending push to \(token.prefix(6))...") try await client.sendAlertNotification( apnsNotification, deviceToken: token, deadline: .now() + .seconds(10)) log(info: "Sent push to \(token.prefix(6))...") } } catch let error as APNSError { print(error) } catch { log(error: error.localizedDescription) } do { log("Closing client") try client.syncShutdown() } catch { log(error: error.localizedDescription) } } }