Geotab Keyless Mobile SDK: Guide for Keyless Integration Partners — 1.2 Release Candidate
Learn more about the Geotab Keyless SDK Logging. This release extends that set of data, and now transmits that to Geotab. Geotab is now provided visibility at scale to identify, improve and resolve Bluetooth issues that negatively impact our users.

Support Document

0 mins to read

1.2 Release Candidate: Guide for Keyless Integration Partners

December 2024

What’s new?

The 1.2.0 Release Candidate version is available in the following Github repositories:

  1. Android: 1.2.0-rc.2
  2. iOS: 1.2.0-rc.2

Previously, logging Bluetooth connection and command diagnostic data was made accessible to you to capture. This release extends that set of data, and now transmits that to Geotab. Geotab is now given visibility at scale to identify, improve, and resolve Bluetooth issues that negatively impact our users. We continue to provide you the ability to capture the same information for your own use, which is now extended.

Why are we excited about this?

  1. Geotab’s #1 priority for Keyless is to provide a very highly reliable solution that continues to work in all locations without connectivity.
  2. Ahead of this release, Geotab had no visibility of Bluetooth connection and command failure and slowness, until communication was established with the GO telematics device.

What’s logged?

  1. No Personally Identifiable Information (PII) is captured.
  2. All data has been thoroughly reviewed by the Geotab Legal team, and this full list is transparent and made accessible to you to capture.

How does logging work?

The diagnostic data will be transmitted once hourly when your application is open in the foreground or background on the user's mobile device. Diagnostic data is stored on the user's device until it is transmitted to Geotab. The recording and transmission of this diagnostic data has been designed to have minimal storage and upload requirements, reducing the overall impact to users. The Geotab Keyless Mobile SDK has been designed to have:

  1. A local data storage limit of 20 MB, and
  2. Monthly data uploads that are expected to be less than 1 MB.

Use of the Geotab Keyless Mobile SDK will not alter your data residency requirements and no personally identifiable information (PII) will be recorded or transmitted through the Geotab Keyless Mobile SDK.

Frequently asked questions

To address any questions or concerns raised by your users we have outlined some key components of the new functionality that you can communicate to your users.

  1. No Personally Identifiable Information (PII): The diagnostic data recorded and transmitted does not include PII. The full set of data is publicly available for review: .
  2. Local Data Storage 20 MB Limit: The diagnostic data is stored on the user's device until it is transmitted to Geotab; Geotab's technical design is implemented to never exceed 20 MB in local storage.
    1. Six (6) unlocks/locks is approximately 1 MB in local storage; and
    2. Stored information is removed after transmission.
  3. Monthly Data Upload Less than 1 MB Expected:
    1. Diagnostic data is transmitted compressed; and
    2. Six (6) unlocks/locks = 40 kB compressed; in an atypical very high usage scenario of 6 unlocks/locks daily for 30 days, this would result in 1.2 MB monthly data upload.
  4. Data Upload Frequency: Diagnostic data is transmitted once hourly when your application is open in the foreground or background on the user's mobile device.
  5. Diagnostic Data Residency: Diagnostic data transmitted to Geotab has been verified to follow the same data residency requirements as Geotab's vehicle telematics data.

Important Disclaimer: While you can use this information to communicate the impact of the changes made in the recent update to Geotab’s Keyless Mobile SDK, this information is provided for informational purposes only. It is not intended to be comprehensive, to contain all the information that an individual or legal entity may need in order to evaluate the impact of its use of the Geotab Keyless Mobile SDK or to be considered legal advice. This information is being provided as a courtesy by Geotab without any warranties or representations. Any updates to your agreements or policies with your customers must be determined by you. In addition, Geotab reserves the right to update, modify, or delete any information related to the Geotab Keyless Mobile SDK through a new notice at any time.

Release notes

See the Keyless Release Notes for other quality and user experience improvements.

Core required changes

Updated KeylessManager error types

When updating to version 1.2.0, some KeylessManager error types you might be dependent on may no longer be available due to renaming. The updated error types are simply prefixed with Manager*

Pre-1.2.0

1.2.0

AlreadyConnected

ManagerAlreadyConnected

AlreadyExecuting

ManagerAlreadyExecuting

AlreadyDisconnected

ManagerAlreadyDisconnected

NotReadyToConnect

ManagerNotReadyToConnect

NotReadyToExecute

ManagerNotReadyToExecute

NotReadyToDisconnect

ManagerNotReadyToDisconnect

ProcessingDisconnect

ManagerProcessingDisconnect

New step for iOS

The release of version 1.2.0 of the iOS Keyless SDK introduces Swift Package Manager (SPM) for distribution. SPM makes it easy to integrate external libraries and frameworks into iOS projects. The iOS Keyless SDK is hosted in a private repository keyless-sdk-ios. Therefore, in order for Xcode to be able to add the SDK as a package dependency, a GitHub account with access to the repository must be used in Xcode. Follow these instructions for adding a GitHub account:

