-
-
Save michael94ellis/92828bba252ccabd071279be098e26e6 to your computer and use it in GitHub Desktop.
// | |
// UDPListener.swift | |
// | |
// Created by Michael Robert Ellis on 12/16/21. | |
// | |
import Foundation | |
import Network | |
import Combine | |
class UDPListener: ObservableObject { | |
var listener: NWListener? | |
var connection: NWConnection? | |
var queue = DispatchQueue.global(qos: .userInitiated) | |
/// New data will be place in this variable to be received by observers | |
@Published private(set) public var messageReceived: Data? | |
/// When there is an active listening NWConnection this will be `true` | |
@Published private(set) public var isReady: Bool = false | |
/// Default value `true`, this will become false if the UDPListener ceases listening for any reason | |
@Published public var listening: Bool = true | |
/// A convenience init using Int instead of NWEndpoint.Port | |
convenience init(on port: Int) { | |
self.init(on: NWEndpoint.Port(integerLiteral: NWEndpoint.Port.IntegerLiteralType(port))) | |
} | |
/// Use this init or the one that takes an Int to start the listener | |
init(on port: NWEndpoint.Port) { | |
let params = NWParameters.udp | |
params.allowFastOpen = true | |
self.listener = try? NWListener(using: params, on: port) | |
self.listener?.stateUpdateHandler = { update in | |
switch update { | |
case .ready: | |
self.isReady = true | |
print("Listener connected to port \(port)") | |
case .failed, .cancelled: | |
// Announce we are no longer able to listen | |
self.listening = false | |
self.isReady = false | |
print("Listener disconnected from port \(port)") | |
default: | |
print("Listener connecting to port \(port)...") | |
} | |
} | |
self.listener?.newConnectionHandler = { connection in | |
print("Listener receiving new message") | |
self.createConnection(connection: connection) | |
} | |
self.listener?.start(queue: self.queue) | |
} | |
func createConnection(connection: NWConnection) { | |
self.connection = connection | |
self.connection?.stateUpdateHandler = { (newState) in | |
switch (newState) { | |
case .ready: | |
print("Listener ready to receive message - \(connection)") | |
self.receive() | |
case .cancelled, .failed: | |
print("Listener failed to receive message - \(connection)") | |
// Cancel the listener, something went wrong | |
self.listener?.cancel() | |
// Announce we are no longer able to listen | |
self.listening = false | |
default: | |
print("Listener waiting to receive message - \(connection)") | |
} | |
} | |
self.connection?.start(queue: .global()) | |
} | |
func receive() { | |
self.connection?.receiveMessage { data, context, isComplete, error in | |
if let unwrappedError = error { | |
print("Error: NWError received in \(#function) - \(unwrappedError)") | |
return | |
} | |
guard isComplete, let data = data else { | |
print("Error: Received nil Data with context - \(String(describing: context))") | |
return | |
} | |
self.messageReceived = data | |
if self.listening { | |
self.receive() | |
} | |
} | |
} | |
func cancel() { | |
self.listening = false | |
self.connection?.cancel() | |
} | |
} |
Hello Michael,
Thanks for your message.
As I am quite new to Swift, I have no idea how to set these up properly.
Here is my current code : https://1drv.ms/f/s!AnbLWtm1n95EhdxreBEZqLzpfq_7ag?e=0BUpNC
My objective would be to use the .onReceive(_:perform:) to get the Udp message data, decode it and update the displayed values.
Currently, the
Could you point out what I am missing to make it work ?
If you have some time, we can try to setup a video call to work on this...
(I won't have access to a Mac from tomorrow so it may be tricky to debug on my side...)
Many thanks in advance!
Thanks for your answer and advice.
Please find below the link to the repository I created in case you have some time :
https://github.com/Arttrm/IOS_RC_Telemetry_App
I will for sure continue digging to try to figure out what I am missing.
Many thanks again!
Hello,
Thanks for your message.
My aim is to listen to some UDP message sent from an ESP device (currently simulated with a emulated packet sender)
As you mention, the UDP listener starts right when the swift program starts.
As indicated in one of the comments on Medium, I moved the self.receive() command within the createConnection() function.
As shown on the screenshot below, the messaged are properly received within the class.
However I am not sure that the UdpListener.UdpMsgString gets updated outside the createConnection() function. (I might not have defined the variable properly which might cause the issue...)
Indeed the .onReceive() command in the content view does not detect any reception.
Moreover, when I create a button to display the UdpListener.UdpMsgString within the content view, it always displays an empty string...
Would you have any idea of the origin of the issue ?
I tried to have a look at you iTello project but I have not found when you are using a .onReceive structure or anything similar.
Could you point out where in your code you are doing such things?
Many thanks again.
Hi Michael,
I finally succeeded to make the UdpListener class to transfer data to the ContentView using the notification center. (I am not sure that this is the proper way to handle this but it seems to work...)
Many thanks again for your help through this project !
Hi Micheal.
As many others, i'm a newbe, but hope you can shoot me in the right direction.
I need to build a app with UDP connections to 3 or more hosts.
I have ended up with your class.
my problem is that i need to detect which host, there is returning packets.
I made 3 connections with
@State var connection1: NWConnection?
@State var connection2: NWConnection?
@State var connection3: NWConnection?
and
connection1 = NWConnection(host: host1, port: port, using: .udp)
connection1!.stateUpdateHandler = { (newState) in
switch (newState) {
case .preparing:
NSLog("Entered state: preparing")
case .ready:
NSLog("Entered state: ready")
case .setup:
NSLog("Entered state: setup")
case .cancelled:
NSLog("Entered state: cancelled")
case .waiting:
NSLog("Entered state: waiting")
case .failed:
NSLog("Entered state: failed")
default:
NSLog("Entered an unknown state")
}
}
connection1!.viabilityUpdateHandler = { (isViable) in
if (isViable) {
NSLog("Connection is viable")
connected = true
} else {
NSLog("Connection is not viable")
}
}
i send data to host with..........
func send1(_ payload: String) {
let myPayload = Data(payload.utf8)
connection1!.send(content: myPayload, completion: .contentProcessed({ sendError in
if let error = sendError {
NSLog("Unable to process and send the data: (error)")
} else {
NSLog("Data has been sent to 1")
print(payload)
connection1!.receiveMessage { (data, context, isComplete, error) in
guard let myData = data else {
print("Der er modtage fejl")
return }
NSLog("--------Received message on 1: " + String(decoding: myData, as: UTF8.self))
}
}
}))
}
The send function, is sending, but the connection1!.recieveMessage does not work, i'm not able to get a listneer work, for each connection, but the reciever in your class is working fine, but i can not detect which host the recieeMessage came from.
I'm think about using 3 classes class UDPListener: , and just rename them.
Sorry for the long message, but i hope it make sense.
All the best
Finn
Can you please correct the formatting? You may not be combining the calls correctly
Thank you Michael, the UDP listener code worked great for a project I am considering that receives data from a piece of aviation hardware. Saved me a bunch of time sort all of this out on my own.
Hello Michael,
I am trying to use your UDP Listener class in a project.
In this post : https://medium.com/@michaelrobertellis/how-to-make-a-swift-ios-udp-listener-using-apples-network-framework-f7cef6f4e45f, you mention that the .onReceive(_:perform:) command could be used to transfer the received data to the View.
Could you please explain me how to use this command to live stream the received data in a Text area on the app?
Many thanks in advance!