WiFi Packets Delayed on Real iPhone but not Simulator - ios

I'm working on an app that connects to a device on the same local WiFi network. The device takes measurements and streams the data to the iPhone. The iPhone plots it in real time.
The problem I've run into is the iPhone delays the packets by up to 200ms every half second or so. This causes noticeable stutter in the UI that I'd like to eliminate.
I started debugging the issue by placing signposts in the code when packet is received.
Looking at the profiler, you can easily see the gaps in data.
Zooming in on the space after a gap, you can see a burst of packets received.
I've checked and it isn't dropping any packets. It is simply delaying them from my app.
The weird thing is this isn't an issue on the simulator or with the Android version of the app so I know it isn't an issue with the device or the WiFi network.
Here is the same code running on the simulator showing a much more even distribution of packets.
Has anyone experienced anything like this? Is this just some kind of battery saving limitation of the iPhone hardware? Is there anyway to ensure a more timely delivery of the data to my application?
I tried rewriting the connection using SwiftNIO and ended up with the same results. I've also tried changing the serviceClass parameter of the connection to all the possibilities with no change.
Here is the relevant connection code.
private func udpReceive() {
if udpConnection?.state == .ready {
udpConnection?.receive(minimumIncompleteLength: 1, maximumLength: Int(Hangboard.shared.BufferSize), completion: { content, contentContext, isComplete, error in
os_signpost(
.begin,
log: log,
name: "udpReceive",
signpostID: signpostID
)
Task {
if let content = content {
let _ = await asyncResult(for: Hangboard.shared.udpDataReceivedNative(data: content.toKotlinByteArray(), offset: 0, length: Int32(content.count)))
}
os_signpost(
.end,
log: log,
name: "udpReceive",
signpostID: signpostID
)
self.udpReceive()
}
})
} else {
disconnect(hadError: true)
}
}
private func startUdpListener(port: NWEndpoint.Port) {
let params = NWParameters.udp
params.allowFastOpen = true
params.serviceClass = .responsiveData
let listener = try? NWListener(using: params, on: port)
self.udpListener = listener
listener?.newConnectionLimit = 1
listener?.newConnectionHandler = { connection in
connection.parameters.serviceClass = .responsiveData
self.startUdpConnection(connection: connection)
}
listener?.start(queue: .global(qos: .userInteractive))
}
private func startUdpConnection(connection: NWConnection) {
self.udpConnection = connection
connection.stateUpdateHandler = { state in
switch state {
case .ready:
self.udpReceive()
case .failed(let error):
print("Connection error! \(error.localizedDescription)")
self.disconnect(hadError: true)
default:
break
}
}
connection.start(queue: .global(qos: .userInteractive))
}

Turns out the reason for this was because I was still running a Bonjour search in the background.
Disabling the search when connecting and restarting it on disconnect removed the latency issues.
Apple's tech support mentioned this can be a problem when includePeerToPeer is enabled on a connection.

Related

Unable to send files between 2 device using Network Framework

