NetworkService.swift 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import Foundation
  2. import Network
  3. import UIKit
  4. class NetworkService: ObservableObject {
  5. @Published var isConnected = false
  6. @Published var connectionStatus = "Disconnected"
  7. private var connection: NWConnection?
  8. private let queue = DispatchQueue(label: "NetworkService")
  9. // Configuration
  10. private let defaultHost = "192.168.1.100" // Change to your Pi's IP
  11. private let defaultPort: UInt16 = 8080
  12. func connect(host: String = "", port: UInt16 = 0) {
  13. let targetHost = host.isEmpty ? defaultHost : host
  14. let targetPort = port == 0 ? defaultPort : port
  15. connection = NWConnection(
  16. host: NWEndpoint.Host(targetHost),
  17. port: NWEndpoint.Port(integerLiteral: targetPort),
  18. using: .tcp
  19. )
  20. connection?.stateUpdateHandler = { [weak self] state in
  21. DispatchQueue.main.async {
  22. switch state {
  23. case .ready:
  24. self?.isConnected = true
  25. self?.connectionStatus = "Connected to \(targetHost):\(targetPort)"
  26. case .failed(let error):
  27. self?.isConnected = false
  28. self?.connectionStatus = "Failed: \(error.localizedDescription)"
  29. case .cancelled:
  30. self?.isConnected = false
  31. self?.connectionStatus = "Connection cancelled"
  32. default:
  33. self?.isConnected = false
  34. self?.connectionStatus = "Connecting..."
  35. }
  36. }
  37. }
  38. connection?.start(queue: queue)
  39. }
  40. func disconnect() {
  41. connection?.cancel()
  42. connection = nil
  43. isConnected = false
  44. connectionStatus = "Disconnected"
  45. }
  46. func sendCommand(_ command: LauncherCommand) {
  47. guard let connection = connection, isConnected else {
  48. print("No connection available")
  49. return
  50. }
  51. do {
  52. let data = try JSONEncoder().encode(command)
  53. connection.send(content: data, completion: .contentProcessed { error in
  54. if let error = error {
  55. print("Send error: \(error)")
  56. }
  57. })
  58. } catch {
  59. print("Encoding error: \(error)")
  60. }
  61. }
  62. func requestPhoto(completion: @escaping (UIImage?) -> Void) {
  63. guard let connection = connection, isConnected else {
  64. completion(nil)
  65. return
  66. }
  67. connection.receive(minimumIncompleteLength: 1, maximumLength: 1024*1024) { data, _, isComplete, error in
  68. if let data = data, !data.isEmpty {
  69. let image = UIImage(data: data)
  70. completion(image)
  71. } else {
  72. completion(nil)
  73. }
  74. }
  75. }
  76. func startVideoStream(onFrame: @escaping (UIImage?) -> Void) {
  77. guard let connection = connection, isConnected else {
  78. return
  79. }
  80. let command = LauncherCommand.startVideoStream()
  81. sendCommand(command)
  82. // Continuously receive video frames
  83. func receiveFrame() {
  84. connection.receive(minimumIncompleteLength: 1, maximumLength: 1024*1024) { data, _, isComplete, error in
  85. if let data = data, !data.isEmpty {
  86. let image = UIImage(data: data)
  87. DispatchQueue.main.async {
  88. onFrame(image)
  89. }
  90. // Continue receiving frames
  91. receiveFrame()
  92. }
  93. }
  94. }
  95. receiveFrame()
  96. }
  97. func stopVideoStream() {
  98. let command = LauncherCommand.stopVideoStream()
  99. sendCommand(command)
  100. }
  101. }
  102. struct LauncherCommand: Codable {
  103. let action: String
  104. let angle: Double?
  105. let mode: String?
  106. let timestamp: Date
  107. init(action: String, angle: Double? = nil, mode: String? = nil) {
  108. self.action = action
  109. self.angle = angle
  110. self.mode = mode
  111. self.timestamp = Date()
  112. }
  113. }
  114. // Predefined commands
  115. extension LauncherCommand {
  116. static func aimLeft() -> LauncherCommand {
  117. LauncherCommand(action: "aim_left")
  118. }
  119. static func aimRight() -> LauncherCommand {
  120. LauncherCommand(action: "aim_right")
  121. }
  122. static func fire(angle: Double) -> LauncherCommand {
  123. LauncherCommand(action: "fire", angle: angle)
  124. }
  125. static func home() -> LauncherCommand {
  126. LauncherCommand(action: "home")
  127. }
  128. static func setMode(_ mode: String) -> LauncherCommand {
  129. LauncherCommand(action: "set_mode", mode: mode)
  130. }
  131. static func capturePhoto() -> LauncherCommand {
  132. LauncherCommand(action: "capture_photo")
  133. }
  134. static func startVideoStream() -> LauncherCommand {
  135. LauncherCommand(action: "start_video_stream")
  136. }
  137. static func stopVideoStream() -> LauncherCommand {
  138. LauncherCommand(action: "stop_video_stream")
  139. }
  140. static func stop() -> LauncherCommand {
  141. LauncherCommand(action: "stop")
  142. }
  143. static func status() -> LauncherCommand {
  144. LauncherCommand(action: "status")
  145. }
  146. }