diff --git a/Soyuz.xcodeproj/xcshareddata/xcschemes/Soyuz.xcscheme b/Soyuz.xcodeproj/xcshareddata/xcschemes/Soyuz.xcscheme new file mode 100644 index 0000000..b7b5afd --- /dev/null +++ b/Soyuz.xcodeproj/xcshareddata/xcschemes/Soyuz.xcscheme @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Soyuz.xcodeproj/xcuserdata/madeline.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Soyuz.xcodeproj/xcuserdata/madeline.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..6af6e7e --- /dev/null +++ b/Soyuz.xcodeproj/xcuserdata/madeline.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Soyuz/ViewModels/MoonrakerSocketManagerNative.swift b/Soyuz/ViewModels/MoonrakerSocketManagerNative.swift new file mode 100644 index 0000000..f726e1d --- /dev/null +++ b/Soyuz/ViewModels/MoonrakerSocketManagerNative.swift @@ -0,0 +1,207 @@ +// +// MoonrakerSocketManagerNative.swift +// Soyuz +// +// Created by Madeline Pace on 6/27/23. +// + +import Foundation + +import Foundation +import Network +import AppKit +//import Starscream + + +class MoonrakerSocketManagerNative: ObservableObject { + let WEBSOCKET_TIMEOUT_INTERVAL: TimeInterval = 60.0 + + // Websocket JSON-RPC published data + @Published var state: String + @Published var progress: Double + @Published var extruderTemperature: Double + @Published var bedTemperature: Double + + // Active connection published data + @Published var isConnected = false + @Published var socketHost: String + @Published var socketPort: String + + // Published NWConnection for listing connection information + @Published var connection: NWConnection? + @Published var friendlyHostname: String = "" + + var notification = UserNotificationHandler.shared + + private var socket: NWConnection? + private var lastPingDate = Date() + + // MARK: PRM init() + init() { + state = "" + progress = 0.0 + extruderTemperature = 0.0 + bedTemperature = 0.0 + socketHost = "" + socketPort = "" + + // Set up sleep/wake notification observers + let center = NSWorkspace.shared.notificationCenter; + let mainQueue = OperationQueue.main + + center.addObserver(forName: NSWorkspace.screensDidWakeNotification, object: nil, queue: mainQueue) { notification in + self.screenChangedSleepState(notification) + } + + center.addObserver(forName: NSWorkspace.screensDidSleepNotification, object: nil, queue: mainQueue) { notification in + self.screenChangedSleepState(notification) + } + } + + // Called from the UI with an endpoint. + // Momentarily connect/disconnects from the endpoint to retrieve the host/port + // calls private function openWebsocket to process the host/port + func connectToBonjourEndpoint(_ endpoint: NWEndpoint) { + // Debug stuff + endpoint.txtRecord?.forEach({ (key: String, value: NWTXTRecord.Entry) in + print("\(key): \(value)") + }) + + print("About to connect to endpoint \(endpoint.debugDescription)") + + if connection == nil || connection?.state == .cancelled { + // //let parameters = NWParameters.tcp + // //let options = NWProtocolWebSocket.Options() + // //parameters.defaultProtocolStack.applicationProtocols.insert(options, at: 0) + // let paramet + connection = NWConnection(to: endpoint, using: .tcp) + } + // + // connection!.stateUpdateHandler = { [self] state in + // switch state { + // case .setup: + // break + // case .ready: + // self.isConnected = true + // case .failed(let error): + // self.isConnected = false + // print("[NWConnection websocket] connection failed: \(error)") + // case .cancelled: + // self.isConnected = false + // print("Connection cancelled.") + // default: + // break + // } + // } + // + connection?.stateUpdateHandler = { [self] state in + switch state { + case .ready: + if let innerEndpoint = connection?.currentPath?.remoteEndpoint, case .hostPort(let host, let port) = innerEndpoint { + let hostPortDebugOutput = "Connected to \(host):\(port)" + + print(hostPortDebugOutput) + + let hostString = "\(host)" + let regex = try! Regex("%(.+)") + let match = hostString.firstMatch(of: regex) + + let sanitizedHost = hostString.replacingOccurrences(of: match?.0 ?? "", with: "") + + print("[sanitized] Resolved \(sanitizedHost):\(port)") + + connection?.cancel() + + DispatchQueue.main.async { + self.friendlyHostname = endpoint.toFriendlyString() + self.socketHost = sanitizedHost + self.socketPort = "\(port)" + self.openWebsocket() + } + } + default: + break + } + } + connection?.start(queue: .global()) + } + + func disconnect() { + print("disconnect() called") + self.isConnected = false + //socket?.disconnect() + //socket = nil + } + + func openWebsocket() { + let parameters = NWParameters.tcp + let socketUrl = URL(string: "ws://\(socketHost):\(socketPort)/websocket") + let options = NWProtocolWebSocket.Options() + parameters.defaultProtocolStack.applicationProtocols.insert(options, at: 0) + socket = NWConnection(to: .url(socketUrl!), using: parameters) + socket?.stateUpdateHandler = { state in + switch state { + case .setup: + print("[websocket] Connection setup.") + case .ready: + print("[websocket] Connection ready.") + case .failed(let error): + print("[websocket] Connection failed: \(error)") + case .cancelled: + print("[websocket] Connection cancelled.") + default: + break + } + } + + socket?.start(queue: .global()) + } + + func socketConnectionChangedState() { + + } + + + // TODO: This may not work properly when already connected to the socket + private func reconnectWebsocket() { + if socket == nil { + print("Socket doesn't exist. Fail-safe triggered.") + return + } + + //socket!.disconnect() + //self.openWebsocket() + } + + // MARK: Callbacks + func screenChangedSleepState(_ notification: Notification) { + switch(notification.name) { + case NSWorkspace.screensDidSleepNotification: + print("Screen slept. Disconnecting..") + self.disconnect() + //socket?.disconnect() + case NSWorkspace.screensDidWakeNotification: + print("Screen awoke. Opening websocket..") + //self.openWebsocket() + default: + return + } + } +} + +// Properly formatted JSON-RPC Request for use with Starscream +// MARK: JSON-RPC Request Codable +//struct JsonRpcRequest: Codable { +// var jsonrpc = "2.0" +// let method: String +// let params: [String: [String: String?]] +// var id = 1 +// +// func encode(to encoder: Encoder) throws { +// var container = encoder.container(keyedBy: CodingKeys.self) +// try container.encode(jsonrpc, forKey: .jsonrpc) +// try container.encode(method, forKey: .method) +// try container.encode(params, forKey: .params) +// try container.encode(id, forKey: .id) +// } +//}