I'm having trouble using network framework to transfer a collection of 1 minute video files between a recording device and a repository master device. All devices are on the same wifi. But the problem is that the distance between devices somehow is player a role in the transfer. When I have strong wifi signal on both devices, but if the devices are further than 100 feet apart, the transfer won't happen. Conversely, if both devices have very weak wifi signal (e.g. 1 bar), but are next to each other, the transfer happens without a hitch. It's as if the devices are doing something more akin to wifi direct than transfer for a LAN, which is what I really want. I'm assuming someone familiar with the framework can advise how to entirely avoid using any peer to peer wifi direct type of transfer, and force the system to transfer over the WLAN. That way all I need to do is make sure all devices have a decent wifi signal (e.g. are on the network with a decent connection), and regardless of how far they are physically from each other, the files will transfer without a hitch. Hope someone can clear this up for me.
Thanks.
Sending files between 2 devices at close range
disabled Bluetooth
Made sure both devices are on the same local network.
#Paulw11 - Thank you for the response, I have provided more details that I should have provided initially, I do apologize for that. Thanks again for the help.
We are using the mDNS approach.
func startBrowsing() {
guard browser == nil else { return }
let params = NWParameters()
params.includePeerToPeer = true
params.requiredInterfaceType = .wifi
let browser = NWBrowser(for: .bonjour(type: bonjourService, domain: nil), using: params)
self.browser = browser
browser.browseResultsChangedHandler = {
[weak self] results, changes in
guard let self = self else { return }
self.delegate?.peerBrowser(self, didUpdateResults: Array(results))
}
browser.stateUpdateHandler = { [weak self] newState in
guard let self = self else { return }
os_log("[browser] %#", newState.debugDescription)
switch newState {
case .cancelled:
break
case .failed:
os_log("[browser] restarting")
self.browser?.cancel()
self.startBrowsing()
case .ready:
break
case .setup:
break
#unknown default:
break
}
}
browser.start(queue: .main)
}
There are no such errors. I can see the standard communication is still working and because of that the sender(iPhone) will continue to send the files to iPad (receiver) with out fail but the receiver couldn’t able to receive it.
3.No
The code used to send data from one peer to another.
func sendMessage(type: UInt32, content: Data) {
guard connection?.state == .ready else {
return
}
let framerMessage = NWProtocolFramer.Message(messageType: type)
let context = NWConnection.ContentContext(identifier: "Message", metadata: [framerMessage])
connection?.send(content: content, contentContext: context, isComplete: true, completion: .idempotent)
}

swift firebase onDisconnectRemoveValue not firing when turning off wifi

I want to be able to remove a connection value from my app's real-time firebase database when they lose connection unexpectedly. This does not seem to be possible from what I have tried already.
I have tried using the "goOffline" function to properly close the sockets because from what I've heard, it doesn't close properly when you turn off your wifi.
func connect() {
let connectionsRef = self.rootRef.child("connections")
AF.request("https://projectname.cloudfunctions.net/Connect").response { response in
if response.data != nil {
if self.visiblename != nil {
connectionsRef.observeSingleEvent(of: .value, with: { snapshot in
for value in JSON(snapshot.value!).arrayValue {
if value["Address"].string! == self.visiblename {
let connectionRef = connectionsRef.child(String(value["Index"].int!))
connectionRef.keepSynced(true)
connectionRef.onDisconnectRemoveValue()
}
}
})
}
}
}
}
self.reachability.whenUnreachable = { _ in
Database.database().goOffline()
}
self.reachability.whenReachable = { _ in
Database.database().goOnline()
}
do {
try self.reachability.startNotifier()
} catch {}
It does automatically remove the value after around 60 seconds but I need my app to be able to handle any internet interruptions and remove the connection value quickly.
Also, if there is no available way to remove the value from the client when the client turns off their wifi. Is there a way to detect the disconnection from the server on the server itself? I have tried comparing date.getTime() to another date.getTime() variable that when you invoke the Connect request it updates the variable. Then the server watches but it didn't seem to work because for some reason it stopped watching the variable as soon as the client disconnected and doesn't have time to realize it. I assume this is because the server is based on cloud functions and has no reason to run when no clients are invoking it.

Can use DJI SDK to replicate "Pitch Lock" feature of DJI Go app?

