├── .gitignore ├── .swiftlint.yml ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ └── ActiveLookSDK.xcscheme ├── ActiveLookSDK.playground ├── Pages │ └── API evolutions.xcplaygroundpage │ │ └── Contents.swift ├── contents.xcplayground ├── playground.xcworkspace │ └── contents.xcworkspacedata └── timeline.xctimeline ├── ActiveLookSDK.podspec ├── CHANGELOG.md ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── Classes │ ├── Internal │ │ ├── Array+ActiveLook.swift │ │ ├── CBCharacteristic+ActiveLook.swift │ │ ├── CBPeripheral+ActiveLook.swift │ │ ├── CBService+ActiveLook.swift │ │ ├── CBUUID+ActiveLook.swift │ │ ├── CommandID.swift │ │ ├── Data+HexEncodedString.swift │ │ ├── Debugging.swift │ │ ├── GlassesInitializer.swift │ │ ├── GlassesUpdater │ │ │ ├── Downloader.swift │ │ │ ├── Firmware.swift │ │ │ ├── FirmwareUpdater.swift │ │ │ ├── GlassesUpdateParameters.swift │ │ │ ├── GlassesUpdater.swift │ │ │ ├── GlassesUpdaterURL.swift │ │ │ ├── NetworkMonitor.swift │ │ │ ├── UpdateProgress.swift │ │ │ └── VersionChecker.swift │ │ ├── ImageConverter.swift │ │ ├── ImageMDP05.swift │ │ ├── Int+ActiveLook.swift │ │ ├── Int16+ActiveLook.swift │ │ ├── Int8+ActiveLook.swift │ │ ├── Pixel.swift │ │ ├── SerializedGlasses.swift │ │ ├── String+ActiveLook.swift │ │ ├── UIImage+ActiveLook.swift │ │ ├── UInt16+ActiveLook.swift │ │ └── UInt32+ActiveLook.swift │ └── Public │ │ ├── ActiveLookError.swift │ │ ├── ActiveLookSDK.swift │ │ ├── ActiveLookTypes.swift │ │ ├── Configuration.swift │ │ ├── ConfigurationDescription.swift │ │ ├── ConfigurationElementsInfo.swift │ │ ├── DeviceInformation.swift │ │ ├── DiscoveredGlasses.swift │ │ ├── FontData.swift │ │ ├── FontInfo.swift │ │ ├── FreeSpace.swift │ │ ├── GaugeInfo.swift │ │ ├── Glasses.swift │ │ ├── GlassesSettings.swift │ │ ├── GlassesUpdate.swift │ │ ├── GlassesVersion.swift │ │ ├── ImageData.swift │ │ ├── ImageData1bpp.swift │ │ ├── ImageInfo.swift │ │ ├── ImgSaveFmt.swift │ │ ├── LayoutExtraCmd.swift │ │ ├── LayoutParameters.swift │ │ ├── PageInfo.swift │ │ ├── SensorParameters.swift │ │ └── WidgetType.swift └── Heatshrink │ ├── RNHeatshrinkDecoder.m │ ├── RNHeatshrinkEncoder.m │ ├── heatshrink_decoder.c │ ├── heatshrink_encoder.c │ └── include │ ├── RNHeatshrinkDecoder.h │ ├── RNHeatshrinkEncoder.h │ ├── heatshrink-Bridging-Header.h │ ├── heatshrink_common.h │ ├── heatshrink_config.h │ ├── heatshrink_decoder.h │ ├── heatshrink_encoder.h │ └── module.modulemap ├── Tests └── ActiveLookSDKTests │ ├── ActiveLookSDKTests.swift │ └── GlassesUpdaterTests │ └── VersionCheckerTests.swift └── swiftlint.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/xcode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=xcode 3 | 4 | ### Xcode ### 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## User settings 10 | xcuserdata/ 11 | DerivedData/ 12 | 13 | ### Xcode Patch ### 14 | *.xcodeproj/* 15 | !*.xcodeproj/project.pbxproj 16 | !*.xcodeproj/xcshareddata/ 17 | !*.xcworkspace/contents.xcworkspacedata 18 | **/xcshareddata/WorkspaceSettings.xcsettings 19 | 20 | # End of https://www.toptal.com/developers/gitignore/api/xcode -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - type_body_length 3 | - trailing_whitespace 4 | - identifier_name 5 | - line_length 6 | - file_length 7 | opt_in_rules: 8 | - empty_count 9 | - empty_string 10 | excluded: 11 | - DerivedData 12 | #identifier_name: 13 | # min_length: 2 14 | # max_length: 45 15 | line_length: 16 | warning: 200 17 | error: 400 18 | ignores_function_declarations: true 19 | ignores_comments: true 20 | ignores_urls: true 21 | #function_body_length: 22 | # warning: 300 23 | # error: 500 24 | #function_parameter_count: 25 | # warning: 6 26 | # error: 8 27 | #type_body_length: 28 | # warning: 300 29 | # error: 500 30 | #file_length: 31 | # warning: 1000 32 | # error: 1500 33 | # ignore_comment_only_lines: true 34 | #cyclomatic_complexity: 35 | # warning: 15 36 | # error: 25 37 | #reporter: "xcode" -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/ActiveLookSDK.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 67 | 68 | 74 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ActiveLookSDK.playground/Pages/API evolutions.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import ActiveLookSDK 3 | 4 | // - Glasses protection (in case of disconnect) 5 | 6 | // MARK - SDK updates public 7 | 8 | public protocol SerializedData: Codable { 9 | } 10 | 11 | public extension ActiveLookSDK { 12 | func retrieveLastDiscoveredGlasses(from serializedData: SerializedData) -> DiscoveredGlasses? { 13 | return nil 14 | } 15 | 16 | func connect( 17 | onGlassesConnected connectionCallback: @escaping (Glasses) -> Void, 18 | onGlassesDisconnected disconnectionCallback: @escaping (UUID) -> Void, // Addition of argument. Glasses or UUID ? 19 | onConnectionError connectionErrorCallback: @escaping (Error) -> Void 20 | ) { } 21 | } 22 | 23 | public extension DiscoveredGlasses { 24 | // internal implementation 25 | var serializedData: some SerializedData { identifier.uuidString } 26 | } 27 | 28 | // internal implementation 29 | extension String: SerializedData { } 30 | 31 | 32 | // MARK: - Retrieve SDK 33 | 34 | var activeLookSDK: ActiveLookSDK? 35 | 36 | do { 37 | // swiftlint:disable:next multiline_arguments 38 | activeLookSDK = try ActiveLookSDK.shared(token: "") { _ in 39 | // TODO (Pierre Rougeot) 01/03/2022 Implement Firmware Update 40 | } onUpdateProgressCallback: { _ in 41 | // TODO (Pierre Rougeot) 01/03/2022 Implement Firmware Update 42 | } onUpdateSuccessCallback: { _ in 43 | // TODO (Pierre Rougeot) 01/03/2022 Implement Firmware Update 44 | } onUpdateFailureCallback: { _ in 45 | // TODO (Pierre Rougeot) 01/03/2022 Implement Firmware Update 46 | } 47 | } catch { 48 | activeLookSDK = nil 49 | } 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | // MARK: - Retrieve last discovered glasses 59 | 60 | var lastDiscoveredGlasses: DiscoveredGlasses? 61 | var lastDiscoveredGlassesPersistentSerializedData: SerializedData? // from user defaults 62 | 63 | func retrieveLastDiscoveredGlasses() -> DiscoveredGlasses? { 64 | var glasses: DiscoveredGlasses? 65 | if let discoveredGlasses = lastDiscoveredGlasses { 66 | glasses = discoveredGlasses 67 | } else if let data = lastDiscoveredGlassesPersistentSerializedData { 68 | glasses = activeLookSDK?.retrieveLastDiscoveredGlasses(from: data) 69 | } 70 | lastDiscoveredGlasses = glasses 71 | lastDiscoveredGlassesPersistentSerializedData = glasses?.serializedData 72 | return glasses 73 | } 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | // MARK: - Scan for new glasses 83 | 84 | func scan(timeout: TimeInterval, _ completion: @escaping (DiscoveredGlasses?) -> Void) { 85 | activeLookSDK?.startScanning { discoveredGlasses in 86 | completion(discoveredGlasses) 87 | } onScanError: { error in 88 | print(error) 89 | completion(nil) 90 | } 91 | 92 | DispatchQueue.main.asyncAfter(deadline: .now() + timeout) { 93 | activeLookSDK?.stopScanning() 94 | } 95 | } 96 | 97 | 98 | 99 | 100 | 101 | 102 | // MARK: - Connect to discovered glasses 103 | 104 | let scanTimeout: TimeInterval = 30 105 | var connectedGlasses: Glasses? 106 | 107 | func connect(scanTimeOut: TimeInterval, _ completion: @escaping (Glasses?) -> Void) { 108 | if let discoveredGlasses = retrieveLastDiscoveredGlasses() { 109 | process(discoveredGlasses: discoveredGlasses, with: completion) 110 | } else { 111 | scan(timeout: scanTimeOut) { discoveredGlasses in 112 | process(discoveredGlasses: discoveredGlasses, with: completion) 113 | } 114 | } 115 | } 116 | 117 | func process(discoveredGlasses: DiscoveredGlasses?, with completion: @escaping (Glasses?) -> Void) { 118 | discoveredGlasses?.connect { glasses in 119 | connectedGlasses = glasses 120 | completion(glasses) 121 | } onGlassesDisconnected: { 122 | guard let glasses = connectedGlasses else { return } 123 | connectedGlasses = nil 124 | onGlassesDisconnection(glasses, completion: completion) 125 | } onConnectionError: { error in 126 | print(error) 127 | completion(nil) 128 | } 129 | } 130 | 131 | func onGlassesDisconnection(_ connectedGlasses: Glasses, completion: @escaping (Glasses?) -> Void) { 132 | // if relevant 133 | connect(scanTimeOut: scanTimeout, completion) 134 | } 135 | 136 | func onGlassesError(_ connectedGlasses: Glasses, completion: @escaping (Glasses?) -> Void) { 137 | // if relevant 138 | connect(scanTimeOut: scanTimeout, completion) 139 | } 140 | 141 | 142 | 143 | // MARK: - App Life Cycle 144 | 145 | class ExtensionDelegate: NSObject /*, WKExtensionDelegate, ObservableObject */ { 146 | 147 | func applicationDidFinishLaunching() { 148 | } 149 | 150 | func applicationDidBecomeActive() { 151 | // if in preparation mode 152 | connect(scanTimeOut: scanTimeout) { _ in } 153 | } 154 | 155 | func applicationWillResignActive() { 156 | // if in preparation mode 157 | connectedGlasses?.disconnect() 158 | connectedGlasses = nil 159 | } 160 | 161 | func applicationDidEnterBackground() { 162 | } 163 | 164 | func applicationWillEnterForeground() { 165 | } 166 | } 167 | 168 | connect(scanTimeOut: 5) { glasses in 169 | guard let identifier = glasses?.identifier else { return } 170 | print(identifier) 171 | } 172 | -------------------------------------------------------------------------------- /ActiveLookSDK.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ActiveLookSDK.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ActiveLookSDK.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ActiveLookSDK.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint ActiveLookSDK.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'ActiveLookSDK' 11 | s.version = '4.5.5' 12 | s.summary = 'An iOS library to interact with ActiveLook eyewear' 13 | s.description = <<-DESC 14 | This CocoaPod provides the ability to connect to ActiveLook eyewear running 15 | a firmware >= 4.0.0 and send various commands 16 | DESC 17 | 18 | s.homepage = 'https://github.com/ActiveLook/ios-sdk' 19 | s.license = { :type => 'Apache', :file => 'LICENSE' } 20 | s.author = { "Sylvain Romillon" => "sylvain.romillon@microoled.net" } 21 | s.source = { :git => 'https://github.com/ActiveLook/ios-sdk.git', :tag => s.version.to_s } 22 | s.source_files = 'Sources/**/*' 23 | s.swift_version = '5.0' 24 | 25 | s.ios.deployment_target = '12.0' 26 | s.watchos.deployment_target = '6.0' 27 | end 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## Version 4.5.5 4 | 5 | ### Fixes 6 | 7 | - Skip update if we can assume local network without internet 8 | 9 | ## Version 4.5.4 10 | 11 | ### Breaking Change 12 | 13 | - SDK init token parameter changed 14 | 15 | ### Fixes 16 | 17 | - FW update issue 18 | 19 | ## Version 4.5.3 20 | 21 | ### Fixes 22 | 23 | - Dispatch firmware & config download error closure on main thread 24 | 25 | ## Version 4.5.2 26 | 27 | ### Fixes 28 | - Check battery level before glasses update 29 | - Block config update if battery level < 10% 30 | - Empty config line are not anymore queued 31 | - Fix `cancelConnection` on `DiscoveredGlasses` & `serializedGlasses` 32 | 33 | ## Version 4.5.1 34 | 35 | ### Fixes 36 | - `addSubCommandText` : add Null Terminated char to string length 37 | - `widgetTargetLeft`: workaround to fix bmp position outside of the widget 38 | - change FW API URL 39 | 40 | ## Version 4.5.0 41 | 42 | ### New features 43 | - New commands : 44 | - `holdFlush` : When held, new display commands are stored in memory and are displayed when the graphic engine is flushed. 45 | - `layoutDisplayExtended` & `layoutClearAndDisplayExtended` with `ExtraCmd`: Extra commands allow you to add elements to an existing layout without saving the modification. 46 | - `anim` : delete, clear, display saved animations 47 | - `ImgSaveFmt`: new image save format `4bpp HeatShrink Save Comp` 48 | - `widget` : still under development, for debugging purpose (requirement : FW >= 4.11) 49 | 50 | ## Version 4.4.2 51 | 52 | ### Fixes 53 | - Don't need anymore stuffing byte to flush glasses stack 54 | 55 | ## Version 4.4.1 56 | 57 | ### Fixes 58 | - Hotfixe init TimeOutDuration 59 | 60 | ## Version 4.4.0 61 | 62 | ### New features 63 | - Stack commands before sending to eyewear 64 | - New commands : 65 | - `layoutClearAndDisplayExtended` : clear a layout before displaying it at a position 66 | - `layoutClearAndDisplay` : clear a layout before displaying it 67 | - `layoutClearExtended` : clear a layout at a position 68 | - `pageClearAndDisplay` : clear a page before displaying it 69 | - `imgSave` : save image in new format 70 | - `streamImg` : display an image without saving it 71 | - `polyline` : choose the thickness of your polyline 72 | 73 | ## Version 4.3.0 74 | 75 | ### Breaking changes 76 | - Use an anonymous function to accept update 77 | 78 | ### Fixes 79 | - Auto-reconnect of glasses after turning Bluetooth OFF/ON in Settings.app 80 | - Firmware comparison using also major number 81 | - Firmware comparison using .numeric Strings' compare option 82 | - Connected to unknown glasses 83 | 84 | ## Version 4.2.5 85 | 86 | ### New features 87 | - Display update ongoing while updating 88 | - Do a cfgSet("ALooK") as first command. 89 | 90 | ### Fixes 91 | - Fix not empty stack on connection in glasses 92 | - Firmware update progress precision 93 | - Configuration update progress precision 94 | - Low battery error before update started 95 | - Increase reboot delay 96 | - Solve a reconnection issue involving phone's BLE activity change 97 | - Make disconnect upon firmware update intentional 98 | - Notify only onUpdateFailureCallback() if disconnect happens during glasses update 99 | - Call disconnectionCallback() upon Bluetooth being powered off 100 | 101 | ### Changes 102 | - Refactor .rebooting case 103 | - Change notifications to align with android ones 104 | - Change rebooting state to updatingFirmware = 100 105 | - Initialize connection callbacks no more upon glasses initializer error 106 | 107 | --- 108 | 109 | ## Version 4.2.4.1 110 | 111 | ### Fixes 112 | - fix: add a delay before reconnecting to FW until 4.3.2 113 | - fix: generate configuration download path with full version 114 | 115 | --- 116 | 117 | ## Version 4.2.4 118 | 119 | ### New features 120 | - During the update process, possibility to accept or deny an update if available, using the SDK's closure `onUpdateAvailableCallback() -> Bool` 121 | 122 | ### Changes 123 | - If the given `token` is invalid, the update is aborted and the glasses are not connected. 124 | 125 | --- 126 | 127 | ## Version 4.2.3 128 | 129 | ### Fixes 130 | - Loosing connection with the glasses during a version check will not block auto-reconnect anymore (tested only on checks, no updates) 131 | - If the device's BLE is turned off while glasses are connected, they will auto-reconnect when the BLE is back on (non persistent) 132 | - Mirrors Android's management of FlowControl status updates 133 | - Flow control internal management 134 | - Fix cancel connection on disconnected glasses 135 | 136 | ### Changes 137 | - Add reboot delay as parameters 138 | - Retry update on every connection 139 | 140 | --- 141 | 142 | ## Version 4.2.2 143 | 144 | ### New features 145 | - Allows cancelling connection using `glasses` object 146 | - New `serializedGlasses` object allowing reconnecting without scanning 147 | - Allows cancelling connection using `serializedGlasses` object 148 | 149 | ### Changes 150 | - Upon connection loss, always trigger the `onGlassesDisconnected()` callback 151 | 152 | ### Fixes 153 | - All connection loss will trigger an auto-reconnect attempt, unless being initiated using `glasses.disconnect()` 154 | 155 | --- 156 | 157 | ## Version 4.2.1 158 | 159 | ### New features 160 | - Adds an `UP_TO_DATE` state 161 | 162 | ### Changes 163 | - Changes `progress` type to `Double` instead of `Int` 164 | 165 | ### Fixes 166 | - Calls to `onProgressUpdate()` closures 167 | 168 | --- 169 | 170 | ## Version 4.2.0 171 | 172 | ### New features 173 | - Include firmware update 174 | - get latest firmware from repository 175 | - Include configuration update 176 | - get latest configuration from repository 177 | - Add compatibility for watchOS 178 | 179 | ### Caveats 180 | - Configuration update can sometime disconnect the glasses 181 | - Usually with big configurations 182 | 183 | ### Fixes 184 | - Changes `imgList` `commandID`, and `ImageInfo` implementation to return correct `img ID` 185 | - Implements `polyline()` 186 | - Sets hardware in glasses initializer, and dispatch result on main if update not available 187 | - Changes the way the MTU for BLE is calculated 188 | - Updates `CommandID`'s `imgDelete` to `0x46` 189 | - Adds entry in list of commands to handle 190 | 191 | ### Changes 192 | - Bumps minimum iOS version to 13 193 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Microoled 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "ActiveLookSDK", 7 | platforms: [ 8 | .iOS(.v13), 9 | .watchOS(.v6), 10 | .macOS(.v12) 11 | ], 12 | products: [ 13 | .library( 14 | name: "ActiveLookSDK", 15 | targets: ["ActiveLookSDK","Heatshrink"]) 16 | ], 17 | dependencies: [], 18 | targets: [ 19 | .target( 20 | name: "ActiveLookSDK", 21 | dependencies: ["Heatshrink"], 22 | path: "Sources", 23 | exclude: ["Heatshrink"]), 24 | .target( 25 | name: "Heatshrink", 26 | path: "Sources/Heatshrink"), 27 | .testTarget( 28 | name: "ActiveLookSDKTests", 29 | dependencies: ["ActiveLookSDK"]), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ActiveLookSDK 2 | 3 | ## Requirements 4 | 5 | In order to use the ActiveLook SDK for iOS, you should have XCode installed together 6 | with [cocoapods](https://cocoapods.org). 7 | The SDK is also available using SPM. 8 | 9 | ## License 10 | 11 | See `LICENCE`. 12 | _TLDR:_ [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 13 | 14 | ## Installation 15 | 16 | ### CocoaPods 17 | To integrate ActiveLookSDK into your Xcode project using CocoaPods, specify it 18 | in your Podfile: 19 | ``` 20 | pod 'ActiveLookSDK', 21 | :git => 'https://github.com/ActiveLook/ios-sdk.git', 22 | :tag = 'v4.5.5' 23 | ``` 24 | 25 | Then run the command: 26 | `pod install' 27 | 28 | An example Podfile is included in the `demo-app` repo available on github at 29 | [demo-app](https://github.com/ActiveLook/demo-app) 30 | 31 | ### Swift Package Manager 32 | To integrate ActiveLookSDK into your Xcode project using SPM, add a new package with the url https://github.com/ActiveLook/ios-sdk.git using the `main` branch. 33 | 34 | ### Info.plist 35 | To access Core Bluetooth APIs on apps linked on or after iOS 13, fill in the 36 | `NSBluetoothAlwaysUsageDescription` key in your app's `Info.plist`. 37 | 38 | Also, add the `App Transport Security Settings` dictionary with the `Allow Arbitrary Loads` key set to `YES`. 39 | 40 | ## Example 41 | 42 | To test the SDK, clone the [demo-app](https://github.com/ActiveLook/demo-app): 43 | `git clone https://github.com/ActiveLook/demo-app.git` 44 | 45 | ## Documentation 46 | 47 | The code is commented so that the documentation can be built on your machine using Xcode's `Build configuration` command, enabling symbolic documentation. 48 | 49 | ## Initialization 50 | 51 | To start using the SDK, first import the ActiveLookSDK module: 52 | 53 | ```swift 54 | import ActiveLookSDK 55 | ``` 56 | 57 | Then, use its `shared` property to access the shared singleton. This can be called from anywhere within your application. 58 | 59 | ```swift 60 | var activeLook: ActiveLookSDK = ActiveLookSDK.shared 61 | ``` 62 | Then, use its `shared` property to access the shared singleton. This can be called from anywhere within your application. 63 | 64 | ## Scanning 65 | 66 | To scan for available ActiveLook glasses, simply use `startScanning( onGlassesDiscovered: onScanError: )`. 67 | 68 | When a device is discovered, the `onGlassesDiscovered` callback will be called. 69 | Upon failure, the `onScanError` callback will be called instead. 70 | 71 | You can handle these cases by providing closures as parameters: 72 | 73 | ```swift 74 | activeLook.startScanning( 75 | onGlassesDiscovered: { [weak self] (discoveredGlasses: DiscoveredGlasses) in 76 | print("discovered glasses: \(discoveredGlasses.name)") 77 | self?.addDiscoveredGlasses(discoveredGlasses) 78 | 79 | }, onScanError: { (error: Error) in 80 | print("error while scanning: \(error.localizedDescription)") 81 | } 82 | ) 83 | ``` 84 | To stop scanning, call `stopScanning()`. 85 | 86 | ## Connect to ActiveLook glasses 87 | 88 | To connect to a pair of discovered glasses, use the `connect(onGlassesConnected:onGlassesDisconnected:onConnectionError:)` method on the `DiscoveredGlasses` object. 89 | 90 | If the connection is successful, the `onGlassesConnected` callback will be called and will return a `Glasses` object, which can then be used to get information about the connected ActiveLook glasses or send commands. 91 | 92 | If the connection fails, the `onConnectionError` callback will be called instead. 93 | 94 | Finally, if the connection to the glasses is lost at any point, later, while the app is running, the `onGlassesDisconnected` callback will be called. 95 | 96 | ```swift 97 | discoveredGlasses.connect( 98 | onGlassesConnected: { (glasses: Glasses) in 99 | print("glasses connected: \(glasses.name)") 100 | }, onGlassesDisconnected: { 101 | print("disconnected from glasses") 102 | }, onConnectionError: { (error: Error) in 103 | print("error while connecting to glasses: \(error.localizedDescription)") 104 | }) 105 | ``` 106 | 107 | If you need to share the `Glasses` object between several View Controllers, and you find it hard or inconvenient to hold onto the `onGlassesDisconnected` callback, you can reset it or provide a new one by using the `onDisconnect()` method on the `Glasses` object: 108 | 109 | ```swift 110 | glasses.onDisconnect { [weak self] in 111 | guard let self = self else { return } 112 | 113 | let alert = UIAlertController(title: "Glasses disconnected", message: "Connection to glasses lost", preferredStyle: .alert) 114 | self.present(alert, animated: true) 115 | } 116 | ``` 117 | 118 | ## Device information 119 | 120 | To get information relative to discovered glasses as published over Bluetooth, you can access the following public properties: 121 | 122 | ```swift 123 | // Print the name of the glasses 124 | print(discoveredGlasses.name) 125 | 126 | // Print the glasses' manufacturer id 127 | print(discoveredGlasses.manufacturerId) 128 | 129 | // Print the glasses' identifier 130 | print(discoveredGlasses.identifier) 131 | ``` 132 | 133 | Once connected, you can access more information about the device such as its firmware version, the model number etc... by using the `getDeviceInformation()` method: 134 | 135 | ```swift 136 | // Print the model number 137 | print(connectedGlasses.getDeviceInformation().modelNumber) 138 | ``` 139 | 140 | ## Commands 141 | 142 | All available commands are exposed as methods in the `Glasses` class. Examples are available in the Example application. 143 | 144 | Most commands require parameters to be sent. 145 | 146 | ```swift 147 | // Power on the glasses 148 | glasses.power(on: true) 149 | 150 | // Set the display luminance level 151 | glasses.luma(level: 15) 152 | 153 | // Draw a circle at the center of the screen 154 | glasses.circ(x: 152, y: 128, radius: 50) 155 | 156 | // Enable gesture detection sensor 157 | glasses.gesture(enabled: true) 158 | ``` 159 | 160 | When a response is expected from the glasses, a closure can be provided to the callback parameter. (The callback will be called asynchronously) 161 | 162 | ```swift 163 | glasses.battery { (batteryLevel : Int) in 164 | print("current battery level: \(batteryLevel)") 165 | } 166 | ``` 167 | 168 | ## Notifications 169 | 170 | It is possible to subscribe to three types of notifications from the glasses. Once notified, the corresponding closure is called, if provided. 171 | 172 | * Battery level updates: 173 | ```swift 174 | glasses.subscribeToBatteryLevelNotifications(onBatteryLevelUpdate: { (batteryLevel: Int) -> (Void) in 175 | print("battery level update: \(batteryLevel)") 176 | }) 177 | ``` 178 | -> An update is sent periodically, every 30 second. 179 | 180 | * Gesture detection sensor triggered 181 | ```swift 182 | glasses.subscribeToFlowControlNotifications(onFlowControlUpdate: { (flowControlState: FlowControlState) -> (Void) in 183 | print("flow control state update: \(flowControlState)") 184 | }) 185 | ``` 186 | 187 | * Flow control events (when the state of the flow control changes) 188 | ```swift 189 | glasses.subscribeToSensorInterfaceNotifications(onSensorInterfaceTriggered: { () -> (Void) in 190 | print("sensor interface triggered") 191 | }) 192 | ``` 193 | -> Only non-internal states are passed thru. See `Public > ActiveLookType.swift : public enum FlowControlState{}` for more information. 194 | 195 | ## Disconnect 196 | 197 | When done interacting with ActiveLook glasses, simply call the `disconnect()` method: 198 | 199 | ```swift 200 | glasses.disconnect() 201 | ``` 202 | 203 | ## Acknolegment 204 | 205 | We are currently using code from [nobre84/heatshrink-objc](https://github.com/nobre84/heatshrink-objc) (thanks to him). We did'nt find a easiest way to use it than add it to our code. 206 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/Array+ActiveLook.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | extension Array { 19 | func chunked(into size: Int) -> [[Element]] { 20 | return stride(from: 0, to: count, by: size).map { 21 | Array(self[$0 ..< Swift.min($0 + size, count)]) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/CBCharacteristic+ActiveLook.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | import CoreBluetooth 18 | 19 | extension CBCharacteristic { 20 | 21 | var valueAsUTF8: String { 22 | if let value = self.value { 23 | return String(decoding: value, as: UTF8.self) 24 | } 25 | return "" 26 | } 27 | 28 | var valueAsInt: Int { 29 | if let value = self.value { 30 | return Int([UInt8](value)[0]) 31 | } 32 | return 0 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/CBPeripheral+ActiveLook.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | import CoreBluetooth 18 | 19 | extension CBPeripheral { 20 | 21 | func getService(withUUID uuid: CBUUID) -> CBService? { 22 | if let services = self.services, let index = services.firstIndex(where: { $0.uuid == uuid }) { 23 | return services[index] 24 | } 25 | return nil 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/CBService+ActiveLook.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | import CoreBluetooth 18 | 19 | extension CBService { 20 | 21 | func getCharacteristic(forUUID uuid: CBUUID) -> CBCharacteristic? { 22 | if let characteristics = self.characteristics, 23 | let index = characteristics.firstIndex(where: { $0.uuid == uuid }) { 24 | return characteristics[index] 25 | } 26 | return nil 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/CBUUID+ActiveLook.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | import CoreBluetooth 18 | 19 | extension CBUUID { 20 | 21 | 22 | // MARK: - Generic access service and characteristics identifiers 23 | 24 | static let GenericAccessService = CBUUID.init(string: "00001800-0000-1000-8000-00805f9b34fb") 25 | 26 | static let DeviceNameCharacteristic = CBUUID.init(string: "00002a00-0000-1000-8000-00805f9b34fb") 27 | static let AppearanceCharacteristic = CBUUID.init(string: "00002a01-0000-1000-8000-00805f9b34fb") 28 | static let PeripheralPreferredConnectionParametersCharacteristic = CBUUID.init(string: "00002a02-0000-1000-8000-00805f9b34fb") 29 | 30 | 31 | // MARK: - Device information service and characteristics identifiers 32 | 33 | static let DeviceInformationService = CBUUID.init(string: "0000180a-0000-1000-8000-00805f9b34fb") 34 | 35 | static let ManufacturerNameCharacteristic = CBUUID.init(string: "00002a29-0000-1000-8000-00805f9b34fb") 36 | static let ModelNumberCharacteristic = CBUUID.init(string: "00002a24-0000-1000-8000-00805f9b34fb") 37 | static let SerialNumberCharateristic = CBUUID.init(string: "00002a25-0000-1000-8000-00805f9b34fb") 38 | static let HardwareVersionCharateristic = CBUUID.init(string: "00002a27-0000-1000-8000-00805f9b34fb") 39 | static let FirmwareVersionCharateristic = CBUUID.init(string: "00002a26-0000-1000-8000-00805f9b34fb") 40 | static let SoftwareVersionCharateristic = CBUUID.init(string: "00002a28-0000-1000-8000-00805f9b34fb") 41 | 42 | static let DeviceInformationCharacteristicsUUIDs = [ManufacturerNameCharacteristic, 43 | ModelNumberCharacteristic, 44 | SerialNumberCharateristic, 45 | HardwareVersionCharateristic, 46 | FirmwareVersionCharateristic, 47 | SoftwareVersionCharateristic] 48 | 49 | 50 | // MARK: - Battery service and characteristics identifiers 51 | 52 | static let BatteryService = CBUUID.init(string: "0000180f-0000-1000-8000-00805f9b34fb") 53 | 54 | static let BatteryLevelCharacteristic = CBUUID.init(string: "00002a19-0000-1000-8000-00805f9b34fb") 55 | 56 | 57 | // MARK: - ActiveLook services and characteristics identifiers 58 | 59 | static let ActiveLookCommandsInterfaceService = CBUUID.init(string: "0783b03e-8535-b5a0-7140-a304d2495cb7") 60 | 61 | static let ActiveLookTxCharacteristic = CBUUID.init(string: "0783b03e-8535-b5a0-7140-a304d2495cb8") 62 | static let ActiveLookRxCharacteristic = CBUUID.init(string: "0783b03e-8535-b5a0-7140-a304d2495cba") 63 | static let ActiveLookUICharacteristic = CBUUID.init(string: "0783b03e-8535-b5a0-7140-a304d2495cbc") 64 | static let ActiveLookFlowControlCharacteristic = CBUUID.init(string: "0783b03e-8535-b5a0-7140-a304d2495cb9") 65 | static let ActiveLookSensorInterfaceCharacteristic = CBUUID.init(string: "0783b03e-8535-b5a0-7140-a304d2495cbb") 66 | 67 | static let ActiveLookCharacteristicsUUIDS = [ActiveLookTxCharacteristic, 68 | ActiveLookRxCharacteristic, 69 | ActiveLookUICharacteristic, 70 | ActiveLookFlowControlCharacteristic, 71 | ActiveLookSensorInterfaceCharacteristic] 72 | 73 | 74 | // MARK: - SUOTA services and characteristics identifiers 75 | 76 | static let NOTIFICATION_DESCRIPTOR = CBUUID.init(string: "00002902-0000-1000-8000-00805f9b34fb") // NOT NEEDED FOR iOS... 77 | 78 | // static let SPOTA_SERVICE_UUID = CBUUID.init(string: "0000fef5-0000-1000-8000-00805f9b34fb") 79 | static let SpotaService = CBUUID.init(string: "0000fef5-0000-1000-8000-00805f9b34fb") 80 | static let SPOTA_SERV_STATUS_UUID = CBUUID.init(string: "5f78df94-798c-46f5-990a-b3eb6a065c88") 81 | static let SPOTA_MEM_DEV_UUID = CBUUID.init(string: "8082caa8-41a6-4021-91c6-56f9b954cc34") 82 | static let SPOTA_GPIO_MAP_UUID = CBUUID.init(string: "724249f0-5eC3-4b5f-8804-42345af08651") 83 | static let SPOTA_PATCH_LEN_UUID = CBUUID.init(string: "9d84b9a3-000c-49d8-9183-855b673fda31") 84 | static let SPOTA_PATCH_DATA_UUID = CBUUID.init(string: "457871e8-d516-4ca1-9116-57d0b17b9cb2") 85 | 86 | static let SUOTA_VERSION_UUID = CBUUID.init(string: "64B4E8B5-0DE5-401B-A21D-ACC8DB3B913A") 87 | static let SUOTA_PATCH_DATA_CHAR_SIZE_UUID = CBUUID.init(string: "42C3DFDD-77BE-4D9C-8454-8F875267FB3B") 88 | static let SUOTA_MTU_UUID = CBUUID.init(string: "B7DE1EEA-823D-43BB-A3AF-C4903DFCE23C") 89 | static let SUOTA_L2CAP_PSM_UUID = CBUUID.init(string: "61C8849C-F639-4765-946E-5C3419BEBB2A") 90 | 91 | static let SUOTA_UUIDS = [NOTIFICATION_DESCRIPTOR, 92 | SpotaService, 93 | SPOTA_SERV_STATUS_UUID, 94 | SPOTA_MEM_DEV_UUID, 95 | SPOTA_GPIO_MAP_UUID, 96 | SPOTA_PATCH_LEN_UUID, 97 | SPOTA_PATCH_DATA_UUID, 98 | SUOTA_VERSION_UUID, 99 | SUOTA_PATCH_DATA_CHAR_SIZE_UUID, 100 | SUOTA_MTU_UUID, 101 | SUOTA_L2CAP_PSM_UUID] 102 | } 103 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/CommandID.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | internal enum CommandID: UInt8 { 19 | 20 | case power = 0x00 21 | case clear = 0x01 22 | case grey = 0x02 23 | case demo = 0x03 24 | @available(*, deprecated, renamed:"demo", message: "use demo commandID instead") 25 | case test = 0x04 // deprecated since 4.0.0 26 | case battery = 0x05 27 | case vers = 0x06 28 | case led = 0x08 29 | case shift = 0x09 30 | case settings = 0x0A 31 | 32 | case luma = 0x10 33 | 34 | case sensor = 0x20 35 | case gesture = 0x21 36 | case als = 0x22 37 | 38 | case color = 0x30 39 | case point = 0x31 40 | case line = 0x32 41 | case rect = 0x33 42 | case rectf = 0x34 43 | case circ = 0x35 44 | case circf = 0x36 45 | case txt = 0x37 46 | case polyline = 0x38 47 | case holdFlush = 0x39 48 | 49 | case widget = 0x3A 50 | 51 | case qspiErase = 0x0D 52 | case qspiWrite = 0x0E 53 | 54 | case imgSave = 0x41 55 | case imgDisplay = 0x42 56 | case imgStream = 0x44 57 | case imgSave1bpp = 0x45 58 | case imgDelete = 0x46 59 | case imgList = 0x47 60 | 61 | case fontList = 0x50 62 | case fontSave = 0x51 63 | case fontSelect = 0x52 64 | case fontDelete = 0x53 65 | 66 | case layoutSave = 0x60 67 | case layoutDelete = 0x61 68 | case layoutDisplay = 0x62 69 | case layoutClear = 0x63 70 | case layoutList = 0x64 71 | case layoutPosition = 0x65 72 | case layoutDisplayExtended = 0x66 73 | case layoutGet = 0x67 74 | case layoutClearExtended = 0x68 75 | case layoutClearAndDisplay = 0x69 76 | case layoutClearAndDisplayExtended = 0x6A 77 | 78 | case gaugeDisplay = 0x70 79 | case gaugeSave = 0x71 80 | case gaugeDelete = 0x72 81 | case gaugeList = 0x73 82 | case gaugeGet = 0x74 83 | 84 | case pageSave = 0x80 85 | case pageGet = 0x81 86 | case pageDelete = 0x82 87 | case pageDisplay = 0x83 88 | case pageClear = 0x84 89 | case pageList = 0x85 90 | case pageClearAndDisplay = 0x86 91 | 92 | case animSave = 0x95 93 | case animDelete = 0x96 94 | case animDisplay = 0x97 95 | case animClear = 0x98 96 | case animList = 0x99 97 | 98 | case pixelCount = 0xA5 99 | case getChargingCounter = 0xA7 100 | case getChargingTime = 0xA8 101 | case resetChargingParam = 0xAA 102 | 103 | case wConfigID = 0xA1 104 | case rConfigID = 0xA2 105 | case setConfigID = 0xA3 106 | 107 | case cfgWrite = 0xD0 108 | case cfgRead = 0xD1 109 | case cfgSet = 0xD2 110 | case cfgList = 0xD3 111 | case cfgRename = 0xD4 112 | case cfgDelete = 0xD5 113 | case cfgDeleteLessUsed = 0xD6 114 | case cfgFreeSpace = 0xD7 115 | case cfgGetNb = 0xD8 116 | 117 | case shutdown = 0xE0 118 | case reset = 0xE1 119 | } 120 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/Data+HexEncodedString.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | extension Data { 19 | func hexEncodedString() -> String { 20 | return map { String(format: "%02hhx", $0) }.joined() 21 | } 22 | } 23 | 24 | extension StringProtocol { 25 | var hexaData: Data { .init(hexa) } 26 | var hexaBytes: [UInt8] { .init(hexa) } 27 | private var hexa: UnfoldSequence { 28 | sequence(state: startIndex) { startIndex in 29 | guard startIndex < self.endIndex else { return nil } 30 | let endIndex = self.index(startIndex, offsetBy: 2, limitedBy: self.endIndex) ?? self.endIndex 31 | defer { startIndex = endIndex } 32 | return UInt8(self[startIndex.. (Void))? 44 | private var initErrorClosure: ((Error) -> (Void))? 45 | private var initTimeoutTimer: Timer? 46 | private var initPollTimer: Timer? 47 | 48 | private var spotaService: CBService? 49 | 50 | private var batteryLevelCharacteristic: CBCharacteristic? 51 | private var txCharacteristic: CBCharacteristic? 52 | private var rxCharacteristic: CBCharacteristic? 53 | private var flowControlCharacteristic: CBCharacteristic? 54 | private var sensorInterfaceCharacteristic: CBCharacteristic? 55 | 56 | private var deviceNameCharacteristic: CBCharacteristic? 57 | 58 | // MARK: - Life cycle 59 | 60 | override init() 61 | { 62 | super.init() 63 | 64 | guard let sdk = try? ActiveLookSDK.shared() else { 65 | fatalError(String(format: "SDK Singleton NOT AVAILABLE @ %i", #line)) 66 | } 67 | 68 | updateParameters = sdk.updateParameters 69 | } 70 | 71 | 72 | // MARK: - Private methods 73 | 74 | private func isReady() -> Bool 75 | { 76 | if glasses.peripheral.state != .connected { 77 | return false 78 | } 79 | 80 | let di = glasses.getDeviceInformation() 81 | 82 | let requiredProperties: [Any?] = [ 83 | rxCharacteristic, txCharacteristic, batteryLevelCharacteristic, 84 | flowControlCharacteristic, sensorInterfaceCharacteristic, di.manufacturerName, di.modelNumber, 85 | di.serialNumber, di.hardwareVersion, di.firmwareVersion, di.softwareVersion 86 | ] 87 | 88 | for prop in requiredProperties { 89 | if prop == nil { 90 | return false 91 | } 92 | } 93 | 94 | updateParameters?.hardware = di.hardwareVersion! 95 | 96 | if !txCharacteristic!.isNotifying { return false } 97 | 98 | if !flowControlCharacteristic!.isNotifying { return false } 99 | 100 | if !batteryLevelCharacteristic!.isNotifying { return false } 101 | 102 | return true 103 | } 104 | 105 | 106 | private func isDone() 107 | { 108 | self.initSuccessClosure?() 109 | self.initSuccessClosure = nil 110 | 111 | self.initTimeoutTimer?.invalidate() 112 | 113 | self.glasses.resetPeripheralDelegate() 114 | } 115 | 116 | 117 | private func failed(with error: GlassesInitializerError ) 118 | { 119 | self.initErrorClosure?(ActiveLookError.connectionTimeoutError) 120 | self.initErrorClosure = nil 121 | 122 | self.initPollTimer?.invalidate() 123 | 124 | self.glasses.resetPeripheralDelegate() 125 | } 126 | 127 | 128 | // MARK: - Internal methods 129 | 130 | func initialize(_ glasses: Glasses, 131 | onSuccess successClosure: @escaping () -> (Void), 132 | onError errorClosure: @escaping (Error) -> (Void)) 133 | { 134 | 135 | self.glasses = glasses 136 | 137 | // We're setting ourselves as the peripheral delegate in order to complete the init process. 138 | // When the process is done, we'll set the original delegate back in `isDone()` 139 | glasses.setPeripheralDelegate(to: self) 140 | 141 | initSuccessClosure = successClosure 142 | initErrorClosure = errorClosure 143 | 144 | print("initializing glasses") 145 | 146 | glasses.peripheral.discoverServices([CBUUID.DeviceInformationService, 147 | CBUUID.BatteryService, 148 | CBUUID.ActiveLookCommandsInterfaceService]) 149 | 150 | // We're 'polling', or checking regularly that we've received all needed information about the glasses 151 | initPollTimer = Timer.scheduledTimer(withTimeInterval: initPollInterval, repeats: true) { (timer) in 152 | if self.isReady() { 153 | self.isDone() 154 | timer.invalidate() 155 | } 156 | } 157 | 158 | // We're failing after an arbitrary timeout duration 159 | /*initTimeoutTimer = Timer.scheduledTimer(withTimeInterval: initTimeoutDuration, repeats: false) { _ in 160 | self.failed(with: GlassesInitializerError.glassesInitializer( 161 | message: String(format: "connectionTimeoutError: ", #line))) 162 | }*/ 163 | } 164 | 165 | 166 | // MARK: - CBPeripheralDelegate 167 | 168 | // TODO: make delegate react to battery level notifications ??? 169 | public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) 170 | { 171 | guard error == nil else { 172 | failed(with: GlassesInitializerError.glassesInitializer( 173 | message: String(format: "error while discovering services: ", error.debugDescription))) 174 | return 175 | } 176 | 177 | guard let services = peripheral.services else { 178 | failed(with: GlassesInitializerError.glassesInitializer( 179 | message: String(format: "no services discovered for peripheral: ", peripheral ))) 180 | return 181 | } 182 | 183 | for service in services { 184 | print("discovered service: \(service.uuid)") 185 | switch service.uuid { 186 | 187 | case CBUUID.ActiveLookCommandsInterfaceService : 188 | peripheral.discoverCharacteristics(CBUUID.ActiveLookCharacteristicsUUIDS, for: service) 189 | 190 | case CBUUID.BatteryService : 191 | peripheral.discoverCharacteristics([CBUUID.BatteryLevelCharacteristic], for: service) 192 | 193 | case CBUUID.DeviceInformationService : 194 | peripheral.discoverCharacteristics(CBUUID.DeviceInformationCharacteristicsUUIDs, for: service) 195 | 196 | default: 197 | // print("discovered unknown service: \(service.uuid)") 198 | break 199 | } 200 | } 201 | } 202 | 203 | 204 | public func peripheral(_ peripheral: CBPeripheral, 205 | didDiscoverCharacteristicsFor service: CBService, 206 | error: Error?) 207 | { 208 | guard error == nil else { 209 | failed(with: GlassesInitializerError.glassesInitializer( 210 | message: String(format: "error while discovering characteristics: ", error.debugDescription, 211 | ", for service: ", service, "@ ", #line))) 212 | return 213 | } 214 | 215 | guard let characteristics = service.characteristics else { 216 | failed(with: GlassesInitializerError.glassesInitializer( 217 | message: String(format: "error while discovering characteristics: ", error.debugDescription, 218 | ", for service: ", service, "@ ", #line))) 219 | return 220 | } 221 | 222 | 223 | switch service.uuid { 224 | case CBUUID.DeviceInformationService : 225 | service.characteristics?.forEach({ 226 | peripheral.readValue(for: $0) 227 | }) 228 | 229 | default : 230 | break 231 | } 232 | 233 | for characteristic in characteristics 234 | { 235 | switch characteristic.uuid 236 | { 237 | case CBUUID.ActiveLookRxCharacteristic: 238 | rxCharacteristic = characteristic 239 | 240 | case CBUUID.ActiveLookTxCharacteristic: 241 | txCharacteristic = characteristic 242 | peripheral.setNotifyValue(true, for: characteristic) 243 | 244 | case CBUUID.BatteryLevelCharacteristic: 245 | batteryLevelCharacteristic = characteristic 246 | peripheral.readValue(for: characteristic) 247 | peripheral.setNotifyValue(true, for: characteristic) 248 | 249 | case CBUUID.ActiveLookFlowControlCharacteristic: 250 | flowControlCharacteristic = characteristic 251 | peripheral.setNotifyValue(true, for: characteristic) 252 | 253 | case CBUUID.ActiveLookSensorInterfaceCharacteristic: 254 | sensorInterfaceCharacteristic = characteristic 255 | 256 | default: 257 | break 258 | } 259 | 260 | self.glasses.peripheral.discoverDescriptors(for: characteristic) 261 | } 262 | } 263 | 264 | 265 | func peripheral(_ peripheral: CBPeripheral, 266 | didUpdateValueFor characteristic: CBCharacteristic, 267 | error: Error?) 268 | { 269 | guard error == nil else { 270 | failed(with: GlassesInitializerError.glassesInitializer( 271 | message: String(format: "error reading value for characteristics", error.debugDescription, 272 | ", for characteristic: ", characteristic, "@ ", #line))) 273 | return 274 | } 275 | } 276 | 277 | 278 | func peripheral(_ peripheral: CBPeripheral, 279 | didDiscoverDescriptorsFor characteristic: CBCharacteristic, 280 | error: Error?) 281 | { 282 | guard error == nil else { 283 | failed(with: GlassesInitializerError.glassesInitializer( 284 | message: String(format: "error discovering descriptors for characteristics", 285 | error.debugDescription, ", for characteristic: ", characteristic, "@ ", #line))) 286 | return 287 | } 288 | } 289 | 290 | public func peripheral(_ peripheral: CBPeripheral, 291 | didUpdateNotificationStateFor characteristic: CBCharacteristic, 292 | error: Error?) 293 | { 294 | guard error == nil else { 295 | let desc = error!.localizedDescription 296 | print("error while updating notification state : \(desc) for characteristic: \(characteristic.uuid)") 297 | return 298 | } 299 | // print("peripheral did update notification state for characteristic: \(characteristic) in: \(#fileID)") 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/GlassesUpdater/Downloader.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2022 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | 19 | // MARK: - Internal Enums 20 | 21 | internal enum DownloaderError: Error { 22 | case DownloaderError(message: String) 23 | } 24 | 25 | 26 | // MARK: - Definition 27 | 28 | internal class Downloader: NSObject { 29 | 30 | // MARK: - Private Variables 31 | private var task: URLSessionDataTask? 32 | 33 | private var cancelOperations: Bool = false { 34 | didSet { 35 | if cancelOperations == true { 36 | task?.cancel() 37 | } 38 | } 39 | } 40 | 41 | 42 | // MARK: - Life Cycle 43 | 44 | override init() { } 45 | 46 | deinit { 47 | task = nil 48 | } 49 | 50 | 51 | // MARK: - Internal Methods 52 | 53 | internal func abort() { 54 | cancelOperations = true 55 | } 56 | 57 | internal func downloadFirmware(at url: URL, 58 | onSuccess successClosure: @escaping ( Data ) -> (Void), 59 | onError errorClosure: @escaping ( GlassesUpdateError ) -> (Void)) 60 | { 61 | dlog(message: "",line: #line, function: #function, file: #fileID) 62 | 63 | task = URLSession.shared.dataTask( with: url ) { data, response, error in 64 | guard error == nil else { 65 | DispatchQueue.main.async { 66 | errorClosure( GlassesUpdateError.downloader( 67 | message: String(format: "Client error @", #line) ) ) 68 | } 69 | return 70 | } 71 | 72 | guard let httpResponse = response as? HTTPURLResponse else { 73 | print("invalid response") 74 | DispatchQueue.main.async { 75 | errorClosure(GlassesUpdateError.downloader(message: "Invalid Response")) 76 | } 77 | return 78 | } 79 | 80 | guard httpResponse.statusCode != 403 else { 81 | print("403") 82 | DispatchQueue.main.async { 83 | errorClosure(GlassesUpdateError.invalidToken) 84 | } 85 | return 86 | } 87 | 88 | guard (200...299).contains(httpResponse.statusCode) else 89 | { 90 | DispatchQueue.main.async { 91 | errorClosure( GlassesUpdateError.downloader( 92 | message: String(format: "Server error @", #line) ) ) 93 | } 94 | return 95 | } 96 | 97 | guard let data = data else { 98 | DispatchQueue.main.async { 99 | errorClosure( GlassesUpdateError.downloader( 100 | message: String(format: "ERROR while downloading file @", #line) )) 101 | } 102 | return 103 | } 104 | 105 | DispatchQueue.main.async { 106 | successClosure( Data(data) ) 107 | } 108 | 109 | } 110 | task?.resume() 111 | } 112 | 113 | internal func downloadConfiguration(at url: URL, 114 | onSuccess successClosure: @escaping ( String ) -> (Void), 115 | onError errorClosure: @escaping ( GlassesUpdateError ) -> (Void)) 116 | { 117 | dlog(message: "",line: #line, function: #function, file: #fileID) 118 | 119 | task = URLSession.shared.dataTask( with: url ) { data, response, error in 120 | guard error == nil else { 121 | DispatchQueue.main.async { 122 | errorClosure( GlassesUpdateError.downloader( 123 | message: String(format: "Client error @", #line) ) ) 124 | } 125 | return 126 | } 127 | 128 | guard let httpResponse = response as? HTTPURLResponse, 129 | (200...299).contains(httpResponse.statusCode) 130 | else { 131 | DispatchQueue.main.async { 132 | errorClosure( GlassesUpdateError.downloader( 133 | message: String(format: "Server error @", #line) ) ) 134 | } 135 | return 136 | } 137 | 138 | guard let data = data else { 139 | DispatchQueue.main.async { 140 | errorClosure( GlassesUpdateError.downloader( 141 | message: String(format: "ERROR while downloading file @", #line) )) 142 | } 143 | return 144 | } 145 | 146 | DispatchQueue.main.async { 147 | successClosure( String(decoding: data, as: UTF8.self) ) 148 | } 149 | 150 | } 151 | task?.resume() 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/GlassesUpdater/Firmware.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | 19 | // MARK: - Internal Typealiases 20 | 21 | internal typealias Chunck = [UInt8] 22 | internal typealias Block = ( size: Int, chunks: [Chunck] ) 23 | internal typealias Blocks = [Block] 24 | 25 | 26 | // MARK: - Internal Enum 27 | 28 | internal enum FirmwareError: Error { 29 | case firmwareNullChunksize 30 | } 31 | 32 | 33 | // MARK: - 34 | internal struct Firmware { 35 | 36 | 37 | // MARK: - Private Properties 38 | 39 | private var bytes: [UInt8] 40 | 41 | 42 | // MARK: - Internal Properties 43 | 44 | internal var blocks: Blocks 45 | 46 | 47 | // MARK: - Life Cycle 48 | 49 | init(with content : Data) { 50 | bytes = [] 51 | blocks = [] 52 | 53 | content.forEach( { byte in 54 | bytes.append(byte) 55 | } ) 56 | 57 | bytes.append(0x00) 58 | 59 | // used for the CRC 60 | guard bytes.count == (content.count + 1) else { 61 | return 62 | } 63 | 64 | // basic CRC of the firmware, as defined by SUOTA 65 | for index in 0 ..< content.count { 66 | bytes[content.count] ^= bytes[index] 67 | } 68 | } 69 | 70 | 71 | mutating func getSuotaBlocks(_ blockSize: Int, _ chunkSize: Int) throws { 72 | 73 | guard chunkSize > 0 else { 74 | throw FirmwareError.firmwareNullChunksize 75 | } 76 | 77 | let blockSize = min(bytes.count, max(blockSize, chunkSize)) 78 | let chunkSize = min(blockSize, chunkSize) 79 | 80 | var blockOffset = 0 81 | 82 | while blockOffset < bytes.count { 83 | let currentBlockSize = min(blockSize, bytes.count - blockOffset) 84 | 85 | var chunks: [Chunck] = [] 86 | var chunkOffset = 0 87 | 88 | while chunkOffset < currentBlockSize { 89 | 90 | let currentChunkSize = min(chunkSize, currentBlockSize - chunkOffset) 91 | var chunk: Chunck = [] 92 | 93 | let startIndex = blockOffset + chunkOffset 94 | let endIndex = startIndex + currentChunkSize - 1 95 | 96 | chunk = Array(bytes[startIndex...endIndex]) 97 | chunks.append(chunk) 98 | chunkOffset += currentChunkSize 99 | } 100 | 101 | self.blocks.append(Block(size: currentBlockSize, chunks: chunks)) 102 | blockOffset += currentBlockSize 103 | } 104 | } 105 | 106 | 107 | func description() -> String { 108 | return String("firmware: \(bytes[0...5]) of size: \(bytes.count)") 109 | } 110 | 111 | func size() -> Int{ 112 | return bytes.count 113 | } 114 | 115 | func getBytes() -> [UInt8]{ 116 | return bytes 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/GlassesUpdater/GlassesUpdateParameters.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | import CoreBluetooth 18 | 19 | // MARK: - Internal Enum 20 | 21 | internal enum UpdateState : String { 22 | case NOT_INITIALIZED 23 | case startingUpdate 24 | case retrievingDeviceInformations 25 | case deviceInformationsRetrieved 26 | case checkingFwVersion 27 | case noFwUpdateAvailable 28 | // case DOWNLOADING_FIRMWARE -> calling startClosure(GlassesUpdate) 29 | case downloadingFw 30 | // case UPDATING_FIRMWARE -> calling progressClosure(GlassesUpdate) 31 | case updatingFw 32 | case rebooting 33 | case checkingConfigVersion 34 | // case DOWNLOADING_CONFIGURATION -> calling startClosure(GlassesUpdate) 35 | case noConfigUpdateAvailable 36 | case downloadingConfig 37 | // case UPDATING_CONFIGURATION -> calling progressClosure(GlassesUpdate) 38 | case updatingConfig 39 | // -> calling successClosure(GlassesUpdate) 40 | case upToDate 41 | // case ERROR_UPDATE_FAIL_LOW_BATTERY 42 | // case ERROR_UPDATE_FORBIDDEN // UNAVAILABLE 43 | // case ERROR_DOWNGRADE_FORBIDDEN 44 | case updateForbidden 45 | case downgradeForbidden 46 | // case ERROR_UPDATE_FAIL 47 | // -> calling failurexClosure(GlassesUpdate) 48 | case updateFailed 49 | case lowBattery 50 | // -> calling failureClosure(.gu(ERROR_UPDATE_FAIL_LOW_BATTERY)) 51 | } 52 | 53 | 54 | // MARK: - Definition 55 | 56 | internal class GlassesUpdateParameters { 57 | 58 | // TODO: refactor glassesUpdateParameter / GlassesUpdate / SdkGlassesUpdate 59 | // TODO: CREATE NEW class `UpdaterParameters{}`? to dissociate GlassesUpdate parameters from SDK ones? 60 | 61 | // FIXME: vvv RELATED TO Updater vvv 62 | var token: String 63 | var startClosure: StartClosureSignature 64 | var updateAvailableClosure: UpdateAvailableClosureSignature 65 | var progressClosure: ProgressClosureSignature 66 | var successClosure: SuccessClosureSignature 67 | var failureClosure: FailureClosureSignature 68 | // FIXME: ^^^ RELATED TO Updater ^^^ 69 | 70 | // FIXME: vvv RELATED TO GlassesUpdate vvvv 71 | var discoveredGlasses: DiscoveredGlasses? 72 | 73 | var hardware: String 74 | 75 | var state: UpdateState? 76 | 77 | private var softwareVersions: [SoftwareLocation : SoftwareVersions?] 78 | 79 | private var progress: Double = 0 80 | private var batteryLevel: Int? 81 | 82 | // used to match `UpdateStates` to `SDKGlassesUpdate.States` 83 | private var updateStateToGlassesUpdate: [[UpdateState]] 84 | 85 | private let downloadingFW: [UpdateState] = [.downloadingFw] 86 | private let updatingFW: [UpdateState] = [.updatingFw, .rebooting] 87 | private let downloadingCfg: [UpdateState] = [.downloadingConfig] 88 | private let updatingCfg: [UpdateState] = [.updatingConfig, .upToDate] 89 | private let updateFailed: [UpdateState] = [.updateFailed] 90 | private let updateFailedLowBattery: [UpdateState] = [.lowBattery] 91 | private let updateForbidden: [UpdateState] = [.updateForbidden] // TODO: ASANA task "Check glasses FW version <= SDK version" – https://app.asana.com/0/1201639829815358/1202209982822311 – 220504 92 | private let downgradeForbidden: [UpdateState] = [.downgradeForbidden] // TODO: ASANA task "Check glasses FW version <= SDK version" – https://app.asana.com/0/1201639829815358/1202209982822311 – 220504 93 | 94 | // FIXME: ^^^ RELATED TO GlassesUpdate ^^^ 95 | 96 | // MARK: - Life Cycle 97 | 98 | init(_ token: String, 99 | _ onUpdateStartCallback: @escaping StartClosureSignature, 100 | _ onUpdateAvailableCallback: @escaping UpdateAvailableClosureSignature, 101 | _ onUpdateProgressCallback: @escaping ProgressClosureSignature, 102 | _ onUpdateSuccessCallback: @escaping SuccessClosureSignature, 103 | _ onUpdateFailureCallback: @escaping FailureClosureSignature ) 104 | { 105 | // FIXME: vvv RELATED TO Updater vvv 106 | self.token = token 107 | self.startClosure = onUpdateStartCallback 108 | self.updateAvailableClosure = onUpdateAvailableCallback 109 | self.progressClosure = onUpdateProgressCallback 110 | self.successClosure = onUpdateSuccessCallback 111 | self.failureClosure = onUpdateFailureCallback 112 | // FIXME: ^^^ RELATED TO Updater ^^^ 113 | 114 | // FIXME: vvv RELATED TO GlassesUpdate vvv 115 | self.hardware = "" 116 | 117 | self.updateStateToGlassesUpdate = [downloadingFW, updatingFW, 118 | downloadingCfg, updatingCfg, 119 | updateFailed, updateFailedLowBattery, 120 | updateForbidden, downgradeForbidden] 121 | 122 | self.softwareVersions = [ .device: nil, .remote: nil ] 123 | // FIXME: ^^^ RELATED TO GlassesUpdate ^^^ 124 | } 125 | 126 | 127 | // MARK: - Internal Functions 128 | 129 | func notify(_ stateUpdate: UpdateState, _ progress: Double = 0, _ batteryLevel: Int? = nil) 130 | { 131 | dlog(message: "progress update to \(stateUpdate) – \(progress)", 132 | line: #line, function: #function, file: #fileID) 133 | 134 | state = stateUpdate 135 | 136 | var closureToSummon: StartClosureSignature? // all closures have the same signature 137 | 138 | switch stateUpdate 139 | { 140 | case .downloadingFw, .downloadingConfig: 141 | // start closure 142 | self.progress = 0 143 | closureToSummon = startClosure 144 | 145 | case .updatingFw, .updatingConfig: 146 | // progress closure 147 | if ( progress <= self.progress ) { return } 148 | 149 | self.progress = progress 150 | closureToSummon = progressClosure 151 | 152 | case .rebooting: 153 | // we are calling notify(.rebooting) twice: 154 | // - once to signify that the fw update has completed, thus progress == 100 155 | // - the 2nd time, to call the success() closure, so the notification flow is on par with Android 156 | self.progress = 100 157 | 158 | if progress > 100 { 159 | closureToSummon = successClosure 160 | } else { 161 | closureToSummon = progressClosure 162 | } 163 | 164 | case .upToDate: 165 | // success closure 166 | self.progress = 100 167 | closureToSummon = successClosure 168 | 169 | case .updateFailed, .lowBattery, .updateForbidden, .downgradeForbidden: 170 | // failure closure 171 | if batteryLevel != nil { self.batteryLevel = batteryLevel } 172 | print("failure state : \(stateUpdate)") 173 | closureToSummon = failureClosure 174 | 175 | default: 176 | // the remaining UpdateStates are not forwarded to the application. 177 | return 178 | } 179 | 180 | closureToSummon!(createSDKGU(state!)) 181 | } 182 | 183 | 184 | func createSDKGU(_ state: UpdateState) -> SdkGlassesUpdate 185 | { 186 | return SdkGlassesUpdate(for: nil, 187 | state: retrieveState(from: state)!, 188 | progress: self.progress, 189 | batteryLevel: self.batteryLevel, 190 | sourceFirmwareVersion: self.getVersion(for: .device, softwareClass: .firmwares), 191 | targetFirmwareVersion: self.getVersion(for: .remote, softwareClass: .firmwares), 192 | sourceConfigurationVersion: self.getVersion(for: .device, softwareClass: .configurations), 193 | targetConfigurationVersion: self.getVersion(for: .remote, softwareClass: .configurations)) 194 | } 195 | 196 | 197 | func set(version: SoftwareClassProtocol, for location: SoftwareLocation) 198 | { 199 | dlog(message: "",line: #line, function: #function, file: #fileID) 200 | switch version { 201 | case is ConfigurationVersion: 202 | let fwVers = softwareVersions[location]!!.firmware 203 | softwareVersions[location] = SoftwareVersions(firmware: fwVers, configuration: version as! ConfigurationVersion) 204 | break 205 | case is FirmwareVersion: 206 | let cfgVers = ConfigurationVersion(major: 0) 207 | softwareVersions[location] = SoftwareVersions(firmware: version as! FirmwareVersion, configuration: cfgVers) 208 | break 209 | default: 210 | // Unknown 211 | break 212 | } 213 | } 214 | 215 | 216 | func getVersions() -> String { 217 | var versions = "" 218 | versions.append(getVersion(for: .device, softwareClass: .firmwares)) 219 | versions.append(getVersion(for: .remote, softwareClass: .firmwares)) 220 | versions.append(getVersion(for: .device, softwareClass: .configurations)) 221 | versions.append(getVersion(for: .remote, softwareClass: .configurations)) 222 | return versions 223 | } 224 | 225 | 226 | func reset() { 227 | discoveredGlasses = nil 228 | hardware = "" 229 | state = .NOT_INITIALIZED 230 | progress = 0 231 | } 232 | 233 | 234 | func isUpdating() -> Bool { 235 | switch state { 236 | case .NOT_INITIALIZED, .rebooting, .upToDate, .updateFailed: 237 | return false 238 | default: 239 | return true 240 | } 241 | } 242 | 243 | func isRebooting() -> Bool { 244 | switch state { 245 | case .rebooting: 246 | return true 247 | default: 248 | return false 249 | } 250 | } 251 | 252 | func firmwareHasBeenUpdated () { 253 | softwareVersions[.device]!!.firmware = softwareVersions[.remote]!!.firmware 254 | } 255 | 256 | 257 | // TODO: remove after micooled says so 258 | func needDelayAfterReboot() -> Bool 259 | { 260 | let fwVers = softwareVersions[.device]!!.firmware 261 | 262 | return (fwVers.major < 4) || (fwVers.major == 4 && fwVers.minor < 3) || (fwVers.major == 4 && fwVers.minor == 3 && fwVers.patch < 2) 263 | } 264 | 265 | 266 | // MARK: - Private functions 267 | 268 | /// Retrieve the `State` associated with the internal `UpdateState`. 269 | /// 270 | /// The table `updateStateToGlassesUpdate[]` is used to match the different cases. 271 | /// 272 | /// - parameter stateUpdate: UpdateState — The internal state to retrieve 273 | /// 274 | /// - returns: `Optional` `State` to include in a `GlassesUpdate` object. Nil if not found. 275 | /// 276 | private func retrieveState(from stateUpdate: UpdateState) -> State? 277 | { 278 | guard let index = updateStateToGlassesUpdate.firstIndex( where: { 279 | updateStateArr in updateStateArr.contains( stateUpdate ) }) 280 | else { 281 | return nil 282 | } 283 | return State(rawValue: index)! 284 | } 285 | 286 | 287 | private func getVersion(for location: SoftwareLocation, softwareClass: SoftwareClass) -> String 288 | { 289 | let na = "n/a" 290 | 291 | guard let swVers: SoftwareVersions = softwareVersions[location]! else { 292 | return na 293 | } 294 | 295 | var version: String = "" 296 | switch softwareClass { 297 | case .firmwares: 298 | version = swVers.firmware.version 299 | break 300 | case .configurations: 301 | version = swVers.configuration.major != 0 ? swVers.configuration.description : na 302 | break 303 | } 304 | return version 305 | } 306 | // FIXME: ^^^ RELATED TO GlassesUpdate ^^^ 307 | } 308 | 309 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/GlassesUpdater/GlassesUpdaterURL.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | 19 | // MARK: - Definition 20 | 21 | internal final class GlassesUpdaterURL { 22 | 23 | 24 | // MARK: - Private Properties 25 | 26 | private static var _shared: GlassesUpdaterURL! 27 | 28 | private let scheme: String = "https" 29 | private let host: String = "fw.activelook.net" 30 | private let port: Int? = nil // Set to `nil` if no port number assigned 31 | 32 | private let minCompatibility = "4" 33 | 34 | private let apiVersion = "v1" 35 | private var softwareClass: SoftwareClass = .firmwares 36 | private var hardware: String = "" 37 | private var channel: String = "" 38 | 39 | private var generatedURLComponents = URLComponents() 40 | 41 | private weak var sdk: ActiveLookSDK? 42 | 43 | 44 | // MARK: - Initializers 45 | 46 | init() { 47 | GlassesUpdaterURL._shared = self 48 | 49 | guard let sdk = try? ActiveLookSDK.shared() else { 50 | fatalError(String(format: "SDK Singleton NOT AVAILABLE @ %i", #line)) 51 | } 52 | 53 | self.sdk = sdk 54 | } 55 | 56 | 57 | // MARK: - Internal Methods 58 | 59 | static func shared() -> GlassesUpdaterURL 60 | { 61 | switch (self._shared) { 62 | case let (i?): 63 | return i 64 | default: 65 | _shared = GlassesUpdaterURL() 66 | return _shared 67 | } 68 | } 69 | 70 | 71 | func configurationHistoryURL(for firmwareVersion: FirmwareVersion) -> URL { 72 | 73 | dlog(message: "",line: #line, function: #function, file: #fileID) 74 | 75 | softwareClass = .configurations 76 | return generateURL(for: firmwareVersion) 77 | } 78 | 79 | 80 | func configurationDownloadURL(using apiPathString: String) -> URL { 81 | 82 | dlog(message: "",line: #line, function: #function, file: #fileID) 83 | 84 | softwareClass = .configurations 85 | return generateDownloadURL(for: apiPathString) 86 | } 87 | 88 | 89 | func firmwareHistoryURL(for firmwareVersion: FirmwareVersion) -> URL { 90 | 91 | dlog(message: "",line: #line, function: #function, file: #fileID) 92 | 93 | softwareClass = .firmwares 94 | return generateURL(for: firmwareVersion) 95 | } 96 | 97 | 98 | func firmwareDownloadURL(using apiPathString: String) -> URL { 99 | 100 | dlog(message: "",line: #line, function: #function, file: #fileID) 101 | 102 | softwareClass = .firmwares 103 | return generateDownloadURL(for: apiPathString) 104 | } 105 | 106 | 107 | // MARK: - Private Methods 108 | 109 | private func generateURL(for firmwareVersion: FirmwareVersion) -> URL 110 | { 111 | guard let hardware = sdk?.updateParameters.hardware else { 112 | fatalError("NO HARDWARE SET") 113 | } 114 | 115 | guard let token = sdk?.updateParameters.token else { 116 | fatalError("NO TOKEN SET") 117 | } 118 | 119 | let pathComponents = [ 120 | apiVersion, 121 | softwareClass.rawValue, 122 | hardware, 123 | token 124 | ] 125 | 126 | let separator: Character = "/" 127 | var path = pathComponents.joined(separator: separator.description) 128 | if ( separator != path.first ) { 129 | path.insert(separator, at: path.startIndex) 130 | } 131 | 132 | var tempURLCompts = URLComponents() 133 | tempURLCompts.scheme = self.scheme 134 | tempURLCompts.host = self.host 135 | tempURLCompts.port = self.port 136 | tempURLCompts.path = path 137 | 138 | let versionQueryTag: String 139 | 140 | if softwareClass == .firmwares { 141 | versionQueryTag = "min-version" 142 | } else { 143 | versionQueryTag = "max-version" 144 | } 145 | 146 | tempURLCompts.queryItems = [ 147 | URLQueryItem(name: "compatibility", value: self.minCompatibility), 148 | URLQueryItem(name: versionQueryTag, value: firmwareVersion.minVersion) 149 | ] 150 | 151 | return tempURLCompts.url! 152 | } 153 | 154 | 155 | private func generateDownloadURL(for version: String) -> URL 156 | { 157 | guard let hardware = sdk?.updateParameters.hardware else { 158 | fatalError("NO HARDWARE SET") 159 | } 160 | 161 | guard let token = sdk?.updateParameters.token else { 162 | fatalError("NO TOKEN SET") 163 | } 164 | 165 | var version = version 166 | 167 | let separator: Character = "/" 168 | 169 | if ( version.first == separator ) { 170 | _ = version.removeFirst() 171 | } 172 | 173 | let pathComponents = [ 174 | self.apiVersion, 175 | self.softwareClass.rawValue, 176 | hardware, 177 | token, 178 | version 179 | ] 180 | 181 | var path = pathComponents.joined(separator: separator.description) 182 | if ( separator != path.first ) { 183 | path.insert(separator, at: path.startIndex) 184 | } 185 | 186 | var tempURLCompts = URLComponents() 187 | tempURLCompts.scheme = self.scheme 188 | tempURLCompts.host = self.host 189 | tempURLCompts.port = self.port 190 | tempURLCompts.path = path 191 | 192 | return tempURLCompts.url! 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/GlassesUpdater/NetworkMonitor.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | import Network 18 | 19 | // TODO: Post notification on connectivity status change, and subscribe in VersionChecker, Downloader, ... 20 | extension Notification.Name { 21 | static let connectivityStatus = Notification.Name(rawValue: "connectivityStatusChanged") 22 | } 23 | 24 | extension NWInterface.InterfaceType: CaseIterable { 25 | public static var allCases: [NWInterface.InterfaceType] = [ 26 | .other, 27 | .wifi, 28 | .cellular, 29 | .loopback, 30 | .wiredEthernet 31 | ] 32 | } 33 | 34 | final class NetworkMonitor { 35 | static let shared = NetworkMonitor() 36 | 37 | private let queue = DispatchQueue(label: "NetworkConnectivityMonitor") 38 | private let monitor: NWPathMonitor 39 | 40 | private(set) var isConnected = false 41 | private(set) var isExpensive = false 42 | private(set) var currentConnectionType: NWInterface.InterfaceType? 43 | 44 | private init() { 45 | monitor = NWPathMonitor() 46 | } 47 | 48 | func startMonitoring() { 49 | monitor.pathUpdateHandler = { [weak self] path in 50 | self?.isConnected = path.status != .unsatisfied 51 | self?.isExpensive = path.isExpensive 52 | self?.currentConnectionType = 53 | NWInterface.InterfaceType.allCases.filter{ path.usesInterfaceType($0) }.first 54 | 55 | NotificationCenter.default.post(name: .connectivityStatus, object: nil) 56 | dlog(message: "networkStatus changed.... #########", 57 | line: #line, function: #function, file: #fileID) 58 | 59 | } 60 | monitor.start(queue: queue) 61 | dlog(message: "isConnected: \(isConnected)", 62 | line: #line, function: #function, file: #fileID) 63 | 64 | } 65 | 66 | func stopMonitoring() { 67 | monitor.cancel() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/GlassesUpdater/UpdateProgress.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | // FIXME: IS THIS CLASS STILL NEEDED? - 220506 19 | final internal class UpdateProgress: GlassesUpdate { 20 | 21 | private let discoveredGlasses: DiscoveredGlasses 22 | private let state: State 23 | private let progress: Double 24 | private let batteryLevel: Int? 25 | private let sourceFirmwareVersion: String 26 | private let targetFirmwareVersion: String 27 | private let sourceConfigurationVersion: String 28 | private let targetConfigurationVersion: String 29 | 30 | 31 | init( _ discoveredGlasses: DiscoveredGlasses, 32 | _ state: State, 33 | _ progress: Double, 34 | _ batteryLevel: Int?, 35 | _ sourceFirmwareVersion: String, 36 | _ targetFirmwareVersion: String, 37 | _ sourceConfigurationVersion: String, 38 | _ targetConfigurationVersion: String ) { 39 | 40 | self.discoveredGlasses = discoveredGlasses 41 | self.state = state 42 | self.progress = progress 43 | self.batteryLevel = batteryLevel 44 | self.sourceFirmwareVersion = sourceFirmwareVersion 45 | self.targetFirmwareVersion = targetFirmwareVersion 46 | self.sourceConfigurationVersion = sourceConfigurationVersion 47 | self.targetConfigurationVersion = targetConfigurationVersion 48 | } 49 | 50 | 51 | func getDiscoveredGlasses() -> DiscoveredGlasses { 52 | return self.discoveredGlasses; 53 | } 54 | 55 | 56 | func getState() -> State { 57 | return self.state; 58 | } 59 | 60 | 61 | func getProgress() -> Double { 62 | return self.progress; 63 | } 64 | 65 | func getBatteryLevel() -> Int? { 66 | return self.batteryLevel 67 | } 68 | 69 | 70 | func getSourceFirmwareVersion() -> String { 71 | return self.sourceFirmwareVersion; 72 | } 73 | 74 | 75 | func getTargetFirmwareVersion() -> String { 76 | return self.targetFirmwareVersion; 77 | } 78 | 79 | 80 | func getSourceConfigurationVersion() -> String { 81 | return self.sourceConfigurationVersion; 82 | } 83 | 84 | 85 | func getTargetConfigurationVersion() -> String { 86 | return self.targetConfigurationVersion; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/ImageConverter.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | import Foundation 16 | import UIKit 17 | #if canImport(Heatshrink) 18 | import Heatshrink 19 | #endif 20 | 21 | internal class ImageConverter { 22 | 23 | internal func getImageData(img: UIImage, fmt: ImgSaveFmt) -> ImageData{ 24 | let matrix : [[Int]] = convert(img: img, fmt: fmt) 25 | let width : Int = matrix[0].count 26 | var cmds : [UInt8] = [] 27 | 28 | switch fmt { 29 | case .MONO_4BPP: 30 | cmds = getCmd4Bpp(matrix: matrix) 31 | return ImageData(width: UInt16(width), data: cmds) 32 | case .MONO_4BPP_HEATSHRINK, .MONO_4BPP_HEATSHRINK_SAVE_COMP: 33 | let encodedImg = getCmd4Bpp(matrix: matrix) 34 | let matrixData = Data(bytes: encodedImg, count: encodedImg.count) 35 | cmds = getCmdCompress4BppHeatshrink(encodedImg: matrixData) 36 | return ImageData(width: UInt16(width), data: cmds, size: UInt32(encodedImg.count)) 37 | default: 38 | print("Unknown image format") 39 | break 40 | } 41 | 42 | return ImageData() 43 | } 44 | 45 | internal func getImageData1bpp(img: UIImage, fmt: ImgSaveFmt) -> ImageData1bpp{ 46 | let matrix : [[Int]] = convert(img: img, fmt: fmt) 47 | let width : Int = matrix[0].count 48 | var cmds : [[UInt8]] = [[]] 49 | 50 | switch fmt { 51 | case .MONO_1BPP: 52 | cmds = getCmd1Bpp(matrix: matrix) 53 | break 54 | default: 55 | print("Unknown image format") 56 | break 57 | } 58 | 59 | return ImageData1bpp(width: UInt16(width), data: cmds) 60 | } 61 | 62 | internal func getImageDataStream1bpp(img: UIImage, fmt: ImgStreamFmt) -> ImageData1bpp{ 63 | let matrix : [[Int]] = convertStream(img: img, fmt: fmt) 64 | let width : Int = matrix[0].count 65 | var cmds : [[UInt8]] = [[]] 66 | 67 | switch fmt { 68 | case .MONO_1BPP: 69 | cmds = getCmd1Bpp(matrix: matrix) 70 | break 71 | default: 72 | print("Unknown image format") 73 | break 74 | } 75 | 76 | return ImageData1bpp(width: UInt16(width), data: cmds) 77 | } 78 | 79 | internal func getImageDataStream4bpp(img: UIImage, fmt: ImgStreamFmt) -> ImageData{ 80 | let matrix : [[Int]] = convertStream(img: img, fmt: fmt) 81 | let width : Int = matrix[0].count 82 | var cmds : [UInt8] = [] 83 | 84 | switch fmt { 85 | case .MONO_4BPP_HEATSHRINK: 86 | let encodedImg = getCmd4Bpp(matrix: matrix) 87 | let matrixData = Data(bytes: encodedImg, count: encodedImg.count) 88 | cmds = getCmdCompress4BppHeatshrink(encodedImg: matrixData) 89 | return ImageData(width: UInt16(width), data: cmds, size: UInt32(encodedImg.count)) 90 | default: 91 | print("Unknown image format") 92 | break 93 | } 94 | 95 | return ImageData() 96 | } 97 | 98 | //MARK: - Convert pixels to specific format without compression 99 | private func convert(img: UIImage, fmt: ImgSaveFmt) -> [[Int]]{ 100 | var convert : [[Int]] = [[]] 101 | 102 | switch fmt { 103 | case .MONO_1BPP: 104 | convert = ImageMDP05().convert1Bpp(image: img) 105 | break 106 | case .MONO_4BPP: 107 | convert = ImageMDP05().convertDefault(image: img) 108 | break 109 | case .MONO_4BPP_HEATSHRINK, .MONO_4BPP_HEATSHRINK_SAVE_COMP: 110 | convert = ImageMDP05().convertDefault(image: img) 111 | break 112 | } 113 | 114 | return convert; 115 | } 116 | 117 | private func convertStream(img: UIImage, fmt: ImgStreamFmt) -> [[Int]]{ 118 | var convert : [[Int]] = [[]] 119 | 120 | switch fmt { 121 | case .MONO_1BPP: 122 | convert = ImageMDP05().convert1Bpp(image: img) 123 | break 124 | case .MONO_4BPP_HEATSHRINK: 125 | convert = ImageMDP05().convertDefault(image: img) 126 | break 127 | } 128 | 129 | return convert; 130 | } 131 | 132 | 133 | //MARK: - Prepare command to save image 134 | private func getCmd4Bpp(matrix : [[Int]]) -> [UInt8]{ 135 | let height = matrix.count 136 | let width = matrix[0].count 137 | 138 | //Compresse img 4 bit per pixel 139 | var encodedImg : [UInt8] = [] 140 | 141 | for y in 0 ..< height{ 142 | var b : UInt8 = 0; 143 | var shift : UInt8 = 0; 144 | for x in 0 ..< width { 145 | let pxl : UInt8 = UInt8(matrix[y][x]) 146 | //compress 4 bit per pixel 147 | b += pxl << shift 148 | shift += 4 149 | 150 | if (shift == 8){ 151 | encodedImg.append(b) 152 | b = 0; 153 | shift = 0; 154 | } 155 | } 156 | if (shift != 0){ 157 | encodedImg.append(b) 158 | } 159 | } 160 | return encodedImg 161 | } 162 | 163 | private func getCmdCompress4BppHeatshrink(encodedImg: Data) -> [UInt8]{ 164 | let encoder = RNHeatshrinkEncoder(windowSize: 8, andLookaheadSize: 4) 165 | return [UInt8](encoder.encode(encodedImg)) 166 | } 167 | 168 | private func getCmd1Bpp(matrix : [[Int]]) -> [[UInt8]]{ 169 | let height = matrix.count 170 | let width = matrix[0].count 171 | 172 | //Compress img 1 bit per pixel 173 | var encodedImg : [[UInt8]] = [[]] 174 | 175 | for y in 0 ..< height{ 176 | var byte : UInt8 = 0 177 | var shift : UInt8 = 0 178 | var encodedLine : [UInt8] = [] 179 | 180 | for x in 0 ..< width { 181 | let pxl : UInt8 = UInt8(matrix[y][x]) 182 | 183 | //compress 1 bit per pixel 184 | byte += pxl << shift 185 | shift += 1 186 | if(shift == 8){ 187 | encodedLine.append(byte) 188 | byte = 0 189 | shift = 0 190 | } 191 | } 192 | if(shift != 0){ 193 | encodedLine.append(byte) 194 | } 195 | encodedImg.insert(encodedLine, at: y) 196 | } 197 | 198 | encodedImg.removeLast() 199 | return encodedImg 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/ImageMDP05.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | import UIKit 18 | 19 | internal class ImageMDP05{ 20 | 21 | ///convert image to MDP05 default format 22 | internal func convertDefault(image : UIImage) -> [[Int]]{ 23 | let rotatedPixelsArray : [[Pixel]] = image.getRotatedPixels_180() 24 | 25 | let height = rotatedPixelsArray.count 26 | let width = rotatedPixelsArray[0].count 27 | 28 | var encodedImg : [[Int]] = [[Int]](repeating: [Int](repeating: 0, count: width), count: height) 29 | 30 | //reduce to 4bpp 31 | for y in 0 ..< height { 32 | for x in 0 ..< width { 33 | let gray8bit : Int = rotatedPixelsArray[y][x].rgbTo8bitGrayWeightedConvertion() 34 | //convert gray8bit to gray4bit 35 | let gray4bit = Int((Double(gray8bit)/16)) 36 | encodedImg[y][x] = gray4bit 37 | } 38 | } 39 | return encodedImg 40 | } 41 | 42 | ///convert image to MDP05 1bpp format 43 | internal func convert1Bpp(image : UIImage) -> [[Int]]{ 44 | let rotatedPixelsArray : [[Pixel]] = image.getRotatedPixels_180() 45 | 46 | let height = rotatedPixelsArray.count 47 | let width = rotatedPixelsArray[0].count 48 | 49 | var encodedImg : [[Int]] = [[Int]](repeating: [Int](repeating: 0, count: width), count: height) 50 | 51 | //reduce to 1 bpp 52 | for y in 0 ..< height { 53 | for x in 0 ..< width { 54 | //convert gray8bit in gray1bit 55 | if (rotatedPixelsArray[y][x].rgbTo8bitGrayWeightedConvertion() > 0){ 56 | encodedImg[y][x] = 1 57 | }else{ 58 | encodedImg[y][x] = 0 59 | } 60 | } 61 | } 62 | 63 | return encodedImg 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/Int+ActiveLook.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | extension Int { 19 | 20 | /// Is returning the same output as `asUInt8Array`. 21 | /// Not tested performance wise... 22 | var byteArray: [UInt8] { 23 | withUnsafeBytes(of: self.bigEndian) { 24 | Array($0) 25 | } 26 | } 27 | 28 | var asUInt8Array: [UInt8] { 29 | let firstByte = UInt8(truncatingIfNeeded: self >> 24) 30 | let secondByte = UInt8(truncatingIfNeeded: self >> 16) 31 | let thirdByte = UInt8(truncatingIfNeeded: self >> 8) 32 | let fourthByte = UInt8(truncatingIfNeeded: self) 33 | return [firstByte, secondByte, thirdByte, fourthByte] 34 | } 35 | 36 | internal static func fromUInt16ByteArray(bytes: [UInt8]) -> Int { 37 | guard bytes.count >= 2 else { return 0 } 38 | 39 | return Int(bytes[0]) << 8 + Int(bytes[1]) 40 | } 41 | 42 | internal static func fromUInt24ByteArray(bytes: [UInt8]) -> Int { 43 | guard bytes.count >= 3 else { return 0 } 44 | 45 | return Int(bytes[0]) << 16 + Int(bytes[1]) << 8 + Int(bytes[2]) 46 | } 47 | 48 | internal static func fromUInt32ByteArray(bytes: [UInt8]) -> Int { 49 | guard bytes.count >= 4 else { return 0 } 50 | 51 | return Int(bytes[0]) << 24 + Int(bytes[1]) << 16 + Int(bytes[2]) << 8 + Int(bytes[3]) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/Int16+ActiveLook.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | extension Int16 { 19 | 20 | /// is returning the same output as `asUInt8Array`. 21 | /// Not tested performance wise... 22 | var byteArray: [UInt8] { 23 | withUnsafeBytes(of: self.bigEndian) { // 24 | Array($0) 25 | } 26 | } 27 | 28 | var asUInt8Array: [UInt8] { 29 | let msb = UInt8(truncatingIfNeeded: self >> 8) 30 | let lsb = UInt8(truncatingIfNeeded: self) 31 | return [msb, lsb] 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/Int8+ActiveLook.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | extension Int8 { 19 | 20 | var asUInt8: UInt8 { 21 | return UInt8(bitPattern: self) 22 | } 23 | 24 | var asInt16: Int16 { 25 | return Int16(self) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/Pixel.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | import Foundation 16 | 17 | internal class Pixel { 18 | var r : UInt8 19 | var g : UInt8 20 | var b : UInt8 21 | 22 | internal init(){ 23 | self.r = 0 24 | self.g = 0 25 | self.b = 0 26 | } 27 | 28 | internal init(r: UInt8, g: UInt8, b: UInt8){ 29 | self.r = r 30 | self.g = g 31 | self.b = b 32 | } 33 | 34 | internal func rgbTo8bitGrayWeightedConvertion() -> Int{ 35 | var grayPixel : Double = Double(self.r) * 0.299 36 | grayPixel += Double(self.g) * 0.587 37 | grayPixel += Double(self.b) * 0.114 38 | return Int(grayPixel.rounded()) 39 | } 40 | 41 | internal func rgbTo8bitGrayDirectConvertion() -> Int{ 42 | var grayPixel : Double = (Double(self.r) + Double(self.g) + Double(self.b)) / 3 43 | return Int(grayPixel.rounded()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/SerializedGlasses.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | public typealias SerializedGlasses = Data 19 | 20 | fileprivate var encoder = JSONEncoder() 21 | fileprivate var decoder = JSONDecoder() 22 | 23 | internal struct UnserializedGlasses: Codable { 24 | var id: String 25 | var name: String 26 | var manId: String 27 | 28 | func serialize() throws -> SerializedGlasses { 29 | do { 30 | return try encoder.encode(self) 31 | } catch { 32 | throw ActiveLookError.serializeError 33 | } 34 | } 35 | } 36 | 37 | extension SerializedGlasses { 38 | func unserialize() throws -> UnserializedGlasses { 39 | do { 40 | return try decoder.decode(UnserializedGlasses.self, from: self) 41 | } catch { 42 | throw ActiveLookError.unserializeError 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/String+ActiveLook.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | extension String { 19 | 20 | var asNullTerminatedUInt8Array: [UInt8] { 21 | return self.utf8CString.map { UInt8($0) } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/UIImage+ActiveLook.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | import Foundation 16 | import UIKit 17 | 18 | extension UIImage{ 19 | 20 | internal func getPixels() -> [[Pixel]]{ 21 | guard let cgImage = self.cgImage, 22 | let data = cgImage.dataProvider?.data, 23 | let bytes = CFDataGetBytePtr(data) else { 24 | fatalError("Couldn't access image data") 25 | } 26 | assert(cgImage.colorSpace?.model == .rgb) 27 | 28 | let bytesPerPixel = cgImage.bitsPerPixel / cgImage.bitsPerComponent 29 | 30 | var PixelsArray : [[Pixel]] = [[Pixel]](repeating: [Pixel](repeating: Pixel.init(), count: cgImage.width), count: cgImage.height) 31 | for y in 0 ..< cgImage.height { 32 | for x in 0 ..< cgImage.width { 33 | let offset = (y * cgImage.bytesPerRow) + (x * bytesPerPixel) 34 | let pixel : Pixel = Pixel.init(r: bytes[offset], g: bytes[offset + 1], b: bytes[offset + 2]) 35 | PixelsArray[y][x] = pixel 36 | } 37 | } 38 | 39 | return PixelsArray 40 | } 41 | 42 | 43 | internal func getRotatedPixels_180() -> [[Pixel]]{ 44 | let Pixels = self.getPixels() 45 | let height = Pixels.count 46 | let width = Pixels[0].count 47 | 48 | var PixelsArray : [[Pixel]] = [[Pixel]](repeating: [Pixel](repeating: Pixel.init(), count: width), count: height) 49 | 50 | for y in 0 ..< height { 51 | for x in 0 ..< width { 52 | PixelsArray[height - y - 1][width - x - 1] = Pixels[y][x] 53 | } 54 | } 55 | return PixelsArray 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/UInt16+ActiveLook.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | extension UInt16 { 19 | 20 | /// is returning the same output as `asUInt8Array`. 21 | /// Not tested performance wise... 22 | var byteArray: [UInt8] { 23 | withUnsafeBytes(of: self.littleEndian) { 24 | Array($0) 25 | } 26 | } 27 | 28 | var asUInt8Array: [UInt8] { 29 | let msb = UInt8(truncatingIfNeeded: self >> 8) 30 | let lsb = UInt8(truncatingIfNeeded: self) 31 | return [msb, lsb] 32 | } 33 | 34 | internal static func fromUInt16ByteArray(bytes: [UInt8]) -> UInt16 { 35 | guard bytes.count >= 2 else { return 0 } 36 | 37 | return UInt16(bytes[0]) << 8 + UInt16(bytes[1]) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Classes/Internal/UInt32+ActiveLook.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | extension UInt32 { 19 | 20 | /// is returning the same output as `asUInt8Array`. 21 | /// Not tested performance wise... 22 | var byteArray: [UInt8] { 23 | withUnsafeBytes(of: self.littleEndian) { 24 | Array($0) 25 | } 26 | } 27 | 28 | var asUInt8Array: [UInt8] { 29 | let firstByte = UInt8(truncatingIfNeeded: self >> 24) 30 | let secondByte = UInt8(truncatingIfNeeded: self >> 16) 31 | let thirdByte = UInt8(truncatingIfNeeded: self >> 8) 32 | let fourthByte = UInt8(truncatingIfNeeded: self) 33 | return [firstByte, secondByte, thirdByte, fourthByte] 34 | } 35 | 36 | internal static func fromUInt32ByteArray(bytes: [UInt8]) -> UInt32 { 37 | guard bytes.count >= 2 else { return 0 } 38 | return UInt32(bytes[0]) << 24 + UInt32(bytes[1]) << 16 + UInt32(bytes[2]) << 8 + UInt32(bytes[3]) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Classes/Public/ActiveLookError.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import CoreBluetooth 17 | 18 | /// ActiveLook specific errors 19 | public enum ActiveLookError: Error { 20 | case unknownError 21 | case connectionTimeoutError 22 | case bluetoothPoweredOffError 23 | case bluetoothUnsupportedError 24 | case bluetoothUnauthorizedError 25 | case initializationError 26 | case startScanningAlreadyCalled 27 | case sdkInitMissingParameters 28 | case sdkCannotChangeParameters 29 | case sdkUpdateFailed 30 | case sdkDevelopment 31 | case serializeError 32 | case unserializeError 33 | case alreadyConnected 34 | case cannotRetrieveGlasses 35 | case connectUsingAlreadyCalled 36 | } 37 | 38 | extension ActiveLookError: LocalizedError { 39 | public var errorDescription: String? { 40 | switch self { 41 | case .unknownError: 42 | return "Unknown error" 43 | case .connectionTimeoutError: 44 | return "Connection timeout error" 45 | case .bluetoothUnsupportedError: 46 | return "Bluetooth is not supported on this device" 47 | case .bluetoothUnauthorizedError: 48 | return "Bluetooth is not authorized on this device" 49 | case .bluetoothPoweredOffError: 50 | return "Bluetooth is powered off" 51 | case .initializationError: 52 | return "Error while initializing glasses" 53 | case .startScanningAlreadyCalled: 54 | return "startScanning() has already been called" 55 | case .sdkInitMissingParameters: 56 | return "All parameters are needed at initialization" 57 | case .sdkCannotChangeParameters: 58 | return "Parameters cannot be changed after initialization" 59 | case .sdkUpdateFailed: 60 | return "Glasses update failed" 61 | case .sdkDevelopment: 62 | return "SDK development ERROR" 63 | case .serializeError: 64 | return "Cannot serialize glasses" 65 | case .unserializeError: 66 | return "Cannot unserialize data" 67 | case .alreadyConnected: 68 | return "Already connected to glasses" 69 | case .cannotRetrieveGlasses: 70 | return "cannot retrieve glasses from SerializedGlasses" 71 | case .connectUsingAlreadyCalled: 72 | return "connect(using: ...) has already been called" 73 | } 74 | } 75 | 76 | internal static func bluetoothErrorFromState(state: CBManagerState) -> Error { 77 | switch state { 78 | case .poweredOff: 79 | return ActiveLookError.bluetoothPoweredOffError 80 | case .unsupported: 81 | return ActiveLookError.bluetoothUnsupportedError 82 | case .unauthorized: 83 | return ActiveLookError.bluetoothUnauthorizedError 84 | default: 85 | return ActiveLookError.unknownError 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/Classes/Public/ActiveLookTypes.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Available test patterns 19 | public enum DemoPattern: UInt8 { 20 | case fill = 0x00 21 | case cross = 0x01 22 | case image = 0x02 23 | } 24 | 25 | /// Available states for the glasses' green LED 26 | @objc public enum LedState: UInt8 { 27 | case off = 0x00 28 | case on = 0x01 29 | case toggle = 0x02 30 | case blink = 0x03 31 | } 32 | 33 | /// Configurable sensor modes 34 | public enum SensorMode: UInt8 { 35 | case ALSArray = 0x00 36 | case ALSPeriod = 0x01 37 | case rangingPeriod = 0x02 38 | } 39 | 40 | /// Rotation / orientation of text being displayed 41 | public enum TextRotation: UInt8 { 42 | case bottomRL = 0x00 43 | case bottomLR = 0x01 44 | case leftBT = 0x02 45 | case leftTB = 0x03 46 | case topLR = 0x04 47 | case topRL = 0x05 48 | case rightTB = 0x06 49 | case rightBT = 0x07 50 | } 51 | 52 | /// The Flow Control state. 53 | /// 54 | /// The Flow Control server provides a method to prevent the Client's application from overloading the BLE memory buffer of the ActiveLook® device. 55 | /// The SDK manages the sending and / or stacking of commands. 56 | /// Only non-internal States are forwarded thru to the Client application. 57 | @objc public enum FlowControlState: Int { 58 | case on = 1 // internal 59 | case off = 2 // internal 60 | case error = 3 61 | case overflow = 4 62 | case unexpectedDataType = 5 63 | case missingConfiguration = 6 64 | } 65 | 66 | public typealias Point = (x: UInt16, y: UInt16) 67 | 68 | internal typealias CommandResponseData = [UInt8] 69 | 70 | // Hold or flush commands 71 | @objc public enum HoldFlushAction: UInt8 { 72 | case HOLD = 0 73 | case FLUSH = 1 74 | } 75 | -------------------------------------------------------------------------------- /Sources/Classes/Public/Configuration.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Information about a Configuration. 19 | public class Configuration { 20 | 21 | /// The configuration number 22 | public let number: UInt8 23 | 24 | /// The configuration ID 25 | public let id: UInt32 26 | 27 | internal let deprecatedBytes: [UInt8] 28 | 29 | public init(number: UInt8, id: UInt32) { 30 | self.number = number 31 | self.id = id 32 | self.deprecatedBytes = [0x00, 0x00, 0x00] 33 | } 34 | 35 | internal func toCommandData() -> [UInt8] { 36 | var data: [UInt8] = [self.number] 37 | data.append(contentsOf: self.id.asUInt8Array) 38 | data.append(contentsOf: self.deprecatedBytes) 39 | 40 | return data 41 | } 42 | 43 | internal static func fromCommandResponseData(_ data: CommandResponseData) -> Configuration { 44 | guard data.count >= 5 else { return Configuration(number: 0, id: 0) } 45 | 46 | let d = data 47 | let number = d[0] 48 | let configID = UInt32.fromUInt32ByteArray(bytes: [d[1], d[2], d[3], d[4]]) 49 | 50 | return Configuration(number: number, id: configID) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Classes/Public/ConfigurationDescription.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Information about free space. 19 | public class ConfigurationDescription { 20 | 21 | /// The configuration name 22 | public let name: String 23 | 24 | /// The configuration size 25 | public let size: UInt32 26 | 27 | /// The configuration version 28 | public let version: UInt32 29 | 30 | /// The configuration usage count 31 | public let usageCnt: UInt8 32 | 33 | /// The configuration install count 34 | public let installCnt: UInt8 35 | 36 | /// The configuration flag for system configuration 37 | public let isSystem: Bool 38 | 39 | init(_ name: String, _ size: UInt32, _ version: UInt32, _ usageCnt: UInt8, _ installCnt: UInt8, _ isSystem: Bool) { 40 | self.name = name 41 | self.size = size 42 | self.version = version 43 | self.usageCnt = usageCnt 44 | self.installCnt = installCnt 45 | self.isSystem = isSystem 46 | } 47 | 48 | internal static func fromCommandResponseData(_ data: CommandResponseData) -> [ConfigurationDescription] { 49 | var results: [ConfigurationDescription] = [] 50 | var offset = 0 51 | while offset < data.count { 52 | let subData = Array(data.suffix(from: offset)) 53 | let nameSize = (subData.firstIndex(of: 0) ?? 0) + 1 54 | 55 | guard subData.count >= nameSize + 11 else { return results } 56 | 57 | let name = String(cString: Array(subData[0 ... nameSize - 1])) 58 | let size = UInt32.fromUInt32ByteArray(bytes: Array(subData[nameSize ... nameSize + 3])) 59 | let version = UInt32.fromUInt32ByteArray(bytes: Array(subData[nameSize + 4 ... nameSize + 7])) 60 | let usageCnt = subData[nameSize + 8] 61 | let installCnt = subData[nameSize + 9] 62 | let isSystem = subData[nameSize + 10] != 0 63 | 64 | results.append(ConfigurationDescription(name, size, version, usageCnt, installCnt, isSystem)) 65 | offset += nameSize + 11 66 | } 67 | return results 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Classes/Public/ConfigurationElementsInfo.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Information about free space. 19 | public class ConfigurationElementsInfo { 20 | 21 | /// The configuration version 22 | public let version: UInt32 23 | 24 | /// The number of image in this configuration 25 | public let nbImg: UInt8 26 | 27 | /// The number of layout in this configuration 28 | public let nbLayout: UInt8 29 | 30 | /// The number of font in this configuration 31 | public let nbFont: UInt8 32 | 33 | /// The number of page in this configuration 34 | public let nbPage: UInt8 35 | 36 | /// The number of gauge in this configuration 37 | public let nbGauge: UInt8 38 | 39 | init(_ version: UInt32, _ nbImg: UInt8, _ nbLayout: UInt8, _ nbFont: UInt8, _ nbPage: UInt8, _ nbGauge: UInt8) { 40 | self.version = version 41 | self.nbImg = nbImg 42 | self.nbLayout = nbLayout 43 | self.nbFont = nbFont 44 | self.nbPage = nbPage 45 | self.nbGauge = nbGauge 46 | } 47 | 48 | internal static func fromCommandResponseData(_ data: CommandResponseData) -> ConfigurationElementsInfo { 49 | guard data.count >= 9 else { return ConfigurationElementsInfo(0, 0, 0, 0, 0, 0) } 50 | 51 | let version = UInt32.fromUInt32ByteArray(bytes: Array(data[0...3])) 52 | let nbImg = data[4] 53 | let nbLayout = data[5] 54 | let nbFont = data[6] 55 | let nbPage = data[7] 56 | let nbGauge = data[8] 57 | 58 | return ConfigurationElementsInfo(version, nbImg, nbLayout, nbFont, nbPage, nbGauge) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/Classes/Public/DeviceInformation.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Device information published over BlueTooth 19 | public struct DeviceInformation { 20 | 21 | /// The name of the device's manufacturer 22 | public var manufacturerName: String? 23 | 24 | /// The device's model number 25 | public var modelNumber: String? 26 | 27 | /// The device's serial number 28 | public var serialNumber: String? 29 | 30 | /// The device's hardware version 31 | public var hardwareVersion: String? 32 | 33 | /// The device's firmware version 34 | public var firmwareVersion: String? 35 | 36 | /// The device's software version 37 | public var softwareVersion: String? 38 | 39 | init() {} 40 | 41 | init( 42 | _ manufacturerName: String?, 43 | _ modelNumber: String?, 44 | _ serialNumber: String?, 45 | _ hardwareVersion: String?, 46 | _ firmwareVersion: String?, 47 | _ softwareVersion: String? 48 | ) { 49 | self.manufacturerName = manufacturerName 50 | self.modelNumber = modelNumber 51 | self.serialNumber = serialNumber 52 | self.hardwareVersion = hardwareVersion 53 | self.firmwareVersion = firmwareVersion 54 | self.softwareVersion = softwareVersion 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Classes/Public/DiscoveredGlasses.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | import CoreBluetooth 18 | 19 | /// A representation of ActiveLook® glasses, discovered while scanning for Bluetooth devices. 20 | /// 21 | /// Glasses can be discovered while scanning for ActiveLook® devices using the ActiveLookSDK class. 22 | /// Once discovered, it is possible to connect to them by calling the connect(glasses:) method on the discovered object. 23 | /// 24 | /// Once connected, the `onGlassesConnected` callback of the connect method will be called. 25 | /// Upon error, the `onConnectionError` callback of the connect method will be called instead. 26 | /// If the connection is lost, the `onGlassesDisconnected` callback of the connect method will be called. 27 | /// 28 | /// Note: This callback can also be set / changed via the `onDisconnect() method`. 29 | /// 30 | /// Once connected, the connection callback will return a `Glasses` object, which can then be used to send command to the ActiveLook® glasses. 31 | /// 32 | public class DiscoveredGlasses { 33 | 34 | // MARK: - Public properties 35 | 36 | /// The name of the glasses, as advertised over Bluetooth. 37 | public var name: String 38 | 39 | /// The identifier of the glasses, as advertised over Bluetooth. It is not guaranteed to be unique over a certain period and across sessions. 40 | public var identifier: UUID 41 | 42 | /// The manufacturer id as set on the device as a hex string. 43 | public var manufacturerId: String 44 | 45 | // MARK: - Internal properties 46 | 47 | internal var centralManager: CBCentralManager 48 | internal var peripheral: CBPeripheral 49 | 50 | internal var connectionCallback: ((Glasses) -> Void)? 51 | internal var disconnectionCallback: (() -> Void)? 52 | internal var connectionErrorCallback: ((Error) -> Void)? 53 | 54 | // MARK: - Initializers 55 | 56 | internal init(peripheral: CBPeripheral, centralManager: CBCentralManager, advertisementData: [String: Any]) { 57 | self.identifier = peripheral.identifier 58 | self.name = (advertisementData["kCBAdvDataLocalName"] as? String) ?? "Unnamed glasses" 59 | self.manufacturerId = (advertisementData["kCBAdvDataManufacturerData"] as? Data)?.hexEncodedString() ?? "Unknown" 60 | 61 | self.peripheral = peripheral 62 | self.centralManager = centralManager 63 | } 64 | 65 | internal init(peripheral: CBPeripheral, centralManager: CBCentralManager, name: String, manufacturerId: String) { 66 | self.identifier = peripheral.identifier 67 | self.name = name 68 | self.manufacturerId = manufacturerId 69 | 70 | self.peripheral = peripheral 71 | self.centralManager = centralManager 72 | } 73 | 74 | internal init?(with serializedGlasses: SerializedGlasses, centralManager: CBCentralManager) { 75 | 76 | guard let usG = try? serializedGlasses.unserialize() 77 | else { 78 | // throw ActiveLookError.unserializeError 79 | return nil 80 | } 81 | 82 | guard let gUuid = UUID(uuidString: usG.id) 83 | else { 84 | // throw ActiveLookError.unserializeError 85 | return nil 86 | } 87 | 88 | guard let p = centralManager.retrievePeripherals(withIdentifiers: [ gUuid ]).first 89 | else { 90 | return nil 91 | } 92 | 93 | self.peripheral = p 94 | self.identifier = p.identifier 95 | 96 | self.name = usG.name 97 | self.manufacturerId = usG.manId 98 | 99 | self.centralManager = centralManager 100 | } 101 | 102 | 103 | // MARK: - Public methods 104 | 105 | /// Connect to the discovered glasses. Once the connection has been established over Buetooth, 106 | /// the glasses still need to be initialized before being considered as 'connected'. 107 | /// If this step takes too long, a timeout error will be issued via the `onConnectionError` callback. 108 | /// 109 | /// If successful, a `Glasses` object representing connected glasses is returned and can be used to 110 | /// send commands to ActiveLook glasses. 111 | /// 112 | /// - connectionCallback: A callback called asynchronously when the connection is successful. 113 | /// - disconnectionCallback: A callback called asynchronously when the connection to the device is lost. 114 | /// - connectionErrorCallback: A callback called asynchronously when a connection error occurs. 115 | public func connect( 116 | onGlassesConnected connectionCallback: @escaping (Glasses) -> Void, 117 | onGlassesDisconnected disconnectionCallback: @escaping () -> Void, 118 | onConnectionError connectionErrorCallback: @escaping (Error) -> Void 119 | ) { 120 | self.connectionCallback = connectionCallback 121 | self.disconnectionCallback = disconnectionCallback 122 | self.connectionErrorCallback = connectionErrorCallback 123 | 124 | guard self.centralManager.state == .poweredOn else { 125 | connectionErrorCallback(ActiveLookError.bluetoothErrorFromState(state: centralManager.state)) 126 | return 127 | } 128 | 129 | print("connecting to glasses ", name) 130 | centralManager.connect(peripheral, options: nil) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Sources/Classes/Public/FontData.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Data describing a font that can be saved on the device. 19 | public class FontData { 20 | 21 | /// The encoded data representing the font 22 | public let data: [UInt8] 23 | 24 | public init(data: [UInt8]) { 25 | self.data = data 26 | } 27 | 28 | public init(height: UInt8, data: [UInt8]) { 29 | var temp: [UInt8] = [0x01, height] 30 | temp.append(contentsOf: data) 31 | self.data = temp 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Classes/Public/FontInfo.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Data describing a font that can be saved on the device. 19 | public class FontInfo { 20 | 21 | /// The id of the font 22 | public let id: UInt8 23 | 24 | /// The height of characters in pixels 25 | public let height: UInt8 26 | 27 | public init(id: UInt8, height: UInt8) { 28 | self.id = id 29 | self.height = height 30 | } 31 | 32 | internal static func fromCommandResponseData(_ data: CommandResponseData) -> [FontInfo] { 33 | var results: [FontInfo] = [] 34 | var offset = 0 35 | while offset < data.count { 36 | results.append(FontInfo(id: data[offset], height: data[offset+1])) 37 | offset += 2 38 | } 39 | return results 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Classes/Public/FreeSpace.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Information about free space. 19 | public class FreeSpace { 20 | 21 | /// The total available space 22 | public let totalSize: UInt32 23 | 24 | /// The available free space 25 | public let freeSpace: UInt32 26 | 27 | init(_ totalSize: UInt32, _ freeSpace: UInt32) { 28 | self.totalSize = totalSize 29 | self.freeSpace = freeSpace 30 | } 31 | 32 | internal static func fromCommandResponseData(_ data: CommandResponseData) -> FreeSpace { 33 | guard data.count >= 8 else { return FreeSpace(0, 0) } 34 | 35 | let totalSpace = UInt32.fromUInt32ByteArray(bytes: Array(data[0...3])) 36 | let freeSpace = UInt32.fromUInt32ByteArray(bytes: Array(data[4...7])) 37 | 38 | return FreeSpace(totalSpace, freeSpace) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Classes/Public/GaugeInfo.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Information about free space. 19 | public class GaugeInfo { 20 | 21 | /// The x coordinate of the gauge 22 | public let x: Int 23 | 24 | /// The y coordinate of the gauge 25 | public let y: Int 26 | 27 | /// The radius of the gauge 28 | public let r: UInt16 29 | 30 | /// The internal radius of the gauge 31 | public let rin: UInt16 32 | 33 | /// The start angle of the gauge 34 | public let start: UInt8 35 | 36 | /// The end angle of the gauge 37 | public let end: UInt8 38 | 39 | /// The orientation of the gauge 40 | public let clockwise: Bool 41 | 42 | init(_ x: Int, _ y: Int, _ r: UInt16, _ rin: UInt16, _ start: UInt8, _ end: UInt8, _ clockwise: Bool) { 43 | self.x = x 44 | self.y = y 45 | self.r = r 46 | self.rin = rin 47 | self.start = start 48 | self.end = end 49 | self.clockwise = clockwise 50 | } 51 | 52 | internal static func fromCommandResponseData(_ data: CommandResponseData) -> GaugeInfo { 53 | guard data.count >= 10 else { return GaugeInfo(0, 0, 0, 0, 0, 0, false) } 54 | 55 | let x = Int.fromUInt16ByteArray(bytes: Array(data[0...1])) 56 | let y = Int.fromUInt16ByteArray(bytes: Array(data[2...3])) 57 | let r = UInt16.fromUInt16ByteArray(bytes: Array(data[4...5])) 58 | let rin = UInt16.fromUInt16ByteArray(bytes: Array(data[6...7])) 59 | let start = data[8] 60 | let end = data[9] 61 | let clockwise = data[10] != 0 62 | 63 | return GaugeInfo(x, y, r, rin, start, end, clockwise) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Classes/Public/GlassesSettings.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Information about the current glasses settings 19 | public struct GlassesSettings { 20 | 21 | /// The horizontal shift currently set, applied to the whole screen and to all layouts, pages etc... 22 | public let xShift: Int 23 | 24 | /// The vertical shift currently set, applied to the whole screen and to all layouts, pages etc... 25 | public let yShift: Int 26 | 27 | /// The current display luminance, as an Integer between 0 and 15 28 | public let luma: Int 29 | 30 | /// true if the auto brightness adjustment sensor is enabled, false otherwise 31 | public let brightnessAdjustmentEnabled: Bool 32 | 33 | /// true if the auto gesture detection sensor is enabled, false otherwise 34 | public let gestureDetectionEnabled: Bool 35 | 36 | init(_ xShift: Int, _ yShift: Int, _ luma: Int, _ brightnessAdjustmentEnabled: Bool, _ gestureDetectionEnabled: Bool) { 37 | self.xShift = xShift 38 | self.yShift = yShift 39 | self.luma = luma 40 | self.brightnessAdjustmentEnabled = brightnessAdjustmentEnabled 41 | self.gestureDetectionEnabled = gestureDetectionEnabled 42 | } 43 | 44 | internal static func fromCommandResponseData(_ data: CommandResponseData) -> GlassesSettings { 45 | guard data.count >= 5 else { return GlassesSettings(0, 0, 0, false, false) } 46 | 47 | let shiftX = Int(Int8(bitPattern: data[0])) 48 | let shiftY = Int(Int8(bitPattern: data[1])) 49 | let luma = Int(data[2]) 50 | let brightnessAdjustmentEnabled = data[3] == 0x01 51 | let gestureDetectionEnabled = data[4] == 0x01 52 | return GlassesSettings(shiftX, shiftY, luma, brightnessAdjustmentEnabled, gestureDetectionEnabled) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Classes/Public/GlassesUpdate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | public protocol GlassesUpdate { 19 | func getDiscoveredGlasses() -> DiscoveredGlasses 20 | func getState() -> State 21 | func getProgress() -> Double 22 | func getBatteryLevel() -> Int? 23 | func getSourceFirmwareVersion() -> String 24 | func getTargetFirmwareVersion() -> String 25 | func getSourceConfigurationVersion() -> String 26 | func getTargetConfigurationVersion() -> String 27 | } 28 | 29 | 30 | @objc public enum State: Int { 31 | case DOWNLOADING_FIRMWARE 32 | case UPDATING_FIRMWARE // ALSO sent in onUpdateAvailable() 33 | case DOWNLOADING_CONFIGURATION 34 | case UPDATING_CONFIGURATION // ALSO sent in onUpdateAvailable() 35 | case ERROR_UPDATE_FAIL 36 | case ERROR_UPDATE_FAIL_LOW_BATTERY 37 | case ERROR_UPDATE_FORBIDDEN 38 | case ERROR_DOWNGRADE_FORBIDDEN // TODO: ASANA task "Check glasses FW version <= SDK version" – https://app.asana.com/0/1201639829815358/1202209982822311 – 220504 39 | } 40 | 41 | 42 | public class SdkGlassesUpdate: GlassesUpdate { 43 | 44 | private var discoveredGlasses: DiscoveredGlasses? 45 | private var state: State 46 | private var progress: Double 47 | private var batteryLevel: Int? 48 | private var sourceFirmwareVersion: String 49 | private var targetFirmwareVersion: String 50 | private var sourceConfigurationVersion: String 51 | private var targetConfigurationVersion: String 52 | 53 | internal init(for discoveredGlasses: DiscoveredGlasses?, 54 | state : State = .DOWNLOADING_FIRMWARE, 55 | progress: Double = 0, 56 | batteryLevel: Int? = nil, 57 | sourceFirmwareVersion: String = "", 58 | targetFirmwareVersion: String = "", 59 | sourceConfigurationVersion: String = "", 60 | targetConfigurationVersion: String = "" 61 | ) { 62 | self.discoveredGlasses = discoveredGlasses 63 | self.state = state 64 | self.progress = progress 65 | self.batteryLevel = batteryLevel 66 | self.sourceFirmwareVersion = sourceFirmwareVersion 67 | self.targetFirmwareVersion = targetFirmwareVersion 68 | self.sourceConfigurationVersion = sourceConfigurationVersion 69 | self.targetConfigurationVersion = targetConfigurationVersion 70 | } 71 | 72 | public func getDiscoveredGlasses() -> DiscoveredGlasses { 73 | return discoveredGlasses! 74 | } 75 | 76 | public func getState() -> State { 77 | return state 78 | } 79 | 80 | public func getProgress() -> Double { 81 | return progress 82 | } 83 | 84 | public func getBatteryLevel() -> Int? { 85 | return batteryLevel 86 | } 87 | 88 | public func getSourceFirmwareVersion() -> String { 89 | return sourceFirmwareVersion 90 | } 91 | 92 | public func getTargetFirmwareVersion() -> String { 93 | return targetFirmwareVersion 94 | } 95 | 96 | public func getSourceConfigurationVersion() -> String { 97 | return sourceConfigurationVersion 98 | } 99 | 100 | public func getTargetConfigurationVersion() -> String { 101 | return targetConfigurationVersion 102 | } 103 | 104 | public func description() -> String { 105 | return "state: \(state) - progress: \(progress) - battery level: \(batteryLevel != nil ? String(batteryLevel!) : "not available")" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Sources/Classes/Public/GlassesVersion.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | 19 | public enum SoftwareClass: String { 20 | case firmwares 21 | case configurations 22 | } 23 | 24 | internal enum SoftwareLocation: String { 25 | case device 26 | case remote 27 | } 28 | 29 | public struct HardwareProperties { 30 | let manufacturing: Manufacturing 31 | let serialNumber: Int 32 | } 33 | 34 | public struct Manufacturing { 35 | let year: Int 36 | let week: Int 37 | } 38 | 39 | public protocol SoftwareClassProtocol { 40 | var description: String { get } 41 | } 42 | 43 | public struct FirmwareVersion: SoftwareClassProtocol, Equatable { 44 | let major: Int 45 | let minor: Int 46 | let patch: Int 47 | var extra: String? 48 | var path: String? 49 | var error: Error? 50 | 51 | public var description: String { 52 | get { 53 | return "\(major).\(minor).\(patch)\(extra ?? "")" 54 | } 55 | } 56 | 57 | public var version: String { 58 | get { 59 | return "\(major).\(minor).\(patch)\(extra ?? "")" 60 | } 61 | } 62 | 63 | public var minVersion: String { 64 | get { 65 | return "\(major).\(minor).\(patch)" 66 | } 67 | } 68 | 69 | public static func ==(lhs: FirmwareVersion, rhs: FirmwareVersion) -> Bool { 70 | return lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch == rhs.patch 71 | } 72 | 73 | public static func >(lhs: FirmwareVersion, rhs: FirmwareVersion) -> Bool { 74 | 75 | if (lhs.major > rhs.major) { 76 | return true 77 | } 78 | 79 | if (lhs.major == rhs.major) && (lhs.minor > rhs.minor) { 80 | return true 81 | } 82 | 83 | if (lhs.major == rhs.major) && (lhs.minor == rhs.minor) && (lhs.patch > rhs.patch) { 84 | return true 85 | } 86 | 87 | return false 88 | } 89 | } 90 | 91 | public struct ConfigurationVersion: SoftwareClassProtocol, Equatable { 92 | let major: Int 93 | var path: String? 94 | var error: Error? 95 | 96 | public var description: String { 97 | get { 98 | return "\(major)" 99 | } 100 | } 101 | 102 | public static func ==(lhs: ConfigurationVersion, rhs: ConfigurationVersion) -> Bool { 103 | return lhs.major == rhs.major 104 | } 105 | 106 | public static func >(lhs: ConfigurationVersion, rhs: ConfigurationVersion) -> Bool { 107 | return (lhs.major > rhs.major) 108 | } 109 | } 110 | 111 | public struct SoftwareVersions { 112 | var firmware: FirmwareVersion 113 | var configuration: ConfigurationVersion 114 | } 115 | 116 | 117 | /// Information about the glasses 118 | public struct GlassesVersion { 119 | 120 | /// The installed firmware version 121 | public var firmwareVersion: String { 122 | get { 123 | return softwares.firmware.description 124 | } 125 | } 126 | 127 | /// The installed firmware major 128 | public var fwMajor: Int { 129 | get { 130 | return softwares.firmware.major 131 | } 132 | } 133 | 134 | /// The installed firmware minor 135 | public var fwMinor: Int { 136 | get { 137 | return softwares.firmware.minor 138 | } 139 | } 140 | 141 | /// The installed firmware patch 142 | public var fwPatch: Int { 143 | get { 144 | return softwares.firmware.patch 145 | } 146 | } 147 | 148 | /// The year the glasses were manufactured 149 | public var manufacturingYear: Int { 150 | get { 151 | return hardware.manufacturing.year 152 | } 153 | } 154 | 155 | /// The week the glasses were manufactured 156 | public var manufacturingWeek: Int { 157 | get { 158 | return hardware.manufacturing.week 159 | } 160 | } 161 | 162 | /// The glasses serial number 163 | public var serialNumber: Int { 164 | get { 165 | return hardware.serialNumber 166 | } 167 | } 168 | 169 | // The glasses' hardware manufacturing properties 170 | public var hardware : HardwareProperties 171 | 172 | // The glasses' softwares - Firmware AND Configuration - version 173 | public var softwares : SoftwareVersions 174 | 175 | init(_ firmwareVersion: String, 176 | _ manufacturingYear: Int, 177 | _ manufacturingWeek: Int, 178 | _ serialNumber: Int, 179 | _ major : Int, 180 | _ minor : Int, 181 | _ patch : Int, 182 | _ extra : String ) 183 | { 184 | 185 | let manufacturing = Manufacturing(year: manufacturingYear, week: manufacturingYear) 186 | self.hardware = HardwareProperties(manufacturing: manufacturing, 187 | serialNumber: serialNumber) 188 | 189 | let firmware = FirmwareVersion(major: major, minor: minor, patch: patch, extra: extra) 190 | let configuration = ConfigurationVersion(major: 0) 191 | self.softwares = SoftwareVersions(firmware: firmware, configuration: configuration) 192 | } 193 | 194 | 195 | static func fromCommandResponseData(_ data: CommandResponseData) -> GlassesVersion { 196 | guard data.count >= 9 else { return GlassesVersion("unknown", 0, 0, 0, 0, 0, 0, "") } 197 | 198 | let major = Int(data[0]) 199 | let minor = Int(data[1]) 200 | let patch = Int(data[2]) 201 | let extra = String(decoding: [data[3]], as: UTF8.self) 202 | let firmwareVersion = "\(major).\(minor).\(patch)\(extra)" 203 | 204 | let manufacturingYear = Int(data[4]) 205 | let manufacturingWeek = Int(data[5]) 206 | 207 | let serialNumberBytes = Array(data[6...8]) 208 | let serialNumber = Int.fromUInt24ByteArray(bytes: serialNumberBytes) 209 | 210 | return GlassesVersion( firmwareVersion, 211 | manufacturingYear, 212 | manufacturingWeek, 213 | serialNumber, 214 | major, 215 | minor, 216 | patch, 217 | extra ) 218 | } 219 | 220 | } 221 | -------------------------------------------------------------------------------- /Sources/Classes/Public/ImageData.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Data describing an image that can be saved on the device. 19 | public class ImageData { 20 | 21 | /// The width of the image 22 | public let width: UInt16 23 | 24 | /// The encoded data representing the image 25 | public let data: [UInt8] 26 | 27 | public let size: UInt32 28 | 29 | public init() { 30 | self.width = 0 31 | self.data = [] 32 | self.size = 0 33 | } 34 | 35 | public init(width: UInt16, data: [UInt8]) { 36 | self.width = width 37 | self.data = data 38 | self.size = UInt32(data.count) 39 | } 40 | 41 | public init(width: UInt16, data: [UInt8], size: UInt32) { 42 | self.width = width 43 | self.data = data 44 | self.size = size 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/Classes/Public/ImageData1bpp.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Data describing an image that can be saved on the device. 19 | public class ImageData1bpp { 20 | 21 | /// The width of the image 22 | public let width: UInt16 23 | 24 | /// The encoded data representing the image 25 | public let data: [[UInt8]] 26 | 27 | public let size: UInt32 28 | 29 | public init() { 30 | self.width = 0 31 | self.data = [] 32 | self.size = 0 33 | } 34 | 35 | public init(width: UInt16, data: [[UInt8]]) { 36 | self.width = width 37 | self.data = data 38 | self.size = UInt32(data.count * data[0].count) 39 | } 40 | 41 | public init(width: UInt16, data: [[UInt8]], size: UInt32) { 42 | self.width = width 43 | self.data = data 44 | self.size = size 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/Classes/Public/ImageInfo.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Information about an Image. Not an actual image object. 19 | public class ImageInfo { 20 | 21 | /// The image id 22 | public let id: Int 23 | 24 | /// The image width in pixels 25 | public let width: Int 26 | 27 | /// The image height in pixels 28 | public let height: Int 29 | 30 | init(_ id: Int, _ width: Int, _ height: Int) { 31 | self.id = id 32 | self.width = width 33 | self.height = height 34 | } 35 | 36 | internal static func fromCommandResponseData(_ data: CommandResponseData) -> ImageInfo { 37 | guard data.count >= 5 else { return ImageInfo(0, 0, 0) } 38 | 39 | let id = Int(data[0]) 40 | let width = Int.fromUInt16ByteArray(bytes: [data[1], data[2]]) 41 | let height = Int.fromUInt16ByteArray(bytes: [data[3], data[4]]) 42 | 43 | return ImageInfo(id, width, height) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Classes/Public/ImgSaveFmt.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Supported image saving format 19 | @objc public enum ImgSaveFmt: UInt8 { 20 | case MONO_4BPP = 0 ///4bpp 21 | case MONO_1BPP = 1 ///1bpp, transformed into 4bpp by the firmware before saving 22 | case MONO_4BPP_HEATSHRINK = 2 ///4bpp with Heatshrink compression, decompressed into 4bpp by the firmware before saving 23 | case MONO_4BPP_HEATSHRINK_SAVE_COMP = 3 ///4bpp with Heatshrink compression, stored compressed, decompressed into 4bpp before display 24 | } 25 | 26 | /// Supported image streaming format 27 | @objc public enum ImgStreamFmt: UInt8 { 28 | case MONO_1BPP = 1 ///1bpp 29 | case MONO_4BPP_HEATSHRINK = 2 ///4bpp with Heatshrink compression 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Classes/Public/LayoutExtraCmd.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Parameters defining a layout 19 | public class LayoutExtraCmd { 20 | 21 | 22 | // TODO Add additional commands 23 | public var subCommands: [UInt8] 24 | 25 | public init(){ 26 | self.subCommands = [] 27 | } 28 | 29 | public func addSubCommandBitmap(id: UInt8, x: Int16, y: Int16){ 30 | self.subCommands.append(0x00) 31 | self.subCommands.append(id) 32 | self.subCommands += x.asUInt8Array 33 | self.subCommands += y.asUInt8Array 34 | } 35 | 36 | public func addSubCommandCirc(x: UInt16, y: UInt16, r: UInt16){ 37 | self.subCommands.append(0x01) 38 | self.subCommands += x.asUInt8Array 39 | self.subCommands += y.asUInt8Array 40 | self.subCommands += r.asUInt8Array 41 | } 42 | 43 | public func addSubCommandCircf(x: UInt16, y: UInt16, r: UInt16){ 44 | self.subCommands.append(0x02) 45 | self.subCommands += x.asUInt8Array 46 | self.subCommands += y.asUInt8Array 47 | self.subCommands += r.asUInt8Array 48 | } 49 | 50 | public func addSubCommandColor(c: UInt8){ 51 | self.subCommands.append(0x03) 52 | self.subCommands.append(c) 53 | } 54 | 55 | public func addSubCommandFont(f: UInt8){ 56 | self.subCommands.append(0x04) 57 | self.subCommands.append(f) 58 | } 59 | 60 | public func addSubCommandLine(x1: UInt16, y1: UInt16, x2: UInt16, y2: UInt16){ 61 | self.subCommands.append(0x05) 62 | self.subCommands += x1.asUInt8Array 63 | self.subCommands += y1.asUInt8Array 64 | self.subCommands += x2.asUInt8Array 65 | self.subCommands += y2.asUInt8Array 66 | } 67 | 68 | public func addSubCommandPoint(x: UInt8, y: UInt16){ 69 | self.subCommands.append(0x06) 70 | self.subCommands.append(x) 71 | self.subCommands += y.asUInt8Array 72 | } 73 | 74 | public func addSubCommandRect(x1: UInt16, y1: UInt16, x2: UInt16, y2: UInt16) { 75 | self.subCommands.append(0x07) 76 | self.subCommands += x1.asUInt8Array 77 | self.subCommands += y1.asUInt8Array 78 | self.subCommands += x2.asUInt8Array 79 | self.subCommands += y2.asUInt8Array 80 | } 81 | 82 | public func addSubCommandRectf(x1: UInt16, y1: UInt16, x2: UInt16, y2: UInt16){ 83 | self.subCommands.append(0x08) 84 | self.subCommands += x1.asUInt8Array 85 | self.subCommands += y1.asUInt8Array 86 | self.subCommands += x2.asUInt8Array 87 | self.subCommands += y2.asUInt8Array 88 | } 89 | 90 | public func addSubCommandText(x: UInt16, y: UInt16, txt: String) { 91 | self.subCommands.append(0x09) 92 | self.subCommands += x.asUInt8Array 93 | self.subCommands += y.asUInt8Array 94 | self.subCommands.append(UInt8(txt.asNullTerminatedUInt8Array.count)) 95 | self.subCommands += Array(txt.asNullTerminatedUInt8Array) 96 | } 97 | 98 | public func addSubCommandGauge(gaugeId: UInt8) { 99 | self.subCommands.append(0x0A) 100 | self.subCommands.append(gaugeId) 101 | } 102 | 103 | public func addSubCommandAnim(handlerId: UInt8, id: UInt8, delay: Int16, repeatAnim: UInt8, x: Int16, y: Int16) { 104 | self.subCommands.append(0x0B) 105 | self.subCommands.append(handlerId) 106 | self.subCommands.append(id) 107 | self.subCommands += delay.asUInt8Array 108 | self.subCommands.append(repeatAnim) 109 | self.subCommands += x.asUInt8Array 110 | self.subCommands += y.asUInt8Array 111 | } 112 | 113 | 114 | public func toCommandData() -> [UInt8] { 115 | var data: [UInt8] = [] 116 | data.append(contentsOf: self.subCommands) 117 | return data 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Sources/Classes/Public/LayoutParameters.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Parameters defining a layout 19 | public class LayoutParameters { 20 | 21 | /// The layout ID 22 | public let id: UInt8 23 | 24 | /// The x coordinate of the start position (upper left) of the layout area 25 | public let x: UInt16 26 | 27 | /// The y coordinate of the start position (upper left) of the layout area 28 | public let y: UInt8 29 | 30 | /// The width of the layout area 31 | public let width: UInt16 32 | 33 | /// The height of the layout area 34 | public let height: UInt8 35 | 36 | /// The foreground color of the layout area from 0 to 15 37 | public let foregroundColor: UInt8 38 | 39 | /// The background color of the layout area from 0 to 15 40 | public let backgroundColor: UInt8 41 | 42 | /// The font used to draw the text value of the layout 43 | public let font: UInt8 44 | 45 | /// Define if the argument of the layout is displayed using text 46 | public let textValid: Bool 47 | 48 | /// Define the x coordinate of the position of the text value in the layout area 49 | public let textX: UInt16 50 | 51 | /// Define the y coordinate of the position of the text value in the layout area 52 | public let textY: UInt8 53 | 54 | /// Define the text rotation 55 | public let textRotation: TextRotation 56 | 57 | /// Define the argument opacity(on/off) 58 | public let textOpacity: Bool 59 | 60 | // TODO Add additional commands 61 | public var subCommands: [UInt8] 62 | 63 | public init( 64 | id: UInt8, 65 | x: UInt16, 66 | y: UInt8, 67 | width: UInt16, 68 | height: UInt8, 69 | foregroundColor: UInt8, 70 | backgroundColor: UInt8, 71 | font: UInt8, 72 | textValid: Bool, 73 | textX: UInt16, 74 | textY: UInt8, 75 | textRotation: TextRotation, 76 | textOpacity: Bool) { 77 | 78 | self.id = id 79 | self.x = x 80 | self.y = y 81 | self.width = width 82 | self.height = height 83 | self.foregroundColor = foregroundColor 84 | self.backgroundColor = backgroundColor 85 | self.font = font 86 | self.textValid = textValid 87 | self.textX = textX 88 | self.textY = textY 89 | self.textRotation = textRotation 90 | self.textOpacity = textOpacity 91 | self.subCommands = [] 92 | } 93 | 94 | public func addSubCommandBitmap(id: UInt8, x: Int16, y: Int16) -> LayoutParameters { 95 | self.subCommands.append(0x00) 96 | self.subCommands.append(id) 97 | self.subCommands += x.asUInt8Array 98 | self.subCommands += y.asUInt8Array 99 | return self 100 | } 101 | 102 | public func addSubCommandCirc(x: UInt16, y: UInt16, r: UInt16) -> LayoutParameters { 103 | self.subCommands.append(0x01) 104 | self.subCommands += x.asUInt8Array 105 | self.subCommands += y.asUInt8Array 106 | self.subCommands += r.asUInt8Array 107 | return self 108 | } 109 | 110 | public func addSubCommandCircf(x: UInt16, y: UInt16, r: UInt16) -> LayoutParameters { 111 | self.subCommands.append(0x02) 112 | self.subCommands += x.asUInt8Array 113 | self.subCommands += y.asUInt8Array 114 | self.subCommands += r.asUInt8Array 115 | return self 116 | } 117 | 118 | public func addSubCommandColor(c: UInt8) -> LayoutParameters { 119 | self.subCommands.append(0x03) 120 | self.subCommands.append(c) 121 | return self 122 | } 123 | 124 | public func addSubCommandFont(f: UInt8) -> LayoutParameters { 125 | self.subCommands.append(0x04) 126 | self.subCommands.append(f) 127 | return self 128 | } 129 | 130 | public func addSubCommandLine(x1: UInt16, y1: UInt16, x2: UInt16, y2: UInt16) -> LayoutParameters { 131 | self.subCommands.append(0x05) 132 | self.subCommands += x1.asUInt8Array 133 | self.subCommands += y1.asUInt8Array 134 | self.subCommands += x2.asUInt8Array 135 | self.subCommands += y2.asUInt8Array 136 | return self 137 | } 138 | 139 | public func addSubCommandPoint(x: UInt8, y: UInt16) -> LayoutParameters { 140 | self.subCommands.append(0x06) 141 | self.subCommands.append(x) 142 | self.subCommands += y.asUInt8Array 143 | return self 144 | } 145 | 146 | public func addSubCommandRect(x1: UInt16, y1: UInt16, x2: UInt16, y2: UInt16) -> LayoutParameters { 147 | self.subCommands.append(0x07) 148 | self.subCommands += x1.asUInt8Array 149 | self.subCommands += y1.asUInt8Array 150 | self.subCommands += x2.asUInt8Array 151 | self.subCommands += y2.asUInt8Array 152 | return self 153 | } 154 | 155 | public func addSubCommandRectf(x1: UInt16, y1: UInt16, x2: UInt16, y2: UInt16) -> LayoutParameters { 156 | self.subCommands.append(0x08) 157 | self.subCommands += x1.asUInt8Array 158 | self.subCommands += y1.asUInt8Array 159 | self.subCommands += x2.asUInt8Array 160 | self.subCommands += y2.asUInt8Array 161 | return self 162 | } 163 | 164 | public func addSubCommandText(x: UInt16, y: UInt16, txt: String) -> LayoutParameters { 165 | self.subCommands.append(0x09) 166 | self.subCommands += x.asUInt8Array 167 | self.subCommands += y.asUInt8Array 168 | self.subCommands.append(UInt8(txt.asNullTerminatedUInt8Array.count)) 169 | self.subCommands += Array(txt.asNullTerminatedUInt8Array) 170 | return self 171 | } 172 | 173 | public func addSubCommandGauge(gaugeId: UInt8) -> LayoutParameters { 174 | self.subCommands.append(0x0A) 175 | self.subCommands.append(gaugeId) 176 | return self 177 | } 178 | 179 | 180 | public func toCommandData() -> [UInt8] { 181 | var data: [UInt8] = [] 182 | 183 | data.append(self.id) 184 | data.append(UInt8(self.subCommands.count)) 185 | data.append(contentsOf: self.x.asUInt8Array) 186 | data.append(self.y) 187 | data.append(contentsOf: self.width.asUInt8Array) 188 | data.append(self.height) 189 | data.append(self.foregroundColor) 190 | data.append(self.backgroundColor) 191 | data.append(self.font) 192 | data.append(self.textValid ? 0x01 : 0x00) 193 | data.append(contentsOf: self.textX.asUInt8Array) 194 | data.append(self.textY) 195 | data.append(self.textRotation.rawValue) 196 | data.append(self.textOpacity ? 0x01 : 0x00) 197 | data.append(contentsOf: self.subCommands) 198 | 199 | return data 200 | } 201 | 202 | internal static func fromCommandResponseData(_ data: CommandResponseData) -> LayoutParameters { 203 | guard data.count >= 10 else { return LayoutParameters(id: 0, x: 0, y: 0, width: 0, height: 0, foregroundColor: 0, backgroundColor: 0, font: 0, textValid: false, textX: 0, textY: 0, textRotation: .topLR, textOpacity: false) } 204 | 205 | let id = 0 206 | let x = UInt16.fromUInt16ByteArray(bytes: Array(data[1...2])) 207 | let y = data[3] 208 | let width = UInt16.fromUInt16ByteArray(bytes: Array(data[4...5])) 209 | let height = data[6] 210 | let foregroundColor = data[7] 211 | let backgroundColor = data[8] 212 | let font = data[9] 213 | let textValid = data[10] != 0x00 214 | let textX = UInt16.fromUInt16ByteArray(bytes: Array(data[11...12])) 215 | let textY = data[13] 216 | let textRotation = TextRotation(rawValue: data[12]) ?? .topLR 217 | let textOpacity = data[15] != 0x00 218 | 219 | return LayoutParameters(id: UInt8(id), x: x, y: y, width: width, height: height, foregroundColor: foregroundColor, backgroundColor: backgroundColor, font: font, textValid: textValid, textX: textX, textY: textY, textRotation: textRotation, textOpacity: textOpacity) 220 | } 221 | 222 | // MARK: - Defined for non-native integration 223 | @objc public func toString() -> NSString { 224 | var str: String = "" 225 | str.append("\(self.id)") 226 | str.append("\(self.subCommands.count)") 227 | str.append("\(self.x.asUInt8Array)") 228 | str.append("\(self.y)") 229 | str.append("\(self.width.asUInt8Array)") 230 | str.append("\(self.height)") 231 | str.append("\(self.foregroundColor)") 232 | str.append("\(self.backgroundColor)") 233 | str.append("\(self.font)") 234 | str.append("\(self.textValid ? 0x01 : 0x00)") 235 | str.append("\(self.textX.asUInt8Array)") 236 | str.append("\(self.textY)") 237 | str.append("\(self.textRotation.rawValue)") 238 | str.append("\(self.textOpacity ? 0x01 : 0x00)") 239 | str.append("\(self.subCommands)") 240 | return NSString.init(string: str) 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /Sources/Classes/Public/PageInfo.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Information about a page. 19 | public class PageInfo { 20 | 21 | /// The page id 22 | public let id: UInt8 23 | 24 | /// The page payload 25 | public let payload: [UInt8] 26 | 27 | init(_ id: UInt8, _ layoutIds: [UInt8], _ xs: [Int16], _ ys: [UInt8]) { 28 | self.id = id 29 | var payload: [UInt8] = [id] 30 | for i in (0.. PageInfo { 44 | guard data.count >= 1 else { return PageInfo(0, [], [], []) } 45 | return PageInfo(data) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Classes/Public/SensorParameters.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | /// Parameters of the optical sensor. 19 | public class SensorParameters { 20 | 21 | /// The ALS array to compare to when luma is changed. 22 | public let alsArray: [UInt16] 23 | 24 | /// The ALS period. 25 | public let alsPeriod: UInt16 26 | 27 | /// The ranging period. 28 | public let rangingPeriod: UInt16 29 | 30 | public init(alsArray: [UInt16], alsPeriod: UInt16, rangingPeriod: UInt16) { 31 | self.alsArray = alsArray 32 | self.alsPeriod = alsPeriod < 250 ? 250 : alsPeriod 33 | self.rangingPeriod = rangingPeriod < 250 ? 250 : rangingPeriod 34 | } 35 | 36 | internal static func fromCommandResponseData(_ data: CommandResponseData) -> SensorParameters { 37 | guard data.count >= 22 else { return SensorParameters(alsArray: [], alsPeriod: 0, rangingPeriod: 0) } 38 | 39 | let alsArrayData = Array(data[0...17]) 40 | var alsArray: [UInt16] = [] 41 | 42 | for arrayItem in alsArrayData.chunked(into: 2) { 43 | alsArray.append(UInt16.fromUInt16ByteArray(bytes: [arrayItem[0], arrayItem[1]])) 44 | } 45 | 46 | let alsPeriod = UInt16.fromUInt16ByteArray(bytes: [data[18], data[19]]) 47 | let rangingPeriod = UInt16.fromUInt16ByteArray(bytes: [data[20], data[21]]) 48 | 49 | return SensorParameters(alsArray: alsArray, alsPeriod: alsPeriod, rangingPeriod: rangingPeriod) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Classes/Public/WidgetType.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2021 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import Foundation 17 | 18 | //Format of the widget 19 | @objc public enum WidgetSize: UInt8 { 20 | case large = 0 //Dimensions 244px * 122px 21 | case thin = 1 //Dimensions 244px * 61px 22 | case half = 2 //Dimensions 122px * 61px 23 | } 24 | 25 | //Format of the shownValue 26 | @objc public enum WidgetValueType: UInt8 { 27 | case text = 0 //Doesn't change shownValue 28 | case number = 1 //Add thousands separator : "1234567" → "1 234 567"Split the decimals on "." or ",". Ex : "12.34" → "12." + "34 29 | case duration_hms = 2 //Split in 3 on ":". Ex : "0:55:35" → "0:" + "55:" + "35" 30 | case duration_hm = 3 //Split in 2 on ":". Ex : "0:55" → "0:" + "55" 31 | case duration_ms = 4 //Split in 2 on ":". Ex : "55:35" → "55:" + "35" 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Heatshrink/RNHeatshrinkDecoder.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNHeatshrinkDecoder.m 3 | // Pods 4 | // 5 | // Created by Rafael Nobre on 06/05/17. 6 | // 7 | // 8 | 9 | #import "RNHeatshrinkDecoder.h" 10 | #import "heatshrink_decoder.h" 11 | 12 | @interface RNHeatshrinkDecoder() 13 | 14 | @property (nonatomic) NSInteger inputBufferSize; 15 | @property (nonatomic) NSInteger windowBitSize; 16 | @property (nonatomic) NSInteger windowSize; 17 | @property (nonatomic) NSInteger lookaheadBitSize; 18 | @property (nonatomic) NSInteger lookaheadSize; 19 | @property (nonatomic) heatshrink_decoder *decoder; 20 | 21 | @end 22 | 23 | @implementation RNHeatshrinkDecoder 24 | 25 | - (instancetype)init { 26 | if (self = [self initWithWindowSize:10 andLookaheadSize:5]) { 27 | 28 | } 29 | return self; 30 | } 31 | 32 | - (instancetype)initWithWindowSize:(NSInteger)windowBitSize andLookaheadSize:(NSInteger)lookaheadBitSize { 33 | if (self = [self initWithBufferSize:4096 windowSize:windowBitSize andLookaheadSize:lookaheadBitSize]) { 34 | 35 | } 36 | return self; 37 | } 38 | 39 | - (instancetype)initWithBufferSize:(NSInteger)inputBufferSize windowSize:(NSInteger)windowBitSize andLookaheadSize:(NSInteger)lookaheadBitSize { 40 | if (self = [super init]) { 41 | _inputBufferSize = inputBufferSize; 42 | _windowBitSize = windowBitSize; 43 | _windowSize = 1 << windowBitSize; 44 | _lookaheadBitSize = lookaheadBitSize; 45 | _lookaheadSize = 1 << lookaheadBitSize; 46 | _decoder = heatshrink_decoder_alloc(inputBufferSize, windowBitSize, lookaheadBitSize); 47 | } 48 | return self; 49 | } 50 | 51 | static void die(NSString *message) { 52 | [NSException raise:@"RNHeatshrinkDieException" format:@"Reason: %@", message]; 53 | } 54 | 55 | - (NSData *)decodeData:(NSData *)dataToDecode { 56 | NSMutableData *output = [[NSMutableData alloc] initWithCapacity:dataToDecode.length * 2]; 57 | 58 | if (dataToDecode.length == 0) { 59 | return output; 60 | } 61 | 62 | heatshrink_decoder_reset(_decoder); 63 | 64 | uint8_t *data = (uint8_t*)[dataToDecode bytes]; 65 | size_t data_sz = [dataToDecode length]; 66 | size_t sink_sz = 0; 67 | size_t poll_sz = 0; 68 | size_t out_sz = 4096; 69 | uint8_t out_buf[out_sz]; 70 | memset(out_buf, 0, out_sz); 71 | 72 | HSD_sink_res sres; 73 | HSD_poll_res pres; 74 | HSD_finish_res fres; 75 | 76 | size_t sunk = 0; 77 | 78 | do { 79 | if (sunk < data_sz) { 80 | sres = heatshrink_decoder_sink(_decoder, &data[sunk], data_sz - sunk, &sink_sz); 81 | if (sres < 0) { die(@"sink"); } 82 | sunk += sink_sz; 83 | } 84 | 85 | do { 86 | pres = heatshrink_decoder_poll(_decoder, out_buf, out_sz, &poll_sz); 87 | if (pres < 0) { die(@"poll"); } 88 | [output appendBytes:out_buf length:poll_sz]; 89 | } while (pres == HSDR_POLL_MORE); 90 | 91 | if (sunk == data_sz) { 92 | fres = heatshrink_decoder_finish(_decoder); 93 | if (fres < 0) { die(@"finish"); } 94 | if (fres == HSDR_FINISH_DONE) { break; } 95 | } 96 | 97 | } while (1); 98 | 99 | return output; 100 | } 101 | 102 | - (void)dealloc { 103 | heatshrink_decoder_free(_decoder); 104 | } 105 | 106 | @end 107 | -------------------------------------------------------------------------------- /Sources/Heatshrink/RNHeatshrinkEncoder.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNHeatshrinkEncoder.m 3 | // Pods 4 | // 5 | // Created by Rafael Nobre on 06/05/17. 6 | // 7 | // 8 | 9 | #import "RNHeatshrinkEncoder.h" 10 | #import "heatshrink_encoder.h" 11 | 12 | @interface RNHeatshrinkEncoder() 13 | 14 | @property (nonatomic) NSInteger windowBitSize; 15 | @property (nonatomic) NSInteger windowSize; 16 | @property (nonatomic) NSInteger lookaheadBitSize; 17 | @property (nonatomic) NSInteger lookaheadSize; 18 | @property (nonatomic) heatshrink_encoder *encoder; 19 | 20 | @end 21 | 22 | @implementation RNHeatshrinkEncoder 23 | 24 | - (instancetype)initWithWindowSize:(NSInteger)windowBitSize andLookaheadSize:(NSInteger)lookaheadBitSize { 25 | if (self = [super init]) { 26 | _windowBitSize = windowBitSize; 27 | _windowSize = 1 << windowBitSize; 28 | _lookaheadBitSize = lookaheadBitSize; 29 | _lookaheadSize = 1 << lookaheadBitSize; 30 | _encoder = heatshrink_encoder_alloc(windowBitSize, lookaheadBitSize); 31 | heatshrink_encoder_reset(_encoder); 32 | } 33 | return self; 34 | } 35 | 36 | - (instancetype)init { 37 | if (self = [self initWithWindowSize:10 andLookaheadSize:5]) { 38 | 39 | } 40 | return self; 41 | } 42 | 43 | static void die(NSString *message) { 44 | [NSException raise:@"RNHeatshrinkDieException" format:@"Reason: %@", message]; 45 | } 46 | 47 | - (NSData *)encodeData:(NSData *)dataToEncode { 48 | NSMutableData *output = [[NSMutableData alloc] initWithCapacity:dataToEncode.length * 2]; 49 | 50 | if (dataToEncode.length == 0) { 51 | return output; 52 | } 53 | 54 | heatshrink_encoder_reset(_encoder); 55 | 56 | uint8_t *data = (uint8_t*)[dataToEncode bytes]; 57 | size_t data_sz = [dataToEncode length]; 58 | size_t sink_sz = 0; 59 | size_t poll_sz = 0; 60 | size_t out_sz = 4096; 61 | uint8_t out_buf[out_sz]; 62 | memset(out_buf, 0, out_sz); 63 | 64 | HSE_sink_res sres; 65 | HSE_poll_res pres; 66 | HSE_finish_res fres; 67 | 68 | size_t sunk = 0; 69 | 70 | do { 71 | if (sunk < data_sz) { 72 | sres = heatshrink_encoder_sink(_encoder, &data[sunk], data_sz - sunk, &sink_sz); 73 | if (sres < 0) { die(@"sink"); } 74 | sunk += sink_sz; 75 | } 76 | 77 | do { 78 | pres = heatshrink_encoder_poll(_encoder, out_buf, out_sz, &poll_sz); 79 | if (pres < 0) { die(@"poll"); } 80 | [output appendBytes:out_buf length:poll_sz]; 81 | } while (pres == HSER_POLL_MORE); 82 | 83 | if (sunk == data_sz) { 84 | fres = heatshrink_encoder_finish(_encoder); 85 | if (fres < 0) { die(@"finish"); } 86 | if (fres == HSER_FINISH_DONE) { break; } 87 | } 88 | 89 | } while (1); 90 | 91 | return output; 92 | } 93 | 94 | 95 | - (void)dealloc { 96 | heatshrink_encoder_free(_encoder); 97 | } 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /Sources/Heatshrink/heatshrink_decoder.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "heatshrink_decoder.h" 4 | 5 | /* States for the polling state machine. */ 6 | typedef enum { 7 | HSDS_TAG_BIT, /* tag bit */ 8 | HSDS_YIELD_LITERAL, /* ready to yield literal byte */ 9 | HSDS_BACKREF_INDEX_MSB, /* most significant byte of index */ 10 | HSDS_BACKREF_INDEX_LSB, /* least significant byte of index */ 11 | HSDS_BACKREF_COUNT_MSB, /* most significant byte of count */ 12 | HSDS_BACKREF_COUNT_LSB, /* least significant byte of count */ 13 | HSDS_YIELD_BACKREF, /* ready to yield back-reference */ 14 | } HSD_state; 15 | 16 | #if HEATSHRINK_DEBUGGING_LOGS 17 | #include 18 | #include 19 | #include 20 | #define LOG(...) fprintf(stderr, __VA_ARGS__) 21 | #define ASSERT(X) assert(X) 22 | static const char *state_names[] = { 23 | "tag_bit", 24 | "yield_literal", 25 | "backref_index_msb", 26 | "backref_index_lsb", 27 | "backref_count_msb", 28 | "backref_count_lsb", 29 | "yield_backref", 30 | }; 31 | #else 32 | #define LOG(...) /* no-op */ 33 | #define ASSERT(X) /* no-op */ 34 | #endif 35 | 36 | typedef struct { 37 | uint8_t *buf; /* output buffer */ 38 | size_t buf_size; /* buffer size */ 39 | size_t *output_size; /* bytes pushed to buffer, so far */ 40 | } output_info; 41 | 42 | #define NO_BITS ((uint16_t)-1) 43 | 44 | /* Forward references. */ 45 | static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count); 46 | static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte); 47 | 48 | #if HEATSHRINK_DYNAMIC_ALLOC 49 | heatshrink_decoder *heatshrink_decoder_alloc(uint16_t input_buffer_size, 50 | uint8_t window_sz2, 51 | uint8_t lookahead_sz2) { 52 | if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) || 53 | (window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) || 54 | (input_buffer_size == 0) || 55 | (lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) || 56 | (lookahead_sz2 >= window_sz2)) { 57 | return NULL; 58 | } 59 | size_t buffers_sz = (1 << window_sz2) + input_buffer_size; 60 | size_t sz = sizeof(heatshrink_decoder) + buffers_sz; 61 | heatshrink_decoder *hsd = HEATSHRINK_MALLOC(sz); 62 | if (hsd == NULL) { return NULL; } 63 | hsd->input_buffer_size = input_buffer_size; 64 | hsd->window_sz2 = window_sz2; 65 | hsd->lookahead_sz2 = lookahead_sz2; 66 | heatshrink_decoder_reset(hsd); 67 | LOG("-- allocated decoder with buffer size of %zu (%zu + %u + %u)\n", 68 | sz, sizeof(heatshrink_decoder), (1 << window_sz2), input_buffer_size); 69 | return hsd; 70 | } 71 | 72 | void heatshrink_decoder_free(heatshrink_decoder *hsd) { 73 | size_t buffers_sz = (1 << hsd->window_sz2) + hsd->input_buffer_size; 74 | size_t sz = sizeof(heatshrink_decoder) + buffers_sz; 75 | HEATSHRINK_FREE(hsd, sz); 76 | (void)sz; /* may not be used by free */ 77 | } 78 | #endif 79 | 80 | void heatshrink_decoder_reset(heatshrink_decoder *hsd) { 81 | size_t buf_sz = 1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd); 82 | size_t input_sz = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd); 83 | memset(hsd->buffers, 0, buf_sz + input_sz); 84 | hsd->state = HSDS_TAG_BIT; 85 | hsd->input_size = 0; 86 | hsd->input_index = 0; 87 | hsd->bit_index = 0x00; 88 | hsd->current_byte = 0x00; 89 | hsd->output_count = 0; 90 | hsd->output_index = 0; 91 | hsd->head_index = 0; 92 | } 93 | 94 | /* Copy SIZE bytes into the decoder's input buffer, if it will fit. */ 95 | HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, 96 | uint8_t *in_buf, size_t size, size_t *input_size) { 97 | if ((hsd == NULL) || (in_buf == NULL) || (input_size == NULL)) { 98 | return HSDR_SINK_ERROR_NULL; 99 | } 100 | 101 | size_t rem = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd) - hsd->input_size; 102 | if (rem == 0) { 103 | *input_size = 0; 104 | return HSDR_SINK_FULL; 105 | } 106 | 107 | size = rem < size ? rem : size; 108 | LOG("-- sinking %zd bytes\n", size); 109 | /* copy into input buffer (at head of buffers) */ 110 | memcpy(&hsd->buffers[hsd->input_size], in_buf, size); 111 | hsd->input_size += size; 112 | *input_size = size; 113 | return HSDR_SINK_OK; 114 | } 115 | 116 | 117 | /***************** 118 | * Decompression * 119 | *****************/ 120 | 121 | #define BACKREF_COUNT_BITS(HSD) (HEATSHRINK_DECODER_LOOKAHEAD_BITS(HSD)) 122 | #define BACKREF_INDEX_BITS(HSD) (HEATSHRINK_DECODER_WINDOW_BITS(HSD)) 123 | 124 | // States 125 | static HSD_state st_tag_bit(heatshrink_decoder *hsd); 126 | static HSD_state st_yield_literal(heatshrink_decoder *hsd, 127 | output_info *oi); 128 | static HSD_state st_backref_index_msb(heatshrink_decoder *hsd); 129 | static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd); 130 | static HSD_state st_backref_count_msb(heatshrink_decoder *hsd); 131 | static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd); 132 | static HSD_state st_yield_backref(heatshrink_decoder *hsd, 133 | output_info *oi); 134 | 135 | HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, 136 | uint8_t *out_buf, size_t out_buf_size, size_t *output_size) { 137 | if ((hsd == NULL) || (out_buf == NULL) || (output_size == NULL)) { 138 | return HSDR_POLL_ERROR_NULL; 139 | } 140 | *output_size = 0; 141 | 142 | output_info oi; 143 | oi.buf = out_buf; 144 | oi.buf_size = out_buf_size; 145 | oi.output_size = output_size; 146 | 147 | while (1) { 148 | LOG("-- poll, state is %d (%s), input_size %d\n", 149 | hsd->state, state_names[hsd->state], hsd->input_size); 150 | uint8_t in_state = hsd->state; 151 | switch (in_state) { 152 | case HSDS_TAG_BIT: 153 | hsd->state = st_tag_bit(hsd); 154 | break; 155 | case HSDS_YIELD_LITERAL: 156 | hsd->state = st_yield_literal(hsd, &oi); 157 | break; 158 | case HSDS_BACKREF_INDEX_MSB: 159 | hsd->state = st_backref_index_msb(hsd); 160 | break; 161 | case HSDS_BACKREF_INDEX_LSB: 162 | hsd->state = st_backref_index_lsb(hsd); 163 | break; 164 | case HSDS_BACKREF_COUNT_MSB: 165 | hsd->state = st_backref_count_msb(hsd); 166 | break; 167 | case HSDS_BACKREF_COUNT_LSB: 168 | hsd->state = st_backref_count_lsb(hsd); 169 | break; 170 | case HSDS_YIELD_BACKREF: 171 | hsd->state = st_yield_backref(hsd, &oi); 172 | break; 173 | default: 174 | return HSDR_POLL_ERROR_UNKNOWN; 175 | } 176 | 177 | /* If the current state cannot advance, check if input or output 178 | * buffer are exhausted. */ 179 | if (hsd->state == in_state) { 180 | if (*output_size == out_buf_size) { return HSDR_POLL_MORE; } 181 | return HSDR_POLL_EMPTY; 182 | } 183 | } 184 | } 185 | 186 | static HSD_state st_tag_bit(heatshrink_decoder *hsd) { 187 | uint32_t bits = get_bits(hsd, 1); // get tag bit 188 | if (bits == NO_BITS) { 189 | return HSDS_TAG_BIT; 190 | } else if (bits) { 191 | return HSDS_YIELD_LITERAL; 192 | } else if (HEATSHRINK_DECODER_WINDOW_BITS(hsd) > 8) { 193 | return HSDS_BACKREF_INDEX_MSB; 194 | } else { 195 | hsd->output_index = 0; 196 | return HSDS_BACKREF_INDEX_LSB; 197 | } 198 | } 199 | 200 | static HSD_state st_yield_literal(heatshrink_decoder *hsd, 201 | output_info *oi) { 202 | /* Emit a repeated section from the window buffer, and add it (again) 203 | * to the window buffer. (Note that the repetition can include 204 | * itself.)*/ 205 | if (*oi->output_size < oi->buf_size) { 206 | uint16_t byte = get_bits(hsd, 8); 207 | if (byte == NO_BITS) { return HSDS_YIELD_LITERAL; } /* out of input */ 208 | uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; 209 | uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; 210 | uint8_t c = byte & 0xFF; 211 | LOG("-- emitting literal byte 0x%02x ('%c')\n", c, isprint(c) ? c : '.'); 212 | buf[hsd->head_index++ & mask] = c; 213 | push_byte(hsd, oi, c); 214 | return HSDS_TAG_BIT; 215 | } else { 216 | return HSDS_YIELD_LITERAL; 217 | } 218 | } 219 | 220 | static HSD_state st_backref_index_msb(heatshrink_decoder *hsd) { 221 | uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); 222 | ASSERT(bit_ct > 8); 223 | uint16_t bits = get_bits(hsd, bit_ct - 8); 224 | LOG("-- backref index (msb), got 0x%04x (+1)\n", bits); 225 | if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_MSB; } 226 | hsd->output_index = bits << 8; 227 | return HSDS_BACKREF_INDEX_LSB; 228 | } 229 | 230 | static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd) { 231 | uint8_t bit_ct = BACKREF_INDEX_BITS(hsd); 232 | uint16_t bits = get_bits(hsd, bit_ct < 8 ? bit_ct : 8); 233 | LOG("-- backref index (lsb), got 0x%04x (+1)\n", bits); 234 | if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_LSB; } 235 | hsd->output_index |= bits; 236 | hsd->output_index++; 237 | uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); 238 | hsd->output_count = 0; 239 | return (br_bit_ct > 8) ? HSDS_BACKREF_COUNT_MSB : HSDS_BACKREF_COUNT_LSB; 240 | } 241 | 242 | static HSD_state st_backref_count_msb(heatshrink_decoder *hsd) { 243 | uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); 244 | ASSERT(br_bit_ct > 8); 245 | uint16_t bits = get_bits(hsd, br_bit_ct - 8); 246 | LOG("-- backref count (msb), got 0x%04x (+1)\n", bits); 247 | if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_MSB; } 248 | hsd->output_count = bits << 8; 249 | return HSDS_BACKREF_COUNT_LSB; 250 | } 251 | 252 | static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd) { 253 | uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd); 254 | uint16_t bits = get_bits(hsd, br_bit_ct < 8 ? br_bit_ct : 8); 255 | LOG("-- backref count (lsb), got 0x%04x (+1)\n", bits); 256 | if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_LSB; } 257 | hsd->output_count |= bits; 258 | hsd->output_count++; 259 | return HSDS_YIELD_BACKREF; 260 | } 261 | 262 | static HSD_state st_yield_backref(heatshrink_decoder *hsd, 263 | output_info *oi) { 264 | size_t count = oi->buf_size - *oi->output_size; 265 | if (count > 0) { 266 | size_t i = 0; 267 | if (hsd->output_count < count) count = hsd->output_count; 268 | uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)]; 269 | uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1; 270 | uint16_t neg_offset = hsd->output_index; 271 | LOG("-- emitting %zu bytes from -%u bytes back\n", count, neg_offset); 272 | ASSERT(neg_offset <= mask + 1); 273 | ASSERT(count <= (size_t)(1 << BACKREF_COUNT_BITS(hsd))); 274 | 275 | for (i=0; ihead_index - neg_offset) & mask]; 277 | push_byte(hsd, oi, c); 278 | buf[hsd->head_index & mask] = c; 279 | hsd->head_index++; 280 | LOG(" -- ++ 0x%02x\n", c); 281 | } 282 | hsd->output_count -= count; 283 | if (hsd->output_count == 0) { return HSDS_TAG_BIT; } 284 | } 285 | return HSDS_YIELD_BACKREF; 286 | } 287 | 288 | /* Get the next COUNT bits from the input buffer, saving incremental progress. 289 | * Returns NO_BITS on end of input, or if more than 15 bits are requested. */ 290 | static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count) { 291 | uint16_t accumulator = 0; 292 | int i = 0; 293 | if (count > 15) { return NO_BITS; } 294 | LOG("-- popping %u bit(s)\n", count); 295 | 296 | /* If we aren't able to get COUNT bits, suspend immediately, because we 297 | * don't track how many bits of COUNT we've accumulated before suspend. */ 298 | if (hsd->input_size == 0) { 299 | if (hsd->bit_index < (1 << (count - 1))) { return NO_BITS; } 300 | } 301 | 302 | for (i = 0; i < count; i++) { 303 | if (hsd->bit_index == 0x00) { 304 | if (hsd->input_size == 0) { 305 | LOG(" -- out of bits, suspending w/ accumulator of %u (0x%02x)\n", 306 | accumulator, accumulator); 307 | return NO_BITS; 308 | } 309 | hsd->current_byte = hsd->buffers[hsd->input_index++]; 310 | LOG(" -- pulled byte 0x%02x\n", hsd->current_byte); 311 | if (hsd->input_index == hsd->input_size) { 312 | hsd->input_index = 0; /* input is exhausted */ 313 | hsd->input_size = 0; 314 | } 315 | hsd->bit_index = 0x80; 316 | } 317 | accumulator <<= 1; 318 | if (hsd->current_byte & hsd->bit_index) { 319 | accumulator |= 0x01; 320 | if (0) { 321 | LOG(" -- got 1, accumulator 0x%04x, bit_index 0x%02x\n", 322 | accumulator, hsd->bit_index); 323 | } 324 | } else { 325 | if (0) { 326 | LOG(" -- got 0, accumulator 0x%04x, bit_index 0x%02x\n", 327 | accumulator, hsd->bit_index); 328 | } 329 | } 330 | hsd->bit_index >>= 1; 331 | } 332 | 333 | if (count > 1) { LOG(" -- accumulated %08x\n", accumulator); } 334 | return accumulator; 335 | } 336 | 337 | HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd) { 338 | if (hsd == NULL) { return HSDR_FINISH_ERROR_NULL; } 339 | switch (hsd->state) { 340 | case HSDS_TAG_BIT: 341 | return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; 342 | 343 | /* If we want to finish with no input, but are in these states, it's 344 | * because the 0-bit padding to the last byte looks like a backref 345 | * marker bit followed by all 0s for index and count bits. */ 346 | case HSDS_BACKREF_INDEX_LSB: 347 | case HSDS_BACKREF_INDEX_MSB: 348 | case HSDS_BACKREF_COUNT_LSB: 349 | case HSDS_BACKREF_COUNT_MSB: 350 | return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; 351 | 352 | /* If the output stream is padded with 0xFFs (possibly due to being in 353 | * flash memory), also explicitly check the input size rather than 354 | * uselessly returning MORE but yielding 0 bytes when polling. */ 355 | case HSDS_YIELD_LITERAL: 356 | return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE; 357 | 358 | default: 359 | return HSDR_FINISH_MORE; 360 | } 361 | } 362 | 363 | static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte) { 364 | LOG(" -- pushing byte: 0x%02x ('%c')\n", byte, isprint(byte) ? byte : '.'); 365 | oi->buf[(*oi->output_size)++] = byte; 366 | (void)hsd; 367 | } 368 | -------------------------------------------------------------------------------- /Sources/Heatshrink/include/RNHeatshrinkDecoder.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNHeatshrinkDecoder.h 3 | // Pods 4 | // 5 | // Created by Rafael Nobre on 06/05/17. 6 | // 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface RNHeatshrinkDecoder : NSObject 14 | 15 | - (instancetype)initWithWindowSize:(NSInteger)windowBitSize andLookaheadSize:(NSInteger)lookaheadBitSize; 16 | 17 | - (instancetype)initWithBufferSize:(NSInteger)inputBufferSize windowSize:(NSInteger)windowBitSize andLookaheadSize:(NSInteger)lookaheadBitSize; 18 | 19 | - (NSData *)decodeData:(NSData *)dataToDecode; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /Sources/Heatshrink/include/RNHeatshrinkEncoder.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNHeatshrinkEncoder.h 3 | // Pods 4 | // 5 | // Created by Rafael Nobre on 06/05/17. 6 | // 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface RNHeatshrinkEncoder : NSObject 14 | 15 | - (instancetype)initWithWindowSize:(NSInteger)windowBitSize andLookaheadSize:(NSInteger)lookaheadBitSize; 16 | 17 | - (NSData *)encodeData:(NSData *)dataToEncode; 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /Sources/Heatshrink/include/heatshrink-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #ifndef heatshrink-Bridging-Header.h 2 | #define heatshrink-Bridging-Header.h 3 | 4 | #import 5 | #import 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /Sources/Heatshrink/include/heatshrink_common.h: -------------------------------------------------------------------------------- 1 | #ifndef HEATSHRINK_H 2 | #define HEATSHRINK_H 3 | 4 | #define HEATSHRINK_AUTHOR "Scott Vokes " 5 | #define HEATSHRINK_URL "https://github.com/atomicobject/heatshrink" 6 | 7 | /* Version 0.4.1 */ 8 | #define HEATSHRINK_VERSION_MAJOR 0 9 | #define HEATSHRINK_VERSION_MINOR 4 10 | #define HEATSHRINK_VERSION_PATCH 1 11 | 12 | #define HEATSHRINK_MIN_WINDOW_BITS 4 13 | #define HEATSHRINK_MAX_WINDOW_BITS 15 14 | 15 | #define HEATSHRINK_MIN_LOOKAHEAD_BITS 3 16 | 17 | #define HEATSHRINK_LITERAL_MARKER 0x01 18 | #define HEATSHRINK_BACKREF_MARKER 0x00 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/Heatshrink/include/heatshrink_config.h: -------------------------------------------------------------------------------- 1 | #ifndef HEATSHRINK_CONFIG_H 2 | #define HEATSHRINK_CONFIG_H 3 | 4 | /* Should functionality assuming dynamic allocation be used? */ 5 | #ifndef HEATSHRINK_DYNAMIC_ALLOC 6 | #define HEATSHRINK_DYNAMIC_ALLOC 1 7 | #endif 8 | 9 | #if HEATSHRINK_DYNAMIC_ALLOC 10 | /* Optional replacement of malloc/free */ 11 | #define HEATSHRINK_MALLOC(SZ) malloc(SZ) 12 | #define HEATSHRINK_FREE(P, SZ) free(P) 13 | #else 14 | /* Required parameters for static configuration */ 15 | #define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 32 16 | #define HEATSHRINK_STATIC_WINDOW_BITS 8 17 | #define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4 18 | #endif 19 | 20 | /* Turn on logging for debugging. */ 21 | #define HEATSHRINK_DEBUGGING_LOGS 0 22 | 23 | /* Use indexing for faster compression. (This requires additional space.) */ 24 | #define HEATSHRINK_USE_INDEX 1 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /Sources/Heatshrink/include/heatshrink_decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef HEATSHRINK_DECODER_H 2 | #define HEATSHRINK_DECODER_H 3 | 4 | #include 5 | #include 6 | #include "heatshrink_common.h" 7 | #include "heatshrink_config.h" 8 | 9 | typedef enum { 10 | HSDR_SINK_OK, /* data sunk, ready to poll */ 11 | HSDR_SINK_FULL, /* out of space in internal buffer */ 12 | HSDR_SINK_ERROR_NULL=-1, /* NULL argument */ 13 | } HSD_sink_res; 14 | 15 | typedef enum { 16 | HSDR_POLL_EMPTY, /* input exhausted */ 17 | HSDR_POLL_MORE, /* more data remaining, call again w/ fresh output buffer */ 18 | HSDR_POLL_ERROR_NULL=-1, /* NULL arguments */ 19 | HSDR_POLL_ERROR_UNKNOWN=-2, 20 | } HSD_poll_res; 21 | 22 | typedef enum { 23 | HSDR_FINISH_DONE, /* output is done */ 24 | HSDR_FINISH_MORE, /* more output remains */ 25 | HSDR_FINISH_ERROR_NULL=-1, /* NULL arguments */ 26 | } HSD_finish_res; 27 | 28 | #if HEATSHRINK_DYNAMIC_ALLOC 29 | #define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(BUF) \ 30 | ((BUF)->input_buffer_size) 31 | #define HEATSHRINK_DECODER_WINDOW_BITS(BUF) \ 32 | ((BUF)->window_sz2) 33 | #define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ 34 | ((BUF)->lookahead_sz2) 35 | #else 36 | #define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_) \ 37 | HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 38 | #define HEATSHRINK_DECODER_WINDOW_BITS(_) \ 39 | (HEATSHRINK_STATIC_WINDOW_BITS) 40 | #define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \ 41 | (HEATSHRINK_STATIC_LOOKAHEAD_BITS) 42 | #endif 43 | 44 | typedef struct { 45 | uint16_t input_size; /* bytes in input buffer */ 46 | uint16_t input_index; /* offset to next unprocessed input byte */ 47 | uint16_t output_count; /* how many bytes to output */ 48 | uint16_t output_index; /* index for bytes to output */ 49 | uint16_t head_index; /* head of window buffer */ 50 | uint8_t state; /* current state machine node */ 51 | uint8_t current_byte; /* current byte of input */ 52 | uint8_t bit_index; /* current bit index */ 53 | 54 | #if HEATSHRINK_DYNAMIC_ALLOC 55 | /* Fields that are only used if dynamically allocated. */ 56 | uint8_t window_sz2; /* window buffer bits */ 57 | uint8_t lookahead_sz2; /* lookahead bits */ 58 | uint16_t input_buffer_size; /* input buffer size */ 59 | 60 | /* Input buffer, then expansion window buffer */ 61 | uint8_t buffers[]; 62 | #else 63 | /* Input buffer, then expansion window buffer */ 64 | uint8_t buffers[(1 << HEATSHRINK_DECODER_WINDOW_BITS(_)) 65 | + HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_)]; 66 | #endif 67 | } heatshrink_decoder; 68 | 69 | #if HEATSHRINK_DYNAMIC_ALLOC 70 | /* Allocate a decoder with an input buffer of INPUT_BUFFER_SIZE bytes, 71 | * an expansion buffer size of 2^WINDOW_SZ2, and a lookahead 72 | * size of 2^lookahead_sz2. (The window buffer and lookahead sizes 73 | * must match the settings used when the data was compressed.) 74 | * Returns NULL on error. */ 75 | heatshrink_decoder *heatshrink_decoder_alloc(uint16_t input_buffer_size, 76 | uint8_t expansion_buffer_sz2, uint8_t lookahead_sz2); 77 | 78 | /* Free a decoder. */ 79 | void heatshrink_decoder_free(heatshrink_decoder *hsd); 80 | #endif 81 | 82 | /* Reset a decoder. */ 83 | void heatshrink_decoder_reset(heatshrink_decoder *hsd); 84 | 85 | /* Sink at most SIZE bytes from IN_BUF into the decoder. *INPUT_SIZE is set to 86 | * indicate how many bytes were actually sunk (in case a buffer was filled). */ 87 | HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd, 88 | uint8_t *in_buf, size_t size, size_t *input_size); 89 | 90 | /* Poll for output from the decoder, copying at most OUT_BUF_SIZE bytes into 91 | * OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */ 92 | HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd, 93 | uint8_t *out_buf, size_t out_buf_size, size_t *output_size); 94 | 95 | /* Notify the dencoder that the input stream is finished. 96 | * If the return value is HSDR_FINISH_MORE, there is still more output, so 97 | * call heatshrink_decoder_poll and repeat. */ 98 | HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd); 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /Sources/Heatshrink/include/heatshrink_encoder.h: -------------------------------------------------------------------------------- 1 | #ifndef HEATSHRINK_ENCODER_H 2 | #define HEATSHRINK_ENCODER_H 3 | 4 | #include 5 | #include 6 | #include "heatshrink_common.h" 7 | #include "heatshrink_config.h" 8 | 9 | typedef enum { 10 | HSER_SINK_OK, /* data sunk into input buffer */ 11 | HSER_SINK_ERROR_NULL=-1, /* NULL argument */ 12 | HSER_SINK_ERROR_MISUSE=-2, /* API misuse */ 13 | } HSE_sink_res; 14 | 15 | typedef enum { 16 | HSER_POLL_EMPTY, /* input exhausted */ 17 | HSER_POLL_MORE, /* poll again for more output */ 18 | HSER_POLL_ERROR_NULL=-1, /* NULL argument */ 19 | HSER_POLL_ERROR_MISUSE=-2, /* API misuse */ 20 | } HSE_poll_res; 21 | 22 | typedef enum { 23 | HSER_FINISH_DONE, /* encoding is complete */ 24 | HSER_FINISH_MORE, /* more output remaining; use poll */ 25 | HSER_FINISH_ERROR_NULL=-1, /* NULL argument */ 26 | } HSE_finish_res; 27 | 28 | #if HEATSHRINK_DYNAMIC_ALLOC 29 | #define HEATSHRINK_ENCODER_WINDOW_BITS(HSE) \ 30 | ((HSE)->window_sz2) 31 | #define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(HSE) \ 32 | ((HSE)->lookahead_sz2) 33 | #define HEATSHRINK_ENCODER_INDEX(HSE) \ 34 | ((HSE)->search_index) 35 | struct hs_index { 36 | uint16_t size; 37 | int16_t index[]; 38 | }; 39 | #else 40 | #define HEATSHRINK_ENCODER_WINDOW_BITS(_) \ 41 | (HEATSHRINK_STATIC_WINDOW_BITS) 42 | #define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(_) \ 43 | (HEATSHRINK_STATIC_LOOKAHEAD_BITS) 44 | #define HEATSHRINK_ENCODER_INDEX(HSE) \ 45 | (&(HSE)->search_index) 46 | struct hs_index { 47 | uint16_t size; 48 | int16_t index[2 << HEATSHRINK_STATIC_WINDOW_BITS]; 49 | }; 50 | #endif 51 | 52 | typedef struct { 53 | uint16_t input_size; /* bytes in input buffer */ 54 | uint16_t match_scan_index; 55 | uint16_t match_length; 56 | uint16_t match_pos; 57 | uint16_t outgoing_bits; /* enqueued outgoing bits */ 58 | uint8_t outgoing_bits_count; 59 | uint8_t flags; 60 | uint8_t state; /* current state machine node */ 61 | uint8_t current_byte; /* current byte of output */ 62 | uint8_t bit_index; /* current bit index */ 63 | #if HEATSHRINK_DYNAMIC_ALLOC 64 | uint8_t window_sz2; /* 2^n size of window */ 65 | uint8_t lookahead_sz2; /* 2^n size of lookahead */ 66 | #if HEATSHRINK_USE_INDEX 67 | struct hs_index *search_index; 68 | #endif 69 | /* input buffer and / sliding window for expansion */ 70 | uint8_t buffer[]; 71 | #else 72 | #if HEATSHRINK_USE_INDEX 73 | struct hs_index search_index; 74 | #endif 75 | /* input buffer and / sliding window for expansion */ 76 | uint8_t buffer[2 << HEATSHRINK_ENCODER_WINDOW_BITS(_)]; 77 | #endif 78 | } heatshrink_encoder; 79 | 80 | #if HEATSHRINK_DYNAMIC_ALLOC 81 | /* Allocate a new encoder struct and its buffers. 82 | * Returns NULL on error. */ 83 | heatshrink_encoder *heatshrink_encoder_alloc(uint8_t window_sz2, 84 | uint8_t lookahead_sz2); 85 | 86 | /* Free an encoder. */ 87 | void heatshrink_encoder_free(heatshrink_encoder *hse); 88 | #endif 89 | 90 | /* Reset an encoder. */ 91 | void heatshrink_encoder_reset(heatshrink_encoder *hse); 92 | 93 | /* Sink up to SIZE bytes from IN_BUF into the encoder. 94 | * INPUT_SIZE is set to the number of bytes actually sunk (in case a 95 | * buffer was filled.). */ 96 | HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse, 97 | uint8_t *in_buf, size_t size, size_t *input_size); 98 | 99 | /* Poll for output from the encoder, copying at most OUT_BUF_SIZE bytes into 100 | * OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */ 101 | HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse, 102 | uint8_t *out_buf, size_t out_buf_size, size_t *output_size); 103 | 104 | /* Notify the encoder that the input stream is finished. 105 | * If the return value is HSER_FINISH_MORE, there is still more output, so 106 | * call heatshrink_encoder_poll and repeat. */ 107 | HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse); 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /Sources/Heatshrink/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module Heatshrink [system]{ 2 | header "heatshrink-Bridging-Header.h" 3 | export * 4 | } 5 | -------------------------------------------------------------------------------- /Tests/ActiveLookSDKTests/ActiveLookSDKTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2022 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import XCTest 17 | @testable import ActiveLookSDK 18 | 19 | final class ActiveLookSDKTests: XCTestCase { 20 | 21 | let token = "invalid token" 22 | var sut: ActiveLookSDK! 23 | var onUpdateStart: StartClosureSignature = { update in } 24 | var onUpdateAvailable: UpdateAvailableClosureSignature = { update in return true } 25 | var onUpdateProgress: ProgressClosureSignature = { update in } 26 | var onUpdateSuccess: SuccessClosureSignature = { update in } 27 | var onUpdateFailure: FailureClosureSignature = { update in } 28 | 29 | override func setUpWithError() throws { 30 | sut = try ActiveLookSDK.shared(token: token, 31 | onUpdateStartCallback: onUpdateStart, 32 | onUpdateAvailableCallback: onUpdateAvailable, 33 | onUpdateProgressCallback: onUpdateProgress, 34 | onUpdateSuccessCallback: onUpdateSuccess, 35 | onUpdateFailureCallback: onUpdateFailure) 36 | } 37 | 38 | override func tearDownWithError() throws { 39 | sut = nil 40 | } 41 | 42 | func test_SDK_takesOnUpdateAvailableCallback() throws { 43 | XCTAssertNotNil(sut, "ActiveLookSDK needs onUpdateAvailableCallback() to get initialized.") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/ActiveLookSDKTests/GlassesUpdaterTests/VersionCheckerTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2022 Microoled 4 | Licensed under the Apache License, Version 2.0 (the “License”); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an “AS IS” BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | */ 15 | 16 | import XCTest 17 | @testable import ActiveLookSDK 18 | 19 | class VersionCheckerTests: XCTestCase { 20 | 21 | var sut: VersionChecker! 22 | 23 | override func setUpWithError() throws { 24 | // Put setup code here. This method is called before the invocation of each test method in the class. 25 | sut = VersionChecker() 26 | } 27 | 28 | override func tearDownWithError() throws { 29 | // Put teardown code here. This method is called after the invocation of each test method in the class. 30 | } 31 | 32 | 33 | func test_VersionChecker_whenGivenInvalidToken_shouldFailWithInvalidTokenError() { 34 | // given 35 | // when 36 | // then 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - type_body_length 3 | - trailing_whitespace 4 | - identifier_name 5 | - line_length 6 | - file_length 7 | opt_in_rules: 8 | - empty_count 9 | - empty_string 10 | excluded: 11 | - DerivedData 12 | #identifier_name: 13 | # min_length: 2 14 | # max_length: 45 15 | line_length: 16 | warning: 200 17 | error: 400 18 | ignores_function_declarations: true 19 | ignores_comments: true 20 | ignores_urls: true 21 | #function_body_length: 22 | # warning: 300 23 | # error: 500 24 | #function_parameter_count: 25 | # warning: 6 26 | # error: 8 27 | #type_body_length: 28 | # warning: 300 29 | # error: 500 30 | #file_length: 31 | # warning: 1000 32 | # error: 1500 33 | # ignore_comment_only_lines: true 34 | #cyclomatic_complexity: 35 | # warning: 15 36 | # error: 25 37 | #reporter: "xcode" --------------------------------------------------------------------------------