MenuBarExtra lists Bonjour services. Clicking on a service opens a WebSocket connection.
TODO: Consume the API data from the websocket queries and display them in the UI
This commit is contained in:
parent
5f429b8cb4
commit
ee2583172a
@ -19,6 +19,7 @@
|
|||||||
E180B61D2992D53700425DB0 /* PrinterObjectsQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = E180B61C2992D53700425DB0 /* PrinterObjectsQuery.swift */; };
|
E180B61D2992D53700425DB0 /* PrinterObjectsQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = E180B61C2992D53700425DB0 /* PrinterObjectsQuery.swift */; };
|
||||||
E180B61F2992DBB000425DB0 /* KlipperMonMenuBarExtraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E180B61E2992DBB000425DB0 /* KlipperMonMenuBarExtraView.swift */; };
|
E180B61F2992DBB000425DB0 /* KlipperMonMenuBarExtraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E180B61E2992DBB000425DB0 /* KlipperMonMenuBarExtraView.swift */; };
|
||||||
E180B6222993256E00425DB0 /* PrinterRequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E180B6212993256E00425DB0 /* PrinterRequestManager.swift */; };
|
E180B6222993256E00425DB0 /* PrinterRequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E180B6212993256E00425DB0 /* PrinterRequestManager.swift */; };
|
||||||
|
E1E8B07729949E2700BABE4B /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = E1E8B07629949E2700BABE4B /* Starscream */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -63,6 +64,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
E1E8B07729949E2700BABE4B /* Starscream in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -162,6 +164,9 @@
|
|||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
name = KlipperMon;
|
name = KlipperMon;
|
||||||
|
packageProductDependencies = (
|
||||||
|
E1E8B07629949E2700BABE4B /* Starscream */,
|
||||||
|
);
|
||||||
productName = KlipperMon;
|
productName = KlipperMon;
|
||||||
productReference = E180B5E52992CD9100425DB0 /* KlipperMon.app */;
|
productReference = E180B5E52992CD9100425DB0 /* KlipperMon.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
@ -234,6 +239,9 @@
|
|||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
mainGroup = E180B5DC2992CD9100425DB0;
|
mainGroup = E180B5DC2992CD9100425DB0;
|
||||||
|
packageReferences = (
|
||||||
|
E1E8B07529949E2700BABE4B /* XCRemoteSwiftPackageReference "Starscream" */,
|
||||||
|
);
|
||||||
productRefGroup = E180B5E62992CD9100425DB0 /* Products */;
|
productRefGroup = E180B5E62992CD9100425DB0 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@ -601,6 +609,25 @@
|
|||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
|
E1E8B07529949E2700BABE4B /* XCRemoteSwiftPackageReference "Starscream" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/daltoniam/Starscream.git";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 4.0.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
E1E8B07629949E2700BABE4B /* Starscream */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = E1E8B07529949E2700BABE4B /* XCRemoteSwiftPackageReference "Starscream" */;
|
||||||
|
productName = Starscream;
|
||||||
|
};
|
||||||
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
|
||||||
/* Begin XCVersionGroup section */
|
/* Begin XCVersionGroup section */
|
||||||
E180B5F32992CD9200425DB0 /* KlipperMon.xcdatamodeld */ = {
|
E180B5F32992CD9200425DB0 /* KlipperMon.xcdatamodeld */ = {
|
||||||
isa = XCVersionGroup;
|
isa = XCVersionGroup;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>NSBonjourServices</key>
|
<key>NSBonjourServices</key>
|
||||||
<array>
|
<array>
|
||||||
|
<string>_moonraker._tcp.</string>
|
||||||
<string>_http._tcp.</string>
|
<string>_http._tcp.</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import AppKit
|
import AppKit
|
||||||
import Network
|
|
||||||
|
|
||||||
struct KlipperMenuBarButtonStyle: ButtonStyle {
|
struct KlipperMenuBarButtonStyle: ButtonStyle {
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
@ -36,9 +35,9 @@ struct KlipperMonMenuBarExtraView: View {
|
|||||||
@State var hotendHotTemp: Bool = false
|
@State var hotendHotTemp: Bool = false
|
||||||
@State var bedHotTemp: Bool = false
|
@State var bedHotTemp: Bool = false
|
||||||
|
|
||||||
@State var nwBrowserDiscoveredItems: [NWEndpoint] = []
|
//@State var nwBrowserDiscoveredItems: [NWEndpoint] = []
|
||||||
|
|
||||||
var nwBrowser = NWBrowser(for: .bonjour(type: "_moonraker._tcp.", domain: "local."), using: .tcp)
|
//var nwBrowser = NWBrowser(for: .bonjour(type: "_moonraker._tcp.", domain: "local."), using: .tcp)
|
||||||
|
|
||||||
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||||
|
|
||||||
@ -120,7 +119,7 @@ struct KlipperMonMenuBarExtraView: View {
|
|||||||
.frame(minWidth: 220, maxWidth: 250)
|
.frame(minWidth: 220, maxWidth: 250)
|
||||||
.onReceive(timer) { input in
|
.onReceive(timer) { input in
|
||||||
Task {
|
Task {
|
||||||
await printerManager.queryPrinterStats()
|
//await printerManager.queryPrinterStats()
|
||||||
|
|
||||||
if let query = printerManager.printerObjectsQuery {
|
if let query = printerManager.printerObjectsQuery {
|
||||||
hotendHotTemp = (query.result.status.extruder.temperature > DANGERTEMP) ? true : false
|
hotendHotTemp = (query.result.status.extruder.temperature > DANGERTEMP) ? true : false
|
||||||
@ -132,45 +131,11 @@ struct KlipperMonMenuBarExtraView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Testing bonjour stuff
|
// Testing bonjour stuff
|
||||||
.onAppear {
|
ForEach(printerManager.nwBrowserDiscoveredItems, id: \.hashValue) { endpoint in
|
||||||
nwBrowser.browseResultsChangedHandler = { (newResults, changes) in
|
|
||||||
print("[update] Results changed.")
|
|
||||||
newResults.forEach { result in
|
|
||||||
print(result)
|
|
||||||
self.nwBrowserDiscoveredItems.append(result.endpoint)
|
|
||||||
}
|
|
||||||
//self.nwBrowserDiscoveredItems.append(newResults.description)
|
|
||||||
}
|
|
||||||
nwBrowser.stateUpdateHandler = { newState in
|
|
||||||
switch newState {
|
|
||||||
case .failed(let error):
|
|
||||||
print("[error] nwbrowser: \(error)")
|
|
||||||
case .ready:
|
|
||||||
print("[ready] nwbrowser")
|
|
||||||
case .setup:
|
|
||||||
print("[setup] nwbrowser")
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nwBrowser.start(queue: DispatchQueue.main)
|
|
||||||
}
|
|
||||||
ForEach(nwBrowserDiscoveredItems, id: \.hashValue) { item in
|
|
||||||
Button {
|
Button {
|
||||||
let connection = NWConnection(to: item, using: .tcp)
|
printerManager.resolveBonjourHost(endpoint)
|
||||||
connection.stateUpdateHandler = { newState in
|
|
||||||
switch newState {
|
|
||||||
case .failed(let error):
|
|
||||||
print("[error] nwconnection: \(error)")
|
|
||||||
case .ready:
|
|
||||||
print("[ready] nwconnection")
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connection.start(queue: DispatchQueue.main)
|
|
||||||
} label: {
|
} label: {
|
||||||
Text(item.debugDescription)
|
Text(endpoint.debugDescription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,21 +7,148 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Network
|
import Network
|
||||||
|
import Starscream
|
||||||
|
|
||||||
@MainActor
|
struct JsonRpcRequest: Encodable {
|
||||||
class PrinterRequestManager: ObservableObject {
|
let jsonrpc = "2.0"
|
||||||
|
let method: String
|
||||||
|
let params: [String: String]
|
||||||
|
//let id = UUID()
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrinterRequestManager: ObservableObject, WebSocketDelegate {
|
||||||
|
func didReceive(event: Starscream.WebSocketEvent, client: Starscream.WebSocket) {
|
||||||
|
switch event {
|
||||||
|
case .connected(let headers):
|
||||||
|
//isConnected = true
|
||||||
|
print("websocket is connected: \(headers)")
|
||||||
|
case .disconnected(let reason, let code):
|
||||||
|
//isConnected = false
|
||||||
|
print("websocket is disconnected: \(reason) with code: \(code)")
|
||||||
|
case .text(let string):
|
||||||
|
print("Received text: \(string)")
|
||||||
|
case .binary(let data):
|
||||||
|
print("Received data: \(data.count)")
|
||||||
|
case .ping(_):
|
||||||
|
break
|
||||||
|
case .pong(_):
|
||||||
|
break
|
||||||
|
case .viabilityChanged(_):
|
||||||
|
break
|
||||||
|
case .reconnectSuggested(_):
|
||||||
|
break
|
||||||
|
case .cancelled:
|
||||||
|
//isConnected = false
|
||||||
|
break
|
||||||
|
case .error(let error):
|
||||||
|
print("[error] Starscream: \(error.debugDescription)")
|
||||||
|
//isConnected = false
|
||||||
|
//handleError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// REST query results
|
||||||
@Published var printerObjectsQuery: PrinterObjectsQuery?
|
@Published var printerObjectsQuery: PrinterObjectsQuery?
|
||||||
|
|
||||||
|
// Websocket RPC-JSON endpoints discovered via bonjour
|
||||||
|
@Published var nwBrowserDiscoveredItems: [NWEndpoint] = []
|
||||||
|
|
||||||
@Published var printerCommsOkay = false
|
@Published var printerCommsOkay = false
|
||||||
|
|
||||||
|
var socket: WebSocket!
|
||||||
|
|
||||||
|
private var socketHost, socketPort: String?
|
||||||
|
|
||||||
|
//var nwBrowser: NWBrowser!
|
||||||
|
let nwBrowser = NWBrowser(for: .bonjour(type: "_moonraker._tcp", domain: "local."), using: .tcp)
|
||||||
|
var connection: NWConnection!
|
||||||
|
|
||||||
static let shared = PrinterRequestManager()
|
static let shared = PrinterRequestManager()
|
||||||
|
|
||||||
//let nwBrowser = NWBrowser(for: .bonjour(type: "_moonraker._tcp", domain: "local."), using: .tcp)
|
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
|
// MARK: Starscream shit
|
||||||
|
//
|
||||||
|
//
|
||||||
|
print("init PRM..")
|
||||||
|
// var request = URLRequest(url: URL(string: "http://10.0.21.39:7125/websocket")!)
|
||||||
|
// request.timeoutInterval = 5
|
||||||
|
// socket = WebSocket(request: request)
|
||||||
|
// socket.delegate = self
|
||||||
|
//socket.connect()
|
||||||
|
|
||||||
|
//let data = try! JSONEncoder().encode(JsonRpcRequest(method: "printer.objects.list", params: [:]))
|
||||||
|
//socket.write(data: data)
|
||||||
|
|
||||||
|
// MARK: NWBrowser shit
|
||||||
|
//
|
||||||
|
//
|
||||||
|
nwBrowser.browseResultsChangedHandler = { (newResults, changes) in
|
||||||
|
print("[update] Results changed.")
|
||||||
|
newResults.forEach { result in
|
||||||
|
print(result)
|
||||||
|
self.nwBrowserDiscoveredItems.append(result.endpoint)
|
||||||
|
}
|
||||||
|
//self.nwBrowserDiscoveredItems.append(newResults.description)
|
||||||
|
}
|
||||||
|
nwBrowser.stateUpdateHandler = { newState in
|
||||||
|
switch newState {
|
||||||
|
case .failed(let error):
|
||||||
|
print("[error] nwbrowser: \(error)")
|
||||||
|
case .ready:
|
||||||
|
print("[ready] nwbrowser")
|
||||||
|
if let innerEndpoint = self.connection?.currentPath?.remoteEndpoint, case .hostPort(let host, let port) = innerEndpoint {
|
||||||
|
print("Connected to:", "\(host):\(port)")
|
||||||
|
}
|
||||||
|
case .setup:
|
||||||
|
print("[setup] nwbrowser")
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nwBrowser.start(queue: DispatchQueue.main)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func openWebsocket() {
|
||||||
|
if let host = socketHost, let port = socketPort {
|
||||||
|
//let fullUrlString = "http://\(socketHost):\(socketPort)/websocket"
|
||||||
|
var request = URLRequest(url: URL(string: "http://\(host):\(port)/websocket")!)
|
||||||
|
request.timeoutInterval = 5
|
||||||
|
socket = WebSocket(request: request)
|
||||||
|
socket.delegate = self
|
||||||
|
socket.connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveBonjourHost(_ endpoint: NWEndpoint) {
|
||||||
|
connection = NWConnection(to: endpoint, using: .tcp)
|
||||||
|
connection.stateUpdateHandler = { [self] state in
|
||||||
|
switch state {
|
||||||
|
case .ready:
|
||||||
|
if let innerEndpoint = connection.currentPath?.remoteEndpoint, case .hostPort(let host, let port) = innerEndpoint {
|
||||||
|
print("Connected to \(host):\(port)")
|
||||||
|
let hostString = "\(host)"
|
||||||
|
let regex = try! Regex("%en0")
|
||||||
|
let match = hostString.firstMatch(of: regex)
|
||||||
|
let sanitizedHost = hostString.replacingOccurrences(of: match!.0, with: "")
|
||||||
|
|
||||||
|
print("[sanitized] Resolved \(sanitizedHost):\(port)")
|
||||||
|
socketHost = sanitizedHost
|
||||||
|
socketPort = "\(port)"
|
||||||
|
connection.cancel()
|
||||||
|
self.openWebsocket()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connection.start(queue: .global())
|
||||||
|
//connection.cancel()
|
||||||
|
|
||||||
|
//self.openWebsocket()
|
||||||
|
}
|
||||||
|
// NWConnection shit
|
||||||
|
//connection = NWConnection(
|
||||||
|
|
||||||
func queryPrinterStats() async {
|
func queryPrinterStats() async {
|
||||||
guard let url = URL(string: "http://10.0.21.39/printer/objects/query?extruder&virtual_sdcard&print_stats&heater_bed") else {
|
guard let url = URL(string: "http://10.0.21.39/printer/objects/query?extruder&virtual_sdcard&print_stats&heater_bed") else {
|
||||||
fatalError("Missing URL")
|
fatalError("Missing URL")
|
||||||
|
Loading…
Reference in New Issue
Block a user