I'm trying to replicate the "Pitch Lock" on / off feature of the DJI Go app. How can I do this?
I'm using XCode 8.2.1, building for iOS 10.1, connecting to an Osmo Mobile with an iPhone 6s attached. The Osmo Mobile has the latest firmware (version 01.30.01.52).
Everything works so far (registerApp, connecting via bluetooth, getting handheld button presses, getting gimbal battery updates, getting gimbal updates).
Setting setGimbalWorkMode to either .freeMode or .yawFollowMode doesn't seem to have any effect. No error is returned in the completion block, but there's no effect on Gimbal operation.
The gimbal behaves as if it is in .freeMode (always moves to the exact direction handheld stick is pointing), but DJIGimbalDelegate only receives .yawFollowMode updates (which is what the pitchLock mode should do).
Setting setGimbalWorkMode to other modes results in an error (as expected with Osmo Mobile device).
Here's how I'm trying to toggle pitchLock on/off.
#IBAction func pitchLockPressed(_ sender: UIButton) {
pitchLock = !pitchLock
if let gimbal = fetchGimbal() {
var workMode : DJIGimbalWorkMode = .freeMode // .freeMode .fpvMode and .unknown return error using Osmo Mobile
if pitchLock {
workMode = .yawFollowMode
}
gimbal.setGimbalWorkMode(workMode, withCompletion: { (error) in
if (error != nil) {
print("error workMode: \(error?.localizedDescription)")
self.pitchLock = !(self.pitchLock) // back to previous
}
})
}
}
Here's the delegate, which only reports .yawFollowMode no matter what I do:
func gimbal(_ gimbal: DJIGimbal, didUpdate gimbalState: DJIGimbalState) {
// var needUpdate = false
if lastReportedWorkMode != gimbalState.workMode {
lastReportedWorkMode = gimbalState.workMode
switch lastReportedWorkMode {
case DJIGimbalWorkMode.fpvMode:
print("FPV\n")
case DJIGimbalWorkMode.freeMode:
print("Free\n")
case DJIGimbalWorkMode.yawFollowMode:
print("Yaw-follow\n")
case DJIGimbalWorkMode.unknown:
print("Unknown\n")
}
}
Anyone getting setGimbalWorkMode to actually change gimbal modes?

replaykit startrecording sometimes never enters completion handler

I am using replay kit to save a video of my screen during gameplay but randomly on occasion startRecordingWithMicrophoneEnabled and recorder.stopRecordingWithHandler never enters the completion handler
it doesn't throw an error, it just runs and hangs indefinitely.
if recorder.available && recorder.microphoneEnabled {
recorder.startRecordingWithMicrophoneEnabled(true) { [unowned self] (error) in
if let unwrappedError = error {
print(unwrappedError.localizedDescription)
} else {
print("called")
self.manager.instructions.text = "Click to Start Game"
}
}
}
if recorder.available && recorder.microphoneEnabled {
print("initiating stop recording")
recorder.stopRecordingWithHandler { [unowned self] (RPPreviewViewController, error) in
print("in completion handler")
if let previewView = RPPreviewViewController {
print("will transition to gameplay video")
previewView.previewControllerDelegate = self
self.presentViewController(previewView, animated: true, completion: nil)
self.sessionHandler.session.stopRunning()
}
}
}
I was getting this same thing. Was working on one device, and not on another. Only difference was the working device was on iOS version 10.1.0 and the other was iOS version 10.0.2 - upgraded to 10.2.0 and it started working immediately.
I had the same problem and just found its cause (or maybe just a cause). If your device is connected to a WiFi that has no internet access this problem occurs. If you connect it to a WiFi that has internet access or disable WiFi it works just fine. I guess when starting a recording, ReplayKit tries to connect to some Apple servers but never reaches them and also never times out. You can observe the same behavior with the App Store. When you are connected to a WiFi without internet it tries to load the store forever and never times out.

How to check when ios device just lost connection to internet

how to detect losing connection to network as fast as it can be
now im using reachabitity framework for swift and it takes abouut 3 sec to detect that there is no network so user can make the app crash
here how i check it :
func setupReachability() {
do {
self.reachability = try Reachability.reachabilityForInternetConnection()
} catch {
print("Cannot setup reachability monitoring")
return
}
self.reachability!.whenReachable = { reachability in
self.loginButton(true)
}
self.reachability!.whenUnreachable = { reachability in
self.loginButton(false)
}
do { try self.reachability!.startNotifier() } catch {
print("Cannot start reachability monitoring")
return
}
print("Started reachability")
}
You always, ALWAYS, must handle errors in your connections. You absolutely cannot rely on Reachability. You also cannot rely that your connection goes to the server that you want to connect to. It is possible and practically happens a lot that you get a reply from a totally different server in a totally different format than you expected, and you need to handle that. (Take your app to the nearest Starbucks and check whether it survives).

Resources