Error Codes and Tracking State (v3.3.2)
In most cases no code changes are required to upgrade from 3.3.1 to 3.3.2. The error cases your app receives are unchanged, and new features are only needed when you want them. Please check only 5. Where the error case changes and 6. Where the behavior changes below.
1. Tracking keeps going in more situations
| Situation | 3.3.1 | 3.3.2 |
|---|---|---|
| A Bluetooth device such as AirPods is connected or disconnected during tracking and the audio engine fails to restart | Tracking ends with didFail(error: .audioInitializationFailed) | didInterrupt() → automatic resume → didResume(). If another app is using audio, the SDK checks again every 30 seconds, and if it still cannot resume, interruptionRecoveryFailed |
| After Siri reads a notification aloud while music is playing | Recording sometimes did not restart | The SDK tries to resume once Siri finishes reading |
| An alarm turns off while a resume retry is scheduled | didResume() could be called twice | The retry is skipped if tracking has already resumed |
If the audio engine fails while not tracking, it is audioInitializationFailed as before.
2. 5-digit error codes (Beta) cleaned up
The numbers of the 5-digit error code (error.errorCode), provided as Beta since 3.2.0, are now organized by cause, and you can branch on them by name. Cleaning up the cases and the official switch to 5-digit codes are planned for v4.0.0.
Branching by code name
func didFail(error: Asleep.AsleepError) {
switch error.errorCode {
case .createConflict: // 22409 the previous session is not closed yet
// ...
case .commonExpired: // 11403 plan expired · too many requests
// ...
case .uploadTrackingTerminated: // 23499 tracking ended due to an upload error
// ...
default:
print(error.errorCode.code, error.errorCode.message ?? "")
}
}- Code that branches on cases (
case .httpStatus,case .startTrackingNetworkFail, etc.) keeps working as before. startTrackingNetworkFailandstopTrackingNetworkFailnow show a deprecation warning. Only the warning is new; the behavior is the same, and they are delivered as before until v4.0.0.- For the full code table, see Error Codes.
errorCode.message
errorCode.messageIf the server sent a message, it is included here. It is for checking the cause (logging) and may change without notice, so branch your app logic on the number (code) or the code name.
== compares only the number
== compares only the numberBecause message holds the server message and can differ each time, == on AsleepErrorCode compares only code.
// 3.3.2: true regardless of the server message
[.audioSilenced, .audioUnsilenced, .uploadFailed].contains(error.errorCode)3. Changed codes
In the situations below, the error number your app receives (errorCode.code) differs from 3.3.1. If you branch on this number or collect statistics with it, please check.
| Situation | 3.3.1 | 3.3.2 |
|---|---|---|
| Session creation 400 | 22000 | 22400 |
| Session creation 403 · 429 (plan expired · too many requests) | 22000 | 11403 |
| Session creation 404 (user does not exist) | 22000 | 11404 |
startTracking() while already tracking | 22000 | 11010 |
| Recorder failed to start | 22000 | 11003 |
| Upload 5xx | 11500 | 23500 |
| init server 5xx | 11500 | 21500 |
| init other failures · no internet | 11000 | 21000 |
Token issue 401 · 404 (invalid appId / appSecret) | 12000 | 12002 |
| Report 400 · 401 · 404 · 5xx · other | 11xxx | 32400 · 32401 · 32404 · 32500 · 32000 |
| Report list 400 · 401 · 5xx · other | 11xxx | 33400 · 33401 · 33500 · 33000 |
| Average report 400 · 404 · 5xx · other | 11xxx | 34400 · 34404 · 34500 · 34000 |
| Session deletion 401 · 5xx · other | 11xxx | 25401 · 25500 · 25000 |
| User deletion 401 · 404 · 5xx · other · no internet | 11xxx | 26401 · 11404 · 26500 · 26000 · 26000 |
| Analysis request 400 · 401 · 404 · 5xx · other | 11xxx | 31400 · 31401 · 31404 · 31500 · 31000 |
| Metadata save rejected by the server with 400 | 27000 | 27400 |
| ODA model file corrupted | 10002 | 10001 |
4. Overlapping calls (tracking state)
When another call overlaps while tracking is starting, in progress, or stopping, it is handled as below.
| Call | When | 3.3.1 | 3.3.2 |
|---|---|---|---|
createSleepTrackingManager | Starting · tracking · stopping | Replaced with a new manager (the previous manager's recording could stop without notice) | Returns the current manager as is. The newly passed delegate · config · recording options are not used |
startTracking() | Starting | The second call also proceeds | Ignored (no callback) |
startTracking() | Stopping · init in progress | Starts a new tracking | Ignored (no callback) |
startTracking() | Tracking | alreadyTracking | alreadyTracking (same) |
stopTracking() | Starting | Only the recorder stops | Stops normally as soon as starting finishes |
initAsleepConfig | Before the previous init responds | Both proceed, callback twice | Ignored (no callback) |
initAsleepConfig | Tracking | Recording stops without notice | Closes the session normally and sends didClose, then proceeds with init (didFail on failure) |
setup | Starting · tracking · stopping | Ignored only while tracking | Ignored in all three |
Asleep.isSleepTrackingAlive()
A function that tells whether tracking is in progress has been added.
if Asleep.isSleepTrackingAlive() {
// starting · tracking · stopping
}- Returns
truewhile starting afterstartTracking(), while tracking, and while stopping afterstopTracking(). - The state is kept only in memory. After the app is terminated and launched again, it is
false.
5. Where the error case changes
In 3.3.x the error cases your app receives are not supposed to change, but the following do.
| Situation | 3.3.1 | 3.3.2 |
|---|---|---|
| Engine restart fails after a Bluetooth device is connected · disconnected during tracking | audioInitializationFailed (tracking ends) | didInterrupt() → didResume(), or interruptionRecoveryFailed if it cannot resume |
| No response to the request that closes the previous session during init | stopTrackingNetworkFail | httpStatus (errorCode 21000) |
| Token issue 401 · 404 | tokenIssueFailed | tokenInvalidCredentials |
Tracking starts before ODA setup has finished | unableODA | setupIncomplete (new, 10000) |
| Metadata save rejected by the server with 400 | saveMetadataFailed | metadataValidationFailed (with the server message) |
6. Where the behavior changes
- The analysis completion notification (
didComplete) is decided by the function you called. Only a manager created withcreateSleepTrackingManager(config:delegate:recordingPath:recordingType:)checks for analysis completion and sendsdidComplete. If you pass anAsleepCompletableTrackingDelegatetocreateSleepTrackingManager(config:delegate:), 3.3.1 sentdidCompletebut 3.3.2 does not.didCreate(sessionId:)is called in both cases as before. createSleepTrackingManagerduring tracking returns the current manager instead of a new one. Apps that created a new manager per screen to change the delegate will receive callbacks on the delegate passed first while tracking.- When the last chunk arrives after the session is closed right after tracking ends and is rejected, 3.3.1 sent
uploadTrackingTerminated, but 3.3.2 does not. A rejection for the session being tracked is handled as before.
7. Good to know
Recording can pause until the app is opened when an emergency or missing-person alert arrivesIf an emergency or missing-person alert arrives while the app is in the background, the alert sound briefly takes the audio, recording pauses, and
didFail(error: .cannotActivateInBackground)may be called. Because iOS does not allow turning audio back on in the background, the SDK cannot resume right away. When the app returns to the foreground, the SDK resumes recording automatically. Guide the user to open the app, for example with a local notification.
Updated 4 days ago
