NetworkService.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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. DispatchQueue.main.async {
  65. completion(nil)
  66. }
  67. return
  68. }
  69. // Add timeout for photo requests
  70. let timeoutTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false) { _ in
  71. DispatchQueue.main.async {
  72. completion(nil)
  73. }
  74. }
  75. connection.receive(minimumIncompleteLength: 1, maximumLength: 1024*1024) { data, _, isComplete, error in
  76. timeoutTimer.invalidate()
  77. if let error = error {
  78. print("Photo receive error: \(error)")
  79. DispatchQueue.main.async {
  80. completion(nil)
  81. }
  82. return
  83. }
  84. if let data = data, !data.isEmpty {
  85. let image = UIImage(data: data)
  86. DispatchQueue.main.async {
  87. completion(image)
  88. }
  89. } else {
  90. DispatchQueue.main.async {
  91. completion(nil)
  92. }
  93. }
  94. }
  95. }
  96. private var isStreamingActive = false
  97. func startVideoStream(onFrame: @escaping (UIImage?) -> Void) {
  98. guard let connection = connection, isConnected else {
  99. return
  100. }
  101. let command = LauncherCommand.startVideoStream()
  102. sendCommand(command)
  103. isStreamingActive = true
  104. // Continuously receive video frames
  105. func receiveFrame() {
  106. guard isStreamingActive else { return }
  107. connection.receive(minimumIncompleteLength: 1, maximumLength: 1024*1024) { data, _, isComplete, error in
  108. guard self.isStreamingActive else { return }
  109. if let error = error {
  110. print("Video stream error: \(error)")
  111. DispatchQueue.main.async {
  112. onFrame(nil)
  113. }
  114. return
  115. }
  116. if let data = data, !data.isEmpty {
  117. let image = UIImage(data: data)
  118. DispatchQueue.main.async {
  119. onFrame(image)
  120. }
  121. // Continue receiving frames only if still streaming
  122. if self.isStreamingActive {
  123. receiveFrame()
  124. }
  125. }
  126. }
  127. }
  128. receiveFrame()
  129. }
  130. func stopVideoStream() {
  131. isStreamingActive = false
  132. let command = LauncherCommand.stopVideoStream()
  133. sendCommand(command)
  134. }
  135. }
  136. struct LauncherCommand: Codable {
  137. let action: String
  138. let angle: Double?
  139. let mode: String?
  140. let timestamp: Date
  141. init(action: String, angle: Double? = nil, mode: String? = nil) {
  142. self.action = action
  143. self.angle = angle
  144. self.mode = mode
  145. self.timestamp = Date()
  146. }
  147. }
  148. // Predefined commands
  149. extension LauncherCommand {
  150. static func aimLeft() -> LauncherCommand {
  151. LauncherCommand(action: "aim_left")
  152. }
  153. static func aimRight() -> LauncherCommand {
  154. LauncherCommand(action: "aim_right")
  155. }
  156. static func fire(angle: Double) -> LauncherCommand {
  157. LauncherCommand(action: "fire", angle: angle)
  158. }
  159. static func home() -> LauncherCommand {
  160. LauncherCommand(action: "home")
  161. }
  162. static func setMode(_ mode: String) -> LauncherCommand {
  163. LauncherCommand(action: "set_mode", mode: mode)
  164. }
  165. static func capturePhoto() -> LauncherCommand {
  166. LauncherCommand(action: "capture_photo")
  167. }
  168. static func startVideoStream() -> LauncherCommand {
  169. LauncherCommand(action: "start_video_stream")
  170. }
  171. static func stopVideoStream() -> LauncherCommand {
  172. LauncherCommand(action: "stop_video_stream")
  173. }
  174. static func stop() -> LauncherCommand {
  175. LauncherCommand(action: "stop")
  176. }
  177. static func status() -> LauncherCommand {
  178. LauncherCommand(action: "status")
  179. }
  180. }