Add mobile remote control for iOS

This commit is contained in:
Oleg Kalachev
2018-02-19 06:38:24 +03:00
parent 1db6453fd2
commit 431b97a011
27 changed files with 5556 additions and 2 deletions

17
apps/ios/.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
# Xcode
.DS_Store
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
!default.xcworkspace
xcuserdata
profile
*.moved-aside
DerivedData
Pods/

14
apps/ios/Podfile Normal file
View File

@@ -0,0 +1,14 @@
project 'cleverrc.xcodeproj/'
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'cleverrc' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
# Pods for cleverrc
pod 'SwiftSocket', '~> 2.0'
pod 'NotificationBannerSwift'
end

21
apps/ios/Podfile.lock Normal file
View File

@@ -0,0 +1,21 @@
PODS:
- MarqueeLabel/Swift (3.1.4)
- NotificationBannerSwift (1.5.4):
- MarqueeLabel/Swift
- SnapKit (~> 4.0)
- SnapKit (4.0.0)
- SwiftSocket (2.0.2)
DEPENDENCIES:
- NotificationBannerSwift
- SwiftSocket (~> 2.0)
SPEC CHECKSUMS:
MarqueeLabel: bf768455fe88d427f71476ebb23f9092b660f40b
NotificationBannerSwift: 4f6666c8421dcf11be0812dd1093d932c15921af
SnapKit: a42d492c16e80209130a3379f73596c3454b7694
SwiftSocket: 6f4c9c63fbc5c1d61188936bb3c599fd546f40ae
PODFILE CHECKSUM: fd5199f69c3ee8c1fbc0dd582477d890c8b2a24f
COCOAPODS: 1.4.0

View File

@@ -0,0 +1,444 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 48;
objects = {
/* Begin PBXBuildFile section */
7C0AB7AB202A744400BAED27 /* BinUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0AB7AA202A744400BAED27 /* BinUtils.swift */; };
7C51654120139237004D1F4D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C51654020139237004D1F4D /* AppDelegate.swift */; };
7C51654320139237004D1F4D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C51654220139237004D1F4D /* ViewController.swift */; };
7C51654620139237004D1F4D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7C51654420139237004D1F4D /* Main.storyboard */; };
7C51654820139237004D1F4D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7C51654720139237004D1F4D /* Assets.xcassets */; };
7C51654B20139237004D1F4D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7C51654920139237004D1F4D /* LaunchScreen.storyboard */; };
7C516553201526BA004D1F4D /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = 7C516552201526BA004D1F4D /* index.html */; };
7C51655520153180004D1F4D /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 7C51655420153180004D1F4D /* main.js */; };
7CA401E22033CE17009FAA3B /* main.css in Resources */ = {isa = PBXBuildFile; fileRef = 7CA401E12033CE17009FAA3B /* main.css */; };
7CA401E42033FA34009FAA3B /* telemetry.js in Resources */ = {isa = PBXBuildFile; fileRef = 7CA401E32033FA34009FAA3B /* telemetry.js */; };
7CA401E6203471D9009FAA3B /* clever.svg in Resources */ = {isa = PBXBuildFile; fileRef = 7CA401E5203471D8009FAA3B /* clever.svg */; };
C25141CAF1A7125F3CE29DDC /* Pods_cleverrc.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C38C04523251039FF13DDCD /* Pods_cleverrc.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
5C38C04523251039FF13DDCD /* Pods_cleverrc.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_cleverrc.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7C0AB7AA202A744400BAED27 /* BinUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinUtils.swift; sourceTree = "<group>"; };
7C45DCE9203A75A2009C73F5 /* roslib.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = roslib.js; sourceTree = "<group>"; };
7C51653D20139237004D1F4D /* cleverrc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cleverrc.app; sourceTree = BUILT_PRODUCTS_DIR; };
7C51654020139237004D1F4D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7C51654220139237004D1F4D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
7C51654520139237004D1F4D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
7C51654720139237004D1F4D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
7C51654A20139237004D1F4D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
7C51654C20139237004D1F4D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7C516552201526BA004D1F4D /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = index.html; sourceTree = "<group>"; };
7C51655420153180004D1F4D /* main.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = "<group>"; };
7CA401E12033CE17009FAA3B /* main.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = main.css; sourceTree = "<group>"; };
7CA401E32033FA34009FAA3B /* telemetry.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = telemetry.js; sourceTree = "<group>"; };
7CA401E5203471D8009FAA3B /* clever.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = clever.svg; sourceTree = "<group>"; };
AAC9195BF3A9BF6942EF4D0B /* Pods-cleverrc.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-cleverrc.release.xcconfig"; path = "Pods/Target Support Files/Pods-cleverrc/Pods-cleverrc.release.xcconfig"; sourceTree = "<group>"; };
CB200F4B933204EA97E0E2E4 /* Pods-cleverrc.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-cleverrc.debug.xcconfig"; path = "Pods/Target Support Files/Pods-cleverrc/Pods-cleverrc.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
7C51653A20139237004D1F4D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C25141CAF1A7125F3CE29DDC /* Pods_cleverrc.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
4FA3968F2242239E15A656D2 /* Pods */ = {
isa = PBXGroup;
children = (
CB200F4B933204EA97E0E2E4 /* Pods-cleverrc.debug.xcconfig */,
AAC9195BF3A9BF6942EF4D0B /* Pods-cleverrc.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
66C638F0021EBE07741B26F3 /* Frameworks */ = {
isa = PBXGroup;
children = (
5C38C04523251039FF13DDCD /* Pods_cleverrc.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
7C51653420139237004D1F4D = {
isa = PBXGroup;
children = (
66C638F0021EBE07741B26F3 /* Frameworks */,
4FA3968F2242239E15A656D2 /* Pods */,
7C51653E20139237004D1F4D /* Products */,
7C51653F20139237004D1F4D /* cleverrc */,
);
sourceTree = "<group>";
};
7C51653E20139237004D1F4D /* Products */ = {
isa = PBXGroup;
children = (
7C51653D20139237004D1F4D /* cleverrc.app */,
);
name = Products;
sourceTree = "<group>";
};
7C51653F20139237004D1F4D /* cleverrc */ = {
isa = PBXGroup;
children = (
7C45DCE9203A75A2009C73F5 /* roslib.js */,
7C51654020139237004D1F4D /* AppDelegate.swift */,
7C51654720139237004D1F4D /* Assets.xcassets */,
7C0AB7AA202A744400BAED27 /* BinUtils.swift */,
7C51654C20139237004D1F4D /* Info.plist */,
7C51654920139237004D1F4D /* LaunchScreen.storyboard */,
7C51654420139237004D1F4D /* Main.storyboard */,
7C51654220139237004D1F4D /* ViewController.swift */,
7CA401E5203471D8009FAA3B /* clever.svg */,
7C516552201526BA004D1F4D /* index.html */,
7CA401E12033CE17009FAA3B /* main.css */,
7C51655420153180004D1F4D /* main.js */,
7CA401E32033FA34009FAA3B /* telemetry.js */,
);
path = cleverrc;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
7C51653C20139237004D1F4D /* cleverrc */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7C51654F20139237004D1F4D /* Build configuration list for PBXNativeTarget "cleverrc" */;
buildPhases = (
9F096121C4A02BCE9D4FD1B9 /* [CP] Check Pods Manifest.lock */,
7C51653920139237004D1F4D /* Sources */,
7C51653A20139237004D1F4D /* Frameworks */,
7C51653B20139237004D1F4D /* Resources */,
A37DBBAD5E44E632F8A8A204 /* [CP] Embed Pods Frameworks */,
9BAB41D26FC0095C7C86B9DE /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = cleverrc;
productName = cleverrc;
productReference = 7C51653D20139237004D1F4D /* cleverrc.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
7C51653520139237004D1F4D /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0920;
ORGANIZATIONNAME = "Copter Express";
TargetAttributes = {
7C51653C20139237004D1F4D = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 7C51653820139237004D1F4D /* Build configuration list for PBXProject "cleverrc" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 7C51653420139237004D1F4D;
productRefGroup = 7C51653E20139237004D1F4D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
7C51653C20139237004D1F4D /* cleverrc */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
7C51653B20139237004D1F4D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7C51654B20139237004D1F4D /* LaunchScreen.storyboard in Resources */,
7CA401E6203471D9009FAA3B /* clever.svg in Resources */,
7CA401E42033FA34009FAA3B /* telemetry.js in Resources */,
7C516553201526BA004D1F4D /* index.html in Resources */,
7C51654820139237004D1F4D /* Assets.xcassets in Resources */,
7CA401E22033CE17009FAA3B /* main.css in Resources */,
7C51654620139237004D1F4D /* Main.storyboard in Resources */,
7C51655520153180004D1F4D /* main.js in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
9BAB41D26FC0095C7C86B9DE /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-cleverrc/Pods-cleverrc-resources.sh\"\n";
showEnvVarsInLog = 0;
};
9F096121C4A02BCE9D4FD1B9 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-cleverrc-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
A37DBBAD5E44E632F8A8A204 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-cleverrc/Pods-cleverrc-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework",
"${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework",
"${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework",
"${BUILT_PRODUCTS_DIR}/SwiftSocket/SwiftSocket.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftSocket.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-cleverrc/Pods-cleverrc-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
7C51653920139237004D1F4D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7C51654320139237004D1F4D /* ViewController.swift in Sources */,
7C51654120139237004D1F4D /* AppDelegate.swift in Sources */,
7C0AB7AB202A744400BAED27 /* BinUtils.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
7C51654420139237004D1F4D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
7C51654520139237004D1F4D /* Base */,
);
name = Main.storyboard;
path = .;
sourceTree = "<group>";
};
7C51654920139237004D1F4D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
7C51654A20139237004D1F4D /* Base */,
);
name = LaunchScreen.storyboard;
path = .;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
7C51654D20139237004D1F4D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.2;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
7C51654E20139237004D1F4D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.2;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
7C51655020139237004D1F4D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = CB200F4B933204EA97E0E2E4 /* Pods-cleverrc.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 7QY6KJ2672;
INFOPLIST_FILE = cleverrc/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = coex.cleverrc;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
7C51655120139237004D1F4D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AAC9195BF3A9BF6942EF4D0B /* Pods-cleverrc.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 7QY6KJ2672;
INFOPLIST_FILE = cleverrc/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = coex.cleverrc;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
7C51653820139237004D1F4D /* Build configuration list for PBXProject "cleverrc" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7C51654D20139237004D1F4D /* Debug */,
7C51654E20139237004D1F4D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7C51654F20139237004D1F4D /* Build configuration list for PBXNativeTarget "cleverrc" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7C51655020139237004D1F4D /* Debug */,
7C51655120139237004D1F4D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 7C51653520139237004D1F4D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:cleverrc.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:cleverrc.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,46 @@
//
// AppDelegate.swift
// cleverrc
//
// Created by Oleg Kalachev on 20.01.2018.
// Copyright © 2018 Copter Express. All rights reserved.
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
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:.
}
}

