Unable to enable precision landing via api call - swift

I'm working with a Phantom 4 Pro drone, which have precision landing capacity. I'm using Swift and a iPad for controlling the drone. In DJI Go software, I can enable it, and it works correctly. However, in the app that I'm working on, any calls to enable it fails.
This is the code that tries to enable it:
static func setPrecisionLandingEnabled(precisionLandingEnabled: Bool, _ completeFunction: #escaping (Error?) -> Void) throws {
guard let djiKeyManager = DJISDKManager.keyManager() else {
GLog.Log("Error in Flight Controller Observer. Problem getting djiKeyManager in \(#file) \(#function)")
throw FlightControllerManager.FlightControllerError.cantGetKeyManager
}
guard let precisionLandingEnabledKey = DJIFlightControllerKey(param: DJIFlightAssistantParamPrecisionLandingEnabled) else {
GLog.Log("Error in Flight Controller Observer. Problem getting precisionLandingEnabledKey in \(#file) \(#function)")
throw FlightControllerManager.FlightControllerError.cantGetKey
}
djiKeyManager.setValue(precisionLandingEnabled, for: precisionLandingEnabledKey, withCompletion: completeFunction)
}
When I call the function, the completion function of DJI SDK returned the following error:
Error Domain=DJISDKErrorDomain Code=-1013 \"Current product does not
support this feature.(code:-1013)\"
The API in question is found here: https://developer.dji.com/api-reference/ios-api/Components/IntelligentFlightAssistant/DJIIntelligentFlightAssistant.html?search=precision&i=0&#djiintelligentflightassistant_setprecisionlandingenabled_inline
I checked, and there's no parameters passed in when issuing the "take-off" call that is related to precision landing. So, why am I getting this error when I know that the drone have this feature (as verified by DJI's own app)? Does the drone have to be flying first before enabling this? Or are there other conditions that must be met before I can enable this?

It looks to me like you should be creating a DJIFlightAssistant object, and then using the existing method setPrecisionLandingEnabled(_: Bool, completion: DJICompletionBlock)
Why are you writing your own setPrecisionLandingEnabled() method?

Related

How to get "Area under aircraft unsuitable for landing" message?

When I initiate auto-landing in the DJI Fly app I sometimes get the following message, especially under bad lighting conditions:
Now, in my own code, when I call DJIFlightController.startLandingWithCompletion, the drone would not land and the completion block gets executed without any error.
My question is, how can I intercept the equivalent to DJIs error message as shown above? What code is relevant for that?
EDIT 1:
I am also checking if a landing confirmation is needed with the following code:
func observeConfirmLanding() {
guard let confirmLandingKey = DJIFlightControllerKey(param: DJIFlightControllerParamConfirmLanding) else { return }
DJISDKManager.keyManager()?.startListeningForChanges(on: confirmLandingKey, withListener: self) { (oldValue: DJIKeyedValue?, newValue: DJIKeyedValue?) in
DispatchQueue.main.async {
if let oldBoolValue = oldValue?.boolValue,
let newBoolValue = newValue?.boolValue,
oldBoolValue != newBoolValue {
self.landingConfirmationNeeded = newBoolValue
self.logger.debug("Landing confirmation is needed")
}
}
}
}
It never enters the closure.
As I understood the landing confirmation might be needed at a height of 0.3m, but in my case, the landing process gets interrupted at different heights that are more than 0.3m, e.g. already at 2m or 1.5m
EDIT 2:
I have changed the surface below the drone in my basement by adding a bright carpet with a distinct pattern. This improves the whole stability of the drone AND even more important: The drone just lands without being interrupted. I do not get the warning message in the DJI Fly app any more.
I check for isLandingConfirmation the way Brien suggests in his comment, I finally get true when testing this in the simulator.
extension FlightControllerObserver: DJIFlightControllerDelegate {
func flightController(_ fc: DJIFlightController, didUpdate state: DJIFlightControllerState) {
if (landingConfirmationNeeded != state.isLandingConfirmationNeeded) {
landingConfirmationNeeded = state.isLandingConfirmationNeeded
}
}
But, when I test this in my basement (flight mode "OPTI") and outside (flight mode "GPS") the drone just lands without waiting for any confirmation.
While I learned a lot, it is still a miracle to me which class in the DJI Mobile SDK is responsible for "throwing" that warning message.
If the completion handler completes without any error you might need to check if isLandingConfirmationNeeded in DJIFlightControllerState is set to true. If thats the case then you will need to implement the function confirmLandingWithCompletion.
Sounds relevant to your experience looking at the documentation
(void)confirmLandingWithCompletion:(DJICompletionBlock)completion Confirms continuation of landing action. When the clearance between
the aircraft and the ground is less than 0.3m, the aircraft will pause
landing and wait for user's confirmation. Can use
isLandingConfirmationNeeded in DJIFlightControllerState to check if
confirmation is needed. It is supported by flight controller firmware
3.2.0.0 and above.
The landingProtectionState property of the DJIVisionControlState class could be a good place to look for the cause of that error message. One of the potential states that sounds relevant is
DJIVisionLandingProtectionStateNotSafeToLand -> Landing area is not flat
enough to be considered safe for landing. The aircraft should be moved
to an area that is more flat and an auto-land should be attempted
again or the user should land the aircraft manually.
Also within a section of DJI's documentation there is a section on an article about flight control that talks about landing protection and forcing a landing. I couldn't see any functions in the SDK to force a landing.
You should be aware of that the fly app does not use the sdk internally. It uses the middlelayer directly.
You often get different behaivor when using the SDK compared to the app. Some functions are not available at all.
I usually disable it completly, I want it to land when I say so :-)
(exit_landing_ground_not_smooth_enable g_config.landing.exit_landing_ground_not_smooth_enable)

