Repository: UPetersen/LibreMonitor
Branch: master
Commit: 275b833a216b
Files: 73
Total size: 460.9 KB
Directory structure:
gitextract_hx6qj96v/
├── LICENSE.md
├── LibreMonitor/
│ ├── AppDelegate.swift
│ ├── Assets.xcassets/
│ │ └── AppIcon.appiconset/
│ │ └── Contents.json
│ ├── Base.lproj/
│ │ └── LaunchScreen.storyboard
│ ├── BloodGlucose+CoreDataClass.swift
│ ├── BloodGlucose+CoreDataProperties.swift
│ ├── Bluetooth/
│ │ ├── Data_types+Extensions.swift
│ │ ├── NSData+CRC8.h
│ │ ├── NSData+CRC8.m
│ │ ├── NSData+SLIP.h
│ │ ├── NSData+SLIP.m
│ │ ├── SLIPBuffer.swift
│ │ ├── SimbleeManager.swift
│ │ ├── constants.h
│ │ └── data_types.h
│ ├── HeaderData+CoreDataClass.swift
│ ├── HeaderData+CoreDataProperties.swift
│ ├── Info.plist
│ ├── LibreMonitor-Bridging-Header.h
│ ├── LibreMonitor.entitlements
│ ├── LibreMonitor.xcdatamodeld/
│ │ ├── .xccurrentversion
│ │ └── LibreMonitor.xcdatamodel/
│ │ └── contents
│ ├── LibreMonitorUITests-Bridging-Header.h
│ ├── Main.storyboard
│ ├── Model/
│ │ ├── CRC.swift
│ │ ├── LibreSensor.swift
│ │ ├── Measurement.swift
│ │ ├── SensorData.swift
│ │ └── SensorState.swift
│ ├── ModelCoreData/
│ │ ├── BloodGlucose+CoreDataClass.swift
│ │ ├── BloodGlucose+CoreDataProperties.swift
│ │ ├── CoreDataStack.swift
│ │ ├── HeaderData+CoreDataClass.swift
│ │ ├── HeaderData+CoreDataProperties.swift
│ │ ├── Reader+CoreDataClass.swift
│ │ ├── Reader+CoreDataProperties.swift
│ │ ├── Sensor+CoreDataClass.swift
│ │ └── Sensor+CoreDataProperties.swift
│ ├── Reader+CoreDataClass.swift
│ ├── Reader+CoreDataProperties.swift
│ ├── Sensor+CoreDataClass.swift
│ ├── Sensor+CoreDataProperties.swift
│ ├── SimbleeCode/
│ │ ├── crc8.h
│ │ ├── crc8.m
│ │ ├── libUBP.h
│ │ └── libUBP.m
│ ├── ViewControllers/
│ │ ├── AdjustmentsTableViewController.swift
│ │ └── BloodSugarTableViewController.swift
│ └── Views/
│ ├── BloodSugarGraphView.swift
│ └── BloodSugarGraphViewTableViewCell.swift
├── LibreMonitor.ino
├── LibreMonitor.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ └── contents.xcworkspacedata
│ └── xcuserdata/
│ └── Uwe.xcuserdatad/
│ └── xcschemes/
│ ├── LibreMonitor.xcscheme
│ └── xcschememanagement.plist
├── LibreMonitor.xcworkspace/
│ ├── contents.xcworkspacedata
│ └── xcuserdata/
│ └── Uwe.xcuserdatad/
│ └── xcdebugger/
│ └── Breakpoints_v2.xcbkptlist
├── LibreMonitorRFduino.ino
├── LibreMonitorTests/
│ ├── BluetoothTestData.swift
│ ├── Info.plist
│ ├── LibreMonitorTestSensorData.swift
│ ├── LibreMonitorTests-Bridging-Header.h
│ ├── LibreMonitorTests.swift
│ ├── SimbleeCode/
│ │ ├── crc8.h
│ │ ├── crc8.m
│ │ ├── libUBP.h
│ │ └── libUBP.m
│ └── TransmissionTests.swift
├── LibreMonitorUITests/
│ ├── Info.plist
│ └── LibreMonitorUITests.swift
├── Podfile
├── README.md
└── libUBP RFduino.cpp
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE.md
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: LibreMonitor/AppDelegate.swift
================================================
//
// AppDelegate.swift
// LibreMonitor
//
// Created by Uwe Petersen on 14.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import UIKit
import CoreBluetooth
import UserNotifications
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
var window: UIWindow?
var coreDataStack = CoreDataStack()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Allow local notifications for iOS 10
let center = UNUserNotificationCenter.current()
let options: UNAuthorizationOptions = [.alert, .badge, .sound]
center.requestAuthorization(options: options) { (granted, error) in
if granted {
// application.registerForRemoteNotifications()
}
}
// Do not show a badge icon value unless data has been received
UIApplication.shared.applicationIconBadgeNumber = 0 // hide badge number
// Override point for customization after application launch.
// let splitViewController = self.window!.rootViewController as! UISplitViewController
// let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
// navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
// splitViewController.delegate = self
//
// let masterNavigationController = splitViewController.viewControllers[0] as! UINavigationController
// let controller = masterNavigationController.topViewController as! MasterViewController
// controller.managedObjectContext = self.persistentContainer.viewContext
print("In didFinishLaunchingWithOptions")
let tabBarController = self.window?.rootViewController as! UITabBarController
if let childViewControllers = tabBarController.viewControllers {
for childViewController in childViewControllers where childViewController is UINavigationController {
let navigationController = childViewController as! UINavigationController
let bloodSugarTableViewController = navigationController.topViewController as! BloodSugarTableViewController
// Set core data stack in view controller
bloodSugarTableViewController.coreDataStack = coreDataStack
}
}
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
print("In applicationWillResignActive")
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
print("In applicationDidEnterBackground")
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
print("In applicationWillEnterForeground")
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
print("In applicationDidBecomeActive")
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
print("In applicationWillTerminate")
// self.saveContext()
coreDataStack.saveContext()
}
// // MARK: - Split view
//
// func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
// guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
// guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
// if topAsDetailController.detailItem == nil {
// // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
// return true
// }
// return false
// }
// // MARK: - Core Data stack
//
// lazy var persistentContainer: NSPersistentContainer = {
// /*
// The persistent container for the application. This implementation
// creates and returns a container, having loaded the store for the
// application to it. This property is optional since there are legitimate
// error conditions that could cause the creation of the store to fail.
// */
// let container = NSPersistentContainer(name: "LibreMonitor")
// container.loadPersistentStores(completionHandler: { (storeDescription, error) in
// if let error = error as NSError? {
// // Replace this implementation with code to handle the error appropriately.
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//
// /*
// Typical reasons for an error here include:
// * The parent directory does not exist, cannot be created, or disallows writing.
// * The persistent store is not accessible, due to permissions or data protection when the device is locked.
// * The device is out of space.
// * The store could not be migrated to the current model version.
// Check the error message to determine what the actual problem was.
// */
// fatalError("Unresolved error \(error), \(error.userInfo)")
// }
// })
// return container
// }()
//
// // MARK: - Core Data Saving support
//
// func saveContext () {
// let context = persistentContainer.viewContext
// if context.hasChanges {
// do {
// try context.save()
// } catch {
// // Replace this implementation with code to handle the error appropriately.
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
// let nserror = error as NSError
// fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
// }
// }
// }
}
================================================
FILE: LibreMonitor/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: LibreMonitor/Base.lproj/LaunchScreen.storyboard
================================================
================================================
FILE: LibreMonitor/BloodGlucose+CoreDataClass.swift
================================================
//
// BloodGlucose+CoreDataClass.swift
// LibreMonitor
//
// Created by Uwe Petersen on 14.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
public class BloodGlucose: NSManagedObject {
}
================================================
FILE: LibreMonitor/BloodGlucose+CoreDataProperties.swift
================================================
//
// BloodGlucose+CoreDataProperties.swift
// LibreMonitor
//
// Created by Uwe Petersen on 14.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
extension BloodGlucose {
@nonobjc public class func fetchRequest() -> NSFetchRequest {
return NSFetchRequest(entityName: "BloodGlucose");
}
@NSManaged public var bytes: String?
@NSManaged public var date: NSDate?
@NSManaged public var dateString: String?
@NSManaged public var id: Int32
@NSManaged public var type: Int16
@NSManaged public var value: Double
@NSManaged public var sensor: Sensor?
}
================================================
FILE: LibreMonitor/Bluetooth/Data_types+Extensions.swift
================================================
//
// Data_types+Extensions.swift
// LibreMonitor
//
// Created by Uwe Petersen on 15.05.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
extension IDNDataType {
func deviceIDString() -> String {
var _self = self // make a copy of self to be able to access it via a pointer (does not work on self itself)
// Extrakt device ID by creating an array from the tuple
let deviceIDString = withUnsafePointer(to: &_self.deviceID, { (ptr) -> String? in
let uint8Ptr = unsafeBitCast(ptr, to: UnsafePointer.self)
var deviceIDString: String = String(format: "%02X", arguments: [uint8Ptr[0]])
for index in 1...12 {
deviceIDString += String(format: ":%02X", arguments: [uint8Ptr[index]])
}
return deviceIDString
})
print(deviceIDString!)
return deviceIDString ?? "no device id"
}
}
extension SystemInformationDataType {
func uidString() -> String {
var _self = self // make a copy of self to be able to access it via a pointer (does not work on self itself)
// Extrakt UID by creating an array from the tuple
let uidString = withUnsafePointer(to: &_self.uid, { (ptr) -> String? in
let uint8Ptr = unsafeBitCast(ptr, to: UnsafePointer.self)
var uidString: String = String(format: "%02X", arguments: [uint8Ptr[0]])
for index in 1...7 {
uidString += String(format: "%02X", arguments: [uint8Ptr[index]])
// uidString += String(format: ":%02X", arguments: [uint8Ptr[index]])
}
return uidString
})
print(uidString!)
return uidString ?? "no uid"
}
}
//extension RawDataType {
// func byteString() -> String {
//
// var _self = self // make a copy of self to be able to access it via a pointer (does not work on self itself)
//
// // Extrakt UID by creating an array from the tuple
//
// let byteString = withUnsafePointer(&_self.bytes, { (ptr) -> String? in
//
// let uint8Ptr = unsafeBitCast(ptr, UnsafePointer.self)
//
// var byteString: String = String(format: "%02X", arguments: [uint8Ptr[0]])
// for index in 1...5 {
// byteString += String(format: "%02X", arguments: [uint8Ptr[index]])
// // uidString += String(format: ":%02X", arguments: [uint8Ptr[index]])
// }
// return byteString
// })
//
// print(byteString!)
//
// return byteString ?? "no bytes"
// }
//}
================================================
FILE: LibreMonitor/Bluetooth/NSData+CRC8.h
================================================
//
// NSData+CRC8.h
// Bluetooth LE Test
//
// Created by Chas Conway on 12/11/13.
// Copyright (c) 2013 Chas Conway. All rights reserved.
//
#import
@interface NSData (CRC8)
- (signed char)CRC8Checksum;
//+ (signed char)CRC8ChecksumFromBuffer:(signed char *)dataBuffer bytesToRead:(signed char)bytesToRead;
+ (signed char)CRC8ChecksumFromBuffer:(signed char *)dataBuffer bytesToRead:(uint16_t)bytesToRead; // changed to uint16_t by Uwi on 2016-12-26
@end
================================================
FILE: LibreMonitor/Bluetooth/NSData+CRC8.m
================================================
//
// NSData+CRC8.m
// Bluetooth LE Test
//
// Created by Chas Conway on 12/11/13.
// Copyright (c) 2013 Chas Conway. All rights reserved.
//
#import "NSData+CRC8.h"
#define CRC8INIT 0x00
#define CRC8POLY 0x18 //0X18 = X^8+X^5+X^4+X^0
@implementation NSData (CRC8)
- (signed char)CRC8Checksum {
signed char *buffer = malloc(self.length);
[self getBytes:buffer length:self.length];
return [NSData CRC8ChecksumFromBuffer:buffer bytesToRead:self.length];
}
//+ (signed char)CRC8ChecksumFromBuffer:(signed char *)dataBuffer bytesToRead:(signed char)bytesToRead {
+ (signed char)CRC8ChecksumFromBuffer:(signed char *)dataBuffer bytesToRead:(uint16_t)bytesToRead { // changed to uint16_t by Uwi on 2016-12-26
signed char crc;
uint16_t loop_count;
signed char bit_counter;
signed char data;
signed char feedback_bit;
crc = CRC8INIT;
for (loop_count = 0; loop_count != bytesToRead; loop_count++)
{
data = dataBuffer[loop_count];
bit_counter = 8;
do {
feedback_bit = (crc ^ data) & 0x01;
if ( feedback_bit == 0x01 ) {
crc = crc ^ CRC8POLY;
}
crc = (crc >> 1) & 0x7F;
if ( feedback_bit == 0x01 ) {
crc = crc | 0x80;
}
data = data >> 1;
bit_counter--;
} while (bit_counter > 0);
}
return crc;
}
@end
================================================
FILE: LibreMonitor/Bluetooth/NSData+SLIP.h
================================================
//
// NSData+SLIP.h
// Arduino Greenhouse
//
// Created by Chas Conway on 5/23/14.
// Copyright (c) 2014 Chas Conway. All rights reserved.
//
#import
@interface NSData (SLIP)
- (NSIndexSet *)indexesOfEndBytes;
- (NSData *)unescapedData;
- (BOOL)beginsWithEndByte;
- (BOOL)endsWithEndByte;
@end
================================================
FILE: LibreMonitor/Bluetooth/NSData+SLIP.m
================================================
//
// NSData+SLIP.m
// Arduino Greenhouse
//
// Created by Chas Conway on 5/23/14.
// Copyright (c) 2014 Chas Conway. All rights reserved.
//
// Modified by Uwe Petersen
#import "NSData+SLIP.h"
// Serial Line IP (SLIP) escaping constants
#define ESCAPE_BYTE 0xDB
#define END_BYTE 0xC0
#define ESCAPED_ESCAPE_BYTE 0xDD
#define ESCAPED_END_BYTE 0xDC
const uint8_t escapeSequence[1] = {ESCAPE_BYTE};
const uint8_t endSequence[1] = {END_BYTE};
const uint8_t escapedEndSequence[2] = {ESCAPE_BYTE, ESCAPED_END_BYTE};
const uint8_t escapedEscapeSequence[2] = {ESCAPE_BYTE, ESCAPED_ESCAPE_BYTE};
@implementation NSData (SLIP)
- (NSIndexSet *)indexesOfEndBytes {
__block NSMutableIndexSet *endByteIndices = [NSMutableIndexSet new];
[self enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
for (NSInteger i = byteRange.location; i < (byteRange.location + byteRange.length); i++) { // For each byte in the range
uint8_t aByte = *((uint8_t *)bytes + i);
if (aByte == END_BYTE) [endByteIndices addIndex:i];
}
}];
return endByteIndices;
}
- (NSData *)unescapedData {
NSMutableData *outputData = [[NSMutableData alloc] initWithData:self];
BOOL done = NO;
NSUInteger notYetUnescaped = 0; // 2016-06-30, Uwe Petersen:
while (!done) { // Search for escaped END bytes
// 2016-06-30, Uwe Petersen:
NSRange resultRange = [outputData rangeOfData:[NSData dataWithBytes:escapedEndSequence length:2] options:0 range:NSMakeRange(notYetUnescaped, outputData.length - notYetUnescaped)];
// NSRange resultRange = [outputData rangeOfData:[NSData dataWithBytes:escapedEndSequence length:2] options:0 range:NSMakeRange(0, outputData.length)];
if (resultRange.location != NSNotFound) { // Found an occurance
// Replace that escaped occurance with the unescaped value
[outputData replaceBytesInRange:resultRange withBytes:endSequence length:1];
notYetUnescaped = resultRange.location + 1; // 2016-06-30, Uwe Petersen
} else { // Didn't find any more occurances, so exit while loop
done = YES;
}
}
done = NO;
notYetUnescaped = 0; // 2016-06-30, Uwe Petersen
while (!done) { // Search for escaped ESCAPE bytes
// 2016-06-30, Uwe Petersen:
NSRange resultRange = [outputData rangeOfData:[NSData dataWithBytes:escapedEscapeSequence length:2] options:0 range:NSMakeRange(notYetUnescaped, outputData.length - notYetUnescaped)];
// NSRange resultRange = [outputData rangeOfData:[NSData dataWithBytes:escapedEscapeSequence length:2] options:0 range:NSMakeRange(0, outputData.length)];
if (resultRange.location != NSNotFound) { // Found an occurance
// Replace that escaped occurance with the unescaped value
[outputData replaceBytesInRange:resultRange withBytes:escapeSequence length:1];
notYetUnescaped = resultRange.location + 1; // 2016-06-30, Uwe Petersen
} else { // Didn't find any more occurances, so exit while loop
done = YES;
}
}
return [NSData dataWithData:outputData];
}
- (BOOL)beginsWithEndByte {
NSIndexSet *endByteIndices = [self indexesOfEndBytes];
return [endByteIndices containsIndex:0];
}
- (BOOL)endsWithEndByte {
NSIndexSet *endByteIndices = [self indexesOfEndBytes];
return [endByteIndices containsIndex:(self.length - 1)];
}
@end
================================================
FILE: LibreMonitor/Bluetooth/SLIPBuffer.swift
================================================
//
// SLIPBuffer.swift
// UBA-Demo
//
// Created by Chas Conway on 2/4/15.
// Copyright (c) 2015 Chas Conway. All rights reserved.
//
// Modified by Uwe Petersen
import Foundation
let PacketIdentifierLength = MemoryLayout.size
let PacketFlagsLength = MemoryLayout.size
let PacketChecksumLength = MemoryLayout.size
protocol SLIPBufferDelegate {
func slipBufferReceivedPayload(_ payloadData: Data, payloadIdentifier: UInt16, txFlags: UInt8)
}
class SLIPBuffer {
var rxBuffer = Data()
var delegate:SLIPBufferDelegate?
/// Appends more escaped bytes (i.e. data that were received via bluetooth) to the buffer and scans the buffer for complete frames according to the Serial Line Internet Protocol (SLIP). If a complete frame is detected, its payload data is extracted and a delegate method is called.
///
/// The bytes that are appended are escaped bytes according to the serial line internet protocol (SLIP). SLIP works as follows:
/// - A transmission packet (the payload data) is appended by a special "END" byte, which distinguishes the datagram boundaries in the byte stream.
/// - If the END byte occurs in the payload data to be sent, the two byte sequence [ESC, ESC_END] is sent instead.
/// - If the ESC byte occurs in the payload data to be sent, the two byte sequence [ESC, ESC_ESC] is sent instead.
/// - Variants of the protocol may also begin transmission packets (the payload data) with an END byte (which is the case in this realization of SLIP)
///
/// The special bytes used by SLIP are:
/// - 0xC0 ... END -> Frame End (and Frame Beginning in this realization)
/// - 0xDB ... ESC -> Frame Escape
/// - 0xDC ... ESC_END -> Transposed Frame END
/// - 0xDD ... ESC_ESC -> Transposed Frame ESC
///
/// Reference: https://en.m.wikipedia.org/wiki/Serial_Line_Internet_Protocol
///
/// - parameter escapedData: data with escape bytes to be appended to the buffer
func appendEscapedBytes(_ escapedData: Data) {
rxBuffer.append(escapedData)
scanRxBufferForFrames()
}
/// Scans the buffer for complete frames according to the Serial Line Internet Protocol (SLIP). If a complete frame is detected, the crc is checked and the payload extracted and a delegate method called.
///
/// Extracting the payload means the following steps:
/// - Extract the payload from the SLIP frame:
/// - Remove the END byte at the beginning and at the end of the frame.
/// - If the original payload data had contained the special bytes END (0xC0) or ESC (0xDB) they where replaced by the special byte sequences [END, ESC_END] ([0xC0, 0xDC]) and [ESC, ESC_ESC] ([0xDB, 0xDD]) because of the SLIP and this has to be reversed.
/// - Check CRC and remove the rcr bytes:
/// - The datagram is appended with one byte containting a CRC8, calculated over the original payload data. This CRC8 is compared with the corresponding CRC8 of the payload data.
/// - If the CRCs are equal, a delegate method is called with the payload data (with the one appended CRC byte removed).
///
/// The serial line internet protocol (SLIP) works as follows:
/// - A transmission packet (the payload data) is appended by a special "END" byte, which distinguishes the datagram boundaries in the byte stream.
/// - If the END byte occurs in the payload data to be sent, the two byte sequence [ESC, ESC_END] is sent instead.
/// - If the ESC byte occurs in the payload data to be sent, the two byte sequence [ESC, ESC_ESC] is sent instead.
/// - Variants of the protocol may also begin transmission packets (the payload data) with an END byte (which is the case in this realization of SLIP)
///
/// The special bytes used by SLIP are:
/// - 0xC0 ... END -> Frame End (and Frame Beginning in this realization)
/// - 0xDB ... ESC -> Frame Escape
/// - 0xDC ... ESC_END -> Transposed Frame END
/// - 0xDD ... ESC_ESC -> Transposed Frame ESC
///
/// Reference: https://en.m.wikipedia.org/wiki/Serial_Line_Internet_Protocol
///
func scanRxBufferForFrames() {
// get indices of all END bytes
// TODO: idexesOfEndBytes is an Objective-C-extension of NSData. Reprogram this in Swift for data type "Data"
guard let endByteIndices = NSData.init(data: rxBuffer).indexesOfEndBytes() else {
return
}
// Loop over the END bytes and search for a complete frame, i.e. a sequence of bytes as follows: [END ... at-least-two-bytes ... END]
var previousEndByteIndex = NSNotFound
for endByteIndex in endByteIndices {
print(String(endByteIndex.description) as Any)
if (previousEndByteIndex != NSNotFound) {
if endByteIndex - previousEndByteIndex > 2 { // Contains at least one byte and checksum byte
print("Identified a potential SLIP frame")
print(self.rxBuffer.debugDescription)
// Extact the frame (END byte at beginning and end are aleady removed)
let escapedPacket = self.rxBuffer.subdata(in: Range((previousEndByteIndex + 1).."
//
// 2.) Characteristics
// Simble provides three Characteristics: one read and two write characteristics. This is
// a "hard coded" feature of the simblee and cannot be changed. The debugDescription
// of these three characteristics is as follows:
//
// a) Read Characteristic:
// ""
// ... with properties:
// __C.CBCharacteristicProperties(rawValue: 18)
// Broadcast: [false]
// Read: [true]
// WriteWithoutResponse: [false]
// Write: [false]
// Notify: [true]
// Indicate: [false]
// AuthenticatedSignedWrites: [false]
// ExtendedProperties: [false]
// NotifyEncryptionRequired: [false]
// BroaIndicateEncryptionRequireddcast: [false]
//
// b) First Write Characteristic:
// ""
// ... with properties:
// __C.CBCharacteristicProperties(rawValue: 12)
// Broadcast: [false]
// Read: [false]
// WriteWithoutResponse: [true]
// Write: [true]
// Notify: [false]
// Indicate: [false]
// AuthenticatedSignedWrites: [false]
// ExtendedProperties: [false]
// NotifyEncryptionRequired: [false]
// BroaIndicateEncryptionRequireddcast: [false]
//
// c) Second Write Characteristic:
// ""
// ... with properties:
// __C.CBCharacteristicProperties(rawValue: 12)
// Broadcast: [false]
// Read: [false]
// WriteWithoutResponse: [true]
// Write: [true]
// Notify: [false]
// Indicate: [false]
// AuthenticatedSignedWrites: [false]
// ExtendedProperties: [false]
// NotifyEncryptionRequired: [false]
// BroaIndicateEncryptionRequireddcast: [false]
//
// 3.) Further information on Simblee services and characteristics can be found on
// http://forum.rfduino.com/index.php?topic=1066.15
//
import Foundation
import UIKit
import CoreBluetooth
public enum SimbleeManagerState: String {
case Unassigned = "Unassigned"
case Scanning = "Scanning"
case Disconnected = "Disconnected"
case DisconnectedManually = "Disconnected manually"
case Connecting = "Connecting"
case Connected = "Connected"
case Notifying = "Notifying"
}
protocol SimbleeManagerDelegate {
func simbleeManagerPeripheralStateChanged(_ state:SimbleeManagerState)
func simbleeManagerReceivedMessage(_ messageIdentifier:UInt16, txFlags:UInt8, payloadData:Data)
}
class SimbleeManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, SLIPBufferDelegate {
// MARK: - Properties
var centralManager: CBCentralManager!
var peripheral: CBPeripheral?
var slipBuffer = SLIPBuffer()
fileprivate let serviceUUIDs:[CBUUID]? = [CBUUID(string: "2220")]
var BLEScanDuration = 3.0
var delegate: SimbleeManagerDelegate? {
didSet {
// Help delegate initialize by sending current state directly after delegate assignment
delegate?.simbleeManagerPeripheralStateChanged(state)
}
}
var state: SimbleeManagerState = .Unassigned {
didSet {
// Help delegate initialize by sending current state directly after delegate assignment
delegate?.simbleeManagerPeripheralStateChanged(state)
}
}
// MARK: - Mehods
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil, options: nil)
slipBuffer.delegate = self
}
func scanForSimblee() {
if centralManager.state == .poweredOn {
print ("Start scanning for Simblee")
centralManager.scanForPeripherals(withServices: serviceUUIDs, options: nil)
state = .Scanning
}
}
func connect() {
if let peripheral = peripheral {
peripheral.delegate = self
centralManager.stopScan()
centralManager.connect(peripheral, options: nil)
state = .Connecting }
}
func disconnectManually() {
switch state {
case .Connected, .Connecting, .Notifying:
state = .DisconnectedManually // to avoid reconnect in didDisconnetPeripheral
centralManager.cancelPeripheralConnection(peripheral!)
default:
break
}
// if state == .Connected || peripheral?.state == .Connecting {
// centralManager.cancelPeripheralConnection(peripheral!)
// }
}
// MARK: - CBCentralManagerDelegate
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("centralManagerDidUpdateState")
// TODO: maybe handle the case of bluetooth beeing switched of (and sometimes later) on again here by stopping and restarting scanning
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("didDiscoverPeripheral with name \(peripheral.name)")
self.peripheral = peripheral
connect()
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("didConnectPeripheral")
state = .Connected
// Discover all Services. This might be helpful if writing is needed some time
peripheral.discoverServices(nil)
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("didFailToConnectPeripheral")
state = .Disconnected
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("didDisconnectPeripheral")
switch state {
case .DisconnectedManually:
state = .Disconnected
default:
state = .Disconnected
scanForSimblee()
}
// Keep this code in case you want it some later time: it is used for reconnection only in background mode
// state = .Disconnected
// // Start scanning, if disconnection occurred in background mode
// if UIApplication.sharedApplication().applicationState == .Background ||
// UIApplication.sharedApplication().applicationState == .Inactive {
// scanForSimblee()
// }
}
// MARK: - CBPeripheralDelegate
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
print("didDiscoverServices")
if let services = peripheral.services {
print("Discovered services on RFduino");
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
// print("Service: ")
// debugPrint(service.debugDescription)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
print("didDiscoverCharacteristicsForService");
if let error = error {
print("An error occured: \(error.localizedDescription)")
}
if let characteristics = service.characteristics {
for characteristic in characteristics {
// print("Characteristic: ")
// debugPrint(characteristic.debugDescription)
// print("... with properties: ")
// debugPrint(characteristic.properties)
// print("Broadcast: ", [characteristic.properties.contains(.Broadcast)])
// print("Read: ", [characteristic.properties.contains(.Read)])
// print("WriteWithoutResponse: ", [characteristic.properties.contains(.WriteWithoutResponse)])
// print("Write: ", [characteristic.properties.contains(.Write)])
// print("Notify: ", [characteristic.properties.contains(.Notify)])
// print("Indicate: ", [characteristic.properties.contains(.Indicate)])
// print("AuthenticatedSignedWrites: ", [characteristic.properties.contains(.AuthenticatedSignedWrites )])
// print("ExtendedProperties: ", [characteristic.properties.contains(.ExtendedProperties)])
// print("NotifyEncryptionRequired: ", [characteristic.properties.contains(.NotifyEncryptionRequired)])
// print("BroaIndicateEncryptionRequireddcast: ", [characteristic.properties.contains(.IndicateEncryptionRequired)])
// Choose the notifiying characteristic and Register to be notified whenever the simblee transmits
if (characteristic.properties.intersection(.notify)) == .notify {
peripheral.setNotifyValue(true, for: characteristic)
}
}
} else {
print("Discovered characteristics on RFduino, but no characteristics listed. There must be some error.");
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
print("didUpdateNotificationStateForCharacteristic")
if let error = error {
print("An error occured: \(error.localizedDescription)")
}
state = .Notifying
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print("didUpdateValueForCharacteristic")
if let error = error {
print("Characteristic update error = \(error.localizedDescription)")
} else {
if let value = characteristic.value {
slipBuffer.appendEscapedBytes(value)
}
}
}
// MARK: - SLIPBufferDelegate
func slipBufferReceivedPayload(_ payloadData: Data, payloadIdentifier: UInt16, txFlags: UInt8) {
// Inform delegate
if let delegate = delegate {
delegate.simbleeManagerReceivedMessage(payloadIdentifier, txFlags: txFlags, payloadData: payloadData)
}
}
}
================================================
FILE: LibreMonitor/Bluetooth/constants.h
================================================
// Define unique identifiers for each data packet here. This identifier is transfered together with the data and can be used in the corresponding iOS app to identify a packet, even if there are packets of the same type
#define ALL_BYTES 0x1007 //
#define IDN_DATA 0x2001 //
#define SYSTEM_INFORMATION_DATA 0x2002 //
#define BATTERY_DATA 0x2005 //
================================================
FILE: LibreMonitor/Bluetooth/data_types.h
================================================
// Data types for data to be transfered
// Need a struct with one variable and the packed attribute to make this an array in swift (in a simple way
// Battery
typedef struct __attribute__((packed)) {
float voltage;
float temperature;
} BatteryDataType;
/// System information command response
/// @detail Contains the relevant information received with the system information command is already distributed into the follwing parameters. See the BM019 manual and the CR95HF manual for information on the the codes and flag meanings
/// @param resultCode 0x80 if no error
/// @param responseFlags
/// @param infoFlags bit 0 is set to 1 in case of error
/// @param errorCode error code
/// @param uid 8 bytes containing the uid, order already reversed, e.g. (colons only inserted for readability): E0:07:A0:00:00:0C:48:BD. All zeros in case of an error.
typedef struct __attribute__((packed)) {
uint8_t uid[8];
uint8_t resultCode;
uint8_t responseFlags;
uint8_t infoFlags;
uint8_t errorCode;
} SystemInformationDataType;
/// IDN command response, which is the device ID
/// @detail Contains the relevant information received with the IDN command is already distributed into the follwing parameters. See the BM019 manual and the CR95HF manual for information on the the codes and flag meanings
/// @param resultCode 0x00 if no error
/// @param deiceID 13 bytes containing the device ID.
typedef struct __attribute__((packed)) {
uint8_t resultCode;
uint8_t deviceID[13];
uint8_t romCRC[2];
} IDNDataType;
/// The first 344 bytes of data as read from FRAM of the Freestyle Libre Sensor
/// @detail Contains 24 byes of header, 296 bytes of body with blood sugar data
/// and 24 bytes of footer
/// @param bytes 344 bytes containing the the raw data
typedef struct __attribute__((packed)) {
uint8_t allBytes[344];
} AllBytesDataType;
================================================
FILE: LibreMonitor/HeaderData+CoreDataClass.swift
================================================
//
// HeaderData+CoreDataClass.swift
// LibreMonitor
//
// Created by Uwe Petersen on 14.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
@objc(HeaderData)
public class HeaderData: NSManagedObject {
}
================================================
FILE: LibreMonitor/HeaderData+CoreDataProperties.swift
================================================
//
// HeaderData+CoreDataProperties.swift
// LibreMonitor
//
// Created by Uwe Petersen on 14.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
extension HeaderData {
@nonobjc public class func fetchRequest() -> NSFetchRequest {
return NSFetchRequest(entityName: "HeaderData");
}
@NSManaged public var bytes: String?
@NSManaged public var date: NSDate?
}
================================================
FILE: LibreMonitor/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
APPL
CFBundleShortVersionString
1.0
CFBundleVersion
1
LSRequiresIPhoneOS
UIBackgroundModes
bluetooth-central
UIFileSharingEnabled
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
Main
UIRequiredDeviceCapabilities
armv7
healthkit
UIStatusBarTintParameters
UINavigationBar
Style
UIBarStyleDefault
Translucent
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
================================================
FILE: LibreMonitor/LibreMonitor-Bridging-Header.h
================================================
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "NSData+CRC8.h"
#import "NSData+SLIP.h"
#import "constants.h"
#import "data_types.h"
================================================
FILE: LibreMonitor/LibreMonitor.entitlements
================================================
com.apple.developer.healthkit
================================================
FILE: LibreMonitor/LibreMonitor.xcdatamodeld/.xccurrentversion
================================================
_XCCurrentVersionName
LibreMonitor.xcdatamodel
================================================
FILE: LibreMonitor/LibreMonitor.xcdatamodeld/LibreMonitor.xcdatamodel/contents
================================================
================================================
FILE: LibreMonitor/LibreMonitorUITests-Bridging-Header.h
================================================
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "NSData+CRC8.h"
#import "NSData+SLIP.h"
#import "constants.h"
#import "data_types.h"
================================================
FILE: LibreMonitor/Main.storyboard
================================================
The following sets forth attribution notices for third party software that may be contained in portions of this product.
---
The following software or parts of it may be included in this product:
Cg
Cg
Cg
Cg
see https://github.com/krzyzanowskim/CryptoSwift
Copyright (C) 2014 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com>
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
- The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
- Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
- This notice may not be removed or altered from any source or binary distribution.
---
The following software or parts of it may be included in this product:
Cg
Cg
Cg
Cg
see https://github.com/cconway/RFduinoUBP
The MIT License (MIT)
Copyright (c) 2015 cconway
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. ---
The following software or parts of it may be included in this product: RFduinoUBP, see https://github.com/cconway/RFduinoUBP
---
The following software or parts of it may be included in this product:
Cg
Cg
Cg
Cg
see hhttps://github.com/danielgindi/Charts
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: LibreMonitor/Model/CRC.swift
================================================
//
// CRC.swift
// LibreMonitor
//
// Created by Uwe Petersen on 26.07.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
//
// Part of this code is taken from
// CRC.swift
// CryptoSwift
//
// Created by Marcin Krzyzanowski on 25/08/14.
// Copyright (c) 2014 Marcin Krzyzanowski. All rights reserved.
//
import Foundation
final class Crc {
/// Table of precalculated crc16 values
static let crc16table: [UInt16] = [0, 4489, 8978, 12955, 17956, 22445, 25910, 29887, 35912, 40385, 44890, 48851, 51820, 56293, 59774, 63735, 4225, 264, 13203, 8730, 22181, 18220, 30135, 25662, 40137, 36160, 49115, 44626, 56045, 52068, 63999, 59510, 8450, 12427, 528, 5017, 26406, 30383, 17460, 21949, 44362, 48323, 36440, 40913, 60270, 64231, 51324, 55797, 12675, 8202, 4753, 792, 30631, 26158, 21685, 17724, 48587, 44098, 40665, 36688, 64495, 60006, 55549, 51572, 16900, 21389, 24854, 28831, 1056, 5545, 10034, 14011, 52812, 57285, 60766, 64727, 34920, 39393, 43898, 47859, 21125, 17164, 29079, 24606, 5281, 1320, 14259, 9786, 57037, 53060, 64991, 60502, 39145, 35168, 48123, 43634, 25350, 29327, 16404, 20893, 9506, 13483, 1584, 6073, 61262, 65223, 52316, 56789, 43370, 47331, 35448, 39921, 29575, 25102, 20629, 16668, 13731, 9258, 5809, 1848, 65487, 60998, 56541, 52564, 47595, 43106, 39673, 35696, 33800, 38273, 42778, 46739, 49708, 54181, 57662, 61623, 2112, 6601, 11090, 15067, 20068, 24557, 28022, 31999, 38025, 34048, 47003, 42514, 53933, 49956, 61887, 57398, 6337, 2376, 15315, 10842, 24293, 20332, 32247, 27774, 42250, 46211, 34328, 38801, 58158, 62119, 49212, 53685, 10562, 14539, 2640, 7129, 28518, 32495, 19572, 24061, 46475, 41986, 38553, 34576, 62383, 57894, 53437, 49460, 14787, 10314, 6865, 2904, 32743, 28270, 23797, 19836, 50700, 55173, 58654, 62615, 32808, 37281, 41786, 45747, 19012, 23501, 26966, 30943, 3168, 7657, 12146, 16123, 54925, 50948, 62879, 58390, 37033, 33056, 46011, 41522, 23237, 19276, 31191, 26718, 7393, 3432, 16371, 11898, 59150, 63111, 50204, 54677, 41258, 45219, 33336, 37809, 27462, 31439, 18516, 23005, 11618, 15595, 3696, 8185, 63375, 58886, 54429, 50452, 45483, 40994, 37561, 33584, 31687, 27214, 22741, 18780, 15843, 11370, 7921, 3960]
/// Calculates crc16. Taken from https://github.com/krzyzanowskim/CryptoSwift with modifications (reversing and byte swapping) to adjust for crc as used by Freestyle Libre
///
/// - parameter message: Array of bytes for which the crc is to be calculated
/// - parameter seed: seed for crc
///
/// - returns: crc16
static func crc16(_ message:[UInt8], seed: UInt16? = nil) -> UInt16 {
var crc: UInt16 = seed != nil ? seed! : 0x0000
// calculate crc
for chunk in BytesSequence(chunkSize: 256, data: message) {
for b in chunk {
crc = (crc >> 8) ^ crc16table[Int((crc ^ UInt16(b)) & 0xFF)]
}
}
// reverse the bits (modification by Uwe Petersen, 2016-06-059
var reverseCrc = UInt16(0)
for _ in 0..<16 {
reverseCrc = reverseCrc << 1 | crc & 1
crc >>= 1
}
// swap bytes and return (modification by Uwe Petersen, 2016-06-059
return reverseCrc.byteSwapped
}
/// Checks crc for an array of bytes.
///
/// Assumes that the first two bytes are the crc16 of the bytes array and compares the corresponding value with the crc16 calculated over the rest of the array of bytes.
///
/// - parameter bytes: Array of bytes with a crc in the first two bytes
///
/// - returns: true if crc is valid
static func hasValidCrc16InFirstTwoBytes(_ bytes: [UInt8]) -> Bool {
print(Array(bytes.dropFirst(2)))
let calculatedCrc = Crc.crc16(Array(bytes.dropFirst(2)), seed: 0xffff)
let enclosedCrc = (UInt16(bytes[0]) << 8) | UInt16(bytes[1])
// print(String(format: "Calculated crc is %X and enclosed crc is %x", arguments: [calculatedCrc, enclosedCrc]))
return calculatedCrc == enclosedCrc
}
}
/// Struct BytesSequence, taken from https://github.com/krzyzanowskim/CryptoSwift
struct BytesSequence: Sequence {
let chunkSize: Int
let data: Array
func makeIterator() -> AnyIterator> {
var offset:Int = 0
return AnyIterator {
let end = Swift.min(self.chunkSize, self.data.count - offset)
let result = self.data[offset.. 10100 00000 00000 00000 00000 01001 01100 10000 01011 11000
// | | | | | | | | | |
// | | | | | | | | | +- = 24 -> "R"
// | | | | | | | | +------- = 11 -> "C"
// | | | | | | | +------------- = 16 -> "H"
// | | | | | | +------------------- = 12 -> "D"
// | | | | | +------------------------- = 9 -> "9"
// | | | | +------------------------------- = 0 -> "0"
// | | | +------------------------------------- = 0 -> "0"
// | | +------------------------------------------- = 0 -> "0"
// | +------------------------------------------------- = 0 -> "0"
// +------------------------------------------------------- = 20 -> "M"
//
// 3.) Prepend "0" at the beginning an thus receive "0M00009DHCR"
let lookupTable = ["0","1","2","3","4","5","6","7","8","9","A","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","T","U","V","W","X","Y","Z"]
let uidString = self.uid.substring(from: self.uid.characters.index(self.uid.startIndex, offsetBy: 4)) // "E007A0000025905E" -> "A0000025905E"
var serialNumber = ""
guard uidString.lengthOfBytes(using: String.Encoding.ascii) == 12,
let uidAsInt = Int(uidString, radix: 16) // "A0000025905E" -> 175921862905950
else {return ""}
let uidAsBinaryString = String(uidAsInt, radix: 2) + "00" // -> "10100000000000000000000000100101100100000101111000"
let length = uidAsBinaryString.lengthOfBytes(using: String.Encoding.ascii)
for index in stride(from: 0, to: length, by: 5) {
if index + 4 < length {
let startIndex = uidAsBinaryString.startIndex
let leftIndex = uidAsBinaryString.index(startIndex, offsetBy: index)
let rightIndex = uidAsBinaryString.index(startIndex, offsetBy: index+5)
let range = leftIndex..= 0 && theInt < lookupTable.count {
serialNumber += lookupTable[theInt] // "10100" -> 20 -> "M"
}
}
}
serialNumber = "0" + serialNumber // "M00009DHCR" -> "0M00009DHCR"
return serialNumber
}()
lazy var prettyUid: String = {
var prettyUid = self.uid
let length = self.uid.lengthOfBytes(using: String.Encoding.ascii)
for index in stride(from: 2, to: length, by: 2).reversed() {
prettyUid.insert(Character(":"), at: prettyUid.characters.index(prettyUid.startIndex, offsetBy: index))
}
return prettyUid
}()
init(withUID uid: String) {
self.uid = uid
}
}
================================================
FILE: LibreMonitor/Model/Measurement.swift
================================================
//
// Measurement.swift
// LibreMonitor
//
// Created by Uwe Petersen on 25.08.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
/// Structure for one glucose measurement including value, date and raw data bytes
struct Measurement {
/// The date for this measurement
let date: Date
/// The bytes as read from the sensor. All data is derived from this \"raw data"
let bytes: [UInt8]
/// The bytes as String
let byteString: String
/// The raw value as read from the sensor
let rawValue: Int
/// slope to calculate glucose from raw value in (mg/dl)/raw
let slope: Double
/// glucose offset to be added in mg/dl
let offset: Double
/// The glucose value in mg/dl
let glucose: Double
/// Initialize a new glucose measurement
///
/// - parameter bytes: raw data bytes as read from the sensor
/// - parameter slope: slope to calculate glucose from raw value in (mg/dl)/raw
/// - parameter offset: glucose offset to be added in mg/dl
/// - parameter date: date of the measurement
///
/// - returns: Measurement
init(bytes: [UInt8], slope: Double = 0.1, offset: Double = 0.0, date: Date) {
self.bytes = bytes
self.byteString = bytes.reduce("", {$0 + String(format: "%02X", arguments: [$1])})
self.rawValue = (Int(bytes[1]) << 8) & 0x0F00 + Int(bytes[0])
self.slope = slope
self.offset = offset
self.glucose = offset + slope * Double(rawValue)
self.date = date
}
var description: String {
return String("Glucose: \(glucose) (mg/dl), date: \(date), slope: \(slope), offset: \(offset),rawValue: \(rawValue), bytes: \(bytes)" )
}
}
================================================
FILE: LibreMonitor/Model/SensorData.swift
================================================
//
// SensorData
// LibreMonitor
//
// Created by Uwe Petersen on 26.07.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
/// Structure for data from Freestyle Libre sensor
/// To be initialized with the bytes as read via nfc. Provides all derived data.
struct SensorData {
/// Number of bytes of sensor data to be used (read only), i.e. 344 bytes (24 for header, 296 for body and 24 for footer)
let numberOfBytes = 344 // Header and body and footer of Freestyle Libre data (i.e. 40 blocks of 8 bytes)
/// Array of 344 bytes as read via nfc
let bytes: [UInt8]
/// Subarray of 24 header bytes
let header: [UInt8]
/// Subarray of 296 body bytes
let body: [UInt8]
/// Subarray of 24 footer bytes
let footer: [UInt8]
/// Date when data was read from sensor
let date: Date
/// Minutes (approx) since start of sensor
let minutesSinceStart: Int
/// Index on the next block of trend data that the sensor will measure and store
let nextTrendBlock: Int
/// Index on the next block of history data that the sensor will create from trend data and store
let nextHistoryBlock: Int
/// true if the header crc, stored in the first two bytes, is equal to the calculated crc
var hasValidHeaderCRC: Bool {
return Crc.hasValidCrc16InFirstTwoBytes(header)
}
/// true if the body crc, stored in the first two bytes, is equal to the calculated crc
var hasValidBodyCRC: Bool {
return Crc.hasValidCrc16InFirstTwoBytes(body)
}
/// true if the footer crc, stored in the first two bytes, is equal to the calculated crc
var hasValidFooterCRC: Bool {
return Crc.hasValidCrc16InFirstTwoBytes(footer)
}
/// Sensor state (ready, failure, starting etc.)
var state: SensorState {
switch header[4] {
case 01:
return SensorState.notYetStarted
case 02:
return SensorState.starting
case 03:
return SensorState.ready
case 04:
return SensorState.expired
case 05:
return SensorState.shutdown
case 06:
return SensorState.failure
default:
return SensorState.unknown
}
}
init?(bytes: [UInt8], date: Date = Date()) {
guard bytes.count == numberOfBytes else {
return nil
}
self.bytes = bytes
self.date = date
let headerRange = 0..<24 // 24 bytes, i.e. 3 blocks a 8 bytes
let bodyRange = 24..<320 // 296 bytes, i.e. 37 blocks a 8 bytes
let footerRange = 320..<344 // 24 bytes, i.e. 3 blocks a 8 bytes
self.header = Array(bytes[headerRange])
self.body = Array(bytes[bodyRange])
self.footer = Array(bytes[footerRange])
self.nextTrendBlock = Int(body[2])
self.nextHistoryBlock = Int(body[3])
self.minutesSinceStart = Int(body[293]) << 8 + Int(body[292])
}
/// Get array of 16 trend glucose measurements.
/// Each array is sorted such that the most recent value is at index 0 and corresponds to the time when the sensor was read, i.e. self.date. The following measurements are each one more minute behind, i.e. -1 minute, -2 mintes, -3 minutes, ... -15 minutes.
///
/// - parameter offset: offset in mg/dl that is added
/// - parameter slope: slope in (mg/dl)/ raw
///
/// - returns: Array of Measurements
func trendMeasurements(_ offset: Double = 0.0, slope: Double = 0.1) -> [Measurement] {
var measurements = [Measurement]()
// Trend data is stored in body from byte 4 to byte 4+96=100 in units of 6 bytes. Index on data such that most recent block is first.
for blockIndex in 0...15 {
var index = 4 + (nextTrendBlock - 1 - blockIndex) * 6 // runs backwards
if index < 4 {
index = index + 96 // if end of ring buffer is reached shift to beginning of ring buffer
}
let range = index.. [Measurement] {
let mostRecentHistoryDate = date.addingTimeInterval( 60.0 * -Double( (minutesSinceStart - 3) % 15 + 3 ) )
var measurements = [Measurement]()
// History data is stored in body from byte 100 to byte 100+192-1=291 in units of 6 bytes. Index on data such that most recent block is first.
for blockIndex in 0..<32 {
var index = 100 + (nextHistoryBlock - 1 - blockIndex) * 6 // runs backwards
if index < 100 {
index = index + 192 // if end of ring buffer is reached shift to beginning of ring buffer
}
let range = index.. NSFetchRequest {
return NSFetchRequest(entityName: "BloodGlucose");
}
@NSManaged public var bytes: String?
@NSManaged public var dateString: String?
@NSManaged public var id: Int32
@NSManaged public var type: Int16
@NSManaged public var value: Double
@NSManaged public var date: NSDate?
@NSManaged public var sensor: Sensor?
}
================================================
FILE: LibreMonitor/ModelCoreData/CoreDataStack.swift
================================================
//
// CoreDataStack.swift
// LibreMonitor
//
// Created by Uwe Petersen on 13.04.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import UIKit
import CoreData
class CoreDataStack: NSObject {
// var managedObjectContext: NSManagedObjectContext
override init() {
}
// override init() {
//
// // This resource is the same name as your xcdatamodeld contained in your project.
// guard let modelURL = NSBundle.mainBundle().URLForResource("DataModel", withExtension:"momd") else {
// fatalError("Error loading model from bundle")
// }
//
// // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
// guard let mom = NSManagedObjectModel(contentsOfURL: modelURL) else {
// fatalError("Error initializing mom from: \(modelURL)")
// }
//
// let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
//
// managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
// managedObjectContext.persistentStoreCoordinator = psc
//
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
// let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
// let docURL = urls[urls.endIndex-1]
// /* The directory the application uses to store the Core Data store file.
// This code uses a file named "DataModel.sqlite" in the application's documents directory.
// */
// let storeURL = docURL.URLByAppendingPathComponent("DataModel.sqlite")
// do {
// try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
// } catch {
// fatalError("Error migrating store: \(error)")
// }
// }
// }
lazy var applicationDocumentsDirectory: URL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named "UPP.LibreMonitor" in the application's documents Application Support directory.
var urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return urls[urls.count-1]
}()
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = Bundle.main.url(forResource: "LibreMonitor", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
return coordinator
}()
lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// MARK: - Core Data Saving support
func saveContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
}
================================================
FILE: LibreMonitor/ModelCoreData/HeaderData+CoreDataClass.swift
================================================
//
// HeaderData+CoreDataClass.swift
// LibreMonitor
//
// Created by Uwe Petersen on 09.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
@objc(HeaderData)
public class HeaderData: NSManagedObject {
}
================================================
FILE: LibreMonitor/ModelCoreData/HeaderData+CoreDataProperties.swift
================================================
//
// HeaderData+CoreDataProperties.swift
// LibreMonitor
//
// Created by Uwe Petersen on 09.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
extension HeaderData {
@nonobjc public class func fetchRequest() -> NSFetchRequest {
return NSFetchRequest(entityName: "HeaderData");
}
@NSManaged public var bytes: String?
@NSManaged public var date: NSDate?
}
================================================
FILE: LibreMonitor/ModelCoreData/Reader+CoreDataClass.swift
================================================
//
// Reader+CoreDataClass.swift
// LibreMonitor
//
// Created by Uwe Petersen on 09.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
public class Reader: NSManagedObject {
}
================================================
FILE: LibreMonitor/ModelCoreData/Reader+CoreDataProperties.swift
================================================
//
// Reader+CoreDataProperties.swift
// LibreMonitor
//
// Created by Uwe Petersen on 09.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
extension Reader {
@nonobjc public class func fetchRequest() -> NSFetchRequest {
return NSFetchRequest(entityName: "Reader");
}
@NSManaged public var batteryVoltage: Double
@NSManaged public var temperature: Double
@NSManaged public var uid: String?
}
================================================
FILE: LibreMonitor/ModelCoreData/Sensor+CoreDataClass.swift
================================================
//
// Sensor+CoreDataClass.swift
// LibreMonitor
//
// Created by Uwe Petersen on 09.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
public class Sensor: NSManagedObject {
}
================================================
FILE: LibreMonitor/ModelCoreData/Sensor+CoreDataProperties.swift
================================================
//
// Sensor+CoreDataProperties.swift
// LibreMonitor
//
// Created by Uwe Petersen on 09.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
extension Sensor {
@nonobjc public class func fetchRequest() -> NSFetchRequest {
return NSFetchRequest(entityName: "Sensor");
}
@NSManaged public var startDate: NSDate?
@NSManaged public var uid: String?
@NSManaged public var lastScanDate: NSDate?
@NSManaged public var minutesSinceStart: Int32
@NSManaged public var bloodGlucose: NSSet?
}
// MARK: Generated accessors for bloodGlucose
extension Sensor {
@objc(addBloodGlucoseObject:)
@NSManaged public func addToBloodGlucose(_ value: BloodGlucose)
@objc(removeBloodGlucoseObject:)
@NSManaged public func removeFromBloodGlucose(_ value: BloodGlucose)
@objc(addBloodGlucose:)
@NSManaged public func addToBloodGlucose(_ values: NSSet)
@objc(removeBloodGlucose:)
@NSManaged public func removeFromBloodGlucose(_ values: NSSet)
}
================================================
FILE: LibreMonitor/Reader+CoreDataClass.swift
================================================
//
// Reader+CoreDataClass.swift
// LibreMonitor
//
// Created by Uwe Petersen on 14.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
public class Reader: NSManagedObject {
}
================================================
FILE: LibreMonitor/Reader+CoreDataProperties.swift
================================================
//
// Reader+CoreDataProperties.swift
// LibreMonitor
//
// Created by Uwe Petersen on 14.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
extension Reader {
@nonobjc public class func fetchRequest() -> NSFetchRequest {
return NSFetchRequest(entityName: "Reader");
}
@NSManaged public var batteryVoltage: Double
@NSManaged public var temperature: Double
@NSManaged public var uid: String?
}
================================================
FILE: LibreMonitor/Sensor+CoreDataClass.swift
================================================
//
// Sensor+CoreDataClass.swift
// LibreMonitor
//
// Created by Uwe Petersen on 14.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
public class Sensor: NSManagedObject {
}
================================================
FILE: LibreMonitor/Sensor+CoreDataProperties.swift
================================================
//
// Sensor+CoreDataProperties.swift
// LibreMonitor
//
// Created by Uwe Petersen on 14.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import CoreData
extension Sensor {
@nonobjc public class func fetchRequest() -> NSFetchRequest {
return NSFetchRequest(entityName: "Sensor");
}
@NSManaged public var lastScanDate: NSDate?
@NSManaged public var minutesSinceStart: Int32
@NSManaged public var startDate: NSDate?
@NSManaged public var uid: String?
@NSManaged public var bloodGlucose: NSSet?
}
// MARK: Generated accessors for bloodGlucose
extension Sensor {
@objc(addBloodGlucoseObject:)
@NSManaged public func addToBloodGlucose(_ value: BloodGlucose)
@objc(removeBloodGlucoseObject:)
@NSManaged public func removeFromBloodGlucose(_ value: BloodGlucose)
@objc(addBloodGlucose:)
@NSManaged public func addToBloodGlucose(_ values: NSSet)
@objc(removeBloodGlucose:)
@NSManaged public func removeFromBloodGlucose(_ values: NSSet)
}
================================================
FILE: LibreMonitor/SimbleeCode/crc8.h
================================================
// LICENSES: [077915]
// -----------------------------------
// The contents of this file contains the aggregate of contributions
// covered under one or more licences. The full text of those licenses
// can be found in the "LICENSES" file at the top level of this project
// identified by the MD5 fingerprints listed above.
//
// Uwe Petersen: Modified version to test transmission part in iOS
#import
typedef uint8_t byte;
byte CRC8(void *data_in, byte number_of_bytes_to_read);
================================================
FILE: LibreMonitor/SimbleeCode/crc8.m
================================================
// LICENSES: [077915]
// -----------------------------------
// The contents of this file contains the aggregate of contributions
// covered under one or more licences. The full text of those licenses
// can be found in the "LICENSES" file at the top level of this project
// identified by the MD5 fingerprints listed above.
//
//
// Uwe Petersen: Modified version to test transmission part in iOS
#include "crc8.h"
#define CRC8INIT 0x00
#define CRC8POLY 0x18 // 0X18 = X^8+X^5+X^4+X^0
byte CRC8(void *bytes, byte number_of_bytes_to_read) {
byte *data_in = (byte*) bytes;
byte crc;
uint16_t loop_count;
byte bit_counter;
byte data;
byte feedback_bit;
crc = CRC8INIT;
for (loop_count = 0; loop_count != number_of_bytes_to_read; loop_count++)
{
data = data_in[loop_count];
bit_counter = 8;
do {
feedback_bit = (crc ^ data) & 0x01;
if ( feedback_bit == 0x01 ) {
crc = crc ^ CRC8POLY;
}
crc = (crc >> 1) & 0x7F;
if ( feedback_bit == 0x01 ) {
crc = crc | 0x80;
}
data = data >> 1;
bit_counter--;
} while (bit_counter > 0);
}
return crc;
}
================================================
FILE: LibreMonitor/SimbleeCode/libUBP.h
================================================
//
// Uwe Petersen: Modified version to test transmission part in iOS
#import
typedef uint8_t byte;
#ifndef libUBP_h
#define libUBP_h
#endif
typedef enum {
UBP_TxFlagNone = 0 << 0,
UBP_TxFlagIsRPC = 1 << 0,
UBP_TxFlagRequiresACK = 1 << 1
} UBP_TxFlags;
// Public
void UBP_pump();
bool UBP_queuePacketTransmission(unsigned short packetIdentifier, UBP_TxFlags txFlags, const char *packetBytes, unsigned short byteCount);
//bool UBP_isBusy();
// 2016-06-26: Needed for tests
void getTxBuffer(char *txBuffer, int *txBufferLength);
void SimbleeBLE_onConnect();
void SimbleeBLE_onDisconnect();
// Private
void _UBP_pumpTxQueue();
//void _UBP_ingestRxBytes(char *receivedBytes, int byteCount);
int _UBP_makeEscapedCopy(const char *inputBuffer, unsigned short inputBufferLength, char *outputBuffer, unsigned short outputBufferLength);
int _UBP_makeUnEscapedCopy(const char *inputBuffer, unsigned short inputBufferLength, char *outputBuffer);
void _UBP_hostDisconnected();
// To be implemented by end-user externally
//extern void UBP_incomingChecksumFailed() __attribute__((weak));
//extern void UBP_receivedPacket(unsigned short packetIdentifier, UBP_TxFlags txFlags, void *packetBuffer) __attribute__((weak));
//extern void UBP_didAdvertise(bool start) __attribute__((weak));
//extern void UBP_didConnect() __attribute__((weak));
//extern void UBP_didDisconnect() __attribute__((weak));
================================================
FILE: LibreMonitor/SimbleeCode/libUBP.m
================================================
//
// Uwe Petersen: Modified version to test transmission part in iOS
#import "libUBP.h"
#import "crc8.h"
// Build-time configurations
//#define BUFFER_LENGTH 64 // Uwe changed this on 2016-01-04
#define BUFFER_LENGTH 400 // Uwe changed this on 2016-07-24
#define TX_CHUNK_SIZE 20
#define PACKET_ID_SIZE 2
// Serial Line IP (SLIP) escaping constants
#define ESCAPE_BYTE 0xDB
#define END_BYTE 0xC0
#define ESCAPED_ESCAPE_BYTE 0xDD
#define ESCAPED_END_BYTE 0xDC
const char escapeSequence_[1] = {ESCAPE_BYTE};
const char endSequence_[1] = {END_BYTE};
const char escapedEndSequence_[2] = {ESCAPE_BYTE, ESCAPED_END_BYTE};
const char escapedEscapeSequence_[2] = {ESCAPE_BYTE, ESCAPED_ESCAPE_BYTE};
// Buffers
char ubpTxBuffer[BUFFER_LENGTH];
int ubpTxBufferLength = 0;
int ubpTxBufferSentByteCount = 0;
char ubpRxBuffer[BUFFER_LENGTH];
int ubpRxBufferLength = 0;
char ubpUnescapedRxBufferBuffer[BUFFER_LENGTH];
int ubpUnescapedRxBufferBufferLength = 0;
// State Variables
bool UBP_isTxPending = false;
bool hostIsConnected = true;
//bool hostIsConnected = false;
// 2016-06-27: for testing purposes simulate simblee
char simbleeBuffer[BUFFER_LENGTH];
int simbleeBufferLength = 0;
/*
// Functions
bool UBP_isBusy() {
return UBP_isTxPending;
}
*/
void UBP_pump() {
_UBP_pumpTxQueue();
}
void _UBP_pumpTxQueue() {
if (UBP_isTxPending) {
char *nextByteToSend = ubpTxBuffer + ubpTxBufferSentByteCount;
// Try sending the next chunk
if (ubpTxBufferSentByteCount < ubpTxBufferLength && hostIsConnected) { // Haven't already sent all the bytes
int retryRemainingCount = 1000; // Limit the number of times we retry sending to avoid getting into an infinite loop
int remainingByteCount = ubpTxBufferLength - ubpTxBufferSentByteCount;
if (remainingByteCount >= TX_CHUNK_SIZE) { // Can fill the TX output buffer
// Send is queued (the ble stack delays send to the start of the next tx window)
while (hostIsConnected && retryRemainingCount > 0) {
retryRemainingCount--;
};
// 2016-06-27: This changed to the above to enable testing
// while ( SimbleeBLE.send(nextByteToSend, TX_CHUNK_SIZE) == false && hostIsConnected && retryRemainingCount > 0) {
// retryRemainingCount--;
// }; // send() returns false if all tx buffers in use (can't enqueue, try again later)
ubpTxBufferSentByteCount += TX_CHUNK_SIZE;
} else { // Only partial TX buffer remaining to send
// Send is queued (the ble stack delays send to the start of the next tx window)
while ( hostIsConnected && retryRemainingCount > 0) {
retryRemainingCount--;
}; // send() returns false if all tx buffers in use (can't enqueue, try again later)
// 2016-06-27: This changed to the above to enable testing
// while ( SimbleeBLE.send(nextByteToSend, remainingByteCount) == false && hostIsConnected && retryRemainingCount > 0) {
// retryRemainingCount--;
// }; // send() returns false if all tx buffers in use (can't enqueue, try again later)
ubpTxBufferSentByteCount += remainingByteCount;
UBP_isTxPending = false;
}
}
}
}
/*
void _UBP_ingestRxBytes(char *receivedBytes, int byteCount) {
Serial.print(byteCount);
Serial.println(" bytes receieved");
// NOTE: Assuming not called unless len > 0
// Determine what to do with incoming fragment
if ( *receivedBytes == END_BYTE ) { // Fragment has leading END byte, signals start of packet
// Set fragment as the beginning of the reconstruction buffer
memcpy(ubpRxBuffer, receivedBytes, byteCount);
ubpRxBufferLength = byteCount;
} else if (ubpRxBufferLength > 0) { // Already have fragments in the reconstruction buffer
// Append fragment to reconstruction buffer
memcpy(ubpRxBuffer + ubpRxBufferLength, receivedBytes, byteCount);
ubpRxBufferLength += byteCount;
}
// Check RX buffer for trailing END byte
if ( *(ubpRxBuffer + ubpRxBufferLength - 1) == END_BYTE) { // RX buffer ends with END byte, looks like packet is complete
byte firstNonControlIndex = 1;
byte escapedDataLength = ubpRxBufferLength - 2; // "- 2" for leading/trailing control chars
// Un-escape the incoming payload
ubpUnescapedRxBufferBufferLength = _UBP_makeUnEscapedCopy(ubpRxBuffer + firstNonControlIndex, escapedDataLength, ubpUnescapedRxBufferBuffer);
byte payloadDataLength = ubpUnescapedRxBufferBufferLength - 1; // -1 to account for checksum
// Calculate checksum over payload, i.e. all bytes except for last checksum byte)
char calculatedChecksum = CRC8(ubpUnescapedRxBufferBuffer, payloadDataLength * sizeof(byte));
// Extract embedded checksum value
char receivedChecksum = *(ubpUnescapedRxBufferBuffer + payloadDataLength); // NOTE: Omitting '-1' because checksum byte comes just after payloadDataLength
// Verify the checksum
if (calculatedChecksum == receivedChecksum) {
unsigned short packetIdentifier = *(ubpUnescapedRxBufferBuffer);
UBP_TxFlags txFlags = (UBP_TxFlags) *(ubpUnescapedRxBufferBuffer + PACKET_ID_SIZE);
if (UBP_receivedPacket) {
void *packetBuffer = (ubpUnescapedRxBufferBuffer + PACKET_ID_SIZE + 1); // skip +
UBP_receivedPacket(packetIdentifier, txFlags, packetBuffer);
}
} else {
Serial.println("Incoming packet checksum invalid");
// Reset
ubpRxBufferLength = 0;
ubpUnescapedRxBufferBufferLength = 0;
if (UBP_incomingChecksumFailed) {
UBP_incomingChecksumFailed();
}
}
} // else haven't RX'd final fragment yet, keep waiting
}
*/
// only needed to retreive the data for testing
void getTxBuffer(char *txBuffer, int *txBufferLength) {
for (int i=0; i +
// Calculate and append checksum
byte checksumValue = CRC8(ubpTxBuffer + 1, payloadLength); // Checksum over all payload bytes (minus the leading END byte, checksum, and trailing END byte)
*(ubpTxBuffer + ubpTxBufferLength) = checksumValue;
ubpTxBufferLength++;
// Append trailing END byte
*(ubpTxBuffer + ubpTxBufferLength) = END_BYTE;
ubpTxBufferLength++;
// Mark as ready to begin transmission
ubpTxBufferSentByteCount = 0;
UBP_isTxPending = true;
} else {
return false; // Return false if we couldn't escape the content because it was going to overflow the output buffer
}
}
// FIXME: 2016-06-27: this was missing in the original code
return true;
}
int _UBP_makeEscapedCopy(const char *inputBuffer, unsigned short inputBufferLength, char *outputBuffer, unsigned short outputBufferLength) {
unsigned int bytesCopied = 0;
const char *inputBytes = inputBuffer; // Cast here to avoid compiler warnings later
for (char i = 0; i < inputBufferLength; i++) { // For each byte to append
// Escape any control characters. Refer to Serial Line IP (SLIP) spec.
char aByte = *(inputBytes + i);
if (aByte == (char) ESCAPE_BYTE) { // Escape an ESCAPE_BYTE
// if (aByte == ESCAPE_BYTE) { // Escape an ESCAPE_BYTE
if (bytesCopied + 1 >= outputBufferLength) return -1; // Would¥ overflow destination buffer
else {
*(outputBuffer + bytesCopied++) = ESCAPE_BYTE; // Write ESCAPE_BYTE to buffer and increment offset
*(outputBuffer + bytesCopied++) = ESCAPED_ESCAPE_BYTE; // Write escaped ESCAPE_BYTE to buffer and increment offset
}
// } else if (aByte == END_BYTE) { // Escape an END_BYTE
} else if (aByte == (char) END_BYTE) { // Escape an END_BYTE
if (bytesCopied + 1 >= outputBufferLength) return -1; // Would overflow destination buffer
else {
*(outputBuffer + bytesCopied++) = ESCAPE_BYTE; // Write ESCAPE_BYTE to buffer and increment offset
*(outputBuffer + bytesCopied++) = ESCAPED_END_BYTE; // Write escaped END_BYTE to buffer and increment offset
}
} else { // Not a control character
if (bytesCopied >= outputBufferLength) return -1; // Would overflow destination buffer
else *(outputBuffer + bytesCopied++) = aByte; // Copy the unmolested byte to the buffer and increment offset
}
}
return bytesCopied;
}
/*
int _UBP_makeUnEscapedCopy(const char *inputBuffer, unsigned short inputBufferLength, char *outputBuffer) {
bool done = false;
char * destinationBufferPtr = outputBuffer;
const char * sourceBufferPtr = inputBuffer;
// UNESCAPE END Sequence_
while (!done && (sourceBufferPtr - inputBuffer) < inputBufferLength) {
char * substringPtr = strstr(sourceBufferPtr, escapedEndSequence_);
if (substringPtr == NULL) done = true;
else {
// Copy bytes between last-copied byte and next escape byte
char lengthToCopy = substringPtr - sourceBufferPtr; // How many bytes between last byte copied and next escape byte
memcpy(destinationBufferPtr, sourceBufferPtr, lengthToCopy);
destinationBufferPtr += lengthToCopy;
sourceBufferPtr += lengthToCopy;
// Replace escaped source sequence with unescaped version during copy, increment pointer
memcpy(destinationBufferPtr, endSequence_, sizeof(endSequence_));
destinationBufferPtr += sizeof(endSequence_);
// Increment pointer past escaped end Sequence_
sourceBufferPtr += sizeof(escapedEndSequence_);
}
}
// UNESCAPE ESCAPE SEQUENCE
done = false;
while (!done && (sourceBufferPtr - inputBuffer) < inputBufferLength) {
char * substringPtr = strstr(sourceBufferPtr, escapedEscapeSequence_);
if (substringPtr == NULL) done = true;
else {
// Copy bytes between last-copied byte and next escape byte
char lengthToCopy = substringPtr - sourceBufferPtr; // How many bytes between last byte copied and next escape byte
memcpy(destinationBufferPtr, sourceBufferPtr, lengthToCopy);
destinationBufferPtr += lengthToCopy;
sourceBufferPtr += lengthToCopy;
// Replace escaped source sequence with unescaped version during copy, increment pointer
memcpy(destinationBufferPtr, escapeSequence_, sizeof(escapeSequence_));
destinationBufferPtr += sizeof(escapeSequence_);
// Increment pointer past escaped end Sequence_
sourceBufferPtr += sizeof(escapedEscapeSequence_);
}
}
// COPY ANY TRAILING BYTES
char lengthToCopy = (inputBuffer + inputBufferLength) - sourceBufferPtr; // How many bytes remain to be copied
memcpy(destinationBufferPtr, sourceBufferPtr, lengthToCopy);
destinationBufferPtr += lengthToCopy;
sourceBufferPtr += lengthToCopy;
return (destinationBufferPtr - outputBuffer); // Return the total number of bytes copied to the destination buffer
}
*/
void _UBP_hostDisconnected() {
hostIsConnected = false;
// Reset TX subsystem
UBP_isTxPending = false;
// Reset RX subsystem
ubpRxBufferLength = 0;
// Invoke user callback
// 2016-06-28: commentet out for testing
// if (UBP_didDisconnect) UBP_didDisconnect();
}
// RFduino EVENTS
// ----------------------------------------------------
//void SimbleeBLE_onAdvertisement(bool start) {
//
// if (UBP_didAdvertise) UBP_didAdvertise(start);
//}
//
void SimbleeBLE_onConnect() {
hostIsConnected = true;
// 2016-06-29: uncommented and added for testing purposes
// if (UBP_didConnect) UBP_didConnect();
UBP_isTxPending = false;
}
//void SimbleeBLE_onReceive(char *data, int len) {
//
// _UBP_ingestRxBytes(data, len);
//}
void SimbleeBLE_onDisconnect() {
_UBP_hostDisconnected();
}
================================================
FILE: LibreMonitor/ViewControllers/AdjustmentsTableViewController.swift
================================================
//
// AdjustmentsTableViewController.swift
// LibreMonitor
//
// Created by Uwe Petersen on 27.05.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import UIKit
class AdjustmentsTableViewController: UITableViewController, UITextFieldDelegate {
let numberFormatter = NumberFormatter()
@IBOutlet weak var offsetTextField: UITextField!
@IBOutlet weak var slopeTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
numberFormatter.numberStyle = .decimal
offsetTextField.delegate = self // for handling return key
slopeTextField.delegate = self
let bloodGlucoseOffset = UserDefaults.standard.double(forKey: "bloodGlucoseOffset")
let bloodGlucoseSlope = UserDefaults.standard.double(forKey: "bloodGlucoseSlope")
offsetTextField.text = numberFormatter.string(from: NSNumber(value: bloodGlucoseOffset))
slopeTextField.text = numberFormatter.string(from: NSNumber(value: bloodGlucoseSlope))
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(AdjustmentsTableViewController.didTapSaveButton))
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .undo, target: self, action: #selector(AdjustmentsTableViewController.didTapUndoButton))
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
checkInputForTextField(textField)
return true
}
func didTapSaveButton() {
checkInputForTextField(offsetTextField)
checkInputForTextField(slopeTextField)
}
func checkInputForTextField(_ textField: UITextField) {
guard let aNumber = numberFormatter.number(from: textField.text!) else {
displayAlertForTextField(textField)
return
}
switch textField {
case offsetTextField:
let bloodGlucoseOffset = Double(aNumber)
UserDefaults.standard.set(bloodGlucoseOffset, forKey: "bloodGlucoseOffset")
case slopeTextField:
let bloodGlucoseSlope = Double(aNumber)
UserDefaults.standard.set(bloodGlucoseSlope, forKey: "bloodGlucoseSlope")
default:
fatalError("Fatal Error in \(#file): textField not handled in switch case")
break
}
// update table view
NotificationCenter.default.post(name: Notification.Name(rawValue: "updateBloodSugarTableViewController"), object: self)
resignFirstResponder()
navigationController?.dismiss(animated: true, completion: nil)
}
func didTapUndoButton() {
resignFirstResponder()
navigationController?.dismiss(animated: true, completion: nil)
}
func displayAlertForTextField(_ textField: UITextField) {
let message = String(format: "\"\(textField.text!)\" ist kein gültiger Wert, bitte korrigieren.", [])
let alertController = UIAlertController(title: "Ungültige Eingabe", message: message, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
}
================================================
FILE: LibreMonitor/ViewControllers/BloodSugarTableViewController.swift
================================================
//
// TableViewController.swift
//
// Created by Uwe Petersen on 01.02.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import UIKit
import CoreBluetooth
import CoreData
import UserNotifications
class BloodSugarTableViewController: UITableViewController, SimbleeManagerDelegate {
var coreDataStack = CoreDataStack()
var simbleeManager = SimbleeManager()
var sensorData: SensorData?
var trendMeasurements: [Measurement]?
var historyMeasurements: [Measurement]?
var batteryVoltage = 0.0
var bloodGlucoseOffset: Double!
var bloodGlucoseSlope: Double!
var sensor: LibreSensor?
var deviceID = "-"
var temperatureString = "_"
var timeInMinutesSinceStartOfSensor = 0
var timeOfLastScan = Date()
var transmissionDuration = TimeInterval()
var timeOfTransmissionStart = Date()
var dateFormatter = DateFormatter()
var timeFormatter = DateFormatter()
var notificationTimer = Timer()
var showNotification = true
/// Enum for the sections of this table view
fileprivate enum Section: Int {
case connectionData, generalData, graph, trendData, historyData
/// Count of enum cases (has to be adjusted to this/each very enum)
/// Source: http://stackoverflow.com/questions/27094878/how-do-i-get-the-count-of-a-swift-enum
static let count: Int = {
var max: Int = 0
while let _ = Section(rawValue: max) { max += 1 }
return max
}()
}
@IBAction func doRefresh(_ sender: UIRefreshControl) {
sender.endRefreshing()
tableView.reloadData()
}
// MARK: - View Controller life ciycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// self.navigationItem.leftBarButtonItem = self.editButtonItem()
simbleeManager.delegate = self
self.title = "LibreMonitor"
let connectButtonTitle = connectButtonTitleForState(simbleeManager.state)
let conncectButton = UIBarButtonItem(title: connectButtonTitle, style: .plain, target: self, action: #selector(BloodSugarTableViewController.didTapConnectButton))
self.navigationItem.rightBarButtonItem = conncectButton
bloodGlucoseOffset = UserDefaults.standard.double(forKey: "bloodGlucoseOffset")
bloodGlucoseSlope = UserDefaults.standard.double(forKey: "bloodGlucoseSlope")
if bloodGlucoseSlope <= 0.00001 {
bloodGlucoseSlope = 1.0
UserDefaults.standard.set(bloodGlucoseSlope, forKey: "bloodGlucoseSlope")
}
dateFormatter.dateFormat = "yyyy-MM-dd"
timeFormatter.dateFormat = "HH:mm:ss"
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
bloodGlucoseOffset = UserDefaults.standard.double(forKey: "bloodGlucoseOffset")
bloodGlucoseSlope = UserDefaults.standard.double(forKey: "bloodGlucoseSlope")
// Notification for updating table view after application did become active again
NotificationCenter.default.addObserver(self, selector: #selector(BloodSugarTableViewController.updateTableView), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
// Notification for updating table view after having changed the offset and/of slope
NotificationCenter.default.addObserver(self, selector: #selector(BloodSugarTableViewController.updateTableView), name: NSNotification.Name(rawValue: "updateBloodSugarTableViewController"), object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
super.viewWillDisappear(true)
}
func didTapConnectButton() {
switch (simbleeManager.state) {
case .Unassigned:
simbleeManager.scanForSimblee()
case .Scanning:
simbleeManager.centralManager.stopScan()
simbleeManager.state = .Disconnected
case .Connected, .Connecting, .Notifying:
simbleeManager.disconnectManually()
case .Disconnected, .DisconnectedManually:
simbleeManager.connect()
}
}
func updateTableView() {
self.tableView.reloadData()
}
// MARK: - Table View
override func numberOfSections(in tableView: UITableView) -> Int {
return Section.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch Section(rawValue: section)! {
case .connectionData: return 3
case .generalData: return 7
case .graph: return 1
case .trendData: return 16
case .historyData: return 32
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath as NSIndexPath).section == 2 {
// Draw graph
let cell = tableView.dequeueReusableCell(withIdentifier: "BloodSugarGraphTableViewCell", for: indexPath)
guard let theCell = cell as? BloodSugarGraphViewTableViewCell else {return cell}
if let trendMeasurements = trendMeasurements, let historyMeasurements = historyMeasurements {
theCell.lineChartView.trendMeasurements = trendMeasurements
theCell.lineChartView.historyMeasurements = historyMeasurements
theCell.lineChartView.setGlucoseCharts(trendMeasurements, historyMeasurements: historyMeasurements)
theCell.setNeedsDisplay()
theCell.lineChartView.setNeedsDisplay()
}
return theCell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
self.configureCell(cell, atIndexPath: indexPath)
return cell
}
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if (indexPath as NSIndexPath).section == 0 && (indexPath as NSIndexPath).row == 0 {
didTapConnectButton()
} else if (indexPath as NSIndexPath).section == 0 && (indexPath as NSIndexPath).row == 2 {
// ChangeBloodGlucoseAdjustments
performSegue(withIdentifier: "ChangeBloodGlucoseAdjustments", sender: self)
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch section {
case 0: return "Connection"
case 1: return "General data"
case 2:
let seconds = (NSDate() as NSDate).timeIntervalSince(timeOfLastScan).truncatingRemainder(dividingBy: 60.0)
let minutes = (Date().timeIntervalSince(timeOfLastScan) - seconds) / 60.0
return String(format: "Graph from %2.0f:%02.0f minutes ago", arguments: [minutes, seconds])
case 3: return "Last 15 minutes"
case 4: return "Last eight hours"
case 5: return "Neue Letzte 15 Minuten"
case 6: return "Neue Letzte 8 Stunden"
default: return nil
}
}
func configureCell(_ cell: UITableViewCell, atIndexPath indexPath: IndexPath) {
cell.backgroundColor = UIColor.white
cell.detailTextLabel?.textColor = UIColor.black
cell.accessoryType = .none
switch Section(rawValue: (indexPath as NSIndexPath).section)! {
case .connectionData:
switch (indexPath as NSIndexPath).row {
case 0:
cell.textLabel?.text = "Simblee status"
cell.detailTextLabel?.text = simbleeManager.state.rawValue
cell.backgroundColor = colorForConnectionState()
case 1:
cell.textLabel?.text = "Last scan:"
if let sennsorData = sensorData {
cell.detailTextLabel?.text = String(format: "on \(dateFormatter.string(from: sennsorData.date as Date)), at \(timeFormatter.string(from: sennsorData.date as Date)) o'clock, in %.2f s", arguments: [transmissionDuration])
if Date().timeIntervalSince(sennsorData.date as Date) > 240.0 {
cell.backgroundColor = UIColor.red
}
}
case 2:
cell.textLabel?.text = "Offset / Slope:"
cell.detailTextLabel?.text = String(format: "%.0f mg/dl, %.4f", arguments: [bloodGlucoseOffset, bloodGlucoseSlope])
cell.accessoryType = .disclosureIndicator
default: break
}
case .generalData:
switch (indexPath as NSIndexPath).row {
case 0:
cell.textLabel?.text = "BM019 ID"
cell.detailTextLabel?.text = deviceID
case 1:
var crcString = String()
var color = UIColor()
if let sensorData = sensorData {
crcString += ", crcs: \(sensorData.hasValidHeaderCRC), \(sensorData.hasValidBodyCRC), \(sensorData.hasValidFooterCRC)"
color = colorForSensorState( (sensorData.hasValidHeaderCRC && sensorData.hasValidBodyCRC && sensorData.hasValidFooterCRC) )
} else {
crcString = ", nil"
color = UIColor.lightGray
}
cell.textLabel?.text = "Sensor SN"
if let sensor = sensor {
cell.detailTextLabel?.text = sensor.serialNumber + crcString // + " (" + sensor.prettyUid + ")"
} else {
cell.detailTextLabel?.text = ""
}
cell.backgroundColor = color
case 2:
cell.textLabel?.text = "Environment"
cell.detailTextLabel?.text = String(format: "%3.1f V", arguments: [batteryVoltage]) + ", " + temperatureString
if batteryVoltage < 3.0 {
cell.backgroundColor = UIColor.orange
}
case 3:
cell.textLabel?.text = "Blocks"
if let sennsorData = sensorData {
cell.detailTextLabel?.text = "Trend: \(sennsorData.nextTrendBlock), history: \(sennsorData.nextHistoryBlock), minutes: \(sennsorData.minutesSinceStart)"
}
case 4:
cell.textLabel?.text = "Glucose"
if let trendMeasurements = trendMeasurements {
let currentGlucose = trendMeasurements[0].glucose
let longDelta = currentGlucose - trendMeasurements[15].glucose
let shortDelta = (currentGlucose - trendMeasurements[8].glucose) * 2.0 * 16.0/15.0
let longPrediction = currentGlucose + longDelta
let shortPrediction = currentGlucose + shortDelta
cell.detailTextLabel?.text = String(format: "%0.0f, Delta: %0.0f (%0.0f), Prognosis: %0.0f (%0.0f)", arguments: [currentGlucose, longDelta, shortDelta, longPrediction, shortPrediction])
if longPrediction < 70.0 || shortPrediction < 70.0 || longPrediction > 180.0 || shortPrediction > 180.0 || (abs(longDelta) > 30.0 && abs(shortDelta) > 30.0) {
cell.detailTextLabel?.textColor = UIColor.red
} else {
cell.detailTextLabel?.textColor = UIColor.black
}
}
case 5:
cell.textLabel?.text = "Sensor started"
if let sennsorData = sensorData {
let minutes = sennsorData.minutesSinceStart
let days = Int( Double(minutes) / 24.0 / 60.0 )
let hours = Int( Double(minutes) / 60.0 ) - days*24
let minutesRest = minutes - days*24*60 - hours*60
cell.detailTextLabel?.text = String(format: "%d day(s), %d hour(s) and %d minute(s) ago", arguments: [days, hours, minutesRest])
}
// cell.detailTextLabel?.text = startOfSensorString
case 6:
cell.textLabel?.text = "Sensor status"
if let sennsorData = sensorData {
cell.detailTextLabel?.text = sennsorData.state.description
} else {
cell.detailTextLabel?.text = "nil"
}
default:
cell.textLabel?.text = "Something ..."
cell.detailTextLabel?.text = "... didn't work"
}
case .graph:
break
case .trendData:
let index = (indexPath as NSIndexPath).row
if let measurements = trendMeasurements {
let timeAsString = timeFormatter.string(from: measurements[index].date as Date)
let dateAsString = dateFormatter.string(from: measurements[index].date as Date)
cell.textLabel?.text = String(format: "%0.1f mg/dl", measurements[index].glucose)
let rawString = String(format: "%0d", measurements[index].rawValue)
// let temp = (Int(measurements[index].bytes[4] & 0x0F) << 6) + Int(measurements[index].bytes[3]) >> 2
// let hugo = Int(measurements[index].bytes[3] & 3)
let temp = (Int(measurements[index].bytes[5] & 0x0F) << 8) + Int(measurements[index].bytes[4])
let hugo = Int(measurements[index].bytes[3])
cell.detailTextLabel?.text = "\(timeAsString), \(rawString), \(measurements[index].byteString), \(temp), \(hugo), \(dateAsString), \(index)"
}
case .historyData:
let index = (indexPath as NSIndexPath).row
if let measurements = historyMeasurements {
let timeAsString = timeFormatter.string(from: measurements[index].date as Date)
let dateAsString = dateFormatter.string(from: measurements[index].date as Date)
cell.textLabel?.text = String(format: "%0.1f mg/dl", measurements[index].glucose)
let rawString = String(format: "%0d", measurements[index].rawValue)
// let temp = (Int(measurements[index].bytes[4] & 0x0F) << 6) + Int(measurements[index].bytes[3]) >> 2
// let hugo = Int(measurements[index].bytes[3] & 3)
let temp = (Int(measurements[index].bytes[5] & 0x0F) << 8) + Int(measurements[index].bytes[4])
let hugo = Int(measurements[index].bytes[3])
cell.detailTextLabel?.text = "\(timeAsString), \(rawString), \(measurements[index].byteString), \(temp), \(hugo), \(dateAsString), \(index)"
}
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if (indexPath as NSIndexPath).section == 2 {
return CGFloat(300)
}
return CGFloat(20)
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(20)
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("Prepare for segue")
if segue.identifier == "ChangeBloodGlucoseAdjustments" {
print("Segue ChangeBloodGlucoseAdjustments")
}
}
// MARK: - SimbleeManagerDelegate
func simbleeManagerPeripheralStateChanged(_ state: SimbleeManagerState) {
self.navigationItem.rightBarButtonItem?.title? = connectButtonTitleForState(state)
switch state {
case .Unassigned, .Connecting, .Connected, .Scanning, .Disconnected, .DisconnectedManually:
self.triggerNotificationContentForBadgeIcon(value: 0) // no badge number if not notifying
case .Notifying:
break
}
tableView.reloadData()
}
func simbleeManagerReceivedMessage(_ messageIdentifier: UInt16, txFlags: UInt8, payloadData: Data) {
print("Received SLIP payload with ID = \(messageIdentifier)")
switch messageIdentifier {
case 0x2002: // system information data, including UID (e.g. E0:07:A0:00:00:0C:48:BD")
// Convention: System Information data is the first packet sent via bluetooth, thus delete all internal data and reload table view
timeOfTransmissionStart = Date()
deviceID = "-"
print(payloadData.debugDescription)
var systemInformationData = SystemInformationDataType(uid: (0, 0, 0, 0, 0, 0, 0, 0), resultCode: 0, responseFlags: 0, infoFlags: 0, errorCode: 0)
(payloadData as NSData).getBytes(&systemInformationData, length: payloadData.count) // get payload data into corresponding memory -> tuples
print(String(format: "result code %02X", arguments: [systemInformationData.resultCode]))
print(String(format: "response flags %02X", arguments: [systemInformationData.responseFlags]))
print(String(format: "info flags %02X", arguments: [systemInformationData.infoFlags]))
print(String(format: "error code %02X", arguments: [systemInformationData.errorCode]))
let uidString = systemInformationData.uidString()
sensor = LibreSensor(withUID: uidString)
// Convention: System Information data is the first packet sent from RFDuino, thus delete all internal data and reload table view
tableView.reloadData()
case 0x2005: // Battery
var batteryDataPayload = BatteryDataType(voltage: 0, temperature: 0)
(payloadData as NSData).getBytes(&batteryDataPayload, length:payloadData.count)
batteryVoltage = Double(batteryDataPayload.voltage)
temperatureString = String(format: "%4.1f °C", arguments: [batteryDataPayload.temperature])
case 0x1007: // all data bytes (all 344 bytes, i.e. 43 blocks)
print("received all data bytes packet")
var bytes = [UInt8](repeating: 0, count: 344)
(payloadData as NSData).getBytes(&bytes, length: 344)
sensorData = SensorData(bytes: bytes, date: Date())
if let sennsorData = sensorData {
trendMeasurements = sennsorData.trendMeasurements(bloodGlucoseOffset, slope: bloodGlucoseSlope)
historyMeasurements = sennsorData.historyMeasurements(bloodGlucoseOffset, slope: bloodGlucoseSlope)
notificationForGlucoseMeasurements(trendMeasurements!)
if let historyMeasurements = historyMeasurements , sennsorData.hasValidBodyCRC && sennsorData.hasValidHeaderCRC && sennsorData.state == .ready {
// fetch all records that are newer than the oldest history measurement of the new data
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let request = BloodGlucose.fetchRequest() as NSFetchRequest
do {
let fetchedBloodGlucoses = try coreDataStack.managedObjectContext.fetch(request)
// Loop over all and check if new data exists and store the new data if not yet existent
historyMeasurements.forEach({measurement in
var storeMeasurement = true
// Check if there is already a record stored for the same time
for bloodGlucose in fetchedBloodGlucoses {
// Store value if dates are less than two mintues apart from each other (in either direction)
if let bloodGlucoseDate = bloodGlucose.date, abs(bloodGlucoseDate.timeIntervalSince(measurement.date)) < 2.0 * 60.0 {
storeMeasurement = false
break
}
}
// Store if there isn't a measurement yet for this time and if it is a possible value (i.e. greater than zero and greater than offset)
// if storeMeasurement && (measurement.glucose > bloodGlucoseOffset) && (measurement.glucose > 0.0) {
// Weird xcode 8.1 error enforces to reverse the comparison
if storeMeasurement && (bloodGlucoseOffset < measurement.glucose) && (0.0 < measurement.glucose) {
if let glucose = NSEntityDescription.insertNewObject(forEntityName: "BloodGlucose", into: coreDataStack.managedObjectContext) as? BloodGlucose {
glucose.date = measurement.date as NSDate?
glucose.bytes = measurement.byteString
glucose.value = measurement.glucose
glucose.dateString = dateFormatter.string(from: measurement.date as Date)
}
}
})
coreDataStack.saveContext()
} catch {
fatalError("Failed to fetch BloodGlucose: \(error)")
}
}
} else {
trendMeasurements = nil
historyMeasurements = nil
}
tableView.reloadData()
case 0x2001: // IDN data, including device ID, example: RESPONSE CODE: 0 LENGTH: 15, DEVICE ID: 4E 46 43 20 46 53 32 4A 41 53 54 32 0, ROM CRC: 75D2
// Convention: IDN data is the last packet sent via bluetooth
print(payloadData.debugDescription)
var idnData = IDNDataType(resultCode: 0, deviceID: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), romCRC: (0, 0))
(payloadData as NSData).getBytes(&idnData, length: payloadData.count) // get payload data into corresponding memory -> tuples
print(String(format: "resultCode %02X", arguments: [idnData.resultCode]))
let deviceIDString = idnData.deviceIDString()
print(deviceIDString)
self.deviceID = deviceIDString
timeOfLastScan = Date()
transmissionDuration = Date().timeIntervalSince(timeOfTransmissionStart)
// Convention: the idn data is the last packet sent from RFDuino within a cycle, thus reload table view after having received it
tableView.reloadData()
default:
break
}
}
// MARK: - Helper functions
func colorForConnectionState() -> UIColor {
switch (simbleeManager.state) {
case .Unassigned, .Disconnected, .DisconnectedManually:
return UIColor.red
case .Scanning, .Connecting, .Connected:
return UIColor(red: CGFloat(0.9), green: CGFloat(0.9), blue: CGFloat(1), alpha: CGFloat(1))
case .Notifying:
return UIColor.green
}
}
func colorForSensorState(_ bool: Bool) -> UIColor {
if bool == false {
return UIColor.red
}
return UIColor.white
}
func connectButtonTitleForState(_ state: SimbleeManagerState) -> String {
switch state {
case .Unassigned, .Disconnected, .DisconnectedManually:
return "connect"
case .Connected, .Connecting, .Scanning, .Notifying:
return "disconnect"
}
}
// MARK: - Notifications and badge icon
func notificationForGlucoseMeasurements(_ trendMeasurements: [Measurement] ) {
let currentGlucose = trendMeasurements[0].glucose
let longDelta = currentGlucose - trendMeasurements[15].glucose
let shortDelta = (currentGlucose - trendMeasurements[8].glucose) * 2.0 * 16.0/15.0
let longPrediction = currentGlucose + longDelta
let shortPrediction = currentGlucose + shortDelta
// Show alert if conditions are reached
if ((longPrediction > 0 && (longPrediction < 60.0 || longPrediction > 180.0)) ||
(shortPrediction > 0 && (shortPrediction < 66.0 || shortPrediction > 180.0)) ||
(abs(longDelta) > 30.0 && abs(shortDelta) > 30.0)) && showNotification {
let body = String(format: "%0.0f --> %0.0f (%0.0f), Delta: %0.0f (%0.0f)", arguments: [currentGlucose, longPrediction, shortPrediction, longDelta, shortDelta])
let content = self.notificationContentForBloodSugarWarning(body)
let timeTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.5, repeats: false)
let request = UNNotificationRequest(identifier: "LocalNotification", content: content, trigger: timeTrigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
// Do something with error
print("Error, blood sugar notification could not be triggered due to: \(error)")
} else {
// Request was added successfully
print("triggered blood sugar notification")
}
}
// timer to hide notification for 10 minutes
showNotification = false
notificationTimer = Timer.scheduledTimer(timeInterval: TimeInterval(10.0*60.0), target: self, selector: #selector(BloodSugarTableViewController.notificationTimerFired), userInfo: nil, repeats: false)
}
// UIApplication.sharedApplication().cancelAllLocalNotifications()
self.triggerNotificationContentForBadgeIcon(value: Int(round(longPrediction)))
}
func triggerNotificationContentForBadgeIcon(value: Int) {
UIApplication.shared.applicationIconBadgeNumber = value
// let content = UNMutableNotificationContent()
// content.badge = NSNumber(value: value)
//
// let timeTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 1, repeats: false)
// let request = UNNotificationRequest(identifier: "Badge", content: content, trigger: timeTrigger)
// UNUserNotificationCenter.current().add(request) { error in
// if let error = error {
// // Do something with error
// print("Error, badge notification could not be triggered due to \(error)")
// } else {
// // Request was added successfully
// print("triggered badge notification")
// }
// }
}
/// Returns a UNMutableNotificationContent with a body
///
/// - parameter body: body to be displayd
///
/// - returns: the notification content
func notificationContentForBloodSugarWarning(_ body: String) -> UNMutableNotificationContent {
let content = UNMutableNotificationContent()
content.title = "Glukose beachten"
content.subtitle = "Notification Subtitle"
content.body = body
// content.badge = 1
content.sound = UNNotificationSound.default()
content.categoryIdentifier = "GLUCOSE_WARNING_CATEGORY"
content.userInfo = ["UUID": "123456789" ] // assign a unique identifier to the notification so that we can retrieve it later
return content
}
func notificationTimerFired() {
showNotification = true
}
}
================================================
FILE: LibreMonitor/Views/BloodSugarGraphView.swift
================================================
//
// BloodSugarGraphView.swift
//
// Created by Uwe Petersen on 23.03.16.
//
// Blood sugar data ranges from time of last scan to approx 8 hours before. This data shall be plotted completeley.
// But the inital x-range of the graph shall be from the current time/date to 8 hours backwards.
// The user might pan and zoom to see other parts of the data.
import Foundation
import UIKit
import Charts
class BloodSugarGraphView: LineChartView {
var trendMeasurements: [Measurement]?
var historyMeasurements: [Measurement]?
lazy var dateValueFormatter: DateValueFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
return DateValueFormatter(dateFormatter: dateFormatter)
}()
// Just the outlets are needed here, for these UI elements to be accessible from the corresponding parent table view controller
// target action and that stuff is then all handled from within the table view controller
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
/// Draws a line chart with glucose over time for trend and history measurments.
///
/// - parameter trendMeasurements: trend measurement data (16 values for now and last 15 minutes)
/// - parameter historyMeasurements: history measurement data (32 values for last eight hours, each beeing 15 minutes apart)
func setGlucoseCharts(_ trendMeasurements: [Measurement]?, historyMeasurements: [Measurement]?) {
self.noDataText = "No blood sugar data available"
guard let trendMeasurements = trendMeasurements, let historyMeasurements = historyMeasurements else {return}
// Trend data set (data needs to be ordered from lowest x-value to highest x-value)
var trendEntries = [ChartDataEntry]()
trendMeasurements.reversed().forEach{
let timeIntervall = $0.date.timeIntervalSince1970
trendEntries.append(ChartDataEntry(x: timeIntervall, y: $0.glucose))
}
let trendLineChartDataSet = LineChartDataSet(values: trendEntries, label: "Trend")
// History data set (data needs to be ordered from lowest x-value to highest x-value)
var historyEntries = [ChartDataEntry]()
historyMeasurements.reversed().forEach{
let timeIntervall = $0.date.timeIntervalSince1970
historyEntries.append(ChartDataEntry(x: timeIntervall, y: $0.glucose))
}
let historyLineChartDataSet = LineChartDataSet(values: historyEntries, label: "History")
// format data sets and create line chart with the data sets
formatLineChartDataSet(historyLineChartDataSet)
formatLineChartDataSet(trendLineChartDataSet)
let lineChartData = LineChartData(dataSets: [historyLineChartDataSet, trendLineChartDataSet])
print(lineChartData.debugDescription)
lineChartData.setValueFont(NSUIFont.systemFont(ofSize: CGFloat(9.0)))
// Line for upper and lower threshold of ideal glucose values
let lowerLimitLine = ChartLimitLine(limit: 70.0)
let upperLimitLine = ChartLimitLine(limit: 100.0)
self.rightAxis.addLimitLine(lowerLimitLine)
self.rightAxis.addLimitLine(upperLimitLine)
self.rightAxis.drawLimitLinesBehindDataEnabled = true
// the blood sugar graph
self.data = lineChartData
self.chartDescription?.text = "LibreMonitor"
self.chartDescription?.font = NSUIFont.systemFont(ofSize: CGFloat(4))
self.xAxis.valueFormatter = dateValueFormatter
// Display the last 8 hours (no matter of the date range of the data to be plotted)
let oldXAxisMaximum = self.xAxis.axisMaximum // i.e. date when the chart was refreshed last time
let highestXValue = lineChartData.xMax // i.e. date of last scan (or sample)
self.xAxis.axisMaximum = Date().timeIntervalSince1970 // set maximum to current date
// Adjust zoom and x-offset
if self.xAxis.axisMaximum - highestXValue > 10.0 * 60.0 {
// if last sample was more than 10 minutes ago, zoom out such that all the last 8 hours are displayed. The rest of the data can be displayed e.g. by paning
self.fitScreen()
let xTimeRange = self.xAxis.axisMaximum - self.xAxis.axisMinimum
let eightHours = 8.0 * 3600.0
let scaleX = xTimeRange / eightHours
let xOffset = xTimeRange - eightHours
self.zoom(scaleX: CGFloat(scaleX), scaleY: 1, x: CGFloat(xOffset), y: 0)
} else {
// otherwise keep zoom ratio but shift line to the left about the amount of time since the last date the chart was refresehd
let offsetXMaximum = self.xAxis.axisMaximum - oldXAxisMaximum
self.zoom(scaleX: 1, scaleY: 1, x: CGFloat(offsetXMaximum), y: 0)
}
self.notifyDataSetChanged()
}
/// Formats the line and the data points.
///
/// Formatted are circle radius, line width and line mode (cubic)
///
/// - parameter lineChartDataSet: lineChartDataSet to be formatted
func formatLineChartDataSet (_ lineChartDataSet: LineChartDataSet) {
lineChartDataSet.circleRadius = CGFloat(3.0)
lineChartDataSet.mode = .cubicBezier
lineChartDataSet.cubicIntensity = 0.05
lineChartDataSet.lineWidth = lineChartDataSet.lineWidth * CGFloat(3.0)
}
}
/// Formatter class for time values (x-axis) of blood sugar graph.
///
/// The x-axis ticks are shown as time of day in "HH:mm"-format.
class DateValueFormatter: NSObject, IAxisValueFormatter {
var dateFormatter = DateFormatter()
init(dateFormatter: DateFormatter) {
self.dateFormatter = dateFormatter
// self.dateFormatter.dateFormat = "HH:mm"
}
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
return dateFormatter.string(from: Date(timeIntervalSince1970: value))
}
}
================================================
FILE: LibreMonitor/Views/BloodSugarGraphViewTableViewCell.swift
================================================
//
// BloodSugarGraphViewTableViewCell.swift
//
// Created by Uwe Petersen on 23.03.16.
//
import Foundation
import UIKit
import Charts
class BloodSugarGraphViewTableViewCell: UITableViewCell {
// Just the outlets are needed here, for these UI elements to be accessible from the corresponding parent table view controller
// target action and that stuff is then all handled from within the table view controller
@IBOutlet weak var barChartView: BloodSugarGraphView!
@IBOutlet weak var lineChartView: BloodSugarGraphView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
================================================
FILE: LibreMonitor.ino
================================================
///
/// LibreMonitor
///
/// Copyright (c) 2015 Uwe Petersen, all right reserved
///
/// Wiring connections:
///
/// BM019 RFDuino/Simblee
/// DIN: pin 2 IRQ: GPIO pin 2
/// SS: pin 3 SS: GPIO pin 3
/// MISO: pin 4 MISO: GPIO pin 4
/// MOSI: pin 5 MOSI: GPIO pin 5
/// SCK: pin 6 SCK: GPIO pin 6
/// SS0: pin 7 +3V-pin
/// GND: pin 10 GND-pin
///
/// BM019 BM019
/// SS0: pin 7 VDD: pin 8 (Output 3,3V from BM019)
///
/// BM019 Lipo Power source (I use a 100mAh lip which lasts a full day
/// VIN: pin 9 "+" of lipo / lipo charger
/// GND: pin 10 GND/"-" of lipo / lipo charger
///
/// You can place a switch between GND of lipo/lipo-charger and GND of the BM019
///
/// If wired as suggested above, then you have to change the Simblee SPI pins for SS, MOSI, MISO and SCK.
/// This is done in the variant.h file. In my case this file is located in
///
/// /Users/[my user name]/Library/Arduino15/packages/Simblee/hardware/Simblee/1.0.0/variants/Simblee
///
/// In this file set the defines for the SPI pins as follows:
///
/// #define PIN_SPI_SS (3u)
/// #define PIN_SPI_MOSI (5u)
/// #define PIN_SPI_MISO (4u)
/// #define PIN_SPI_SCK (6u)
///
///
/// Acknowledgements:
///
/// RFDuinoUBP
///
/// This code uses portions of RFduinoUB, which can be retrieved from
/// https://github.com/cconway/RFduinoUBP under the following license:
/// The MIT License (MIT)
/// Copyright (c) 2015 cconway
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
/// copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
/// The above copyright notice and this permission notice shall be included in all
/// copies or substantial portions of the Software.
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
/// SOFTWARE.
///
/// Solutions Cubed LLC
///
/// The author is grateful to Solutions Cubed LLC, see http://www.solutions-cubed.com,
/// for providing helpful code samples for their BM019 nfc module, portions of which are
/// used within this code.
///
// Include application, user and local libraries
#include
#include
#include // the sensor communicates using SPI, so include the library
// This code uses the SLIP protocol to transer the data via bluetooth, see https://github.com/cconway/RFduinoUBP
#include
#include
#include
#include
#include
// Define variables and constants
/* CR95HF Commands */
#define IDN 0x01 // identification number of CR95HF
#define SELECT_PROTOCOL 0x02 // select protocol
#define POLL 0x03 // poll
#define SENDRECEIVE 0x04 // send and receive data (most commonly used)
#define READ 0x05 // read values from registers internal to CR95HF
#define WRITE 0x06 // write values to registers internal to CR95HF
#define ECHO 0x55
// send receive commands for ISO/IEC 15693 protocol
#define INVENTORY 0x01 // receives information about tags in range
#define STAY_QUIET 0x02 // selected unit will not send back a response
#define READ_BLOCK 0x20 // read single block of memory from RF tag
#define WRITE_BLOCK 0x21 // write single block to memory of RF tag
#define LOCK_BLOCK 0x22 // permanently locks a block of memory on RF tag
#define READ_BLOCKS 0x23 // reads multiple blocks of memory from RF tag
#define WRITE_BLOCKS 0x24 // writes multiple blocks of memory to RF tag
#define SELECT 0x25 // used to select a specific tag for communication via the uid
#define RESET_TO_READY 0x26 // resets RF tag to ready state
#define WRITE_AFI 0x27 // writes application family identifier to RF tag
#define LOCK_AFI 0x28 // permanently locks application family identifier
#define WRITE_DSFID 0x29 // writes data storage format identifier to RF tag
#define LOCK_DSFID 0x2A // permanentlylocks data storage format identifier
#define GET_SYSTEM_INFORMATION 0x2B // gets information from RF tag that includes memory
// block size in bytes and number of memory blocks
#define GET_BLOCKS_SECURITY_STATUS 0x2C
// Request flag with bits set these rules:
// Bit Flag name Description
// 1 Sub-carrier flag 0 ... single sub-carrier used
// 1 ... two sub-carriers used
// 2 Data rate flag 0 ... low data rate is used
// 1 ... high data rate is used
// 3 Inventory flag State determines how bits 5-8 are defined
// 0 ... flag not set
// 1 ... flag set
// 4 Protocol 0: no protocol extension
// extension flag 1: protocol format is extended
// If Inventory flag = 0 (not set):
// 5 Select flag 0: request shall be executed based on setting of the address flag
// 1: request shall be executed only by devices in the selected state
// 6 Address flag 0: request is not addressed
// 1: request is addressed, optional UID field is present
// 7 Option flag Meaning is defined by the command description, if not used set to 0
// 8 reserved
// If Inventory flag = 1 (set):
// 5 AFI flag 0: AFI field is not present
// 1: AFI field is present
// 6 Number of 0: 16 slots
// slots flag 1: 1 slot
// 7 Option flag Meaning is defined by the command description, if not used set to 0
// 8 reserved
//
// Helper bit field by to quickly calculate integers from hex and vice versa:
// Bit no.: 8 7 6 5 4 3 2 1
// value: 128 64 32 16 8 4 2 1
// choose: 0 0 0 0 4 0 2 1 result is 0x03
//
// request flags byte, 0x26 means:
// single sub carrier, high data rate, inventory flag set, no protocoll extentsion, AFI not present, one slot)
// Settings
//#define DEBUG
const int SS_PIN = 3; // Slave Select pin, changed to new value on 2016-03-21
const int IRQ_PIN = 2; // IRQ/DIN pin used for wake-up pulse
byte RXBuffer[400]; // receive buffer
byte dataBuffer[400]; // buffer for Freestyle Libre byte data
byte NFCReady = 0; // used to track NFC state
const int SPI_FREQUENCY = 2000; // max. for CR95HF is 2000 = 2 MHz
//const uint64_t SLEEP_DURATION = 20000; // Duration of Simblee ultra low power mode in ms
const uint64_t SLEEP_DURATION = 120000; // Duration of Simblee ultra low power mode in ms
// Code
void setupPins() {
Serial.println("Setting Simblee pins ...");
pinMode(IRQ_PIN, OUTPUT);
pinMode(SS_PIN, OUTPUT);
// Commented out by Uwi on 15.11.2015: was not needed obviously
// digitalWrite(SS_PIN, HIGH);
Serial.println("... done setting Simblee pins.");
}
void setupSPI() {
Serial.println("Setting up SPI ...");
SPI.begin();
SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST);
SPI.setFrequency(SPI_FREQUENCY);
Serial.println("... done setting up SPI.");
}
// --------------------------
void setupBluetoothConnection() {
Serial.println("Setup Bluetooth stack and start connection...");
SimbleeBLE.deviceName = "LibreCGM";
SimbleeBLE.customUUID = "2220";
SimbleeBLE.advertisementData = "data";
SimbleeBLE.advertisementInterval = MILLISECONDS(500); // Default was 300
SimbleeBLE.txPowerLevel = 4; // Possible values: -20, -16, -12, -8, -4, 0 or +4 (dbM) // up to 20016-05-17 this was 0
// SimbleeBLE.txPowerLevel = -4; // Possible values: -20, -16, -12, -8, -4, 0 or +4 (dbM) // up to 20016-05-17 this was 0
SimbleeBLE.begin(); // Start the BLE stack
Serial.println("... done seting up Bluetooth stack and starting connection.");
}
// Add setup code
void setup() {
Serial.begin(9600);
setupPins(); // set RFduino/Simblee pins
setupSPI();
Serial.println("Please provide power to the BM019 within the next 5 seconds ...");
delay(5000);
sendWakeupPulse(IRQ_PIN); // wake up BM019 and set to SPI (since SS_0 is wired up to be HIGH)
readWakeUPEventRegister(SS_PIN, RXBuffer);
setupBluetoothConnection();
}
// SetProtocol_Command programs the CR95HF for ISO/IEC 15693 operation.
// If the correct response is received the serial monitor is used to display successful programming.
// Warning: if the parameters of the protocol are changed, e.g. sub carrier, then the request flags
// of the other commands (e.g. inventory, read single block) have to be changed accordingly.
void SetProtocol_Command() {
// step 1 send the command
digitalWrite(SS_PIN, LOW);
SPI.transfer(0x00); // SPI control byte to send command to CR95HF
SPI.transfer(0x02); // Set protocol command
SPI.transfer(0x02); // length of data to follow
SPI.transfer(0x01); // code for ISO/IEC 15693
SPI.transfer(0x0F); // Up till 2016-06-13: crc16, single, 30%, wait for SOF (wrong: Wait for SOF, 100% modulation, append CRC)
// SPI.transfer(0x0D); // Up till 2016-06-13: crc16, single, 30%, wait for SOF (wrong: Wait for SOF, 100% modulation, append CRC)
digitalWrite(SS_PIN, HIGH);
delay(1);
// step 2, poll for data ready
pollSPIUntilResponsIsReady(SS_PIN, RXBuffer);
// step 3, read the data
receiveSPIResponse(SS_PIN, RXBuffer);
#ifdef DEBUG
Serial.println("RXBuffer is");
for (byte i = 0; i < 2; i++) {
Serial.print(RXBuffer[i], HEX);
Serial.print(" ");
}
#endif
if ((RXBuffer[0] == 0) & (RXBuffer[1] == 0)) {
Serial.println("PROTOCOL SET-"); //
NFCReady = 1; // NFC is ready
} else {
Serial.println("BAD RESPONSE TO SET PROTOCOL");
NFCReady = 0; // NFC not ready
}
Serial.println(" ");
}
// Reads the single block of 8 bytes with number blockNum from the BM019.
// The BM019 has 244 Blocks of 8 bytes that can be read via ISO 15693 commands. The blocks are numbered 0 to 243.
// Returns the result code of the command response (that indicates wether there was success or an error)
byte ReadSingleBlockReturn(int blockNum) {
RXBuffer[0] = SENDRECEIVE; // command code for send receive CR95HF command
RXBuffer[1] = 0x03; // length of data that follows (3 bytes)
// RXBuffer[2] = 0x02; // request Flags byte, single carrier, high data rate
RXBuffer[2] = 0x03; // request Flags byte dual carrier, high data rate
RXBuffer[3] = 0x20; // Inventory Command for ISO/IEC 15693
RXBuffer[4] = blockNum; // Block number
// step 1 send the command
sendSPICommand(SS_PIN, RXBuffer, 5);
// step 2, poll for data ready
pollSPIUntilResponsIsReady(SS_PIN, RXBuffer);
// step 3, read the data
receiveSPIResponse(SS_PIN, RXBuffer);
delay(1);
#ifdef DEBUG
// Print to Serial
if (RXBuffer[0] == 128) {
Serial.printf("The block #%d:", blockNum);
for (byte i = 3; i < RXBuffer[1] + 3 - 4; i++) {
Serial.print(RXBuffer[i], HEX);
Serial.print(" ");
}
} else {
Serial.print("NO Single block available - ");
Serial.print("RESPONSE CODE: ");
Serial.println(RXBuffer[0], HEX);
}
Serial.println(" ");
#endif
return RXBuffer[0]; // result code of command response
}
/// @brief Sends command to BM019 and receives response serveral times until response with no error, max maxTrial times.
/// @detail After maxTrials trials the last response is returned, even if there is still an error.
/// @detail Warning: Ensure that RXBuffer has appropriate size.
/// @param ssPin slave select pin for SPI digital write
/// @param RXBuffer buffer used for command and response
/// @param maxTrials max number of trials
void runSPICommandUntilNoError(int ssPin, byte *command, int length, byte *RXBuffer, int maxTrials) {
int count = 0;
bool success;
do {
delay(1);
#ifdef DEBUG
Serial.printf("Before: Count: %d, success: %b, RXBuffer[0]: %x \r\n", count, success, RXBuffer[0]);
#endif
count++;
// clear RXBuffer with zeros
memset(RXBuffer, 0, sizeof(RXBuffer));
// run SPI command
sendPollReceiveSPINew(ssPin, command, sizeof(command), RXBuffer);
success = responseHasNoError(RXBuffer);
#ifdef DEBUG
Serial.printf("After: Count: %d, success: %b, RXBuffer[0]: %x \r\n", count, success, RXBuffer[0]);
#endif
} while ( !success && (count < maxTrials));
delay(1);
#ifdef DEBUG
Serial.printf("Exiting at count: %d, RXBuffer[0]: %x \r\n", count, RXBuffer[0]);
#endif
}
/// @brief Sends IDN command to BM019 and receives response serveral times until response with no error, max maxTrial times.
/// @detail After maxTrials trials the last response is returned, even if there is still an error.
/// @detail Warning: Ensure that RXBuffer has appropriate size.
/// @param ssPin slave select pin for SPI digital write
/// @param RXBuffer buffer used for command and response
/// @param maxTrials max number of trials
void runIDNCommandUntilNoError(int ssPin, byte *command, int length, byte *RXBuffer, int maxTrials) {
int count = 0;
bool success;
do {
#ifdef DEBUG
Serial.printf("Before: Count: %d, success: %b, RXBuffer[0]: %x \r\n", count, success, RXBuffer[0]);
#endif
count++;
// clear RXBuffer with zeros
memset(RXBuffer, 0, sizeof(RXBuffer));
// run SPI command
sendPollReceiveSPINew(ssPin, command, sizeof(command), RXBuffer);
success = idnResponseHasNoError(RXBuffer);
#ifdef DEBUG
Serial.printf("After: Count: %d, success: %b, RXBuffer[0]: %x \r\n", count, success, RXBuffer[0]);
#endif
} while ( !success && (count < maxTrials));
delay(10);
#ifdef DEBUG
Serial.printf("Exiting at count: %d, RXBuffer[0]: %x \r\n", count, RXBuffer[0]);
#endif
}
/// Runs the system information command several times, i.e. maxTrials times or until a response with no error is gotten, whatever happens first.
/// @param ssPin SS_PIN used for SPI
/// @param RXBuffer buffer used for send and receive
/// @param maxTrials maximum number of trials. If reached, the result is returned, even if there still is an error present
/// @brief Get system information from BM019
/// @details The response is read into RXBuffer and that's it, no matter if there is an error or not.
/// @n Command format for CR95HF:
/// @n 8 bits for request flags,
/// @n 8 bits for the get system information command,i.e 0x2B,
/// @n 64 bits for an optional UID (not used in non-addressed modes).
/// @details Example get system info command (CR95HF command embedded in BM019 command):
/// @details 0x04 ... BM019 send/receive command code
/// @details 0x02 ... BM019 length of CR95HF command data that follows
/// @details 0x02 ... CR95HF request flags byte (high data rate used)
/// @details 0x2B ... CR95HF get system information command
/// @n
/// @n
/// @details Example CR95HF response with no error:
/// @details 0x80 ... result code
/// @details 0x12 ... length of following data (12 bytes)
/// @details 0x00 ... response flags
/// @details 0x0F ... info flags
/// @details 0x69 0x55 0x19 0x38 0x42 0x20 0x02 0xE0 ... data: UID
/// @details 0x00 ... DSFID (supported and field is present in response if bit 1 of info flags is set)
/// @details 0x00 ... AFI (supported and field is present in response if bit 2 of info flags is set)
/// @details 0x3F 0X03 ... memory size (supported and field is present in response if bit 3 of info flags is set)
/// @details 0x20 ... IC Ref (supported and field is present in response if bit 4 of info flags is set)
/// @details 0xB4 0xA9 ... CRC16
/// @details 0x00 ... error
/// @n
/// @details Example CR95HF response with error (bit 0 of response flag is set)
/// @details 0x01 ... response flags
/// @details 0x01 ... error code (returned when error bit is set)
/// @details 0xB4 0xA9 ... CRC16
/// @details 0X01 ... (error CRC16 or collision error bits)
void runSystemInformationCommandUntilNoError(int ssPin, byte *RXBuffer, int maxTrials) {
#ifdef DEBUG
Serial.printf("ssPin: %d, maxTrials: %d, RXBuffer[0]: %x \r\n", ssPin, maxTrials, RXBuffer[0]);
#endif
byte command[4];
command[0] = 0x04; // command code for send receive CR95HF command
command[1] = 0x02; // length of data that follows (3 bytes)
command[2] = 0x03; // request Flags byte, dual sub carrier
// command[2] = 0x02; // request Flags byte, single sub carrier
command[3] = 0x2B; // get system information command for ISO/IEC 15693
delay(10);
#ifdef DEBUG
Serial.printf("ssPin: %d, maxTrials: %d, RXBuffer[0]: %x \r\n", ssPin, maxTrials, RXBuffer[0]);
#endif
// run command until no error, but only max 10 times
runSPICommandUntilNoError(ssPin, command, sizeof(command), RXBuffer, maxTrials);
}
/// Runs the inventory command several times, i.e. maxTrials times or until a response with no error is gotten, whatever happens first.
/// @param ssPin SS_PIN used for SPI
/// @param RXBuffer buffer used for send and receive
/// @param maxTrials maximum number of trials. If reached, the result is returned, even if there still is an error present
void runIDNCommand(int ssPin, byte *RXBuffer, int maxTrials) {
#ifdef DEBUG
Serial.printf("ssPin: %d, maxTrials: %d, RXBuffer[0]: %x \r\n", ssPin, maxTrials, RXBuffer[0]);
#endif
byte command[2];
command[0] = 0x01; // command code for send receive CR95HF command
command[1] = 0x00; // length of data that follows (0 bytes)
delay(10);
#ifdef DEBUG
Serial.printf("ssPin: %d, maxTrials: %d, RXBuffer[0]: %x \r\n", ssPin, maxTrials, RXBuffer[0]);
#endif
// run command until no error, but only max 10 times
runIDNCommandUntilNoError(ssPin, command, sizeof(command), RXBuffer, maxTrials);
}
//Example response of CR95HF for inventory command and data positions/indices
//+------+--------+----------------------------------------------------------------------+
//|Result| Length | Data |
//| code | +--------+-----+---------------------------------------+---------+-----+
//| | |Response| | | | |
//| | | flags |DSFID| UID |CRC16 |Error|
//+------+--------+--------+---------------------------------------------+---------+-----+
//| 0x80 |0x0D(13)| 0x00 |0x00 |0x51 0x69 0x19 0x38 0x42 0x20 0x02 0xE0|0x84 0x28|0x00 |
//+------+--------+--------+-----+---------------------------------------+---------+-----+
//| 0 | 1 | 2 | 3 | 4 5 6 7 8 9 10 11 | 12 13 | 14 |
//+------+--------+--------+-----+---------------------------------------+---------+-----+
// 0 1 2 3 4 5 6 7 8 9 10 11 12
//
/// Retreive idn data from RXBuffer from IDN command
/// This ist the struct later to be transmitted via bluetooth
/// @detail There is no error possible in the response since this is just pure device information and thus not rely on RFID and a tag in the field
/// @param RXBuffer buffer containing the response from the IDN command
/// @param idnType struct with retreived data
IDNDataType idnDataFromIDNResponse(byte *RXBuffer) {
IDNDataType idnData;
idnData.resultCode = RXBuffer[0];
// Device ID has 13 bytes
for (int i = 0; i < 13; i++) {
idnData.deviceID[i] = RXBuffer[i + 2]; // TODO: cchek and continue here
}
// ROM CRC are the last two bytes of the data
int length = RXBuffer[2];
idnData.romCRC[0] = RXBuffer[length - 2];
idnData.romCRC[1] = RXBuffer[length - 1];
return idnData;
}
/// Retreive system information from RXBuffer from system information command
/// This ist the struct later to be transmitted via bluetooth
/// @param RXBuffer buffer containing the response from the system information command
/// @param SystemInformationType struct with retreived data
SystemInformationDataType systemInformationDataFromGetSystemInformationResponse(byte *RXBuffer) {
// SystemInformationType retreiveSystemInformationValues(byte *RXBuffer) {
SystemInformationDataType systemInformationData;
systemInformationData.resultCode = RXBuffer[0];
systemInformationData.responseFlags = RXBuffer[2];
// check for no error in result code and handle accordingly
if (systemInformationData.resultCode == 0x80) { // no error in result code
// check for no error in response flags
if ((systemInformationData.responseFlags & 0x01) == 0) {
// no error in response flags
systemInformationData.infoFlags = RXBuffer[3];
for (int i = 0; i < 8; i++) {
systemInformationData.uid[i] = RXBuffer[11 - i];
}
systemInformationData.errorCode = RXBuffer[RXBuffer[1] + 2 - 1];
} else {
// error case
systemInformationData.errorCode = RXBuffer[3];
}
} else {
// error case
clearBuffer(systemInformationData.uid);
systemInformationData.errorCode = RXBuffer[3];
}
return systemInformationData;
}
/// Sends a packet of data (c-struct) via bluetooth to a smartphone application
/// @detail Any packet to be transfered is a c-struct. These structs are defined in data_types.h.
/// @param packetIdentifier identifier that can be used to treat a packet separately in the app. Identifiers are defined in constants.h
/// @param txFlags don't know yet, what these are fore
/// @param *packetBytes pointer on the c-struct, that is the packet
/// @param byteCount number of bytes to be transfered
bool pumpViaBluetooth(unsigned short packetIdentifier, UBP_TxFlags txFlags, const char *packetBytes, unsigned short byteCount) {
bool success = UBP_queuePacketTransmission(packetIdentifier, txFlags, packetBytes, byteCount);
delay(1);
#ifdef DEBUG
if (success) Serial.println("Packet queued successfully");
else Serial.println("Failed to enqueue packet");
#endif
// put your main code here, to run repeatedly:
while (UBP_isBusy() == true) UBP_pump();
}
/// Returns the voltage on the RFDuino VDD pin as a float in Volts.
/// Code is from the RFDuino Forum, see http://forum.rfduino.com/index.php?topic=265.0 for details
float voltageOnVDD() {
analogReference(VBG); // Sets the Reference to 1.2V band gap
analogSelection(VDD_1_3_PS); // Selects VDD with 1/3 prescaling as the analog source
int sensorValue = analogRead(1); // the pin has no meaning, it uses VDD pin
return sensorValue * (3.6 / 1023.0); // convert value to voltage;
}
/// Print all data of systemInformationData to serial console
void printSystemInformationData(SystemInformationDataType systemInformationData) {
Serial.println("Printing system information data to serial output:");
Serial.printf("Result code: %x\r\n", systemInformationData.resultCode);
Serial.printf("Response flags: %x\r\n", systemInformationData.responseFlags);
Serial.printf("uid: %x", systemInformationData.uid[0]);
for (int i = 1; i < 8; i++) {
Serial.printf(":%x", systemInformationData.uid[i]);
}
Serial.println("");
Serial.printf("Error code: %x\r\n", systemInformationData.errorCode);
}
///===================================================================================================
// The loop
///===================================================================================================
void loop() {
#ifdef DEBUG
Serial.println("In the loop");
#endif
// Start up BM019 if not yet started
if (NFCReady == 0) {
delay(100);
SetProtocol_Command(); // ISO 15693 settings
Serial.println("After SetProtocoll_Command()");
delay(100);
} else {
// ------- Read system information from BM019 ----------------------------------------------------------------------------
//#ifdef DEBUG
Serial.println("Get system information command ...");
//#endif
runSystemInformationCommandUntilNoError(SS_PIN, RXBuffer, 10);
#ifdef DEBUG
Serial.println("... retreive system information ...");
#endif
SystemInformationDataType systemInformationData = systemInformationDataFromGetSystemInformationResponse(RXBuffer);
//------- Read IDN information from BM019 (IDN Command) -------------------------------------------------------------------
//#ifdef DEBUG
Serial.println("IDN command ...");
//#endif
runIDNCommand(SS_PIN, RXBuffer, 10);
#ifdef DEBUG
Serial.println("... retreive IDN information ...");
#endif
IDNDataType idnData = idnDataFromIDNResponse(RXBuffer);
// ----------- Read 43 data blocks into RXBuffer ---------------
//#ifdef DEBUG
Serial.println("Read all data");
//#endif
for (int i = 0; i < sizeof(RXBuffer); i++) {
RXBuffer[i] = 0;
}
for (int i = 0; i < sizeof(dataBuffer); i++) {
dataBuffer[i] = 0;
}
byte resultCode = 0;
int trials = 0;
int maxTrials = 10;
for (int i = 0; i < 43; i++) { // Need only 43 of 244 blocks
resultCode = ReadSingleBlockReturn(i);
#ifdef DEBUG
printf("resultCode 0x%x\n\r", resultCode);
#endif
if (resultCode != 0x80 && trials < maxTrials) {
printf("Error 0x%x\n\r", resultCode);
i--; // repeat same block if error occured, but
trials++; // not more than maxTrials times per block
} else if (trials >= maxTrials) {
break;
} else {
trials = 0;
for (int j = 3; j < RXBuffer[1] + 3 - 4; j++) {
dataBuffer[i * 8 + j - 3] = RXBuffer[j];
#ifdef DEBUG
Serial.print(RXBuffer[j], HEX);
Serial.print(" ");
#endif
}
}
}
// ----------- All data collected, send BM019 to hibernate -------------------------
//#ifdef DEBUG
Serial.println("Sending CR95HF to hibernate ...");
//#endif
sendCR95HFToHibernate(SS_PIN);
// ------- Transmit system information data via bluetooth. By convention this is the first transmission -----------------
bool ergo = pumpViaBluetooth(SYSTEM_INFORMATION_DATA, UBP_TxFlagIsRPC, (char *) &systemInformationData, sizeof(SystemInformationDataType));
#ifdef DEBUG
printSystemInformationData(systemInformationData);
#endif
//-------- transmit dataBuffer via bluetooth ---------------------------------------------------------
AllBytesDataType allBytes;
#ifdef DEBUG
Serial.println("----about to send all data bytes packet");
#endif
for (int i = 0; i < sizeof(allBytes.allBytes); i++) {
allBytes.allBytes[i] = 0;
}
for (int i = 0; i < 344; i++) {
allBytes.allBytes[i] = dataBuffer[i];
}
//#ifdef DEBUG
Serial.printf("Sizeof ist : %d\n", sizeof(AllBytesDataType));
//#endif
bool success = UBP_queuePacketTransmission(ALL_BYTES, UBP_TxFlagIsRPC, (char *) &allBytes, sizeof(AllBytesDataType));
#ifdef DEBUG
if (success) Serial.println("----all data bytes packet queued successfully");
else Serial.println("----Failed to enqueue all data bytes packet");
#endif
while (UBP_isBusy() == true) UBP_pump();
//-------- Read Battery level and Simblee temperature and transmit via bluetooth ---------------------
BatteryDataType batteryData;
batteryData.voltage = voltageOnVDD();
batteryData.temperature = Simblee_temperature(CELSIUS);
#ifdef DEBUG
Serial.printf("Battery voltage: %f\r\n", batteryData.voltage);
#endif
// Transmitt via bluetooth
success = UBP_queuePacketTransmission(BATTERY_DATA, UBP_TxFlagIsRPC, (char *) &batteryData, sizeof(BatteryDataType));
// if (success) Serial.println("Battery data packet queued successfully");
// else Serial.println("Failed to enqueue battery data packet");
// put your main code here, to run repeatedly:
while (UBP_isBusy() == true) UBP_pump();
#ifdef DEBUG
Serial.printf("Sent Battery voltage: %f\r\n", batteryData.voltage);
#endif
//-------- transmit IDN data via bluetooth. By convention this is the last transmission -----------------
ergo = pumpViaBluetooth(IDN_DATA, UBP_TxFlagNone, (char *) &idnData, sizeof(IDNDataType));
success = UBP_queuePacketTransmission(IDN_DATA, UBP_TxFlagIsRPC, (char *) &idnData, sizeof(IDNDataType));
delay(10);
#ifdef DEBUG
if (success) Serial.println("IDN data packet queued successfully");
else Serial.println("Failed to enqueue IDN data packet");
#endif
// put your main code here, to run repeatedly:
while (UBP_isBusy() == true) UBP_pump();
#ifdef DEBUG
Serial.printf("IDN: %x", idnData.deviceID[0]);
for (int i = 1; i < 13; i++) {
Serial.printf(":%x", idnData.deviceID[i]);
}
Serial.println("... done");
#endif
//--------- send Simblee into ultra low power mode ---------------------------------------------------
//#ifdef DEBUG
Serial.println("Sending RFDuino to sleep ...");
//#endif
Simblee_ULPDelay(SLEEP_DURATION); //
//--------- send Simblee woke up again, now also wake up BM019 and repeat the loope cycle ------------
#ifdef DEBUG
Serial.println("... RFDuino woke up again");
Serial.println("Wake up CR95HF with wake up pulse...");
#endif
sendWakeupPulse(IRQ_PIN); // Wake up BM019 (low pulse on IRQ_PIN)
readWakeUPEventRegister(SS_PIN, RXBuffer);
setupSPI();
delay(10);
SetProtocol_Command(); // ISO 15693 settings
delay(100);
#ifdef DEBUG
Serial.println("... CR95HF woke up again. Receiving wake up response");
#endif
}
}
================================================
FILE: LibreMonitor.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
9F659E798C0CD22711C55DE6 /* Pods_LibreMonitorUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 887C2B24AC9C0E842F9CE127 /* Pods_LibreMonitorUITests.framework */; };
C60178961E3E7FE5008FCF3A /* BluetoothTestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60178951E3E7FE5008FCF3A /* BluetoothTestData.swift */; };
C61C296C1DB16C60009A5885 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C61C296B1DB16C60009A5885 /* Main.storyboard */; };
C65E6DA21E45070800A85E0E /* TransmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C65E6DA11E45070800A85E0E /* TransmissionTests.swift */; };
C6652F1E1DB1801D00237E7D /* crc8.m in Sources */ = {isa = PBXBuildFile; fileRef = C6652F1B1DB1801D00237E7D /* crc8.m */; };
C6652F1F1DB1801D00237E7D /* libUBP.m in Sources */ = {isa = PBXBuildFile; fileRef = C6652F1D1DB1801D00237E7D /* libUBP.m */; };
C6822D661DB13F2200D08393 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822D651DB13F2200D08393 /* AppDelegate.swift */; };
C6822D691DB13F2200D08393 /* LibreMonitor.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C6822D671DB13F2200D08393 /* LibreMonitor.xcdatamodeld */; };
C6822D721DB13F2200D08393 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C6822D711DB13F2200D08393 /* Assets.xcassets */; };
C6822D751DB13F2200D08393 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C6822D731DB13F2200D08393 /* LaunchScreen.storyboard */; };
C6822D801DB13F2200D08393 /* LibreMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822D7F1DB13F2200D08393 /* LibreMonitorTests.swift */; };
C6822D8B1DB13F2200D08393 /* LibreMonitorUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822D8A1DB13F2200D08393 /* LibreMonitorUITests.swift */; };
C6822DAA1DB1616300D08393 /* Data_types+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DA31DB1616300D08393 /* Data_types+Extensions.swift */; };
C6822DAB1DB1616300D08393 /* NSData+CRC8.m in Sources */ = {isa = PBXBuildFile; fileRef = C6822DA51DB1616300D08393 /* NSData+CRC8.m */; };
C6822DAC1DB1616300D08393 /* NSData+SLIP.m in Sources */ = {isa = PBXBuildFile; fileRef = C6822DA71DB1616300D08393 /* NSData+SLIP.m */; };
C6822DAD1DB1616300D08393 /* SimbleeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DA81DB1616300D08393 /* SimbleeManager.swift */; };
C6822DAE1DB1616300D08393 /* SLIPBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DA91DB1616300D08393 /* SLIPBuffer.swift */; };
C6822DB51DB1618400D08393 /* CRC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DB01DB1618400D08393 /* CRC.swift */; };
C6822DB61DB1618400D08393 /* LibreSensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DB11DB1618400D08393 /* LibreSensor.swift */; };
C6822DB71DB1618400D08393 /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DB21DB1618400D08393 /* Measurement.swift */; };
C6822DB81DB1618400D08393 /* SensorData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DB31DB1618400D08393 /* SensorData.swift */; };
C6822DB91DB1618400D08393 /* SensorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DB41DB1618400D08393 /* SensorState.swift */; };
C6822DBD1DB161AB00D08393 /* AdjustmentsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DBB1DB161AB00D08393 /* AdjustmentsTableViewController.swift */; };
C6822DBE1DB161AB00D08393 /* BloodSugarTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DBC1DB161AB00D08393 /* BloodSugarTableViewController.swift */; };
C6822DC21DB161C700D08393 /* BloodSugarGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DC01DB161C700D08393 /* BloodSugarGraphView.swift */; };
C6822DC31DB161C700D08393 /* BloodSugarGraphViewTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DC11DB161C700D08393 /* BloodSugarGraphViewTableViewCell.swift */; };
C6822DC51DB161F900D08393 /* LibreMonitorTestSensorData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DC41DB161F900D08393 /* LibreMonitorTestSensorData.swift */; };
C6822DC81DB162E900D08393 /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DC71DB162E900D08393 /* CoreDataStack.swift */; };
C6822DD51DB1631D00D08393 /* BloodGlucose+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DCB1DB1631D00D08393 /* BloodGlucose+CoreDataClass.swift */; };
C6822DD61DB1631D00D08393 /* BloodGlucose+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DCC1DB1631D00D08393 /* BloodGlucose+CoreDataProperties.swift */; };
C6822DD71DB1631D00D08393 /* Reader+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DCD1DB1631D00D08393 /* Reader+CoreDataClass.swift */; };
C6822DD81DB1631D00D08393 /* Reader+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DCE1DB1631D00D08393 /* Reader+CoreDataProperties.swift */; };
C6822DD91DB1631D00D08393 /* Sensor+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DCF1DB1631D00D08393 /* Sensor+CoreDataClass.swift */; };
C6822DDA1DB1631D00D08393 /* Sensor+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DD01DB1631D00D08393 /* Sensor+CoreDataProperties.swift */; };
C6822DDB1DB1631D00D08393 /* HeaderData+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DD11DB1631D00D08393 /* HeaderData+CoreDataClass.swift */; };
C6822DDC1DB1631D00D08393 /* HeaderData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6822DD21DB1631D00D08393 /* HeaderData+CoreDataProperties.swift */; };
C6DDBDBB1DC512410048D275 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6DDBDBA1DC512410048D275 /* HealthKit.framework */; };
F1BB69F1B49E13B76E4A90C4 /* Pods_LibreMonitor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAD184EB2119C8F78D731C7E /* Pods_LibreMonitor.framework */; };
F8C71C986A327FDC6776D4B2 /* Pods_LibreMonitorTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 402C08AB93E799ED917B1043 /* Pods_LibreMonitorTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
C6822D7C1DB13F2200D08393 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = C6822D5A1DB13F2200D08393 /* Project object */;
proxyType = 1;
remoteGlobalIDString = C6822D611DB13F2200D08393;
remoteInfo = LibreMonitor;
};
C6822D871DB13F2200D08393 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = C6822D5A1DB13F2200D08393 /* Project object */;
proxyType = 1;
remoteGlobalIDString = C6822D611DB13F2200D08393;
remoteInfo = LibreMonitor;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
20B072583FD87F7318BD08D0 /* Pods-LibreMonitorTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LibreMonitorTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LibreMonitorTests/Pods-LibreMonitorTests.debug.xcconfig"; sourceTree = ""; };
402C08AB93E799ED917B1043 /* Pods_LibreMonitorTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LibreMonitorTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
771CB977837C430DB5630E78 /* Pods-LibreMonitor.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LibreMonitor.release.xcconfig"; path = "Pods/Target Support Files/Pods-LibreMonitor/Pods-LibreMonitor.release.xcconfig"; sourceTree = ""; };
887C2B24AC9C0E842F9CE127 /* Pods_LibreMonitorUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LibreMonitorUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9C01D6FD2C7EC8B142986E40 /* Pods-LibreMonitor.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LibreMonitor.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LibreMonitor/Pods-LibreMonitor.debug.xcconfig"; sourceTree = ""; };
C60178951E3E7FE5008FCF3A /* BluetoothTestData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothTestData.swift; sourceTree = ""; };
C61C296B1DB16C60009A5885 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; };
C65E6DA11E45070800A85E0E /* TransmissionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransmissionTests.swift; sourceTree = ""; };
C6652F1A1DB1801D00237E7D /* crc8.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = crc8.h; path = SimbleeCode/crc8.h; sourceTree = ""; };
C6652F1B1DB1801D00237E7D /* crc8.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = crc8.m; path = SimbleeCode/crc8.m; sourceTree = ""; };
C6652F1C1DB1801D00237E7D /* libUBP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = libUBP.h; path = SimbleeCode/libUBP.h; sourceTree = ""; };
C6652F1D1DB1801D00237E7D /* libUBP.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = libUBP.m; path = SimbleeCode/libUBP.m; sourceTree = ""; };
C6652F201DB1812C00237E7D /* LibreMonitorTests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "LibreMonitorTests-Bridging-Header.h"; sourceTree = ""; };
C6652F211DB181E900237E7D /* LibreMonitor-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "LibreMonitor-Bridging-Header.h"; sourceTree = ""; };
C6822D621DB13F2200D08393 /* LibreMonitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LibreMonitor.app; sourceTree = BUILT_PRODUCTS_DIR; };
C6822D651DB13F2200D08393 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
C6822D681DB13F2200D08393 /* LibreMonitor.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LibreMonitor.xcdatamodel; sourceTree = ""; };
C6822D711DB13F2200D08393 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
C6822D741DB13F2200D08393 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
C6822D761DB13F2200D08393 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
C6822D7B1DB13F2200D08393 /* LibreMonitorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LibreMonitorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
C6822D7F1DB13F2200D08393 /* LibreMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreMonitorTests.swift; sourceTree = ""; };
C6822D811DB13F2200D08393 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
C6822D861DB13F2200D08393 /* LibreMonitorUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LibreMonitorUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
C6822D8A1DB13F2200D08393 /* LibreMonitorUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibreMonitorUITests.swift; sourceTree = ""; };
C6822D8C1DB13F2200D08393 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
C6822DA11DB1616300D08393 /* constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = constants.h; path = Bluetooth/constants.h; sourceTree = ""; };
C6822DA21DB1616300D08393 /* data_types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = data_types.h; path = Bluetooth/data_types.h; sourceTree = ""; };
C6822DA31DB1616300D08393 /* Data_types+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Data_types+Extensions.swift"; path = "Bluetooth/Data_types+Extensions.swift"; sourceTree = ""; };
C6822DA41DB1616300D08393 /* NSData+CRC8.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSData+CRC8.h"; path = "Bluetooth/NSData+CRC8.h"; sourceTree = ""; };
C6822DA51DB1616300D08393 /* NSData+CRC8.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSData+CRC8.m"; path = "Bluetooth/NSData+CRC8.m"; sourceTree = ""; };
C6822DA61DB1616300D08393 /* NSData+SLIP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSData+SLIP.h"; path = "Bluetooth/NSData+SLIP.h"; sourceTree = ""; };
C6822DA71DB1616300D08393 /* NSData+SLIP.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSData+SLIP.m"; path = "Bluetooth/NSData+SLIP.m"; sourceTree = ""; };
C6822DA81DB1616300D08393 /* SimbleeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SimbleeManager.swift; path = Bluetooth/SimbleeManager.swift; sourceTree = ""; };
C6822DA91DB1616300D08393 /* SLIPBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SLIPBuffer.swift; path = Bluetooth/SLIPBuffer.swift; sourceTree = ""; };
C6822DB01DB1618400D08393 /* CRC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CRC.swift; path = Model/CRC.swift; sourceTree = ""; };
C6822DB11DB1618400D08393 /* LibreSensor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LibreSensor.swift; path = Model/LibreSensor.swift; sourceTree = ""; };
C6822DB21DB1618400D08393 /* Measurement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Measurement.swift; path = Model/Measurement.swift; sourceTree = ""; };
C6822DB31DB1618400D08393 /* SensorData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SensorData.swift; path = Model/SensorData.swift; sourceTree = ""; };
C6822DB41DB1618400D08393 /* SensorState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SensorState.swift; path = Model/SensorState.swift; sourceTree = ""; };
C6822DBB1DB161AB00D08393 /* AdjustmentsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AdjustmentsTableViewController.swift; path = ViewControllers/AdjustmentsTableViewController.swift; sourceTree = ""; };
C6822DBC1DB161AB00D08393 /* BloodSugarTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BloodSugarTableViewController.swift; path = ViewControllers/BloodSugarTableViewController.swift; sourceTree = ""; };
C6822DC01DB161C700D08393 /* BloodSugarGraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BloodSugarGraphView.swift; path = Views/BloodSugarGraphView.swift; sourceTree = ""; };
C6822DC11DB161C700D08393 /* BloodSugarGraphViewTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BloodSugarGraphViewTableViewCell.swift; path = Views/BloodSugarGraphViewTableViewCell.swift; sourceTree = ""; };
C6822DC41DB161F900D08393 /* LibreMonitorTestSensorData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibreMonitorTestSensorData.swift; sourceTree = ""; };
C6822DC71DB162E900D08393 /* CoreDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CoreDataStack.swift; path = ModelCoreData/CoreDataStack.swift; sourceTree = ""; };
C6822DCB1DB1631D00D08393 /* BloodGlucose+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BloodGlucose+CoreDataClass.swift"; sourceTree = ""; };
C6822DCC1DB1631D00D08393 /* BloodGlucose+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BloodGlucose+CoreDataProperties.swift"; sourceTree = ""; };
C6822DCD1DB1631D00D08393 /* Reader+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Reader+CoreDataClass.swift"; sourceTree = ""; };
C6822DCE1DB1631D00D08393 /* Reader+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Reader+CoreDataProperties.swift"; sourceTree = ""; };
C6822DCF1DB1631D00D08393 /* Sensor+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sensor+CoreDataClass.swift"; sourceTree = ""; };
C6822DD01DB1631D00D08393 /* Sensor+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sensor+CoreDataProperties.swift"; sourceTree = ""; };
C6822DD11DB1631D00D08393 /* HeaderData+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HeaderData+CoreDataClass.swift"; sourceTree = ""; };
C6822DD21DB1631D00D08393 /* HeaderData+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HeaderData+CoreDataProperties.swift"; sourceTree = ""; };
C6DDBDBA1DC512410048D275 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; };
C6DDBDBC1DC512420048D275 /* LibreMonitor.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LibreMonitor.entitlements; sourceTree = ""; };
D8227FF2A03737CDDF22876F /* Pods-LibreMonitorUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LibreMonitorUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LibreMonitorUITests/Pods-LibreMonitorUITests.debug.xcconfig"; sourceTree = ""; };
EAD184EB2119C8F78D731C7E /* Pods_LibreMonitor.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LibreMonitor.framework; sourceTree = BUILT_PRODUCTS_DIR; };
ECBB8DB6FD66440727AC6903 /* Pods-LibreMonitorTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LibreMonitorTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-LibreMonitorTests/Pods-LibreMonitorTests.release.xcconfig"; sourceTree = ""; };
F858C2A695CEC9FB81DC9A5A /* Pods-LibreMonitorUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LibreMonitorUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-LibreMonitorUITests/Pods-LibreMonitorUITests.release.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
C6822D5F1DB13F2200D08393 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F1BB69F1B49E13B76E4A90C4 /* Pods_LibreMonitor.framework in Frameworks */,
C6DDBDBB1DC512410048D275 /* HealthKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C6822D781DB13F2200D08393 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F8C71C986A327FDC6776D4B2 /* Pods_LibreMonitorTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C6822D831DB13F2200D08393 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9F659E798C0CD22711C55DE6 /* Pods_LibreMonitorUITests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
8F126C8830CE9383D368A1DE /* Pods */ = {
isa = PBXGroup;
children = (
9C01D6FD2C7EC8B142986E40 /* Pods-LibreMonitor.debug.xcconfig */,
771CB977837C430DB5630E78 /* Pods-LibreMonitor.release.xcconfig */,
20B072583FD87F7318BD08D0 /* Pods-LibreMonitorTests.debug.xcconfig */,
ECBB8DB6FD66440727AC6903 /* Pods-LibreMonitorTests.release.xcconfig */,
D8227FF2A03737CDDF22876F /* Pods-LibreMonitorUITests.debug.xcconfig */,
F858C2A695CEC9FB81DC9A5A /* Pods-LibreMonitorUITests.release.xcconfig */,
);
name = Pods;
sourceTree = "";
};
C12F12DE60464196E06F6804 /* Frameworks */ = {
isa = PBXGroup;
children = (
C6DDBDBA1DC512410048D275 /* HealthKit.framework */,
EAD184EB2119C8F78D731C7E /* Pods_LibreMonitor.framework */,
402C08AB93E799ED917B1043 /* Pods_LibreMonitorTests.framework */,
887C2B24AC9C0E842F9CE127 /* Pods_LibreMonitorUITests.framework */,
);
name = Frameworks;
sourceTree = "";
};
C6652F121DB17FC100237E7D /* SimbleeCode */ = {
isa = PBXGroup;
children = (
C6652F1A1DB1801D00237E7D /* crc8.h */,
C6652F1B1DB1801D00237E7D /* crc8.m */,
C6652F1C1DB1801D00237E7D /* libUBP.h */,
C6652F1D1DB1801D00237E7D /* libUBP.m */,
);
name = SimbleeCode;
sourceTree = "";
};
C6822D591DB13F2200D08393 = {
isa = PBXGroup;
children = (
C6822D641DB13F2200D08393 /* LibreMonitor */,
C6822D7E1DB13F2200D08393 /* LibreMonitorTests */,
C6822D891DB13F2200D08393 /* LibreMonitorUITests */,
C6822D631DB13F2200D08393 /* Products */,
8F126C8830CE9383D368A1DE /* Pods */,
C12F12DE60464196E06F6804 /* Frameworks */,
);
sourceTree = "";
};
C6822D631DB13F2200D08393 /* Products */ = {
isa = PBXGroup;
children = (
C6822D621DB13F2200D08393 /* LibreMonitor.app */,
C6822D7B1DB13F2200D08393 /* LibreMonitorTests.xctest */,
C6822D861DB13F2200D08393 /* LibreMonitorUITests.xctest */,
);
name = Products;
sourceTree = "";
};
C6822D641DB13F2200D08393 /* LibreMonitor */ = {
isa = PBXGroup;
children = (
C6DDBDBC1DC512420048D275 /* LibreMonitor.entitlements */,
C6822DC61DB162D600D08393 /* ModelCoreData */,
C6822DBF1DB161B300D08393 /* Views */,
C6822DBA1DB1619100D08393 /* ViewControllers */,
C6822DAF1DB1617400D08393 /* Model */,
C6822DA01DB1614C00D08393 /* Bluetooth */,
C6822D651DB13F2200D08393 /* AppDelegate.swift */,
C61C296B1DB16C60009A5885 /* Main.storyboard */,
C6822D711DB13F2200D08393 /* Assets.xcassets */,
C6822D731DB13F2200D08393 /* LaunchScreen.storyboard */,
C6822D761DB13F2200D08393 /* Info.plist */,
C6822D671DB13F2200D08393 /* LibreMonitor.xcdatamodeld */,
C6652F211DB181E900237E7D /* LibreMonitor-Bridging-Header.h */,
);
path = LibreMonitor;
sourceTree = "";
};
C6822D7E1DB13F2200D08393 /* LibreMonitorTests */ = {
isa = PBXGroup;
children = (
C60178951E3E7FE5008FCF3A /* BluetoothTestData.swift */,
C6822D7F1DB13F2200D08393 /* LibreMonitorTests.swift */,
C6822DC41DB161F900D08393 /* LibreMonitorTestSensorData.swift */,
C65E6DA11E45070800A85E0E /* TransmissionTests.swift */,
C6652F121DB17FC100237E7D /* SimbleeCode */,
C6652F201DB1812C00237E7D /* LibreMonitorTests-Bridging-Header.h */,
C6822D811DB13F2200D08393 /* Info.plist */,
);
path = LibreMonitorTests;
sourceTree = "";
};
C6822D891DB13F2200D08393 /* LibreMonitorUITests */ = {
isa = PBXGroup;
children = (
C6822D8A1DB13F2200D08393 /* LibreMonitorUITests.swift */,
C6822D8C1DB13F2200D08393 /* Info.plist */,
);
path = LibreMonitorUITests;
sourceTree = "";
};
C6822DA01DB1614C00D08393 /* Bluetooth */ = {
isa = PBXGroup;
children = (
C6822DA11DB1616300D08393 /* constants.h */,
C6822DA21DB1616300D08393 /* data_types.h */,
C6822DA31DB1616300D08393 /* Data_types+Extensions.swift */,
C6822DA41DB1616300D08393 /* NSData+CRC8.h */,
C6822DA51DB1616300D08393 /* NSData+CRC8.m */,
C6822DA61DB1616300D08393 /* NSData+SLIP.h */,
C6822DA71DB1616300D08393 /* NSData+SLIP.m */,
C6822DA81DB1616300D08393 /* SimbleeManager.swift */,
C6822DA91DB1616300D08393 /* SLIPBuffer.swift */,
);
name = Bluetooth;
sourceTree = "";
};
C6822DAF1DB1617400D08393 /* Model */ = {
isa = PBXGroup;
children = (
C6822DB01DB1618400D08393 /* CRC.swift */,
C6822DB11DB1618400D08393 /* LibreSensor.swift */,
C6822DB21DB1618400D08393 /* Measurement.swift */,
C6822DB31DB1618400D08393 /* SensorData.swift */,
C6822DB41DB1618400D08393 /* SensorState.swift */,
);
name = Model;
sourceTree = "";
};
C6822DBA1DB1619100D08393 /* ViewControllers */ = {
isa = PBXGroup;
children = (
C6822DBB1DB161AB00D08393 /* AdjustmentsTableViewController.swift */,
C6822DBC1DB161AB00D08393 /* BloodSugarTableViewController.swift */,
);
name = ViewControllers;
sourceTree = "";
};
C6822DBF1DB161B300D08393 /* Views */ = {
isa = PBXGroup;
children = (
C6822DC01DB161C700D08393 /* BloodSugarGraphView.swift */,
C6822DC11DB161C700D08393 /* BloodSugarGraphViewTableViewCell.swift */,
);
name = Views;
sourceTree = "";
};
C6822DC61DB162D600D08393 /* ModelCoreData */ = {
isa = PBXGroup;
children = (
C6822DCB1DB1631D00D08393 /* BloodGlucose+CoreDataClass.swift */,
C6822DCC1DB1631D00D08393 /* BloodGlucose+CoreDataProperties.swift */,
C6822DCD1DB1631D00D08393 /* Reader+CoreDataClass.swift */,
C6822DCE1DB1631D00D08393 /* Reader+CoreDataProperties.swift */,
C6822DCF1DB1631D00D08393 /* Sensor+CoreDataClass.swift */,
C6822DD01DB1631D00D08393 /* Sensor+CoreDataProperties.swift */,
C6822DD11DB1631D00D08393 /* HeaderData+CoreDataClass.swift */,
C6822DD21DB1631D00D08393 /* HeaderData+CoreDataProperties.swift */,
C6822DC71DB162E900D08393 /* CoreDataStack.swift */,
);
name = ModelCoreData;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
C6822D611DB13F2200D08393 /* LibreMonitor */ = {
isa = PBXNativeTarget;
buildConfigurationList = C6822D8F1DB13F2200D08393 /* Build configuration list for PBXNativeTarget "LibreMonitor" */;
buildPhases = (
ACC5AE5D94CFB4897C7237F1 /* [CP] Check Pods Manifest.lock */,
C6822D5E1DB13F2200D08393 /* Sources */,
C6822D5F1DB13F2200D08393 /* Frameworks */,
C6822D601DB13F2200D08393 /* Resources */,
7ABA004F1E59594A6108DE52 /* [CP] Embed Pods Frameworks */,
19AB2018DB2E7FFBCFDA6F6B /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = LibreMonitor;
productName = LibreMonitor;
productReference = C6822D621DB13F2200D08393 /* LibreMonitor.app */;
productType = "com.apple.product-type.application";
};
C6822D7A1DB13F2200D08393 /* LibreMonitorTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = C6822D921DB13F2200D08393 /* Build configuration list for PBXNativeTarget "LibreMonitorTests" */;
buildPhases = (
BDD349C2E88A839DFD7221CD /* [CP] Check Pods Manifest.lock */,
C6822D771DB13F2200D08393 /* Sources */,
C6822D781DB13F2200D08393 /* Frameworks */,
C6822D791DB13F2200D08393 /* Resources */,
937C85C62A3BBC97CAB8CD1C /* [CP] Embed Pods Frameworks */,
A86EDE06FCDE9DED1918E865 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
C6822D7D1DB13F2200D08393 /* PBXTargetDependency */,
);
name = LibreMonitorTests;
productName = LibreMonitorTests;
productReference = C6822D7B1DB13F2200D08393 /* LibreMonitorTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
C6822D851DB13F2200D08393 /* LibreMonitorUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = C6822D951DB13F2200D08393 /* Build configuration list for PBXNativeTarget "LibreMonitorUITests" */;
buildPhases = (
4B935F2F04D9D35ADFEF9391 /* [CP] Check Pods Manifest.lock */,
C6822D821DB13F2200D08393 /* Sources */,
C6822D831DB13F2200D08393 /* Frameworks */,
C6822D841DB13F2200D08393 /* Resources */,
951B3668E1ADFC92ED178434 /* [CP] Embed Pods Frameworks */,
A725505D03BE411795C63648 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
C6822D881DB13F2200D08393 /* PBXTargetDependency */,
);
name = LibreMonitorUITests;
productName = LibreMonitorUITests;
productReference = C6822D861DB13F2200D08393 /* LibreMonitorUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
C6822D5A1DB13F2200D08393 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0800;
LastUpgradeCheck = 0820;
ORGANIZATIONNAME = "Uwe Petersen";
TargetAttributes = {
C6822D611DB13F2200D08393 = {
CreatedOnToolsVersion = 8.0;
DevelopmentTeam = 26F6YWJYL8;
LastSwiftMigration = 0810;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
};
com.apple.HealthKit = {
enabled = 1;
};
};
};
C6822D7A1DB13F2200D08393 = {
CreatedOnToolsVersion = 8.0;
DevelopmentTeam = 26F6YWJYL8;
LastSwiftMigration = 0800;
ProvisioningStyle = Automatic;
TestTargetID = C6822D611DB13F2200D08393;
};
C6822D851DB13F2200D08393 = {
CreatedOnToolsVersion = 8.0;
DevelopmentTeam = 26F6YWJYL8;
LastSwiftMigration = 0800;
ProvisioningStyle = Automatic;
TestTargetID = C6822D611DB13F2200D08393;
};
};
};
buildConfigurationList = C6822D5D1DB13F2200D08393 /* Build configuration list for PBXProject "LibreMonitor" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = C6822D591DB13F2200D08393;
productRefGroup = C6822D631DB13F2200D08393 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
C6822D611DB13F2200D08393 /* LibreMonitor */,
C6822D7A1DB13F2200D08393 /* LibreMonitorTests */,
C6822D851DB13F2200D08393 /* LibreMonitorUITests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
C6822D601DB13F2200D08393 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C6822D751DB13F2200D08393 /* LaunchScreen.storyboard in Resources */,
C6822D721DB13F2200D08393 /* Assets.xcassets in Resources */,
C61C296C1DB16C60009A5885 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C6822D791DB13F2200D08393 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
C6822D841DB13F2200D08393 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
19AB2018DB2E7FFBCFDA6F6B /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LibreMonitor/Pods-LibreMonitor-resources.sh\"\n";
showEnvVarsInLog = 0;
};
4B935F2F04D9D35ADFEF9391 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
7ABA004F1E59594A6108DE52 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LibreMonitor/Pods-LibreMonitor-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
937C85C62A3BBC97CAB8CD1C /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LibreMonitorTests/Pods-LibreMonitorTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
951B3668E1ADFC92ED178434 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LibreMonitorUITests/Pods-LibreMonitorUITests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
A725505D03BE411795C63648 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LibreMonitorUITests/Pods-LibreMonitorUITests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
A86EDE06FCDE9DED1918E865 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LibreMonitorTests/Pods-LibreMonitorTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
ACC5AE5D94CFB4897C7237F1 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
BDD349C2E88A839DFD7221CD /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
C6822D5E1DB13F2200D08393 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C6822DDA1DB1631D00D08393 /* Sensor+CoreDataProperties.swift in Sources */,
C6822DC81DB162E900D08393 /* CoreDataStack.swift in Sources */,
C6822DC21DB161C700D08393 /* BloodSugarGraphView.swift in Sources */,
C6822DD81DB1631D00D08393 /* Reader+CoreDataProperties.swift in Sources */,
C6822DD51DB1631D00D08393 /* BloodGlucose+CoreDataClass.swift in Sources */,
C6822DDC1DB1631D00D08393 /* HeaderData+CoreDataProperties.swift in Sources */,
C6822DAC1DB1616300D08393 /* NSData+SLIP.m in Sources */,
C6822D661DB13F2200D08393 /* AppDelegate.swift in Sources */,
C6822DB61DB1618400D08393 /* LibreSensor.swift in Sources */,
C6822D691DB13F2200D08393 /* LibreMonitor.xcdatamodeld in Sources */,
C6822DD61DB1631D00D08393 /* BloodGlucose+CoreDataProperties.swift in Sources */,
C6822DC31DB161C700D08393 /* BloodSugarGraphViewTableViewCell.swift in Sources */,
C6822DB71DB1618400D08393 /* Measurement.swift in Sources */,
C6822DB91DB1618400D08393 /* SensorState.swift in Sources */,
C6822DDB1DB1631D00D08393 /* HeaderData+CoreDataClass.swift in Sources */,
C6822DD71DB1631D00D08393 /* Reader+CoreDataClass.swift in Sources */,
C6822DB81DB1618400D08393 /* SensorData.swift in Sources */,
C6822DBD1DB161AB00D08393 /* AdjustmentsTableViewController.swift in Sources */,
C6822DBE1DB161AB00D08393 /* BloodSugarTableViewController.swift in Sources */,
C6822DAD1DB1616300D08393 /* SimbleeManager.swift in Sources */,
C6822DB51DB1618400D08393 /* CRC.swift in Sources */,
C6822DAE1DB1616300D08393 /* SLIPBuffer.swift in Sources */,
C6822DAB1DB1616300D08393 /* NSData+CRC8.m in Sources */,
C6822DAA1DB1616300D08393 /* Data_types+Extensions.swift in Sources */,
C6822DD91DB1631D00D08393 /* Sensor+CoreDataClass.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C6822D771DB13F2200D08393 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C60178961E3E7FE5008FCF3A /* BluetoothTestData.swift in Sources */,
C6822DC51DB161F900D08393 /* LibreMonitorTestSensorData.swift in Sources */,
C6652F1F1DB1801D00237E7D /* libUBP.m in Sources */,
C65E6DA21E45070800A85E0E /* TransmissionTests.swift in Sources */,
C6652F1E1DB1801D00237E7D /* crc8.m in Sources */,
C6822D801DB13F2200D08393 /* LibreMonitorTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C6822D821DB13F2200D08393 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C6822D8B1DB13F2200D08393 /* LibreMonitorUITests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
C6822D7D1DB13F2200D08393 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = C6822D611DB13F2200D08393 /* LibreMonitor */;
targetProxy = C6822D7C1DB13F2200D08393 /* PBXContainerItemProxy */;
};
C6822D881DB13F2200D08393 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = C6822D611DB13F2200D08393 /* LibreMonitor */;
targetProxy = C6822D871DB13F2200D08393 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
C6822D731DB13F2200D08393 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
C6822D741DB13F2200D08393 /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
C6822D8D1DB13F2200D08393 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
C6822D8E1DB13F2200D08393 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
C6822D901DB13F2200D08393 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9C01D6FD2C7EC8B142986E40 /* Pods-LibreMonitor.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = LibreMonitor/LibreMonitor.entitlements;
DEVELOPMENT_TEAM = 26F6YWJYL8;
INFOPLIST_FILE = LibreMonitor/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = UPP.LibreMonitor;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "LibreMonitor/LibreMonitor-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
C6822D911DB13F2200D08393 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 771CB977837C430DB5630E78 /* Pods-LibreMonitor.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = LibreMonitor/LibreMonitor.entitlements;
DEVELOPMENT_TEAM = 26F6YWJYL8;
INFOPLIST_FILE = LibreMonitor/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = UPP.LibreMonitor;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "LibreMonitor/LibreMonitor-Bridging-Header.h";
SWIFT_VERSION = 3.0;
};
name = Release;
};
C6822D931DB13F2200D08393 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 20B072583FD87F7318BD08D0 /* Pods-LibreMonitorTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ENABLE_MODULES = YES;
DEVELOPMENT_TEAM = 26F6YWJYL8;
INFOPLIST_FILE = LibreMonitorTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
NEW_SETTING = "";
PRODUCT_BUNDLE_IDENTIFIER = UPP.LibreMonitorTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "LibreMonitorTests/LibreMonitorTests-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LibreMonitor.app/LibreMonitor";
};
name = Debug;
};
C6822D941DB13F2200D08393 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = ECBB8DB6FD66440727AC6903 /* Pods-LibreMonitorTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ENABLE_MODULES = YES;
DEVELOPMENT_TEAM = 26F6YWJYL8;
INFOPLIST_FILE = LibreMonitorTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
NEW_SETTING = "";
PRODUCT_BUNDLE_IDENTIFIER = UPP.LibreMonitorTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "LibreMonitorTests/LibreMonitorTests-Bridging-Header.h";
SWIFT_VERSION = 3.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LibreMonitor.app/LibreMonitor";
};
name = Release;
};
C6822D961DB13F2200D08393 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D8227FF2A03737CDDF22876F /* Pods-LibreMonitorUITests.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
DEVELOPMENT_TEAM = 26F6YWJYL8;
INFOPLIST_FILE = LibreMonitorUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = UPP.LibreMonitorUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
TEST_TARGET_NAME = LibreMonitor;
};
name = Debug;
};
C6822D971DB13F2200D08393 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F858C2A695CEC9FB81DC9A5A /* Pods-LibreMonitorUITests.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
DEVELOPMENT_TEAM = 26F6YWJYL8;
INFOPLIST_FILE = LibreMonitorUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = UPP.LibreMonitorUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
TEST_TARGET_NAME = LibreMonitor;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
C6822D5D1DB13F2200D08393 /* Build configuration list for PBXProject "LibreMonitor" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C6822D8D1DB13F2200D08393 /* Debug */,
C6822D8E1DB13F2200D08393 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C6822D8F1DB13F2200D08393 /* Build configuration list for PBXNativeTarget "LibreMonitor" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C6822D901DB13F2200D08393 /* Debug */,
C6822D911DB13F2200D08393 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C6822D921DB13F2200D08393 /* Build configuration list for PBXNativeTarget "LibreMonitorTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C6822D931DB13F2200D08393 /* Debug */,
C6822D941DB13F2200D08393 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C6822D951DB13F2200D08393 /* Build configuration list for PBXNativeTarget "LibreMonitorUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C6822D961DB13F2200D08393 /* Debug */,
C6822D971DB13F2200D08393 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCVersionGroup section */
C6822D671DB13F2200D08393 /* LibreMonitor.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
C6822D681DB13F2200D08393 /* LibreMonitor.xcdatamodel */,
);
currentVersion = C6822D681DB13F2200D08393 /* LibreMonitor.xcdatamodel */;
path = LibreMonitor.xcdatamodeld;
sourceTree = "";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = C6822D5A1DB13F2200D08393 /* Project object */;
}
================================================
FILE: LibreMonitor.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: LibreMonitor.xcodeproj/xcuserdata/Uwe.xcuserdatad/xcschemes/LibreMonitor.xcscheme
================================================
================================================
FILE: LibreMonitor.xcodeproj/xcuserdata/Uwe.xcuserdatad/xcschemes/xcschememanagement.plist
================================================
SchemeUserState
LibreMonitor.xcscheme
orderHint
0
SuppressBuildableAutocreation
C6822D611DB13F2200D08393
primary
C6822D7A1DB13F2200D08393
primary
C6822D851DB13F2200D08393
primary
================================================
FILE: LibreMonitor.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: LibreMonitor.xcworkspace/xcuserdata/Uwe.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
================================================
================================================
FILE: LibreMonitorRFduino.ino
================================================
//#include
///
/// LibreMonitor
///
/// Copyright (c) 2015 Uwe Petersen, all right reserved
///
/// Wiring connections:
///
/// BM019 RFduino/Simblee
/// DIN: pin 2 IRQ: GPIO pin 2
/// SS: pin 3 SS: GPIO pin 3
/// MISO: pin 4 MISO: GPIO pin 4
/// MOSI: pin 5 MOSI: GPIO pin 5
/// SCK: pin 6 SCK: GPIO pin 6
/// SS0: pin 7 +3V-pin
/// GND: pin 10 GND-pin
///
/// BM019 BM019
/// SS0: pin 7 VDD: pin 8 (Output 3,3V from BM019)
///
/// BM019 Lipo Power source (I use a 100mAh lip which lasts a full day
/// VIN: pin 9 "+" of lipo / lipo charger
/// GND: pin 10 GND/"-" of lipo / lipo charger
///
/// You can place a switch between GND of lipo/lipo-charger and GND of the BM019
///
/// SPI-WIRING
/// If wired as suggested above, then you have to change the Simblee SPI pins for SS, MOSI, MISO and SCK.
/// This is done in the variant.h file. In my case this file is located (dependent on the Simblee/RFduino
/// version) in
///
/// /Users/[my user name]/Library/Arduino15/packages/Simblee/hardware/Simblee/1.0.0/variants/Simblee or
/// /Users/[my user name]/Library/Arduino15/packages/RFduino/hardware/RFduino/2.3.3/variants
///
/// In this file set the defines for the SPI pins as follows:
///
/// #define PIN_SPI_SS (3u)
/// #define PIN_SPI_MOSI (5u)
/// #define PIN_SPI_MISO (4u)
/// #define PIN_SPI_SCK (6u)
///
///
/// Acknowledgements:
///
/// RFduinoUBP
///
/// This code uses portions of RFduinoUB, which can be retrieved from
/// https://github.com/cconway/RFduinoUBP under the following license:
/// The MIT License (MIT)
/// Copyright (c) 2015 cconway
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
/// copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
/// The above copyright notice and this permission notice shall be included in all
/// copies or substantial portions of the Software.
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
/// SOFTWARE.
///
/// Solutions Cubed LLC
///
/// The author is grateful to Solutions Cubed LLC, see http://www.solutions-cubed.com,
/// for providing helpful code samples for their BM019 nfc module, portions of which are
/// used within this code.
///
///
/// Choose between Simblee or RFduino hardware
///
/// If you want to switch between RFduino and Simblee or vice versa, be aware of the following tasks:
/// 1. Follow the instructions in the simblee quick start guide (https://www.simblee.com/Simblee_Quickstart_Guide_v1.1.0.pdf)
/// or the RFduino dcoumentation (https://github.com/RFduino/RFduino/blob/master/README.md) on how to install the
/// arduino libraries needed.
/// 2. Set your additional board manager in the Arduino IDE preferences to either Simblee or RFduino.
/// With the Arduino IDE open, click on Arduino > Preferences
/// a) For Simblee: Copy and Paste the following link into Additional Boards Manager
/// URLs: https://www.simblee.com/package_simblee166_index.json then press “OK”
/// b) For RFduino: Add http://rfduino.com/package_rfduino166_index.json to Additional Board Manager URLs and save.
/// 3. Go to Tools > Board: > Boards Manager... and install the corresponding board.
/// 4. Go to Go to Tools > Board: and choose the Simblee or RFduino board.
/// 5. In the arduino IDE goto sketch and incorporate the RFduinoBLE or the SimbleBLE library respectively
/// 6. If you had to install the Simblee or RFduino package be sure to reconfigure the SPI wiring in the
/// variants.h file, as explained in the SPI-WIRING section in the above comments on wiring connections
/// 7. Set the following #define to SIMBLEE for using a Simblee or comment it out to use a RFduino
/// 8. Do the same in the libUPB.cpp in the corresponding library for this project.
///
//#define SIMBLEE // IF a Simblee is used, you have to define SIMBLEE, otherwise it is assumed you use a RFduino.
// Include application, user and local libraries
#ifdef SIMBLEE
#include
#include
#else
#include
#endif
#include // the sensor communicates using SPI, so include the library
// This code uses the SLIP protocol to transer the data via bluetooth, see https://github.com/cconway/RFduinoUBP
#include
#include
#include
#include
#include
// Define variables and constants
/* CR95HF Commands */
#define IDN 0x01 // identification number of CR95HF
#define SELECT_PROTOCOL 0x02 // select protocol
#define POLL 0x03 // poll
#define SENDRECEIVE 0x04 // send and receive data (most commonly used)
#define READ 0x05 // read values from registers internal to CR95HF
#define WRITE 0x06 // write values to registers internal to CR95HF
#define ECHO 0x55
// send receive commands for ISO/IEC 15693 protocol
#define INVENTORY 0x01 // receives information about tags in range
#define STAY_QUIET 0x02 // selected unit will not send back a response
#define READ_BLOCK 0x20 // read single block of memory from RF tag
#define WRITE_BLOCK 0x21 // write single block to memory of RF tag
#define LOCK_BLOCK 0x22 // permanently locks a block of memory on RF tag
#define READ_BLOCKS 0x23 // reads multiple blocks of memory from RF tag
#define WRITE_BLOCKS 0x24 // writes multiple blocks of memory to RF tag
#define SELECT 0x25 // used to select a specific tag for communication via the uid
#define RESET_TO_READY 0x26 // resets RF tag to ready state
#define WRITE_AFI 0x27 // writes application family identifier to RF tag
#define LOCK_AFI 0x28 // permanently locks application family identifier
#define WRITE_DSFID 0x29 // writes data storage format identifier to RF tag
#define LOCK_DSFID 0x2A // permanentlylocks data storage format identifier
#define GET_SYSTEM_INFORMATION 0x2B // gets information from RF tag that includes memory
// block size in bytes and number of memory blocks
#define GET_BLOCKS_SECURITY_STATUS 0x2C
// Request flag with bits set these rules:
// Bit Flag name Description
// 1 Sub-carrier flag 0 ... single sub-carrier used
// 1 ... two sub-carriers used
// 2 Data rate flag 0 ... low data rate is used
// 1 ... high data rate is used
// 3 Inventory flag State determines how bits 5-8 are defined
// 0 ... flag not set
// 1 ... flag set
// 4 Protocol 0: no protocol extension
// extension flag 1: protocol format is extended
// If Inventory flag = 0 (not set):
// 5 Select flag 0: request shall be executed based on setting of the address flag
// 1: request shall be executed only by devices in the selected state
// 6 Address flag 0: request is not addressed
// 1: request is addressed, optional UID field is present
// 7 Option flag Meaning is defined by the command description, if not used set to 0
// 8 reserved
// If Inventory flag = 1 (set):
// 5 AFI flag 0: AFI field is not present
// 1: AFI field is present
// 6 Number of 0: 16 slots
// slots flag 1: 1 slot
// 7 Option flag Meaning is defined by the command description, if not used set to 0
// 8 reserved
//
// Helper bit field by to quickly calculate integers from hex and vice versa:
// Bit no.: 8 7 6 5 4 3 2 1
// value: 128 64 32 16 8 4 2 1
// choose: 0 0 0 0 4 0 2 1 result is 0x03
//
// request flags byte, 0x26 means:
// single sub carrier, high data rate, inventory flag set, no protocoll extentsion, AFI not present, one slot)
// Settings
#define DEBUG
const int SS_PIN = 3; // Slave Select pin, changed to new value on 2016-03-21
const int IRQ_PIN = 2; // IRQ/DIN pin used for wake-up pulse
byte RXBuffer[400]; // receive buffer
byte dataBuffer[400]; // buffer for Freestyle Libre byte data
byte NFCReady = 0; // used to track NFC state
const int SPI_FREQUENCY = 2000; // max. for CR95HF is 2000 = 2 MHz
const uint64_t SLEEP_DURATION = 20000; // Duration of Simblee ultra low power mode in ms
//const uint64_t SLEEP_DURATION = 120000; // Duration of Simblee ultra low power mode in ms
// Code
void setupPins() {
Serial.println("Setting Simblee pins ...");
pinMode(IRQ_PIN, OUTPUT);
pinMode(SS_PIN, OUTPUT);
// Commented out by Uwi on 15.11.2015: was not needed obviously
// digitalWrite(SS_PIN, HIGH);
Serial.println("... done setting Simblee pins.");
}
void setupSPI() {
Serial.println("Setting up SPI ...");
SPI.begin();
SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST);
SPI.setFrequency(SPI_FREQUENCY);
Serial.println("... done setting up SPI.");
}
// --------------------------
void setupBluetoothConnection() {
Serial.println("Setup Bluetooth stack and start connection...");
#ifdef SIMBLEE
SimbleeBLE.deviceName = "LibreCGM";
SimbleeBLE.customUUID = "2220";
SimbleeBLE.advertisementData = "data";
SimbleeBLE.advertisementInterval = MILLISECONDS(500); // Default was 300
SimbleeBLE.txPowerLevel = 4; // Possible values: -20, -16, -12, -8, -4, 0 or +4 (dbM) // up to 20016-05-17 this was 0
// SimbleeBLE.txPowerLevel = -4; // Possible values: -20, -16, -12, -8, -4, 0 or +4 (dbM) // up to 20016-05-17 this was 0
SimbleeBLE.begin(); // Start the BLE stack
#else
RFduinoBLE.deviceName = "LibreCGM";
// RFduinoBLE.customUUID = "2220";
RFduinoBLE.advertisementData = "data";
RFduinoBLE.advertisementInterval = MILLISECONDS(500); // Default was 300
RFduinoBLE.txPowerLevel = 4; // Possible values: -20, -16, -12, -8, -4, 0 or +4 (dbM) // up to 20016-05-17 this was 0
// SimbleeBLE.txPowerLevel = -4; // Possible values: -20, -16, -12, -8, -4, 0 or +4 (dbM) // up to 20016-05-17 this was 0
RFduinoBLE.begin(); // Start the BLE stack
#endif
Serial.println("... done seting up Bluetooth stack and starting connection.");
}
// Add setup code
void setup() {
Serial.begin(9600);
setupPins(); // set RFduino/Simblee pins
setupSPI();
Serial.println("Please provide power to the BM019 within the next 5 seconds ...");
delay(5000);
sendWakeupPulse(IRQ_PIN); // wake up BM019 and set to SPI (since SS_0 is wired up to be HIGH)
readWakeUPEventRegister(SS_PIN, RXBuffer);
setupBluetoothConnection();
}
// SetProtocol_Command programs the CR95HF for ISO/IEC 15693 operation.
// If the correct response is received the serial monitor is used to display successful programming.
// Warning: if the parameters of the protocol are changed, e.g. sub carrier, then the request flags
// of the other commands (e.g. inventory, read single block) have to be changed accordingly.
void SetProtocol_Command() {
// step 1 send the command
digitalWrite(SS_PIN, LOW);
SPI.transfer(0x00); // SPI control byte to send command to CR95HF
SPI.transfer(0x02); // Set protocol command
SPI.transfer(0x02); // length of data to follow
SPI.transfer(0x01); // code for ISO/IEC 15693
SPI.transfer(0x0F); // Up till 2016-06-13: crc16, single, 30%, wait for SOF (wrong: Wait for SOF, 100% modulation, append CRC)
// SPI.transfer(0x0D); // Up till 2016-06-13: crc16, single, 30%, wait for SOF (wrong: Wait for SOF, 100% modulation, append CRC)
digitalWrite(SS_PIN, HIGH);
delay(1);
// step 2, poll for data ready
pollSPIUntilResponsIsReady(SS_PIN, RXBuffer);
// step 3, read the data
receiveSPIResponse(SS_PIN, RXBuffer);
#ifdef DEBUG
Serial.println("RXBuffer is");
for (byte i = 0; i < 2; i++) {
Serial.print(RXBuffer[i], HEX);
Serial.print(" ");
}
#endif
if ((RXBuffer[0] == 0) & (RXBuffer[1] == 0)) {
Serial.println("PROTOCOL SET-"); //
NFCReady = 1; // NFC is ready
} else {
Serial.println("BAD RESPONSE TO SET PROTOCOL");
NFCReady = 0; // NFC not ready
}
Serial.println(" ");
}
// Reads the single block of 8 bytes with number blockNum from the BM019.
// The BM019 has 244 Blocks of 8 bytes that can be read via ISO 15693 commands. The blocks are numbered 0 to 243.
// Returns the result code of the command response (that indicates wether there was success or an error)
byte ReadSingleBlockReturn(int blockNum) {
RXBuffer[0] = SENDRECEIVE; // command code for send receive CR95HF command
RXBuffer[1] = 0x03; // length of data that follows (3 bytes)
// RXBuffer[2] = 0x02; // request Flags byte, single carrier, high data rate
RXBuffer[2] = 0x03; // request Flags byte dual carrier, high data rate
RXBuffer[3] = 0x20; // read single block Command for ISO/IEC 15693
RXBuffer[4] = blockNum; // Block number
// step 1 send the command
sendSPICommand(SS_PIN, RXBuffer, 5);
// step 2, poll for data ready
pollSPIUntilResponsIsReady(SS_PIN, RXBuffer);
// step 3, read the data
receiveSPIResponse(SS_PIN, RXBuffer);
delay(1);
#ifdef DEBUG
// Print to Serial
if (RXBuffer[0] == 128) {
Serial.printf("The block #%d:", blockNum);
for (byte i = 3; i < RXBuffer[1] + 3 - 4; i++) {
Serial.print(RXBuffer[i], HEX);
Serial.print(" ");
}
} else {
Serial.print("NO Single block available - ");
Serial.print("RESPONSE CODE: ");
Serial.println(RXBuffer[0], HEX);
}
Serial.println(" ");
#endif
return RXBuffer[0]; // result code of command response
}
/// @brief Sends command to BM019 and receives response serveral times until response with no error, max maxTrial times.
/// @detail After maxTrials trials the last response is returned, even if there is still an error.
/// @detail Warning: Ensure that RXBuffer has appropriate size.
/// @param ssPin slave select pin for SPI digital write
/// @param RXBuffer buffer used for command and response
/// @param maxTrials max number of trials
void runSPICommandUntilNoError(int ssPin, byte *command, int length, byte *RXBuffer, int maxTrials) {
int count = 0;
bool success;
do {
delay(1);
#ifdef DEBUG
Serial.printf("Before: Count: %d, success: %b, RXBuffer[0]: %x \r\n", count, success, RXBuffer[0]);
#endif
count++;
// clear RXBuffer with zeros
memset(RXBuffer, 0, sizeof(RXBuffer));
// run SPI command
sendPollReceiveSPINew(ssPin, command, sizeof(command), RXBuffer);
success = responseHasNoError(RXBuffer);
#ifdef DEBUG
Serial.printf("After: Count: %d, success: %b, RXBuffer[0]: %x \r\n", count, success, RXBuffer[0]);
#endif
} while ( !success && (count < maxTrials));
delay(1);
#ifdef DEBUG
Serial.printf("Exiting at count: %d, RXBuffer[0]: %x \r\n", count, RXBuffer[0]);
#endif
}
/// @brief Sends IDN command to BM019 and receives response serveral times until response with no error, max maxTrial times.
/// @detail After maxTrials trials the last response is returned, even if there is still an error.
/// @detail Warning: Ensure that RXBuffer has appropriate size.
/// @param ssPin slave select pin for SPI digital write
/// @param RXBuffer buffer used for command and response
/// @param maxTrials max number of trials
void runIDNCommandUntilNoError(int ssPin, byte *command, int length, byte *RXBuffer, int maxTrials) {
int count = 0;
bool success;
do {
#ifdef DEBUG
Serial.printf("Before: Count: %d, success: %b, RXBuffer[0]: %x \r\n", count, success, RXBuffer[0]);
#endif
count++;
// clear RXBuffer with zeros
memset(RXBuffer, 0, sizeof(RXBuffer));
// run SPI command
sendPollReceiveSPINew(ssPin, command, sizeof(command), RXBuffer);
success = idnResponseHasNoError(RXBuffer);
#ifdef DEBUG
Serial.printf("After: Count: %d, success: %b, RXBuffer[0]: %x \r\n", count, success, RXBuffer[0]);
#endif
} while ( !success && (count < maxTrials));
delay(10);
#ifdef DEBUG
Serial.printf("Exiting at count: %d, RXBuffer[0]: %x \r\n", count, RXBuffer[0]);
#endif
}
/// Runs the system information command several times, i.e. maxTrials times or until a response with no error is gotten, whatever happens first.
/// @param ssPin SS_PIN used for SPI
/// @param RXBuffer buffer used for send and receive
/// @param maxTrials maximum number of trials. If reached, the result is returned, even if there still is an error present
/// @brief Get system information from BM019
/// @details The response is read into RXBuffer and that's it, no matter if there is an error or not.
/// @n Command format for CR95HF:
/// @n 8 bits for request flags,
/// @n 8 bits for the get system information command,i.e 0x2B,
/// @n 64 bits for an optional UID (not used in non-addressed modes).
/// @details Example get system info command (CR95HF command embedded in BM019 command):
/// @details 0x04 ... BM019 send/receive command code
/// @details 0x02 ... BM019 length of CR95HF command data that follows
/// @details 0x02 ... CR95HF request flags byte (high data rate used)
/// @details 0x2B ... CR95HF get system information command
/// @n
/// @n
/// @details Example CR95HF response with no error:
/// @details 0x80 ... result code
/// @details 0x12 ... length of following data (12 bytes)
/// @details 0x00 ... response flags
/// @details 0x0F ... info flags
/// @details 0x69 0x55 0x19 0x38 0x42 0x20 0x02 0xE0 ... data: UID
/// @details 0x00 ... DSFID (supported and field is present in response if bit 1 of info flags is set)
/// @details 0x00 ... AFI (supported and field is present in response if bit 2 of info flags is set)
/// @details 0x3F 0X03 ... memory size (supported and field is present in response if bit 3 of info flags is set)
/// @details 0x20 ... IC Ref (supported and field is present in response if bit 4 of info flags is set)
/// @details 0xB4 0xA9 ... CRC16
/// @details 0x00 ... error
/// @n
/// @details Example CR95HF response with error (bit 0 of response flag is set)
/// @details 0x01 ... response flags
/// @details 0x01 ... error code (returned when error bit is set)
/// @details 0xB4 0xA9 ... CRC16
/// @details 0X01 ... (error CRC16 or collision error bits)
void runSystemInformationCommandUntilNoError(int ssPin, byte *RXBuffer, int maxTrials) {
#ifdef DEBUG
Serial.printf("ssPin: %d, maxTrials: %d, RXBuffer[0]: %x \r\n", ssPin, maxTrials, RXBuffer[0]);
#endif
byte command[4];
command[0] = 0x04; // command code for send receive CR95HF command
command[1] = 0x02; // length of data that follows (3 bytes)
command[2] = 0x03; // request Flags byte, dual sub carrier
// command[2] = 0x02; // request Flags byte, single sub carrier
command[3] = 0x2B; // get system information command for ISO/IEC 15693
delay(10);
#ifdef DEBUG
Serial.printf("ssPin: %d, maxTrials: %d, RXBuffer[0]: %x \r\n", ssPin, maxTrials, RXBuffer[0]);
#endif
// run command until no error, but only max 10 times
runSPICommandUntilNoError(ssPin, command, sizeof(command), RXBuffer, maxTrials);
}
/// Runs the inventory command several times, i.e. maxTrials times or until a response with no error is gotten, whatever happens first.
/// @param ssPin SS_PIN used for SPI
/// @param RXBuffer buffer used for send and receive
/// @param maxTrials maximum number of trials. If reached, the result is returned, even if there still is an error present
void runIDNCommand(int ssPin, byte *RXBuffer, int maxTrials) {
#ifdef DEBUG
Serial.printf("ssPin: %d, maxTrials: %d, RXBuffer[0]: %x \r\n", ssPin, maxTrials, RXBuffer[0]);
#endif
byte command[2];
command[0] = 0x01; // command code for send receive CR95HF command
command[1] = 0x00; // length of data that follows (0 bytes)
delay(10);
#ifdef DEBUG
Serial.printf("ssPin: %d, maxTrials: %d, RXBuffer[0]: %x \r\n", ssPin, maxTrials, RXBuffer[0]);
#endif
// run command until no error, but only max 10 times
runIDNCommandUntilNoError(ssPin, command, sizeof(command), RXBuffer, maxTrials);
}
//Example response of CR95HF for inventory command and data positions/indices
//+------+--------+----------------------------------------------------------------------+
//|Result| Length | Data |
//| code | +--------+-----+---------------------------------------+---------+-----+
//| | |Response| | | | |
//| | | flags |DSFID| UID |CRC16 |Error|
//+------+--------+--------+---------------------------------------------+---------+-----+
//| 0x80 |0x0D(13)| 0x00 |0x00 |0x51 0x69 0x19 0x38 0x42 0x20 0x02 0xE0|0x84 0x28|0x00 |
//+------+--------+--------+-----+---------------------------------------+---------+-----+
//| 0 | 1 | 2 | 3 | 4 5 6 7 8 9 10 11 | 12 13 | 14 |
//+------+--------+--------+-----+---------------------------------------+---------+-----+
// 0 1 2 3 4 5 6 7 8 9 10 11 12
//
/// Retreive idn data from RXBuffer from IDN command
/// This ist the struct later to be transmitted via bluetooth
/// @detail There is no error possible in the response since this is just pure device information and thus not rely on RFID and a tag in the field
/// @param RXBuffer buffer containing the response from the IDN command
/// @param idnType struct with retreived data
IDNDataType idnDataFromIDNResponse(byte *RXBuffer) {
IDNDataType idnData;
idnData.resultCode = RXBuffer[0];
// Device ID has 13 bytes
for (int i = 0; i < 13; i++) {
idnData.deviceID[i] = RXBuffer[i + 2]; // TODO: cchek and continue here
}
// ROM CRC are the last two bytes of the data
int length = RXBuffer[2];
idnData.romCRC[0] = RXBuffer[length - 2];
idnData.romCRC[1] = RXBuffer[length - 1];
return idnData;
}
/// Retreive system information from RXBuffer from system information command
/// This ist the struct later to be transmitted via bluetooth
/// @param RXBuffer buffer containing the response from the system information command
/// @param SystemInformationType struct with retreived data
SystemInformationDataType systemInformationDataFromGetSystemInformationResponse(byte *RXBuffer) {
// SystemInformationType retreiveSystemInformationValues(byte *RXBuffer) {
SystemInformationDataType systemInformationData;
systemInformationData.resultCode = RXBuffer[0];
systemInformationData.responseFlags = RXBuffer[2];
// check for no error in result code and handle accordingly
if (systemInformationData.resultCode == 0x80) { // no error in result code
// check for no error in response flags
if ((systemInformationData.responseFlags & 0x01) == 0) {
// no error in response flags
systemInformationData.infoFlags = RXBuffer[3];
for (int i = 0; i < 8; i++) {
systemInformationData.uid[i] = RXBuffer[11 - i];
}
systemInformationData.errorCode = RXBuffer[RXBuffer[1] + 2 - 1];
} else {
// error case
systemInformationData.errorCode = RXBuffer[3];
}
} else {
// error case
clearBuffer(systemInformationData.uid);
systemInformationData.errorCode = RXBuffer[3];
}
return systemInformationData;
}
/// Sends a packet of data (c-struct) via bluetooth to a smartphone application
/// @detail Any packet to be transfered is a c-struct. These structs are defined in data_types.h.
/// @param packetIdentifier identifier that can be used to treat a packet separately in the app. Identifiers are defined in constants.h
/// @param txFlags don't know yet, what these are fore
/// @param *packetBytes pointer on the c-struct, that is the packet
/// @param byteCount number of bytes to be transfered
bool pumpViaBluetooth(unsigned short packetIdentifier, UBP_TxFlags txFlags, const char *packetBytes, unsigned short byteCount) {
bool success = UBP_queuePacketTransmission(packetIdentifier, txFlags, packetBytes, byteCount);
delay(1);
#ifdef DEBUG
if (success) Serial.println("Packet queued successfully");
else Serial.println("Failed to enqueue packet");
#endif
// put your main code here, to run repeatedly:
while (UBP_isBusy() == true) UBP_pump();
}
/// Returns the voltage on the Simblee/RFduino VDD pin as a float in Volts.
/// Code is from the RFduino Forum, see http://forum.rfduino.com/index.php?topic=265.0 for details
float voltageOnVDD() {
analogReference(VBG); // Sets the Reference to 1.2V band gap
analogSelection(VDD_1_3_PS); // Selects VDD with 1/3 prescaling as the analog source
int sensorValue = analogRead(1); // the pin has no meaning, it uses VDD pin
return sensorValue * (3.6 / 1023.0); // convert value to voltage;
}
/// Print all data of systemInformationData to serial console
void printSystemInformationData(SystemInformationDataType systemInformationData) {
Serial.println("Printing system information data to serial output:");
Serial.printf("Result code: %x\r\n", systemInformationData.resultCode);
Serial.printf("Response flags: %x\r\n", systemInformationData.responseFlags);
Serial.printf("uid: %x", systemInformationData.uid[0]);
for (int i = 1; i < 8; i++) {
Serial.printf(":%x", systemInformationData.uid[i]);
}
Serial.println("");
Serial.printf("Error code: %x\r\n", systemInformationData.errorCode);
}
///===================================================================================================
// The loop
///===================================================================================================
void loop() {
#ifdef DEBUG
Serial.println("In the loop");
#endif
// Start up BM019 if not yet started
if (NFCReady == 0) {
delay(100);
SetProtocol_Command(); // ISO 15693 settings
Serial.println("After SetProtocoll_Command()");
delay(100);
} else {
// ------- Read system information from BM019 ----------------------------------------------------------------------------
//#ifdef DEBUG
Serial.println("Get system information command ...");
//#endif
runSystemInformationCommandUntilNoError(SS_PIN, RXBuffer, 10);
#ifdef DEBUG
Serial.println("... retreive system information ...");
#endif
SystemInformationDataType systemInformationData = systemInformationDataFromGetSystemInformationResponse(RXBuffer);
//------- Read IDN information from BM019 (IDN Command) -------------------------------------------------------------------
//#ifdef DEBUG
Serial.println("IDN command ...");
//#endif
runIDNCommand(SS_PIN, RXBuffer, 10);
#ifdef DEBUG
Serial.println("... retreive IDN information ...");
#endif
IDNDataType idnData = idnDataFromIDNResponse(RXBuffer);
// ----------- Read 43 data blocks into RXBuffer ---------------
//#ifdef DEBUG
Serial.println("Read all data");
//#endif
for (int i = 0; i < sizeof(RXBuffer); i++) {
RXBuffer[i] = 0;
}
for (int i = 0; i < sizeof(dataBuffer); i++) {
dataBuffer[i] = 0;
}
byte resultCode = 0;
int trials = 0;
int maxTrials = 10;
for (int i = 0; i < 43; i++) { // Need only 43 of 244 blocks
resultCode = ReadSingleBlockReturn(i);
#ifdef DEBUG
printf("resultCode 0x%x\n\r", resultCode);
#endif
if (resultCode != 0x80 && trials < maxTrials) {
printf("Error 0x%x\n\r", resultCode);
i--; // repeat same block if error occured, but
trials++; // not more than maxTrials times per block
} else if (trials >= maxTrials) {
break;
} else {
trials = 0;
for (int j = 3; j < RXBuffer[1] + 3 - 4; j++) {
dataBuffer[i * 8 + j - 3] = RXBuffer[j];
#ifdef DEBUG
Serial.print(RXBuffer[j], HEX);
Serial.print(" ");
#endif
}
}
}
// ----------- All data collected, send BM019 to hibernate -------------------------
//#ifdef DEBUG
Serial.println("Sending CR95HF to hibernate ...");
//#endif
sendCR95HFToHibernate(SS_PIN);
// ------- Transmit system information data via bluetooth. By convention this is the first transmission -----------------
bool ergo = pumpViaBluetooth(SYSTEM_INFORMATION_DATA, UBP_TxFlagIsRPC, (char *) &systemInformationData, sizeof(SystemInformationDataType));
#ifdef DEBUG
printSystemInformationData(systemInformationData);
#endif
//-------- transmit dataBuffer via bluetooth ---------------------------------------------------------
AllBytesDataType allBytes;
#ifdef DEBUG
Serial.println("----about to send all data bytes packet");
#endif
for (int i = 0; i < sizeof(allBytes.allBytes); i++) {
allBytes.allBytes[i] = 0;
}
for (int i = 0; i < 344; i++) {
allBytes.allBytes[i] = dataBuffer[i];
}
//#ifdef DEBUG
Serial.printf("Sizeof ist : %d\n", sizeof(AllBytesDataType));
//#endif
bool success = UBP_queuePacketTransmission(ALL_BYTES, UBP_TxFlagIsRPC, (char *) &allBytes, sizeof(AllBytesDataType));
#ifdef DEBUG
if (success) Serial.println("----all data bytes packet queued successfully");
else Serial.println("----Failed to enqueue all data bytes packet");
#endif
while (UBP_isBusy() == true) UBP_pump();
//-------- Read Battery level and Simblee temperature and transmit via bluetooth ---------------------
BatteryDataType batteryData;
batteryData.voltage = voltageOnVDD();
#ifdef SIMBLEE
batteryData.temperature = Simblee_temperature(CELSIUS);
#else
batteryData.temperature = RFduino_temperature(CELSIUS);
#endif
#ifdef DEBUG
Serial.printf("Battery voltage: %f\r\n", batteryData.voltage);
#endif
// Transmitt via bluetooth
success = UBP_queuePacketTransmission(BATTERY_DATA, UBP_TxFlagIsRPC, (char *) &batteryData, sizeof(BatteryDataType));
// if (success) Serial.println("Battery data packet queued successfully");
// else Serial.println("Failed to enqueue battery data packet");
// put your main code here, to run repeatedly:
while (UBP_isBusy() == true) UBP_pump();
#ifdef DEBUG
Serial.printf("Sent Battery voltage: %f\r\n", batteryData.voltage);
#endif
//-------- transmit IDN data via bluetooth. By convention this is the last transmission -----------------
ergo = pumpViaBluetooth(IDN_DATA, UBP_TxFlagNone, (char *) &idnData, sizeof(IDNDataType));
success = UBP_queuePacketTransmission(IDN_DATA, UBP_TxFlagIsRPC, (char *) &idnData, sizeof(IDNDataType));
delay(10);
#ifdef DEBUG
if (success) Serial.println("IDN data packet queued successfully");
else Serial.println("Failed to enqueue IDN data packet");
#endif
// put your main code here, to run repeatedly:
while (UBP_isBusy() == true) UBP_pump();
#ifdef DEBUG
Serial.printf("IDN: %x", idnData.deviceID[0]);
for (int i = 1; i < 13; i++) {
Serial.printf(":%x", idnData.deviceID[i]);
}
Serial.println("... done");
#endif
//--------- send Simblee into ultra low power mode ---------------------------------------------------
//#ifdef DEBUG
Serial.println("Sending Simblee/RFduino to sleep ...");
//#endif
RFduino_ULPDelay(SLEEP_DURATION); //
//--------- send Simblee woke up again, now also wake up BM019 and repeat the loope cycle ------------
#ifdef DEBUG
Serial.println("... Simblee/RFduino woke up again");
Serial.println("Wake up CR95HF with wake up pulse...");
#endif
sendWakeupPulse(IRQ_PIN); // Wake up BM019 (low pulse on IRQ_PIN)
readWakeUPEventRegister(SS_PIN, RXBuffer);
setupSPI();
delay(10);
SetProtocol_Command(); // ISO 15693 settings
delay(100);
#ifdef DEBUG
Serial.println("... CR95HF woke up again. Receiving wake up response");
#endif
}
}
================================================
FILE: LibreMonitorTests/BluetoothTestData.swift
================================================
//
// BluetoothTestData.swift
// LibreMonitor
//
// Created by Uwe Petersen on 29.01.17.
// Copyright © 2017 Uwe PetersenaString.append("All rights reserved.
//
// To test is weather these bytes are transferred and restored without any error.
// Thus check, if after all transferal actions the result is the same
import Foundation
struct BluetoothTestData {
var sensorData = [[String]]()
init() {
}
static func data() -> [String] {
var sensorData = [String]()
// # ID: E0:07:A0:00:00:25:90:5E
// # Memory content: Sensor 1
var aString = String()
aString.append("73 C3 18 59 05 00 03 39") // 0x00
aString.append("51 04 09 54 00 00 00 00") // 0x01
aString.append("00 00 00 00 00 00 00 00") // 0x02
aString.append("00 82 08 0A 11 00 C0 4A") // 0x03
aString.append("65 00 0F 00 C0 46 25 80") // 0x04
aString.append("0E 00 C0 4A 25 80 10 00") // 0x05
aString.append("C0 4E 25 80 11 00 C0 4E") // 0x06
aString.append("25 80 10 00 C0 52 65 00") // 0x07
aString.append("0E 00 C0 52 25 80 10 00") // 0x08
aString.append("C0 52 25 80 10 00 C0 3E") // 0x09
aString.append("25 80 11 00 C0 42 25 80") // 0x0a
aString.append("11 00 C0 42 25 80 11 00") // 0x0b
aString.append("C0 42 25 80 11 00 C0 46") // 0x0c
aString.append("25 80 11 00 C0 46 25 80") // 0x0d
aString.append("11 00 C0 46 65 00 10 00") // 0x0e
aString.append("C0 4A 65 00 00 00 C0 B6") // 0x0f
aString.append("64 00 00 00 C0 CE 64 00") // 0x10
aString.append("00 00 C0 E6 64 00 00 00") // 0x11
aString.append("C0 FA 64 00 00 00 C0 0A") // 0x12
aString.append("65 00 00 00 C0 12 65 00") // 0x13
aString.append("00 00 C0 22 65 00 00 00") // 0x14
aString.append("C0 2E 65 00 00 00 C0 3E") // 0x15
aString.append("25 80 00 00 C0 52 65 00") // 0x16
aString.append("00 00 C0 6E 26 80 00 00") // 0x17
aString.append("C0 66 26 80 00 00 C0 5A") // 0x18
aString.append("26 80 00 00 C0 52 26 80") // 0x19
aString.append("00 00 C0 4A 26 80 00 00") // 0x1a
aString.append("C0 42 26 80 00 00 C0 3E") // 0x1b
aString.append("26 80 00 00 C0 36 26 80") // 0x1c
aString.append("00 00 C0 2E 26 80 00 00") // 0x1d
aString.append("C0 2E 26 80 00 00 C0 26") // 0x1e
aString.append("26 80 00 00 C0 22 26 80") // 0x1f
aString.append("00 00 C0 22 26 80 00 00") // 0x20
aString.append("C0 22 26 80 00 00 C0 1E") // 0x21
aString.append("26 80 00 00 C0 22 26 80") // 0x22
aString.append("00 00 C0 22 26 80 00 00") // 0x23
aString.append("C0 22 26 80 00 00 C0 72") // 0x24
aString.append("24 80 00 00 C0 02 24 80") // 0x25
aString.append("00 00 C0 5E 24 80 00 00") // 0x26
aString.append("C0 96 64 00 09 54 00 00") // 0x27
aString.append("D4 AE 00 01 15 04 39 51") //
aString.append("14 07 96 80 5A 00 ED A6") //
aString.append("0E 90 1A C8 04 D7 C8 69") //
/*
aString.append("9E 42 21 83 F2 90 07 00") //
aString.append("06 08 02 24 0C 43 17 3C") //
aString.append("C2 43 08 08 B2 40 DF 00") //
aString.append("08 08 D2 42 A2 F9 08 08") //
aString.append("D2 42 A3 F9 08 08 0C 41") //
aString.append("0C 53 92 12 90 1C 5C 93") //
aString.append("03 20 A2 41 08 08 02 3C") //
aString.append("B2 43 08 08 1C 43 21 53") //
aString.append("30 41 0A 12 4A 4C 4C 93") //
aString.append("0B 20 B2 40 50 CC 02 07") //
aString.append("92 D3 00 07 B2 C0 00 02") //
aString.append("00 07 A2 D2 00 07 02 3C") //
aString.append("92 12 82 1C 32 D0 D8 00") //
aString.append("E2 B3 C3 1C 09 28 E2 C3") //
aString.append("C3 1C 4A 93 05 24 12 C3") //
aString.append("12 10 A4 1C 12 11 A4 1C") //
aString.append("3A 41 30 41 0A 12 0B 12") //
aString.append("08 12 09 12 06 12 F2 90") //
aString.append("07 00 06 08 68 20 B0 12") //
aString.append("3A FB 61 20 92 12 78 1C") //
aString.append("3A 40 FA F9 4C 43 8A 12") //
aString.append("3B 40 84 1C 26 4B 38 40") //
aString.append("B0 F9 39 40 A4 1C B2 90") //
aString.append("00 20 A4 1C 09 2C 1F 43") //
aString.append("B0 12 30 FB 3F 40 00 20") //
aString.append("2F 89 82 4F A4 1C 06 3C") //
aString.append("0F 43 B0 12 30 FB B2 50") //
aString.append("00 E0 A4 1C 92 52 A4 1C") //
aString.append("A4 1C 2F 49 7E 42 0D 43") //
aString.append("0C 48 AB 12 1F 43 5E 43") //
aString.append("3D 40 22 00 0C 48 AB 12") //
aString.append("B2 90 00 01 A4 1C 08 28") //
aString.append("7F 43 7E 42 0D 43 0C 48") //
aString.append("AB 12 7C 40 34 00 29 3C") //
aString.append("6C 42 8A 12 2F 49 3F F0") //
aString.append("FF 07 82 4F A6 1C 1F 42") //
aString.append("A6 1C 7E 40 0B 00 3D 40") //
aString.append("16 00 0C 48 AB 12 7C 40") //
aString.append("05 00 8A 12 2F 49 7E 40") //
aString.append("0C 00 3D 40 28 00 0C 48") //
aString.append("AB 12 7C 40 06 00 8A 12") //
aString.append("2F 49 7E 40 0C 00 3D 40") //
aString.append("34 00 0C 48 AB 12 B2 B0") //
aString.append("10 00 22 01 06 2C 7C 40") //
aString.append("28 00 92 12 8C 1C 0C 43") //
aString.append("05 3C E2 C2 C3 1C 92 12") //
aString.append("94 1C 1C 43 30 40 6C 5F") //
aString.append("5E 43 3D 40 21 00 0C 48") //
aString.append("00 46 C2 43 08 08 E2 D2") //
aString.append("C3 1C 92 12 98 1C 4C 93") //
aString.append("30 41 F2 90 07 00 06 08") //
aString.append("02 24 0C 43 30 41 B0 12") //
aString.append("3A FB 08 24 A2 43 DC F8") //
aString.append("7C 40 28 00 92 12 8C 1C") //
aString.append("0C 43 30 41 92 12 78 1C") //
aString.append("92 D3 00 07 B2 40 4B D8") //
aString.append("02 07 B2 C0 00 02 00 07") //
aString.append("A2 D2 00 07 92 43 DC F8") //
aString.append("32 D0 D8 00 E2 B3 C3 1C") //
aString.append("05 28 E2 C3 C3 1C 92 42") //
aString.append("A4 1C DE F8 5C 43 92 12") //
aString.append("86 1C E2 C2 C3 1C 92 12") //
aString.append("94 1C 1C 43 30 41 F2 90") //
aString.append("07 00 06 08 02 24 0C 43") //
aString.append("30 41 C2 43 08 08 E2 D2") //
aString.append("C3 1C 92 12 72 1C 1C 43") //
aString.append("30 41 0A 12 92 12 20 1C") //
aString.append("4C 93 14 24 F2 90 05 00") //
aString.append("0C 08 10 20 1D 42 06 08") //
aString.append("5F 42 06 08 0D 93 07 20") //
aString.append("7F 93 05 20 92 12 94 1C") //
aString.append("C2 43 08 08 12 3C 7F 90") //
aString.append("10 00 02 28 0C 43 0E 3C") //
aString.append("C2 43 08 08 0C 43 07 3C") //
aString.append("0E 4C 0E 5E 0A 4D 0A 5E") //
aString.append("A2 4A 08 08 1C 53 0C 9F") //
aString.append("F7 2B 1C 43 3A 41 30 41") //
aString.append("0A 12 4A 4C B0 12 C8 5B") //
aString.append("6A 92 10 20 7E 40 0B 00") //
aString.append("3D 40 16 00 3C 40 B0 F9") //
aString.append("92 12 4C 1C 82 4C A6 1C") //
aString.append("92 52 A6 1C A6 1C 92 52") //
aString.append("A6 1C A6 1C 3A 41 30 41") //
aString.append("0E 43 1C 42 B6 F9 B0 12") //
aString.append("DE 5E 1D 42 AA 1C 12 C3") //
aString.append("0D 10 0D 11 0C 9D 02 2C") //
aString.append("0D 8C 04 3C 0C 8D 0D 4C") //
aString.append("3E 40 00 02 3D 90 00 02") //
aString.append("02 28 3D 40 FF 01 5F 42") //
aString.append("A8 F9 0F 9D 06 2C B2 D0") //
aString.append("40 00 AE 1C F2 D0 40 00") //
aString.append("C2 1C 0D DE 82 4D AA 1C") //
aString.append("30 41 0A 12 0B 12 08 12") //
aString.append("09 12 5B 42 A9 F9 48 43") //
aString.append("C2 93 64 F8 09 34 F2 C0") //
aString.append("80 00 64 F8 6B B2 01 28") //
aString.append("58 43 4C 43 92 12 86 1C") //
aString.append("59 42 64 F8 0A 3C 6A 93") //
aString.append("04 20 B2 B0 00 02 00 08") //
aString.append("04 28 7C 40 06 00 92 12") //
aString.append("88 1C 1A 42 9E 01 4A 93") //
aString.append("1F 24 4E 49 6E 83 7E 90") //
aString.append("03 00 F7 2F 7A 90 10 00") //
aString.append("02 20 58 B3 12 2C 4C 4A") //
aString.append("7C 50 11 00 92 12 60 1C") //
aString.append("5B B3 03 28 7A 90 06 00") //
aString.append("E8 27 6B B3 03 28 7A 90") //
aString.append("0E 00 E3 27 7A 90 10 00") //
aString.append("D6 23 58 B3 DE 2F D9 3F") //
aString.append("5E 42 64 F8 6E 83 7E 90") //
aString.append("03 00 25 2C 92 12 96 1C") //
aString.append("92 12 9A 1C 00 3C 3F 40") //
aString.append("33 05 3F 53 FE 2F A2 B3") //
aString.append("22 01 15 28 3F 40 4E C3") //
aString.append("03 43 0B 43 3F 53 3B 63") //
aString.append("FD 2F B2 B0 10 00 22 01") //
aString.append("07 28 B2 B0 00 02 00 08") //
aString.append("03 2C 92 12 70 1C 07 3C") //
aString.append("7C 40 0A 00 02 3C 7C 40") //
aString.append("0B 00 92 12 8C 1C 92 12") //
aString.append("58 1C A2 D2 00 08 32 D2") //
aString.append("30 40 6E 5F 30 41 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("FF FF 84 FD 25 00 9A FC") //
aString.append("14 00 50 FC 19 00 20 FC") //
aString.append("03 00 5F F5 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 AB AB 4A FB") //
aString.append("E2 00 3C FA E1 00 AE FB") //
aString.append("AB AB 2C 5A A4 00 CA FB") //
aString.append("A3 00 56 5A A2 00 BA F9") //
aString.append("A1 00 24 57 A0 00 AB AB") //
aString.append("00 00 00 00 FF FF FF FF") //
aString.append("20 00 71 62 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 AE 5C 00 00 A8 57") //
aString.append("00 00 28 4E 68 45 00 00") //
aString.append("DC 5F AE 5A 7A 5A DA 50") //
*/
sensorData.append(aString)
// # ID: E0:07:A0:00:00:43:98:59
// # Memory content: Sensor 2
aString = String()
aString.append("E6 84 70 50 05 00 03 D9") // 0x00
aString.append("50 04 A9 53 00 00 00 00") // 0x01
aString.append("00 00 00 00 00 00 00 00") // 0x02
aString.append("E3 2F 08 03 DA 01 C8 D0") // 0x03
aString.append("D7 80 D6 01 C8 C4 D7 80") // 0x04
aString.append("D0 01 C8 B8 17 81 CE 01") // 0x05
aString.append("C8 B0 D7 80 C9 01 C8 A8") // 0x06
aString.append("D7 80 C2 01 C8 A8 D7 80") // 0x07
aString.append("BF 01 C8 A8 D7 80 BB 01") // 0x08
aString.append("C8 AC D7 80 0D 02 C8 38") // 0x09
aString.append("D8 80 03 02 C8 30 D8 80") // 0x0a
aString.append("FE 01 C8 2C 18 81 FA 01") // 0x0b
aString.append("88 1A D8 80 F0 01 C8 FC") // 0x0c
aString.append("17 81 E9 01 C8 EC 17 81") // 0x0d
aString.append("E5 01 C8 DC D7 80 DE 01") // 0x0e
aString.append("C8 D8 D7 80 DA 02 C8 04") // 0x0f
aString.append("18 81 61 02 C8 18 18 81") // 0x10
aString.append("EF 01 C8 FC 17 81 72 01") // 0x11
aString.append("C8 F4 D7 80 85 01 C8 D8") // 0x12
aString.append("17 81 89 01 C8 B8 D7 80") // 0x13
aString.append("92 01 C8 EC D7 80 AA 01") // 0x14
aString.append("C8 DC D7 80 CB 01 C8 DC") // 0x15
aString.append("17 81 DE 01 C8 F4 D7 80") // 0x16
aString.append("04 02 C8 D0 D7 80 08 02") // 0x17
aString.append("C8 18 D8 80 10 02 C8 C8") // 0x18
aString.append("D7 80 25 02 C8 EC D7 80") // 0x19
aString.append("02 03 C8 B4 D7 80 97 03") // 0x1a
aString.append("C8 AC D7 80 0C 04 C8 AC") // 0x1b
aString.append("D7 80 3D 04 C8 84 D7 80") // 0x1c
aString.append("8B 04 C8 94 D7 80 A2 04") // 0x1d
aString.append("C8 A0 D7 80 7F 04 C8 CC") // 0x1e
aString.append("17 81 73 04 C8 A4 17 81") // 0x1f
aString.append("6B 04 C8 9C D7 80 5E 04") // 0x20
aString.append("C8 70 D7 80 47 04 C8 4C") // 0x21
aString.append("D7 80 50 04 C8 DC 17 81") // 0x22
aString.append("36 04 C8 08 D8 80 49 04") // 0x23
aString.append("C8 FC 17 81 64 04 C8 18") // 0x24
aString.append("18 81 5F 04 88 FA 17 81") // 0x25
aString.append("D5 03 C8 04 18 81 54 03") // 0x26
aString.append("C8 04 D8 80 A9 53 00 00") // 0x27
aString.append("66 0F 00 01 BE 04 D9 50") //
aString.append("14 07 96 80 5A 00 ED A6") //
aString.append("12 78 1A C8 04 95 09 6C") //
/*
aString.append("9E 42 21 83 F2 90 07 00") //
aString.append("06 08 02 24 0C 43 17 3C") //
aString.append("C2 43 08 08 B2 40 DF 00") //
aString.append("08 08 D2 42 A2 F9 08 08") //
aString.append("D2 42 A3 F9 08 08 0C 41") //
aString.append("0C 53 92 12 90 1C 5C 93") //
aString.append("03 20 A2 41 08 08 02 3C") //
aString.append("B2 43 08 08 1C 43 21 53") //
aString.append("30 41 0A 12 4A 4C 4C 93") //
aString.append("0B 20 B2 40 50 CC 02 07") //
aString.append("92 D3 00 07 B2 C0 00 02") //
aString.append("00 07 A2 D2 00 07 02 3C") //
aString.append("92 12 82 1C 32 D0 D8 00") //
aString.append("E2 B3 C3 1C 09 28 E2 C3") //
aString.append("C3 1C 4A 93 05 24 12 C3") //
aString.append("12 10 A4 1C 12 11 A4 1C") //
aString.append("3A 41 30 41 0A 12 0B 12") //
aString.append("08 12 09 12 06 12 F2 90") //
aString.append("07 00 06 08 68 20 B0 12") //
aString.append("3A FB 61 20 92 12 78 1C") //
aString.append("3A 40 FA F9 4C 43 8A 12") //
aString.append("3B 40 84 1C 26 4B 38 40") //
aString.append("B0 F9 39 40 A4 1C B2 90") //
aString.append("00 20 A4 1C 09 2C 1F 43") //
aString.append("B0 12 30 FB 3F 40 00 20") //
aString.append("2F 89 82 4F A4 1C 06 3C") //
aString.append("0F 43 B0 12 30 FB B2 50") //
aString.append("00 E0 A4 1C 92 52 A4 1C") //
aString.append("A4 1C 2F 49 7E 42 0D 43") //
aString.append("0C 48 AB 12 1F 43 5E 43") //
aString.append("3D 40 22 00 0C 48 AB 12") //
aString.append("B2 90 00 01 A4 1C 08 28") //
aString.append("7F 43 7E 42 0D 43 0C 48") //
aString.append("AB 12 7C 40 34 00 29 3C") //
aString.append("6C 42 8A 12 2F 49 3F F0") //
aString.append("FF 07 82 4F A6 1C 1F 42") //
aString.append("A6 1C 7E 40 0B 00 3D 40") //
aString.append("16 00 0C 48 AB 12 7C 40") //
aString.append("05 00 8A 12 2F 49 7E 40") //
aString.append("0C 00 3D 40 28 00 0C 48") //
aString.append("AB 12 7C 40 06 00 8A 12") //
aString.append("2F 49 7E 40 0C 00 3D 40") //
aString.append("34 00 0C 48 AB 12 B2 B0") //
aString.append("10 00 22 01 06 2C 7C 40") //
aString.append("28 00 92 12 8C 1C 0C 43") //
aString.append("05 3C E2 C2 C3 1C 92 12") //
aString.append("94 1C 1C 43 30 40 6C 5F") //
aString.append("5E 43 3D 40 21 00 0C 48") //
aString.append("00 46 C2 43 08 08 E2 D2") //
aString.append("C3 1C 92 12 98 1C 4C 93") //
aString.append("30 41 F2 90 07 00 06 08") //
aString.append("02 24 0C 43 30 41 B0 12") //
aString.append("3A FB 08 24 A2 43 DC F8") //
aString.append("7C 40 28 00 92 12 8C 1C") //
aString.append("0C 43 30 41 92 12 78 1C") //
aString.append("92 D3 00 07 B2 40 4B D8") //
aString.append("02 07 B2 C0 00 02 00 07") //
aString.append("A2 D2 00 07 92 43 DC F8") //
aString.append("32 D0 D8 00 E2 B3 C3 1C") //
aString.append("05 28 E2 C3 C3 1C 92 42") //
aString.append("A4 1C DE F8 5C 43 92 12") //
aString.append("86 1C E2 C2 C3 1C 92 12") //
aString.append("94 1C 1C 43 30 41 F2 90") //
aString.append("07 00 06 08 02 24 0C 43") //
aString.append("30 41 C2 43 08 08 E2 D2") //
aString.append("C3 1C 92 12 72 1C 1C 43") //
aString.append("30 41 0A 12 92 12 20 1C") //
aString.append("4C 93 14 24 F2 90 05 00") //
aString.append("0C 08 10 20 1D 42 06 08") //
aString.append("5F 42 06 08 0D 93 07 20") //
aString.append("7F 93 05 20 92 12 94 1C") //
aString.append("C2 43 08 08 12 3C 7F 90") //
aString.append("10 00 02 28 0C 43 0E 3C") //
aString.append("C2 43 08 08 0C 43 07 3C") //
aString.append("0E 4C 0E 5E 0A 4D 0A 5E") //
aString.append("A2 4A 08 08 1C 53 0C 9F") //
aString.append("F7 2B 1C 43 3A 41 30 41") //
aString.append("0A 12 4A 4C B0 12 C8 5B") //
aString.append("6A 92 10 20 7E 40 0B 00") //
aString.append("3D 40 16 00 3C 40 B0 F9") //
aString.append("92 12 4C 1C 82 4C A6 1C") //
aString.append("92 52 A6 1C A6 1C 92 52") //
aString.append("A6 1C A6 1C 3A 41 30 41") //
aString.append("0E 43 1C 42 B6 F9 B0 12") //
aString.append("DE 5E 1D 42 AA 1C 12 C3") //
aString.append("0D 10 0D 11 0C 9D 02 2C") //
aString.append("0D 8C 04 3C 0C 8D 0D 4C") //
aString.append("3E 40 00 02 3D 90 00 02") //
aString.append("02 28 3D 40 FF 01 5F 42") //
aString.append("A8 F9 0F 9D 06 2C B2 D0") //
aString.append("40 00 AE 1C F2 D0 40 00") //
aString.append("C2 1C 0D DE 82 4D AA 1C") //
aString.append("30 41 0A 12 0B 12 08 12") //
aString.append("09 12 5B 42 A9 F9 48 43") //
aString.append("C2 93 64 F8 09 34 F2 C0") //
aString.append("80 00 64 F8 6B B2 01 28") //
aString.append("58 43 4C 43 92 12 86 1C") //
aString.append("59 42 64 F8 0A 3C 6A 93") //
aString.append("04 20 B2 B0 00 02 00 08") //
aString.append("04 28 7C 40 06 00 92 12") //
aString.append("88 1C 1A 42 9E 01 4A 93") //
aString.append("1F 24 4E 49 6E 83 7E 90") //
aString.append("03 00 F7 2F 7A 90 10 00") //
aString.append("02 20 58 B3 12 2C 4C 4A") //
aString.append("7C 50 11 00 92 12 60 1C") //
aString.append("5B B3 03 28 7A 90 06 00") //
aString.append("E8 27 6B B3 03 28 7A 90") //
aString.append("0E 00 E3 27 7A 90 10 00") //
aString.append("D6 23 58 B3 DE 2F D9 3F") //
aString.append("5E 42 64 F8 6E 83 7E 90") //
aString.append("03 00 25 2C 92 12 96 1C") //
aString.append("92 12 9A 1C 00 3C 3F 40") //
aString.append("33 05 3F 53 FE 2F A2 B3") //
aString.append("22 01 15 28 3F 40 4E C3") //
aString.append("03 43 0B 43 3F 53 3B 63") //
aString.append("FD 2F B2 B0 10 00 22 01") //
aString.append("07 28 B2 B0 00 02 00 08") //
aString.append("03 2C 92 12 70 1C 07 3C") //
aString.append("7C 40 0A 00 02 3C 7C 40") //
aString.append("0B 00 92 12 8C 1C 92 12") //
aString.append("58 1C A2 D2 00 08 32 D2") //
aString.append("30 40 6E 5F 30 41 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("FF FF 84 FD 25 00 9A FC") //
aString.append("14 00 50 FC 19 00 20 FC") //
aString.append("03 00 5F F5 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 AB AB 4A FB") //
aString.append("E2 00 3C FA E1 00 AE FB") //
aString.append("AB AB 2C 5A A4 00 CA FB") //
aString.append("A3 00 56 5A A2 00 BA F9") //
aString.append("A1 00 24 57 A0 00 AB AB") //
aString.append("00 00 00 00 FF FF FF FF") //
aString.append("20 00 71 62 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 AE 5C 00 00 A8 57") //
aString.append("00 00 28 4E 68 45 00 00") //
aString.append("DC 5F AE 5A 7A 5A DA 50") //
*/
sensorData.append(aString)
// # ID: E0:07:A0:00:00:50:DE:AE
// # Memory content: Sensor 3
aString = String()
aString.append("78 13 88 53 05 00 03 7D") // 0x00
aString.append("51 04 4D 54 00 00 00 00") // 0x01
aString.append("00 00 00 00 00 00 00 00") // 0x02
aString.append("1C FA 0C 0E 1A 00 C0 46") // 0x03
aString.append("66 80 1C 00 C0 3E 66 80") // 0x04
aString.append("1B 00 C0 36 66 80 1A 00") // 0x05
aString.append("C0 2A 66 80 1B 00 C0 22") // 0x06
aString.append("66 80 1B 00 C0 1E 66 80") // 0x07
aString.append("19 00 C0 1A A6 80 1B 00") // 0x08
aString.append("C0 16 66 80 1A 00 C0 1A") // 0x09
aString.append("A6 80 1A 00 C0 1E A6 80") // 0x0a
aString.append("1B 00 C0 22 66 80 1B 00") // 0x0b
aString.append("C0 2A 66 80 1F 00 C0 5A") // 0x0c
aString.append("66 80 1A 00 C0 52 66 80") // 0x0d
aString.append("19 00 C0 4E 66 80 1C 00") // 0x0e
aString.append("C0 4A 66 80 00 00 C8 26") // 0x0f
aString.append("E0 80 00 00 C8 CA 1F 81") // 0x10
aString.append("00 00 C8 6E 1F 81 00 00") // 0x11
aString.append("C8 26 1F 81 00 00 C0 3A") // 0x12
aString.append("A5 80 00 00 C0 02 67 80") // 0x13
aString.append("00 00 C0 6E A7 80 00 00") // 0x14
aString.append("C0 6A A7 80 00 00 C0 42") // 0x15
aString.append("67 80 00 00 C0 4A A7 80") // 0x16
aString.append("00 00 C0 2E A7 80 00 00") // 0x17
aString.append("C0 F6 66 80 00 00 C8 96") // 0x18
aString.append("66 80 00 00 C0 3E 66 80") // 0x19
aString.append("CB 01 C8 0C 61 80 2B 89") // 0x1a
aString.append("88 7E 53 00 00 80 B0 B6") // 0x1b
aString.append("C2 86 1A 09 94 DE 84 80") // 0x1c
aString.append("AC 02 C8 B8 47 00 21 01") // 0x1d
aString.append("C8 C8 8B 00 A0 00 C8 A8") // 0x1e
aString.append("8D 00 00 00 C0 26 5C 80") // 0x1f
aString.append("00 00 C0 DE 64 80 00 00") // 0x20
aString.append("C0 FA 66 80 00 00 C0 DE") // 0x21
aString.append("67 80 00 00 C0 92 A8 80") // 0x22
aString.append("00 00 C0 E2 A8 80 00 00") // 0x23
aString.append("C0 1E A9 80 00 00 C8 F2") // 0x24
aString.append("A8 80 00 00 C8 96 22 81") // 0x25
aString.append("00 00 C8 0A 61 81 00 00") // 0x26
aString.append("C8 7E 20 81 4D 54 00 00") // 0x27
aString.append("6C 0C 00 01 D3 04 7D 51") //
aString.append("14 07 96 80 5A 00 ED A6") //
aString.append("14 76 1A C8 04 E4 39 6C") //
/*
aString.append("9E 42 21 83 F2 90 07 00") //
aString.append("06 08 02 24 0C 43 17 3C") //
aString.append("C2 43 08 08 B2 40 DF 00") //
aString.append("08 08 D2 42 A2 F9 08 08") //
aString.append("D2 42 A3 F9 08 08 0C 41") //
aString.append("0C 53 92 12 90 1C 5C 93") //
aString.append("03 20 A2 41 08 08 02 3C") //
aString.append("B2 43 08 08 1C 43 21 53") //
aString.append("30 41 0A 12 4A 4C 4C 93") //
aString.append("0B 20 B2 40 50 CC 02 07") //
aString.append("92 D3 00 07 B2 C0 00 02") //
aString.append("00 07 A2 D2 00 07 02 3C") //
aString.append("92 12 82 1C 32 D0 D8 00") //
aString.append("E2 B3 C3 1C 09 28 E2 C3") //
aString.append("C3 1C 4A 93 05 24 12 C3") //
aString.append("12 10 A4 1C 12 11 A4 1C") //
aString.append("3A 41 30 41 0A 12 0B 12") //
aString.append("08 12 09 12 06 12 F2 90") //
aString.append("07 00 06 08 68 20 B0 12") //
aString.append("3A FB 61 20 92 12 78 1C") //
aString.append("3A 40 FA F9 4C 43 8A 12") //
aString.append("3B 40 84 1C 26 4B 38 40") //
aString.append("B0 F9 39 40 A4 1C B2 90") //
aString.append("00 20 A4 1C 09 2C 1F 43") //
aString.append("B0 12 30 FB 3F 40 00 20") //
aString.append("2F 89 82 4F A4 1C 06 3C") //
aString.append("0F 43 B0 12 30 FB B2 50") //
aString.append("00 E0 A4 1C 92 52 A4 1C") //
aString.append("A4 1C 2F 49 7E 42 0D 43") //
aString.append("0C 48 AB 12 1F 43 5E 43") //
aString.append("3D 40 22 00 0C 48 AB 12") //
aString.append("B2 90 00 01 A4 1C 08 28") //
aString.append("7F 43 7E 42 0D 43 0C 48") //
aString.append("AB 12 7C 40 34 00 29 3C") //
aString.append("6C 42 8A 12 2F 49 3F F0") //
aString.append("FF 07 82 4F A6 1C 1F 42") //
aString.append("A6 1C 7E 40 0B 00 3D 40") //
aString.append("16 00 0C 48 AB 12 7C 40") //
aString.append("05 00 8A 12 2F 49 7E 40") //
aString.append("0C 00 3D 40 28 00 0C 48") //
aString.append("AB 12 7C 40 06 00 8A 12") //
aString.append("2F 49 7E 40 0C 00 3D 40") //
aString.append("34 00 0C 48 AB 12 B2 B0") //
aString.append("10 00 22 01 06 2C 7C 40") //
aString.append("28 00 92 12 8C 1C 0C 43") //
aString.append("05 3C E2 C2 C3 1C 92 12") //
aString.append("94 1C 1C 43 30 40 6C 5F") //
aString.append("5E 43 3D 40 21 00 0C 48") //
aString.append("00 46 C2 43 08 08 E2 D2") //
aString.append("C3 1C 92 12 98 1C 4C 93") //
aString.append("30 41 F2 90 07 00 06 08") //
aString.append("02 24 0C 43 30 41 B0 12") //
aString.append("3A FB 08 24 A2 43 DC F8") //
aString.append("7C 40 28 00 92 12 8C 1C") //
aString.append("0C 43 30 41 92 12 78 1C") //
aString.append("92 D3 00 07 B2 40 4B D8") //
aString.append("02 07 B2 C0 00 02 00 07") //
aString.append("A2 D2 00 07 92 43 DC F8") //
aString.append("32 D0 D8 00 E2 B3 C3 1C") //
aString.append("05 28 E2 C3 C3 1C 92 42") //
aString.append("A4 1C DE F8 5C 43 92 12") //
aString.append("86 1C E2 C2 C3 1C 92 12") //
aString.append("94 1C 1C 43 30 41 F2 90") //
aString.append("07 00 06 08 02 24 0C 43") //
aString.append("30 41 C2 43 08 08 E2 D2") //
aString.append("C3 1C 92 12 72 1C 1C 43") //
aString.append("30 41 0A 12 92 12 20 1C") //
aString.append("4C 93 14 24 F2 90 05 00") //
aString.append("0C 08 10 20 1D 42 06 08") //
aString.append("5F 42 06 08 0D 93 07 20") //
aString.append("7F 93 05 20 92 12 94 1C") //
aString.append("C2 43 08 08 12 3C 7F 90") //
aString.append("10 00 02 28 0C 43 0E 3C") //
aString.append("C2 43 08 08 0C 43 07 3C") //
aString.append("0E 4C 0E 5E 0A 4D 0A 5E") //
aString.append("A2 4A 08 08 1C 53 0C 9F") //
aString.append("F7 2B 1C 43 3A 41 30 41") //
aString.append("0A 12 4A 4C B0 12 C8 5B") //
aString.append("6A 92 10 20 7E 40 0B 00") //
aString.append("3D 40 16 00 3C 40 B0 F9") //
aString.append("92 12 4C 1C 82 4C A6 1C") //
aString.append("92 52 A6 1C A6 1C 92 52") //
aString.append("A6 1C A6 1C 3A 41 30 41") //
aString.append("0E 43 1C 42 B6 F9 B0 12") //
aString.append("DE 5E 1D 42 AA 1C 12 C3") //
aString.append("0D 10 0D 11 0C 9D 02 2C") //
aString.append("0D 8C 04 3C 0C 8D 0D 4C") //
aString.append("3E 40 00 02 3D 90 00 02") //
aString.append("02 28 3D 40 FF 01 5F 42") //
aString.append("A8 F9 0F 9D 06 2C B2 D0") //
aString.append("40 00 AE 1C F2 D0 40 00") //
aString.append("C2 1C 0D DE 82 4D AA 1C") //
aString.append("30 41 0A 12 0B 12 08 12") //
aString.append("09 12 5B 42 A9 F9 48 43") //
aString.append("C2 93 64 F8 09 34 F2 C0") //
aString.append("80 00 64 F8 6B B2 01 28") //
aString.append("58 43 4C 43 92 12 86 1C") //
aString.append("59 42 64 F8 0A 3C 6A 93") //
aString.append("04 20 B2 B0 00 02 00 08") //
aString.append("04 28 7C 40 06 00 92 12") //
aString.append("88 1C 1A 42 9E 01 4A 93") //
aString.append("1F 24 4E 49 6E 83 7E 90") //
aString.append("03 00 F7 2F 7A 90 10 00") //
aString.append("02 20 58 B3 12 2C 4C 4A") //
aString.append("7C 50 11 00 92 12 60 1C") //
aString.append("5B B3 03 28 7A 90 06 00") //
aString.append("E8 27 6B B3 03 28 7A 90") //
aString.append("0E 00 E3 27 7A 90 10 00") //
aString.append("D6 23 58 B3 DE 2F D9 3F") //
aString.append("5E 42 64 F8 6E 83 7E 90") //
aString.append("03 00 25 2C 92 12 96 1C") //
aString.append("92 12 9A 1C 00 3C 3F 40") //
aString.append("33 05 3F 53 FE 2F A2 B3") //
aString.append("22 01 15 28 3F 40 4E C3") //
aString.append("03 43 0B 43 3F 53 3B 63") //
aString.append("FD 2F B2 B0 10 00 22 01") //
aString.append("07 28 B2 B0 00 02 00 08") //
aString.append("03 2C 92 12 70 1C 07 3C") //
aString.append("7C 40 0A 00 02 3C 7C 40") //
aString.append("0B 00 92 12 8C 1C 92 12") //
aString.append("58 1C A2 D2 00 08 32 D2") //
aString.append("30 40 6E 5F 30 41 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("FF FF 84 FD 25 00 9A FC") //
aString.append("14 00 50 FC 19 00 20 FC") //
aString.append("03 00 5F F5 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 AB AB 4A FB") //
aString.append("E2 00 3C FA E1 00 AE FB") //
aString.append("AB AB 2C 5A A4 00 CA FB") //
aString.append("A3 00 56 5A A2 00 BA F9") //
aString.append("A1 00 24 57 A0 00 AB AB") //
aString.append("00 00 00 00 FF FF FF FF") //
aString.append("20 00 71 62 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 AE 5C 00 00 A8 57") //
aString.append("00 00 28 4E 68 45 00 00") //
aString.append("DC 5F AE 5A 7A 5A DA 50") //
*/
sensorData.append(aString)
// # ID: E0:07:A0:00:00:46:5C:54
// # Memory content: Sensor 4
aString = String()
aString.append("85 44 88 53 05 00 03 95") // 0x00
aString.append("51 04 65 54 00 00 00 00") // 0x01
aString.append("00 00 00 00 00 00 00 00") // 0x02
aString.append("F5 D6 04 10 16 00 C0 4A") // 0x03
aString.append("67 80 16 00 C0 46 A7 80") // 0x04
aString.append("16 00 C0 46 A7 80 16 00") // 0x05
aString.append("C0 46 A7 80 15 00 C0 4A") // 0x06
aString.append("A7 80 16 00 C0 4A 67 80") // 0x07
aString.append("15 00 C0 4A A7 80 16 00") // 0x08
aString.append("C0 4A 67 80 16 00 C0 46") // 0x09
aString.append("A7 80 16 00 C0 46 A7 80") // 0x0a
aString.append("16 00 C0 46 A7 80 15 00") // 0x0b
aString.append("C0 46 A7 80 15 00 C0 46") // 0x0c
aString.append("A7 80 16 00 C0 46 67 80") // 0x0d
aString.append("15 00 C0 46 67 80 16 00") // 0x0e
aString.append("C0 46 67 80 00 00 C0 CE") // 0x0f
aString.append("A7 80 00 00 C0 B6 A7 80") // 0x10
aString.append("00 00 C0 BA A7 80 00 00") // 0x11
aString.append("C0 BE A7 80 00 00 C0 BE") // 0x12
aString.append("A7 80 00 00 C0 AA A7 80") // 0x13
aString.append("00 00 C0 8E A7 80 00 00") // 0x14
aString.append("C0 8A 67 80 00 00 C0 7E") // 0x15
aString.append("A7 80 00 00 C0 72 A7 80") // 0x16
aString.append("00 00 C0 66 A7 80 00 00") // 0x17
aString.append("C0 5E 67 80 00 00 C0 56") // 0x18
aString.append("A7 80 00 00 C0 4E A7 80") // 0x19
aString.append("00 00 C0 4A 67 80 00 00") // 0x1a
aString.append("C0 46 67 80 00 00 C0 CA") // 0x1b
aString.append("A7 80 00 00 C0 C6 A7 80") // 0x1c
aString.append("00 00 C0 C6 A7 80 00 00") // 0x1d
aString.append("C0 C6 A7 80 00 00 C0 C6") // 0x1e
aString.append("A7 80 00 00 C0 CA A7 80") // 0x1f
aString.append("00 00 C0 CE A7 80 00 00") // 0x20
aString.append("C0 CE A7 80 00 00 C0 D2") // 0x21
aString.append("A7 80 00 00 C0 DA 67 80") // 0x22
aString.append("00 00 C0 D6 A7 80 00 00") // 0x23
aString.append("C0 CA A7 80 00 00 C0 CE") // 0x24
aString.append("A7 80 00 00 C0 D2 A7 80") // 0x25
aString.append("00 00 C0 D6 A7 80 00 00") // 0x26
aString.append("C0 DA A7 80 65 54 00 00") // 0x27
aString.append("D5 75 00 01 D3 04 95 51") //
aString.append("14 07 96 80 5A 00 ED A6") //
aString.append("12 9C 1A C8 04 A3 69 68") //
/*
aString.append("9E 42 21 83 F2 90 07 00") //
aString.append("06 08 02 24 0C 43 17 3C") //
aString.append("C2 43 08 08 B2 40 DF 00") //
aString.append("08 08 D2 42 A2 F9 08 08") //
aString.append("D2 42 A3 F9 08 08 0C 41") //
aString.append("0C 53 92 12 90 1C 5C 93") //
aString.append("03 20 A2 41 08 08 02 3C") //
aString.append("B2 43 08 08 1C 43 21 53") //
aString.append("30 41 0A 12 4A 4C 4C 93") //
aString.append("0B 20 B2 40 50 CC 02 07") //
aString.append("92 D3 00 07 B2 C0 00 02") //
aString.append("00 07 A2 D2 00 07 02 3C") //
aString.append("92 12 82 1C 32 D0 D8 00") //
aString.append("E2 B3 C3 1C 09 28 E2 C3") //
aString.append("C3 1C 4A 93 05 24 12 C3") //
aString.append("12 10 A4 1C 12 11 A4 1C") //
aString.append("3A 41 30 41 0A 12 0B 12") //
aString.append("08 12 09 12 06 12 F2 90") //
aString.append("07 00 06 08 68 20 B0 12") //
aString.append("3A FB 61 20 92 12 78 1C") //
aString.append("3A 40 FA F9 4C 43 8A 12") //
aString.append("3B 40 84 1C 26 4B 38 40") //
aString.append("B0 F9 39 40 A4 1C B2 90") //
aString.append("00 20 A4 1C 09 2C 1F 43") //
aString.append("B0 12 30 FB 3F 40 00 20") //
aString.append("2F 89 82 4F A4 1C 06 3C") //
aString.append("0F 43 B0 12 30 FB B2 50") //
aString.append("00 E0 A4 1C 92 52 A4 1C") //
aString.append("A4 1C 2F 49 7E 42 0D 43") //
aString.append("0C 48 AB 12 1F 43 5E 43") //
aString.append("3D 40 22 00 0C 48 AB 12") //
aString.append("B2 90 00 01 A4 1C 08 28") //
aString.append("7F 43 7E 42 0D 43 0C 48") //
aString.append("AB 12 7C 40 34 00 29 3C") //
aString.append("6C 42 8A 12 2F 49 3F F0") //
aString.append("FF 07 82 4F A6 1C 1F 42") //
aString.append("A6 1C 7E 40 0B 00 3D 40") //
aString.append("16 00 0C 48 AB 12 7C 40") //
aString.append("05 00 8A 12 2F 49 7E 40") //
aString.append("0C 00 3D 40 28 00 0C 48") //
aString.append("AB 12 7C 40 06 00 8A 12") //
aString.append("2F 49 7E 40 0C 00 3D 40") //
aString.append("34 00 0C 48 AB 12 B2 B0") //
aString.append("10 00 22 01 06 2C 7C 40") //
aString.append("28 00 92 12 8C 1C 0C 43") //
aString.append("05 3C E2 C2 C3 1C 92 12") //
aString.append("94 1C 1C 43 30 40 6C 5F") //
aString.append("5E 43 3D 40 21 00 0C 48") //
aString.append("00 46 C2 43 08 08 E2 D2") //
aString.append("C3 1C 92 12 98 1C 4C 93") //
aString.append("30 41 F2 90 07 00 06 08") //
aString.append("02 24 0C 43 30 41 B0 12") //
aString.append("3A FB 08 24 A2 43 DC F8") //
aString.append("7C 40 28 00 92 12 8C 1C") //
aString.append("0C 43 30 41 92 12 78 1C") //
aString.append("92 D3 00 07 B2 40 4B D8") //
aString.append("02 07 B2 C0 00 02 00 07") //
aString.append("A2 D2 00 07 92 43 DC F8") //
aString.append("32 D0 D8 00 E2 B3 C3 1C") //
aString.append("05 28 E2 C3 C3 1C 92 42") //
aString.append("A4 1C DE F8 5C 43 92 12") //
aString.append("86 1C E2 C2 C3 1C 92 12") //
aString.append("94 1C 1C 43 30 41 F2 90") //
aString.append("07 00 06 08 02 24 0C 43") //
aString.append("30 41 C2 43 08 08 E2 D2") //
aString.append("C3 1C 92 12 72 1C 1C 43") //
aString.append("30 41 0A 12 92 12 20 1C") //
aString.append("4C 93 14 24 F2 90 05 00") //
aString.append("0C 08 10 20 1D 42 06 08") //
aString.append("5F 42 06 08 0D 93 07 20") //
aString.append("7F 93 05 20 92 12 94 1C") //
aString.append("C2 43 08 08 12 3C 7F 90") //
aString.append("10 00 02 28 0C 43 0E 3C") //
aString.append("C2 43 08 08 0C 43 07 3C") //
aString.append("0E 4C 0E 5E 0A 4D 0A 5E") //
aString.append("A2 4A 08 08 1C 53 0C 9F") //
aString.append("F7 2B 1C 43 3A 41 30 41") //
aString.append("0A 12 4A 4C B0 12 C8 5B") //
aString.append("6A 92 10 20 7E 40 0B 00") //
aString.append("3D 40 16 00 3C 40 B0 F9") //
aString.append("92 12 4C 1C 82 4C A6 1C") //
aString.append("92 52 A6 1C A6 1C 92 52") //
aString.append("A6 1C A6 1C 3A 41 30 41") //
aString.append("0E 43 1C 42 B6 F9 B0 12") //
aString.append("DE 5E 1D 42 AA 1C 12 C3") //
aString.append("0D 10 0D 11 0C 9D 02 2C") //
aString.append("0D 8C 04 3C 0C 8D 0D 4C") //
aString.append("3E 40 00 02 3D 90 00 02") //
aString.append("02 28 3D 40 FF 01 5F 42") //
aString.append("A8 F9 0F 9D 06 2C B2 D0") //
aString.append("40 00 AE 1C F2 D0 40 00") //
aString.append("C2 1C 0D DE 82 4D AA 1C") //
aString.append("30 41 0A 12 0B 12 08 12") //
aString.append("09 12 5B 42 A9 F9 48 43") //
aString.append("C2 93 64 F8 09 34 F2 C0") //
aString.append("80 00 64 F8 6B B2 01 28") //
aString.append("58 43 4C 43 92 12 86 1C") //
aString.append("59 42 64 F8 0A 3C 6A 93") //
aString.append("04 20 B2 B0 00 02 00 08") //
aString.append("04 28 7C 40 06 00 92 12") //
aString.append("88 1C 1A 42 9E 01 4A 93") //
aString.append("1F 24 4E 49 6E 83 7E 90") //
aString.append("03 00 F7 2F 7A 90 10 00") //
aString.append("02 20 58 B3 12 2C 4C 4A") //
aString.append("7C 50 11 00 92 12 60 1C") //
aString.append("5B B3 03 28 7A 90 06 00") //
aString.append("E8 27 6B B3 03 28 7A 90") //
aString.append("0E 00 E3 27 7A 90 10 00") //
aString.append("D6 23 58 B3 DE 2F D9 3F") //
aString.append("5E 42 64 F8 6E 83 7E 90") //
aString.append("03 00 25 2C 92 12 96 1C") //
aString.append("92 12 9A 1C 00 3C 3F 40") //
aString.append("33 05 3F 53 FE 2F A2 B3") //
aString.append("22 01 15 28 3F 40 4E C3") //
aString.append("03 43 0B 43 3F 53 3B 63") //
aString.append("FD 2F B2 B0 10 00 22 01") //
aString.append("07 28 B2 B0 00 02 00 08") //
aString.append("03 2C 92 12 70 1C 07 3C") //
aString.append("7C 40 0A 00 02 3C 7C 40") //
aString.append("0B 00 92 12 8C 1C 92 12") //
aString.append("58 1C A2 D2 00 08 32 D2") //
aString.append("30 40 6E 5F 30 41 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("FF FF 84 FD 25 00 9A FC") //
aString.append("14 00 50 FC 19 00 20 FC") //
aString.append("03 00 5F F5 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 00 00 AB AB 4A FB") //
aString.append("E2 00 3C FA E1 00 AE FB") //
aString.append("AB AB 2C 5A A4 00 CA FB") //
aString.append("A3 00 56 5A A2 00 BA F9") //
aString.append("A1 00 24 57 A0 00 AB AB") //
aString.append("00 00 00 00 FF FF FF FF") //
aString.append("20 00 71 62 00 00 00 00") //
aString.append("00 00 00 00 00 00 00 00") //
aString.append("00 00 AE 5C 00 00 A8 57") //
aString.append("00 00 28 4E 68 45 00 00") //
aString.append("DC 5F AE 5A 7A 5A DA 50") //
*/
// sensorData.append(aString)
return sensorData
}
}
================================================
FILE: LibreMonitorTests/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
BNDL
CFBundleShortVersionString
1.0
CFBundleVersion
1
================================================
FILE: LibreMonitorTests/LibreMonitorTestSensorData.swift
================================================
//
// LibreMonitorTestSensorData.swift
// LibreMonitor
//
// Created by Uwe Petersen on 28.07.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import Foundation
import XCTest
//@testable import Pods_LibreMonitor
@testable import LibreMonitor
class LibreMonitorTestSensorData: XCTestCase {
// Adaptions for the c-code from the arduino part
typealias byte = UInt8
// local properties for string slip Buffer content
var slipBufferPayloadData: Data = Data()
var slipBufferPayloadIdentifier: UInt16 = 0
var slipBufferTxFlags: UInt8 = 0
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func arrayOfValidTestInputs() -> [[UInt8]] {
var testString2 = "FD 61 18 19 01 00 00 00" // 0 should return "62 C2", i.e. 25282 oder 49762 (with bytes not swapped)
testString2.append("00 00 00 00 00 00 00 00") // 1
testString2.append("00 00 00 00 00 00 00 00") // 2
testString2.append("62 C2 00 00 00 00 00 00") // 3
testString2.append("00 00 00 00 00 00 00 00") // 4
testString2.append("00 00 00 00 00 00 00 00") // 5
testString2.append("00 00 00 00 00 00 00 00") // 6
testString2.append("00 00 00 00 00 00 00 00") // 7
testString2.append("00 00 00 00 00 00 00 00") // 8
testString2.append("00 00 00 00 00 00 00 00") // 9
testString2.append("00 00 00 00 00 00 00 00") // 10
testString2.append("00 00 00 00 00 00 00 00") // 11
testString2.append("00 00 00 00 00 00 00 00") // 12
testString2.append("00 00 00 00 00 00 00 00") // 13
testString2.append("00 00 00 00 00 00 00 00") // 14
testString2.append("00 00 00 00 00 00 00 00") // 15
testString2.append("00 00 00 00 00 00 00 00") // 16
testString2.append("00 00 00 00 00 00 00 00") // 17
testString2.append("00 00 00 00 00 00 00 00") // 18
testString2.append("00 00 00 00 00 00 00 00") // 19
testString2.append("00 00 00 00 00 00 00 00") // 20
testString2.append("00 00 00 00 00 00 00 00") // 21
testString2.append("00 00 00 00 00 00 00 00") // 22
testString2.append("00 00 00 00 00 00 00 00") // 23
testString2.append("00 00 00 00 00 00 00 00") // 24
testString2.append("00 00 00 00 00 00 00 00") // 25
testString2.append("00 00 00 00 00 00 00 00") // 26
testString2.append("00 00 00 00 00 00 00 00") // 27
testString2.append("00 00 00 00 00 00 00 00") // 28
testString2.append("00 00 00 00 00 00 00 00") // 29
testString2.append("00 00 00 00 00 00 00 00") // 30
testString2.append("00 00 00 00 00 00 00 00") // 31
testString2.append("00 00 00 00 00 00 00 00") // 32
testString2.append("00 00 00 00 00 00 00 00") // 33
testString2.append("00 00 00 00 00 00 00 00") // 34
testString2.append("00 00 00 00 00 00 00 00") // 35
testString2.append("00 00 00 00 00 00 00 00") // 36
testString2.append("00 00 00 00 00 00 00 00") // 37
testString2.append("00 00 00 00 00 00 00 00") // 38
testString2.append("00 00 00 00 00 00 00 00") // 39
testString2.append("58 C7 00 01 15 04 96 50") // 40
testString2.append("14 07 96 80 5A 00 ED A6") // 41
testString2.append("12 13 1B C8 04 99 28 66") // 42
testString2 = testString2.replacingOccurrences(of: " ", with: "")
let bytes2 = stringToBytes(testString2)
var testString3 = "90 DC 18 19 03 00 00 00" // 0
testString3.append("00 00 00 00 00 00 00 00") // 1
testString3.append("00 00 00 00 00 00 00 00") // 2
testString3.append("E1 D7 06 03 FC 04 C8 24") // 3
testString3.append("DA 80 F1 04 C8 30 9A 80") // 4
testString3.append("E4 04 C8 24 9A 80 D4 04") // 5
testString3.append("C8 10 5A 80 CB 04 C8 6C") // 6
testString3.append("9A 80 C2 04 C8 78 DA 80") // 7 "C204C878DA80" for trend test
testString3.append("2D 05 C8 18 D9 80 2B 05") // 8
testString3.append("88 0E D9 81 33 05 C8 2C") // 9
testString3.append("D9 80 31 05 C8 38 D9 80") // 10
testString3.append("31 05 C8 28 D9 80 2E 05") // 11
testString3.append("C8 34 D9 80 2B 05 C8 98") // 12
testString3.append("99 80 22 05 C8 C0 99 80") // 13
testString3.append("18 05 C8 00 9A 80 0A 05") // 14
testString3.append("C8 08 9A 80 00 00 88 06") // 15
testString3.append("98 80 55 05 C8 2C D9 80") // 16
testString3.append("27 05 C8 98 99 80 00 00") // 17 "2705C8989980" for history test
testString3.append("00 00 00 00 00 00 00 00") // 18
testString3.append("00 00 00 00 00 00 00 00") // 19
testString3.append("00 00 00 00 00 00 00 00") // 20
testString3.append("00 00 00 00 00 00 00 00") // 21
testString3.append("00 00 00 00 00 00 00 00") // 22
testString3.append("00 00 00 00 00 00 00 00") // 23
testString3.append("00 00 00 00 00 00 00 00") // 24
testString3.append("00 00 00 00 00 00 00 00") // 25
testString3.append("00 00 00 00 00 00 00 00") // 26
testString3.append("00 00 00 00 00 00 00 00") // 27
testString3.append("00 00 00 00 00 00 00 00") // 28
testString3.append("00 00 00 00 00 00 00 00") // 29
testString3.append("00 00 00 00 00 00 00 00") // 30
testString3.append("00 00 00 00 00 00 00 00") // 31
testString3.append("00 00 00 00 00 00 00 00") // 32
testString3.append("00 00 00 00 00 00 00 00") // 33
testString3.append("00 00 00 00 00 00 00 00") // 34
testString3.append("00 00 00 00 00 00 00 00") // 35
testString3.append("00 00 00 00 00 00 00 00") // 36
testString3.append("00 00 00 00 00 00 00 00") // 37
testString3.append("00 00 00 00 00 00 00 00") // 38
testString3.append("00 00 00 00 37 00 00 00") // 39
testString3.append("58 C7 00 01 15 04 96 50") // 40
testString3.append("14 07 96 80 5A 00 ED A6") // 41
testString3.append("12 13 1B C8 04 99 28 66") // 42
testString3 = testString3.replacingOccurrences(of: " ", with: "")
let bytes3 = stringToBytes(testString3)
return [bytes2, bytes3]
}
func testCrcTable() {
}
func testExample() {
// Test valid crc and other data for the test cases
print("------- valid test cases for crc and other ------")
let validTestCaseBytes = arrayOfValidTestInputs().last!
let date = Date()
if let sensorData = SensorData(bytes: validTestCaseBytes, date: date) {
print(sensorData)
print("Header validity: " + String(sensorData.hasValidHeaderCRC))
print("Body validity:" + String(sensorData.hasValidBodyCRC))
XCTAssert(sensorData.hasValidHeaderCRC == true, "Unvalid header crc")
XCTAssert(sensorData.hasValidBodyCRC == true, "Unvalid body crc")
XCTAssert(sensorData.hasValidFooterCRC == true, "Unvalid footer crc")
XCTAssert(sensorData.minutesSinceStart == 55, "Wrong minutesSinceStart counter")
XCTAssert(sensorData.nextTrendBlock == 6, "Wrong next trend block")
XCTAssert(sensorData.nextHistoryBlock == 3, "Wrong next history block")
XCTAssert(sensorData.date == date, "Wrong date")
XCTAssert(sensorData.state == .ready, "Wrong sensor state")
// Test trend glucose measurement
let trendMeasurements = sensorData.trendMeasurements(0.0, slope: 0.1)
XCTAssert(trendMeasurements.count == 16, "Wrong number of trend measurements")
let byteStringInTrendMeasurement = trendMeasurements[0].byteString
let byteTrendStringAsReadFromSensor = "C204C878DA80"
XCTAssert(byteStringInTrendMeasurement == byteTrendStringAsReadFromSensor, "Wrong byte string") // "[194, 4, 200, 120, 218, 128]" or "C204C878DA80"
XCTAssert(trendMeasurements[0].date == date, "Wrong date")
XCTAssert(trendMeasurements[0].rawValue == 1218, "Wrong raw value")
XCTAssert((trendMeasurements[0].glucose - 121.8) < 0.001, "Wrong glucose value")
// Test count and byte strings of history glucose measurement
let historyMeasurements = sensorData.historyMeasurements(0.0, slope: 0.1)
XCTAssert(historyMeasurements.count == 32, "Wrong number of history measurements")
let byteStringInHistoryMeasurement = historyMeasurements[0].byteString
let byteHIstoryStringAsReadFromSensor = "2705C8989980"
XCTAssert(byteStringInHistoryMeasurement == byteHIstoryStringAsReadFromSensor, "Wrong byte string") // "[194, 4, 200, 120, 218, 128]" or "C204C878DA80"
// Test dates of history glucose measurements. Date of most recent value must be 10 minutes behind and date of second most recent value must be 10 + 15 = 25 minutes behind
let tenMintues = 10 * 60
let twentyFiveMintues = 25 * 60
XCTAssert( Int(round(date.timeIntervalSince(historyMeasurements[0].date))) == tenMintues, "Wrong history date, not 10 minutes apart")
XCTAssert( Int(round(date.timeIntervalSince(historyMeasurements[1].date))) == twentyFiveMintues, "Wrong history date, not 25 minutes apart")
// Test raw values and glucose values of history measurements
XCTAssert(historyMeasurements[0].rawValue == 1319, "Wrong raw value")
XCTAssert((historyMeasurements[0].glucose - 131.9) < 0.001, "Wrong glucose value")
}
// change data of body and ensure that crc is now different
print("------- invalid test cases for crc ------")
var invalidTestCaseBytes = validTestCaseBytes
let endIndex = invalidTestCaseBytes.endIndex-1
invalidTestCaseBytes[endIndex] = invalidTestCaseBytes[endIndex] | 0xff // change data such that crc will not match any more
if let sensorData = SensorData(bytes: invalidTestCaseBytes, date: Date()) {
print(sensorData)
print("Header validity: " + String(sensorData.hasValidHeaderCRC))
XCTAssertFalse(sensorData.hasValidFooterCRC == true, "Error in footer crc")
}
}
func stringToBytes(_ theString: String) -> [UInt8] {
let length = theString.lengthOfBytes(using: String.Encoding.utf8)
guard length % 2 == 0 else {
print("Error in \(#function): String does not have an even number of characters and is thus not a valid string of pairs of characters where each pair represents a byte.")
return [0]
}
var theBytes = [UInt8]()
for index in stride(from: 0, to: length, by: 2) {
let aIndex = theString.characters.index(theString.startIndex, offsetBy: index)
let bIndex = theString.characters.index(theString.startIndex, offsetBy: index+2)
let range = aIndex..
// Excaped data: -> crc is cf as hex or -49 as Int (calculated without the c0 start and end byte)
// Original data: <0303c810 1980>
// Excaped data: crc is 1e as hex or 30 as Int (calculated without the c0 start and end byte)
// unescaped: <06100148 02c8c859 0018a4> -> crc is -92 (as Int)
// escaped:
//
// unescaped: <01200000 4e464320 4653324a 41535432 00000053> crc is -> 83 as Int
// excaped:
//
// unescaped: <06100100 00000000 0029d0> -> crc is -48 as Int
// escaped:
let bytesArray: [[UInt8]] = [
[0xB0, 0x04, 0xC8, 0xB0, 0xD7, 0x00],
[0xCA, 0x02, 0xC8, 0x6C, 0x58, 0x01],
[0x9D, 0x02, 0xC8, 0x98, 0x97, 0x01],
[0x6B, 0x02, 0xC8, 0x38, 0x97, 0x00],
[0xCA, 0x02, 0xC8, 0x6C, 0x58, 0x01, 0x9C, 0x03, 0xC8, 0xC0, 0xDB, 0xDD, 0x1A, 0x80, 0x03, 0x03, 0xC8, 0x10, 0x19, 0x80, 0x01, 0x20, 0x00, 0x00, 0x4e, 0x46, 0x43, 0x20, 0x46, 0x53, 0x32, 0x4a, 0x41, 0x53, 0x54, 0x32, 0x00, 0x00, 0x00, 0x53],
[0xCA, 0x02, 0xC8, 0x6C, 0x58, 0x01, 0x9C, 0x03, 0xC8, 0xC0, 0xDB, 0xDD, 0xDC, 0x1A, 0x80, 0x03, 0x03, 0xC8, 0x10, 0x19, 0x80, 0x01, 0x20, 0x00, 0x00, 0x4e, 0x46, 0x43, 0x20, 0x46, 0x53, 0x32, 0x4a, 0x41, 0x53, 0x54, 0x32, 0x00, 0x00, 0x00, 0x53],
[0xCA, 0x02, 0xC8, 0x6C, 0x58, 0x01],
[0x9C, 0x03, 0xC8, 0xC0, 0x1A, 0x80],
[0x03, 0x03, 0xC8, 0x10, 0x19, 0x80],
[0x06, 0x10, 0x01, 0x48, 0x02, 0xc8, 0xc8, 0x59, 0x00, 0x18, 0xa4],
[0x01, 0x20, 0x00, 0x00, 0x4e, 0x46, 0x43, 0x20, 0x46, 0x53, 0x32, 0x4a, 0x41, 0x53, 0x54, 0x32, 0x00, 0x00, 0x00, 0x53],
[0x06, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xd0]
]
for bytes in bytesArray {
transmission(forPayload: bytes, packetIdentifier: UInt16(1234))
}
for testData in BluetoothTestData.data() {
// let range = testData.startIndex...testData.index(testData.startIndex, offsetBy: 244) // okay values on laat: 7; 201; 253; 255; not okay values:
let aString = testData.replacingOccurrences(of: " ", with: "")
let bytes = stringToBytes(aString)
transmission(forPayload: bytes, packetIdentifier: UInt16(9999))
}
}
// Delegate function for test purposes that receives the data (as received via bluetooth) and stores data in local properties
func slipBufferReceivedPayload(_ payloadData: Data, payloadIdentifier: UInt16, txFlags: UInt8) {
slipBufferPayloadData = payloadData
slipBufferPayloadIdentifier = payloadIdentifier
slipBufferTxFlags = txFlags
print("Payload is: " + slipBufferPayloadData.debugDescription)
}
// Test complete transmission of a packet of bytes with SLIP, see https://de.wikipedia.org/wiki/Serial_Line_Internet_Protocol
func transmission(forPayload payload: [UInt8], packetIdentifier: UInt16) {
// Connect simblee
SimbleeBLE_onConnect()
// 1. escape the payload using SLIP
let dataPayload = Data(bytes: UnsafePointer(payload), count: payload.count) // convert to NSData
let count = dataPayload.count / MemoryLayout.size
var cCharArray = [CChar](repeating: 0, count: count) // c-char array, format to be transmitted
(dataPayload as NSData).getBytes(&cCharArray, length: count) // read into the c-char array
// queue the packet and escape data if necessary according to SLIP, transmit and check for success
let success = UBP_queuePacketTransmission(packetIdentifier, UBP_TxFlagIsRPC, cCharArray, UInt16(payload.count))
XCTAssert(success, "Failed to queue packet with SLIP")
// 2. get the escaped payload as transmitted from transmission buffer
var txBuffer = [CChar](repeating: 0, count:440) /// Warning: This should not be hardcoded
var txBufferLength: Int32 = 0
getTxBuffer(&txBuffer, &txBufferLength)
// let escapedDataPayload = Data(bytes: UnsafePointer(txBuffer), count: Int(txBufferLength))
let escapedDataPayload = Data(bytes: UnsafeRawPointer(txBuffer), count: Int(txBufferLength))
print("Original data: " + dataPayload.debugDescription)
print("Excaped data: " + escapedDataPayload.debugDescription)
// 3. receive escaped data payload and unescape again. The unescaped data is stored in local properties
let slipBuffer = SLIPBuffer()
slipBuffer.delegate = self
slipBuffer.appendEscapedBytes(escapedDataPayload)
XCTAssertEqual(slipBufferPayloadData, dataPayload, "Transmitted and received payload are not equal")
XCTAssertEqual(slipBufferPayloadIdentifier, packetIdentifier, "Transmitted and received identifier are not equal")
// Disconnect simblee (resets the buffer)
SimbleeBLE_onDisconnect()
}
// MARK: crc test code
func testCrc8() {
// Test the crc calculation for some known cases of bytes
//
// Original data: <9c03c8c0 1a80>
// Excaped data: -> crc is cf as hex or -49 as Int (calculated without the c0 start and end byte)
// Original data: <0303c810 1980>
// Excaped data: crc is 1e as hex or 30 as Int (calculated without the c0 start and end byte)
//
// unescaped: <06100148 02c8c859 0018a4> -> crc is -92 (as Int)
// escaped:
//
// unescaped: <01200000 4e464320 4653324a 41535432 00000053> crc is -> 83 as Int
// excaped:
//
// unescaped: <06100100 00000000 0029d0> -> crc is -48 as Int
// escaped:
let bytesArray: [[UInt8]] = [
[0xd2, 0x04, 0x01, 0x9c, 0x03, 0xc8, 0xdb, 0xdc, 0x1a, 0x80, 0xcf],
[0xd2, 0x04, 0x01, 0x03, 0x03, 0xC8, 0x10, 0x19, 0x80, 0x1e],
[0x06, 0x10, 0x01, 0x48, 0x02, 0xc8, 0xc8, 0x59, 0x00, 0x18, 0xa4],
[0x01, 0x20, 0x00, 0x00, 0x4e, 0x46, 0x43, 0x20, 0x46, 0x53, 0x32, 0x4a, 0x41, 0x53, 0x54, 0x32, 0x00, 0x00, 0x00, 0x53],
[0x06, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xd0]
]
for bytes in bytesArray {
testCrc(forBytes: bytes)
testArduinoCrc(forBytes: bytes)
}
}
func testArduinoCrc(forBytes bytes: [UInt8]) {
// Test an array of bytes, where the last byte contains the crc8 that should be calculated (this last byte is then the embedded crc8)
var bytesToTransfer = bytes
let bytesToTransferLength = bytes.count - 1
let calculatedCrc: UInt8 = CRC8(&bytesToTransfer, UInt16(bytesToTransferLength))
let embeddedCrc = bytes[bytes.count-1]
print(String(format: "Arduino crc calculated is 0x%2x and embecced ist 0x%2x", arguments: [calculatedCrc, embeddedCrc]))
XCTAssert(embeddedCrc == calculatedCrc, "Arduino Code: calculated and embedde crc do not match.")
}
func testCrc(forBytes bytes: [UInt8]) {
// Last byte contains crc
let PacketChecksumLength = MemoryLayout.size
// Convert bytes to NSData
let data = Data(bytes: UnsafePointer(bytes), count: bytes.count)
// Extract embedded checksum from packet
var embeddedChecksumByte:Int8 = 0
(data as NSData).getBytes(&embeddedChecksumByte, range: NSMakeRange(data.count - PacketChecksumLength, PacketChecksumLength))
// Calculate checksum over bytes, excluding the last byte which is the embedded checksum
let payload = Data(bytes: UnsafePointer(bytes), count: bytes.count - PacketChecksumLength)
let calculatedCrc8 = (payload as NSData).crc8Checksum()
// Assert that they both are equal
print(String(format: "Embedded crc8 is %d, calculated crc8 is %d", arguments: [embeddedChecksumByte, calculatedCrc8]))
XCTAssert(embeddedChecksumByte == calculatedCrc8, "the crc8 on nsdata is false")
}
// MARK: example code for performance test
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
func stringToBytes(_ theString: String) -> [UInt8] {
let length = theString.lengthOfBytes(using: String.Encoding.utf8)
guard length % 2 == 0 else {
print("Error in \(#function): String does not have an even number of characters and is thus not a valid string of pairs of characters where each pair represents a byte.")
return [0]
}
var theBytes = [UInt8]()
for index in stride(from: 0, to: length, by: 2) {
let aIndex = theString.characters.index(theString.startIndex, offsetBy: index)
let bIndex = theString.characters.index(theString.startIndex, offsetBy: index+2)
let range = aIndex..
typedef uint8_t byte;
//byte CRC8(void *data_in, byte number_of_bytes_to_read);
byte CRC8(void *data_in, uint16_t number_of_bytes_to_read); // fixed bug that made app crashed with data longer than 256 bytes
================================================
FILE: LibreMonitorTests/SimbleeCode/crc8.m
================================================
// LICENSES: [077915]
// -----------------------------------
// The contents of this file contains the aggregate of contributions
// covered under one or more licences. The full text of those licenses
// can be found in the "LICENSES" file at the top level of this project
// identified by the MD5 fingerprints listed above.
//
//
// Uwe Petersen: Modified version to test transmission part in iOS
#include "crc8.h"
#define CRC8INIT 0x00
#define CRC8POLY 0x18 // 0X18 = X^8+X^5+X^4+X^0
//byte CRC8(void *bytes, byte number_of_bytes_to_read) {
byte CRC8(void *bytes, uint16_t number_of_bytes_to_read) {// fixed bug that made app crashed with data longer than 256 bytes
byte *data_in = (byte*) bytes;
byte crc;
uint16_t loop_count;
byte bit_counter;
byte data;
byte feedback_bit;
crc = CRC8INIT;
for (loop_count = 0; loop_count != number_of_bytes_to_read; loop_count++)
{
data = data_in[loop_count];
bit_counter = 8;
do {
feedback_bit = (crc ^ data) & 0x01;
if ( feedback_bit == 0x01 ) {
crc = crc ^ CRC8POLY;
}
crc = (crc >> 1) & 0x7F;
if ( feedback_bit == 0x01 ) {
crc = crc | 0x80;
}
data = data >> 1;
bit_counter--;
} while (bit_counter > 0);
}
return crc;
}
================================================
FILE: LibreMonitorTests/SimbleeCode/libUBP.h
================================================
//
// Uwe Petersen: Modified version to test transmission part in iOS
#import
typedef uint8_t byte;
#ifndef libUBP_h
#define libUBP_h
#endif
typedef enum {
UBP_TxFlagNone = 0 << 0,
UBP_TxFlagIsRPC = 1 << 0,
UBP_TxFlagRequiresACK = 1 << 1
} UBP_TxFlags;
// Public
void UBP_pump();
bool UBP_queuePacketTransmission(unsigned short packetIdentifier, UBP_TxFlags txFlags, const char *packetBytes, unsigned short byteCount);
//bool UBP_isBusy();
// 2016-06-26: Needed for tests
void getTxBuffer(char *txBuffer, int *txBufferLength);
void SimbleeBLE_onConnect();
void SimbleeBLE_onDisconnect();
// Private
void _UBP_pumpTxQueue();
//void _UBP_ingestRxBytes(char *receivedBytes, int byteCount);
int _UBP_makeEscapedCopy(const char *inputBuffer, unsigned short inputBufferLength, char *outputBuffer, unsigned short outputBufferLength);
int _UBP_makeUnEscapedCopy(const char *inputBuffer, unsigned short inputBufferLength, char *outputBuffer);
void _UBP_hostDisconnected();
// To be implemented by end-user externally
//extern void UBP_incomingChecksumFailed() __attribute__((weak));
//extern void UBP_receivedPacket(unsigned short packetIdentifier, UBP_TxFlags txFlags, void *packetBuffer) __attribute__((weak));
//extern void UBP_didAdvertise(bool start) __attribute__((weak));
//extern void UBP_didConnect() __attribute__((weak));
//extern void UBP_didDisconnect() __attribute__((weak));
================================================
FILE: LibreMonitorTests/SimbleeCode/libUBP.m
================================================
//
// Uwe Petersen: Modified version to test transmission part in iOS
#import "libUBP.h"
#import "crc8.h"
// Build-time configurations
//#define BUFFER_LENGTH 64 // Uwe changed this on 2016-01-04
//#define BUFFER_LENGTH 400 // Uwe changed this on 2016-07-24
#define BUFFER_LENGTH 440 // Uwe changed this on 2016-12-25
#define TX_CHUNK_SIZE 20
#define PACKET_ID_SIZE 2
// Serial Line IP (SLIP) escaping constants
#define ESCAPE_BYTE 0xDB
#define END_BYTE 0xC0
#define ESCAPED_ESCAPE_BYTE 0xDD
#define ESCAPED_END_BYTE 0xDC
const char escapeSequence_[1] = {ESCAPE_BYTE};
const char endSequence_[1] = {END_BYTE};
const char escapedEndSequence_[2] = {ESCAPE_BYTE, ESCAPED_END_BYTE};
const char escapedEscapeSequence_[2] = {ESCAPE_BYTE, ESCAPED_ESCAPE_BYTE};
// Buffers
char ubpTxBuffer[BUFFER_LENGTH];
int ubpTxBufferLength = 0;
int ubpTxBufferSentByteCount = 0;
char ubpRxBuffer[BUFFER_LENGTH];
int ubpRxBufferLength = 0;
char ubpUnescapedRxBufferBuffer[BUFFER_LENGTH];
int ubpUnescapedRxBufferBufferLength = 0;
// State Variables
bool UBP_isTxPending = false;
bool hostIsConnected = true;
//bool hostIsConnected = false;
// 2016-06-27: for testing purposes simulate simblee
char simbleeBuffer[BUFFER_LENGTH];
int simbleeBufferLength = 0;
/*
// Functions
bool UBP_isBusy() {
return UBP_isTxPending;
}
*/
void UBP_pump() {
_UBP_pumpTxQueue();
}
void _UBP_pumpTxQueue() {
if (UBP_isTxPending) {
char *nextByteToSend = ubpTxBuffer + ubpTxBufferSentByteCount;
// Try sending the next chunk
if (ubpTxBufferSentByteCount < ubpTxBufferLength && hostIsConnected) { // Haven't already sent all the bytes
int retryRemainingCount = 1000; // Limit the number of times we retry sending to avoid getting into an infinite loop
int remainingByteCount = ubpTxBufferLength - ubpTxBufferSentByteCount;
if (remainingByteCount >= TX_CHUNK_SIZE) { // Can fill the TX output buffer
// Send is queued (the ble stack delays send to the start of the next tx window)
while (hostIsConnected && retryRemainingCount > 0) {
retryRemainingCount--;
};
// 2016-06-27: This changed to the above to enable testing
// while ( SimbleeBLE.send(nextByteToSend, TX_CHUNK_SIZE) == false && hostIsConnected && retryRemainingCount > 0) {
// retryRemainingCount--;
// }; // send() returns false if all tx buffers in use (can't enqueue, try again later)
ubpTxBufferSentByteCount += TX_CHUNK_SIZE;
} else { // Only partial TX buffer remaining to send
// Send is queued (the ble stack delays send to the start of the next tx window)
while ( hostIsConnected && retryRemainingCount > 0) {
retryRemainingCount--;
}; // send() returns false if all tx buffers in use (can't enqueue, try again later)
// 2016-06-27: This changed to the above to enable testing
// while ( SimbleeBLE.send(nextByteToSend, remainingByteCount) == false && hostIsConnected && retryRemainingCount > 0) {
// retryRemainingCount--;
// }; // send() returns false if all tx buffers in use (can't enqueue, try again later)
ubpTxBufferSentByteCount += remainingByteCount;
UBP_isTxPending = false;
}
}
}
}
/*
void _UBP_ingestRxBytes(char *receivedBytes, int byteCount) {
Serial.print(byteCount);
Serial.println(" bytes receieved");
// NOTE: Assuming not called unless len > 0
// Determine what to do with incoming fragment
if ( *receivedBytes == END_BYTE ) { // Fragment has leading END byte, signals start of packet
// Set fragment as the beginning of the reconstruction buffer
memcpy(ubpRxBuffer, receivedBytes, byteCount);
ubpRxBufferLength = byteCount;
} else if (ubpRxBufferLength > 0) { // Already have fragments in the reconstruction buffer
// Append fragment to reconstruction buffer
memcpy(ubpRxBuffer + ubpRxBufferLength, receivedBytes, byteCount);
ubpRxBufferLength += byteCount;
}
// Check RX buffer for trailing END byte
if ( *(ubpRxBuffer + ubpRxBufferLength - 1) == END_BYTE) { // RX buffer ends with END byte, looks like packet is complete
byte firstNonControlIndex = 1;
byte escapedDataLength = ubpRxBufferLength - 2; // "- 2" for leading/trailing control chars
// Un-escape the incoming payload
ubpUnescapedRxBufferBufferLength = _UBP_makeUnEscapedCopy(ubpRxBuffer + firstNonControlIndex, escapedDataLength, ubpUnescapedRxBufferBuffer);
byte payloadDataLength = ubpUnescapedRxBufferBufferLength - 1; // -1 to account for checksum
// Calculate checksum over payload, i.e. all bytes except for last checksum byte)
char calculatedChecksum = CRC8(ubpUnescapedRxBufferBuffer, payloadDataLength * sizeof(byte));
// Extract embedded checksum value
char receivedChecksum = *(ubpUnescapedRxBufferBuffer + payloadDataLength); // NOTE: Omitting '-1' because checksum byte comes just after payloadDataLength
// Verify the checksum
if (calculatedChecksum == receivedChecksum) {
unsigned short packetIdentifier = *(ubpUnescapedRxBufferBuffer);
UBP_TxFlags txFlags = (UBP_TxFlags) *(ubpUnescapedRxBufferBuffer + PACKET_ID_SIZE);
if (UBP_receivedPacket) {
void *packetBuffer = (ubpUnescapedRxBufferBuffer + PACKET_ID_SIZE + 1); // skip +
UBP_receivedPacket(packetIdentifier, txFlags, packetBuffer);
}
} else {
Serial.println("Incoming packet checksum invalid");
// Reset
ubpRxBufferLength = 0;
ubpUnescapedRxBufferBufferLength = 0;
if (UBP_incomingChecksumFailed) {
UBP_incomingChecksumFailed();
}
}
} // else haven't RX'd final fragment yet, keep waiting
}
*/
// only needed to retreive the data for testing
void getTxBuffer(char *txBuffer, int *txBufferLength) {
for (int i=0; i +
// Calculate and append checksum
byte checksumValue = CRC8(ubpTxBuffer + 1, payloadLength); // Checksum over all payload bytes (minus the leading END byte, checksum, and trailing END byte)
*(ubpTxBuffer + ubpTxBufferLength) = checksumValue;
ubpTxBufferLength++;
// Append trailing END byte
*(ubpTxBuffer + ubpTxBufferLength) = END_BYTE;
ubpTxBufferLength++;
// Mark as ready to begin transmission
ubpTxBufferSentByteCount = 0;
UBP_isTxPending = true;
} else {
return false; // Return false if we couldn't escape the content because it was going to overflow the output buffer
}
}
// 2016-06-27: this was missing in the original code
return true;
}
int _UBP_makeEscapedCopy(const char *inputBuffer, unsigned short inputBufferLength, char *outputBuffer, unsigned short outputBufferLength) {
unsigned int bytesCopied = 0;
const char *inputBytes = inputBuffer; // Cast here to avoid compiler warnings later
// 2016-07-24: changed i from char to int to avoid buffer over flow that had happend for more numbers larger than 256
//for (char i = 0; i < inputBufferLength; i++) { // For each byte to append
for (int i = 0; i < inputBufferLength; i++) { // For each byte to append
// printf("i = %i", (int) i);
// Escape any control characters. Refer to Serial Line IP (SLIP) spec.
char aByte = *(inputBytes + i);
if (aByte == (char) ESCAPE_BYTE) { // Escape an ESCAPE_BYTE
// if (aByte == ESCAPE_BYTE) { // Escape an ESCAPE_BYTE
if (bytesCopied + 1 >= outputBufferLength) return -1; // Would¥ overflow destination buffer
else {
*(outputBuffer + bytesCopied++) = ESCAPE_BYTE; // Write ESCAPE_BYTE to buffer and increment offset
*(outputBuffer + bytesCopied++) = ESCAPED_ESCAPE_BYTE; // Write escaped ESCAPE_BYTE to buffer and increment offset
}
// } else if (aByte == END_BYTE) { // Escape an END_BYTE
} else if (aByte == (char) END_BYTE) { // Escape an END_BYTE
if (bytesCopied + 1 >= outputBufferLength) return -1; // Would overflow destination buffer
else {
*(outputBuffer + bytesCopied++) = ESCAPE_BYTE; // Write ESCAPE_BYTE to buffer and increment offset
*(outputBuffer + bytesCopied++) = ESCAPED_END_BYTE; // Write escaped END_BYTE to buffer and increment offset
}
} else { // Not a control character
if (bytesCopied >= outputBufferLength) return -1; // Would overflow destination buffer
else *(outputBuffer + bytesCopied++) = aByte; // Copy the unmolested byte to the buffer and increment offset
}
}
return bytesCopied;
}
/*
int _UBP_makeUnEscapedCopy(const char *inputBuffer, unsigned short inputBufferLength, char *outputBuffer) {
bool done = false;
char * destinationBufferPtr = outputBuffer;
const char * sourceBufferPtr = inputBuffer;
// UNESCAPE END Sequence_
while (!done && (sourceBufferPtr - inputBuffer) < inputBufferLength) {
char * substringPtr = strstr(sourceBufferPtr, escapedEndSequence_);
if (substringPtr == NULL) done = true;
else {
// Copy bytes between last-copied byte and next escape byte
char lengthToCopy = substringPtr - sourceBufferPtr; // How many bytes between last byte copied and next escape byte
memcpy(destinationBufferPtr, sourceBufferPtr, lengthToCopy);
destinationBufferPtr += lengthToCopy;
sourceBufferPtr += lengthToCopy;
// Replace escaped source sequence with unescaped version during copy, increment pointer
memcpy(destinationBufferPtr, endSequence_, sizeof(endSequence_));
destinationBufferPtr += sizeof(endSequence_);
// Increment pointer past escaped end Sequence_
sourceBufferPtr += sizeof(escapedEndSequence_);
}
}
// UNESCAPE ESCAPE SEQUENCE
done = false;
while (!done && (sourceBufferPtr - inputBuffer) < inputBufferLength) {
char * substringPtr = strstr(sourceBufferPtr, escapedEscapeSequence_);
if (substringPtr == NULL) done = true;
else {
// Copy bytes between last-copied byte and next escape byte
char lengthToCopy = substringPtr - sourceBufferPtr; // How many bytes between last byte copied and next escape byte
memcpy(destinationBufferPtr, sourceBufferPtr, lengthToCopy);
destinationBufferPtr += lengthToCopy;
sourceBufferPtr += lengthToCopy;
// Replace escaped source sequence with unescaped version during copy, increment pointer
memcpy(destinationBufferPtr, escapeSequence_, sizeof(escapeSequence_));
destinationBufferPtr += sizeof(escapeSequence_);
// Increment pointer past escaped end Sequence_
sourceBufferPtr += sizeof(escapedEscapeSequence_);
}
}
// COPY ANY TRAILING BYTES
char lengthToCopy = (inputBuffer + inputBufferLength) - sourceBufferPtr; // How many bytes remain to be copied
memcpy(destinationBufferPtr, sourceBufferPtr, lengthToCopy);
destinationBufferPtr += lengthToCopy;
sourceBufferPtr += lengthToCopy;
return (destinationBufferPtr - outputBuffer); // Return the total number of bytes copied to the destination buffer
}
*/
void _UBP_hostDisconnected() {
hostIsConnected = false;
// Reset TX subsystem
UBP_isTxPending = false;
// Reset RX subsystem
ubpRxBufferLength = 0;
// Invoke user callback
// 2016-06-28: commentet out for testing
// if (UBP_didDisconnect) UBP_didDisconnect();
}
// RFduino EVENTS
// ----------------------------------------------------
//void SimbleeBLE_onAdvertisement(bool start) {
//
// if (UBP_didAdvertise) UBP_didAdvertise(start);
//}
//
void SimbleeBLE_onConnect() {
hostIsConnected = true;
// 2016-06-29: uncommented and added for testing purposes
// if (UBP_didConnect) UBP_didConnect();
UBP_isTxPending = false;
}
//void SimbleeBLE_onReceive(char *data, int len) {
//
// _UBP_ingestRxBytes(data, len);
//}
void SimbleeBLE_onDisconnect() {
_UBP_hostDisconnected();
}
================================================
FILE: LibreMonitorTests/TransmissionTests.swift
================================================
//
// TransmissionTests.swift
// LibreMonitor
//
// Created by Uwe Petersen on 03.02.17.
// Copyright © 2017 Uwe Petersen. All rights reserved.
//
import Foundation
import XCTest
//@testable import Pods_LibreMonitor
@testable import LibreMonitor
class TransmissionTests: XCTestCase {
var testData = [[UInt8]]()
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
guard let dataAsString = BluetoothTestData.data().last else {
return
}
let aString = dataAsString.replacingOccurrences(of: " ", with: "")
let bytes = stringToBytes(aString)
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testTransmission() {
}
func stringToBytes(_ theString: String) -> [UInt8] {
let length = theString.lengthOfBytes(using: String.Encoding.utf8)
guard length % 2 == 0 else {
print("Error in \(#function): String does not have an even number of characters and is thus not a valid string of pairs of characters where each pair represents a byte.")
return [0]
}
var theBytes = [UInt8]()
for index in stride(from: 0, to: length, by: 2) {
let aIndex = theString.characters.index(theString.startIndex, offsetBy: index)
let bIndex = theString.characters.index(theString.startIndex, offsetBy: index+2)
let range = aIndex..
CFBundleDevelopmentRegion
en
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
BNDL
CFBundleShortVersionString
1.0
CFBundleVersion
1
================================================
FILE: LibreMonitorUITests/LibreMonitorUITests.swift
================================================
//
// LibreMonitorUITests.swift
// LibreMonitorUITests
//
// Created by Uwe Petersen on 14.10.16.
// Copyright © 2016 Uwe Petersen. All rights reserved.
//
import XCTest
class LibreMonitorUITests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
// UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
XCUIApplication().launch()
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
}
================================================
FILE: Podfile
================================================
# Uncomment this line to define a global platform for your project
# platform :ios, '10.0'
# Uncomment this line if you're using Swift
use_frameworks!
target 'LibreMonitor' do
#pod 'Charts' '~> 2.2.5'
#pod 'Charts', :branch => 'Swift-3.0'
#pod 'Charts', :git => 'https://github.com/danielgindi/Charts.git', :branch => 'Swift-3.0'
#pod 'Charts', :git => 'https://github.com/danielgindi/Charts.git', :branch => 'Chart2.2.5-Swift3.0'
#pod 'Charts', :git => 'https://github.com/danielgindi/Charts.git', :branch => 'master'
pod 'Charts'
end
target 'LibreMonitorTests' do
end
target 'LibreMonitorUITests' do
end
================================================
FILE: README.md
================================================
# LibreMonitor - Monitor your Freestyle Libre
LibreMonitor is a little DIY device that uses near field communication to read data from a Freestyle Libre sensor and transmit it via bluetooth low energy to an iPhone application. LibreMonitor scans the sensor every two minutes. It transfers all the 32 history values for the last eight hours and the 16 trend values for the current time and the last 15 minutes and displays them in a chart and in a table.
Be aware that only the so called raw data is used and you have to choose slope and intercept yourself to have the application calculate useful glucose values. Values that mostly work fine for my sensors are 0.13 for slope and -20 for offset. Any other internal information such as from the temperature sensors is not yet fully understood and thus neglected.
This code is published so that others can contribute and help to improve it or use it to improve their own devices.
LibreMonitor has no affiliation of any kind with Abbott. This is a DIY project for research purposes. The code provided here might provide wrong results. You will have to build your own device and are responsible for the results. Use at your own risk.
## What you need to build a LibreMonitor
### 1. Buy some hardware
Parts needed for a LibreMonitor are
* [BM019 NFC module](http://www.solutions-cubed.com/bm019/) capable of ISO/IEC 15693 commands. Possible sources are [Solutions Cubed LLC](http://www.solutions-cubed.com/bm019/), [Warbutech](http://www.warburtech.co.uk/products/modules/solutions.cubed.bm019.serial.to.nfc.converter/) or
[Robotshop](http://www.robotshop.com/eu/en/serial-to-nfc-converter-module.html).
* [Simblee](https://www.simblee.com) or [RFDuino](http://www.rfduino.com) and a corresponding USB Programming Shield. I recommend to get a startet kit. See their Webites for Distributors.
* Lipo, e.g. [this one](http://www.exp-tech.de/polymer-lithium-ion-battery-110mah-5695) (100 mAh is fine for a full day).
* Lipo charger (optional), e.g. [this](https://www.adafruit.com/product/1304) or [this](https://www.adafruit.com/products/1904) from adafruit.
* Switch (optional but helpful if you mount a lipo charger).
### 2. Do the wiring
Wire the parts as shown in the following diagram (courtesy to [libxMike](https://github.com/libxmike?tab=following)).
It is suggested to mount and test everything on a breadboard before soldering the final device. Below are pictures of another LibreMonitor device without a lipo charger. As you can see, one can save a lot of space by cutting of the black part of the stacks for Pins GPIO2 to GPIO6, push them through the pin holes of the BM019 and then solder the parts together. Therefore you also have to cut off the stack pins on the other side, too. It is suggested to bend the black part of the other stacks by 90 degree. Thus, you can still plug in the USB Programming Shield (RFD22121) but save some space.
Another device, this time with a lipo charger:
### 3. Program your Simblee
Simblee is IoT for connecting Everyone and Everything (IoT4EE).
It incorporates Mobile, Bluetooth® Smart, Mesh, Cloud and other forms of wireless connectivity.
The software to program the Simblee is standard Arduino code. It consists of LibreMonitor.ino and the library contained in LibreMonitorArduinoLibrary.zip. Refer to the [Simblee quick start guide](https://www.simblee.com/Simblee_Quickstart_Guide_v1.0.pdf) on the [Simblee website](https://www.simblee.com) for instructions on how to program the Simblee. If you wired your LibreMonitor as described above, don't forget to reconfigure the SPI pins of the Simblee in the variant.h file (see the wiring information in LibreMonitor.ino for more information on this)
### 4. Build and run the LibreMonior iOS application
The iOS application requires Xcode 8, swift 3.0 and iOS 10. Download the Xcode project. Run [cocopoads](https://cocoapods.org) to install the [charts](https://github.com/danielgindi/Charts) library, needed for the blood sugar graph. Build the application and run it on the phone and start it. If you want to receive notifications for high or low glucose values and have a badge icon displayed, allow for the corresponding settings, when asked. Once the app is running set values for slope and offset (e.g. 0.13 and -20, press the corresponding row to get into the settings view). Connect to your Simblee by pressing "connect". Once the Simblee ist detected and connected the "Simblee status" should change to "Notifying" and be green. Place the LibreMonitor device above your Freestyle Libre and after no more than two minutes the data should be displayed or refreshed. See the screenshots below.
#### Some explanations
The "Glucose" row shows the current glucose value and two "delta values" that show how the glucose is about to develop (linear extrapolation for the next fifteen minutes). The first delta value is the difference of the current and the oldest minute-value, the delta value in braces is the difference of the current glucose value and the glucose value from 8 minutes ago, multiplied by two. The two "prognosis" glucose values are calculated by adding the delta values to the current glucose value. Glucose is calculated from the raw value as follows:
glucose = raw * slope + offset
The "Last 15 minutes" and "Last eight hours" sections display the glucose values, corresponding date, the raw value, the 6 bytes of data as read from the Freestyle Libre sensor and some other test data.
### General Troubleshooting
* If a crc is wrong, most likely the device is not located near enough to the Freestyle Libre sensor.
* If the data is not refreshed, disconnect and reconnect.
* If the device cannot be connected, check whether bluetooth is switched on.
## Suggested readings
[Blog by Pierre Vandevenne](http://type1tennis.blogspot.de) with information on the internals of the Freestyle Libre and suggestions on how to choose slope and offset. Without his work all this would probably not have been possible.
## Similar projects
* [LimiTTer](https://github.com/JoernL/LimiTTer). Similar device, but data is sent to [xDrip+](https://github.com/jamorham/xDrip-plus), an Android app.
* [Freestyle Libre Alarm](https://github.com/pimpimmi/LibreAlarm/wiki). Uses as Sony smart watch to read data from the Freestyle Libre and send it to an Android phone.
* [Bluereader](https://www.startnext.com/bluereader) project by [Sandra Kessler](http://unendlichkeit.net/wordpress/) who got funding to build a small neat device. I intend to adapt this project to work with bluereader once the first devices are available.
* [Android reader application](https://github.com/vicktor/FreeStyleLibre-NFC-Reader) by Viktor Bautista that was helpful at the beginning of this work.
## Personal note
As of September 2018: I used different versions of the LibreMonitor and similar hardware (Marek’s Transmitter) based on Simblee or RFDuino since early 2016 up to March 2018 and liked it very much. When I started the project nothing similar was available. Meanwhile many other projects have evolved and also commercial hardware is available. Since March 2018 I have been using the [MiaoMiao](https://www.miaomiao.cool) hardware and I have to say that in my opinion it is much better than anything I had seen before. Thus I will no longer support the LibreMonitor hardware but only MiaoMiao hardware with the LibreMonitor iPhone application. So if you want to use the LibreMonitor with MiaoMiao, use the swift4 branch. If you choose to use the old LibreMonitor hardware, choose the old master branch.
================================================
FILE: libUBP RFduino.cpp
================================================
//
// libUBP.cpp
// C++ code
// ----------------------------------
// Developed with embedXcode+
// http://embedXcode.weebly.com
//
// Project BM019 Inventory
//
// Created by Uwe Petersen, 02.01.16 14:13
// Uwe Petersen
//
// Copyright (c) Uwe Petersen, 2016
// Licence <#license#>
//
// See libUBP.h and ReadMe.txt for references
//
// Core library for code-sense - IDE-based
#if defined(WIRING) // Wiring specific
# include "Wiring.h"
#elif defined(MAPLE_IDE) // Maple specific
# include "WProgram.h"
#elif defined(ROBOTIS) // Robotis specific
# include "libpandora_types.h"
# include "pandora.h"
#elif defined(MPIDE) // chipKIT specific
# include "WProgram.h"
#elif defined(DIGISPARK) // Digispark specific
# include "Arduino.h"
#elif defined(ENERGIA) // LaunchPad specific
# include "Energia.h"
#elif defined(LITTLEROBOTFRIENDS) // LittleRobotFriends specific
# include "LRF.h"
#elif defined(MICRODUINO) // Microduino specific
# include "Arduino.h"
#elif defined(TEENSYDUINO) // Teensy specific
# include "Arduino.h"
#elif defined(REDBEARLAB) // RedBearLab specific
# include "Arduino.h"
#elif defined(RFDUINO) // RFduino specific
# include "Arduino.h"
#elif defined(SPARK) // Spark specific
# include "application.h"
#elif defined(ARDUINO) // Arduino 1.0 and 1.5 specific
# include "Arduino.h"
#else // error
# error Platform not defined
#endif // end IDE
// Code
//#define SIMBLEE
#include "libUBP.h"
#include
#include "crc8.h"
// Choose Simblee or RFduino
#ifdef SIMBLEE
#include
#else
#include
#endif
// Build-time configurations
//#define BUFFER_LENGTH 64
//#define BUFFER_LENGTH 64 // Uwi changed this on 2016-01-04
//#define BUFFER_LENGTH 400 // Uwi changed this on 2016-07-24
#define BUFFER_LENGTH 430 // Uwi changed this on 2016-08-07
#define TX_CHUNK_SIZE 20
#define PACKET_ID_SIZE 2
// Serial Line IP (SLIP) escaping constants
#define ESCAPE_BYTE 0xDB
#define END_BYTE 0xC0
#define ESCAPED_ESCAPE_BYTE 0xDD
#define ESCAPED_END_BYTE 0xDC
const char escapeSequence[1] = {ESCAPE_BYTE};
const char endSequence[1] = {END_BYTE};
const char escapedEndSequence[2] = {ESCAPE_BYTE, ESCAPED_END_BYTE};
const char escapedEscapeSequence[2] = {ESCAPE_BYTE, ESCAPED_ESCAPE_BYTE};
// Buffers
char ubpTxBuffer[BUFFER_LENGTH];
int ubpTxBufferLength = 0;
int ubpTxBufferSentByteCount = 0;
char ubpRxBuffer[BUFFER_LENGTH];
int ubpRxBufferLength = 0;
char ubpUnescapedRxBufferBuffer[BUFFER_LENGTH];
int ubpUnescapedRxBufferBufferLength = 0;
// State Variables
bool UBP_isTxPending = false;
bool hostIsConnected = false;
// Functions
bool UBP_isBusy() {
return UBP_isTxPending;
}
void UBP_pump() {
_UBP_pumpTxQueue();
}
void _UBP_pumpTxQueue() {
if (UBP_isTxPending) {
char *nextByteToSend = ubpTxBuffer + ubpTxBufferSentByteCount;
// Try sending the next chunk
if (ubpTxBufferSentByteCount < ubpTxBufferLength && hostIsConnected) { // Haven't already sent all the bytes
int retryRemainingCount = 1000; // Limit the number of times we retry sending to avoid getting into an infinite loop
int remainingByteCount = ubpTxBufferLength - ubpTxBufferSentByteCount;
if (remainingByteCount >= TX_CHUNK_SIZE) { // Can fill the TX output buffer
#ifdef SIMBLEE
// Send is queued (the ble stack delays send to the start of the next tx window)
while ( SimbleeBLE.send(nextByteToSend, TX_CHUNK_SIZE) == false && hostIsConnected && retryRemainingCount > 0) {
#else
while ( RFduinoBLE.send(nextByteToSend, TX_CHUNK_SIZE) == false && hostIsConnected && retryRemainingCount > 0) {
#endif
retryRemainingCount--;
}; // send() returns false if all tx buffers in use (can't enqueue, try again later)
// delay(20); // 2016-07-12: try to get fewer transfer errors using a small delay
#ifdef DEBUG
Serial.printf("Sending the bytes\n");
#endif
ubpTxBufferSentByteCount += TX_CHUNK_SIZE;
} else { // Only partial TX buffer remaining to send
// Send is queued (the ble stack delays send to the start of the next tx window)
#ifdef SIMBLEE
while ( SimbleeBLE.send(nextByteToSend, remainingByteCount) == false && hostIsConnected && retryRemainingCount > 0) {#
#else
while ( RFduinoBLE.send(nextByteToSend, remainingByteCount) == false && hostIsConnected && retryRemainingCount > 0) {
#endif
retryRemainingCount--;
}; // send() returns false if all tx buffers in use (can't enqueue, try again later)
ubpTxBufferSentByteCount += remainingByteCount;
// delay(20); // 2016-07-12: try to get fewer transfer errors using a small delay
#ifdef DEBUG
Serial.printf("Sending the remaining bytes\n");
#endif
UBP_isTxPending = false;
}
}
}
}
void _UBP_ingestRxBytes(char *receivedBytes, int byteCount) {
Serial.print(byteCount);
Serial.println(" bytes receieved");
// NOTE: Assuming not called unless len > 0
// Determine what to do with incoming fragment
if ( *receivedBytes == END_BYTE ) { // Fragment has leading END byte, signals start of packet
// Set fragment as the beginning of the reconstruction buffer
memcpy(ubpRxBuffer, receivedBytes, byteCount);
ubpRxBufferLength = byteCount;
} else if (ubpRxBufferLength > 0) { // Already have fragments in the reconstruction buffer
// Append fragment to reconstruction buffer
memcpy(ubpRxBuffer + ubpRxBufferLength, receivedBytes, byteCount);
ubpRxBufferLength += byteCount;
}
// Check RX buffer for trailing END byte
if ( *(ubpRxBuffer + ubpRxBufferLength - 1) == END_BYTE) { // RX buffer ends with END byte, looks like packet is complete
byte firstNonControlIndex = 1;
byte escapedDataLength = ubpRxBufferLength - 2; // "- 2" for leading/trailing control chars
// Un-escape the incoming payload
ubpUnescapedRxBufferBufferLength = _UBP_makeUnEscapedCopy(ubpRxBuffer + firstNonControlIndex, escapedDataLength, ubpUnescapedRxBufferBuffer);
byte payloadDataLength = ubpUnescapedRxBufferBufferLength - 1; // -1 to account for checksum
// Calculate checksum over payload, i.e. all bytes except for last checksum byte)
char calculatedChecksum = CRC8(ubpUnescapedRxBufferBuffer, payloadDataLength * sizeof(byte));
// Extract embedded checksum value
char receivedChecksum = *(ubpUnescapedRxBufferBuffer + payloadDataLength); // NOTE: Omitting '-1' because checksum byte comes just after payloadDataLength
// Verify the checksum
if (calculatedChecksum == receivedChecksum) {
unsigned short packetIdentifier = *(ubpUnescapedRxBufferBuffer);
UBP_TxFlags txFlags = (UBP_TxFlags) *(ubpUnescapedRxBufferBuffer + PACKET_ID_SIZE);
if (UBP_receivedPacket) {
void *packetBuffer = (ubpUnescapedRxBufferBuffer + PACKET_ID_SIZE + 1); // skip +
UBP_receivedPacket(packetIdentifier, txFlags, packetBuffer);
}
} else {
Serial.println("Incoming packet checksum invalid");
// Reset
ubpRxBufferLength = 0;
ubpUnescapedRxBufferBufferLength = 0;
if (UBP_incomingChecksumFailed) {
UBP_incomingChecksumFailed();
}
}
} // else haven't RX'd final fragment yet, keep waiting
}
bool UBP_queuePacketTransmission(unsigned short packetIdentifier, UBP_TxFlags txFlags, const char *packetBytes, unsigned short byteCount) {
if (UBP_isTxPending) { // Preexisting transmission still in progress
#ifdef DEBUG
Serial.println("Could not queue packet because preexisting transmission is still in progress");
#endif
return false;
} else { // Ready to queue a new transmission
if (hostIsConnected == false) {
#ifdef DEBUG
Serial.println("Host not connected");
#endif
return false;
}
#ifdef DEBUG
Serial.println("ready to queue a new transmission");
#endif
ubpTxBufferLength = 0;
// Start off with the END_BYTE as required for SLIP
ubpTxBuffer[0] = END_BYTE;
ubpTxBufferLength++;
// Prepend the packet identifier
memcpy(ubpTxBuffer + ubpTxBufferLength, &packetIdentifier, sizeof(packetIdentifier)); // TODO: Escape the identifier
ubpTxBufferLength += sizeof(packetIdentifier);
// Append the transmission flags
ubpTxBuffer[ubpTxBufferLength] = txFlags;
ubpTxBufferLength++;
#ifdef DEBUG
Serial.println("appended the transmission flags");
#endif
// Copy the escaped contents of packetBytes into the TX buffer following the packet identifier
int escapedByteCount = _UBP_makeEscapedCopy(packetBytes, byteCount, ubpTxBuffer + ubpTxBufferLength, BUFFER_LENGTH);
#ifdef DEBUG
Serial.println("made escaped copy");
#endif
if (escapedByteCount != -1) { // Escaping succeeded
ubpTxBufferLength += escapedByteCount;
int payloadLength = ubpTxBufferLength - 1; // Length so far minus leading END byte //sizeof(packetIdentifier) + escapedByteCount; // +
// Calculate and append checksum
byte checksumValue = CRC8(ubpTxBuffer + 1, payloadLength); // Checksum over all payload bytes (minus the leading END byte, checksum, and trailing END byte)
*(ubpTxBuffer + ubpTxBufferLength) = checksumValue;
ubpTxBufferLength++;
// Append trailing END byte
*(ubpTxBuffer + ubpTxBufferLength) = END_BYTE;
ubpTxBufferLength++;
// Mark as ready to begin transmission
ubpTxBufferSentByteCount = 0;
UBP_isTxPending = true;
} else {
#ifdef DEBUG
Serial.println("Couldn't escape the content because it was going to overflow the output buffer");
#endif
return false; // Return false if we couldn't escape the content because it was going to overflow the output buffer
}
}
}
int _UBP_makeEscapedCopy(const char *inputBuffer, unsigned short inputBufferLength, char *outputBuffer, unsigned short outputBufferLength) {
unsigned int bytesCopied = 0;
const char *inputBytes = inputBuffer; // Cast here to avoid compiler warnings later
// 2016-07-24: changed i from char to int to avoid buffer over flow that had happend for more numbers larger than 256
//for (char i = 0; i < inputBufferLength; i++) { // For each byte to append
for (int i = 0; i < inputBufferLength; i++) { // For each byte to append
#ifdef DEBUG
Serial.printf("Input Buffer length is %d and i = %d\n", inputBufferLength, i);
#endif
// Escape any control characters. Refer to Serial Line IP (SLIP) spec.
char aByte = *(inputBytes + i);
if (aByte == ESCAPE_BYTE) { // Escape an ESCAPE_BYTE
#ifdef DEBUG
Serial.printf("%d:%x ESC\n", bytesCopied, aByte);
#endif
if (bytesCopied + 1 >= outputBufferLength) return -1; // Would overflow destination buffer
else {
*(outputBuffer + bytesCopied++) = ESCAPE_BYTE; // Write ESCAPE_BYTE to buffer and increment offset
*(outputBuffer + bytesCopied++) = ESCAPED_ESCAPE_BYTE; // Write escaped ESCAPE_BYTE to buffer and increment offset
}
} else if (aByte == END_BYTE) { // Escape an END_BYTE
#ifdef DEBUG
Serial.printf("%d:%x END\n", bytesCopied, aByte);
#endif
if (bytesCopied + 1 >= outputBufferLength) return -1; // Would overflow destination buffer
else {
*(outputBuffer + bytesCopied++) = ESCAPE_BYTE; // Write ESCAPE_BYTE to buffer and increment offset
*(outputBuffer + bytesCopied++) = ESCAPED_END_BYTE; // Write escaped END_BYTE to buffer and increment offset
}
} else { // Not a control character
#ifdef DEBUG
Serial.printf("%d:%x\n", bytesCopied, aByte);
#endif
if (bytesCopied >= outputBufferLength) return -1; // Would overflow destination buffer
else *(outputBuffer + bytesCopied++) = aByte; // Copy the unmolested byte to the buffer and increment offset
}
}
return bytesCopied;
}
int _UBP_makeUnEscapedCopy(const char *inputBuffer, unsigned short inputBufferLength, char *outputBuffer) {
bool done = false;
char * destinationBufferPtr = outputBuffer;
const char * sourceBufferPtr = inputBuffer;
// UNESCAPE END SEQUENCE
while (!done && (sourceBufferPtr - inputBuffer) < inputBufferLength) {
char * substringPtr = strstr(sourceBufferPtr, escapedEndSequence);
if (substringPtr == NULL) done = true;
else {
// Copy bytes between last-copied byte and next escape byte
char lengthToCopy = substringPtr - sourceBufferPtr; // How many bytes between last byte copied and next escape byte
memcpy(destinationBufferPtr, sourceBufferPtr, lengthToCopy);
destinationBufferPtr += lengthToCopy;
sourceBufferPtr += lengthToCopy;
// Replace escaped source sequence with unescaped version during copy, increment pointer
memcpy(destinationBufferPtr, endSequence, sizeof(endSequence));
destinationBufferPtr += sizeof(endSequence);
// Increment pointer past escaped end sequence
sourceBufferPtr += sizeof(escapedEndSequence);
}
}
// UNESCAPE ESCAPE SEQUENCE
done = false;
while (!done && (sourceBufferPtr - inputBuffer) < inputBufferLength) {
char * substringPtr = strstr(sourceBufferPtr, escapedEscapeSequence);
if (substringPtr == NULL) done = true;
else {
// Copy bytes between last-copied byte and next escape byte
char lengthToCopy = substringPtr - sourceBufferPtr; // How many bytes between last byte copied and next escape byte
memcpy(destinationBufferPtr, sourceBufferPtr, lengthToCopy);
destinationBufferPtr += lengthToCopy;
sourceBufferPtr += lengthToCopy;
// Replace escaped source sequence with unescaped version during copy, increment pointer
memcpy(destinationBufferPtr, escapeSequence, sizeof(escapeSequence));
destinationBufferPtr += sizeof(escapeSequence);
// Increment pointer past escaped end sequence
sourceBufferPtr += sizeof(escapedEscapeSequence);
}
}
// COPY ANY TRAILING BYTES
char lengthToCopy = (inputBuffer + inputBufferLength) - sourceBufferPtr; // How many bytes remain to be copied
memcpy(destinationBufferPtr, sourceBufferPtr, lengthToCopy);
destinationBufferPtr += lengthToCopy;
sourceBufferPtr += lengthToCopy;
return (destinationBufferPtr - outputBuffer); // Return the total number of bytes copied to the destination buffer
}
void _UBP_hostDisconnected() {
hostIsConnected = false;
// Reset TX subsystem
UBP_isTxPending = false;
// Reset RX subsystem
ubpRxBufferLength = 0;
// Invoke user callback
if (UBP_didDisconnect) UBP_didDisconnect();
}
// Simblee/RFduino EVENTS
// ----------------------------------------------------
#ifdef SIMBLEE
void SimbleeBLE_onAdvertisement(bool start) {
if (UBP_didAdvertise) UBP_didAdvertise(start);
}
void SimbleeBLE_onConnect() {
hostIsConnected = true;
if (UBP_didConnect) UBP_didConnect();
}
void SimbleeBLE_onReceive(char *data, int len) {
_UBP_ingestRxBytes(data, len);
}
void SimbleeBLE_onDisconnect() {
_UBP_hostDisconnected();
}
#else
void RFduinoBLE_onAdvertisement(bool start) {
if (UBP_didAdvertise) UBP_didAdvertise(start);
}
void RFduinoBLE_onConnect() {
hostIsConnected = true;
if (UBP_didConnect) UBP_didConnect();
}
void RFduinoBLE_onReceive(char *data, int len) {
_UBP_ingestRxBytes(data, len);
}
void RFduinoBLE_onDisconnect() {
_UBP_hostDisconnected();
}
#endif