123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- import Foundation
- import Network
- import UIKit
- class NetworkService: ObservableObject {
- @Published var isConnected = false
- @Published var connectionStatus = "Disconnected"
- private var connection: NWConnection?
- private let queue = DispatchQueue(label: "NetworkService")
- // Configuration
- private let defaultHost = "192.168.1.100" // Change to your Pi's IP
- private let defaultPort: UInt16 = 8080
- func connect(host: String = "", port: UInt16 = 0) {
- let targetHost = host.isEmpty ? defaultHost : host
- let targetPort = port == 0 ? defaultPort : port
- connection = NWConnection(
- host: NWEndpoint.Host(targetHost),
- port: NWEndpoint.Port(integerLiteral: targetPort),
- using: .tcp
- )
- connection?.stateUpdateHandler = { [weak self] state in
- DispatchQueue.main.async {
- switch state {
- case .ready:
- self?.isConnected = true
- self?.connectionStatus = "Connected to \(targetHost):\(targetPort)"
- case .failed(let error):
- self?.isConnected = false
- self?.connectionStatus = "Failed: \(error.localizedDescription)"
- case .cancelled:
- self?.isConnected = false
- self?.connectionStatus = "Connection cancelled"
- default:
- self?.isConnected = false
- self?.connectionStatus = "Connecting..."
- }
- }
- }
- connection?.start(queue: queue)
- }
- func disconnect() {
- connection?.cancel()
- connection = nil
- isConnected = false
- connectionStatus = "Disconnected"
- }
- func sendCommand(_ command: LauncherCommand) {
- guard let connection = connection, isConnected else {
- print("No connection available")
- return
- }
- do {
- let data = try JSONEncoder().encode(command)
- connection.send(content: data, completion: .contentProcessed { error in
- if let error = error {
- print("Send error: \(error)")
- }
- })
- } catch {
- print("Encoding error: \(error)")
- }
- }
- func requestPhoto(completion: @escaping (UIImage?) -> Void) {
- guard let connection = connection, isConnected else {
- DispatchQueue.main.async {
- completion(nil)
- }
- return
- }
- // Add timeout for photo requests
- let timeoutTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false) { _ in
- DispatchQueue.main.async {
- completion(nil)
- }
- }
- connection.receive(minimumIncompleteLength: 1, maximumLength: 1024*1024) { data, _, isComplete, error in
- timeoutTimer.invalidate()
- if let error = error {
- print("Photo receive error: \(error)")
- DispatchQueue.main.async {
- completion(nil)
- }
- return
- }
- if let data = data, !data.isEmpty {
- let image = UIImage(data: data)
- DispatchQueue.main.async {
- completion(image)
- }
- } else {
- DispatchQueue.main.async {
- completion(nil)
- }
- }
- }
- }
- private var isStreamingActive = false
- func startVideoStream(onFrame: @escaping (UIImage?) -> Void) {
- guard let connection = connection, isConnected else {
- return
- }
- let command = LauncherCommand.startVideoStream()
- sendCommand(command)
- isStreamingActive = true
- // Continuously receive video frames
- func receiveFrame() {
- guard isStreamingActive else { return }
- connection.receive(minimumIncompleteLength: 1, maximumLength: 1024*1024) { data, _, isComplete, error in
- guard self.isStreamingActive else { return }
- if let error = error {
- print("Video stream error: \(error)")
- DispatchQueue.main.async {
- onFrame(nil)
- }
- return
- }
- if let data = data, !data.isEmpty {
- let image = UIImage(data: data)
- DispatchQueue.main.async {
- onFrame(image)
- }
- // Continue receiving frames only if still streaming
- if self.isStreamingActive {
- receiveFrame()
- }
- }
- }
- }
- receiveFrame()
- }
- func stopVideoStream() {
- isStreamingActive = false
- let command = LauncherCommand.stopVideoStream()
- sendCommand(command)
- }
- }
- struct LauncherCommand: Codable {
- let action: String
- let angle: Double?
- let mode: String?
- let timestamp: Date
- init(action: String, angle: Double? = nil, mode: String? = nil) {
- self.action = action
- self.angle = angle
- self.mode = mode
- self.timestamp = Date()
- }
- }
- // Predefined commands
- extension LauncherCommand {
- static func aimLeft() -> LauncherCommand {
- LauncherCommand(action: "aim_left")
- }
- static func aimRight() -> LauncherCommand {
- LauncherCommand(action: "aim_right")
- }
- static func fire(angle: Double) -> LauncherCommand {
- LauncherCommand(action: "fire", angle: angle)
- }
- static func home() -> LauncherCommand {
- LauncherCommand(action: "home")
- }
- static func setMode(_ mode: String) -> LauncherCommand {
- LauncherCommand(action: "set_mode", mode: mode)
- }
- static func capturePhoto() -> LauncherCommand {
- LauncherCommand(action: "capture_photo")
- }
- static func startVideoStream() -> LauncherCommand {
- LauncherCommand(action: "start_video_stream")
- }
- static func stopVideoStream() -> LauncherCommand {
- LauncherCommand(action: "stop_video_stream")
- }
- static func stop() -> LauncherCommand {
- LauncherCommand(action: "stop")
- }
- static func status() -> LauncherCommand {
- LauncherCommand(action: "status")
- }
- }
|