View File

@@ -0,0 +1,100 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "cleverios180.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "cleverios180-1.png",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

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

View File

@@ -0,0 +1,20 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.10586584359407425" green="0.10589186102151871" blue="0.10586420446634293" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="landscape">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="cleverrc" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<wkWebView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dtJ-LN-BYT">
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
<color key="backgroundColor" red="0.12939286231994629" green="0.12942266464233398" blue="0.12939092516899109" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
</wkWebView>
</subviews>
<color key="backgroundColor" red="0.1097869947552681" green="0.10981365293264389" blue="0.10978532582521439" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="dtJ-LN-BYT" firstAttribute="height" secondItem="8bC-Xf-vdC" secondAttribute="height" id="KRn-ag-67x"/>
<constraint firstItem="dtJ-LN-BYT" firstAttribute="width" secondItem="8bC-Xf-vdC" secondAttribute="width" id="qOr-gg-e4M"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
<connections>
<outlet property="webView" destination="dtJ-LN-BYT" id="bQ3-jy-o6E"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="138.0809595202399" y="124"/>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,453 @@
//
// BinUtils.swift
// BinUtils
//
// Created by Nicolas Seriot on 12/03/16.
// Copyright © 2016 Nicolas Seriot. All rights reserved.
//
import Foundation
import CoreFoundation
// MARK: protocol UnpackedType
public protocol Unpackable {}
extension NSString: Unpackable {}
extension Bool: Unpackable {}
extension Int: Unpackable {}
extension Double: Unpackable {}
// MARK: protocol DataConvertible
protocol DataConvertible {}
extension DataConvertible {
init?(data: Data) {
guard data.count == MemoryLayout<Self>.size else { return nil }
self = data.withUnsafeBytes { $0.pointee }
}
init?(bytes: [UInt8]) {
let data = Data(bytes:bytes)
self.init(data:data)
}
var data: Data {
var value = self
return Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
}
extension Bool : DataConvertible { }
extension Int8 : DataConvertible { }
extension Int16 : DataConvertible { }
extension Int32 : DataConvertible { }
extension Int64 : DataConvertible { }
extension UInt8 : DataConvertible { }
extension UInt16 : DataConvertible { }
extension UInt32 : DataConvertible { }
extension UInt64 : DataConvertible { }
extension Float32 : DataConvertible { }
extension Float64 : DataConvertible { }
// MARK: String extension
extension String {
subscript (from:Int, to:Int) -> String {
return NSString(string: self).substring(with: NSMakeRange(from, to-from))
}
}
// MARK: Data extension
extension Data {
var bytes : [UInt8] {
return self.withUnsafeBytes {
[UInt8](UnsafeBufferPointer(start: $0, count: self.count))
}
}
}
// MARK: functions
public func hexlify(_ data:Data) -> String {
// similar to hexlify() in Python's binascii module
// https://docs.python.org/2/library/binascii.html
var s = String()
var byte: UInt8 = 0
for i in 0 ..< data.count {
NSData(data: data).getBytes(&byte, range: NSMakeRange(i, 1))
s = s.appendingFormat("%02x", byte)
}
return s as String
}
public func unhexlify(_ string:String) -> Data? {
// similar to unhexlify() in Python's binascii module
// https://docs.python.org/2/library/binascii.html
let s = string.uppercased().replacingOccurrences(of: " ", with: "")
let nonHexCharacterSet = CharacterSet(charactersIn: "0123456789ABCDEF").inverted
if let range = s.rangeOfCharacter(from: nonHexCharacterSet) {
print("-- found non hex character at range \(range)")
return nil
}
var data = Data(capacity: s.count / 2)
for i in stride(from: 0, to:s.count, by:2) {
let byteString = s[i, i+2]
let byte = UInt8(byteString.withCString { strtoul($0, nil, 16) })
data.append([byte] as [UInt8], count: 1)
}
return data
}
func readIntegerType<T:DataConvertible>(_ type:T.Type, bytes:[UInt8], loc:inout Int) -> T {
let size = MemoryLayout<T>.size
let sub = Array(bytes[loc..<(loc+size)])
loc += size
return T(bytes: sub)!
}
func readFloatingPointType<T:DataConvertible>(_ type:T.Type, bytes:[UInt8], loc:inout Int, isBigEndian:Bool) -> T {
let size = MemoryLayout<T>.size
let sub = Array(bytes[loc..<(loc+size)])
loc += size
let sub_ = isBigEndian ? sub.reversed() : sub
return T(bytes: sub_)!
}
func isBigEndianFromMandatoryByteOrderFirstCharacter(_ format:String) -> Bool {
guard let firstChar = format.first else { assertionFailure("empty format"); return false }
let s = NSString(string: String(firstChar))
let c = s.substring(to: 1)
if c == "@" { assertionFailure("native size and alignment is unsupported") }
if c == "=" || c == "<" { return false }
if c == ">" || c == "!" { return true }
assertionFailure("format '\(format)' first character must be among '=<>!'")
return false
}
// akin to struct.calcsize(fmt)
func numberOfBytesInFormat(_ format:String) -> Int {
var numberOfBytes = 0
var n = 0 // repeat counter
var mutableFormat = format
while !mutableFormat.isEmpty {
let c = mutableFormat.remove(at: mutableFormat.startIndex)
if let i = Int(String(c)) , 0...9 ~= i {
if n > 0 { n *= 10 }
n += i
continue
}
if c == "s" {
numberOfBytes += max(n,1)
n = 0
continue
}
for _ in 0..<max(n,1) {
switch(c) {
case "@", "<", "=", ">", "!", " ":
()
case "c", "b", "B", "x", "?":
numberOfBytes += 1
case "h", "H":
numberOfBytes += 2
case "i", "l", "I", "L", "f":
numberOfBytes += 4
case "q", "Q", "d":
numberOfBytes += 8
case "P":
numberOfBytes += MemoryLayout<Int>.size
default:
assertionFailure("-- unsupported format \(c)")
}
}
n = 0
}
return numberOfBytes
}
func formatDoesMatchDataLength(_ format:String, data:Data) -> Bool {
let sizeAccordingToFormat = numberOfBytesInFormat(format)
let dataLength = data.count
if sizeAccordingToFormat != dataLength {
print("format \"\(format)\" expects \(sizeAccordingToFormat) bytes but data is \(dataLength) bytes")
return false
}
return true
}
/*
pack() and unpack() should behave as Python's struct module https://docs.python.org/2/library/struct.html BUT:
- native size and alignment '@' is not supported
- as a consequence, the byte order specifier character is mandatory and must be among "=<>!"
- native byte order '=' assumes a little-endian system (eg. Intel x86)
- Pascal strings 'p' and native pointers 'P' are not supported
*/
public enum BinUtilsError: Error {
case formatDoesMatchDataLength(format:String, dataSize:Int)
case unsupportedFormat(character:Character)
}
public func pack(_ format:String, _ objects:[Any], _ stringEncoding:String.Encoding=String.Encoding.windowsCP1252) -> Data {
var objectsQueue = objects
var mutableFormat = format
var mutableData = Data()
var isBigEndian = false
let firstCharacter = mutableFormat.remove(at: mutableFormat.startIndex)
switch(firstCharacter) {
case "<", "=":
isBigEndian = false
case ">", "!":
isBigEndian = true
case "@":
assertionFailure("native size and alignment '@' is unsupported'")
default:
assertionFailure("unsupported format chacracter'")
}
var n = 0 // repeat counter
while !mutableFormat.isEmpty {
let c = mutableFormat.remove(at: mutableFormat.startIndex)
if let i = Int(String(c)) , 0...9 ~= i {
if n > 0 { n *= 10 }
n += i
continue
}
var o : Any = 0
if c == "s" {
o = objectsQueue.remove(at: 0)
guard let stringData = (o as! String).data(using: .utf8) else { assertionFailure(); return Data() }
var bytes = stringData.bytes
let expectedSize = max(1, n)
// pad ...
while bytes.count < expectedSize { bytes.append(0x00) }
// ... or trunk
if bytes.count > expectedSize { bytes = Array(bytes[0..<expectedSize]) }
assert(bytes.count == expectedSize)
if isBigEndian { bytes = bytes.reversed() }
mutableData.append(bytes, count: bytes.count)
n = 0
continue
}
for _ in 0..<max(n,1) {
var bytes : [UInt8] = []
if c != "x" {
o = objectsQueue.removeFirst()
}
switch(c) {
case "?":
bytes = (o as! Bool) ? [0x01] : [0x00]
case "c":
let charAsString = (o as! NSString).substring(to: 1)
guard let data = charAsString.data(using: stringEncoding) else {
assertionFailure("cannot decode character \(charAsString) using encoding \(stringEncoding)")
return Data()
}
bytes = data.bytes
case "b":
bytes = Int8(truncatingIfNeeded:o as! Int).data.bytes
case "h":
bytes = Int16(truncatingIfNeeded:o as! Int).data.bytes
case "i", "l":
bytes = Int32(truncatingIfNeeded:o as! Int).data.bytes
case "q", "Q":
bytes = Int64(o as! Int).data.bytes
case "B":
bytes = UInt8(truncatingIfNeeded:o as! Int).data.bytes
case "H":
bytes = UInt16(truncatingIfNeeded:o as! Int).data.bytes
case "I", "L":
bytes = UInt32(truncatingIfNeeded:o as! Int).data.bytes
case "f":
bytes = Float32(o as! Double).data.bytes
case "d":
bytes = Float64(o as! Double).data.bytes
case "x":
bytes = [0x00]
default:
assertionFailure("Unsupported packing format: \(c)")
}
if isBigEndian { bytes = bytes.reversed() }
let data = Data(bytes)
mutableData.append(data)
}
n = 0
}
return mutableData
}
public func unpack(_ format:String, _ data:Data, _ stringEncoding:String.Encoding=String.Encoding.windowsCP1252) throws -> [Unpackable] {
assert(CFByteOrderGetCurrent() == 1 /* CFByteOrderLittleEndian */, "\(#file) assumes little endian, but host is big endian")
let isBigEndian = isBigEndianFromMandatoryByteOrderFirstCharacter(format)
if formatDoesMatchDataLength(format, data: data) == false {
throw BinUtilsError.formatDoesMatchDataLength(format:format, dataSize:data.count)
}
var a : [Unpackable] = []
var loc = 0
let bytes = data.bytes
var n = 0 // repeat counter
var mutableFormat = format
mutableFormat.remove(at: mutableFormat.startIndex) // consume byte-order specifier
while !mutableFormat.isEmpty {
let c = mutableFormat.remove(at: mutableFormat.startIndex)
if let i = Int(String(c)) , 0...9 ~= i {
if n > 0 { n *= 10 }
n += i
continue
}
if c == "s" {
let length = max(n,1)
let sub = Array(bytes[loc..<loc+length])
guard let s = NSString(bytes: sub, length: length, encoding: stringEncoding.rawValue) else {
assertionFailure("-- not a string: \(sub)")
return []
}
a.append(s)
loc += length
n = 0
continue
}
for _ in 0..<max(n,1) {
var o : Unpackable?
switch(c) {
case "c":
let optionalString = NSString(bytes: [bytes[loc]], length: 1, encoding: String.Encoding.utf8.rawValue)
loc += 1
guard let s = optionalString else { assertionFailure(); return [] }
o = s
case "b":
let r = readIntegerType(Int8.self, bytes:bytes, loc:&loc)
o = Int(r)
case "B":
let r = readIntegerType(UInt8.self, bytes:bytes, loc:&loc)
o = Int(r)
case "?":
let r = readIntegerType(Bool.self, bytes:bytes, loc:&loc)
o = r ? true : false
case "h":
let r = readIntegerType(Int16.self, bytes:bytes, loc:&loc)
o = Int(isBigEndian ? Int16(bigEndian: r) : r)
case "H":
let r = readIntegerType(UInt16.self, bytes:bytes, loc:&loc)
o = Int(isBigEndian ? UInt16(bigEndian: r) : r)
case "i":
fallthrough
case "l":
let r = readIntegerType(Int32.self, bytes:bytes, loc:&loc)
o = Int(isBigEndian ? Int32(bigEndian: r) : r)
case "I":
fallthrough
case "L":
let r = readIntegerType(UInt32.self, bytes:bytes, loc:&loc)
o = Int(isBigEndian ? UInt32(bigEndian: r) : r)
case "q":
let r = readIntegerType(Int64.self, bytes:bytes, loc:&loc)
o = Int(isBigEndian ? Int64(bigEndian: r) : r)
case "Q":
let r = readIntegerType(UInt64.self, bytes:bytes, loc:&loc)
o = Int(isBigEndian ? UInt64(bigEndian: r) : r)
case "f":
let r = readFloatingPointType(Float32.self, bytes:bytes, loc:&loc, isBigEndian:isBigEndian)
o = Double(r)
case "d":
let r = readFloatingPointType(Float64.self, bytes:bytes, loc:&loc, isBigEndian:isBigEndian)
o = Double(r)
case "x":
loc += 1
case " ":
()
default:
throw BinUtilsError.unsupportedFormat(character:c)
}
if let o = o { a.append(o) }
}
n = 0
}
return a
}

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Clever RC</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,70 @@
//
// ViewController.swift
// cleverrc
//
// Created by Oleg Kalachev on 20.01.2018.
// Copyright © 2018 Copter Express. All rights reserved.
//
import UIKit
import WebKit
import SwiftSocket
import NotificationBannerSwift
class ViewController: UIViewController, WKScriptMessageHandler {
@IBOutlet weak var webView: WKWebView!
let impactGenerator = UIImpactFeedbackGenerator(style: .medium)
let notificationGenerator = UINotificationFeedbackGenerator()
let udpSocket = UDPClient(address:"255.255.255.255", port: 35602)
override func viewDidLoad() {
super.viewDidLoad()
// Don't lock screen
UIApplication.shared.isIdleTimerDisabled = true
// Setup webview event handlers
webView.configuration.userContentController.add(self, name: "control")
webView.configuration.userContentController.add(self, name: "controlStart")
webView.configuration.userContentController.add(self, name: "lowBattery")
webView.configuration.userContentController.add(self, name: "notification")
// Load the main page
let url = Bundle.main.url(forResource: "index", withExtension: "html")
let requestObj = URLRequest(url: url!)
webView.load(requestObj)
// Setup UDP broadcasting
udpSocket.enableBroadcast()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if (message.name == "control") {
// Send UDP control message
let m = message.body as! NSDictionary;
let d = pack("<hhhh", [m["x"]!, m["y"]!, m["z"]!, m["r"]!])
_ = udpSocket.send(data: d)
} else if (message.name == "lowBattery") {
// Got low battery notification
print("Low battery notification")
notificationGenerator.notificationOccurred(.warning)
} else if (message.name == "notification") {
// Got notification message
print(message)
let m = message.body as! NSDictionary;
let level = m["level"] as! Int
if level == 4 {
let banner = NotificationBanner(title: m["msg"] as! String, style: .warning)
banner.show()
} else {
let banner = NotificationBanner(title: m["msg"] as! String, style: .danger)
banner.show()
}
}
}
}

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 52.5 52.5" enable-background="new 0 0 52.5 52.5" xml:space="preserve">
<g>
<path fill="white" fill-opacity="0.5" d="M35.7,17.2c0.3-0.3,0.5-0.7,0.7-1.1c0.2-0.4,0.3-0.9,0.3-1.4c0-0.5-0.1-1-0.3-1.4c-0.2-0.5-0.5-0.9-0.8-1.3
c-0.4-0.4-0.9-0.7-1.4-0.9c-0.6-0.2-1.2-0.3-1.8-0.2c-0.5,0.1-0.9,0.2-1.3,0.4c-0.2,0.1-0.4,0.3-0.6,0.4L30.2,12
c-0.1,0.1-0.1,0.1-0.2,0.1c-0.1,0.1-0.3,0.2-0.4,0.2c-0.1,0.1-0.3,0.1-0.4,0.2c-0.1,0-0.2,0.1-0.4,0.1c-0.1,0-0.2,0-0.3,0
c-0.6,0.1-1.3-0.1-1.9-0.4c-0.2-0.1-0.4-0.2-0.6-0.4c0,0-0.1-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1c0,0,0,0-0.1-0.1
c-0.1-0.1-0.1-0.2-0.1-0.3c0,0,0,0,0-0.1l0-6.5c-0.5-0.6-1.1-1.1-1.7-1.6c-0.9-0.7-1.9-1.4-3-1.8c-0.9-0.4-1.8-0.7-2.7-1
c-0.9-0.2-1.7-0.3-2.6-0.4c-0.9-0.1-1.8,0-2.7,0.1c-0.7,0.1-1.5,0.2-2.2,0.5C9.4,0.9,8.1,1.5,7,2.2C6.2,2.7,5.4,3.3,4.7,3.9
C4,4.6,3.4,5.3,2.8,6c-0.5,0.7-1,1.4-1.3,2.2C1.1,9,0.8,9.8,0.6,10.6c-0.2,0.7-0.4,1.4-0.4,2.1C0,13.7,0,14.6,0,15.6
c0,0.8,0.2,1.7,0.4,2.5c0.2,0.9,0.5,1.7,0.8,2.5C1.8,21.8,2.5,23,3.4,24c0.3,0.4,0.7,0.8,1,1.1c0.2,0.2,0.3,0.3,0.5,0.5H11
c0.1-0.1,0.1-0.2,0.1-0.2c0-0.1,0.1-0.1,0.1-0.2c0-0.1,0.1-0.1,0.1-0.2c0.1-0.4,0.2-0.8,0.1-1.1c0-0.1,0-0.1,0-0.2
c0-0.1-0.1-0.1-0.1-0.2c0-0.1-0.1-0.2-0.2-0.2c0,0-0.1-0.1-0.1-0.1l0,0c0,0,0-0.1-0.1-0.1c0,0,0,0,0,0l-0.1-0.1
c-0.1-0.1-0.2-0.3-0.3-0.4c-0.2-0.3-0.4-0.7-0.5-1c-0.1-0.3-0.2-0.7-0.3-1c-0.1-0.6-0.1-1.1,0-1.7c0.1-0.5,0.3-1,0.5-1.5
c0.3-0.5,0.6-0.9,1-1.3c0.4-0.4,0.9-0.7,1.4-0.9c0.6-0.2,1.2-0.4,1.8-0.4c0.7,0,1.3,0.1,1.9,0.3c0.7,0.2,1.3,0.6,1.8,1.1
c0.5,0.5,1,1.1,1.3,1.8c0.2,0.4,0.3,0.7,0.3,1.1c0.1,0.4,0.1,0.8,0.1,1.3c0,0.4-0.1,0.8-0.2,1.1c-0.1,0.3-0.3,0.7-0.4,1
c-0.1,0.2-0.2,0.3-0.3,0.5c0,0.1-0.1,0.1-0.1,0.2l0,0l0,0l0,0.1c0,0,0,0.1-0.1,0.1l-0.1,0.1c0,0-0.1,0.1-0.1,0.1
c-0.1,0.1-0.1,0.1-0.1,0.2c0,0.1-0.1,0.1-0.1,0.2c0,0.1,0,0.1,0,0.2c-0.1,0.3-0.1,0.7,0.1,1.1c0,0.1,0,0.1,0.1,0.2
c0,0.1,0.1,0.1,0.1,0.2c0,0.1,0.1,0.2,0.2,0.3h7.2l0-7.5c0,0,0,0,0-0.1c0-0.1,0-0.1,0.1-0.2c0-0.1,0.1-0.1,0.1-0.2
c0,0,0.1-0.1,0.1-0.1l0.1,0c0.1-0.1,0.2-0.1,0.3-0.2c0.5-0.3,1-0.5,1.6-0.6c0.2,0,0.3,0,0.5,0c0,0,0.1,0,0.1,0c0.1,0,0.1,0,0.2,0
c0.1,0,0.2,0,0.4,0.1c0.3,0.1,0.7,0.2,0.9,0.4c0.1,0.1,0.2,0.1,0.3,0.2c0,0,0.1,0.1,0.1,0.1l0,0l-0.1,0.1c0,0,0,0,0,0l0.1-0.1
l0.2,0.2c0.1,0.1,0.2,0.2,0.4,0.2c0.2,0.1,0.5,0.3,0.8,0.4c0.7,0.2,1.4,0.3,2,0.1c0.4-0.1,0.7-0.2,1.1-0.4
C35.1,17.7,35.4,17.5,35.7,17.2z"/>
<g>
<polygon points="41.6,29.7 41.6,29.7 41.6,29.6 41.6,29.6 "/>
<path fill="white" fill-opacity="0.5" d="M28.6,49.2c0.9,0.8,2,1.4,3,1.9c0.8,0.4,1.7,0.7,2.6,0.9c0.9,0.2,1.8,0.4,2.7,0.4c0.9,0.1,1.9,0,2.8-0.1
c0.7-0.1,1.4-0.2,2.1-0.4c1.3-0.4,2.5-0.9,3.6-1.6c0.8-0.5,1.6-1.1,2.3-1.7c0.7-0.7,1.4-1.4,2-2.2c0.5-0.6,0.9-1.3,1.2-2
c0.4-0.8,0.7-1.7,1-2.5c0.2-0.6,0.3-1.3,0.4-1.9c0.1-1,0.2-2,0.1-3c0-0.8-0.2-1.6-0.3-2.4c-0.2-0.9-0.5-1.7-0.8-2.6
c-0.5-1.2-1.2-2.4-2.1-3.4c-0.3-0.4-0.6-0.8-1-1.1l-0.5-0.5h-6.1l-0.2,0.3c0,0.1-0.1,0.1-0.1,0.2c0,0.1-0.1,0.1-0.1,0.2
c-0.1,0.4-0.2,0.8-0.1,1.1c0,0.1,0,0.1,0,0.2c0,0.1,0.1,0.1,0.1,0.2c0,0.1,0.1,0.2,0.2,0.3c0,0,0.1,0.1,0.1,0.1l0,0
c0,0,0,0.1,0.1,0.1l0,0.1l0.1-0.1l0,0l0,0l0,0l0,0l0,0l0.1,0.1c0.2,0.2,0.3,0.4,0.5,0.7c0.3,0.5,0.5,1,0.6,1.5
c0.1,0.7,0.1,1.5,0,2.2c-0.2,0.6-0.4,1.2-0.8,1.8c-0.3,0.4-0.7,0.8-1.1,1.1c-0.4,0.3-0.8,0.5-1.3,0.7c-0.5,0.2-1,0.3-1.5,0.3
c-0.5,0-0.9,0-1.3-0.1c-0.5-0.1-1-0.3-1.4-0.6c-0.5-0.3-1-0.7-1.3-1.1c-0.4-0.5-0.7-1-0.9-1.6c-0.2-0.6-0.3-1.3-0.2-2
c0-0.4,0.1-0.7,0.2-1c0.1-0.4,0.3-0.8,0.5-1.1c0.1-0.2,0.2-0.3,0.3-0.5c0-0.1,0.1-0.1,0.1-0.2l0.1,0c0,0,0,0,0,0l-0.1-0.1l0.1-0.1
c0,0,0-0.1,0.1-0.1l0.1-0.1c0,0,0.1-0.1,0.1-0.1c0-0.1,0.1-0.1,0.1-0.2c0-0.1,0.1-0.1,0.1-0.2c0-0.1,0-0.1,0-0.2
c0.1-0.3,0.1-0.7-0.1-1.1c0-0.1,0-0.1-0.1-0.2c0-0.1-0.1-0.2-0.1-0.2c0-0.1-0.1-0.2-0.1-0.2h-7.2l0,7.5c0,0,0,0,0,0.1
c0,0.1-0.1,0.2-0.1,0.3c0,0,0,0.1-0.1,0.1c0,0-0.1,0.1-0.1,0.1c0,0,0,0-0.1,0c-0.1,0.1-0.3,0.2-0.4,0.3c-0.6,0.4-1.3,0.6-1.9,0.6
c-0.1,0-0.2,0-0.3,0c-0.1,0-0.2,0-0.3,0c-0.4-0.1-0.7-0.2-1-0.4c-0.1-0.1-0.2-0.2-0.3-0.2c0,0-0.1-0.1-0.1-0.1h0l0,0l0,0
c0,0-0.1-0.1-0.1-0.1L22,34.8c-0.2-0.2-0.4-0.3-0.6-0.4c-0.4-0.2-0.9-0.3-1.3-0.4c-0.3,0-0.7,0-1,0.1c-0.3,0.1-0.5,0.1-0.8,0.2
c-0.5,0.2-1,0.5-1.3,0.9c-0.3,0.4-0.6,0.8-0.8,1.3C16,37,15.9,37.5,15.9,38c0,0.5,0.1,0.9,0.3,1.3c0.2,0.4,0.4,0.7,0.7,1
c0.3,0.3,0.6,0.5,1,0.7c0.3,0.2,0.7,0.3,1,0.4c0.4,0.1,0.9,0.1,1.4,0c0.2,0,0.5-0.1,0.7-0.2c0.3-0.1,0.5-0.2,0.7-0.4
c0.1-0.1,0.2-0.2,0.3-0.3l0.1-0.1l0,0l0,0c0,0,0,0,0,0l0.1-0.1c0.1,0,0.1-0.1,0.2-0.1c0.1-0.1,0.2-0.2,0.3-0.2
c0.3-0.2,0.6-0.3,0.9-0.3c0.2,0,0.3,0,0.5,0h0.1c0.2,0,0.3,0,0.5,0c0.5,0.1,1,0.3,1.5,0.6c0.1,0.1,0.2,0.1,0.3,0.2l0.1,0
c0,0,0.1,0.1,0.1,0.1c0,0.1,0.1,0.1,0.1,0.2c0,0.1,0,0.1,0.1,0.2c0,0,0,0,0,0.1l0,0v6.5C27.4,48.2,28,48.7,28.6,49.2z"/>
<polygon points="22.3,34.9 22.3,34.9 22.3,34.9 22.3,34.9 "/>
</g>
<g>
<polygon points="40.7,30.2 40.7,30.2 40.7,30.2 "/>
<path fill="white" fill-opacity="0.5" d="M49.2,24c0.7-0.9,1.3-1.8,1.8-2.9c0.4-0.7,0.7-1.5,0.9-2.3c0.2-0.8,0.4-1.7,0.5-2.6c0.2-1.7,0.1-3.4-0.3-5
c-0.3-1.3-0.8-2.6-1.5-3.8c-0.4-0.7-0.9-1.4-1.4-2.1c-0.6-0.7-1.3-1.4-2-2c-0.6-0.5-1.3-1-2-1.4c-0.8-0.4-1.5-0.8-2.3-1.1
c-1-0.4-2-0.6-3.1-0.7C39.3,0.1,38.9,0,38.5,0c-0.2,0-0.4,0-0.6,0h-0.1c-0.5,0-0.9,0-1.4,0.1c-0.8,0.1-1.7,0.2-2.5,0.4
c-0.9,0.2-1.7,0.6-2.5,0.9c-0.8,0.4-1.5,0.8-2.2,1.3c-0.6,0.4-1.1,0.9-1.6,1.4c-0.2,0.2-0.5,0.5-0.7,0.7V11
c0.1,0.1,0.2,0.1,0.2,0.1c0.1,0,0.1,0.1,0.2,0.1c0.1,0,0.1,0.1,0.2,0.1c0.4,0.1,0.8,0.2,1.1,0.1c0.1,0,0.1,0,0.2,0
c0.1,0,0.1-0.1,0.2-0.1c0.1,0,0.2-0.1,0.3-0.2c0,0,0.1-0.1,0.1-0.1l0,0c0,0,0.1,0,0.1-0.1l0.1-0.1c0.2-0.1,0.3-0.2,0.5-0.3
c0.3-0.2,0.7-0.4,1-0.5c0.3-0.1,0.6-0.2,1-0.2c0.6-0.1,1.1-0.1,1.7,0c0.5,0.1,1,0.3,1.5,0.5c0.5,0.3,0.9,0.6,1.3,1
c0.4,0.4,0.7,0.9,0.9,1.4c0.2,0.6,0.4,1.2,0.4,1.8c0,0.6-0.1,1.3-0.3,1.9c-0.2,0.7-0.6,1.3-1,1.8c-0.5,0.5-1.1,1-1.8,1.3
c-0.4,0.1-0.7,0.3-1.1,0.3c-0.4,0.1-0.8,0.1-1.3,0.1c-0.4,0-0.7-0.1-1.1-0.2c-0.4-0.1-0.7-0.3-1-0.5c-0.2-0.1-0.3-0.2-0.5-0.3
c-0.1,0-0.1-0.1-0.2-0.1l-0.1-0.1c0,0-0.1,0-0.1-0.1c0,0-0.1-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1c-0.1-0.1-0.1-0.1-0.2-0.1
c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1,0-0.2,0C28.4,18,28,18,27.6,18.1c-0.1,0-0.1,0-0.2,0.1c-0.1,0-0.2,0.1-0.2,0.1
c-0.1,0-0.2,0.1-0.2,0.1v7.2l7.5,0c0,0,0,0,0.1,0c0.1,0,0.2,0.1,0.3,0.1c0,0,0.1,0,0.1,0.1c0,0,0.1,0.1,0.1,0.1l0.1,0.1
c0.1,0.1,0.2,0.3,0.3,0.4c0.4,0.6,0.6,1.3,0.6,1.9c0,0.1,0,0.2,0,0.3c0,0.1,0,0.2,0,0.3c-0.1,0.4-0.2,0.7-0.4,1
c-0.1,0.1-0.2,0.2-0.2,0.3c0,0-0.1,0.1-0.1,0.1l0,0l0,0l-0.2,0.2c-0.1,0.1-0.2,0.2-0.2,0.3c-0.1,0.2-0.3,0.5-0.3,0.7
c-0.2,0.7-0.3,1.4-0.1,2c0.1,0.4,0.2,0.7,0.4,1.1c0.2,0.4,0.5,0.7,0.8,0.9c0.3,0.3,0.7,0.5,1.1,0.7c0.4,0.2,0.9,0.3,1.4,0.3
c0,0,0,0,0,0c0.5,0,1-0.1,1.4-0.3c0.5-0.2,0.9-0.5,1.3-0.8c0.4-0.4,0.7-0.9,0.9-1.4c0.2-0.6,0.3-1.2,0.2-1.8
c-0.1-0.5-0.2-0.9-0.5-1.3c-0.1-0.2-0.3-0.4-0.4-0.6l-0.1-0.1l0,0l0,0l0,0l0,0l0-0.1l0,0l-0.1,0.1l-0.1-0.1
c-0.1-0.1-0.1-0.1-0.1-0.2c-0.1-0.1-0.2-0.3-0.2-0.4c-0.1-0.1-0.1-0.3-0.2-0.4c0-0.1-0.1-0.2-0.1-0.4c0-0.1,0-0.2,0-0.3
c-0.1-0.6,0.1-1.3,0.4-1.9c0.1-0.2,0.2-0.4,0.4-0.6c0,0,0.1-0.1,0.1-0.1c0,0,0.1-0.1,0.1-0.1c0,0,0.1,0,0.1-0.1
c0.1-0.1,0.2-0.1,0.3-0.1c0,0,0,0,0.1,0l0,0h6.5C48.2,25.1,48.7,24.5,49.2,24z"/>
</g>
</g>
<g id="XMLID_14_">
</g>
<g id="XMLID_15_">
</g>
<g id="XMLID_16_">
</g>
<g id="XMLID_17_">
</g>
<g id="XMLID_18_">
</g>
<g id="XMLID_19_">
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,23 @@
<html>
<head>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, minimal-ui">
<link rel="stylesheet" href="main.css">
<script src="roslib.js"></script>
</head>
<body>
<div class="telemetry"><span class="mode">DISCONNECTED</span></div>
<div class="battery"></div>
<div class="logo"></div>
<div class="container">
<div class="stick stick-left">
<div class="stick-pointer"></div>
</div>
<div class="stick stick-right">
<div class="stick-pointer"></div>
</div>
</div>
<script src="main.js" type="text/javascript"></script>
<script src="telemetry.js" type="text/javascript"></script>
</body>
</html>

View File

@@ -0,0 +1,91 @@
html, body {
margin: 0;
padding: 0;
user-select: none;
font-family: sans-serif;
background: #212121;
color: rgba(255, 255, 255, 0.9);
}
.stick {
border-radius: 50%;
width: 5cm;
height: 5cm;
position: relative;
transform: translateZ(0);
border: 4px solid rgba(255,255,255,.4);
box-shadow: 0 0 0 1px rgba(0,0,0,.2), inset 0 0 0 1px rgba(0,0,0,.2);
}
.stick-pointer {
position: absolute;
border-radius: 50%;
background-color: rgba(255,255,255,.25);
box-shadow: 0 0 10px rgba(0,0,0,.3);
width: 3cm;
height: 3cm;
margin-left: -1.5cm;
margin-top: -1.5cm;
top: 2.5cm;
left: 2.5cm;
pointer-events: none;
transform: translateZ(0);
}
.container {
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
height: 100%;
}
.telemetry {
position: absolute;
text-align: center;
width: 100%;
top: 30px;
font-size: 20px;
user-select: none;
pointer-events: none;
}
body.armed .telemetry .mode {
font-weight: bold;
}
@keyframes scale {
0% { transform: scale(1.0); }
50% { transform: scale(1.2); }
100% { transform: scale(1.0); }
}
.battery {
position: absolute;
text-align: center;
width: 100%;
bottom: 30px;
font-size: 20px;
user-select: none;
pointer-events: none;
}
body.low-battery .battery {
color: #ff554b;
animation: scale 0.3s 1 ease-in-out}
.logo {
position: absolute;
background: url(clever.svg);
-webkit-background-size: 50px;
background-size: 50px;
width: 50px;
height: 50px;
top: 50%;
left: 50%;
margin-top: -25px;
margin-left: -25px;
font-size: 20px;
user-select: none;
pointer-events: none;
}

126
apps/ios/cleverrc/main.js Normal file
View File

@@ -0,0 +1,126 @@
function throttle(func, ms) {
var isThrottled = false,
savedArgs,
savedThis;
function wrapper() {
if (isThrottled) {
savedArgs = arguments;
savedThis = this;
return;
}
func.apply(this, arguments);
isThrottled = true;
setTimeout(function() {
isThrottled = false;
if (savedArgs) {
wrapper.apply(savedThis, savedArgs);
savedArgs = savedThis = null;
}
}, ms);
}
return wrapper;
}
function callNativeApp(name, msg) {
try {
webkit.messageHandlers[name].postMessage(msg);
return true;
} catch(err) {
console.warn('The native context does not exist yet');
return false;
}
}
var rcLastPublish = null;
function rcPublish() {
callNativeApp('control', controlMessage);
rcLastPublish = new Date();
}
rcPublishThrottled = throttle(rcPublish, 30);
setInterval(function() {
if (rcLastPublish !== null && new Date() - rcLastPublish > 800) {
rcPublishThrottled();
}
}, 50);
var body = document.querySelector('body');
var stickLeft = document.querySelector('.stick-left');
var stickRight = document.querySelector('.stick-right');
var controlMessage = { x: 0, y: 0, z: 0, r: 0 };
function onStickTouchMove(touch) {
var target = touch.target;
var targetRect = target.getBoundingClientRect();
var stickPointer = target.querySelector('.stick-pointer');
var offsetX = touch.clientX - targetRect.left;
var offsetY = touch.clientY - targetRect.top;
var x = 2 * offsetX / targetRect.width;
var y = 2 * offsetY / targetRect.height;
x = Math.max(0, x);
x = Math.min(2, x);
y = Math.max(0, y);
y = Math.min(2, y);
stickPointer.style.left = (x * 50) + '%';
stickPointer.style.top = (y * 50) + '%';
x -= 1;
y = 1 - y;
if (target.matches('.stick-left')) {
controlMessage.z = Math.round((y + 1) * 500);
controlMessage.r = Math.round(x * 1000);
} else if (target.matches('.stick-right')) {
controlMessage.x = Math.round(y * 1000);
controlMessage.y = Math.round(x * 1000);
}
}
body.addEventListener('touchmove', function (e) {
e.preventDefault();
});
function stickTouchStart(e) {
setControlMode();
callNativeApp('controlStart');
onStickTouchMove(e.changedTouches[0]);
rcPublishThrottled();
e.stopPropagation();
e.preventDefault();
}
function stickTouchMove(e) {
onStickTouchMove(e.changedTouches[0]);
rcPublishThrottled();
e.stopPropagation();
e.preventDefault();
}
function stickTouchEnd(e) {
var pointer = e.target.querySelector('.stick-pointer');
if (e.target.matches('.stick-left')) {
controlMessage.r = 0;
pointer.style.left = '50%';
} else if (e.target.matches('.stick-right')) {
controlMessage.x = 0;
controlMessage.y = 0;
pointer.style.left = '50%';
pointer.style.top = '50%';
}
rcPublishThrottled();
}
stickLeft.addEventListener('touchmove', stickTouchMove);
stickRight.addEventListener('touchmove', stickTouchMove);
stickLeft.addEventListener('touchstart', stickTouchStart);
stickRight.addEventListener('touchstart', stickTouchStart);
stickLeft.addEventListener('touchend', stickTouchEnd);
stickRight.addEventListener('touchend', stickTouchEnd);

3693
apps/ios/cleverrc/roslib.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,85 @@
var url = 'ws://192.168.11.1:9090';
var modeEl = document.querySelector('.telemetry .mode');
var batteryEl = document.querySelector('.battery');
var ros = new ROSLIB.Ros({ url: url });
ros.on('connection', function () {
body.classList.add('connected');
});
ros.on('close', function () {
body.classList.remove('connected');
modeEl.classList.remove('armed');
modeEl.innerHTML = 'DISCONNECTED';
batteryEl.innerHTML = '';
setTimeout(function() {
modeEl.innerHTML = 'RECONNECTING';
ros.connect(url);
}, 2000);
});
var fcuState;
new ROSLIB.Topic({
ros: ros,
name: '/state_latched',
messageType: 'mavros_msgs/State'
}).subscribe(function(message) {
body.classList.toggle('fcu-disconnected', !message.connected);
body.classList.toggle('armed', message.armed);
fcuState = message;
modeEl.classList.toggle('armed', fcuState.armed);
modeEl.innerHTML = message.connected ? fcuState.mode : 'DISCONNECTED FROM FCU';
console.log('state', message);
});
function notifyLowBattery() {
callNativeApp('lowBattery');
}
notifyLowBatteryThrottled = throttle(notifyLowBattery, 10000);
new ROSLIB.Topic({
ros: ros,
name: '/mavros/battery',
messageType: 'sensor_msgs/BatteryState',
throttle_rate: 5000
}).subscribe(function(message) {
var LOW_BATTERY = 3.8;
batteryEl.innerHTML = (message.cell_voltage[0].toFixed(2) + ' V') || '';
if (message.cell_voltage[0] < LOW_BATTERY) {
console.log('low battery');
callNativeApp('lowBattery');
body.classList.remove('low-battery');
void body.offsetWidth; // trick for repeating animation
body.classList.add('low-battery');
} else {
body.classList.remove('low-battery');
}
});
new ROSLIB.Topic({
ros: ros,
name: '/rosout_agg',
messageType: 'rosgraph_msgs/Log'
}).subscribe(function(message) {
if(message.level >= 4) {
if (message.msg.startsWith('CMD: ')) {
return;
}
callNativeApp('notification', message);
}
});
var setMode = new ROSLIB.Service({
ros: ros,
name : '/mavros/set_mode',
serviceType : 'mavros_msgs/SetMode'
});
function setControlMode() {
var CONTROL_MODE = 'STABILIZED';
setMode.callService(new ROSLIB.ServiceRequest({ custom_mode: CONTROL_MODE }));
}

View File

@@ -11,6 +11,7 @@ find_package(catkin REQUIRED COMPONENTS
nodelet
pluginlib
roscpp
genmsg
rospy
std_msgs
message_generation
@@ -157,7 +158,9 @@ add_library(aruco_vpe
## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
# add_executable(${PROJECT_NAME}_node src/clever_node.cpp)
add_executable(rc src/rc.cpp)
target_link_libraries(rc ${catkin_LIBRARIES})
## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the

View File

@@ -48,6 +48,9 @@
<!-- rosbridge -->
<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" if="$(arg rosbridge)"/>
<!-- rc backend -->
<!--<node name="rc" pkg="clever" type="rc" output="screen"/>-->
<!-- FPV video streaming -->
<include file="$(find clever)/launch/fpv_camera.launch" if="$(arg fpv_camera)">
<arg name="device" value="$(arg fpv_camera_device)"/>

View File

@@ -37,7 +37,6 @@
- 3dr_radio
- actuator_control
- hil_controls
- manual_control
- vfr_hud
- px4flow
- vision_speed_estimate

118
clever/src/rc.cpp Normal file
View File

@@ -0,0 +1,118 @@
// CLEVER mobile remote control support:
// * Send ManualControl messages through UDP
// * `latched_state` topic
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <thread>
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "mavros_msgs/State.h"
#include "mavros_msgs/ManualControl.h"
struct ControlMessage
{
int16_t x, y, z, r;
} __attribute__((packed));
class RC
{
public:
RC():
nh(),
nh_priv("~")
{
// Create socket thread
std::thread t(&RC::socketThread, this);
t.detach();
initLatchedState();
}
private:
ros::NodeHandle nh, nh_priv;
ros::Subscriber state_sub;
ros::Publisher state_pub;
mavros_msgs::StateConstPtr state_msg;
void handleState(const mavros_msgs::StateConstPtr& state)
{
if (!state_msg ||
state->connected != state_msg->connected ||
state->mode != state_msg->mode ||
state->armed != state_msg->armed) {
state_msg = state;
state_pub.publish(state_msg);
}
}
void initLatchedState()
{
state_sub = nh.subscribe("mavros/state", 1, &RC::handleState, this);
state_pub = nh.advertise<mavros_msgs::State>("state_latched", 1, true);
}
int createSocket(int port)
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_port = htons(port);
if (bind(sockfd, (sockaddr *)&sin, sizeof(sin)) < 0) {
ROS_FATAL("socket bind error: %s", strerror(errno));
close(sockfd);
ros::shutdown();
}
return sockfd;
}
void socketThread()
{
int port;
nh_priv.param("port", port, 35602);
int sockfd = createSocket(port);
char buff[9999];
ros::Publisher manual_control_pub = nh.advertise<mavros_msgs::ManualControl>("mavros/manual_control/send", 1);
mavros_msgs::ManualControl manual_control_msg;
sockaddr_in client_addr;
socklen_t client_addr_size = sizeof(client_addr);
ROS_INFO("UDP RC initialized on port %d", port);
while (true) {
// read next UDP packet
int bsize = recvfrom(sockfd, &buff[0], sizeof(buff) - 1, 0, (sockaddr *) &client_addr, &client_addr_size);
if (bsize < 0) {
ROS_ERROR("recvfrom() error: %s", strerror(errno));
} else if (bsize != sizeof(ControlMessage)) {
ROS_ERROR_THROTTLE(30, "Wrong UDP packet size: %d", bsize);
}
// unpack message
// warning: ignore endianness, so the code is platform-dependent
ControlMessage *msg = (ControlMessage *)buff;
manual_control_msg.x = msg->x;
manual_control_msg.y = msg->y;
manual_control_msg.z = msg->z;
manual_control_msg.r = msg->r;
manual_control_pub.publish(manual_control_msg);
}
}
};
int main(int argc, char **argv)
{
ros::init(argc, argv, "rc");
RC rc;
ros::spin();
}