├── .remarkrc ├── jitpack.yml ├── xyo-android-library ├── consumer-rules.pro ├── .gitignore ├── version.properties ├── .settings │ └── org.eclipse.buildship.core.prefs ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── java │ │ └── network │ │ │ └── xyo │ │ │ └── sdk │ │ │ ├── XyoSdk.kt │ │ │ ├── XyoServer.kt │ │ │ ├── XyoNetwork.kt │ │ │ ├── XyoNode.kt │ │ │ ├── XyoClient.kt │ │ │ ├── XyoTcpIpServer.kt │ │ │ ├── XyoTcpIpNetwork.kt │ │ │ ├── XyoBleNetwork.kt │ │ │ ├── bluetooth │ │ │ ├── XyoUuids.kt │ │ │ ├── packet │ │ │ │ ├── XyoInputStream.kt │ │ │ │ ├── XyoBluetoothOutgoingPacket.kt │ │ │ │ └── XyoBluetoothIncomingPacket.kt │ │ │ ├── client │ │ │ │ ├── XyoBridgeX.kt │ │ │ │ ├── XyoIosAppX.kt │ │ │ │ ├── XyoAndroidAppX.kt │ │ │ │ ├── XyoBluetoothClientPipe.kt │ │ │ │ ├── XyoSentinelX.kt │ │ │ │ └── XyoBluetoothClient.kt │ │ │ ├── XyoBleSdk.kt │ │ │ ├── node │ │ │ │ └── XyoBleNode.kt │ │ │ └── advertiser │ │ │ │ └── XyoBluetoothAdvertiser.kt │ │ │ ├── XyoBoundWitnessTarget.kt │ │ │ ├── XyoBleServer.kt │ │ │ ├── XyoSnappyDBStorageProvider.kt │ │ │ ├── XyoTcpIpClient.kt │ │ │ ├── Base58.kt │ │ │ ├── XyoBleClient.kt │ │ │ └── XyoNodeBuilder.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── xyo-android-sample ├── .gitignore ├── .settings │ └── org.eclipse.buildship.core.prefs ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── src │ └── main │ │ ├── res │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── styles.xml │ │ │ └── strings.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── drawable │ │ │ ├── ic_dashboard_black_24dp.xml │ │ │ └── ic_launcher_background.xml │ │ ├── menu │ │ │ └── bottom_nav_menu.xml │ │ ├── layout │ │ │ ├── fragment_tcpip_server.xml │ │ │ ├── activity_main.xml │ │ │ ├── fragment_ble_server.xml │ │ │ ├── fragment_tcpip_client.xml │ │ │ └── fragment_ble_client.xml │ │ ├── navigation │ │ │ └── mobile_navigation.xml │ │ └── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ └── network │ │ │ └── xyo │ │ │ └── sdk │ │ │ └── sample │ │ │ ├── UIThread.kt │ │ │ ├── ui │ │ │ ├── tcpip_server │ │ │ │ └── TcpIpServerFragment.kt │ │ │ ├── ble_server │ │ │ │ └── BleServerFragment.kt │ │ │ ├── tcpip_client │ │ │ │ └── TcpIpClientFragment.kt │ │ │ └── ble_client │ │ │ │ └── BleClientFragment.kt │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro ├── build.gradle ├── gradlew.bat └── gradlew ├── .vscode └── settings.json ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .github ├── workflows │ ├── minorBuild.yml │ ├── build.yml │ ├── prepRelease.yml │ └── release.yml └── ISSUE_TEMPLATE │ ├── documentation-needed.md │ ├── feature_request.md │ └── bug_report.md ├── .settings └── org.eclipse.buildship.core.prefs ├── .gitignore ├── sonar-project.properties ├── gradle.properties ├── gradlew.bat ├── gradlew ├── LICENSE └── README.md /.remarkrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 3 | -------------------------------------------------------------------------------- /xyo-android-library/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /xyo-android-library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /xyo-android-sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "automatic" 3 | } -------------------------------------------------------------------------------- /xyo-android-library/version.properties: -------------------------------------------------------------------------------- 1 | #Fri Dec 03 15:15:41 PST 2021 2 | VERSION_PATCH=44 3 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':xyo-android-sample', ':xyo-android-library' 2 | rootProject.name='sdk-xyo-android' 3 | -------------------------------------------------------------------------------- /xyo-android-library/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /xyo-android-sample/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork-v1/sdk-xyo-android/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /xyo-android-library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | xyo-android-library 3 | 4 | -------------------------------------------------------------------------------- /xyo-android-sample/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork-v1/sdk-xyo-android/HEAD/xyo-android-sample/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork-v1/sdk-xyo-android/HEAD/xyo-android-sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork-v1/sdk-xyo-android/HEAD/xyo-android-sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork-v1/sdk-xyo-android/HEAD/xyo-android-sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork-v1/sdk-xyo-android/HEAD/xyo-android-sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork-v1/sdk-xyo-android/HEAD/xyo-android-sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork-v1/sdk-xyo-android/HEAD/xyo-android-sample/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork-v1/sdk-xyo-android/HEAD/xyo-android-sample/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork-v1/sdk-xyo-android/HEAD/xyo-android-sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork-v1/sdk-xyo-android/HEAD/xyo-android-sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XYOracleNetwork-v1/sdk-xyo-android/HEAD/xyo-android-sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoSdk.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import network.xyo.base.XYBase 3 | 4 | class XyoSdk : XYBase() { 5 | companion object { 6 | val nodes = mutableListOf() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Oct 13 10:29:53 PDT 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip 7 | -------------------------------------------------------------------------------- /xyo-android-sample/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Apr 13 13:11:06 PDT 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoServer.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import network.xyo.sdkcorekotlin.network.XyoProcedureCatalog 3 | import network.xyo.sdkcorekotlin.node.XyoRelayNode 4 | 5 | abstract class XyoServer(relayNode: XyoRelayNode, procedureCatalog: XyoProcedureCatalog) : XyoBoundWitnessTarget(relayNode, procedureCatalog) 6 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoNetwork.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import network.xyo.base.XYBase 3 | 4 | abstract class XyoNetwork(val type: Type) : XYBase() { 5 | 6 | enum class Type { 7 | BluetoothLE, 8 | TcpIp, 9 | Other 10 | } 11 | 12 | abstract val client: XyoClient 13 | abstract val server: XyoServer 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/minorBuild.yml: -------------------------------------------------------------------------------- 1 | name: Minor Branch Build 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'develop' 7 | - 'release' 8 | - 'master' 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: pre-build 17 | run: chmod +x ./gradlew 18 | - name: build 19 | run: ./gradlew assemble -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'develop' 7 | pull_request: 8 | branches: 9 | - 'master' 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: pre-build 18 | run: chmod +x ./gradlew 19 | - name: build 20 | run: ./gradlew clean assemble -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/drawable/ic_dashboard_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoNode.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import network.xyo.base.XYBase 3 | 4 | class XyoNode(val networks: Map) : XYBase() { 5 | fun setAllListeners(name: String, listener: XyoBoundWitnessTarget.Listener) { 6 | networks.forEach { 7 | it.value.client.listeners[name] = listener 8 | it.value.server.listeners[name] = listener 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows thumbnail db 2 | Thumbs.db 3 | 4 | # OSX files 5 | .DS_Store 6 | 7 | # built application files 8 | *.apk 9 | *.ap_ 10 | 11 | # files for the dex VM 12 | *.dex 13 | 14 | # Java class files 15 | *.class 16 | 17 | # generated files 18 | bin/ 19 | gen/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Eclipse project files 26 | .classpath 27 | .project 28 | 29 | # Android Studio 30 | .idea 31 | .gradle 32 | /*/local.properties 33 | /*/out 34 | /*/*/build 35 | build 36 | /*/*/production 37 | *.iml 38 | *.iws 39 | *.ipr 40 | *~ 41 | *.swp -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoClient.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import network.xyo.sdkcorekotlin.network.XyoProcedureCatalog 3 | import network.xyo.sdkcorekotlin.node.XyoRelayNode 4 | 5 | abstract class XyoClient( 6 | relayNode: XyoRelayNode, 7 | procedureCatalog: XyoProcedureCatalog, 8 | open var autoBoundWitness: Boolean, 9 | open var knownBridges: List? = null 10 | ) : XyoBoundWitnessTarget(relayNode, procedureCatalog) { 11 | // this is not a parameter since scanning has to start off of false 12 | open var scan: Boolean = false 13 | open var deviceCount = 0 14 | open var xyoDeviceCount = 0 15 | open var nearbyXyoDeviceCount = 0 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation-needed.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation needed 3 | about: Suggest documentation that is needed 4 | title: "[DOCUMENTATION]:" 5 | labels: documentation 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your documentation request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the documentation and format you would like** 14 | A clear and concise description of what documentation you would like to see and what type for format. 15 | Ex. Step-by-step, Paragraph explainer, screenshots, etc. 16 | 17 | **Additional context** 18 | Add any other context or screenshots about the document request here. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for XYO SDK Android 4 | title: "[FEATURE]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. This could include specific devices, android versions, etc. 21 | -------------------------------------------------------------------------------- /xyo-android-sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve the XYO Android SDK 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Observed behavior** 11 | A clear and concise description of what exactly happened. 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Smartphone (please complete the following information):** 27 | - Device: [e.g. Samsung Galaxy, Google Pixel] 28 | - OS: [e.g. Android 10] 29 | - Browser [e.g. stock browser, chrome] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/workflows/prepRelease.yml: -------------------------------------------------------------------------------- 1 | name: Prepare Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'release' 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: pre-build 15 | run: chmod +x ./gradlew 16 | - name: build 17 | run: ./gradlew :xyo-android-library:assemble 18 | - name: check in local changes 19 | run: | 20 | git status 21 | git add xyo-android-library/version.properties 22 | - name: commit file 23 | run: | 24 | git config --local user.email "action@github.com" 25 | git config --local user.name "GitHub Action" 26 | git commit -m "version bump" -a 27 | - name: push changes 28 | uses: ad-m/github-push-action@master 29 | with: 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | branch: 'release' 32 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=XYOracleNetwork_sdk-xyo-android 2 | sonar.projectName=sdk-xyo-android 3 | 4 | # ===================================================== 5 | # Meta-data for the project 6 | # ===================================================== 7 | 8 | sonar.links.homepage=https://github.com/XYOracleNetwork/sdk-xyo-android 9 | sonar.links.ci=https://github.com/XYOracleNetwork/sdk-xyo-android 10 | sonar.links.scm=https://github.com/XYOracleNetwork/sdk-xyo-android 11 | sonar.links.issue=https://github.com/XYOracleNetwork/sdk-xyo-android/issues 12 | 13 | 14 | # ===================================================== 15 | # Properties that will be shared amongst all modules 16 | # ===================================================== 17 | 18 | sonar.host.url=https://sonarcloud.io 19 | sonar.organization=xyo-network 20 | sonar.login=${SONAR_TOKEN} 21 | sonar.sources=. 22 | 23 | sonar.java.binaries=. 24 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | XYO SDK Sample 3 | BLE Client 4 | BLE Server 5 | TcpIp Client 6 | TcpIp Server 7 | Auto BoundWitness 8 | Auto Bridge 9 | Accept Bridging 10 | Scan 11 | Listen 12 | Public Key: 13 | Detected Devices: 14 | Detected XYO Devices: 15 | Nearby XYO Devices: 16 | 17 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoTcpIpServer.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import network.xyo.sdkcorekotlin.network.XyoProcedureCatalog 3 | import network.xyo.sdkcorekotlin.node.XyoRelayNode 4 | 5 | class XyoTcpIpServer( 6 | relayNode: XyoRelayNode, 7 | procedureCatalog: XyoProcedureCatalog, 8 | autoBridge: Boolean, 9 | acceptBridging: Boolean, 10 | listen: Boolean 11 | ) : XyoServer(relayNode, procedureCatalog) { 12 | 13 | override var autoBridge: Boolean 14 | get() { return false } 15 | set(_) { } 16 | 17 | override var acceptBridging: Boolean 18 | get() { return false } 19 | set(_) { } 20 | 21 | var listen: Boolean 22 | get() { return false } 23 | set(_) { } 24 | 25 | init { 26 | this.autoBridge = autoBridge 27 | this.acceptBridging = acceptBridging 28 | this.listen = listen 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/menu/bottom_nav_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/layout/fragment_tcpip_server.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 19 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoTcpIpNetwork.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import network.xyo.sdkcorekotlin.network.XyoProcedureCatalog 3 | import network.xyo.sdkcorekotlin.node.XyoRelayNode 4 | 5 | @kotlin.ExperimentalUnsignedTypes 6 | class XyoTcpIpNetwork( 7 | relayNode: XyoRelayNode, 8 | procedureCatalog: XyoProcedureCatalog, 9 | override val client: XyoTcpIpClient = XyoTcpIpClient( 10 | relayNode, 11 | procedureCatalog, 12 | autoBoundWitness = true, 13 | autoBridge = true, 14 | acceptBridging = false 15 | ), 16 | override val server: XyoTcpIpServer = XyoTcpIpServer( 17 | relayNode, 18 | procedureCatalog, 19 | autoBridge = true, 20 | acceptBridging = false, 21 | listen = false 22 | ) 23 | ) : XyoNetwork(Type.TcpIp) { 24 | init { 25 | client.knownBridges = client.knownBridges ?: listOf("ws://alpha-peers.xyo.network:11000") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoBleNetwork.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import android.content.Context 3 | import kotlinx.coroutines.InternalCoroutinesApi 4 | import network.xyo.sdkcorekotlin.network.XyoProcedureCatalog 5 | import network.xyo.sdkcorekotlin.node.XyoRelayNode 6 | 7 | @InternalCoroutinesApi 8 | @kotlin.ExperimentalUnsignedTypes 9 | class XyoBleNetwork( 10 | context: Context, 11 | relayNode: XyoRelayNode, 12 | procedureCatalog: XyoProcedureCatalog, 13 | override val client: XyoBleClient = XyoBleClient( 14 | context, 15 | relayNode, 16 | procedureCatalog, 17 | autoBoundWitness = true, 18 | autoBridge = false, 19 | acceptBridging = false, 20 | scan = true 21 | ), 22 | override val server: XyoBleServer = XyoBleServer( 23 | context, 24 | relayNode, 25 | procedureCatalog, 26 | autoBridge = false, 27 | acceptBridging = false, 28 | listen = true 29 | ) 30 | ) : XyoNetwork(Type.BluetoothLE) 31 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/XyoUuids.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth 2 | 3 | import java.util.* 4 | 5 | /** 6 | * All the XYO Bluetooth UUIDs. 7 | */ 8 | object XyoUuids { 9 | // The descriptor to use when to manage subscribing to notifications. 10 | val NOTIFY_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")!! 11 | 12 | // The primary GATT service that will be advertised. 13 | var XYO_SERVICE = UUID.fromString("d684352e-df36-484e-bc98-2d5398c5593e")!! 14 | 15 | // The GATT characteristic to be written to when creating pipes. This will be in the XYO_SERVICE. 16 | val XYO_PIPE = UUID.fromString("727a3639-0eb4-4525-b1bc-7fa456490b2d")!! 17 | 18 | // The password GATT characteristic to write to when changing the password. 19 | val XYO_PASSWORD = UUID.fromString("727a3639-0eb4-4525-b1bc-7fa4564A0b2d")!! 20 | 21 | // The password bound witness data characteristic characteristic to be written to. 22 | val XYO_CHANGE_BW_DATA = UUID.fromString("727a3639-0eb4-4525-b1bc-7fa4564B0b2d")!! 23 | 24 | val XYO_RESET_DEVICE = UUID.fromString("727a3639-0eb4-4525-b1bc-7fa4564C0b2d")!! 25 | val XYO_PUBLIC_KEY = UUID.fromString("727a3639-0eb4-4525-b1bc-7fa4564D0b2d")!! 26 | } 27 | 28 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/java/network/xyo/sdk/sample/UIThread.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.sample 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import kotlinx.coroutines.* 6 | import kotlin.coroutines.AbstractCoroutineContextElement 7 | import kotlin.coroutines.Continuation 8 | import kotlin.coroutines.ContinuationInterceptor 9 | import kotlin.coroutines.CoroutineContext 10 | 11 | // this needs to be imported by sdk-ui-android (https://github.com/XYOracleNetwork/sdk-ui-android) 12 | private class AndroidContinuation(val cont: Continuation) : Continuation by cont { 13 | override fun resumeWith(result: Result) { 14 | if (Looper.myLooper() == Looper.getMainLooper()) cont.resumeWith(result) 15 | else Handler(Looper.getMainLooper()).post { cont.resumeWith(result) } 16 | } 17 | 18 | } 19 | 20 | object UIThread : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { 21 | override fun interceptContinuation(continuation: Continuation): Continuation = 22 | AndroidContinuation(continuation) 23 | } 24 | 25 | fun ui( 26 | context: CoroutineContext = UIThread, 27 | start: CoroutineStart = CoroutineStart.DEFAULT, 28 | block: suspend CoroutineScope.() -> Unit 29 | ): Job { 30 | return GlobalScope.launch(context, start, block) 31 | } -------------------------------------------------------------------------------- /xyo-android-library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/arietrouw/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | 27 | -keepattributes InnerClasses 28 | -keepattributes Signature 29 | -keepattributes Exceptions 30 | -keepattributes *Annotation* 31 | -keepattributes EnclosingMethod 32 | 33 | -keep public class network.xyo.** { *; } 34 | -keep class androidx.core.app.CoreComponentFactory { *; } -------------------------------------------------------------------------------- /xyo-android-sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/packet/XyoInputStream.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth.packet 2 | 3 | class XyoInputStream { 4 | private val donePackets = ArrayList() 5 | private var currentBuffer: XyoBluetoothIncomingPacket? = null 6 | var onComplete : ((done: ByteArray) -> Any?)? = null 7 | 8 | fun addChunk (data: ByteArray) { 9 | if (currentBuffer == null) { 10 | currentBuffer = XyoBluetoothIncomingPacket(data) 11 | 12 | if (currentBuffer?.done == true) { 13 | val finished = currentBuffer?.getCurrentBuffer() ?: return 14 | donePackets.add(finished) 15 | onDone() 16 | currentBuffer = null 17 | } 18 | 19 | return 20 | } 21 | 22 | val finished = currentBuffer?.addPacket(data) ?: return 23 | donePackets.add(finished) 24 | onDone() 25 | currentBuffer = null 26 | } 27 | 28 | private fun onDone () { 29 | val cb = onComplete 30 | 31 | if (cb != null && donePackets.size > 0) { 32 | cb(donePackets[0]) 33 | donePackets.removeAt(0) 34 | } 35 | } 36 | 37 | fun getOldestPacket () : ByteArray? { 38 | if (donePackets.isEmpty()) { 39 | return null 40 | } 41 | 42 | val done = donePackets.first() 43 | 44 | donePackets.removeAt(0) 45 | 46 | return done 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /xyo-android-sample/src/main/java/network/xyo/sdk/sample/ui/tcpip_server/TcpIpServerFragment.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.sample.ui.tcpip_server 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import network.xyo.sdk.sample.databinding.FragmentTcpipServerBinding 9 | import network.xyo.sdk.sample.ui 10 | 11 | class TcpIpServerFragment : Fragment() { 12 | 13 | private var _binding: FragmentTcpipServerBinding? = null 14 | private val binding get() = _binding!! 15 | 16 | override fun onCreateView( 17 | inflater: LayoutInflater, 18 | container: ViewGroup?, 19 | savedInstanceState: Bundle? 20 | ): View? { 21 | _binding = FragmentTcpipServerBinding.inflate(inflater, container, false) 22 | val view = binding.root 23 | return view 24 | } 25 | 26 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 27 | super.onViewCreated(view, savedInstanceState) 28 | addStatus("Not Implemented for Android") 29 | } 30 | 31 | fun addStatus(status: String) { 32 | ui { 33 | binding.textTcpipServer.let { 34 | val sb = StringBuilder() 35 | sb.append(it.text) 36 | sb.append("\r\n") 37 | sb.append(status) 38 | it.text = sb.toString() 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/navigation/mobile_navigation.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 19 | 20 | 25 | 26 | 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | 8 | jobs: 9 | publish: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@master 15 | - uses: actions/setup-java@v1 16 | with: 17 | java-version: 1.8 18 | - name: pre-build 19 | run: chmod +x gradlew 20 | - name: install 21 | run: ./gradlew install 22 | - name: assemble 23 | run: ./gradlew :xyo-android-library:assembleRelease 24 | - name: print version 25 | run: | 26 | echo "##[set-output name=version;]$(gradle -q printVersion)" 27 | id: release_version 28 | - name: bintray upload 29 | env: 30 | BINTRAY_USER: ${{ secrets.BINTRAY_USER }} 31 | BINTRAY_KEY: ${{ secrets.BINTRAY_KEY }} 32 | run: ./gradlew :xyo-android-library:bintrayUpload 33 | - name: get commit message 34 | run: | 35 | echo ::set-env name=commitmsg::$(git log --format=%B -n 1 ${{ github.event.after }}) 36 | - name: show commit message 37 | run: echo $commitmsg 38 | - name: Create Release 39 | id: create_release 40 | uses: actions/create-release@v1 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | with: 44 | tag_name: ${{ steps.release_version.outputs.version }} 45 | release_name: Release ${{ env.commitmsg }} 46 | draft: true 47 | prerelease: false -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/packet/XyoBluetoothOutgoingPacket.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth.packet 2 | 3 | import java.nio.ByteBuffer 4 | 5 | /** 6 | * A class to chunk bluetooth data when writing to a GATT. 7 | * 8 | * @param chunkSize The number of bytes per chunk. 9 | * @param bytes The bytes to chunk. 10 | * @param sizeOfSize The number of bytes to prepend the size with 11 | */ 12 | class XyoBluetoothOutgoingPacket(private val chunkSize: Int, bytes: ByteArray, private val sizeOfSize: Int) { 13 | private var currentIndex = 0 14 | private val sizeWithBytes = getSizeWithBytes(bytes) 15 | 16 | private fun getSizeWithBytes(bytes: ByteArray): ByteArray { 17 | val buff = ByteBuffer.allocate(bytes.size + sizeOfSize) 18 | 19 | when (sizeOfSize) { 20 | 1 -> buff.put((bytes.size + 1).toByte()) 21 | 2 -> buff.putShort((bytes.size + 2).toShort()) 22 | 4 -> buff.putInt(bytes.size + 4) 23 | } 24 | 25 | buff.put(bytes) 26 | return buff.array() 27 | } 28 | 29 | /** 30 | * If there are more packets to send. 31 | */ 32 | val canSendNext: Boolean 33 | get() { 34 | return sizeWithBytes.size != currentIndex 35 | } 36 | 37 | 38 | /** 39 | * Gets the next packet to send. 40 | */ 41 | fun getNext(): ByteArray { 42 | var size = Math.min(chunkSize, (sizeWithBytes.size - currentIndex)) 43 | //possible to get a negative size -- will cause NegativeArraySizeException 44 | if (size < 0 ) { 45 | size = 0 46 | } 47 | 48 | val packet = ByteArray(size) 49 | 50 | for (i in packet.indices) { 51 | packet[i] = sizeWithBytes[currentIndex] 52 | currentIndex++ 53 | } 54 | 55 | return packet 56 | } 57 | } -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/packet/XyoBluetoothIncomingPacket.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth.packet 2 | 3 | 4 | import java.nio.ByteBuffer 5 | 6 | /** 7 | * A class to help receive chucked data that came from XyoBluetoothOutgoingPacket. 8 | * 9 | * @param firstPacket The first chunk sent by the other party. 10 | */ 11 | class XyoBluetoothIncomingPacket(firstPacket: ByteArray) { 12 | private var packets = ArrayList() 13 | private var currentSize = 0 14 | private var totalSize = 0 15 | 16 | /** 17 | * If there is no more packets to add. 18 | */ 19 | val done: Boolean 20 | get() = (currentSize >= totalSize && totalSize != 0) 21 | 22 | /** 23 | * Adds a chunk to incoming packet. 24 | * 25 | * @param toAdd The chunk to add. 26 | * @return If the packet if finished, it will return the completed packet. 27 | */ 28 | fun addPacket(toAdd: ByteArray): ByteArray? { 29 | if (totalSize == 0 && currentSize == 0) { 30 | totalSize = ByteBuffer.wrap(toAdd.copyOfRange(0, 4)).int 31 | packets.add(toAdd.copyOfRange(4, toAdd.size)) 32 | currentSize += toAdd.size 33 | return null 34 | } 35 | 36 | packets.add(toAdd) 37 | currentSize += toAdd.size 38 | 39 | if (totalSize == currentSize) { 40 | return getCurrentBuffer() 41 | } 42 | 43 | return null 44 | } 45 | 46 | /** 47 | * Get the current packet buffer. 48 | */ 49 | fun getCurrentBuffer(): ByteArray { 50 | val buff = ByteBuffer.allocate(currentSize - 4) 51 | 52 | for (i in 0 until packets.size) { 53 | buff.put(packets[i]) 54 | } 55 | 56 | return buff.array() 57 | } 58 | 59 | init { 60 | addPacket(firstPacket) 61 | } 62 | } -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoBoundWitnessTarget.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import network.xyo.base.XYBase 3 | import network.xyo.sdkcorekotlin.boundWitness.XyoBoundWitness 4 | import network.xyo.sdkcorekotlin.network.XyoProcedureCatalog 5 | import network.xyo.sdkcorekotlin.node.XyoRelayNode 6 | 7 | abstract class XyoBoundWitnessTarget( 8 | val relayNode: XyoRelayNode, 9 | val procedureCatalog: XyoProcedureCatalog 10 | ) : XYBase() { 11 | 12 | val publicKey: String? 13 | get() { 14 | if (relayNode.originState.signers.isEmpty()) { 15 | return null 16 | } 17 | 18 | return relayNode.originState.signers.first().publicKey.bytesCopy.toBase58String() 19 | } 20 | 21 | open class Listener : XYBase() { 22 | open fun boundWitnessStarted(source: Any?, target: XyoBoundWitnessTarget) { 23 | log.info("boundWitnessStarted") 24 | } 25 | 26 | open fun boundWitnessCompleted(source: Any?, target: XyoBoundWitnessTarget, boundWitness: XyoBoundWitness?, error: String?) { 27 | log.info("boundWitnessCompleted") 28 | } 29 | } 30 | 31 | // the interaction listener 32 | val listeners = mutableMapOf() 33 | 34 | fun boundWitnessStarted(source: Any?) { 35 | listeners.forEach { 36 | it.value.boundWitnessStarted(source, this) 37 | } 38 | } 39 | 40 | fun boundWitnessCompleted(source: Any?, boundWitness: XyoBoundWitness?, error: String?) { 41 | listeners.forEach { 42 | it.value.boundWitnessCompleted(source, this, boundWitness, error) 43 | } 44 | } 45 | 46 | // accept bound witnesses that have bridges payloads 47 | abstract var acceptBridging: Boolean 48 | 49 | // when auto bound witnessing, should we bridge our chain 50 | abstract var autoBridge: Boolean 51 | } 52 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 24 | 25 | 36 | 37 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /xyo-android-sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'maven-publish' 4 | 5 | android { 6 | compileSdkVersion 31 7 | buildToolsVersion "30.0.3" 8 | ndkVersion "21.1.6352462" 9 | defaultConfig { 10 | applicationId "network.xyo.sdk.sample" 11 | minSdkVersion 21 12 | targetSdkVersion 31 13 | versionCode 1 14 | versionName "1.0" 15 | } 16 | 17 | compileOptions { 18 | sourceCompatibility = 1.8 19 | targetCompatibility = 1.8 20 | } 21 | 22 | buildFeatures { 23 | viewBinding true 24 | } 25 | 26 | lintOptions { 27 | htmlReport false 28 | abortOnError false 29 | warningsAsErrors false 30 | disable "HardwareIds", "MissingPermission" 31 | } 32 | 33 | kotlinOptions { 34 | jvmTarget = "1.8" 35 | } 36 | 37 | buildTypes { 38 | release { 39 | minifyEnabled false 40 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 41 | } 42 | debug { 43 | debuggable true 44 | } 45 | } 46 | 47 | configurations { 48 | all { 49 | exclude group: 'org.json', module: 'json' 50 | } 51 | } 52 | } 53 | 54 | dependencies { 55 | implementation fileTree(dir: 'libs', include: ['*.jar']) 56 | implementation "org.jetbrains.kotlin:kotlin-stdlib:1.6.0" 57 | implementation "org.jetbrains.kotlin:kotlin-reflect:1.6.0" 58 | implementation 'androidx.appcompat:appcompat:1.4.0' 59 | implementation 'androidx.core:core-ktx:1.7.0' 60 | implementation 'com.google.android.material:material:1.4.0' 61 | implementation 'androidx.constraintlayout:constraintlayout:2.1.2' 62 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' 63 | implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' 64 | implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' 65 | api 'com.github.xyoraclenetwork:sdk-ble-android:4.1.3' 66 | api 'com.github.xyoraclenetwork:sdk-core-kotlin:3.1.4' 67 | implementation project(':xyo-android-library') 68 | } 69 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/client/XyoBridgeX.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth.client 2 | 3 | import android.bluetooth.BluetoothDevice 4 | import android.content.Context 5 | import android.os.Build 6 | import network.xyo.ble.generic.devices.XYBluetoothDevice 7 | import network.xyo.ble.generic.devices.XYCreator 8 | import network.xyo.ble.generic.scanner.XYScanResult 9 | import java.util.* 10 | import java.util.concurrent.ConcurrentHashMap 11 | 12 | @kotlin.ExperimentalUnsignedTypes 13 | open class XyoBridgeX: XyoBluetoothClient { 14 | 15 | constructor(context: Context, scanResult: XYScanResult, hash: String) : super(context, scanResult, hash) 16 | 17 | constructor(context: Context, scanResult: XYScanResult, hash: String, transport: Int) : super(context, scanResult, hash, transport) 18 | 19 | companion object : XYCreator() { 20 | 21 | fun enable(enable: Boolean) { 22 | if (enable) { 23 | xyoManufactureIdToCreator[XyoBluetoothClientDeviceType.BridgeX.raw] = this 24 | } else { 25 | xyoManufactureIdToCreator.remove(XyoBluetoothClientDeviceType.BridgeX.raw) 26 | } 27 | } 28 | 29 | override fun getDevicesFromScanResult( 30 | context: Context, 31 | scanResult: XYScanResult, 32 | globalDevices: ConcurrentHashMap, 33 | foundDevices: HashMap 35 | ) { 36 | val hash = hashFromScanResult(scanResult) 37 | val existingDevice = globalDevices[hash] 38 | if (existingDevice != null) { 39 | existingDevice.rssi = scanResult.rssi 40 | existingDevice.updateBluetoothDevice(scanResult.device) 41 | } else { 42 | val createdDevice = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 43 | XyoBridgeX(context, scanResult, hash, BluetoothDevice.TRANSPORT_LE) 44 | } else { 45 | XyoBridgeX(context, scanResult, hash) 46 | } 47 | foundDevices[hash] = createdDevice 48 | globalDevices[hash] = createdDevice 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/client/XyoIosAppX.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth.client 2 | 3 | import android.bluetooth.BluetoothDevice 4 | import android.content.Context 5 | import android.os.Build 6 | import network.xyo.ble.generic.devices.XYBluetoothDevice 7 | import network.xyo.ble.generic.devices.XYCreator 8 | import network.xyo.ble.generic.scanner.XYScanResult 9 | import java.util.* 10 | import java.util.concurrent.ConcurrentHashMap 11 | 12 | @kotlin.ExperimentalUnsignedTypes 13 | open class XyoIosAppX : XyoBluetoothClient { 14 | 15 | constructor(context: Context, scanResult: XYScanResult, hash: String) : super(context, scanResult, hash) 16 | 17 | constructor(context: Context, scanResult: XYScanResult, hash: String, transport: Int) : super(context, scanResult, hash, transport) 18 | 19 | companion object : XYCreator() { 20 | 21 | fun enable(enable: Boolean) { 22 | if (enable) { 23 | xyoManufactureIdToCreator[XyoBluetoothClientDeviceType.IosAppX.raw] = this 24 | } else { 25 | xyoManufactureIdToCreator.remove(XyoBluetoothClientDeviceType.IosAppX.raw) 26 | } 27 | } 28 | 29 | override fun getDevicesFromScanResult( 30 | context: Context, 31 | scanResult: XYScanResult, 32 | globalDevices: ConcurrentHashMap, 33 | foundDevices: HashMap 35 | ) { 36 | val hash = hashFromScanResult(scanResult) 37 | 38 | val existingDevice = globalDevices[hash] 39 | if (existingDevice != null) { 40 | existingDevice.rssi = scanResult.rssi 41 | existingDevice.updateBluetoothDevice(scanResult.device) 42 | } else { 43 | val createdDevice = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 44 | XyoIosAppX(context, scanResult, hash, BluetoothDevice.TRANSPORT_LE) 45 | } else { 46 | XyoIosAppX(context, scanResult, hash) 47 | } 48 | foundDevices[hash] = createdDevice 49 | globalDevices[hash] = createdDevice 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/client/XyoAndroidAppX.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth.client 2 | 3 | import android.bluetooth.BluetoothDevice 4 | import android.content.Context 5 | import android.os.Build 6 | import network.xyo.ble.generic.devices.XYBluetoothDevice 7 | import network.xyo.ble.generic.devices.XYCreator 8 | import network.xyo.ble.generic.scanner.XYScanResult 9 | import java.util.* 10 | import java.util.concurrent.ConcurrentHashMap 11 | 12 | @kotlin.ExperimentalUnsignedTypes 13 | open class XyoAndroidAppX: XyoBluetoothClient{ 14 | 15 | constructor(context: Context, scanResult: XYScanResult, hash: String) : super(context, scanResult, hash) 16 | 17 | constructor(context: Context, scanResult: XYScanResult, hash: String, transport: Int) : super(context, scanResult, hash, transport) 18 | 19 | companion object : XYCreator() { 20 | 21 | fun enable(enable: Boolean) { 22 | if (enable) { 23 | xyoManufactureIdToCreator[XyoBluetoothClientDeviceType.AndroidAppX.raw] = this 24 | } else { 25 | xyoManufactureIdToCreator.remove(XyoBluetoothClientDeviceType.AndroidAppX.raw) 26 | } 27 | } 28 | 29 | override fun getDevicesFromScanResult( 30 | context: Context, 31 | scanResult: XYScanResult, 32 | globalDevices: ConcurrentHashMap, 33 | foundDevices: HashMap 35 | ) { 36 | val hash = hashFromScanResult(scanResult) 37 | val existingDevice = globalDevices[hash] 38 | if (existingDevice != null) { 39 | existingDevice.rssi = scanResult.rssi 40 | existingDevice.updateBluetoothDevice(scanResult.device) 41 | } else { 42 | val createdDevice = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 43 | XyoAndroidAppX(context, scanResult, hash, BluetoothDevice.TRANSPORT_LE) 44 | } else { 45 | XyoAndroidAppX(context, scanResult, hash) 46 | } 47 | foundDevices[hash] = createdDevice 48 | globalDevices[hash] = createdDevice 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/XyoBleSdk.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth 2 | 3 | import android.content.Context 4 | import kotlinx.coroutines.InternalCoroutinesApi 5 | import kotlinx.coroutines.sync.Mutex 6 | import network.xyo.ble.generic.gatt.server.XYBluetoothAdvertiser 7 | import network.xyo.ble.generic.gatt.server.XYBluetoothGattServer 8 | import network.xyo.sdk.bluetooth.advertiser.XyoBluetoothAdvertiser 9 | import network.xyo.sdk.bluetooth.server.XyoBluetoothServer 10 | import java.util.* 11 | 12 | @kotlin.ExperimentalUnsignedTypes 13 | class XyoBleSdk { 14 | @InternalCoroutinesApi 15 | companion object { 16 | private var server: XyoBluetoothServer? = null 17 | private var advertiser: XyoBluetoothAdvertiser? = null 18 | private val initServerMutex = Mutex(false) 19 | private val initAdvertiserMutex = Mutex(false) 20 | 21 | private fun createNewAdvertiser(context: Context, major: UShort?, minor: UShort?): XyoBluetoothAdvertiser { 22 | val newAdvertiser = XyoBluetoothAdvertiser( 23 | major ?: Random().nextInt(Short.MAX_VALUE * 2 + 1).toUShort(), 24 | minor ?: Random().nextInt(Short.MAX_VALUE * 2 + 1).toUShort(), 25 | XYBluetoothAdvertiser(context)) 26 | newAdvertiser.configureAdvertiser() 27 | advertiser = newAdvertiser 28 | return newAdvertiser 29 | } 30 | 31 | private suspend fun initServer(context: Context): XyoBluetoothServer { 32 | val newServer = XyoBluetoothServer(XYBluetoothGattServer(context)) 33 | newServer.initServer() 34 | server = newServer 35 | return newServer 36 | } 37 | 38 | suspend fun server(context: Context): XyoBluetoothServer { 39 | initServerMutex.lock(this) 40 | val result = server ?: initServer(context) 41 | initServerMutex.unlock(this) 42 | return result 43 | } 44 | 45 | suspend fun advertiser(context: Context, major: UShort? = null, minor: UShort? = null): XyoBluetoothAdvertiser { 46 | initAdvertiserMutex.lock(this) 47 | val result = advertiser ?: createNewAdvertiser(context, major, minor) 48 | initAdvertiserMutex.unlock(this) 49 | return result 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /xyo-android-sample/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/layout/fragment_ble_server.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 28 | 29 | 35 | 36 | 42 | 43 | 49 | 50 | 56 | 57 | 58 | 59 | 68 | 81 | 82 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/node/XyoBleNode.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth.node 2 | 3 | import kotlinx.coroutines.GlobalScope 4 | import kotlinx.coroutines.InternalCoroutinesApi 5 | import kotlinx.coroutines.launch 6 | import network.xyo.ble.generic.devices.XYBluetoothDevice 7 | import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResult 8 | import network.xyo.ble.generic.scanner.XYSmartScan 9 | import network.xyo.ble.generic.scanner.XYSmartScanListener 10 | import network.xyo.sdk.bluetooth.client.XyoBluetoothClient 11 | import network.xyo.sdk.bluetooth.server.XyoBluetoothServer 12 | import network.xyo.sdkcorekotlin.hashing.XyoHash 13 | import network.xyo.sdkcorekotlin.network.XyoNetworkHandler 14 | import network.xyo.sdkcorekotlin.network.XyoNetworkPipe 15 | import network.xyo.sdkcorekotlin.network.XyoProcedureCatalog 16 | import network.xyo.sdkcorekotlin.node.XyoRelayNode 17 | import network.xyo.sdkcorekotlin.repositories.XyoBridgeQueueRepository 18 | import network.xyo.sdkcorekotlin.repositories.XyoOriginBlockRepository 19 | import network.xyo.sdkcorekotlin.repositories.XyoOriginChainStateRepository 20 | 21 | @kotlin.ExperimentalUnsignedTypes 22 | open class XyoBleNode(private val procedureCatalog: XyoProcedureCatalog, 23 | blockRepository: XyoOriginBlockRepository, 24 | stateRepository: XyoOriginChainStateRepository, 25 | bridgeQueueRepository: XyoBridgeQueueRepository, 26 | hashingProvider: XyoHash.XyoHashProvider) : 27 | XyoRelayNode(blockRepository, stateRepository, bridgeQueueRepository, hashingProvider) { 28 | 29 | private var canBoundWitness = true 30 | 31 | val scanCallback = object : XYSmartScanListener() { 32 | override fun entered(device: XYBluetoothDevice) { 33 | super.entered(device) 34 | 35 | if (device is XyoBluetoothClient) { 36 | GlobalScope.launch { 37 | tryBoundWitnessWithDevice(device) 38 | } 39 | } 40 | } 41 | } 42 | 43 | @InternalCoroutinesApi 44 | val serverCallback = object : XyoBluetoothServer.Listener { 45 | override fun onPipe(pipe: XyoNetworkPipe) { 46 | GlobalScope.launch { 47 | if (canBoundWitness) { 48 | canBoundWitness = false 49 | val handler = XyoNetworkHandler(pipe) 50 | 51 | boundWitness(handler, procedureCatalog) 52 | 53 | canBoundWitness = true 54 | return@launch 55 | } 56 | 57 | pipe.close() 58 | } 59 | } 60 | } 61 | 62 | suspend fun tryBoundWitnessWithDevice(device: XyoBluetoothClient) { 63 | if (canBoundWitness) { 64 | canBoundWitness = false 65 | 66 | device.connection { 67 | val pipe = device.createPipe() 68 | 69 | if (pipe != null) { 70 | val handler = XyoNetworkHandler(pipe) 71 | 72 | val bw = boundWitness(handler, procedureCatalog) 73 | return@connection XYBluetoothResult(bw != null) 74 | } 75 | 76 | return@connection XYBluetoothResult(false) 77 | } 78 | 79 | 80 | canBoundWitness = true 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoBleServer.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import android.content.Context 3 | import kotlinx.coroutines.GlobalScope 4 | import kotlinx.coroutines.InternalCoroutinesApi 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.runBlocking 7 | import network.xyo.sdk.bluetooth.XyoBleSdk 8 | import network.xyo.sdk.bluetooth.advertiser.XyoBluetoothAdvertiser 9 | import network.xyo.sdk.bluetooth.server.XyoBluetoothServer 10 | import network.xyo.sdkcorekotlin.network.XyoNetworkHandler 11 | import network.xyo.sdkcorekotlin.network.XyoNetworkPipe 12 | import network.xyo.sdkcorekotlin.network.XyoProcedureCatalog 13 | import network.xyo.sdkcorekotlin.node.XyoNodeListener 14 | import network.xyo.sdkcorekotlin.node.XyoRelayNode 15 | 16 | @InternalCoroutinesApi 17 | @kotlin.ExperimentalUnsignedTypes 18 | class XyoBleServer( 19 | context: Context, 20 | relayNode: XyoRelayNode, 21 | procedureCatalog: XyoProcedureCatalog, 22 | autoBridge: Boolean, 23 | acceptBridging: Boolean, 24 | listen: Boolean 25 | ) : XyoServer(relayNode, procedureCatalog) { 26 | 27 | override var autoBridge: Boolean = false 28 | override var acceptBridging: Boolean = false 29 | 30 | var advertiser: XyoBluetoothAdvertiser? = null 31 | lateinit var server: XyoBluetoothServer 32 | 33 | var listen: Boolean 34 | get() { return advertiser?.started ?: false } 35 | set(value) { 36 | runBlocking { 37 | advertiser?.let { advertiser -> 38 | if (value) { 39 | log.info("Starting Advertiser") 40 | advertiser.startAdvertiser() 41 | } else { 42 | log.info("Stopping Advertiser") 43 | advertiser.stopAdvertiser() 44 | } 45 | } 46 | } 47 | } 48 | 49 | init { 50 | this.autoBridge = autoBridge 51 | this.acceptBridging = acceptBridging 52 | GlobalScope.launch { 53 | initServer(context) 54 | this@XyoBleServer.listen = listen 55 | } 56 | } 57 | 58 | private suspend fun initServer(context: Context): Boolean { 59 | advertiser = XyoBleSdk.advertiser(context) 60 | server = XyoBleSdk.server(context) 61 | var errorMessage: String? = null 62 | server.listener = object : XyoBluetoothServer.Listener { 63 | override fun onPipe(pipe: XyoNetworkPipe) { 64 | log.info("onPipe") 65 | GlobalScope.launch { 66 | boundWitnessStarted(null) 67 | val handler = XyoNetworkHandler(pipe) 68 | relayNode.addListener("XyoBleServer", object : XyoNodeListener() { 69 | override fun onBoundWitnessEndFailure(error: Exception?) { 70 | errorMessage = error?.message ?: error?.toString() ?: "Unknown Error" 71 | } 72 | }) 73 | val bw = relayNode.boundWitness(handler, procedureCatalog) 74 | relayNode.removeListener("XyoBleServer") 75 | boundWitnessCompleted(null, bw, errorMessage) 76 | pipe.close() 77 | return@launch 78 | } 79 | } 80 | } 81 | log.info("Initialized Server") 82 | return true 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoSnappyDBStorageProvider.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import android.content.Context 3 | import android.util.Base64 4 | import com.snappydb.DB 5 | import com.snappydb.DBFactory 6 | import com.snappydb.SnappydbException 7 | import kotlinx.coroutines.Deferred 8 | import kotlinx.coroutines.GlobalScope 9 | import kotlinx.coroutines.async 10 | import network.xyo.sdkcorekotlin.persist.XyoKeyValueStore 11 | import network.xyo.sdkcorekotlin.persist.XyoStorageException 12 | 13 | /** 14 | * A key value store implementation of the XyoStorageProviderInterface, in android using SnappyDB. 15 | * 16 | * For more information about SnappyDB: http://snappydb.com/ 17 | * 18 | * @param context The android context to open the SnappyDB with. 19 | */ 20 | open class XyoSnappyDBStorageProvider(private var context: Context) : XyoKeyValueStore { 21 | 22 | private val db = DBFactory.open(context) 23 | // TODO -DB is not being closed 24 | // TODO - use sync blocks - snappyDB is not thread safe 25 | // TODO - move save functions here instead of using getDB ? 26 | 27 | // get an instance of the opened DB 28 | fun getDB(): DB? { 29 | return db 30 | } 31 | 32 | override suspend fun containsKey(key: ByteArray): Boolean { 33 | try { 34 | return db.exists(makeKey(key)) 35 | } catch (dbException: SnappydbException) { 36 | throw XyoStorageException("Failed to read: $dbException") 37 | } 38 | } 39 | 40 | override suspend fun delete(key: ByteArray) { 41 | try { 42 | db.del(makeKey(key)) 43 | } catch (dbException: SnappydbException) { 44 | throw XyoStorageException("Failed to delete: $dbException") 45 | } 46 | } 47 | 48 | override suspend fun getAllKeys(): Iterator { 49 | try { 50 | val i = db.allKeysIterator() 51 | 52 | return object : Iterator { 53 | override fun hasNext(): Boolean { 54 | val hasNext = i.hasNext() 55 | 56 | if (!hasNext) { 57 | i.close() 58 | } 59 | 60 | return hasNext 61 | } 62 | 63 | override fun next(): ByteArray { 64 | return getKey(i.next(1)[0]) 65 | } 66 | } 67 | } catch (dbException: SnappydbException) { 68 | return arrayOf().iterator() 69 | } 70 | } 71 | 72 | override suspend fun read(key: ByteArray): ByteArray? { 73 | var result: ByteArray? = null 74 | try { 75 | result = db.getBytes(makeKey(key)) 76 | } catch (dbException: SnappydbException) { 77 | 78 | } 79 | return result 80 | } 81 | 82 | override suspend fun write(key: ByteArray, value: ByteArray) { 83 | try { 84 | db.put(makeKey(key), value) 85 | } catch (dbException: SnappydbException) { 86 | throw XyoStorageException("Failed to write: $dbException") 87 | } 88 | } 89 | 90 | fun reset() { 91 | db.destroy() 92 | } 93 | 94 | private fun makeKey(byteArray: ByteArray): String { 95 | return String(Base64.encode(byteArray, 0)) 96 | } 97 | 98 | private fun getKey(string: String): ByteArray { 99 | return Base64.decode(string, 0) 100 | } 101 | 102 | companion object { 103 | const val ARCHIVIST_LIST = "archlist" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/layout/fragment_tcpip_client.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 28 | 29 | 35 | 36 | 42 | 43 | 49 | 50 | 56 | 57 | 63 | 64 | 65 | 74 | 87 | 88 | -------------------------------------------------------------------------------- /xyo-android-library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'maven-publish' 4 | 5 | group = 'network.xyo' 6 | 7 | buildscript { 8 | repositories { 9 | mavenCentral() 10 | mavenLocal() 11 | google() 12 | maven { 13 | url "https://plugins.gradle.org/m2/" 14 | } 15 | } 16 | } 17 | 18 | Properties versionProps = new Properties() 19 | def versionPropsFile = file('version.properties') 20 | 21 | if (versionPropsFile.exists()) 22 | versionProps.load(new FileInputStream(versionPropsFile)) 23 | 24 | def majorVersion = 3 25 | def minorVersion = 1 26 | def patch = 44 27 | 28 | def verString = majorVersion + '.' + minorVersion + '.' + patch 29 | 30 | task printVersion { 31 | println verString 32 | } 33 | 34 | android { 35 | compileSdkVersion 31 36 | buildToolsVersion "30.0.3" 37 | ndkVersion "21.1.6352462" 38 | 39 | versionProps['VERSION_PATCH'] = patch.toString() 40 | versionProps.store(versionPropsFile.newWriter(), null) 41 | 42 | compileOptions { 43 | sourceCompatibility = 1.8 44 | targetCompatibility = 1.8 45 | kotlinOptions { 46 | allWarningsAsErrors = false 47 | } 48 | } 49 | 50 | defaultConfig { 51 | minSdkVersion 21 52 | targetSdkVersion 31 53 | } 54 | 55 | lintOptions { 56 | htmlReport false 57 | abortOnError false 58 | warningsAsErrors false 59 | disable "HardwareIds", "MissingPermission" 60 | } 61 | 62 | buildTypes { 63 | release { 64 | minifyEnabled false 65 | } 66 | debug { 67 | minifyEnabled false 68 | } 69 | } 70 | 71 | packagingOptions { 72 | exclude 'META-INF/build.kotlin_module' 73 | exclude 'META-INF/atomicfu.kotlin_module' 74 | exclude 'META-INF/library_release.kotlin_module' 75 | exclude 'META-INF/sdk-ble-android' 76 | exclude 'META-INF/sdk-base-android' 77 | exclude 'META-INF/sdk-core-kotlin' 78 | } 79 | 80 | } 81 | 82 | publishing { 83 | publications { 84 | Production(MavenPublication) { 85 | artifact("$buildDir/outputs/aar/xyo-android-library-release.aar") 86 | groupId 'network.xyo' 87 | artifactId 'sdk-xyo-android' 88 | version verString 89 | 90 | //The publication doesn't know about our dependencies, so we have to manually add them to the pom 91 | pom.withXml { 92 | def dependenciesNode = asNode().getAt('dependencies')[0] ?: asNode().appendNode('dependencies') 93 | //Iterate over the compile dependencies (we don't want the test ones), adding a node for each 94 | configurations.implementation.allDependencies.each { 95 | if (it.name != 'unspecified') { 96 | def dependencyNode = dependenciesNode.appendNode('dependency') 97 | dependencyNode.appendNode('groupId', it.group) 98 | dependencyNode.appendNode('artifactId', it.name) 99 | dependencyNode.appendNode('version', it.version) 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | dependencies { 108 | implementation fileTree(dir: 'libs', include: ['*.jar']) 109 | implementation "org.jetbrains.kotlin:kotlin-stdlib:1.6.0" 110 | implementation "org.jetbrains.kotlin:kotlin-reflect:1.6.0" 111 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2" 112 | implementation 'androidx.appcompat:appcompat:1.4.0' 113 | implementation 'androidx.core:core-ktx:1.7.0' 114 | api 'com.github.xyoraclenetwork:sdk-ble-android:4.1.3' 115 | api 'com.github.xyoraclenetwork:sdk-core-kotlin:3.1.4' 116 | implementation 'com.snappydb:snappydb-lib:0.5.2' 117 | } -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoTcpIpClient.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import android.net.Uri 3 | import java.io.IOException 4 | import java.net.Socket 5 | import kotlinx.coroutines.GlobalScope 6 | import kotlinx.coroutines.launch 7 | import kotlinx.coroutines.sync.Mutex 8 | import network.xyo.sdkcorekotlin.boundWitness.XyoBoundWitness 9 | import network.xyo.sdkcorekotlin.network.XyoNetworkHandler 10 | import network.xyo.sdkcorekotlin.network.XyoProcedureCatalog 11 | import network.xyo.sdkcorekotlin.network.tcp.XyoTcpPipe 12 | import network.xyo.sdkcorekotlin.node.XyoNodeListener 13 | import network.xyo.sdkcorekotlin.node.XyoRelayNode 14 | import java.nio.ByteBuffer 15 | 16 | @kotlin.ExperimentalUnsignedTypes 17 | class XyoTcpIpClient( 18 | relayNode: XyoRelayNode, 19 | procedureCatalog: XyoProcedureCatalog, 20 | override var autoBridge: Boolean, 21 | override var acceptBridging: Boolean, 22 | autoBoundWitness: Boolean 23 | ) : XyoClient(relayNode, procedureCatalog, autoBoundWitness) { 24 | 25 | init { 26 | relayNode.addListener("XyoTcpIpClient", object : XyoNodeListener() { 27 | override fun onBoundWitnessEndSuccess(boundWitness: XyoBoundWitness) { 28 | super.onBoundWitnessEndSuccess(boundWitness) 29 | if (autoBridge && ByteBuffer.wrap((relayNode.stateRepository.getIndex()?.valueCopy)).int % 5 == 0) { 30 | GlobalScope.launch { 31 | this@XyoTcpIpClient.bridge() 32 | } 33 | } 34 | } 35 | }) 36 | } 37 | 38 | private val bridgeMutex = Mutex() 39 | 40 | suspend fun bridge(): String? { 41 | var errorMessage: String? = null 42 | var networkErrorMessage: String? = null 43 | if (bridgeMutex.tryLock()) { 44 | log.info("bridge - started: [${knownBridges?.size}]") 45 | knownBridges?.let { knownBridges -> 46 | if (knownBridges.isEmpty()) { 47 | log.info("No known bridges, skipping bridging!") 48 | errorMessage = "No Known Bridges" 49 | } else { 50 | var bw: XyoBoundWitness? = null 51 | knownBridges.forEach { bridge -> 52 | log.info("Trying to bridge: $bridge") 53 | boundWitnessStarted(bridge) 54 | try { 55 | if (bw == null) { 56 | val uri = Uri.parse(bridge) 57 | 58 | log.info("Trying to bridge [info]: ${uri.host}:${uri.port}") 59 | 60 | val socket = Socket(uri.host, uri.port) 61 | val pipe = XyoTcpPipe(socket, null) 62 | val handler = XyoNetworkHandler(pipe) 63 | 64 | log.info("Starting Bridge BoundWitness") 65 | relayNode.addListener("XyoTcpIpClient-bridge", object : XyoNodeListener() { 66 | override fun onBoundWitnessEndFailure(error: Exception?) { 67 | errorMessage = error?.message ?: error?.toString() ?: "Unknown Error" 68 | } 69 | }) 70 | bw = relayNode.boundWitness(handler, procedureCatalog) 71 | relayNode.removeListener("XyoTcpIpClient-bridge") 72 | pipe.close() 73 | log.info("Bridge Result: $bw") 74 | } 75 | } catch (e: IOException) { 76 | log.info("Bridging Excepted $e") 77 | networkErrorMessage = e.message ?: e.toString() 78 | } 79 | boundWitnessCompleted(bridge, bw, errorMessage ?: networkErrorMessage) 80 | } 81 | } 82 | } 83 | bridgeMutex.unlock() 84 | } 85 | return errorMessage ?: networkErrorMessage 86 | } 87 | 88 | fun startBridge() { 89 | 90 | } 91 | 92 | override var scan: Boolean 93 | get() { return false } 94 | set(_) { } 95 | } 96 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/advertiser/XyoBluetoothAdvertiser.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth.advertiser 2 | 3 | import android.bluetooth.BluetoothAdapter 4 | import android.bluetooth.le.AdvertiseData 5 | import android.bluetooth.le.AdvertiseSettings 6 | import android.os.ParcelUuid 7 | import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResult 8 | import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResultErrorCode 9 | import network.xyo.ble.generic.gatt.server.XYBluetoothAdvertiser 10 | import network.xyo.ble.generic.gatt.server.XYIBeaconAdvertiseDataCreator 11 | import network.xyo.sdk.bluetooth.XyoUuids 12 | import java.nio.ByteBuffer 13 | 14 | /** 15 | * A class for managing XYO advertising. 16 | * 17 | * @property major The device major to advertise 18 | * @property minor The device minor to advertise 19 | * @param advertiser The XY advertiser to advertise with. 20 | */ 21 | @kotlin.ExperimentalUnsignedTypes 22 | class XyoBluetoothAdvertiser( 23 | private val major: UShort, 24 | private val minor: UShort, 25 | private val advertiser: XYBluetoothAdvertiser 26 | ) { 27 | 28 | private var includeName = false 29 | 30 | /** 31 | * Start a advertisement cycle 32 | */ 33 | fun configureAdvertiser() { 34 | includeName = BluetoothAdapter.getDefaultAdapter().setName("Xyo") 35 | if (advertiser.isMultiAdvertisementSupported) { 36 | configureAdverserMulti() 37 | return 38 | } 39 | configureAdvertiserSingle() 40 | } 41 | 42 | // private fun getAdvertiseUuid (uuid: UUID, major: ByteArray, minor: ByteArray): UUID { 43 | // val uuidString = uuid.toString().dropLast(8) 44 | // val majorString = major.toHexString().drop(2) 45 | // val minorString = minor.toHexString().drop(2) 46 | // 47 | // return UUID.fromString( minorString + majorString + uuidString) 48 | // } 49 | 50 | private fun configureAdverserMulti() { 51 | val encodeMajor = ByteBuffer.allocate(2).putShort(major.toShort()).array() 52 | val encodedMinor = ByteBuffer.allocate(2).putShort(minor.toShort()).array() 53 | val advertiseData = XYIBeaconAdvertiseDataCreator.create( 54 | encodeMajor, 55 | encodedMinor, 56 | XyoUuids.XYO_SERVICE, 57 | APPLE_MANUFACTURER_ID, 58 | false 59 | ).build() 60 | 61 | val responseData = AdvertiseData.Builder() 62 | .setIncludeDeviceName(includeName) 63 | .addServiceUuid(ParcelUuid( 64 | XyoUuids.XYO_SERVICE 65 | )) 66 | .build() 67 | 68 | advertiser.advertisingData = advertiseData 69 | advertiser.advertisingResponse = responseData 70 | advertiser.changeContactable(true) 71 | advertiser.changeAdvertisingMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED) 72 | advertiser.changeAdvertisingTxLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) 73 | } 74 | 75 | private fun configureAdvertiserSingle() { 76 | val advertiseData = AdvertiseData.Builder() 77 | .addServiceUuid(ParcelUuid(XyoUuids.XYO_SERVICE)) 78 | .setIncludeDeviceName(includeName) 79 | .build() 80 | 81 | advertiser.advertisingData = advertiseData 82 | advertiser.changeContactable(true) 83 | advertiser.changeAdvertisingMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED) 84 | advertiser.changeAdvertisingTxLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) 85 | } 86 | 87 | /** 88 | * Stop the current advertisement cycle 89 | */ 90 | fun stopAdvertiser() { 91 | advertiser.stopAdvertising() 92 | _started = false 93 | } 94 | 95 | suspend fun startAdvertiser(): XYBluetoothResult { 96 | val result = advertiser.startAdvertising() 97 | _started = result.error == XYBluetoothResultErrorCode.None 98 | return result 99 | } 100 | 101 | var _started: Boolean = false 102 | val started: Boolean 103 | get() { return _started } 104 | 105 | companion object { 106 | const val APPLE_MANUFACTURER_ID = 76 107 | } 108 | } -------------------------------------------------------------------------------- /xyo-android-sample/src/main/java/network/xyo/sdk/sample/ui/ble_server/BleServerFragment.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.sample.ui.ble_server 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import kotlinx.coroutines.InternalCoroutinesApi 9 | import network.xyo.sdk.XyoBleNetwork 10 | import network.xyo.sdk.XyoBoundWitnessTarget 11 | import network.xyo.sdk.XyoSdk 12 | import network.xyo.sdk.sample.databinding.FragmentBleServerBinding 13 | import network.xyo.sdk.sample.ui 14 | import network.xyo.sdkcorekotlin.boundWitness.XyoBoundWitness 15 | 16 | @InternalCoroutinesApi 17 | @kotlin.ExperimentalUnsignedTypes 18 | class BleServerFragment : Fragment() { 19 | 20 | var statusText = "" 21 | private var _binding: FragmentBleServerBinding? = null 22 | private val binding get() = _binding!! 23 | 24 | fun addStatus(status: String) { 25 | statusText = "${statusText}\r\n$status" 26 | binding.textBleServer.let { 27 | ui { 28 | it.text = statusText 29 | } 30 | } 31 | } 32 | 33 | override fun onCreateView( 34 | inflater: LayoutInflater, 35 | container: ViewGroup?, 36 | savedInstanceState: Bundle? 37 | ): View? { 38 | _binding = FragmentBleServerBinding.inflate(inflater, container, false) 39 | val view = binding.root 40 | return view 41 | } 42 | 43 | fun updateUI() { 44 | ui { 45 | (XyoSdk.nodes[0].networks["ble"] as? XyoBleNetwork)?.let { network -> 46 | binding.acceptBridging.isChecked = network.server.acceptBridging 47 | binding.acceptBridging.setOnCheckedChangeListener { _, isChecked -> 48 | network.server.acceptBridging = isChecked 49 | } 50 | 51 | binding.autoBridge.isChecked = network.server.autoBridge 52 | binding.autoBridge.setOnCheckedChangeListener { _, isChecked -> 53 | network.server.autoBridge = isChecked 54 | } 55 | 56 | binding.listen.isChecked = network.server.listen 57 | binding.listen.setOnCheckedChangeListener { _, isChecked -> 58 | network.server.listen = isChecked 59 | } 60 | } 61 | } 62 | } 63 | 64 | override fun onResume() { 65 | updateUI() 66 | super.onResume() 67 | } 68 | 69 | override fun onDestroyView() { 70 | super.onDestroyView() 71 | _binding = null 72 | (XyoSdk.nodes[0].networks["ble"] as? XyoBleNetwork)?.server?.listeners?.remove("sample") 73 | } 74 | 75 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 76 | super.onViewCreated(view, savedInstanceState) 77 | 78 | (XyoSdk.nodes[0].networks["ble"] as? XyoBleNetwork)?.let { network -> 79 | ui { 80 | network.server.listeners["sample"] = object : XyoBoundWitnessTarget.Listener() { 81 | override fun boundWitnessStarted(source: Any?, target: XyoBoundWitnessTarget) { 82 | super.boundWitnessStarted(source, target) 83 | addStatus("Bound Witness Started [${source?.javaClass?.name}]") 84 | } 85 | 86 | override fun boundWitnessCompleted(source: Any?, target: XyoBoundWitnessTarget, boundWitness: XyoBoundWitness?, error:String?) { 87 | super.boundWitnessCompleted(source, target, boundWitness, error) 88 | val index = target.relayNode.originState.index.valueCopy.toList().toString() 89 | if (error == null) { 90 | addStatus("Bound Witness Completed [$index] [${boundWitness?.completed}]") 91 | } else { 92 | addStatus("Bound Witness Failed [$error]") 93 | } 94 | addStatus("- - - - - -") 95 | } 96 | } 97 | updateUI() 98 | binding.textBleServer.text = "" 99 | binding.publicKey.text = network.server.publicKey 100 | } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /xyo-android-sample/src/main/java/network/xyo/sdk/sample/ui/tcpip_client/TcpIpClientFragment.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.sample.ui.tcpip_client 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import network.xyo.sdk.XyoBoundWitnessTarget 9 | import network.xyo.sdk.XyoSdk 10 | import network.xyo.sdk.XyoTcpIpNetwork 11 | import network.xyo.sdk.sample.databinding.FragmentTcpipClientBinding 12 | import network.xyo.sdk.sample.ui 13 | import network.xyo.sdkcorekotlin.boundWitness.XyoBoundWitness 14 | 15 | @kotlin.ExperimentalUnsignedTypes 16 | class TcpIpClientFragment : Fragment() { 17 | 18 | private var _binding: FragmentTcpipClientBinding? = null 19 | private val binding get() = _binding!! 20 | 21 | override fun onCreateView( 22 | inflater: LayoutInflater, 23 | container: ViewGroup?, 24 | savedInstanceState: Bundle? 25 | ): View? { 26 | _binding = FragmentTcpipClientBinding.inflate(inflater, container, false) 27 | val view = binding.root 28 | return view 29 | } 30 | 31 | fun addStatus(status: String) { 32 | ui { 33 | val sb = StringBuilder() 34 | sb.append(binding.textTcpipClient.text) 35 | sb.append("\r\n") 36 | sb.append(status) 37 | binding.textTcpipClient.text = sb.toString() 38 | } 39 | } 40 | 41 | fun updateUI() { 42 | ui { 43 | (XyoSdk.nodes[0].networks["tcpip"] as? XyoTcpIpNetwork)?.let { network -> 44 | binding.acceptBridging.isChecked = network.client.acceptBridging 45 | binding.acceptBridging.setOnCheckedChangeListener { _, isChecked -> 46 | network.client.acceptBridging = isChecked 47 | } 48 | 49 | binding.autoBoundWitness.isChecked = network.client.autoBoundWitness 50 | binding.autoBoundWitness.setOnCheckedChangeListener { _, isChecked -> 51 | network.client.autoBoundWitness = isChecked 52 | } 53 | 54 | binding.autoBridge.isChecked = network.client.autoBridge 55 | binding.autoBridge.setOnCheckedChangeListener { _, isChecked -> 56 | network.client.autoBridge = isChecked 57 | } 58 | 59 | binding.scan.isChecked = network.client.scan 60 | binding.scan.setOnCheckedChangeListener { _, isChecked -> 61 | network.client.scan = isChecked 62 | } 63 | } 64 | } 65 | } 66 | 67 | override fun onResume() { 68 | updateUI() 69 | super.onResume() 70 | } 71 | 72 | override fun onDestroyView() { 73 | super.onDestroyView() 74 | _binding = null 75 | (XyoSdk.nodes[0].networks["tcpip"] as? XyoTcpIpNetwork)?.client?.listeners?.remove("sample") 76 | } 77 | 78 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 79 | super.onViewCreated(view, savedInstanceState) 80 | 81 | (XyoSdk.nodes[0].networks["tcpip"] as? XyoTcpIpNetwork)?.let { network -> 82 | 83 | network.client.listeners["sample"] = object : XyoBoundWitnessTarget.Listener() { 84 | override fun boundWitnessStarted(source: Any?, target: XyoBoundWitnessTarget) { 85 | super.boundWitnessStarted(source, target) 86 | addStatus("Bound Witness Started [${source?.javaClass?.name}]") 87 | } 88 | 89 | override fun boundWitnessCompleted(source: Any?, target: XyoBoundWitnessTarget, boundWitness: XyoBoundWitness?, error:String?) { 90 | super.boundWitnessCompleted(source, target, boundWitness, error) 91 | val index = target.relayNode.originState.index.valueCopy.toList().toString() 92 | if (error == null) { 93 | addStatus("Bound Witness Completed $index [${boundWitness?.completed}]") 94 | } else { 95 | addStatus("Bound Witness Failed [$error]") 96 | } 97 | addStatus("- - - - - -") 98 | } 99 | 100 | } 101 | 102 | ui { 103 | binding.textTcpipClient.text = "" 104 | binding.publicKey.text = network.client.publicKey 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/client/XyoBluetoothClientPipe.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth.client 2 | 3 | import android.bluetooth.BluetoothGatt 4 | import android.util.Log 5 | import kotlinx.coroutines.* 6 | import network.xyo.ble.generic.devices.XYBluetoothDevice 7 | import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResult 8 | import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResultErrorCode 9 | import network.xyo.ble.generic.listeners.XYBluetoothDeviceListener 10 | import network.xyo.sdk.bluetooth.XyoUuids 11 | import network.xyo.sdkcorekotlin.network.XyoAdvertisePacket 12 | import network.xyo.sdkcorekotlin.network.XyoNetworkPipe 13 | import network.xyo.sdkcorekotlin.schemas.XyoSchemas 14 | import network.xyo.sdkobjectmodelkotlin.structure.XyoObjectStructure 15 | import kotlin.coroutines.suspendCoroutine 16 | import network.xyo.sdkobjectmodelkotlin.toHexString 17 | import kotlin.coroutines.resume 18 | 19 | @kotlin.ExperimentalUnsignedTypes 20 | class XyoBluetoothClientPipe(val client: XyoBluetoothClient) : XyoNetworkPipe { 21 | 22 | override val initiationData: XyoAdvertisePacket? = null 23 | 24 | /** 25 | * Closes the pipe between parties. In this case, disconnects from the device and closes the GATT. This should 26 | * be called after the pipe is finished being used. 27 | */ 28 | override suspend fun close(): Boolean { 29 | Log.i(TAG, "close: started") 30 | client.disconnect() 31 | return true 32 | } 33 | 34 | override fun getNetworkHeuristics(): Array { 35 | Log.i(TAG, "getNetworkHeuristics: started") 36 | val toReturn = ArrayList() 37 | 38 | val rssi = client.rssi 39 | 40 | if (rssi != null) { 41 | val encodedRssi = XyoObjectStructure.newInstance(XyoSchemas.RSSI, byteArrayOf(rssi.toByte())) 42 | toReturn.add(encodedRssi) 43 | } 44 | 45 | val pwr = XyoObjectStructure.newInstance(XyoSchemas.BLE_POWER_LVL, byteArrayOf(client.power)) 46 | toReturn.add(pwr) 47 | 48 | return toReturn.toTypedArray() 49 | } 50 | 51 | fun wrapAsync() = GlobalScope.async { 52 | return@async client.readIncoming() 53 | } 54 | 55 | /** 56 | * Sends data and waits for a response if the waitForResponse flag is set to true. 57 | * NOTE: The send and recieve are abstracted away from the caller, thus the exact bytes transferred is unclear. 58 | * 59 | * @param data Data to send to the other end of the pipe. 60 | * @param waitForResponse If this flag is set, this function will wait for a response. If not, will return 61 | * null. 62 | * @return A deferred ByteArray of the response of the server. If waitForResponse is null, will return null. 63 | */ 64 | override suspend fun send(data: ByteArray, waitForResponse: Boolean): ByteArray? { 65 | Log.i(TAG, "send: started") 66 | return suspendCoroutine { cont -> 67 | val disconnectKey = this.toString() + Math.random().toString() 68 | 69 | val sendAndReceive = GlobalScope.async { 70 | Log.i(TAG, "send: sendAndReceive: started") 71 | val job = wrapAsync() 72 | val packetError = client.chunkSend(data, XyoUuids.XYO_PIPE, XyoUuids.XYO_SERVICE, 4) 73 | 74 | Log.i(TAG, "Sent entire packet to the server.") 75 | if (packetError == XYBluetoothResultErrorCode.None) { 76 | Log.i(TAG, "Sent entire packet to the server (good).") 77 | var valueIn: ByteArray? = null 78 | 79 | if (waitForResponse) { 80 | valueIn = job.await() 81 | } 82 | 83 | Log.i(TAG, "Have read entire server response packet. ${valueIn?.toHexString()}") 84 | client.removeListener(disconnectKey) 85 | cont.resume(valueIn) 86 | } else { 87 | client.log.info("Error sending entire packet to the server. ${packetError.name}") 88 | client.removeListener(disconnectKey) 89 | cont.resume(null) 90 | } 91 | } 92 | 93 | // add the disconnect listener 94 | client.log.info("Adding disconnect listener.") 95 | client.addListener(disconnectKey, object : XYBluetoothDeviceListener() { 96 | override fun connectionStateChanged(device: XYBluetoothDevice, newState: Int) { 97 | Log.i(TAG, "send: connectionStateChanged: $newState") 98 | when (newState) { 99 | 100 | BluetoothGatt.STATE_DISCONNECTED -> { 101 | client.log.info("Someone disconnected.") 102 | 103 | if (cont.context.isActive) { 104 | client.log.info("Context is still active.") 105 | client.removeListener(disconnectKey) 106 | 107 | client.log.info("Canceling send and receive.") 108 | 109 | cont.resume(null) 110 | cont.context.cancel() 111 | sendAndReceive.cancel() 112 | } 113 | } 114 | } 115 | } 116 | }) 117 | } 118 | } 119 | 120 | companion object { 121 | const val TAG = "XyoBluetoothClientPipe" 122 | } 123 | } -------------------------------------------------------------------------------- /xyo-android-sample/src/main/java/network/xyo/sdk/sample/ui/ble_client/BleClientFragment.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.sample.ui.ble_client 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.Fragment 9 | 10 | import kotlinx.coroutines.GlobalScope 11 | import kotlinx.coroutines.InternalCoroutinesApi 12 | import kotlinx.coroutines.delay 13 | import kotlinx.coroutines.launch 14 | import network.xyo.sdk.sample.R 15 | import network.xyo.sdk.XyoBleNetwork 16 | import network.xyo.sdk.XyoBoundWitnessTarget 17 | import network.xyo.sdk.XyoSdk 18 | import network.xyo.sdk.sample.databinding.FragmentBleClientBinding 19 | import network.xyo.sdk.sample.ui 20 | 21 | import network.xyo.sdkcorekotlin.boundWitness.XyoBoundWitness 22 | 23 | @InternalCoroutinesApi 24 | @kotlin.ExperimentalUnsignedTypes 25 | class BleClientFragment : Fragment() { 26 | 27 | var deviceCount = 0 28 | var xyoDeviceCount = 0 29 | var nearbyXyoDeviceCount = 0 30 | 31 | var autoUpdateUi = false 32 | var statusText = "" 33 | 34 | private var _binding: FragmentBleClientBinding? = null 35 | private val binding get() = _binding!! 36 | 37 | override fun onCreateView( 38 | inflater: LayoutInflater, 39 | container: ViewGroup?, 40 | savedInstanceState: Bundle? 41 | ): View? { 42 | _binding = FragmentBleClientBinding.inflate(inflater, container, false) 43 | val view = binding.root 44 | return view 45 | } 46 | 47 | override fun onDestroyView() { 48 | super.onDestroyView() 49 | _binding = null 50 | (XyoSdk.nodes[0].networks["ble"] as? XyoBleNetwork)?.client?.listeners?.remove("sample") 51 | } 52 | 53 | override fun onAttach(context: Context) { 54 | super.onAttach(context) 55 | autoUpdateUi = true 56 | GlobalScope.launch { 57 | while(autoUpdateUi) { 58 | this@BleClientFragment.updateUI() 59 | delay(1000) 60 | } 61 | } 62 | } 63 | 64 | override fun onDetach() { 65 | autoUpdateUi = false 66 | super.onDetach() 67 | } 68 | 69 | fun addStatus(status: String) { 70 | statusText = "${statusText}\r\n$status" 71 | ui { 72 | binding.textBleClient.text = statusText 73 | } 74 | } 75 | 76 | @InternalCoroutinesApi 77 | private fun updateUI() { 78 | ui { 79 | (XyoSdk.nodes[0].networks["ble"] as? XyoBleNetwork)?.let { network -> 80 | binding.acceptBridging.isChecked = network.client.acceptBridging 81 | binding.acceptBridging?.setOnCheckedChangeListener { _, isChecked -> 82 | network.client.acceptBridging = isChecked 83 | } 84 | 85 | binding.autoBoundWitness?.isChecked = network.client.autoBoundWitness 86 | binding.autoBoundWitness?.setOnCheckedChangeListener { _, isChecked -> 87 | network.client.autoBoundWitness = isChecked 88 | } 89 | 90 | binding.autoBridge?.isChecked = network.client.autoBridge 91 | binding.autoBridge?.setOnCheckedChangeListener { _, isChecked -> 92 | network.client.autoBridge = isChecked 93 | } 94 | 95 | binding.scan?.isChecked = network.client.scan 96 | binding.scan?.setOnCheckedChangeListener { _, isChecked -> 97 | network.client.scan = isChecked 98 | } 99 | 100 | deviceCount = network.client.deviceCount 101 | xyoDeviceCount = network.client.xyoDeviceCount 102 | nearbyXyoDeviceCount = network.client.nearbyXyoDeviceCount 103 | } 104 | binding.detectedDevices?.text = deviceCount.toString() 105 | binding.detectedXyoDevices?.text = xyoDeviceCount.toString() 106 | binding.nearbyXyoDevices?.text = nearbyXyoDeviceCount.toString() 107 | } 108 | } 109 | 110 | override fun onResume() { 111 | updateUI() 112 | super.onResume() 113 | } 114 | 115 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 116 | super.onViewCreated(view, savedInstanceState) 117 | 118 | (XyoSdk.nodes[0].networks["ble"] as? XyoBleNetwork)?.let { network -> 119 | 120 | network.client.listeners["sample"] = object : XyoBoundWitnessTarget.Listener() { 121 | override fun boundWitnessStarted(source: Any?, target: XyoBoundWitnessTarget) { 122 | super.boundWitnessStarted(source, target) 123 | addStatus("Bound Witness Started [${source?.javaClass?.name}]") 124 | } 125 | 126 | override fun boundWitnessCompleted(source: Any?, target: XyoBoundWitnessTarget, boundWitness: XyoBoundWitness?, error:String?) { 127 | super.boundWitnessCompleted(source, target, boundWitness, error) 128 | val index = target.relayNode.originState.index.valueCopy.toList().toString() 129 | if (error == null) { 130 | addStatus("Bound Witness Completed $index [${boundWitness?.completed}]") 131 | } else { 132 | addStatus("Bound Witness Failed [$error]") 133 | } 134 | addStatus("- - - - - -") 135 | } 136 | } 137 | 138 | ui { 139 | binding.textBleClient.text = "" 140 | binding.publicKey.text = network.client.publicKey 141 | } 142 | } 143 | } 144 | } -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/Base58.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | 3 | /** 4 | * Base58 is a way to encode addresses (or arbitrary data) as alphanumeric strings. 5 | * Compared to base64, this encoding eliminates ambiguities created by O0Il and potential splits from punctuation 6 | * 7 | * The basic idea of the encoding is to treat the data bytes as a large number represented using 8 | * base-256 digits, convert the number to be represented using base-58 digits, preserve the exact 9 | * number of leading zeros (which are otherwise lost during the mathematical operations on the 10 | * numbers), and finally represent the resulting base-58 digits as alphanumeric ASCII characters. 11 | * 12 | * This is the Kotlin implementation of base58 - it is based implementation of base58 in java 13 | * in bitcoinj (https://bitcoinj.github.io) - thanks to Google Inc. and Andreas Schildbach 14 | * 15 | */ 16 | 17 | import java.util.* 18 | 19 | private const val ENCODED_ZERO = '1' 20 | private const val CHECKSUM_SIZE = 4 21 | 22 | private const val alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 23 | private val alphabetIndices by lazy { 24 | IntArray(128) { alphabet.indexOf(it.toChar()) } 25 | } 26 | 27 | /** 28 | * Encodes the bytes as a base58 string (no checksum is appended). 29 | * 30 | * @return the base58-encoded string 31 | */ 32 | @kotlin.ExperimentalUnsignedTypes 33 | fun ByteArray.toBase58String(): String { 34 | 35 | val input = copyOf(size) // since we modify it in-place 36 | if (input.isEmpty()) { 37 | return "" 38 | } 39 | // Count leading zeros. 40 | var zeros = 0 41 | while (zeros < input.size && input[zeros].toInt() == 0) { 42 | ++zeros 43 | } 44 | // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters) 45 | val encoded = CharArray(input.size * 2) // upper bound 46 | var outputStart = encoded.size 47 | var inputStart = zeros 48 | while (inputStart < input.size) { 49 | encoded[--outputStart] = alphabet[divmod(input, inputStart.toUInt(), 256.toUInt(), 58.toUInt()).toInt()] 50 | if (input[inputStart].toInt() == 0) { 51 | ++inputStart // optimization - skip leading zeros 52 | } 53 | } 54 | // Preserve exactly as many leading encoded zeros in output as there were leading zeros in data. 55 | while (outputStart < encoded.size && encoded[outputStart] == ENCODED_ZERO) { 56 | ++outputStart 57 | } 58 | while (--zeros >= 0) { 59 | encoded[--outputStart] = ENCODED_ZERO 60 | } 61 | // Return encoded string (including encoded leading zeros). 62 | return String(encoded, outputStart, encoded.size - outputStart) 63 | } 64 | 65 | /** 66 | * Decodes the base58 string into a [ByteArray] 67 | * 68 | * @return the decoded data bytes 69 | * @throws NumberFormatException if the string is not a valid base58 string 70 | */ 71 | @kotlin.ExperimentalUnsignedTypes 72 | @Throws(NumberFormatException::class) 73 | fun String.fromBase58(): ByteArray { 74 | if (isEmpty()) { 75 | return ByteArray(0) 76 | } 77 | // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). 78 | val input58 = ByteArray(length) 79 | for (i in 0 until length) { 80 | val c = this[i] 81 | val digit = if (c.toInt() < 128) alphabetIndices[c.toInt()] else -1 82 | if (digit < 0) { 83 | throw NumberFormatException("Illegal character $c at position $i") 84 | } 85 | input58[i] = digit.toByte() 86 | } 87 | // Count leading zeros. 88 | var zeros = 0 89 | while (zeros < input58.size && input58[zeros].toInt() == 0) { 90 | ++zeros 91 | } 92 | // Convert base-58 digits to base-256 digits. 93 | val decoded = ByteArray(length) 94 | var outputStart = decoded.size 95 | var inputStart = zeros 96 | while (inputStart < input58.size) { 97 | decoded[--outputStart] = divmod(input58, inputStart.toUInt(), 58.toUInt(), 256.toUInt()).toByte() 98 | if (input58[inputStart].toInt() == 0) { 99 | ++inputStart // optimization - skip leading zeros 100 | } 101 | } 102 | // Ignore extra leading zeroes that were added during the calculation. 103 | while (outputStart < decoded.size && decoded[outputStart].toInt() == 0) { 104 | ++outputStart 105 | } 106 | // Return decoded data (including original number of leading zeros). 107 | return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.size) 108 | } 109 | 110 | /** 111 | * Divides a number, represented as an array of bytes each containing a single digit 112 | * in the specified base, by the given divisor. The given number is modified in-place 113 | * to contain the quotient, and the return value is the remainder. 114 | * 115 | * @param number the number to divide 116 | * @param firstDigit the index within the array of the first non-zero digit 117 | * (this is used for optimization by skipping the leading zeros) 118 | * @param base the base in which the number's digits are represented (up to 256) 119 | * @param divisor the number to divide by (up to 256) 120 | * @return the remainder of the division operation 121 | */ 122 | @kotlin.ExperimentalUnsignedTypes 123 | private fun divmod(number: ByteArray, firstDigit: UInt, base: UInt, divisor: UInt): UInt { 124 | // this is just long division which accounts for the base of the input digits 125 | var remainder = 0.toUInt() 126 | for (i in firstDigit until number.size.toUInt()) { 127 | val digit = number[i.toInt()].toUByte() 128 | val temp = remainder * base + digit 129 | number[i.toInt()] = (temp / divisor).toByte() 130 | remainder = temp % divisor 131 | } 132 | return remainder 133 | } 134 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /xyo-android-sample/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/res/layout/fragment_ble_client.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 28 | 29 | 35 | 36 | 42 | 43 | 49 | 50 | 56 | 57 | 63 | 64 | 68 | 74 | 81 | 82 | 86 | 92 | 99 | 100 | 104 | 110 | 117 | 118 | 119 | 120 | 129 | 142 | 143 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoBleClient.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("SpellCheckingInspection") 2 | 3 | package network.xyo.sdk 4 | import android.content.Context 5 | import android.util.Log 6 | import java.util.* 7 | import kotlinx.coroutines.GlobalScope 8 | import kotlinx.coroutines.launch 9 | import kotlinx.coroutines.sync.Mutex 10 | import network.xyo.ble.generic.devices.XYBluetoothDevice 11 | import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResult 12 | import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResultErrorCode 13 | import network.xyo.ble.generic.scanner.XYSmartScanListener 14 | import network.xyo.ble.generic.scanner.XYSmartScanModern 15 | import network.xyo.sdk.bluetooth.client.* 16 | import network.xyo.sdkcorekotlin.boundWitness.XyoBoundWitness 17 | import network.xyo.sdkcorekotlin.crypto.signing.ecdsa.secp256k.XyoSha256WithSecp256K 18 | import network.xyo.sdkcorekotlin.network.XyoNetworkHandler 19 | import network.xyo.sdkcorekotlin.network.XyoProcedureCatalog 20 | import network.xyo.sdkcorekotlin.node.XyoNodeListener 21 | import network.xyo.sdkcorekotlin.node.XyoRelayNode 22 | 23 | @kotlin.ExperimentalUnsignedTypes 24 | class XyoBleClient( 25 | context: Context, 26 | relayNode: XyoRelayNode, 27 | procedureCatalog: XyoProcedureCatalog, 28 | autoBridge: Boolean, 29 | acceptBridging: Boolean, 30 | autoBoundWitness: Boolean, 31 | scan: Boolean 32 | ) : XyoClient(relayNode, procedureCatalog, autoBoundWitness) { 33 | 34 | override var autoBridge: Boolean = false 35 | override var acceptBridging: Boolean = false 36 | 37 | override var deviceCount = 0 38 | override var xyoDeviceCount = 0 39 | override var nearbyXyoDeviceCount = 0 40 | 41 | var supportBridgeX = false 42 | var supportSentinelX = false 43 | var minimumRssi = -70 44 | 45 | val minBWTimeGap = 10 * 1000 46 | 47 | var lastBoundWitnessTime = Date().time - minBWTimeGap // ten seconds ago 48 | 49 | private var scanner: XYSmartScanModern 50 | 51 | private val boundWitnessMutex = Mutex() 52 | 53 | override var scan: Boolean 54 | get() { 55 | // val tag = "TAG " 56 | // Log.i(tag, "Does this scan??") 57 | return scanner.started() 58 | } 59 | set(value) { 60 | GlobalScope.launch { 61 | if (value && !scanner.started()) { 62 | scanner.start() 63 | } else if (!value && scanner.started()) { 64 | scanner.stop() 65 | } 66 | } 67 | } 68 | 69 | private val scannerListener = object : XYSmartScanListener() { 70 | // override fun onStart 71 | override fun entered(device: XYBluetoothDevice) { 72 | val tag = "TAG " 73 | super.entered(device) 74 | deviceCount++ 75 | Log.i(tag, "Xyo Device Entered: ${device.id}") 76 | xyoDeviceCount++ 77 | } 78 | 79 | override fun exited(device: XYBluetoothDevice) { 80 | val tag = "TAG " 81 | super.exited(device) 82 | deviceCount-- 83 | Log.i(tag, "Xyo Device Exited: ${device.id}") 84 | xyoDeviceCount-- 85 | } 86 | override fun detected(device: XYBluetoothDevice) { 87 | super.detected(device) 88 | if (this@XyoBleClient.autoBoundWitness) { 89 | if (Date().time - lastBoundWitnessTime > minBWTimeGap) { 90 | (device as? XyoBluetoothClient)?.let { client -> 91 | device.rssi?.let { rssi -> 92 | if (rssi < minimumRssi) { 93 | log.info("Rssi too low: $rssi") 94 | return 95 | } 96 | (client as? XyoBridgeX)?.let { 97 | if (!supportBridgeX) { 98 | log.info("BridgeX not Supported: $rssi") 99 | return 100 | } 101 | } 102 | (client as? XyoSentinelX)?.let { 103 | if (!supportSentinelX) { 104 | log.info("SentinelX not Supported: $rssi") 105 | return 106 | } 107 | } 108 | nearbyXyoDeviceCount++ 109 | GlobalScope.launch { 110 | tryBoundWitnessWithDevice(client) 111 | } 112 | } 113 | } 114 | } 115 | } 116 | } 117 | } 118 | 119 | suspend fun tryBoundWitnessWithDevice(device: XyoBluetoothClient) { 120 | if (boundWitnessMutex.tryLock()) { 121 | boundWitnessStarted(device) 122 | 123 | var errorMessage: String? = null 124 | 125 | val result = device.connection { 126 | val pipe = device.createPipe() 127 | 128 | if (pipe != null) { 129 | val handler = XyoNetworkHandler(pipe) 130 | 131 | relayNode.addListener("tryBoundWitnessWithDevice", object : XyoNodeListener() { 132 | override fun onBoundWitnessEndFailure(error: Exception?) { 133 | errorMessage = error?.message ?: error?.toString() ?: "Unknown Error" 134 | } 135 | }) 136 | 137 | val bw = relayNode.boundWitness(handler, procedureCatalog) 138 | 139 | relayNode.removeListener("tryBoundWitnessWithDevice") 140 | 141 | return@connection XYBluetoothResult(bw) 142 | } 143 | 144 | return@connection XYBluetoothResult(null) 145 | } 146 | val errorCode: String? = 147 | if (result.error != XYBluetoothResultErrorCode.None) { 148 | result.error.toString() 149 | } else { 150 | errorMessage 151 | } 152 | boundWitnessCompleted(device, result.value, errorCode) 153 | lastBoundWitnessTime = Date().time 154 | boundWitnessMutex.unlock() 155 | } 156 | } 157 | 158 | init { 159 | this.autoBridge = autoBridge 160 | this.acceptBridging = acceptBridging 161 | XyoBluetoothClient.enable(true) 162 | XyoBridgeX.enable(true) 163 | XyoSentinelX.enable(true) 164 | XyoIosAppX.enable(true) 165 | XyoAndroidAppX.enable(true) 166 | XyoSha256WithSecp256K.enable() 167 | this.scanner = XYSmartScanModern(context) 168 | this.scanner.addListener("xyo_client", this.scannerListener) 169 | this.scan = scan 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /xyo-android-sample/src/main/java/network/xyo/sdk/sample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.sample 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.location.LocationManager 7 | import android.util.Log 8 | import android.os.Bundle 9 | import com.google.android.material.bottomnavigation.BottomNavigationView 10 | import androidx.appcompat.app.AppCompatActivity 11 | import androidx.core.app.ActivityCompat 12 | import androidx.core.content.ContextCompat 13 | import androidx.navigation.findNavController 14 | import androidx.navigation.ui.AppBarConfiguration 15 | import androidx.navigation.ui.setupActionBarWithNavController 16 | import androidx.navigation.ui.setupWithNavController 17 | import kotlinx.coroutines.GlobalScope 18 | import kotlinx.coroutines.InternalCoroutinesApi 19 | import kotlinx.coroutines.launch 20 | import network.xyo.sdk.* 21 | import network.xyo.sdkcorekotlin.heuristics.XyoHeuristicGetter 22 | import network.xyo.sdkobjectmodelkotlin.structure.XyoObjectStructure 23 | import java.nio.ByteBuffer 24 | import network.xyo.sdkcorekotlin.schemas.* 25 | import network.xyo.sdkobjectmodelkotlin.structure.XyoIterableStructure 26 | 27 | @InternalCoroutinesApi 28 | @kotlin.ExperimentalUnsignedTypes 29 | class MainActivity : AppCompatActivity() { 30 | lateinit var node: XyoNode 31 | 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | 35 | checkPermissions() 36 | } 37 | 38 | private fun checkPermissions () { 39 | // check if we already have the permission 40 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) 41 | == PackageManager.PERMISSION_GRANTED) { 42 | 43 | // if we do, continue the setup process 44 | setupNodeAndUI() 45 | return 46 | } 47 | 48 | // if we do not, request the permission 49 | ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1) 50 | } 51 | 52 | 53 | // this gets called when the user takes action on a permission request 54 | override fun onRequestPermissionsResult( 55 | requestCode: Int, 56 | permissions: Array, 57 | grantResults: IntArray 58 | ) { 59 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 60 | 61 | if (requestCode != 1) { 62 | return 63 | } 64 | 65 | // recheck the permissions 66 | checkPermissions() 67 | } 68 | 69 | private fun setupNodeAndUI () = GlobalScope.launch { 70 | // initializeXyoSimpleWithGps() 71 | // initializeXyoSimple() 72 | // initializeXyoBleClientOnly() 73 | // initializeXyoBleServerOnly() 74 | initializeXyoBleOnly() 75 | ui { 76 | 77 | setContentView(R.layout.activity_main) 78 | val navView: BottomNavigationView = findViewById(R.id.nav_view) 79 | 80 | val navController = findNavController(R.id.nav_host_fragment) 81 | // Passing each menu ID as a set of Ids because each 82 | // menu should be considered as top level destinations. 83 | val appBarConfiguration = AppBarConfiguration( 84 | setOf( 85 | R.id.navigation_ble_client, R.id.navigation_ble_server, R.id.navigation_tcpip_client, R.id.navigation_tcpip_server 86 | ) 87 | ) 88 | setupActionBarWithNavController(navController, appBarConfiguration) 89 | navView.setupWithNavController(navController) 90 | } 91 | } 92 | 93 | private suspend fun initializeXyoSimple() { 94 | val builder = XyoNodeBuilder() 95 | node = builder.build(this) 96 | } 97 | 98 | private suspend fun initializeXyoSimpleWithGps() { 99 | val builder = XyoNodeBuilder() 100 | node = builder.build(this) 101 | (node.networks["ble"] as? XyoBleNetwork)?.client?.relayNode?.addHeuristic( 102 | "GPS", 103 | object: XyoHeuristicGetter { 104 | override fun getHeuristic(): XyoObjectStructure? { 105 | val locationManager = applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager 106 | 107 | if (ContextCompat.checkSelfPermission(applicationContext, android.Manifest.permission.ACCESS_FINE_LOCATION) 108 | == PackageManager.PERMISSION_GRANTED) { 109 | val lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) 110 | 111 | if (lastLocation != null) { 112 | val encodedLat = ByteBuffer.allocate(8).putDouble(lastLocation.latitude).array() 113 | val encodedLng = ByteBuffer.allocate(8).putDouble(lastLocation.longitude).array() 114 | val lat = XyoObjectStructure.newInstance(XyoSchemas.LAT, encodedLat) 115 | val lng = XyoObjectStructure.newInstance(XyoSchemas.LNG, encodedLng) 116 | 117 | return XyoIterableStructure.createUntypedIterableObject(XyoSchemas.GPS, arrayOf(lat, lng)) 118 | } 119 | } 120 | return null 121 | } 122 | } 123 | ) 124 | } 125 | 126 | private suspend fun initializeXyoBleClientOnly() { 127 | val builder = XyoNodeBuilder() 128 | val tag = "TAG: " 129 | node = builder.build(this) 130 | (node.networks["ble"] as? XyoBleNetwork)?.let { network -> 131 | Log.i(tag, "Ble? Hello? ") 132 | network.client.autoBridge = true 133 | network.client.autoBoundWitness = true 134 | network.client.scan = true 135 | Log.i(tag, "Client Scan? $network.client.scan ") 136 | network.server.autoBridge = false 137 | network.server.listen = false 138 | } 139 | (node.networks["tcpip"] as? XyoTcpIpNetwork)?.let { network -> 140 | network.client.autoBridge = false 141 | network.client.autoBoundWitness = false 142 | network.server.autoBridge = false 143 | network.server.listen = false 144 | } 145 | } 146 | 147 | private suspend fun initializeXyoBleServerOnly() { 148 | val builder = XyoNodeBuilder() 149 | node = builder.build(this) 150 | (node.networks["ble"] as? XyoBleNetwork)?.let { network -> 151 | network.client.autoBridge = false 152 | network.client.autoBoundWitness = false 153 | network.client.scan = false 154 | network.server.autoBridge = true 155 | network.server.listen = true 156 | } 157 | (node.networks["tcpip"] as? XyoTcpIpNetwork)?.let { network -> 158 | network.client.autoBridge = false 159 | network.client.autoBoundWitness = false 160 | network.server.autoBridge = false 161 | network.server.listen = false 162 | } 163 | } 164 | 165 | private suspend fun initializeXyoBleOnly() { 166 | val builder = XyoNodeBuilder() 167 | node = builder.build(this) 168 | (node.networks["tcpip"] as? XyoTcpIpNetwork)?.let { network -> 169 | network.client.autoBridge = false 170 | network.client.autoBoundWitness = false 171 | network.server.autoBridge = false 172 | network.server.listen = false 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/client/XyoSentinelX.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth.client 2 | 3 | import android.bluetooth.BluetoothDevice 4 | import android.bluetooth.BluetoothGattCharacteristic 5 | import android.content.Context 6 | import android.os.Build 7 | import kotlinx.coroutines.* 8 | import network.xyo.ble.devices.xy.XY4BluetoothDevice 9 | import network.xyo.ble.devices.xy.XYFinderBluetoothDevice 10 | import network.xyo.ble.devices.xy.XYFinderBluetoothDeviceStayAwake 11 | import network.xyo.ble.firmware.XYBluetoothDeviceUpdate 12 | import network.xyo.ble.firmware.XYOtaFile 13 | import network.xyo.ble.firmware.XYOtaUpdate 14 | import network.xyo.ble.firmware.XYOtaUpdateListener 15 | import network.xyo.ble.generic.devices.XYBluetoothDevice 16 | import network.xyo.ble.generic.devices.XYCreator 17 | import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResult 18 | import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResultErrorCode 19 | import network.xyo.ble.generic.scanner.XYScanResult 20 | import network.xyo.ble.generic.services.standard.BatteryService 21 | import network.xyo.ble.generic.services.standard.DeviceInformationService 22 | import network.xyo.ble.services.dialog.SpotaService 23 | import network.xyo.ble.services.xy.PrimaryService 24 | import network.xyo.sdk.bluetooth.XyoUuids 25 | import java.io.ByteArrayInputStream 26 | import java.nio.ByteBuffer 27 | import java.util.* 28 | import java.util.concurrent.ConcurrentHashMap 29 | import kotlin.experimental.and 30 | 31 | @kotlin.ExperimentalUnsignedTypes 32 | open class XyoSentinelX : XyoBluetoothClient { 33 | 34 | constructor(context: Context, scanResult: XYScanResult, hash: String) : super(context, scanResult, hash) 35 | 36 | constructor(context: Context, scanResult: XYScanResult, hash: String, transport: Int) : super(context, scanResult, hash, transport) 37 | 38 | private val sentinelListeners = HashMap() 39 | private var lastButtonPressTime: Long = 0 40 | 41 | //Keep as public 42 | val batteryService = BatteryService(this) 43 | val primary = PrimaryService(this) 44 | val deviceInformationService = DeviceInformationService(this) 45 | val spotaService = SpotaService(this) 46 | 47 | fun addButtonListener(key: String, listener: Listener) { 48 | sentinelListeners[key] = listener 49 | } 50 | 51 | fun removeButtonListener(key: String) { 52 | sentinelListeners.remove(key) 53 | } 54 | 55 | fun isClaimed(): Boolean { 56 | val iBeaconData = scanResult?.scanRecord?.getManufacturerSpecificData(0x4c) ?: return true 57 | 58 | if (iBeaconData.size == 23) { 59 | val flags = iBeaconData[21] 60 | return flags and 1.toByte() != 0.toByte() 61 | } 62 | 63 | return true 64 | } 65 | 66 | private fun isButtonPressed(scanResult: XYScanResult): Boolean { 67 | val iBeaconData = scanResult.scanRecord?.getManufacturerSpecificData(0x4c) ?: return true 68 | 69 | if (iBeaconData.size == 23) { 70 | val flags = iBeaconData[21] 71 | return flags and 2.toByte() != 0.toByte() 72 | } 73 | 74 | return false 75 | } 76 | 77 | override fun onDetect(scanResult: XYScanResult?) { 78 | if (scanResult != null && isButtonPressed(scanResult) && lastButtonPressTime < System.currentTimeMillis() - 11_000) { 79 | // button of sentinel x is pressed 80 | lastButtonPressTime = System.currentTimeMillis() 81 | // TODO - added delay to allow listener attachment before calling it. onButtonPressed needs to be separate. 82 | CoroutineScope(Dispatchers.IO).launch { 83 | delay(1000) 84 | for ((_, l) in sentinelListeners) { 85 | l.onButtonPressed() 86 | } 87 | } 88 | 89 | return 90 | } 91 | 92 | return 93 | } 94 | 95 | /** 96 | * Changes the password on the remote device if the current password is correct. 97 | * @param password The password of the device now. 98 | * @param newPassword The password to change on the remote device. 99 | * @return An XYBluetoothError if there was an issue writing the packet. 100 | */ 101 | suspend fun changePassword(password: ByteArray, newPassword: ByteArray): XYBluetoothResultErrorCode { 102 | val encoded = ByteBuffer.allocate(2 + password.size + newPassword.size) 103 | .put((password.size + 1).toByte()) 104 | .put(password) 105 | .put((newPassword.size + 1).toByte()) 106 | .put(newPassword) 107 | .array() 108 | 109 | return chunkSend(encoded, XyoUuids.XYO_PASSWORD, XyoUuids.XYO_SERVICE, 1) 110 | } 111 | 112 | /** 113 | * Changes the bound witness data on the remote device 114 | * @param boundWitnessData The data to include in tche remote devices bound witness. 115 | * @param password The password of the device to so it can write the boundWitnessData 116 | * @return An XYBluetoothError if there was an issue writing the packet. 117 | */ 118 | suspend fun changeBoundWitnessData(password: ByteArray, boundWitnessData: ByteArray): XYBluetoothResultErrorCode { 119 | val encoded = ByteBuffer.allocate(3 + password.size + boundWitnessData.size) 120 | .put((password.size + 1).toByte()) 121 | .put(password) 122 | .putShort((boundWitnessData.size + 2).toShort()) 123 | .put(boundWitnessData) 124 | .array() 125 | 126 | return chunkSend(encoded, XyoUuids.XYO_CHANGE_BW_DATA, XyoUuids.XYO_SERVICE, 4) 127 | } 128 | 129 | suspend fun getBoundWitnessData(): XYBluetoothResult { 130 | return findAndReadCharacteristicBytes(XyoUuids.XYO_SERVICE, XyoUuids.XYO_CHANGE_BW_DATA) 131 | } 132 | 133 | /** 134 | * Reset the device. 135 | * @param password The password of the device to so it can write the boundWitnessData 136 | * @return An XYBluetoothError if there was an issue writing the packet. 137 | */ 138 | suspend fun resetDevice(password: ByteArray): XYBluetoothResult { 139 | val msg = ByteBuffer.allocate(password.size + 2) 140 | .put((password.size + 2).toByte()) 141 | .put((password.size + 1).toByte()) 142 | .put(password) 143 | .array() 144 | 145 | return findAndWriteCharacteristic( 146 | XyoUuids.XYO_SERVICE, 147 | XyoUuids.XYO_RESET_DEVICE, 148 | msg, 149 | BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) 150 | } 151 | 152 | /** 153 | * Lock the device. 154 | */ 155 | suspend fun lock() = connection { 156 | return@connection primary.lock.set(XY4BluetoothDevice.DefaultLockCode) 157 | } 158 | 159 | /** 160 | * Unlock the device 161 | */ 162 | suspend fun unlock() = connection { 163 | return@connection primary.unlock.set(XY4BluetoothDevice.DefaultLockCode) 164 | } 165 | 166 | suspend fun stayAwake() = connection { 167 | return@connection primary.stayAwake.set(XYFinderBluetoothDeviceStayAwake.On.state) 168 | } 169 | 170 | suspend fun fallAsleep() = connection { 171 | return@connection primary.stayAwake.set(XYFinderBluetoothDeviceStayAwake.Off.state) 172 | } 173 | 174 | suspend fun batteryLevel() = connection { 175 | return@connection batteryService.level.get() 176 | } 177 | 178 | /** 179 | * Firmware Update 180 | * @param fileByteArray the firmware as a ByteArray 181 | * @param listener listener for progress, failed, completed 182 | */ 183 | fun updateFirmware(fileByteArray: ByteArrayInputStream, listener: XYOtaUpdateListener) { 184 | val otaFile = fileByteArray.let { XYOtaFile.getByStream(it) } 185 | val updater = XYBluetoothDeviceUpdate(spotaService, this, otaFile) 186 | updater.addListener("SentinelXDevice", listener) 187 | updater.start() 188 | } 189 | 190 | companion object : XYCreator() { 191 | 192 | open class Listener { 193 | open fun onButtonPressed() {} 194 | } 195 | 196 | fun enable(enable: Boolean) { 197 | if (enable) { 198 | xyoManufactureIdToCreator[XyoBluetoothClientDeviceType.SentinelX.raw] = this 199 | } else { 200 | xyoManufactureIdToCreator.remove(XyoBluetoothClientDeviceType.SentinelX.raw) 201 | } 202 | } 203 | 204 | override fun getDevicesFromScanResult( 205 | context: Context, 206 | scanResult: XYScanResult, 207 | globalDevices: ConcurrentHashMap, 208 | foundDevices: HashMap 210 | ) { 211 | val hash = hashFromScanResult(scanResult) 212 | val existingDevice = globalDevices[hash] 213 | if (existingDevice != null) { 214 | existingDevice.rssi = scanResult.rssi 215 | existingDevice.updateBluetoothDevice(scanResult.device) 216 | } else { 217 | val createdDevice = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 218 | XyoSentinelX(context, scanResult, hash, BluetoothDevice.TRANSPORT_LE) 219 | } else { 220 | XyoSentinelX(context, scanResult, hash) 221 | } 222 | foundDevices[hash] = createdDevice 223 | globalDevices[hash] = createdDevice 224 | } 225 | } 226 | } 227 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [logo]: https://cdn.xy.company/img/brand/XYO_full_colored.png 2 | 3 | [![logo]](https://xyo.network) 4 | 5 | # sdk-xyo-android 6 | 7 | [![CI](https://github.com/XYOracleNetwork/sdk-xyo-android/workflows/Build/badge.svg)](https://github.com/XYOracleNetwork/sdk-xyo-android/actions?query=workflow%3ACI+branch%3Adevelop) [![Release](https://github.com/XYOracleNetwork/sdk-xyo-android/workflows/Release/badge.svg)](https://github.com/XYOracleNetwork/sdk-xyo-android/actions?query=workflow%3ARelease+branch%3Amaster) [![Download](https://api.bintray.com/packages/xyoraclenetwork/xyo/sdk-xyo-android/images/download.svg) ](https://bintray.com/xyoraclenetwork/xyo/sdk-xyo-android/_latestVersion) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/9712b501940e45428072255a283fa23a)](https://www.codacy.com?utm_source=github.com&utm_medium=referral&utm_content=XYOracleNetwork/sdk-xyo-android&utm_campaign=Badge_Grade) [![BCH compliance](https://bettercodehub.com/edge/badge/XYOracleNetwork/sdk-xyo-android?branch=master)](https://bettercodehub.com/) [![Maintainability](https://api.codeclimate.com/v1/badges/9380b23945bb43599ab4/maintainability)](https://codeclimate.com/github/XYOracleNetwork/sdk-xyo-android/maintainability) [![Known Vulnerabilities](https://snyk.io/test/github/XYOracleNetwork/sdk-xyo-android/badge.svg?targetFile=xyo-android-library/build.gradle)](https://snyk.io/test/github/XYOracleNetwork/sdk-xyo-android?targetFile=xyo-android-library/build.gradle) 8 | 9 | > The XYO Foundation provides this source code available in our efforts to advance the understanding of the XYO Procotol and its possible uses. We continue to maintain this software in the interest of developer education. Usage of this source code is not intended for production. 10 | 11 | ## Table of Contents 12 | 13 | - [Title](#sdk-xyo-android) 14 | - [Description](#description) 15 | - [Gradle Build](#gradle-build) 16 | - [Maven Build](#maven-build) 17 | - [Examples](#examples) 18 | - [Usage](#usage) 19 | - [Architecture](#architecture) 20 | - [Maintainers](#maintainers) 21 | - [Contributing](#contributing) 22 | - [License](#license) 23 | - [Credits](#credits) 24 | 25 | ## Description  26 | 27 | A high-level SDK for interacting with the XYO network. 28 | Including BLE, TCP/IP, Bound Witnessing, and Bridging. Use this instead of `sdk-core-kotlin` for integration with your app project. 29 | 30 | > As you are developing on the SDK, be sure to keep an eye out for updates, as they may include core and ble updates 31 | 32 | ## Gradle Build 33 | 34 | ```gradle 35 | compile 'network.xyo:sdk-xyo-android:3.1.38' 36 | ``` 37 | 38 | ## Maven Build 39 | 40 | ```maven 41 | 42 | network.xyo 43 | sdk-xyo-android 44 | 3.1.38 45 | pom 46 | 47 | 48 | ``` 49 | 50 | ## Permissions and Set Up 51 | 52 | When using Android Studio and an Android device to test BLE Client and Server be sure to enable permissions and add an initialization function in your `MainActivity` 53 | 54 | `AndroidManifest.xml` 55 | 56 | ```xml 57 | 58 | 59 | 60 | ``` 61 | 62 | `MainActivity.kt` 63 | 64 | ```kotlin 65 | GlobalScope.launch { 66 | // place initialization function here for node build needed 67 | // you can look at the sample app for code example 68 | } 69 | ``` 70 | 71 | ## Examples 72 | 73 | Copy this code to test. Look below for specific usage. 74 | 75 | One line is all it takes to start your node 76 | 77 | ```kotlin 78 | val node = XyoNodeBuilder().build(context) 79 | ``` 80 | 81 | For a more complex test, create a listener callback. 82 | 83 | ``` kotlin 84 | // callback for node events 85 | val listener = object : XyoBoundWitnessTarget.Listener() { 86 | override fun boundWitnessCompleted(boundWitness: XyoBoundWitness?, error: String?) { 87 | super.boundWitnessCompleted(boundWitness, error) 88 | 89 | println("New bound witness!") 90 | } 91 | 92 | override fun boundWitnessStarted() { 93 | super.boundWitnessStarted() 94 | 95 | println("Bound witness started!") 96 | 97 | } 98 | } 99 | ``` 100 | You can also configure to your specific roles. 101 | 102 | ```kotlin 103 | // build and configure the node 104 | val builder = XyoNodeBuilder() 105 | builder.setListener(listener) 106 | 107 | // create the node 108 | val context = getContextSomehow() 109 | val node = builder.build(context) 110 | 111 | // configure tcp 112 | val tcpNetwork = node.networks["tcpip"] ?: return 113 | tcpNetwork.client.autoBridge = true 114 | tcpNetwork.client.autoBoundWitness = true 115 | tcpNetwork.client.scan = false 116 | tcpNetwork.client.knownBridges = ["public key of bridge", "public key of bridge"] 117 | 118 | // configure ble 119 | val bleNetwork = node.networks["ble"] ?: return 120 | bleNetwork.client.autoBridge = true 121 | bleNetwork.client.autoBoundWitness = true 122 | bleNetwork.client.scan = false 123 | ``` 124 | 125 | You can also use a heuristic getter, here is an example to get GPS. 126 | 127 | ```kotlin 128 | (node.networks["ble"] as? XyoBleNetwork)?.client?.relayNode?.addHeuristic( 129 | "GPS", 130 | object: XyoHeuristicGetter { 131 | override fun getHeuristic(): XyoObjectStructure? { 132 | val locationManager = applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager 133 | 134 | if (ContextCompat.checkSelfPermission(applicationContext, android.Manifest.permission.ACCESS_FINE_LOCATION) 135 | == PackageManager.PERMISSION_GRANTED) { 136 | val lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) 137 | 138 | if (lastLocation != null) { 139 | val encodedLat = ByteBuffer.allocate(8).putDouble(lastLocation.latitude).array() 140 | val encodedLng = ByteBuffer.allocate(8).putDouble(lastLocation.longitude).array() 141 | val lat = XyoObjectStructure.newInstance(XyoSchemas.LAT, encodedLat) 142 | val lng = XyoObjectStructure.newInstance(XyoSchemas.LNG, encodedLng) 143 | 144 | return XyoIterableStructure.createUntypedIterableObject(XyoSchemas.GPS, arrayOf(lat, lng)) 145 | } 146 | } 147 | return null 148 | } 149 | } 150 | ) 151 | ``` 152 | 153 | ## Usage 154 | 155 | Build an XYO Node 156 | 157 | ```kotlin 158 | val builder = XYONodeBuilder() 159 | ``` 160 | 161 | After calling the node builder, you can start the build 162 | 163 | ```kotlin 164 | val node = XyoNode() 165 | 166 | node = builder.build(this) 167 | ``` 168 | 169 | Once you have a build, you have access to properties to help you shape your node and what you want out of it. 170 | 171 | ```kotlin 172 | node.networks["this can be "ble" or "tcpip""] 173 | ``` 174 | 175 | After choosing the network, you have these properties available 176 | 177 | Client 178 | 179 | ```kotlin 180 | // select the network 181 | val network = node.networks["network"] 182 | 183 | // a flag to tell the client to automatically bridge 184 | network.client.autoBridge 185 | 186 | // a flag to tell the client to automatically bound witness 187 | network.client.autoBoundWitness 188 | 189 | // a flag to tell the client to automatically scan 190 | network.client.scan 191 | ``` 192 | 193 | Server 194 | 195 | ```kotlin 196 | // select the network 197 | val network = node.networks["network"] 198 | 199 | // a flag to tell the server to automatically bridge 200 | network.server.autoBridge 201 | 202 | // a flag to tell the client to automatically listen for bridging 203 | network.server.listen 204 | ``` 205 | 206 | These will allow your app to actively seek devices to bound witness with and bridge from the client to the server. 207 | 208 | You can also get payload data from the bound witness 209 | 210 | ```kotlin 211 | node.listener.getPayloadData(target: XyoBoundWitnessTarget) 212 | ``` 213 | 214 | This will return a byteArray. 215 | 216 | There are other properties from the client and server which you can find in the source code as well as a reference guide that we have prepared. 217 | 218 | 219 | ## Architecture 220 | 221 | This sdk is built on a client/server to ensure ease of understanding during development. (The client takes on "central" role, and the server the "peripheral"). This allows us to define roles with simplicity.  222 | 223 | > SDK-XYO-ANDROID TREE 224 | 225 | - XyoSDK 226 | - mutableList `` 227 | - `XyoNode(storage, networks)` 228 | - `listeners` 229 | - `boundWitnessTarget` 230 | - XyoClient, XyoServer 231 | - Ble 232 | - `context` 233 | - `relayNode` 234 | - `procedureCatalog` 235 | - `autoBridge` 236 | - `acceptBridging` 237 | - `autoBoundWitness` 238 | - `scan` 239 | 240 | - TcpIp 241 | - `relayNode` 242 | - `procedureCatalog` 243 | - `autoBridge` 244 | - `acceptBridging` 245 | - `autoBoundWitness` 246 | 247 | ## Sample App 248 | 249 | Please refer to the [xyo-android-sample](/xyo-android-sample/src/main/java/network/xyo/sdk/sample/MainActivity.kt) for an exmple implementation for bound witness and bridging.  250 | 251 | ### Install 252 | 253 | To use the sample app to measure functionality 254 | 255 | - Launch [Android Studio](https://developer.android.com/studio/install) 256 | - Click on `Open an existing Android Studio Project` 257 | - Navigate to `/xyo-android-sample` in your file explorer 258 | 259 | Once you open the sample in Android Studio it will execute the build. 260 | 261 | You can then run the app in a simulator of your choice or an Android device.  262 | 263 | This sample app includes client bridging and bound witnessing with a BLE server listener. 264 | 265 | ## Maintainers 266 | 267 | - Arie Trouw 268 | 269 | ## Contributing 270 | 271 | Please note that any contributions must clear the `release` branch. 272 | 273 | ## License 274 | 275 | See the [LICENSE](LICENSE) file for license details. 276 | 277 | ## Credits 278 | 279 | Made with 🔥and ❄️ by [XYO](https://www.xyo.network) 280 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/XyoNodeBuilder.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk 2 | import android.content.Context 3 | import kotlinx.coroutines.InternalCoroutinesApi 4 | import java.lang.Exception 5 | import java.nio.ByteBuffer 6 | import network.xyo.base.XYBase 7 | import network.xyo.sdkcorekotlin.crypto.signing.XyoSigner 8 | import network.xyo.sdkcorekotlin.crypto.signing.ecdsa.secp256k.XyoSha256WithSecp256K 9 | import network.xyo.sdkcorekotlin.hashing.XyoBasicHashBase 10 | import network.xyo.sdkcorekotlin.hashing.XyoHash 11 | import network.xyo.sdkcorekotlin.heuristics.XyoUnixTime 12 | import network.xyo.sdkcorekotlin.network.XyoProcedureCatalog 13 | import network.xyo.sdkcorekotlin.network.XyoProcedureCatalogFlags 14 | import network.xyo.sdkcorekotlin.node.XyoRelayNode 15 | import network.xyo.sdkcorekotlin.persist.XyoKeyValueStore 16 | import network.xyo.sdkcorekotlin.persist.repositories.XyoStorageBridgeQueueRepository 17 | import network.xyo.sdkcorekotlin.persist.repositories.XyoStorageOriginBlockRepository 18 | import network.xyo.sdkcorekotlin.persist.repositories.XyoStorageOriginStateRepository 19 | import network.xyo.sdkcorekotlin.schemas.XyoSchemas 20 | 21 | @kotlin.ExperimentalUnsignedTypes 22 | class XyoNodeBuilder : XYBase() { 23 | private var networks = mutableMapOf() 24 | private var storage: XyoKeyValueStore? = null 25 | private var listener: XyoBoundWitnessTarget.Listener? = null 26 | 27 | private var relayNode: XyoRelayNode? = null 28 | private var procedureCatalog: XyoProcedureCatalog? = null 29 | private var blockRepository: XyoStorageOriginBlockRepository? = null 30 | private var stateRepository: XyoStorageOriginStateRepository? = null 31 | private var bridgeQueueRepository: XyoStorageBridgeQueueRepository? = null 32 | private var hashingProvider: XyoHash.XyoHashProvider? = null 33 | private var knownBridges = mutableListOf() 34 | 35 | fun addNetwork(name: String, network: XyoNetwork) { 36 | networks[name] = network 37 | } 38 | 39 | fun setStorage(storage: XyoKeyValueStore) { 40 | this.storage = storage 41 | } 42 | 43 | fun setListener(listener: XyoBoundWitnessTarget.Listener) { 44 | this.listener = listener 45 | } 46 | 47 | @InternalCoroutinesApi 48 | suspend fun build(context: Context): XyoNode { 49 | if (XyoSdk.nodes.isNotEmpty()) { 50 | throw Exception() 51 | } 52 | 53 | if (storage == null) { 54 | log.info("No storage specified, using default") 55 | setDefaultStorage(context) 56 | } 57 | 58 | if (hashingProvider == null) { 59 | log.info("No hashingProvider specified, using default") 60 | setDefaultHashingProvider() 61 | } 62 | 63 | if (blockRepository == null) { 64 | log.info("No blockRepository specified, using default") 65 | setDefaultBlockRepository() 66 | } 67 | 68 | if (stateRepository == null) { 69 | log.info("No stateRepository specified, using default") 70 | setDefaultStateRepository() 71 | } 72 | 73 | if (bridgeQueueRepository == null) { 74 | log.info("No bridgeQueueRepository specified, using default") 75 | setDefaultBridgeQueueRepository() 76 | } 77 | 78 | if (procedureCatalog == null) { 79 | log.info("No procedureCatalog specified, using default") 80 | setDefaultProcedureCatalog() 81 | } 82 | 83 | if (relayNode == null) { 84 | log.info("No relayNode specified, using default") 85 | setDefaultRelayNode() 86 | } 87 | 88 | if (networks.isEmpty()) { 89 | log.info("No networks specified, using default") 90 | setDefaultNetworks(context) 91 | } 92 | 93 | val node = XyoNode(networks) 94 | XyoSdk.nodes.add(node) 95 | 96 | restoreAndInitBlockStorage() 97 | 98 | listener?.let { 99 | node.setAllListeners("default", it) 100 | } 101 | 102 | return node 103 | } 104 | 105 | private fun setDefaultProcedureCatalog() { 106 | /*procedureCatalog = object : XyoProcedureCatalog { 107 | override fun canDo(byteArray: ByteArray): Boolean { 108 | if (true) { 109 | return true 110 | } 111 | 112 | return ByteBuffer.wrap(byteArray).int and 1 != 0 113 | } 114 | 115 | override fun choose(byteArray: ByteArray): ByteArray { 116 | return byteArrayOf(0x00, 0x00, 0x00, 0x01) 117 | } 118 | 119 | override fun getEncodedCanDo(): ByteArray { 120 | if (true) { 121 | return byteArrayOf(0x00, 0x00, 0x00, 0xff.toByte()) 122 | } 123 | 124 | return byteArrayOf(0x00, 0x00, 0x00, 0x01) 125 | } 126 | } */ 127 | procedureCatalog = object : XyoProcedureCatalog { 128 | val canDoByte = XyoProcedureCatalogFlags.BOUND_WITNESS or XyoProcedureCatalogFlags.GIVE_ORIGIN_CHAIN or XyoProcedureCatalogFlags.TAKE_ORIGIN_CHAIN 129 | 130 | override fun canDo(byteArray: ByteArray): Boolean { 131 | if (byteArray.isEmpty()) { 132 | return false 133 | } 134 | 135 | return byteArray.last().toInt() and canDoByte != 0 136 | } 137 | 138 | override fun choose(byteArray: ByteArray): ByteArray { 139 | if (byteArray.isEmpty()) { 140 | return byteArrayOf(XyoProcedureCatalogFlags.BOUND_WITNESS.toByte()) 141 | } 142 | 143 | val interestedIn = byteArray.last().toInt() 144 | 145 | if (interestedIn and XyoProcedureCatalogFlags.GIVE_ORIGIN_CHAIN != 0) { 146 | return byteArrayOf(XyoProcedureCatalogFlags.TAKE_ORIGIN_CHAIN.toByte()) 147 | } 148 | 149 | if (interestedIn and XyoProcedureCatalogFlags.TAKE_ORIGIN_CHAIN != 0) { 150 | return byteArrayOf(XyoProcedureCatalogFlags.GIVE_ORIGIN_CHAIN.toByte()) 151 | } 152 | 153 | return byteArrayOf(XyoProcedureCatalogFlags.BOUND_WITNESS.toByte()) 154 | } 155 | 156 | override fun getEncodedCanDo(): ByteArray { 157 | 158 | return byteArrayOf(0x00, 0x00, 0x00, canDoByte.toByte()) 159 | } 160 | } 161 | } 162 | 163 | private fun setDefaultBlockRepository() { 164 | storage?.let { storage -> 165 | hashingProvider?.let { hashingProvider -> 166 | blockRepository = XyoStorageOriginBlockRepository(storage, hashingProvider) 167 | return 168 | } 169 | log.error("Missing hashingProvider", true) 170 | return 171 | } 172 | log.error("Missing storage", true) 173 | } 174 | 175 | private fun setDefaultStateRepository() { 176 | storage?.let { storage -> 177 | stateRepository = XyoStorageOriginStateRepository(storage) 178 | return 179 | } 180 | log.error("Missing storage", true) 181 | } 182 | 183 | private fun setDefaultBridgeQueueRepository() { 184 | storage?.let { storage -> 185 | bridgeQueueRepository = XyoStorageBridgeQueueRepository(storage) 186 | return 187 | } 188 | log.error("Missing storage", true) 189 | } 190 | 191 | private fun setDefaultHashingProvider() { 192 | hashingProvider = XyoBasicHashBase.createHashType(XyoSchemas.SHA_256, "SHA-256") 193 | } 194 | 195 | private fun setDefaultRelayNode() { 196 | blockRepository?.let { blockRepository -> 197 | stateRepository?.let { stateRepository -> 198 | bridgeQueueRepository?.let { bridgeQueueRepository -> 199 | hashingProvider?.let { hashingProvider -> 200 | relayNode = XyoRelayNode( 201 | blockRepository, 202 | stateRepository, 203 | bridgeQueueRepository, 204 | hashingProvider 205 | ) 206 | return 207 | } 208 | log.error("Missing hashingProvider", true) 209 | return 210 | } 211 | log.error("Missing bridgeQueueRepository", true) 212 | return 213 | } 214 | log.error("Missing stateRepository", true) 215 | return 216 | } 217 | log.error("Missing blockRepository", true) 218 | } 219 | 220 | private suspend fun getSigner(): XyoSigner { 221 | storage!!.let { storage -> 222 | val currentSigner = storage.read(SIGNER_KEY) 223 | 224 | if (currentSigner == null) { 225 | val newSigner = XyoSha256WithSecp256K.newInstance() 226 | storage.write(SIGNER_KEY, newSigner.privateKey.bytesCopy) 227 | return newSigner 228 | } 229 | 230 | return XyoSha256WithSecp256K.newInstance(currentSigner) 231 | } 232 | } 233 | 234 | private suspend fun restoreAndInitBlockStorage() { 235 | relayNode!!.let { relayNode -> 236 | relayNode.originBlocksToBridge.removeWeight = 2 237 | relayNode.originBlocksToBridge.sendLimit = 38 238 | relayNode.addHeuristic("TIME", XyoUnixTime.getter) 239 | 240 | val currentSigner = getSigner() 241 | 242 | stateRepository!!.restore(arrayListOf(currentSigner)) 243 | bridgeQueueRepository!!.restore() 244 | 245 | if (ByteBuffer.wrap((relayNode.originState.index.valueCopy)).int == 0) { 246 | relayNode.selfSignOriginChain() 247 | } 248 | } 249 | } 250 | 251 | @InternalCoroutinesApi 252 | private fun setDefaultNetworks(context: Context) { 253 | relayNode?.let { relayNode -> 254 | procedureCatalog?.let { procedureCatalog -> 255 | addNetwork("ble", XyoBleNetwork(context, relayNode, procedureCatalog)) 256 | addNetwork("tcpip", XyoTcpIpNetwork(relayNode, procedureCatalog)) 257 | return 258 | } 259 | log.error("Missing procedureCatalog", true) 260 | return 261 | } 262 | log.error("Missing relayNode", true) 263 | } 264 | 265 | private fun setDefaultStorage(context: Context) { 266 | setStorage(XyoSnappyDBStorageProvider(context)) 267 | } 268 | 269 | companion object { 270 | private val SIGNER_KEY = "SIGNER_KEY".toByteArray() 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /xyo-android-library/src/main/java/network/xyo/sdk/bluetooth/client/XyoBluetoothClient.kt: -------------------------------------------------------------------------------- 1 | package network.xyo.sdk.bluetooth.client 2 | 3 | import android.annotation.SuppressLint 4 | import android.bluetooth.BluetoothDevice 5 | import android.bluetooth.BluetoothGatt 6 | import android.bluetooth.BluetoothGattCharacteristic 7 | import android.content.Context 8 | import android.os.Build 9 | import android.util.Log 10 | import kotlinx.coroutines.* 11 | import network.xyo.ble.devices.apple.XYAppleBluetoothDevice 12 | import network.xyo.ble.devices.apple.XYIBeaconBluetoothDevice 13 | import network.xyo.ble.generic.devices.XYBluetoothDevice 14 | import network.xyo.ble.generic.devices.XYCreator 15 | import network.xyo.ble.generic.gatt.peripheral.XYBluetoothGattCallback 16 | import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResult 17 | import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResultErrorCode 18 | import network.xyo.ble.generic.scanner.XYScanResult 19 | import network.xyo.sdk.bluetooth.XyoUuids 20 | import network.xyo.sdk.bluetooth.packet.XyoBluetoothIncomingPacket 21 | import network.xyo.sdk.bluetooth.packet.XyoBluetoothOutgoingPacket 22 | import network.xyo.sdkcorekotlin.network.XyoNetworkPipe 23 | import java.nio.ByteBuffer 24 | import java.util.* 25 | import java.util.concurrent.ConcurrentHashMap 26 | import kotlin.collections.HashMap 27 | import kotlin.coroutines.resume 28 | import kotlin.coroutines.suspendCoroutine 29 | import kotlin.experimental.and 30 | 31 | enum class XyoBluetoothClientDeviceType(val raw: Byte){ 32 | SentinelX(0x01), 33 | IosAppX(0x02), 34 | BridgeX(0x03), 35 | AndroidAppX(0x04), 36 | } 37 | 38 | /** 39 | * A Bluetooth client that can create a XyoNetworkPipe. This pipe can be used with the sdk-core-kotlin to talk to 40 | * other XYO enabled devices. 41 | * 42 | * @property context Context of the device 43 | * @property device The android bluetooth device 44 | * @property hash The unique hash of the device 45 | */ 46 | @kotlin.ExperimentalUnsignedTypes 47 | open class XyoBluetoothClient : XYIBeaconBluetoothDevice { 48 | 49 | constructor(context: Context, scanResult: XYScanResult, hash: String) : super(context, scanResult, hash) 50 | 51 | constructor(context: Context, scanResult: XYScanResult, hash: String, transport: Int) : super(context, scanResult, hash, transport) 52 | 53 | /** 54 | * The standard size of the MTU of the connection. This value is used when chunking large amounts of data. 55 | */ 56 | private var mtu = DEFAULT_MTU 57 | 58 | /** 59 | * creates a XyoNetworkPipe with THIS bluetooth device. 60 | * @return A Deferred XyoNetworkPipe if successful, null if not. 61 | */ 62 | suspend fun createPipe(): XyoNetworkPipe? { 63 | findAndWriteCharacteristicNotify(XyoUuids.XYO_SERVICE, XyoUuids.XYO_PIPE, true) 64 | 65 | val requestMtu = requestMtu(MAX_MTU) 66 | 67 | mtu = (requestMtu.value ?: mtu) - 3 68 | 69 | return XyoBluetoothClientPipe(this) 70 | } 71 | 72 | /** 73 | * Get the public Key 74 | */ 75 | suspend fun getPublicKey(): XYBluetoothResult { 76 | return findAndReadCharacteristicBytes(XyoUuids.XYO_SERVICE, XyoUuids.XYO_PUBLIC_KEY) 77 | } 78 | 79 | /** 80 | * Preforms a chunk send 81 | * 82 | * @param outgoingPacket The packet to send to the server. This value will be chunked accordingly, if larger than 83 | * the MTU of the connection. 84 | * @param characteristic The characteristic UUID to write to. 85 | * @param service The service UUID to write to. 86 | * @param sizeOfSize size of the packet header size to send 87 | * @return An XYBluetoothError if there was an issue writing the packet. 88 | */ 89 | suspend fun chunkSend(outgoingPacket: ByteArray, characteristic: UUID, service: UUID, sizeOfSize: Int): XYBluetoothResultErrorCode { 90 | Log.i(TAG, "chunkSend: started") 91 | val chunkedOutgoingPacket = XyoBluetoothOutgoingPacket(mtu, outgoingPacket, sizeOfSize) 92 | 93 | var errorCode = XYBluetoothResultErrorCode.None 94 | 95 | connection { 96 | while (chunkedOutgoingPacket.canSendNext && errorCode == XYBluetoothResultErrorCode.None) { 97 | val result = findAndWriteCharacteristic( 98 | service, 99 | characteristic, 100 | chunkedOutgoingPacket.getNext(), 101 | BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT 102 | ) 103 | delay(500) 104 | if (result.error != XYBluetoothResultErrorCode.None) { 105 | errorCode = result.error 106 | } 107 | } 108 | return@connection XYBluetoothResult(true) 109 | } 110 | return errorCode 111 | } 112 | 113 | 114 | /** 115 | * Listens for notifications to read incoming packets. This must be invoked before notifications 116 | * are sent. Timeout of the first notification is defined with FIRST_NOTIFY_TIMEOUT, in 117 | * milliseconds and the notification timeout delta is defined as NOTIFY_TIMEOUT in milliseconds. 118 | * 119 | * @return A deferred ByteArray of the value read. If there was an error or timeout, will return null. 120 | */ 121 | suspend fun readIncoming(): ByteArray? { 122 | Log.i(TAG, "readIncoming: started") 123 | return suspendCoroutine { cont -> 124 | val key = this.toString() + Math.random().toString() 125 | 126 | centralCallback.addListener(key, object : XYBluetoothGattCallback() { 127 | var numberOfPackets = 0 128 | var hasResumed = false 129 | 130 | var timeoutJob: Job = GlobalScope.launch { 131 | delay(FIRST_NOTIFY_TIMEOUT.toLong()) 132 | if (isActive) { 133 | Log.e(TAG, "readIncoming: timeout") 134 | hasResumed = true 135 | centralCallback.removeListener(key) 136 | cont.resume(null) 137 | } 138 | } 139 | 140 | var incomingPacket: XyoBluetoothIncomingPacket? = null 141 | 142 | override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?) { 143 | Log.i(TAG, "readIncoming: onCharacteristicChanged") 144 | super.onCharacteristicChanged(gatt, characteristic) 145 | val value = characteristic?.value 146 | 147 | if (characteristic?.uuid == XyoUuids.XYO_PIPE && !hasResumed) { 148 | 149 | if (numberOfPackets == 0 && value != null) { 150 | incomingPacket = XyoBluetoothIncomingPacket(value) 151 | } else if (value != null) { 152 | incomingPacket?.addPacket(value) 153 | } 154 | 155 | if (incomingPacket?.done == true) { 156 | hasResumed = true 157 | centralCallback.removeListener(key) 158 | timeoutJob.cancel() 159 | cont.resume(incomingPacket?.getCurrentBuffer()) 160 | } else { 161 | timeoutJob.cancel() 162 | timeoutJob = GlobalScope.launch { 163 | delay(NOTIFY_TIMEOUT.toLong()) 164 | hasResumed = true 165 | centralCallback.removeListener(key) 166 | cont.resume(null) 167 | } 168 | } 169 | 170 | numberOfPackets++ 171 | } 172 | } 173 | }) 174 | } 175 | } 176 | 177 | 178 | companion object : XYCreator() { 179 | const val TAG = "XyoBluetoothClient" 180 | const val FIRST_NOTIFY_TIMEOUT = 12_000 181 | const val NOTIFY_TIMEOUT = 10_000 182 | const val MAX_MTU = 512 183 | const val DEFAULT_MTU = 22 184 | 185 | const val DEVICE_TYPE_MASK = 0x3f.toByte() 186 | 187 | @SuppressLint("UseSparseArrays") //SparseArrays cannot use Byte as key 188 | val xyoManufactureIdToCreator = HashMap() 189 | 190 | /** 191 | * Enable this device to be created on scan. 192 | * 193 | * @param enable Weather or not to enable the device. 194 | */ 195 | fun enable(enable: Boolean) { 196 | if (enable) { 197 | serviceToCreator[XyoUuids.XYO_SERVICE] = this 198 | uuidToCreator[XyoUuids.XYO_SERVICE] = this 199 | XYBluetoothGattCallback.blockNotificationCallback = true 200 | XYIBeaconBluetoothDevice.enable(true) 201 | } else { 202 | serviceToCreator.remove(XyoUuids.XYO_SERVICE) 203 | uuidToCreator.remove(XyoUuids.XYO_SERVICE) 204 | XYIBeaconBluetoothDevice.enable(false) 205 | XYBluetoothGattCallback.blockNotificationCallback = false 206 | } 207 | } 208 | 209 | private fun majorFromScanResult(scanResult: XYScanResult): UShort? { 210 | val bytes = scanResult.scanRecord?.getManufacturerSpecificData(XYAppleBluetoothDevice.MANUFACTURER_ID) 211 | return if (bytes != null) { 212 | val buffer = ByteBuffer.wrap(bytes) 213 | buffer.getShort(18).toUShort() 214 | } else { 215 | null 216 | } 217 | } 218 | 219 | private fun minorFromScanResult(scanResult: XYScanResult): UShort? { 220 | val bytes = scanResult.scanRecord?.getManufacturerSpecificData(XYAppleBluetoothDevice.MANUFACTURER_ID) 221 | return if (bytes != null) { 222 | val buffer = ByteBuffer.wrap(bytes) 223 | buffer.getShort(20).toUShort() 224 | } else { 225 | null 226 | } 227 | } 228 | 229 | internal fun hashFromScanResult(scanResult: XYScanResult): String { 230 | val uuid = iBeaconUuidFromScanResult(scanResult) 231 | val major = majorFromScanResult(scanResult) 232 | val minor = minorFromScanResult(scanResult) 233 | 234 | return "$uuid:$major:$minor" 235 | } 236 | 237 | override fun getDevicesFromScanResult( 238 | context: Context, 239 | scanResult: XYScanResult, 240 | globalDevices: ConcurrentHashMap, 241 | foundDevices: HashMap 243 | ) { 244 | val hash = hashFromScanResult(scanResult) 245 | 246 | val existingDevice = globalDevices[hash] 247 | if (existingDevice != null) { 248 | existingDevice.rssi = scanResult.rssi 249 | existingDevice.updateBluetoothDevice(scanResult.device) 250 | } else { 251 | log.info("Device Creating: $hash") 252 | val ad = scanResult.scanRecord?.getManufacturerSpecificData(0x4c) 253 | 254 | if (ad?.size == 23) { 255 | val id = ad[19] 256 | 257 | // masks the byte with 00111111 - AT: Is this a Check if is Xyo Enabled Device? 258 | if (xyoManufactureIdToCreator.containsKey(id and DEVICE_TYPE_MASK)) { 259 | xyoManufactureIdToCreator[id and DEVICE_TYPE_MASK]?.getDevicesFromScanResult(context, scanResult, globalDevices, foundDevices) 260 | return 261 | } else { 262 | log.info("Not an Xyo Device - Not Creating: $hash") 263 | 264 | val createdDevice = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 265 | XyoBluetoothClient(context, scanResult, hash, BluetoothDevice.TRANSPORT_LE) 266 | } else { 267 | XyoBluetoothClient(context, scanResult, hash) 268 | } 269 | 270 | foundDevices[hash] = createdDevice 271 | globalDevices[hash] = createdDevice 272 | } 273 | } 274 | } 275 | } 276 | } 277 | } 278 | --------------------------------------------------------------------------------