Why is DispatchQueue.main.async not working in Swift app?

I'm struggling with asynchronous code in Swift. I have to disable 2 buttons if there is no internet connection. I have 2 DispatchQueue.main.async calls in 2 different functions. 1 of them works, but the other one doesn't disable the button (the good thing is that you can click on the button and nothing happens). I am testing my code on my iPhone because the simulator does not run the SDK I am using properly.
All these functions look like this (this is the one at the end that does not work):
public func disableButton2(_ check: Bool) {
DispatchQueue.main.async{
self.button2.isEnabled = check
}
I have used this and it was working fine
DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { [weak self] in
guard let self = self else {
return
}
try this you will get the result.
A kind of more accurate approach, in order to explain "why asyncAfter works instead of just async" would be that DispatchQueue.main.asyncAfter would capture the thread in order to make the UI change to happend.
But most of the times could be because your are trying to update a view element within another view, say UICollectionView inside a UIViewController. In order to solve this you should call async method inside the method that makes this UI update

listen for device changes using API state did change

Im am using Mbient Lab API to talk to a device. Using the api I discovered there is a stateDidChange Variable that looks like this:
var stateDidChange: (() -> Void)? { get set }
How would I use this method in viewcontroller to detect a change in the device (such as another device has connected to it already) or can someone provide documentation on what this variable does. Link to the API is https://mbientlab.com/documents/metawear/ios/latest/Classes/ScannerModelItem.html
Simply assign a block to stateDidChange and handle your logic inside that block.
let item = ScannerModelItem()
item.stateDidChange = {
// Handle state change
}

Forcing update to placeholder complication on Apple Watch

Here a simple issue I am facing while starting to experiment with WatchKit and complications.
I created a simple app which is showing a complication with a public string “Y” and by clicking it, the Apple Watch app is shown with a simple switch.
I wrote my code in getPlaceholderTemplateForComplication in ComplicationController.swift and added a switch IBAction in InterfaceController.swift.
By changing the value of the switch, the public string cycles between “N” and “Y”. I would like to have it changed in the complication as well. However I am noticing that the complication stays as it is initially at “Y”.
I found a similar question about forcing complication updates, but it was related to a TimeLine complication func and not placeHolder.
func updateComplication() {
let complicationServer = CLKComplicationServer.sharedInstance()
for complication in complicationServer.activeComplications {
complicationServer.reloadTimelineForComplication(complication)
}
}
It is not clear to me on where and how to use this in my case.
As suggested I worked on getCurrentTimelineEntryForComplication .
In order to test a ModularComplication only, I used:
switch complication.family {
case .ModularSmall:
let modularSmallTemplate =
CLKComplicationTemplateModularSmallRingText()
modularSmallTemplate.textProvider =
CLKSimpleTextProvider(text: stringa)
modularSmallTemplate.fillFraction = 0.95
modularSmallTemplate.ringStyle = CLKComplicationRingStyle.Closed
let template = CLKComplicationTimelineEntry(
date: NSDate(), complicationTemplate: modularSmallTemplate)
handler(template)
default:
handler(nil)
}
I have the switch IBAction in InterfaceController.swift.
I am encountering issues in using:
func updateComplication() {
let complicationServer = CLKComplicationServer.sharedInstance()
for complication in complicationServer.activeComplications {
complicationServer.reloadTimelineForComplication(complication)
}
}
Where do I have to write the above mentioned func, in order to be able to call it from inside the IBAction?
If I write it in ComplicationController.swift, by calling it from the IBAction in InterfaceController.swift as:
ComplicationController.updateComplication()
I get the error “Missing argument for parameter #1 in call”,
while if I write it in InterfaceController and call it in the IBAction, although the build is successful, when running the App and changing the value of the switch I get the following error: “fatal error: unexpectedly found nil while unwrapping an Optional value” on the line:
for complication in complicationServer.activeComplications
of func updateComplication.
It's not the placeholder text that you want to update. The placeholder template is a static template that's displayed in the selection screen for your complication while you are customizing your watch face. It's only called once to get the placeholder text, and doesn't get called when a timeline is reloaded.
The complication's current (actual, live) timeline entry is provided by getCurrentTimelineEntryForComplication. This function is where you should use your model's on state of the switch to create a CLKTextProvider containing a "N" or "Y" for the active complication family.
Finally, you should call updateComplication() from your switch IBAction to reload your complication timeline, which would update the current entry to show the new state of the switch on the watch face.
Since your complication is updated manually, you should return a nil updateDate in getNextRequestedUpdateDateWithHandler to avoid scheduling your complication for any regular updates.
Update for your edited question:
You should include the updateComplication function in your interface controller, as your complication controller isn't meant to be instantiated by you.
In regard to the "Unexpectedly found nil while unwrapping an Optional value" error, this is an issue that has been mentioned on the Apple Developer Forums.
If you are already running watchOS 2.1, you could try the latest watchOS beta to see if the issue has been fixed yet for 2.2. You should also file a bug report and dupe radar 22947535.

Accessing the media library on OS X with Swift code

I am an experienced coder, however a novice writing code for Apple devices. I am attempting to access the media library on my OS X device using Swift. I can find dozens of examples accomplishing this task for iOS devices, and successfully implemented some code to do this for iOS. However I am having a difficult time trying to do the same for OS X.
Can anyone please point me to, or offer any suggestions that would help me access the media library (itunes) on an OS X device using Swift?
edit: To clarify, if I am writing for iOS I can make a call such as MPMediaQuery to query the media library. I am looking for something similar that can be used in Swift code written for OS X.
Thanks in advance.
It appears you're referring specifically to the iTunes library. It is created, maintained, and accessed through the Apple supplied iTunes application. Fortunately, iTunes is highly scriptable, either directly through AppleScript, or by incorporating AppleScript calls within your own application.
To get an idea of what's possible, start by opening the Script Editor app, located in /Applications/Utilities, and select File -> Open Dictionary...
The list includes all applications that support scripting. Choose iTunes to display a browser detailing its interface. For example, selecting iTunes Suite -> track displays the properties you can access:
How to write AppleScript code and/or how to incorporate it into your own application is far beyond the scope of a single question here. However, there are many resources on the Apple developer site that can help you get going. A logical place to begin is: AppleScript Overview.
I've been working on this myself for the last month off and on. This is what I was able to come up with.
You have to do the same thing with the media objects once you've loaded them. The calls are non-blocking.
I used the MediaLibrary reference in the OS X library to figure this out. It's a good place to get the rest of the details.
import Cocoa
import MediaLibrary
class ViewController: NSViewController {
var library : MLMediaLibrary!
var iTunes : MLMediaSource!
var rootGroup : MLMediaGroup!
override func viewDidLoad() {
super.viewDidLoad()
let options : [String : AnyObject] = [MLMediaLoadIncludeSourcesKey: [MLMediaSourceiTunesIdentifier]]
library = MLMediaLibrary(options: options)
library.addObserver(self, forKeyPath: "mediaSources", options: NSKeyValueObservingOptions.New, context: nil)
library.mediaSources
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
guard let path = keyPath else { return }
switch path {
case "mediaSources":
loadSources()
case "rootMediaGroup":
loadRootGroup()
default:
print("Nothing to do for \(path)")
}
}
func loadSources(){
if let mediaSources = library.mediaSources {
for (ident, source) in mediaSources {
print("Ident: \(ident)")
print("Source Ident: \(source.mediaSourceIdentifier)")
iTunes = source
iTunes.addObserver(self, forKeyPath: "rootMediaGroup", options: NSKeyValueObservingOptions.New, context: nil)
iTunes.rootMediaGroup
}
}
}
func loadRootGroup(){
if let rootGroup = iTunes.rootMediaGroup {
print("Root Group Identifier: \(rootGroup.identifier)")
print("Root Group Type Ident: \(rootGroup.typeIdentifier)")
}
}
}
I cut out my specific code as I'm writing an Uploader that takes a subset of my library for streaming for others at my office. But this should get you started.