document Image

Once the GitHub account has been added, follow these instructions to add the appropriate package version:

  1. Right click in the project navigator pane:
  2. document Image

  3. Paste the iOS Keyless SDK git repository URL: https://github.com/Geotab/keyless-sdk-ios. Set Dependency rule to “Exact Version” and the version to the latest tag “1.2.0-rc.2”:
  4. document Image

  5. Click Add Package:
  6. document Image

  7. Select target and clean the project:
  8. document Image

  9. Added package dependency:

document Image

To allow your app to upload logs in the background, add an additional array value to the UIBackgroundModes key to your application Info.plist file:

<key>UIBackgroundModeskey> <array> ... <string>processingstring> array>

You will also need to add the key BGTaskSchedulerPermittedIdentifiers to your Info.plist flie with an array value containing:

<key>BGTaskSchedulerPermittedIdentifierskey> <array> <string>com.geotab.keyless.loguploaderstring> array>

Your Info.plist should end up looking something like this:

xml version="1.0" encoding="UTF-8"?> <plist version="1.0"> <dict> ... <key>NSBluetoothPeripheralUsageDescriptionkey> <string>App uses Bluetooth to send commands to your vehiclestring> <key>NSBluetoothAlwaysUsageDescriptionkey> <string>App uses Bluetooth to connect to your vehiclestring> <key>UIBackgroundModeskey> <array> <string>bluetooth-peripheralstring> <string>processingstring> array> <key>BGTaskSchedulerPermittedIdentifierskey> <array> <string>com.geotab.keyless.loguploaderstring> array> ... dict> plist>

New step for Android

Using Kotlin (DSL) and gradle version catalogs, set up the application dependency source using the following approach:

  1. Update the settings.gradle.kts file with the following:
  2. //... (other configuration) dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() val properties = Properties() val githubPropertiesFile = File(".github.properties") if (githubPropertiesFile.exists()) { properties.load(githubPropertiesFile.inputStream()) } maven { name = "GitHubPackages" url = uri("https://maven.pkg.github.com/Geotab/keyless-sdk-android") credentials { username = properties.getProperty("GITHUB_USERNAME") ?: System.getenv("GITHUB_USERNAME") password = properties.getProperty("GITHUB_PERSONAL_ACCESS_TOKEN") ?: System.getenv("GITHUB_PERSONAL_ACCESS_TOKEN") } } } } // ... (other configuration) include("app")

  3. Update the app’s libs.versions.toml file with the following:
  4. [versions] keylessSdk = "1.2.0-rc.2" [libraries] geotab-keyless-sdk = { group = "com.geotab", name = "keyless", version.ref = "keylessSdk" }

  5. Update the app’s build.gradle.kts file with the following:

// ... (other configuration) dependencies { // ... // Geotab Keyless SDK implementation(libs.geotab.keyless.sdk) } // ... (other configuration)

Follow these steps to create your personal access token. The personal access token can be stored in a properties file at the root of the project and ignored by version control as a security precaution.

Optional: Implementing the logger interface

The Logger interface is how you would capture the same logs for your own use; this has been available in previous versions with changes in 1.2.0 outlined here to take advantage of this.

The KeylessManager and KeylessClient (deprecated - use KeylessManager) accept Logger implementations where an integrator's desired implementation on how to collect the logs can be provided.

Logger interface and KeylessManager updates

  1. The Logger interfaces for both Android and iOS have been updated with new method signatures described below; please update your implementation to accommodate.
  2. KeylessManager adds a new method flushLogs(). It triggers the upload of logs to the Geotab log collection service, which is beneficial should you wish to immediately transmit logs for access by Geotab Support. Supporting flushLogs() call into your application is not required as the Keyless SDK will attempt to upload logs hourly when your application is open in the foreground or background. However, it is recommended for when unexpected errors occur.

The following sections show the differences between SDK versions for the logger interface.

iOS logger interface

Pre-1.2.0

Old log method signature

/// The `Logger` is used throughout the components of the framework. public protocol Logger { /// Log with the log level provided. /// - Parameters: /// - level: The log level /// - message: The log message /// - timeInterval: The timestamp, in seconds, since epoch that the log occurred /// - file: The file the log occurred on /// - function: The function the log occurred in /// - line: The line the log occurred on func log(_ level: LogLevel, message: () -> Any, context: LogContext) }

1.2.0 (new)

New log method signature

/// The `Logger` is used throughout the components of the framework. public protocol Logger { var logLevel: LogLevel { get } /// Log with the log level provided. /// - Parameters: /// - level: The log level /// - message: The log message /// - attributes: A dictionary of any additional attributes func log(_ level: LogLevel, message: String, attributes: [String: Any]) }

Force Flush Logs

