Compare commits
1 commit
master
...
puppetModu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6d10619ff |
BIN
.DS_Store
vendored
Normal file
|
|
@ -7,6 +7,7 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* 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 */; };
|
A901EC48237C43C000687BE6 /* Data+LittleEndianTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A901EC47237C43C000687BE6 /* Data+LittleEndianTypes.swift */; };
|
||||||
A901EC4D237C47D800687BE6 /* CPBDataSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = A901EC4C237C47D800687BE6 /* CPBDataSeries.swift */; };
|
A901EC4D237C47D800687BE6 /* CPBDataSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = A901EC4C237C47D800687BE6 /* CPBDataSeries.swift */; };
|
||||||
A92B9F0D234DE4A2002374F0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92B9F0C234DE4A2002374F0 /* AppDelegate.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 */; };
|
A9BED77A234FBEAB002FFF53 /* CommandQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BED763234FBEAB002FFF53 /* CommandQueue.swift */; };
|
||||||
A9BED77B234FBEAB002FFF53 /* LogHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BED764234FBEAB002FFF53 /* LogHelper.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 */; };
|
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 */; };
|
A9DF06122359B0600094327F /* ButtonStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DF06112359B0600094327F /* ButtonStatusViewController.swift */; };
|
||||||
A9DF06142359B31C0094327F /* ButtonStatusPanelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DF06132359B31C0094327F /* ButtonStatusPanelViewController.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 */; };
|
A9E4C02E2358E6B400B5A493 /* UIColor+LightAndDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E4C02D2358E6B400B5A493 /* UIColor+LightAndDark.swift */; };
|
||||||
|
|
@ -222,6 +213,7 @@
|
||||||
/* Begin PBXFileReference section */
|
/* 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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
A9E4C02D2358E6B400B5A493 /* UIColor+LightAndDark.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+LightAndDark.swift"; sourceTree = "<group>"; };
|
||||||
|
|
@ -491,7 +478,6 @@
|
||||||
children = (
|
children = (
|
||||||
A986AB92235942D600DF12C8 /* LightSensorViewController.swift */,
|
A986AB92235942D600DF12C8 /* LightSensorViewController.swift */,
|
||||||
A98F1B8A23594BE400C5269E /* LightSensorPanelViewController.swift */,
|
A98F1B8A23594BE400C5269E /* LightSensorPanelViewController.swift */,
|
||||||
A9D12E8523E3C5CF00F3C259 /* LightChartPanelViewController.swift */,
|
|
||||||
);
|
);
|
||||||
path = LightSensor;
|
path = LightSensor;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -500,6 +486,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A98E4D3123566785005B166D /* cpb.scn */,
|
A98E4D3123566785005B166D /* cpb.scn */,
|
||||||
|
2981E3B423C6526C00974EA2 /* Sparky_Gold1.dae */,
|
||||||
A96AA5BA235F903D004A06BA /* CPB_PcbSurface_Color.png */,
|
A96AA5BA235F903D004A06BA /* CPB_PcbSurface_Color.png */,
|
||||||
);
|
);
|
||||||
path = models3d;
|
path = models3d;
|
||||||
|
|
@ -546,8 +533,6 @@
|
||||||
A9BED700234F754B002FFF53 /* Utils */ = {
|
A9BED700234F754B002FFF53 /* Utils */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A99BA722235000520080D03F /* ScreenFlowManager.swift */,
|
|
||||||
A9BED70C234F76C5002FFF53 /* LocalizationManager.swift */,
|
|
||||||
A9716BA92350128100CB174D /* RssiUI.swift */,
|
A9716BA92350128100CB174D /* RssiUI.swift */,
|
||||||
A96AA5D2236358A8004A06BA /* UIView+RoundedCorners.swift */,
|
A96AA5D2236358A8004A06BA /* UIView+RoundedCorners.swift */,
|
||||||
A96AA5D423635A89004A06BA /* NSLayoutConstraint+ChangeMultiplier.swift */,
|
A96AA5D423635A89004A06BA /* NSLayoutConstraint+ChangeMultiplier.swift */,
|
||||||
|
|
@ -558,10 +543,11 @@
|
||||||
A96AA5C0236050E4004A06BA /* NavigationBarWithScrollAwareRightButton.swift */,
|
A96AA5C0236050E4004A06BA /* NavigationBarWithScrollAwareRightButton.swift */,
|
||||||
A9BED701234F754B002FFF53 /* GradientView.swift */,
|
A9BED701234F754B002FFF53 /* GradientView.swift */,
|
||||||
A96AA5D023634F03004A06BA /* GridView.swift */,
|
A96AA5D023634F03004A06BA /* GridView.swift */,
|
||||||
|
A9BED70C234F76C5002FFF53 /* LocalizationManager.swift */,
|
||||||
|
A99BA722235000520080D03F /* ScreenFlowManager.swift */,
|
||||||
A9B3569E2365D420002093EE /* PanelInsetView.swift */,
|
A9B3569E2365D420002093EE /* PanelInsetView.swift */,
|
||||||
A958E77D238B7457006A225A /* NormalBrightnessSliderControl.swift */,
|
A958E77D238B7457006A225A /* NormalBrightnessSliderControl.swift */,
|
||||||
A958E77F238B75D9006A225A /* NormalBrightnessColorPickerController.swift */,
|
A958E77F238B75D9006A225A /* NormalBrightnessColorPickerController.swift */,
|
||||||
A9D12E8323E3661800F3C259 /* UIColor+Interpolate.swift */,
|
|
||||||
);
|
);
|
||||||
path = Utils;
|
path = Utils;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -633,19 +619,10 @@
|
||||||
A9BED766234FBEAB002FFF53 /* Data+ScanValue.swift */,
|
A9BED766234FBEAB002FFF53 /* Data+ScanValue.swift */,
|
||||||
A901EC47237C43C000687BE6 /* Data+LittleEndianTypes.swift */,
|
A901EC47237C43C000687BE6 /* Data+LittleEndianTypes.swift */,
|
||||||
A9BDEC40237C75C900EDDC46 /* Types+Data.swift */,
|
A9BDEC40237C75C900EDDC46 /* Types+Data.swift */,
|
||||||
A9D5B21223E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift */,
|
|
||||||
);
|
);
|
||||||
path = Utils;
|
path = Utils;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
A9D5B20E23E0983F00178E1B /* AutoConnect */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
A9D5B20F23E0985600178E1B /* AutoConnectViewController.swift */,
|
|
||||||
);
|
|
||||||
path = AutoConnect;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
A9DF06102359B04C0094327F /* ButtonStatus */ = {
|
A9DF06102359B04C0094327F /* ButtonStatus */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -692,10 +669,8 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A9BED716234FBDBA002FFF53 /* StartupViewController.swift */,
|
A9BED716234FBDBA002FFF53 /* StartupViewController.swift */,
|
||||||
A9D5B21523E3024700178E1B /* BluetoothStatusViewController.swift */,
|
|
||||||
A9482988237B73B100B6FC13 /* Common */,
|
A9482988237B73B100B6FC13 /* Common */,
|
||||||
A9BED703234F75E4002FFF53 /* Tips */,
|
A9BED703234F75E4002FFF53 /* Tips */,
|
||||||
A9D5B20E23E0983F00178E1B /* AutoConnect */,
|
|
||||||
A9EAA18D2350B26B00FA615E /* Scanner */,
|
A9EAA18D2350B26B00FA615E /* Scanner */,
|
||||||
A9EAA1982351F1DD00FA615E /* Home */,
|
A9EAA1982351F1DD00FA615E /* Home */,
|
||||||
A9EAA19F2352973400FA615E /* Neopixels */,
|
A9EAA19F2352973400FA615E /* Neopixels */,
|
||||||
|
|
@ -826,6 +801,7 @@
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
2981E3B523C6526C00974EA2 /* Sparky_Gold1.dae in Resources */,
|
||||||
A98E4D3223566785005B166D /* cpb.scn in Resources */,
|
A98E4D3223566785005B166D /* cpb.scn in Resources */,
|
||||||
A92B9F19234DE4A5002374F0 /* LaunchScreen.storyboard in Resources */,
|
A92B9F19234DE4A5002374F0 /* LaunchScreen.storyboard in Resources */,
|
||||||
A99BA71F234FFECF0080D03F /* DefaultPreferences.plist in Resources */,
|
A99BA71F234FFECF0080D03F /* DefaultPreferences.plist in Resources */,
|
||||||
|
|
@ -957,7 +933,6 @@
|
||||||
A92B9F0D234DE4A2002374F0 /* AppDelegate.swift in Sources */,
|
A92B9F0D234DE4A2002374F0 /* AppDelegate.swift in Sources */,
|
||||||
A96AA5C6236312CE004A06BA /* ToneGeneratorViewController.swift in Sources */,
|
A96AA5C6236312CE004A06BA /* ToneGeneratorViewController.swift in Sources */,
|
||||||
A9BED77D234FBEAB002FFF53 /* Data+ScanValue.swift in Sources */,
|
A9BED77D234FBEAB002FFF53 /* Data+ScanValue.swift in Sources */,
|
||||||
A9D12E8623E3C5CF00F3C259 /* LightChartPanelViewController.swift in Sources */,
|
|
||||||
A901EC48237C43C000687BE6 /* Data+LittleEndianTypes.swift in Sources */,
|
A901EC48237C43C000687BE6 /* Data+LittleEndianTypes.swift in Sources */,
|
||||||
A980FE6D2357D14400E3A909 /* TipPowerUpViewController.swift in Sources */,
|
A980FE6D2357D14400E3A909 /* TipPowerUpViewController.swift in Sources */,
|
||||||
A94AC14E2364A82B0062AB11 /* CPBBle.swift in Sources */,
|
A94AC14E2364A82B0062AB11 /* CPBBle.swift in Sources */,
|
||||||
|
|
@ -990,7 +965,6 @@
|
||||||
A958E782238B7936006A225A /* BlePeripheral+CPBAccelerometer.swift in Sources */,
|
A958E782238B7936006A225A /* BlePeripheral+CPBAccelerometer.swift in Sources */,
|
||||||
A9DF06122359B0600094327F /* ButtonStatusViewController.swift in Sources */,
|
A9DF06122359B0600094327F /* ButtonStatusViewController.swift in Sources */,
|
||||||
A96AA5D3236358A8004A06BA /* UIView+RoundedCorners.swift in Sources */,
|
A96AA5D3236358A8004A06BA /* UIView+RoundedCorners.swift in Sources */,
|
||||||
A9D5B21023E0985600178E1B /* AutoConnectViewController.swift in Sources */,
|
|
||||||
A98E4D2823565B76005B166D /* TipWelcomeViewController.swift in Sources */,
|
A98E4D2823565B76005B166D /* TipWelcomeViewController.swift in Sources */,
|
||||||
A9BED70B234F7617002FFF53 /* Config.swift in Sources */,
|
A9BED70B234F7617002FFF53 /* Config.swift in Sources */,
|
||||||
A980FE6F2357D15400E3A909 /* TipDiscoverViewController.swift in Sources */,
|
A980FE6F2357D15400E3A909 /* TipDiscoverViewController.swift in Sources */,
|
||||||
|
|
@ -1006,7 +980,6 @@
|
||||||
A98F1B8B23594BE400C5269E /* LightSensorPanelViewController.swift in Sources */,
|
A98F1B8B23594BE400C5269E /* LightSensorPanelViewController.swift in Sources */,
|
||||||
A96AA5C32362F488004A06BA /* AboutViewController.swift in Sources */,
|
A96AA5C32362F488004A06BA /* AboutViewController.swift in Sources */,
|
||||||
A9A08BAF23A7957700498069 /* BlePeripheral+CPBButtons.swift in Sources */,
|
A9A08BAF23A7957700498069 /* BlePeripheral+CPBButtons.swift in Sources */,
|
||||||
A9D12E8423E3661800F3C259 /* UIColor+Interpolate.swift in Sources */,
|
|
||||||
A98F1B892359494500C5269E /* ConfigUI.swift in Sources */,
|
A98F1B892359494500C5269E /* ConfigUI.swift in Sources */,
|
||||||
A9BED773234FBEAB002FFF53 /* UartPacketManager.swift in Sources */,
|
A9BED773234FBEAB002FFF53 /* UartPacketManager.swift in Sources */,
|
||||||
A986AB93235942D600DF12C8 /* LightSensorViewController.swift in Sources */,
|
A986AB93235942D600DF12C8 /* LightSensorViewController.swift in Sources */,
|
||||||
|
|
@ -1025,8 +998,6 @@
|
||||||
A9EAA1A3235297B400FA615E /* NeopixelsLightSequenceViewController.swift in Sources */,
|
A9EAA1A3235297B400FA615E /* NeopixelsLightSequenceViewController.swift in Sources */,
|
||||||
A901EC4D237C47D800687BE6 /* CPBDataSeries.swift in Sources */,
|
A901EC4D237C47D800687BE6 /* CPBDataSeries.swift in Sources */,
|
||||||
A99BA71D234FFE780080D03F /* Settings.swift in Sources */,
|
A99BA71D234FFE780080D03F /* Settings.swift in Sources */,
|
||||||
A9D5B21323E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift in Sources */,
|
|
||||||
A9D5B21623E3024700178E1B /* BluetoothStatusViewController.swift in Sources */,
|
|
||||||
A9BED775234FBEAB002FFF53 /* BlePeripheral+Battery.swift in Sources */,
|
A9BED775234FBEAB002FFF53 /* BlePeripheral+Battery.swift in Sources */,
|
||||||
A94AC1482363A7BD0062AB11 /* TemperaturePanelViewController.swift in Sources */,
|
A94AC1482363A7BD0062AB11 /* TemperaturePanelViewController.swift in Sources */,
|
||||||
A9EAA1912350B26B00FA615E /* ScannerViewController.swift in Sources */,
|
A9EAA1912350B26B00FA615E /* ScannerViewController.swift in Sources */,
|
||||||
|
|
@ -1047,7 +1018,6 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
A9D12E8723E3C5CF00F3C259 /* LightChartPanelViewController.swift in Sources */,
|
|
||||||
A9A08B3923A523C900498069 /* Int+Bytes.swift in Sources */,
|
A9A08B3923A523C900498069 /* Int+Bytes.swift in Sources */,
|
||||||
A9A08B3A23A523C900498069 /* UIView+AllSubviewsWithClass.swift in Sources */,
|
A9A08B3A23A523C900498069 /* UIView+AllSubviewsWithClass.swift in Sources */,
|
||||||
A9A08BAD23A794F500498069 /* BlePeripheralSimulated+CPBLight.swift in Sources */,
|
A9A08BAD23A794F500498069 /* BlePeripheralSimulated+CPBLight.swift in Sources */,
|
||||||
|
|
@ -1063,7 +1033,6 @@
|
||||||
A9A08B4523A523C900498069 /* Data+ScanValue.swift in Sources */,
|
A9A08B4523A523C900498069 /* Data+ScanValue.swift in Sources */,
|
||||||
A9A08B4623A523C900498069 /* Data+LittleEndianTypes.swift in Sources */,
|
A9A08B4623A523C900498069 /* Data+LittleEndianTypes.swift in Sources */,
|
||||||
A9A08B4723A523C900498069 /* TipPowerUpViewController.swift in Sources */,
|
A9A08B4723A523C900498069 /* TipPowerUpViewController.swift in Sources */,
|
||||||
A9D12E8823E4434000F3C259 /* UIColor+Interpolate.swift in Sources */,
|
|
||||||
A9A08B4823A523C900498069 /* CPBBle.swift in Sources */,
|
A9A08B4823A523C900498069 /* CPBBle.swift in Sources */,
|
||||||
A9A08B4923A523C900498069 /* NavigationBarWithScrollAwareRightButton.swift in Sources */,
|
A9A08B4923A523C900498069 /* NavigationBarWithScrollAwareRightButton.swift in Sources */,
|
||||||
A9A08B4B23A523C900498069 /* UartPacketManagerBase.swift in Sources */,
|
A9A08B4B23A523C900498069 /* UartPacketManagerBase.swift in Sources */,
|
||||||
|
|
@ -1081,7 +1050,6 @@
|
||||||
A9A08B5723A523C900498069 /* GridView.swift in Sources */,
|
A9A08B5723A523C900498069 /* GridView.swift in Sources */,
|
||||||
A9A08B5823A523C900498069 /* BlePeripheral+Uart.swift in Sources */,
|
A9A08B5823A523C900498069 /* BlePeripheral+Uart.swift in Sources */,
|
||||||
A9A08B5923A523C900498069 /* BlePeripheral+CPBCommon.swift in Sources */,
|
A9A08B5923A523C900498069 /* BlePeripheral+CPBCommon.swift in Sources */,
|
||||||
A9D5B21123E0985600178E1B /* AutoConnectViewController.swift in Sources */,
|
|
||||||
A9A08B5A23A523C900498069 /* BlePeripheral+CPBToneGenerator.swift in Sources */,
|
A9A08B5A23A523C900498069 /* BlePeripheral+CPBToneGenerator.swift in Sources */,
|
||||||
A9A08B5B23A523C900498069 /* RssiUI.swift in Sources */,
|
A9A08B5B23A523C900498069 /* RssiUI.swift in Sources */,
|
||||||
A9A08B5C23A523C900498069 /* HelpViewController.swift in Sources */,
|
A9A08B5C23A523C900498069 /* HelpViewController.swift in Sources */,
|
||||||
|
|
@ -1098,7 +1066,6 @@
|
||||||
A9A08B6823A523C900498069 /* TipWelcomeViewController.swift in Sources */,
|
A9A08B6823A523C900498069 /* TipWelcomeViewController.swift in Sources */,
|
||||||
A9A08BA423A5286700498069 /* BlePeripheralSimulated.swift in Sources */,
|
A9A08BA423A5286700498069 /* BlePeripheralSimulated.swift in Sources */,
|
||||||
A9A08B6923A523C900498069 /* Config.swift in Sources */,
|
A9A08B6923A523C900498069 /* Config.swift in Sources */,
|
||||||
A9D5B21423E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift in Sources */,
|
|
||||||
A9A08B6A23A523C900498069 /* TipDiscoverViewController.swift in Sources */,
|
A9A08B6A23A523C900498069 /* TipDiscoverViewController.swift in Sources */,
|
||||||
A9A08B6B23A523C900498069 /* ModulePanelViewController.swift in Sources */,
|
A9A08B6B23A523C900498069 /* ModulePanelViewController.swift in Sources */,
|
||||||
A9A08B6C23A523C900498069 /* TipsViewController.swift in Sources */,
|
A9A08B6C23A523C900498069 /* TipsViewController.swift in Sources */,
|
||||||
|
|
@ -1113,7 +1080,6 @@
|
||||||
A9A08B7323A523C900498069 /* BleManager.swift in Sources */,
|
A9A08B7323A523C900498069 /* BleManager.swift in Sources */,
|
||||||
A9A08B7423A523C900498069 /* LightSensorPanelViewController.swift in Sources */,
|
A9A08B7423A523C900498069 /* LightSensorPanelViewController.swift in Sources */,
|
||||||
A9A08B7523A523C900498069 /* AboutViewController.swift in Sources */,
|
A9A08B7523A523C900498069 /* AboutViewController.swift in Sources */,
|
||||||
A9D5B21723E3024800178E1B /* BluetoothStatusViewController.swift in Sources */,
|
|
||||||
A9A08BAA23A7891500498069 /* ObjectBuilder.m in Sources */,
|
A9A08BAA23A7891500498069 /* ObjectBuilder.m in Sources */,
|
||||||
A9A08B7623A523C900498069 /* ConfigUI.swift in Sources */,
|
A9A08B7623A523C900498069 /* ConfigUI.swift in Sources */,
|
||||||
A9A08B7723A523C900498069 /* UartPacketManager.swift in Sources */,
|
A9A08B7723A523C900498069 /* UartPacketManager.swift in Sources */,
|
||||||
|
|
@ -1303,7 +1269,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 8;
|
CURRENT_PROJECT_VERSION = 5;
|
||||||
DEVELOPMENT_TEAM = 2X94RM7457;
|
DEVELOPMENT_TEAM = 2X94RM7457;
|
||||||
INFOPLIST_FILE = BluefruitPlayground/Info.plist;
|
INFOPLIST_FILE = BluefruitPlayground/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
|
@ -1311,13 +1277,13 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2.0;
|
MARKETING_VERSION = 1.1.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
|
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
|
||||||
PRODUCT_NAME = "Bluefruit Playground";
|
PRODUCT_NAME = "Bluefruit Playground";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "BluefruitPlayground/BluefruitPlayground-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "BluefruitPlayground/BluefruitPlayground-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = 1;
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -1328,7 +1294,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 8;
|
CURRENT_PROJECT_VERSION = 5;
|
||||||
DEVELOPMENT_TEAM = 2X94RM7457;
|
DEVELOPMENT_TEAM = 2X94RM7457;
|
||||||
INFOPLIST_FILE = BluefruitPlayground/Info.plist;
|
INFOPLIST_FILE = BluefruitPlayground/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
|
@ -1336,12 +1302,12 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2.0;
|
MARKETING_VERSION = 1.1.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
|
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
|
||||||
PRODUCT_NAME = "Bluefruit Playground";
|
PRODUCT_NAME = "Bluefruit Playground";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "BluefruitPlayground/BluefruitPlayground-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "BluefruitPlayground/BluefruitPlayground-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = 1;
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|
@ -1392,7 +1358,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 8;
|
CURRENT_PROJECT_VERSION = 5;
|
||||||
DEVELOPMENT_TEAM = 2X94RM7457;
|
DEVELOPMENT_TEAM = 2X94RM7457;
|
||||||
INFOPLIST_FILE = "BluefruitPlayground-SimulatedBluetooth-Info.plist";
|
INFOPLIST_FILE = "BluefruitPlayground-SimulatedBluetooth-Info.plist";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
|
@ -1400,7 +1366,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2.0;
|
MARKETING_VERSION = 1.1.2;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D SIMULATEBLUETOOTH";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D SIMULATEBLUETOOTH";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
|
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
@ -1418,7 +1384,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 8;
|
CURRENT_PROJECT_VERSION = 5;
|
||||||
DEVELOPMENT_TEAM = 2X94RM7457;
|
DEVELOPMENT_TEAM = 2X94RM7457;
|
||||||
INFOPLIST_FILE = "BluefruitPlayground-SimulatedBluetooth-Info.plist";
|
INFOPLIST_FILE = "BluefruitPlayground-SimulatedBluetooth-Info.plist";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
|
@ -1426,7 +1392,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2.0;
|
MARKETING_VERSION = 1.1.2;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D SIMULATEBLUETOOTH";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D SIMULATEBLUETOOTH";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
|
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
|
||||||
BIN
BluefruitPlayground/.DS_Store
vendored
Normal file
|
|
@ -27,15 +27,8 @@ class BleManager: NSObject {
|
||||||
private var centralManagerPoweredOnSemaphore = DispatchSemaphore(value: 1)
|
private var centralManagerPoweredOnSemaphore = DispatchSemaphore(value: 1)
|
||||||
|
|
||||||
// Scanning
|
// Scanning
|
||||||
var isScanning: Bool {
|
var isScanning = false
|
||||||
return scanningStartTime != nil
|
|
||||||
}
|
|
||||||
var scanningElapsedTime: TimeInterval? {
|
|
||||||
guard let scanningStartTime = scanningStartTime else { return nil }
|
|
||||||
return CACurrentMediaTime() - scanningStartTime
|
|
||||||
}
|
|
||||||
private var isScanningWaitingToStart = false
|
private var isScanningWaitingToStart = false
|
||||||
internal var scanningStartTime: TimeInterval? // Time when the scanning started. nil if stopped
|
|
||||||
private var scanningServicesFilter: [CBUUID]?
|
private var scanningServicesFilter: [CBUUID]?
|
||||||
internal var peripheralsFound = [UUID: BlePeripheral]()
|
internal var peripheralsFound = [UUID: BlePeripheral]()
|
||||||
private var peripheralsFoundLock = NSLock()
|
private var peripheralsFoundLock = NSLock()
|
||||||
|
|
@ -121,7 +114,7 @@ class BleManager: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DLog("start scan")
|
// DLog("start scan")
|
||||||
scanningStartTime = CACurrentMediaTime()
|
isScanning = true
|
||||||
NotificationCenter.default.post(name: .didStartScanning, object: nil)
|
NotificationCenter.default.post(name: .didStartScanning, object: nil)
|
||||||
|
|
||||||
let options = BleManager.kAlwaysAllowDuplicateKeys ? [CBCentralManagerScanOptionAllowDuplicatesKey: true] : nil
|
let options = BleManager.kAlwaysAllowDuplicateKeys ? [CBCentralManagerScanOptionAllowDuplicatesKey: true] : nil
|
||||||
|
|
@ -132,7 +125,7 @@ class BleManager: NSObject {
|
||||||
func stopScan() {
|
func stopScan() {
|
||||||
// DLog("stop scan")
|
// DLog("stop scan")
|
||||||
centralManager?.stopScan()
|
centralManager?.stopScan()
|
||||||
scanningStartTime = nil
|
isScanning = false
|
||||||
isScanningWaitingToStart = false
|
isScanningWaitingToStart = false
|
||||||
NotificationCenter.default.post(name: .didStopScanning, object: nil)
|
NotificationCenter.default.post(name: .didStopScanning, object: nil)
|
||||||
}
|
}
|
||||||
|
|
@ -218,19 +211,11 @@ class BleManager: NSObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func disconnect(from peripheral: BlePeripheral, waitForQueuedCommands: Bool = false) {
|
func disconnect(from peripheral: BlePeripheral) {
|
||||||
guard let centralManager = centralManager else { return}
|
|
||||||
|
|
||||||
DLog("disconnect")
|
DLog("disconnect")
|
||||||
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||||
|
centralManager?.cancelPeripheralConnection(peripheral.peripheral)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func reconnecToPeripherals(withIdentifiers identifiers: [UUID], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
func reconnecToPeripherals(withIdentifiers identifiers: [UUID], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
||||||
|
|
@ -314,10 +299,7 @@ extension BleManager: CBCentralManagerDelegate {
|
||||||
if isScanning {
|
if isScanning {
|
||||||
isScanningWaitingToStart = true
|
isScanningWaitingToStart = true
|
||||||
}
|
}
|
||||||
scanningStartTime = nil
|
isScanning = false
|
||||||
|
|
||||||
// Remove all peripherals found (Important because the BlePeripheral queues could contain old commands that were processing when the bluetooth state changed)
|
|
||||||
peripheralsFound.removeAll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .didUpdateBleState, object: nil)
|
NotificationCenter.default.post(name: .didUpdateBleState, object: nil)
|
||||||
|
|
|
||||||
|
|
@ -207,12 +207,6 @@ class BlePeripheral: NSObject {
|
||||||
commandQueue.append(command)
|
commandQueue.append(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Connection
|
|
||||||
func disconnect(centralManager: CBCentralManager) {
|
|
||||||
let command = BleCommand(type: .disconnect, parameters: [centralManager], completion: nil)
|
|
||||||
commandQueue.append(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Service
|
// MARK: - Service
|
||||||
func discoveredService(uuid: CBUUID) -> CBService? {
|
func discoveredService(uuid: CBUUID) -> CBService? {
|
||||||
let service = peripheral.services?.first(where: {$0.uuid == uuid})
|
let service = peripheral.services?.first(where: {$0.uuid == uuid})
|
||||||
|
|
@ -325,7 +319,6 @@ class BlePeripheral: NSObject {
|
||||||
case writeCharacteristic
|
case writeCharacteristic
|
||||||
case writeCharacteristicAndWaitNofity
|
case writeCharacteristicAndWaitNofity
|
||||||
case readDescriptor
|
case readDescriptor
|
||||||
case disconnect
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CommandError: Error {
|
enum CommandError: Error {
|
||||||
|
|
@ -369,8 +362,6 @@ class BlePeripheral: NSObject {
|
||||||
write(with: command)
|
write(with: command)
|
||||||
case .readDescriptor:
|
case .readDescriptor:
|
||||||
readDescriptor(with: command)
|
readDescriptor(with: command)
|
||||||
case .disconnect:
|
|
||||||
disconnect(with: command)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -500,12 +491,6 @@ class BlePeripheral: NSObject {
|
||||||
|
|
||||||
peripheral.readValue(for: descriptor)
|
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
|
// MARK: - CBPeripheralDelegate
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,7 @@ extension BlePeripheral {
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
func cpbButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
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: 0, responseHandler: { response in
|
||||||
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBButtonsServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBButtonsCharacteristicUUID, timePeriod: timePeriod, responseHandler: { response in
|
|
||||||
|
|
||||||
switch response {
|
switch response {
|
||||||
case let .success((data, uuid)):
|
case let .success((data, uuid)):
|
||||||
|
|
@ -68,21 +67,7 @@ extension BlePeripheral {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cpbButtonsCharacteristic = characteristic
|
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(()))
|
completion?(.success(()))
|
||||||
case .failure(let error):
|
|
||||||
DLog("Error receiving initial button state data: \(error)")
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
case let .failure(error):
|
case let .failure(error):
|
||||||
self.cpbButtonsCharacteristic = nil
|
self.cpbButtonsCharacteristic = nil
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ extension BlePeripheral {
|
||||||
static let kCPBLightServiceUUID = CBUUID(string: "ADAF0300-C332-42A8-93BD-25E905756CB8")
|
static let kCPBLightServiceUUID = CBUUID(string: "ADAF0300-C332-42A8-93BD-25E905756CB8")
|
||||||
private static let kCPBLightCharacteristicUUID = CBUUID(string: "ADAF0301-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
|
// MARK: - Custom properties
|
||||||
private struct CustomPropertiesKeys {
|
private struct CustomPropertiesKeys {
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ extension BlePeripheral {
|
||||||
static let kCPBTemperatureServiceUUID = CBUUID(string: "ADAF0100-C332-42A8-93BD-25E905756CB8")
|
static let kCPBTemperatureServiceUUID = CBUUID(string: "ADAF0100-C332-42A8-93BD-25E905756CB8")
|
||||||
private static let kCPBTemperatureCharacteristicUUID = CBUUID(string: "ADAF0101-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
|
// MARK: - Custom properties
|
||||||
private struct CustomPropertiesKeys {
|
private struct CustomPropertiesKeys {
|
||||||
static var cpbTemperatureCharacteristic: CBCharacteristic?
|
static var cpbTemperatureCharacteristic: CBCharacteristic?
|
||||||
|
|
@ -33,7 +31,7 @@ extension BlePeripheral {
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
func cpbTemperatureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
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 {
|
switch response {
|
||||||
case let .success((data, uuid)):
|
case let .success((data, uuid)):
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import Foundation
|
||||||
import CoreBluetooth
|
import CoreBluetooth
|
||||||
|
|
||||||
class BleManagerSimulated: BleManager {
|
class BleManagerSimulated: BleManager {
|
||||||
|
|
@ -20,7 +20,7 @@ class BleManagerSimulated: BleManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func startScan(withServices services: [CBUUID]? = nil) {
|
override func startScan(withServices services: [CBUUID]? = nil) {
|
||||||
scanningStartTime = CACurrentMediaTime()
|
isScanning = true
|
||||||
|
|
||||||
// Add simulated peripheral
|
// Add simulated peripheral
|
||||||
let simulatedBlePeripheral = BlePeripheralSimulated()
|
let simulatedBlePeripheral = BlePeripheralSimulated()
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -17,8 +17,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
|
|
||||||
startup()
|
startup()
|
||||||
|
|
||||||
ScreenFlowManager.enableBleStateManagement()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,8 +40,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
func applicationWillTerminate(_ application: UIApplication) {
|
func applicationWillTerminate(_ application: UIApplication) {
|
||||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||||
|
|
||||||
ScreenFlowManager.disableBleStateManagement()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Startup
|
// MARK: - Startup
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 325 KiB |
|
Before Width: | Height: | Size: 261 KiB |
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 87 KiB |
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 996 B After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
|
@ -7,12 +7,12 @@
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"color" : {
|
"color" : {
|
||||||
"color-space" : "srgb",
|
"color-space" : "display-p3",
|
||||||
"components" : {
|
"components" : {
|
||||||
"red" : "0.949",
|
"red" : "0.010",
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.961",
|
"blue" : "0.802",
|
||||||
"green" : "0.961"
|
"green" : "0.219"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -13,12 +13,9 @@ struct Config {
|
||||||
// Debug-----------------------------------------------------------------------------
|
// Debug-----------------------------------------------------------------------------
|
||||||
static let isDebugEnabled = _isDebugAssertConfiguration()
|
static let isDebugEnabled = _isDebugAssertConfiguration()
|
||||||
|
|
||||||
// Fastlane snapshots
|
|
||||||
private static let areFastlaneSnapshotsRunning = UserDefaults.standard.bool(forKey: "FASTLANE_SNAPSHOT")
|
|
||||||
|
|
||||||
// Bluetooth
|
// Bluetooth
|
||||||
#if SIMULATEBLUETOOTH
|
#if SIMULATEBLUETOOTH
|
||||||
static let isTutorialEnabled = areFastlaneSnapshotsRunning || !isDebugEnabled
|
static let isTutorialEnabled = !isDebugEnabled
|
||||||
static let isBleUnsupportedWarningEnabled = false
|
static let isBleUnsupportedWarningEnabled = false
|
||||||
static let bleManager = BleManagerSimulated.simulated
|
static let bleManager = BleManagerSimulated.simulated
|
||||||
#else
|
#else
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,10 @@
|
||||||
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
|
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
|
||||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||||
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
|
<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>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
|
|
|
||||||
BIN
BluefruitPlayground/Model/.DS_Store
vendored
Normal file
|
|
@ -30,7 +30,8 @@ class CPBBle {
|
||||||
// Constants
|
// Constants
|
||||||
private static let kLightSequenceFramesPerSecond = 10
|
private static let kLightSequenceFramesPerSecond = 10
|
||||||
private static let kLightSequenceDefaultBrightness: CGFloat = 0.25
|
private static let kLightSequenceDefaultBrightness: CGFloat = 0.25
|
||||||
public static let kLightSequenceDefaultSpeed: Double = 0.3
|
private static let kLightSequenceDefaultSpeed: Double = 0.3
|
||||||
|
|
||||||
// Singleton
|
// Singleton
|
||||||
static let shared = CPBBle()
|
static let shared = CPBBle()
|
||||||
|
|
||||||
|
|
@ -51,30 +52,25 @@ class CPBBle {
|
||||||
weak var buttonsDelegate: CPBBleButtonsDelegate?
|
weak var buttonsDelegate: CPBBleButtonsDelegate?
|
||||||
weak var accelerometerDelegate: CPBBleAccelerometerDelegate?
|
weak var accelerometerDelegate: CPBBleAccelerometerDelegate?
|
||||||
|
|
||||||
|
var neopixelLightSequenceAnimationBrightness: CGFloat = CPBBle.kLightSequenceDefaultBrightness
|
||||||
|
|
||||||
|
var neopixelLightSequenceAnimationSpeed: Double = CPBBle.kLightSequenceDefaultSpeed {
|
||||||
|
|
||||||
|
didSet {
|
||||||
|
lightSequenceAnimation?.speed = neopixelLightSequenceAnimationSpeed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
private var temperatureData = CPBDataSeries<Float>()
|
private var temperatureData = CPBDataSeries<Float>()
|
||||||
private var lightData = CPBDataSeries<Float>()
|
private var lightData = CPBDataSeries<Float>()
|
||||||
private var accelerometerData = CPBDataSeries<BlePeripheral.AccelerometerValue>()
|
private var accelerometerData = CPBDataSeries<BlePeripheral.AccelerometerValue>()
|
||||||
private weak var blePeripheral: BlePeripheral?
|
private weak var blePeripheral: BlePeripheral?
|
||||||
|
|
||||||
private var currentLightSequenceAnimation: LightSequenceAnimation?
|
private var lightSequenceAnimation: LightSequenceAnimation?
|
||||||
public var neopixelCurrentLightSequenceAnimationSpeed: Double {
|
|
||||||
get {
|
|
||||||
return currentLightSequenceAnimation?.speed ?? 0
|
|
||||||
}
|
|
||||||
|
|
||||||
set {
|
|
||||||
currentLightSequenceAnimation?.speed = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
private init() {
|
private init() {
|
||||||
registerNotifications(enabled: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
registerNotifications(enabled: false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Setup
|
// MARK: - Setup
|
||||||
|
|
@ -193,10 +189,6 @@ class CPBBle {
|
||||||
return blePeripheral?.cpbAccelerometerLastValue()
|
return blePeripheral?.cpbAccelerometerLastValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
func lightDataSeries() -> [CPBDataSeries<Float>.Entry] {
|
|
||||||
return lightData.values
|
|
||||||
}
|
|
||||||
|
|
||||||
func temperatureDataSeries() -> [CPBDataSeries<Float>.Entry] {
|
func temperatureDataSeries() -> [CPBDataSeries<Float>.Entry] {
|
||||||
return temperatureData.values
|
return temperatureData.values
|
||||||
}
|
}
|
||||||
|
|
@ -257,9 +249,36 @@ class CPBBle {
|
||||||
|
|
||||||
private func receiveButtonsData(response: Result<(BlePeripheral.ButtonsState, UUID), Error>) {
|
private func receiveButtonsData(response: Result<(BlePeripheral.ButtonsState, UUID), Error>) {
|
||||||
switch response {
|
switch response {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
case let .success(buttonsState, uuid):
|
case let .success(buttonsState, uuid):
|
||||||
DLog("Buttons: \(buttonsState.slideSwitch == .left ? "⬅️":"➡️") \(buttonsState.buttonA == .pressed ? "🔳":"🔲") \(buttonsState.buttonB == .pressed ? "🔳":"🔲") ")
|
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
|
// Send to delegate
|
||||||
if let buttonsDelegate = buttonsDelegate {
|
if let buttonsDelegate = buttonsDelegate {
|
||||||
DispatchQueue.main.async { // Delegates are called in the main thread
|
DispatchQueue.main.async { // Delegates are called in the main thread
|
||||||
|
|
@ -273,9 +292,15 @@ class CPBBle {
|
||||||
NotificationUserInfoKey.uuid.rawValue: uuid,
|
NotificationUserInfoKey.uuid.rawValue: uuid,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
DLog("Error receiving light data: \(error)")
|
DLog("Error receiving light data: \(error)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func receiveAccelerometerData(response: Result<(BlePeripheral.AccelerometerValue, UUID), Error>) {
|
private func receiveAccelerometerData(response: Result<(BlePeripheral.AccelerometerValue, UUID), Error>) {
|
||||||
|
|
@ -323,26 +348,19 @@ class CPBBle {
|
||||||
blePeripheral?.cpbPixelSetColor(index: 0, color: color, pixelMask: pixelMask)
|
blePeripheral?.cpbPixelSetColor(index: 0, color: color, pixelMask: pixelMask)
|
||||||
}
|
}
|
||||||
|
|
||||||
func neopixelStartLightSequence(_ lightSequenceGenerator: LightSequenceGenerator,
|
func neopixelStartLightSequence(_ lightSequenceGenerator: LightSequenceGenerator) {
|
||||||
framesPerSecond: Int = CPBBle.kLightSequenceFramesPerSecond,
|
|
||||||
speed: Double = CPBBle.kLightSequenceDefaultSpeed,
|
|
||||||
brightness: CGFloat = CPBBle.kLightSequenceDefaultBrightness,
|
|
||||||
repeating: Bool = true,
|
|
||||||
sendLightSequenceNotifications: Bool = true) {
|
|
||||||
neopixelStopLightSequence()
|
neopixelStopLightSequence()
|
||||||
|
|
||||||
currentLightSequenceAnimation = LightSequenceAnimation(lightSequenceGenerator: lightSequenceGenerator, framesPerSecond: framesPerSecond, repeating: repeating)
|
lightSequenceAnimation = LightSequenceAnimation(lightSequenceGenerator: lightSequenceGenerator, framesPerSecond: CPBBle.kLightSequenceFramesPerSecond)
|
||||||
currentLightSequenceAnimation!.speed = speed
|
lightSequenceAnimation!.speed = neopixelLightSequenceAnimationSpeed
|
||||||
currentLightSequenceAnimation!.start(stopHandler: { [weak self] in
|
lightSequenceAnimation!.start() { [weak self] pixelsBytes in
|
||||||
self?.blePeripheral?.cpbPixelSetAllPixelsColor(.clear)
|
|
||||||
}) { [weak self] pixelsBytes in
|
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard let blePeripheral = self.blePeripheral else { return }
|
guard let blePeripheral = self.blePeripheral else { return }
|
||||||
|
|
||||||
let pixelBytesAdjustingBrightness = pixelsBytes.map {[
|
let pixelBytesAdjustingBrightness = pixelsBytes.map {[
|
||||||
UInt8(CGFloat($0[0]) * brightness),
|
UInt8(CGFloat($0[0]) * self.neopixelLightSequenceAnimationBrightness),
|
||||||
UInt8(CGFloat($0[1]) * brightness),
|
UInt8(CGFloat($0[1]) * self.neopixelLightSequenceAnimationBrightness),
|
||||||
UInt8(CGFloat($0[2]) * brightness),
|
UInt8(CGFloat($0[2]) * self.neopixelLightSequenceAnimationBrightness),
|
||||||
]}
|
]}
|
||||||
|
|
||||||
let lightData = pixelBytesAdjustingBrightness.reduce(Data()) { (data, element) in
|
let lightData = pixelBytesAdjustingBrightness.reduce(Data()) { (data, element) in
|
||||||
|
|
@ -351,35 +369,16 @@ class CPBBle {
|
||||||
blePeripheral.cpbPixelsWriteData(offset: 0, pixelData: lightData)
|
blePeripheral.cpbPixelsWriteData(offset: 0, pixelData: lightData)
|
||||||
|
|
||||||
// Send notification
|
// Send notification
|
||||||
if sendLightSequenceNotifications {
|
|
||||||
NotificationCenter.default.post(name: .didUpdateNeopixelLightSequence, object: nil, userInfo: [
|
NotificationCenter.default.post(name: .didUpdateNeopixelLightSequence, object: nil, userInfo: [
|
||||||
NotificationUserInfoKey.value.rawValue: pixelsBytes,
|
NotificationUserInfoKey.value.rawValue: pixelsBytes,
|
||||||
NotificationUserInfoKey.uuid.rawValue: blePeripheral.identifier,
|
NotificationUserInfoKey.uuid.rawValue: blePeripheral.identifier,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func neopixelStopLightSequence() {
|
func neopixelStopLightSequence() {
|
||||||
currentLightSequenceAnimation?.stop()
|
lightSequenceAnimation?.stop()
|
||||||
currentLightSequenceAnimation = nil
|
lightSequenceAnimation = 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)}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,8 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
protocol LightSequenceGenerator {
|
protocol LightSequenceGenerator {
|
||||||
var numFrames: Int { get }
|
func numFrames() -> Int
|
||||||
var numPixels: Int { get }
|
func numPixels() -> Int
|
||||||
var isCyclic: Bool { get }
|
|
||||||
func colorsForFrame(_ frame: Int) -> [[UInt8]]
|
func colorsForFrame(_ frame: Int) -> [[UInt8]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -20,16 +19,12 @@ class LightSequence {
|
||||||
// static let kLightSequenceDefaultBrightness: CGFloat = 0.25
|
// static let kLightSequenceDefaultBrightness: CGFloat = 0.25
|
||||||
static let kNumPixels = 10
|
static let kNumPixels = 10
|
||||||
|
|
||||||
var numPixels: Int {
|
func numPixels() -> Int {
|
||||||
return LightSequence.kNumPixels
|
return LightSequence.kNumPixels
|
||||||
}
|
}
|
||||||
|
|
||||||
var isCyclic: Bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
static func preprocessColorPalette(colors: [UIColor]) -> [[UInt8]] {
|
static func preprocessColorPalette(colors: [UIColor]/*, brightness: CGFloat*/) -> [[UInt8]] {
|
||||||
let colorsBytes = colors.map({ color -> [UInt8] in
|
let colorsBytes = colors.map({ color -> [UInt8] in
|
||||||
let colorBytes = BlePeripheral.pixelUInt8FromColor(color)
|
let colorBytes = BlePeripheral.pixelUInt8FromColor(color)
|
||||||
return colorBytes
|
return colorBytes
|
||||||
|
|
@ -51,16 +46,16 @@ class RotateLightSequence: LightSequence, LightSequenceGenerator {
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
override init() {
|
override init() {
|
||||||
colorsBytes = LightSequence.preprocessColorPalette(colors: RotateLightSequence.kColors)
|
colorsBytes = LightSequence.preprocessColorPalette(colors: RotateLightSequence.kColors/*, brightness: LightSequence.kLightSequenceDefaultBrightness*/)
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
var numFrames: Int {
|
func numFrames() -> Int {
|
||||||
return RotateLightSequence.kNumFrames
|
return RotateLightSequence.kNumFrames
|
||||||
}
|
}
|
||||||
|
|
||||||
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
|
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]] {
|
private func rotate(numPixels: Int, colors: [[UInt8]], frame: Int) -> [[UInt8]] {
|
||||||
|
|
@ -88,22 +83,22 @@ class PulseLightSequence: LightSequence, LightSequenceGenerator {
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
override init() {
|
override init() {
|
||||||
colorsBytes = LightSequence.preprocessColorPalette(colors: PulseLightSequence.kColors)
|
colorsBytes = LightSequence.preprocessColorPalette(colors: PulseLightSequence.kColors/*, brightness: LightSequence.kLightSequenceDefaultBrightness*/)
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
var numFrames: Int {
|
func numFrames() -> Int {
|
||||||
return PulseLightSequence.kNumFrames
|
return PulseLightSequence.kNumFrames
|
||||||
}
|
}
|
||||||
|
|
||||||
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
|
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]] {
|
private func pulse(numPixels: Int, colors: [[UInt8]], frame: Int) -> [[UInt8]] {
|
||||||
var lightBytes = [[UInt8]](repeating: [0, 0, 0], count:numPixels)
|
var lightBytes = [[UInt8]](repeating: [0, 0, 0], count:numPixels)
|
||||||
let reverse = frame >= numFrames / 2
|
let reverse = frame >= numFrames() / 2
|
||||||
let colorIndex = reverse ? (numFrames - 1) - frame : frame
|
let colorIndex = reverse ? (numFrames() - 1) - frame : frame
|
||||||
for i in 0..<numPixels {
|
for i in 0..<numPixels {
|
||||||
//DLog("pixel: \(i) color: \(colorIndex)")
|
//DLog("pixel: \(i) color: \(colorIndex)")
|
||||||
let colorBytes = colors[colorIndex]
|
let colorBytes = colors[colorIndex]
|
||||||
|
|
@ -128,25 +123,25 @@ class SizzleLightSequence: LightSequence, LightSequenceGenerator {
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
override init() {
|
override init() {
|
||||||
colorsBytes = LightSequence.preprocessColorPalette(colors: SizzleLightSequence.kColors)
|
colorsBytes = LightSequence.preprocessColorPalette(colors: SizzleLightSequence.kColors/*, brightness: LightSequence.kLightSequenceDefaultBrightness*/)
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
var numFrames:Int {
|
func numFrames() -> Int {
|
||||||
return SizzleLightSequence.kNumFrames
|
return SizzleLightSequence.kNumFrames
|
||||||
}
|
}
|
||||||
|
|
||||||
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
|
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]] {
|
private func sizzle(numPixels: Int, colors: [[UInt8]], frame: Int) -> [[UInt8]] {
|
||||||
var lightBytes = [[UInt8]](repeating: [0, 0, 0], count:numPixels)
|
var lightBytes = [[UInt8]](repeating: [0, 0, 0], count:numPixels)
|
||||||
let forwardNumFrames = numFrames / 2
|
let forwardNumFrames = numFrames() / 2
|
||||||
let reverse = frame >= forwardNumFrames
|
let reverse = frame >= forwardNumFrames
|
||||||
|
|
||||||
let evenIndex = reverse ? (frame % forwardNumFrames) : (forwardNumFrames - 1) - frame
|
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 {
|
for i in 0..<numPixels {
|
||||||
//DLog("pixel: \(i) color: \(colorIndex)")
|
//DLog("pixel: \(i) color: \(colorIndex)")
|
||||||
|
|
@ -169,16 +164,16 @@ class SweepLightSequence: LightSequence, LightSequenceGenerator {
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
override init() {
|
override init() {
|
||||||
colorsBytes = LightSequence.preprocessColorPalette(colors: SweepLightSequence.kColors)
|
colorsBytes = LightSequence.preprocessColorPalette(colors: SweepLightSequence.kColors/*, brightness: LightSequence.kLightSequenceDefaultBrightness*/)
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
var numFrames: Int {
|
func numFrames() -> Int {
|
||||||
return SweepLightSequence.kNumFrames
|
return SweepLightSequence.kNumFrames
|
||||||
}
|
}
|
||||||
|
|
||||||
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
|
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]] {
|
private func sweep(numPixels: Int, colors: [[UInt8]], frame: Int) -> [[UInt8]] {
|
||||||
|
|
@ -193,78 +188,3 @@ class SweepLightSequence: LightSequence, LightSequenceGenerator {
|
||||||
return lightBytes
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -21,67 +21,57 @@ class LightSequenceAnimation {
|
||||||
|
|
||||||
private var displaylink: CADisplayLink?
|
private var displaylink: CADisplayLink?
|
||||||
|
|
||||||
|
//private var simulatedFrame = 0.0
|
||||||
private var startingTimestamp: TimeInterval = 0
|
private var startingTimestamp: TimeInterval = 0
|
||||||
private var frameHandler: (([[UInt8]])->())?
|
private var frameHandler: (([[UInt8]])->())?
|
||||||
private var stopHandler: (()->())?
|
|
||||||
private var repeating: Bool
|
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
init(lightSequenceGenerator: LightSequenceGenerator, framesPerSecond: Int, repeating: Bool) {
|
init(lightSequenceGenerator: LightSequenceGenerator, framesPerSecond: Int) {
|
||||||
self.lightSequenceGenerator = lightSequenceGenerator
|
self.lightSequenceGenerator = lightSequenceGenerator
|
||||||
self.lightSequenceFramesPerSecond = framesPerSecond
|
self.lightSequenceFramesPerSecond = framesPerSecond
|
||||||
self.repeating = repeating
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
stop()
|
stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(stopHandler:(()->())? = nil, frameHandler: @escaping ([[UInt8]])->()) {
|
func start(frameHandler: @escaping ([[UInt8]])->()) {
|
||||||
self.stopHandler = stopHandler
|
|
||||||
self.frameHandler = frameHandler
|
self.frameHandler = frameHandler
|
||||||
|
|
||||||
// Create displayLink if needed
|
// Create displayLink if needed
|
||||||
if displaylink == nil {
|
if displaylink == nil {
|
||||||
displaylink = CADisplayLink(target: self, selector: #selector(displayLinkStep))
|
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 }
|
guard let displaylink = displaylink else { return }
|
||||||
displaylink.preferredFramesPerSecond = lightSequenceFramesPerSecond
|
displaylink.preferredFramesPerSecond = lightSequenceFramesPerSecond
|
||||||
startingTimestamp = CACurrentMediaTime()
|
startingTimestamp = displaylink.timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop() {
|
func stop() {
|
||||||
displaylink?.invalidate()
|
displaylink?.invalidate()
|
||||||
displaylink = nil
|
displaylink = nil
|
||||||
displaylink?.remove(from: .main, forMode: .default)
|
|
||||||
stopHandler?()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func displayLinkStep(displaylink: CADisplayLink) {
|
@objc func displayLinkStep(displaylink: CADisplayLink) {
|
||||||
|
|
||||||
let currentTimestamp = CACurrentMediaTime() - startingTimestamp
|
//let fps = Double(currentLightSequenceFramesPerSecond)
|
||||||
let numFrames = Double(lightSequenceGenerator.numFrames)
|
let currentTimestamp = displaylink.timestamp - 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 frame = (currentTimestamp * numFrames * speed).truncatingRemainder(dividingBy:numFrames)
|
let frame = (currentTimestamp * numFrames * speed).truncatingRemainder(dividingBy:numFrames)
|
||||||
|
//let frame = simulatedFrame.truncatingRemainder(dividingBy:numFrames)
|
||||||
|
//simulatedFrame += 0.4
|
||||||
|
|
||||||
//DLog("frame: \(frame)")
|
//DLog("frame: \(frame)")
|
||||||
|
|
||||||
var pixelsBytes: [[UInt8]]
|
var pixelsBytes: [[UInt8]]
|
||||||
if LightSequenceAnimation.kIsFrameInterpolationEnabled {
|
if LightSequenceAnimation.kIsFrameInterpolationEnabled {
|
||||||
let preFrame = Int(floor(frame))
|
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 postFactor = frame - Double(preFrame)
|
||||||
let preFactor = 1 - postFactor
|
let preFactor = 1 - postFactor
|
||||||
|
|
||||||
//DLog("pre: \(preFrame), post: \(postFrame), frame: \(frame)")
|
|
||||||
|
|
||||||
let pixelsBytesPre = lightSequenceGenerator.colorsForFrame(preFrame)
|
let pixelsBytesPre = lightSequenceGenerator.colorsForFrame(preFrame)
|
||||||
let pixelsBytesPost = lightSequenceGenerator.colorsForFrame(postFrame)
|
let pixelsBytesPost = lightSequenceGenerator.colorsForFrame(postFrame)
|
||||||
|
|
||||||
|
|
|
||||||
BIN
BluefruitPlayground/Resources/.DS_Store
vendored
Normal file
|
|
@ -6,11 +6,22 @@
|
||||||
// Startup
|
// Startup
|
||||||
"startup_bluetooth_unsupported" = "This device doesn't support Bluetooth Low Energy which is needed to connect to the Circuit Playground Bluefruit device";
|
"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
|
"bluetooth_unsupported" = "This device doesn't support Bluetooth Low Energy";
|
||||||
"splash_restoringconnection" = "Restoring connection...";
|
"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
|
// Welcome
|
||||||
"tip0_title" = "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_link_url" = "https://learn.adafruit.com/bluefruit-playground-app/firmware";
|
||||||
"tip1_action" = "Next";
|
"tip1_action" = "Next";
|
||||||
"tip2_title" = "Discover";
|
"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";
|
"tip2_action" = "Begin pairing";
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -61,26 +72,9 @@ https://github.com/mindsnacks/MSWeakTimer";
|
||||||
"about_ios_link1_text" = "uf2 firmware file";
|
"about_ios_link1_text" = "uf2 firmware file";
|
||||||
"about_ios_link1_url" = "https://learn.adafruit.com/bluefruit-playground-app/firmware";
|
"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
|
||||||
"scanner_title" = "Select CPB";
|
"scanner_title" = "Finding CPB";
|
||||||
|
|
||||||
"scanner_connecting" = "Connecting...";
|
"scanner_connecting" = "Connecting...";
|
||||||
"scanner_discoveringservices" = "Discovering services...";
|
"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_subtitle" = "Select a Circuit Playground Bluefruit device to connect to:";
|
||||||
"scanner_unnamed" = "<Unknown>";
|
"scanner_unnamed" = "<Unknown>";
|
||||||
|
|
||||||
"scanner_automatic_action" = "Select device automatically";
|
|
||||||
"scanner_problems_action" = "I can't find my device";
|
"scanner_problems_action" = "I can't find my device";
|
||||||
"scanner_backbutton" = "Disconnect";
|
"scanner_backbutton" = "Disconnect";
|
||||||
|
|
||||||
|
|
@ -115,9 +108,9 @@ Download it from the Bluefruit Playground guide at learn.adafruit.com";
|
||||||
|
|
||||||
// Modules
|
// Modules
|
||||||
"modules_title" = "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_color_subtitle" = "Control LED color & animation";
|
||||||
"modules_light_title" = "Light Sensor";
|
"modules_light_title" = "Light Sensor";
|
||||||
"modules_light_subtitle" = "View continuous light sensor readings";
|
"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_accelerometer_subtitle" = "View orientation based on accelerometer data";
|
||||||
"modules_temperature_title" = "Temperature";
|
"modules_temperature_title" = "Temperature";
|
||||||
"modules_temperature_subtitle" = "View current temperature readings";
|
"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
|
||||||
"neopixels_title" = "NeoPixels";
|
"neopixels_title" = "Neopixels";
|
||||||
"neopixels_help" = "NeoPixels mode allows you to control the color of CPB’s built-in NeoPixel LEDs individually, as a group, or by playing preset animations.
|
"neopixels_help" = "Neopixel mode allows you to control the color of CPB’s 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_title" = "Light Sequence";
|
||||||
"neopixels_sequence_speed" = "Speed";
|
"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_title" = "Luminance Reading";
|
||||||
"lightsensor_panel_unit" = "Light level";
|
"lightsensor_panel_unit" = "Light level";
|
||||||
"lightsensor_chartpanel_title" = "Luminance Chart";
|
|
||||||
|
|
||||||
// Button Status
|
// Button Status
|
||||||
"buttonstatus_title" = "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_celsius" = "ºC";
|
||||||
"temperature_units_farenheit" = "ºF";
|
"temperature_units_farenheit" = "ºF";
|
||||||
|
|
||||||
"temperature_panel_title" = "Temperature Chart";
|
"temperature_panel_title" = "Temperature Reading";
|
||||||
"temperature_chart_nodata" = "No chart data available";
|
"temperature_chart_nodata" = "No chart data available";
|
||||||
|
|
||||||
// Help
|
// Help
|
||||||
|
|
|
||||||
334
BluefruitPlayground/Resources/models3d/Sparky_Gold1.dae
Normal file
|
|
@ -16,22 +16,20 @@ import UIKit
|
||||||
class NavigationBarWithScrollAwareRightButton : UINavigationBar {
|
class NavigationBarWithScrollAwareRightButton : UINavigationBar {
|
||||||
// Constants
|
// Constants
|
||||||
private static let kButtonTag = 100
|
private static let kButtonTag = 100
|
||||||
|
public struct CustomButtonMetrics {
|
||||||
|
static let rightMargin: CGFloat = 8//12
|
||||||
|
|
||||||
|
static let bottomMarginForLargeState: CGFloat = 9
|
||||||
|
static let bottomMarginForSmallState: CGFloat = 2
|
||||||
|
|
||||||
// Config (navbar metrics)
|
|
||||||
static let navBarHeightSmallState: CGFloat = 44
|
static let navBarHeightSmallState: CGFloat = 44
|
||||||
static let navBarHeightLargeState: CGFloat = 96.5
|
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
|
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
private var navigationButton: UIButton?// = UIButton(type: .custom)
|
private var navigationButton: UIButton?// = UIButton(type: .custom)
|
||||||
private var topViewController: UIViewController?
|
private var topViewController: UIViewController?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
public func setRightButton(topViewController: UIViewController, image: UIImage?, target: Any?, action: Selector) {
|
public func setRightButton(topViewController: UIViewController, image: UIImage?, target: Any?, action: Selector) {
|
||||||
navigationButton?.removeFromSuperview()
|
navigationButton?.removeFromSuperview()
|
||||||
|
|
@ -49,8 +47,8 @@ class NavigationBarWithScrollAwareRightButton : UINavigationBar {
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
|
||||||
button.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -NavigationBarWithScrollAwareRightButton.rightMargin),
|
button.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -CustomButtonMetrics.rightMargin),
|
||||||
button.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -NavigationBarWithScrollAwareRightButton.bottomMarginForLargeState),
|
button.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -CustomButtonMetrics.bottomMarginForLargeState),
|
||||||
])
|
])
|
||||||
|
|
||||||
self.layoutIfNeeded() // Force layout to remove adding animation
|
self.layoutIfNeeded() // Force layout to remove adding animation
|
||||||
|
|
@ -61,14 +59,21 @@ class NavigationBarWithScrollAwareRightButton : UINavigationBar {
|
||||||
self.topViewController = topViewController
|
self.topViewController = topViewController
|
||||||
topViewController.navigationController?.delegate = self
|
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 {
|
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() {
|
public func updateRightButtonPosition() {
|
||||||
let yDelta = NavigationBarWithScrollAwareRightButton.bottomMarginForLargeState - NavigationBarWithScrollAwareRightButton.bottomMarginForSmallState
|
let yDelta = CustomButtonMetrics.bottomMarginForLargeState - CustomButtonMetrics.bottomMarginForSmallState
|
||||||
|
|
||||||
let y = yDelta * max(0, (1-navigationBarScrollViewProgress))
|
let y = yDelta * max(0, (1-navigationBarScrollViewProgress))
|
||||||
navigationButton?.transform = CGAffineTransform(translationX: 0, y: y)
|
navigationButton?.transform = CGAffineTransform(translationX: 0, y: y)
|
||||||
|
|
|
||||||
|
|
@ -10,68 +10,33 @@ import UIKit
|
||||||
|
|
||||||
struct ScreenFlowManager {
|
struct ScreenFlowManager {
|
||||||
|
|
||||||
// Data
|
|
||||||
private static var wasManualScanningLastUsed = false // Last scanning method used
|
|
||||||
|
|
||||||
// MARK: - Go to app areas
|
// MARK: - Go to app areas
|
||||||
public static func gotoAutoconnect() {
|
public static func gotoScanner() {
|
||||||
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
|
|
||||||
|
|
||||||
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
|
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
|
||||||
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: AutoConnectViewController.kNavigationControllerIdentifier)
|
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: ScannerViewController.kIdentifier)
|
||||||
changeRootViewController(rootViewController: rootNavigationViewController, animationOptions: transition)
|
ScreenFlowManager.changeRootViewController(rootViewController: rootNavigationViewController, animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func goToManualScan() {
|
public static func restoreAndGoToHome() {
|
||||||
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 }
|
|
||||||
|
|
||||||
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
|
|
||||||
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: ScannerViewController.kNavigationControllerIdentifier)
|
|
||||||
changeRootViewController(rootViewController: rootNavigationViewController, animationOptions: .transitionFlipFromRight)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func restoreAndGoToCPBModules() {
|
|
||||||
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
|
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
|
||||||
|
|
||||||
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: HomeViewController.kNavigationControllerIdentifier)
|
// Add home to scanner
|
||||||
|
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: ScannerViewController.kIdentifier) as! UINavigationController
|
||||||
|
let homeViewController = mainStoryboard.instantiateViewController(withIdentifier: HomeViewController.kIdentifier)
|
||||||
|
rootNavigationViewController.viewControllers.append(homeViewController)
|
||||||
|
|
||||||
changeRootViewController(rootViewController: rootNavigationViewController) {
|
ScreenFlowManager.changeRootViewController(rootViewController: rootNavigationViewController, animated: false)
|
||||||
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
|
// 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 }
|
guard let window = UIApplication.shared.keyWindow else { return }
|
||||||
|
|
||||||
if let animationOptions = animationOptions {
|
if animated {
|
||||||
|
rootViewController.view.layoutIfNeeded()
|
||||||
|
UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: {
|
||||||
window.rootViewController = rootViewController
|
window.rootViewController = rootViewController
|
||||||
// rootViewController.view.layoutIfNeeded()
|
|
||||||
UIView.transition(with: window, duration: 0.3, options: animationOptions, animations: {
|
|
||||||
}, completion: { completed in
|
}, completion: { completed in
|
||||||
completionHandler?()
|
completionHandler?()
|
||||||
})
|
})
|
||||||
|
|
@ -81,92 +46,4 @@ struct ScreenFlowManager {
|
||||||
completionHandler?()
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
BluefruitPlayground/ViewControllers/.DS_Store
vendored
Normal file
|
|
@ -24,7 +24,7 @@ class AccelerometerPanelViewController: ModulePanelViewController {
|
||||||
@IBOutlet weak var accelerometerEulerYLabel: UILabel!
|
@IBOutlet weak var accelerometerEulerYLabel: UILabel!
|
||||||
@IBOutlet weak var accelerometerEulerZLabel: UILabel!
|
@IBOutlet weak var accelerometerEulerZLabel: UILabel!
|
||||||
|
|
||||||
|
private var currentState = BlePeripheral.ButtonsState(slideSwitch: .left, buttonA: .released, buttonB: .released)
|
||||||
|
|
||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
|
@ -37,6 +37,16 @@ class AccelerometerPanelViewController: ModulePanelViewController {
|
||||||
accelerometerTitleLabel.text = localizationManager.localizedString("accelerometer_panel_accelerometer_title")
|
accelerometerTitleLabel.text = localizationManager.localizedString("accelerometer_panel_accelerometer_title")
|
||||||
|
|
||||||
accelerometerEulerAnglesTitleLabel.text = localizationManager.localizedString("accelerometer_panel_eulerangles_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(
|
func accelerationReceived(
|
||||||
|
|
@ -55,4 +65,22 @@ class AccelerometerPanelViewController: ModulePanelViewController {
|
||||||
accelerometerEulerZLabel.text = String(format: "%.0f", zDeg)
|
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
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,31 +8,315 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import SceneKit
|
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 {
|
class AccelerometerViewController: ModuleViewController {
|
||||||
// Constants
|
// Constants
|
||||||
static let kIdentifier = "AccelerometerViewController"
|
static let kIdentifier = "AccelerometerViewController"
|
||||||
|
|
||||||
|
|
||||||
|
//For Recording Function
|
||||||
|
var recordButtonWasSelected: Bool = false
|
||||||
|
var preferStatusBarHidden: Bool!
|
||||||
|
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
@IBOutlet weak var sceneView: SCNView!
|
@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
|
// Data
|
||||||
private var circuitNode: SCNNode!
|
private var jawNode: SCNNode!
|
||||||
|
|
||||||
|
private var headNode: SCNNode!
|
||||||
|
|
||||||
|
private var sparkyFaceNode: SCNNode!
|
||||||
|
|
||||||
private var valuesPanelViewController: AccelerometerPanelViewController!
|
private var valuesPanelViewController: AccelerometerPanelViewController!
|
||||||
|
|
||||||
private var acceleration = BlePeripheral.AccelerometerValue(x: 0, y: 0, z: 0)
|
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
|
// MARK: - Lifecycle
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
// Add panels
|
// Add panels
|
||||||
valuesPanelViewController = (addPanelViewController(storyboardIdentifier: AccelerometerPanelViewController.kIdentifier) as! AccelerometerPanelViewController)
|
valuesPanelViewController = (addPanelViewController(storyboardIdentifier: AccelerometerPanelViewController.kIdentifier) as! AccelerometerPanelViewController)
|
||||||
|
|
||||||
// Load base
|
// Load base
|
||||||
let scene = SCNScene(named: "cpb.scn")!
|
let scene = SCNScene(named: "Sparky_Gold1.dae")!
|
||||||
scene.background.contents = UIColor.clear
|
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
|
// Setup scene
|
||||||
sceneView.scene = scene
|
sceneView.scene = scene
|
||||||
|
|
@ -41,13 +325,44 @@ class AccelerometerViewController: ModuleViewController {
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
let localizationManager = LocalizationManager.shared
|
let localizationManager = LocalizationManager.shared
|
||||||
self.title = localizationManager.localizedString("accelerometer_title")
|
self.title = localizationManager.localizedString("Puppets")
|
||||||
moduleHelpMessage = localizationManager.localizedString("accelerometer_help")
|
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) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
switchToFrontCamera()
|
||||||
|
|
||||||
// Initial value
|
// Initial value
|
||||||
if let acceleration = CPBBle.shared.accelerometerLastValue() {
|
if let acceleration = CPBBle.shared.accelerometerLastValue() {
|
||||||
self.acceleration = acceleration
|
self.acceleration = acceleration
|
||||||
|
|
@ -56,6 +371,136 @@ class AccelerometerViewController: ModuleViewController {
|
||||||
|
|
||||||
// Set delegate
|
// Set delegate
|
||||||
CPBBle.shared.accelerometerDelegate = self
|
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) {
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
|
@ -68,31 +513,112 @@ class AccelerometerViewController: ModuleViewController {
|
||||||
// MARK: - UI
|
// MARK: - UI
|
||||||
private func updateValueUI() {
|
private func updateValueUI() {
|
||||||
|
|
||||||
|
if playIntroAnimation == true {
|
||||||
|
|
||||||
// Calculate Euler Angles
|
// Calculate Euler Angles
|
||||||
let eulerAngles = eulerAnglesFromAcceleration()
|
let eulerAngles = eulerAnglesFromAcceleration()
|
||||||
|
|
||||||
|
let eulerAnglesForHead = eulerAnglesFromAccelerationForHead()
|
||||||
//DLog("Euler: pitch: \(eulerAngles.x) yaw: \(eulerAngles.y) roll: \(eulerAngles.z)")
|
//DLog("Euler: pitch: \(eulerAngles.x) yaw: \(eulerAngles.y) roll: \(eulerAngles.z)")
|
||||||
|
|
||||||
// Update circuit model orientation
|
// Update circuit model orientation
|
||||||
SCNTransaction.animationDuration = BlePeripheral.kCPBAcceleromterDefaultPeriod
|
SCNTransaction.animationDuration = BlePeripheral.kCPBAcceleromterDefaultPeriod
|
||||||
circuitNode.eulerAngles = eulerAngles
|
|
||||||
|
jawNode.eulerAngles = eulerAngles
|
||||||
|
headNode.eulerAngles = eulerAnglesForHead
|
||||||
|
|
||||||
|
|
||||||
// Update panel
|
// Update panel
|
||||||
valuesPanelViewController.accelerationReceived(acceleration: self.acceleration, eulerAngles: eulerAngles)
|
valuesPanelViewController.accelerationReceived(acceleration: self.acceleration, eulerAngles: eulerAngles)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func eulerAnglesFromAcceleration() -> SCNVector3 {
|
private func eulerAnglesFromAcceleration() -> SCNVector3 {
|
||||||
// https://robotics.stackexchange.com/questions/6953/how-to-calculate-euler-angles-from-gyroscope-output
|
// https://robotics.stackexchange.com/questions/6953/how-to-calculate-euler-angles-from-gyroscope-output
|
||||||
let accelAngleX = atan2(acceleration.y, acceleration.z)
|
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))
|
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
|
// MARK: - CPBBleAccelerometerDelegate
|
||||||
extension AccelerometerViewController: CPBBleAccelerometerDelegate {
|
extension AccelerometerViewController: CPBBleAccelerometerDelegate {
|
||||||
|
|
||||||
func cpbleAccelerationReceived(_ acceleration: BlePeripheral.AccelerometerValue) {
|
func cpbleAccelerationReceived(_ acceleration: BlePeripheral.AccelerometerValue) {
|
||||||
self.acceleration = acceleration
|
self.acceleration = acceleration
|
||||||
updateValueUI()
|
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}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -38,6 +38,16 @@ class ButtonStatusPanelViewController: ModulePanelViewController {
|
||||||
// Init
|
// Init
|
||||||
switchImageView.tintColor = offColor
|
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
|
// Localization
|
||||||
let localizationManager = LocalizationManager.shared
|
let localizationManager = LocalizationManager.shared
|
||||||
titleLabel.text = localizationManager.localizedString("buttonstatus_panel_title")
|
titleLabel.text = localizationManager.localizedString("buttonstatus_panel_title")
|
||||||
|
|
@ -57,6 +67,8 @@ class ButtonStatusPanelViewController: ModulePanelViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func animateDown(view: UIView) {
|
private func animateDown(view: UIView) {
|
||||||
|
|
||||||
|
|
||||||
UIView.animate(withDuration: 0.2) {
|
UIView.animate(withDuration: 0.2) {
|
||||||
view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
|
view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
|
||||||
view.tintColor = self.onColor
|
view.tintColor = self.onColor
|
||||||
|
|
@ -88,6 +100,7 @@ class ButtonStatusPanelViewController: ModulePanelViewController {
|
||||||
|
|
||||||
if buttonsState.buttonA != currentState.buttonA {
|
if buttonsState.buttonA != currentState.buttonA {
|
||||||
animateState(view: buttonAStatusView, isPressed: buttonsState.buttonA == .pressed)
|
animateState(view: buttonAStatusView, isPressed: buttonsState.buttonA == .pressed)
|
||||||
|
// NotificationCenter.default.post(name:NSNotification.Name(rawValue: "Command"), object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if buttonsState.buttonB != currentState.buttonB {
|
if buttonsState.buttonB != currentState.buttonB {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import UIKit
|
||||||
|
|
||||||
class HomeViewController: UIViewController {
|
class HomeViewController: UIViewController {
|
||||||
// Constants
|
// Constants
|
||||||
static let kNavigationControllerIdentifier = "ModulesNavigationController"
|
|
||||||
static let kIdentifier = "HomeViewController"
|
static let kIdentifier = "HomeViewController"
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
|
|
@ -67,60 +66,18 @@ class HomeViewController: UIViewController {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
// Setup UI
|
// Setup UI
|
||||||
/*
|
|
||||||
let topContentInsetForDetails: CGFloat = 20
|
let topContentInsetForDetails: CGFloat = 20
|
||||||
baseTableView.contentInset = UIEdgeInsets(top: topContentInsetForDetails, left: 0, bottom: 0, right: 0)
|
baseTableView.contentInset = UIEdgeInsets(top: topContentInsetForDetails, left: 0, bottom: 0, right: 0)
|
||||||
*/
|
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
let localizationManager = LocalizationManager.shared
|
let localizationManager = LocalizationManager.shared
|
||||||
self.title = localizationManager.localizedString("modules_title")
|
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
|
// MARK: - UITableViewDataSource
|
||||||
extension HomeViewController: 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 {
|
func numberOfSections(in tableView: UITableView) -> Int {
|
||||||
return 2 // Details + Modules
|
return 2 // Details + Modules
|
||||||
}
|
}
|
||||||
|
|
@ -130,31 +87,23 @@ extension HomeViewController: UITableViewDataSource {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return menuItems.count + 1 // + 1 disconnect
|
return menuItems.count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let reuseIdentifier = indexPath.section == 0 ? "DetailsCell" : "ModuleCell"
|
||||||
let cellType = cellTypeFromIndexPath(indexPath)
|
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath)
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: cellType.reuseIdentifier, for: indexPath)
|
|
||||||
|
|
||||||
// Add cell data here instead of using willDisplay to avoid problems with automatic dimension calculation
|
// Add cell data here instead of using willDisplay to avoid problems with automatic dimension calculation
|
||||||
let localizationManager = LocalizationManager.shared
|
let localizationManager = LocalizationManager.shared
|
||||||
|
let isDetails = indexPath.section == 0
|
||||||
|
|
||||||
switch(cellType) {
|
if isDetails {
|
||||||
case .details:
|
|
||||||
let detailsCell = cell as! TitleTableViewCell
|
let detailsCell = cell as! TitleTableViewCell
|
||||||
detailsCell.titleLabel.text = localizationManager.localizedString("modules_subtitle")
|
detailsCell.titleLabel.text = localizationManager.localizedString("modules_subtitle")
|
||||||
/*
|
}
|
||||||
case .peripheral
|
else {
|
||||||
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:
|
|
||||||
let moduleCell = cell as! CommonTableViewCell
|
let moduleCell = cell as! CommonTableViewCell
|
||||||
let menuItem = menuItems[indexPath.row]
|
let menuItem = menuItems[indexPath.row]
|
||||||
|
|
||||||
|
|
@ -164,16 +113,12 @@ extension HomeViewController: UITableViewDataSource {
|
||||||
let subtitleStringId = menuItem.subtitleStringId
|
let subtitleStringId = menuItem.subtitleStringId
|
||||||
moduleCell.subtitleLabel?.text = localizationManager.localizedString(subtitleStringId)
|
moduleCell.subtitleLabel?.text = localizationManager.localizedString(subtitleStringId)
|
||||||
|
|
||||||
|
//moduleCell.setPanelBackgroundColor(menuItem.color)
|
||||||
moduleCell.iconImageView.backgroundColor = menuItem.color
|
moduleCell.iconImageView.backgroundColor = menuItem.color
|
||||||
moduleCell.iconImageView.layer.borderColor = UIColor(named: "text_default")?.cgColor
|
moduleCell.iconImageView.layer.borderColor = UIColor(named: "text_default")?.cgColor
|
||||||
moduleCell.iconImageView.layer.borderWidth = 1
|
moduleCell.iconImageView.layer.borderWidth = 1
|
||||||
moduleCell.iconImageView.layer.cornerRadius = 7
|
moduleCell.iconImageView.layer.cornerRadius = 7
|
||||||
moduleCell.iconImageView.layer.masksToBounds = true
|
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
|
return cell
|
||||||
|
|
@ -190,12 +135,10 @@ extension HomeViewController: UITableViewDelegate {
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
|
||||||
let cellType = cellTypeFromIndexPath(indexPath)
|
let isDetails = indexPath.section == 0
|
||||||
switch(cellType) {
|
guard !isDetails else { return }
|
||||||
case .details:
|
|
||||||
break
|
guard let module = Modules(rawValue: indexPath.row) else { return }
|
||||||
case .module:
|
|
||||||
if let module = Modules(rawValue: indexPath.row) {
|
|
||||||
var storyboardId: String? = nil
|
var storyboardId: String? = nil
|
||||||
switch module {
|
switch module {
|
||||||
case .color:
|
case .color:
|
||||||
|
|
@ -214,34 +157,8 @@ extension HomeViewController: UITableViewDelegate {
|
||||||
|
|
||||||
if let identifier = storyboardId, let viewController = storyboard?.instantiateViewController(withIdentifier: identifier) {
|
if let identifier = storyboardId, let viewController = storyboard?.instantiateViewController(withIdentifier: identifier) {
|
||||||
|
|
||||||
// Show viewController with completion block
|
|
||||||
CATransaction.begin()
|
|
||||||
self.show(viewController, sender: self)
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -16,7 +16,6 @@ class LightSensorViewController: ModuleViewController {
|
||||||
private var circuitViewController: CircuitViewController!
|
private var circuitViewController: CircuitViewController!
|
||||||
private var lightmeterPanelViewController: LightSensorPanelViewController!
|
private var lightmeterPanelViewController: LightSensorPanelViewController!
|
||||||
private var light: Float?
|
private var light: Float?
|
||||||
private var chartPanelViewController: LightChartPanelViewController!
|
|
||||||
|
|
||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
|
@ -25,9 +24,6 @@ class LightSensorViewController: ModuleViewController {
|
||||||
// Add panels
|
// Add panels
|
||||||
lightmeterPanelViewController = (addPanelViewController(storyboardIdentifier: LightSensorPanelViewController.kIdentifier) as! LightSensorPanelViewController)
|
lightmeterPanelViewController = (addPanelViewController(storyboardIdentifier: LightSensorPanelViewController.kIdentifier) as! LightSensorPanelViewController)
|
||||||
|
|
||||||
chartPanelViewController = (addPanelViewController(storyboardIdentifier: LightChartPanelViewController.kIdentifier) as! LightChartPanelViewController)
|
|
||||||
|
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
let localizationManager = LocalizationManager.shared
|
let localizationManager = LocalizationManager.shared
|
||||||
self.title = localizationManager.localizedString("lightsensor_title")
|
self.title = localizationManager.localizedString("lightsensor_title")
|
||||||
|
|
@ -65,9 +61,6 @@ class LightSensorViewController: ModuleViewController {
|
||||||
private func updateValueUI() {
|
private func updateValueUI() {
|
||||||
if let light = self.light {
|
if let light = self.light {
|
||||||
lightmeterPanelViewController.lightValueReceived(light)
|
lightmeterPanelViewController.lightValueReceived(light)
|
||||||
|
|
||||||
// Update chart
|
|
||||||
chartPanelViewController.lightValueReceived()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,7 @@ class CircuitViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init neopixels color
|
// Init neopixels color
|
||||||
for (i, neopixelView) in self.neopixelsContainerView.subviews.enumerated() {
|
neopixelsReset(animated: false)
|
||||||
if i < self.isNeopixelSelected.count {
|
|
||||||
neopixelView.backgroundColor = .clear
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//neopixelsReset(animated: false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
|
@ -103,6 +98,7 @@ class CircuitViewController: UIViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Notifications
|
// MARK: - Notifications
|
||||||
private var didUpdateNeopixelLightSequenceObserver: NSObjectProtocol?
|
private var didUpdateNeopixelLightSequenceObserver: NSObjectProtocol?
|
||||||
|
|
||||||
|
|
@ -126,6 +122,7 @@ class CircuitViewController: UIViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let didUpdateNeopixelLightSequenceObserver = didUpdateNeopixelLightSequenceObserver {notificationCenter.removeObserver(didUpdateNeopixelLightSequenceObserver)}
|
if let didUpdateNeopixelLightSequenceObserver = didUpdateNeopixelLightSequenceObserver {notificationCenter.removeObserver(didUpdateNeopixelLightSequenceObserver)}
|
||||||
|
|
@ -171,6 +168,7 @@ class CircuitViewController: UIViewController {
|
||||||
// Animate
|
// Animate
|
||||||
UIView.animate(withDuration: 0.2, animations: {
|
UIView.animate(withDuration: 0.2, animations: {
|
||||||
selectedView.transform = isSelected ? .identity:CGAffineTransform(scaleX: 1.2, y: 1.2)
|
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
|
selectedView.alpha = isSelected ? 1:CircuitViewController.kUnselectedButtonAlpha
|
||||||
}) { (_) in
|
}) { (_) in
|
||||||
selectedView.transform = .identity
|
selectedView.transform = .identity
|
||||||
|
|
@ -183,7 +181,7 @@ class CircuitViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
func setNeopixelsColor(_ color: UIColor, onlySelected: Bool, animated: Bool, baseColor: UIColor? = nil) {
|
func setNeopixelsColor(_ color: UIColor, onlySelected: Bool, animated: Bool) {
|
||||||
|
|
||||||
if onlySelected {
|
if onlySelected {
|
||||||
CPBBle.shared.neopixelSetPixelColor(color, pixelMask: isNeopixelSelected)
|
CPBBle.shared.neopixelSetPixelColor(color, pixelMask: isNeopixelSelected)
|
||||||
|
|
@ -194,10 +192,9 @@ class CircuitViewController: UIViewController {
|
||||||
|
|
||||||
// UI Animation
|
// UI Animation
|
||||||
UIView.animate(withDuration: animated ? 0.1: 0) {
|
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() {
|
for (i, neopixelView) in self.neopixelsContainerView.subviews.enumerated() {
|
||||||
if i < self.isNeopixelSelected.count && (!onlySelected || self.isNeopixelSelected[i]) {
|
if i < self.isNeopixelSelected.count && (!onlySelected || self.isNeopixelSelected[i]) {
|
||||||
neopixelView.backgroundColor = baseColor ?? color
|
neopixelView.backgroundColor = color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,9 @@ class NeoPixelsViewController: ModuleViewController {
|
||||||
circuitViewController.neopixelsReset(animated: true)
|
circuitViewController.neopixelsReset(animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func selectColor(_ color: UIColor, baseColor: UIColor) {
|
private func selectColor(_ color: UIColor) {
|
||||||
// Update circuit
|
// Update circuit
|
||||||
circuitViewController.setNeopixelsColor(color, onlySelected: true, animated: true, baseColor: baseColor)
|
circuitViewController.setNeopixelsColor(color, onlySelected: true, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Page Management
|
// MARK: - Page Management
|
||||||
|
|
@ -104,15 +104,14 @@ class NeoPixelsViewController: ModuleViewController {
|
||||||
|
|
||||||
// MARK: - NeopixelsColorPaletteViewControllerDelegate
|
// MARK: - NeopixelsColorPaletteViewControllerDelegate
|
||||||
extension NeoPixelsViewController: NeopixelsColorPaletteViewControllerDelegate {
|
extension NeoPixelsViewController: NeopixelsColorPaletteViewControllerDelegate {
|
||||||
func colorPaletteColorSelected(color: UIColor, baseColor: UIColor) {
|
func colorPaletteColorSelected(color: UIColor) {
|
||||||
selectColor(color, baseColor: baseColor)
|
selectColor(color)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - NeopixelColorWheelViewControllerDelegate
|
// MARK: - NeopixelsColorPaletteViewControllerDelegate
|
||||||
extension NeoPixelsViewController: NeopixelColorWheelViewControllerDelegate {
|
extension NeoPixelsViewController: NeopixelColorWheelViewControllerDelegate {
|
||||||
func colorWheelColorSelected(color: UIColor, baseColor: UIColor) {
|
func colorWheelColorSelected(color: UIColor) {
|
||||||
selectColor(color, baseColor: baseColor)
|
selectColor(color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
import FlexColorPicker
|
import FlexColorPicker
|
||||||
|
|
||||||
protocol NeopixelsColorPaletteViewControllerDelegate: class {
|
protocol NeopixelsColorPaletteViewControllerDelegate: class {
|
||||||
func colorPaletteColorSelected(color: UIColor, baseColor: UIColor)
|
func colorPaletteColorSelected(color: UIColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
class NeopixelsColorPaletteViewController: ModulePanelViewController {
|
class NeopixelsColorPaletteViewController: ModulePanelViewController {
|
||||||
|
|
@ -124,6 +124,6 @@ class NeopixelsColorPaletteViewController: ModulePanelViewController {
|
||||||
let hsbColor = HSBColor(color: color)
|
let hsbColor = HSBColor(color: color)
|
||||||
let colorWithBrighness = hsbColor.withBrightness(brightness).toUIColor()
|
let colorWithBrighness = hsbColor.withBrightness(brightness).toUIColor()
|
||||||
|
|
||||||
delegate?.colorPaletteColorSelected(color: colorWithBrighness, baseColor: color)
|
delegate?.colorPaletteColorSelected(color: colorWithBrighness)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
import FlexColorPicker
|
import FlexColorPicker
|
||||||
|
|
||||||
protocol NeopixelColorWheelViewControllerDelegate: class {
|
protocol NeopixelColorWheelViewControllerDelegate: class {
|
||||||
func colorWheelColorSelected(color: UIColor, baseColor: UIColor)
|
func colorWheelColorSelected(color: UIColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
class NeopixelsColorWheelViewController: ModulePanelViewController {
|
class NeopixelsColorWheelViewController: ModulePanelViewController {
|
||||||
|
|
@ -96,7 +96,7 @@ class NeopixelsColorWheelViewController: ModulePanelViewController {
|
||||||
let hsbColor = HSBColor(color: color)
|
let hsbColor = HSBColor(color: color)
|
||||||
let colorWithBrighness = hsbColor.withBrightness(brightness).toUIColor()
|
let colorWithBrighness = hsbColor.withBrightness(brightness).toUIColor()
|
||||||
|
|
||||||
delegate?.colorWheelColorSelected(color: colorWithBrighness, baseColor: color)
|
delegate?.colorWheelColorSelected(color: colorWithBrighness)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class NeopixelsLightSequenceViewController: ModulePanelViewController {
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
private var previewViewControllers = [PixelsPreviewViewController]()
|
private var previewViewControllers = [PixelsPreviewViewController]()
|
||||||
|
private var speed: Double = 0.3
|
||||||
|
|
||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
|
@ -29,10 +30,8 @@ class NeopixelsLightSequenceViewController: ModulePanelViewController {
|
||||||
let _ = previewViewControllers.map{$0.tag = $0.view.superview?.tag ?? 0}
|
let _ = previewViewControllers.map{$0.tag = $0.view.superview?.tag ?? 0}
|
||||||
|
|
||||||
// Set initial speed
|
// Set initial speed
|
||||||
let speed = CPBBle.kLightSequenceDefaultSpeed
|
|
||||||
speedSlider.value = Float(speed)
|
speedSlider.value = Float(speed)
|
||||||
//speedChanged(speedSlider)
|
speedChanged(speedSlider)
|
||||||
let _ = previewViewControllers.map{$0.speed = speed}
|
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
let localizationManager = LocalizationManager.shared
|
let localizationManager = LocalizationManager.shared
|
||||||
|
|
@ -48,10 +47,10 @@ class NeopixelsLightSequenceViewController: ModulePanelViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func speedChanged(_ sender: UISlider) {
|
@IBAction func speedChanged(_ sender: UISlider) {
|
||||||
let speed = Double(sender.value)
|
speed = Double(sender.value)
|
||||||
DLog("speed: \(speed)")
|
DLog("speed: \(speed)")
|
||||||
|
|
||||||
CPBBle.shared.neopixelCurrentLightSequenceAnimationSpeed = speed
|
CPBBle.shared.neopixelLightSequenceAnimationSpeed = speed
|
||||||
let _ = previewViewControllers.map{$0.speed = speed}
|
let _ = previewViewControllers.map{$0.speed = speed}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ class PixelsPreviewViewController: UIViewController {
|
||||||
|
|
||||||
guard let lightSequenceGenerator = lightSequenceGeneratorForTag(self.tag) else { return }
|
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!.speed = speed
|
||||||
lightSequenceAnimation!.start() { [weak self] pixelsBytes in
|
lightSequenceAnimation!.start() { [weak self] pixelsBytes in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
@ -72,8 +72,6 @@ class PixelsPreviewViewController: UIViewController {
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
lightSequenceAnimation?.stop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
|
|
|
||||||
|
|
@ -70,17 +70,6 @@ class AboutViewController: UIViewController {
|
||||||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,16 +55,6 @@ extension ScanProblemsViewController: UITableViewDataSource {
|
||||||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,10 @@ import CoreBluetooth
|
||||||
|
|
||||||
class ScannerViewController: UIViewController {
|
class ScannerViewController: UIViewController {
|
||||||
// Constants
|
// Constants
|
||||||
static let kNavigationControllerIdentifier = "ScannerNavigationController"
|
static let kIdentifier = "ScannerNavigationController" //"ScannerViewController"
|
||||||
|
|
||||||
// Config
|
// Config
|
||||||
|
private static let kServicesToScan: [CBUUID]? = nil //[BlePeripheral.kUartServiceUUID]
|
||||||
private static let kDelayToShowWait: TimeInterval = 1.0
|
private static let kDelayToShowWait: TimeInterval = 1.0
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
|
|
@ -21,23 +22,13 @@ class ScannerViewController: UIViewController {
|
||||||
@IBOutlet weak var waitView: UIView!
|
@IBOutlet weak var waitView: UIView!
|
||||||
@IBOutlet weak var waitLabel: UILabel!
|
@IBOutlet weak var waitLabel: UILabel!
|
||||||
@IBOutlet weak var problemsButton: UIButton!
|
@IBOutlet weak var problemsButton: UIButton!
|
||||||
@IBOutlet weak var scanAutomaticallyButton: CornerShadowButton!
|
|
||||||
@IBOutlet weak var actionsContainerView: UIStackView!
|
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
private let refreshControl = UIRefreshControl()
|
private let refreshControl = UIRefreshControl()
|
||||||
private let bleManager = Config.bleManager
|
private let bleManager = Config.bleManager
|
||||||
private var peripheralList = PeripheralList(bleManager: Config.bleManager)
|
private var peripheralList = PeripheralList(bleManager: Config.bleManager)
|
||||||
|
|
||||||
private var selectedPeripheral: BlePeripheral? {
|
private var selectedPeripheral: BlePeripheral?
|
||||||
didSet {
|
|
||||||
if isViewLoaded {
|
|
||||||
UIView.animate(withDuration: 0.3) {
|
|
||||||
self.actionsContainerView.alpha = self.selectedPeripheral == nil ? 1:0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var infoAlertController: UIAlertController?
|
private var infoAlertController: UIAlertController?
|
||||||
|
|
||||||
private var isBaseTableScrolling = false
|
private var isBaseTableScrolling = false
|
||||||
|
|
@ -68,7 +59,6 @@ class ScannerViewController: UIViewController {
|
||||||
|
|
||||||
waitLabel.text = localizationManager.localizedString("scanner_searching")
|
waitLabel.text = localizationManager.localizedString("scanner_searching")
|
||||||
problemsButton.setTitle(localizationManager.localizedString("scanner_problems_action").uppercased(), for: .normal)
|
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) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
|
@ -85,7 +75,7 @@ class ScannerViewController: UIViewController {
|
||||||
if connectedPeripherals.count == 1, let peripheral = connectedPeripherals.first {
|
if connectedPeripherals.count == 1, let peripheral = connectedPeripherals.first {
|
||||||
DLog("Disconnect from previously connected peripheral")
|
DLog("Disconnect from previously connected peripheral")
|
||||||
// Disconnect from peripheral
|
// Disconnect from peripheral
|
||||||
disconnect(peripheral: peripheral)
|
bleManager.disconnect(from: peripheral)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,17 +89,11 @@ class ScannerViewController: UIViewController {
|
||||||
updateScannedPeripherals()
|
updateScannedPeripherals()
|
||||||
|
|
||||||
// Start scannning
|
// Start scannning
|
||||||
//bleManager.startScan(withServices: ScannerViewController.kServicesToScan)
|
bleManager.startScan(withServices: ScannerViewController.kServicesToScan)
|
||||||
if !bleManager.isScanning {
|
|
||||||
bleManager.startScan()
|
|
||||||
|
|
||||||
}
|
|
||||||
// Remove saved peripheral for autoconnect
|
|
||||||
Settings.autoconnectPeripheralIdentifier = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
super.viewDidDisappear(animated)
|
||||||
|
|
||||||
// Stop scanning
|
// Stop scanning
|
||||||
bleManager.stopScan()
|
bleManager.stopScan()
|
||||||
|
|
@ -119,6 +103,7 @@ class ScannerViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|
||||||
// Ble Notifications
|
// Ble Notifications
|
||||||
registerNotifications(enabled: false)
|
registerNotifications(enabled: false)
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +217,7 @@ class ScannerViewController: UIViewController {
|
||||||
alertController.addAction(okAction)
|
alertController.addAction(okAction)
|
||||||
self.present(alertController, animated: true, completion: nil)
|
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
|
// Reload table
|
||||||
reloadBaseTable()
|
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) {
|
private func peripheralDidUpdateName(notification: Notification) {
|
||||||
|
|
@ -296,16 +293,17 @@ class ScannerViewController: UIViewController {
|
||||||
self.present(viewController, animated: true, completion: nil)
|
self.present(viewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func scanAutomatically(_ sender: Any) {
|
|
||||||
ScreenFlowManager.gotoAutoconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func showPeripheralDetails() {
|
private func showPeripheralDetails() {
|
||||||
// Save selected peripheral for autoconnect
|
// Save selected peripheral for autoconnect
|
||||||
Settings.autoconnectPeripheralIdentifier = selectedPeripheral?.identifier
|
Settings.autoconnectPeripheralIdentifier = selectedPeripheral?.identifier
|
||||||
|
|
||||||
// Go to home screen
|
// 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
|
// MARK: - UI
|
||||||
|
|
@ -459,6 +457,15 @@ extension ScannerViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
||||||
|
isBaseTableScrolling = false
|
||||||
|
|
||||||
|
if isScannerTableWaitingForReload {
|
||||||
|
reloadBaseTable()
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
|
||||||
// NavigationBar Button Custom Animation
|
// NavigationBar Button Custom Animation
|
||||||
|
|
@ -468,7 +475,7 @@ extension ScannerViewController {
|
||||||
|
|
||||||
// Move Refresh control when a large title is used
|
// Move Refresh control when a large title is used
|
||||||
if let height = navigationController?.navigationBar.frame.height {
|
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
|
// Hide details opacity when showing the refresh control
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,6 @@ class StartupViewController: UIViewController {
|
||||||
private static let kServicesToReconnect = [BlePeripheral.kUartServiceUUID]
|
private static let kServicesToReconnect = [BlePeripheral.kUartServiceUUID]
|
||||||
private static let kReconnectTimeout = 2.0
|
private static let kReconnectTimeout = 2.0
|
||||||
|
|
||||||
// UI
|
|
||||||
@IBOutlet weak var restoreConnectionLabel: UILabel!
|
|
||||||
|
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
private let bleSupportSemaphore = DispatchSemaphore(value: 0)
|
private let bleSupportSemaphore = DispatchSemaphore(value: 0)
|
||||||
private var startTime: CFAbsoluteTime!
|
private var startTime: CFAbsoluteTime!
|
||||||
|
|
@ -28,12 +24,6 @@ class StartupViewController: UIViewController {
|
||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
// UI
|
|
||||||
restoreConnectionLabel.alpha = 0
|
|
||||||
|
|
||||||
// Localization
|
|
||||||
restoreConnectionLabel.text = LocalizationManager.shared.localizedString("splash_restoringconnection")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
|
@ -134,7 +124,6 @@ class StartupViewController: UIViewController {
|
||||||
// MARK: - Reconnect previously connnected Ble Peripheral
|
// MARK: - Reconnect previously connnected Ble Peripheral
|
||||||
private func reconnecToPeripheral(withIdentifier identifier: UUID) {
|
private func reconnecToPeripheral(withIdentifier identifier: UUID) {
|
||||||
DLog("Reconnecting...")
|
DLog("Reconnecting...")
|
||||||
|
|
||||||
// Reconnect
|
// Reconnect
|
||||||
let isTryingToReconnect = BleManager.shared.reconnecToPeripherals(withIdentifiers: [identifier], withServices: StartupViewController.kServicesToReconnect, timeout: StartupViewController.kReconnectTimeout)
|
let isTryingToReconnect = BleManager.shared.reconnecToPeripherals(withIdentifiers: [identifier], withServices: StartupViewController.kServicesToReconnect, timeout: StartupViewController.kReconnectTimeout)
|
||||||
|
|
||||||
|
|
@ -156,22 +145,23 @@ class StartupViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func didDisconnectFromPeripheral() {
|
private func didDisconnectFromPeripheral() {
|
||||||
|
/*
|
||||||
|
// Clear selected peripheral
|
||||||
|
self.selectedPeripheral = nil
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
// Autoconnect failed
|
// Autoconnect failed
|
||||||
connected(peripheral: nil)
|
connected(peripheral: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func connected(peripheral: BlePeripheral?) {
|
private func connected(peripheral: BlePeripheral?) {
|
||||||
if let peripheral = peripheral {
|
if let peripheral = peripheral {
|
||||||
// Show restoring connection label
|
|
||||||
UIView.animate(withDuration: 0.2) {
|
|
||||||
self.restoreConnectionLabel.alpha = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup peripheral
|
// Setup peripheral
|
||||||
CPBBle.shared.setupPeripheral(blePeripheral: peripheral) { [weak self] result in
|
CPBBle.shared.setupPeripheral(blePeripheral: peripheral) { [weak self] result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success():
|
case .success():
|
||||||
ScreenFlowManager.restoreAndGoToCPBModules()
|
ScreenFlowManager.restoreAndGoToHome()
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
DLog("Failed setup peripheral: \(error.localizedDescription)")
|
DLog("Failed setup peripheral: \(error.localizedDescription)")
|
||||||
|
|
@ -190,15 +180,12 @@ class StartupViewController: UIViewController {
|
||||||
|
|
||||||
// MARK: - Screen Flow
|
// MARK: - Screen Flow
|
||||||
private func gotoInitialScreen() {
|
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)")
|
DLog("Start app with viewController: \(viewControllerIdentifier)")
|
||||||
|
|
||||||
// Change splash screen to main screen
|
// Change splash screen to main screen
|
||||||
if let rootViewController = self.storyboard?.instantiateViewController(withIdentifier: viewControllerIdentifier) {
|
if let rootViewController = self.storyboard?.instantiateViewController(withIdentifier: viewControllerIdentifier) {
|
||||||
ScreenFlowManager.changeRootViewController(rootViewController: rootViewController) {
|
ScreenFlowManager.changeRootViewController(rootViewController: rootViewController, animated: false)
|
||||||
// Start scannning
|
|
||||||
//Config.bleManager.startScan()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ class TemperaturePanelViewController: ModulePanelViewController {
|
||||||
// MARK: - Line Chart
|
// MARK: - Line Chart
|
||||||
private func setupChart() {
|
private func setupChart() {
|
||||||
//chartView.delegate = self
|
//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.dragEnabled = false
|
||||||
chartView.isUserInteractionEnabled = false
|
chartView.isUserInteractionEnabled = false
|
||||||
|
|
|
||||||
|
|
@ -86,15 +86,6 @@ class TipViewController: UIViewController {
|
||||||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ class TipsViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func skip(_ sender: Any) {
|
@IBAction func skip(_ sender: Any) {
|
||||||
ScreenFlowManager.gotoAutoconnect()
|
ScreenFlowManager.gotoScanner()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func pageControlTapHandler(sender: UIPageControl) {
|
@objc private func pageControlTapHandler(sender: UIPageControl) {
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,6 @@ class ToneGeneratorViewController: UIViewController {
|
||||||
@IBOutlet weak var keysContainerView: UIView!
|
@IBOutlet weak var keysContainerView: UIView!
|
||||||
@IBOutlet weak var whiteKeysStackView: UIStackView!
|
@IBOutlet weak var whiteKeysStackView: UIStackView!
|
||||||
@IBOutlet weak var speakerImageView: UIImageView!
|
@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
|
// Data
|
||||||
private var tonesPlaying = Set<Int>()
|
private var tonesPlaying = Set<Int>()
|
||||||
|
|
@ -79,17 +75,6 @@ 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
|
// Localization
|
||||||
let localizationManager = LocalizationManager.shared
|
let localizationManager = LocalizationManager.shared
|
||||||
self.title = localizationManager.localizedString("tonegenerator_title")
|
self.title = localizationManager.localizedString("tonegenerator_title")
|
||||||
|
|
@ -104,26 +89,6 @@ class ToneGeneratorViewController: UIViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
@objc private func keyDown(_ sender: UIButton) {
|
@objc private func keyDown(_ sender: UIButton) {
|
||||||
let tag = sender.tag
|
let tag = sender.tag
|
||||||
|
|
@ -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? {
|
private func frequencyForKeyTag(_ tag: Int) -> Double? {
|
||||||
var frequency: Double? = nil
|
var frequency: Double? = nil
|
||||||
if tag >= 100 && tag < 200 { // is white note
|
if tag >= 100 && tag < 200 { // is white note
|
||||||
|
|
@ -199,4 +150,3 @@ class ToneGeneratorViewController: UIViewController {
|
||||||
self.present(navigationController, animated: true, completion: nil)
|
self.present(navigationController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// 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.
|
// UI tests must launch the application that they test.
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
|
|
@ -40,7 +40,7 @@ class BluefruitPlaygroundUITests: XCTestCase {
|
||||||
let scrollViewsQuery = app.scrollViews
|
let scrollViewsQuery = app.scrollViews
|
||||||
let elementsQuery = scrollViewsQuery.otherElements
|
let elementsQuery = scrollViewsQuery.otherElements
|
||||||
|
|
||||||
sleep(2) // Wait for the intro animation
|
sleep(3) // Wait for the intro animation
|
||||||
snapshot("01a_Welcome")
|
snapshot("01a_Welcome")
|
||||||
|
|
||||||
elementsQuery.buttons["LET'S GET STARTED..."].tap()
|
elementsQuery.buttons["LET'S GET STARTED..."].tap()
|
||||||
|
|
@ -51,7 +51,7 @@ class BluefruitPlaygroundUITests: XCTestCase {
|
||||||
elementsQuery.buttons["BEGIN PAIRING"].tap()
|
elementsQuery.buttons["BEGIN PAIRING"].tap()
|
||||||
|
|
||||||
let tablesQuery = app.tables
|
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")
|
snapshot("02_Modules")
|
||||||
|
|
||||||
|
|
|
||||||
16
Gemfile.lock
|
|
@ -1,7 +1,7 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.2)
|
CFPropertyList (2.3.6)
|
||||||
addressable (2.7.0)
|
addressable (2.7.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
|
|
@ -18,7 +18,7 @@ GEM
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.7.5)
|
dotenv (2.7.5)
|
||||||
emoji_regex (1.0.1)
|
emoji_regex (1.0.1)
|
||||||
excon (0.71.1)
|
excon (0.71.0)
|
||||||
faraday (0.17.1)
|
faraday (0.17.1)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday-cookie_jar (0.0.6)
|
faraday-cookie_jar (0.0.6)
|
||||||
|
|
@ -27,7 +27,7 @@ GEM
|
||||||
faraday_middleware (0.13.1)
|
faraday_middleware (0.13.1)
|
||||||
faraday (>= 0.7.4, < 1.0)
|
faraday (>= 0.7.4, < 1.0)
|
||||||
fastimage (2.1.7)
|
fastimage (2.1.7)
|
||||||
fastlane (2.138.0)
|
fastlane (2.137.0)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.3, < 3.0.0)
|
addressable (>= 2.3, < 3.0.0)
|
||||||
babosa (>= 1.0.2, < 2.0.0)
|
babosa (>= 1.0.2, < 2.0.0)
|
||||||
|
|
@ -36,7 +36,7 @@ GEM
|
||||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
emoji_regex (>= 0.1, < 2.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 (~> 0.17)
|
||||||
faraday-cookie_jar (~> 0.0.6)
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
faraday_middleware (~> 0.13.1)
|
faraday_middleware (~> 0.13.1)
|
||||||
|
|
@ -61,7 +61,7 @@ GEM
|
||||||
tty-screen (>= 0.6.3, < 1.0.0)
|
tty-screen (>= 0.6.3, < 1.0.0)
|
||||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||||
word_wrap (~> 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 (~> 0.3.0)
|
||||||
xcpretty-travis-formatter (>= 0.0.3)
|
xcpretty-travis-formatter (>= 0.0.3)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
|
|
@ -93,7 +93,7 @@ GEM
|
||||||
http-cookie (1.0.3)
|
http-cookie (1.0.3)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
json (2.3.0)
|
json (2.1.0)
|
||||||
jwt (2.1.0)
|
jwt (2.1.0)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mime-types (3.3)
|
mime-types (3.3)
|
||||||
|
|
@ -121,7 +121,7 @@ GEM
|
||||||
faraday (~> 0.9)
|
faraday (~> 0.9)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simctl (1.6.7)
|
simctl (1.6.6)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
slack-notifier (2.3.2)
|
slack-notifier (2.3.2)
|
||||||
|
|
@ -138,7 +138,7 @@ GEM
|
||||||
unf_ext (0.0.7.6)
|
unf_ext (0.0.7.6)
|
||||||
unicode-display_width (1.6.0)
|
unicode-display_width (1.6.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.14.0)
|
xcodeproj (1.12.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
|
|
||||||
|
|
@ -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/
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
"iPhone 11",
|
"iPhone 11",
|
||||||
"iPhone 8 Plus",
|
"iPhone 8 Plus",
|
||||||
"iPhone 8",
|
"iPhone 8",
|
||||||
|
"iPhone SE",
|
||||||
|
|
||||||
# "iPhone SE",
|
|
||||||
# "iPhone 6",
|
# "iPhone 6",
|
||||||
# "iPhone 6 Plus",
|
# "iPhone 6 Plus",
|
||||||
# "iPhone 5",
|
# "iPhone 5",
|
||||||
|
|
@ -29,8 +29,7 @@ languages([
|
||||||
# Where should the resulting screenshots be stored?
|
# Where should the resulting screenshots be stored?
|
||||||
# output_directory "./screenshots"
|
# output_directory "./screenshots"
|
||||||
|
|
||||||
# remove the '#' to clear all previously generated screenshots before creating new ones
|
# clear_previous_screenshots true # remove the '#' to clear all previously generated screenshots before creating new ones
|
||||||
clear_previous_screenshots true
|
|
||||||
|
|
||||||
# Choose which project/workspace to use
|
# Choose which project/workspace to use
|
||||||
# project "./Project.xcodeproj"
|
# project "./Project.xcodeproj"
|
||||||
|
|
|
||||||