├── .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"
--------------------------------------------------------------------------------