Refactor code, ensure websocket connection (and @ObservableObject updates) occur on main thread
This commit is contained in:
parent
ba42ff1207
commit
0a8670ed71
@ -13,7 +13,7 @@ struct SoyuzApp: App {
|
|||||||
|
|
||||||
@State var currentIcon = "move.3d"
|
@State var currentIcon = "move.3d"
|
||||||
|
|
||||||
@ObservedObject var printerManager = PrinterRequestManager()
|
@ObservedObject static var printerManager = PrinterRequestManager()
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
// WindowGroup(id: "floating-stats") {
|
// WindowGroup(id: "floating-stats") {
|
||||||
@ -22,13 +22,13 @@ struct SoyuzApp: App {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
WindowGroup("Configuration", id: "soyuz_cfg", content: {
|
WindowGroup("Configuration", id: "soyuz_cfg", content: {
|
||||||
PrinterConfigView(printerManager: printerManager)
|
PrinterConfigView(printerManager: SoyuzApp.printerManager)
|
||||||
//.frame(minWidth: 300, maxWidth: 600, minHeight: 60, maxHeight: 100)
|
//.frame(minWidth: 300, maxWidth: 600, minHeight: 60, maxHeight: 100)
|
||||||
})
|
})
|
||||||
.windowResizability(.contentSize)
|
.windowResizability(.contentSize)
|
||||||
|
|
||||||
MenuBarExtra("Soyuz", systemImage: currentIcon) {
|
MenuBarExtra("Soyuz", systemImage: currentIcon) {
|
||||||
SoyuzMenuBarExtraView(printerManager: printerManager, currentMenuBarIcon: $currentIcon)
|
SoyuzMenuBarExtraView(printerManager: SoyuzApp.printerManager, currentMenuBarIcon: $currentIcon)
|
||||||
.padding([.top, .leading, .trailing], 8)
|
.padding([.top, .leading, .trailing], 8)
|
||||||
.padding([.bottom], 6)
|
.padding([.bottom], 6)
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,9 @@ import Network
|
|||||||
import AppKit
|
import AppKit
|
||||||
import Starscream
|
import Starscream
|
||||||
|
|
||||||
// MARK: Bonjour Protocol
|
|
||||||
|
|
||||||
|
// Protocol defining minimal API for network discovery
|
||||||
|
// MARK: Net Discovery Protocol
|
||||||
protocol NetworkDiscoveryEngine {
|
protocol NetworkDiscoveryEngine {
|
||||||
func startScan(queue: DispatchQueue)
|
func startScan(queue: DispatchQueue)
|
||||||
func setBrowseResultsChangedHandler(_ handler: @escaping ((Set<NWBrowser.Result>, Set<NWBrowser.Result.Change>) -> Void))
|
func setBrowseResultsChangedHandler(_ handler: @escaping ((Set<NWBrowser.Result>, Set<NWBrowser.Result.Change>) -> Void))
|
||||||
@ -32,8 +33,9 @@ extension NWBrowser: NetworkDiscoveryEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Starscream Protocol
|
|
||||||
|
|
||||||
|
// Properly formatted JSON-RPC Request for use with Starscream
|
||||||
|
// MARK: JSON-RPC Request Codable
|
||||||
struct JsonRpcRequest: Codable {
|
struct JsonRpcRequest: Codable {
|
||||||
var jsonrpc = "2.0"
|
var jsonrpc = "2.0"
|
||||||
let method: String
|
let method: String
|
||||||
@ -49,15 +51,17 @@ struct JsonRpcRequest: Codable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: PrinterRequestManager
|
||||||
//@MainActor
|
//@MainActor
|
||||||
class PrinterRequestManager: ObservableObject, WebSocketDelegate {
|
class PrinterRequestManager: ObservableObject, WebSocketDelegate {
|
||||||
let WEBSOCKET_TIMEOUT_INTERVAL: TimeInterval = 60.0
|
let WEBSOCKET_TIMEOUT_INTERVAL: TimeInterval = 60.0
|
||||||
|
|
||||||
// Debug stuff
|
// Debug timestamp stuff
|
||||||
let startDate = Date()
|
let startDate = Date()
|
||||||
let startDateString: String
|
let startDateString: String
|
||||||
let filename: URL
|
let filename: URL
|
||||||
|
|
||||||
|
// Debug file writing stuff
|
||||||
func writeToDebugLog(_ output: String) {
|
func writeToDebugLog(_ output: String) {
|
||||||
do {
|
do {
|
||||||
let fileHandle = try FileHandle(forWritingTo: filename)
|
let fileHandle = try FileHandle(forWritingTo: filename)
|
||||||
@ -91,38 +95,12 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
|
|||||||
@Published var socketPort: String
|
@Published var socketPort: String
|
||||||
|
|
||||||
let nwBrowser: NetworkDiscoveryEngine
|
let nwBrowser: NetworkDiscoveryEngine
|
||||||
//let nwBrowser = NWBrowser(for: .bonjourWithTXTRecord(type: "_moonraker._tcp", domain: "local."), using: .tcp)
|
|
||||||
var connection: NWConnection!
|
var connection: NWConnection!
|
||||||
|
|
||||||
var socket: WebSocket?
|
var socket: WebSocket?
|
||||||
var lastPingDate = Date()
|
var lastPingDate = Date()
|
||||||
|
|
||||||
// Parse a JSON-RPC query-response message
|
// MARK: PRM init()
|
||||||
func parse_response(_ response: jsonRpcResponse) {
|
|
||||||
state = response.result.status.print_stats?.state ?? ""
|
|
||||||
progress = response.result.status.virtual_sdcard?.progress ?? 0.0
|
|
||||||
extruderTemperature = response.result.status.extruder?.temperature ?? 0.0
|
|
||||||
bedTemperature = response.result.status.heater_bed?.temperature ?? 0.0
|
|
||||||
|
|
||||||
print(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse a JSON-RPC update message
|
|
||||||
func parse_update(_ update: jsonRpcUpdate) {
|
|
||||||
if let newState = update.params.status?.print_stats?.state {
|
|
||||||
state = newState
|
|
||||||
}
|
|
||||||
if let newProgress = update.params.status?.virtual_sdcard?.progress {
|
|
||||||
progress = newProgress
|
|
||||||
}
|
|
||||||
if let newExtruderTemp = update.params.status?.extruder?.temperature {
|
|
||||||
extruderTemperature = newExtruderTemp
|
|
||||||
}
|
|
||||||
if let newBedTemp = update.params.status?.heater_bed?.temperature {
|
|
||||||
bedTemperature = newBedTemp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(browser: NetworkDiscoveryEngine = NWBrowser(for: .bonjourWithTXTRecord(type: "_moonraker._tcp", domain: "local."), using: .tcp)) {
|
init(browser: NetworkDiscoveryEngine = NWBrowser(for: .bonjourWithTXTRecord(type: "_moonraker._tcp", domain: "local."), using: .tcp)) {
|
||||||
state = ""
|
state = ""
|
||||||
progress = 0.0
|
progress = 0.0
|
||||||
@ -131,9 +109,8 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
|
|||||||
socketHost = ""
|
socketHost = ""
|
||||||
socketPort = ""
|
socketPort = ""
|
||||||
nwBrowser = browser
|
nwBrowser = browser
|
||||||
//reconnectionTimer = nil
|
|
||||||
|
|
||||||
// MARK: Debug stuff
|
// Debug output-to-file functionality
|
||||||
startDateString = "\(startDate)\n\n"
|
startDateString = "\(startDate)\n\n"
|
||||||
filename = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("klippermon-debug-\(startDateString).txt")
|
filename = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("klippermon-debug-\(startDateString).txt")
|
||||||
|
|
||||||
@ -143,7 +120,7 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
|
|||||||
print("[error] Couldn't write to \(filename) - \(error)")
|
print("[error] Couldn't write to \(filename) - \(error)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Bonjour browser initialization at instantiation
|
// Bonjour browser results changed handler
|
||||||
nwBrowser.setBrowseResultsChangedHandler({ (newResults, changes) in
|
nwBrowser.setBrowseResultsChangedHandler({ (newResults, changes) in
|
||||||
print("[update] Results changed.")
|
print("[update] Results changed.")
|
||||||
newResults.forEach { result in
|
newResults.forEach { result in
|
||||||
@ -164,12 +141,8 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Start up the bonjour browser, get results and process them in the update handler
|
|
||||||
nwBrowser.startScan(queue: DispatchQueue.main)
|
|
||||||
|
|
||||||
// Screen sleep functionality
|
// Set up sleep/wake notification observers
|
||||||
// NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(screenDidSleep(_:)), name: NSWorkspace.screensDidSleepNotification, object: nil)
|
|
||||||
// NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(screenDidWake(_:)), name: NSWorkspace.screensDidWakeNotification, object: nil)
|
|
||||||
let center = NSWorkspace.shared.notificationCenter;
|
let center = NSWorkspace.shared.notificationCenter;
|
||||||
let mainQueue = OperationQueue.main
|
let mainQueue = OperationQueue.main
|
||||||
|
|
||||||
@ -180,30 +153,15 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
|
|||||||
center.addObserver(forName: NSWorkspace.screensDidSleepNotification, object: nil, queue: mainQueue) { notification in
|
center.addObserver(forName: NSWorkspace.screensDidSleepNotification, object: nil, queue: mainQueue) { notification in
|
||||||
self.screenChangedSleepState(notification)
|
self.screenChangedSleepState(notification)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func screenChangedSleepState(_ notification: Notification) {
|
// Start up the bonjour browser, get results and process them in the update handler
|
||||||
switch(notification.name) {
|
nwBrowser.startScan(queue: DispatchQueue.main)
|
||||||
case NSWorkspace.screensDidSleepNotification:
|
|
||||||
socket?.disconnect()
|
|
||||||
case NSWorkspace.screensDidWakeNotification:
|
|
||||||
self.openWebsocket()
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func screenDidWake(_ notification: Notification) {
|
|
||||||
print("Screen woke: \(notification.name)")
|
|
||||||
if socket != nil {
|
|
||||||
self.openWebsocket()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called from the UI with an endpoint.
|
// Called from the UI with an endpoint.
|
||||||
// Momentarily connect/disconnects from the endpoint to retrieve the host/port
|
// Momentarily connect/disconnects from the endpoint to retrieve the host/port
|
||||||
// calls private function openWebsocket to process the host/port
|
// calls private function openWebsocket to process the host/port
|
||||||
func resolveBonjourHost(_ endpoint: NWEndpoint) {
|
func connectToBonjourEndpoint(_ endpoint: NWEndpoint) {
|
||||||
// Debug stuff
|
// Debug stuff
|
||||||
endpoint.txtRecord?.forEach({ (key: String, value: NWTXTRecord.Entry) in
|
endpoint.txtRecord?.forEach({ (key: String, value: NWTXTRecord.Entry) in
|
||||||
print("\(key): \(value)")
|
print("\(key): \(value)")
|
||||||
@ -226,10 +184,13 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
|
|||||||
|
|
||||||
print("[sanitized] Resolved \(sanitizedHost):\(port)")
|
print("[sanitized] Resolved \(sanitizedHost):\(port)")
|
||||||
|
|
||||||
socketHost = sanitizedHost
|
|
||||||
socketPort = "\(port)"
|
|
||||||
connection.cancel()
|
connection.cancel()
|
||||||
self.openWebsocket()
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.socketHost = sanitizedHost
|
||||||
|
self.socketPort = "\(port)"
|
||||||
|
self.openWebsocket()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -238,18 +199,10 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
|
|||||||
connection.start(queue: .global())
|
connection.start(queue: .global())
|
||||||
}
|
}
|
||||||
|
|
||||||
func reconnectWebsocket() {
|
|
||||||
if socket == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
socket!.disconnect()
|
// MARK: Private functions
|
||||||
self.openWebsocket()
|
|
||||||
//socket!.write(ping: "PING!".data(using: .utf8)!)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opens the websocket connection
|
// Opens the websocket connection
|
||||||
// TODO: host and port should be function arguments probably maybe
|
|
||||||
private func openWebsocket() {
|
private func openWebsocket() {
|
||||||
//let fullUrlString = "http://\(socketHost):\(socketPort)/websocket"
|
//let fullUrlString = "http://\(socketHost):\(socketPort)/websocket"
|
||||||
var request = URLRequest(url: URL(string: "http://\(socketHost):\(socketPort)/websocket")!)
|
var request = URLRequest(url: URL(string: "http://\(socketHost):\(socketPort)/websocket")!)
|
||||||
@ -257,14 +210,29 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
|
|||||||
socket = WebSocket(request: request)
|
socket = WebSocket(request: request)
|
||||||
socket!.delegate = self
|
socket!.delegate = self
|
||||||
socket!.connect()
|
socket!.connect()
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Check that this keeps the connection alive properly
|
private func reconnectWebsocket() {
|
||||||
Timer.scheduledTimer(withTimeInterval: 30.0, repeats: true) { [self] timer in
|
if socket == nil {
|
||||||
//self.checkWebsocketIsAlive()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
socket!.disconnect()
|
||||||
|
self.openWebsocket()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Callsbacks
|
||||||
|
func screenChangedSleepState(_ notification: Notification) {
|
||||||
|
switch(notification.name) {
|
||||||
|
case NSWorkspace.screensDidSleepNotification:
|
||||||
|
socket?.disconnect()
|
||||||
|
case NSWorkspace.screensDidWakeNotification:
|
||||||
|
self.openWebsocket()
|
||||||
|
default:
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: delegate callback for Starscream WebSocketClient
|
|
||||||
func didReceive(event: Starscream.WebSocketEvent, client: Starscream.WebSocket) {
|
func didReceive(event: Starscream.WebSocketEvent, client: Starscream.WebSocket) {
|
||||||
switch event {
|
switch event {
|
||||||
case .connected(let headers):
|
case .connected(let headers):
|
||||||
@ -325,4 +293,30 @@ class PrinterRequestManager: ObservableObject, WebSocketDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: JSON-RPC Parsing
|
||||||
|
// Parse a JSON-RPC query-response message
|
||||||
|
func parse_response(_ response: jsonRpcResponse) {
|
||||||
|
state = response.result.status.print_stats?.state ?? ""
|
||||||
|
progress = response.result.status.virtual_sdcard?.progress ?? 0.0
|
||||||
|
extruderTemperature = response.result.status.extruder?.temperature ?? 0.0
|
||||||
|
bedTemperature = response.result.status.heater_bed?.temperature ?? 0.0
|
||||||
|
|
||||||
|
print(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a JSON-RPC update message
|
||||||
|
func parse_update(_ update: jsonRpcUpdate) {
|
||||||
|
if let newState = update.params.status?.print_stats?.state {
|
||||||
|
state = newState
|
||||||
|
}
|
||||||
|
if let newProgress = update.params.status?.virtual_sdcard?.progress {
|
||||||
|
progress = newProgress
|
||||||
|
}
|
||||||
|
if let newExtruderTemp = update.params.status?.extruder?.temperature {
|
||||||
|
extruderTemperature = newExtruderTemp
|
||||||
|
}
|
||||||
|
if let newBedTemp = update.params.status?.heater_bed?.temperature {
|
||||||
|
bedTemperature = newBedTemp
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ struct PrinterConfigView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Text(result.endpoint.toFriendlyString())
|
Text(result.endpoint.toFriendlyString())
|
||||||
Button {
|
Button {
|
||||||
printerManager.resolveBonjourHost(result.endpoint)
|
printerManager.connectToBonjourEndpoint(result.endpoint)
|
||||||
} label: {
|
} label: {
|
||||||
Text("Connect")
|
Text("Connect")
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
Loading…
Reference in New Issue
Block a user