瀏覽代碼

first commit

949177 5 年之前
當前提交
1db6c9dbd6
共有 94 個文件被更改,包括 9477 次插入0 次删除
  1. 3 0
      .gitignore
  2. 9 0
      README.md
  3. 14 0
      config.xml
  4. 196 0
      hooks/README.md
  5. 1 0
      jsconfig.json
  6. 226 0
      package-lock.json
  7. 13 0
      package.json
  8. 15 0
      plugins/android.json
  9. 170 0
      plugins/cordova-plugin-ble-central/CHANGES.txt
  10. 13 0
      plugins/cordova-plugin-ble-central/LICENSE.txt
  11. 957 0
      plugins/cordova-plugin-ble-central/README.md
  12. 16 0
      plugins/cordova-plugin-ble-central/examples/battery/README.md
  13. 13 0
      plugins/cordova-plugin-ble-central/examples/battery/config.xml
  14. 83 0
      plugins/cordova-plugin-ble-central/examples/battery/hooks/README.md
  15. 61 0
      plugins/cordova-plugin-ble-central/examples/battery/www/css/index.css
  16. 31 0
      plugins/cordova-plugin-ble-central/examples/battery/www/index.html
  17. 105 0
      plugins/cordova-plugin-ble-central/examples/battery/www/js/index.js
  18. 37 0
      plugins/cordova-plugin-ble-central/examples/bluefruitle/README.md
  19. 13 0
      plugins/cordova-plugin-ble-central/examples/bluefruitle/config.xml
  20. 83 0
      plugins/cordova-plugin-ble-central/examples/bluefruitle/hooks/README.md
  21. 77 0
      plugins/cordova-plugin-ble-central/examples/bluefruitle/www/css/index.css
  22. 34 0
      plugins/cordova-plugin-ble-central/examples/bluefruitle/www/index.html
  23. 158 0
      plugins/cordova-plugin-ble-central/examples/bluefruitle/www/js/index.js
  24. 9 0
      plugins/cordova-plugin-ble-central/examples/heartrate/README.md
  25. 12 0
      plugins/cordova-plugin-ble-central/examples/heartrate/config.xml
  26. 196 0
      plugins/cordova-plugin-ble-central/examples/heartrate/hooks/README.md
  27. 27 0
      plugins/cordova-plugin-ble-central/examples/heartrate/www/css/index.css
  28. 20 0
      plugins/cordova-plugin-ble-central/examples/heartrate/www/index.html
  29. 83 0
      plugins/cordova-plugin-ble-central/examples/heartrate/www/js/index.js
  30. 18 0
      plugins/cordova-plugin-ble-central/examples/metawear/README.md
  31. 12 0
      plugins/cordova-plugin-ble-central/examples/metawear/config.xml
  32. 83 0
      plugins/cordova-plugin-ble-central/examples/metawear/hooks/README.md
  33. 95 0
      plugins/cordova-plugin-ble-central/examples/metawear/www/css/index.css
  34. 37 0
      plugins/cordova-plugin-ble-central/examples/metawear/www/index.html
  35. 156 0
      plugins/cordova-plugin-ble-central/examples/metawear/www/js/index.js
  36. 18 0
      plugins/cordova-plugin-ble-central/examples/redbearlab/README.md
  37. 12 0
      plugins/cordova-plugin-ble-central/examples/redbearlab/config.xml
  38. 83 0
      plugins/cordova-plugin-ble-central/examples/redbearlab/hooks/README.md
  39. 77 0
      plugins/cordova-plugin-ble-central/examples/redbearlab/www/css/index.css
  40. 34 0
      plugins/cordova-plugin-ble-central/examples/redbearlab/www/index.html
  41. 124 0
      plugins/cordova-plugin-ble-central/examples/redbearlab/www/js/index.js
  42. 16 0
      plugins/cordova-plugin-ble-central/examples/rfduinoLedButton/README.md
  43. 13 0
      plugins/cordova-plugin-ble-central/examples/rfduinoLedButton/config.xml
  44. 83 0
      plugins/cordova-plugin-ble-central/examples/rfduinoLedButton/hooks/README.md
  45. 61 0
      plugins/cordova-plugin-ble-central/examples/rfduinoLedButton/www/css/index.css
  46. 31 0
      plugins/cordova-plugin-ble-central/examples/rfduinoLedButton/www/index.html
  47. 186 0
      plugins/cordova-plugin-ble-central/examples/rfduinoLedButton/www/js/index.js
  48. 15 0
      plugins/cordova-plugin-ble-central/examples/robosmart/README.md
  49. 13 0
      plugins/cordova-plugin-ble-central/examples/robosmart/config.xml
  50. 83 0
      plugins/cordova-plugin-ble-central/examples/robosmart/hooks/README.md
  51. 66 0
      plugins/cordova-plugin-ble-central/examples/robosmart/www/css/index.css
  52. 37 0
      plugins/cordova-plugin-ble-central/examples/robosmart/www/index.html
  53. 157 0
      plugins/cordova-plugin-ble-central/examples/robosmart/www/js/index.js
  54. 17 0
      plugins/cordova-plugin-ble-central/examples/sensortag/README.md
  55. 13 0
      plugins/cordova-plugin-ble-central/examples/sensortag/config.xml
  56. 83 0
      plugins/cordova-plugin-ble-central/examples/sensortag/hooks/README.md
  57. 61 0
      plugins/cordova-plugin-ble-central/examples/sensortag/www/css/index.css
  58. 31 0
      plugins/cordova-plugin-ble-central/examples/sensortag/www/index.html
  59. 141 0
      plugins/cordova-plugin-ble-central/examples/sensortag/www/js/index.js
  60. 18 0
      plugins/cordova-plugin-ble-central/examples/sensortag_cc2650/README.md
  61. 13 0
      plugins/cordova-plugin-ble-central/examples/sensortag_cc2650/config.xml
  62. 83 0
      plugins/cordova-plugin-ble-central/examples/sensortag_cc2650/hooks/README.md
  63. 61 0
      plugins/cordova-plugin-ble-central/examples/sensortag_cc2650/www/css/index.css
  64. 32 0
      plugins/cordova-plugin-ble-central/examples/sensortag_cc2650/www/index.html
  65. 216 0
      plugins/cordova-plugin-ble-central/examples/sensortag_cc2650/www/js/index.js
  66. 64 0
      plugins/cordova-plugin-ble-central/package.json
  67. 94 0
      plugins/cordova-plugin-ble-central/plugin.xml
  68. 719 0
      plugins/cordova-plugin-ble-central/src/android/BLECentralPlugin.java
  69. 61 0
      plugins/cordova-plugin-ble-central/src/android/BLECommand.java
  70. 158 0
      plugins/cordova-plugin-ble-central/src/android/Helper.java
  71. 880 0
      plugins/cordova-plugin-ble-central/src/android/Peripheral.java
  72. 47 0
      plugins/cordova-plugin-ble-central/src/android/UUIDHelper.java
  73. 78 0
      plugins/cordova-plugin-ble-central/src/browser/BLECentralPlugin.js
  74. 73 0
      plugins/cordova-plugin-ble-central/src/ios/BLECentralPlugin.h
  75. 944 0
      plugins/cordova-plugin-ble-central/src/ios/BLECentralPlugin.m
  76. 16 0
      plugins/cordova-plugin-ble-central/src/ios/BLECommandContext.h
  77. 10 0
      plugins/cordova-plugin-ble-central/src/ios/BLECommandContext.m
  78. 37 0
      plugins/cordova-plugin-ble-central/src/ios/CBPeripheral+Extensions.h
  79. 271 0
      plugins/cordova-plugin-ble-central/src/ios/CBPeripheral+Extensions.m
  80. 132 0
      plugins/cordova-plugin-ble-central/src/wp/BLECentralPlugin.cs
  81. 11 0
      plugins/cordova-plugin-ble-central/tests/plugin.xml
  82. 71 0
      plugins/cordova-plugin-ble-central/tests/tests.js
  83. 308 0
      plugins/cordova-plugin-ble-central/www/ble.js
  84. 35 0
      plugins/cordova-plugin-compat/README.md
  85. 33 0
      plugins/cordova-plugin-compat/RELEASENOTES.md
  86. 65 0
      plugins/cordova-plugin-compat/package.json
  87. 37 0
      plugins/cordova-plugin-compat/plugin.xml
  88. 70 0
      plugins/cordova-plugin-compat/src/android/BuildHelper.java
  89. 138 0
      plugins/cordova-plugin-compat/src/android/PermissionHelper.java
  90. 18 0
      plugins/fetch.json
  91. 2 0
      typings/cordova-typings.d.ts
  92. 27 0
      www/css/index.css
  93. 20 0
      www/index.html
  94. 104 0
      www/js/index.js

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
1
+node_modules
2
+platforms
3
+.vscode

+ 9 - 0
README.md

@@ -0,0 +1,9 @@
1
+BLE Heart Rate Demo
2
+
3
+Connects to a peripherial providing the [Heart Rate Service](http://goo.gl/wKH3X7).
4
+
5
+Works with iOS or Android 4.3+.
6
+
7
+    $ cordova platform add android
8
+    $ cordova plugin add cordova-plugin-ble-central
9
+    $ cordova run

+ 14 - 0
config.xml

@@ -0,0 +1,14 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<widget id="com.megster.example.hrm" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+    <name>HeartRate</name>
4
+    <description>
5
+        BLE Heart Rate Monitor Demo
6
+    </description>
7
+    <author href="https://github.com/don">
8
+        Don Coleman
9
+    </author>
10
+    <content src="index.html" />
11
+    <access origin="*" />
12
+    <plugin name="cordova-plugin-ble-central" spec="~1.2.2" />
13
+    <engine name="android" spec="~7.1.4" />
14
+</widget>

+ 196 - 0
hooks/README.md

@@ -0,0 +1,196 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Cordova Hooks
22
+
23
+Cordova Hooks represent special scripts which could be added by application and plugin developers or even by your own build system  to customize cordova commands. Hook scripts could be defined by adding them to the special predefined folder (`/hooks`) or via configuration files (`config.xml` and `plugin.xml`) and run serially in the following order: 
24
+* Application hooks from `/hooks`;
25
+* Application hooks from `config.xml`;
26
+* Plugin hooks from `plugins/.../plugin.xml`.
27
+
28
+__Remember__: Make your scripts executable.
29
+
30
+__Note__: `.cordova/hooks` directory is also supported for backward compatibility, but we don't recommend using it as it is deprecated.
31
+
32
+## Supported hook types
33
+The following hook types are supported:
34
+
35
+    after_build/
36
+    after_compile/
37
+    after_docs/
38
+    after_emulate/
39
+    after_platform_add/
40
+    after_platform_rm/
41
+    after_platform_ls/
42
+    after_plugin_add/
43
+    after_plugin_ls/
44
+    after_plugin_rm/
45
+    after_plugin_search/
46
+    after_plugin_install/   <-- Plugin hooks defined in plugin.xml are executed exclusively for a plugin being installed
47
+    after_prepare/
48
+    after_run/
49
+    after_serve/
50
+    before_build/
51
+    before_compile/
52
+    before_docs/
53
+    before_emulate/
54
+    before_platform_add/
55
+    before_platform_rm/
56
+    before_platform_ls/
57
+    before_plugin_add/
58
+    before_plugin_ls/
59
+    before_plugin_rm/
60
+    before_plugin_search/
61
+    before_plugin_install/   <-- Plugin hooks defined in plugin.xml are executed exclusively for a plugin being installed
62
+    before_plugin_uninstall/   <-- Plugin hooks defined in plugin.xml are executed exclusively for a plugin being uninstalled
63
+    before_prepare/
64
+    before_run/
65
+    before_serve/
66
+    pre_package/ <-- Windows 8 and Windows Phone only.
67
+
68
+## Ways to define hooks
69
+### Via '/hooks' directory
70
+To execute custom action when corresponding hook type is fired, use hook type as a name for a subfolder inside 'hooks' directory and place you script file here, for example:
71
+
72
+    # script file will be automatically executed after each build
73
+    hooks/after_build/after_build_custom_action.js
74
+
75
+
76
+### Config.xml
77
+
78
+Hooks can be defined in project's `config.xml` using `<hook>` elements, for example:
79
+
80
+    <hook type="before_build" src="scripts/appBeforeBuild.bat" />
81
+    <hook type="before_build" src="scripts/appBeforeBuild.js" />
82
+    <hook type="before_plugin_install" src="scripts/appBeforePluginInstall.js" />
83
+
84
+    <platform name="wp8">
85
+        <hook type="before_build" src="scripts/wp8/appWP8BeforeBuild.bat" />
86
+        <hook type="before_build" src="scripts/wp8/appWP8BeforeBuild.js" />
87
+        <hook type="before_plugin_install" src="scripts/wp8/appWP8BeforePluginInstall.js" />
88
+        ...
89
+    </platform>
90
+
91
+    <platform name="windows8">
92
+        <hook type="before_build" src="scripts/windows8/appWin8BeforeBuild.bat" />
93
+        <hook type="before_build" src="scripts/windows8/appWin8BeforeBuild.js" />
94
+        <hook type="before_plugin_install" src="scripts/windows8/appWin8BeforePluginInstall.js" />
95
+        ...
96
+    </platform>
97
+
98
+### Plugin hooks (plugin.xml)
99
+
100
+As a plugin developer you can define hook scripts using `<hook>` elements in a `plugin.xml` like that:
101
+
102
+    <hook type="before_plugin_install" src="scripts/beforeInstall.js" />
103
+    <hook type="after_build" src="scripts/afterBuild.js" />
104
+
105
+    <platform name="wp8">
106
+        <hook type="before_plugin_install" src="scripts/wp8BeforeInstall.js" />
107
+        <hook type="before_build" src="scripts/wp8BeforeBuild.js" />
108
+        ...
109
+    </platform>
110
+
111
+`before_plugin_install`, `after_plugin_install`, `before_plugin_uninstall` plugin hooks will be fired exclusively for the plugin being installed/uninstalled.
112
+
113
+## Script Interface
114
+
115
+### Javascript
116
+
117
+If you are writing hooks in Javascript you should use the following module definition:
118
+```javascript
119
+module.exports = function(context) {
120
+    ...
121
+}
122
+```
123
+
124
+You can make your scipts async using Q:
125
+```javascript
126
+module.exports = function(context) {
127
+    var Q = context.requireCordovaModule('q');
128
+    var deferral = new Q.defer();
129
+
130
+    setTimeout(function(){
131
+    	console.log('hook.js>> end');
132
+		deferral.resolve();
133
+    }, 1000);
134
+
135
+    return deferral.promise;
136
+}
137
+```
138
+
139
+`context` object contains hook type, executed script full path, hook options, command-line arguments passed to Cordova and top-level "cordova" object:
140
+```json
141
+{
142
+	"hook": "before_plugin_install",
143
+	"scriptLocation": "c:\\script\\full\\path\\appBeforePluginInstall.js",
144
+	"cmdLine": "The\\exact\\command\\cordova\\run\\with arguments",
145
+	"opts": {
146
+		"projectRoot":"C:\\path\\to\\the\\project",
147
+		"cordova": {
148
+			"platforms": ["wp8"],
149
+			"plugins": ["com.plugin.withhooks"],
150
+			"version": "0.21.7-dev"
151
+		},
152
+		"plugin": {
153
+			"id": "com.plugin.withhooks",
154
+			"pluginInfo": {
155
+				...
156
+			},
157
+			"platform": "wp8",
158
+			"dir": "C:\\path\\to\\the\\project\\plugins\\com.plugin.withhooks"
159
+		}
160
+	},
161
+	"cordova": {...}
162
+}
163
+
164
+```
165
+`context.opts.plugin` object will only be passed to plugin hooks scripts.
166
+
167
+You can also require additional Cordova modules in your script using `context.requireCordovaModule` in the following way:
168
+```javascript
169
+var Q = context.requireCordovaModule('q');
170
+```
171
+
172
+__Note__:  new module loader script interface is used for the `.js` files defined via `config.xml` or `plugin.xml` only. 
173
+For compatibility reasons hook files specified via `/hooks` folders are run via Node child_process spawn, see 'Non-javascript' section below.
174
+
175
+### Non-javascript
176
+
177
+Non-javascript scripts are run via Node child_process spawn from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
178
+
179
+* CORDOVA_VERSION - The version of the Cordova-CLI.
180
+* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
181
+* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
182
+* CORDOVA_HOOK - Path to the hook that is being executed.
183
+* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
184
+
185
+If a script returns a non-zero exit code, then the parent cordova command will be aborted.
186
+
187
+## Writing hooks
188
+
189
+We highly recommend writting your hooks using Node.js so that they are
190
+cross-platform. Some good examples are shown here:
191
+
192
+[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
193
+
194
+Also, note that even if you are working on Windows, and in case your hook scripts aren't bat files (which is recommended, if you want your scripts to work in non-Windows operating systems) Cordova CLI will expect a shebang line as the first line for it to know the interpreter it needs to use to launch the script. The shebang line should match the following example:
195
+
196
+    #!/usr/bin/env [name_of_interpreter_executable]

+ 1 - 0
jsconfig.json

@@ -0,0 +1 @@
1
+{}

+ 226 - 0
package-lock.json

@@ -0,0 +1,226 @@
1
+{
2
+  "requires": true,
3
+  "lockfileVersion": 1,
4
+  "dependencies": {
5
+    "cordova-android": {
6
+      "version": "7.1.4",
7
+      "resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-7.1.4.tgz",
8
+      "integrity": "sha512-Rtvu002I83uzfVyCsE6p2krFKVHt9TSAqZUATes+zH+o9cdxYGrLHY+PKCQo4SLCdSMdrkIHCDnQPTYTp/d7+g==",
9
+      "requires": {
10
+        "abbrev": "*",
11
+        "android-versions": "1.4.0",
12
+        "ansi": "*",
13
+        "balanced-match": "*",
14
+        "base64-js": "1.2.0",
15
+        "big-integer": "1.6.32",
16
+        "bplist-parser": "*",
17
+        "brace-expansion": "*",
18
+        "concat-map": "*",
19
+        "cordova-common": "2.2.5",
20
+        "cordova-registry-mapper": "*",
21
+        "elementtree": "0.1.6",
22
+        "glob": "5.0.15",
23
+        "inflight": "*",
24
+        "inherits": "*",
25
+        "minimatch": "*",
26
+        "nopt": "3.0.1",
27
+        "once": "*",
28
+        "path-is-absolute": "1.0.1",
29
+        "plist": "2.1.0",
30
+        "properties-parser": "0.2.3",
31
+        "q": "1.4.1",
32
+        "sax": "0.3.5",
33
+        "semver": "5.5.0",
34
+        "shelljs": "0.5.3",
35
+        "underscore": "*",
36
+        "unorm": "*",
37
+        "wrappy": "*",
38
+        "xmlbuilder": "8.2.2",
39
+        "xmldom": "*"
40
+      },
41
+      "dependencies": {
42
+        "abbrev": {
43
+          "version": "1.1.1",
44
+          "bundled": true
45
+        },
46
+        "android-versions": {
47
+          "version": "1.4.0",
48
+          "bundled": true,
49
+          "requires": {
50
+            "semver": "^5.4.1"
51
+          }
52
+        },
53
+        "ansi": {
54
+          "version": "0.3.1",
55
+          "bundled": true
56
+        },
57
+        "balanced-match": {
58
+          "version": "1.0.0",
59
+          "bundled": true
60
+        },
61
+        "base64-js": {
62
+          "version": "1.2.0",
63
+          "bundled": true
64
+        },
65
+        "big-integer": {
66
+          "version": "1.6.32",
67
+          "bundled": true
68
+        },
69
+        "bplist-parser": {
70
+          "version": "0.1.1",
71
+          "bundled": true,
72
+          "requires": {
73
+            "big-integer": "^1.6.7"
74
+          }
75
+        },
76
+        "brace-expansion": {
77
+          "version": "1.1.11",
78
+          "bundled": true,
79
+          "requires": {
80
+            "balanced-match": "^1.0.0",
81
+            "concat-map": "0.0.1"
82
+          }
83
+        },
84
+        "concat-map": {
85
+          "version": "0.0.1",
86
+          "bundled": true
87
+        },
88
+        "cordova-common": {
89
+          "version": "2.2.5",
90
+          "bundled": true,
91
+          "requires": {
92
+            "ansi": "^0.3.1",
93
+            "bplist-parser": "^0.1.0",
94
+            "cordova-registry-mapper": "^1.1.8",
95
+            "elementtree": "0.1.6",
96
+            "glob": "^5.0.13",
97
+            "minimatch": "^3.0.0",
98
+            "plist": "^2.1.0",
99
+            "q": "^1.4.1",
100
+            "shelljs": "^0.5.3",
101
+            "underscore": "^1.8.3",
102
+            "unorm": "^1.3.3"
103
+          }
104
+        },
105
+        "cordova-registry-mapper": {
106
+          "version": "1.1.15",
107
+          "bundled": true
108
+        },
109
+        "elementtree": {
110
+          "version": "0.1.6",
111
+          "bundled": true,
112
+          "requires": {
113
+            "sax": "0.3.5"
114
+          }
115
+        },
116
+        "glob": {
117
+          "version": "5.0.15",
118
+          "bundled": true,
119
+          "requires": {
120
+            "inflight": "^1.0.4",
121
+            "inherits": "2",
122
+            "minimatch": "2 || 3",
123
+            "once": "^1.3.0",
124
+            "path-is-absolute": "^1.0.0"
125
+          }
126
+        },
127
+        "inflight": {
128
+          "version": "1.0.6",
129
+          "bundled": true,
130
+          "requires": {
131
+            "once": "^1.3.0",
132
+            "wrappy": "1"
133
+          }
134
+        },
135
+        "inherits": {
136
+          "version": "2.0.3",
137
+          "bundled": true
138
+        },
139
+        "minimatch": {
140
+          "version": "3.0.4",
141
+          "bundled": true,
142
+          "requires": {
143
+            "brace-expansion": "^1.1.7"
144
+          }
145
+        },
146
+        "nopt": {
147
+          "version": "3.0.1",
148
+          "bundled": true,
149
+          "requires": {
150
+            "abbrev": "1"
151
+          }
152
+        },
153
+        "once": {
154
+          "version": "1.4.0",
155
+          "bundled": true,
156
+          "requires": {
157
+            "wrappy": "1"
158
+          }
159
+        },
160
+        "path-is-absolute": {
161
+          "version": "1.0.1",
162
+          "bundled": true
163
+        },
164
+        "plist": {
165
+          "version": "2.1.0",
166
+          "bundled": true,
167
+          "requires": {
168
+            "base64-js": "1.2.0",
169
+            "xmlbuilder": "8.2.2",
170
+            "xmldom": "0.1.x"
171
+          }
172
+        },
173
+        "properties-parser": {
174
+          "version": "0.2.3",
175
+          "bundled": true
176
+        },
177
+        "q": {
178
+          "version": "1.4.1",
179
+          "bundled": true
180
+        },
181
+        "sax": {
182
+          "version": "0.3.5",
183
+          "bundled": true
184
+        },
185
+        "semver": {
186
+          "version": "5.5.0",
187
+          "bundled": true
188
+        },
189
+        "shelljs": {
190
+          "version": "0.5.3",
191
+          "bundled": true
192
+        },
193
+        "underscore": {
194
+          "version": "1.9.1",
195
+          "bundled": true
196
+        },
197
+        "unorm": {
198
+          "version": "1.4.1",
199
+          "bundled": true
200
+        },
201
+        "wrappy": {
202
+          "version": "1.0.2",
203
+          "bundled": true
204
+        },
205
+        "xmlbuilder": {
206
+          "version": "8.2.2",
207
+          "bundled": true
208
+        },
209
+        "xmldom": {
210
+          "version": "0.1.27",
211
+          "bundled": true
212
+        }
213
+      }
214
+    },
215
+    "cordova-plugin-ble-central": {
216
+      "version": "1.2.2",
217
+      "resolved": "https://registry.npmjs.org/cordova-plugin-ble-central/-/cordova-plugin-ble-central-1.2.2.tgz",
218
+      "integrity": "sha512-81+BD9UQSp8Fuati9bPvKG/6lSGHHtw3JeLxQl8Q85ZS1n3GkSfA3DBMqKRjmOG6OVe4csR+XcO7cd75HMl6Qg=="
219
+    },
220
+    "cordova-plugin-compat": {
221
+      "version": "1.2.0",
222
+      "resolved": "https://registry.npmjs.org/cordova-plugin-compat/-/cordova-plugin-compat-1.2.0.tgz",
223
+      "integrity": "sha1-C8ZXVyduvZIMASzpIOJ0F3V2Nz4="
224
+    }
225
+  }
226
+}

+ 13 - 0
package.json

@@ -0,0 +1,13 @@
1
+{
2
+  "name": "com.megster.example.hrm",
3
+  "version": "0.0.1",
4
+  "displayName": "HeartRate",
5
+  "cordova": {
6
+    "platforms": [
7
+      "android"
8
+    ]
9
+  },
10
+  "dependencies": {
11
+    "cordova-android": "~7.1.4"
12
+  }
13
+}

+ 15 - 0
plugins/android.json

@@ -0,0 +1,15 @@
1
+{
2
+  "prepare_queue": {
3
+    "installed": [],
4
+    "uninstalled": []
5
+  },
6
+  "config_munge": {
7
+    "files": {}
8
+  },
9
+  "installed_plugins": {
10
+    "cordova-plugin-ble-central": {
11
+      "PACKAGE_NAME": "com.megster.example.hrm"
12
+    }
13
+  },
14
+  "dependent_plugins": {}
15
+}

+ 170 - 0
plugins/cordova-plugin-ble-central/CHANGES.txt

@@ -0,0 +1,170 @@
1
+= 1.2.2 =
2
+Remove lambda from Peripheral.java to maintain 1.6 source compatibility #602
3
+Remove showBluetoothSettings for iOS #603
4
+
5
+= 1.2.1 =
6
+Fix EXC_BAD_ACCESS on iOS #389 Thanks claudiovolpato
7
+Return error if bad device id is passed to disconnect #410
8
+Better error message when location permission is denied Android #218
9
+Scan returns an error if location services are disabled Android #527
10
+Improve autoconnect for iOS #599
11
+add ble.refreshDeviceCache (Android) #587 Thanks Domvel
12
+add ble.bondedDevices (Android)
13
+add ble.connectedPeripheralsWithServices and ble.peripheralsWithIdentifiers (iOS)
14
+
15
+= 1.2.0 =
16
+Added un-scanned Peripheral concept on Android #560 Thanks doug-a-brunner
17
+Fixed failure to fire callbacks on Android when read or write in flight #561 Thanks doug-a-brunner
18
+Fixed dangling promises when reconnecting Android #562 Thanks doug-a-brunner
19
+Added error when starting a scan while another is running Android #565 Thanks doug-a-brunner
20
+Request MTU Size on Android #568 Thanks Domvel and Algoritma
21
+Don't prompt user to enable Bluetooth on iOS CBCentralManagerOptionShowPowerAlertKey #580 #174 Thanks H0rst and cairinmichie
22
+Implement showBluetoothSettings on iOS #591 Thanks cairinmichie
23
+Improve disconnect logic on Android #582
24
+
25
+= 1.1.9 =
26
+iOS error #558
27
+
28
+= 1.1.8 =
29
+Fix merge conflicts
30
+
31
+= 1.1.7 =
32
+Use same characteristic uuid with different service in iOS #349 Thanks Riccardo Degan (riccardodegan-geekcups)
33
+ble.read() example #346 Thanks Kelton Temby (ktemby)
34
+Remove pending stopNotificationCallback for iOS #355 Thanks Georges-Etienne Legendre (legege)
35
+add missing `resolve` and `reject` callbacks to Promise wrapper #360 Thanks Audrius Jakumavicius
36
+(aj-dev)
37
+Fix documentation typo #371 Thanks keanyc
38
+Fix documentation typo #424 Thanks ChanHyuk-Im
39
+Fix duplicate symbol when using with with cordova-plugin-ble-peripheral #373 Thanks Luca Torella (lucatorella)
40
+Add admonition about using with beacons #413 Hugh Barnes (hughbris)
41
+Handle errors in didUpdateValueForCharacteristic #385 Thanks soyelporras
42
+Fix spelling error in Android code # Thanks doug-a-brunner
43
+cordova-plugin-compat deprecated #466 #483 thanks ddugue
44
+Fix NullPointer exception on scan #504
45
+Fixed deprecated iOS CBPeripheral RSSI calls and build warning #446 Thanks doug-a-brunner
46
+Trapped commands that caused iOS API misuse warnings #450 Thanks doug-a-brunner
47
+Fire callbacks on iOS when device is disconnecting #451 Thanks doug-a-brunner
48
+Fixed NSInvalidArgumentException when 'undefined' passed to plugin cmds #452 Thanks doug-a-brunner
49
+Better errors on Android, when trying to read or write to a non-existing service #486 Thanks ddugue
50
+Add autoConnect support #499 Thanks hypersolution1
51
+
52
+= 1.1.4 =
53
+Prevents scan from removing connecting Peripherals on Android #315 & #341 Thanks mebrunet
54
+Documentation fixes #330 Thanks motla
55
+Documenation clarification about Location Services #318 Thanks petrometro
56
+Ensure peripheral is connected for startNotification and stopNotification on Android #343
57
+Error message for Android 4.3 devices that don't support BLE #263 Thanks PeacePan
58
+Must call scan before connect. Update documentation #340
59
+
60
+= 1.1.3 =
61
+NSBluetoothPeripheralUsageDescription #324 Thanks Tim Kim
62
+
63
+= 1.1.2 =
64
+Call connect failure callback for peripheral if user disables Bluetooth #264
65
+Fix iOS problem with multiple keys in service data #288 Thanks Lebbeous Fogle-Weekley
66
+Add errorMessage to JSON object that is returned (to connect failure callback) when a peripheral disconnects
67
+Call gatt.disconnect() before gatt.close() to get problematic devices to disconnect #221, #254, #214
68
+Include version of JavaScript API with promises #247 Thanks Kelly Campbell
69
+stopNotification on Android writes DISABLE_NOTIFICATION_VALUE to the descriptor #225 Thanks zjw1918
70
+
71
+= 1.1.1 =
72
+Update advertising data in peripheral while scanning (Android) #253
73
+
74
+= 1.1.0 =
75
+Add documentation about receiving notifications in the background on iOS #169
76
+Add option to report duplicates during scan #172 Thanks Chris Armstrong
77
+Read RSSI value from an active BLE connection #171 Thanks Chris Armstrong
78
+Register for callbacks on Bluetooth state change #136 Thanks Ryan Harvey
79
+Fix example for write characteristic #195 Thanks Wynout van der Veer
80
+Fix documentation for write & writeWithoutResponse #193 Thanks Blake Parkinson
81
+Update CC2650 example #200 Thanks jplourenco
82
+Connect peripheral with missing ble-flag (Android SDK 23) #226 Thanks PeacePan
83
+
84
+= 1.0.6 =
85
+Fix compile error with Cordova 5.x #219
86
+
87
+= 1.0.5 =
88
+Request Permissions for Android 6.0 (API level 23) #182
89
+Update documentation for isEnabled #170
90
+
91
+= 1.0.4 =
92
+Fix compile error with ios@4.0.1 #161
93
+
94
+= 1.0.3 =
95
+Don't block UI thread on Android when starting scan #121 Thanks Kelly Campbell
96
+Return characteristic even if properties don't match #132 #113 Thanks kanayo
97
+StopNotification for Android fixes #51
98
+Fix conflicts with the BluetoothSerial plugin #114
99
+
100
+= 1.0.2 =
101
+Update plugin id for examples
102
+Fix npm keywords
103
+
104
+= 1.0.1 =
105
+Handle services that reuse UUIDs across characteristics #82 #94 Thanks ggordan
106
+Disconnect cancels pending connections on iOS #93 Thanks rrharvey
107
+Add dummy browser platform implementation for better PhoneGap developer app support #87 #90 Thanks surajpindoria
108
+Replace notify in examples with startNotification #63
109
+Stop notification from stacking on Android #71 Thanks charlesalton
110
+Connect failure callback returns the peripheral #16
111
+Better log message for unsupported iOS hardware #60
112
+Update bluefruitle example to work with new versions of the hardware
113
+
114
+= 1.0.0 =
115
+Change plugin id cordova-plugin-ble-central
116
+Move to NPM #86
117
+iOS 9 #62 Thanks Khashayar Pourdeilami
118
+
119
+= 0.1.9 =
120
+Add start of WP8 for PGDA
121
+
122
+= 0.1.8 =
123
+Remove SDK version from config.xml (user is responsible for adding)
124
+Add tests for plugin
125
+Fix BluetoothLE example for Adafruit nRF8001 #57
126
+
127
+= 0.1.7 =
128
+Add showBluetoothSettings and enable for Android #43
129
+Update documentation about UUIDs #38
130
+
131
+= 0.1.6 =
132
+startNotification handles both notifications and indications
133
+
134
+= 0.1.5 =
135
+add startScan and stopScan #40
136
+update to RFduino example
137
+
138
+= 0.1.4 =
139
+Change Android behavior for leScan without service list
140
+
141
+= 0.1.3 =
142
+Remove NO_RESULT on iOS fixes #32
143
+
144
+= 0.1.2 =
145
+Ensure connect success callback is only called 1x on iOS #30
146
+Rename notify to startNotification
147
+Add stopNotification (iOS only)
148
+
149
+= 0.1.1 =
150
+Return reason code when write fails on iOS #29
151
+
152
+= 0.1.0 =
153
+Return advertising data in scan results #6, #7, #18
154
+Connect success returns service, characteristic and descriptor info #6
155
+iOS connectCallbackId is stored in Peripheral #17
156
+Move plugin directory to top level for phonegap build compatibility #20
157
+Rename writeCommand to writeWithoutResponse #21
158
+Services callback latch is per peripheral  #19
159
+Connect callback is per peripheral #17
160
+Fix iOS crash when scanning 2x #5
161
+Add ble.isEnabled method #11
162
+Add RedBearLab example
163
+Add BatteryService example
164
+
165
+= 0.0.2 =
166
+iOS - fix bug read callback was being called 2x
167
+iOS - fix bug write callback wasn't being called
168
+
169
+= 0.0.1 =
170
+initial release

+ 13 - 0
plugins/cordova-plugin-ble-central/LICENSE.txt

@@ -0,0 +1,13 @@
1
+Copyright 2014-2016 Don Coleman
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+    http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.

文件差異過大導致無法顯示
+ 957 - 0
plugins/cordova-plugin-ble-central/README.md


+ 16 - 0
plugins/cordova-plugin-ble-central/examples/battery/README.md

@@ -0,0 +1,16 @@
1
+## Battery
2
+
3
+Read the Battery Level from the [Battery Service 0x180](https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.battery_service.xml)
4
+
5
+ * Read Battery Level
6
+
7
+Hardware
8
+
9
+ * Any peripheral with Battery Service
10
+
11
+Install
12
+
13
+    $ cordova platform add android ios
14
+    $ cordova plugin add cordova-plugin-ble-central
15
+    $ cordova run
16
+    

+ 13 - 0
plugins/cordova-plugin-ble-central/examples/battery/config.xml

@@ -0,0 +1,13 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<widget id="com.megster.battery" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+    <name>Battery Test</name>
4
+    <description>
5
+        Cordova Bluetooth Plugin to read Battery Service
6
+    </description>
7
+    <author href="https://github.com/don">
8
+           Don Coleman
9
+    </author>
10
+    <content src="index.html" />
11
+    <access origin="*" />
12
+    <preference name="orientation" value="portrait" />
13
+</widget>

+ 83 - 0
plugins/cordova-plugin-ble-central/examples/battery/hooks/README.md

@@ -0,0 +1,83 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Cordova Hooks
22
+
23
+This directory may contain scripts used to customize cordova commands. This
24
+directory used to exist at `.cordova/hooks`, but has now been moved to the
25
+project root. Any scripts you add to these directories will be executed before
26
+and after the commands corresponding to the directory name. Useful for
27
+integrating your own build systems or integrating with version control systems.
28
+
29
+__Remember__: Make your scripts executable.
30
+
31
+## Hook Directories
32
+The following subdirectories will be used for hooks:
33
+
34
+    after_build/
35
+    after_compile/
36
+    after_docs/
37
+    after_emulate/
38
+    after_platform_add/
39
+    after_platform_rm/
40
+    after_platform_ls/
41
+    after_plugin_add/
42
+    after_plugin_ls/
43
+    after_plugin_rm/
44
+    after_plugin_search/
45
+    after_prepare/
46
+    after_run/
47
+    after_serve/
48
+    before_build/
49
+    before_compile/
50
+    before_docs/
51
+    before_emulate/
52
+    before_platform_add/
53
+    before_platform_rm/
54
+    before_platform_ls/
55
+    before_plugin_add/
56
+    before_plugin_ls/
57
+    before_plugin_rm/
58
+    before_plugin_search/
59
+    before_prepare/
60
+    before_run/
61
+    before_serve/
62
+    pre_package/ <-- Windows 8 and Windows Phone only.
63
+
64
+## Script Interface
65
+
66
+All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
67
+
68
+* CORDOVA_VERSION - The version of the Cordova-CLI.
69
+* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
70
+* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
71
+* CORDOVA_HOOK - Path to the hook that is being executed.
72
+* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
73
+
74
+If a script returns a non-zero exit code, then the parent cordova command will be aborted.
75
+
76
+
77
+## Writing hooks
78
+
79
+We highly recommend writting your hooks using Node.js so that they are
80
+cross-platform. Some good examples are shown here:
81
+
82
+[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
83
+

+ 61 - 0
plugins/cordova-plugin-ble-central/examples/battery/www/css/index.css

@@ -0,0 +1,61 @@
1
+body {
2
+    font-family: "Helvetica Neue";
3
+    font-weight: lighter;
4
+    color: #2a2a2a;
5
+    background-color: #f0f0ff;
6
+    
7
+    -webkit-appearance: none;  
8
+    -webkit-touch-callout: none;
9
+    -webkit-tap-highlight-color: rgba(0,0,0,0); 
10
+    
11
+    -webkit-touch-callout: none; -webkit-user-select: none;
12
+}
13
+
14
+h1 {
15
+    text-align: center;
16
+}
17
+
18
+button {
19
+    margin: 15px;
20
+    -webkit-appearance:none;    
21
+    font-size: 1.2em;
22
+}
23
+
24
+#mainPage {
25
+    text-align:center;
26
+}
27
+
28
+#detailPage {
29
+    text-align:center;
30
+    font-size: 2em;
31
+}
32
+
33
+#detailPage div {
34
+    margin: 20px;
35
+}
36
+
37
+#detailPage button {
38
+    margin-top: 40px;
39
+    font-size: 0.6em; /* undo 2em from parent */
40
+}
41
+
42
+ul {
43
+    list-style: none;
44
+    border-bottom: 1px solid #d3d3d3;    
45
+    text-align: left;
46
+}
47
+
48
+li {
49
+    margin-left: -40px;
50
+    padding: 5px;        
51
+    padding-top: 10px;
52
+    min-height: 50px;
53
+    border-top: 1px solid #d3d3d3;    
54
+    font-size: 0.9em;
55
+}
56
+
57
+button {
58
+    -webkit-appearance: none;
59
+    font-size: 1.5em;
60
+    border-radius: 0;
61
+}

+ 31 - 0
plugins/cordova-plugin-ble-central/examples/battery/www/index.html

@@ -0,0 +1,31 @@
1
+<!DOCTYPE html>
2
+<html>
3
+    <head>
4
+        <meta charset="utf-8" />
5
+        <meta name="format-detection" content="telephone=no" />
6
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1" />
7
+
8
+        <link rel="stylesheet" type="text/css" href="css/index.css" />
9
+        <title>SensorTag</title>
10
+    </head>
11
+    <body>
12
+        <div class="app">
13
+            <p style="text-align:center; padding-top: 20px;">Choose a peripheral with a battery service</p>
14
+            <div id="mainPage">
15
+                <ul id="deviceList">
16
+                </ul>
17
+                <button id="refreshButton">Refresh</button>
18
+            </div>
19
+            <div id="detailPage">
20
+                <div id="batteryState"></div>
21
+                <button id="batteryStateButton">Read Battery State</button>
22
+                <button id="disconnectButton">Disconnect</button>
23
+            </div>
24
+        </div>
25
+        <script type="text/javascript" src="cordova.js"></script>
26
+        <script type="text/javascript" src="js/index.js"></script>
27
+        <script type="text/javascript">
28
+            app.initialize();
29
+        </script>
30
+    </body>
31
+</html>

+ 105 - 0
plugins/cordova-plugin-ble-central/examples/battery/www/js/index.js

@@ -0,0 +1,105 @@
1
+// (c) 2014 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+/* global mainPage, deviceList, refreshButton */
16
+/* global detailPage, batteryState, batteryStateButton, disconnectButton */
17
+/* global ble  */
18
+/* jshint browser: true , devel: true*/
19
+'use strict';
20
+
21
+var battery = {
22
+    service: "180F",
23
+    level: "2A19"
24
+};
25
+
26
+var app = {
27
+    initialize: function() {
28
+        this.bindEvents();
29
+        detailPage.hidden = true;
30
+    },
31
+    bindEvents: function() {
32
+        document.addEventListener('deviceready', this.onDeviceReady, false);
33
+        refreshButton.addEventListener('touchstart', this.refreshDeviceList, false);
34
+        batteryStateButton.addEventListener('touchstart', this.readBatteryState, false);
35
+        disconnectButton.addEventListener('touchstart', this.disconnect, false);
36
+        deviceList.addEventListener('touchstart', this.connect, false); // assume not scrolling
37
+    },
38
+    onDeviceReady: function() {
39
+        app.refreshDeviceList();
40
+    },
41
+    refreshDeviceList: function() {
42
+        deviceList.innerHTML = ''; // empties the list
43
+        // scan for all devices
44
+        ble.scan([], 5, app.onDiscoverDevice, app.onError);
45
+    },
46
+    onDiscoverDevice: function(device) {
47
+
48
+        console.log(JSON.stringify(device));
49
+        var listItem = document.createElement('li'),
50
+            html = '<b>' + device.name + '</b><br/>' +
51
+                'RSSI: ' + device.rssi + '&nbsp;|&nbsp;' +
52
+                device.id;
53
+
54
+        listItem.dataset.deviceId = device.id;  // TODO
55
+        listItem.innerHTML = html;
56
+        deviceList.appendChild(listItem);
57
+
58
+    },
59
+    connect: function(e) {
60
+        var deviceId = e.target.dataset.deviceId,
61
+            onConnect = function() {
62
+
63
+                // TODO check if we have the battery service
64
+                // TODO check if the battery service can notify us
65
+                //ble.startNotification(deviceId, battery.service, battery.level, app.onBatteryLevelChange, app.onError);
66
+                batteryStateButton.dataset.deviceId = deviceId;
67
+                disconnectButton.dataset.deviceId = deviceId;
68
+                app.showDetailPage();
69
+            };
70
+
71
+        ble.connect(deviceId, onConnect, app.onError);
72
+    },
73
+    onBatteryLevelChange: function(data) {
74
+        console.log(data);
75
+        var message;
76
+        var a = new Uint8Array(data);
77
+        batteryState.innerHTML = a[0];
78
+    },
79
+    readBatteryState: function(event) {
80
+        console.log("readBatteryState");
81
+        var deviceId = event.target.dataset.deviceId;
82
+        ble.read(deviceId, battery.service, battery.level, app.onReadBatteryLevel, app.onError);
83
+    },
84
+    onReadBatteryLevel: function(data) {
85
+        console.log(data);
86
+        var message;
87
+        var a = new Uint8Array(data);
88
+        batteryState.innerHTML = a[0];
89
+    },
90
+    disconnect: function(event) {
91
+        var deviceId = event.target.dataset.deviceId;
92
+        ble.disconnect(deviceId, app.showMainPage, app.onError);
93
+    },
94
+    showMainPage: function() {
95
+        mainPage.hidden = false;
96
+        detailPage.hidden = true;
97
+    },
98
+    showDetailPage: function() {
99
+        mainPage.hidden = true;
100
+        detailPage.hidden = false;
101
+    },
102
+    onError: function(reason) {
103
+        alert("ERROR: " + reason); // real apps should use notification.alert
104
+    }
105
+};

+ 37 - 0
plugins/cordova-plugin-ble-central/examples/bluefruitle/README.md

@@ -0,0 +1,37 @@
1
+## Adafruit UART
2
+
3
+This example will connect to hardware running the Nordic UART service. Many of the Bluetooth LE products from Adafruit run the Nordic UART service. This example will also connect to the [Adafruit Bluefruit LE Friend](https://www.adafruit.com/products/2267), [puck.js](http://puckjs.com), or [BBC Micro:bit](http://microbit.org/) running [Espruino](http://www.espruino.com/).
4
+
5
+## Android
6
+
7
+	cordova platform add android
8
+	cordova run android --device
9
+
10
+NOTE: Some Android devices have trouble filtering by UUID when scanning. If your Android phone can't find your Bluetooth peripheral, try removing the service filter.
11
+
12
+Change 
13
+
14
+	ble.scan([bluefruit.serviceUUID], 5, app.onDiscoverDevice, app.onError);
15
+
16
+To 
17
+
18
+	ble.scan([], 5, app.onDiscoverDevice, app.onError);
19
+
20
+## iOS
21
+
22
+	cordova platform add ios
23
+	cordova run ios --device
24
+	
25
+Note: Sometimes Xcode can't deploy from the command line. If that happens, open BluefruitLE.xcworkspace and deploy to your phone using Xcode.
26
+
27
+    open platforms/ios/BluefruitLE.xcworkspace
28
+
29
+## Building a Peripheral
30
+
31
+If you have an Arduino Uno and [Adafruit's Bluefruit LE](http://www.adafruit.com/products/1697) breakout board. You can run the [callbackEcho sketch](https://github.com/adafruit/Adafruit_nRF8001/blob/master/examples/callbackEcho/callbackEcho.ino) and see [Adafruit's tutorial](https://learn.adafruit.com/getting-started-with-the-nrf8001-bluefruit-le-breakout/software-uart-service) for setting up the hardware and Arduino code.
32
+
33
+Hardware
34
+
35
+ * [Arduino](http://www.adafruit.com/products/50)
36
+ * [BluefruitLE](http://www.adafruit.com/products/1697)
37
+

+ 13 - 0
plugins/cordova-plugin-ble-central/examples/bluefruitle/config.xml

@@ -0,0 +1,13 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<widget id="com.megster.bluefruitle" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+    <name>BluefruitLE</name>
4
+    <description>
5
+        A sample Apache Cordova application connects to the Nordic UART Service.
6
+    </description>
7
+    <author href="http://github.com/don">
8
+        Don Coleman
9
+    </author>
10
+    <content src="index.html" />
11
+    <access origin="*" />
12
+    <plugin name="cordova-plugin-ble-central" spec="~1.1.4" />
13
+</widget>

+ 83 - 0
plugins/cordova-plugin-ble-central/examples/bluefruitle/hooks/README.md

@@ -0,0 +1,83 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Cordova Hooks
22
+
23
+This directory may contain scripts used to customize cordova commands. This
24
+directory used to exist at `.cordova/hooks`, but has now been moved to the
25
+project root. Any scripts you add to these directories will be executed before
26
+and after the commands corresponding to the directory name. Useful for
27
+integrating your own build systems or integrating with version control systems.
28
+
29
+__Remember__: Make your scripts executable.
30
+
31
+## Hook Directories
32
+The following subdirectories will be used for hooks:
33
+
34
+    after_build/
35
+    after_compile/
36
+    after_docs/
37
+    after_emulate/
38
+    after_platform_add/
39
+    after_platform_rm/
40
+    after_platform_ls/
41
+    after_plugin_add/
42
+    after_plugin_ls/
43
+    after_plugin_rm/
44
+    after_plugin_search/
45
+    after_prepare/
46
+    after_run/
47
+    after_serve/
48
+    before_build/
49
+    before_compile/
50
+    before_docs/
51
+    before_emulate/
52
+    before_platform_add/
53
+    before_platform_rm/
54
+    before_platform_ls/
55
+    before_plugin_add/
56
+    before_plugin_ls/
57
+    before_plugin_rm/
58
+    before_plugin_search/
59
+    before_prepare/
60
+    before_run/
61
+    before_serve/
62
+    pre_package/ <-- Windows 8 and Windows Phone only.
63
+
64
+## Script Interface
65
+
66
+All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
67
+
68
+* CORDOVA_VERSION - The version of the Cordova-CLI.
69
+* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
70
+* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
71
+* CORDOVA_HOOK - Path to the hook that is being executed.
72
+* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
73
+
74
+If a script returns a non-zero exit code, then the parent cordova command will be aborted.
75
+
76
+
77
+## Writing hooks
78
+
79
+We highly recommend writting your hooks using Node.js so that they are
80
+cross-platform. Some good examples are shown here:
81
+
82
+[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
83
+

+ 77 - 0
plugins/cordova-plugin-ble-central/examples/bluefruitle/www/css/index.css

@@ -0,0 +1,77 @@
1
+body {
2
+    font-family: "Helvetica Neue";
3
+    font-weight: lighter;
4
+    color: #2a2a2a;
5
+    background-color: #f0f0ff;
6
+    
7
+    -webkit-appearance: none;  
8
+    -webkit-touch-callout: none;
9
+    -webkit-tap-highlight-color: rgba(0,0,0,0); 
10
+    
11
+    -webkit-touch-callout: none; -webkit-user-select: none;
12
+}
13
+
14
+h1 {
15
+    text-align: center;
16
+}
17
+
18
+button {
19
+    margin: 15px;
20
+    -webkit-appearance:none;    
21
+    font-size: 1.2em;
22
+}
23
+
24
+/* TODO */
25
+input[type=range] {
26
+    width: 100%;
27
+}
28
+
29
+#mainPage {
30
+    text-align:center;
31
+}
32
+
33
+#detailPage {
34
+    text-align:center;
35
+    font-size: 2em;
36
+}
37
+
38
+#detailPage div {
39
+    margin: 20px;
40
+}
41
+
42
+#detailPage button {
43
+    margin-top: 40px;
44
+    font-size: 0.6em; /* undo 2em from parent */
45
+}
46
+
47
+ul {
48
+    list-style: none;
49
+    border-bottom: 1px solid #d3d3d3;    
50
+    text-align: left;
51
+}
52
+
53
+li {
54
+    margin-left: -40px;
55
+    padding: 5px;        
56
+    padding-top: 10px;
57
+    min-height: 50px;
58
+    border-top: 1px solid #d3d3d3;    
59
+    font-size: 0.9em;
60
+}
61
+
62
+button {
63
+    -webkit-appearance: none;
64
+    font-size: 1.5em;
65
+    border-radius: 0;
66
+}
67
+
68
+#resultDiv {
69
+    font: 16px "Source Sans", helvetica, arial, sans-serif;
70
+    font-weight: 200;
71
+    display: block;
72
+    -webkit-border-radius: 6px;
73
+    width: 100%;
74
+    height: 140px;
75
+    text-align: left;    
76
+    overflow: auto;
77
+}

+ 34 - 0
plugins/cordova-plugin-ble-central/examples/bluefruitle/www/index.html

@@ -0,0 +1,34 @@
1
+<!DOCTYPE html>
2
+<html>
3
+    <head>
4
+        <meta charset="utf-8" />
5
+        <meta name="format-detection" content="telephone=no" />
6
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1,  target-densitydpi=device-dpi" />
7
+        
8
+        <link rel="stylesheet" type="text/css" href="css/index.css" />
9
+        <title>BluefruitLE</title>
10
+    </head>
11
+    <body>
12
+        <div class="app">
13
+            <h1>BluefruitLE</h1>
14
+            <div id="mainPage">
15
+                <ul id="deviceList">                    
16
+                </ul>
17
+                <button id="refreshButton">Refresh</button>
18
+            </div>
19
+            <div id="detailPage">
20
+                <div id="resultDiv"></div>
21
+                <div>
22
+                    <input type="text" id="messageInput" value="Hello"/>
23
+                    <button id="sendButton">Send</button>                    
24
+                </div>
25
+                <button id="disconnectButton">Disconnect</button>
26
+            </div>            
27
+        </div>
28
+        <script type="text/javascript" src="cordova.js"></script>
29
+        <script type="text/javascript" src="js/index.js"></script>
30
+        <script type="text/javascript">
31
+            app.initialize();
32
+        </script>
33
+    </body>
34
+</html>

+ 158 - 0
plugins/cordova-plugin-ble-central/examples/bluefruitle/www/js/index.js

@@ -0,0 +1,158 @@
1
+// (c) 2014 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+/* global mainPage, deviceList, refreshButton */
16
+/* global detailPage, resultDiv, messageInput, sendButton, disconnectButton */
17
+/* global ble  */
18
+/* jshint browser: true , devel: true*/
19
+'use strict';
20
+
21
+// ASCII only
22
+function bytesToString(buffer) {
23
+    return String.fromCharCode.apply(null, new Uint8Array(buffer));
24
+}
25
+
26
+// ASCII only
27
+function stringToBytes(string) {
28
+    var array = new Uint8Array(string.length);
29
+    for (var i = 0, l = string.length; i < l; i++) {
30
+        array[i] = string.charCodeAt(i);
31
+    }
32
+    return array.buffer;
33
+}
34
+
35
+// this is Nordic's UART service
36
+var bluefruit = {
37
+    serviceUUID: '6e400001-b5a3-f393-e0a9-e50e24dcca9e',
38
+    txCharacteristic: '6e400002-b5a3-f393-e0a9-e50e24dcca9e', // transmit is from the phone's perspective
39
+    rxCharacteristic: '6e400003-b5a3-f393-e0a9-e50e24dcca9e'  // receive is from the phone's perspective
40
+};
41
+
42
+var app = {
43
+    initialize: function() {
44
+        this.bindEvents();
45
+        detailPage.hidden = true;
46
+    },
47
+    bindEvents: function() {
48
+        document.addEventListener('deviceready', this.onDeviceReady, false);
49
+        refreshButton.addEventListener('touchstart', this.refreshDeviceList, false);
50
+        sendButton.addEventListener('click', this.sendData, false);
51
+        disconnectButton.addEventListener('touchstart', this.disconnect, false);
52
+        deviceList.addEventListener('touchstart', this.connect, false); // assume not scrolling
53
+    },
54
+    onDeviceReady: function() {
55
+        app.refreshDeviceList();
56
+    },
57
+    refreshDeviceList: function() {
58
+        deviceList.innerHTML = ''; // empties the list
59
+        ble.scan([bluefruit.serviceUUID], 5, app.onDiscoverDevice, app.onError);
60
+        
61
+        // if Android can't find your device try scanning for all devices
62
+        // ble.scan([], 5, app.onDiscoverDevice, app.onError);
63
+    },
64
+    onDiscoverDevice: function(device) {
65
+        var listItem = document.createElement('li'),
66
+            html = '<b>' + device.name + '</b><br/>' +
67
+                'RSSI: ' + device.rssi + '&nbsp;|&nbsp;' +
68
+                device.id;
69
+
70
+        listItem.dataset.deviceId = device.id;
71
+        listItem.innerHTML = html;
72
+        deviceList.appendChild(listItem);
73
+    },
74
+    connect: function(e) {
75
+        var deviceId = e.target.dataset.deviceId,
76
+            onConnect = function(peripheral) {
77
+                app.determineWriteType(peripheral);
78
+
79
+                // subscribe for incoming data
80
+                ble.startNotification(deviceId, bluefruit.serviceUUID, bluefruit.rxCharacteristic, app.onData, app.onError);
81
+                sendButton.dataset.deviceId = deviceId;
82
+                disconnectButton.dataset.deviceId = deviceId;
83
+                resultDiv.innerHTML = "";
84
+                app.showDetailPage();
85
+            };
86
+
87
+        ble.connect(deviceId, onConnect, app.onError);
88
+    },
89
+    determineWriteType: function(peripheral) {
90
+        // Adafruit nRF8001 breakout uses WriteWithoutResponse for the TX characteristic
91
+        // Newer Bluefruit devices use Write Request for the TX characteristic
92
+
93
+        var characteristic = peripheral.characteristics.filter(function(element) {
94
+            if (element.characteristic.toLowerCase() === bluefruit.txCharacteristic) {
95
+                return element;
96
+            }
97
+        })[0];
98
+
99
+        if (characteristic.properties.indexOf('WriteWithoutResponse') > -1) {
100
+            app.writeWithoutResponse = true;
101
+        } else {
102
+            app.writeWithoutResponse = false;
103
+        }
104
+
105
+    },
106
+    onData: function(data) { // data received from Arduino
107
+        console.log(data);
108
+        resultDiv.innerHTML = resultDiv.innerHTML + "Received: " + bytesToString(data) + "<br/>";
109
+        resultDiv.scrollTop = resultDiv.scrollHeight;
110
+    },
111
+    sendData: function(event) { // send data to Arduino
112
+
113
+        var success = function() {
114
+            console.log("success");
115
+            resultDiv.innerHTML = resultDiv.innerHTML + "Sent: " + messageInput.value + "<br/>";
116
+            resultDiv.scrollTop = resultDiv.scrollHeight;
117
+        };
118
+
119
+        var failure = function() {
120
+            alert("Failed writing data to the bluefruit le");
121
+        };
122
+
123
+        var data = stringToBytes(messageInput.value);
124
+        var deviceId = event.target.dataset.deviceId;
125
+
126
+        if (app.writeWithoutResponse) {
127
+            ble.writeWithoutResponse(
128
+                deviceId,
129
+                bluefruit.serviceUUID,
130
+                bluefruit.txCharacteristic,
131
+                data, success, failure
132
+            );
133
+        } else {
134
+            ble.write(
135
+                deviceId,
136
+                bluefruit.serviceUUID,
137
+                bluefruit.txCharacteristic,
138
+                data, success, failure
139
+            );
140
+        }
141
+
142
+    },
143
+    disconnect: function(event) {
144
+        var deviceId = event.target.dataset.deviceId;
145
+        ble.disconnect(deviceId, app.showMainPage, app.onError);
146
+    },
147
+    showMainPage: function() {
148
+        mainPage.hidden = false;
149
+        detailPage.hidden = true;
150
+    },
151
+    showDetailPage: function() {
152
+        mainPage.hidden = true;
153
+        detailPage.hidden = false;
154
+    },
155
+    onError: function(reason) {
156
+        alert("ERROR: " + JSON.stringify(reason)); // real apps should use notification.alert
157
+    }
158
+};

+ 9 - 0
plugins/cordova-plugin-ble-central/examples/heartrate/README.md

@@ -0,0 +1,9 @@
1
+BLE Heart Rate Demo
2
+
3
+Connects to a peripherial providing the [Heart Rate Service](http://goo.gl/wKH3X7).
4
+
5
+Works with iOS or Android 4.3+.
6
+
7
+    $ cordova platform add android
8
+    $ cordova plugin add cordova-plugin-ble-central
9
+    $ cordova run

+ 12 - 0
plugins/cordova-plugin-ble-central/examples/heartrate/config.xml

@@ -0,0 +1,12 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<widget id="com.megster.example.hrm" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+    <name>HeartRate</name>
4
+    <description>
5
+        BLE Heart Rate Monitor Demo
6
+    </description>
7
+    <author href="https://github.com/don">
8
+        Don Coleman
9
+    </author>
10
+    <content src="index.html" />
11
+    <access origin="*" />
12
+</widget>

+ 196 - 0
plugins/cordova-plugin-ble-central/examples/heartrate/hooks/README.md

@@ -0,0 +1,196 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Cordova Hooks
22
+
23
+Cordova Hooks represent special scripts which could be added by application and plugin developers or even by your own build system  to customize cordova commands. Hook scripts could be defined by adding them to the special predefined folder (`/hooks`) or via configuration files (`config.xml` and `plugin.xml`) and run serially in the following order: 
24
+* Application hooks from `/hooks`;
25
+* Application hooks from `config.xml`;
26
+* Plugin hooks from `plugins/.../plugin.xml`.
27
+
28
+__Remember__: Make your scripts executable.
29
+
30
+__Note__: `.cordova/hooks` directory is also supported for backward compatibility, but we don't recommend using it as it is deprecated.
31
+
32
+## Supported hook types
33
+The following hook types are supported:
34
+
35
+    after_build/
36
+    after_compile/
37
+    after_docs/
38
+    after_emulate/
39
+    after_platform_add/
40
+    after_platform_rm/
41
+    after_platform_ls/
42
+    after_plugin_add/
43
+    after_plugin_ls/
44
+    after_plugin_rm/
45
+    after_plugin_search/
46
+    after_plugin_install/   <-- Plugin hooks defined in plugin.xml are executed exclusively for a plugin being installed
47
+    after_prepare/
48
+    after_run/
49
+    after_serve/
50
+    before_build/
51
+    before_compile/
52
+    before_docs/
53
+    before_emulate/
54
+    before_platform_add/
55
+    before_platform_rm/
56
+    before_platform_ls/
57
+    before_plugin_add/
58
+    before_plugin_ls/
59
+    before_plugin_rm/
60
+    before_plugin_search/
61
+    before_plugin_install/   <-- Plugin hooks defined in plugin.xml are executed exclusively for a plugin being installed
62
+    before_plugin_uninstall/   <-- Plugin hooks defined in plugin.xml are executed exclusively for a plugin being uninstalled
63
+    before_prepare/
64
+    before_run/
65
+    before_serve/
66
+    pre_package/ <-- Windows 8 and Windows Phone only.
67
+
68
+## Ways to define hooks
69
+### Via '/hooks' directory
70
+To execute custom action when corresponding hook type is fired, use hook type as a name for a subfolder inside 'hooks' directory and place you script file here, for example:
71
+
72
+    # script file will be automatically executed after each build
73
+    hooks/after_build/after_build_custom_action.js
74
+
75
+
76
+### Config.xml
77
+
78
+Hooks can be defined in project's `config.xml` using `<hook>` elements, for example:
79
+
80
+    <hook type="before_build" src="scripts/appBeforeBuild.bat" />
81
+    <hook type="before_build" src="scripts/appBeforeBuild.js" />
82
+    <hook type="before_plugin_install" src="scripts/appBeforePluginInstall.js" />
83
+
84
+    <platform name="wp8">
85
+        <hook type="before_build" src="scripts/wp8/appWP8BeforeBuild.bat" />
86
+        <hook type="before_build" src="scripts/wp8/appWP8BeforeBuild.js" />
87
+        <hook type="before_plugin_install" src="scripts/wp8/appWP8BeforePluginInstall.js" />
88
+        ...
89
+    </platform>
90
+
91
+    <platform name="windows8">
92
+        <hook type="before_build" src="scripts/windows8/appWin8BeforeBuild.bat" />
93
+        <hook type="before_build" src="scripts/windows8/appWin8BeforeBuild.js" />
94
+        <hook type="before_plugin_install" src="scripts/windows8/appWin8BeforePluginInstall.js" />
95
+        ...
96
+    </platform>
97
+
98
+### Plugin hooks (plugin.xml)
99
+
100
+As a plugin developer you can define hook scripts using `<hook>` elements in a `plugin.xml` like that:
101
+
102
+    <hook type="before_plugin_install" src="scripts/beforeInstall.js" />
103
+    <hook type="after_build" src="scripts/afterBuild.js" />
104
+
105
+    <platform name="wp8">
106
+        <hook type="before_plugin_install" src="scripts/wp8BeforeInstall.js" />
107
+        <hook type="before_build" src="scripts/wp8BeforeBuild.js" />
108
+        ...
109
+    </platform>
110
+
111
+`before_plugin_install`, `after_plugin_install`, `before_plugin_uninstall` plugin hooks will be fired exclusively for the plugin being installed/uninstalled.
112
+
113
+## Script Interface
114
+
115
+### Javascript
116
+
117
+If you are writing hooks in Javascript you should use the following module definition:
118
+```javascript
119
+module.exports = function(context) {
120
+    ...
121
+}
122
+```
123
+
124
+You can make your scipts async using Q:
125
+```javascript
126
+module.exports = function(context) {
127
+    var Q = context.requireCordovaModule('q');
128
+    var deferral = new Q.defer();
129
+
130
+    setTimeout(function(){
131
+    	console.log('hook.js>> end');
132
+		deferral.resolve();
133
+    }, 1000);
134
+
135
+    return deferral.promise;
136
+}
137
+```
138
+
139
+`context` object contains hook type, executed script full path, hook options, command-line arguments passed to Cordova and top-level "cordova" object:
140
+```json
141
+{
142
+	"hook": "before_plugin_install",
143
+	"scriptLocation": "c:\\script\\full\\path\\appBeforePluginInstall.js",
144
+	"cmdLine": "The\\exact\\command\\cordova\\run\\with arguments",
145
+	"opts": {
146
+		"projectRoot":"C:\\path\\to\\the\\project",
147
+		"cordova": {
148
+			"platforms": ["wp8"],
149
+			"plugins": ["com.plugin.withhooks"],
150
+			"version": "0.21.7-dev"
151
+		},
152
+		"plugin": {
153
+			"id": "com.plugin.withhooks",
154
+			"pluginInfo": {
155
+				...
156
+			},
157
+			"platform": "wp8",
158
+			"dir": "C:\\path\\to\\the\\project\\plugins\\com.plugin.withhooks"
159
+		}
160
+	},
161
+	"cordova": {...}
162
+}
163
+
164
+```
165
+`context.opts.plugin` object will only be passed to plugin hooks scripts.
166
+
167
+You can also require additional Cordova modules in your script using `context.requireCordovaModule` in the following way:
168
+```javascript
169
+var Q = context.requireCordovaModule('q');
170
+```
171
+
172
+__Note__:  new module loader script interface is used for the `.js` files defined via `config.xml` or `plugin.xml` only. 
173
+For compatibility reasons hook files specified via `/hooks` folders are run via Node child_process spawn, see 'Non-javascript' section below.
174
+
175
+### Non-javascript
176
+
177
+Non-javascript scripts are run via Node child_process spawn from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
178
+
179
+* CORDOVA_VERSION - The version of the Cordova-CLI.
180
+* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
181
+* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
182
+* CORDOVA_HOOK - Path to the hook that is being executed.
183
+* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
184
+
185
+If a script returns a non-zero exit code, then the parent cordova command will be aborted.
186
+
187
+## Writing hooks
188
+
189
+We highly recommend writting your hooks using Node.js so that they are
190
+cross-platform. Some good examples are shown here:
191
+
192
+[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
193
+
194
+Also, note that even if you are working on Windows, and in case your hook scripts aren't bat files (which is recommended, if you want your scripts to work in non-Windows operating systems) Cordova CLI will expect a shebang line as the first line for it to know the interpreter it needs to use to launch the script. The shebang line should match the following example:
195
+
196
+    #!/usr/bin/env [name_of_interpreter_executable]

+ 27 - 0
plugins/cordova-plugin-ble-central/examples/heartrate/www/css/index.css

@@ -0,0 +1,27 @@
1
+* {
2
+    -webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
3
+}
4
+
5
+body {
6
+    -webkit-touch-callout: none;                /* prevent callout to copy image, etc when tap to hold */
7
+    -webkit-text-size-adjust: none;             /* prevent webkit from resizing text to fit */
8
+    -webkit-user-select: none;                  /* prevent copy paste, to allow, change 'none' to 'text' */
9
+    font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif;
10
+    height:100%;
11
+    margin:0px;
12
+    padding:0px;
13
+    width:100%;
14
+    text-align: center;
15
+}
16
+
17
+div {
18
+    padding: 20px;
19
+}
20
+
21
+h1 {
22
+    font-size:3em;
23
+}
24
+
25
+h2 {
26
+    font-size:2em;
27
+}

+ 20 - 0
plugins/cordova-plugin-ble-central/examples/heartrate/www/index.html

@@ -0,0 +1,20 @@
1
+<!DOCTYPE html>
2
+<html>
3
+    <head>
4
+        <meta charset="utf-8" />
5
+        <meta name="format-detection" content="telephone=no" />
6
+        <meta name="msapplication-tap-highlight" content="no" />
7
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
8
+        <link rel="stylesheet" type="text/css" href="css/index.css" />
9
+        <title>Heart Rate</title>
10
+    </head>
11
+    <body>
12
+        <div>
13
+            <h2>BLE Heart Rate Demo</h2>
14
+            <h1 id="beatsPerMinute">...</h1>
15
+            <div id="statusDiv"></div>
16
+        </div>
17
+        <script type="text/javascript" src="cordova.js"></script>
18
+        <script type="text/javascript" src="js/index.js"></script>
19
+    </body>
20
+</html>

+ 83 - 0
plugins/cordova-plugin-ble-central/examples/heartrate/www/js/index.js

@@ -0,0 +1,83 @@
1
+// (c) 2015 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+/* global ble, statusDiv, beatsPerMinute */
16
+/* jshint browser: true , devel: true*/
17
+
18
+// See BLE heart rate service http://goo.gl/wKH3X7
19
+var heartRate = {
20
+    service: '180d',
21
+    measurement: '2a37'
22
+};
23
+
24
+var app = {
25
+    initialize: function() {
26
+        this.bindEvents();
27
+    },
28
+    bindEvents: function() {
29
+        document.addEventListener('deviceready', this.onDeviceReady, false);
30
+    },
31
+    onDeviceReady: function() {
32
+        app.scan();
33
+    },
34
+    scan: function() {
35
+        app.status("Scanning for Heart Rate Monitor");
36
+
37
+        var foundHeartRateMonitor = false;
38
+
39
+        function onScan(peripheral) {
40
+            // this is demo code, assume there is only one heart rate monitor
41
+            console.log("Found " + JSON.stringify(peripheral));
42
+            foundHeartRateMonitor = true;
43
+
44
+            ble.connect(peripheral.id, app.onConnect, app.onDisconnect);
45
+        }
46
+
47
+        function scanFailure(reason) {
48
+            alert("BLE Scan Failed");
49
+        }
50
+
51
+        ble.scan([heartRate.service], 5, onScan, scanFailure);
52
+
53
+        setTimeout(function() {
54
+            if (!foundHeartRateMonitor) {
55
+                app.status("Did not find a heart rate monitor.");
56
+            }
57
+        }, 5000);
58
+    },
59
+    onConnect: function(peripheral) {
60
+        app.status("Connected to " + peripheral.id);
61
+        ble.startNotification(peripheral.id, heartRate.service, heartRate.measurement, app.onData, app.onError);
62
+    },
63
+    onDisconnect: function(reason) {
64
+        alert("Disconnected " + reason);
65
+        beatsPerMinute.innerHTML = "...";
66
+        app.status("Disconnected");
67
+    },
68
+    onData: function(buffer) {
69
+        // assuming heart rate measurement is Uint8 format, real code should check the flags
70
+        // See the characteristic specs http://goo.gl/N7S5ZS
71
+        var data = new Uint8Array(buffer);
72
+        beatsPerMinute.innerHTML = data[1];
73
+    },
74
+    onError: function(reason) {
75
+        alert("There was an error " + reason);
76
+    },
77
+    status: function(message) {
78
+        console.log(message);
79
+        statusDiv.innerHTML = message;
80
+    }
81
+};
82
+
83
+app.initialize();

+ 18 - 0
plugins/cordova-plugin-ble-central/examples/metawear/README.md

@@ -0,0 +1,18 @@
1
+## MetaWare
2
+
3
+Example using mbientlab's [MetaWare](http://mbientlab.com/product/).
4
+
5
+Read values from the button press on the MetaWear.
6
+
7
+Operate the Motor and Buzzer on the MetaWare.
8
+
9
+Hardware
10
+
11
+ * [MetaWare](http://mbientlab.com/product/)
12
+
13
+Install
14
+
15
+    $ cordova platform add android ios
16
+    $ cordova plugin add cordova-plugin-ble-central
17
+    $ cordova run
18
+

+ 12 - 0
plugins/cordova-plugin-ble-central/examples/metawear/config.xml

@@ -0,0 +1,12 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<widget id="com.megster.metawear" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+    <name>MetaWear</name>
4
+    <description>
5
+        A sample Apache Cordova application that connects to mbientlab's MetaWear.
6
+    </description>
7
+    <author href="https://github.com/don">
8
+        Don Coleman
9
+    </author>
10
+    <content src="index.html" />
11
+    <access origin="*" />
12
+</widget>

+ 83 - 0
plugins/cordova-plugin-ble-central/examples/metawear/hooks/README.md

@@ -0,0 +1,83 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Cordova Hooks
22
+
23
+This directory may contain scripts used to customize cordova commands. This
24
+directory used to exist at `.cordova/hooks`, but has now been moved to the
25
+project root. Any scripts you add to these directories will be executed before
26
+and after the commands corresponding to the directory name. Useful for
27
+integrating your own build systems or integrating with version control systems.
28
+
29
+__Remember__: Make your scripts executable.
30
+
31
+## Hook Directories
32
+The following subdirectories will be used for hooks:
33
+
34
+    after_build/
35
+    after_compile/
36
+    after_docs/
37
+    after_emulate/
38
+    after_platform_add/
39
+    after_platform_rm/
40
+    after_platform_ls/
41
+    after_plugin_add/
42
+    after_plugin_ls/
43
+    after_plugin_rm/
44
+    after_plugin_search/
45
+    after_prepare/
46
+    after_run/
47
+    after_serve/
48
+    before_build/
49
+    before_compile/
50
+    before_docs/
51
+    before_emulate/
52
+    before_platform_add/
53
+    before_platform_rm/
54
+    before_platform_ls/
55
+    before_plugin_add/
56
+    before_plugin_ls/
57
+    before_plugin_rm/
58
+    before_plugin_search/
59
+    before_prepare/
60
+    before_run/
61
+    before_serve/
62
+    pre_package/ <-- Windows 8 and Windows Phone only.
63
+
64
+## Script Interface
65
+
66
+All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
67
+
68
+* CORDOVA_VERSION - The version of the Cordova-CLI.
69
+* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
70
+* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
71
+* CORDOVA_HOOK - Path to the hook that is being executed.
72
+* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
73
+
74
+If a script returns a non-zero exit code, then the parent cordova command will be aborted.
75
+
76
+
77
+## Writing hooks
78
+
79
+We highly recommend writting your hooks using Node.js so that they are
80
+cross-platform. Some good examples are shown here:
81
+
82
+[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
83
+

+ 95 - 0
plugins/cordova-plugin-ble-central/examples/metawear/www/css/index.css

@@ -0,0 +1,95 @@
1
+body {
2
+    font-family: "Helvetica Neue";
3
+    font-weight: lighter;
4
+    color: #2a2a2a;
5
+    background-color: #f0f0ff;
6
+    
7
+    -webkit-appearance: none;  
8
+    -webkit-touch-callout: none;
9
+    -webkit-tap-highlight-color: rgba(0,0,0,0); 
10
+    
11
+    -webkit-touch-callout: none; -webkit-user-select: none;
12
+}
13
+
14
+h1 {
15
+    text-align: center;
16
+}
17
+
18
+button {
19
+    margin: 15px;
20
+    -webkit-appearance:none;    
21
+    font-size: 1.2em;
22
+}
23
+
24
+/* TODO */
25
+input[type=range] {
26
+    width: 100%;
27
+}
28
+
29
+#mainPage {
30
+    text-align:center;
31
+}
32
+
33
+#detailPage {
34
+    text-align:center;
35
+    font-size: 2em;
36
+}
37
+
38
+#detailPage div {
39
+    margin: 20px;
40
+}
41
+
42
+#detailPage button {
43
+    margin-top: 40px;
44
+    font-size: 0.6em; /* undo 2em from parent */
45
+}
46
+
47
+ul {
48
+    list-style: none;
49
+    border-bottom: 1px solid #d3d3d3;    
50
+    text-align: left;
51
+}
52
+
53
+li {
54
+    margin-left: -40px;
55
+    padding: 5px;        
56
+    padding-top: 10px;
57
+    min-height: 50px;
58
+    border-top: 1px solid #d3d3d3;    
59
+    font-size: 0.9em;
60
+}
61
+
62
+button {
63
+    -webkit-appearance: none;
64
+    font-size: 1.5em;
65
+    border-radius: 0;
66
+}
67
+
68
+#resultDiv {
69
+    font: 16px "Source Sans", helvetica, arial, sans-serif;
70
+    font-weight: 200;
71
+    display: block;
72
+    -webkit-border-radius: 6px;
73
+    width: 100%;
74
+    height: 140px;
75
+    text-align: left;    
76
+    overflow: auto;
77
+}
78
+
79
+.haptic {
80
+    font-size: 0.5em;
81
+}
82
+
83
+.buttonDiv {
84
+    padding: 0px;
85
+    margin: 0px;
86
+    font-size: 2em;
87
+}
88
+
89
+#detailPage .haptic button {
90
+    margin-top: 15px;    
91
+}
92
+
93
+input[type="number"] {
94
+   width:50px;
95
+}

+ 37 - 0
plugins/cordova-plugin-ble-central/examples/metawear/www/index.html

@@ -0,0 +1,37 @@
1
+<!DOCTYPE html>
2
+<html>
3
+    <head>
4
+        <meta charset="utf-8" />
5
+        <meta name="format-detection" content="telephone=no" />
6
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1" />
7
+        
8
+        <link rel="stylesheet" type="text/css" href="css/index.css" />
9
+        <title>MetaWear</title>
10
+    </head>
11
+    <body>
12
+        <div class="app">
13
+            <h1>MetaWear</h1>
14
+            <div id="mainPage">
15
+                <ul id="deviceList">                    
16
+                </ul>
17
+                <button id="refreshButton">Refresh</button>
18
+            </div>
19
+            <div id="detailPage">
20
+                <div id="resultDiv"></div>
21
+                <div class="haptic">
22
+                    <label>Pulse Width&nbsp;<input type="number" id="pulseWidthInput" value="500"/></label>&nbsp;ms
23
+                    <div class="buttonDiv">
24
+                        <button id="motorButton">Motor</button>
25
+                        <button id="buzzerButton">Buzzer</button>                    
26
+                    </div>
27
+                </div>
28
+                <button id="disconnectButton">Disconnect</button>
29
+            </div>            
30
+        </div>
31
+        <script type="text/javascript" src="cordova.js"></script>
32
+        <script type="text/javascript" src="js/index.js"></script>
33
+        <script type="text/javascript">
34
+            app.initialize();
35
+        </script>
36
+    </body>
37
+</html>

+ 156 - 0
plugins/cordova-plugin-ble-central/examples/metawear/www/js/index.js

@@ -0,0 +1,156 @@
1
+// (c) 2014 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+/* global mainPage, deviceList, refreshButton */
16
+/* global detailPage, resultDiv, pulseWidthInput, motorButton, buzzerButton, disconnectButton */
17
+/* global ble, cordova */
18
+/* jshint browser: true , devel: true*/
19
+'use strict';
20
+
21
+// this is MetaWear's UART service
22
+var metawear = {
23
+    serviceUUID: "326a9000-85cb-9195-d9dd-464cfbbae75a",
24
+    txCharacteristic: "326a9001-85cb-9195-d9dd-464cfbbae75a", // transmit is from the phone's perspective
25
+    rxCharacteristic: "326a9006-85cb-9195-d9dd-464cfbbae75a"  // receive is from the phone's perspective
26
+};
27
+
28
+var app = {
29
+    deviceId: "",
30
+    initialize: function() {
31
+        this.bindEvents();
32
+        detailPage.hidden = true;
33
+    },
34
+    bindEvents: function() {
35
+        document.addEventListener('deviceready', this.onDeviceReady, false);
36
+        refreshButton.addEventListener('touchstart', this.refreshDeviceList, false);
37
+        motorButton.addEventListener('click', this.onMotorButton, false);
38
+        buzzerButton.addEventListener('click', this.onBuzzerButton, false);
39
+        disconnectButton.addEventListener('touchstart', this.disconnect, false);
40
+        deviceList.addEventListener('touchstart', this.connect, false); // assume not scrolling
41
+    },
42
+    onDeviceReady: function() {
43
+        app.refreshDeviceList();
44
+    },
45
+    refreshDeviceList: function() {
46
+        deviceList.innerHTML = ''; // empties the list
47
+        if (cordova.platformId === 'android') { // Android filtering is broken
48
+            ble.scan([], 5, app.onDiscoverDevice, app.onError);
49
+        } else {
50
+            ble.scan([metawear.serviceUUID], 5, app.onDiscoverDevice, app.onError);
51
+        }
52
+    },
53
+    onDiscoverDevice: function(device) {
54
+        var listItem = document.createElement('li'),
55
+            html = '<b>' + device.name + '</b><br/>' +
56
+                'RSSI: ' + device.rssi + '&nbsp;|&nbsp;' +
57
+                device.id;
58
+
59
+        listItem.dataset.deviceId = device.id;
60
+        listItem.innerHTML = html;
61
+        deviceList.appendChild(listItem);
62
+    },
63
+    connect: function(e) {
64
+        app.deviceId = e.target.dataset.deviceId;
65
+
66
+        var onConnect = function() {
67
+            app.enableButtonFeedback(app.subscribeForIncomingData, app.onError);
68
+            app.showDetailPage();
69
+        };
70
+
71
+        ble.connect(app.deviceId, onConnect, app.onError);
72
+    },
73
+    onData: function(buffer) { // data received from MetaWear
74
+
75
+        var data = new Uint8Array(buffer);
76
+        var message = "";
77
+
78
+        if (data[0] === 1 && data[1] === 1) { // module = 1, opscode = 1
79
+            if (data[2] === 1) { // button state
80
+                message = "Button pressed";
81
+            } else {
82
+                message = "Button released";
83
+            }
84
+        }
85
+
86
+        resultDiv.innerHTML = resultDiv.innerHTML + message + "<br/>";
87
+        resultDiv.scrollTop = resultDiv.scrollHeight;
88
+    },
89
+    writeData: function(buffer, success, failure) { // to to be sent to MetaWear
90
+
91
+        if (!success) {
92
+            success = function() {
93
+                console.log("success");
94
+                resultDiv.innerHTML = resultDiv.innerHTML + "Sent: " + JSON.stringify(new Uint8Array(buffer)) + "<br/>";
95
+                resultDiv.scrollTop = resultDiv.scrollHeight;
96
+            };
97
+        }
98
+
99
+        if (!failure) {
100
+            failure = app.onError;
101
+        }
102
+
103
+        ble.writeCommand(app.deviceId, metawear.serviceUUID, metawear.txCharacteristic, buffer, success, failure);
104
+    },
105
+    subscribeForIncomingData: function() {
106
+        ble.startNotification(app.deviceId, metawear.serviceUUID, metawear.rxCharacteristic, app.onData, app.onError);
107
+    },
108
+    enableButtonFeedback: function(success, failure) {
109
+        var data = new Uint8Array(6);
110
+        data[0] = 0x01; // mechanical switch
111
+        data[1] = 0x01; // switch state ops code
112
+        data[2] = 0x01; // enable
113
+
114
+        app.writeData(data.buffer, success, failure);
115
+    },
116
+    onMotorButton: function(event) {
117
+        var pulseWidth = pulseWidthInput.value;
118
+        var data = new Uint8Array(6);
119
+        data[0] = 0x07; // module
120
+        data[1] = 0x01; // pulse ops code
121
+        data[2] = 0x80; // Motor
122
+        data[3] = pulseWidth & 0xFF; // Pulse Width
123
+        data[4] = pulseWidth >> 8; // Pulse Width
124
+        data[5] = 0x00; // Some magic bullshit
125
+
126
+        app.writeData(data.buffer);
127
+    },
128
+    onBuzzerButton: function(event) {
129
+        var pulseWidth = pulseWidthInput.value;
130
+        var data = new Uint8Array(6);
131
+        data[0] = 0x07; // module
132
+        data[1] = 0x01; // pulse ops code
133
+        data[2] = 0xF8; // Buzzer
134
+        data[3] = pulseWidth & 0xFF; // Pulse Width
135
+        data[4] = pulseWidth >> 8; // Pulse Width
136
+        data[5] = 0x01; // Some magic?
137
+
138
+        app.writeData(data.buffer);
139
+    },
140
+    disconnect: function(event) {
141
+        ble.disconnect(app.deviceId, app.showMainPage, app.onError);
142
+        app.deviceId = "";
143
+    },
144
+    showMainPage: function() {
145
+        mainPage.hidden = false;
146
+        detailPage.hidden = true;
147
+    },
148
+    showDetailPage: function() {
149
+        mainPage.hidden = true;
150
+        detailPage.hidden = false;
151
+        resultDiv.innerHTML = "<i>Press the button on the MetaWear</i><br/>";
152
+    },
153
+    onError: function(reason) {
154
+        alert("ERROR: " + reason); // real apps should use notification.alert
155
+    }
156
+};

+ 18 - 0
plugins/cordova-plugin-ble-central/examples/redbearlab/README.md

@@ -0,0 +1,18 @@
1
+## RedBearLab UART
2
+
3
+UART example using an Arduino and [RedBear Lab's Bluetooth Shield](http://redbearlab.com/bleshield/) or any of the RedBear Lab boards: [Blend](http://redbearlab.com/blend/), [Blend Micro](http://redbearlab.com/blendmicro/), [nRF51822](http://redbearlab.com/redbearlab-nrf51822/), or [BLE Nano](http://redbearlab.com/blenano/).
4
+
5
+Use the [SimpleChat sketch](https://codebender.cc/sketch:37518) on your Arduino hardware.
6
+
7
+Hardware
8
+
9
+ * [Arduino Uno](http://www.makershed.com/products/arduino-uno-revision-3)
10
+ * [BLE Shield](http://www.makershed.com/products/bluetooth-low-energy-ble-shield-for-arduino-2-0)
11
+
12
+Install
13
+
14
+    $ cordova platform add android ios
15
+    $ cordova plugin add cordova-plugin-ble-central
16
+    $ cordova run
17
+
18
+Once the app is running. Open the serial console to your Arduino. Data sent from the phone will show up in the serial console. Data sent from the Arduino will show up in on the phone.

+ 12 - 0
plugins/cordova-plugin-ble-central/examples/redbearlab/config.xml

@@ -0,0 +1,12 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<widget id="com.megster.ble.redbear.chat" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+    <name>RedBear Chat</name>
4
+    <description>
5
+        A sample app from a phone to RedBear Labs BLE hardware.
6
+    </description>
7
+    <author href="http://github.com/don">
8
+        Don Coleman
9
+    </author>
10
+    <content src="index.html" />
11
+    <access origin="*" />
12
+</widget>

+ 83 - 0
plugins/cordova-plugin-ble-central/examples/redbearlab/hooks/README.md

@@ -0,0 +1,83 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Cordova Hooks
22
+
23
+This directory may contain scripts used to customize cordova commands. This
24
+directory used to exist at `.cordova/hooks`, but has now been moved to the
25
+project root. Any scripts you add to these directories will be executed before
26
+and after the commands corresponding to the directory name. Useful for
27
+integrating your own build systems or integrating with version control systems.
28
+
29
+__Remember__: Make your scripts executable.
30
+
31
+## Hook Directories
32
+The following subdirectories will be used for hooks:
33
+
34
+    after_build/
35
+    after_compile/
36
+    after_docs/
37
+    after_emulate/
38
+    after_platform_add/
39
+    after_platform_rm/
40
+    after_platform_ls/
41
+    after_plugin_add/
42
+    after_plugin_ls/
43
+    after_plugin_rm/
44
+    after_plugin_search/
45
+    after_prepare/
46
+    after_run/
47
+    after_serve/
48
+    before_build/
49
+    before_compile/
50
+    before_docs/
51
+    before_emulate/
52
+    before_platform_add/
53
+    before_platform_rm/
54
+    before_platform_ls/
55
+    before_plugin_add/
56
+    before_plugin_ls/
57
+    before_plugin_rm/
58
+    before_plugin_search/
59
+    before_prepare/
60
+    before_run/
61
+    before_serve/
62
+    pre_package/ <-- Windows 8 and Windows Phone only.
63
+
64
+## Script Interface
65
+
66
+All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
67
+
68
+* CORDOVA_VERSION - The version of the Cordova-CLI.
69
+* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
70
+* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
71
+* CORDOVA_HOOK - Path to the hook that is being executed.
72
+* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
73
+
74
+If a script returns a non-zero exit code, then the parent cordova command will be aborted.
75
+
76
+
77
+## Writing hooks
78
+
79
+We highly recommend writting your hooks using Node.js so that they are
80
+cross-platform. Some good examples are shown here:
81
+
82
+[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
83
+

+ 77 - 0
plugins/cordova-plugin-ble-central/examples/redbearlab/www/css/index.css

@@ -0,0 +1,77 @@
1
+body {
2
+    font-family: "Helvetica Neue";
3
+    font-weight: lighter;
4
+    color: #2a2a2a;
5
+    background-color: #f0f0ff;
6
+    
7
+    -webkit-appearance: none;  
8
+    -webkit-touch-callout: none;
9
+    -webkit-tap-highlight-color: rgba(0,0,0,0); 
10
+    
11
+    -webkit-touch-callout: none; -webkit-user-select: none;
12
+}
13
+
14
+h1 {
15
+    text-align: center;
16
+}
17
+
18
+button {
19
+    margin: 15px;
20
+    -webkit-appearance:none;    
21
+    font-size: 1.2em;
22
+}
23
+
24
+/* TODO */
25
+input[type=range] {
26
+    width: 100%;
27
+}
28
+
29
+#mainPage {
30
+    text-align:center;
31
+}
32
+
33
+#detailPage {
34
+    text-align:center;
35
+    font-size: 2em;
36
+}
37
+
38
+#detailPage div {
39
+    margin: 20px;
40
+}
41
+
42
+#detailPage button {
43
+    margin-top: 40px;
44
+    font-size: 0.6em; /* undo 2em from parent */
45
+}
46
+
47
+ul {
48
+    list-style: none;
49
+    border-bottom: 1px solid #d3d3d3;    
50
+    text-align: left;
51
+}
52
+
53
+li {
54
+    margin-left: -40px;
55
+    padding: 5px;        
56
+    padding-top: 10px;
57
+    min-height: 50px;
58
+    border-top: 1px solid #d3d3d3;    
59
+    font-size: 0.9em;
60
+}
61
+
62
+button {
63
+    -webkit-appearance: none;
64
+    font-size: 1.5em;
65
+    border-radius: 0;
66
+}
67
+
68
+#resultDiv {
69
+    font: 16px "Source Sans", helvetica, arial, sans-serif;
70
+    font-weight: 200;
71
+    display: block;
72
+    -webkit-border-radius: 6px;
73
+    width: 100%;
74
+    height: 140px;
75
+    text-align: left;    
76
+    overflow: auto;
77
+}

+ 34 - 0
plugins/cordova-plugin-ble-central/examples/redbearlab/www/index.html

@@ -0,0 +1,34 @@
1
+<!DOCTYPE html>
2
+<html>
3
+    <head>
4
+        <meta charset="utf-8" />
5
+        <meta name="format-detection" content="telephone=no" />
6
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1,  target-densitydpi=device-dpi" />
7
+
8
+        <link rel="stylesheet" type="text/css" href="css/index.css" />
9
+        <title>RedBearLab Chat</title>
10
+    </head>
11
+    <body>
12
+        <div class="app">
13
+            <h1>RedBearLab</h1>
14
+            <div id="mainPage">
15
+                <ul id="deviceList">
16
+                </ul>
17
+                <button id="refreshButton">Refresh</button>
18
+            </div>
19
+            <div id="detailPage">
20
+                <div id="resultDiv"></div>
21
+                <div>
22
+                    <input type="text" id="messageInput" value="Hello"/>
23
+                    <button id="sendButton">Send</button>
24
+                </div>
25
+                <button id="disconnectButton">Disconnect</button>
26
+            </div>
27
+        </div>
28
+        <script type="text/javascript" src="cordova.js"></script>
29
+        <script type="text/javascript" src="js/index.js"></script>
30
+        <script type="text/javascript">
31
+            app.initialize();
32
+        </script>
33
+    </body>
34
+</html>

+ 124 - 0
plugins/cordova-plugin-ble-central/examples/redbearlab/www/js/index.js

@@ -0,0 +1,124 @@
1
+// (c) 2014 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+/* global mainPage, deviceList, refreshButton */
16
+/* global detailPage, resultDiv, messageInput, sendButton, disconnectButton */
17
+/* global ble, cordova  */
18
+/* jshint browser: true , devel: true*/
19
+'use strict';
20
+
21
+// ASCII only
22
+function bytesToString(buffer) {
23
+    return String.fromCharCode.apply(null, new Uint8Array(buffer));
24
+}
25
+
26
+// ASCII only
27
+function stringToBytes(string) {
28
+    var array = new Uint8Array(string.length);
29
+    for (var i = 0, l = string.length; i < l; i++) {
30
+        array[i] = string.charCodeAt(i);
31
+    }
32
+    return array.buffer;
33
+}
34
+
35
+// this is RedBear Lab's UART service
36
+var redbear = {
37
+    serviceUUID: "713D0000-503E-4C75-BA94-3148F18D941E",
38
+    txCharacteristic: "713D0003-503E-4C75-BA94-3148F18D941E", // transmit is from the phone's perspective
39
+    rxCharacteristic: "713D0002-503E-4C75-BA94-3148F18D941E"  // receive is from the phone's perspective
40
+};
41
+
42
+var app = {
43
+    initialize: function() {
44
+        this.bindEvents();
45
+        detailPage.hidden = true;
46
+    },
47
+    bindEvents: function() {
48
+        document.addEventListener('deviceready', this.onDeviceReady, false);
49
+        refreshButton.addEventListener('touchstart', this.refreshDeviceList, false);
50
+        sendButton.addEventListener('click', this.sendData, false);
51
+        disconnectButton.addEventListener('touchstart', this.disconnect, false);
52
+        deviceList.addEventListener('touchstart', this.connect, false); // assume not scrolling
53
+    },
54
+    onDeviceReady: function() {
55
+        app.refreshDeviceList();
56
+    },
57
+    refreshDeviceList: function() {
58
+        deviceList.innerHTML = ''; // empties the list
59
+        if (cordova.platformId === 'android') { // Android filtering is broken
60
+            ble.scan([], 5, app.onDiscoverDevice, app.onError);
61
+        } else {
62
+            ble.scan([redbear.serviceUUID], 5, app.onDiscoverDevice, app.onError);
63
+        }
64
+    },
65
+    onDiscoverDevice: function(device) {
66
+        var listItem = document.createElement('li'),
67
+            html = '<b>' + device.name + '</b><br/>' +
68
+                'RSSI: ' + device.rssi + '&nbsp;|&nbsp;' +
69
+                device.id;
70
+
71
+        listItem.dataset.deviceId = device.id;
72
+        listItem.innerHTML = html;
73
+        deviceList.appendChild(listItem);
74
+    },
75
+    connect: function(e) {
76
+        var deviceId = e.target.dataset.deviceId,
77
+            onConnect = function() {
78
+                // subscribe for incoming data
79
+                ble.startNotification(deviceId, redbear.serviceUUID, redbear.rxCharacteristic, app.onData, app.onError);
80
+                sendButton.dataset.deviceId = deviceId;
81
+                disconnectButton.dataset.deviceId = deviceId;
82
+                app.showDetailPage();
83
+            };
84
+
85
+        ble.connect(deviceId, onConnect, app.onError);
86
+    },
87
+    onData: function(data) { // data received from Arduino
88
+        console.log(data);
89
+        resultDiv.innerHTML = resultDiv.innerHTML + "Received: " + bytesToString(data) + "<br/>";
90
+        resultDiv.scrollTop = resultDiv.scrollHeight;
91
+    },
92
+    sendData: function(event) { // send data to Arduino
93
+
94
+        var success = function() {
95
+            console.log("success");
96
+            resultDiv.innerHTML = resultDiv.innerHTML + "Sent: " + messageInput.value + "<br/>";
97
+            resultDiv.scrollTop = resultDiv.scrollHeight;
98
+        };
99
+
100
+        var failure = function() {
101
+            alert("Failed writing data to the redbear hardware");
102
+        };
103
+
104
+        var data = stringToBytes(messageInput.value);
105
+        var deviceId = event.target.dataset.deviceId;
106
+        ble.writeWithoutResponse(deviceId, redbear.serviceUUID, redbear.txCharacteristic, data, success, failure);
107
+
108
+    },
109
+    disconnect: function(event) {
110
+        var deviceId = event.target.dataset.deviceId;
111
+        ble.disconnect(deviceId, app.showMainPage, app.onError);
112
+    },
113
+    showMainPage: function() {
114
+        mainPage.hidden = false;
115
+        detailPage.hidden = true;
116
+    },
117
+    showDetailPage: function() {
118
+        mainPage.hidden = true;
119
+        detailPage.hidden = false;
120
+    },
121
+    onError: function(reason) {
122
+        alert("ERROR: " + reason); // real apps should use notification.alert
123
+    }
124
+};

+ 16 - 0
plugins/cordova-plugin-ble-central/examples/rfduinoLedButton/README.md

@@ -0,0 +1,16 @@
1
+## RFduino LED Button
2
+
3
+Requires an [RFduino](http://rfudino.com) running the [LED Button sketch](https://github.com/RFduino/RFduino/blob/cfe1a448524f2dafc25f62cccd900484925bba8a/libraries/RFduinoBLE/examples/LedButton/LedButton.ino).
4
+
5
+Hardware
6
+
7
+ * [RFduino](http://www.rfduino.com/product/rfd22102-rfduino-dip/)
8
+ * [Button Shield](http://www.rfduino.com/product/rfd22122-rgb-button-shield-for-rfduino/)
9
+ * [USB Shield](http://www.rfduino.com/product/rfd22124-pcb-usb-shield-for-rfduino/)
10
+
11
+
12
+Install
13
+
14
+    $ cordova platform add android ios
15
+    $ cordova plugin add cordova-plugin-ble-central
16
+    $ cordova run

+ 13 - 0
plugins/cordova-plugin-ble-central/examples/rfduinoLedButton/config.xml

@@ -0,0 +1,13 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<widget id="com.megster.rfduino.ledbutton" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+    <name>RFduinoLedButton</name>
4
+    <description>
5
+        Use Cordova Bluetooth Plugin to control RFduino LED Button Sketch 
6
+    </description>
7
+    <author href="https://github.com/don">
8
+        Don Coleman
9
+    </author>
10
+    <content src="index.html" />
11
+    <access origin="*" />
12
+    <preference name="orientation" value="portrait" />
13
+</widget>

+ 83 - 0
plugins/cordova-plugin-ble-central/examples/rfduinoLedButton/hooks/README.md

@@ -0,0 +1,83 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Cordova Hooks
22
+
23
+This directory may contain scripts used to customize cordova commands. This
24
+directory used to exist at `.cordova/hooks`, but has now been moved to the
25
+project root. Any scripts you add to these directories will be executed before
26
+and after the commands corresponding to the directory name. Useful for
27
+integrating your own build systems or integrating with version control systems.
28
+
29
+__Remember__: Make your scripts executable.
30
+
31
+## Hook Directories
32
+The following subdirectories will be used for hooks:
33
+
34
+    after_build/
35
+    after_compile/
36
+    after_docs/
37
+    after_emulate/
38
+    after_platform_add/
39
+    after_platform_rm/
40
+    after_platform_ls/
41
+    after_plugin_add/
42
+    after_plugin_ls/
43
+    after_plugin_rm/
44
+    after_plugin_search/
45
+    after_prepare/
46
+    after_run/
47
+    after_serve/
48
+    before_build/
49
+    before_compile/
50
+    before_docs/
51
+    before_emulate/
52
+    before_platform_add/
53
+    before_platform_rm/
54
+    before_platform_ls/
55
+    before_plugin_add/
56
+    before_plugin_ls/
57
+    before_plugin_rm/
58
+    before_plugin_search/
59
+    before_prepare/
60
+    before_run/
61
+    before_serve/
62
+    pre_package/ <-- Windows 8 and Windows Phone only.
63
+
64
+## Script Interface
65
+
66
+All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
67
+
68
+* CORDOVA_VERSION - The version of the Cordova-CLI.
69
+* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
70
+* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
71
+* CORDOVA_HOOK - Path to the hook that is being executed.
72
+* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
73
+
74
+If a script returns a non-zero exit code, then the parent cordova command will be aborted.
75
+
76
+
77
+## Writing hooks
78
+
79
+We highly recommend writting your hooks using Node.js so that they are
80
+cross-platform. Some good examples are shown here:
81
+
82
+[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
83
+

+ 61 - 0
plugins/cordova-plugin-ble-central/examples/rfduinoLedButton/www/css/index.css

@@ -0,0 +1,61 @@
1
+body {
2
+    font-family: "Helvetica Neue";
3
+    font-weight: lighter;
4
+    color: #2a2a2a;
5
+    background-color: #f0f0ff;
6
+    
7
+    -webkit-appearance: none;  
8
+    -webkit-touch-callout: none;
9
+    -webkit-tap-highlight-color: rgba(0,0,0,0); 
10
+    
11
+    -webkit-touch-callout: none; -webkit-user-select: none;
12
+}
13
+
14
+h1 {
15
+    text-align: center;
16
+}
17
+
18
+button {
19
+    margin: 15px;
20
+    -webkit-appearance:none;    
21
+    font-size: 1.2em;
22
+}
23
+
24
+#mainPage {
25
+    text-align:center;
26
+}
27
+
28
+#detailPage {
29
+    text-align:center;
30
+    font-size: 2em;
31
+}
32
+
33
+#detailPage div {
34
+    margin: 20px;
35
+}
36
+
37
+#detailPage button {
38
+    margin-top: 40px;
39
+    font-size: 0.6em; /* undo 2em from parent */
40
+}
41
+
42
+ul {
43
+    list-style: none;
44
+    border-bottom: 1px solid #d3d3d3;    
45
+    text-align: left;
46
+}
47
+
48
+li {
49
+    margin-left: -40px;
50
+    padding: 5px;        
51
+    padding-top: 10px;
52
+    min-height: 50px;
53
+    border-top: 1px solid #d3d3d3;    
54
+    font-size: 0.9em;
55
+}
56
+
57
+button {
58
+    -webkit-appearance: none;
59
+    font-size: 1.5em;
60
+    border-radius: 0;
61
+}

+ 31 - 0
plugins/cordova-plugin-ble-central/examples/rfduinoLedButton/www/index.html

@@ -0,0 +1,31 @@
1
+<!DOCTYPE html>
2
+<html>
3
+    <head>
4
+        <meta charset="utf-8" />
5
+        <meta name="format-detection" content="telephone=no" />
6
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1,  target-densitydpi=device-dpi" />
7
+        
8
+        <link rel="stylesheet" type="text/css" href="css/index.css" />
9
+        <title>RFduino LED Button</title>
10
+    </head>
11
+    <body>
12
+        <div class="app">
13
+            <h1>RFduino LED Button</h1>
14
+            <div id="mainPage">
15
+                <ul id="deviceList">                    
16
+                </ul>
17
+                <button id="refreshButton">Refresh</button>
18
+            </div>
19
+            <div id="detailPage">
20
+                <div id="buttonState">Button Released</div>
21
+                <div id="ledButton">Hold for LED</div>
22
+                <button id="disconnectButton">Disconnect</button>
23
+            </div>            
24
+        </div>
25
+        <script type="text/javascript" src="cordova.js"></script>
26
+        <script type="text/javascript" src="js/index.js"></script>
27
+        <script type="text/javascript">
28
+            app.initialize();
29
+        </script>
30
+    </body>
31
+</html>

+ 186 - 0
plugins/cordova-plugin-ble-central/examples/rfduinoLedButton/www/js/index.js

@@ -0,0 +1,186 @@
1
+// (c) 2014-2015 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+/* global mainPage, deviceList, refreshButton */
16
+/* global detailPage, buttonState, ledButton, disconnectButton */
17
+/* global ble, cordova  */
18
+/* jshint browser: true , devel: true*/
19
+'use strict';
20
+
21
+var arrayBufferToInt = function (ab) {
22
+    var a = new Uint8Array(ab);
23
+    return a[0];
24
+};
25
+
26
+var rfduino = {
27
+    serviceUUID: "2220",
28
+    receiveCharacteristic: "2221",
29
+    sendCharacteristic: "2222",
30
+    disconnectCharacteristic: "2223"
31
+};
32
+
33
+// returns advertising data as hashmap of byte arrays keyed by type
34
+// advertising data is length, type, data
35
+// https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile
36
+function parseAdvertisingData(bytes) {
37
+    var length, type, data, i = 0, advertisementData = {};
38
+
39
+    while (length !== 0) {
40
+
41
+        length = bytes[i] & 0xFF;
42
+        i++;
43
+
44
+        type = bytes[i] & 0xFF;
45
+        i++;
46
+
47
+        data = bytes.slice(i, i + length - 1); // length includes type byte, but not length byte
48
+        i += length - 2;  // move to end of data
49
+        i++;
50
+
51
+        advertisementData[type] = data;
52
+    }
53
+
54
+    return advertisementData;
55
+}
56
+
57
+// RFduino advertises the sketch its running in the Manufacturer field 0xFF
58
+// RFduino provides a UART-like service so all sketchs look the same (0x2220)
59
+// This RFduino "service" name is used to different functions on the boards
60
+var getRFduinoService = function(scanRecord) {
61
+    var mfgData;
62
+
63
+    if (cordova.platformId === 'ios') {
64
+        mfgData = arrayBufferToIntArray(scanRecord.kCBAdvDataManufacturerData);
65
+    } else { // android
66
+        var ad = parseAdvertisingData(arrayBufferToIntArray(scanRecord));
67
+        mfgData = ad[0xFF];
68
+    }
69
+
70
+    if (mfgData) {
71
+      // ignore 1st 2 bytes of mfg data
72
+      return bytesToString(mfgData.slice(2));
73
+    } else {
74
+      return "";
75
+    }
76
+};
77
+
78
+// Convert ArrayBuffer to int[] for easier processing.
79
+// If Uint8Array.slice worked, this would be unnecessary
80
+var arrayBufferToIntArray = function(buffer) {
81
+    var result;
82
+
83
+    if (buffer) {
84
+        var typedArray = new Uint8Array(buffer);
85
+        result = [];
86
+        for (var i = 0; i < typedArray.length; i++) {
87
+            result[i] = typedArray[i];
88
+        }
89
+    }
90
+
91
+    return result;
92
+};
93
+
94
+var bytesToString = function (bytes) {
95
+    var bytesAsString = "";
96
+    for (var i = 0; i < bytes.length; i++) {
97
+        bytesAsString += String.fromCharCode(bytes[i]);
98
+    }
99
+    return bytesAsString;
100
+};
101
+
102
+var app = {
103
+    initialize: function() {
104
+        this.bindEvents();
105
+        detailPage.hidden = true;
106
+    },
107
+    bindEvents: function() {
108
+        document.addEventListener('deviceready', this.onDeviceReady, false);
109
+        refreshButton.addEventListener('touchstart', this.refreshDeviceList, false);
110
+        ledButton.addEventListener('touchstart', this.sendData, false);
111
+        ledButton.addEventListener('touchend', this.sendData, false);
112
+        disconnectButton.addEventListener('touchstart', this.disconnect, false);
113
+        deviceList.addEventListener('touchstart', this.connect, false); // assume not scrolling
114
+    },
115
+    onDeviceReady: function() {
116
+        app.refreshDeviceList();
117
+    },
118
+    refreshDeviceList: function() {
119
+        deviceList.innerHTML = ''; // empties the list
120
+        ble.scan([rfduino.serviceUUID], 5, app.onDiscoverDevice, app.onError);
121
+    },
122
+    onDiscoverDevice: function(device) {
123
+        var listItem = document.createElement('li'),
124
+            html = '<b>' + device.name + '</b><br/>' +
125
+                'RSSI: ' + device.rssi + '&nbsp;|&nbsp;' +
126
+                'Advertising: ' + getRFduinoService(device.advertising) + '<br/>' +
127
+                device.id;
128
+
129
+        listItem.dataset.deviceId = device.id;
130
+        listItem.innerHTML = html;
131
+        deviceList.appendChild(listItem);
132
+    },
133
+    connect: function(e) {
134
+        var deviceId = e.target.dataset.deviceId,
135
+            onConnect = function() {
136
+                // subscribe for incoming data
137
+                ble.startNotification(deviceId, rfduino.serviceUUID, rfduino.receiveCharacteristic, app.onData, app.onError);
138
+                disconnectButton.dataset.deviceId = deviceId;
139
+                ledButton.dataset.deviceId = deviceId;
140
+                app.showDetailPage();
141
+            };
142
+
143
+        ble.connect(deviceId, onConnect, app.onError);
144
+    },
145
+    onData: function(data) { // data received from rfduino
146
+        console.log(data);
147
+        var buttonValue = arrayBufferToInt(data);
148
+        if (buttonValue === 1) {
149
+            buttonState.innerHTML = "Button Pressed";
150
+        } else {
151
+            buttonState.innerHTML = "Button Released";
152
+        }
153
+    },
154
+    sendData: function(event) { // send data to rfduino
155
+
156
+        var success = function() {
157
+            console.log("success");
158
+        };
159
+
160
+        var failure = function() {
161
+            alert("Failed writing data to the rfduino");
162
+        };
163
+
164
+        var data = new Uint8Array(1);
165
+        data[0] = event.type === 'touchstart' ? 0x1 : 0x0;
166
+        var deviceId = event.target.dataset.deviceId;
167
+
168
+        ble.writeWithoutResponse(deviceId, rfduino.serviceUUID, rfduino.sendCharacteristic, data.buffer, success, failure);
169
+
170
+    },
171
+    disconnect: function(event) {
172
+        var deviceId = event.target.dataset.deviceId;
173
+        ble.disconnect(deviceId, app.showMainPage, app.onError);
174
+    },
175
+    showMainPage: function() {
176
+        mainPage.hidden = false;
177
+        detailPage.hidden = true;
178
+    },
179
+    showDetailPage: function() {
180
+        mainPage.hidden = true;
181
+        detailPage.hidden = false;
182
+    },
183
+    onError: function(reason) {
184
+        alert("ERROR: " + reason); // real apps should use notification.alert
185
+    }
186
+};

+ 15 - 0
plugins/cordova-plugin-ble-central/examples/robosmart/README.md

@@ -0,0 +1,15 @@
1
+# RoboSmart
2
+
3
+Control a [SmartBotics](http://www.smartbotics.com/) RoboSmart Lightbulb from your phone.
4
+
5
+Hardware
6
+
7
+    * [RoboSmart Lightbulb](http://www.smartbotics.com/#!where-to-buy/c1kf9)
8
+    
9
+Install
10
+
11
+    $ cordova platform add android ios
12
+    $ cordova plugin add cordova-plugin-ble-central
13
+    $ cordova run
14
+    
15
+

+ 13 - 0
plugins/cordova-plugin-ble-central/examples/robosmart/config.xml

@@ -0,0 +1,13 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<widget id="com.megster.robosmart" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+    <name>RoboSmart</name>
4
+    <description>
5
+        Use Cordova Bluetooth Plugin to control Smartbotics RoboSmart lightbulb
6
+    </description>
7
+    <author href="https://github.com/don">
8
+        Don Coleman
9
+    </author>
10
+    <content src="index.html" />
11
+    <access origin="*" />
12
+    <preference name="orientation" value="portrait" />    
13
+</widget>

+ 83 - 0
plugins/cordova-plugin-ble-central/examples/robosmart/hooks/README.md

@@ -0,0 +1,83 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Cordova Hooks
22
+
23
+This directory may contain scripts used to customize cordova commands. This
24
+directory used to exist at `.cordova/hooks`, but has now been moved to the
25
+project root. Any scripts you add to these directories will be executed before
26
+and after the commands corresponding to the directory name. Useful for
27
+integrating your own build systems or integrating with version control systems.
28
+
29
+__Remember__: Make your scripts executable.
30
+
31
+## Hook Directories
32
+The following subdirectories will be used for hooks:
33
+
34
+    after_build/
35
+    after_compile/
36
+    after_docs/
37
+    after_emulate/
38
+    after_platform_add/
39
+    after_platform_rm/
40
+    after_platform_ls/
41
+    after_plugin_add/
42
+    after_plugin_ls/
43
+    after_plugin_rm/
44
+    after_plugin_search/
45
+    after_prepare/
46
+    after_run/
47
+    after_serve/
48
+    before_build/
49
+    before_compile/
50
+    before_docs/
51
+    before_emulate/
52
+    before_platform_add/
53
+    before_platform_rm/
54
+    before_platform_ls/
55
+    before_plugin_add/
56
+    before_plugin_ls/
57
+    before_plugin_rm/
58
+    before_plugin_search/
59
+    before_prepare/
60
+    before_run/
61
+    before_serve/
62
+    pre_package/ <-- Windows 8 and Windows Phone only.
63
+
64
+## Script Interface
65
+
66
+All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
67
+
68
+* CORDOVA_VERSION - The version of the Cordova-CLI.
69
+* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
70
+* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
71
+* CORDOVA_HOOK - Path to the hook that is being executed.
72
+* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
73
+
74
+If a script returns a non-zero exit code, then the parent cordova command will be aborted.
75
+
76
+
77
+## Writing hooks
78
+
79
+We highly recommend writting your hooks using Node.js so that they are
80
+cross-platform. Some good examples are shown here:
81
+
82
+[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
83
+

+ 66 - 0
plugins/cordova-plugin-ble-central/examples/robosmart/www/css/index.css

@@ -0,0 +1,66 @@
1
+body {
2
+    font-family: "Helvetica Neue";
3
+    font-weight: lighter;
4
+    color: #2a2a2a;
5
+    background-color: #f0f0ff;
6
+    
7
+    -webkit-appearance: none;  
8
+    -webkit-touch-callout: none;
9
+    -webkit-tap-highlight-color: rgba(0,0,0,0); 
10
+    
11
+    -webkit-touch-callout: none; -webkit-user-select: none;
12
+}
13
+
14
+h1 {
15
+    text-align: center;
16
+}
17
+
18
+button {
19
+    margin: 15px;
20
+    -webkit-appearance:none;    
21
+    font-size: 1.2em;
22
+}
23
+
24
+/* TODO */
25
+input[type=range] {
26
+    width: 100%;
27
+}
28
+
29
+#mainPage {
30
+    text-align:center;
31
+}
32
+
33
+#detailPage {
34
+    text-align:center;
35
+    font-size: 2em;
36
+}
37
+
38
+#detailPage div {
39
+    margin: 20px;
40
+}
41
+
42
+#detailPage button {
43
+    margin-top: 40px;
44
+    font-size: 0.6em; /* undo 2em from parent */
45
+}
46
+
47
+ul {
48
+    list-style: none;
49
+    border-bottom: 1px solid #d3d3d3;    
50
+    text-align: left;
51
+}
52
+
53
+li {
54
+    margin-left: -40px;
55
+    padding: 5px;        
56
+    padding-top: 10px;
57
+    min-height: 50px;
58
+    border-top: 1px solid #d3d3d3;    
59
+    font-size: 0.9em;
60
+}
61
+
62
+button {
63
+    -webkit-appearance: none;
64
+    font-size: 1.5em;
65
+    border-radius: 0;
66
+}

+ 37 - 0
plugins/cordova-plugin-ble-central/examples/robosmart/www/index.html

@@ -0,0 +1,37 @@
1
+<!DOCTYPE html>
2
+<html>
3
+    <head>
4
+        <meta charset="utf-8" />
5
+        <meta name="format-detection" content="telephone=no" />
6
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1,  target-densitydpi=device-dpi" />
7
+        
8
+        <link rel="stylesheet" type="text/css" href="css/index.css" />
9
+        <title>RoboSmart</title>
10
+    </head>
11
+    <body>
12
+        <div class="app">
13
+            <h1>RoboSmart</h1>
14
+            <div id="mainPage">
15
+                <ul id="deviceList">                    
16
+                </ul>
17
+                <button id="refreshButton">Refresh</button>
18
+            </div>
19
+            <div id="detailPage">
20
+                <!-- Topcoat Switch would look nicer http://topcoat.io/topcoat/topcoat-mobile-light.html#switch -->
21
+                <div>
22
+                    <input type="checkbox" id="lightSwitch" name="lightSwitch" class="switch" />
23
+                    <label for="lightSwitch">Switch</label>
24
+                </div>
25
+                <div class="rangeDiv">
26
+                    <input type="range" min="0" max="255" value="0" name="dimmer" id="dimmer" class="topcoat-range"/>
27
+                </div>
28
+                <button id="disconnectButton">Disconnect</button>
29
+            </div>            
30
+        </div>
31
+        <script type="text/javascript" src="cordova.js"></script>
32
+        <script type="text/javascript" src="js/index.js"></script>
33
+        <script type="text/javascript">
34
+            app.initialize();
35
+        </script>
36
+    </body>
37
+</html>

+ 157 - 0
plugins/cordova-plugin-ble-central/examples/robosmart/www/js/index.js

@@ -0,0 +1,157 @@
1
+// (c) 2014 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+/* global mainPage, deviceList, refreshButton */
16
+/* global detailPage, lightSwitch, dimmer, disconnectButton */
17
+/* global cordova, ble  */
18
+/* jshint browser: true , devel: true*/
19
+'use strict';
20
+
21
+var arrayBufferToInt = function (ab) {
22
+    var a = new Uint8Array(ab);
23
+    return a[0];
24
+};
25
+
26
+var robosmart = {
27
+    service: 'FF10',
28
+    lightSwitch: 'FF11',
29
+    brightness: 'FF12',
30
+    powerConsumed: 'FF16',
31
+    name: 'FF17',
32
+    description: 'FF18',
33
+    room: 'FF19',
34
+    locationName: 'FF1b',
35
+    locationGps: 'FF1d',
36
+    disconnect: 'FF1a',
37
+};
38
+
39
+var app = {
40
+    initialize: function() {
41
+        this.bindEvents();
42
+        detailPage.hidden = true;
43
+    },
44
+    bindEvents: function() {
45
+        document.addEventListener('deviceready', this.onDeviceReady, false);
46
+        refreshButton.addEventListener('touchstart', this.refreshDeviceList, false);
47
+        lightSwitch.addEventListener('change', this.setOnOff, false);
48
+        dimmer.addEventListener('change', this.setBrightness, false);
49
+        disconnectButton.addEventListener('touchstart', this.disconnect, false);
50
+        deviceList.addEventListener('touchstart', this.connect, false); // assume not scrolling
51
+    },
52
+    onDeviceReady: function() {
53
+        app.refreshDeviceList();
54
+    },
55
+    refreshDeviceList: function() {
56
+        deviceList.innerHTML = ''; // empties the list
57
+
58
+        if (cordova.platformId === 'android') {
59
+            // Bug: Android can't find RoboSmart when scanning for service
60
+            ble.scan([], 5, app.onDiscoverDevice, app.onError);
61
+        } else {
62
+            ble.scan([robosmart.service], 5, app.onDiscoverDevice, app.onError);
63
+        }
64
+    },
65
+    onDiscoverDevice: function(device) {
66
+        var listItem = document.createElement('li'),
67
+            html = '<b>' + device.name + '</b><br/>' +
68
+                'RSSI: ' + device.rssi + '&nbsp;|&nbsp;' +
69
+                device.id;
70
+
71
+        listItem.dataset.deviceId = device.id;
72
+        listItem.innerHTML = html;
73
+        deviceList.appendChild(listItem);
74
+    },
75
+    connect: function(e) {
76
+        var deviceId = e.target.dataset.deviceId,
77
+            onConnect = function() {
78
+                // subscribe for power consumption notifications
79
+                disconnectButton.dataset.deviceId = deviceId;
80
+                lightSwitch.dataset.deviceId = deviceId;
81
+                dimmer.dataset.deviceId = deviceId;
82
+                app.showDetailPage();
83
+                app.syncUI(deviceId);
84
+            };
85
+
86
+        ble.connect(deviceId, onConnect, app.onError);
87
+    },
88
+    setOnOff: function(event) { // send data to robosmart
89
+
90
+        var success = function() {
91
+            console.log("success");
92
+        };
93
+
94
+        var failure = function() {
95
+            alert("Failed writing data to the robosmart");
96
+        };
97
+
98
+        var data = new Uint8Array(1);
99
+        data[0] = event.target.checked ? 0x1 : 0x0;
100
+        var deviceId = event.target.dataset.deviceId;
101
+
102
+        ble.write(deviceId, robosmart.service, robosmart.lightSwitch, data.buffer, success, failure);
103
+    },
104
+    setBrightness: function(event) { // send data to robosmart
105
+
106
+        var success = function() {
107
+            // if the light was off and brightness is adjusted, the light will turn on
108
+            lightSwitch.checked = true;
109
+            console.log("success");
110
+        };
111
+
112
+        var failure = function() {
113
+            alert("Failed writing data to the robosmart");
114
+        };
115
+
116
+        var deviceId = event.target.dataset.deviceId;
117
+
118
+        // pass the brightness from the slider to the light
119
+        var brightness = new Uint8Array(1);
120
+        brightness[0] = event.target.value;
121
+
122
+        ble.write(deviceId, robosmart.service, robosmart.brightness, brightness.buffer, success, failure);
123
+    },
124
+    syncUI: function(deviceId) {
125
+
126
+        var switchCallback = function(data) {
127
+            lightSwitch.checked = arrayBufferToInt(data) === 0x1;
128
+        };
129
+
130
+        var dimmerCallback = function(data) {
131
+            dimmer.value = arrayBufferToInt(data);
132
+        };
133
+
134
+        var failure = function(reason) {
135
+            console.log("Error syncing UI with the current state " + reason);
136
+        };
137
+
138
+        ble.read(deviceId, robosmart.service, robosmart.lightSwitch, switchCallback, failure);
139
+        ble.read(deviceId, robosmart.service, robosmart.brightness, dimmerCallback, failure);
140
+
141
+    },
142
+    disconnect: function(event) {
143
+        var deviceId = event.target.dataset.deviceId;
144
+        ble.disconnect(deviceId, app.showMainPage, app.onError);
145
+    },
146
+    showMainPage: function() {
147
+        mainPage.hidden = false;
148
+        detailPage.hidden = true;
149
+    },
150
+    showDetailPage: function() {
151
+        mainPage.hidden = true;
152
+        detailPage.hidden = false;
153
+    },
154
+    onError: function(reason) {
155
+        alert("ERROR: " + reason); // real apps should use notification.alert
156
+    }
157
+};

+ 17 - 0
plugins/cordova-plugin-ble-central/examples/sensortag/README.md

@@ -0,0 +1,17 @@
1
+## SensorTag
2
+
3
+Interact with the [TI SensorTag](http://www.ti.com/tool/cc2541dk-sensor)
4
+
5
+ * Read Button Presses
6
+ * Read Accelerometer Data
7
+
8
+Hardware
9
+
10
+ * [TI SensorTag](http://www.ti.com/tool/cc2541dk-sensor)
11
+
12
+Install
13
+
14
+    $ cordova platform add android ios
15
+    $ cordova plugin add cordova-plugin-ble-central
16
+    $ cordova run
17
+    

+ 13 - 0
plugins/cordova-plugin-ble-central/examples/sensortag/config.xml

@@ -0,0 +1,13 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<widget id="com.megster.sensortag" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+    <name>SensorTag</name>
4
+    <description>
5
+        Use Cordova Bluetooth Plugin to read values from TI SensorTag
6
+    </description>
7
+    <author href="https://github.com/don">
8
+           Don Coleman
9
+    </author>
10
+    <content src="index.html" />
11
+    <access origin="*" />
12
+    <preference name="orientation" value="portrait" />    
13
+</widget>

+ 83 - 0
plugins/cordova-plugin-ble-central/examples/sensortag/hooks/README.md

@@ -0,0 +1,83 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Cordova Hooks
22
+
23
+This directory may contain scripts used to customize cordova commands. This
24
+directory used to exist at `.cordova/hooks`, but has now been moved to the
25
+project root. Any scripts you add to these directories will be executed before
26
+and after the commands corresponding to the directory name. Useful for
27
+integrating your own build systems or integrating with version control systems.
28
+
29
+__Remember__: Make your scripts executable.
30
+
31
+## Hook Directories
32
+The following subdirectories will be used for hooks:
33
+
34
+    after_build/
35
+    after_compile/
36
+    after_docs/
37
+    after_emulate/
38
+    after_platform_add/
39
+    after_platform_rm/
40
+    after_platform_ls/
41
+    after_plugin_add/
42
+    after_plugin_ls/
43
+    after_plugin_rm/
44
+    after_plugin_search/
45
+    after_prepare/
46
+    after_run/
47
+    after_serve/
48
+    before_build/
49
+    before_compile/
50
+    before_docs/
51
+    before_emulate/
52
+    before_platform_add/
53
+    before_platform_rm/
54
+    before_platform_ls/
55
+    before_plugin_add/
56
+    before_plugin_ls/
57
+    before_plugin_rm/
58
+    before_plugin_search/
59
+    before_prepare/
60
+    before_run/
61
+    before_serve/
62
+    pre_package/ <-- Windows 8 and Windows Phone only.
63
+
64
+## Script Interface
65
+
66
+All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
67
+
68
+* CORDOVA_VERSION - The version of the Cordova-CLI.
69
+* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
70
+* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
71
+* CORDOVA_HOOK - Path to the hook that is being executed.
72
+* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
73
+
74
+If a script returns a non-zero exit code, then the parent cordova command will be aborted.
75
+
76
+
77
+## Writing hooks
78
+
79
+We highly recommend writting your hooks using Node.js so that they are
80
+cross-platform. Some good examples are shown here:
81
+
82
+[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
83
+

+ 61 - 0
plugins/cordova-plugin-ble-central/examples/sensortag/www/css/index.css

@@ -0,0 +1,61 @@
1
+body {
2
+    font-family: "Helvetica Neue";
3
+    font-weight: lighter;
4
+    color: #2a2a2a;
5
+    background-color: #f0f0ff;
6
+    
7
+    -webkit-appearance: none;  
8
+    -webkit-touch-callout: none;
9
+    -webkit-tap-highlight-color: rgba(0,0,0,0); 
10
+    
11
+    -webkit-touch-callout: none; -webkit-user-select: none;
12
+}
13
+
14
+h1 {
15
+    text-align: center;
16
+}
17
+
18
+button {
19
+    margin: 15px;
20
+    -webkit-appearance:none;    
21
+    font-size: 1.2em;
22
+}
23
+
24
+#mainPage {
25
+    text-align:center;
26
+}
27
+
28
+#detailPage {
29
+    text-align:center;
30
+    font-size: 2em;
31
+}
32
+
33
+#detailPage div {
34
+    margin: 20px;
35
+}
36
+
37
+#detailPage button {
38
+    margin-top: 40px;
39
+    font-size: 0.6em; /* undo 2em from parent */
40
+}
41
+
42
+ul {
43
+    list-style: none;
44
+    border-bottom: 1px solid #d3d3d3;    
45
+    text-align: left;
46
+}
47
+
48
+li {
49
+    margin-left: -40px;
50
+    padding: 5px;        
51
+    padding-top: 10px;
52
+    min-height: 50px;
53
+    border-top: 1px solid #d3d3d3;    
54
+    font-size: 0.9em;
55
+}
56
+
57
+button {
58
+    -webkit-appearance: none;
59
+    font-size: 1.5em;
60
+    border-radius: 0;
61
+}

+ 31 - 0
plugins/cordova-plugin-ble-central/examples/sensortag/www/index.html

@@ -0,0 +1,31 @@
1
+<!DOCTYPE html>
2
+<html>
3
+    <head>
4
+        <meta charset="utf-8" />
5
+        <meta name="format-detection" content="telephone=no" />
6
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1" />
7
+
8
+        <link rel="stylesheet" type="text/css" href="css/index.css" />
9
+        <title>SensorTag</title>
10
+    </head>
11
+    <body>
12
+        <div class="app">
13
+            <h1>SensorTag</h1>
14
+            <div id="mainPage">
15
+                <ul id="deviceList">
16
+                </ul>
17
+                <button id="refreshButton">Refresh</button>
18
+            </div>
19
+            <div id="detailPage">
20
+                <div id="accelerometerData">waiting...</div>
21
+                <div id="buttonState"></div>
22
+                <button id="disconnectButton">Disconnect</button>
23
+            </div>
24
+        </div>
25
+        <script type="text/javascript" src="cordova.js"></script>
26
+        <script type="text/javascript" src="js/index.js"></script>
27
+        <script type="text/javascript">
28
+            app.initialize();
29
+        </script>
30
+    </body>
31
+</html>

+ 141 - 0
plugins/cordova-plugin-ble-central/examples/sensortag/www/js/index.js

@@ -0,0 +1,141 @@
1
+// (c) 2014 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+/* global mainPage, deviceList, refreshButton */
16
+/* global detailPage, accelerometerData, buttonState, disconnectButton */
17
+/* global ble  */
18
+/* jshint browser: true , devel: true*/
19
+'use strict';
20
+
21
+// http://processors.wiki.ti.com/index.php/SensorTag_User_Guide#Simple_Key_Service
22
+var button = {
23
+    service: "FFE0",
24
+    data: "FFE1", // Bit 2: side key, Bit 1- right key, Bit 0 –left key
25
+};
26
+
27
+
28
+// http://processors.wiki.ti.com/index.php/SensorTag_User_Guide#Accelerometer_2
29
+var accelerometer = {
30
+    service: "F000AA10-0451-4000-B000-000000000000",
31
+    data: "F000AA11-0451-4000-B000-000000000000", // read/notify 3 bytes X : Y : Z
32
+    configuration: "F000AA12-0451-4000-B000-000000000000", // read/write 1 byte
33
+    period: "F000AA13-0451-4000-B000-000000000000" // read/write 1 byte Period = [Input*10]ms
34
+};
35
+
36
+var app = {
37
+    initialize: function() {
38
+        this.bindEvents();
39
+        detailPage.hidden = true;
40
+    },
41
+    bindEvents: function() {
42
+        document.addEventListener('deviceready', this.onDeviceReady, false);
43
+        refreshButton.addEventListener('touchstart', this.refreshDeviceList, false);
44
+        disconnectButton.addEventListener('touchstart', this.disconnect, false);
45
+        deviceList.addEventListener('touchstart', this.connect, false); // assume not scrolling
46
+    },
47
+    onDeviceReady: function() {
48
+        app.refreshDeviceList();
49
+    },
50
+    refreshDeviceList: function() {
51
+        deviceList.innerHTML = ''; // empties the list
52
+        // scan for all devices
53
+        ble.scan([], 5, app.onDiscoverDevice, app.onError);
54
+    },
55
+    onDiscoverDevice: function(device) {
56
+
57
+        // we're not limiting scanning by services, so filter
58
+        // the list for devices with "Sensor" in the name
59
+        if (device.name.match(/sensor/i)) {
60
+
61
+            var listItem = document.createElement('li'),
62
+                html = '<b>' + device.name + '</b><br/>' +
63
+                    'RSSI: ' + device.rssi + '&nbsp;|&nbsp;' +
64
+                    device.id;
65
+
66
+            listItem.dataset.deviceId = device.id;  // TODO
67
+            listItem.innerHTML = html;
68
+            deviceList.appendChild(listItem);
69
+
70
+        }
71
+    },
72
+    connect: function(e) {
73
+        var deviceId = e.target.dataset.deviceId,
74
+            onConnect = function() {
75
+
76
+                ble.startNotification(deviceId, button.service, button.data, app.onButtonData, app.onError);
77
+                // subscribing for incoming data
78
+                ble.startNotification(deviceId, accelerometer.service, accelerometer.data, app.onAccelerometerData, app.onError);
79
+                // turn accelerometer on
80
+                var configData = new Uint8Array(1);
81
+                configData[0] = 0xFF;
82
+                ble.write(deviceId, accelerometer.service, accelerometer.configuration, configData.buffer, 
83
+                    function() { console.log("Started accelerometer."); },app.onError);
84
+                disconnectButton.dataset.deviceId = deviceId;
85
+                app.showDetailPage();
86
+            };
87
+
88
+        ble.connect(deviceId, onConnect, app.onError);
89
+    },
90
+    onButtonData: function(data) {
91
+        console.log(data);
92
+        var message;
93
+        var a = new Uint8Array(data);
94
+        switch(a[0]) { // should really check the bits in case bit 3 is set too
95
+        case 0:
96
+            message = "No buttons are pressed";
97
+            break;
98
+        case 1:
99
+            message = "Right button is pressed";
100
+            break;
101
+        case 2:
102
+            message = "Left button is pressed";
103
+            break;
104
+        case 3:
105
+            message = "Both buttons are pressed";
106
+            break;
107
+        default:
108
+            message = "Error";
109
+        }
110
+
111
+        buttonState.innerHTML = message;
112
+    },
113
+    onAccelerometerData: function(data) {
114
+        console.log(data);
115
+        var message;
116
+        var a = new Uint8Array(data);
117
+
118
+        // TODO get a template to line this up
119
+        // TODO round or format numbers for better display
120
+        message = "X: " + a[0]/64 + "<br/>" +
121
+                  "Y: " + a[1]/64 + "<br/>" +
122
+                  "Z: " + a[2]/64 * -1;
123
+
124
+        accelerometerData.innerHTML = message;
125
+    },
126
+    disconnect: function(event) {
127
+        var deviceId = event.target.dataset.deviceId;
128
+        ble.disconnect(deviceId, app.showMainPage, app.onError);
129
+    },
130
+    showMainPage: function() {
131
+        mainPage.hidden = false;
132
+        detailPage.hidden = true;
133
+    },
134
+    showDetailPage: function() {
135
+        mainPage.hidden = true;
136
+        detailPage.hidden = false;
137
+    },
138
+    onError: function(reason) {
139
+        alert("ERROR: " + reason); // real apps should use notification.alert
140
+    }
141
+};

+ 18 - 0
plugins/cordova-plugin-ble-central/examples/sensortag_cc2650/README.md

@@ -0,0 +1,18 @@
1
+## SensorTag
2
+
3
+Interact with the [TI CC2650 SensorTag](http://www.ti.com/tool/CC2650STK)
4
+
5
+ * Read Button Presses
6
+ * Read Accelerometer/Magnetometer/Gyroscope Data
7
+ * Read Barometer/Temperature Data
8
+
9
+Hardware
10
+
11
+ * [TI CC2650 SensorTag](http://www.ti.com/tool/CC2650STK)
12
+
13
+Install
14
+
15
+    $ cordova platform add android ios
16
+    $ cordova plugin add cordova-plugin-ble-central
17
+    $ cordova run
18
+    

+ 13 - 0
plugins/cordova-plugin-ble-central/examples/sensortag_cc2650/config.xml

@@ -0,0 +1,13 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<widget id="com.yertnamreg.sensortag.cc2650" version="0.0.2" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
3
+    <name>CC2650 SensorTag</name>
4
+    <description>
5
+        Use Cordova Bluetooth Plugin to read values from TI CC2650 SensorTag
6
+    </description>
7
+    <author href="https://github.com/treygerman">
8
+           Trey German
9
+    </author>
10
+    <content src="index.html" />
11
+    <access origin="*" />
12
+    <preference name="orientation" value="portrait" />
13
+</widget>

+ 83 - 0
plugins/cordova-plugin-ble-central/examples/sensortag_cc2650/hooks/README.md

@@ -0,0 +1,83 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Cordova Hooks
22
+
23
+This directory may contain scripts used to customize cordova commands. This
24
+directory used to exist at `.cordova/hooks`, but has now been moved to the
25
+project root. Any scripts you add to these directories will be executed before
26
+and after the commands corresponding to the directory name. Useful for
27
+integrating your own build systems or integrating with version control systems.
28
+
29
+__Remember__: Make your scripts executable.
30
+
31
+## Hook Directories
32
+The following subdirectories will be used for hooks:
33
+
34
+    after_build/
35
+    after_compile/
36
+    after_docs/
37
+    after_emulate/
38
+    after_platform_add/
39
+    after_platform_rm/
40
+    after_platform_ls/
41
+    after_plugin_add/
42
+    after_plugin_ls/
43
+    after_plugin_rm/
44
+    after_plugin_search/
45
+    after_prepare/
46
+    after_run/
47
+    after_serve/
48
+    before_build/
49
+    before_compile/
50
+    before_docs/
51
+    before_emulate/
52
+    before_platform_add/
53
+    before_platform_rm/
54
+    before_platform_ls/
55
+    before_plugin_add/
56
+    before_plugin_ls/
57
+    before_plugin_rm/
58
+    before_plugin_search/
59
+    before_prepare/
60
+    before_run/
61
+    before_serve/
62
+    pre_package/ <-- Windows 8 and Windows Phone only.
63
+
64
+## Script Interface
65
+
66
+All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
67
+
68
+* CORDOVA_VERSION - The version of the Cordova-CLI.
69
+* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
70
+* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
71
+* CORDOVA_HOOK - Path to the hook that is being executed.
72
+* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
73
+
74
+If a script returns a non-zero exit code, then the parent cordova command will be aborted.
75
+
76
+
77
+## Writing hooks
78
+
79
+We highly recommend writting your hooks using Node.js so that they are
80
+cross-platform. Some good examples are shown here:
81
+
82
+[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
83
+

+ 61 - 0
plugins/cordova-plugin-ble-central/examples/sensortag_cc2650/www/css/index.css

@@ -0,0 +1,61 @@
1
+body {
2
+    font-family: "Helvetica Neue";
3
+    font-weight: lighter;
4
+    color: #2a2a2a;
5
+    background-color: #f0f0ff;
6
+    
7
+    -webkit-appearance: none;  
8
+    -webkit-touch-callout: none;
9
+    -webkit-tap-highlight-color: rgba(0,0,0,0); 
10
+    
11
+    -webkit-touch-callout: none; -webkit-user-select: none;
12
+}
13
+
14
+h1 {
15
+    text-align: center;
16
+}
17
+
18
+button {
19
+    margin: 15px;
20
+    -webkit-appearance:none;    
21
+    font-size: 1.2em;
22
+}
23
+
24
+#mainPage {
25
+    text-align:center;
26
+}
27
+
28
+#detailPage {
29
+    text-align:center;
30
+    font-size: 1em;
31
+}
32
+
33
+#detailPage div {
34
+    margin: 20px;
35
+}
36
+
37
+#detailPage button {
38
+    margin-top: 40px;
39
+    font-size: 0.6em; /* undo 2em from parent */
40
+}
41
+
42
+ul {
43
+    list-style: none;
44
+    border-bottom: 1px solid #d3d3d3;    
45
+    text-align: left;
46
+}
47
+
48
+li {
49
+    margin-left: -40px;
50
+    padding: 5px;        
51
+    padding-top: 10px;
52
+    min-height: 50px;
53
+    border-top: 1px solid #d3d3d3;    
54
+    font-size: 0.9em;
55
+}
56
+
57
+button {
58
+    -webkit-appearance: none;
59
+    font-size: 1.5em;
60
+    border-radius: 0;
61
+}

+ 32 - 0
plugins/cordova-plugin-ble-central/examples/sensortag_cc2650/www/index.html

@@ -0,0 +1,32 @@
1
+<!DOCTYPE html>
2
+<html>
3
+    <head>
4
+        <meta charset="utf-8" />
5
+        <meta name="format-detection" content="telephone=no" />
6
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1" />
7
+
8
+        <link rel="stylesheet" type="text/css" href="css/index.css" />
9
+        <title>SensorTag</title>
10
+    </head>
11
+    <body>
12
+        <div class="app">
13
+            <h1>SensorTag</h1>
14
+            <div id="mainPage">
15
+                <ul id="deviceList">
16
+                </ul>
17
+                <button id="refreshButton">Refresh</button>
18
+            </div>
19
+            <div id="detailPage">
20
+                <div id="accelerometerData">waiting...</div>
21
+                <div id="barometerData">waiting...</div>
22
+                <div id="buttonState"></div>
23
+                <button id="disconnectButton">Disconnect</button>
24
+            </div>
25
+        </div>
26
+        <script type="text/javascript" src="cordova.js"></script>
27
+        <script type="text/javascript" src="js/index.js"></script>
28
+        <script type="text/javascript">
29
+            app.initialize();
30
+        </script>
31
+    </body>
32
+</html>

+ 216 - 0
plugins/cordova-plugin-ble-central/examples/sensortag_cc2650/www/js/index.js

@@ -0,0 +1,216 @@
1
+// (c) 2014 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+/* global mainPage, deviceList, refreshButton */
16
+/* global detailPage, accelerometerData, buttonState, disconnectButton */
17
+/* global ble  */
18
+/* jshint browser: true , devel: true*/
19
+'use strict';
20
+
21
+// http://processors.wiki.ti.com/index.php/SensorTag_User_Guide#Simple_Key_Service
22
+var button = {
23
+    service: "FFE0",
24
+    data: "FFE1", // Bit 2: side key, Bit 1- right key, Bit 0 –left key
25
+};
26
+
27
+//http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User%27s_Guide
28
+var accelerometer = {
29
+    service: "F000AA80-0451-4000-B000-000000000000",
30
+    data: "F000AA81-0451-4000-B000-000000000000", // read/notify 3 bytes X : Y : Z
31
+    notification:"F0002902-0451-4000-B000-000000000000",
32
+    configuration: "F000AA82-0451-4000-B000-000000000000", // read/write 1 byte
33
+    period: "F000AA83-0451-4000-B000-000000000000" // read/write 1 byte Period = [Input*10]ms
34
+};
35
+
36
+var barometer = {
37
+    service: "F000AA40-0451-4000-B000-000000000000",
38
+    data: "F000AA41-0451-4000-B000-000000000000",
39
+    notification: "F0002902-0451-4000-B000-000000000000",
40
+    configuration: "F000AA42-0451-4000-B000-000000000000",
41
+    period: "F000AA43-0451-4000-B000-000000000000"
42
+
43
+};
44
+
45
+// button bitmask
46
+var LEFT_BUTTON = 1;  // 0001
47
+var RIGHT_BUTTON = 2; // 0010
48
+var REED_SWITCH = 4;  // 0100
49
+
50
+var app = {
51
+    initialize: function() {
52
+        this.bindEvents();
53
+        detailPage.hidden = true;
54
+    },
55
+    bindEvents: function() {
56
+        document.addEventListener('deviceready', this.onDeviceReady, false);
57
+        refreshButton.addEventListener('touchstart', this.refreshDeviceList, false);
58
+        disconnectButton.addEventListener('touchstart', this.disconnect, false);
59
+        deviceList.addEventListener('touchstart', this.connect, false); // assume not scrolling
60
+    },
61
+    onDeviceReady: function() {
62
+        app.refreshDeviceList();
63
+    },
64
+    refreshDeviceList: function() {
65
+        deviceList.innerHTML = ''; // empties the list
66
+        // scan for CC2560 SensorTags
67
+        ble.scan(['AA80'], 5, app.onDiscoverDevice, app.onError);
68
+    },
69
+    onDiscoverDevice: function(device) {
70
+        var listItem = document.createElement('li'),
71
+            html = '<b>' + device.name + '</b><br/>' +
72
+                'RSSI: ' + device.rssi + '&nbsp;|&nbsp;' +
73
+                device.id;
74
+
75
+        listItem.dataset.deviceId = device.id;  // TODO
76
+        listItem.innerHTML = html;
77
+        deviceList.appendChild(listItem);
78
+    },
79
+    connect: function(e) {
80
+        var deviceId = e.target.dataset.deviceId,
81
+            onConnect = function() {
82
+
83
+                //Subscribe to button service
84
+                ble.startNotification(deviceId, button.service, button.data, app.onButtonData, app.onError);
85
+                //Subscribe to accelerometer service
86
+                ble.startNotification(deviceId, accelerometer.service, accelerometer.data, app.onAccelerometerData, app.onError);
87
+                //Subscribe to barometer service
88
+                ble.startNotification(deviceId, barometer.service, barometer.data, app.onBarometerData, app.onError);
89
+
90
+
91
+                // turn accelerometer on
92
+                var configData = new Uint16Array(1);
93
+                //Turn on gyro, accel, and mag, 2G range, Disable wake on motion
94
+                configData[0] = 0x007F;
95
+                ble.write(deviceId, accelerometer.service, accelerometer.configuration, configData.buffer,
96
+                    function() { console.log("Started accelerometer."); },app.onError);
97
+
98
+                var periodData = new Uint8Array(1);
99
+                periodData[0] = 0x0A;
100
+                ble.write(deviceId, accelerometer.service, accelerometer.period, periodData.buffer,
101
+                    function() { console.log("Configured accelerometer period."); },app.onError);
102
+
103
+                //Turn on barometer
104
+                var barometerConfig = new Uint8Array(1);
105
+                barometerConfig[0] = 0x01;
106
+                ble.write(deviceId, barometer.service, barometer.configuration, barometerConfig.buffer,
107
+                    function() { console.log("Started barometer."); },app.onError);
108
+
109
+                //Associate the deviceID with the disconnect button
110
+                disconnectButton.dataset.deviceId = deviceId;
111
+                app.showDetailPage();
112
+            };
113
+
114
+        ble.connect(deviceId, onConnect, app.onError);
115
+    },
116
+    onButtonData: function(data) {
117
+        console.log(data);
118
+        var state = new Uint8Array(data);
119
+        var message = '';
120
+
121
+        if (state === 0) {
122
+            message = 'No buttons are pressed.';
123
+        }
124
+
125
+        if (state & LEFT_BUTTON) {
126
+            message += 'Left button is pressed.<br/>';
127
+        }
128
+
129
+        if (state & RIGHT_BUTTON) {
130
+            message += 'Right button is pressed.<br/>';
131
+        }
132
+
133
+        if (state & REED_SWITCH) {
134
+            message += 'Reed switch is activated.<br/>';
135
+        }
136
+
137
+        buttonState.innerHTML = message;
138
+    },
139
+
140
+    sensorMpu9250GyroConvert: function(data){
141
+        return data / (65536/500);
142
+    },
143
+
144
+    sensorMpu9250AccConvert: function(data){
145
+        // Change  /2 to match accel range...i.e. 16 g would be /16
146
+        return data / (32768 / 2);
147
+    },
148
+
149
+    onAccelerometerData: function(data) {
150
+        console.log(data);
151
+        var message;
152
+        var a = new Int16Array(data);
153
+
154
+        //0 gyro x
155
+        //1 gyro y
156
+        //2 gyro z
157
+        //3 accel x
158
+        //4 accel y
159
+        //5 accel z
160
+        //6 mag x
161
+        //7 mag y
162
+        //8 mag z
163
+
164
+        // TODO get a template to line this up
165
+        // TODO round or format numbers for better display
166
+        message = "Gyro <br/>"+
167
+                  "X: " + app.sensorMpu9250GyroConvert(a[0]) + "<br/>" +
168
+                  "Y: " + app.sensorMpu9250GyroConvert(a[1]) + "<br/>" +
169
+                  "Z: " + app.sensorMpu9250GyroConvert(a[2]) + "<br/>" +
170
+                  "Accel <br/>"+
171
+                  "X: " + app.sensorMpu9250AccConvert(a[3]) + "<br/>" +
172
+                  "Y: " + app.sensorMpu9250AccConvert(a[4]) + "<br/>" +
173
+                  "Z: " + app.sensorMpu9250AccConvert(a[5]) + "<br/>" +
174
+                  "Mag <br/>"+
175
+                  "X: " + a[6] + "<br/>" +
176
+                  "Y: " + a[7] + "<br/>" +
177
+                  "Z: " + a[8] + "<br/>" ;
178
+
179
+        accelerometerData.innerHTML = message;
180
+    },
181
+    sensorBarometerConvert: function(data){
182
+        return (data / 100);
183
+
184
+    },
185
+    onBarometerData: function(data) {
186
+         console.log(data);
187
+         var message;
188
+         var a = new Uint8Array(data);
189
+
190
+         //0-2 Temp
191
+         //3-5 Pressure
192
+         message =  "Temperature <br/>" +
193
+                    app.sensorBarometerConvert( a[0] | (a[1] << 8) | (a[2] << 16)) + "°C <br/>" +
194
+                    "Pressure <br/>" +
195
+                    app.sensorBarometerConvert( a[3] | (a[4] << 8) | (a[5] << 16)) + "hPa <br/>" ;
196
+
197
+
198
+        barometerData.innerHTML = message;
199
+
200
+    },
201
+    disconnect: function(event) {
202
+        var deviceId = event.target.dataset.deviceId;
203
+        ble.disconnect(deviceId, app.showMainPage, app.onError);
204
+    },
205
+    showMainPage: function() {
206
+        mainPage.hidden = false;
207
+        detailPage.hidden = true;
208
+    },
209
+    showDetailPage: function() {
210
+        mainPage.hidden = true;
211
+        detailPage.hidden = false;
212
+    },
213
+    onError: function(reason) {
214
+        alert("ERROR: " + reason); // real apps should use notification.alert
215
+    }
216
+};

+ 64 - 0
plugins/cordova-plugin-ble-central/package.json

@@ -0,0 +1,64 @@
1
+{
2
+  "_from": "cordova-plugin-ble-central",
3
+  "_id": "cordova-plugin-ble-central@1.2.2",
4
+  "_inBundle": false,
5
+  "_integrity": "sha512-81+BD9UQSp8Fuati9bPvKG/6lSGHHtw3JeLxQl8Q85ZS1n3GkSfA3DBMqKRjmOG6OVe4csR+XcO7cd75HMl6Qg==",
6
+  "_location": "/cordova-plugin-ble-central",
7
+  "_phantomChildren": {},
8
+  "_requested": {
9
+    "type": "tag",
10
+    "registry": true,
11
+    "raw": "cordova-plugin-ble-central",
12
+    "name": "cordova-plugin-ble-central",
13
+    "escapedName": "cordova-plugin-ble-central",
14
+    "rawSpec": "",
15
+    "saveSpec": null,
16
+    "fetchSpec": "latest"
17
+  },
18
+  "_requiredBy": [
19
+    "#USER",
20
+    "/"
21
+  ],
22
+  "_resolved": "https://registry.npmjs.org/cordova-plugin-ble-central/-/cordova-plugin-ble-central-1.2.2.tgz",
23
+  "_shasum": "22f4ed72c2fb0b7ef0e7eb983a419e7aaf07eb21",
24
+  "_spec": "cordova-plugin-ble-central",
25
+  "_where": "/Users/young/Develop/wifi-ble-client",
26
+  "author": {
27
+    "name": "Don Coleman",
28
+    "email": "don.coleman@gmail.com"
29
+  },
30
+  "bugs": {
31
+    "url": "https://github.com/don/cordova-plugin-ble-central/issues"
32
+  },
33
+  "bundleDependencies": false,
34
+  "cordova": {
35
+    "id": "cordova-plugin-ble-central",
36
+    "platforms": [
37
+      "ios",
38
+      "android",
39
+      "wp8"
40
+    ]
41
+  },
42
+  "deprecated": false,
43
+  "description": "Bluetooth Low Energy (BLE) Central Plugin",
44
+  "homepage": "https://github.com/don/cordova-plugin-ble-central#readme",
45
+  "keywords": [
46
+    "cordova",
47
+    "bluetooth",
48
+    "ble",
49
+    "bluetoothle",
50
+    "bluetooth le",
51
+    "low energy",
52
+    "smart",
53
+    "ecosystem:cordova",
54
+    "cordova-ios",
55
+    "cordova-android"
56
+  ],
57
+  "license": "Apache-2.0",
58
+  "name": "cordova-plugin-ble-central",
59
+  "repository": {
60
+    "type": "git",
61
+    "url": "git+https://github.com/don/cordova-plugin-ble-central.git"
62
+  },
63
+  "version": "1.2.2"
64
+}

+ 94 - 0
plugins/cordova-plugin-ble-central/plugin.xml

@@ -0,0 +1,94 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<plugin
3
+    xmlns="http://www.phonegap.com/ns/plugins/1.0"
4
+    xmlns:android="http://schemas.android.com/apk/res/android"
5
+    id="cordova-plugin-ble-central"
6
+    version="1.2.2">
7
+
8
+    <name>BLE</name>
9
+    <description>Bluetooth Low Energy (BLE) Central Plugin</description>
10
+    <license>Apache 2.0</license>
11
+    <keywords>bluetooth, BLE, bluetooth low energy, bluetooth smart</keywords>
12
+
13
+    <repo>https://github.com/don/cordova-plugin-ble-central.git</repo>
14
+    <issue>https://github.com/don/cordova-plugin-ble-central/issues</issue>
15
+
16
+    <dependency id="cordova-plugin-compat" version="^1.2.0" />
17
+
18
+    <js-module src="www/ble.js" name="ble">
19
+        <clobbers target="ble" />
20
+    </js-module>
21
+
22
+    <platform name="ios">
23
+
24
+        <config-file target="config.xml" parent="/widget">
25
+            <feature name="BLE">
26
+                <param name="ios-package" value="BLECentralPlugin" onload="true"/>
27
+            </feature>
28
+        </config-file>
29
+
30
+        <header-file src="src/ios/BLECentralPlugin.h" target-dir="BLECentralPlugin" />
31
+        <source-file src="src/ios/BLECentralPlugin.m" target-dir="BLECentralPlugin" />
32
+
33
+        <header-file src="src/ios/CBPeripheral+Extensions.h" target-dir="BLECentralPlugin" />
34
+        <source-file src="src/ios/CBPeripheral+Extensions.m" target-dir="BLECentralPlugin" />
35
+
36
+        <header-file src="src/ios/BLECommandContext.h" target-dir="BLECentralPlugin" />
37
+        <source-file src="src/ios/BLECommandContext.m" target-dir="BLECentralPlugin" />
38
+
39
+        <!-- frameworks -->
40
+        <framework src="CoreBluetooth.framework" />
41
+
42
+        <preference name="BLUETOOTH_USAGE_DESCRIPTION" default=" " />
43
+        <config-file target="*-Info.plist" parent="NSBluetoothPeripheralUsageDescription">
44
+            <string>$BLUETOOTH_USAGE_DESCRIPTION</string>
45
+        </config-file>
46
+    </platform>
47
+
48
+    <platform name="android">
49
+        <config-file target="res/xml/config.xml" parent="/widget">
50
+            <feature name="BLE">
51
+                <param name="android-package" value="com.megster.cordova.ble.central.BLECentralPlugin"/>
52
+            </feature>
53
+        </config-file>
54
+
55
+        <config-file target="AndroidManifest.xml" parent="/manifest">
56
+            <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
57
+            <uses-permission android:name="android.permission.BLUETOOTH"/>
58
+            <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
59
+        </config-file>
60
+
61
+        <source-file src="src/android/BLECentralPlugin.java"
62
+            target-dir="src/com/megster/cordova/ble/central"/>
63
+        <source-file src="src/android/BLECommand.java"
64
+            target-dir="src/com/megster/cordova/ble/central"/>
65
+        <source-file src="src/android/Peripheral.java"
66
+            target-dir="src/com/megster/cordova/ble/central"/>
67
+        <source-file src="src/android/Helper.java"
68
+            target-dir="src/com/megster/cordova/ble/central"/>
69
+        <source-file src="src/android/UUIDHelper.java"
70
+            target-dir="src/com/megster/cordova/ble/central"/>
71
+
72
+    </platform>
73
+
74
+    <platform name="browser">
75
+        <js-module src="src/browser/BLECentralPlugin.js" name="BLECentralPlugin">
76
+            <merges target="ble" />
77
+        </js-module>
78
+    </platform>
79
+
80
+    <platform name="wp8">
81
+        <config-file target="config.xml" parent="/*">
82
+            <feature name="BLE">
83
+                <param name="wp-package" value="BLECentralPlugin"/>
84
+            </feature>
85
+        </config-file>
86
+
87
+         <config-file target="Properties/WMAppManifest.xml" parent="/Deployment/App/Capabilities">
88
+             <Capability Name="ID_CAP_PROXIMITY" />
89
+         </config-file>
90
+
91
+         <source-file src="src/wp/BLECentralPlugin.cs" />
92
+
93
+    </platform>
94
+</plugin>

+ 719 - 0
plugins/cordova-plugin-ble-central/src/android/BLECentralPlugin.java

@@ -0,0 +1,719 @@
1
+// (c) 2014-2016 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package com.megster.cordova.ble.central;
16
+
17
+import android.Manifest;
18
+import android.app.Activity;
19
+import android.bluetooth.BluetoothAdapter;
20
+import android.bluetooth.BluetoothDevice;
21
+import android.bluetooth.BluetoothGattCharacteristic;
22
+import android.bluetooth.BluetoothManager;
23
+import android.content.BroadcastReceiver;
24
+import android.content.Context;
25
+import android.content.Intent;
26
+import android.content.pm.PackageManager;
27
+import android.content.IntentFilter;
28
+import android.os.Handler;
29
+import android.os.Build;
30
+
31
+import android.provider.Settings;
32
+import org.apache.cordova.CallbackContext;
33
+import org.apache.cordova.CordovaArgs;
34
+import org.apache.cordova.CordovaPlugin;
35
+import org.apache.cordova.LOG;
36
+import org.apache.cordova.PermissionHelper;
37
+import org.apache.cordova.PluginResult;
38
+import org.json.JSONArray;
39
+import org.json.JSONObject;
40
+import org.json.JSONException;
41
+
42
+import java.util.*;
43
+
44
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_DUAL;
45
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
46
+
47
+public class BLECentralPlugin extends CordovaPlugin implements BluetoothAdapter.LeScanCallback {
48
+    // actions
49
+    private static final String SCAN = "scan";
50
+    private static final String START_SCAN = "startScan";
51
+    private static final String STOP_SCAN = "stopScan";
52
+    private static final String START_SCAN_WITH_OPTIONS = "startScanWithOptions";
53
+    private static final String BONDED_DEVICES = "bondedDevices";
54
+    private static final String LIST = "list";
55
+
56
+    private static final String CONNECT = "connect";
57
+    private static final String AUTOCONNECT = "autoConnect";
58
+    private static final String DISCONNECT = "disconnect";
59
+
60
+    private static final String REQUEST_MTU = "requestMtu";
61
+    private static final String REFRESH_DEVICE_CACHE = "refreshDeviceCache";
62
+
63
+    private static final String READ = "read";
64
+    private static final String WRITE = "write";
65
+    private static final String WRITE_WITHOUT_RESPONSE = "writeWithoutResponse";
66
+
67
+    private static final String READ_RSSI = "readRSSI";
68
+
69
+    private static final String START_NOTIFICATION = "startNotification"; // register for characteristic notification
70
+    private static final String STOP_NOTIFICATION = "stopNotification"; // remove characteristic notification
71
+
72
+    private static final String IS_ENABLED = "isEnabled";
73
+    private static final String IS_CONNECTED  = "isConnected";
74
+
75
+    private static final String SETTINGS = "showBluetoothSettings";
76
+    private static final String ENABLE = "enable";
77
+
78
+    private static final String START_STATE_NOTIFICATIONS = "startStateNotifications";
79
+    private static final String STOP_STATE_NOTIFICATIONS = "stopStateNotifications";
80
+
81
+    // callbacks
82
+    CallbackContext discoverCallback;
83
+    private CallbackContext enableBluetoothCallback;
84
+
85
+    private static final String TAG = "BLEPlugin";
86
+    private static final int REQUEST_ENABLE_BLUETOOTH = 1;
87
+
88
+    BluetoothAdapter bluetoothAdapter;
89
+
90
+    // key is the MAC Address
91
+    Map<String, Peripheral> peripherals = new LinkedHashMap<String, Peripheral>();
92
+
93
+    // scan options
94
+    boolean reportDuplicates = false;
95
+
96
+    // Android 23 requires new permissions for BluetoothLeScanner.startScan()
97
+    private static final String ACCESS_COARSE_LOCATION = Manifest.permission.ACCESS_COARSE_LOCATION;
98
+    private static final int REQUEST_ACCESS_COARSE_LOCATION = 2;
99
+    private CallbackContext permissionCallback;
100
+    private UUID[] serviceUUIDs;
101
+    private int scanSeconds;
102
+
103
+    // Bluetooth state notification
104
+    CallbackContext stateCallback;
105
+    BroadcastReceiver stateReceiver;
106
+    Map<Integer, String> bluetoothStates = new Hashtable<Integer, String>() {{
107
+        put(BluetoothAdapter.STATE_OFF, "off");
108
+        put(BluetoothAdapter.STATE_TURNING_OFF, "turningOff");
109
+        put(BluetoothAdapter.STATE_ON, "on");
110
+        put(BluetoothAdapter.STATE_TURNING_ON, "turningOn");
111
+    }};
112
+
113
+    public void onDestroy() {
114
+        removeStateListener();
115
+    }
116
+
117
+    public void onReset() {
118
+        removeStateListener();
119
+    }
120
+
121
+    @Override
122
+    public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException {
123
+        LOG.d(TAG, "action = " + action);
124
+
125
+        if (bluetoothAdapter == null) {
126
+            Activity activity = cordova.getActivity();
127
+            boolean hardwareSupportsBLE = activity.getApplicationContext()
128
+                                            .getPackageManager()
129
+                                            .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) &&
130
+                                            Build.VERSION.SDK_INT >= 18;
131
+            if (!hardwareSupportsBLE) {
132
+              LOG.w(TAG, "This hardware does not support Bluetooth Low Energy.");
133
+              callbackContext.error("This hardware does not support Bluetooth Low Energy.");
134
+              return false;
135
+            }
136
+            BluetoothManager bluetoothManager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE);
137
+            bluetoothAdapter = bluetoothManager.getAdapter();
138
+        }
139
+
140
+        boolean validAction = true;
141
+
142
+        if (action.equals(SCAN)) {
143
+
144
+            UUID[] serviceUUIDs = parseServiceUUIDList(args.getJSONArray(0));
145
+            int scanSeconds = args.getInt(1);
146
+            resetScanOptions();
147
+            findLowEnergyDevices(callbackContext, serviceUUIDs, scanSeconds);
148
+
149
+        } else if (action.equals(START_SCAN)) {
150
+
151
+            UUID[] serviceUUIDs = parseServiceUUIDList(args.getJSONArray(0));
152
+            resetScanOptions();
153
+            findLowEnergyDevices(callbackContext, serviceUUIDs, -1);
154
+
155
+        } else if (action.equals(STOP_SCAN)) {
156
+
157
+            bluetoothAdapter.stopLeScan(this);
158
+            callbackContext.success();
159
+
160
+        } else if (action.equals(LIST)) {
161
+
162
+            listKnownDevices(callbackContext);
163
+
164
+        } else if (action.equals(CONNECT)) {
165
+
166
+            String macAddress = args.getString(0);
167
+            connect(callbackContext, macAddress);
168
+
169
+        } else if (action.equals(AUTOCONNECT)) {
170
+
171
+            String macAddress = args.getString(0);
172
+            autoConnect(callbackContext, macAddress);
173
+
174
+        } else if (action.equals(DISCONNECT)) {
175
+
176
+            String macAddress = args.getString(0);
177
+            disconnect(callbackContext, macAddress);
178
+
179
+        } else if (action.equals(REQUEST_MTU)) {
180
+
181
+            String macAddress = args.getString(0);
182
+            int mtuValue = args.getInt(1);
183
+            requestMtu(callbackContext, macAddress, mtuValue);
184
+
185
+        } else if (action.equals(REFRESH_DEVICE_CACHE)) {
186
+
187
+            String macAddress = args.getString(0);
188
+            long timeoutMillis = args.getLong(1);
189
+
190
+            refreshDeviceCache(callbackContext, macAddress, timeoutMillis);
191
+
192
+        } else if (action.equals(READ)) {
193
+
194
+            String macAddress = args.getString(0);
195
+            UUID serviceUUID = uuidFromString(args.getString(1));
196
+            UUID characteristicUUID = uuidFromString(args.getString(2));
197
+            read(callbackContext, macAddress, serviceUUID, characteristicUUID);
198
+
199
+        } else if (action.equals(READ_RSSI)) {
200
+
201
+            String macAddress = args.getString(0);
202
+            readRSSI(callbackContext, macAddress);
203
+
204
+        } else if (action.equals(WRITE)) {
205
+
206
+            String macAddress = args.getString(0);
207
+            UUID serviceUUID = uuidFromString(args.getString(1));
208
+            UUID characteristicUUID = uuidFromString(args.getString(2));
209
+            byte[] data = args.getArrayBuffer(3);
210
+            int type = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
211
+            write(callbackContext, macAddress, serviceUUID, characteristicUUID, data, type);
212
+
213
+        } else if (action.equals(WRITE_WITHOUT_RESPONSE)) {
214
+
215
+            String macAddress = args.getString(0);
216
+            UUID serviceUUID = uuidFromString(args.getString(1));
217
+            UUID characteristicUUID = uuidFromString(args.getString(2));
218
+            byte[] data = args.getArrayBuffer(3);
219
+            int type = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE;
220
+            write(callbackContext, macAddress, serviceUUID, characteristicUUID, data, type);
221
+
222
+        } else if (action.equals(START_NOTIFICATION)) {
223
+
224
+            String macAddress = args.getString(0);
225
+            UUID serviceUUID = uuidFromString(args.getString(1));
226
+            UUID characteristicUUID = uuidFromString(args.getString(2));
227
+            registerNotifyCallback(callbackContext, macAddress, serviceUUID, characteristicUUID);
228
+
229
+        } else if (action.equals(STOP_NOTIFICATION)) {
230
+
231
+            String macAddress = args.getString(0);
232
+            UUID serviceUUID = uuidFromString(args.getString(1));
233
+            UUID characteristicUUID = uuidFromString(args.getString(2));
234
+            removeNotifyCallback(callbackContext, macAddress, serviceUUID, characteristicUUID);
235
+
236
+        } else if (action.equals(IS_ENABLED)) {
237
+
238
+            if (bluetoothAdapter.isEnabled()) {
239
+                callbackContext.success();
240
+            } else {
241
+                callbackContext.error("Bluetooth is disabled.");
242
+            }
243
+
244
+        } else if (action.equals(IS_CONNECTED)) {
245
+
246
+            String macAddress = args.getString(0);
247
+
248
+            if (peripherals.containsKey(macAddress) && peripherals.get(macAddress).isConnected()) {
249
+                callbackContext.success();
250
+            } else {
251
+                callbackContext.error("Not connected.");
252
+            }
253
+
254
+        } else if (action.equals(SETTINGS)) {
255
+
256
+            Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
257
+            cordova.getActivity().startActivity(intent);
258
+            callbackContext.success();
259
+
260
+        } else if (action.equals(ENABLE)) {
261
+
262
+            enableBluetoothCallback = callbackContext;
263
+            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
264
+            cordova.startActivityForResult(this, intent, REQUEST_ENABLE_BLUETOOTH);
265
+
266
+        } else if (action.equals(START_STATE_NOTIFICATIONS)) {
267
+
268
+            if (this.stateCallback != null) {
269
+                callbackContext.error("State callback already registered.");
270
+            } else {
271
+                this.stateCallback = callbackContext;
272
+                addStateListener();
273
+                sendBluetoothStateChange(bluetoothAdapter.getState());
274
+            }
275
+
276
+        } else if (action.equals(STOP_STATE_NOTIFICATIONS)) {
277
+
278
+            if (this.stateCallback != null) {
279
+                // Clear callback in JavaScript without actually calling it
280
+                PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
281
+                result.setKeepCallback(false);
282
+                this.stateCallback.sendPluginResult(result);
283
+                this.stateCallback = null;
284
+            }
285
+            removeStateListener();
286
+            callbackContext.success();
287
+
288
+        } else if (action.equals(START_SCAN_WITH_OPTIONS)) {
289
+            UUID[] serviceUUIDs = parseServiceUUIDList(args.getJSONArray(0));
290
+            JSONObject options = args.getJSONObject(1);
291
+
292
+            resetScanOptions();
293
+            this.reportDuplicates = options.optBoolean("reportDuplicates", false);
294
+            findLowEnergyDevices(callbackContext, serviceUUIDs, -1);
295
+
296
+        } else if (action.equals(BONDED_DEVICES)) {
297
+
298
+            getBondedDevices(callbackContext);
299
+
300
+        } else {
301
+
302
+            validAction = false;
303
+
304
+        }
305
+
306
+        return validAction;
307
+    }
308
+
309
+    private void getBondedDevices(CallbackContext callbackContext) {
310
+        JSONArray bonded = new JSONArray();
311
+        Set<BluetoothDevice> bondedDevices =  bluetoothAdapter.getBondedDevices();
312
+
313
+        for (BluetoothDevice device : bondedDevices) {
314
+            device.getBondState();
315
+            int type = device.getType();
316
+
317
+            // just low energy devices (filters out classic and unknown devices)
318
+            if (type == DEVICE_TYPE_LE || type == DEVICE_TYPE_DUAL) {
319
+                Peripheral p = new Peripheral(device);
320
+                bonded.put(p.asJSONObject());
321
+            }
322
+        }
323
+
324
+        callbackContext.success(bonded);
325
+    }
326
+
327
+    private UUID[] parseServiceUUIDList(JSONArray jsonArray) throws JSONException {
328
+        List<UUID> serviceUUIDs = new ArrayList<UUID>();
329
+
330
+        for(int i = 0; i < jsonArray.length(); i++){
331
+            String uuidString = jsonArray.getString(i);
332
+            serviceUUIDs.add(uuidFromString(uuidString));
333
+        }
334
+
335
+        return serviceUUIDs.toArray(new UUID[jsonArray.length()]);
336
+    }
337
+
338
+    private void onBluetoothStateChange(Intent intent) {
339
+        final String action = intent.getAction();
340
+
341
+        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
342
+            final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
343
+            sendBluetoothStateChange(state);
344
+        }
345
+    }
346
+
347
+    private void sendBluetoothStateChange(int state) {
348
+        if (this.stateCallback != null) {
349
+            PluginResult result = new PluginResult(PluginResult.Status.OK, this.bluetoothStates.get(state));
350
+            result.setKeepCallback(true);
351
+            this.stateCallback.sendPluginResult(result);
352
+        }
353
+    }
354
+
355
+    private void addStateListener() {
356
+        if (this.stateReceiver == null) {
357
+            this.stateReceiver = new BroadcastReceiver() {
358
+                @Override
359
+                public void onReceive(Context context, Intent intent) {
360
+                    onBluetoothStateChange(intent);
361
+                }
362
+            };
363
+        }
364
+
365
+        try {
366
+            IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
367
+            webView.getContext().registerReceiver(this.stateReceiver, intentFilter);
368
+        } catch (Exception e) {
369
+            LOG.e(TAG, "Error registering state receiver: " + e.getMessage(), e);
370
+        }
371
+    }
372
+
373
+    private void removeStateListener() {
374
+        if (this.stateReceiver != null) {
375
+            try {
376
+                webView.getContext().unregisterReceiver(this.stateReceiver);
377
+            } catch (Exception e) {
378
+                LOG.e(TAG, "Error unregistering state receiver: " + e.getMessage(), e);
379
+            }
380
+        }
381
+        this.stateCallback = null;
382
+        this.stateReceiver = null;
383
+    }
384
+
385
+    private void connect(CallbackContext callbackContext, String macAddress) {
386
+        if (!peripherals.containsKey(macAddress) && BLECentralPlugin.this.bluetoothAdapter.checkBluetoothAddress(macAddress)) {
387
+            BluetoothDevice device = BLECentralPlugin.this.bluetoothAdapter.getRemoteDevice(macAddress);
388
+            Peripheral peripheral = new Peripheral(device);
389
+            peripherals.put(macAddress, peripheral);
390
+        }
391
+
392
+        Peripheral peripheral = peripherals.get(macAddress);
393
+        if (peripheral != null) {
394
+            peripheral.connect(callbackContext, cordova.getActivity(), false);
395
+        } else {
396
+            callbackContext.error("Peripheral " + macAddress + " not found.");
397
+        }
398
+
399
+    }
400
+
401
+    private void autoConnect(CallbackContext callbackContext, String macAddress) {
402
+        Peripheral peripheral = peripherals.get(macAddress);
403
+
404
+        // allow auto-connect to connect to devices without scanning
405
+        if (peripheral == null) {
406
+            if (BluetoothAdapter.checkBluetoothAddress(macAddress)) {
407
+                BluetoothDevice device = bluetoothAdapter.getRemoteDevice(macAddress);
408
+                peripheral = new Peripheral(device);
409
+                peripherals.put(device.getAddress(), peripheral);
410
+            } else {
411
+                callbackContext.error(macAddress + " is not a valid MAC address.");
412
+                return;
413
+            }
414
+        }
415
+
416
+        peripheral.connect(callbackContext, cordova.getActivity(), true);
417
+
418
+    }
419
+
420
+    private void disconnect(CallbackContext callbackContext, String macAddress) {
421
+
422
+        Peripheral peripheral = peripherals.get(macAddress);
423
+        if (peripheral != null) {
424
+            peripheral.disconnect();
425
+            callbackContext.success();
426
+        } else {
427
+            String message = "Peripheral " + macAddress + " not found.";
428
+            LOG.w(TAG, message);
429
+            callbackContext.error(message);
430
+        }
431
+
432
+    }
433
+
434
+    private void requestMtu(CallbackContext callbackContext, String macAddress, int mtuValue) {
435
+
436
+        Peripheral peripheral = peripherals.get(macAddress);
437
+        if (peripheral != null) {
438
+            peripheral.requestMtu(mtuValue);
439
+        }
440
+        callbackContext.success();
441
+    }
442
+
443
+    private void refreshDeviceCache(CallbackContext callbackContext, String macAddress, long timeoutMillis) {
444
+
445
+        Peripheral peripheral = peripherals.get(macAddress);
446
+
447
+        if (peripheral != null) {
448
+            peripheral.refreshDeviceCache(callbackContext, timeoutMillis);
449
+        } else {
450
+            String message = "Peripheral " + macAddress + " not found.";
451
+            LOG.w(TAG, message);
452
+            callbackContext.error(message);
453
+        }
454
+    }
455
+
456
+    private void read(CallbackContext callbackContext, String macAddress, UUID serviceUUID, UUID characteristicUUID) {
457
+
458
+        Peripheral peripheral = peripherals.get(macAddress);
459
+
460
+        if (peripheral == null) {
461
+            callbackContext.error("Peripheral " + macAddress + " not found.");
462
+            return;
463
+        }
464
+
465
+        if (!peripheral.isConnected()) {
466
+            callbackContext.error("Peripheral " + macAddress + " is not connected.");
467
+            return;
468
+        }
469
+
470
+        //peripheral.readCharacteristic(callbackContext, serviceUUID, characteristicUUID);
471
+        peripheral.queueRead(callbackContext, serviceUUID, characteristicUUID);
472
+
473
+    }
474
+
475
+    private void readRSSI(CallbackContext callbackContext, String macAddress) {
476
+
477
+        Peripheral peripheral = peripherals.get(macAddress);
478
+
479
+        if (peripheral == null) {
480
+            callbackContext.error("Peripheral " + macAddress + " not found.");
481
+            return;
482
+        }
483
+
484
+        if (!peripheral.isConnected()) {
485
+            callbackContext.error("Peripheral " + macAddress + " is not connected.");
486
+            return;
487
+        }
488
+        peripheral.queueReadRSSI(callbackContext);
489
+    }
490
+
491
+    private void write(CallbackContext callbackContext, String macAddress, UUID serviceUUID, UUID characteristicUUID,
492
+                       byte[] data, int writeType) {
493
+
494
+        Peripheral peripheral = peripherals.get(macAddress);
495
+
496
+        if (peripheral == null) {
497
+            callbackContext.error("Peripheral " + macAddress + " not found.");
498
+            return;
499
+        }
500
+
501
+        if (!peripheral.isConnected()) {
502
+            callbackContext.error("Peripheral " + macAddress + " is not connected.");
503
+            return;
504
+        }
505
+
506
+        //peripheral.writeCharacteristic(callbackContext, serviceUUID, characteristicUUID, data, writeType);
507
+        peripheral.queueWrite(callbackContext, serviceUUID, characteristicUUID, data, writeType);
508
+
509
+    }
510
+
511
+    private void registerNotifyCallback(CallbackContext callbackContext, String macAddress, UUID serviceUUID, UUID characteristicUUID) {
512
+
513
+        Peripheral peripheral = peripherals.get(macAddress);
514
+        if (peripheral != null) {
515
+
516
+            if (!peripheral.isConnected()) {
517
+                callbackContext.error("Peripheral " + macAddress + " is not connected.");
518
+                return;
519
+            }
520
+
521
+            //peripheral.setOnDataCallback(serviceUUID, characteristicUUID, callbackContext);
522
+            peripheral.queueRegisterNotifyCallback(callbackContext, serviceUUID, characteristicUUID);
523
+
524
+        } else {
525
+
526
+            callbackContext.error("Peripheral " + macAddress + " not found");
527
+
528
+        }
529
+
530
+    }
531
+
532
+    private void removeNotifyCallback(CallbackContext callbackContext, String macAddress, UUID serviceUUID, UUID characteristicUUID) {
533
+
534
+        Peripheral peripheral = peripherals.get(macAddress);
535
+        if (peripheral != null) {
536
+
537
+            if (!peripheral.isConnected()) {
538
+                callbackContext.error("Peripheral " + macAddress + " is not connected.");
539
+                return;
540
+            }
541
+
542
+            peripheral.queueRemoveNotifyCallback(callbackContext, serviceUUID, characteristicUUID);
543
+
544
+        } else {
545
+
546
+            callbackContext.error("Peripheral " + macAddress + " not found");
547
+
548
+        }
549
+
550
+    }
551
+
552
+    private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] serviceUUIDs, int scanSeconds) {
553
+
554
+        if (!locationServicesEnabled()) {
555
+            callbackContext.error("Location Services are disabled");
556
+            return;
557
+        }
558
+
559
+        if(!PermissionHelper.hasPermission(this, ACCESS_COARSE_LOCATION)) {
560
+            // save info so we can call this method again after permissions are granted
561
+            permissionCallback = callbackContext;
562
+            this.serviceUUIDs = serviceUUIDs;
563
+            this.scanSeconds = scanSeconds;
564
+            PermissionHelper.requestPermission(this, REQUEST_ACCESS_COARSE_LOCATION, ACCESS_COARSE_LOCATION);
565
+            return;
566
+        }
567
+
568
+        // return error if already scanning
569
+        if (bluetoothAdapter.isDiscovering()) {
570
+            LOG.w(TAG, "Tried to start scan while already running.");
571
+            callbackContext.error("Tried to start scan while already running.");
572
+            return;
573
+        }
574
+
575
+        // clear non-connected cached peripherals
576
+        for(Iterator<Map.Entry<String, Peripheral>> iterator = peripherals.entrySet().iterator(); iterator.hasNext(); ) {
577
+            Map.Entry<String, Peripheral> entry = iterator.next();
578
+            Peripheral device = entry.getValue();
579
+            boolean connecting = device.isConnecting();
580
+            if (connecting){
581
+                LOG.d(TAG, "Not removing connecting device: " + device.getDevice().getAddress());
582
+            }
583
+            if(!entry.getValue().isConnected() && !connecting) {
584
+                iterator.remove();
585
+            }
586
+        }
587
+
588
+        discoverCallback = callbackContext;
589
+
590
+        if (serviceUUIDs != null && serviceUUIDs.length > 0) {
591
+            bluetoothAdapter.startLeScan(serviceUUIDs, this);
592
+        } else {
593
+            bluetoothAdapter.startLeScan(this);
594
+        }
595
+
596
+        if (scanSeconds > 0) {
597
+            Handler handler = new Handler();
598
+            handler.postDelayed(new Runnable() {
599
+                @Override
600
+                public void run() {
601
+                    LOG.d(TAG, "Stopping Scan");
602
+                    BLECentralPlugin.this.bluetoothAdapter.stopLeScan(BLECentralPlugin.this);
603
+                }
604
+            }, scanSeconds * 1000);
605
+        }
606
+
607
+        PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
608
+        result.setKeepCallback(true);
609
+        callbackContext.sendPluginResult(result);
610
+    }
611
+
612
+    private boolean locationServicesEnabled() {
613
+        int locationMode = 0;
614
+        try {
615
+            locationMode = Settings.Secure.getInt(cordova.getActivity().getContentResolver(), Settings.Secure.LOCATION_MODE);
616
+        } catch (Settings.SettingNotFoundException e) {
617
+            LOG.e(TAG, "Location Mode Setting Not Found", e);
618
+        }
619
+        return (locationMode > 0);
620
+    }
621
+
622
+    private void listKnownDevices(CallbackContext callbackContext) {
623
+
624
+        JSONArray json = new JSONArray();
625
+
626
+        // do we care about consistent order? will peripherals.values() be in order?
627
+        for (Map.Entry<String, Peripheral> entry : peripherals.entrySet()) {
628
+            Peripheral peripheral = entry.getValue();
629
+            if (!peripheral.isUnscanned()) {
630
+                json.put(peripheral.asJSONObject());
631
+            }
632
+        }
633
+
634
+        PluginResult result = new PluginResult(PluginResult.Status.OK, json);
635
+        callbackContext.sendPluginResult(result);
636
+    }
637
+
638
+    @Override
639
+    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
640
+
641
+        String address = device.getAddress();
642
+        boolean alreadyReported = peripherals.containsKey(address) && !peripherals.get(address).isUnscanned();
643
+
644
+        if (!alreadyReported) {
645
+
646
+            Peripheral peripheral = new Peripheral(device, rssi, scanRecord);
647
+            peripherals.put(device.getAddress(), peripheral);
648
+
649
+            if (discoverCallback != null) {
650
+                PluginResult result = new PluginResult(PluginResult.Status.OK, peripheral.asJSONObject());
651
+                result.setKeepCallback(true);
652
+                discoverCallback.sendPluginResult(result);
653
+            }
654
+
655
+        } else {
656
+            Peripheral peripheral = peripherals.get(address);
657
+            peripheral.update(rssi, scanRecord);
658
+            if (reportDuplicates && discoverCallback != null) {
659
+                PluginResult result = new PluginResult(PluginResult.Status.OK, peripheral.asJSONObject());
660
+                result.setKeepCallback(true);
661
+                discoverCallback.sendPluginResult(result);
662
+            }
663
+        }
664
+    }
665
+
666
+    @Override
667
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
668
+
669
+        if (requestCode == REQUEST_ENABLE_BLUETOOTH) {
670
+
671
+            if (resultCode == Activity.RESULT_OK) {
672
+                LOG.d(TAG, "User enabled Bluetooth");
673
+                if (enableBluetoothCallback != null) {
674
+                    enableBluetoothCallback.success();
675
+                }
676
+            } else {
677
+                LOG.d(TAG, "User did *NOT* enable Bluetooth");
678
+                if (enableBluetoothCallback != null) {
679
+                    enableBluetoothCallback.error("User did not enable Bluetooth");
680
+                }
681
+            }
682
+
683
+            enableBluetoothCallback = null;
684
+        }
685
+    }
686
+
687
+    /* @Override */
688
+    public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) {
689
+        for(int result:grantResults) {
690
+            if(result == PackageManager.PERMISSION_DENIED) {
691
+                LOG.d(TAG, "User *rejected* Coarse Location Access");
692
+                this.permissionCallback.error("Location permission not granted.");
693
+                return;
694
+            }
695
+        }
696
+
697
+        switch(requestCode) {
698
+            case REQUEST_ACCESS_COARSE_LOCATION:
699
+                LOG.d(TAG, "User granted Coarse Location Access");
700
+                findLowEnergyDevices(permissionCallback, serviceUUIDs, scanSeconds);
701
+                this.permissionCallback = null;
702
+                this.serviceUUIDs = null;
703
+                this.scanSeconds = -1;
704
+                break;
705
+        }
706
+    }
707
+
708
+    private UUID uuidFromString(String uuid) {
709
+        return UUIDHelper.uuidFromString(uuid);
710
+    }
711
+
712
+    /**
713
+     * Reset the BLE scanning options
714
+     */
715
+    private void resetScanOptions() {
716
+        this.reportDuplicates = false;
717
+    }
718
+
719
+}

+ 61 - 0
plugins/cordova-plugin-ble-central/src/android/BLECommand.java

@@ -0,0 +1,61 @@
1
+package com.megster.cordova.ble.central;
2
+
3
+import org.apache.cordova.CallbackContext;
4
+
5
+import java.util.UUID;
6
+
7
+/**
8
+ * Android BLE stack is async but doesn't queue commands, so it ignore additional commands when processing. WTF?
9
+ * This is an object to encapsulate the command data for queuing
10
+ */
11
+class BLECommand {
12
+    // Types
13
+    public static int READ = 10000;
14
+    public static int REGISTER_NOTIFY = 10001;
15
+    public static int REMOVE_NOTIFY = 10002;
16
+    public static int READ_RSSI = 10003;
17
+    // BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
18
+    // BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
19
+
20
+    private CallbackContext callbackContext;
21
+    private UUID serviceUUID;
22
+    private UUID characteristicUUID;
23
+    private byte[] data;
24
+    private int type;
25
+
26
+
27
+    public BLECommand(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID, int type) {
28
+        this.callbackContext = callbackContext;
29
+        this.serviceUUID = serviceUUID;
30
+        this.characteristicUUID = characteristicUUID;
31
+        this.type = type;
32
+    }
33
+
34
+    public BLECommand(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID, byte[] data, int type) {
35
+        this.callbackContext = callbackContext;
36
+        this.serviceUUID = serviceUUID;
37
+        this.characteristicUUID = characteristicUUID;
38
+        this.data = data;
39
+        this.type = type;
40
+    }
41
+
42
+    public int getType() {
43
+        return type;
44
+    }
45
+
46
+    public CallbackContext getCallbackContext() {
47
+        return callbackContext;
48
+    }
49
+
50
+    public UUID getServiceUUID() {
51
+        return serviceUUID;
52
+    }
53
+
54
+    public UUID getCharacteristicUUID() {
55
+        return characteristicUUID;
56
+    }
57
+
58
+    public byte[] getData() {
59
+        return data;
60
+    }
61
+}

+ 158 - 0
plugins/cordova-plugin-ble-central/src/android/Helper.java

@@ -0,0 +1,158 @@
1
+// (c) 2104 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package com.megster.cordova.ble.central;
16
+
17
+import android.bluetooth.BluetoothGattCharacteristic;
18
+import android.bluetooth.BluetoothGattDescriptor;
19
+import org.json.JSONArray;
20
+
21
+public class Helper {
22
+
23
+    public static JSONArray decodeProperties(BluetoothGattCharacteristic characteristic) {
24
+
25
+        // NOTE: props strings need to be consistent across iOS and Android
26
+        JSONArray props = new JSONArray();
27
+        int properties = characteristic.getProperties();
28
+
29
+        if ((properties & BluetoothGattCharacteristic.PROPERTY_BROADCAST) != 0x0 ) {
30
+            props.put("Broadcast");
31
+        }
32
+
33
+        if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) != 0x0 ) {
34
+            props.put("Read");
35
+        }
36
+
37
+        if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0x0 ) {
38
+            props.put("WriteWithoutResponse");
39
+        }
40
+
41
+        if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0x0 ) {
42
+            props.put("Write");
43
+        }
44
+
45
+        if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0x0 ) {
46
+            props.put("Notify");
47
+        }
48
+
49
+        if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0x0 ) {
50
+            props.put("Indicate");
51
+        }
52
+
53
+        if ((properties & BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) != 0x0 ) {
54
+            // Android calls this "write with signature", using iOS name for now
55
+            props.put("AuthenticateSignedWrites");
56
+        }
57
+
58
+        if ((properties & BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) != 0x0 ) {
59
+            props.put("ExtendedProperties");
60
+        }
61
+
62
+//      iOS only?
63
+//
64
+//            if ((p & CBCharacteristicPropertyNotifyEncryptionRequired) != 0x0) {  // 0x100
65
+//                [props addObject:@"NotifyEncryptionRequired"];
66
+//            }
67
+//
68
+//            if ((p & CBCharacteristicPropertyIndicateEncryptionRequired) != 0x0) { // 0x200
69
+//                [props addObject:@"IndicateEncryptionRequired"];
70
+//            }
71
+
72
+        return props;
73
+    }
74
+
75
+    public static JSONArray decodePermissions(BluetoothGattCharacteristic characteristic) {
76
+
77
+        // NOTE: props strings need to be consistent across iOS and Android
78
+        JSONArray props = new JSONArray();
79
+        int permissions = characteristic.getPermissions();
80
+
81
+        if ((permissions & BluetoothGattCharacteristic.PERMISSION_READ) != 0x0 ) {
82
+            props.put("Read");
83
+        }
84
+
85
+        if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE) != 0x0 ) {
86
+            props.put("Write");
87
+        }
88
+
89
+        if ((permissions & BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED) != 0x0 ) {
90
+            props.put("ReadEncrypted");
91
+        }
92
+
93
+        if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED) != 0x0 ) {
94
+            props.put("WriteEncrypted");
95
+        }
96
+
97
+        if ((permissions & BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM) != 0x0 ) {
98
+            props.put("ReadEncryptedMITM");
99
+        }
100
+
101
+        if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM) != 0x0 ) {
102
+            props.put("WriteEncryptedMITM");
103
+        }
104
+
105
+        if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED) != 0x0 ) {
106
+            props.put("WriteSigned");
107
+        }
108
+
109
+        if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM) != 0x0 ) {
110
+            props.put("WriteSignedMITM");
111
+        }
112
+
113
+        return props;
114
+    }
115
+
116
+    public static JSONArray decodePermissions(BluetoothGattDescriptor descriptor) {
117
+
118
+        // NOTE: props strings need to be consistent across iOS and Android
119
+        JSONArray props = new JSONArray();
120
+        int permissions = descriptor.getPermissions();
121
+
122
+        if ((permissions & BluetoothGattDescriptor.PERMISSION_READ) != 0x0 ) {
123
+            props.put("Read");
124
+        }
125
+
126
+        if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE) != 0x0 ) {
127
+            props.put("Write");
128
+        }
129
+
130
+        if ((permissions & BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED) != 0x0 ) {
131
+            props.put("ReadEncrypted");
132
+        }
133
+
134
+        if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED) != 0x0 ) {
135
+            props.put("WriteEncrypted");
136
+        }
137
+
138
+        if ((permissions & BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED_MITM) != 0x0 ) {
139
+            props.put("ReadEncryptedMITM");
140
+        }
141
+
142
+        if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED_MITM) != 0x0 ) {
143
+            props.put("WriteEncryptedMITM");
144
+        }
145
+
146
+        if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED) != 0x0 ) {
147
+            props.put("WriteSigned");
148
+        }
149
+
150
+        if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED_MITM) != 0x0 ) {
151
+            props.put("WriteSignedMITM");
152
+        }
153
+
154
+        return props;
155
+    }
156
+
157
+}
158
+

+ 880 - 0
plugins/cordova-plugin-ble-central/src/android/Peripheral.java

@@ -0,0 +1,880 @@
1
+// (c) 2104 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package com.megster.cordova.ble.central;
16
+
17
+import android.app.Activity;
18
+
19
+import android.bluetooth.*;
20
+import android.os.Build;
21
+import android.os.Handler;
22
+import android.util.Base64;
23
+import org.apache.cordova.CallbackContext;
24
+import org.apache.cordova.LOG;
25
+import org.apache.cordova.PluginResult;
26
+import org.json.JSONArray;
27
+import org.json.JSONException;
28
+import org.json.JSONObject;
29
+
30
+import java.util.*;
31
+import java.util.concurrent.ConcurrentLinkedQueue;
32
+
33
+import java.lang.reflect.Method;
34
+
35
+/**
36
+ * Peripheral wraps the BluetoothDevice and provides methods to convert to JSON.
37
+ */
38
+public class Peripheral extends BluetoothGattCallback {
39
+
40
+    // 0x2902 org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
41
+    //public final static UUID CLIENT_CHARACTERISTIC_CONFIGURATION_UUID = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB");
42
+    public final static UUID CLIENT_CHARACTERISTIC_CONFIGURATION_UUID = UUIDHelper.uuidFromString("2902");
43
+    private static final String TAG = "Peripheral";
44
+
45
+    private static final int FAKE_PERIPHERAL_RSSI = 0x7FFFFFFF;
46
+
47
+    private BluetoothDevice device;
48
+    private byte[] advertisingData;
49
+    private int advertisingRSSI;
50
+    private boolean autoconnect = false;
51
+    private boolean connected = false;
52
+    private boolean connecting = false;
53
+    private ConcurrentLinkedQueue<BLECommand> commandQueue = new ConcurrentLinkedQueue<BLECommand>();
54
+    private boolean bleProcessing;
55
+
56
+    BluetoothGatt gatt;
57
+
58
+    private CallbackContext connectCallback;
59
+    private CallbackContext refreshCallback;
60
+    private CallbackContext readCallback;
61
+    private CallbackContext writeCallback;
62
+    private Activity currentActivity;
63
+
64
+    private Map<String, CallbackContext> notificationCallbacks = new HashMap<String, CallbackContext>();
65
+
66
+    public Peripheral(BluetoothDevice device) {
67
+
68
+        LOG.d(TAG, "Creating un-scanned peripheral entry for address: " + device.getAddress());
69
+
70
+        this.device = device;
71
+        this.advertisingRSSI = FAKE_PERIPHERAL_RSSI;
72
+        this.advertisingData = null;
73
+
74
+    }
75
+
76
+    public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord) {
77
+
78
+        this.device = device;
79
+        this.advertisingRSSI = advertisingRSSI;
80
+        this.advertisingData = scanRecord;
81
+
82
+    }
83
+
84
+    private void gattConnect() {
85
+
86
+        if (gatt != null) {
87
+            gatt.disconnect();
88
+            gatt.close();
89
+            gatt = null;
90
+        }
91
+        connected = false;
92
+        connecting = true;
93
+        queueCleanup();
94
+        callbackCleanup();
95
+
96
+        BluetoothDevice device = getDevice();
97
+        if (Build.VERSION.SDK_INT < 23) {
98
+            gatt = device.connectGatt(currentActivity, autoconnect, this);
99
+        } else {
100
+            gatt = device.connectGatt(currentActivity, autoconnect, this, BluetoothDevice.TRANSPORT_LE);
101
+        }
102
+
103
+    }
104
+
105
+    public void connect(CallbackContext callbackContext, Activity activity, boolean auto) {
106
+        currentActivity = activity;
107
+        autoconnect = auto;
108
+        connectCallback = callbackContext;
109
+
110
+        gattConnect();
111
+
112
+        PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
113
+        result.setKeepCallback(true);
114
+        callbackContext.sendPluginResult(result);
115
+    }
116
+
117
+    // the app requested the central disconnect from the peripheral
118
+    // disconnect the gatt, do not call connectCallback.error
119
+    public void disconnect() {
120
+        connected = false;
121
+        connecting = false;
122
+
123
+        if (gatt != null) {
124
+            gatt.disconnect();
125
+            gatt.close();
126
+            gatt = null;
127
+        }
128
+        queueCleanup();
129
+        callbackCleanup();
130
+    }
131
+
132
+    // the peripheral disconnected
133
+    // always call connectCallback.error to notify the app
134
+    private void peripheralDisconnected() {
135
+        connected = false;
136
+        connecting = false;
137
+
138
+        // don't remove the gatt for autoconnect
139
+        if (!autoconnect && gatt != null) {
140
+            gatt.disconnect();
141
+            gatt.close();
142
+            gatt = null;
143
+        }
144
+
145
+        sendDisconnectMessage();
146
+
147
+        queueCleanup();
148
+        callbackCleanup();
149
+    }
150
+
151
+    // notify the phone that the peripheral disconnected
152
+    private void sendDisconnectMessage() {
153
+        if (connectCallback != null) {
154
+            JSONObject message = this.asJSONObject("Peripheral Disconnected");
155
+            if (autoconnect) {
156
+                PluginResult result = new PluginResult(PluginResult.Status.ERROR, message);
157
+                result.setKeepCallback(true);
158
+                connectCallback.sendPluginResult(result);
159
+            } else {
160
+                connectCallback.error(message);
161
+                connectCallback = null;
162
+            }
163
+        }
164
+    }
165
+
166
+    @Override
167
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
168
+        LOG.d(TAG, "mtu=" + mtu + ", status=" + status);
169
+        super.onMtuChanged(gatt, mtu, status);
170
+    }
171
+
172
+    public void requestMtu(int mtuValue) {
173
+        if (gatt != null) {
174
+            LOG.d(TAG, "requestMtu mtu=" + mtuValue);
175
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
176
+                gatt.requestMtu(mtuValue);
177
+            }
178
+        }
179
+    }
180
+
181
+    /**
182
+     * Uses reflection to refresh the device cache. This *might* be helpful if a peripheral changes
183
+     * services or characteristics and does not correctly implement Service Changed 0x2a05
184
+     * on Generic Attribute Service 0x1801.
185
+     *
186
+     * Since this uses an undocumented API it's not guaranteed to work.
187
+     *
188
+     */
189
+    public void refreshDeviceCache(CallbackContext callback, final long timeoutMillis) {
190
+        LOG.d(TAG, "refreshDeviceCache");
191
+
192
+        boolean success = false;
193
+        if (gatt != null) {
194
+            try {
195
+                final Method refresh = gatt.getClass().getMethod("refresh");
196
+                if (refresh != null) {
197
+                    success = (Boolean)refresh.invoke(gatt);
198
+                    if (success) {
199
+                        this.refreshCallback = callback;
200
+                        Handler handler = new Handler();
201
+                        handler.postDelayed(new Runnable() {
202
+                            @Override
203
+                            public void run() {
204
+                                LOG.d(TAG, "Waiting " + timeoutMillis + " milliseconds before discovering services");
205
+                                gatt.discoverServices();
206
+                            }
207
+                        }, timeoutMillis);
208
+                    }
209
+                } else {
210
+                    LOG.w(TAG, "Refresh method not found on gatt");
211
+                }
212
+            } catch(Exception e) {
213
+                LOG.e(TAG, "refreshDeviceCache Failed", e);
214
+            }
215
+        }
216
+
217
+        if (!success) {
218
+            callback.error("Service refresh failed");
219
+        }
220
+    }
221
+
222
+    public boolean isUnscanned() {
223
+        return advertisingData == null;
224
+    }
225
+
226
+    public JSONObject asJSONObject()  {
227
+
228
+        JSONObject json = new JSONObject();
229
+
230
+        try {
231
+            json.put("name", device.getName());
232
+            json.put("id", device.getAddress()); // mac address
233
+            if (advertisingData != null) {
234
+                json.put("advertising", byteArrayToJSON(advertisingData));
235
+            }
236
+            // TODO real RSSI if we have it, else
237
+            if (advertisingRSSI != FAKE_PERIPHERAL_RSSI) {
238
+                json.put("rssi", advertisingRSSI);
239
+            }
240
+        } catch (JSONException e) { // this shouldn't happen
241
+            e.printStackTrace();
242
+        }
243
+
244
+        return json;
245
+    }
246
+
247
+    public JSONObject asJSONObject(String errorMessage)  {
248
+
249
+        JSONObject json = new JSONObject();
250
+
251
+        try {
252
+            json.put("name", device.getName());
253
+            json.put("id", device.getAddress()); // mac address
254
+            json.put("errorMessage", errorMessage);
255
+        } catch (JSONException e) { // this shouldn't happen
256
+            e.printStackTrace();
257
+        }
258
+
259
+        return json;
260
+    }
261
+
262
+    public JSONObject asJSONObject(BluetoothGatt gatt) {
263
+
264
+        JSONObject json = asJSONObject();
265
+
266
+        try {
267
+            JSONArray servicesArray = new JSONArray();
268
+            JSONArray characteristicsArray = new JSONArray();
269
+            json.put("services", servicesArray);
270
+            json.put("characteristics", characteristicsArray);
271
+
272
+            if (connected && gatt != null) {
273
+                for (BluetoothGattService service : gatt.getServices()) {
274
+                    servicesArray.put(UUIDHelper.uuidToString(service.getUuid()));
275
+
276
+                    for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
277
+                        JSONObject characteristicsJSON = new JSONObject();
278
+                        characteristicsArray.put(characteristicsJSON);
279
+
280
+                        characteristicsJSON.put("service", UUIDHelper.uuidToString(service.getUuid()));
281
+                        characteristicsJSON.put("characteristic", UUIDHelper.uuidToString(characteristic.getUuid()));
282
+                        //characteristicsJSON.put("instanceId", characteristic.getInstanceId());
283
+
284
+                        characteristicsJSON.put("properties", Helper.decodeProperties(characteristic));
285
+                            // characteristicsJSON.put("propertiesValue", characteristic.getProperties());
286
+
287
+                        if (characteristic.getPermissions() > 0) {
288
+                            characteristicsJSON.put("permissions", Helper.decodePermissions(characteristic));
289
+                            // characteristicsJSON.put("permissionsValue", characteristic.getPermissions());
290
+                        }
291
+
292
+                        JSONArray descriptorsArray = new JSONArray();
293
+
294
+                        for (BluetoothGattDescriptor descriptor: characteristic.getDescriptors()) {
295
+                            JSONObject descriptorJSON = new JSONObject();
296
+                            descriptorJSON.put("uuid", UUIDHelper.uuidToString(descriptor.getUuid()));
297
+                            descriptorJSON.put("value", descriptor.getValue()); // always blank
298
+
299
+                            if (descriptor.getPermissions() > 0) {
300
+                                descriptorJSON.put("permissions", Helper.decodePermissions(descriptor));
301
+                                // descriptorJSON.put("permissionsValue", descriptor.getPermissions());
302
+                            }
303
+                            descriptorsArray.put(descriptorJSON);
304
+                        }
305
+                        if (descriptorsArray.length() > 0) {
306
+                            characteristicsJSON.put("descriptors", descriptorsArray);
307
+                        }
308
+                    }
309
+                }
310
+            }
311
+        } catch (JSONException e) { // TODO better error handling
312
+            e.printStackTrace();
313
+        }
314
+
315
+        return json;
316
+    }
317
+
318
+    static JSONObject byteArrayToJSON(byte[] bytes) throws JSONException {
319
+        JSONObject object = new JSONObject();
320
+        object.put("CDVType", "ArrayBuffer");
321
+        object.put("data", Base64.encodeToString(bytes, Base64.NO_WRAP));
322
+        return object;
323
+    }
324
+
325
+    public boolean isConnected() {
326
+        return connected;
327
+    }
328
+
329
+    public boolean isConnecting() {
330
+        return connecting;
331
+    }
332
+
333
+    public BluetoothDevice getDevice() {
334
+        return device;
335
+    }
336
+
337
+    @Override
338
+    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
339
+        super.onServicesDiscovered(gatt, status);
340
+
341
+        // refreshCallback is a kludge for refreshing services, if it exists, it temporarily
342
+        // overrides the connect callback. Unfortunately this edge case make the code confusing.
343
+
344
+        if (status == BluetoothGatt.GATT_SUCCESS) {
345
+            PluginResult result = new PluginResult(PluginResult.Status.OK, this.asJSONObject(gatt));
346
+            result.setKeepCallback(true);
347
+            if (refreshCallback != null) {
348
+                refreshCallback.sendPluginResult(result);
349
+                refreshCallback = null;
350
+            } else {
351
+                connectCallback.sendPluginResult(result);
352
+            }
353
+        } else {
354
+            LOG.e(TAG, "Service discovery failed. status = " + status);
355
+            if (refreshCallback != null) {
356
+                refreshCallback.error(this.asJSONObject("Service discovery failed"));
357
+                refreshCallback = null;
358
+            } else {
359
+                connectCallback.error(this.asJSONObject("Service discovery failed"));
360
+                disconnect();
361
+            }
362
+        }
363
+    }
364
+
365
+    @Override
366
+    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
367
+
368
+        this.gatt = gatt;
369
+
370
+        if (newState == BluetoothGatt.STATE_CONNECTED) {
371
+            LOG.d(TAG, "onConnectionStateChange CONNECTED");
372
+            connected = true;
373
+            connecting = false;
374
+            gatt.discoverServices();
375
+
376
+        } else {  // Disconnected
377
+            LOG.d(TAG, "onConnectionStateChange DISCONNECTED");
378
+            connected = false;
379
+            peripheralDisconnected();
380
+
381
+        }
382
+
383
+    }
384
+
385
+    @Override
386
+    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
387
+        super.onCharacteristicChanged(gatt, characteristic);
388
+        LOG.d(TAG, "onCharacteristicChanged " + characteristic);
389
+
390
+        CallbackContext callback = notificationCallbacks.get(generateHashKey(characteristic));
391
+
392
+        if (callback != null) {
393
+            PluginResult result = new PluginResult(PluginResult.Status.OK, characteristic.getValue());
394
+            result.setKeepCallback(true);
395
+            callback.sendPluginResult(result);
396
+        }
397
+    }
398
+
399
+    @Override
400
+    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
401
+        super.onCharacteristicRead(gatt, characteristic, status);
402
+        LOG.d(TAG, "onCharacteristicRead " + characteristic);
403
+
404
+        synchronized(this) {
405
+            if (readCallback != null) {
406
+                if (status == BluetoothGatt.GATT_SUCCESS) {
407
+                    readCallback.success(characteristic.getValue());
408
+                } else {
409
+                    readCallback.error("Error reading " + characteristic.getUuid() + " status=" + status);
410
+                }
411
+
412
+                readCallback = null;
413
+            }
414
+        }
415
+
416
+        commandCompleted();
417
+    }
418
+
419
+    @Override
420
+    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
421
+        super.onCharacteristicWrite(gatt, characteristic, status);
422
+        LOG.d(TAG, "onCharacteristicWrite " + characteristic);
423
+
424
+        synchronized(this) {
425
+            if (writeCallback != null) {
426
+                if (status == BluetoothGatt.GATT_SUCCESS) {
427
+                    writeCallback.success();
428
+                } else {
429
+                    writeCallback.error(status);
430
+                }
431
+
432
+                writeCallback = null;
433
+            }
434
+        }
435
+
436
+        commandCompleted();
437
+    }
438
+
439
+    @Override
440
+    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
441
+        super.onDescriptorWrite(gatt, descriptor, status);
442
+        LOG.d(TAG, "onDescriptorWrite " + descriptor);
443
+        commandCompleted();
444
+    }
445
+
446
+
447
+    @Override
448
+    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
449
+        super.onReadRemoteRssi(gatt, rssi, status);
450
+        synchronized(this) {
451
+            if (readCallback != null) {
452
+                if (status == BluetoothGatt.GATT_SUCCESS) {
453
+                    updateRssi(rssi);
454
+                    readCallback.success(rssi);
455
+                } else {
456
+                    readCallback.error("Error reading RSSI status=" + status);
457
+                }
458
+
459
+                readCallback = null;
460
+            }
461
+        }
462
+        commandCompleted();
463
+    }
464
+
465
+    // Update rssi and scanRecord.
466
+    public void update(int rssi, byte[] scanRecord) {
467
+        this.advertisingRSSI = rssi;
468
+        this.advertisingData = scanRecord;
469
+    }
470
+
471
+    public void updateRssi(int rssi) {
472
+        advertisingRSSI = rssi;
473
+    }
474
+
475
+    // This seems way too complicated
476
+    private void registerNotifyCallback(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID) {
477
+
478
+        boolean success = false;
479
+
480
+        if (gatt == null) {
481
+            callbackContext.error("BluetoothGatt is null");
482
+            return;
483
+        }
484
+
485
+        BluetoothGattService service = gatt.getService(serviceUUID);
486
+        BluetoothGattCharacteristic characteristic = findNotifyCharacteristic(service, characteristicUUID);
487
+        String key = generateHashKey(serviceUUID, characteristic);
488
+
489
+        if (characteristic != null) {
490
+
491
+            notificationCallbacks.put(key, callbackContext);
492
+
493
+            if (gatt.setCharacteristicNotification(characteristic, true)) {
494
+
495
+                // Why doesn't setCharacteristicNotification write the descriptor?
496
+                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIGURATION_UUID);
497
+                if (descriptor != null) {
498
+
499
+                    // prefer notify over indicate
500
+                    if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
501
+                        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
502
+                    } else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
503
+                        descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
504
+                    } else {
505
+                        LOG.w(TAG, "Characteristic " + characteristicUUID + " does not have NOTIFY or INDICATE property set");
506
+                    }
507
+
508
+                    if (gatt.writeDescriptor(descriptor)) {
509
+                        success = true;
510
+                    } else {
511
+                        callbackContext.error("Failed to set client characteristic notification for " + characteristicUUID);
512
+                    }
513
+
514
+                } else {
515
+                    callbackContext.error("Set notification failed for " + characteristicUUID);
516
+                }
517
+
518
+            } else {
519
+                callbackContext.error("Failed to register notification for " + characteristicUUID);
520
+            }
521
+
522
+        } else {
523
+            callbackContext.error("Characteristic " + characteristicUUID + " not found");
524
+        }
525
+
526
+        if (!success) {
527
+            commandCompleted();
528
+        }
529
+    }
530
+
531
+    private void removeNotifyCallback(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID) {
532
+
533
+        if (gatt == null) {
534
+            callbackContext.error("BluetoothGatt is null");
535
+            return;
536
+        }
537
+
538
+        BluetoothGattService service = gatt.getService(serviceUUID);
539
+        BluetoothGattCharacteristic characteristic = findNotifyCharacteristic(service, characteristicUUID);
540
+        String key = generateHashKey(serviceUUID, characteristic);
541
+
542
+        if (characteristic != null) {
543
+
544
+            notificationCallbacks.remove(key);
545
+
546
+            if (gatt.setCharacteristicNotification(characteristic, false)) {
547
+                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIGURATION_UUID);
548
+                if (descriptor != null) {
549
+                    descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
550
+                    gatt.writeDescriptor(descriptor);
551
+                }
552
+                callbackContext.success();
553
+            } else {
554
+                // TODO we can probably ignore and return success anyway since we removed the notification callback
555
+                callbackContext.error("Failed to stop notification for " + characteristicUUID);
556
+            }
557
+
558
+        } else {
559
+            callbackContext.error("Characteristic " + characteristicUUID + " not found");
560
+        }
561
+
562
+        commandCompleted();
563
+
564
+    }
565
+
566
+    // Some devices reuse UUIDs across characteristics, so we can't use service.getCharacteristic(characteristicUUID)
567
+    // instead check the UUID and properties for each characteristic in the service until we find the best match
568
+    // This function prefers Notify over Indicate
569
+    private BluetoothGattCharacteristic findNotifyCharacteristic(BluetoothGattService service, UUID characteristicUUID) {
570
+        BluetoothGattCharacteristic characteristic = null;
571
+
572
+        // Check for Notify first
573
+        List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
574
+        for (BluetoothGattCharacteristic c : characteristics) {
575
+            if ((c.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0 && characteristicUUID.equals(c.getUuid())) {
576
+                characteristic = c;
577
+                break;
578
+            }
579
+        }
580
+
581
+        if (characteristic != null) return characteristic;
582
+
583
+        // If there wasn't Notify Characteristic, check for Indicate
584
+        for (BluetoothGattCharacteristic c : characteristics) {
585
+            if ((c.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0 && characteristicUUID.equals(c.getUuid())) {
586
+                characteristic = c;
587
+                break;
588
+            }
589
+        }
590
+
591
+        // As a last resort, try and find ANY characteristic with this UUID, even if it doesn't have the correct properties
592
+        if (characteristic == null) {
593
+            characteristic = service.getCharacteristic(characteristicUUID);
594
+        }
595
+
596
+        return characteristic;
597
+    }
598
+
599
+    private void readCharacteristic(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID) {
600
+
601
+        boolean success = false;
602
+
603
+        if (gatt == null) {
604
+            callbackContext.error("BluetoothGatt is null");
605
+            return;
606
+        }
607
+
608
+        BluetoothGattService service = gatt.getService(serviceUUID);
609
+
610
+        if (service == null) {
611
+            callbackContext.error("Service " + serviceUUID + " not found.");
612
+            return;
613
+        }
614
+
615
+        BluetoothGattCharacteristic characteristic = findReadableCharacteristic(service, characteristicUUID);
616
+
617
+        if (characteristic == null) {
618
+            callbackContext.error("Characteristic " + characteristicUUID + " not found.");
619
+        } else {
620
+            synchronized(this) {
621
+                readCallback = callbackContext;
622
+                if (gatt.readCharacteristic(characteristic)) {
623
+                    success = true;
624
+                } else {
625
+                    readCallback = null;
626
+                    callbackContext.error("Read failed");
627
+                }
628
+            }
629
+        }
630
+
631
+        if (!success) {
632
+            commandCompleted();
633
+        }
634
+
635
+    }
636
+
637
+    private void readRSSI(CallbackContext callbackContext) {
638
+
639
+        boolean success = false;
640
+
641
+        if (gatt == null) {
642
+            callbackContext.error("BluetoothGatt is null");
643
+            return;
644
+        }
645
+
646
+        synchronized(this) {
647
+            readCallback = callbackContext;
648
+
649
+            if (gatt.readRemoteRssi()) {
650
+                success = true;
651
+            } else {
652
+                readCallback = null;
653
+                callbackContext.error("Read RSSI failed");
654
+            }
655
+        }
656
+
657
+        if (!success) {
658
+            commandCompleted();
659
+        }
660
+
661
+    }
662
+
663
+    // Some peripherals re-use UUIDs for multiple characteristics so we need to check the properties
664
+    // and UUID of all characteristics instead of using service.getCharacteristic(characteristicUUID)
665
+    private BluetoothGattCharacteristic findReadableCharacteristic(BluetoothGattService service, UUID characteristicUUID) {
666
+        BluetoothGattCharacteristic characteristic = null;
667
+
668
+        int read = BluetoothGattCharacteristic.PROPERTY_READ;
669
+
670
+        List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
671
+        for (BluetoothGattCharacteristic c : characteristics) {
672
+            if ((c.getProperties() & read) != 0 && characteristicUUID.equals(c.getUuid())) {
673
+                characteristic = c;
674
+                break;
675
+            }
676
+        }
677
+
678
+        // As a last resort, try and find ANY characteristic with this UUID, even if it doesn't have the correct properties
679
+        if (characteristic == null) {
680
+            characteristic = service.getCharacteristic(characteristicUUID);
681
+        }
682
+
683
+        return characteristic;
684
+    }
685
+
686
+    private void writeCharacteristic(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID, byte[] data, int writeType) {
687
+
688
+        boolean success = false;
689
+
690
+        if (gatt == null) {
691
+            callbackContext.error("BluetoothGatt is null");
692
+            return;
693
+        }
694
+
695
+        BluetoothGattService service = gatt.getService(serviceUUID);
696
+
697
+        if (service == null) {
698
+            callbackContext.error("Service " + serviceUUID + " not found.");
699
+            return;
700
+        }
701
+
702
+        BluetoothGattCharacteristic characteristic = findWritableCharacteristic(service, characteristicUUID, writeType);
703
+
704
+        if (characteristic == null) {
705
+            callbackContext.error("Characteristic " + characteristicUUID + " not found.");
706
+        } else {
707
+            characteristic.setValue(data);
708
+            characteristic.setWriteType(writeType);
709
+            synchronized(this) {
710
+                writeCallback = callbackContext;
711
+
712
+                if (gatt.writeCharacteristic(characteristic)) {
713
+                    success = true;
714
+                } else {
715
+                    writeCallback = null;
716
+                    callbackContext.error("Write failed");
717
+                }
718
+            }
719
+        }
720
+
721
+        if (!success) {
722
+            commandCompleted();
723
+        }
724
+
725
+    }
726
+
727
+    // Some peripherals re-use UUIDs for multiple characteristics so we need to check the properties
728
+    // and UUID of all characteristics instead of using service.getCharacteristic(characteristicUUID)
729
+    private BluetoothGattCharacteristic findWritableCharacteristic(BluetoothGattService service, UUID characteristicUUID, int writeType) {
730
+        BluetoothGattCharacteristic characteristic = null;
731
+
732
+        // get write property
733
+        int writeProperty = BluetoothGattCharacteristic.PROPERTY_WRITE;
734
+        if (writeType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) {
735
+            writeProperty = BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE;
736
+        }
737
+
738
+        List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
739
+        for (BluetoothGattCharacteristic c : characteristics) {
740
+            if ((c.getProperties() & writeProperty) != 0 && characteristicUUID.equals(c.getUuid())) {
741
+                characteristic = c;
742
+                break;
743
+            }
744
+        }
745
+
746
+        // As a last resort, try and find ANY characteristic with this UUID, even if it doesn't have the correct properties
747
+        if (characteristic == null) {
748
+            characteristic = service.getCharacteristic(characteristicUUID);
749
+        }
750
+
751
+        return characteristic;
752
+    }
753
+
754
+    public void queueRead(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID) {
755
+        BLECommand command = new BLECommand(callbackContext, serviceUUID, characteristicUUID, BLECommand.READ);
756
+        queueCommand(command);
757
+    }
758
+
759
+    public void queueWrite(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID, byte[] data, int writeType) {
760
+        BLECommand command = new BLECommand(callbackContext, serviceUUID, characteristicUUID, data, writeType);
761
+        queueCommand(command);
762
+    }
763
+
764
+    public void queueRegisterNotifyCallback(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID) {
765
+        BLECommand command = new BLECommand(callbackContext, serviceUUID, characteristicUUID, BLECommand.REGISTER_NOTIFY);
766
+        queueCommand(command);
767
+    }
768
+
769
+    public void queueRemoveNotifyCallback(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID) {
770
+        BLECommand command = new BLECommand(callbackContext, serviceUUID, characteristicUUID, BLECommand.REMOVE_NOTIFY);
771
+        queueCommand(command);
772
+    }
773
+
774
+
775
+    public void queueReadRSSI(CallbackContext callbackContext) {
776
+        BLECommand command = new BLECommand(callbackContext, null, null, BLECommand.READ_RSSI);
777
+        queueCommand(command);
778
+    }
779
+
780
+    private void queueCleanup() {
781
+        bleProcessing = false;
782
+        BLECommand command;
783
+        for (;;) {
784
+            command = commandQueue.poll();
785
+            if (command != null) {
786
+                command.getCallbackContext().error("Peripheral Disconnected");
787
+            }
788
+            else {
789
+                break;
790
+            }
791
+        }
792
+    }
793
+
794
+    private void callbackCleanup() {
795
+        synchronized(this) {
796
+            if (readCallback != null) {
797
+                readCallback.error(this.asJSONObject("Peripheral Disconnected"));
798
+                readCallback = null;
799
+                commandCompleted();
800
+            }
801
+            if (writeCallback != null) {
802
+                writeCallback.error(this.asJSONObject("Peripheral Disconnected"));
803
+                writeCallback = null;
804
+                commandCompleted();
805
+            }
806
+        }
807
+    }
808
+
809
+    // add a new command to the queue
810
+    private void queueCommand(BLECommand command) {
811
+        LOG.d(TAG,"Queuing Command " + command);
812
+        commandQueue.add(command);
813
+
814
+        PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
815
+        result.setKeepCallback(true);
816
+        command.getCallbackContext().sendPluginResult(result);
817
+
818
+        if (!bleProcessing) {
819
+            processCommands();
820
+        }
821
+    }
822
+
823
+    // command finished, queue the next command
824
+    private void commandCompleted() {
825
+        LOG.d(TAG,"Processing Complete");
826
+        bleProcessing = false;
827
+        processCommands();
828
+    }
829
+
830
+    // process the queue
831
+    private void processCommands() {
832
+        LOG.d(TAG,"Processing Commands");
833
+
834
+        if (bleProcessing) { return; }
835
+
836
+        BLECommand command = commandQueue.poll();
837
+        if (command != null) {
838
+            if (command.getType() == BLECommand.READ) {
839
+                LOG.d(TAG,"Read " + command.getCharacteristicUUID());
840
+                bleProcessing = true;
841
+                readCharacteristic(command.getCallbackContext(), command.getServiceUUID(), command.getCharacteristicUUID());
842
+            } else if (command.getType() == BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) {
843
+                LOG.d(TAG,"Write " + command.getCharacteristicUUID());
844
+                bleProcessing = true;
845
+                writeCharacteristic(command.getCallbackContext(), command.getServiceUUID(), command.getCharacteristicUUID(), command.getData(), command.getType());
846
+            } else if (command.getType() == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) {
847
+                LOG.d(TAG,"Write No Response " + command.getCharacteristicUUID());
848
+                bleProcessing = true;
849
+                writeCharacteristic(command.getCallbackContext(), command.getServiceUUID(), command.getCharacteristicUUID(), command.getData(), command.getType());
850
+            } else if (command.getType() == BLECommand.REGISTER_NOTIFY) {
851
+                LOG.d(TAG,"Register Notify " + command.getCharacteristicUUID());
852
+                bleProcessing = true;
853
+                registerNotifyCallback(command.getCallbackContext(), command.getServiceUUID(), command.getCharacteristicUUID());
854
+            } else if (command.getType() == BLECommand.REMOVE_NOTIFY) {
855
+                LOG.d(TAG,"Remove Notify " + command.getCharacteristicUUID());
856
+                bleProcessing = true;
857
+                removeNotifyCallback(command.getCallbackContext(), command.getServiceUUID(), command.getCharacteristicUUID());
858
+            } else if (command.getType() == BLECommand.READ_RSSI) {
859
+                LOG.d(TAG,"Read RSSI");
860
+                bleProcessing = true;
861
+                readRSSI(command.getCallbackContext());
862
+            } else {
863
+                // this shouldn't happen
864
+                throw new RuntimeException("Unexpected BLE Command type " + command.getType());
865
+            }
866
+        } else {
867
+            LOG.d(TAG, "Command Queue is empty.");
868
+        }
869
+
870
+    }
871
+
872
+    private String generateHashKey(BluetoothGattCharacteristic characteristic) {
873
+        return generateHashKey(characteristic.getService().getUuid(), characteristic);
874
+    }
875
+
876
+    private String generateHashKey(UUID serviceUUID, BluetoothGattCharacteristic characteristic) {
877
+        return String.valueOf(serviceUUID) + "|" + characteristic.getUuid() + "|" + characteristic.getInstanceId();
878
+    }
879
+
880
+}

+ 47 - 0
plugins/cordova-plugin-ble-central/src/android/UUIDHelper.java

@@ -0,0 +1,47 @@
1
+// (c) 2104 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package com.megster.cordova.ble.central;
16
+
17
+import java.util.UUID;
18
+import java.util.regex.Matcher;
19
+import java.util.regex.Pattern;
20
+
21
+public class UUIDHelper {
22
+
23
+    // base UUID used to build 128 bit Bluetooth UUIDs
24
+    public static final String UUID_BASE = "0000XXXX-0000-1000-8000-00805f9b34fb";
25
+
26
+    // handle 16 and 128 bit UUIDs
27
+    public static UUID uuidFromString(String uuid) {
28
+
29
+        if (uuid.length() == 4) {
30
+            uuid = UUID_BASE.replace("XXXX", uuid);
31
+        }
32
+        return UUID.fromString(uuid);
33
+    }
34
+
35
+    // return 16 bit UUIDs where possible
36
+    public static String uuidToString(UUID uuid) {
37
+        String longUUID = uuid.toString();
38
+        Pattern pattern = Pattern.compile("0000(.{4})-0000-1000-8000-00805f9b34fb", Pattern.CASE_INSENSITIVE);
39
+        Matcher matcher = pattern.matcher(longUUID);
40
+        if (matcher.matches()) {
41
+            // 16 bit UUID
42
+            return matcher.group(1);
43
+        } else {
44
+            return longUUID;
45
+        }
46
+    }
47
+}

+ 78 - 0
plugins/cordova-plugin-ble-central/src/browser/BLECentralPlugin.js

@@ -0,0 +1,78 @@
1
+function notSupported() {
2
+    console.log('BLE is not supported on the browser');
3
+}
4
+
5
+module.exports = {
6
+    scan: function(services, seconds, success, failure) {
7
+        notSupported();
8
+        if (failure) failure();
9
+    },
10
+    startScan: function(services, success, failure) {
11
+        notSupported();
12
+        if (failure) failure();
13
+    },
14
+    stopScan: function(success, failure) {
15
+        notSupported();
16
+        if (failure) failure();
17
+    },
18
+    startScanWithOptions: function(services, options, success, failure) {
19
+        notSupported();
20
+        if (failure) failure();
21
+    },
22
+    connect: function(device_id, connectSuccess, connectFailure) {
23
+        notSupported();
24
+        if (connectFailure) connectFailure();
25
+    },
26
+    disconnect: function(device_id, connectSuccess, connectFailure) {
27
+        notSupported();
28
+        if (connectFailure) connectFailure();
29
+    },
30
+    read: function(device_id, service_uuid, characteristic_uuid, success, failure) {
31
+        notSupported();
32
+        if (failure) failure();
33
+    },
34
+    readRSSI: function(device_id, success, failure) {
35
+        notSupported();
36
+        if (failure) failure();
37
+    },
38
+    write: function(device_id, service_uuid, characteristic_uuid, data, success, failure) {
39
+        notSupported();
40
+        if (failure) failure();
41
+    },
42
+    writeWithoutResponse: function(device_id, service_uuid, characteristic_uuid, data, success, failure) {
43
+        notSupported();
44
+        if (failure) failure();
45
+    },
46
+    startNotification: function(device_id, service_uuid, characteristic_uuid, success, failure) {
47
+        notSupported();
48
+        if (failure) failure();
49
+    },
50
+    stopNotifcation: function(device_id, service_uuid, characteristic_uuid, success, failure) {
51
+        notSupported();
52
+        if (failure) failure();
53
+    },
54
+    isEnabled: function(success, failure) {
55
+        notSupported();
56
+        if (failure) failure();
57
+    },
58
+    isConnected: function(device_id, success, failure) {
59
+        notSupported();
60
+        if (failure) failure();
61
+    },
62
+    showBluetoothSettings: function(success, failure) {
63
+        notSupported();
64
+        if (failure) failure();
65
+    },
66
+    enable: function(success, failure) {
67
+        notSupported();
68
+        if (failure) failure();
69
+    },
70
+    startStateNotifications: function(success, failure) {
71
+      notSupported();
72
+      if (failure) failure();
73
+    },
74
+    stopStateNotifications: function(success, failure) {
75
+      notSupported();
76
+      if (failure) failure();
77
+    }
78
+};

+ 73 - 0
plugins/cordova-plugin-ble-central/src/ios/BLECentralPlugin.h

@@ -0,0 +1,73 @@
1
+//
2
+//  BLECentralPlugin.h
3
+//  BLE Central Cordova Plugin
4
+//
5
+//  (c) 2104 Don Coleman
6
+//
7
+// Licensed under the Apache License, Version 2.0 (the "License");
8
+// you may not use this file except in compliance with the License.
9
+// You may obtain a copy of the License at
10
+//
11
+//     http://www.apache.org/licenses/LICENSE-2.0
12
+//
13
+// Unless required by applicable law or agreed to in writing, software
14
+// distributed under the License is distributed on an "AS IS" BASIS,
15
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+// See the License for the specific language governing permissions and
17
+// limitations under the License.
18
+
19
+#ifndef BLECentralPlugin_h
20
+#define BLECentralPlugin_h
21
+
22
+#import <Cordova/CDV.h>
23
+#import <CoreBluetooth/CoreBluetooth.h>
24
+#import "BLECommandContext.h"
25
+#import "CBPeripheral+Extensions.h"
26
+
27
+@interface BLECentralPlugin : CDVPlugin <CBCentralManagerDelegate, CBPeripheralDelegate> {
28
+    NSString* discoverPeripheralCallbackId;
29
+    NSString* stateCallbackId;
30
+    NSMutableDictionary* connectCallbacks;
31
+    NSMutableDictionary *readCallbacks;
32
+    NSMutableDictionary *writeCallbacks;
33
+    NSMutableDictionary *notificationCallbacks;
34
+    NSMutableDictionary *startNotificationCallbacks;
35
+    NSMutableDictionary *stopNotificationCallbacks;
36
+    NSMutableDictionary *connectCallbackLatches;
37
+    NSMutableDictionary *readRSSICallbacks;
38
+}
39
+
40
+@property (strong, nonatomic) NSMutableSet *peripherals;
41
+@property (strong, nonatomic) CBCentralManager *manager;
42
+
43
+- (void)scan:(CDVInvokedUrlCommand *)command;
44
+- (void)startScan:(CDVInvokedUrlCommand *)command;
45
+- (void)startScanWithOptions:(CDVInvokedUrlCommand *)command;
46
+- (void)stopScan:(CDVInvokedUrlCommand *)command;
47
+- (void)connectedPeripheralsWithServices:(CDVInvokedUrlCommand*)command;
48
+- (void)peripheralsWithIdentifiers:(CDVInvokedUrlCommand*)command;
49
+
50
+- (void)connect:(CDVInvokedUrlCommand *)command;
51
+- (void)autoConnect:(CDVInvokedUrlCommand *)command;
52
+- (void)disconnect:(CDVInvokedUrlCommand *)command;
53
+
54
+- (void)read:(CDVInvokedUrlCommand *)command;
55
+- (void)write:(CDVInvokedUrlCommand *)command;
56
+- (void)writeWithoutResponse:(CDVInvokedUrlCommand *)command;
57
+
58
+- (void)startNotification:(CDVInvokedUrlCommand *)command;
59
+- (void)stopNotification:(CDVInvokedUrlCommand *)command;
60
+
61
+- (void)isEnabled:(CDVInvokedUrlCommand *)command;
62
+- (void)isConnected:(CDVInvokedUrlCommand *)command;
63
+
64
+- (void)startStateNotifications:(CDVInvokedUrlCommand *)command;
65
+- (void)stopStateNotifications:(CDVInvokedUrlCommand *)command;
66
+
67
+- (void)onReset;
68
+
69
+- (void)readRSSI:(CDVInvokedUrlCommand *)command;
70
+
71
+@end
72
+
73
+#endif

+ 944 - 0
plugins/cordova-plugin-ble-central/src/ios/BLECentralPlugin.m

@@ -0,0 +1,944 @@
1
+//
2
+//  BLECentralPlugin.m
3
+//  BLE Central Cordova Plugin
4
+//
5
+//  (c) 2104-2018 Don Coleman
6
+//
7
+// Licensed under the Apache License, Version 2.0 (the "License");
8
+// you may not use this file except in compliance with the License.
9
+// You may obtain a copy of the License at
10
+//
11
+//     http://www.apache.org/licenses/LICENSE-2.0
12
+//
13
+// Unless required by applicable law or agreed to in writing, software
14
+// distributed under the License is distributed on an "AS IS" BASIS,
15
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+// See the License for the specific language governing permissions and
17
+// limitations under the License.
18
+
19
+#import "BLECentralPlugin.h"
20
+#import <Cordova/CDV.h>
21
+
22
+@interface BLECentralPlugin() {
23
+    NSDictionary *bluetoothStates;
24
+}
25
+- (CBPeripheral *)findPeripheralByUUID:(NSString *)uuid;
26
+- (void)stopScanTimer:(NSTimer *)timer;
27
+@end
28
+
29
+@implementation BLECentralPlugin
30
+
31
+@synthesize manager;
32
+@synthesize peripherals;
33
+
34
+- (void)pluginInitialize {
35
+    NSLog(@"Cordova BLE Central Plugin");
36
+    NSLog(@"(c)2014-2016 Don Coleman");
37
+
38
+    [super pluginInitialize];
39
+
40
+    peripherals = [NSMutableSet new];
41
+    manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:@{CBCentralManagerOptionShowPowerAlertKey: @NO}];
42
+
43
+    connectCallbacks = [NSMutableDictionary new];
44
+    connectCallbackLatches = [NSMutableDictionary new];
45
+    readCallbacks = [NSMutableDictionary new];
46
+    writeCallbacks = [NSMutableDictionary new];
47
+    notificationCallbacks = [NSMutableDictionary new];
48
+    startNotificationCallbacks = [NSMutableDictionary new];
49
+    stopNotificationCallbacks = [NSMutableDictionary new];
50
+    bluetoothStates = [NSDictionary dictionaryWithObjectsAndKeys:
51
+                       @"unknown", @(CBCentralManagerStateUnknown),
52
+                       @"resetting", @(CBCentralManagerStateResetting),
53
+                       @"unsupported", @(CBCentralManagerStateUnsupported),
54
+                       @"unauthorized", @(CBCentralManagerStateUnauthorized),
55
+                       @"off", @(CBCentralManagerStatePoweredOff),
56
+                       @"on", @(CBCentralManagerStatePoweredOn),
57
+                       nil];
58
+    readRSSICallbacks = [NSMutableDictionary new];
59
+}
60
+
61
+#pragma mark - Cordova Plugin Methods
62
+
63
+// TODO add timeout
64
+- (void)connect:(CDVInvokedUrlCommand *)command {
65
+    NSLog(@"connect");
66
+    NSString *uuid = [command argumentAtIndex:0];
67
+
68
+    CBPeripheral *peripheral = [self findPeripheralByUUID:uuid];
69
+
70
+    if (peripheral) {
71
+        NSLog(@"Connecting to peripheral with UUID : %@", uuid);
72
+
73
+        [connectCallbacks setObject:[command.callbackId copy] forKey:[peripheral uuidAsString]];
74
+        [manager connectPeripheral:peripheral options:nil];
75
+    } else {
76
+        NSString *error = [NSString stringWithFormat:@"Could not find peripheral %@.", uuid];
77
+        NSLog(@"%@", error);
78
+        CDVPluginResult *pluginResult = nil;
79
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error];
80
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
81
+    }
82
+
83
+}
84
+
85
+// This works different than Android. iOS needs to know about the peripheral UUID
86
+// If not scanning, try connectedPeripheralsWIthServices or peripheralsWithIdentifiers
87
+- (void)autoConnect:(CDVInvokedUrlCommand *)command {
88
+    NSLog(@"autoConnect");
89
+    NSString *uuid = [command argumentAtIndex:0];
90
+    
91
+    CBPeripheral *peripheral = [self findPeripheralByUUID:uuid];
92
+    
93
+    if (peripheral) {
94
+        NSLog(@"Autoconnecting to peripheral with UUID : %@", uuid);
95
+        
96
+        [connectCallbacks setObject:[command.callbackId copy] forKey:[peripheral uuidAsString]];
97
+        [manager connectPeripheral:peripheral options:nil];
98
+    } else {
99
+        NSString *error = [NSString stringWithFormat:@"Could not find peripheral %@.", uuid];
100
+        NSLog(@"%@", error);
101
+        CDVPluginResult *pluginResult = nil;
102
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error];
103
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
104
+    }
105
+    
106
+}
107
+
108
+// disconnect: function (device_id, success, failure) {
109
+- (void)disconnect:(CDVInvokedUrlCommand*)command {
110
+    NSLog(@"disconnect");
111
+
112
+    NSString *uuid = [command argumentAtIndex:0];
113
+    CBPeripheral *peripheral = [self findPeripheralByUUID:uuid];
114
+
115
+    if (!peripheral) {
116
+        NSString *message = [NSString stringWithFormat:@"Peripheral %@ not found", uuid];
117
+        NSLog(@"%@", message);
118
+        CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:message];
119
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
120
+
121
+    } else {
122
+
123
+        [connectCallbacks removeObjectForKey:uuid];
124
+        [self cleanupOperationCallbacks:peripheral withResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Peripheral disconnected"]];
125
+
126
+        if (peripheral && peripheral.state != CBPeripheralStateDisconnected) {
127
+            [manager cancelPeripheralConnection:peripheral];
128
+        }
129
+
130
+        CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
131
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
132
+    }
133
+}
134
+
135
+// read: function (device_id, service_uuid, characteristic_uuid, success, failure) {
136
+- (void)read:(CDVInvokedUrlCommand*)command {
137
+    NSLog(@"read");
138
+
139
+    BLECommandContext *context = [self getData:command prop:CBCharacteristicPropertyRead];
140
+    if (context) {
141
+        CBPeripheral *peripheral = [context peripheral];
142
+        if ([peripheral state] != CBPeripheralStateConnected) {
143
+            CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Peripheral is not connected"];
144
+            [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
145
+            return;
146
+        }
147
+        CBCharacteristic *characteristic = [context characteristic];
148
+
149
+        NSString *key = [self keyForPeripheral: peripheral andCharacteristic:characteristic];
150
+        [readCallbacks setObject:[command.callbackId copy] forKey:key];
151
+
152
+        [peripheral readValueForCharacteristic:characteristic];  // callback sends value
153
+    }
154
+}
155
+
156
+// write: function (device_id, service_uuid, characteristic_uuid, value, success, failure) {
157
+- (void)write:(CDVInvokedUrlCommand*)command {
158
+    BLECommandContext *context = [self getData:command prop:CBCharacteristicPropertyWrite];
159
+    NSData *message = [command argumentAtIndex:3]; // This is binary
160
+    if (context) {
161
+        if (message != nil) {
162
+            CBPeripheral *peripheral = [context peripheral];
163
+            if ([peripheral state] != CBPeripheralStateConnected) {
164
+                CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Peripheral is not connected"];
165
+                [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
166
+                return;
167
+            }
168
+            CBCharacteristic *characteristic = [context characteristic];
169
+
170
+            NSString *key = [self keyForPeripheral: peripheral andCharacteristic:characteristic];
171
+            [writeCallbacks setObject:[command.callbackId copy] forKey:key];
172
+
173
+            // TODO need to check the max length
174
+            [peripheral writeValue:message forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
175
+
176
+            // response is sent from didWriteValueForCharacteristic
177
+        } else {
178
+            CDVPluginResult *pluginResult = nil;
179
+            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"message was null"];
180
+            [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
181
+        }
182
+    }
183
+}
184
+
185
+// writeWithoutResponse: function (device_id, service_uuid, characteristic_uuid, value, success, failure) {
186
+- (void)writeWithoutResponse:(CDVInvokedUrlCommand*)command {
187
+    NSLog(@"writeWithoutResponse");
188
+
189
+    BLECommandContext *context = [self getData:command prop:CBCharacteristicPropertyWriteWithoutResponse];
190
+    NSData *message = [command argumentAtIndex:3]; // This is binary
191
+
192
+    if (context) {
193
+        CDVPluginResult *pluginResult = nil;
194
+        if (message != nil) {
195
+            CBPeripheral *peripheral = [context peripheral];
196
+            if ([peripheral state] != CBPeripheralStateConnected) {
197
+                CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Peripheral is not connected"];
198
+                [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
199
+                return;
200
+            }
201
+            CBCharacteristic *characteristic = [context characteristic];
202
+
203
+            // TODO need to check the max length
204
+            [peripheral writeValue:message forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];
205
+
206
+            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
207
+        } else {
208
+            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"message was null"];
209
+        }
210
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
211
+    }
212
+}
213
+
214
+// success callback is called on notification
215
+// notify: function (device_id, service_uuid, characteristic_uuid, success, failure) {
216
+- (void)startNotification:(CDVInvokedUrlCommand*)command {
217
+    NSLog(@"registering for notification");
218
+
219
+    BLECommandContext *context = [self getData:command prop:CBCharacteristicPropertyNotify]; // TODO name this better
220
+
221
+    if (context) {
222
+        CBPeripheral *peripheral = [context peripheral];
223
+        if ([peripheral state] != CBPeripheralStateConnected) {
224
+            CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Peripheral is not connected"];
225
+            [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
226
+            return;
227
+        }
228
+        CBCharacteristic *characteristic = [context characteristic];
229
+
230
+        NSString *key = [self keyForPeripheral: peripheral andCharacteristic:characteristic];
231
+        NSString *callback = [command.callbackId copy];
232
+        [startNotificationCallbacks setObject: callback forKey: key];
233
+        [stopNotificationCallbacks removeObjectForKey:key];
234
+
235
+        [peripheral setNotifyValue:YES forCharacteristic:characteristic];
236
+
237
+    }
238
+
239
+}
240
+
241
+// stopNotification: function (device_id, service_uuid, characteristic_uuid, success, failure) {
242
+- (void)stopNotification:(CDVInvokedUrlCommand*)command {
243
+    NSLog(@"stop notification");
244
+
245
+    BLECommandContext *context = [self getData:command prop:CBCharacteristicPropertyNotify];
246
+
247
+    if (context) {
248
+        CBPeripheral *peripheral = [context peripheral];    // FIXME is setNotifyValue:NO legal to call on a peripheral not connected?
249
+        CBCharacteristic *characteristic = [context characteristic];
250
+
251
+        NSString *key = [self keyForPeripheral: peripheral andCharacteristic:characteristic];
252
+        NSString *callback = [command.callbackId copy];
253
+        [stopNotificationCallbacks setObject: callback forKey: key];
254
+
255
+        [peripheral setNotifyValue:NO forCharacteristic:characteristic];
256
+        // callback sent from peripheral:didUpdateNotificationStateForCharacteristic:error:
257
+
258
+    }
259
+}
260
+
261
+- (void)isEnabled:(CDVInvokedUrlCommand*)command {
262
+    CDVPluginResult *pluginResult = nil;
263
+    int bluetoothState = [manager state];
264
+
265
+    BOOL enabled = bluetoothState == CBCentralManagerStatePoweredOn;
266
+
267
+    if (enabled) {
268
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
269
+    } else {
270
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:bluetoothState];
271
+    }
272
+    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
273
+}
274
+
275
+- (void)scan:(CDVInvokedUrlCommand*)command {
276
+    NSLog(@"scan");
277
+    discoverPeripheralCallbackId = [command.callbackId copy];
278
+
279
+    NSArray<NSString *> *serviceUUIDStrings = [command argumentAtIndex:0];
280
+    NSNumber *timeoutSeconds = [command argumentAtIndex:1];
281
+    NSArray<CBUUID *> *serviceUUIDs = [self uuidStringsToCBUUIDs:serviceUUIDStrings];
282
+
283
+    [manager scanForPeripheralsWithServices:serviceUUIDs options:nil];
284
+
285
+    [NSTimer scheduledTimerWithTimeInterval:[timeoutSeconds floatValue]
286
+                                     target:self
287
+                                   selector:@selector(stopScanTimer:)
288
+                                   userInfo:[command.callbackId copy]
289
+                                    repeats:NO];
290
+}
291
+
292
+- (void)startScan:(CDVInvokedUrlCommand*)command {
293
+    NSLog(@"startScan");
294
+    discoverPeripheralCallbackId = [command.callbackId copy];
295
+    NSArray<NSString *> *serviceUUIDStrings = [command argumentAtIndex:0];
296
+    NSArray<CBUUID *> *serviceUUIDs = [self uuidStringsToCBUUIDs:serviceUUIDStrings];
297
+
298
+    [manager scanForPeripheralsWithServices:serviceUUIDs options:nil];
299
+}
300
+
301
+- (void)startScanWithOptions:(CDVInvokedUrlCommand*)command {
302
+    NSLog(@"startScanWithOptions");
303
+    discoverPeripheralCallbackId = [command.callbackId copy];
304
+    NSArray<NSString *> *serviceUUIDStrings = [command argumentAtIndex:0];
305
+    NSArray<CBUUID *> *serviceUUIDs = [self uuidStringsToCBUUIDs:serviceUUIDStrings];
306
+    NSDictionary *options = command.arguments[1];
307
+
308
+    NSMutableDictionary *scanOptions = [NSMutableDictionary new];
309
+    NSNumber *reportDuplicates = [options valueForKey: @"reportDuplicates"];
310
+    if (reportDuplicates) {
311
+        [scanOptions setValue:reportDuplicates
312
+                       forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
313
+    }
314
+
315
+    [manager scanForPeripheralsWithServices:serviceUUIDs options:scanOptions];
316
+}
317
+
318
+- (void)stopScan:(CDVInvokedUrlCommand*)command {
319
+    NSLog(@"stopScan");
320
+
321
+    [manager stopScan];
322
+
323
+    if (discoverPeripheralCallbackId) {
324
+        discoverPeripheralCallbackId = nil;
325
+    }
326
+
327
+    CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
328
+    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
329
+}
330
+
331
+
332
+- (void)isConnected:(CDVInvokedUrlCommand*)command {
333
+    CDVPluginResult *pluginResult = nil;
334
+    CBPeripheral *peripheral = [self findPeripheralByUUID:[command argumentAtIndex:0]];
335
+
336
+    if (peripheral && peripheral.state == CBPeripheralStateConnected) {
337
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
338
+    } else {
339
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Not connected"];
340
+    }
341
+    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
342
+}
343
+
344
+- (void)startStateNotifications:(CDVInvokedUrlCommand *)command {
345
+    CDVPluginResult *pluginResult = nil;
346
+
347
+    if (stateCallbackId == nil) {
348
+        stateCallbackId = [command.callbackId copy];
349
+        int bluetoothState = [manager state];
350
+        NSString *state = [bluetoothStates objectForKey:[NSNumber numberWithInt:bluetoothState]];
351
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:state];
352
+        [pluginResult setKeepCallbackAsBool:TRUE];
353
+        NSLog(@"Start state notifications on callback %@", stateCallbackId);
354
+    } else {
355
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"State callback already registered"];
356
+    }
357
+
358
+    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
359
+}
360
+
361
+- (void)stopStateNotifications:(CDVInvokedUrlCommand *)command {
362
+    CDVPluginResult *pluginResult = nil;
363
+
364
+    if (stateCallbackId != nil) {
365
+        // Call with NO_RESULT so Cordova.js will delete the callback without actually calling it
366
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_NO_RESULT];
367
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:stateCallbackId];
368
+        stateCallbackId = nil;
369
+    }
370
+
371
+    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
372
+    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
373
+}
374
+
375
+- (void)onReset {
376
+    stateCallbackId = nil;
377
+}
378
+
379
+- (void)readRSSI:(CDVInvokedUrlCommand*)command {
380
+    NSLog(@"readRSSI");
381
+    NSString *uuid = [command argumentAtIndex:0];
382
+
383
+    CBPeripheral *peripheral = [self findPeripheralByUUID:uuid];
384
+
385
+    if (peripheral && peripheral.state == CBPeripheralStateConnected) {
386
+        [readRSSICallbacks setObject:[command.callbackId copy] forKey:[peripheral uuidAsString]];
387
+        [peripheral readRSSI];
388
+    } else {
389
+        NSString *error = [NSString stringWithFormat:@"Need to be connected to peripheral %@ to read RSSI.", uuid];
390
+        NSLog(@"%@", error);
391
+        CDVPluginResult *pluginResult = nil;
392
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error];
393
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
394
+    }
395
+}
396
+
397
+// Returns a list of the peripherals (containing any of the specified services) currently connected to the system.
398
+// https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1518924-retrieveconnectedperipheralswith?language=objc
399
+- (void)connectedPeripheralsWithServices:(CDVInvokedUrlCommand*)command {
400
+    NSLog(@"connectedPeripheralsWithServices");
401
+    NSArray *serviceUUIDStrings = [command argumentAtIndex:0];
402
+    NSArray<CBUUID *> *serviceUUIDs = [self uuidStringsToCBUUIDs:serviceUUIDStrings];
403
+
404
+    NSArray<CBPeripheral *> *connectedPeripherals = [manager retrieveConnectedPeripheralsWithServices:serviceUUIDs];
405
+    NSMutableArray<NSDictionary *> *connected = [NSMutableArray new];
406
+
407
+    for (CBPeripheral *peripheral in connectedPeripherals) {
408
+        [peripherals addObject:peripheral];
409
+        [connected addObject:[peripheral asDictionary]];
410
+    }
411
+
412
+    CDVPluginResult *pluginResult = nil;
413
+    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:connected];
414
+    NSLog(@"Connected peripherals with services %@ %@", serviceUUIDStrings, connected);
415
+    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
416
+}
417
+
418
+// Returns a list of known peripherals by their identifiers.
419
+// https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1519127-retrieveperipheralswithidentifie?language=objc
420
+- (void)peripheralsWithIdentifiers:(CDVInvokedUrlCommand*)command {
421
+    NSLog(@"peripheralsWithIdentifiers");
422
+    NSArray *identifierUUIDStrings = [command argumentAtIndex:0];
423
+    NSArray<NSUUID *> *identifiers = [self uuidStringsToNSUUIDs:identifierUUIDStrings];
424
+    
425
+    NSArray<CBPeripheral *> *foundPeripherals = [manager retrievePeripheralsWithIdentifiers:identifiers];
426
+    // TODO are any of these connected?
427
+    NSMutableArray<NSDictionary *> *found = [NSMutableArray new];
428
+    
429
+    for (CBPeripheral *peripheral in foundPeripherals) {
430
+        [peripherals addObject:peripheral];   // TODO do we save these?
431
+        [found addObject:[peripheral asDictionary]];
432
+    }
433
+    
434
+    CDVPluginResult *pluginResult = nil;
435
+    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:found];
436
+    NSLog(@"Peripherals with identifiers %@ %@", identifierUUIDStrings, found);
437
+    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
438
+}
439
+
440
+
441
+#pragma mark - timers
442
+
443
+-(void)stopScanTimer:(NSTimer *)timer {
444
+    NSLog(@"stopScanTimer");
445
+
446
+    [manager stopScan];
447
+
448
+    if (discoverPeripheralCallbackId) {
449
+        discoverPeripheralCallbackId = nil;
450
+    }
451
+}
452
+
453
+#pragma mark - CBCentralManagerDelegate
454
+
455
+- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
456
+
457
+    [peripherals addObject:peripheral];
458
+    [peripheral setAdvertisementData:advertisementData RSSI:RSSI];
459
+
460
+    if (discoverPeripheralCallbackId) {
461
+        CDVPluginResult *pluginResult = nil;
462
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[peripheral asDictionary]];
463
+        NSLog(@"Discovered %@", [peripheral asDictionary]);
464
+        [pluginResult setKeepCallbackAsBool:TRUE];
465
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:discoverPeripheralCallbackId];
466
+    }
467
+}
468
+
469
+- (void)centralManagerDidUpdateState:(CBCentralManager *)central
470
+{
471
+    NSLog(@"Status of CoreBluetooth central manager changed %ld %@", (long)central.state, [self centralManagerStateToString: central.state]);
472
+
473
+    if (central.state == CBCentralManagerStateUnsupported)
474
+    {
475
+        NSLog(@"=============================================================");
476
+        NSLog(@"WARNING: This hardware does not support Bluetooth Low Energy.");
477
+        NSLog(@"=============================================================");
478
+    }
479
+
480
+    if (stateCallbackId != nil) {
481
+        CDVPluginResult *pluginResult = nil;
482
+        NSString *state = [bluetoothStates objectForKey:@(central.state)];
483
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:state];
484
+        [pluginResult setKeepCallbackAsBool:TRUE];
485
+        NSLog(@"Report Bluetooth state \"%@\" on callback %@", state, stateCallbackId);
486
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:stateCallbackId];
487
+    }
488
+
489
+    // check and handle disconnected peripherals
490
+    for (CBPeripheral *peripheral in peripherals) {
491
+        if (peripheral.state == CBPeripheralStateDisconnected) {
492
+            [self centralManager:central didDisconnectPeripheral:peripheral error:nil];
493
+        }
494
+    }
495
+}
496
+
497
+- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
498
+    NSLog(@"didConnectPeripheral");
499
+
500
+    peripheral.delegate = self;
501
+
502
+    // NOTE: it's inefficient to discover all services
503
+    [peripheral discoverServices:nil];
504
+
505
+    // NOTE: not calling connect success until characteristics are discovered
506
+}
507
+
508
+- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
509
+    NSLog(@"didDisconnectPeripheral");
510
+
511
+    NSString *connectCallbackId = [connectCallbacks valueForKey:[peripheral uuidAsString]];
512
+    [connectCallbacks removeObjectForKey:[peripheral uuidAsString]];
513
+    [self cleanupOperationCallbacks:peripheral withResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Peripheral disconnected"]];
514
+
515
+    if (connectCallbackId) {
516
+
517
+        NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[peripheral asDictionary]];
518
+
519
+        // add error info
520
+        [dict setObject:@"Peripheral Disconnected" forKey:@"errorMessage"];
521
+        if (error) {
522
+            [dict setObject:[error localizedDescription] forKey:@"errorDescription"];
523
+        }
524
+        // remove extra junk
525
+        [dict removeObjectForKey:@"rssi"];
526
+        [dict removeObjectForKey:@"advertising"];
527
+        [dict removeObjectForKey:@"services"];
528
+
529
+        CDVPluginResult *pluginResult = nil;
530
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dict];
531
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:connectCallbackId];
532
+    }
533
+}
534
+
535
+- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
536
+    NSLog(@"didFailToConnectPeripheral");
537
+
538
+    NSString *connectCallbackId = [connectCallbacks valueForKey:[peripheral uuidAsString]];
539
+    [connectCallbacks removeObjectForKey:[peripheral uuidAsString]];
540
+    [self cleanupOperationCallbacks:peripheral withResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Peripheral disconnected"]];
541
+
542
+    CDVPluginResult *pluginResult = nil;
543
+    pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[peripheral asDictionary]];
544
+    [self.commandDelegate sendPluginResult:pluginResult callbackId:connectCallbackId];
545
+}
546
+
547
+#pragma mark CBPeripheralDelegate
548
+
549
+- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
550
+    NSLog(@"didDiscoverServices");
551
+
552
+    // save the services to tell when all characteristics have been discovered
553
+    NSMutableSet *servicesForPeriperal = [NSMutableSet new];
554
+    [servicesForPeriperal addObjectsFromArray:peripheral.services];
555
+    [connectCallbackLatches setObject:servicesForPeriperal forKey:[peripheral uuidAsString]];
556
+
557
+    for (CBService *service in peripheral.services) {
558
+        [peripheral discoverCharacteristics:nil forService:service]; // discover all is slow
559
+    }
560
+}
561
+
562
+- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
563
+    NSLog(@"didDiscoverCharacteristicsForService");
564
+
565
+    NSString *peripheralUUIDString = [peripheral uuidAsString];
566
+    NSString *connectCallbackId = [connectCallbacks valueForKey:peripheralUUIDString];
567
+    NSMutableSet *latch = [connectCallbackLatches valueForKey:peripheralUUIDString];
568
+
569
+    [latch removeObject:service];
570
+
571
+    if ([latch count] == 0) {
572
+        // Call success callback for connect
573
+        if (connectCallbackId) {
574
+            CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[peripheral asDictionary]];
575
+            [pluginResult setKeepCallbackAsBool:TRUE];
576
+            [self.commandDelegate sendPluginResult:pluginResult callbackId:connectCallbackId];
577
+        }
578
+        [connectCallbackLatches removeObjectForKey:peripheralUUIDString];
579
+    }
580
+
581
+    NSLog(@"Found characteristics for service %@", service);
582
+    for (CBCharacteristic *characteristic in service.characteristics) {
583
+        NSLog(@"Characteristic %@", characteristic);
584
+    }
585
+}
586
+
587
+- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
588
+    NSLog(@"didUpdateValueForCharacteristic");
589
+
590
+    NSString *key = [self keyForPeripheral: peripheral andCharacteristic:characteristic];
591
+    NSString *notifyCallbackId = [notificationCallbacks objectForKey:key];
592
+
593
+    if (notifyCallbackId) {
594
+        NSData *data = characteristic.value; // send RAW data to Javascript
595
+
596
+        CDVPluginResult *pluginResult = nil;
597
+        if (error) {
598
+            NSLog(@"%@", error);
599
+            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]];
600
+        } else {
601
+            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:data];
602
+        }
603
+
604
+        [pluginResult setKeepCallbackAsBool:TRUE]; // keep for notification
605
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:notifyCallbackId];
606
+    }
607
+
608
+    NSString *readCallbackId = [readCallbacks objectForKey:key];
609
+
610
+    if(readCallbackId) {
611
+        NSData *data = characteristic.value; // send RAW data to Javascript
612
+        CDVPluginResult *pluginResult = nil;
613
+        
614
+        if (error) {
615
+            NSLog(@"%@", error);
616
+            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]];
617
+        } else {
618
+            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:data];
619
+        }
620
+        
621
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:readCallbackId];
622
+
623
+        [readCallbacks removeObjectForKey:key];
624
+    }
625
+}
626
+
627
+- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
628
+    NSString *key = [self keyForPeripheral: peripheral andCharacteristic:characteristic];
629
+    NSString *startNotificationCallbackId = [startNotificationCallbacks objectForKey:key];
630
+    NSString *stopNotificationCallbackId = [stopNotificationCallbacks objectForKey:key];
631
+
632
+    CDVPluginResult *pluginResult = nil;
633
+
634
+    // we always call the stopNotificationCallbackId if we have a callback
635
+    // we only call the notificationCallbackId on errors and if there is no stopNotificationCallbackId
636
+
637
+    if (stopNotificationCallbackId) {
638
+        if (error) {
639
+            NSLog(@"%@", error);
640
+            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]];
641
+        } else {
642
+            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
643
+        }
644
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:stopNotificationCallbackId];
645
+        [stopNotificationCallbacks removeObjectForKey:key];
646
+        [notificationCallbacks removeObjectForKey:key];
647
+        NSAssert(![startNotificationCallbacks objectForKey:key], @"%@ existed in both start and stop notification callback dicts!", key);
648
+    }
649
+    
650
+    if (startNotificationCallbackId) {
651
+        if (error) {
652
+            NSLog(@"%@", error);
653
+            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]];
654
+            [self.commandDelegate sendPluginResult:pluginResult callbackId:startNotificationCallbackId];
655
+            [startNotificationCallbacks removeObjectForKey:key];
656
+        } else {
657
+            // notification start succeeded, move the callback to the value notifications dict
658
+            [notificationCallbacks setObject:startNotificationCallbackId forKey:key];
659
+            [startNotificationCallbacks removeObjectForKey:key];
660
+        }
661
+    }
662
+}
663
+
664
+
665
+- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
666
+    // This is the callback for write
667
+
668
+    NSString *key = [self keyForPeripheral: peripheral andCharacteristic:characteristic];
669
+    NSString *writeCallbackId = [writeCallbacks objectForKey:key];
670
+
671
+    if (writeCallbackId) {
672
+        CDVPluginResult *pluginResult = nil;
673
+        if (error) {
674
+            NSLog(@"%@", error);
675
+            pluginResult = [CDVPluginResult
676
+                resultWithStatus:CDVCommandStatus_ERROR
677
+                messageAsString:[error localizedDescription]
678
+            ];
679
+        } else {
680
+            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
681
+        }
682
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:writeCallbackId];
683
+        [writeCallbacks removeObjectForKey:key];
684
+    }
685
+
686
+}
687
+
688
+- (void)peripheral:(CBPeripheral*)peripheral didReadRSSI:(NSNumber*)rssi error:(NSError*)error {
689
+    NSLog(@"didReadRSSI %@", rssi);
690
+    NSString *key = [peripheral uuidAsString];
691
+    NSString *readRSSICallbackId = [readRSSICallbacks objectForKey: key];
692
+    [peripheral setSavedRSSI:rssi];
693
+    if (readRSSICallbackId) {
694
+        CDVPluginResult* pluginResult = nil;
695
+        if (error) {
696
+            NSLog(@"%@", error);
697
+            pluginResult = [CDVPluginResult
698
+                resultWithStatus:CDVCommandStatus_ERROR
699
+                messageAsString:[error localizedDescription]];
700
+        } else {
701
+            pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
702
+                messageAsInt: (int) [rssi integerValue]];
703
+        }
704
+        [self.commandDelegate sendPluginResult:pluginResult callbackId: readRSSICallbackId];
705
+        [readRSSICallbacks removeObjectForKey:readRSSICallbackId];
706
+    }
707
+}
708
+
709
+#pragma mark - internal implemetation
710
+
711
+- (CBPeripheral*)findPeripheralByUUID:(NSString*)uuid {
712
+    CBPeripheral *peripheral = nil;
713
+
714
+    for (CBPeripheral *p in peripherals) {
715
+
716
+        NSString* other = p.identifier.UUIDString;
717
+
718
+        if ([uuid isEqualToString:other]) {
719
+            peripheral = p;
720
+            break;
721
+        }
722
+    }
723
+    return peripheral;
724
+}
725
+
726
+// RedBearLab
727
+-(CBService *) findServiceFromUUID:(CBUUID *)UUID p:(CBPeripheral *)p {
728
+    for(int i = 0; i < p.services.count; i++) {
729
+        CBService *s = [p.services objectAtIndex:i];
730
+        if ([self compareCBUUID:s.UUID UUID2:UUID])
731
+            return s;
732
+    }
733
+
734
+    return nil; //Service not found on this peripheral
735
+}
736
+
737
+// Find a characteristic in service with a specific property
738
+-(CBCharacteristic *) findCharacteristicFromUUID:(CBUUID *)UUID service:(CBService*)service prop:(CBCharacteristicProperties)prop {
739
+    NSLog(@"Looking for %@ with properties %lu", UUID, (unsigned long)prop);
740
+    for(int i=0; i < service.characteristics.count; i++)
741
+    {
742
+        CBCharacteristic *c = [service.characteristics objectAtIndex:i];
743
+        if ((c.properties & prop) != 0x0 && [c.UUID.UUIDString isEqualToString: UUID.UUIDString]) {
744
+            return c;
745
+        }
746
+    }
747
+   return nil; //Characteristic with prop not found on this service
748
+}
749
+
750
+// Find a characteristic in service by UUID
751
+-(CBCharacteristic *) findCharacteristicFromUUID:(CBUUID *)UUID service:(CBService*)service {
752
+    NSLog(@"Looking for %@", UUID);
753
+    for(int i=0; i < service.characteristics.count; i++)
754
+    {
755
+        CBCharacteristic *c = [service.characteristics objectAtIndex:i];
756
+        if ([c.UUID.UUIDString isEqualToString: UUID.UUIDString]) {
757
+            return c;
758
+        }
759
+    }
760
+   return nil; //Characteristic not found on this service
761
+}
762
+
763
+// RedBearLab
764
+-(int) compareCBUUID:(CBUUID *) UUID1 UUID2:(CBUUID *)UUID2 {
765
+    char b1[16];
766
+    char b2[16];
767
+    [UUID1.data getBytes:b1 length:16];
768
+    [UUID2.data getBytes:b2 length:16];
769
+
770
+    if (memcmp(b1, b2, UUID1.data.length) == 0)
771
+        return 1;
772
+    else
773
+        return 0;
774
+}
775
+
776
+// expecting deviceUUID, serviceUUID, characteristicUUID in command.arguments
777
+-(BLECommandContext*) getData:(CDVInvokedUrlCommand*)command prop:(CBCharacteristicProperties)prop {
778
+    NSLog(@"getData");
779
+
780
+    CDVPluginResult *pluginResult = nil;
781
+
782
+    NSString *deviceUUIDString = [command argumentAtIndex:0];
783
+    NSString *serviceUUIDString = [command argumentAtIndex:1];
784
+    NSString *characteristicUUIDString = [command argumentAtIndex:2];
785
+
786
+    CBUUID *serviceUUID = [CBUUID UUIDWithString:serviceUUIDString];
787
+    CBUUID *characteristicUUID = [CBUUID UUIDWithString:characteristicUUIDString];
788
+
789
+    CBPeripheral *peripheral = [self findPeripheralByUUID:deviceUUIDString];
790
+
791
+    if (!peripheral) {
792
+
793
+        NSLog(@"Could not find peripheral with UUID %@", deviceUUIDString);
794
+
795
+        NSString *errorMessage = [NSString stringWithFormat:@"Could not find peripheral with UUID %@", deviceUUIDString];
796
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage];
797
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
798
+
799
+        return nil;
800
+    }
801
+
802
+    CBService *service = [self findServiceFromUUID:serviceUUID p:peripheral];
803
+
804
+    if (!service)
805
+    {
806
+        NSLog(@"Could not find service with UUID %@ on peripheral with UUID %@",
807
+              serviceUUIDString,
808
+              peripheral.identifier.UUIDString);
809
+
810
+
811
+        NSString *errorMessage = [NSString stringWithFormat:@"Could not find service with UUID %@ on peripheral with UUID %@",
812
+                                  serviceUUIDString,
813
+                                  peripheral.identifier.UUIDString];
814
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage];
815
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
816
+
817
+        return nil;
818
+    }
819
+
820
+    CBCharacteristic *characteristic = [self findCharacteristicFromUUID:characteristicUUID service:service prop:prop];
821
+
822
+    // Special handling for INDICATE. If charateristic with notify is not found, check for indicate.
823
+    if (prop == CBCharacteristicPropertyNotify && !characteristic) {
824
+        characteristic = [self findCharacteristicFromUUID:characteristicUUID service:service prop:CBCharacteristicPropertyIndicate];
825
+    }
826
+
827
+    // As a last resort, try and find ANY characteristic with this UUID, even if it doesn't have the correct properties
828
+    if (!characteristic) {
829
+        characteristic = [self findCharacteristicFromUUID:characteristicUUID service:service];
830
+    }
831
+
832
+    if (!characteristic)
833
+    {
834
+        NSLog(@"Could not find characteristic with UUID %@ on service with UUID %@ on peripheral with UUID %@",
835
+              characteristicUUIDString,
836
+              serviceUUIDString,
837
+              peripheral.identifier.UUIDString);
838
+
839
+        NSString *errorMessage = [NSString stringWithFormat:
840
+                                  @"Could not find characteristic with UUID %@ on service with UUID %@ on peripheral with UUID %@",
841
+                                  characteristicUUIDString,
842
+                                  serviceUUIDString,
843
+                                  peripheral.identifier.UUIDString];
844
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage];
845
+        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
846
+
847
+        return nil;
848
+    }
849
+
850
+    BLECommandContext *context = [[BLECommandContext alloc] init];
851
+    [context setPeripheral:peripheral];
852
+    [context setService:service];
853
+    [context setCharacteristic:characteristic];
854
+    return context;
855
+}
856
+
857
+-(NSString *) keyForPeripheral: (CBPeripheral *)peripheral andCharacteristic:(CBCharacteristic *)characteristic {
858
+    return [NSString stringWithFormat:@"%@|%@|%@", [peripheral uuidAsString], [characteristic.service UUID], [characteristic UUID]];
859
+}
860
+
861
++(BOOL) isKey: (NSString *)key forPeripheral:(CBPeripheral *)peripheral {
862
+    NSArray *keyArray = [key componentsSeparatedByString: @"|"];
863
+    return [[peripheral uuidAsString] compare:keyArray[0]] == NSOrderedSame;
864
+}
865
+
866
+-(void) cleanupOperationCallbacks: (CBPeripheral *)peripheral withResult:(CDVPluginResult *) result {
867
+    for(id key in readCallbacks.allKeys) {
868
+        if([BLECentralPlugin isKey:key forPeripheral:peripheral]) {
869
+            NSString *callbackId = [readCallbacks valueForKey:key];
870
+            [self.commandDelegate sendPluginResult:result callbackId:callbackId];
871
+            [readCallbacks removeObjectForKey:key];
872
+            NSLog(@"Cleared read callback %@ for key %@", callbackId, key);
873
+        }
874
+    }
875
+    for(id key in writeCallbacks.allKeys) {
876
+        if([BLECentralPlugin isKey:key forPeripheral:peripheral]) {
877
+            NSString *callbackId = [writeCallbacks valueForKey:key];
878
+            [self.commandDelegate sendPluginResult:result callbackId:callbackId];
879
+            [writeCallbacks removeObjectForKey:key];
880
+            NSLog(@"Cleared write callback %@ for key %@", callbackId, key);
881
+        }
882
+    }
883
+    for(id key in startNotificationCallbacks.allKeys) {
884
+        if([BLECentralPlugin isKey:key forPeripheral:peripheral]) {
885
+            NSString *callbackId = [startNotificationCallbacks valueForKey:key];
886
+            [self.commandDelegate sendPluginResult:result callbackId:callbackId];
887
+            [startNotificationCallbacks removeObjectForKey:key];
888
+            NSLog(@"Cleared start notification callback %@ for key %@", callbackId, key);
889
+        }
890
+    }
891
+    for(id key in stopNotificationCallbacks.allKeys) {
892
+        if([BLECentralPlugin isKey:key forPeripheral:peripheral]) {
893
+            NSString *callbackId = [stopNotificationCallbacks valueForKey:key];
894
+            [self.commandDelegate sendPluginResult:result callbackId:callbackId];
895
+            [stopNotificationCallbacks removeObjectForKey:key];
896
+            NSLog(@"Cleared stop notification callback %@ for key %@", callbackId, key);
897
+        }
898
+    }
899
+    [notificationCallbacks removeAllObjects];
900
+}
901
+
902
+#pragma mark - util
903
+
904
+- (NSString*) centralManagerStateToString: (int)state {
905
+    switch(state)
906
+    {
907
+        case CBCentralManagerStateUnknown:
908
+            return @"State unknown (CBCentralManagerStateUnknown)";
909
+        case CBCentralManagerStateResetting:
910
+            return @"State resetting (CBCentralManagerStateUnknown)";
911
+        case CBCentralManagerStateUnsupported:
912
+            return @"State BLE unsupported (CBCentralManagerStateResetting)";
913
+        case CBCentralManagerStateUnauthorized:
914
+            return @"State unauthorized (CBCentralManagerStateUnauthorized)";
915
+        case CBCentralManagerStatePoweredOff:
916
+            return @"State BLE powered off (CBCentralManagerStatePoweredOff)";
917
+        case CBCentralManagerStatePoweredOn:
918
+            return @"State powered up and ready (CBCentralManagerStatePoweredOn)";
919
+        default:
920
+            return @"State unknown";
921
+    }
922
+
923
+    return @"Unknown state";
924
+}
925
+
926
+- (NSArray<CBUUID *> *) uuidStringsToCBUUIDs: (NSArray<NSString *> *)uuidStrings {
927
+    NSMutableArray *uuids = [NSMutableArray new];
928
+    for (int i = 0; i < [uuidStrings count]; i++) {
929
+        CBUUID *uuid = [CBUUID UUIDWithString:[uuidStrings objectAtIndex: i]];
930
+        [uuids addObject:uuid];
931
+    }
932
+    return uuids;
933
+}
934
+
935
+- (NSArray<NSUUID *> *) uuidStringsToNSUUIDs: (NSArray<NSString *> *)uuidStrings {
936
+    NSMutableArray *uuids = [NSMutableArray new];
937
+    for (int i = 0; i < [uuidStrings count]; i++) {
938
+        NSUUID *uuid = [[NSUUID alloc]initWithUUIDString:[uuidStrings objectAtIndex: i]];
939
+        [uuids addObject:uuid];
940
+    }
941
+    return uuids;
942
+}
943
+
944
+@end

+ 16 - 0
plugins/cordova-plugin-ble-central/src/ios/BLECommandContext.h

@@ -0,0 +1,16 @@
1
+//
2
+//  BLECommandContext
3
+//  Holds peripherial, service and characteristic
4
+//
5
+
6
+#import <Foundation/Foundation.h>
7
+#import <CoreBluetooth/CoreBluetooth.h>
8
+
9
+@interface BLECommandContext : NSObject
10
+
11
+@property CBPeripheral *peripheral;
12
+@property CBService *service;
13
+@property CBCharacteristic *characteristic;
14
+
15
+@end
16
+

+ 10 - 0
plugins/cordova-plugin-ble-central/src/ios/BLECommandContext.m

@@ -0,0 +1,10 @@
1
+//
2
+//  BLECommandContext.m
3
+//  Holds peripherial, service and characteristic
4
+//
5
+
6
+#import "BLECommandContext.h"
7
+
8
+@implementation BLECommandContext
9
+
10
+@end

+ 37 - 0
plugins/cordova-plugin-ble-central/src/ios/CBPeripheral+Extensions.h

@@ -0,0 +1,37 @@
1
+//
2
+//  CBPeripheral+Extensions.h
3
+//  BLE Central Cordova Plugin
4
+//
5
+//  (c) 2104 Don Coleman
6
+//
7
+// Licensed under the Apache License, Version 2.0 (the "License");
8
+// you may not use this file except in compliance with the License.
9
+// You may obtain a copy of the License at
10
+//
11
+//     http://www.apache.org/licenses/LICENSE-2.0
12
+//
13
+// Unless required by applicable law or agreed to in writing, software
14
+// distributed under the License is distributed on an "AS IS" BASIS,
15
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+// See the License for the specific language governing permissions and
17
+// limitations under the License.
18
+
19
+#import <objc/runtime.h>
20
+#import <Foundation/Foundation.h>
21
+#import <CoreBluetooth/CoreBluetooth.h>
22
+#import <Cordova/CDV.h>
23
+
24
+
25
+@interface CBPeripheral(com_megster_ble_extension)
26
+
27
+@property (nonatomic, retain) NSDictionary *advertising;
28
+@property (nonatomic, retain) NSNumber *savedRSSI;
29
+
30
+-(void)setAdvertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber*)rssi;
31
+-(NSDictionary *)asDictionary;
32
+-(NSString *)uuidAsString;
33
+
34
+@end
35
+
36
+
37
+

+ 271 - 0
plugins/cordova-plugin-ble-central/src/ios/CBPeripheral+Extensions.m

@@ -0,0 +1,271 @@
1
+//
2
+//  CBPeripheral+Extensions.m
3
+//  BLE Central Cordova Plugin
4
+//
5
+//  (c) 2104 Don Coleman
6
+//
7
+// Licensed under the Apache License, Version 2.0 (the "License");
8
+// you may not use this file except in compliance with the License.
9
+// You may obtain a copy of the License at
10
+//
11
+//     http://www.apache.org/licenses/LICENSE-2.0
12
+//
13
+// Unless required by applicable law or agreed to in writing, software
14
+// distributed under the License is distributed on an "AS IS" BASIS,
15
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+// See the License for the specific language governing permissions and
17
+// limitations under the License.
18
+
19
+#import "CBPeripheral+Extensions.h"
20
+
21
+static char ADVERTISING_IDENTIFER;
22
+static char SAVED_RSSI_IDENTIFER;
23
+
24
+static NSDictionary *dataToArrayBuffer(NSData* data) {
25
+    return @{
26
+             @"CDVType" : @"ArrayBuffer",
27
+             @"data" :[data base64EncodedStringWithOptions:0]
28
+             };
29
+}
30
+
31
+@implementation CBPeripheral(com_megster_ble_extension)
32
+
33
+-(NSString *)uuidAsString {
34
+    if (self.identifier.UUIDString) {
35
+        return self.identifier.UUIDString;
36
+    } else {
37
+        return @"";
38
+    }
39
+}
40
+
41
+
42
+-(NSDictionary *)asDictionary {
43
+    NSString *uuidString = NULL;
44
+    if (self.identifier.UUIDString) {
45
+        uuidString = self.identifier.UUIDString;
46
+    } else {
47
+        uuidString = @"";
48
+    }
49
+
50
+    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
51
+    [dictionary setObject: uuidString forKey: @"id"];
52
+
53
+    if ([self name]) {
54
+        [dictionary setObject: [self name] forKey: @"name"];
55
+    }
56
+
57
+    if ([self savedRSSI]) {
58
+        [dictionary setObject: [self savedRSSI] forKey: @"rssi"];
59
+    }
60
+
61
+    if ([self advertising]) {
62
+        [dictionary setObject: [self advertising] forKey: @"advertising"];
63
+    }
64
+
65
+    if([[self services] count] > 0) {
66
+        [self serviceAndCharacteristicInfo: dictionary];
67
+    }
68
+
69
+    return dictionary;
70
+}
71
+
72
+// AdvertisementData is from didDiscoverPeripheral. RFduino advertises a service name in the Mfg Data Field.
73
+-(void)setAdvertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)rssi {
74
+    [self setAdvertising:[self serializableAdvertisementData: advertisementData]];
75
+    [self setSavedRSSI: rssi];
76
+}
77
+
78
+// Translates the Advertisement Data from didDiscoverPeripheral into a structure that can be serialized as JSON
79
+//
80
+// This version keeps the iOS constants for keys, future versions could create more friendly keys
81
+//
82
+// Advertisement Data from a Peripheral could look something like
83
+//
84
+// advertising = {
85
+//     kCBAdvDataChannel = 39;
86
+//     kCBAdvDataIsConnectable = 1;
87
+//     kCBAdvDataLocalName = foo;
88
+//     kCBAdvDataManufacturerData = {
89
+//         CDVType = ArrayBuffer;
90
+//         data = "AABoZWxsbw==";
91
+//     };
92
+//     kCBAdvDataServiceData = {
93
+//         FED8 = {
94
+//             CDVType = ArrayBuffer;
95
+//             data = "ACAAYWJjBw==";
96
+//         };
97
+//     };
98
+//     kCBAdvDataServiceUUIDs = (
99
+//         FED8
100
+//     );
101
+//     kCBAdvDataTxPowerLevel = 32;
102
+//};
103
+- (NSDictionary *) serializableAdvertisementData: (NSDictionary *) advertisementData {
104
+    NSMutableDictionary *dict = [advertisementData mutableCopy];
105
+
106
+    // Service Data is a dictionary of CBUUID and NSData
107
+    // Convert to String keys with Array Buffer values
108
+    NSMutableDictionary *serviceData = [dict objectForKey:CBAdvertisementDataServiceDataKey];
109
+    if (serviceData) {
110
+        NSLog(@"%@", serviceData);
111
+
112
+        for (CBUUID *key in [serviceData allKeys]) {
113
+            [serviceData setObject:dataToArrayBuffer([serviceData objectForKey:key]) forKey:[key UUIDString]];
114
+            [serviceData removeObjectForKey:key];
115
+        }
116
+    }
117
+
118
+    // Create a new list of Service UUIDs as Strings instead of CBUUIDs
119
+    NSMutableArray *serviceUUIDs = [dict objectForKey:CBAdvertisementDataServiceUUIDsKey];
120
+    NSMutableArray *serviceUUIDStrings;
121
+    if (serviceUUIDs) {
122
+        serviceUUIDStrings = [[NSMutableArray alloc] initWithCapacity:serviceUUIDs.count];
123
+
124
+        for (CBUUID *uuid in serviceUUIDs) {
125
+            [serviceUUIDStrings addObject:[uuid UUIDString]];
126
+        }
127
+
128
+        // replace the UUID list with list of strings
129
+        [dict removeObjectForKey:CBAdvertisementDataServiceUUIDsKey];
130
+        [dict setObject:serviceUUIDStrings forKey:CBAdvertisementDataServiceUUIDsKey];
131
+
132
+    }
133
+
134
+    // Solicited Services UUIDs is an array of CBUUIDs, convert into Strings
135
+    NSMutableArray *solicitiedServiceUUIDs = [dict objectForKey:CBAdvertisementDataSolicitedServiceUUIDsKey];
136
+    NSMutableArray *solicitiedServiceUUIDStrings;
137
+    if (solicitiedServiceUUIDs) {
138
+        // NSLog(@"%@", solicitiedServiceUUIDs);
139
+        solicitiedServiceUUIDStrings = [[NSMutableArray alloc] initWithCapacity:solicitiedServiceUUIDs.count];
140
+
141
+        for (CBUUID *uuid in solicitiedServiceUUIDs) {
142
+            [solicitiedServiceUUIDStrings addObject:[uuid UUIDString]];
143
+        }
144
+
145
+        // replace the UUID list with list of strings
146
+        [dict removeObjectForKey:CBAdvertisementDataSolicitedServiceUUIDsKey];
147
+        [dict setObject:solicitiedServiceUUIDStrings forKey:CBAdvertisementDataSolicitedServiceUUIDsKey];
148
+    }
149
+
150
+    // Convert the manufacturer data
151
+    NSData *mfgData = [dict objectForKey:CBAdvertisementDataManufacturerDataKey];
152
+    if (mfgData) {
153
+        [dict setObject:dataToArrayBuffer([dict objectForKey:CBAdvertisementDataManufacturerDataKey]) forKey:CBAdvertisementDataManufacturerDataKey];
154
+    }
155
+
156
+    return dict;
157
+}
158
+
159
+// Put the service, characteristic, and descriptor data in a format that will serialize through JSON
160
+// sending a list of services and a list of characteristics
161
+- (void) serviceAndCharacteristicInfo: (NSMutableDictionary *) info {
162
+    NSMutableArray *serviceList = [NSMutableArray new];
163
+    NSMutableArray *characteristicList = [NSMutableArray new];
164
+
165
+    // This can move into the CBPeripherial Extension
166
+    for (CBService *service in [self services]) {
167
+        [serviceList addObject:[[service UUID] UUIDString]];
168
+        for (CBCharacteristic *characteristic in service.characteristics) {
169
+            NSMutableDictionary *characteristicDictionary = [NSMutableDictionary new];
170
+            [characteristicDictionary setObject:[[service UUID] UUIDString] forKey:@"service"];
171
+            [characteristicDictionary setObject:[[characteristic UUID] UUIDString] forKey:@"characteristic"];
172
+
173
+            if ([characteristic value]) {
174
+                [characteristicDictionary setObject:dataToArrayBuffer([characteristic value]) forKey:@"value"];
175
+            }
176
+            if ([characteristic properties]) {
177
+                //[characteristicDictionary setObject:[NSNumber numberWithInt:[characteristic properties]] forKey:@"propertiesValue"];
178
+                [characteristicDictionary setObject:[self decodeCharacteristicProperties:characteristic] forKey:@"properties"];
179
+            }
180
+            // permissions only exist on CBMutableCharacteristics
181
+            [characteristicDictionary setObject:[NSNumber numberWithBool:[characteristic isNotifying]] forKey:@"isNotifying"];
182
+            [characteristicList addObject:characteristicDictionary];
183
+
184
+            // descriptors always seem to be nil, probably a bug here
185
+            NSMutableArray *descriptorList = [NSMutableArray new];
186
+            for (CBDescriptor *descriptor in characteristic.descriptors) {
187
+                NSMutableDictionary *descriptorDictionary = [NSMutableDictionary new];
188
+                [descriptorDictionary setObject:[[descriptor UUID] UUIDString] forKey:@"descriptor"];
189
+                if ([descriptor value]) { // should always have a value?
190
+                    [descriptorDictionary setObject:[descriptor value] forKey:@"value"];
191
+                }
192
+                [descriptorList addObject:descriptorDictionary];
193
+            }
194
+            if ([descriptorList count] > 0) {
195
+                [characteristicDictionary setObject:descriptorList forKey:@"descriptors"];
196
+            }
197
+
198
+        }
199
+    }
200
+
201
+    [info setObject:serviceList forKey:@"services"];
202
+    [info setObject:characteristicList forKey:@"characteristics"];
203
+}
204
+
205
+-(NSArray *) decodeCharacteristicProperties: (CBCharacteristic *) characteristic {
206
+    NSMutableArray *props = [NSMutableArray new];
207
+
208
+    CBCharacteristicProperties p = [characteristic properties];
209
+
210
+    // NOTE: props strings need to be consistent across iOS and Android
211
+    if ((p & CBCharacteristicPropertyBroadcast) != 0x0) {
212
+        [props addObject:@"Broadcast"];
213
+    }
214
+
215
+    if ((p & CBCharacteristicPropertyRead) != 0x0) {
216
+        [props addObject:@"Read"];
217
+    }
218
+
219
+    if ((p & CBCharacteristicPropertyWriteWithoutResponse) != 0x0) {
220
+        [props addObject:@"WriteWithoutResponse"];
221
+    }
222
+
223
+    if ((p & CBCharacteristicPropertyWrite) != 0x0) {
224
+        [props addObject:@"Write"];
225
+    }
226
+
227
+    if ((p & CBCharacteristicPropertyNotify) != 0x0) {
228
+        [props addObject:@"Notify"];
229
+    }
230
+
231
+    if ((p & CBCharacteristicPropertyIndicate) != 0x0) {
232
+        [props addObject:@"Indicate"];
233
+    }
234
+
235
+    if ((p & CBCharacteristicPropertyAuthenticatedSignedWrites) != 0x0) {
236
+        [props addObject:@"AutheticateSignedWrites"];
237
+    }
238
+
239
+    if ((p & CBCharacteristicPropertyExtendedProperties) != 0x0) {
240
+        [props addObject:@"ExtendedProperties"];
241
+    }
242
+
243
+    if ((p & CBCharacteristicPropertyNotifyEncryptionRequired) != 0x0) {
244
+        [props addObject:@"NotifyEncryptionRequired"];
245
+    }
246
+
247
+    if ((p & CBCharacteristicPropertyIndicateEncryptionRequired) != 0x0) {
248
+        [props addObject:@"IndicateEncryptionRequired"];
249
+    }
250
+
251
+    return props;
252
+}
253
+
254
+-(void)setAdvertising:(NSDictionary *)newAdvertisingValue{
255
+    objc_setAssociatedObject(self, &ADVERTISING_IDENTIFER, newAdvertisingValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
256
+}
257
+
258
+-(NSString*)advertising{
259
+    return objc_getAssociatedObject(self, &ADVERTISING_IDENTIFER);
260
+}
261
+
262
+
263
+-(void)setSavedRSSI:(NSNumber *)newSavedRSSIValue {
264
+    objc_setAssociatedObject(self, &SAVED_RSSI_IDENTIFER, newSavedRSSIValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
265
+}
266
+
267
+-(NSString*)savedRSSI{
268
+    return objc_getAssociatedObject(self, &SAVED_RSSI_IDENTIFER);
269
+}
270
+
271
+@end

+ 132 - 0
plugins/cordova-plugin-ble-central/src/wp/BLECentralPlugin.cs

@@ -0,0 +1,132 @@
1
+//
2
+//  BLE Central Cordova Plugin
3
+//
4
+//  (c) 2105 Don Coleman
5
+//
6
+// Licensed under the Apache License, Version 2.0 (the "License");
7
+// you may not use this file except in compliance with the License.
8
+// You may obtain a copy of the License at
9
+//
10
+//     http://www.apache.org/licenses/LICENSE-2.0
11
+//
12
+// Unless required by applicable law or agreed to in writing, software
13
+// distributed under the License is distributed on an "AS IS" BASIS,
14
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+// See the License for the specific language governing permissions and
16
+// limitations under the License.
17
+
18
+using System;
19
+using System.Linq;
20
+using System.Collections.Generic;
21
+using System.Diagnostics;
22
+using System.Runtime.Serialization;
23
+using Windows.Networking.Proximity;
24
+using WPCordovaClassLib.Cordova;
25
+using WPCordovaClassLib.Cordova.Commands;
26
+using WPCordovaClassLib.Cordova.JSON;
27
+using Microsoft.Phone.Tasks;
28
+
29
+using Windows.Networking;
30
+using System.Text;
31
+using System.Threading;
32
+
33
+public class BLECentralPlugin : BaseCommand
34
+{
35
+
36
+    private void notImplemented()
37
+    {
38
+      DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Not Implemented"));
39
+    }
40
+
41
+    public void scan(string args)
42
+    {
43
+      notImplemented();
44
+    }
45
+    public void startScan(string args)
46
+    {
47
+      notImplemented();
48
+    }
49
+    public void stopScan(string args)
50
+    {
51
+      notImplemented();
52
+    }
53
+    public void startScanWithOptions(string args)
54
+    {
55
+      notImplemented();
56
+    }
57
+    public void connect(string args)
58
+    {
59
+      notImplemented();
60
+    }
61
+    public void disconnect(string args)
62
+    {
63
+      notImplemented();
64
+    }
65
+    public void read(string args)
66
+    {
67
+      notImplemented();
68
+    }
69
+    public void readRSSI(string args) 
70
+    {
71
+      notImplemented();
72
+    }
73
+    public void write(string args)
74
+    {
75
+      notImplemented();
76
+    }
77
+    public void writeWithoutResponse(string args)
78
+    {
79
+      notImplemented();
80
+    }
81
+    public void startNotification(string args)
82
+    {
83
+      notImplemented();
84
+    }
85
+    public void stopNotification(string args)
86
+    {
87
+      notImplemented();
88
+    }
89
+    public void isConnected(string args)
90
+    {
91
+      notImplemented();
92
+    }
93
+
94
+    public async void isEnabled(string args)
95
+    {
96
+        string callbackId = JsonHelper.Deserialize<string[]>(args)[0];
97
+
98
+        // This is a bad way to do this, improve later
99
+        // See if we can determine in the Connection Manager
100
+        // https://msdn.microsoft.com/library/windows/apps/jj207007(v=vs.105).aspx
101
+        PeerFinder.AlternateIdentities["Bluetooth:Paired"] = "";
102
+
103
+        try
104
+        {
105
+            var peers = await PeerFinder.FindAllPeersAsync();
106
+
107
+            // Handle the result of the FindAllPeersAsync call
108
+        }
109
+        catch (Exception ex)
110
+        {
111
+            if ((uint)ex.HResult == 0x8007048F)
112
+            {
113
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR), callbackId);
114
+            }
115
+            else
116
+            {
117
+                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ex.Message), callbackId);
118
+            }
119
+        }
120
+
121
+        DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId);
122
+    }
123
+
124
+    public void showBluetoothSettings(string args)
125
+    {
126
+        ConnectionSettingsTask connectionSettingsTask = new ConnectionSettingsTask();
127
+        connectionSettingsTask.ConnectionSettingsType = ConnectionSettingsType.Bluetooth;
128
+        connectionSettingsTask.Show();
129
+        DispatchCommandResult(new PluginResult(PluginResult.Status.OK));
130
+    }
131
+
132
+}

+ 11 - 0
plugins/cordova-plugin-ble-central/tests/plugin.xml

@@ -0,0 +1,11 @@
1
+<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
2
+    xmlns:rim="http://www.blackberry.com/ns/widgets"
3
+    xmlns:android="http://schemas.android.com/apk/res/android"
4
+    id="com.megster.cordova.ble.tests"
5
+    version="0.1.9-dev">
6
+    <name>Bluetooth Low Energy Plugin Tests</name>
7
+    <license>Apache 2.0</license>
8
+
9
+    <js-module src="tests.js" name="tests">
10
+    </js-module>
11
+</plugin>

+ 71 - 0
plugins/cordova-plugin-ble-central/tests/tests.js

@@ -0,0 +1,71 @@
1
+exports.defineAutoTests = function () {
2
+
3
+    describe('BLE object', function () {
4
+        it("ble should exist", function () {
5
+            expect(ble).toBeDefined();
6
+        });
7
+
8
+        it("should contain a startScan function", function () {
9
+            expect(typeof ble.startScan).toBeDefined();
10
+            expect(typeof ble.startScan).toBe("function");
11
+        });
12
+    });
13
+
14
+};
15
+
16
+exports.defineManualTests = function (contentEl, createActionButton) {
17
+
18
+    createActionButton('Is Bluetooth Enabled?', function() {
19
+
20
+        ble.isEnabled(
21
+            function() {
22
+                console.log("Bluetooth is enabled");
23
+            },
24
+            function() {
25
+                console.log("Bluetooth is *not* enabled");
26
+            }
27
+        );
28
+    });
29
+
30
+
31
+    if (cordova.platformId !== 'ios') {
32
+
33
+        // not supported on iOS
34
+        createActionButton('Show Bluetooth Settings', function() {
35
+            ble.showBluetoothSettings();
36
+        });
37
+
38
+        // not supported on iOS
39
+        createActionButton('Enable Bluetooth', function() {
40
+
41
+            ble.enable(
42
+                function() {
43
+                    console.log("Bluetooth is enabled");
44
+                },
45
+                function() {
46
+                    console.log("The user did *not* enable Bluetooth");
47
+                }
48
+            );
49
+        });
50
+
51
+    }
52
+
53
+    createActionButton('Scan', function() {
54
+
55
+        var scanSeconds = 5;
56
+        console.log("Scanning for BLE peripherals for " + scanSeconds + " seconds.");
57
+        ble.startScan([], function(device) {
58
+            console.log(JSON.stringify(device));
59
+        }, function(reason) {
60
+            console.log("BLE Scan failed " + reason);
61
+        });
62
+
63
+        setTimeout(ble.stopScan,
64
+            scanSeconds * 1000,
65
+            function() { console.log("Scan complete"); },
66
+            function() { console.log("stopScan failed"); }
67
+        );
68
+
69
+    });
70
+
71
+};

+ 308 - 0
plugins/cordova-plugin-ble-central/www/ble.js

@@ -0,0 +1,308 @@
1
+// (c) 2014-2016 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+/* global cordova, module */
16
+"use strict";
17
+
18
+var stringToArrayBuffer = function(str) {
19
+    var ret = new Uint8Array(str.length);
20
+    for (var i = 0; i < str.length; i++) {
21
+        ret[i] = str.charCodeAt(i);
22
+    }
23
+    return ret.buffer;
24
+};
25
+
26
+var base64ToArrayBuffer = function(b64) {
27
+    return stringToArrayBuffer(atob(b64));
28
+};
29
+
30
+function massageMessageNativeToJs(message) {
31
+    if (message.CDVType == 'ArrayBuffer') {
32
+        message = base64ToArrayBuffer(message.data);
33
+    }
34
+    return message;
35
+}
36
+
37
+// Cordova 3.6 doesn't unwrap ArrayBuffers in nested data structures
38
+// https://github.com/apache/cordova-js/blob/94291706945c42fd47fa632ed30f5eb811080e95/src/ios/exec.js#L107-L122
39
+function convertToNativeJS(object) {
40
+    Object.keys(object).forEach(function (key) {
41
+        var value = object[key];
42
+        object[key] = massageMessageNativeToJs(value);
43
+        if (typeof(value) === 'object') {
44
+            convertToNativeJS(value);
45
+        }
46
+    });
47
+}
48
+
49
+// set of auto-connected device ids
50
+var autoconnected = {};
51
+
52
+module.exports = {
53
+
54
+    scan: function (services, seconds, success, failure) {
55
+        var successWrapper = function(peripheral) {
56
+            convertToNativeJS(peripheral);
57
+            success(peripheral);
58
+        };
59
+        cordova.exec(successWrapper, failure, 'BLE', 'scan', [services, seconds]);
60
+    },
61
+
62
+    startScan: function (services, success, failure) {
63
+        var successWrapper = function(peripheral) {
64
+            convertToNativeJS(peripheral);
65
+            success(peripheral);
66
+        };
67
+        cordova.exec(successWrapper, failure, 'BLE', 'startScan', [services]);
68
+    },
69
+
70
+    stopScan: function (success, failure) {
71
+        cordova.exec(success, failure, 'BLE', 'stopScan', []);
72
+    },
73
+
74
+    startScanWithOptions: function(services, options, success, failure) {
75
+        var successWrapper = function(peripheral) {
76
+            convertToNativeJS(peripheral);
77
+            success(peripheral);
78
+        };
79
+        options = options || {};
80
+        cordova.exec(successWrapper, failure, 'BLE', 'startScanWithOptions', [services, options]);
81
+    },
82
+
83
+    // iOS only
84
+    connectedPeripheralsWithServices: function(services, success, failure) {
85
+        cordova.exec(success, failure, 'BLE', 'connectedPeripheralsWithServices', [services]);
86
+    },
87
+
88
+    // iOS only
89
+    peripheralsWithIdentifiers: function(identifiers, success, failure) {
90
+        cordova.exec(success, failure, 'BLE', 'peripheralsWithIdentifiers', [identifiers]);
91
+    },
92
+
93
+    // Android only
94
+    bondedDevices: function(success, failure) {
95
+        cordova.exec(success, failure, 'BLE', 'bondedDevices', []);
96
+    },
97
+
98
+    // this will probably be removed
99
+    list: function (success, failure) {
100
+        cordova.exec(success, failure, 'BLE', 'list', []);
101
+    },
102
+
103
+    connect: function (device_id, success, failure) {
104
+        // wrap success so nested array buffers in advertising info are handled correctly
105
+        var successWrapper = function(peripheral) {
106
+            convertToNativeJS(peripheral);
107
+            success(peripheral);
108
+        };
109
+        cordova.exec(successWrapper, failure, 'BLE', 'connect', [device_id]);    
110
+    },
111
+
112
+    autoConnect: function (deviceId, connectCallback, disconnectCallback) {
113
+        var disconnectCallbackWrapper;
114
+        autoconnected[deviceId] = true;
115
+
116
+        // wrap connectCallback so nested array buffers in advertising info are handled correctly
117
+        var connectCallbackWrapper = function(peripheral) {
118
+            convertToNativeJS(peripheral);
119
+            connectCallback(peripheral);
120
+        };
121
+
122
+        // iOS needs to reconnect on disconnect, unless ble.disconnect was called. 
123
+        if (cordova.platformId === 'ios') {
124
+            disconnectCallbackWrapper = function(peripheral) {
125
+                // let the app know the peripheral disconnected
126
+                disconnectCallback(peripheral);
127
+    
128
+                // reconnect if we have a peripheral.id and the user didn't call disconnect
129
+                if (peripheral.id && autoconnected[peripheral.id]) {
130
+                    cordova.exec(connectCallbackWrapper, disconnectCallbackWrapper, 'BLE', 'autoConnect', [deviceId]);
131
+                }
132
+            };    
133
+        } else {  // no wrapper for Android
134
+            disconnectCallbackWrapper = disconnectCallback; 
135
+        }
136
+
137
+        cordova.exec(connectCallbackWrapper, disconnectCallbackWrapper, 'BLE', 'autoConnect', [deviceId]);
138
+    },
139
+
140
+    disconnect: function (device_id, success, failure) {
141
+        try {
142
+            delete autoconnected[device_id];
143
+        } catch(e) {
144
+            // ignore error
145
+        }
146
+        cordova.exec(success, failure, 'BLE', 'disconnect', [device_id]);
147
+    },
148
+
149
+    requestMtu: function (device_id, mtu,  success, failure) {
150
+        cordova.exec(success, failure, 'BLE', 'requestMtu', [device_id, mtu]);
151
+    },
152
+
153
+    refreshDeviceCache: function(deviceId, timeoutMillis, success, failure) {
154
+        var successWrapper = function(peripheral) {
155
+            convertToNativeJS(peripheral);
156
+            success(peripheral);
157
+        };
158
+        cordova.exec(successWrapper, failure, 'BLE', 'refreshDeviceCache', [deviceId, timeoutMillis]);
159
+    },
160
+
161
+    // characteristic value comes back as ArrayBuffer in the success callback
162
+    read: function (device_id, service_uuid, characteristic_uuid, success, failure) {
163
+        cordova.exec(success, failure, 'BLE', 'read', [device_id, service_uuid, characteristic_uuid]);
164
+    },
165
+
166
+    // RSSI value comes back as an integer
167
+    readRSSI: function(device_id, success, failure) {
168
+        cordova.exec(success, failure, 'BLE', 'readRSSI', [device_id]);
169
+    },
170
+
171
+    // value must be an ArrayBuffer
172
+    write: function (device_id, service_uuid, characteristic_uuid, value, success, failure) {
173
+        cordova.exec(success, failure, 'BLE', 'write', [device_id, service_uuid, characteristic_uuid, value]);
174
+    },
175
+
176
+    // value must be an ArrayBuffer
177
+    writeWithoutResponse: function (device_id, service_uuid, characteristic_uuid, value, success, failure) {
178
+        cordova.exec(success, failure, 'BLE', 'writeWithoutResponse', [device_id, service_uuid, characteristic_uuid, value]);
179
+    },
180
+
181
+    // value must be an ArrayBuffer
182
+    writeCommand: function (device_id, service_uuid, characteristic_uuid, value, success, failure) {
183
+        console.log("WARNING: writeCommand is deprecated, use writeWithoutResponse");
184
+        cordova.exec(success, failure, 'BLE', 'writeWithoutResponse', [device_id, service_uuid, characteristic_uuid, value]);
185
+    },
186
+
187
+    // success callback is called on notification
188
+    notify: function (device_id, service_uuid, characteristic_uuid, success, failure) {
189
+        console.log("WARNING: notify is deprecated, use startNotification");
190
+        cordova.exec(success, failure, 'BLE', 'startNotification', [device_id, service_uuid, characteristic_uuid]);
191
+    },
192
+
193
+    // success callback is called on notification
194
+    startNotification: function (device_id, service_uuid, characteristic_uuid, success, failure) {
195
+        cordova.exec(success, failure, 'BLE', 'startNotification', [device_id, service_uuid, characteristic_uuid]);
196
+    },
197
+
198
+    // success callback is called when the descriptor 0x2902 is written
199
+    stopNotification: function (device_id, service_uuid, characteristic_uuid, success, failure) {
200
+        cordova.exec(success, failure, 'BLE', 'stopNotification', [device_id, service_uuid, characteristic_uuid]);
201
+    },
202
+
203
+    isConnected: function (device_id, success, failure) {
204
+        cordova.exec(success, failure, 'BLE', 'isConnected', [device_id]);
205
+    },
206
+
207
+    isEnabled: function (success, failure) {
208
+        cordova.exec(success, failure, 'BLE', 'isEnabled', []);
209
+    },
210
+
211
+    enable: function (success, failure) {
212
+        cordova.exec(success, failure, "BLE", "enable", []);
213
+    },
214
+
215
+    showBluetoothSettings: function (success, failure) {
216
+        cordova.exec(success, failure, "BLE", "showBluetoothSettings", []);
217
+    },
218
+
219
+    startStateNotifications: function (success, failure) {
220
+        cordova.exec(success, failure, "BLE", "startStateNotifications", []);
221
+    },
222
+
223
+    stopStateNotifications: function (success, failure) {
224
+        cordova.exec(success, failure, "BLE", "stopStateNotifications", []);
225
+    }
226
+
227
+};
228
+
229
+module.exports.withPromises = {
230
+    scan: module.exports.scan,
231
+    startScan: module.exports.startScan,
232
+    startScanWithOptions: module.exports.startScanWithOptions,
233
+    connect: module.exports.connect,
234
+    startNotification: module.exports.startNotification,
235
+    startStateNotifications: module.exports.startStateNotifications,
236
+
237
+    stopScan: function() {
238
+        return new Promise(function(resolve, reject) {
239
+            module.exports.stopScan(resolve, reject);
240
+        });
241
+    },
242
+
243
+    disconnect: function(device_id) {
244
+        return new Promise(function(resolve, reject) {
245
+            module.exports.disconnect(device_id, resolve, reject);
246
+        });
247
+    },
248
+
249
+    read: function(device_id, service_uuid, characteristic_uuid) {
250
+        return new Promise(function(resolve, reject) {
251
+            module.exports.read(device_id, service_uuid, characteristic_uuid, resolve, reject);
252
+        });
253
+    },
254
+
255
+    write: function(device_id, service_uuid, characteristic_uuid, value) {
256
+        return new Promise(function(resolve, reject) {
257
+            module.exports.write(device_id, service_uuid, characteristic_uuid, value, resolve, reject);
258
+        });
259
+    },
260
+
261
+    writeWithoutResponse: function (device_id, service_uuid, characteristic_uuid, value) {
262
+        return new Promise(function(resolve, reject) {
263
+            module.exports.writeWithoutResponse(device_id, service_uuid, characteristic_uuid, value, resolve, reject);
264
+        });
265
+    },
266
+
267
+    stopNotification: function (device_id, service_uuid, characteristic_uuid) {
268
+        return new Promise(function(resolve, reject) {
269
+            module.exports.stopNotification(device_id, service_uuid, characteristic_uuid, resolve, reject);
270
+        });
271
+    },
272
+
273
+    isConnected: function (device_id) {
274
+        return new Promise(function(resolve, reject) {
275
+            module.exports.isConnected(device_id, resolve, reject);
276
+        });
277
+    },
278
+
279
+    isEnabled: function () {
280
+        return new Promise(function(resolve, reject) {
281
+            module.exports.isEnabled(resolve, reject);
282
+        });
283
+    },
284
+
285
+    enable: function () {
286
+        return new Promise(function(resolve, reject) {
287
+            module.exports.enable(resolve, reject);
288
+        });
289
+    },
290
+
291
+    showBluetoothSettings: function () {
292
+        return new Promise(function(resolve, reject) {
293
+            module.exports.showBluetoothSettings(resolve, reject);
294
+        });
295
+    },
296
+
297
+    stopStateNotifications: function () {
298
+        return new Promise(function(resolve, reject) {
299
+            module.exports.stopStateNotifications(resolve, reject);
300
+        });
301
+    },
302
+
303
+    readRSSI: function(device_id) {
304
+        return new Promise(function(resolve, reject) {
305
+            module.exports.readRSSI(device_id, resolve, reject);
306
+        });
307
+    }
308
+};

+ 35 - 0
plugins/cordova-plugin-compat/README.md

@@ -0,0 +1,35 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+
22
+cordova-plugin-compat
23
+------------------------
24
+
25
+This repo is for remaining backwards compatible with previous versions of Cordova.
26
+
27
+## Deprecated
28
+
29
+> This plugin is no longer being worked on as the functionality provided by this plugin is now included in cordova-android 6.3.0. You should upgrade your application to use version 1.2.0 of this plugin. It will detect whether or not the plugin is required based on the version of cordova-android your app uses.
30
+
31
+## USAGE
32
+
33
+Your plugin can depend on this plugin and use it to handle the new run time permissions Android 6.0.0 (cordova-android 5.0.0) introduced. 
34
+
35
+View [this commit](https://github.com/apache/cordova-plugin-camera/commit/a9c18710f23e86f5b7f8918dfab7c87a85064870) to see how to depend on `cordova-plugin-compat`. View [this file](https://github.com/apache/cordova-plugin-camera/blob/master/src/android/CameraLauncher.java) to see how `PermissionHelper` is being used to request and store permissions. Read more about Android permissions at http://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html#android-permissions.

+ 33 - 0
plugins/cordova-plugin-compat/RELEASENOTES.md

@@ -0,0 +1,33 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+# 
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+# 
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Release Notes
22
+
23
+### 1.2.0 (Sep 18, 2017)
24
+* [CB-12730](https://issues.apache.org/jira/browse/CB-12730) Integrate this plugin into `cordova-android@6.3.0` and deprecate this plugin as it is no longer needed.
25
+* [CB-12730](https://issues.apache.org/jira/browse/CB-12730) Prevent plugin from installing with `cordova-android >= 6.3.0`
26
+
27
+### 1.1.0 (Nov 02, 2016)
28
+* [CB-11625](https://issues.apache.org/jira/browse/CB-11625) Adding the `BuildConfig` fetching code as a backup to using a new preference
29
+* Add github pull request template
30
+
31
+### 1.0.0 (Apr 15, 2016)
32
+* Initial release
33
+* Moved `PermissionHelper.java` into `src`

+ 65 - 0
plugins/cordova-plugin-compat/package.json

@@ -0,0 +1,65 @@
1
+{
2
+  "_from": "cordova-plugin-compat@^1.2.0",
3
+  "_id": "cordova-plugin-compat@1.2.0",
4
+  "_inBundle": false,
5
+  "_integrity": "sha1-C8ZXVyduvZIMASzpIOJ0F3V2Nz4=",
6
+  "_location": "/cordova-plugin-compat",
7
+  "_phantomChildren": {},
8
+  "_requested": {
9
+    "type": "range",
10
+    "registry": true,
11
+    "raw": "cordova-plugin-compat@^1.2.0",
12
+    "name": "cordova-plugin-compat",
13
+    "escapedName": "cordova-plugin-compat",
14
+    "rawSpec": "^1.2.0",
15
+    "saveSpec": null,
16
+    "fetchSpec": "^1.2.0"
17
+  },
18
+  "_requiredBy": [
19
+    "#USER",
20
+    "/"
21
+  ],
22
+  "_resolved": "https://registry.npmjs.org/cordova-plugin-compat/-/cordova-plugin-compat-1.2.0.tgz",
23
+  "_shasum": "0bc65757276ebd920c012ce920e274177576373e",
24
+  "_spec": "cordova-plugin-compat@^1.2.0",
25
+  "_where": "/Users/young/Develop/wifi-ble-client",
26
+  "author": {
27
+    "name": "Apache Software Foundation"
28
+  },
29
+  "bugs": {
30
+    "url": "https://github.com/apache/cordova-plugin-compat/issues"
31
+  },
32
+  "bundleDependencies": false,
33
+  "cordova": {
34
+    "id": "cordova-plugin-compat",
35
+    "platforms": [
36
+      "android"
37
+    ]
38
+  },
39
+  "deprecated": false,
40
+  "description": "[DEPRECATED] This repo is for remaining backwards compatible with previous versions of Cordova.",
41
+  "engines": {
42
+    "cordovaDependencies": {
43
+      "<1.2.0": {
44
+        "cordova": ">=5.0.0"
45
+      },
46
+      ">=1.2.0": {
47
+        "cordova": ">=5.0.0",
48
+        "cordova-android": "<6.3.0"
49
+      }
50
+    }
51
+  },
52
+  "homepage": "http://github.com/apache/cordova-plugin-compat#readme",
53
+  "keywords": [
54
+    "ecosystem:cordova",
55
+    "ecosystem:phonegap",
56
+    "cordova-android"
57
+  ],
58
+  "license": "Apache-2.0",
59
+  "name": "cordova-plugin-compat",
60
+  "repository": {
61
+    "type": "git",
62
+    "url": "git://github.com/apache/cordova-plugin-compat.git"
63
+  },
64
+  "version": "1.2.0"
65
+}

+ 37 - 0
plugins/cordova-plugin-compat/plugin.xml

@@ -0,0 +1,37 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!--
3
+  Licensed to the Apache Software Foundation (ASF) under one
4
+  or more contributor license agreements.  See the NOTICE file
5
+  distributed with this work for additional information
6
+  regarding copyright ownership.  The ASF licenses this file
7
+  to you under the Apache License, Version 2.0 (the
8
+  "License"); you may not use this file except in compliance
9
+  with the License.  You may obtain a copy of the License at
10
+
11
+    http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+  Unless required by applicable law or agreed to in writing,
14
+  software distributed under the License is distributed on an
15
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+  KIND, either express or implied.  See the License for the
17
+  specific language governing permissions and limitations
18
+  under the License.
19
+-->
20
+<plugin 
21
+  xmlns="http://cordova.apache.org/ns/plugins/1.0" id="cordova-plugin-compat" version="1.2.0">
22
+  <name>Compat</name>
23
+  <description>[DEPRECATED] Cordova Compatibility Plugin</description>
24
+  <license>Apache 2.0</license>
25
+  <keywords>cordova,compat</keywords>
26
+  <repo>https://git-wip-us.apache.org/repos/asf/cordova-plugin-compat.git</repo>
27
+  <engines>
28
+    <engine name="cordova" version=">=5.0.0"/>
29
+    <engine name="cordova-android" version="
30
+      <6.3.0"/>
31
+    </engines>
32
+    <!-- android -->
33
+    <platform name="android">
34
+      <source-file src="src/android/PermissionHelper.java" target-dir="src/org/apache/cordova" />
35
+      <source-file src="src/android/BuildHelper.java" target-dir="src/org/apache/cordova" />
36
+    </platform>
37
+  </plugin>

+ 70 - 0
plugins/cordova-plugin-compat/src/android/BuildHelper.java

@@ -0,0 +1,70 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+*/
19
+
20
+package org.apache.cordova;
21
+
22
+/*
23
+ * This is a utility class that allows us to get the BuildConfig variable, which is required
24
+ * for the use of different providers.  This is not guaranteed to work, and it's better for this
25
+ * to be set in the build step in config.xml
26
+ *
27
+ */
28
+
29
+import android.app.Activity;
30
+import android.content.Context;
31
+
32
+import java.lang.reflect.Field;
33
+
34
+
35
+public class BuildHelper {
36
+
37
+
38
+    private static String TAG="BuildHelper";
39
+
40
+    /*
41
+     * This needs to be implemented if you wish to use the Camera Plugin or other plugins
42
+     * that read the Build Configuration.
43
+     *
44
+     * Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
45
+     * StackOverflow.  This is annoying as hell!  However, this method does not work with
46
+     * ProGuard, and you should use the config.xml to define the application_id
47
+     *
48
+     */
49
+
50
+    public static Object getBuildConfigValue(Context ctx, String key)
51
+    {
52
+        try
53
+        {
54
+            Class<?> clazz = Class.forName(ctx.getPackageName() + ".BuildConfig");
55
+            Field field = clazz.getField(key);
56
+            return field.get(null);
57
+        } catch (ClassNotFoundException e) {
58
+            LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
59
+            e.printStackTrace();
60
+        } catch (NoSuchFieldException e) {
61
+            LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
62
+        } catch (IllegalAccessException e) {
63
+            LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
64
+            e.printStackTrace();
65
+        }
66
+
67
+        return null;
68
+    }
69
+
70
+}

+ 138 - 0
plugins/cordova-plugin-compat/src/android/PermissionHelper.java

@@ -0,0 +1,138 @@
1
+/*
2
+       Licensed to the Apache Software Foundation (ASF) under one
3
+       or more contributor license agreements.  See the NOTICE file
4
+       distributed with this work for additional information
5
+       regarding copyright ownership.  The ASF licenses this file
6
+       to you under the Apache License, Version 2.0 (the
7
+       "License"); you may not use this file except in compliance
8
+       with the License.  You may obtain a copy of the License at
9
+
10
+         http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+       Unless required by applicable law or agreed to in writing,
13
+       software distributed under the License is distributed on an
14
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+       KIND, either express or implied.  See the License for the
16
+       specific language governing permissions and limitations
17
+       under the License.
18
+*/
19
+package org.apache.cordova;
20
+
21
+import java.lang.reflect.InvocationTargetException;
22
+import java.lang.reflect.Method;
23
+import java.util.Arrays;
24
+
25
+import org.apache.cordova.CordovaInterface;
26
+import org.apache.cordova.CordovaPlugin;
27
+import org.apache.cordova.LOG;
28
+
29
+import android.content.pm.PackageManager;
30
+
31
+/**
32
+ * This class provides reflective methods for permission requesting and checking so that plugins
33
+ * written for cordova-android 5.0.0+ can still compile with earlier cordova-android versions.
34
+ */
35
+public class PermissionHelper {
36
+    private static final String LOG_TAG = "CordovaPermissionHelper";
37
+
38
+    /**
39
+     * Requests a "dangerous" permission for the application at runtime. This is a helper method
40
+     * alternative to cordovaInterface.requestPermission() that does not require the project to be
41
+     * built with cordova-android 5.0.0+
42
+     *
43
+     * @param plugin        The plugin the permission is being requested for
44
+     * @param requestCode   A requestCode to be passed to the plugin's onRequestPermissionResult()
45
+     *                      along with the result of the permission request
46
+     * @param permission    The permission to be requested
47
+     */
48
+    public static void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
49
+        PermissionHelper.requestPermissions(plugin, requestCode, new String[] {permission});
50
+    }
51
+
52
+    /**
53
+     * Requests "dangerous" permissions for the application at runtime. This is a helper method
54
+     * alternative to cordovaInterface.requestPermissions() that does not require the project to be
55
+     * built with cordova-android 5.0.0+
56
+     *
57
+     * @param plugin        The plugin the permissions are being requested for
58
+     * @param requestCode   A requestCode to be passed to the plugin's onRequestPermissionResult()
59
+     *                      along with the result of the permissions request
60
+     * @param permissions   The permissions to be requested
61
+     */
62
+    public static void requestPermissions(CordovaPlugin plugin, int requestCode, String[] permissions) {
63
+        try {
64
+            Method requestPermission = CordovaInterface.class.getDeclaredMethod(
65
+                    "requestPermissions", CordovaPlugin.class, int.class, String[].class);
66
+
67
+            // If there is no exception, then this is cordova-android 5.0.0+
68
+            requestPermission.invoke(plugin.cordova, plugin, requestCode, permissions);
69
+        } catch (NoSuchMethodException noSuchMethodException) {
70
+            // cordova-android version is less than 5.0.0, so permission is implicitly granted
71
+            LOG.d(LOG_TAG, "No need to request permissions " + Arrays.toString(permissions));
72
+
73
+            // Notify the plugin that all were granted by using more reflection
74
+            deliverPermissionResult(plugin, requestCode, permissions);
75
+        } catch (IllegalAccessException illegalAccessException) {
76
+            // Should never be caught; this is a public method
77
+            LOG.e(LOG_TAG, "IllegalAccessException when requesting permissions " + Arrays.toString(permissions), illegalAccessException);
78
+        } catch(InvocationTargetException invocationTargetException) {
79
+            // This method does not throw any exceptions, so this should never be caught
80
+            LOG.e(LOG_TAG, "invocationTargetException when requesting permissions " + Arrays.toString(permissions), invocationTargetException);
81
+        }
82
+    }
83
+
84
+    /**
85
+     * Checks at runtime to see if the application has been granted a permission. This is a helper
86
+     * method alternative to cordovaInterface.hasPermission() that does not require the project to
87
+     * be built with cordova-android 5.0.0+
88
+     *
89
+     * @param plugin        The plugin the permission is being checked against
90
+     * @param permission    The permission to be checked
91
+     *
92
+     * @return              True if the permission has already been granted and false otherwise
93
+     */
94
+    public static boolean hasPermission(CordovaPlugin plugin, String permission) {
95
+        try {
96
+            Method hasPermission = CordovaInterface.class.getDeclaredMethod("hasPermission", String.class);
97
+
98
+            // If there is no exception, then this is cordova-android 5.0.0+
99
+            return (Boolean) hasPermission.invoke(plugin.cordova, permission);
100
+        } catch (NoSuchMethodException noSuchMethodException) {
101
+            // cordova-android version is less than 5.0.0, so permission is implicitly granted
102
+            LOG.d(LOG_TAG, "No need to check for permission " + permission);
103
+            return true;
104
+        } catch (IllegalAccessException illegalAccessException) {
105
+            // Should never be caught; this is a public method
106
+            LOG.e(LOG_TAG, "IllegalAccessException when checking permission " + permission, illegalAccessException);
107
+        } catch(InvocationTargetException invocationTargetException) {
108
+            // This method does not throw any exceptions, so this should never be caught
109
+            LOG.e(LOG_TAG, "invocationTargetException when checking permission " + permission, invocationTargetException);
110
+        }
111
+        return false;
112
+    }
113
+
114
+    private static void deliverPermissionResult(CordovaPlugin plugin, int requestCode, String[] permissions) {
115
+        // Generate the request results
116
+        int[] requestResults = new int[permissions.length];
117
+        Arrays.fill(requestResults, PackageManager.PERMISSION_GRANTED);
118
+
119
+        try {
120
+            Method onRequestPermissionResult = CordovaPlugin.class.getDeclaredMethod(
121
+                    "onRequestPermissionResult", int.class, String[].class, int[].class);
122
+
123
+            onRequestPermissionResult.invoke(plugin, requestCode, permissions, requestResults);
124
+        } catch (NoSuchMethodException noSuchMethodException) {
125
+            // Should never be caught since the plugin must be written for cordova-android 5.0.0+ if it
126
+            // made it to this point
127
+            LOG.e(LOG_TAG, "NoSuchMethodException when delivering permissions results", noSuchMethodException);
128
+        } catch (IllegalAccessException illegalAccessException) {
129
+            // Should never be caught; this is a public method
130
+            LOG.e(LOG_TAG, "IllegalAccessException when delivering permissions results", illegalAccessException);
131
+        } catch(InvocationTargetException invocationTargetException) {
132
+            // This method may throw a JSONException. We are just duplicating cordova-android's
133
+            // exception handling behavior here; all it does is log the exception in CordovaActivity,
134
+            // print the stacktrace, and ignore it
135
+            LOG.e(LOG_TAG, "InvocationTargetException when delivering permissions results", invocationTargetException);
136
+        }
137
+    }
138
+}

+ 18 - 0
plugins/fetch.json

@@ -0,0 +1,18 @@
1
+{
2
+  "cordova-plugin-ble-central": {
3
+    "source": {
4
+      "type": "registry",
5
+      "id": "cordova-plugin-ble-central"
6
+    },
7
+    "is_top_level": true,
8
+    "variables": {}
9
+  },
10
+  "cordova-plugin-compat": {
11
+    "source": {
12
+      "type": "registry",
13
+      "id": "cordova-plugin-compat@^1.2.0"
14
+    },
15
+    "is_top_level": false,
16
+    "variables": {}
17
+  }
18
+}

+ 2 - 0
typings/cordova-typings.d.ts

@@ -0,0 +1,2 @@
1
+
2
+/// <reference path="../.vscode/typings/cordova/cordova.d.ts"/>

+ 27 - 0
www/css/index.css

@@ -0,0 +1,27 @@
1
+* {
2
+    -webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
3
+}
4
+
5
+body {
6
+    -webkit-touch-callout: none;                /* prevent callout to copy image, etc when tap to hold */
7
+    -webkit-text-size-adjust: none;             /* prevent webkit from resizing text to fit */
8
+    -webkit-user-select: none;                  /* prevent copy paste, to allow, change 'none' to 'text' */
9
+    font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif;
10
+    height:100%;
11
+    margin:0px;
12
+    padding:0px;
13
+    width:100%;
14
+    text-align: center;
15
+}
16
+
17
+div {
18
+    padding: 20px;
19
+}
20
+
21
+h1 {
22
+    font-size:3em;
23
+}
24
+
25
+h2 {
26
+    font-size:2em;
27
+}

+ 20 - 0
www/index.html

@@ -0,0 +1,20 @@
1
+<!DOCTYPE html>
2
+<html>
3
+    <head>
4
+        <meta charset="utf-8" />
5
+        <meta name="format-detection" content="telephone=no" />
6
+        <meta name="msapplication-tap-highlight" content="no" />
7
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
8
+        <link rel="stylesheet" type="text/css" href="css/index.css" />
9
+        <title>Heart Rate</title>
10
+    </head>
11
+    <body>
12
+        <div>
13
+            <h2>BLE Heart Rate Demo</h2>
14
+            <h1 id="beatsPerMinute">...</h1>
15
+            <div id="statusDiv"></div>
16
+        </div>
17
+        <script type="text/javascript" src="cordova.js"></script>
18
+        <script type="text/javascript" src="js/index.js"></script>
19
+    </body>
20
+</html>

+ 104 - 0
www/js/index.js

@@ -0,0 +1,104 @@
1
+// (c) 2015 Don Coleman
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+//     http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+/* global ble, statusDiv, beatsPerMinute */
16
+/* jshint browser: true , devel: true*/
17
+
18
+// See BLE heart rate service http://goo.gl/wKH3X7
19
+var heartRate = {
20
+  service: "180d",
21
+  measurement: "2a37"
22
+};
23
+
24
+function stringToBytes(string) {
25
+    var array = new Uint8Array(string.length);
26
+    for (var i = 0, l = string.length; i < l; i++) {
27
+        array[i] = string.charCodeAt(i);
28
+     }
29
+     return array.buffer;
30
+ }
31
+
32
+var app = {
33
+  initialize: function() {
34
+    this.bindEvents();
35
+  },
36
+  bindEvents: function() {
37
+    document.addEventListener("deviceready", this.onDeviceReady, false);
38
+  },
39
+  onDeviceReady: function() {
40
+    app.scan();
41
+  },
42
+  scan: function() {
43
+    app.status("Scanning for Heart Rate Monitor");
44
+
45
+    var found = false;
46
+
47
+    function onScan(device) {
48
+      // this is demo code, assume there is only one heart rate monitor
49
+      console.log("Found " + JSON.stringify(device));
50
+      found = true;
51
+      if (device.name == "wifitest") {
52
+        ble.connect(device.id, app.onConnect, app.onDisconnect);
53
+      }
54
+    }
55
+
56
+    function scanFailure(reason) {
57
+      alert("BLE Scan Failed");
58
+    }
59
+
60
+    ble.scan([], 5, onScan, scanFailure);
61
+
62
+    setTimeout(function() {
63
+      if (!found) {
64
+        app.status("Did not find a wifi config service.");
65
+      }
66
+    }, 5000);
67
+  },
68
+  onConnect: function(peripheral) {
69
+    app.status("Connected to " + peripheral.id);
70
+    var data = stringToBytes("test");
71
+    ble.write(
72
+      peripheral.id,
73
+      "ffffffff-ffff-ffff-ffff-fffffffffff0",
74
+      "ffffffff-ffff-ffff-ffff-fffffffffff1",
75
+      data,
76
+      function() {
77
+        console.log("success");
78
+      },
79
+      function(error) {
80
+        console.log(error);
81
+      }
82
+    );
83
+  },
84
+  onDisconnect: function(reason) {
85
+    alert("Disconnected " + reason);
86
+    beatsPerMinute.innerHTML = "...";
87
+    app.status("Disconnected");
88
+  },
89
+  onData: function(buffer) {
90
+    // assuming heart rate measurement is Uint8 format, real code should check the flags
91
+    // See the characteristic specs http://goo.gl/N7S5ZS
92
+    var data = new Uint8Array(buffer);
93
+    beatsPerMinute.innerHTML = data[1];
94
+  },
95
+  onError: function(reason) {
96
+    alert("There was an error " + reason);
97
+  },
98
+  status: function(message) {
99
+    console.log(message);
100
+    statusDiv.innerHTML = message;
101
+  }
102
+};
103
+
104
+app.initialize();