Compare commits
1 commit
master
...
puppetModu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6d10619ff |
BIN
.DS_Store
vendored
Normal file
|
|
@ -7,6 +7,7 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
2981E3B523C6526C00974EA2 /* Sparky_Gold1.dae in Resources */ = {isa = PBXBuildFile; fileRef = 2981E3B423C6526C00974EA2 /* Sparky_Gold1.dae */; };
|
||||
A901EC48237C43C000687BE6 /* Data+LittleEndianTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A901EC47237C43C000687BE6 /* Data+LittleEndianTypes.swift */; };
|
||||
A901EC4D237C47D800687BE6 /* CPBDataSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = A901EC4C237C47D800687BE6 /* CPBDataSeries.swift */; };
|
||||
A92B9F0D234DE4A2002374F0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92B9F0C234DE4A2002374F0 /* AppDelegate.swift */; };
|
||||
|
|
@ -177,16 +178,6 @@
|
|||
A9BED77A234FBEAB002FFF53 /* CommandQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BED763234FBEAB002FFF53 /* CommandQueue.swift */; };
|
||||
A9BED77B234FBEAB002FFF53 /* LogHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BED764234FBEAB002FFF53 /* LogHelper.swift */; };
|
||||
A9BED77D234FBEAB002FFF53 /* Data+ScanValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BED766234FBEAB002FFF53 /* Data+ScanValue.swift */; };
|
||||
A9D12E8423E3661800F3C259 /* UIColor+Interpolate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D12E8323E3661800F3C259 /* UIColor+Interpolate.swift */; };
|
||||
A9D12E8623E3C5CF00F3C259 /* LightChartPanelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D12E8523E3C5CF00F3C259 /* LightChartPanelViewController.swift */; };
|
||||
A9D12E8723E3C5CF00F3C259 /* LightChartPanelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D12E8523E3C5CF00F3C259 /* LightChartPanelViewController.swift */; };
|
||||
A9D12E8823E4434000F3C259 /* UIColor+Interpolate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D12E8323E3661800F3C259 /* UIColor+Interpolate.swift */; };
|
||||
A9D5B21023E0985600178E1B /* AutoConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D5B20F23E0985600178E1B /* AutoConnectViewController.swift */; };
|
||||
A9D5B21123E0985600178E1B /* AutoConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D5B20F23E0985600178E1B /* AutoConnectViewController.swift */; };
|
||||
A9D5B21323E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D5B21223E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift */; };
|
||||
A9D5B21423E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D5B21223E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift */; };
|
||||
A9D5B21623E3024700178E1B /* BluetoothStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D5B21523E3024700178E1B /* BluetoothStatusViewController.swift */; };
|
||||
A9D5B21723E3024800178E1B /* BluetoothStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D5B21523E3024700178E1B /* BluetoothStatusViewController.swift */; };
|
||||
A9DF06122359B0600094327F /* ButtonStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DF06112359B0600094327F /* ButtonStatusViewController.swift */; };
|
||||
A9DF06142359B31C0094327F /* ButtonStatusPanelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DF06132359B31C0094327F /* ButtonStatusPanelViewController.swift */; };
|
||||
A9E4C02E2358E6B400B5A493 /* UIColor+LightAndDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E4C02D2358E6B400B5A493 /* UIColor+LightAndDark.swift */; };
|
||||
|
|
@ -222,6 +213,7 @@
|
|||
/* Begin PBXFileReference section */
|
||||
02A29BC776CC7DA5C23560A7 /* Pods-BluefruitPlayground.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BluefruitPlayground.release.xcconfig"; path = "Target Support Files/Pods-BluefruitPlayground/Pods-BluefruitPlayground.release.xcconfig"; sourceTree = "<group>"; };
|
||||
02C55600113CD967F7C0C908 /* Pods-CPX.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CPX.debug.xcconfig"; path = "Target Support Files/Pods-CPX/Pods-CPX.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
2981E3B423C6526C00974EA2 /* Sparky_Gold1.dae */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml.dae; path = Sparky_Gold1.dae; sourceTree = "<group>"; };
|
||||
3348DDE33D89A5959702CAC9 /* Pods-BluefruitPlayground.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BluefruitPlayground.debug.xcconfig"; path = "Target Support Files/Pods-BluefruitPlayground/Pods-BluefruitPlayground.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
41FC984EE1CDB9B54C5084ED /* Pods-CPX.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CPX.release.xcconfig"; path = "Target Support Files/Pods-CPX/Pods-CPX.release.xcconfig"; sourceTree = "<group>"; };
|
||||
8BFE7B5A63DA548F17D0945D /* Pods_BluefruitPlayground.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BluefruitPlayground.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
|
@ -318,11 +310,6 @@
|
|||
A9BED763234FBEAB002FFF53 /* CommandQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandQueue.swift; sourceTree = "<group>"; };
|
||||
A9BED764234FBEAB002FFF53 /* LogHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogHelper.swift; sourceTree = "<group>"; };
|
||||
A9BED766234FBEAB002FFF53 /* Data+ScanValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+ScanValue.swift"; sourceTree = "<group>"; };
|
||||
A9D12E8323E3661800F3C259 /* UIColor+Interpolate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Interpolate.swift"; sourceTree = "<group>"; };
|
||||
A9D12E8523E3C5CF00F3C259 /* LightChartPanelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightChartPanelViewController.swift; sourceTree = "<group>"; };
|
||||
A9D5B20F23E0985600178E1B /* AutoConnectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoConnectViewController.swift; sourceTree = "<group>"; };
|
||||
A9D5B21223E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveLabelIntrinsicSizeFix.swift; sourceTree = "<group>"; };
|
||||
A9D5B21523E3024700178E1B /* BluetoothStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothStatusViewController.swift; sourceTree = "<group>"; };
|
||||
A9DF06112359B0600094327F /* ButtonStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStatusViewController.swift; sourceTree = "<group>"; };
|
||||
A9DF06132359B31C0094327F /* ButtonStatusPanelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStatusPanelViewController.swift; sourceTree = "<group>"; };
|
||||
A9E4C02D2358E6B400B5A493 /* UIColor+LightAndDark.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+LightAndDark.swift"; sourceTree = "<group>"; };
|
||||
|
|
@ -491,7 +478,6 @@
|
|||
children = (
|
||||
A986AB92235942D600DF12C8 /* LightSensorViewController.swift */,
|
||||
A98F1B8A23594BE400C5269E /* LightSensorPanelViewController.swift */,
|
||||
A9D12E8523E3C5CF00F3C259 /* LightChartPanelViewController.swift */,
|
||||
);
|
||||
path = LightSensor;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -500,6 +486,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
A98E4D3123566785005B166D /* cpb.scn */,
|
||||
2981E3B423C6526C00974EA2 /* Sparky_Gold1.dae */,
|
||||
A96AA5BA235F903D004A06BA /* CPB_PcbSurface_Color.png */,
|
||||
);
|
||||
path = models3d;
|
||||
|
|
@ -546,8 +533,6 @@
|
|||
A9BED700234F754B002FFF53 /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A99BA722235000520080D03F /* ScreenFlowManager.swift */,
|
||||
A9BED70C234F76C5002FFF53 /* LocalizationManager.swift */,
|
||||
A9716BA92350128100CB174D /* RssiUI.swift */,
|
||||
A96AA5D2236358A8004A06BA /* UIView+RoundedCorners.swift */,
|
||||
A96AA5D423635A89004A06BA /* NSLayoutConstraint+ChangeMultiplier.swift */,
|
||||
|
|
@ -558,10 +543,11 @@
|
|||
A96AA5C0236050E4004A06BA /* NavigationBarWithScrollAwareRightButton.swift */,
|
||||
A9BED701234F754B002FFF53 /* GradientView.swift */,
|
||||
A96AA5D023634F03004A06BA /* GridView.swift */,
|
||||
A9BED70C234F76C5002FFF53 /* LocalizationManager.swift */,
|
||||
A99BA722235000520080D03F /* ScreenFlowManager.swift */,
|
||||
A9B3569E2365D420002093EE /* PanelInsetView.swift */,
|
||||
A958E77D238B7457006A225A /* NormalBrightnessSliderControl.swift */,
|
||||
A958E77F238B75D9006A225A /* NormalBrightnessColorPickerController.swift */,
|
||||
A9D12E8323E3661800F3C259 /* UIColor+Interpolate.swift */,
|
||||
);
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -633,19 +619,10 @@
|
|||
A9BED766234FBEAB002FFF53 /* Data+ScanValue.swift */,
|
||||
A901EC47237C43C000687BE6 /* Data+LittleEndianTypes.swift */,
|
||||
A9BDEC40237C75C900EDDC46 /* Types+Data.swift */,
|
||||
A9D5B21223E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift */,
|
||||
);
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A9D5B20E23E0983F00178E1B /* AutoConnect */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A9D5B20F23E0985600178E1B /* AutoConnectViewController.swift */,
|
||||
);
|
||||
path = AutoConnect;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A9DF06102359B04C0094327F /* ButtonStatus */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -692,10 +669,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
A9BED716234FBDBA002FFF53 /* StartupViewController.swift */,
|
||||
A9D5B21523E3024700178E1B /* BluetoothStatusViewController.swift */,
|
||||
A9482988237B73B100B6FC13 /* Common */,
|
||||
A9BED703234F75E4002FFF53 /* Tips */,
|
||||
A9D5B20E23E0983F00178E1B /* AutoConnect */,
|
||||
A9EAA18D2350B26B00FA615E /* Scanner */,
|
||||
A9EAA1982351F1DD00FA615E /* Home */,
|
||||
A9EAA19F2352973400FA615E /* Neopixels */,
|
||||
|
|
@ -826,6 +801,7 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2981E3B523C6526C00974EA2 /* Sparky_Gold1.dae in Resources */,
|
||||
A98E4D3223566785005B166D /* cpb.scn in Resources */,
|
||||
A92B9F19234DE4A5002374F0 /* LaunchScreen.storyboard in Resources */,
|
||||
A99BA71F234FFECF0080D03F /* DefaultPreferences.plist in Resources */,
|
||||
|
|
@ -957,7 +933,6 @@
|
|||
A92B9F0D234DE4A2002374F0 /* AppDelegate.swift in Sources */,
|
||||
A96AA5C6236312CE004A06BA /* ToneGeneratorViewController.swift in Sources */,
|
||||
A9BED77D234FBEAB002FFF53 /* Data+ScanValue.swift in Sources */,
|
||||
A9D12E8623E3C5CF00F3C259 /* LightChartPanelViewController.swift in Sources */,
|
||||
A901EC48237C43C000687BE6 /* Data+LittleEndianTypes.swift in Sources */,
|
||||
A980FE6D2357D14400E3A909 /* TipPowerUpViewController.swift in Sources */,
|
||||
A94AC14E2364A82B0062AB11 /* CPBBle.swift in Sources */,
|
||||
|
|
@ -990,7 +965,6 @@
|
|||
A958E782238B7936006A225A /* BlePeripheral+CPBAccelerometer.swift in Sources */,
|
||||
A9DF06122359B0600094327F /* ButtonStatusViewController.swift in Sources */,
|
||||
A96AA5D3236358A8004A06BA /* UIView+RoundedCorners.swift in Sources */,
|
||||
A9D5B21023E0985600178E1B /* AutoConnectViewController.swift in Sources */,
|
||||
A98E4D2823565B76005B166D /* TipWelcomeViewController.swift in Sources */,
|
||||
A9BED70B234F7617002FFF53 /* Config.swift in Sources */,
|
||||
A980FE6F2357D15400E3A909 /* TipDiscoverViewController.swift in Sources */,
|
||||
|
|
@ -1006,7 +980,6 @@
|
|||
A98F1B8B23594BE400C5269E /* LightSensorPanelViewController.swift in Sources */,
|
||||
A96AA5C32362F488004A06BA /* AboutViewController.swift in Sources */,
|
||||
A9A08BAF23A7957700498069 /* BlePeripheral+CPBButtons.swift in Sources */,
|
||||
A9D12E8423E3661800F3C259 /* UIColor+Interpolate.swift in Sources */,
|
||||
A98F1B892359494500C5269E /* ConfigUI.swift in Sources */,
|
||||
A9BED773234FBEAB002FFF53 /* UartPacketManager.swift in Sources */,
|
||||
A986AB93235942D600DF12C8 /* LightSensorViewController.swift in Sources */,
|
||||
|
|
@ -1025,8 +998,6 @@
|
|||
A9EAA1A3235297B400FA615E /* NeopixelsLightSequenceViewController.swift in Sources */,
|
||||
A901EC4D237C47D800687BE6 /* CPBDataSeries.swift in Sources */,
|
||||
A99BA71D234FFE780080D03F /* Settings.swift in Sources */,
|
||||
A9D5B21323E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift in Sources */,
|
||||
A9D5B21623E3024700178E1B /* BluetoothStatusViewController.swift in Sources */,
|
||||
A9BED775234FBEAB002FFF53 /* BlePeripheral+Battery.swift in Sources */,
|
||||
A94AC1482363A7BD0062AB11 /* TemperaturePanelViewController.swift in Sources */,
|
||||
A9EAA1912350B26B00FA615E /* ScannerViewController.swift in Sources */,
|
||||
|
|
@ -1047,7 +1018,6 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A9D12E8723E3C5CF00F3C259 /* LightChartPanelViewController.swift in Sources */,
|
||||
A9A08B3923A523C900498069 /* Int+Bytes.swift in Sources */,
|
||||
A9A08B3A23A523C900498069 /* UIView+AllSubviewsWithClass.swift in Sources */,
|
||||
A9A08BAD23A794F500498069 /* BlePeripheralSimulated+CPBLight.swift in Sources */,
|
||||
|
|
@ -1063,7 +1033,6 @@
|
|||
A9A08B4523A523C900498069 /* Data+ScanValue.swift in Sources */,
|
||||
A9A08B4623A523C900498069 /* Data+LittleEndianTypes.swift in Sources */,
|
||||
A9A08B4723A523C900498069 /* TipPowerUpViewController.swift in Sources */,
|
||||
A9D12E8823E4434000F3C259 /* UIColor+Interpolate.swift in Sources */,
|
||||
A9A08B4823A523C900498069 /* CPBBle.swift in Sources */,
|
||||
A9A08B4923A523C900498069 /* NavigationBarWithScrollAwareRightButton.swift in Sources */,
|
||||
A9A08B4B23A523C900498069 /* UartPacketManagerBase.swift in Sources */,
|
||||
|
|
@ -1081,7 +1050,6 @@
|
|||
A9A08B5723A523C900498069 /* GridView.swift in Sources */,
|
||||
A9A08B5823A523C900498069 /* BlePeripheral+Uart.swift in Sources */,
|
||||
A9A08B5923A523C900498069 /* BlePeripheral+CPBCommon.swift in Sources */,
|
||||
A9D5B21123E0985600178E1B /* AutoConnectViewController.swift in Sources */,
|
||||
A9A08B5A23A523C900498069 /* BlePeripheral+CPBToneGenerator.swift in Sources */,
|
||||
A9A08B5B23A523C900498069 /* RssiUI.swift in Sources */,
|
||||
A9A08B5C23A523C900498069 /* HelpViewController.swift in Sources */,
|
||||
|
|
@ -1098,7 +1066,6 @@
|
|||
A9A08B6823A523C900498069 /* TipWelcomeViewController.swift in Sources */,
|
||||
A9A08BA423A5286700498069 /* BlePeripheralSimulated.swift in Sources */,
|
||||
A9A08B6923A523C900498069 /* Config.swift in Sources */,
|
||||
A9D5B21423E25C1600178E1B /* ActiveLabelIntrinsicSizeFix.swift in Sources */,
|
||||
A9A08B6A23A523C900498069 /* TipDiscoverViewController.swift in Sources */,
|
||||
A9A08B6B23A523C900498069 /* ModulePanelViewController.swift in Sources */,
|
||||
A9A08B6C23A523C900498069 /* TipsViewController.swift in Sources */,
|
||||
|
|
@ -1113,7 +1080,6 @@
|
|||
A9A08B7323A523C900498069 /* BleManager.swift in Sources */,
|
||||
A9A08B7423A523C900498069 /* LightSensorPanelViewController.swift in Sources */,
|
||||
A9A08B7523A523C900498069 /* AboutViewController.swift in Sources */,
|
||||
A9D5B21723E3024800178E1B /* BluetoothStatusViewController.swift in Sources */,
|
||||
A9A08BAA23A7891500498069 /* ObjectBuilder.m in Sources */,
|
||||
A9A08B7623A523C900498069 /* ConfigUI.swift in Sources */,
|
||||
A9A08B7723A523C900498069 /* UartPacketManager.swift in Sources */,
|
||||
|
|
@ -1303,7 +1269,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = 2X94RM7457;
|
||||
INFOPLIST_FILE = BluefruitPlayground/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
|
|
@ -1311,13 +1277,13 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
MARKETING_VERSION = 1.1.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
|
||||
PRODUCT_NAME = "Bluefruit Playground";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "BluefruitPlayground/BluefruitPlayground-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
|
@ -1328,7 +1294,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = 2X94RM7457;
|
||||
INFOPLIST_FILE = BluefruitPlayground/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
|
|
@ -1336,12 +1302,12 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
MARKETING_VERSION = 1.1.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
|
||||
PRODUCT_NAME = "Bluefruit Playground";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "BluefruitPlayground/BluefruitPlayground-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
@ -1392,7 +1358,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = 2X94RM7457;
|
||||
INFOPLIST_FILE = "BluefruitPlayground-SimulatedBluetooth-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
|
|
@ -1400,7 +1366,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
MARKETING_VERSION = 1.1.2;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D SIMULATEBLUETOOTH";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
@ -1418,7 +1384,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = 2X94RM7457;
|
||||
INFOPLIST_FILE = "BluefruitPlayground-SimulatedBluetooth-Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
|
|
@ -1426,7 +1392,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
MARKETING_VERSION = 1.1.2;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D SIMULATEBLUETOOTH";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.adafruit.BluefruitPlayground;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
|
|||
BIN
BluefruitPlayground/.DS_Store
vendored
Normal file
|
|
@ -27,15 +27,8 @@ class BleManager: NSObject {
|
|||
private var centralManagerPoweredOnSemaphore = DispatchSemaphore(value: 1)
|
||||
|
||||
// Scanning
|
||||
var isScanning: Bool {
|
||||
return scanningStartTime != nil
|
||||
}
|
||||
var scanningElapsedTime: TimeInterval? {
|
||||
guard let scanningStartTime = scanningStartTime else { return nil }
|
||||
return CACurrentMediaTime() - scanningStartTime
|
||||
}
|
||||
var isScanning = false
|
||||
private var isScanningWaitingToStart = false
|
||||
internal var scanningStartTime: TimeInterval? // Time when the scanning started. nil if stopped
|
||||
private var scanningServicesFilter: [CBUUID]?
|
||||
internal var peripheralsFound = [UUID: BlePeripheral]()
|
||||
private var peripheralsFoundLock = NSLock()
|
||||
|
|
@ -121,7 +114,7 @@ class BleManager: NSObject {
|
|||
}
|
||||
|
||||
// DLog("start scan")
|
||||
scanningStartTime = CACurrentMediaTime()
|
||||
isScanning = true
|
||||
NotificationCenter.default.post(name: .didStartScanning, object: nil)
|
||||
|
||||
let options = BleManager.kAlwaysAllowDuplicateKeys ? [CBCentralManagerScanOptionAllowDuplicatesKey: true] : nil
|
||||
|
|
@ -132,7 +125,7 @@ class BleManager: NSObject {
|
|||
func stopScan() {
|
||||
// DLog("stop scan")
|
||||
centralManager?.stopScan()
|
||||
scanningStartTime = nil
|
||||
isScanning = false
|
||||
isScanningWaitingToStart = false
|
||||
NotificationCenter.default.post(name: .didStopScanning, object: nil)
|
||||
}
|
||||
|
|
@ -218,19 +211,11 @@ class BleManager: NSObject {
|
|||
}
|
||||
}
|
||||
|
||||
func disconnect(from peripheral: BlePeripheral, waitForQueuedCommands: Bool = false) {
|
||||
guard let centralManager = centralManager else { return}
|
||||
|
||||
func disconnect(from peripheral: BlePeripheral) {
|
||||
|
||||
DLog("disconnect")
|
||||
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
|
||||
if waitForQueuedCommands {
|
||||
// Send the disconnection to the command queue, so all the previous command are executed before disconnecting
|
||||
peripheral.disconnect(centralManager: centralManager)
|
||||
}
|
||||
else {
|
||||
centralManager.cancelPeripheralConnection(peripheral.peripheral)
|
||||
}
|
||||
centralManager?.cancelPeripheralConnection(peripheral.peripheral)
|
||||
}
|
||||
|
||||
func reconnecToPeripherals(withIdentifiers identifiers: [UUID], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
||||
|
|
@ -314,10 +299,7 @@ extension BleManager: CBCentralManagerDelegate {
|
|||
if isScanning {
|
||||
isScanningWaitingToStart = true
|
||||
}
|
||||
scanningStartTime = nil
|
||||
|
||||
// Remove all peripherals found (Important because the BlePeripheral queues could contain old commands that were processing when the bluetooth state changed)
|
||||
peripheralsFound.removeAll()
|
||||
isScanning = false
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(name: .didUpdateBleState, object: nil)
|
||||
|
|
|
|||
|
|
@ -206,12 +206,6 @@ class BlePeripheral: NSObject {
|
|||
let command = BleCommand(type: .discoverDescriptor, parameters: [characteristic], completion: completion)
|
||||
commandQueue.append(command)
|
||||
}
|
||||
|
||||
// MARK: - Connection
|
||||
func disconnect(centralManager: CBCentralManager) {
|
||||
let command = BleCommand(type: .disconnect, parameters: [centralManager], completion: nil)
|
||||
commandQueue.append(command)
|
||||
}
|
||||
|
||||
// MARK: - Service
|
||||
func discoveredService(uuid: CBUUID) -> CBService? {
|
||||
|
|
@ -325,7 +319,6 @@ class BlePeripheral: NSObject {
|
|||
case writeCharacteristic
|
||||
case writeCharacteristicAndWaitNofity
|
||||
case readDescriptor
|
||||
case disconnect
|
||||
}
|
||||
|
||||
enum CommandError: Error {
|
||||
|
|
@ -369,8 +362,6 @@ class BlePeripheral: NSObject {
|
|||
write(with: command)
|
||||
case .readDescriptor:
|
||||
readDescriptor(with: command)
|
||||
case .disconnect:
|
||||
disconnect(with: command)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -500,12 +491,6 @@ class BlePeripheral: NSObject {
|
|||
|
||||
peripheral.readValue(for: descriptor)
|
||||
}
|
||||
|
||||
private func disconnect(with command: BleCommand) {
|
||||
let centralManager = command.parameters!.first as! CBCentralManager
|
||||
centralManager.cancelPeripheralConnection(self.peripheral)
|
||||
finishedExecutingCommand(error: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CBPeripheralDelegate
|
||||
|
|
|
|||
|
|
@ -47,8 +47,7 @@ extension BlePeripheral {
|
|||
// MARK: - Actions
|
||||
func cpbButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
let timePeriod: TimeInterval = 0 // 0 means that the responseHandler will be called only when there is a change
|
||||
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBButtonsServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBButtonsCharacteristicUUID, timePeriod: timePeriod, responseHandler: { response in
|
||||
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBButtonsServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBButtonsCharacteristicUUID, timePeriod: 0, responseHandler: { response in
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
|
|
@ -68,21 +67,7 @@ extension BlePeripheral {
|
|||
}
|
||||
|
||||
self.cpbButtonsCharacteristic = characteristic
|
||||
|
||||
if timePeriod == 0 { // Read initial state if the timePeriod is 0 (update only when changed)
|
||||
CPBBle.shared.buttonsReadState { response in
|
||||
switch response {
|
||||
case .success(_, _):
|
||||
completion?(.success(()))
|
||||
case .failure(let error):
|
||||
DLog("Error receiving initial button state data: \(error)")
|
||||
completion?(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
completion?(.success(()))
|
||||
}
|
||||
completion?(.success(()))
|
||||
|
||||
case let .failure(error):
|
||||
self.cpbButtonsCharacteristic = nil
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ extension BlePeripheral {
|
|||
static let kCPBLightServiceUUID = CBUUID(string: "ADAF0300-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kCPBLightCharacteristicUUID = CBUUID(string: "ADAF0301-C332-42A8-93BD-25E905756CB8")
|
||||
|
||||
private static let kCPBLightDefaultPeriod: TimeInterval = 0.1
|
||||
static let kCPBLightDefaultPeriod: TimeInterval = 0.1
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ extension BlePeripheral {
|
|||
static let kCPBTemperatureServiceUUID = CBUUID(string: "ADAF0100-C332-42A8-93BD-25E905756CB8")
|
||||
private static let kCPBTemperatureCharacteristicUUID = CBUUID(string: "ADAF0101-C332-42A8-93BD-25E905756CB8")
|
||||
|
||||
private static let kCPBTemperatureDefaultPeriod: TimeInterval = 0.1
|
||||
|
||||
// MARK: - Custom properties
|
||||
private struct CustomPropertiesKeys {
|
||||
static var cpbTemperatureCharacteristic: CBCharacteristic?
|
||||
|
|
@ -33,7 +31,7 @@ extension BlePeripheral {
|
|||
// MARK: - Actions
|
||||
func cpbTemperatureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
|
||||
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBTemperatureServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBTemperatureCharacteristicUUID, timePeriod: BlePeripheral.kCPBTemperatureDefaultPeriod, responseHandler: { response in
|
||||
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBTemperatureServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBTemperatureCharacteristicUUID, timePeriod: 0.5, responseHandler: { response in
|
||||
|
||||
switch response {
|
||||
case let .success((data, uuid)):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
// Copyright © 2019 Adafruit. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
import CoreBluetooth
|
||||
|
||||
class BleManagerSimulated: BleManager {
|
||||
|
|
@ -20,7 +20,7 @@ class BleManagerSimulated: BleManager {
|
|||
}
|
||||
|
||||
override func startScan(withServices services: [CBUUID]? = nil) {
|
||||
scanningStartTime = CACurrentMediaTime()
|
||||
isScanning = true
|
||||
|
||||
// Add simulated peripheral
|
||||
let simulatedBlePeripheral = BlePeripheralSimulated()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
|
@ -10,42 +10,38 @@ import UIKit
|
|||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
|
||||
|
||||
startup()
|
||||
|
||||
ScreenFlowManager.enableBleStateManagement()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
|
||||
ScreenFlowManager.disableBleStateManagement()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Startup
|
||||
private func startup() {
|
||||
// Settings
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"red" : "0.949",
|
||||
"red" : "0.010",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.961",
|
||||
"green" : "0.961"
|
||||
"blue" : "0.802",
|
||||
"green" : "0.219"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -12,13 +12,10 @@ struct Config {
|
|||
|
||||
// Debug-----------------------------------------------------------------------------
|
||||
static let isDebugEnabled = _isDebugAssertConfiguration()
|
||||
|
||||
// Fastlane snapshots
|
||||
private static let areFastlaneSnapshotsRunning = UserDefaults.standard.bool(forKey: "FASTLANE_SNAPSHOT")
|
||||
|
||||
// Bluetooth
|
||||
#if SIMULATEBLUETOOTH
|
||||
static let isTutorialEnabled = areFastlaneSnapshotsRunning || !isDebugEnabled
|
||||
static let isTutorialEnabled = !isDebugEnabled
|
||||
static let isBleUnsupportedWarningEnabled = false
|
||||
static let bleManager = BleManagerSimulated.simulated
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@
|
|||
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>This module requires the Camera to function</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>This module reqires library access to store your photos</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
|
|
|
|||
BIN
BluefruitPlayground/Model/.DS_Store
vendored
Normal file
|
|
@ -30,7 +30,8 @@ class CPBBle {
|
|||
// Constants
|
||||
private static let kLightSequenceFramesPerSecond = 10
|
||||
private static let kLightSequenceDefaultBrightness: CGFloat = 0.25
|
||||
public static let kLightSequenceDefaultSpeed: Double = 0.3
|
||||
private static let kLightSequenceDefaultSpeed: Double = 0.3
|
||||
|
||||
// Singleton
|
||||
static let shared = CPBBle()
|
||||
|
||||
|
|
@ -51,30 +52,25 @@ class CPBBle {
|
|||
weak var buttonsDelegate: CPBBleButtonsDelegate?
|
||||
weak var accelerometerDelegate: CPBBleAccelerometerDelegate?
|
||||
|
||||
var neopixelLightSequenceAnimationBrightness: CGFloat = CPBBle.kLightSequenceDefaultBrightness
|
||||
|
||||
var neopixelLightSequenceAnimationSpeed: Double = CPBBle.kLightSequenceDefaultSpeed {
|
||||
|
||||
didSet {
|
||||
lightSequenceAnimation?.speed = neopixelLightSequenceAnimationSpeed
|
||||
}
|
||||
}
|
||||
|
||||
// Data
|
||||
private var temperatureData = CPBDataSeries<Float>()
|
||||
private var lightData = CPBDataSeries<Float>()
|
||||
private var accelerometerData = CPBDataSeries<BlePeripheral.AccelerometerValue>()
|
||||
private weak var blePeripheral: BlePeripheral?
|
||||
|
||||
private var currentLightSequenceAnimation: LightSequenceAnimation?
|
||||
public var neopixelCurrentLightSequenceAnimationSpeed: Double {
|
||||
get {
|
||||
return currentLightSequenceAnimation?.speed ?? 0
|
||||
}
|
||||
|
||||
set {
|
||||
currentLightSequenceAnimation?.speed = newValue
|
||||
}
|
||||
}
|
||||
private var lightSequenceAnimation: LightSequenceAnimation?
|
||||
|
||||
// MARK: - Lifecycle
|
||||
private init() {
|
||||
registerNotifications(enabled: true)
|
||||
}
|
||||
|
||||
deinit {
|
||||
registerNotifications(enabled: false)
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
|
@ -192,11 +188,7 @@ class CPBBle {
|
|||
func accelerometerLastValue() -> BlePeripheral.AccelerometerValue? {
|
||||
return blePeripheral?.cpbAccelerometerLastValue()
|
||||
}
|
||||
|
||||
func lightDataSeries() -> [CPBDataSeries<Float>.Entry] {
|
||||
return lightData.values
|
||||
}
|
||||
|
||||
|
||||
func temperatureDataSeries() -> [CPBDataSeries<Float>.Entry] {
|
||||
return temperatureData.values
|
||||
}
|
||||
|
|
@ -257,9 +249,36 @@ class CPBBle {
|
|||
|
||||
private func receiveButtonsData(response: Result<(BlePeripheral.ButtonsState, UUID), Error>) {
|
||||
switch response {
|
||||
|
||||
|
||||
|
||||
case let .success(buttonsState, uuid):
|
||||
DLog("Buttons: \(buttonsState.slideSwitch == .left ? "⬅️":"➡️") \(buttonsState.buttonA == .pressed ? "🔳":"🔲") \(buttonsState.buttonB == .pressed ? "🔳":"🔲") ")
|
||||
|
||||
// NotificationCenter.default.post(name:NSNotification.Name(rawValue: "Command"), object: nil)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if buttonsState.buttonA == .pressed{
|
||||
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "EmoteOne"), object: nil)
|
||||
}
|
||||
|
||||
if buttonsState.buttonB == .pressed{
|
||||
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "EmoteTwo"), object: nil)
|
||||
}
|
||||
|
||||
if buttonsState.slideSwitch == .right{
|
||||
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "EmoteFour"), object: nil)
|
||||
}
|
||||
|
||||
if buttonsState.slideSwitch == .left{
|
||||
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "EmoteThree"), object: nil)
|
||||
}
|
||||
|
||||
|
||||
// Send to delegate
|
||||
if let buttonsDelegate = buttonsDelegate {
|
||||
DispatchQueue.main.async { // Delegates are called in the main thread
|
||||
|
|
@ -273,9 +292,15 @@ class CPBBle {
|
|||
NotificationUserInfoKey.uuid.rawValue: uuid,
|
||||
])
|
||||
|
||||
|
||||
|
||||
|
||||
case .failure(let error):
|
||||
DLog("Error receiving light data: \(error)")
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private func receiveAccelerometerData(response: Result<(BlePeripheral.AccelerometerValue, UUID), Error>) {
|
||||
|
|
@ -323,26 +348,19 @@ class CPBBle {
|
|||
blePeripheral?.cpbPixelSetColor(index: 0, color: color, pixelMask: pixelMask)
|
||||
}
|
||||
|
||||
func neopixelStartLightSequence(_ lightSequenceGenerator: LightSequenceGenerator,
|
||||
framesPerSecond: Int = CPBBle.kLightSequenceFramesPerSecond,
|
||||
speed: Double = CPBBle.kLightSequenceDefaultSpeed,
|
||||
brightness: CGFloat = CPBBle.kLightSequenceDefaultBrightness,
|
||||
repeating: Bool = true,
|
||||
sendLightSequenceNotifications: Bool = true) {
|
||||
func neopixelStartLightSequence(_ lightSequenceGenerator: LightSequenceGenerator) {
|
||||
neopixelStopLightSequence()
|
||||
|
||||
currentLightSequenceAnimation = LightSequenceAnimation(lightSequenceGenerator: lightSequenceGenerator, framesPerSecond: framesPerSecond, repeating: repeating)
|
||||
currentLightSequenceAnimation!.speed = speed
|
||||
currentLightSequenceAnimation!.start(stopHandler: { [weak self] in
|
||||
self?.blePeripheral?.cpbPixelSetAllPixelsColor(.clear)
|
||||
}) { [weak self] pixelsBytes in
|
||||
lightSequenceAnimation = LightSequenceAnimation(lightSequenceGenerator: lightSequenceGenerator, framesPerSecond: CPBBle.kLightSequenceFramesPerSecond)
|
||||
lightSequenceAnimation!.speed = neopixelLightSequenceAnimationSpeed
|
||||
lightSequenceAnimation!.start() { [weak self] pixelsBytes in
|
||||
guard let self = self else { return }
|
||||
guard let blePeripheral = self.blePeripheral else { return }
|
||||
|
||||
let pixelBytesAdjustingBrightness = pixelsBytes.map {[
|
||||
UInt8(CGFloat($0[0]) * brightness),
|
||||
UInt8(CGFloat($0[1]) * brightness),
|
||||
UInt8(CGFloat($0[2]) * brightness),
|
||||
UInt8(CGFloat($0[0]) * self.neopixelLightSequenceAnimationBrightness),
|
||||
UInt8(CGFloat($0[1]) * self.neopixelLightSequenceAnimationBrightness),
|
||||
UInt8(CGFloat($0[2]) * self.neopixelLightSequenceAnimationBrightness),
|
||||
]}
|
||||
|
||||
let lightData = pixelBytesAdjustingBrightness.reduce(Data()) { (data, element) in
|
||||
|
|
@ -351,35 +369,16 @@ class CPBBle {
|
|||
blePeripheral.cpbPixelsWriteData(offset: 0, pixelData: lightData)
|
||||
|
||||
// Send notification
|
||||
if sendLightSequenceNotifications {
|
||||
NotificationCenter.default.post(name: .didUpdateNeopixelLightSequence, object: nil, userInfo: [
|
||||
NotificationUserInfoKey.value.rawValue: pixelsBytes,
|
||||
NotificationUserInfoKey.uuid.rawValue: blePeripheral.identifier,
|
||||
])
|
||||
}
|
||||
NotificationCenter.default.post(name: .didUpdateNeopixelLightSequence, object: nil, userInfo: [
|
||||
NotificationUserInfoKey.value.rawValue: pixelsBytes,
|
||||
NotificationUserInfoKey.uuid.rawValue: blePeripheral.identifier,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
func neopixelStopLightSequence() {
|
||||
currentLightSequenceAnimation?.stop()
|
||||
currentLightSequenceAnimation = nil
|
||||
}
|
||||
|
||||
// MARK: - BLE Notifications
|
||||
private weak var willdDisconnectFromPeripheralObserver: NSObjectProtocol?
|
||||
|
||||
private func registerNotifications(enabled: Bool) {
|
||||
let notificationCenter = NotificationCenter.default
|
||||
if enabled {
|
||||
willdDisconnectFromPeripheralObserver = notificationCenter.addObserver(forName: .willDisconnectFromPeripheral, object: nil, queue: .main, using: {[weak self] notification in
|
||||
|
||||
// Force clear neopixels on disconnect
|
||||
self?.neopixelSetAllPixelsColor(.clear)
|
||||
})
|
||||
|
||||
} else {
|
||||
if let willdDisconnectFromPeripheralObserver = willdDisconnectFromPeripheralObserver {notificationCenter.removeObserver(willdDisconnectFromPeripheralObserver)}
|
||||
}
|
||||
lightSequenceAnimation?.stop()
|
||||
lightSequenceAnimation = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@
|
|||
import UIKit
|
||||
|
||||
protocol LightSequenceGenerator {
|
||||
var numFrames: Int { get }
|
||||
var numPixels: Int { get }
|
||||
var isCyclic: Bool { get }
|
||||
func numFrames() -> Int
|
||||
func numPixels() -> Int
|
||||
func colorsForFrame(_ frame: Int) -> [[UInt8]]
|
||||
}
|
||||
|
||||
|
|
@ -20,16 +19,12 @@ class LightSequence {
|
|||
// static let kLightSequenceDefaultBrightness: CGFloat = 0.25
|
||||
static let kNumPixels = 10
|
||||
|
||||
var numPixels: Int {
|
||||
func numPixels() -> Int {
|
||||
return LightSequence.kNumPixels
|
||||
}
|
||||
|
||||
var isCyclic: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Utils
|
||||
static func preprocessColorPalette(colors: [UIColor]) -> [[UInt8]] {
|
||||
static func preprocessColorPalette(colors: [UIColor]/*, brightness: CGFloat*/) -> [[UInt8]] {
|
||||
let colorsBytes = colors.map({ color -> [UInt8] in
|
||||
let colorBytes = BlePeripheral.pixelUInt8FromColor(color)
|
||||
return colorBytes
|
||||
|
|
@ -51,16 +46,16 @@ class RotateLightSequence: LightSequence, LightSequenceGenerator {
|
|||
|
||||
// MARK: -
|
||||
override init() {
|
||||
colorsBytes = LightSequence.preprocessColorPalette(colors: RotateLightSequence.kColors)
|
||||
colorsBytes = LightSequence.preprocessColorPalette(colors: RotateLightSequence.kColors/*, brightness: LightSequence.kLightSequenceDefaultBrightness*/)
|
||||
super.init()
|
||||
}
|
||||
|
||||
var numFrames: Int {
|
||||
func numFrames() -> Int {
|
||||
return RotateLightSequence.kNumFrames
|
||||
}
|
||||
|
||||
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
|
||||
return rotate(numPixels: numPixels, colors: colorsBytes, frame: frame)
|
||||
return rotate(numPixels: numPixels(), colors: colorsBytes, frame: frame)
|
||||
}
|
||||
|
||||
private func rotate(numPixels: Int, colors: [[UInt8]], frame: Int) -> [[UInt8]] {
|
||||
|
|
@ -88,22 +83,22 @@ class PulseLightSequence: LightSequence, LightSequenceGenerator {
|
|||
|
||||
// MARK: -
|
||||
override init() {
|
||||
colorsBytes = LightSequence.preprocessColorPalette(colors: PulseLightSequence.kColors)
|
||||
colorsBytes = LightSequence.preprocessColorPalette(colors: PulseLightSequence.kColors/*, brightness: LightSequence.kLightSequenceDefaultBrightness*/)
|
||||
super.init()
|
||||
}
|
||||
|
||||
var numFrames: Int {
|
||||
func numFrames() -> Int {
|
||||
return PulseLightSequence.kNumFrames
|
||||
}
|
||||
|
||||
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
|
||||
return pulse(numPixels: numPixels, colors: colorsBytes, frame: frame)
|
||||
return pulse(numPixels: numPixels(), colors: colorsBytes, frame: frame)
|
||||
}
|
||||
|
||||
private func pulse(numPixels: Int, colors: [[UInt8]], frame: Int) -> [[UInt8]] {
|
||||
var lightBytes = [[UInt8]](repeating: [0, 0, 0], count:numPixels)
|
||||
let reverse = frame >= numFrames / 2
|
||||
let colorIndex = reverse ? (numFrames - 1) - frame : frame
|
||||
let reverse = frame >= numFrames() / 2
|
||||
let colorIndex = reverse ? (numFrames() - 1) - frame : frame
|
||||
for i in 0..<numPixels {
|
||||
//DLog("pixel: \(i) color: \(colorIndex)")
|
||||
let colorBytes = colors[colorIndex]
|
||||
|
|
@ -128,25 +123,25 @@ class SizzleLightSequence: LightSequence, LightSequenceGenerator {
|
|||
|
||||
// MARK: -
|
||||
override init() {
|
||||
colorsBytes = LightSequence.preprocessColorPalette(colors: SizzleLightSequence.kColors)
|
||||
colorsBytes = LightSequence.preprocessColorPalette(colors: SizzleLightSequence.kColors/*, brightness: LightSequence.kLightSequenceDefaultBrightness*/)
|
||||
super.init()
|
||||
}
|
||||
|
||||
var numFrames:Int {
|
||||
func numFrames() -> Int {
|
||||
return SizzleLightSequence.kNumFrames
|
||||
}
|
||||
|
||||
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
|
||||
return sizzle(numPixels: numPixels, colors: colorsBytes, frame: frame)
|
||||
return sizzle(numPixels: numPixels(), colors: colorsBytes, frame: frame)
|
||||
}
|
||||
|
||||
private func sizzle(numPixels: Int, colors: [[UInt8]], frame: Int) -> [[UInt8]] {
|
||||
var lightBytes = [[UInt8]](repeating: [0, 0, 0], count:numPixels)
|
||||
let forwardNumFrames = numFrames / 2
|
||||
let forwardNumFrames = numFrames() / 2
|
||||
let reverse = frame >= forwardNumFrames
|
||||
|
||||
let evenIndex = reverse ? (frame % forwardNumFrames) : (forwardNumFrames - 1) - frame
|
||||
let oddIndex = reverse ? (numFrames - 1) - frame : frame
|
||||
let oddIndex = reverse ? (numFrames() - 1) - frame : frame
|
||||
|
||||
for i in 0..<numPixels {
|
||||
//DLog("pixel: \(i) color: \(colorIndex)")
|
||||
|
|
@ -169,16 +164,16 @@ class SweepLightSequence: LightSequence, LightSequenceGenerator {
|
|||
|
||||
// MARK: -
|
||||
override init() {
|
||||
colorsBytes = LightSequence.preprocessColorPalette(colors: SweepLightSequence.kColors)
|
||||
colorsBytes = LightSequence.preprocessColorPalette(colors: SweepLightSequence.kColors/*, brightness: LightSequence.kLightSequenceDefaultBrightness*/)
|
||||
super.init()
|
||||
}
|
||||
|
||||
var numFrames: Int {
|
||||
func numFrames() -> Int {
|
||||
return SweepLightSequence.kNumFrames
|
||||
}
|
||||
|
||||
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
|
||||
return sweep(numPixels: numPixels, colors: colorsBytes, frame: frame)
|
||||
return sweep(numPixels: numPixels(), colors: colorsBytes, frame: frame)
|
||||
}
|
||||
|
||||
private func sweep(numPixels: Int, colors: [[UInt8]], frame: Int) -> [[UInt8]] {
|
||||
|
|
@ -193,78 +188,3 @@ class SweepLightSequence: LightSequence, LightSequenceGenerator {
|
|||
return lightBytes
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Module Started Animation
|
||||
class FlashLightSequence: LightSequence, LightSequenceGenerator {
|
||||
// Constants
|
||||
private static let kNumFrames = 8 // Default frames per second is 10,
|
||||
|
||||
// Data
|
||||
private var baseColor: UIColor
|
||||
|
||||
// MARK: -
|
||||
init(baseColor: UIColor) {
|
||||
self.baseColor = baseColor
|
||||
super.init()
|
||||
}
|
||||
|
||||
var numFrames: Int {
|
||||
return FlashLightSequence.kNumFrames
|
||||
}
|
||||
|
||||
override var isCyclic: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func colorsForFrame(_ frame: Int) -> [[UInt8]] {
|
||||
return flash(baseColor: baseColor, numPixels: numPixels, frame: frame)
|
||||
}
|
||||
|
||||
private func flash(baseColor: UIColor, numPixels: Int, frame: Int) -> [[UInt8]] {
|
||||
var lightBytes = [[UInt8]](repeating: [0, 0, 0], count:numPixels)
|
||||
|
||||
let factor = CGFloat(frame) / CGFloat(numFrames)
|
||||
|
||||
/*
|
||||
let originWhite: CGFloat
|
||||
let endWhite: CGFloat
|
||||
let fraction: CGFloat
|
||||
if factor < 0.5 {
|
||||
originWhite = 0
|
||||
endWhite = 1
|
||||
fraction = factor * 2
|
||||
}
|
||||
else {
|
||||
originWhite = 1
|
||||
endWhite = 0
|
||||
fraction = (factor - 0.5) * 2
|
||||
}
|
||||
|
||||
let color = UIColor(white: originWhite + (endWhite - originWhite) * fraction, alpha: 1)
|
||||
*/
|
||||
|
||||
let originColor: UIColor
|
||||
let endColor: UIColor
|
||||
let fraction: CGFloat
|
||||
if factor < 0.5 {
|
||||
originColor = .clear
|
||||
endColor = baseColor
|
||||
fraction = factor * 2
|
||||
}
|
||||
else {
|
||||
originColor = baseColor
|
||||
endColor = .clear
|
||||
fraction = (factor - 0.5) * 2
|
||||
}
|
||||
|
||||
let color = originColor.interpolateRGBColorTo(end: endColor, fraction: fraction)
|
||||
//let color = originColor.interpolateHSVColorFrom(end: endColor, fraction: fraction)
|
||||
let colorBytes = BlePeripheral.pixelUInt8FromColor(color)
|
||||
|
||||
for i in 0..<numPixels {
|
||||
lightBytes[i] = colorBytes
|
||||
}
|
||||
|
||||
return lightBytes
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,67 +21,57 @@ class LightSequenceAnimation {
|
|||
|
||||
private var displaylink: CADisplayLink?
|
||||
|
||||
//private var simulatedFrame = 0.0
|
||||
private var startingTimestamp: TimeInterval = 0
|
||||
private var frameHandler: (([[UInt8]])->())?
|
||||
private var stopHandler: (()->())?
|
||||
private var repeating: Bool
|
||||
|
||||
// MARK: -
|
||||
init(lightSequenceGenerator: LightSequenceGenerator, framesPerSecond: Int, repeating: Bool) {
|
||||
init(lightSequenceGenerator: LightSequenceGenerator, framesPerSecond: Int) {
|
||||
self.lightSequenceGenerator = lightSequenceGenerator
|
||||
self.lightSequenceFramesPerSecond = framesPerSecond
|
||||
self.repeating = repeating
|
||||
}
|
||||
|
||||
deinit {
|
||||
stop()
|
||||
}
|
||||
|
||||
func start(stopHandler:(()->())? = nil, frameHandler: @escaping ([[UInt8]])->()) {
|
||||
self.stopHandler = stopHandler
|
||||
func start(frameHandler: @escaping ([[UInt8]])->()) {
|
||||
self.frameHandler = frameHandler
|
||||
|
||||
// Create displayLink if needed
|
||||
if displaylink == nil {
|
||||
displaylink = CADisplayLink(target: self, selector: #selector(displayLinkStep))
|
||||
displaylink!.add(to: .main, forMode: .default)
|
||||
displaylink!.add(to: .current, forMode: .default)
|
||||
}
|
||||
|
||||
guard let displaylink = displaylink else { return }
|
||||
displaylink.preferredFramesPerSecond = lightSequenceFramesPerSecond
|
||||
startingTimestamp = CACurrentMediaTime()
|
||||
startingTimestamp = displaylink.timestamp
|
||||
}
|
||||
|
||||
func stop() {
|
||||
displaylink?.invalidate()
|
||||
displaylink = nil
|
||||
displaylink?.remove(from: .main, forMode: .default)
|
||||
stopHandler?()
|
||||
}
|
||||
|
||||
@objc func displayLinkStep(displaylink: CADisplayLink) {
|
||||
|
||||
let currentTimestamp = CACurrentMediaTime() - startingTimestamp
|
||||
let numFrames = Double(lightSequenceGenerator.numFrames)
|
||||
|
||||
guard repeating || currentTimestamp * numFrames * speed < numFrames else { // Stop if repeating == false and has displayed all frames
|
||||
stop()
|
||||
return
|
||||
}
|
||||
|
||||
//let fps = Double(currentLightSequenceFramesPerSecond)
|
||||
let currentTimestamp = displaylink.timestamp - startingTimestamp
|
||||
let numFrames = Double(lightSequenceGenerator.numFrames())
|
||||
let frame = (currentTimestamp * numFrames * speed).truncatingRemainder(dividingBy:numFrames)
|
||||
//let frame = simulatedFrame.truncatingRemainder(dividingBy:numFrames)
|
||||
//simulatedFrame += 0.4
|
||||
|
||||
//DLog("frame: \(frame)")
|
||||
|
||||
var pixelsBytes: [[UInt8]]
|
||||
if LightSequenceAnimation.kIsFrameInterpolationEnabled {
|
||||
let preFrame = Int(floor(frame))
|
||||
let postFrame = lightSequenceGenerator.isCyclic ? Int(ceil(frame)) % lightSequenceGenerator.numFrames : min(Int(ceil(frame)), lightSequenceGenerator.numFrames)
|
||||
let postFrame = Int(ceil(frame)) % lightSequenceGenerator.numFrames()
|
||||
let postFactor = frame - Double(preFrame)
|
||||
let preFactor = 1 - postFactor
|
||||
|
||||
//DLog("pre: \(preFrame), post: \(postFrame), frame: \(frame)")
|
||||
|
||||
|
||||
let pixelsBytesPre = lightSequenceGenerator.colorsForFrame(preFrame)
|
||||
let pixelsBytesPost = lightSequenceGenerator.colorsForFrame(postFrame)
|
||||
|
||||
|
|
|
|||
BIN
BluefruitPlayground/Resources/.DS_Store
vendored
Normal file
|
|
@ -6,11 +6,22 @@
|
|||
// Startup
|
||||
"startup_bluetooth_unsupported" = "This device doesn't support Bluetooth Low Energy which is needed to connect to the Circuit Playground Bluefruit device";
|
||||
|
||||
// Bluetooth Management
|
||||
|
||||
// Splash
|
||||
"splash_restoringconnection" = "Restoring connection...";
|
||||
|
||||
"bluetooth_unsupported" = "This device doesn't support Bluetooth Low Energy";
|
||||
"bluetooth_notauthorized" = "This app is not authorized to use the Bluetooth Low Energy";
|
||||
"bluetooth_poweredoff" = "Bluetooth is currently powered off";
|
||||
|
||||
"bluetooth_locationpermission_disabled_text" = "Location Services should be enabled to scan bluetooth peripherals.
|
||||
Please, go to settings and enable it to start scanning peripherals";
|
||||
"bluetooth_locationpermission_title" = "This app needs location access";
|
||||
"bluetooth_locationpermission_text" = "Please grant location access so this app can scan for Bluetooth peripherals";
|
||||
"bluetooth_locationpermission_notavailable_title" = "Bluetooth Scanning not available";
|
||||
"bluetooth_locationpermission_notavailable_text" = "Since location access has not been granted, the app will not be able to scan for Bluetooth peripherals";
|
||||
"bluetooth_scanner_errorregisteringapp" = "App cannot be registered for bluetooth scanning.
|
||||
Please reset bluetooth and try again.";
|
||||
"bluetooth_connecting_error" = "Error connecting to peripheral";
|
||||
"bluetooth_advertising_start_error" = "Error starting advertising";
|
||||
|
||||
// Welcome
|
||||
"tip0_title" = "Welcome";
|
||||
|
|
@ -25,7 +36,7 @@ Circuit Playground Bluefruit is available at the Adafruit shop.";
|
|||
"tip1_link_url" = "https://learn.adafruit.com/bluefruit-playground-app/firmware";
|
||||
"tip1_action" = "Next";
|
||||
"tip2_title" = "Discover";
|
||||
"tip2_detail" = "Once your Circuit Playground Bluefruit is powered, move your iPhone towards your Circuit Playground Bluefruit";
|
||||
"tip2_detail" = "Once Circuit Playground Bluefruit is powered, place it near your iPhone and tap the button below.";
|
||||
"tip2_action" = "Begin pairing";
|
||||
|
||||
|
||||
|
|
@ -61,26 +72,9 @@ https://github.com/mindsnacks/MSWeakTimer";
|
|||
"about_ios_link1_text" = "uf2 firmware file";
|
||||
"about_ios_link1_url" = "https://learn.adafruit.com/bluefruit-playground-app/firmware";
|
||||
|
||||
// Bluetooth Status
|
||||
|
||||
"bluetooth_unsupported" = "This device doesn't support Bluetooth";
|
||||
"bluetooth_unsupported_detail" = "Bluetooth support and specifically Bluetooth Low Energy support is needed to communicate with a Circuit Playground Device";
|
||||
"bluetooth_unsupported_le" = "This device doesn't support Bluetooth Low Energy";
|
||||
"bluetooth_unsupported_le_detail" = "Bluetooth Low Energy support is needed to communicate with a Circuit Playground Device";
|
||||
"bluetooth_notauthorized" = "This app is not authorized to use the Bluetooth Low Energy";
|
||||
"bluetooth_notauthorized_detail" = "Bluetooth permission should be granted or Bluefruit Playground to connect to a Circuit Playground Device";
|
||||
"bluetooth_poweredoff" = "Bluetooth is currently powered off";
|
||||
"bluetooth_poweredoff_detail" = "Bluetooth should be enabled on your device for Bluefruit Playground to communicate with a Circuit Playground Device";
|
||||
|
||||
"bluetooth_enable_action" = "Bluetooth permissions";
|
||||
|
||||
// Autoconnect
|
||||
"autoconnect_title" = "Finding CPB";
|
||||
"autoconnect_manual_action" = "Select device manually";
|
||||
"autoconnect_backbutton" = "Autoconnect";
|
||||
|
||||
// Scanner
|
||||
"scanner_title" = "Select CPB";
|
||||
"scanner_title" = "Finding CPB";
|
||||
|
||||
"scanner_connecting" = "Connecting...";
|
||||
"scanner_discoveringservices" = "Discovering services...";
|
||||
|
|
@ -91,7 +85,6 @@ https://github.com/mindsnacks/MSWeakTimer";
|
|||
"scanner_subtitle" = "Select a Circuit Playground Bluefruit device to connect to:";
|
||||
"scanner_unnamed" = "<Unknown>";
|
||||
|
||||
"scanner_automatic_action" = "Select device automatically";
|
||||
"scanner_problems_action" = "I can't find my device";
|
||||
"scanner_backbutton" = "Disconnect";
|
||||
|
||||
|
|
@ -115,9 +108,9 @@ Download it from the Bluefruit Playground guide at learn.adafruit.com";
|
|||
|
||||
// Modules
|
||||
"modules_title" = "Modules";
|
||||
"modules_subtitle" = "Each module interacts with a type of sensor on the Circuit Playground Bluefruit device";
|
||||
"modules_subtitle" = "Each module interacts with a specific type of sensor on the Circuit Playground Bluefruit device";
|
||||
|
||||
"modules_color_title" = "NeoPixels";
|
||||
"modules_color_title" = "Neopixels";
|
||||
"modules_color_subtitle" = "Control LED color & animation";
|
||||
"modules_light_title" = "Light Sensor";
|
||||
"modules_light_subtitle" = "View continuous light sensor readings";
|
||||
|
|
@ -129,18 +122,16 @@ Download it from the Bluefruit Playground guide at learn.adafruit.com";
|
|||
"modules_accelerometer_subtitle" = "View orientation based on accelerometer data";
|
||||
"modules_temperature_title" = "Temperature";
|
||||
"modules_temperature_subtitle" = "View current temperature readings";
|
||||
"modules_disconnect_title" = "Disconnect";
|
||||
"modules_disconnect_subtitle" = "Close the current connection and scan for a new CPB device";
|
||||
|
||||
// Neopixels
|
||||
"neopixels_title" = "NeoPixels";
|
||||
"neopixels_help" = "NeoPixels mode allows you to control the color of CPB’s built-in NeoPixel LEDs individually, as a group, or by playing preset animations.
|
||||
"neopixels_title" = "Neopixels";
|
||||
"neopixels_help" = "Neopixel mode allows you to control the color of 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_speed" = "Speed";
|
||||
|
|
@ -160,7 +151,6 @@ Download it from the Bluefruit Playground guide at learn.adafruit.com";
|
|||
|
||||
"lightsensor_panel_title" = "Luminance Reading";
|
||||
"lightsensor_panel_unit" = "Light level";
|
||||
"lightsensor_chartpanel_title" = "Luminance Chart";
|
||||
|
||||
// Button Status
|
||||
"buttonstatus_title" = "Button Status";
|
||||
|
|
@ -212,7 +202,7 @@ Download it from the Bluefruit Playground guide at learn.adafruit.com";
|
|||
"temperature_units_celsius" = "ºC";
|
||||
"temperature_units_farenheit" = "ºF";
|
||||
|
||||
"temperature_panel_title" = "Temperature Chart";
|
||||
"temperature_panel_title" = "Temperature Reading";
|
||||
"temperature_chart_nodata" = "No chart data available";
|
||||
|
||||
// Help
|
||||
|
|
|
|||
334
BluefruitPlayground/Resources/models3d/Sparky_Gold1.dae
Normal file
|
|
@ -16,22 +16,20 @@ import UIKit
|
|||
class NavigationBarWithScrollAwareRightButton : UINavigationBar {
|
||||
// Constants
|
||||
private static let kButtonTag = 100
|
||||
|
||||
// Config (navbar metrics)
|
||||
static let navBarHeightSmallState: CGFloat = 44
|
||||
static let navBarHeightLargeState: CGFloat = 96.5
|
||||
|
||||
private static let rightMargin: CGFloat = 6//8
|
||||
|
||||
private static let bottomMarginForLargeState: CGFloat = 6//9
|
||||
private static let bottomMarginForSmallState: CGFloat = 2
|
||||
|
||||
public struct CustomButtonMetrics {
|
||||
static let rightMargin: CGFloat = 8//12
|
||||
|
||||
static let bottomMarginForLargeState: CGFloat = 9
|
||||
static let bottomMarginForSmallState: CGFloat = 2
|
||||
|
||||
static let navBarHeightSmallState: CGFloat = 44
|
||||
static let navBarHeightLargeState: CGFloat = 96.5
|
||||
}
|
||||
|
||||
// Data
|
||||
private var navigationButton: UIButton?// = UIButton(type: .custom)
|
||||
private var topViewController: UIViewController?
|
||||
|
||||
|
||||
|
||||
// MARK: -
|
||||
public func setRightButton(topViewController: UIViewController, image: UIImage?, target: Any?, action: Selector) {
|
||||
navigationButton?.removeFromSuperview()
|
||||
|
|
@ -49,8 +47,8 @@ class NavigationBarWithScrollAwareRightButton : UINavigationBar {
|
|||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
|
||||
button.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -NavigationBarWithScrollAwareRightButton.rightMargin),
|
||||
button.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -NavigationBarWithScrollAwareRightButton.bottomMarginForLargeState),
|
||||
button.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -CustomButtonMetrics.rightMargin),
|
||||
button.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -CustomButtonMetrics.bottomMarginForLargeState),
|
||||
])
|
||||
|
||||
self.layoutIfNeeded() // Force layout to remove adding animation
|
||||
|
|
@ -60,15 +58,22 @@ class NavigationBarWithScrollAwareRightButton : UINavigationBar {
|
|||
// Detect topViewControllerChange
|
||||
self.topViewController = topViewController
|
||||
topViewController.navigationController?.delegate = self
|
||||
|
||||
|
||||
/*
|
||||
// Detect content scrollview scroll
|
||||
if let contentScrollView = topViewController.view as? UIScrollView ?? topViewController.view.subviews.first as? UIScrollView {
|
||||
|
||||
contentScrollView.delegate = self
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public var navigationBarScrollViewProgress: CGFloat {
|
||||
return (self.frame.height - NavigationBarWithScrollAwareRightButton.navBarHeightSmallState) / (NavigationBarWithScrollAwareRightButton.navBarHeightLargeState - NavigationBarWithScrollAwareRightButton.navBarHeightSmallState)
|
||||
return (self.frame.height - CustomButtonMetrics.navBarHeightSmallState) / (CustomButtonMetrics.navBarHeightLargeState - CustomButtonMetrics.navBarHeightSmallState)
|
||||
}
|
||||
|
||||
public func updateRightButtonPosition() {
|
||||
let yDelta = NavigationBarWithScrollAwareRightButton.bottomMarginForLargeState - NavigationBarWithScrollAwareRightButton.bottomMarginForSmallState
|
||||
let yDelta = CustomButtonMetrics.bottomMarginForLargeState - CustomButtonMetrics.bottomMarginForSmallState
|
||||
|
||||
let y = yDelta * max(0, (1-navigationBarScrollViewProgress))
|
||||
navigationButton?.transform = CGAffineTransform(translationX: 0, y: y)
|
||||
|
|
|
|||
|
|
@ -10,68 +10,33 @@ import UIKit
|
|||
|
||||
struct ScreenFlowManager {
|
||||
|
||||
// Data
|
||||
private static var wasManualScanningLastUsed = false // Last scanning method used
|
||||
|
||||
// MARK: - Go to app areas
|
||||
public static func gotoAutoconnect() {
|
||||
let bluetoothErrorDisplayed = updateStatusViewControllerIfBluetoothStateIsNotReady()
|
||||
guard !bluetoothErrorDisplayed else { return }
|
||||
|
||||
guard let window = UIApplication.shared.keyWindow else { return }
|
||||
let isAlreadyInAutoconnect = (window.rootViewController as? UINavigationController)?.topViewController is AutoConnectViewController
|
||||
guard !isAlreadyInAutoconnect else { return }
|
||||
|
||||
let isInManualScan = (window.rootViewController as? UINavigationController)?.topViewController is ScannerViewController
|
||||
let transition: UIView.AnimationOptions = isInManualScan ? .transitionFlipFromLeft : .transitionCrossDissolve
|
||||
|
||||
public static func gotoScanner() {
|
||||
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: AutoConnectViewController.kNavigationControllerIdentifier)
|
||||
changeRootViewController(rootViewController: rootNavigationViewController, animationOptions: transition)
|
||||
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: ScannerViewController.kIdentifier)
|
||||
ScreenFlowManager.changeRootViewController(rootViewController: rootNavigationViewController, animated: false)
|
||||
}
|
||||
|
||||
public static func goToManualScan() {
|
||||
let bluetoothErrorDisplayed = updateStatusViewControllerIfBluetoothStateIsNotReady()
|
||||
guard !bluetoothErrorDisplayed else { return }
|
||||
|
||||
guard let window = UIApplication.shared.keyWindow else { return }
|
||||
let isAlreadyInManualScan = (window.rootViewController as? UINavigationController)?.topViewController is ScannerViewController
|
||||
guard !isAlreadyInManualScan else { return }
|
||||
|
||||
public static func restoreAndGoToHome() {
|
||||
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: ScannerViewController.kNavigationControllerIdentifier)
|
||||
changeRootViewController(rootViewController: rootNavigationViewController, animationOptions: .transitionFlipFromRight)
|
||||
|
||||
// Add home to scanner
|
||||
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: ScannerViewController.kIdentifier) as! UINavigationController
|
||||
let homeViewController = mainStoryboard.instantiateViewController(withIdentifier: HomeViewController.kIdentifier)
|
||||
rootNavigationViewController.viewControllers.append(homeViewController)
|
||||
|
||||
ScreenFlowManager.changeRootViewController(rootViewController: rootNavigationViewController, animated: false)
|
||||
}
|
||||
|
||||
public static func restoreAndGoToCPBModules() {
|
||||
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
|
||||
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: HomeViewController.kNavigationControllerIdentifier)
|
||||
|
||||
changeRootViewController(rootViewController: rootNavigationViewController) {
|
||||
CPBBle.shared.neopixelStartLightSequence(FlashLightSequence(baseColor: .lightGray), speed: 1, repeating: false, sendLightSequenceNotifications: false)
|
||||
}
|
||||
}
|
||||
|
||||
public static func gotoCPBModules() {
|
||||
guard let window = UIApplication.shared.keyWindow else { return }
|
||||
ScreenFlowManager.wasManualScanningLastUsed = (window.rootViewController as? UINavigationController)?.topViewController is ScannerViewController
|
||||
|
||||
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: HomeViewController.kNavigationControllerIdentifier)
|
||||
changeRootViewController(rootViewController: rootNavigationViewController) {
|
||||
CPBBle.shared.neopixelStartLightSequence(FlashLightSequence(baseColor: .white), speed: 1, repeating: false, sendLightSequenceNotifications: false)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Change Root View Controller
|
||||
public static func changeRootViewController(rootViewController: UIViewController, animationOptions: UIView.AnimationOptions? = [.transitionCrossDissolve], completionHandler: (() -> Void)? = nil ) {
|
||||
public static func changeRootViewController(rootViewController: UIViewController, animated: Bool = true, completionHandler: (() -> Void)? = nil ) {
|
||||
guard let window = UIApplication.shared.keyWindow else { return }
|
||||
|
||||
if let animationOptions = animationOptions {
|
||||
window.rootViewController = rootViewController
|
||||
// rootViewController.view.layoutIfNeeded()
|
||||
UIView.transition(with: window, duration: 0.3, options: animationOptions, animations: {
|
||||
|
||||
if animated {
|
||||
rootViewController.view.layoutIfNeeded()
|
||||
UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: {
|
||||
window.rootViewController = rootViewController
|
||||
}, completion: { completed in
|
||||
completionHandler?()
|
||||
})
|
||||
|
|
@ -81,92 +46,4 @@ struct ScreenFlowManager {
|
|||
completionHandler?()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Monitor Bluetooth State and automatically change rootViewController
|
||||
private static weak var didDisconnectFromPeripheralObserver: NSObjectProtocol?
|
||||
private static weak var didUpdateBleStateObserver: NSObjectProtocol?
|
||||
|
||||
public static func enableBleStateManagement() {
|
||||
let notificationCenter = NotificationCenter.default
|
||||
|
||||
if didDisconnectFromPeripheralObserver == nil {
|
||||
|
||||
didDisconnectFromPeripheralObserver = notificationCenter.addObserver(forName: .didDisconnectFromPeripheral, object: nil, queue: .main, using: { _ in
|
||||
guard let window = UIApplication.shared.keyWindow else { return }
|
||||
|
||||
// Don't show on startup
|
||||
let topViewController = (window.rootViewController as? UINavigationController)?.topViewController ?? window.rootViewController
|
||||
guard !(topViewController is StartupViewController) else { return }
|
||||
|
||||
// Show disconnection alert
|
||||
let localizationManager = LocalizationManager.shared
|
||||
let alertController = UIAlertController(title: nil, message: localizationManager.localizedString("scanner_peripheraldisconnected"), preferredStyle: .alert)
|
||||
let okAction = UIAlertAction(title: localizationManager.localizedString("dialog_ok"), style: .default) { _ in
|
||||
/*
|
||||
let isInManualScan = (window.rootViewController as? UINavigationController)?.topViewController is ScannerViewController
|
||||
if !isInManualScan { // If it is manual scanning, maintain that screen
|
||||
gotoAutoconnect()
|
||||
}*/
|
||||
if ScreenFlowManager.wasManualScanningLastUsed {
|
||||
goToManualScan()
|
||||
}
|
||||
else {
|
||||
gotoAutoconnect()
|
||||
}
|
||||
}
|
||||
alertController.addAction(okAction)
|
||||
window.rootViewController?.present(alertController, animated: true, completion: nil)
|
||||
})
|
||||
}
|
||||
|
||||
if didUpdateBleStateObserver == nil {
|
||||
didUpdateBleStateObserver = notificationCenter.addObserver(forName: .didUpdateBleState, object: nil, queue: .main) { _ in
|
||||
guard let window = UIApplication.shared.keyWindow else { return }
|
||||
|
||||
let topViewController = (window.rootViewController as? UINavigationController)?.topViewController ?? window.rootViewController
|
||||
let isAllowedToShowBluetoothDialogBasedOnCurrentRootViewController = !(topViewController is TipsViewController) // Don't show bluetooth errroes while showing tips
|
||||
guard isAllowedToShowBluetoothDialogBasedOnCurrentRootViewController else { return }
|
||||
|
||||
let _ = updateStatusViewControllerIfBluetoothStateIsNotReady()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func disableBleStateManagement() {
|
||||
let notificationCenter = NotificationCenter.default
|
||||
if let didDisconnectFromPeripheralObserver = didDisconnectFromPeripheralObserver {notificationCenter.removeObserver(didDisconnectFromPeripheralObserver)
|
||||
self.didDisconnectFromPeripheralObserver = nil
|
||||
}
|
||||
if let didUpdateBleStateObserver = didUpdateBleStateObserver {notificationCenter.removeObserver(didUpdateBleStateObserver)
|
||||
self.didUpdateBleStateObserver = nil
|
||||
}
|
||||
}
|
||||
|
||||
private static func updateStatusViewControllerIfBluetoothStateIsNotReady() -> Bool { // Returns true if the rootViewController was changed to BluetoothStatusViewController
|
||||
guard let window = UIApplication.shared.keyWindow else { return false }
|
||||
|
||||
let bluetoothState = Config.bleManager.state
|
||||
let shouldShowBluetoothDialog = bluetoothState == .poweredOff || bluetoothState == .unsupported || bluetoothState == .unauthorized
|
||||
|
||||
let topViewController = (window.rootViewController as? UINavigationController)?.topViewController ?? window.rootViewController
|
||||
let isShowingEnableBluetoothDialog = topViewController is BluetoothStatusViewController
|
||||
|
||||
var viewControllerIdentifier: String? = nil
|
||||
if shouldShowBluetoothDialog && !isShowingEnableBluetoothDialog {
|
||||
viewControllerIdentifier = BluetoothStatusViewController.kIdentifier
|
||||
}
|
||||
else if !shouldShowBluetoothDialog && isShowingEnableBluetoothDialog {
|
||||
viewControllerIdentifier = AutoConnectViewController.kNavigationControllerIdentifier
|
||||
}
|
||||
|
||||
if let viewControllerIdentifier = viewControllerIdentifier {
|
||||
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
let rootNavigationViewController = mainStoryboard.instantiateViewController(withIdentifier: viewControllerIdentifier)
|
||||
changeRootViewController(rootViewController: rootNavigationViewController)
|
||||
return true
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 accelerometerEulerZLabel: UILabel!
|
||||
|
||||
|
||||
private var currentState = BlePeripheral.ButtonsState(slideSwitch: .left, buttonA: .released, buttonB: .released)
|
||||
|
||||
// MARK: - Lifecycle
|
||||
override func viewDidLoad() {
|
||||
|
|
@ -37,6 +37,16 @@ class AccelerometerPanelViewController: ModulePanelViewController {
|
|||
accelerometerTitleLabel.text = localizationManager.localizedString("accelerometer_panel_accelerometer_title")
|
||||
|
||||
accelerometerEulerAnglesTitleLabel.text = localizationManager.localizedString("accelerometer_panel_eulerangles_title")
|
||||
|
||||
|
||||
// // Read initial state
|
||||
// CPBBle.shared.buttonsReadState { [weak self] response in
|
||||
// switch response {
|
||||
// case let .success(buttonsStatus, _):
|
||||
// buttonsStateReceived(buttonsStatus)
|
||||
// case .failure(let error):
|
||||
// DLog("Error receiving temperature data: \(error)")
|
||||
// }
|
||||
}
|
||||
|
||||
func accelerationReceived(
|
||||
|
|
@ -55,4 +65,22 @@ class AccelerometerPanelViewController: ModulePanelViewController {
|
|||
accelerometerEulerZLabel.text = String(format: "%.0f", zDeg)
|
||||
|
||||
}
|
||||
|
||||
// // MARK: - Data
|
||||
// func buttonsStateReceived(_ buttonsState: BlePeripheral.ButtonsState) {
|
||||
//
|
||||
//
|
||||
//
|
||||
// if buttonsState.buttonA != currentState.buttonA {
|
||||
// // animateState(view: buttonAStatusView, isPressed: buttonsState.buttonA == .pressed)
|
||||
// }
|
||||
//
|
||||
// if buttonsState.buttonB != currentState.buttonB {
|
||||
// // animateState(view: buttonBStatusView, isPressed: buttonsState.buttonB == .pressed)
|
||||
// }
|
||||
//
|
||||
// currentState = buttonsState
|
||||
// }
|
||||
//
|
||||
//}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,31 +8,315 @@
|
|||
|
||||
import UIKit
|
||||
import SceneKit
|
||||
import ReplayKit
|
||||
import AVFoundation
|
||||
|
||||
struct LowPassFilterSignal {
|
||||
/// Current signal value
|
||||
var value: Float
|
||||
|
||||
/// A scaling factor in the range 0.0..<1.0 that determines
|
||||
/// how resistant the value is to change
|
||||
let filterFactor: Float
|
||||
|
||||
/// Update the value, using filterFactor to attenuate changes
|
||||
mutating func update(newValue: Float) {
|
||||
value = filterFactor * value + (1.0 - filterFactor) * newValue
|
||||
}
|
||||
}
|
||||
|
||||
class AccelerometerViewController: ModuleViewController {
|
||||
// Constants
|
||||
static let kIdentifier = "AccelerometerViewController"
|
||||
|
||||
|
||||
//For Recording Function
|
||||
var recordButtonWasSelected: Bool = false
|
||||
var preferStatusBarHidden: Bool!
|
||||
|
||||
|
||||
// UI
|
||||
@IBOutlet weak var sceneView: SCNView!
|
||||
|
||||
|
||||
@IBOutlet var mainCamera: UIView!
|
||||
|
||||
@IBOutlet var rotateCameraRef: UIButton!
|
||||
|
||||
@IBOutlet var backgroundSwapRef: UIButton!
|
||||
|
||||
@IBOutlet var recordButton: UIButton!
|
||||
|
||||
let recorder = RPScreenRecorder.shared()
|
||||
|
||||
@IBAction func backgroundSwapAction(_ sender: Any) {
|
||||
|
||||
backgroundCameraIsActive.toggle()
|
||||
|
||||
if backgroundCameraIsActive{
|
||||
mainCamera.alpha = 0
|
||||
}else{
|
||||
mainCamera.alpha = 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@IBAction func rotateCamAction(_ sender: Any) {
|
||||
|
||||
print("Button Rotation Pressed")
|
||||
guard let currentCameraInput : AVCaptureInput = captureSession?.inputs.first else {
|
||||
return
|
||||
}
|
||||
if let input = currentCameraInput as? AVCaptureDeviceInput
|
||||
{
|
||||
if input.device.position == .back{
|
||||
|
||||
switchToFrontCamera()
|
||||
}
|
||||
|
||||
if input.device.position == .front{
|
||||
|
||||
switchToBackCamera()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@IBAction func recordAction(_ sender: Any) {
|
||||
print("Is Recording...")
|
||||
recordButtonWasSelected = !recordButtonWasSelected
|
||||
recordUpdater()
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Recording Functions
|
||||
func recordUpdater() {
|
||||
|
||||
if recordButtonWasSelected {
|
||||
print("Currently Recording...")
|
||||
recording()
|
||||
// recordButton.backgroundColor = UIColor.red
|
||||
|
||||
} else {
|
||||
// recordButton.backgroundColor = UIColor.white
|
||||
print("Stopped/Not Recording.")
|
||||
stopRecording()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func recording() {
|
||||
|
||||
|
||||
|
||||
RPScreenRecorder.shared().isMicrophoneEnabled = true;
|
||||
|
||||
recordButton.isUserInteractionEnabled = true
|
||||
recordButton.isEnabled = true
|
||||
|
||||
let pulse1 = CASpringAnimation(keyPath: "transform.scale")
|
||||
pulse1.duration = 0.6
|
||||
pulse1.fromValue = 1.0
|
||||
pulse1.toValue = 1.20
|
||||
pulse1.autoreverses = true
|
||||
pulse1.repeatCount = 1
|
||||
pulse1.initialVelocity = 0.8
|
||||
pulse1.damping = 0.8
|
||||
|
||||
let animationGroup = CAAnimationGroup()
|
||||
animationGroup.duration = 2.7
|
||||
animationGroup.repeatCount = 1000
|
||||
animationGroup.animations = [pulse1]
|
||||
|
||||
recordButton.layer.add(animationGroup, forKey: "pulse")
|
||||
|
||||
|
||||
recorder.startRecording { (error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//Works for iPhone
|
||||
func stopRecording() {
|
||||
|
||||
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.phone {
|
||||
recordButton.layer.removeAllAnimations()
|
||||
|
||||
recorder.stopRecording { (previewVC, error) in
|
||||
|
||||
if let previewVC = previewVC{
|
||||
previewVC.previewControllerDelegate = self
|
||||
self.present(previewVC, animated: true, completion: nil)
|
||||
|
||||
}
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad {
|
||||
|
||||
recordButton.layer.removeAllAnimations()
|
||||
|
||||
let recorder = RPScreenRecorder.shared()
|
||||
|
||||
recorder.stopRecording { (previewVC, error) in
|
||||
|
||||
if let previewVC = previewVC {
|
||||
|
||||
previewVC.previewControllerDelegate = self as RPPreviewViewControllerDelegate
|
||||
|
||||
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad {
|
||||
previewVC.modalPresentationStyle = UIModalPresentationStyle.popover
|
||||
previewVC.popoverPresentationController?.sourceRect = self.recordButton.frame //position popover relative to record button - NEEDS TESTING
|
||||
previewVC.popoverPresentationController?.sourceView = self.view
|
||||
//Show Preview
|
||||
self.present(previewVC, animated: true)
|
||||
}
|
||||
|
||||
else {
|
||||
//Set boundaries safe for iPhone X - NEEDS TESTING
|
||||
let safeArea = self.view.safeAreaInsets
|
||||
let safeAreaHeight = self.view.frame.height - safeArea.top
|
||||
let safeAreaWidth = self.view.frame.width - (safeArea.left + safeArea.right)
|
||||
let scaleX = safeAreaWidth / self.view.frame.width
|
||||
let scaleY = safeAreaHeight / self.view.frame.height
|
||||
let scale = min(scaleX, scaleY)
|
||||
previewVC.view.transform = CGAffineTransform(scaleX: scale, y: scale)
|
||||
//Show Preview
|
||||
self.present(previewVC, animated: true) {
|
||||
previewVC.view.frame.origin.x += safeArea.left
|
||||
previewVC.view.frame.origin.y += safeArea.top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//Background
|
||||
var backgroundCameraIsActive = false
|
||||
|
||||
// Camera Data
|
||||
var captureSession: AVCaptureSession?
|
||||
|
||||
var videoPreviewLayer : AVCaptureVideoPreviewLayer?
|
||||
|
||||
var frontCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
|
||||
|
||||
var backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func switchToFrontCamera(){
|
||||
|
||||
print("Front Cam")
|
||||
|
||||
if frontCamera?.isConnected == true {
|
||||
captureSession?.stopRunning()
|
||||
let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
|
||||
do {
|
||||
let input = try AVCaptureDeviceInput(device: captureDevice!)
|
||||
captureSession = AVCaptureSession()
|
||||
captureSession?.addInput(input)
|
||||
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
|
||||
videoPreviewLayer?.frame = view.layer.bounds
|
||||
mainCamera.layer.addSublayer(videoPreviewLayer!)
|
||||
captureSession?.startRunning()
|
||||
}
|
||||
catch{
|
||||
print("Error.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func switchToBackCamera(){
|
||||
print("Back Cam")
|
||||
if backCamera?.isConnected == true {
|
||||
captureSession?.stopRunning()
|
||||
let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
|
||||
do {
|
||||
let input = try AVCaptureDeviceInput(device: captureDevice!)
|
||||
captureSession = AVCaptureSession()
|
||||
captureSession?.addInput(input)
|
||||
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
|
||||
videoPreviewLayer?.frame = view.layer.bounds
|
||||
mainCamera.layer.addSublayer(videoPreviewLayer!)
|
||||
captureSession?.startRunning()
|
||||
}
|
||||
catch{
|
||||
print("Error.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Data
|
||||
private var circuitNode: SCNNode!
|
||||
private var jawNode: SCNNode!
|
||||
|
||||
private var headNode: SCNNode!
|
||||
|
||||
private var sparkyFaceNode: SCNNode!
|
||||
|
||||
private var valuesPanelViewController: AccelerometerPanelViewController!
|
||||
|
||||
private var acceleration = BlePeripheral.AccelerometerValue(x: 0, y: 0, z: 0)
|
||||
|
||||
var accelerometerX = LowPassFilterSignal(value: 0, filterFactor: 0.6)
|
||||
|
||||
var accelerometerY = LowPassFilterSignal(value: 0, filterFactor: 0.7)
|
||||
|
||||
private var playIntroAnimation = false
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
// Add panels
|
||||
valuesPanelViewController = (addPanelViewController(storyboardIdentifier: AccelerometerPanelViewController.kIdentifier) as! AccelerometerPanelViewController)
|
||||
|
||||
// Load base
|
||||
let scene = SCNScene(named: "cpb.scn")!
|
||||
let scene = SCNScene(named: "Sparky_Gold1.dae")!
|
||||
scene.background.contents = UIColor.clear
|
||||
|
||||
circuitNode = scene.rootNode.childNode(withName: "Circuit_Playground_Bluefruit", recursively: false)!
|
||||
jawNode = scene.rootNode.childNode(withName: "jaw", recursively: true)!
|
||||
headNode = scene.rootNode.childNode(withName: "SparkyHead", recursively: true)!
|
||||
sparkyFaceNode = scene.rootNode.childNode(withName: "Face", recursively: true)!
|
||||
|
||||
// Setup scene
|
||||
sceneView.scene = scene
|
||||
|
|
@ -41,13 +325,44 @@ class AccelerometerViewController: ModuleViewController {
|
|||
|
||||
// Localization
|
||||
let localizationManager = LocalizationManager.shared
|
||||
self.title = localizationManager.localizedString("accelerometer_title")
|
||||
self.title = localizationManager.localizedString("Puppets")
|
||||
moduleHelpMessage = localizationManager.localizedString("accelerometer_help")
|
||||
|
||||
|
||||
|
||||
//--- Camera Function ----
|
||||
|
||||
if #available(iOS 10.2, *){
|
||||
|
||||
let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
|
||||
do
|
||||
{
|
||||
let input = try AVCaptureDeviceInput(device: captureDevice!)
|
||||
captureSession = AVCaptureSession()
|
||||
captureSession?.addInput(input)
|
||||
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
|
||||
videoPreviewLayer?.frame = view.layer.bounds
|
||||
mainCamera.layer.addSublayer(videoPreviewLayer!)
|
||||
captureSession?.startRunning()
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Error.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
switchToFrontCamera()
|
||||
|
||||
// Initial value
|
||||
if let acceleration = CPBBle.shared.accelerometerLastValue() {
|
||||
self.acceleration = acceleration
|
||||
|
|
@ -56,6 +371,136 @@ class AccelerometerViewController: ModuleViewController {
|
|||
|
||||
// Set delegate
|
||||
CPBBle.shared.accelerometerDelegate = self
|
||||
|
||||
//Subscribe to command notifications
|
||||
notificatonsForEmoteUse()
|
||||
|
||||
//Sparky's Intro Animation
|
||||
sparkyIntroAnimation()
|
||||
}
|
||||
|
||||
func notificatonsForEmoteUse (){
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(animationOne),name:NSNotification.Name(rawValue: "EmoteOne"), object: nil)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(animationTwo),name:NSNotification.Name(rawValue: "EmoteTwo"), object: nil)
|
||||
|
||||
// NotificationCenter.default.addObserver(self, selector: #selector(switchOne),name:NSNotification.Name(rawValue: "EmoteThree"), object: nil)
|
||||
//
|
||||
// NotificationCenter.default.addObserver(self, selector: #selector(switchTwo),name:NSNotification.Name(rawValue: "EmoteFour"), object: nil)
|
||||
|
||||
}
|
||||
|
||||
func sparkyIntroAnimation(){
|
||||
let scale: Float = 0.0005
|
||||
|
||||
headNode.scale = SCNVector3(x: scale, y: scale, z: scale)
|
||||
let scaleAnimation = SCNAction.scale(to: CGFloat(1), duration: 1.3)
|
||||
let rotationAction = SCNAction.rotateBy(x: 0, y: 12.57, z: 0, duration: 1.4)
|
||||
// let transformAnimation = SCNAction.moveBy(x: 0, y: 0, z: 0, duration: 1.4)
|
||||
|
||||
rotationAction.timingMode = .easeOut
|
||||
|
||||
// rotationAction.timingFunction = { (p: Float) in
|
||||
// return self.easeOutElastic(p)
|
||||
// }
|
||||
|
||||
headNode.runAction(scaleAnimation)
|
||||
headNode.runAction(rotationAction)
|
||||
// headNode.runAction(transformAnimation)
|
||||
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1401)) {
|
||||
self.playIntroAnimation = true
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc func switchOne(){
|
||||
|
||||
backgroundCameraIsActive.toggle()
|
||||
|
||||
if backgroundCameraIsActive{
|
||||
DispatchQueue.main.async {
|
||||
self.mainCamera.alpha = 0
|
||||
}
|
||||
}else{
|
||||
DispatchQueue.main.async {
|
||||
self.mainCamera.alpha = 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@objc func switchTwo(){
|
||||
print("Switched 2")
|
||||
backgroundCameraIsActive.toggle()
|
||||
|
||||
if backgroundCameraIsActive{
|
||||
DispatchQueue.main.async {
|
||||
self.mainCamera.alpha = 0
|
||||
}
|
||||
}else{
|
||||
DispatchQueue.main.async {
|
||||
self.mainCamera.alpha = 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@objc func animationOne(){
|
||||
//Uses a bounce in and out animation for Sparky's eyes.
|
||||
let scale: Float = 0.0005
|
||||
|
||||
sparkyFaceNode.scale = SCNVector3(x: scale, y: scale, z: scale)
|
||||
|
||||
let bounceAction = SCNAction.scale(to: CGFloat(1), duration: 1)
|
||||
|
||||
bounceAction.timingMode = .linear
|
||||
|
||||
// Use a custom timing function
|
||||
bounceAction.timingFunction = { (p: Float) in
|
||||
return self.easeOutElastic(p)
|
||||
}
|
||||
sparkyFaceNode.runAction(bounceAction)
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1500)) {
|
||||
self.sparkyFaceNode.removeAllActions()
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc func animationTwo(){
|
||||
playIntroAnimation = false
|
||||
|
||||
|
||||
let rotateAnimation = SCNAction.rotateTo(x: 0, y: 0, z: -0.3, duration: 0.1)
|
||||
|
||||
let rotateAnimationback = SCNAction.rotateTo(x: 0, y: 0, z: 0.3, duration: 0.1)
|
||||
|
||||
let headReset = SCNAction.rotateTo(x: -0.01, y: 0, z: -0.0, duration: 0.1)
|
||||
|
||||
let sequence = SCNAction.sequence([rotateAnimation, rotateAnimationback,rotateAnimation, rotateAnimationback,rotateAnimation, rotateAnimationback, headReset])
|
||||
|
||||
rotateAnimation.timingMode = .linear
|
||||
|
||||
rotateAnimationback.timingMode = .linear
|
||||
|
||||
|
||||
|
||||
|
||||
headNode.runAction(sequence)
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
|
||||
self.playIntroAnimation = true
|
||||
self.headNode.removeAllActions()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
|
|
@ -68,31 +513,112 @@ class AccelerometerViewController: ModuleViewController {
|
|||
// MARK: - UI
|
||||
private func updateValueUI() {
|
||||
|
||||
if playIntroAnimation == true {
|
||||
|
||||
// Calculate Euler Angles
|
||||
let eulerAngles = eulerAnglesFromAcceleration()
|
||||
|
||||
let eulerAnglesForHead = eulerAnglesFromAccelerationForHead()
|
||||
//DLog("Euler: pitch: \(eulerAngles.x) yaw: \(eulerAngles.y) roll: \(eulerAngles.z)")
|
||||
|
||||
// Update circuit model orientation
|
||||
SCNTransaction.animationDuration = BlePeripheral.kCPBAcceleromterDefaultPeriod
|
||||
circuitNode.eulerAngles = eulerAngles
|
||||
|
||||
jawNode.eulerAngles = eulerAngles
|
||||
headNode.eulerAngles = eulerAnglesForHead
|
||||
|
||||
|
||||
// Update panel
|
||||
valuesPanelViewController.accelerationReceived(acceleration: self.acceleration, eulerAngles: eulerAngles)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func eulerAnglesFromAcceleration() -> SCNVector3 {
|
||||
// https://robotics.stackexchange.com/questions/6953/how-to-calculate-euler-angles-from-gyroscope-output
|
||||
let accelAngleX = atan2(acceleration.y, acceleration.z)
|
||||
// let accelAngleY = atan2(-acceleration.x, sqrt(acceleration.y*acceleration.y + acceleration.z*acceleration.z))
|
||||
|
||||
accelerometerX.update(newValue: accelAngleX)
|
||||
|
||||
return SCNVector3(accelerometerX.value.clamped(min: 0.13, max: 0.8), 0, 0)
|
||||
}
|
||||
|
||||
|
||||
private func eulerAnglesFromAccelerationForHead() -> SCNVector3 {
|
||||
// https://robotics.stackexchange.com/questions/6953/how-to-calculate-euler-angles-from-gyroscope-output
|
||||
let accelAngleX = atan2(acceleration.y, acceleration.z)
|
||||
let accelAngleY = atan2(-acceleration.x, sqrt(acceleration.y*acceleration.y + acceleration.z*acceleration.z))
|
||||
|
||||
return SCNVector3(accelAngleX, accelAngleY, 0)
|
||||
let accelAngleYALT = atan2(-acceleration.x, sqrt(acceleration.y*acceleration.y) + acceleration.z*acceleration.z)
|
||||
|
||||
accelerometerX.update(newValue: accelAngleX)
|
||||
accelerometerY.update(newValue: accelAngleYALT)
|
||||
|
||||
|
||||
return SCNVector3(-accelerometerX.value.clamped(min: 0.1, max: 0.7), -accelerometerY.value, accelerometerY.value)
|
||||
}
|
||||
|
||||
// Timing function that has a "bounce in" effect
|
||||
|
||||
func easeOutElastic(_ t: Float) -> Float {
|
||||
let p: Float = 0.3
|
||||
let result = pow(2.0, -5.0 * t) * sin((t - p / 4.0) * (2.0 * Float.pi) / p) + 1.0
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - CPBBleAccelerometerDelegate
|
||||
extension AccelerometerViewController: CPBBleAccelerometerDelegate {
|
||||
|
||||
func cpbleAccelerationReceived(_ acceleration: BlePeripheral.AccelerometerValue) {
|
||||
self.acceleration = acceleration
|
||||
updateValueUI()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension Float {
|
||||
func clamped(min min: Float, max: Float) -> Float {
|
||||
if self < min {
|
||||
return min
|
||||
}
|
||||
|
||||
if self > max {
|
||||
return max
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension AccelerometerViewController : RPPreviewViewControllerDelegate {
|
||||
func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension UIView {
|
||||
@IBInspectable
|
||||
var newCornerRadius: CGFloat {
|
||||
get {
|
||||
return layer.cornerRadius
|
||||
}
|
||||
set {
|
||||
layer.cornerRadius = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Int {
|
||||
var degreesToRadians: Double { return Double(self) * .pi/180}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,16 @@ class ButtonStatusPanelViewController: ModulePanelViewController {
|
|||
|
||||
// Init
|
||||
switchImageView.tintColor = offColor
|
||||
|
||||
// Read initial state
|
||||
CPBBle.shared.buttonsReadState { [weak self] response in
|
||||
switch response {
|
||||
case let .success(buttonsStatus, _):
|
||||
self?.buttonsStateReceived(buttonsStatus)
|
||||
case .failure(let error):
|
||||
DLog("Error receiving temperature data: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// Localization
|
||||
let localizationManager = LocalizationManager.shared
|
||||
|
|
@ -57,6 +67,8 @@ class ButtonStatusPanelViewController: ModulePanelViewController {
|
|||
}
|
||||
|
||||
private func animateDown(view: UIView) {
|
||||
|
||||
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
|
||||
view.tintColor = self.onColor
|
||||
|
|
@ -88,6 +100,7 @@ class ButtonStatusPanelViewController: ModulePanelViewController {
|
|||
|
||||
if buttonsState.buttonA != currentState.buttonA {
|
||||
animateState(view: buttonAStatusView, isPressed: buttonsState.buttonA == .pressed)
|
||||
// NotificationCenter.default.post(name:NSNotification.Name(rawValue: "Command"), object: nil)
|
||||
}
|
||||
|
||||
if buttonsState.buttonB != currentState.buttonB {
|
||||
|
|
|
|||
|
|
@ -10,9 +10,8 @@ import UIKit
|
|||
|
||||
class HomeViewController: UIViewController {
|
||||
// Constants
|
||||
static let kNavigationControllerIdentifier = "ModulesNavigationController"
|
||||
static let kIdentifier = "HomeViewController"
|
||||
|
||||
|
||||
// UI
|
||||
@IBOutlet weak var baseTableView: UITableView!
|
||||
|
||||
|
|
@ -67,60 +66,18 @@ class HomeViewController: UIViewController {
|
|||
super.viewDidLoad()
|
||||
|
||||
// Setup UI
|
||||
/*
|
||||
let topContentInsetForDetails: CGFloat = 20
|
||||
baseTableView.contentInset = UIEdgeInsets(top: topContentInsetForDetails, left: 0, bottom: 0, right: 0)
|
||||
*/
|
||||
|
||||
// Localization
|
||||
let localizationManager = LocalizationManager.shared
|
||||
self.title = localizationManager.localizedString("modules_title")
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
/*
|
||||
if let customNavigationBar = navigationController?.navigationBar as? NavigationBarWithScrollAwareRightButton {
|
||||
customNavigationBar.setRightButton(topViewController: self, image: UIImage(named: "info"), target: self, action: #selector(about(_:)))
|
||||
}*/
|
||||
|
||||
/*
|
||||
if let peripheral = Config.bleManager.connectedPeripherals().first {
|
||||
peripheral.readRssi()
|
||||
}*/
|
||||
}
|
||||
|
||||
/*
|
||||
// MARK: - Actions
|
||||
@IBAction func about(_ sender: Any) {
|
||||
guard let viewController = storyboard?.instantiateViewController(withIdentifier: AboutViewController.kIdentifier) else { return }
|
||||
|
||||
self.present(viewController, animated: true, completion: nil)
|
||||
}*/
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
extension HomeViewController: UITableViewDataSource {
|
||||
enum CellType {
|
||||
case details
|
||||
case module
|
||||
case disconnect
|
||||
|
||||
var reuseIdentifier: String {
|
||||
switch(self) {
|
||||
case .details: return "DetailsCell"
|
||||
case .module: return "ModuleCell"
|
||||
case .disconnect: return "DisconnectCell"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func cellTypeFromIndexPath(_ indexPath: IndexPath) -> CellType {
|
||||
return indexPath.section == 0 ? .details : indexPath.row == menuItems.count ? .disconnect : .module
|
||||
}
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return 2 // Details + Modules
|
||||
}
|
||||
|
|
@ -130,31 +87,23 @@ extension HomeViewController: UITableViewDataSource {
|
|||
return 1
|
||||
}
|
||||
else {
|
||||
return menuItems.count + 1 // + 1 disconnect
|
||||
return menuItems.count
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let cellType = cellTypeFromIndexPath(indexPath)
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: cellType.reuseIdentifier, for: indexPath)
|
||||
let reuseIdentifier = indexPath.section == 0 ? "DetailsCell" : "ModuleCell"
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath)
|
||||
|
||||
// Add cell data here instead of using willDisplay to avoid problems with automatic dimension calculation
|
||||
let localizationManager = LocalizationManager.shared
|
||||
let isDetails = indexPath.section == 0
|
||||
|
||||
switch(cellType) {
|
||||
case .details:
|
||||
if isDetails {
|
||||
let detailsCell = cell as! TitleTableViewCell
|
||||
detailsCell.titleLabel.text = localizationManager.localizedString("modules_subtitle")
|
||||
/*
|
||||
case .peripheral
|
||||
let peripheralCell = cell as! CommonTableViewCell
|
||||
if let peripheral = Config.bleManager.connectedPeripherals().first {
|
||||
// Fill peripheral data
|
||||
peripheralCell.titleLabel.text = peripheral.name ?? localizationManager.localizedString("scanner_unnamed")
|
||||
peripheralCell.iconImageView.image = RssiUI.signalImage(for: peripheral.rssi)
|
||||
}*/
|
||||
case .module:
|
||||
}
|
||||
else {
|
||||
let moduleCell = cell as! CommonTableViewCell
|
||||
let menuItem = menuItems[indexPath.row]
|
||||
|
||||
|
|
@ -164,18 +113,14 @@ extension HomeViewController: UITableViewDataSource {
|
|||
let subtitleStringId = menuItem.subtitleStringId
|
||||
moduleCell.subtitleLabel?.text = localizationManager.localizedString(subtitleStringId)
|
||||
|
||||
//moduleCell.setPanelBackgroundColor(menuItem.color)
|
||||
moduleCell.iconImageView.backgroundColor = menuItem.color
|
||||
moduleCell.iconImageView.layer.borderColor = UIColor(named: "text_default")?.cgColor
|
||||
moduleCell.iconImageView.layer.borderWidth = 1
|
||||
moduleCell.iconImageView.layer.cornerRadius = 7
|
||||
moduleCell.iconImageView.layer.masksToBounds = true
|
||||
|
||||
case .disconnect:
|
||||
let disconnectCell = cell as! CommonTableViewCell
|
||||
disconnectCell.titleLabel.text = localizationManager.localizedString("modules_disconnect_title")
|
||||
disconnectCell.subtitleLabel?.text = localizationManager.localizedString("modules_disconnect_subtitle")
|
||||
}
|
||||
|
||||
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
|
@ -184,64 +129,36 @@ extension HomeViewController: UITableViewDataSource {
|
|||
extension HomeViewController: UITableViewDelegate {
|
||||
|
||||
/*
|
||||
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
}*/
|
||||
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
}*/
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
let cellType = cellTypeFromIndexPath(indexPath)
|
||||
switch(cellType) {
|
||||
case .details:
|
||||
break
|
||||
case .module:
|
||||
if let module = Modules(rawValue: indexPath.row) {
|
||||
var storyboardId: String? = nil
|
||||
switch module {
|
||||
case .color:
|
||||
storyboardId = NeoPixelsViewController.kIdentifier
|
||||
case .light:
|
||||
storyboardId = LightSensorViewController.kIdentifier
|
||||
case .button:
|
||||
storyboardId = ButtonStatusViewController.kIdentifier
|
||||
case .tone:
|
||||
storyboardId = ToneGeneratorViewController.kIdentifier
|
||||
case .accelerometer:
|
||||
storyboardId = AccelerometerViewController.kIdentifier
|
||||
case .temperature:
|
||||
storyboardId = TemperatureViewController.kIdentifier
|
||||
}
|
||||
|
||||
if let identifier = storyboardId, let viewController = storyboard?.instantiateViewController(withIdentifier: identifier) {
|
||||
|
||||
// Show viewController with completion block
|
||||
CATransaction.begin()
|
||||
self.show(viewController, sender: self)
|
||||
CATransaction.setCompletionBlock({
|
||||
// Flash neopixels with the module color
|
||||
CPBBle.shared.neopixelStartLightSequence(FlashLightSequence(baseColor: module.color), speed: 1, repeating: false, sendLightSequenceNotifications: false)
|
||||
})
|
||||
CATransaction.commit()
|
||||
}
|
||||
}
|
||||
|
||||
case .disconnect:
|
||||
if let peripheral = Config.bleManager.connectedPeripherals().first {
|
||||
Config.bleManager.disconnect(from: peripheral, waitForQueuedCommands: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// MARK: UIScrollViewDelegate
|
||||
extension HomeViewController {
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
// NavigationBar Button Custom Animation
|
||||
if let customNavigationBar = navigationController?.navigationBar as? NavigationBarWithScrollAwareRightButton {
|
||||
customNavigationBar.updateRightButtonPosition()
|
||||
let isDetails = indexPath.section == 0
|
||||
guard !isDetails else { return }
|
||||
|
||||
guard let module = Modules(rawValue: indexPath.row) else { return }
|
||||
var storyboardId: String? = nil
|
||||
switch module {
|
||||
case .color:
|
||||
storyboardId = NeoPixelsViewController.kIdentifier
|
||||
case .light:
|
||||
storyboardId = LightSensorViewController.kIdentifier
|
||||
case .button:
|
||||
storyboardId = ButtonStatusViewController.kIdentifier
|
||||
case .tone:
|
||||
storyboardId = ToneGeneratorViewController.kIdentifier
|
||||
case .accelerometer:
|
||||
storyboardId = AccelerometerViewController.kIdentifier
|
||||
case .temperature:
|
||||
storyboardId = TemperatureViewController.kIdentifier
|
||||
}
|
||||
|
||||
if let identifier = storyboardId, let viewController = storyboard?.instantiateViewController(withIdentifier: identifier) {
|
||||
|
||||
self.show(viewController, sender: self)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -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 lightmeterPanelViewController: LightSensorPanelViewController!
|
||||
private var light: Float?
|
||||
private var chartPanelViewController: LightChartPanelViewController!
|
||||
|
||||
// MARK: - Lifecycle
|
||||
override func viewDidLoad() {
|
||||
|
|
@ -24,9 +23,6 @@ class LightSensorViewController: ModuleViewController {
|
|||
|
||||
// Add panels
|
||||
lightmeterPanelViewController = (addPanelViewController(storyboardIdentifier: LightSensorPanelViewController.kIdentifier) as! LightSensorPanelViewController)
|
||||
|
||||
chartPanelViewController = (addPanelViewController(storyboardIdentifier: LightChartPanelViewController.kIdentifier) as! LightChartPanelViewController)
|
||||
|
||||
|
||||
// Localization
|
||||
let localizationManager = LocalizationManager.shared
|
||||
|
|
@ -65,9 +61,6 @@ class LightSensorViewController: ModuleViewController {
|
|||
private func updateValueUI() {
|
||||
if let light = self.light {
|
||||
lightmeterPanelViewController.lightValueReceived(light)
|
||||
|
||||
// Update chart
|
||||
chartPanelViewController.lightValueReceived()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,14 +44,9 @@ class CircuitViewController: UIViewController {
|
|||
}
|
||||
|
||||
// Init neopixels color
|
||||
for (i, neopixelView) in self.neopixelsContainerView.subviews.enumerated() {
|
||||
if i < self.isNeopixelSelected.count {
|
||||
neopixelView.backgroundColor = .clear
|
||||
}
|
||||
}
|
||||
//neopixelsReset(animated: false)
|
||||
neopixelsReset(animated: false)
|
||||
}
|
||||
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
|
|
@ -67,7 +62,7 @@ class CircuitViewController: UIViewController {
|
|||
// Notifications
|
||||
registerNotifications(enabled: true)
|
||||
}
|
||||
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
|
|
@ -102,7 +97,8 @@ class CircuitViewController: UIViewController {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Notifications
|
||||
private var didUpdateNeopixelLightSequenceObserver: NSObjectProtocol?
|
||||
|
||||
|
|
@ -126,6 +122,7 @@ class CircuitViewController: UIViewController {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
if let didUpdateNeopixelLightSequenceObserver = didUpdateNeopixelLightSequenceObserver {notificationCenter.removeObserver(didUpdateNeopixelLightSequenceObserver)}
|
||||
|
|
@ -171,6 +168,7 @@ class CircuitViewController: UIViewController {
|
|||
// Animate
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
selectedView.transform = isSelected ? .identity:CGAffineTransform(scaleX: 1.2, y: 1.2)
|
||||
//selectedView.layer.borderWidth = self.selectedWidth// isSelected ? self.selectedWidth:0
|
||||
selectedView.alpha = isSelected ? 1:CircuitViewController.kUnselectedButtonAlpha
|
||||
}) { (_) in
|
||||
selectedView.transform = .identity
|
||||
|
|
@ -183,7 +181,7 @@ class CircuitViewController: UIViewController {
|
|||
}
|
||||
|
||||
// MARK: - Actions
|
||||
func setNeopixelsColor(_ color: UIColor, onlySelected: Bool, animated: Bool, baseColor: UIColor? = nil) {
|
||||
func setNeopixelsColor(_ color: UIColor, onlySelected: Bool, animated: Bool) {
|
||||
|
||||
if onlySelected {
|
||||
CPBBle.shared.neopixelSetPixelColor(color, pixelMask: isNeopixelSelected)
|
||||
|
|
@ -194,10 +192,9 @@ class CircuitViewController: UIViewController {
|
|||
|
||||
// UI Animation
|
||||
UIView.animate(withDuration: animated ? 0.1: 0) {
|
||||
// Base color is the color withouth the brightness. Used in the circuit representation to show the colors more vividly
|
||||
for (i, neopixelView) in self.neopixelsContainerView.subviews.enumerated() {
|
||||
if i < self.isNeopixelSelected.count && (!onlySelected || self.isNeopixelSelected[i]) {
|
||||
neopixelView.backgroundColor = baseColor ?? color
|
||||
neopixelView.backgroundColor = color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,9 +80,9 @@ class NeoPixelsViewController: ModuleViewController {
|
|||
circuitViewController.neopixelsReset(animated: true)
|
||||
}
|
||||
|
||||
private func selectColor(_ color: UIColor, baseColor: UIColor) {
|
||||
private func selectColor(_ color: UIColor) {
|
||||
// Update circuit
|
||||
circuitViewController.setNeopixelsColor(color, onlySelected: true, animated: true, baseColor: baseColor)
|
||||
circuitViewController.setNeopixelsColor(color, onlySelected: true, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Page Management
|
||||
|
|
@ -104,15 +104,14 @@ class NeoPixelsViewController: ModuleViewController {
|
|||
|
||||
// MARK: - NeopixelsColorPaletteViewControllerDelegate
|
||||
extension NeoPixelsViewController: NeopixelsColorPaletteViewControllerDelegate {
|
||||
func colorPaletteColorSelected(color: UIColor, baseColor: UIColor) {
|
||||
selectColor(color, baseColor: baseColor)
|
||||
func colorPaletteColorSelected(color: UIColor) {
|
||||
selectColor(color)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - NeopixelColorWheelViewControllerDelegate
|
||||
// MARK: - NeopixelsColorPaletteViewControllerDelegate
|
||||
extension NeoPixelsViewController: NeopixelColorWheelViewControllerDelegate {
|
||||
func colorWheelColorSelected(color: UIColor, baseColor: UIColor) {
|
||||
selectColor(color, baseColor: baseColor)
|
||||
func colorWheelColorSelected(color: UIColor) {
|
||||
selectColor(color)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import UIKit
|
|||
import FlexColorPicker
|
||||
|
||||
protocol NeopixelsColorPaletteViewControllerDelegate: class {
|
||||
func colorPaletteColorSelected(color: UIColor, baseColor: UIColor)
|
||||
func colorPaletteColorSelected(color: UIColor)
|
||||
}
|
||||
|
||||
class NeopixelsColorPaletteViewController: ModulePanelViewController {
|
||||
|
|
@ -124,6 +124,6 @@ class NeopixelsColorPaletteViewController: ModulePanelViewController {
|
|||
let hsbColor = HSBColor(color: color)
|
||||
let colorWithBrighness = hsbColor.withBrightness(brightness).toUIColor()
|
||||
|
||||
delegate?.colorPaletteColorSelected(color: colorWithBrighness, baseColor: color)
|
||||
delegate?.colorPaletteColorSelected(color: colorWithBrighness)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import UIKit
|
|||
import FlexColorPicker
|
||||
|
||||
protocol NeopixelColorWheelViewControllerDelegate: class {
|
||||
func colorWheelColorSelected(color: UIColor, baseColor: UIColor)
|
||||
func colorWheelColorSelected(color: UIColor)
|
||||
}
|
||||
|
||||
class NeopixelsColorWheelViewController: ModulePanelViewController {
|
||||
|
|
@ -96,7 +96,7 @@ class NeopixelsColorWheelViewController: ModulePanelViewController {
|
|||
let hsbColor = HSBColor(color: color)
|
||||
let colorWithBrighness = hsbColor.withBrightness(brightness).toUIColor()
|
||||
|
||||
delegate?.colorWheelColorSelected(color: colorWithBrighness, baseColor: color)
|
||||
delegate?.colorWheelColorSelected(color: colorWithBrighness)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class NeopixelsLightSequenceViewController: ModulePanelViewController {
|
|||
|
||||
// Data
|
||||
private var previewViewControllers = [PixelsPreviewViewController]()
|
||||
private var speed: Double = 0.3
|
||||
|
||||
// MARK: - Lifecycle
|
||||
override func viewDidLoad() {
|
||||
|
|
@ -29,10 +30,8 @@ class NeopixelsLightSequenceViewController: ModulePanelViewController {
|
|||
let _ = previewViewControllers.map{$0.tag = $0.view.superview?.tag ?? 0}
|
||||
|
||||
// Set initial speed
|
||||
let speed = CPBBle.kLightSequenceDefaultSpeed
|
||||
speedSlider.value = Float(speed)
|
||||
//speedChanged(speedSlider)
|
||||
let _ = previewViewControllers.map{$0.speed = speed}
|
||||
speedChanged(speedSlider)
|
||||
|
||||
// Localization
|
||||
let localizationManager = LocalizationManager.shared
|
||||
|
|
@ -48,10 +47,10 @@ class NeopixelsLightSequenceViewController: ModulePanelViewController {
|
|||
}
|
||||
|
||||
@IBAction func speedChanged(_ sender: UISlider) {
|
||||
let speed = Double(sender.value)
|
||||
speed = Double(sender.value)
|
||||
DLog("speed: \(speed)")
|
||||
|
||||
CPBBle.shared.neopixelCurrentLightSequenceAnimationSpeed = speed
|
||||
CPBBle.shared.neopixelLightSequenceAnimationSpeed = speed
|
||||
let _ = previewViewControllers.map{$0.speed = speed}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class PixelsPreviewViewController: UIViewController {
|
|||
|
||||
guard let lightSequenceGenerator = lightSequenceGeneratorForTag(self.tag) else { return }
|
||||
|
||||
lightSequenceAnimation = LightSequenceAnimation(lightSequenceGenerator: lightSequenceGenerator, framesPerSecond: 10, repeating: true)
|
||||
lightSequenceAnimation = LightSequenceAnimation(lightSequenceGenerator: lightSequenceGenerator, framesPerSecond: 10)
|
||||
lightSequenceAnimation!.speed = speed
|
||||
lightSequenceAnimation!.start() { [weak self] pixelsBytes in
|
||||
guard let self = self else { return }
|
||||
|
|
@ -72,8 +72,6 @@ class PixelsPreviewViewController: UIViewController {
|
|||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
lightSequenceAnimation?.stop()
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
|
|
|
|||
|
|
@ -70,17 +70,6 @@ class AboutViewController: UIViewController {
|
|||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
label.configureLinkAttribute = { (type, attributes, isSelected) in
|
||||
var atts = attributes
|
||||
switch type {
|
||||
case customType0, customType1:
|
||||
atts[.underlineStyle] = NSUnderlineStyle.single.rawValue
|
||||
default: ()
|
||||
}
|
||||
|
||||
return atts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,16 +55,6 @@ extension ScanProblemsViewController: UITableViewDataSource {
|
|||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
label.configureLinkAttribute = { (type, attributes, isSelected) in
|
||||
var atts = attributes
|
||||
switch type {
|
||||
case customType:
|
||||
atts[.underlineStyle] = NSUnderlineStyle.single.rawValue
|
||||
default: ()
|
||||
}
|
||||
return atts
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@ import CoreBluetooth
|
|||
|
||||
class ScannerViewController: UIViewController {
|
||||
// Constants
|
||||
static let kNavigationControllerIdentifier = "ScannerNavigationController"
|
||||
static let kIdentifier = "ScannerNavigationController" //"ScannerViewController"
|
||||
|
||||
// Config
|
||||
private static let kServicesToScan: [CBUUID]? = nil //[BlePeripheral.kUartServiceUUID]
|
||||
private static let kDelayToShowWait: TimeInterval = 1.0
|
||||
|
||||
// UI
|
||||
|
|
@ -21,23 +22,13 @@ class ScannerViewController: UIViewController {
|
|||
@IBOutlet weak var waitView: UIView!
|
||||
@IBOutlet weak var waitLabel: UILabel!
|
||||
@IBOutlet weak var problemsButton: UIButton!
|
||||
@IBOutlet weak var scanAutomaticallyButton: CornerShadowButton!
|
||||
@IBOutlet weak var actionsContainerView: UIStackView!
|
||||
|
||||
// Data
|
||||
private let refreshControl = UIRefreshControl()
|
||||
private let bleManager = Config.bleManager
|
||||
private var peripheralList = PeripheralList(bleManager: Config.bleManager)
|
||||
|
||||
private var selectedPeripheral: BlePeripheral? {
|
||||
didSet {
|
||||
if isViewLoaded {
|
||||
UIView.animate(withDuration: 0.3) {
|
||||
self.actionsContainerView.alpha = self.selectedPeripheral == nil ? 1:0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private var selectedPeripheral: BlePeripheral?
|
||||
private var infoAlertController: UIAlertController?
|
||||
|
||||
private var isBaseTableScrolling = false
|
||||
|
|
@ -68,7 +59,6 @@ class ScannerViewController: UIViewController {
|
|||
|
||||
waitLabel.text = localizationManager.localizedString("scanner_searching")
|
||||
problemsButton.setTitle(localizationManager.localizedString("scanner_problems_action").uppercased(), for: .normal)
|
||||
scanAutomaticallyButton.setTitle(localizationManager.localizedString("scanner_automatic_action").uppercased(), for: .normal)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
|
|
@ -85,7 +75,7 @@ class ScannerViewController: UIViewController {
|
|||
if connectedPeripherals.count == 1, let peripheral = connectedPeripherals.first {
|
||||
DLog("Disconnect from previously connected peripheral")
|
||||
// Disconnect from peripheral
|
||||
disconnect(peripheral: peripheral)
|
||||
bleManager.disconnect(from: peripheral)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,26 +89,21 @@ class ScannerViewController: UIViewController {
|
|||
updateScannedPeripherals()
|
||||
|
||||
// Start scannning
|
||||
//bleManager.startScan(withServices: ScannerViewController.kServicesToScan)
|
||||
if !bleManager.isScanning {
|
||||
bleManager.startScan()
|
||||
|
||||
}
|
||||
// Remove saved peripheral for autoconnect
|
||||
Settings.autoconnectPeripheralIdentifier = nil
|
||||
bleManager.startScan(withServices: ScannerViewController.kServicesToScan)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
// Stop scanning
|
||||
bleManager.stopScan()
|
||||
|
||||
|
||||
// Clear peripherals
|
||||
peripheralList.clear()
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
// Ble Notifications
|
||||
registerNotifications(enabled: false)
|
||||
}
|
||||
|
|
@ -232,7 +217,7 @@ class ScannerViewController: UIViewController {
|
|||
alertController.addAction(okAction)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
|
||||
self.disconnect(peripheral: selectedPeripheral)
|
||||
self.bleManager.disconnect(from: selectedPeripheral)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -258,6 +243,18 @@ class ScannerViewController: UIViewController {
|
|||
|
||||
// Reload table
|
||||
reloadBaseTable()
|
||||
|
||||
// If is not the topViewController, pop any other thing and come back to scanning
|
||||
if self.navigationController?.topViewController !== self {
|
||||
self.navigationController?.popToRootViewController(animated: false)
|
||||
|
||||
// Show disconnection alert
|
||||
let localizationManager = LocalizationManager.shared
|
||||
let alertController = UIAlertController(title: nil, message: localizationManager.localizedString("scanner_peripheraldisconnected"), preferredStyle: .alert)
|
||||
let okAction = UIAlertAction(title: localizationManager.localizedString("dialog_ok"), style: .default, handler: nil)
|
||||
alertController.addAction(okAction)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func peripheralDidUpdateName(notification: Notification) {
|
||||
|
|
@ -296,16 +293,17 @@ class ScannerViewController: UIViewController {
|
|||
self.present(viewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func scanAutomatically(_ sender: Any) {
|
||||
ScreenFlowManager.gotoAutoconnect()
|
||||
}
|
||||
|
||||
private func showPeripheralDetails() {
|
||||
// Save selected peripheral for autoconnect
|
||||
Settings.autoconnectPeripheralIdentifier = selectedPeripheral?.identifier
|
||||
|
||||
// Go to home screen
|
||||
ScreenFlowManager.gotoCPBModules()
|
||||
let backItem = UIBarButtonItem()
|
||||
backItem.title = LocalizationManager.shared.localizedString("scanner_backbutton")
|
||||
self.navigationItem.backBarButtonItem = backItem
|
||||
|
||||
guard let modulesViewController = self.storyboard?.instantiateViewController(withIdentifier: HomeViewController.kIdentifier) else { return }
|
||||
self.show(modulesViewController, sender: self)
|
||||
}
|
||||
|
||||
// MARK: - UI
|
||||
|
|
@ -458,7 +456,16 @@ extension ScannerViewController {
|
|||
reloadBaseTable()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
||||
isBaseTableScrolling = false
|
||||
|
||||
if isScannerTableWaitingForReload {
|
||||
reloadBaseTable()
|
||||
}
|
||||
}*/
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
|
||||
// NavigationBar Button Custom Animation
|
||||
|
|
@ -468,7 +475,7 @@ extension ScannerViewController {
|
|||
|
||||
// Move Refresh control when a large title is used
|
||||
if let height = navigationController?.navigationBar.frame.height {
|
||||
refreshControl.bounds = CGRect(x: refreshControl.bounds.origin.x, y: NavigationBarWithScrollAwareRightButton.navBarHeightLargeState - height, width: refreshControl.bounds.size.width, height: refreshControl.bounds.size.height)
|
||||
refreshControl.bounds = CGRect(x: refreshControl.bounds.origin.x, y: NavigationBarWithScrollAwareRightButton.CustomButtonMetrics.navBarHeightLargeState - height, width: refreshControl.bounds.size.width, height: refreshControl.bounds.size.height)
|
||||
}
|
||||
|
||||
// Hide details opacity when showing the refresh control
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@ class StartupViewController: UIViewController {
|
|||
private static let kServicesToReconnect = [BlePeripheral.kUartServiceUUID]
|
||||
private static let kReconnectTimeout = 2.0
|
||||
|
||||
// UI
|
||||
@IBOutlet weak var restoreConnectionLabel: UILabel!
|
||||
|
||||
|
||||
// Data
|
||||
private let bleSupportSemaphore = DispatchSemaphore(value: 0)
|
||||
private var startTime: CFAbsoluteTime!
|
||||
|
|
@ -28,12 +24,6 @@ class StartupViewController: UIViewController {
|
|||
// MARK: - Lifecycle
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// UI
|
||||
restoreConnectionLabel.alpha = 0
|
||||
|
||||
// Localization
|
||||
restoreConnectionLabel.text = LocalizationManager.shared.localizedString("splash_restoringconnection")
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
|
@ -134,7 +124,6 @@ class StartupViewController: UIViewController {
|
|||
// MARK: - Reconnect previously connnected Ble Peripheral
|
||||
private func reconnecToPeripheral(withIdentifier identifier: UUID) {
|
||||
DLog("Reconnecting...")
|
||||
|
||||
// Reconnect
|
||||
let isTryingToReconnect = BleManager.shared.reconnecToPeripherals(withIdentifiers: [identifier], withServices: StartupViewController.kServicesToReconnect, timeout: StartupViewController.kReconnectTimeout)
|
||||
|
||||
|
|
@ -156,22 +145,23 @@ class StartupViewController: UIViewController {
|
|||
}
|
||||
|
||||
private func didDisconnectFromPeripheral() {
|
||||
/*
|
||||
// Clear selected peripheral
|
||||
self.selectedPeripheral = nil
|
||||
*/
|
||||
|
||||
|
||||
// Autoconnect failed
|
||||
connected(peripheral: nil)
|
||||
}
|
||||
|
||||
private func connected(peripheral: BlePeripheral?) {
|
||||
if let peripheral = peripheral {
|
||||
// Show restoring connection label
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.restoreConnectionLabel.alpha = 1
|
||||
}
|
||||
|
||||
// Setup peripheral
|
||||
CPBBle.shared.setupPeripheral(blePeripheral: peripheral) { [weak self] result in
|
||||
switch result {
|
||||
case .success():
|
||||
ScreenFlowManager.restoreAndGoToCPBModules()
|
||||
ScreenFlowManager.restoreAndGoToHome()
|
||||
|
||||
case .failure(let error):
|
||||
DLog("Failed setup peripheral: \(error.localizedDescription)")
|
||||
|
|
@ -190,15 +180,12 @@ class StartupViewController: UIViewController {
|
|||
|
||||
// MARK: - Screen Flow
|
||||
private func gotoInitialScreen() {
|
||||
let viewControllerIdentifier = StartupViewController.kForcedNavigationControllerIdentifier ?? (Settings.areTipsEnabled && Config.isTutorialEnabled ? TipsViewController.kIdentifier : AutoConnectViewController.kNavigationControllerIdentifier)
|
||||
let viewControllerIdentifier = StartupViewController.kForcedNavigationControllerIdentifier ?? (Settings.areTipsEnabled && Config.isTutorialEnabled ? TipsViewController.kIdentifier : ScannerViewController.kIdentifier)
|
||||
DLog("Start app with viewController: \(viewControllerIdentifier)")
|
||||
|
||||
// Change splash screen to main screen
|
||||
if let rootViewController = self.storyboard?.instantiateViewController(withIdentifier: viewControllerIdentifier) {
|
||||
ScreenFlowManager.changeRootViewController(rootViewController: rootViewController) {
|
||||
// Start scannning
|
||||
//Config.bleManager.startScan()
|
||||
}
|
||||
ScreenFlowManager.changeRootViewController(rootViewController: rootViewController, animated: false)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class TemperaturePanelViewController: ModulePanelViewController {
|
|||
// MARK: - Line Chart
|
||||
private func setupChart() {
|
||||
//chartView.delegate = self
|
||||
chartView.backgroundColor = .clear // Fix for Charts 3.0.3 (overrides the default background color)
|
||||
chartView.backgroundColor = .white // Fix for Charts 3.0.3 (overrides the default background color)
|
||||
|
||||
chartView.dragEnabled = false
|
||||
chartView.isUserInteractionEnabled = false
|
||||
|
|
|
|||
|
|
@ -86,15 +86,6 @@ class TipViewController: UIViewController {
|
|||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||
}
|
||||
}
|
||||
label.configureLinkAttribute = { (type, attributes, isSelected) in
|
||||
var atts = attributes
|
||||
switch type {
|
||||
case customType:
|
||||
atts[.underlineStyle] = NSUnderlineStyle.single.rawValue
|
||||
default: ()
|
||||
}
|
||||
return atts
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ class TipsViewController: UIViewController {
|
|||
}
|
||||
|
||||
@IBAction func skip(_ sender: Any) {
|
||||
ScreenFlowManager.gotoAutoconnect()
|
||||
ScreenFlowManager.gotoScanner()
|
||||
}
|
||||
|
||||
@objc private func pageControlTapHandler(sender: UIPageControl) {
|
||||
|
|
|
|||
|
|
@ -46,10 +46,6 @@ class ToneGeneratorViewController: UIViewController {
|
|||
@IBOutlet weak var keysContainerView: UIView!
|
||||
@IBOutlet weak var whiteKeysStackView: UIStackView!
|
||||
@IBOutlet weak var speakerImageView: UIImageView!
|
||||
@IBOutlet weak var halfKeyTop: UIButton!
|
||||
@IBOutlet weak var halfKeyBottom: UIButton!
|
||||
@IBOutlet weak var extraHalfKeyTop: UIButton!
|
||||
@IBOutlet weak var extraHalfKeyBottom: UIButton!
|
||||
|
||||
// Data
|
||||
private var tonesPlaying = Set<Int>()
|
||||
|
|
@ -79,56 +75,25 @@ class ToneGeneratorViewController: UIViewController {
|
|||
}
|
||||
}
|
||||
|
||||
// - Add 2 extra hidden buttons to extend the touch area of the black half-keys
|
||||
|
||||
for button in [extraHalfKeyTop!, extraHalfKeyBottom!] {
|
||||
button.addTarget(self, action: #selector(redirectHalfKeyDown(_:)), for: .touchDown)
|
||||
button.addTarget(self, action: #selector(redirectHalfKeyUp(_:)), for: [.touchUpInside, .touchUpOutside, .touchCancel])
|
||||
}
|
||||
|
||||
// - Round the container so the half-keys that are clipped look rounded too
|
||||
keysContainerView.layer.cornerRadius = 4
|
||||
keysContainerView.layer.masksToBounds = true
|
||||
|
||||
// Localization
|
||||
let localizationManager = LocalizationManager.shared
|
||||
self.title = localizationManager.localizedString("tonegenerator_title")
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
// Navigationbar setup
|
||||
if let customNavigationBar = navigationController?.navigationBar as? NavigationBarWithScrollAwareRightButton {
|
||||
customNavigationBar.setRightButton(topViewController: self, image: UIImage(named: "help"), target: self, action: #selector(help(_:)))
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
// Disable the pop recognizer to avoid problems with keys on the edges
|
||||
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
// Re-enable the pop recognizer
|
||||
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
|
||||
}
|
||||
|
||||
override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
|
||||
// Remove the system gestures from left and right edges
|
||||
return [.top, .bottom] // not left or right
|
||||
}
|
||||
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
// Navigationbar setup
|
||||
if let customNavigationBar = navigationController?.navigationBar as? NavigationBarWithScrollAwareRightButton {
|
||||
customNavigationBar.setRightButton(topViewController: self, image: UIImage(named: "help"), target: self, action: #selector(help(_:)))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
@objc private func keyDown(_ sender: UIButton) {
|
||||
let tag = sender.tag
|
||||
tonesPlaying.insert(tag)
|
||||
|
||||
|
||||
// UI Animation
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.speakerImageView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
|
||||
|
|
@ -143,7 +108,7 @@ class ToneGeneratorViewController: UIViewController {
|
|||
@objc private func keyUp(_ sender: UIButton) {
|
||||
let tag = sender.tag
|
||||
tonesPlaying.remove(tag)
|
||||
|
||||
|
||||
// UI Animation
|
||||
if tonesPlaying.isEmpty {
|
||||
UIView.animate(withDuration: 0.15) {
|
||||
|
|
@ -160,20 +125,6 @@ class ToneGeneratorViewController: UIViewController {
|
|||
}
|
||||
}
|
||||
|
||||
@objc private func redirectHalfKeyDown(_ sender: UIButton) {
|
||||
let targetButton = sender === extraHalfKeyTop ? halfKeyTop : halfKeyBottom
|
||||
targetButton?.sendActions(for: .touchDown)
|
||||
halfKeyTop.isHighlighted = true
|
||||
halfKeyBottom.isHighlighted = true
|
||||
}
|
||||
|
||||
@objc private func redirectHalfKeyUp(_ sender: UIButton) {
|
||||
let targetButton = sender === extraHalfKeyTop ? halfKeyTop : halfKeyBottom
|
||||
targetButton?.sendActions(for: .touchUpInside)
|
||||
halfKeyTop.isHighlighted = false
|
||||
halfKeyBottom.isHighlighted = false
|
||||
}
|
||||
|
||||
private func frequencyForKeyTag(_ tag: Int) -> Double? {
|
||||
var frequency: Double? = nil
|
||||
if tag >= 100 && tag < 200 { // is white note
|
||||
|
|
@ -199,4 +150,3 @@ class ToneGeneratorViewController: UIViewController {
|
|||
self.present(navigationController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class BluefruitPlaygroundUITests: XCTestCase {
|
|||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testSnapshots() {
|
||||
func testExample() {
|
||||
/*
|
||||
// UI tests must launch the application that they test.
|
||||
let app = XCUIApplication()
|
||||
|
|
@ -40,7 +40,7 @@ class BluefruitPlaygroundUITests: XCTestCase {
|
|||
let scrollViewsQuery = app.scrollViews
|
||||
let elementsQuery = scrollViewsQuery.otherElements
|
||||
|
||||
sleep(2) // Wait for the intro animation
|
||||
sleep(3) // Wait for the intro animation
|
||||
snapshot("01a_Welcome")
|
||||
|
||||
elementsQuery.buttons["LET'S GET STARTED..."].tap()
|
||||
|
|
@ -51,7 +51,7 @@ class BluefruitPlaygroundUITests: XCTestCase {
|
|||
elementsQuery.buttons["BEGIN PAIRING"].tap()
|
||||
|
||||
let tablesQuery = app.tables
|
||||
tablesQuery.staticTexts["Simulated Peripheral"].tap()
|
||||
tablesQuery/*@START_MENU_TOKEN@*/.staticTexts["Simulated Peripheral"]/*[[".cells.staticTexts[\"Simulated Peripheral\"]",".staticTexts[\"Simulated Peripheral\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap()
|
||||
|
||||
snapshot("02_Modules")
|
||||
|
||||
|
|
|
|||
16
Gemfile.lock
|
|
@ -1,7 +1,7 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.2)
|
||||
CFPropertyList (2.3.6)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
atomos (0.1.3)
|
||||
|
|
@ -18,7 +18,7 @@ GEM
|
|||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.5)
|
||||
emoji_regex (1.0.1)
|
||||
excon (0.71.1)
|
||||
excon (0.71.0)
|
||||
faraday (0.17.1)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-cookie_jar (0.0.6)
|
||||
|
|
@ -27,7 +27,7 @@ GEM
|
|||
faraday_middleware (0.13.1)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
fastimage (2.1.7)
|
||||
fastlane (2.138.0)
|
||||
fastlane (2.137.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
babosa (>= 1.0.2, < 2.0.0)
|
||||
|
|
@ -36,7 +36,7 @@ GEM
|
|||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 2.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
excon (>= 0.45.0, < 1.0.0)
|
||||
faraday (~> 0.17)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 0.13.1)
|
||||
|
|
@ -61,7 +61,7 @@ GEM
|
|||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcodeproj (>= 1.8.1, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
|
|
@ -93,7 +93,7 @@ GEM
|
|||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
json (2.3.0)
|
||||
json (2.1.0)
|
||||
jwt (2.1.0)
|
||||
memoist (0.16.2)
|
||||
mime-types (3.3)
|
||||
|
|
@ -121,7 +121,7 @@ GEM
|
|||
faraday (~> 0.9)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.7)
|
||||
simctl (1.6.6)
|
||||
CFPropertyList
|
||||
naturally
|
||||
slack-notifier (2.3.2)
|
||||
|
|
@ -138,7 +138,7 @@ GEM
|
|||
unf_ext (0.0.7.6)
|
||||
unicode-display_width (1.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.14.0)
|
||||
xcodeproj (1.12.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
|
|
|
|||
|
|
@ -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 8 Plus",
|
||||
"iPhone 8",
|
||||
|
||||
# "iPhone SE",
|
||||
"iPhone SE",
|
||||
|
||||
# "iPhone 6",
|
||||
# "iPhone 6 Plus",
|
||||
# "iPhone 5",
|
||||
|
|
@ -29,8 +29,7 @@ languages([
|
|||
# Where should the resulting screenshots be stored?
|
||||
# output_directory "./screenshots"
|
||||
|
||||
# remove the '#' to clear all previously generated screenshots before creating new ones
|
||||
clear_previous_screenshots true
|
||||
# clear_previous_screenshots true # remove the '#' to clear all previously generated screenshots before creating new ones
|
||||
|
||||
# Choose which project/workspace to use
|
||||
# project "./Project.xcodeproj"
|
||||
|
|
|
|||