Compare commits

...

1 commit

Author SHA1 Message Date
Trev_Knows
f6d10619ff Version 1.1.0
- Puppet Module
2020-02-04 14:25:18 -05:00
71 changed files with 1505 additions and 2502 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View file

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
2981E3B523C6526C00974EA2 /* Sparky_Gold1.dae in Resources */ = {isa = PBXBuildFile; fileRef = 2981E3B423C6526C00974EA2 /* Sparky_Gold1.dae */; };
A901EC48237C43C000687BE6 /* Data+LittleEndianTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A901EC47237C43C000687BE6 /* Data+LittleEndianTypes.swift */; };
A901EC4D237C47D800687BE6 /* CPBDataSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = A901EC4C237C47D800687BE6 /* CPBDataSeries.swift */; };
A92B9F0D234DE4A2002374F0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92B9F0C234DE4A2002374F0 /* AppDelegate.swift */; };
@ -177,16 +178,6 @@
A9BED77A234FBEAB002FFF53 /* CommandQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BED763234FBEAB002FFF53 /* CommandQueue.swift */; };
A9BED77B234FBEAB002FFF53 /* LogHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BED764234FBEAB002FFF53 /* LogHelper.swift */; };
A9BED77D234FBEAB002FFF53 /* Data+ScanValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BED766234FBEAB002FFF53 /* Data+ScanValue.swift */; };
A9D12E8423E3661800F3C259 /* UIColor+Interpolate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D12E8323E3661800F3C259 /* UIColor+Interpolate.swift */; };
A9D12E8623E3C5CF00F3C259 /* LightChartPanelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D12E8523E3C5CF00F3C259 /* LightChartPanelViewController.swift */; };
A9D12E8723E3C5CF00F3C259 /* LightChartPanelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D12E8523E3C5CF00F3C259 /* LightChartPanelViewController.swift */; };
A9D12E8823E4434000F3C259 /* UIColor+Interpolate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D12E8323E3661800F3C259 /* UIColor+Interpolate.swift */; };
A9D5B21023E0985600178E1B /* AutoConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D5B20F23E0985600178E1B /* AutoConnectViewController.swift */; };
A9D5B21123E0985600178E1B /* AutoConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D5B20F23E0985600178E1B /* AutoConnectViewController.swift */; };
A9D5B21323E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D5B21223E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift */; };
A9D5B21423E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D5B21223E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift */; };
A9D5B21623E3024700178E1B /* BluetoothStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D5B21523E3024700178E1B /* BluetoothStatusViewController.swift */; };
A9D5B21723E3024800178E1B /* BluetoothStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D5B21523E3024700178E1B /* BluetoothStatusViewController.swift */; };
A9DF06122359B0600094327F /* ButtonStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DF06112359B0600094327F /* ButtonStatusViewController.swift */; };
A9DF06142359B31C0094327F /* ButtonStatusPanelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DF06132359B31C0094327F /* ButtonStatusPanelViewController.swift */; };
A9E4C02E2358E6B400B5A493 /* UIColor+LightAndDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E4C02D2358E6B400B5A493 /* UIColor+LightAndDark.swift */; };
@ -222,6 +213,7 @@
/* Begin PBXFileReference section */
02A29BC776CC7DA5C23560A7 /* Pods-BluefruitPlayground.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BluefruitPlayground.release.xcconfig"; path = "Target Support Files/Pods-BluefruitPlayground/Pods-BluefruitPlayground.release.xcconfig"; sourceTree = "<group>"; };
02C55600113CD967F7C0C908 /* Pods-CPX.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CPX.debug.xcconfig"; path = "Target Support Files/Pods-CPX/Pods-CPX.debug.xcconfig"; sourceTree = "<group>"; };
2981E3B423C6526C00974EA2 /* Sparky_Gold1.dae */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml.dae; path = Sparky_Gold1.dae; sourceTree = "<group>"; };
3348DDE33D89A5959702CAC9 /* Pods-BluefruitPlayground.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BluefruitPlayground.debug.xcconfig"; path = "Target Support Files/Pods-BluefruitPlayground/Pods-BluefruitPlayground.debug.xcconfig"; sourceTree = "<group>"; };
41FC984EE1CDB9B54C5084ED /* Pods-CPX.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CPX.release.xcconfig"; path = "Target Support Files/Pods-CPX/Pods-CPX.release.xcconfig"; sourceTree = "<group>"; };
8BFE7B5A63DA548F17D0945D /* Pods_BluefruitPlayground.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BluefruitPlayground.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -318,11 +310,6 @@
A9BED763234FBEAB002FFF53 /* CommandQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandQueue.swift; sourceTree = "<group>"; };
A9BED764234FBEAB002FFF53 /* LogHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogHelper.swift; sourceTree = "<group>"; };
A9BED766234FBEAB002FFF53 /* Data+ScanValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+ScanValue.swift"; sourceTree = "<group>"; };
A9D12E8323E3661800F3C259 /* UIColor+Interpolate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Interpolate.swift"; sourceTree = "<group>"; };
A9D12E8523E3C5CF00F3C259 /* LightChartPanelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightChartPanelViewController.swift; sourceTree = "<group>"; };
A9D5B20F23E0985600178E1B /* AutoConnectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoConnectViewController.swift; sourceTree = "<group>"; };
A9D5B21223E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveLabelIntrinsicSizeFix.swift; sourceTree = "<group>"; };
A9D5B21523E3024700178E1B /* BluetoothStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothStatusViewController.swift; sourceTree = "<group>"; };
A9DF06112359B0600094327F /* ButtonStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStatusViewController.swift; sourceTree = "<group>"; };
A9DF06132359B31C0094327F /* ButtonStatusPanelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStatusPanelViewController.swift; sourceTree = "<group>"; };
A9E4C02D2358E6B400B5A493 /* UIColor+LightAndDark.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+LightAndDark.swift"; sourceTree = "<group>"; };
@ -491,7 +478,6 @@
children = (
A986AB92235942D600DF12C8 /* LightSensorViewController.swift */,
A98F1B8A23594BE400C5269E /* LightSensorPanelViewController.swift */,
A9D12E8523E3C5CF00F3C259 /* LightChartPanelViewController.swift */,
);
path = LightSensor;
sourceTree = "<group>";
@ -500,6 +486,7 @@
isa = PBXGroup;
children = (
A98E4D3123566785005B166D /* cpb.scn */,
2981E3B423C6526C00974EA2 /* Sparky_Gold1.dae */,
A96AA5BA235F903D004A06BA /* CPB_PcbSurface_Color.png */,
);
path = models3d;
@ -546,8 +533,6 @@
A9BED700234F754B002FFF53 /* Utils */ = {
isa = PBXGroup;
children = (
A99BA722235000520080D03F /* ScreenFlowManager.swift */,
A9BED70C234F76C5002FFF53 /* LocalizationManager.swift */,
A9716BA92350128100CB174D /* RssiUI.swift */,
A96AA5D2236358A8004A06BA /* UIView+RoundedCorners.swift */,
A96AA5D423635A89004A06BA /* NSLayoutConstraint+ChangeMultiplier.swift */,
@ -558,10 +543,11 @@
A96AA5C0236050E4004A06BA /* NavigationBarWithScrollAwareRightButton.swift */,
A9BED701234F754B002FFF53 /* GradientView.swift */,
A96AA5D023634F03004A06BA /* GridView.swift */,
A9BED70C234F76C5002FFF53 /* LocalizationManager.swift */,
A99BA722235000520080D03F /* ScreenFlowManager.swift */,
A9B3569E2365D420002093EE /* PanelInsetView.swift */,
A958E77D238B7457006A225A /* NormalBrightnessSliderControl.swift */,
A958E77F238B75D9006A225A /* NormalBrightnessColorPickerController.swift */,
A9D12E8323E3661800F3C259 /* UIColor+Interpolate.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -633,19 +619,10 @@
A9BED766234FBEAB002FFF53 /* Data+ScanValue.swift */,
A901EC47237C43C000687BE6 /* Data+LittleEndianTypes.swift */,
A9BDEC40237C75C900EDDC46 /* Types+Data.swift */,
A9D5B21223E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift */,
);
path = Utils;
sourceTree = "<group>";
};
A9D5B20E23E0983F00178E1B /* AutoConnect */ = {
isa = PBXGroup;
children = (
A9D5B20F23E0985600178E1B /* AutoConnectViewController.swift */,
);
path = AutoConnect;
sourceTree = "<group>";
};
A9DF06102359B04C0094327F /* ButtonStatus */ = {
isa = PBXGroup;
children = (
@ -692,10 +669,8 @@
isa = PBXGroup;
children = (
A9BED716234FBDBA002FFF53 /* StartupViewController.swift */,
A9D5B21523E3024700178E1B /* BluetoothStatusViewController.swift */,
A9482988237B73B100B6FC13 /* Common */,
A9BED703234F75E4002FFF53 /* Tips */,
A9D5B20E23E0983F00178E1B /* AutoConnect */,
A9EAA18D2350B26B00FA615E /* Scanner */,
A9EAA1982351F1DD00FA615E /* Home */,
A9EAA19F2352973400FA615E /* Neopixels */,
@ -826,6 +801,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2981E3B523C6526C00974EA2 /* Sparky_Gold1.dae in Resources */,
A98E4D3223566785005B166D /* cpb.scn in Resources */,
A92B9F19234DE4A5002374F0 /* LaunchScreen.storyboard in Resources */,
A99BA71F234FFECF0080D03F /* DefaultPreferences.plist in Resources */,
@ -957,7 +933,6 @@
A92B9F0D234DE4A2002374F0 /* AppDelegate.swift in Sources */,
A96AA5C6236312CE004A06BA /* ToneGeneratorViewController.swift in Sources */,
A9BED77D234FBEAB002FFF53 /* Data+ScanValue.swift in Sources */,
A9D12E8623E3C5CF00F3C259 /* LightChartPanelViewController.swift in Sources */,
A901EC48237C43C000687BE6 /* Data+LittleEndianTypes.swift in Sources */,
A980FE6D2357D14400E3A909 /* TipPowerUpViewController.swift in Sources */,
A94AC14E2364A82B0062AB11 /* CPBBle.swift in Sources */,
@ -990,7 +965,6 @@
A958E782238B7936006A225A /* BlePeripheral+CPBAccelerometer.swift in Sources */,
A9DF06122359B0600094327F /* ButtonStatusViewController.swift in Sources */,
A96AA5D3236358A8004A06BA /* UIView+RoundedCorners.swift in Sources */,
A9D5B21023E0985600178E1B /* AutoConnectViewController.swift in Sources */,
A98E4D2823565B76005B166D /* TipWelcomeViewController.swift in Sources */,
A9BED70B234F7617002FFF53 /* Config.swift in Sources */,
A980FE6F2357D15400E3A909 /* TipDiscoverViewController.swift in Sources */,
@ -1006,7 +980,6 @@
A98F1B8B23594BE400C5269E /* LightSensorPanelViewController.swift in Sources */,
A96AA5C32362F488004A06BA /* AboutViewController.swift in Sources */,
A9A08BAF23A7957700498069 /* BlePeripheral+CPBButtons.swift in Sources */,
A9D12E8423E3661800F3C259 /* UIColor+Interpolate.swift in Sources */,
A98F1B892359494500C5269E /* ConfigUI.swift in Sources */,
A9BED773234FBEAB002FFF53 /* UartPacketManager.swift in Sources */,
A986AB93235942D600DF12C8 /* LightSensorViewController.swift in Sources */,
@ -1025,8 +998,6 @@
A9EAA1A3235297B400FA615E /* NeopixelsLightSequenceViewController.swift in Sources */,
A901EC4D237C47D800687BE6 /* CPBDataSeries.swift in Sources */,
A99BA71D234FFE780080D03F /* Settings.swift in Sources */,
A9D5B21323E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift in Sources */,
A9D5B21623E3024700178E1B /* BluetoothStatusViewController.swift in Sources */,
A9BED775234FBEAB002FFF53 /* BlePeripheral+Battery.swift in Sources */,
A94AC1482363A7BD0062AB11 /* TemperaturePanelViewController.swift in Sources */,
A9EAA1912350B26B00FA615E /* ScannerViewController.swift in Sources */,
@ -1047,7 +1018,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A9D12E8723E3C5CF00F3C259 /* LightChartPanelViewController.swift in Sources */,
A9A08B3923A523C900498069 /* Int+Bytes.swift in Sources */,
A9A08B3A23A523C900498069 /* UIView+AllSubviewsWithClass.swift in Sources */,
A9A08BAD23A794F500498069 /* BlePeripheralSimulated+CPBLight.swift in Sources */,
@ -1063,7 +1033,6 @@
A9A08B4523A523C900498069 /* Data+ScanValue.swift in Sources */,
A9A08B4623A523C900498069 /* Data+LittleEndianTypes.swift in Sources */,
A9A08B4723A523C900498069 /* TipPowerUpViewController.swift in Sources */,
A9D12E8823E4434000F3C259 /* UIColor+Interpolate.swift in Sources */,
A9A08B4823A523C900498069 /* CPBBle.swift in Sources */,
A9A08B4923A523C900498069 /* NavigationBarWithScrollAwareRightButton.swift in Sources */,
A9A08B4B23A523C900498069 /* UartPacketManagerBase.swift in Sources */,
@ -1081,7 +1050,6 @@
A9A08B5723A523C900498069 /* GridView.swift in Sources */,
A9A08B5823A523C900498069 /* BlePeripheral+Uart.swift in Sources */,
A9A08B5923A523C900498069 /* BlePeripheral+CPBCommon.swift in Sources */,
A9D5B21123E0985600178E1B /* AutoConnectViewController.swift in Sources */,
A9A08B5A23A523C900498069 /* BlePeripheral+CPBToneGenerator.swift in Sources */,
A9A08B5B23A523C900498069 /* RssiUI.swift in Sources */,
A9A08B5C23A523C900498069 /* HelpViewController.swift in Sources */,
@ -1098,7 +1066,6 @@
A9A08B6823A523C900498069 /* TipWelcomeViewController.swift in Sources */,
A9A08BA423A5286700498069 /* BlePeripheralSimulated.swift in Sources */,
A9A08B6923A523C900498069 /* Config.swift in Sources */,
A9D5B21423E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift in Sources */,
A9A08B6A23A523C900498069 /* TipDiscoverViewController.swift in Sources */,
A9A08B6B23A523C900498069 /* ModulePanelViewController.swift in Sources */,
A9A08B6C23A523C900498069 /* TipsViewController.swift in Sources */,
@ -1113,7 +1080,6 @@
A9A08B7323A523C900498069 /* BleManager.swift in Sources */,
A9A08B7423A523C900498069 /* LightSensorPanelViewController.swift in Sources */,
A9A08B7523A523C900498069 /* AboutViewController.swift in Sources */,
A9D5B21723E3024800178E1B /* BluetoothStatusViewController.swift in Sources */,
A9A08BAA23A7891500498069 /* ObjectBuilder.m in Sources */,
A9A08B7623A523C900498069 /* ConfigUI.swift in Sources */,
A9A08B7723A523C900498069 /* UartPacketManager.swift in Sources */,
@ -1303,7 +1269,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 8;
CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = 2X94RM7457;
INFOPLIST_FILE = BluefruitPlayground/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
@ -1311,13 +1277,13 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.0;
MARKETING_VERSION = 1.1.1;
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
PRODUCT_NAME = "Bluefruit Playground";
SWIFT_OBJC_BRIDGING_HEADER = "BluefruitPlayground/BluefruitPlayground-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@ -1328,7 +1294,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 8;
CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = 2X94RM7457;
INFOPLIST_FILE = BluefruitPlayground/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
@ -1336,12 +1302,12 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.0;
MARKETING_VERSION = 1.1.1;
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
PRODUCT_NAME = "Bluefruit Playground";
SWIFT_OBJC_BRIDGING_HEADER = "BluefruitPlayground/BluefruitPlayground-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@ -1392,7 +1358,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 8;
CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = 2X94RM7457;
INFOPLIST_FILE = "BluefruitPlayground-SimulatedBluetooth-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
@ -1400,7 +1366,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.0;
MARKETING_VERSION = 1.1.2;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D SIMULATEBLUETOOTH";
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1418,7 +1384,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 8;
CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = 2X94RM7457;
INFOPLIST_FILE = "BluefruitPlayground-SimulatedBluetooth-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
@ -1426,7 +1392,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.0;
MARKETING_VERSION = 1.1.2;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D SIMULATEBLUETOOTH";
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
PRODUCT_NAME = "$(TARGET_NAME)";

BIN
BluefruitPlayground/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -27,15 +27,8 @@ class BleManager: NSObject {
private var centralManagerPoweredOnSemaphore = DispatchSemaphore(value: 1)
// Scanning
var isScanning: Bool {
return scanningStartTime != nil
}
var scanningElapsedTime: TimeInterval? {
guard let scanningStartTime = scanningStartTime else { return nil }
return CACurrentMediaTime() - scanningStartTime
}
var isScanning = false
private var isScanningWaitingToStart = false
internal var scanningStartTime: TimeInterval? // Time when the scanning started. nil if stopped
private var scanningServicesFilter: [CBUUID]?
internal var peripheralsFound = [UUID: BlePeripheral]()
private var peripheralsFoundLock = NSLock()
@ -121,7 +114,7 @@ class BleManager: NSObject {
}
// DLog("start scan")
scanningStartTime = CACurrentMediaTime()
isScanning = true
NotificationCenter.default.post(name: .didStartScanning, object: nil)
let options = BleManager.kAlwaysAllowDuplicateKeys ? [CBCentralManagerScanOptionAllowDuplicatesKey: true] : nil
@ -132,7 +125,7 @@ class BleManager: NSObject {
func stopScan() {
// DLog("stop scan")
centralManager?.stopScan()
scanningStartTime = nil
isScanning = false
isScanningWaitingToStart = false
NotificationCenter.default.post(name: .didStopScanning, object: nil)
}
@ -218,19 +211,11 @@ class BleManager: NSObject {
}
}
func disconnect(from peripheral: BlePeripheral, waitForQueuedCommands: Bool = false) {
guard let centralManager = centralManager else { return}
func disconnect(from peripheral: BlePeripheral) {
DLog("disconnect")
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
if waitForQueuedCommands {
// Send the disconnection to the command queue, so all the previous command are executed before disconnecting
peripheral.disconnect(centralManager: centralManager)
}
else {
centralManager.cancelPeripheralConnection(peripheral.peripheral)
}
centralManager?.cancelPeripheralConnection(peripheral.peripheral)
}
func reconnecToPeripherals(withIdentifiers identifiers: [UUID], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
@ -314,10 +299,7 @@ extension BleManager: CBCentralManagerDelegate {
if isScanning {
isScanningWaitingToStart = true
}
scanningStartTime = nil
// Remove all peripherals found (Important because the BlePeripheral queues could contain old commands that were processing when the bluetooth state changed)
peripheralsFound.removeAll()
isScanning = false
}
NotificationCenter.default.post(name: .didUpdateBleState, object: nil)

View file

@ -206,12 +206,6 @@ class BlePeripheral: NSObject {
let command = BleCommand(type: .discoverDescriptor, parameters: [characteristic], completion: completion)
commandQueue.append(command)
}
// MARK: - Connection
func disconnect(centralManager: CBCentralManager) {
let command = BleCommand(type: .disconnect, parameters: [centralManager], completion: nil)
commandQueue.append(command)
}
// MARK: - Service
func discoveredService(uuid: CBUUID) -> CBService? {
@ -325,7 +319,6 @@ class BlePeripheral: NSObject {
case writeCharacteristic
case writeCharacteristicAndWaitNofity
case readDescriptor
case disconnect
}
enum CommandError: Error {
@ -369,8 +362,6 @@ class BlePeripheral: NSObject {
write(with: command)
case .readDescriptor:
readDescriptor(with: command)
case .disconnect:
disconnect(with: command)
}
}
@ -500,12 +491,6 @@ class BlePeripheral: NSObject {
peripheral.readValue(for: descriptor)
}
private func disconnect(with command: BleCommand) {
let centralManager = command.parameters!.first as! CBCentralManager
centralManager.cancelPeripheralConnection(self.peripheral)
finishedExecutingCommand(error: nil)
}
}
// MARK: - CBPeripheralDelegate

View file

@ -47,8 +47,7 @@ extension BlePeripheral {
// MARK: - Actions
func cpbButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
let timePeriod: TimeInterval = 0 // 0 means that the responseHandler will be called only when there is a change
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBButtonsServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBButtonsCharacteristicUUID, timePeriod: timePeriod, responseHandler: { response in
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBButtonsServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBButtonsCharacteristicUUID, timePeriod: 0, responseHandler: { response in
switch response {
case let .success((data, uuid)):
@ -68,21 +67,7 @@ extension BlePeripheral {
}
self.cpbButtonsCharacteristic = characteristic
if timePeriod == 0 { // Read initial state if the timePeriod is 0 (update only when changed)
CPBBle.shared.buttonsReadState { response in
switch response {
case .success(_, _):
completion?(.success(()))
case .failure(let error):
DLog("Error receiving initial button state data: \(error)")
completion?(.failure(error))
}
}
}
else {
completion?(.success(()))
}
completion?(.success(()))
case let .failure(error):
self.cpbButtonsCharacteristic = nil

View file

@ -14,7 +14,7 @@ extension BlePeripheral {
static let kCPBLightServiceUUID = CBUUID(string: "ADAF0300-C332-42A8-93BD-25E905756CB8")
private static let kCPBLightCharacteristicUUID = CBUUID(string: "ADAF0301-C332-42A8-93BD-25E905756CB8")
private static let kCPBLightDefaultPeriod: TimeInterval = 0.1
static let kCPBLightDefaultPeriod: TimeInterval = 0.1
// MARK: - Custom properties
private struct CustomPropertiesKeys {

View file

@ -14,8 +14,6 @@ extension BlePeripheral {
static let kCPBTemperatureServiceUUID = CBUUID(string: "ADAF0100-C332-42A8-93BD-25E905756CB8")
private static let kCPBTemperatureCharacteristicUUID = CBUUID(string: "ADAF0101-C332-42A8-93BD-25E905756CB8")
private static let kCPBTemperatureDefaultPeriod: TimeInterval = 0.1
// MARK: - Custom properties
private struct CustomPropertiesKeys {
static var cpbTemperatureCharacteristic: CBCharacteristic?
@ -33,7 +31,7 @@ extension BlePeripheral {
// MARK: - Actions
func cpbTemperatureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBTemperatureServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBTemperatureCharacteristicUUID, timePeriod: BlePeripheral.kCPBTemperatureDefaultPeriod, responseHandler: { response in
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBTemperatureServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBTemperatureCharacteristicUUID, timePeriod: 0.5, responseHandler: { response in
switch response {
case let .success((data, uuid)):

View file

@ -6,7 +6,7 @@
// Copyright © 2019 Adafruit. All rights reserved.
//
import UIKit
import Foundation
import CoreBluetooth
class BleManagerSimulated: BleManager {
@ -20,7 +20,7 @@ class BleManagerSimulated: BleManager {
}
override func startScan(withServices services: [CBUUID]? = nil) {
scanningStartTime = CACurrentMediaTime()
isScanning = true
// Add simulated peripheral
let simulatedBlePeripheral = BlePeripheralSimulated()

View file

@ -1,16 +0,0 @@
//
// ActiveLabelIntrinsicSizeFix.swift
// BluefruitPlayground
//
// Created by Antonio García on 30/01/2020.
// Copyright © 2020 Adafruit. All rights reserved.
//
import ActiveLabel
// Fix as recommended here: https://github.com/optonaut/ActiveLabel.swift/issues/312
extension ActiveLabel {
open override var intrinsicContentSize: CGSize {
return super.intrinsicContentSize
}
}

View file

@ -0,0 +1,22 @@
//
// TouchReleaseRectangularPaletteControl.swift
// BluefruitPlayground
//
// Created by Antonio García on 16/12/2019.
// Copyright © 2019 Adafruit. All rights reserved.
//
import UIKit
import FlexColorPicker
class TouchReleaseRectangularPaletteControl: RectangularPaletteControl {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}

View file

@ -10,42 +10,38 @@ import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
startup()
ScreenFlowManager.enableBleStateManagement()
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.
}
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.
}
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.
}
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.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
ScreenFlowManager.disableBleStateManagement()
}
// MARK: - Startup
private func startup() {
// Settings

View file

@ -1,6 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "scan_cpb.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "scan_cpb@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "scan_cpb@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

View file

@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "scanning_background.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "scanning_background@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "scanning_background@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

View file

@ -1,6 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "bluetooth_status.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "bluetooth_status@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "bluetooth_status@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 996 B

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -7,12 +7,12 @@
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"color-space" : "display-p3",
"components" : {
"red" : "0.949",
"red" : "0.010",
"alpha" : "1.000",
"blue" : "0.961",
"green" : "0.961"
"blue" : "0.802",
"green" : "0.219"
}
}
}

View file

@ -1,20 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "display-p3",
"components" : {
"red" : "0.889",
"alpha" : "0.800",
"blue" : "0.360",
"green" : "0.400"
}
}
}
]
}

File diff suppressed because it is too large Load diff

View file

@ -12,13 +12,10 @@ struct Config {
// Debug-----------------------------------------------------------------------------
static let isDebugEnabled = _isDebugAssertConfiguration()
// Fastlane snapshots
private static let areFastlaneSnapshotsRunning = UserDefaults.standard.bool(forKey: "FASTLANE_SNAPSHOT")
// Bluetooth
#if SIMULATEBLUETOOTH
static let isTutorialEnabled = areFastlaneSnapshotsRunning || !isDebugEnabled
static let isTutorialEnabled = !isDebugEnabled
static let isBleUnsupportedWarningEnabled = false
static let bleManager = BleManagerSimulated.simulated
#else

View file

@ -28,6 +28,10 @@
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
<key>NSCameraUsageDescription</key>
<string>This module requires the Camera to function</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This module reqires library access to store your photos</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>

BIN
BluefruitPlayground/Model/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -30,7 +30,8 @@ class CPBBle {
// Constants
private static let kLightSequenceFramesPerSecond = 10
private static let kLightSequenceDefaultBrightness: CGFloat = 0.25
public static let kLightSequenceDefaultSpeed: Double = 0.3
private static let kLightSequenceDefaultSpeed: Double = 0.3
// Singleton
static let shared = CPBBle()
@ -51,30 +52,25 @@ class CPBBle {
weak var buttonsDelegate: CPBBleButtonsDelegate?
weak var accelerometerDelegate: CPBBleAccelerometerDelegate?
var neopixelLightSequenceAnimationBrightness: CGFloat = CPBBle.kLightSequenceDefaultBrightness
var neopixelLightSequenceAnimationSpeed: Double = CPBBle.kLightSequenceDefaultSpeed {
didSet {
lightSequenceAnimation?.speed = neopixelLightSequenceAnimationSpeed
}
}
// Data
private var temperatureData = CPBDataSeries<Float>()
private var lightData = CPBDataSeries<Float>()
private var accelerometerData = CPBDataSeries<BlePeripheral.AccelerometerValue>()
private weak var blePeripheral: BlePeripheral?
private var currentLightSequenceAnimation: LightSequenceAnimation?
public var neopixelCurrentLightSequenceAnimationSpeed: Double {
get {
return currentLightSequenceAnimation?.speed ?? 0
}
set {
currentLightSequenceAnimation?.speed = newValue
}
}
private var lightSequenceAnimation: LightSequenceAnimation?
// MARK: - Lifecycle
private init() {
registerNotifications(enabled: true)
}
deinit {
registerNotifications(enabled: false)
}
// MARK: - Setup
@ -192,11 +188,7 @@ class CPBBle {
func accelerometerLastValue() -> BlePeripheral.AccelerometerValue? {
return blePeripheral?.cpbAccelerometerLastValue()
}
func lightDataSeries() -> [CPBDataSeries<Float>.Entry] {
return lightData.values
}
func temperatureDataSeries() -> [CPBDataSeries<Float>.Entry] {
return temperatureData.values
}
@ -257,9 +249,36 @@ class CPBBle {
private func receiveButtonsData(response: Result<(BlePeripheral.ButtonsState, UUID), Error>) {
switch response {
case let .success(buttonsState, uuid):
DLog("Buttons: \(buttonsState.slideSwitch == .left ? "⬅️":"➡️") \(buttonsState.buttonA == .pressed ? "🔳":"🔲") \(buttonsState.buttonB == .pressed ? "🔳":"🔲") ")
// NotificationCenter.default.post(name:NSNotification.Name(rawValue: "Command"), object: nil)
if buttonsState.buttonA == .pressed{
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "EmoteOne"), object: nil)
}
if buttonsState.buttonB == .pressed{
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "EmoteTwo"), object: nil)
}
if buttonsState.slideSwitch == .right{
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "EmoteFour"), object: nil)
}
if buttonsState.slideSwitch == .left{
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "EmoteThree"), object: nil)
}
// Send to delegate
if let buttonsDelegate = buttonsDelegate {
DispatchQueue.main.async { // Delegates are called in the main thread
@ -273,9 +292,15 @@ class CPBBle {
NotificationUserInfoKey.uuid.rawValue: uuid,
])
case .failure(let error):
DLog("Error receiving light data: \(error)")
}
}
private func receiveAccelerometerData(response: Result<(BlePeripheral.AccelerometerValue, UUID), Error>) {
@ -323,26 +348,19 @@ class CPBBle {
blePeripheral?.cpbPixelSetColor(index: 0, color: color, pixelMask: pixelMask)
}
func neopixelStartLightSequence(_ lightSequenceGenerator: LightSequenceGenerator,
framesPerSecond: Int = CPBBle.kLightSequenceFramesPerSecond,
speed: Double = CPBBle.kLightSequenceDefaultSpeed,
brightness: CGFloat = CPBBle.kLightSequenceDefaultBrightness,
repeating: Bool = true,
sendLightSequenceNotifications: Bool = true) {
func neopixelStartLightSequence(_ lightSequenceGenerator: LightSequenceGenerator) {
neopixelStopLightSequence()
currentLightSequenceAnimation = LightSequenceAnimation(lightSequenceGenerator: lightSequenceGenerator, framesPerSecond: framesPerSecond, repeating: repeating)
currentLightSequenceAnimation!.speed = speed
currentLightSequenceAnimation!.start(stopHandler: { [weak self] in
self?.blePeripheral?.cpbPixelSetAllPixelsColor(.clear)
}) { [weak self] pixelsBytes in
lightSequenceAnimation = LightSequenceAnimation(lightSequenceGenerator: lightSequenceGenerator, framesPerSecond: CPBBle.kLightSequenceFramesPerSecond)
lightSequenceAnimation!.speed = neopixelLightSequenceAnimationSpeed
lightSequenceAnimation!.start() { [weak self] pixelsBytes in
guard let self = self else { return }
guard let blePeripheral = self.blePeripheral else { return }
let pixelBytesAdjustingBrightness = pixelsBytes.map {[
UInt8(CGFloat($0[0]) * brightness),
UInt8(CGFloat($0[1]) * brightness),
UInt8(CGFloat($0[2]) * brightness),
UInt8(CGFloat($0[0]) * self.neopixelLightSequenceAnimationBrightness),
UInt8(CGFloat($0[1]) * self.neopixelLightSequenceAnimationBrightness),
UInt8(CGFloat($0[2]) * self.neopixelLightSequenceAnimationBrightness),
]}
let lightData = pixelBytesAdjustingBrightness.reduce(Data()) { (data, element) in
@ -351,35 +369,16 @@ class CPBBle {
blePeripheral.cpbPixelsWriteData(offset: 0, pixelData: lightData)
// Send notification
if sendLightSequenceNotifications {
NotificationCenter.default.post(name: .didUpdateNeopixelLightSequence, object: nil, userInfo: [
NotificationUserInfoKey.value.rawValue: pixelsBytes,
NotificationUserInfoKey.uuid.rawValue: blePeripheral.identifier,
])
}
NotificationCenter.default.post(name: .didUpdateNeopixelLightSequence, object: nil, userInfo: [
NotificationUserInfoKey.value.rawValue: pixelsBytes,
NotificationUserInfoKey.uuid.rawValue: blePeripheral.identifier,
])
}
}
func neopixelStopLightSequence() {
currentLightSequenceAnimation?.stop()
currentLightSequenceAnimation = nil
}
// MARK: - BLE Notifications
private weak var willdDisconnectFromPeripheralObserver: NSObjectProtocol?
private func registerNotifications(enabled: Bool) {
let notificationCenter = NotificationCenter.default
if enabled {
willdDisconnectFromPeripheralObserver = notificationCenter.addObserver(forName: .willDisconnectFromPeripheral, object: nil, queue: .main, using: {[weak self] notification in
// Force clear neopixels on disconnect
self?.neopixelSetAllPixelsColor(.clear)
})
} else {
if let willdDisconnectFromPeripheralObserver = willdDisconnectFromPeripheralObserver {notificationCenter.removeObserver(willdDisconnectFromPeripheralObserver)}
}
lightSequenceAnimation?.stop()
lightSequenceAnimation = nil
}
}

View file

@ -9,9 +9,8 @@
import UIKit
protocol LightSequenceGenerator {
var numFrames: Int { get }
var numPixels: Int { get }
var isCyclic: Bool { get }
func numFrames() -> Int
func numPixels() -> Int
func colorsForFrame(_ frame: Int) -> [[UInt8]]
}
@ -20,16 +19,12 @@ class LightSequence {
// static let kLightSequenceDefaultBrightness: CGFloat = 0.25
static let kNumPixels = 10
var numPixels: Int {
func numPixels() -> Int {
return LightSequence.kNumPixels
}
var isCyclic: Bool {
return true
}
// Utils
static func preprocessColorPalette(colors: [UIColor]) -> [[UInt8]] {
static func preprocessColorPalette(colors: [UIColor]/*, brightness: CGFloat*/) -> [[UInt8]] {
let colorsBytes = colors.map({ color -> [UInt8] in
let colorBytes = BlePeripheral.pixelUInt8FromColor(color)
return colorBytes
@ -51,16 +46,16 @@ class RotateLightSequence: LightSequence, LightSequenceGenerator {
// MARK: -
override init() {
colorsBytes = LightSequence.preprocessColorPalette(colors: RotateLightSequence.kColors)
colorsBytes = LightSequence.preprocessColorPalette(colors: RotateLightSequence.kColors/*, brightness: LightSequence.kLightSequenceDefaultBrightness*/)
super.init()
}
var numFrames: Int {
func numFrames() -> Int {
return RotateLightSequence.kNumFrames
}
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
return rotate(numPixels: numPixels, colors: colorsBytes, frame: frame)
return rotate(numPixels: numPixels(), colors: colorsBytes, frame: frame)
}
private func rotate(numPixels: Int, colors: [[UInt8]], frame: Int) -> [[UInt8]] {
@ -88,22 +83,22 @@ class PulseLightSequence: LightSequence, LightSequenceGenerator {
// MARK: -
override init() {
colorsBytes = LightSequence.preprocessColorPalette(colors: PulseLightSequence.kColors)
colorsBytes = LightSequence.preprocessColorPalette(colors: PulseLightSequence.kColors/*, brightness: LightSequence.kLightSequenceDefaultBrightness*/)
super.init()
}
var numFrames: Int {
func numFrames() -> Int {
return PulseLightSequence.kNumFrames
}
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
return pulse(numPixels: numPixels, colors: colorsBytes, frame: frame)
return pulse(numPixels: numPixels(), colors: colorsBytes, frame: frame)
}
private func pulse(numPixels: Int, colors: [[UInt8]], frame: Int) -> [[UInt8]] {
var lightBytes = [[UInt8]](repeating: [0, 0, 0], count:numPixels)
let reverse = frame >= numFrames / 2
let colorIndex = reverse ? (numFrames - 1) - frame : frame
let reverse = frame >= numFrames() / 2
let colorIndex = reverse ? (numFrames() - 1) - frame : frame
for i in 0..<numPixels {
//DLog("pixel: \(i) color: \(colorIndex)")
let colorBytes = colors[colorIndex]
@ -128,25 +123,25 @@ class SizzleLightSequence: LightSequence, LightSequenceGenerator {
// MARK: -
override init() {
colorsBytes = LightSequence.preprocessColorPalette(colors: SizzleLightSequence.kColors)
colorsBytes = LightSequence.preprocessColorPalette(colors: SizzleLightSequence.kColors/*, brightness: LightSequence.kLightSequenceDefaultBrightness*/)
super.init()
}
var numFrames:Int {
func numFrames() -> Int {
return SizzleLightSequence.kNumFrames
}
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
return sizzle(numPixels: numPixels, colors: colorsBytes, frame: frame)
return sizzle(numPixels: numPixels(), colors: colorsBytes, frame: frame)
}
private func sizzle(numPixels: Int, colors: [[UInt8]], frame: Int) -> [[UInt8]] {
var lightBytes = [[UInt8]](repeating: [0, 0, 0], count:numPixels)
let forwardNumFrames = numFrames / 2
let forwardNumFrames = numFrames() / 2
let reverse = frame >= forwardNumFrames
let evenIndex = reverse ? (frame % forwardNumFrames) : (forwardNumFrames - 1) - frame
let oddIndex = reverse ? (numFrames - 1) - frame : frame
let oddIndex = reverse ? (numFrames() - 1) - frame : frame
for i in 0..<numPixels {
//DLog("pixel: \(i) color: \(colorIndex)")
@ -169,16 +164,16 @@ class SweepLightSequence: LightSequence, LightSequenceGenerator {
// MARK: -
override init() {
colorsBytes = LightSequence.preprocessColorPalette(colors: SweepLightSequence.kColors)
colorsBytes = LightSequence.preprocessColorPalette(colors: SweepLightSequence.kColors/*, brightness: LightSequence.kLightSequenceDefaultBrightness*/)
super.init()
}
var numFrames: Int {
func numFrames() -> Int {
return SweepLightSequence.kNumFrames
}
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
return sweep(numPixels: numPixels, colors: colorsBytes, frame: frame)
return sweep(numPixels: numPixels(), colors: colorsBytes, frame: frame)
}
private func sweep(numPixels: Int, colors: [[UInt8]], frame: Int) -> [[UInt8]] {
@ -193,78 +188,3 @@ class SweepLightSequence: LightSequence, LightSequenceGenerator {
return lightBytes
}
}
// MARK: - Module Started Animation
class FlashLightSequence: LightSequence, LightSequenceGenerator {
// Constants
private static let kNumFrames = 8 // Default frames per second is 10,
// Data
private var baseColor: UIColor
// MARK: -
init(baseColor: UIColor) {
self.baseColor = baseColor
super.init()
}
var numFrames: Int {
return FlashLightSequence.kNumFrames
}
override var isCyclic: Bool {
return false
}
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
return flash(baseColor: baseColor, numPixels: numPixels, frame: frame)
}
private func flash(baseColor: UIColor, numPixels: Int, frame: Int) -> [[UInt8]] {
var lightBytes = [[UInt8]](repeating: [0, 0, 0], count:numPixels)
let factor = CGFloat(frame) / CGFloat(numFrames)
/*
let originWhite: CGFloat
let endWhite: CGFloat
let fraction: CGFloat
if factor < 0.5 {
originWhite = 0
endWhite = 1
fraction = factor * 2
}
else {
originWhite = 1
endWhite = 0
fraction = (factor - 0.5) * 2
}
let color = UIColor(white: originWhite + (endWhite - originWhite) * fraction, alpha: 1)
*/
let originColor: UIColor
let endColor: UIColor
let fraction: CGFloat
if factor < 0.5 {
originColor = .clear
endColor = baseColor
fraction = factor * 2
}
else {
originColor = baseColor
endColor = .clear
fraction = (factor - 0.5) * 2
}
let color = originColor.interpolateRGBColorTo(end: endColor, fraction: fraction)
//let color = originColor.interpolateHSVColorFrom(end: endColor, fraction: fraction)
let colorBytes = BlePeripheral.pixelUInt8FromColor(color)
for i in 0..<numPixels {
lightBytes[i] = colorBytes
}
return lightBytes
}
}

View file

@ -21,67 +21,57 @@ class LightSequenceAnimation {
private var displaylink: CADisplayLink?
//private var simulatedFrame = 0.0
private var startingTimestamp: TimeInterval = 0
private var frameHandler: (([[UInt8]])->())?
private var stopHandler: (()->())?
private var repeating: Bool
// MARK: -
init(lightSequenceGenerator: LightSequenceGenerator, framesPerSecond: Int, repeating: Bool) {
init(lightSequenceGenerator: LightSequenceGenerator, framesPerSecond: Int) {
self.lightSequenceGenerator = lightSequenceGenerator
self.lightSequenceFramesPerSecond = framesPerSecond
self.repeating = repeating
}
deinit {
stop()
}
func start(stopHandler:(()->())? = nil, frameHandler: @escaping ([[UInt8]])->()) {
self.stopHandler = stopHandler
func start(frameHandler: @escaping ([[UInt8]])->()) {
self.frameHandler = frameHandler
// Create displayLink if needed
if displaylink == nil {
displaylink = CADisplayLink(target: self, selector: #selector(displayLinkStep))
displaylink!.add(to: .main, forMode: .default)
displaylink!.add(to: .current, forMode: .default)
}
guard let displaylink = displaylink else { return }
displaylink.preferredFramesPerSecond = lightSequenceFramesPerSecond
startingTimestamp = CACurrentMediaTime()
startingTimestamp = displaylink.timestamp
}
func stop() {
displaylink?.invalidate()
displaylink = nil
displaylink?.remove(from: .main, forMode: .default)
stopHandler?()
}
@objc func displayLinkStep(displaylink: CADisplayLink) {
let currentTimestamp = CACurrentMediaTime() - startingTimestamp
let numFrames = Double(lightSequenceGenerator.numFrames)
guard repeating || currentTimestamp * numFrames * speed < numFrames else { // Stop if repeating == false and has displayed all frames
stop()
return
}
//let fps = Double(currentLightSequenceFramesPerSecond)
let currentTimestamp = displaylink.timestamp - startingTimestamp
let numFrames = Double(lightSequenceGenerator.numFrames())
let frame = (currentTimestamp * numFrames * speed).truncatingRemainder(dividingBy:numFrames)
//let frame = simulatedFrame.truncatingRemainder(dividingBy:numFrames)
//simulatedFrame += 0.4
//DLog("frame: \(frame)")
var pixelsBytes: [[UInt8]]
if LightSequenceAnimation.kIsFrameInterpolationEnabled {
let preFrame = Int(floor(frame))
let postFrame = lightSequenceGenerator.isCyclic ? Int(ceil(frame)) % lightSequenceGenerator.numFrames : min(Int(ceil(frame)), lightSequenceGenerator.numFrames)
let postFrame = Int(ceil(frame)) % lightSequenceGenerator.numFrames()
let postFactor = frame - Double(preFrame)
let preFactor = 1 - postFactor
//DLog("pre: \(preFrame), post: \(postFrame), frame: \(frame)")
let pixelsBytesPre = lightSequenceGenerator.colorsForFrame(preFrame)
let pixelsBytesPost = lightSequenceGenerator.colorsForFrame(postFrame)

BIN
BluefruitPlayground/Resources/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -6,11 +6,22 @@
// Startup
"startup_bluetooth_unsupported" = "This device doesn't support Bluetooth Low Energy which is needed to connect to the Circuit Playground Bluefruit device";
// Bluetooth Management
// Splash
"splash_restoringconnection" = "Restoring connection...";
"bluetooth_unsupported" = "This device doesn't support Bluetooth Low Energy";
"bluetooth_notauthorized" = "This app is not authorized to use the Bluetooth Low Energy";
"bluetooth_poweredoff" = "Bluetooth is currently powered off";
"bluetooth_locationpermission_disabled_text" = "Location Services should be enabled to scan bluetooth peripherals.
Please, go to settings and enable it to start scanning peripherals";
"bluetooth_locationpermission_title" = "This app needs location access";
"bluetooth_locationpermission_text" = "Please grant location access so this app can scan for Bluetooth peripherals";
"bluetooth_locationpermission_notavailable_title" = "Bluetooth Scanning not available";
"bluetooth_locationpermission_notavailable_text" = "Since location access has not been granted, the app will not be able to scan for Bluetooth peripherals";
"bluetooth_scanner_errorregisteringapp" = "App cannot be registered for bluetooth scanning.
Please reset bluetooth and try again.";
"bluetooth_connecting_error" = "Error connecting to peripheral";
"bluetooth_advertising_start_error" = "Error starting advertising";
// Welcome
"tip0_title" = "Welcome";
@ -25,7 +36,7 @@ Circuit Playground Bluefruit is available at the Adafruit shop.";
"tip1_link_url" = "https://learn.adafruit.com/bluefruit-playground-app/firmware";
"tip1_action" = "Next";
"tip2_title" = "Discover";
"tip2_detail" = "Once your Circuit Playground Bluefruit is powered, move your iPhone towards your Circuit Playground Bluefruit";
"tip2_detail" = "Once Circuit Playground Bluefruit is powered, place it near your iPhone and tap the button below.";
"tip2_action" = "Begin pairing";
@ -61,26 +72,9 @@ https://github.com/mindsnacks/MSWeakTimer";
"about_ios_link1_text" = "uf2 firmware file";
"about_ios_link1_url" = "https://learn.adafruit.com/bluefruit-playground-app/firmware";
// Bluetooth Status
"bluetooth_unsupported" = "This device doesn't support Bluetooth";
"bluetooth_unsupported_detail" = "Bluetooth support and specifically Bluetooth Low Energy support is needed to communicate with a Circuit Playground Device";
"bluetooth_unsupported_le" = "This device doesn't support Bluetooth Low Energy";
"bluetooth_unsupported_le_detail" = "Bluetooth Low Energy support is needed to communicate with a Circuit Playground Device";
"bluetooth_notauthorized" = "This app is not authorized to use the Bluetooth Low Energy";
"bluetooth_notauthorized_detail" = "Bluetooth permission should be granted or Bluefruit Playground to connect to a Circuit Playground Device";
"bluetooth_poweredoff" = "Bluetooth is currently powered off";
"bluetooth_poweredoff_detail" = "Bluetooth should be enabled on your device for Bluefruit Playground to communicate with a Circuit Playground Device";
"bluetooth_enable_action" = "Bluetooth permissions";
// Autoconnect
"autoconnect_title" = "Finding CPB";
"autoconnect_manual_action" = "Select device manually";
"autoconnect_backbutton" = "Autoconnect";
// Scanner
"scanner_title" = "Select CPB";
"scanner_title" = "Finding CPB";
"scanner_connecting" = "Connecting...";
"scanner_discoveringservices" = "Discovering services...";
@ -91,7 +85,6 @@ https://github.com/mindsnacks/MSWeakTimer";
"scanner_subtitle" = "Select a Circuit Playground Bluefruit device to connect to:";
"scanner_unnamed" = "<Unknown>";
"scanner_automatic_action" = "Select device automatically";
"scanner_problems_action" = "I can't find my device";
"scanner_backbutton" = "Disconnect";
@ -115,9 +108,9 @@ Download it from the Bluefruit Playground guide at learn.adafruit.com";
// Modules
"modules_title" = "Modules";
"modules_subtitle" = "Each module interacts with a type of sensor on the Circuit Playground Bluefruit device";
"modules_subtitle" = "Each module interacts with a specific type of sensor on the Circuit Playground Bluefruit device";
"modules_color_title" = "NeoPixels";
"modules_color_title" = "Neopixels";
"modules_color_subtitle" = "Control LED color & animation";
"modules_light_title" = "Light Sensor";
"modules_light_subtitle" = "View continuous light sensor readings";
@ -129,18 +122,16 @@ Download it from the Bluefruit Playground guide at learn.adafruit.com";
"modules_accelerometer_subtitle" = "View orientation based on accelerometer data";
"modules_temperature_title" = "Temperature";
"modules_temperature_subtitle" = "View current temperature readings";
"modules_disconnect_title" = "Disconnect";
"modules_disconnect_subtitle" = "Close the current connection and scan for a new CPB device";
// Neopixels
"neopixels_title" = "NeoPixels";
"neopixels_help" = "NeoPixels mode allows you to control the color of CPBs built-in NeoPixel LEDs individually, as a group, or by playing preset animations.
"neopixels_title" = "Neopixels";
"neopixels_help" = "Neopixel mode allows you to control the color of CPBs built-in neopixel LEDs individually, as a group, or by playing preset animations.
• Select NeoPixels to control by tapping them in the Circuit Playground Bluefruit image.
• Select neopixels to control by tapping them in the Circuit Playground Bluefruit image.
• Set the color of selected NeoPixels by choosing a color from the Color Palette or Color Wheel at the bottom.
• Set the color of selected neopixels by choosing a color from the Color Palette or Color Wheel at the bottom.
• Tap a light sequence to trigger an animation using all of the NeoPixels.";
• Tap a light sequence to trigger an animation using all of the neopixels.";
"neopixels_sequence_title" = "Light Sequence";
"neopixels_sequence_speed" = "Speed";
@ -160,7 +151,6 @@ Download it from the Bluefruit Playground guide at learn.adafruit.com";
"lightsensor_panel_title" = "Luminance Reading";
"lightsensor_panel_unit" = "Light level";
"lightsensor_chartpanel_title" = "Luminance Chart";
// Button Status
"buttonstatus_title" = "Button Status";
@ -212,7 +202,7 @@ Download it from the Bluefruit Playground guide at learn.adafruit.com";
"temperature_units_celsius" = "ºC";
"temperature_units_farenheit" = "ºF";
"temperature_panel_title" = "Temperature Chart";
"temperature_panel_title" = "Temperature Reading";
"temperature_chart_nodata" = "No chart data available";
// Help

File diff suppressed because one or more lines are too long

View file

@ -16,22 +16,20 @@ import UIKit
class NavigationBarWithScrollAwareRightButton : UINavigationBar {
// Constants
private static let kButtonTag = 100
// Config (navbar metrics)
static let navBarHeightSmallState: CGFloat = 44
static let navBarHeightLargeState: CGFloat = 96.5
private static let rightMargin: CGFloat = 6//8
private static let bottomMarginForLargeState: CGFloat = 6//9
private static let bottomMarginForSmallState: CGFloat = 2
public struct CustomButtonMetrics {
static let rightMargin: CGFloat = 8//12
static let bottomMarginForLargeState: CGFloat = 9
static let bottomMarginForSmallState: CGFloat = 2
static let navBarHeightSmallState: CGFloat = 44
static let navBarHeightLargeState: CGFloat = 96.5
}
// Data
private var navigationButton: UIButton?// = UIButton(type: .custom)
private var topViewController: UIViewController?
// MARK: -
public func setRightButton(topViewController: UIViewController, image: UIImage?, target: Any?, action: Selector) {
navigationButton?.removeFromSuperview()
@ -49,8 +47,8 @@ class NavigationBarWithScrollAwareRightButton : UINavigationBar {
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -NavigationBarWithScrollAwareRightButton.rightMargin),
button.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -NavigationBarWithScrollAwareRightButton.bottomMarginForLargeState),
button.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -CustomButtonMetrics.rightMargin),
button.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -CustomButtonMetrics.bottomMarginForLargeState),
])
self.layoutIfNeeded() // Force layout to remove adding animation
@ -60,15 +58,22 @@ class NavigationBarWithScrollAwareRightButton : UINavigationBar {
// Detect topViewControllerChange
self.topViewController = topViewController
topViewController.navigationController?.delegate = self
/*
// Detect content scrollview scroll
if let contentScrollView = topViewController.view as? UIScrollView ?? topViewController.view.subviews.first as? UIScrollView {
contentScrollView.delegate = self
}
*/
}
public var navigationBarScrollViewProgress: CGFloat {
return (self.frame.height - NavigationBarWithScrollAwareRightButton.navBarHeightSmallState) / (NavigationBarWithScrollAwareRightButton.navBarHeightLargeState - NavigationBarWithScrollAwareRightButton.navBarHeightSmallState)
return (self.frame.height - CustomButtonMetrics.navBarHeightSmallState) / (CustomButtonMetrics.navBarHeightLargeState - CustomButtonMetrics.navBarHeightSmallState)
}
public func updateRightButtonPosition() {
let yDelta = NavigationBarWithScrollAwareRightButton.bottomMarginForLargeState - NavigationBarWithScrollAwareRightButton.bottomMarginForSmallState
let yDelta = CustomButtonMetrics.bottomMarginForLargeState - CustomButtonMetrics.bottomMarginForSmallState
let y = yDelta * max(0, (1-navigationBarScrollViewProgress))
navigationButton?.transform = CGAffineTransform(translationX: 0, y: y)

View file

@ -10,68 +10,33 @@ import UIKit
struct ScreenFlowManager {
// Data
private static var wasManualScanningLastUsed = false // Last scanning method used
// MARK: - Go to app areas
public static func gotoAutoconnect() {
let bluetoothErrorDisplayed = updateStatusViewControllerIfBluetoothStateIsNotReady()
guard !bluetoothErrorDisplayed else { return }
guard let window = UIApplication.shared.keyWindow else { return }
let isAlreadyInAutoconnect = (window.rootViewController as? UINavigationController)?.topViewController is AutoConnectViewController
guard !isAlreadyInAutoconnect else { return }
let isInManualScan = (window.rootViewController as? UINavigationController)?.topViewController is ScannerViewController
let transition: UIView.AnimationOptions = isInManualScan ? .transitionFlipFromLeft : .transitionCrossDissolve
public static func gotoScanner() {
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: AutoConnectViewController.kNavigationControllerIdentifier)
changeRootViewController(rootViewController: rootNavigationViewController, animationOptions: transition)
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: ScannerViewController.kIdentifier)
ScreenFlowManager.changeRootViewController(rootViewController: rootNavigationViewController, animated: false)
}
public static func goToManualScan() {
let bluetoothErrorDisplayed = updateStatusViewControllerIfBluetoothStateIsNotReady()
guard !bluetoothErrorDisplayed else { return }
guard let window = UIApplication.shared.keyWindow else { return }
let isAlreadyInManualScan = (window.rootViewController as? UINavigationController)?.topViewController is ScannerViewController
guard !isAlreadyInManualScan else { return }
public static func restoreAndGoToHome() {
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: ScannerViewController.kNavigationControllerIdentifier)
changeRootViewController(rootViewController: rootNavigationViewController, animationOptions: .transitionFlipFromRight)
// Add home to scanner
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: ScannerViewController.kIdentifier) as! UINavigationController
let homeViewController = mainStoryboard.instantiateViewController(withIdentifier: HomeViewController.kIdentifier)
rootNavigationViewController.viewControllers.append(homeViewController)
ScreenFlowManager.changeRootViewController(rootViewController: rootNavigationViewController, animated: false)
}
public static func restoreAndGoToCPBModules() {
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: HomeViewController.kNavigationControllerIdentifier)
changeRootViewController(rootViewController: rootNavigationViewController) {
CPBBle.shared.neopixelStartLightSequence(FlashLightSequence(baseColor: .lightGray), speed: 1, repeating: false, sendLightSequenceNotifications: false)
}
}
public static func gotoCPBModules() {
guard let window = UIApplication.shared.keyWindow else { return }
ScreenFlowManager.wasManualScanningLastUsed = (window.rootViewController as? UINavigationController)?.topViewController is ScannerViewController
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: HomeViewController.kNavigationControllerIdentifier)
changeRootViewController(rootViewController: rootNavigationViewController) {
CPBBle.shared.neopixelStartLightSequence(FlashLightSequence(baseColor: .white), speed: 1, repeating: false, sendLightSequenceNotifications: false)
}
}
// MARK: - Change Root View Controller
public static func changeRootViewController(rootViewController: UIViewController, animationOptions: UIView.AnimationOptions? = [.transitionCrossDissolve], completionHandler: (() -> Void)? = nil ) {
public static func changeRootViewController(rootViewController: UIViewController, animated: Bool = true, completionHandler: (() -> Void)? = nil ) {
guard let window = UIApplication.shared.keyWindow else { return }
if let animationOptions = animationOptions {
window.rootViewController = rootViewController
// rootViewController.view.layoutIfNeeded()
UIView.transition(with: window, duration: 0.3, options: animationOptions, animations: {
if animated {
rootViewController.view.layoutIfNeeded()
UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: {
window.rootViewController = rootViewController
}, completion: { completed in
completionHandler?()
})
@ -81,92 +46,4 @@ struct ScreenFlowManager {
completionHandler?()
}
}
// MARK: - Monitor Bluetooth State and automatically change rootViewController
private static weak var didDisconnectFromPeripheralObserver: NSObjectProtocol?
private static weak var didUpdateBleStateObserver: NSObjectProtocol?
public static func enableBleStateManagement() {
let notificationCenter = NotificationCenter.default
if didDisconnectFromPeripheralObserver == nil {
didDisconnectFromPeripheralObserver = notificationCenter.addObserver(forName: .didDisconnectFromPeripheral, object: nil, queue: .main, using: { _ in
guard let window = UIApplication.shared.keyWindow else { return }
// Don't show on startup
let topViewController = (window.rootViewController as? UINavigationController)?.topViewController ?? window.rootViewController
guard !(topViewController is StartupViewController) else { return }
// Show disconnection alert
let localizationManager = LocalizationManager.shared
let alertController = UIAlertController(title: nil, message: localizationManager.localizedString("scanner_peripheraldisconnected"), preferredStyle: .alert)
let okAction = UIAlertAction(title: localizationManager.localizedString("dialog_ok"), style: .default) { _ in
/*
let isInManualScan = (window.rootViewController as? UINavigationController)?.topViewController is ScannerViewController
if !isInManualScan { // If it is manual scanning, maintain that screen
gotoAutoconnect()
}*/
if ScreenFlowManager.wasManualScanningLastUsed {
goToManualScan()
}
else {
gotoAutoconnect()
}
}
alertController.addAction(okAction)
window.rootViewController?.present(alertController, animated: true, completion: nil)
})
}
if didUpdateBleStateObserver == nil {
didUpdateBleStateObserver = notificationCenter.addObserver(forName: .didUpdateBleState, object: nil, queue: .main) { _ in
guard let window = UIApplication.shared.keyWindow else { return }
let topViewController = (window.rootViewController as? UINavigationController)?.topViewController ?? window.rootViewController
let isAllowedToShowBluetoothDialogBasedOnCurrentRootViewController = !(topViewController is TipsViewController) // Don't show bluetooth errroes while showing tips
guard isAllowedToShowBluetoothDialogBasedOnCurrentRootViewController else { return }
let _ = updateStatusViewControllerIfBluetoothStateIsNotReady()
}
}
}
public static func disableBleStateManagement() {
let notificationCenter = NotificationCenter.default
if let didDisconnectFromPeripheralObserver = didDisconnectFromPeripheralObserver {notificationCenter.removeObserver(didDisconnectFromPeripheralObserver)
self.didDisconnectFromPeripheralObserver = nil
}
if let didUpdateBleStateObserver = didUpdateBleStateObserver {notificationCenter.removeObserver(didUpdateBleStateObserver)
self.didUpdateBleStateObserver = nil
}
}
private static func updateStatusViewControllerIfBluetoothStateIsNotReady() -> Bool { // Returns true if the rootViewController was changed to BluetoothStatusViewController
guard let window = UIApplication.shared.keyWindow else { return false }
let bluetoothState = Config.bleManager.state
let shouldShowBluetoothDialog = bluetoothState == .poweredOff || bluetoothState == .unsupported || bluetoothState == .unauthorized
let topViewController = (window.rootViewController as? UINavigationController)?.topViewController ?? window.rootViewController
let isShowingEnableBluetoothDialog = topViewController is BluetoothStatusViewController
var viewControllerIdentifier: String? = nil
if shouldShowBluetoothDialog && !isShowingEnableBluetoothDialog {
viewControllerIdentifier = BluetoothStatusViewController.kIdentifier
}
else if !shouldShowBluetoothDialog && isShowingEnableBluetoothDialog {
viewControllerIdentifier = AutoConnectViewController.kNavigationControllerIdentifier
}
if let viewControllerIdentifier = viewControllerIdentifier {
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: viewControllerIdentifier)
changeRootViewController(rootViewController: rootNavigationViewController)
return true
}
else {
return false
}
}
}

View file

@ -1,51 +0,0 @@
//
// UIColor+Interpolate.swift
// BluefruitPlayground
//
// Created by Antonio García on 30/01/2020.
// Copyright © 2020 Adafruit. All rights reserved.
//
import UIKit
extension UIColor {
// based on: https://stackoverflow.com/questions/22868182/uicolor-transition-based-on-progress-value
func interpolateRGBColorTo(end:UIColor, fraction:CGFloat) -> UIColor {
var f = max(0, fraction)
f = min(1, fraction)
guard var c1 = self.cgColor.components, var c2 = end.cgColor.components else { return self }
// Convert 2 components colors to 4 components
if c1.count == 2 {
let whiteComponent = c1[0]
let alphaComponent = c1[1]
c1 = [whiteComponent, whiteComponent, whiteComponent, alphaComponent]
}
if c2.count == 2 {
let whiteComponent = c2[0]
let alphaComponent = c2[1]
c2 = [whiteComponent, whiteComponent, whiteComponent, alphaComponent]
}
// Interpolate
let r = c1[0] + (c2[0] - c1[0]) * f
let g = c1[1] + (c2[1] - c1[1]) * f
let b = c1[2] + (c2[2] - c1[2]) * f
let a = c1[3] + (c2[3] - c1[3]) * f
return UIColor.init(red:r, green:g, blue:b, alpha:a)
}
// Note: getHue can be negative from iOS 10
func interpolateHSVColorFrom(end: UIColor, fraction: CGFloat) -> UIColor {
var f = max(0, fraction)
f = min(1, fraction)
var h1: CGFloat = 0, s1: CGFloat = 0, b1: CGFloat = 0, a1: CGFloat = 0
self.getHue(&h1, saturation: &s1, brightness: &b1, alpha: &a1)
var h2: CGFloat = 0, s2: CGFloat = 0, b2: CGFloat = 0, a2: CGFloat = 0
end.getHue(&h2, saturation: &s2, brightness: &b2, alpha: &a2)
let h = h1 + (h2 - h1) * f
let s = s1 + (s2 - b1) * f
let b = b1 + (b2 - b1) * f
let a = a1 + (a2 - a1) * f
return UIColor(hue: h, saturation: s, brightness: b, alpha: a)
}
}

Binary file not shown.

View file

@ -24,7 +24,7 @@ class AccelerometerPanelViewController: ModulePanelViewController {
@IBOutlet weak var accelerometerEulerYLabel: UILabel!
@IBOutlet weak var accelerometerEulerZLabel: UILabel!
private var currentState = BlePeripheral.ButtonsState(slideSwitch: .left, buttonA: .released, buttonB: .released)
// MARK: - Lifecycle
override func viewDidLoad() {
@ -37,6 +37,16 @@ class AccelerometerPanelViewController: ModulePanelViewController {
accelerometerTitleLabel.text = localizationManager.localizedString("accelerometer_panel_accelerometer_title")
accelerometerEulerAnglesTitleLabel.text = localizationManager.localizedString("accelerometer_panel_eulerangles_title")
// // Read initial state
// CPBBle.shared.buttonsReadState { [weak self] response in
// switch response {
// case let .success(buttonsStatus, _):
// buttonsStateReceived(buttonsStatus)
// case .failure(let error):
// DLog("Error receiving temperature data: \(error)")
// }
}
func accelerationReceived(
@ -55,4 +65,22 @@ class AccelerometerPanelViewController: ModulePanelViewController {
accelerometerEulerZLabel.text = String(format: "%.0f", zDeg)
}
// // MARK: - Data
// func buttonsStateReceived(_ buttonsState: BlePeripheral.ButtonsState) {
//
//
//
// if buttonsState.buttonA != currentState.buttonA {
// // animateState(view: buttonAStatusView, isPressed: buttonsState.buttonA == .pressed)
// }
//
// if buttonsState.buttonB != currentState.buttonB {
// // animateState(view: buttonBStatusView, isPressed: buttonsState.buttonB == .pressed)
// }
//
// currentState = buttonsState
// }
//
//}
}

View file

@ -8,31 +8,315 @@
import UIKit
import SceneKit
import ReplayKit
import AVFoundation
struct LowPassFilterSignal {
/// Current signal value
var value: Float
/// A scaling factor in the range 0.0..<1.0 that determines
/// how resistant the value is to change
let filterFactor: Float
/// Update the value, using filterFactor to attenuate changes
mutating func update(newValue: Float) {
value = filterFactor * value + (1.0 - filterFactor) * newValue
}
}
class AccelerometerViewController: ModuleViewController {
// Constants
static let kIdentifier = "AccelerometerViewController"
//For Recording Function
var recordButtonWasSelected: Bool = false
var preferStatusBarHidden: Bool!
// UI
@IBOutlet weak var sceneView: SCNView!
@IBOutlet var mainCamera: UIView!
@IBOutlet var rotateCameraRef: UIButton!
@IBOutlet var backgroundSwapRef: UIButton!
@IBOutlet var recordButton: UIButton!
let recorder = RPScreenRecorder.shared()
@IBAction func backgroundSwapAction(_ sender: Any) {
backgroundCameraIsActive.toggle()
if backgroundCameraIsActive{
mainCamera.alpha = 0
}else{
mainCamera.alpha = 1
}
}
@IBAction func rotateCamAction(_ sender: Any) {
print("Button Rotation Pressed")
guard let currentCameraInput : AVCaptureInput = captureSession?.inputs.first else {
return
}
if let input = currentCameraInput as? AVCaptureDeviceInput
{
if input.device.position == .back{
switchToFrontCamera()
}
if input.device.position == .front{
switchToBackCamera()
}
}
}
@IBAction func recordAction(_ sender: Any) {
print("Is Recording...")
recordButtonWasSelected = !recordButtonWasSelected
recordUpdater()
}
//Recording Functions
func recordUpdater() {
if recordButtonWasSelected {
print("Currently Recording...")
recording()
// recordButton.backgroundColor = UIColor.red
} else {
// recordButton.backgroundColor = UIColor.white
print("Stopped/Not Recording.")
stopRecording()
}
}
func recording() {
RPScreenRecorder.shared().isMicrophoneEnabled = true;
recordButton.isUserInteractionEnabled = true
recordButton.isEnabled = true
let pulse1 = CASpringAnimation(keyPath: "transform.scale")
pulse1.duration = 0.6
pulse1.fromValue = 1.0
pulse1.toValue = 1.20
pulse1.autoreverses = true
pulse1.repeatCount = 1
pulse1.initialVelocity = 0.8
pulse1.damping = 0.8
let animationGroup = CAAnimationGroup()
animationGroup.duration = 2.7
animationGroup.repeatCount = 1000
animationGroup.animations = [pulse1]
recordButton.layer.add(animationGroup, forKey: "pulse")
recorder.startRecording { (error) in
if let error = error {
print(error)
}
}
}
//Works for iPhone
func stopRecording() {
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.phone {
recordButton.layer.removeAllAnimations()
recorder.stopRecording { (previewVC, error) in
if let previewVC = previewVC{
previewVC.previewControllerDelegate = self
self.present(previewVC, animated: true, completion: nil)
}
if let error = error {
print(error)
}
}
}
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad {
recordButton.layer.removeAllAnimations()
let recorder = RPScreenRecorder.shared()
recorder.stopRecording { (previewVC, error) in
if let previewVC = previewVC {
previewVC.previewControllerDelegate = self as RPPreviewViewControllerDelegate
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad {
previewVC.modalPresentationStyle = UIModalPresentationStyle.popover
previewVC.popoverPresentationController?.sourceRect = self.recordButton.frame //position popover relative to record button - NEEDS TESTING
previewVC.popoverPresentationController?.sourceView = self.view
//Show Preview
self.present(previewVC, animated: true)
}
else {
//Set boundaries safe for iPhone X - NEEDS TESTING
let safeArea = self.view.safeAreaInsets
let safeAreaHeight = self.view.frame.height - safeArea.top
let safeAreaWidth = self.view.frame.width - (safeArea.left + safeArea.right)
let scaleX = safeAreaWidth / self.view.frame.width
let scaleY = safeAreaHeight / self.view.frame.height
let scale = min(scaleX, scaleY)
previewVC.view.transform = CGAffineTransform(scaleX: scale, y: scale)
//Show Preview
self.present(previewVC, animated: true) {
previewVC.view.frame.origin.x += safeArea.left
previewVC.view.frame.origin.y += safeArea.top
}
}
}
if let error = error {
print(error)
}
}
}
}
//Background
var backgroundCameraIsActive = false
// Camera Data
var captureSession: AVCaptureSession?
var videoPreviewLayer : AVCaptureVideoPreviewLayer?
var frontCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
var backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
func switchToFrontCamera(){
print("Front Cam")
if frontCamera?.isConnected == true {
captureSession?.stopRunning()
let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
do {
let input = try AVCaptureDeviceInput(device: captureDevice!)
captureSession = AVCaptureSession()
captureSession?.addInput(input)
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
videoPreviewLayer?.frame = view.layer.bounds
mainCamera.layer.addSublayer(videoPreviewLayer!)
captureSession?.startRunning()
}
catch{
print("Error.")
}
}
}
func switchToBackCamera(){
print("Back Cam")
if backCamera?.isConnected == true {
captureSession?.stopRunning()
let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
do {
let input = try AVCaptureDeviceInput(device: captureDevice!)
captureSession = AVCaptureSession()
captureSession?.addInput(input)
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
videoPreviewLayer?.frame = view.layer.bounds
mainCamera.layer.addSublayer(videoPreviewLayer!)
captureSession?.startRunning()
}
catch{
print("Error.")
}
}
}
// Data
private var circuitNode: SCNNode!
private var jawNode: SCNNode!
private var headNode: SCNNode!
private var sparkyFaceNode: SCNNode!
private var valuesPanelViewController: AccelerometerPanelViewController!
private var acceleration = BlePeripheral.AccelerometerValue(x: 0, y: 0, z: 0)
var accelerometerX = LowPassFilterSignal(value: 0, filterFactor: 0.6)
var accelerometerY = LowPassFilterSignal(value: 0, filterFactor: 0.7)
private var playIntroAnimation = false
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Add panels
valuesPanelViewController = (addPanelViewController(storyboardIdentifier: AccelerometerPanelViewController.kIdentifier) as! AccelerometerPanelViewController)
// Load base
let scene = SCNScene(named: "cpb.scn")!
let scene = SCNScene(named: "Sparky_Gold1.dae")!
scene.background.contents = UIColor.clear
circuitNode = scene.rootNode.childNode(withName: "Circuit_Playground_Bluefruit", recursively: false)!
jawNode = scene.rootNode.childNode(withName: "jaw", recursively: true)!
headNode = scene.rootNode.childNode(withName: "SparkyHead", recursively: true)!
sparkyFaceNode = scene.rootNode.childNode(withName: "Face", recursively: true)!
// Setup scene
sceneView.scene = scene
@ -41,13 +325,44 @@ class AccelerometerViewController: ModuleViewController {
// Localization
let localizationManager = LocalizationManager.shared
self.title = localizationManager.localizedString("accelerometer_title")
self.title = localizationManager.localizedString("Puppets")
moduleHelpMessage = localizationManager.localizedString("accelerometer_help")
//--- Camera Function ----
if #available(iOS 10.2, *){
let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
do
{
let input = try AVCaptureDeviceInput(device: captureDevice!)
captureSession = AVCaptureSession()
captureSession?.addInput(input)
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
videoPreviewLayer?.frame = view.layer.bounds
mainCamera.layer.addSublayer(videoPreviewLayer!)
captureSession?.startRunning()
}
catch
{
print("Error.")
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
switchToFrontCamera()
// Initial value
if let acceleration = CPBBle.shared.accelerometerLastValue() {
self.acceleration = acceleration
@ -56,6 +371,136 @@ class AccelerometerViewController: ModuleViewController {
// Set delegate
CPBBle.shared.accelerometerDelegate = self
//Subscribe to command notifications
notificatonsForEmoteUse()
//Sparky's Intro Animation
sparkyIntroAnimation()
}
func notificatonsForEmoteUse (){
NotificationCenter.default.addObserver(self, selector: #selector(animationOne),name:NSNotification.Name(rawValue: "EmoteOne"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(animationTwo),name:NSNotification.Name(rawValue: "EmoteTwo"), object: nil)
// NotificationCenter.default.addObserver(self, selector: #selector(switchOne),name:NSNotification.Name(rawValue: "EmoteThree"), object: nil)
//
// NotificationCenter.default.addObserver(self, selector: #selector(switchTwo),name:NSNotification.Name(rawValue: "EmoteFour"), object: nil)
}
func sparkyIntroAnimation(){
let scale: Float = 0.0005
headNode.scale = SCNVector3(x: scale, y: scale, z: scale)
let scaleAnimation = SCNAction.scale(to: CGFloat(1), duration: 1.3)
let rotationAction = SCNAction.rotateBy(x: 0, y: 12.57, z: 0, duration: 1.4)
// let transformAnimation = SCNAction.moveBy(x: 0, y: 0, z: 0, duration: 1.4)
rotationAction.timingMode = .easeOut
// rotationAction.timingFunction = { (p: Float) in
// return self.easeOutElastic(p)
// }
headNode.runAction(scaleAnimation)
headNode.runAction(rotationAction)
// headNode.runAction(transformAnimation)
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1401)) {
self.playIntroAnimation = true
}
}
@objc func switchOne(){
backgroundCameraIsActive.toggle()
if backgroundCameraIsActive{
DispatchQueue.main.async {
self.mainCamera.alpha = 0
}
}else{
DispatchQueue.main.async {
self.mainCamera.alpha = 1
}
}
}
@objc func switchTwo(){
print("Switched 2")
backgroundCameraIsActive.toggle()
if backgroundCameraIsActive{
DispatchQueue.main.async {
self.mainCamera.alpha = 0
}
}else{
DispatchQueue.main.async {
self.mainCamera.alpha = 1
}
}
}
@objc func animationOne(){
//Uses a bounce in and out animation for Sparky's eyes.
let scale: Float = 0.0005
sparkyFaceNode.scale = SCNVector3(x: scale, y: scale, z: scale)
let bounceAction = SCNAction.scale(to: CGFloat(1), duration: 1)
bounceAction.timingMode = .linear
// Use a custom timing function
bounceAction.timingFunction = { (p: Float) in
return self.easeOutElastic(p)
}
sparkyFaceNode.runAction(bounceAction)
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1500)) {
self.sparkyFaceNode.removeAllActions()
}
}
@objc func animationTwo(){
playIntroAnimation = false
let rotateAnimation = SCNAction.rotateTo(x: 0, y: 0, z: -0.3, duration: 0.1)
let rotateAnimationback = SCNAction.rotateTo(x: 0, y: 0, z: 0.3, duration: 0.1)
let headReset = SCNAction.rotateTo(x: -0.01, y: 0, z: -0.0, duration: 0.1)
let sequence = SCNAction.sequence([rotateAnimation, rotateAnimationback,rotateAnimation, rotateAnimationback,rotateAnimation, rotateAnimationback, headReset])
rotateAnimation.timingMode = .linear
rotateAnimationback.timingMode = .linear
headNode.runAction(sequence)
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
self.playIntroAnimation = true
self.headNode.removeAllActions()
}
}
override func viewWillDisappear(_ animated: Bool) {
@ -68,31 +513,112 @@ class AccelerometerViewController: ModuleViewController {
// MARK: - UI
private func updateValueUI() {
if playIntroAnimation == true {
// Calculate Euler Angles
let eulerAngles = eulerAnglesFromAcceleration()
let eulerAnglesForHead = eulerAnglesFromAccelerationForHead()
//DLog("Euler: pitch: \(eulerAngles.x) yaw: \(eulerAngles.y) roll: \(eulerAngles.z)")
// Update circuit model orientation
SCNTransaction.animationDuration = BlePeripheral.kCPBAcceleromterDefaultPeriod
circuitNode.eulerAngles = eulerAngles
jawNode.eulerAngles = eulerAngles
headNode.eulerAngles = eulerAnglesForHead
// Update panel
valuesPanelViewController.accelerationReceived(acceleration: self.acceleration, eulerAngles: eulerAngles)
}
}
private func eulerAnglesFromAcceleration() -> SCNVector3 {
// https://robotics.stackexchange.com/questions/6953/how-to-calculate-euler-angles-from-gyroscope-output
let accelAngleX = atan2(acceleration.y, acceleration.z)
// let accelAngleY = atan2(-acceleration.x, sqrt(acceleration.y*acceleration.y + acceleration.z*acceleration.z))
accelerometerX.update(newValue: accelAngleX)
return SCNVector3(accelerometerX.value.clamped(min: 0.13, max: 0.8), 0, 0)
}
private func eulerAnglesFromAccelerationForHead() -> SCNVector3 {
// https://robotics.stackexchange.com/questions/6953/how-to-calculate-euler-angles-from-gyroscope-output
let accelAngleX = atan2(acceleration.y, acceleration.z)
let accelAngleY = atan2(-acceleration.x, sqrt(acceleration.y*acceleration.y + acceleration.z*acceleration.z))
return SCNVector3(accelAngleX, accelAngleY, 0)
let accelAngleYALT = atan2(-acceleration.x, sqrt(acceleration.y*acceleration.y) + acceleration.z*acceleration.z)
accelerometerX.update(newValue: accelAngleX)
accelerometerY.update(newValue: accelAngleYALT)
return SCNVector3(-accelerometerX.value.clamped(min: 0.1, max: 0.7), -accelerometerY.value, accelerometerY.value)
}
// Timing function that has a "bounce in" effect
func easeOutElastic(_ t: Float) -> Float {
let p: Float = 0.3
let result = pow(2.0, -5.0 * t) * sin((t - p / 4.0) * (2.0 * Float.pi) / p) + 1.0
return result
}
}
// MARK: - CPBBleAccelerometerDelegate
extension AccelerometerViewController: CPBBleAccelerometerDelegate {
func cpbleAccelerationReceived(_ acceleration: BlePeripheral.AccelerometerValue) {
self.acceleration = acceleration
updateValueUI()
}
}
extension Float {
func clamped(min min: Float, max: Float) -> Float {
if self < min {
return min
}
if self > max {
return max
}
return self
}
}
extension AccelerometerViewController : RPPreviewViewControllerDelegate {
func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
dismiss(animated: true, completion: nil)
}
}
extension UIView {
@IBInspectable
var newCornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
}
}
}
extension Int {
var degreesToRadians: Double { return Double(self) * .pi/180}
}

View file

@ -1,444 +0,0 @@
//
// AutoConnectViewController.swift
// BluefruitPlayground
//
// Created by Antonio García on 28/01/2020.
// Copyright © 2020 Adafruit. All rights reserved.
//
import UIKit
class AutoConnectViewController: UIViewController {
// Constants
static let kNavigationControllerIdentifier = "AutoConnectNavigationController"
// Config
private static let kMinScanningTimeToAutoconnect: TimeInterval = 5
// UI
@IBOutlet weak var statusLabel: UILabel!
@IBOutlet weak var scanManuallyButton: UIButton!
@IBOutlet weak var problemButton: CornerShadowButton!
@IBOutlet weak var wave0ImageView: UIImageView!
@IBOutlet weak var wave1ImageView: UIImageView!
@IBOutlet weak var wave2ImageView: UIImageView!
@IBOutlet weak var detailLabel: UILabel!
@IBOutlet weak var cpbContainerView: UIView!
@IBOutlet weak var cpbImageView: UIImageView!
@IBOutlet weak var actionsContainerView: UIStackView!
// Data
private let bleManager = Config.bleManager
private var peripheralList = PeripheralList(bleManager: Config.bleManager)
private var selectedPeripheral: BlePeripheral? {
didSet {
if isViewLoaded {
UIView.animate(withDuration: 0.3) {
self.actionsContainerView.alpha = self.selectedPeripheral == nil ? 1:0
}
}
}
}
private let navigationButton = UIButton(type: .custom)
private var isAnimating = false
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Localization
let localizationManager = LocalizationManager.shared
self.title = localizationManager.localizedString("autoconnect_title")
scanManuallyButton.setTitle(localizationManager.localizedString("autoconnect_manual_action").uppercased(), for: .normal)
problemButton.setTitle(localizationManager.localizedString("scanner_problems_action").uppercased(), for: .normal)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.backBarButtonItem = nil // Clear any custom back button
if let customNavigationBar = navigationController?.navigationBar as? NavigationBarWithScrollAwareRightButton {
customNavigationBar.setRightButton(topViewController: self, image: UIImage(named: "info"), target: self, action: #selector(troubleshooting(_:)))
}
// Disconnect if needed
let connectedPeripherals = bleManager.connectedPeripherals()
if connectedPeripherals.count == 1, let peripheral = connectedPeripherals.first {
DLog("Disconnect from previously connected peripheral")
// Disconnect from peripheral
disconnect(peripheral: peripheral)
}
// UI
updateStatusLabel()
// Animations
if !isAnimating {
startAnimating()
}
// Ble Notifications
registerNotifications(enabled: true)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Flush any pending state notifications
didUpdateBleState()
// Update UI
updateScannedPeripherals()
// Start scannning
if !bleManager.isScanning {
bleManager.startScan()
}
// Remove saved peripheral for autoconnect
Settings.autoconnectPeripheralIdentifier = nil
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Stop scanning
bleManager.stopScan()
// Clear peripherals
peripheralList.clear()
// Animations
stopAnimating()
// Ble Notifications
registerNotifications(enabled: false)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is ScannerViewController {
// Go to scanner screen
let backItem = UIBarButtonItem()
backItem.title = LocalizationManager.shared.localizedString("autoconnect_backbutton")
self.navigationItem.backBarButtonItem = backItem
}
}
// MARK: - BLE Notifications
private weak var didUpdateBleStateObserver: NSObjectProtocol?
private weak var didDiscoverPeripheralObserver: NSObjectProtocol?
private weak var willConnectToPeripheralObserver: NSObjectProtocol?
private weak var didConnectToPeripheralObserver: NSObjectProtocol?
private weak var didDisconnectFromPeripheralObserver: NSObjectProtocol?
private weak var peripheralDidUpdateNameObserver: NSObjectProtocol?
private weak var willDiscoverServicesObserver: NSObjectProtocol?
private func registerNotifications(enabled: Bool) {
let notificationCenter = NotificationCenter.default
if enabled {
didUpdateBleStateObserver = notificationCenter.addObserver(forName: .didUpdateBleState, object: nil, queue: .main, using: {[weak self] _ in self?.didUpdateBleState()})
didDiscoverPeripheralObserver = notificationCenter.addObserver(forName: .didDiscoverPeripheral, object: nil, queue: .main, using: {[weak self] _ in self?.didDiscoverPeripheral()})
willConnectToPeripheralObserver = notificationCenter.addObserver(forName: .willConnectToPeripheral, object: nil, queue: .main, using: {[weak self] notification in self?.willConnectToPeripheral(notification: notification)})
didConnectToPeripheralObserver = notificationCenter.addObserver(forName: .didConnectToPeripheral, object: nil, queue: .main, using: {[weak self] notification in self?.didConnectToPeripheral(notification: notification)})
didDisconnectFromPeripheralObserver = notificationCenter.addObserver(forName: .didDisconnectFromPeripheral, object: nil, queue: .main, using: {[weak self] notification in self?.didDisconnectFromPeripheral(notification: notification)})
peripheralDidUpdateNameObserver = notificationCenter.addObserver(forName: .peripheralDidUpdateName, object: nil, queue: .main, using: {[weak self] notification in self?.peripheralDidUpdateName(notification: notification)})
willDiscoverServicesObserver = notificationCenter.addObserver(forName: .willDiscoverServices, object: nil, queue: .main, using: {[weak self] notification in self?.willDiscoverServices(notification: notification)})
} else {
if let didUpdateBleStateObserver = didUpdateBleStateObserver {notificationCenter.removeObserver(didUpdateBleStateObserver)}
if let didDiscoverPeripheralObserver = didDiscoverPeripheralObserver {notificationCenter.removeObserver(didDiscoverPeripheralObserver)}
if let willConnectToPeripheralObserver = willConnectToPeripheralObserver {notificationCenter.removeObserver(willConnectToPeripheralObserver)}
if let didConnectToPeripheralObserver = didConnectToPeripheralObserver {notificationCenter.removeObserver(didConnectToPeripheralObserver)}
if let didDisconnectFromPeripheralObserver = didDisconnectFromPeripheralObserver {notificationCenter.removeObserver(didDisconnectFromPeripheralObserver)}
if let peripheralDidUpdateNameObserver = peripheralDidUpdateNameObserver {notificationCenter.removeObserver(peripheralDidUpdateNameObserver)}
if let willDiscoverServicesObserver = willDiscoverServicesObserver {notificationCenter.removeObserver(willDiscoverServicesObserver)}
}
}
private func didUpdateBleState() {
guard Config.isBleUnsupportedWarningEnabled else { return }
guard let state = bleManager.centralManager?.state else { return }
// Check if there is any error
var errorMessageId: String?
switch state {
case .unsupported:
errorMessageId = "bluetooth_unsupported"
case .unauthorized:
errorMessageId = "bluetooth_notauthorized"
case .poweredOff:
errorMessageId = "bluetooth_poweredoff"
default:
errorMessageId = nil
}
// Show alert if error found
if let errorMessageId = errorMessageId {
let localizationManager = LocalizationManager.shared
let errorMessage = localizationManager.localizedString(errorMessageId)
DLog("Error: \(errorMessage)")
// Reload peripherals
refreshPeripherals()
// Show error
let alertController = UIAlertController(title: localizationManager.localizedString("dialog_error"), message: errorMessage, preferredStyle: .alert)
let okAction = UIAlertAction(title: localizationManager.localizedString("dialog_ok"), style: .default, handler: { (_) -> Void in
if let navController = self.splitViewController?.viewControllers[0] as? UINavigationController {
navController.popViewController(animated: true)
}
})
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
}
private func didDiscoverPeripheral() {
// Update current scanning state
updateScannedPeripherals()
}
private func willConnectToPeripheral(notification: Notification) {
guard let selectedPeripheral = selectedPeripheral, let identifier = notification.userInfo?[BleManager.NotificationUserInfoKey.uuid.rawValue] as? UUID, selectedPeripheral.identifier == identifier else {
DLog("willConnect to an unexpected peripheral")
return
}
let localizationManager = LocalizationManager.shared
updateStatusLabel()
detailLabel.text = localizationManager.localizedString("scanner_connecting")
}
private func didConnectToPeripheral(notification: Notification) {
guard let selectedPeripheral = selectedPeripheral, let identifier = notification.userInfo?[BleManager.NotificationUserInfoKey.uuid.rawValue] as? UUID, selectedPeripheral.identifier == identifier else {
DLog("didConnect to an unexpected peripheral")
return
}
// Setup peripheral
CPBBle.shared.setupPeripheral(blePeripheral: selectedPeripheral) { [weak self] result in
guard let self = self else { return }
switch result {
case .success():
DLog("setupPeripheral success")
// Finished setup
self.showPeripheralDetails()
case .failure(let error):
DLog("setupPeripheral error: \(error.localizedDescription)")
let localizationManager = LocalizationManager.shared
let alertController = UIAlertController(title: localizationManager.localizedString("dialog_error"), message: localizationManager.localizedString("uart_error_peripheralinit"), preferredStyle: .alert)
let okAction = UIAlertAction(title: localizationManager.localizedString("dialog_ok"), style: .default, handler: nil)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
self.disconnect(peripheral: selectedPeripheral)
}
}
}
private func willDiscoverServices(notification: Notification) {
detailLabel.text = LocalizationManager.shared.localizedString("scanner_discoveringservices")
}
private func didDisconnectFromPeripheral(notification: Notification) {
let peripheral = bleManager.peripheral(from: notification)
let currentlyConnectedPeripheralsCount = bleManager.connectedPeripherals().count
guard let selectedPeripheral = selectedPeripheral, selectedPeripheral.identifier == peripheral?.identifier || currentlyConnectedPeripheralsCount == 0 else { // If selected peripheral is disconnected or if there are no peripherals connected (after a failed dfu update)
return
}
// Clear selected peripheral
self.selectedPeripheral = nil
// UI
updateStatusLabel()
}
private func peripheralDidUpdateName(notification: Notification) {
let name = notification.userInfo?[BlePeripheral.NotificationUserInfoKey.name.rawValue] as? String
DLog("centralManager peripheralDidUpdateName: \(name ?? "<unknown>")")
updateStatusLabel()
}
// MARK: - Connections
private func connect(peripheral: BlePeripheral) {
// Connect to selected peripheral
selectedPeripheral = peripheral
bleManager.connect(to: peripheral)
}
private func disconnect(peripheral: BlePeripheral) {
selectedPeripheral = nil
bleManager.disconnect(from: peripheral)
}
// MARK: - Actions
@IBAction func troubleshooting(_ sender: Any) {
guard let viewController = storyboard?.instantiateViewController(withIdentifier: AboutViewController.kIdentifier) else { return }
self.present(viewController, animated: true, completion: nil)
}
@IBAction func scanManually(_ sender: Any) {
ScreenFlowManager.goToManualScan()
}
private func showPeripheralDetails() {
// Save selected peripheral for autoconnect
Settings.autoconnectPeripheralIdentifier = selectedPeripheral?.identifier
ScreenFlowManager.gotoCPBModules()
}
// MARK: - UI
private func refreshPeripherals() {
bleManager.refreshPeripherals()
// reloadBaseTable()
}
private func updateScannedPeripherals() {
// Only update autoconnect if we are not already connecting to a peripheral
guard bleManager.connectedOrConnectingPeripherals().isEmpty else { return }
guard bleManager.scanningElapsedTime ?? 0 > AutoConnectViewController.kMinScanningTimeToAutoconnect else {
DLog("remaining scan time: \(AutoConnectViewController.kMinScanningTimeToAutoconnect - (bleManager.scanningElapsedTime ?? 0))")
return
}
// Sort by RSSI
let filteredPeripherals = peripheralList.filteredPeripherals(forceUpdate: true) // Refresh the peripherals
let sortedPeripherals = filteredPeripherals.sorted { (blePeripheral0, blePeripheral1) -> Bool in
return blePeripheral0.rssi ?? -127 > blePeripheral1.rssi ?? -127
}
//DLog("peripherals: \(sortedPeripherals.count)")
// Connect to closest CPB
guard let peripheral = sortedPeripherals.first else { return }
connect(peripheral: peripheral)
}
private func updateStatusLabel() {
let localizationManager = LocalizationManager.shared
let statusText: String
if let selectedPeripheral = selectedPeripheral {
statusText = "Device found:\n\(selectedPeripheral.name ?? localizationManager.localizedString("scanner_unnamed"))"
// Animate found CPB
UIView.animate(withDuration: 0.1, animations: {
self.cpbContainerView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
}) { finished in
if finished {
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.5, options: [.curveEaseOut], animations: {
self.cpbContainerView.transform = .identity
}, completion: nil)
UIView.animate(withDuration: 0.3) {
self.cpbImageView.alpha = 1
}
}
}
}
else {
UIView.animate(withDuration: 0.3) {
self.cpbImageView.alpha = 0.2
}
statusText = localizationManager.localizedString("scanner_searching")
detailLabel.text = " "
}
statusLabel.text = statusText
}
}
// MARK: UIScrollViewDelegate
extension AutoConnectViewController {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// NavigationBar Button Custom Animation
if let customNavigationBar = navigationController?.navigationBar as? NavigationBarWithScrollAwareRightButton {
customNavigationBar.updateRightButtonPosition()
}
}
}
// MARK: - Animations
extension AutoConnectViewController {
private func stopAnimating() {
isAnimating = false
if isViewLoaded {
let waveImageViews = [wave0ImageView, wave1ImageView]//, wave2ImageView]
for waveImageView in waveImageViews {
if let waveImageView = waveImageView {
waveImageView.layer.removeAllAnimations()
waveImageView.alpha = 0
}
}
}
}
private func startAnimating() {
isAnimating = true
guard isViewLoaded else { return }
let waveImageViews = [wave0ImageView, wave1ImageView]//, wave2ImageView]
// Scanning animation
let duration: Double = 8
let initialScaleFactor: CGFloat = 0.60
let finalScaleFactor: CGFloat = 1.10
let initialAlphaFactor: CGFloat = 0.80
let finalAlphaFactor: CGFloat = 0
// - Initial position
let introMaxValueFactor: CGFloat = 0.7
for (i, waveImageView) in waveImageViews.enumerated() {
if let waveImageView = waveImageView {
//DLog("intro: \(i)")
let factor: CGFloat = CGFloat(i) / CGFloat(waveImageViews.count-1) * introMaxValueFactor
let scaleFactor = (finalScaleFactor - initialScaleFactor) * factor + initialScaleFactor
waveImageView.transform = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor);
let alphaFactor = (finalAlphaFactor - initialAlphaFactor) * factor + initialAlphaFactor
waveImageView.alpha = alphaFactor
// DLog("\(i): factor: \(factor) scale: \(scaleFactor)")
let introDuration = (1 - Double(factor)) * duration
UIView.animate(withDuration: introDuration, delay: 0, options: [.curveEaseOut], animations: {
waveImageView.transform = CGAffineTransform(scaleX: finalScaleFactor, y: finalScaleFactor)
waveImageView.alpha = finalAlphaFactor
}, completion: { _ in
// Ongoing
waveImageView.transform = CGAffineTransform(scaleX: initialScaleFactor, y: initialScaleFactor);
waveImageView.alpha = initialAlphaFactor
UIView.animate(withDuration: duration, delay: 0, options: [.repeat, .curveEaseOut], animations: {
waveImageView.transform = CGAffineTransform(scaleX: finalScaleFactor, y: finalScaleFactor)
waveImageView.alpha = finalAlphaFactor
}, completion: nil)
})
}
}
}
}

View file

@ -1,85 +0,0 @@
//
// BluetoothStatusViewController.swift
// BluefruitPlayground
//
// Created by Antonio García on 30/01/2020.
// Copyright © 2020 Adafruit. All rights reserved.
//
import UIKit
import CoreBluetooth
class BluetoothStatusViewController: UIViewController {
// Constants
//static let kNavigationControllerIdentifier = "BluetoothStatusNavigationController"
static let kIdentifier = "BluetoothStatusViewController"
// UI
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var subtitleLabel: UILabel!
@IBOutlet weak var actionView: UIView!
@IBOutlet weak var actionButton: UIButton!
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Localization
actionButton.setTitle(LocalizationManager.shared.localizedString("bluetooth_enable_action").uppercased(), for: .normal)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Set text messages depending on current state
let messageTitle: String
let message: String
let bluetoothState = Config.bleManager.state
var isActionHidden: Bool
switch bluetoothState {
case .unauthorized:
messageTitle = "bluetooth_notauthorized"
message = "bluetooth_notauthorized_detail"
isActionHidden = false
case .unsupported:
messageTitle = "bluetooth_unsupported_le"
message = "bluetooth_unsupported_le_detail"
isActionHidden = true
case .poweredOff:
messageTitle = "bluetooth_poweredoff"
message = "bluetooth_poweredoff_detail"
isActionHidden = false
default:
DLog("Error: StatusBluetoothViewController in wrong state: \(bluetoothState)")
messageTitle = "bluetooth_unsupported"
message = "bluetooth_unsupported_detail"
isActionHidden = true
break
}
let localizationManager = LocalizationManager.shared
titleLabel.text = localizationManager.localizedString(messageTitle)
subtitleLabel.text = localizationManager.localizedString(message)
let settingsUrl = URL(string: UIApplication.openSettingsURLString)
actionView.isHidden = isActionHidden || settingsUrl == nil || !UIApplication.shared.canOpenURL(settingsUrl!)
}
// MARK: - Actions
@IBAction func enableBluetooth(_ sender: Any) {
let bluetoothState = Config.bleManager.state
if bluetoothState == .poweredOff {
// Force iOS to show the "Turn on bluetooth" alert
let _ = CBCentralManager(delegate: nil, queue: .main, options: [CBCentralManagerOptionShowPowerAlertKey: true])
}
else {
// Go to settings
if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(settingsUrl)
}
}
}
}

View file

@ -37,6 +37,16 @@ class ButtonStatusPanelViewController: ModulePanelViewController {
// Init
switchImageView.tintColor = offColor
// Read initial state
CPBBle.shared.buttonsReadState { [weak self] response in
switch response {
case let .success(buttonsStatus, _):
self?.buttonsStateReceived(buttonsStatus)
case .failure(let error):
DLog("Error receiving temperature data: \(error)")
}
}
// Localization
let localizationManager = LocalizationManager.shared
@ -57,6 +67,8 @@ class ButtonStatusPanelViewController: ModulePanelViewController {
}
private func animateDown(view: UIView) {
UIView.animate(withDuration: 0.2) {
view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
view.tintColor = self.onColor
@ -88,6 +100,7 @@ class ButtonStatusPanelViewController: ModulePanelViewController {
if buttonsState.buttonA != currentState.buttonA {
animateState(view: buttonAStatusView, isPressed: buttonsState.buttonA == .pressed)
// NotificationCenter.default.post(name:NSNotification.Name(rawValue: "Command"), object: nil)
}
if buttonsState.buttonB != currentState.buttonB {

View file

@ -10,9 +10,8 @@ import UIKit
class HomeViewController: UIViewController {
// Constants
static let kNavigationControllerIdentifier = "ModulesNavigationController"
static let kIdentifier = "HomeViewController"
// UI
@IBOutlet weak var baseTableView: UITableView!
@ -67,60 +66,18 @@ class HomeViewController: UIViewController {
super.viewDidLoad()
// Setup UI
/*
let topContentInsetForDetails: CGFloat = 20
baseTableView.contentInset = UIEdgeInsets(top: topContentInsetForDetails, left: 0, bottom: 0, right: 0)
*/
// Localization
let localizationManager = LocalizationManager.shared
self.title = localizationManager.localizedString("modules_title")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
/*
if let customNavigationBar = navigationController?.navigationBar as? NavigationBarWithScrollAwareRightButton {
customNavigationBar.setRightButton(topViewController: self, image: UIImage(named: "info"), target: self, action: #selector(about(_:)))
}*/
/*
if let peripheral = Config.bleManager.connectedPeripherals().first {
peripheral.readRssi()
}*/
}
/*
// MARK: - Actions
@IBAction func about(_ sender: Any) {
guard let viewController = storyboard?.instantiateViewController(withIdentifier: AboutViewController.kIdentifier) else { return }
self.present(viewController, animated: true, completion: nil)
}*/
}
// MARK: - UITableViewDataSource
extension HomeViewController: UITableViewDataSource {
enum CellType {
case details
case module
case disconnect
var reuseIdentifier: String {
switch(self) {
case .details: return "DetailsCell"
case .module: return "ModuleCell"
case .disconnect: return "DisconnectCell"
}
}
}
private func cellTypeFromIndexPath(_ indexPath: IndexPath) -> CellType {
return indexPath.section == 0 ? .details : indexPath.row == menuItems.count ? .disconnect : .module
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2 // Details + Modules
}
@ -130,31 +87,23 @@ extension HomeViewController: UITableViewDataSource {
return 1
}
else {
return menuItems.count + 1 // + 1 disconnect
return menuItems.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellType = cellTypeFromIndexPath(indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: cellType.reuseIdentifier, for: indexPath)
let reuseIdentifier = indexPath.section == 0 ? "DetailsCell" : "ModuleCell"
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath)
// Add cell data here instead of using willDisplay to avoid problems with automatic dimension calculation
let localizationManager = LocalizationManager.shared
let isDetails = indexPath.section == 0
switch(cellType) {
case .details:
if isDetails {
let detailsCell = cell as! TitleTableViewCell
detailsCell.titleLabel.text = localizationManager.localizedString("modules_subtitle")
/*
case .peripheral
let peripheralCell = cell as! CommonTableViewCell
if let peripheral = Config.bleManager.connectedPeripherals().first {
// Fill peripheral data
peripheralCell.titleLabel.text = peripheral.name ?? localizationManager.localizedString("scanner_unnamed")
peripheralCell.iconImageView.image = RssiUI.signalImage(for: peripheral.rssi)
}*/
case .module:
}
else {
let moduleCell = cell as! CommonTableViewCell
let menuItem = menuItems[indexPath.row]
@ -164,18 +113,14 @@ extension HomeViewController: UITableViewDataSource {
let subtitleStringId = menuItem.subtitleStringId
moduleCell.subtitleLabel?.text = localizationManager.localizedString(subtitleStringId)
//moduleCell.setPanelBackgroundColor(menuItem.color)
moduleCell.iconImageView.backgroundColor = menuItem.color
moduleCell.iconImageView.layer.borderColor = UIColor(named: "text_default")?.cgColor
moduleCell.iconImageView.layer.borderWidth = 1
moduleCell.iconImageView.layer.cornerRadius = 7
moduleCell.iconImageView.layer.masksToBounds = true
case .disconnect:
let disconnectCell = cell as! CommonTableViewCell
disconnectCell.titleLabel.text = localizationManager.localizedString("modules_disconnect_title")
disconnectCell.subtitleLabel?.text = localizationManager.localizedString("modules_disconnect_subtitle")
}
return cell
}
}
@ -184,64 +129,36 @@ extension HomeViewController: UITableViewDataSource {
extension HomeViewController: UITableViewDelegate {
/*
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
}*/
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
}*/
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let cellType = cellTypeFromIndexPath(indexPath)
switch(cellType) {
case .details:
break
case .module:
if let module = Modules(rawValue: indexPath.row) {
var storyboardId: String? = nil
switch module {
case .color:
storyboardId = NeoPixelsViewController.kIdentifier
case .light:
storyboardId = LightSensorViewController.kIdentifier
case .button:
storyboardId = ButtonStatusViewController.kIdentifier
case .tone:
storyboardId = ToneGeneratorViewController.kIdentifier
case .accelerometer:
storyboardId = AccelerometerViewController.kIdentifier
case .temperature:
storyboardId = TemperatureViewController.kIdentifier
}
if let identifier = storyboardId, let viewController = storyboard?.instantiateViewController(withIdentifier: identifier) {
// Show viewController with completion block
CATransaction.begin()
self.show(viewController, sender: self)
CATransaction.setCompletionBlock({
// Flash neopixels with the module color
CPBBle.shared.neopixelStartLightSequence(FlashLightSequence(baseColor: module.color), speed: 1, repeating: false, sendLightSequenceNotifications: false)
})
CATransaction.commit()
}
}
case .disconnect:
if let peripheral = Config.bleManager.connectedPeripherals().first {
Config.bleManager.disconnect(from: peripheral, waitForQueuedCommands: true)
}
}
}
}
/*
// MARK: UIScrollViewDelegate
extension HomeViewController {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// NavigationBar Button Custom Animation
if let customNavigationBar = navigationController?.navigationBar as? NavigationBarWithScrollAwareRightButton {
customNavigationBar.updateRightButtonPosition()
let isDetails = indexPath.section == 0
guard !isDetails else { return }
guard let module = Modules(rawValue: indexPath.row) else { return }
var storyboardId: String? = nil
switch module {
case .color:
storyboardId = NeoPixelsViewController.kIdentifier
case .light:
storyboardId = LightSensorViewController.kIdentifier
case .button:
storyboardId = ButtonStatusViewController.kIdentifier
case .tone:
storyboardId = ToneGeneratorViewController.kIdentifier
case .accelerometer:
storyboardId = AccelerometerViewController.kIdentifier
case .temperature:
storyboardId = TemperatureViewController.kIdentifier
}
if let identifier = storyboardId, let viewController = storyboard?.instantiateViewController(withIdentifier: identifier) {
self.show(viewController, sender: self)
}
}
}
*/

View file

@ -1,110 +0,0 @@
//
// LightPanelViewController.swift
// BluefruitPlayground
//
// Created by Antonio García on 31/01/2020.
// Copyright © 2020 Adafruit. All rights reserved.
//
import UIKit
import Charts
class LightChartPanelViewController: ModulePanelViewController {
// Constants
static let kIdentifier = "LightPanelViewController"
// UI
@IBOutlet weak var chartView: LineChartView!
// Data
private var isAutoScrollEnabled = true
private var visibleInterval: TimeInterval = 20 // in seconds
private var dataSet: LineChartDataSet!
private var originTimestamp: CFAbsoluteTime!
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupChart()
// Localization
let localizationManager = LocalizationManager.shared
titleLabel.text = localizationManager.localizedString("lightsensor_chartpanel_title")
}
// MARK: - Line Chart
private func setupChart() {
//chartView.delegate = self
chartView.backgroundColor = .clear // Fix for Charts 3.0.3 (overrides the default background color)
chartView.dragEnabled = false
chartView.isUserInteractionEnabled = false
chartView.chartDescription?.enabled = false
chartView.xAxis.granularityEnabled = true
chartView.xAxis.granularity = 5
chartView.rightAxis.enabled = false
// chartView.rightAxis.valueFormatter =
//chartView.leftAxis.drawZeroLineEnabled = true
//chartView.setExtraOffsets(left: 10, top: 10, right: 10, bottom: 0)
chartView.legend.enabled = false
chartView.noDataText = LocalizationManager.shared.localizedString("temperature_chart_nodata")
// Timestamp
let lightDataSeries = CPBBle.shared.lightDataSeries()
originTimestamp = lightDataSeries.first?.timestamp ?? CFAbsoluteTimeGetCurrent()
// Load initial data
reloadChartEntries()
}
private func reloadChartEntries() {
let lightDataSeries = CPBBle.shared.lightDataSeries()
let chartEntries = lightDataSeries.map { entry -> ChartDataEntry in
let lightReading = Double(entry.value)
return ChartDataEntry(x: entry.timestamp - originTimestamp, y: lightReading)
}
// Add Dataset
dataSet = LineChartDataSet(entries: chartEntries, label: "")
dataSet.drawCirclesEnabled = false
dataSet.drawValuesEnabled = false
dataSet.lineWidth = 2
dataSet.setColor(UIColor.blue)
//dataSet.lineDashLengths = lineDashForPeripheral[identifier]!
//DLog("color: \(color.hexString()!)")
// Set dataset
chartView.data = LineChartData(dataSet: dataSet)
}
private func notifyDataSetChanged() {
let isViewVisible = self.viewIfLoaded?.window != nil // https://stackoverflow.com/questions/2777438/how-to-tell-if-uiviewcontrollers-view-is-visible
guard isViewVisible else { return }
chartView.data?.notifyDataChanged()
chartView.notifyDataSetChanged()
chartView.setVisibleXRangeMaximum(visibleInterval)
chartView.setVisibleXRangeMinimum(visibleInterval)
if isAutoScrollEnabled {
let xOffset = (dataSet.entries.last?.x ?? 0) - (visibleInterval-1)
chartView.moveViewToX(xOffset)
}
}
// MARK: - Actions
func lightValueReceived() {
guard let lastLightDataSeries = CPBBle.shared.lightDataSeries().last else { return }
let light = Double(lastLightDataSeries.value)
let entry = ChartDataEntry(x: lastLightDataSeries.timestamp - originTimestamp, y: light)
let _ = dataSet.append(entry)
notifyDataSetChanged()
}
}

View file

@ -16,7 +16,6 @@ class LightSensorViewController: ModuleViewController {
private var circuitViewController: CircuitViewController!
private var lightmeterPanelViewController: LightSensorPanelViewController!
private var light: Float?
private var chartPanelViewController: LightChartPanelViewController!
// MARK: - Lifecycle
override func viewDidLoad() {
@ -24,9 +23,6 @@ class LightSensorViewController: ModuleViewController {
// Add panels
lightmeterPanelViewController = (addPanelViewController(storyboardIdentifier: LightSensorPanelViewController.kIdentifier) as! LightSensorPanelViewController)
chartPanelViewController = (addPanelViewController(storyboardIdentifier: LightChartPanelViewController.kIdentifier) as! LightChartPanelViewController)
// Localization
let localizationManager = LocalizationManager.shared
@ -65,9 +61,6 @@ class LightSensorViewController: ModuleViewController {
private func updateValueUI() {
if let light = self.light {
lightmeterPanelViewController.lightValueReceived(light)
// Update chart
chartPanelViewController.lightValueReceived()
}
}
}

View file

@ -44,14 +44,9 @@ class CircuitViewController: UIViewController {
}
// Init neopixels color
for (i, neopixelView) in self.neopixelsContainerView.subviews.enumerated() {
if i < self.isNeopixelSelected.count {
neopixelView.backgroundColor = .clear
}
}
//neopixelsReset(animated: false)
neopixelsReset(animated: false)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
@ -67,7 +62,7 @@ class CircuitViewController: UIViewController {
// Notifications
registerNotifications(enabled: true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
@ -102,7 +97,8 @@ class CircuitViewController: UIViewController {
}
}
}
// MARK: - Notifications
private var didUpdateNeopixelLightSequenceObserver: NSObjectProtocol?
@ -126,6 +122,7 @@ class CircuitViewController: UIViewController {
}
}
}
}
} else {
if let didUpdateNeopixelLightSequenceObserver = didUpdateNeopixelLightSequenceObserver {notificationCenter.removeObserver(didUpdateNeopixelLightSequenceObserver)}
@ -171,6 +168,7 @@ class CircuitViewController: UIViewController {
// Animate
UIView.animate(withDuration: 0.2, animations: {
selectedView.transform = isSelected ? .identity:CGAffineTransform(scaleX: 1.2, y: 1.2)
//selectedView.layer.borderWidth = self.selectedWidth// isSelected ? self.selectedWidth:0
selectedView.alpha = isSelected ? 1:CircuitViewController.kUnselectedButtonAlpha
}) { (_) in
selectedView.transform = .identity
@ -183,7 +181,7 @@ class CircuitViewController: UIViewController {
}
// MARK: - Actions
func setNeopixelsColor(_ color: UIColor, onlySelected: Bool, animated: Bool, baseColor: UIColor? = nil) {
func setNeopixelsColor(_ color: UIColor, onlySelected: Bool, animated: Bool) {
if onlySelected {
CPBBle.shared.neopixelSetPixelColor(color, pixelMask: isNeopixelSelected)
@ -194,10 +192,9 @@ class CircuitViewController: UIViewController {
// UI Animation
UIView.animate(withDuration: animated ? 0.1: 0) {
// Base color is the color withouth the brightness. Used in the circuit representation to show the colors more vividly
for (i, neopixelView) in self.neopixelsContainerView.subviews.enumerated() {
if i < self.isNeopixelSelected.count && (!onlySelected || self.isNeopixelSelected[i]) {
neopixelView.backgroundColor = baseColor ?? color
neopixelView.backgroundColor = color
}
}
}

View file

@ -80,9 +80,9 @@ class NeoPixelsViewController: ModuleViewController {
circuitViewController.neopixelsReset(animated: true)
}
private func selectColor(_ color: UIColor, baseColor: UIColor) {
private func selectColor(_ color: UIColor) {
// Update circuit
circuitViewController.setNeopixelsColor(color, onlySelected: true, animated: true, baseColor: baseColor)
circuitViewController.setNeopixelsColor(color, onlySelected: true, animated: true)
}
// MARK: - Page Management
@ -104,15 +104,14 @@ class NeoPixelsViewController: ModuleViewController {
// MARK: - NeopixelsColorPaletteViewControllerDelegate
extension NeoPixelsViewController: NeopixelsColorPaletteViewControllerDelegate {
func colorPaletteColorSelected(color: UIColor, baseColor: UIColor) {
selectColor(color, baseColor: baseColor)
func colorPaletteColorSelected(color: UIColor) {
selectColor(color)
}
}
// MARK: - NeopixelColorWheelViewControllerDelegate
// MARK: - NeopixelsColorPaletteViewControllerDelegate
extension NeoPixelsViewController: NeopixelColorWheelViewControllerDelegate {
func colorWheelColorSelected(color: UIColor, baseColor: UIColor) {
selectColor(color, baseColor: baseColor)
func colorWheelColorSelected(color: UIColor) {
selectColor(color)
}
}

View file

@ -10,7 +10,7 @@ import UIKit
import FlexColorPicker
protocol NeopixelsColorPaletteViewControllerDelegate: class {
func colorPaletteColorSelected(color: UIColor, baseColor: UIColor)
func colorPaletteColorSelected(color: UIColor)
}
class NeopixelsColorPaletteViewController: ModulePanelViewController {
@ -124,6 +124,6 @@ class NeopixelsColorPaletteViewController: ModulePanelViewController {
let hsbColor = HSBColor(color: color)
let colorWithBrighness = hsbColor.withBrightness(brightness).toUIColor()
delegate?.colorPaletteColorSelected(color: colorWithBrighness, baseColor: color)
delegate?.colorPaletteColorSelected(color: colorWithBrighness)
}
}

View file

@ -10,7 +10,7 @@ import UIKit
import FlexColorPicker
protocol NeopixelColorWheelViewControllerDelegate: class {
func colorWheelColorSelected(color: UIColor, baseColor: UIColor)
func colorWheelColorSelected(color: UIColor)
}
class NeopixelsColorWheelViewController: ModulePanelViewController {
@ -96,7 +96,7 @@ class NeopixelsColorWheelViewController: ModulePanelViewController {
let hsbColor = HSBColor(color: color)
let colorWithBrighness = hsbColor.withBrightness(brightness).toUIColor()
delegate?.colorWheelColorSelected(color: colorWithBrighness, baseColor: color)
delegate?.colorWheelColorSelected(color: colorWithBrighness)
}
}

View file

@ -20,6 +20,7 @@ class NeopixelsLightSequenceViewController: ModulePanelViewController {
// Data
private var previewViewControllers = [PixelsPreviewViewController]()
private var speed: Double = 0.3
// MARK: - Lifecycle
override func viewDidLoad() {
@ -29,10 +30,8 @@ class NeopixelsLightSequenceViewController: ModulePanelViewController {
let _ = previewViewControllers.map{$0.tag = $0.view.superview?.tag ?? 0}
// Set initial speed
let speed = CPBBle.kLightSequenceDefaultSpeed
speedSlider.value = Float(speed)
//speedChanged(speedSlider)
let _ = previewViewControllers.map{$0.speed = speed}
speedChanged(speedSlider)
// Localization
let localizationManager = LocalizationManager.shared
@ -48,10 +47,10 @@ class NeopixelsLightSequenceViewController: ModulePanelViewController {
}
@IBAction func speedChanged(_ sender: UISlider) {
let speed = Double(sender.value)
speed = Double(sender.value)
DLog("speed: \(speed)")
CPBBle.shared.neopixelCurrentLightSequenceAnimationSpeed = speed
CPBBle.shared.neopixelLightSequenceAnimationSpeed = speed
let _ = previewViewControllers.map{$0.speed = speed}
}
}

View file

@ -52,7 +52,7 @@ class PixelsPreviewViewController: UIViewController {
guard let lightSequenceGenerator = lightSequenceGeneratorForTag(self.tag) else { return }
lightSequenceAnimation = LightSequenceAnimation(lightSequenceGenerator: lightSequenceGenerator, framesPerSecond: 10, repeating: true)
lightSequenceAnimation = LightSequenceAnimation(lightSequenceGenerator: lightSequenceGenerator, framesPerSecond: 10)
lightSequenceAnimation!.speed = speed
lightSequenceAnimation!.start() { [weak self] pixelsBytes in
guard let self = self else { return }
@ -72,8 +72,6 @@ class PixelsPreviewViewController: UIViewController {
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
lightSequenceAnimation?.stop()
}
override func viewDidLayoutSubviews() {

View file

@ -70,17 +70,6 @@ class AboutViewController: UIViewController {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
label.configureLinkAttribute = { (type, attributes, isSelected) in
var atts = attributes
switch type {
case customType0, customType1:
atts[.underlineStyle] = NSUnderlineStyle.single.rawValue
default: ()
}
return atts
}
}
}

View file

@ -55,16 +55,6 @@ extension ScanProblemsViewController: UITableViewDataSource {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
label.configureLinkAttribute = { (type, attributes, isSelected) in
var atts = attributes
switch type {
case customType:
atts[.underlineStyle] = NSUnderlineStyle.single.rawValue
default: ()
}
return atts
}
}

View file

@ -11,9 +11,10 @@ import CoreBluetooth
class ScannerViewController: UIViewController {
// Constants
static let kNavigationControllerIdentifier = "ScannerNavigationController"
static let kIdentifier = "ScannerNavigationController" //"ScannerViewController"
// Config
private static let kServicesToScan: [CBUUID]? = nil //[BlePeripheral.kUartServiceUUID]
private static let kDelayToShowWait: TimeInterval = 1.0
// UI
@ -21,23 +22,13 @@ class ScannerViewController: UIViewController {
@IBOutlet weak var waitView: UIView!
@IBOutlet weak var waitLabel: UILabel!
@IBOutlet weak var problemsButton: UIButton!
@IBOutlet weak var scanAutomaticallyButton: CornerShadowButton!
@IBOutlet weak var actionsContainerView: UIStackView!
// Data
private let refreshControl = UIRefreshControl()
private let bleManager = Config.bleManager
private var peripheralList = PeripheralList(bleManager: Config.bleManager)
private var selectedPeripheral: BlePeripheral? {
didSet {
if isViewLoaded {
UIView.animate(withDuration: 0.3) {
self.actionsContainerView.alpha = self.selectedPeripheral == nil ? 1:0
}
}
}
}
private var selectedPeripheral: BlePeripheral?
private var infoAlertController: UIAlertController?
private var isBaseTableScrolling = false
@ -68,7 +59,6 @@ class ScannerViewController: UIViewController {
waitLabel.text = localizationManager.localizedString("scanner_searching")
problemsButton.setTitle(localizationManager.localizedString("scanner_problems_action").uppercased(), for: .normal)
scanAutomaticallyButton.setTitle(localizationManager.localizedString("scanner_automatic_action").uppercased(), for: .normal)
}
override func viewWillAppear(_ animated: Bool) {
@ -85,7 +75,7 @@ class ScannerViewController: UIViewController {
if connectedPeripherals.count == 1, let peripheral = connectedPeripherals.first {
DLog("Disconnect from previously connected peripheral")
// Disconnect from peripheral
disconnect(peripheral: peripheral)
bleManager.disconnect(from: peripheral)
}
}
@ -99,26 +89,21 @@ class ScannerViewController: UIViewController {
updateScannedPeripherals()
// Start scannning
//bleManager.startScan(withServices: ScannerViewController.kServicesToScan)
if !bleManager.isScanning {
bleManager.startScan()
}
// Remove saved peripheral for autoconnect
Settings.autoconnectPeripheralIdentifier = nil
bleManager.startScan(withServices: ScannerViewController.kServicesToScan)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Stop scanning
bleManager.stopScan()
// Clear peripherals
peripheralList.clear()
}
deinit {
// Ble Notifications
registerNotifications(enabled: false)
}
@ -232,7 +217,7 @@ class ScannerViewController: UIViewController {
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
self.disconnect(peripheral: selectedPeripheral)
self.bleManager.disconnect(from: selectedPeripheral)
}
}
}
@ -258,6 +243,18 @@ class ScannerViewController: UIViewController {
// Reload table
reloadBaseTable()
// If is not the topViewController, pop any other thing and come back to scanning
if self.navigationController?.topViewController !== self {
self.navigationController?.popToRootViewController(animated: false)
// Show disconnection alert
let localizationManager = LocalizationManager.shared
let alertController = UIAlertController(title: nil, message: localizationManager.localizedString("scanner_peripheraldisconnected"), preferredStyle: .alert)
let okAction = UIAlertAction(title: localizationManager.localizedString("dialog_ok"), style: .default, handler: nil)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
}
private func peripheralDidUpdateName(notification: Notification) {
@ -296,16 +293,17 @@ class ScannerViewController: UIViewController {
self.present(viewController, animated: true, completion: nil)
}
@IBAction func scanAutomatically(_ sender: Any) {
ScreenFlowManager.gotoAutoconnect()
}
private func showPeripheralDetails() {
// Save selected peripheral for autoconnect
Settings.autoconnectPeripheralIdentifier = selectedPeripheral?.identifier
// Go to home screen
ScreenFlowManager.gotoCPBModules()
let backItem = UIBarButtonItem()
backItem.title = LocalizationManager.shared.localizedString("scanner_backbutton")
self.navigationItem.backBarButtonItem = backItem
guard let modulesViewController = self.storyboard?.instantiateViewController(withIdentifier: HomeViewController.kIdentifier) else { return }
self.show(modulesViewController, sender: self)
}
// MARK: - UI
@ -458,7 +456,16 @@ extension ScannerViewController {
reloadBaseTable()
}
}
/*
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
isBaseTableScrolling = false
if isScannerTableWaitingForReload {
reloadBaseTable()
}
}*/
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// NavigationBar Button Custom Animation
@ -468,7 +475,7 @@ extension ScannerViewController {
// Move Refresh control when a large title is used
if let height = navigationController?.navigationBar.frame.height {
refreshControl.bounds = CGRect(x: refreshControl.bounds.origin.x, y: NavigationBarWithScrollAwareRightButton.navBarHeightLargeState - height, width: refreshControl.bounds.size.width, height: refreshControl.bounds.size.height)
refreshControl.bounds = CGRect(x: refreshControl.bounds.origin.x, y: NavigationBarWithScrollAwareRightButton.CustomButtonMetrics.navBarHeightLargeState - height, width: refreshControl.bounds.size.width, height: refreshControl.bounds.size.height)
}
// Hide details opacity when showing the refresh control

View file

@ -17,10 +17,6 @@ class StartupViewController: UIViewController {
private static let kServicesToReconnect = [BlePeripheral.kUartServiceUUID]
private static let kReconnectTimeout = 2.0
// UI
@IBOutlet weak var restoreConnectionLabel: UILabel!
// Data
private let bleSupportSemaphore = DispatchSemaphore(value: 0)
private var startTime: CFAbsoluteTime!
@ -28,12 +24,6 @@ class StartupViewController: UIViewController {
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// UI
restoreConnectionLabel.alpha = 0
// Localization
restoreConnectionLabel.text = LocalizationManager.shared.localizedString("splash_restoringconnection")
}
override func viewDidAppear(_ animated: Bool) {
@ -134,7 +124,6 @@ class StartupViewController: UIViewController {
// MARK: - Reconnect previously connnected Ble Peripheral
private func reconnecToPeripheral(withIdentifier identifier: UUID) {
DLog("Reconnecting...")
// Reconnect
let isTryingToReconnect = BleManager.shared.reconnecToPeripherals(withIdentifiers: [identifier], withServices: StartupViewController.kServicesToReconnect, timeout: StartupViewController.kReconnectTimeout)
@ -156,22 +145,23 @@ class StartupViewController: UIViewController {
}
private func didDisconnectFromPeripheral() {
/*
// Clear selected peripheral
self.selectedPeripheral = nil
*/
// Autoconnect failed
connected(peripheral: nil)
}
private func connected(peripheral: BlePeripheral?) {
if let peripheral = peripheral {
// Show restoring connection label
UIView.animate(withDuration: 0.2) {
self.restoreConnectionLabel.alpha = 1
}
// Setup peripheral
CPBBle.shared.setupPeripheral(blePeripheral: peripheral) { [weak self] result in
switch result {
case .success():
ScreenFlowManager.restoreAndGoToCPBModules()
ScreenFlowManager.restoreAndGoToHome()
case .failure(let error):
DLog("Failed setup peripheral: \(error.localizedDescription)")
@ -190,15 +180,12 @@ class StartupViewController: UIViewController {
// MARK: - Screen Flow
private func gotoInitialScreen() {
let viewControllerIdentifier = StartupViewController.kForcedNavigationControllerIdentifier ?? (Settings.areTipsEnabled && Config.isTutorialEnabled ? TipsViewController.kIdentifier : AutoConnectViewController.kNavigationControllerIdentifier)
let viewControllerIdentifier = StartupViewController.kForcedNavigationControllerIdentifier ?? (Settings.areTipsEnabled && Config.isTutorialEnabled ? TipsViewController.kIdentifier : ScannerViewController.kIdentifier)
DLog("Start app with viewController: \(viewControllerIdentifier)")
// Change splash screen to main screen
if let rootViewController = self.storyboard?.instantiateViewController(withIdentifier: viewControllerIdentifier) {
ScreenFlowManager.changeRootViewController(rootViewController: rootViewController) {
// Start scannning
//Config.bleManager.startScan()
}
ScreenFlowManager.changeRootViewController(rootViewController: rootViewController, animated: false)
}
}

View file

@ -46,7 +46,7 @@ class TemperaturePanelViewController: ModulePanelViewController {
// MARK: - Line Chart
private func setupChart() {
//chartView.delegate = self
chartView.backgroundColor = .clear // Fix for Charts 3.0.3 (overrides the default background color)
chartView.backgroundColor = .white // Fix for Charts 3.0.3 (overrides the default background color)
chartView.dragEnabled = false
chartView.isUserInteractionEnabled = false

View file

@ -86,15 +86,6 @@ class TipViewController: UIViewController {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
label.configureLinkAttribute = { (type, attributes, isSelected) in
var atts = attributes
switch type {
case customType:
atts[.underlineStyle] = NSUnderlineStyle.single.rawValue
default: ()
}
return atts
}
}
}
}

View file

@ -134,7 +134,7 @@ class TipsViewController: UIViewController {
}
@IBAction func skip(_ sender: Any) {
ScreenFlowManager.gotoAutoconnect()
ScreenFlowManager.gotoScanner()
}
@objc private func pageControlTapHandler(sender: UIPageControl) {

View file

@ -46,10 +46,6 @@ class ToneGeneratorViewController: UIViewController {
@IBOutlet weak var keysContainerView: UIView!
@IBOutlet weak var whiteKeysStackView: UIStackView!
@IBOutlet weak var speakerImageView: UIImageView!
@IBOutlet weak var halfKeyTop: UIButton!
@IBOutlet weak var halfKeyBottom: UIButton!
@IBOutlet weak var extraHalfKeyTop: UIButton!
@IBOutlet weak var extraHalfKeyBottom: UIButton!
// Data
private var tonesPlaying = Set<Int>()
@ -79,56 +75,25 @@ class ToneGeneratorViewController: UIViewController {
}
}
// - Add 2 extra hidden buttons to extend the touch area of the black half-keys
for button in [extraHalfKeyTop!, extraHalfKeyBottom!] {
button.addTarget(self, action: #selector(redirectHalfKeyDown(_:)), for: .touchDown)
button.addTarget(self, action: #selector(redirectHalfKeyUp(_:)), for: [.touchUpInside, .touchUpOutside, .touchCancel])
}
// - Round the container so the half-keys that are clipped look rounded too
keysContainerView.layer.cornerRadius = 4
keysContainerView.layer.masksToBounds = true
// Localization
let localizationManager = LocalizationManager.shared
self.title = localizationManager.localizedString("tonegenerator_title")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Navigationbar setup
if let customNavigationBar = navigationController?.navigationBar as? NavigationBarWithScrollAwareRightButton {
customNavigationBar.setRightButton(topViewController: self, image: UIImage(named: "help"), target: self, action: #selector(help(_:)))
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Disable the pop recognizer to avoid problems with keys on the edges
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Re-enable the pop recognizer
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
}
override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
// Remove the system gestures from left and right edges
return [.top, .bottom] // not left or right
}
super.viewWillAppear(animated)
// Navigationbar setup
if let customNavigationBar = navigationController?.navigationBar as? NavigationBarWithScrollAwareRightButton {
customNavigationBar.setRightButton(topViewController: self, image: UIImage(named: "help"), target: self, action: #selector(help(_:)))
}
}
// MARK: - Actions
@objc private func keyDown(_ sender: UIButton) {
let tag = sender.tag
tonesPlaying.insert(tag)
// UI Animation
UIView.animate(withDuration: 0.2) {
self.speakerImageView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
@ -143,7 +108,7 @@ class ToneGeneratorViewController: UIViewController {
@objc private func keyUp(_ sender: UIButton) {
let tag = sender.tag
tonesPlaying.remove(tag)
// UI Animation
if tonesPlaying.isEmpty {
UIView.animate(withDuration: 0.15) {
@ -160,20 +125,6 @@ class ToneGeneratorViewController: UIViewController {
}
}
@objc private func redirectHalfKeyDown(_ sender: UIButton) {
let targetButton = sender === extraHalfKeyTop ? halfKeyTop : halfKeyBottom
targetButton?.sendActions(for: .touchDown)
halfKeyTop.isHighlighted = true
halfKeyBottom.isHighlighted = true
}
@objc private func redirectHalfKeyUp(_ sender: UIButton) {
let targetButton = sender === extraHalfKeyTop ? halfKeyTop : halfKeyBottom
targetButton?.sendActions(for: .touchUpInside)
halfKeyTop.isHighlighted = false
halfKeyBottom.isHighlighted = false
}
private func frequencyForKeyTag(_ tag: Int) -> Double? {
var frequency: Double? = nil
if tag >= 100 && tag < 200 { // is white note
@ -199,4 +150,3 @@ class ToneGeneratorViewController: UIViewController {
self.present(navigationController, animated: true, completion: nil)
}
}

View file

@ -27,7 +27,7 @@ class BluefruitPlaygroundUITests: XCTestCase {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testSnapshots() {
func testExample() {
/*
// UI tests must launch the application that they test.
let app = XCUIApplication()
@ -40,7 +40,7 @@ class BluefruitPlaygroundUITests: XCTestCase {
let scrollViewsQuery = app.scrollViews
let elementsQuery = scrollViewsQuery.otherElements
sleep(2) // Wait for the intro animation
sleep(3) // Wait for the intro animation
snapshot("01a_Welcome")
elementsQuery.buttons["LET'S GET STARTED..."].tap()
@ -51,7 +51,7 @@ class BluefruitPlaygroundUITests: XCTestCase {
elementsQuery.buttons["BEGIN PAIRING"].tap()
let tablesQuery = app.tables
tablesQuery.staticTexts["Simulated Peripheral"].tap()
tablesQuery/*@START_MENU_TOKEN@*/.staticTexts["Simulated Peripheral"]/*[[".cells.staticTexts[\"Simulated Peripheral\"]",".staticTexts[\"Simulated Peripheral\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap()
snapshot("02_Modules")

View file

@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.2)
CFPropertyList (2.3.6)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
@ -18,7 +18,7 @@ GEM
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.5)
emoji_regex (1.0.1)
excon (0.71.1)
excon (0.71.0)
faraday (0.17.1)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
@ -27,7 +27,7 @@ GEM
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7)
fastlane (2.138.0)
fastlane (2.137.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
@ -36,7 +36,7 @@ GEM
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 2.0)
excon (>= 0.71.0, < 1.0.0)
excon (>= 0.45.0, < 1.0.0)
faraday (~> 0.17)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 0.13.1)
@ -61,7 +61,7 @@ GEM
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcodeproj (>= 1.8.1, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
@ -93,7 +93,7 @@ GEM
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
json (2.3.0)
json (2.1.0)
jwt (2.1.0)
memoist (0.16.2)
mime-types (3.3)
@ -121,7 +121,7 @@ GEM
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.7)
simctl (1.6.6)
CFPropertyList
naturally
slack-notifier (2.3.2)
@ -138,7 +138,7 @@ GEM
unf_ext (0.0.7.6)
unicode-display_width (1.6.0)
word_wrap (1.0.0)
xcodeproj (1.14.0)
xcodeproj (1.12.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)

View file

@ -1,3 +0,0 @@
# The Deliverfile allows you to store various App Store Connect metadata
# For more information, check out the docs
# https://docs.fastlane.tools/actions/deliver/

View file

@ -6,8 +6,8 @@
"iPhone 11",
"iPhone 8 Plus",
"iPhone 8",
# "iPhone SE",
"iPhone SE",
# "iPhone 6",
# "iPhone 6 Plus",
# "iPhone 5",
@ -29,8 +29,7 @@ languages([
# Where should the resulting screenshots be stored?
# output_directory "./screenshots"
# remove the '#' to clear all previously generated screenshots before creating new ones
clear_previous_screenshots true
# clear_previous_screenshots true # remove the '#' to clear all previously generated screenshots before creating new ones
# Choose which project/workspace to use
# project "./Project.xcodeproj"