public class KeylessManager { //... public func flushLogs(completion: @escaping (Result<String, Error>) -> Void) //... }


ConsoleLogger example

The following is a ConsoleLogger implementation where the collected logs can be observed in the Xcode debug/output console:

import Keyless import Foundation public struct ConsoleLogger: Logger { public var logLevel: LogLevel public init(logLevel: LogLevel = .verbose) { self.logLevel = logLevel } public func log(_ level: LogLevel, message: String, attributes: [String: Any]) { if attributes.isEmpty { customLog(message, tag: "\(level)") } else { customLog("\(message) \n attributes: \(attributes)", tag: "\(level)") } } /// Custom log function to print messages with a timestamp, file name, and line number. /// - Parameters: /// - message: The message to log. /// - file: The name of the file where the log function is called (default is the current file). /// - function: The name of the function where the log function is called (default is the current function). /// - line: The line number in the file where the log function is called (default is the current line). /// - tag: An optional tag to categorize or label the log message. private func customLog(_ message: Any, file: String = #file, function: String = #function, line: Int = #line, tag: String? = nil) { // Create a date formatter to format the current date and time as a string let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" let timestamp = dateFormatter.string(from: Date()) // Print the formatted log message to the console if let tag = tag { print("\(timestamp) [\(tag)] - \(message)\n") } else { print("\(timestamp) - \(message)\n") } } }

The ConsoleLogger implementation can be used by passing the implementation to the KeylessManager:

import SwiftUI import Keyless @main struct SampleKeylessAppApp: App { init() { Keyless.initialize(logger: ConsoleLogger()) } var body: some Scene { WindowGroup { ContentView() } } }

Android logger interface

Pre-1.2.0

interface Logger { val logLevel: LogLevel // / log something generally unimportant (lowest priority) fun v(message: Any) // / log something which help during debugging (low priority) fun d(message: Any) // / log something which you are really interested but which is not an issue or error (normal priority) fun i(message: Any) // / log something which may cause big trouble soon (high priority) fun w(message: Any) // / log something which will keep you awake at night (highest priority) fun e(message: Any) }


1.2.0 (new)

/** * Interface representing a Logger with various log levels. */ interface Logger { val logLevel: LogLevel /** ... */ /** * Logs a message of normal priority for general informational purposes. * * @param message The message to log. * @param attributes Additional attributes to include with the log entry, default is null. */ fun i(message: String, attributes: Map<String, Any>? = null) = log(LogLevel.Information, message, attributes) /** * Logs a message with the specified log level. * * @param logLevel The level of logging. * @param message The message to log. * @param attributes Additional attributes to include with the log entry, default is null. */ fun log(logLevel: LogLevel, message: String, attributes: Map<String, Any>? = null) }

Force flush logs

/** * The [KeylessManager] class takes an Android [appContext] and optional [Logger] as parameters. * The library logs are disabled by default. You can pass the KeylessManager an implementation * of the Logger interface to consume logs directly. */ class KeylessManager private constructor() { /**

* Uploads collected logs to the Geotab log collection backend.

*

* This function asynchronously uploads the accumulated logs from the Keyless SDK

* to the Geotab log collection backend. It returns a `Result` object containing

* a success message on successful upload, or an error message on failure.

*

* **Note:** This function requires network connectivity and appropriate

* authentication to access the Geotab log collection backend.

*

* @return A `Result` object containing a success message on successful upload,

* or an error message on failure.

*/ suspend fun flushLogs(): Result<String> //... }

FileLogger example

The following is a FileLogger implementation where the collected logs are written to a file. This is accessible to authorized user accounts in the keyless-sdk-android github repository.

/** * Logger implementation that writes log messages to a file and optionally sends them to a Flow * for real-time log observation in the application. * * @property logLevel The minimum log level to process. Defaults to Debug. */ class FileLogger( private val appContext: Context, override val logLevel: LogLevel = LogLevel.Debug, private val maxFileSize: Long = 1024 * 100, // Max size of each log file in bytes private val maxFileCount: Int = 3 // Max number of log files to retain ) : KeylessSdkLogger { companion object { private const val LOG_FILE_NAME = "sample_app_keyless_sdk_logs.txt" private const val LOG_FILE_MIME_TYPE = "text/plain" private const val LOG_FILE_RELATIVE_PATH = "Download" } /** * Logs a message with the specified log level and optional attributes. * The message is written to a file and, if the log level is greater than or equal to * the logger's log level, it's also sent to the internal log channel. * * @param logLevel The severity level of the log message. * @param message The textual content of the log message. * @param attributes An optional map containing additional information related to the log message. */ @RequiresApi(Build.VERSION_CODES.Q) override fun log(logLevel: LogLevel, message: String, attributes: Map<String, Any>?) { if (logLevel >= this.logLevel) { val logMessage = LogMessage(logLevel, message, attributes) writeToFile(logMessage) } } //... other code }

The FileLogger implementation can be passed to the KeylessManager like so:

val viewModel: MainViewModel by viewModels { MainViewModelFactory(KeylessManager.getInstance(applicationContext, logger = FileLogger())) }

scroll-up