├── _config.yml
├── .gitignore
├── IoTWatch
├── monkey.jungle
├── resources
│ ├── strings
│ │ └── strings.xml
│ ├── drawables
│ │ ├── D3.240.png
│ │ ├── D3.40.png
│ │ └── drawables.xml
│ └── layouts
│ │ └── layout.xml
├── .project
├── source
│ ├── IoTWatchApp.mc
│ └── IoTWatchView.mc
└── manifest.xml
├── IoTWatch2
├── monkey.jungle
├── resources
│ ├── strings
│ │ └── strings.xml
│ ├── drawables
│ │ ├── D3.40.png
│ │ ├── D3.240.png
│ │ └── drawables.xml
│ └── layouts
│ │ └── layout.xml
├── .project
├── source
│ ├── IoTWatchApp.mc
│ └── IoTWatchView.mc
└── manifest.xml
├── IoTWatchEH
├── monkey.jungle
├── bin
│ ├── IoTWatchEH.prg
│ ├── IoTWatchEH-settings.json
│ └── IoTWatchEH.prg.debug.xml
├── resources
│ ├── drawables
│ │ ├── D3.240.png
│ │ ├── D3.36.png
│ │ ├── D3.40.png
│ │ └── drawables.xml
│ ├── strings
│ │ └── strings.xml
│ ├── layouts
│ │ └── layout.xml
│ └── properties.xml
├── .project
├── source
│ ├── IoTWatchApp.mc
│ └── IoTWatchView.mc
└── manifest.xml
├── images
├── 15.SAS.png
├── 10.code.png
├── 18.code.png
├── 13.Firewall.png
├── 20.NewInput.png
├── 21.Outputs.png
├── 4.ParseJSON.png
├── MLADBLaunch.png
├── MLSplitData.png
├── 1.NewLogicApp.png
├── 11.container.png
├── 14.createHub.png
├── 2.HTTPRequest.png
├── 23.dashboard.png
├── 6.NewDataset.png
├── MLImportData.png
├── environment1.png
├── environment2.png
├── mlExperiment.png
├── 19.StreamInput.png
├── 22.PowerBIOutput.png
├── MLArchitecture.png
├── MLNEWADBCluster.png
├── MLNEWADBCluster2.png
├── MLNewDatabricks.png
├── MLNewNotebook1.png
├── MLNewNotebook2.png
├── MLNewNotebook3.png
├── MLSelectColumns.png
├── 12.CreateEventHub.png
├── 16.tokenGenerator.png
├── 17.StreamAnalytics.png
├── 7.AddRowsToDataset.png
├── MLADBAttachCluster.png
├── 8.CustomStreamingData.png
└── 5.CreateStreamingDataset.png
├── README.md
├── MLModelApplication.md
├── MLModelTraining.md
└── IoTWatchInstructions.md
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-slate
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | IoTWatchEH/source/IoTWatchView.mc
3 |
--------------------------------------------------------------------------------
/IoTWatch/monkey.jungle:
--------------------------------------------------------------------------------
1 | project.manifest = manifest.xml
2 |
--------------------------------------------------------------------------------
/IoTWatch2/monkey.jungle:
--------------------------------------------------------------------------------
1 | project.manifest = manifest.xml
2 |
--------------------------------------------------------------------------------
/IoTWatchEH/monkey.jungle:
--------------------------------------------------------------------------------
1 | project.manifest = manifest.xml
2 |
--------------------------------------------------------------------------------
/images/15.SAS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/15.SAS.png
--------------------------------------------------------------------------------
/images/10.code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/10.code.png
--------------------------------------------------------------------------------
/images/18.code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/18.code.png
--------------------------------------------------------------------------------
/images/13.Firewall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/13.Firewall.png
--------------------------------------------------------------------------------
/images/20.NewInput.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/20.NewInput.png
--------------------------------------------------------------------------------
/images/21.Outputs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/21.Outputs.png
--------------------------------------------------------------------------------
/images/4.ParseJSON.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/4.ParseJSON.png
--------------------------------------------------------------------------------
/images/MLADBLaunch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/MLADBLaunch.png
--------------------------------------------------------------------------------
/images/MLSplitData.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/MLSplitData.png
--------------------------------------------------------------------------------
/IoTWatch/resources/strings/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | IoTWatch
3 |
4 |
--------------------------------------------------------------------------------
/IoTWatch2/resources/strings/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | IoTWatch2
3 |
4 |
--------------------------------------------------------------------------------
/images/1.NewLogicApp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/1.NewLogicApp.png
--------------------------------------------------------------------------------
/images/11.container.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/11.container.png
--------------------------------------------------------------------------------
/images/14.createHub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/14.createHub.png
--------------------------------------------------------------------------------
/images/2.HTTPRequest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/2.HTTPRequest.png
--------------------------------------------------------------------------------
/images/23.dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/23.dashboard.png
--------------------------------------------------------------------------------
/images/6.NewDataset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/6.NewDataset.png
--------------------------------------------------------------------------------
/images/MLImportData.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/MLImportData.png
--------------------------------------------------------------------------------
/images/environment1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/environment1.png
--------------------------------------------------------------------------------
/images/environment2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/environment2.png
--------------------------------------------------------------------------------
/images/mlExperiment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/mlExperiment.png
--------------------------------------------------------------------------------
/images/19.StreamInput.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/19.StreamInput.png
--------------------------------------------------------------------------------
/images/22.PowerBIOutput.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/22.PowerBIOutput.png
--------------------------------------------------------------------------------
/images/MLArchitecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/MLArchitecture.png
--------------------------------------------------------------------------------
/images/MLNEWADBCluster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/MLNEWADBCluster.png
--------------------------------------------------------------------------------
/images/MLNEWADBCluster2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/MLNEWADBCluster2.png
--------------------------------------------------------------------------------
/images/MLNewDatabricks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/MLNewDatabricks.png
--------------------------------------------------------------------------------
/images/MLNewNotebook1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/MLNewNotebook1.png
--------------------------------------------------------------------------------
/images/MLNewNotebook2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/MLNewNotebook2.png
--------------------------------------------------------------------------------
/images/MLNewNotebook3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/MLNewNotebook3.png
--------------------------------------------------------------------------------
/images/MLSelectColumns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/MLSelectColumns.png
--------------------------------------------------------------------------------
/IoTWatchEH/bin/IoTWatchEH.prg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/IoTWatchEH/bin/IoTWatchEH.prg
--------------------------------------------------------------------------------
/images/12.CreateEventHub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/12.CreateEventHub.png
--------------------------------------------------------------------------------
/images/16.tokenGenerator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/16.tokenGenerator.png
--------------------------------------------------------------------------------
/images/17.StreamAnalytics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/17.StreamAnalytics.png
--------------------------------------------------------------------------------
/images/7.AddRowsToDataset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/7.AddRowsToDataset.png
--------------------------------------------------------------------------------
/images/MLADBAttachCluster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/MLADBAttachCluster.png
--------------------------------------------------------------------------------
/images/8.CustomStreamingData.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/8.CustomStreamingData.png
--------------------------------------------------------------------------------
/images/5.CreateStreamingDataset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/images/5.CreateStreamingDataset.png
--------------------------------------------------------------------------------
/IoTWatch/resources/drawables/D3.240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/IoTWatch/resources/drawables/D3.240.png
--------------------------------------------------------------------------------
/IoTWatch/resources/drawables/D3.40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/IoTWatch/resources/drawables/D3.40.png
--------------------------------------------------------------------------------
/IoTWatch2/resources/drawables/D3.40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/IoTWatch2/resources/drawables/D3.40.png
--------------------------------------------------------------------------------
/IoTWatch2/resources/drawables/D3.240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/IoTWatch2/resources/drawables/D3.240.png
--------------------------------------------------------------------------------
/IoTWatchEH/resources/drawables/D3.240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/IoTWatchEH/resources/drawables/D3.240.png
--------------------------------------------------------------------------------
/IoTWatchEH/resources/drawables/D3.36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/IoTWatchEH/resources/drawables/D3.36.png
--------------------------------------------------------------------------------
/IoTWatchEH/resources/drawables/D3.40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davedoesdemos/ConnectIQ-Watch-IoT/HEAD/IoTWatchEH/resources/drawables/D3.40.png
--------------------------------------------------------------------------------
/IoTWatch/resources/layouts/layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/IoTWatch2/resources/layouts/layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/IoTWatch/resources/drawables/drawables.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/IoTWatch2/resources/drawables/drawables.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/IoTWatchEH/resources/strings/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | IoTWatchEH
3 | Status
4 | xAccel
5 | yAccel
6 | HR
7 |
8 |
--------------------------------------------------------------------------------
/IoTWatch/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | IoTWatch
4 |
5 |
6 |
7 |
8 |
9 | connectiq.builder
10 |
11 |
12 |
13 |
14 |
15 | connectiq.projectNature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/IoTWatch2/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | IoTWatch2
4 |
5 |
6 |
7 |
8 |
9 | connectiq.builder
10 |
11 |
12 |
13 |
14 |
15 | connectiq.projectNature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/IoTWatchEH/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | IoTWatchEH
4 |
5 |
6 |
7 |
8 |
9 | connectiq.builder
10 |
11 |
12 |
13 |
14 |
15 | connectiq.projectNature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/IoTWatch/source/IoTWatchApp.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Application;
2 | using Toybox.WatchUi;
3 |
4 | class IoTWatchApp extends Application.AppBase {
5 |
6 | function initialize() {
7 | AppBase.initialize();
8 | }
9 |
10 | // onStart() is called on application start up
11 | function onStart(state) {
12 | }
13 |
14 | // onStop() is called when your application is exiting
15 | function onStop(state) {
16 | }
17 |
18 | // Return the initial view of your application here
19 | function getInitialView() {
20 | return [ new IoTWatchView()];
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/IoTWatch2/source/IoTWatchApp.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Application;
2 | using Toybox.WatchUi;
3 |
4 | class IoTWatchApp extends Application.AppBase {
5 |
6 | function initialize() {
7 | AppBase.initialize();
8 | }
9 |
10 | // onStart() is called on application start up
11 | function onStart(state) {
12 | }
13 |
14 | // onStop() is called when your application is exiting
15 | function onStop(state) {
16 | }
17 |
18 | // Return the initial view of your application here
19 | function getInitialView() {
20 | return [ new IoTWatchView()];
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/IoTWatchEH/resources/drawables/drawables.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ConnectIQ-Watch-IoT
2 | This is a Connect IQ app for Garmin devices. It will send data in realtime to Azure via the Connect Mobile app.
3 |
4 | # IoTWatch
5 | This version uses a callback on the HR sensor and submits data whenever it changes. Only submits HR.
6 |
7 | # IoTWatch2
8 | This version uses a timer and submits all sensor data as a JSON each time the timer triggers.
9 |
10 | # Instructions
11 | Set up a Logic App with HTTP Response trigger as a POST method. Save the Logic App and copy the URL into the code before building and running. You may need to look at [developer.garmin.com](https://developer.garmin.com) to see how to build Connect IQ apps, as well as how to set up the required tooling such as Eclipse and the SDK.
--------------------------------------------------------------------------------
/IoTWatchEH/resources/layouts/layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/IoTWatch/manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | eng
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/IoTWatch2/manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | eng
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/IoTWatchEH/source/IoTWatchApp.mc:
--------------------------------------------------------------------------------
1 | using Toybox.Application;
2 | using Toybox.WatchUi;
3 |
4 | class IoTWatchApp extends Application.AppBase {
5 |
6 | var IoTView;
7 |
8 | function initialize() {
9 | AppBase.initialize();
10 | }
11 |
12 | // onStart() is called on application start up
13 | function onStart(state) {
14 | Position.enableLocationEvents(Position.LOCATION_CONTINUOUS, method(:onPosition));
15 | }
16 |
17 | // onStop() is called when your application is exiting
18 | function onStop(state) {
19 | Position.enableLocationEvents(Position.LOCATION_DISABLE, method(:onPosition));
20 | }
21 |
22 | function onPosition(info) {
23 | IoTView.setPosition(info);
24 | }
25 |
26 | // Return the initial view of your application here
27 | function getInitialView() {
28 | IoTView = new IoTWatchView();
29 | return [ IoTView ];
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/MLModelApplication.md:
--------------------------------------------------------------------------------
1 | # Heart Rate data anomoly detection Part 2
2 | **produced by Dave Lusty**
3 |
4 | # Introduction
5 | This guide details how to use the machine learning model we trained in the last part to test new readings coming from the watch. In this demo we'll set up the web service in Stream Analytics
6 |
7 | You can find videos of this demo and the rest in the series on Youtube at the following locations:
8 | [Part 1 - Intro](https://youtu.be/_39eKRNK3UU)
9 | [Part 2 - Initial Platform Build](https://youtu.be/9llyGjfKiLo)
10 |
11 | The architecture for this demo is shown below.
12 | 
13 |
14 | ## Prerequisites
15 | You'll need to have completed the previous demo so you can generate some useful data to train the model with. You can find this at [Watch demo infrastructure](https://github.com/davedoesdemos/ConnectIQ-Watch-IoT/blob/master/IoTWatchInstructions.md) but if you don't have a device you can skip over to YouTube and see the demo videos instead.
16 |
17 |
18 | ## Stream Analytics
19 |
20 | ```SQL
21 | WITH bob AS (
22 | SELECT EventProcessedUtcTime, EventEnqueuedUtcTime, heartRate, yAccel, xAccel, altitude, cadence, heading, xMag, yMag, zMag, power, pressure, speed, temp, latitude, longitude, test(heartRate, yAccel, xAccel, altitude, cadence, heading, xMag, yMag, zMag, power, pressure, speed, temp, latitude, longitude) as result
23 | FROM connectIQ
24 | )
25 |
26 | SELECT EventProcessedUtcTime, EventEnqueuedUtcTime, heartRate, yAccel, xAccel, altitude, cadence, heading, xMag, yMag, zMag, power, pressure, speed, temp, latitude, longitude, cast(result.[Scored Labels] as int) AS scoredLabels
27 | INTO connectIQOut2
28 | FROM bob
29 | ```
--------------------------------------------------------------------------------
/IoTWatchEH/manifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | eng
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/IoTWatch/source/IoTWatchView.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi;
2 | using Toybox.Communications as Comm;
3 | using Toybox.Sensor;
4 | using Toybox.Graphics;
5 | using Toybox.System;
6 | using Toybox.Lang;
7 | using Toybox.Time.Gregorian;
8 | using Toybox.Sensor;
9 | using Toybox.Application;
10 | using Toybox.Position;
11 |
12 | class IoTWatchView extends WatchUi.View {
13 | var string_HR;
14 |
15 | function initialize() {
16 | View.initialize();
17 | Sensor.setEnabledSensors( [Sensor.SENSOR_HEARTRATE,Sensor.SENSOR_TEMPERATURE] );
18 | Sensor.enableSensorEvents( method(:onSensor) );
19 | }
20 |
21 | function onSensor(sensor_info) {
22 | var HR = sensor_info.heartRate;
23 | //Fill in this variable with your REST API endpoint
24 | var url = "";
25 | if( sensor_info.heartRate != null )
26 | {
27 | string_HR = HR.toString() + "bpm";
28 | var params = {
29 | "heartRate" => HR
30 | };
31 | var headers = {
32 | "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
33 | };
34 | var options = {
35 | :headers => headers,
36 | :method => Communications.HTTP_REQUEST_METHOD_POST,
37 | :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
38 | };
39 | Communications.makeWebRequest(url, params, options, method(:onReceive));
40 | }
41 | else
42 | {
43 | string_HR = "---bpm";
44 | }
45 | }
46 |
47 | function onReceive(responseCode, data) {
48 | }
49 |
50 | // Load your resources here
51 | function onLayout(dc) {
52 | setLayout(Rez.Layouts.MainLayout(dc));
53 | }
54 |
55 | function onShow() {
56 | }
57 |
58 | function onUpdate(dc) {
59 | View.onUpdate(dc);
60 |
61 | }
62 |
63 | function onHide() {
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/IoTWatchEH/resources/properties.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | yournamespace
5 | YourEventHub
6 | Your SAS Key Name
7 | Your SAS Key
8 | Your User ID
9 | 1000
10 |
11 |
12 |
13 | Your Namespace
14 | Your Event Hub
15 | SAS Key Name
16 | SAS Key
17 | User ID
18 | Timer Delay
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/IoTWatchEH/bin/IoTWatchEH-settings.json:
--------------------------------------------------------------------------------
1 | {"settings":[{"key":"eHNamespace","valueType":"string","defaultValue":"yournamespace","configTitle":"eHNamespace_title","configPrompt":null,"configHelpUrl":null,"configError":null,"configType":"alphaNumeric","configReadonly":false,"configRequired":false,"configOptions":null,"configMin":null,"configMax":null,"configMaxLength":null},{"key":"eHEventHub","valueType":"string","defaultValue":"YourEventHub","configTitle":"eHEventHub_title","configPrompt":null,"configHelpUrl":null,"configError":null,"configType":"alphaNumeric","configReadonly":false,"configRequired":false,"configOptions":null,"configMin":null,"configMax":null,"configMaxLength":null},{"key":"eHSASKeyName","valueType":"string","defaultValue":"Your SAS Key Name","configTitle":"eHSASKeyName_title","configPrompt":null,"configHelpUrl":null,"configError":null,"configType":"alphaNumeric","configReadonly":false,"configRequired":false,"configOptions":null,"configMin":null,"configMax":null,"configMaxLength":null},{"key":"eHSASKey","valueType":"string","defaultValue":"Your SAS Key","configTitle":"eHSASKey_title","configPrompt":null,"configHelpUrl":null,"configError":null,"configType":"alphaNumeric","configReadonly":false,"configRequired":false,"configOptions":null,"configMin":null,"configMax":null,"configMaxLength":null},{"key":"userID","valueType":"string","defaultValue":"Your User ID","configTitle":"userID_title","configPrompt":null,"configHelpUrl":null,"configError":null,"configType":"alphaNumeric","configReadonly":false,"configRequired":false,"configOptions":null,"configMin":null,"configMax":null,"configMaxLength":null},{"key":"timer","valueType":"number","defaultValue":1000,"configTitle":"timer_title","configPrompt":null,"configHelpUrl":null,"configError":null,"configType":"numeric","configReadonly":false,"configRequired":false,"configOptions":null,"configMin":null,"configMax":null,"configMaxLength":null}],"languages":{"valyrian":{"userID_title":"User ID","field1":"Status","eHNamespace_title":"Your Namespace","eHSASKeyName_title":"SAS Key Name","eHEventHub_title":"Your Event Hub","field3":"yAccel","field2":"xAccel","eHSASKey_title":"SAS Key","timer_title":"Timer Delay","AppName":"IoTWatchEH","field4":"HR"}}}
--------------------------------------------------------------------------------
/IoTWatch2/source/IoTWatchView.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi;
2 | using Toybox.Communications as Comm;
3 | using Toybox.Sensor;
4 | using Toybox.Graphics;
5 | using Toybox.System;
6 | using Toybox.Lang;
7 | using Toybox.Time.Gregorian;
8 | using Toybox.Sensor;
9 | using Toybox.Application;
10 | using Toybox.Position;
11 | using Toybox.Timer;
12 |
13 | class IoTWatchView extends WatchUi.View {
14 | var dataTimer = new Timer.Timer();
15 | var string_HR;
16 | //Fill in this variable with your REST API endpoint
17 | var url = "";
18 | //Fill in this variable with the time interval in ms that you want to use for submitting data. Lower values will cause more calls so will cost more!
19 | var timer = 5000;
20 |
21 | function initialize() {
22 | View.initialize();
23 | // Set up a timer
24 | dataTimer.start(method(:timerCallback), timer, true);
25 | }
26 |
27 | function timerCallback() {
28 | var sensorInfo = Sensor.getInfo();
29 | var positionInfo = Position.getInfo();
30 | var xAccel = 0;
31 | var yAccel = 0;
32 | var hR = 0;
33 | var altitude = 0;
34 | var cadence = 0;
35 | var heading = 0;
36 | var xMag = 0;
37 | var yMag = 0;
38 | var zMag = 0;
39 | var power = 0;
40 | var pressure = 0;
41 | var speed = 0;
42 | var temp = 0;
43 | var latitude = 0;
44 | var longitude = 0;
45 |
46 | //Collect Data
47 | //Accelerometer
48 | if (sensorInfo has :accel && sensorInfo.accel != null) {
49 | var accel = sensorInfo.accel;
50 | xAccel = accel[0];
51 | yAccel = accel[1];
52 | }
53 | else {
54 | xAccel = 0;
55 | yAccel = 0;
56 | }
57 | //Heartrate
58 | if (sensorInfo has :heartRate && sensorInfo.heartRate != null) {
59 | hR = sensorInfo.heartRate;
60 | }
61 | else {
62 | hR = 0;
63 | }
64 | //Altitude
65 | if (sensorInfo has :altitude && sensorInfo.altitude != null) {
66 | altitude = sensorInfo.altitude;
67 | }
68 | else {
69 | altitude = 0;
70 | }
71 | //Cadence
72 | if (sensorInfo has :cadence && sensorInfo.cadence != null) {
73 | cadence = sensorInfo.cadence;
74 | }
75 | else {
76 | cadence = 0;
77 | }
78 | //heading
79 | if (sensorInfo has :heading && sensorInfo.heading != null) {
80 | heading = sensorInfo.heading;
81 | }
82 | else {
83 | heading = 0;
84 | }
85 | //magnetometer
86 | if (sensorInfo has :mag && sensorInfo.mag != null) {
87 | var mag = sensorInfo.mag;
88 | xMag = mag[0];
89 | yMag = mag[1];
90 | zMag = mag[2];
91 | }
92 | else {
93 | xMag = 0;
94 | yMag = 1;
95 | zMag = 2;
96 | }
97 | //Power
98 | if (sensorInfo has :power && sensorInfo.power != null) {
99 | power = sensorInfo.power;
100 | }
101 | else {
102 | power = 0;
103 | }
104 | //Pressure
105 | if (sensorInfo has :pressure && sensorInfo.pressure != null) {
106 | pressure = sensorInfo.pressure;
107 | }
108 | else {
109 | pressure = 0;
110 | }
111 | //Speed
112 | if (sensorInfo has :speed && sensorInfo.speed != null) {
113 | speed = sensorInfo.speed;
114 | }
115 | else {
116 | speed = 0;
117 | }
118 | //Temperature
119 | if (sensorInfo has :temp && sensorInfo.temp != null) {
120 | temp = sensorInfo.temp;
121 | }
122 | else {
123 | temp = 0;
124 | }
125 | //Position
126 | if (positionInfo has :position && positionInfo.position != null) {
127 | var location = positionInfo.position.toDegrees();
128 | latitude = location[0];
129 | longitude = location[1];
130 | }
131 | else {
132 | latitude = 0;
133 | longitude = 0;
134 |
135 | }
136 |
137 | //Send the data to the REST API
138 |
139 | var params = {
140 | "heartRate" => hR.toNumber(),
141 | "xAccel" => xAccel.toNumber(),
142 | "yAccel" => yAccel.toNumber(),
143 | "altitude" => altitude.toFloat(),
144 | "cadence" => cadence.toNumber(),
145 | "heading" => heading.toFloat(),
146 | "xMag" => xMag.toNumber(),
147 | "yMag" => yMag.toNumber(),
148 | "zMag" => zMag.toNumber(),
149 | "power" => power.toNumber(),
150 | "pressure" => pressure.toFloat(),
151 | "speed" => speed.toFloat(),
152 | "temp" => temp.toNumber(),
153 | "latitude" => latitude.toFloat(),
154 | "longitude" => longitude.toFloat()
155 | };
156 | var headers = {
157 | "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON
158 | };
159 | var options = {
160 | :headers => headers,
161 | :method => Communications.HTTP_REQUEST_METHOD_POST,
162 | :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
163 | };
164 | Communications.makeWebRequest(url, params, options, method(:onReceive));
165 | }
166 |
167 | function onReceive(responseCode, data) {
168 | }
169 |
170 | // Load your resources here
171 | function onLayout(dc) {
172 | setLayout(Rez.Layouts.MainLayout(dc));
173 | }
174 |
175 | function onShow() {
176 | }
177 |
178 | // Update the view
179 | function onUpdate(dc) {
180 | View.onUpdate(dc);
181 |
182 | }
183 |
184 | function onHide() {
185 | }
186 |
187 | }
188 |
--------------------------------------------------------------------------------
/MLModelTraining.md:
--------------------------------------------------------------------------------
1 | # Heart Rate data anomoly detection
2 | **produced by Dave Lusty**
3 |
4 | # Introduction
5 | This guide details how to use the data generated by the Garmin watch application to train a machine learning model. In this demo we'll generate some resting HR data, then transform it with Data Factory and Azure Databricks. Finally we'll train a model using a "one class SVM" algorithm for anomoly detection before publishing that model as a web service. The one class SVM uses statistical analysis to detect outlier data, so when we generate new data with unusual values (such as during exercise) it will detect an anomoly.
6 |
7 | You can find videos of this demo and the rest in the series on Youtube at the following locations:
8 | [Part 1 - Intro](https://youtu.be/_39eKRNK3UU)
9 | [Part 2 - Initial Platform Build](https://youtu.be/9llyGjfKiLo)
10 |
11 | The architecture for this demo is shown below.
12 | 
13 |
14 | ## Prerequisites
15 | You'll need to have completed the previous demo so you can generate some useful data to train the model with. You can find this at [Watch demo infrastructure](https://github.com/davedoesdemos/ConnectIQ-Watch-IoT/blob/master/IoTWatchInstructions.md) but if you don't have a device you can skip over to YouTube and see the demo videos instead.
16 |
17 | # Generate data
18 | If you have previous test data in your storage account, open that account and delete the entire container and data. For this demo to work, we need some stable resting heart rate data so the previous data might not be useful. Please bear in mind we're doing this so the demo works - in real life we don't ordinarily cheat like this with our data sets for machine learning. Once deleted, recreate the storage container so that new data will have somewhere to go.
19 | Next, sit in a chair and relax for a few minutes. Lying down will also work. Once relaxed, start the demo app on your watch to start recording "resting" data. Continue to relax for a few minutes to gather a reasonable amount of training data. Stop the app on your watch and check that you have some data in your storage account.
20 |
21 | # Data Transformation
22 |
23 | For the transformation, we'll be using Azure Databricks with a transformation script written in Scala. There are many, many ways to achieve this transformation and I'll cover some of those in later demos. We'll be triggering the script from Azure Data Factory here. Although not strictly necessary, a normal next step would be to "industrialise" the process of training the model so that we can later re-train based on new data. This would involve a recurring job in Data Factory to copy new data for training.
24 |
25 | ## Azure Databricks
26 |
27 | First, create a new Azure Databricks workspace called "connectiq" and place it into your demo resource group with the othe components for the watch demo. Select the standard pricing tier and choose not to place the workspace on your network.
28 |
29 | 
30 |
31 | Once the workspace is deployed, open it in the portal and choose Launch Workspace.
32 |
33 | 
34 |
35 | Once in the workspace, click New Notebook to create a notebook. This is similar to a script, and allows you to place code into a document and see output from that code within the same document.
36 |
37 | 
38 |
39 | Give the notebook a name such as ConvertAvroToCSV, and choose Scala as the language. Everything here can also be achived in Python so when writing your own scripts choose
40 |
41 | 
42 |
43 | 
44 |
45 | ```scala
46 | import org.apache.spark.sql.functions._
47 | import org.apache.spark.sql.types._
48 |
49 | //Set up Blob storage keys
50 | spark.conf.set(
51 | "fs.azure.sas.YOURWATCHDATACONTAINER.YOURSTORAGEACCOUNT.blob.core.windows.net",
52 | "https://YOURSTORAGEACCOUNT.blob.core.windows.net/?sv=2018-03-28&ss=REST OF YOUR SAS TOKEN")
53 | spark.conf.set(
54 | "fs.azure.sas.YOURTARGETDATACONTAINER.YOURSTORAGEACCOUNT.blob.core.windows.net",
55 | "https://YOURSTORAGEACCOUNT.blob.core.windows.net/?sv=2018-03-28&ss=REST OF YOUR SAS TOKEN")
56 |
57 | //Create the schema based on what the watch app sends
58 | val watchSchema = (new StructType )
59 | .add("heartRate",IntegerType)
60 | .add("yAccel",IntegerType)
61 | .add("xAccel",IntegerType)
62 | .add("altitude",FloatType)
63 | .add("cadence",IntegerType)
64 | .add("heading",FloatType)
65 | .add("xMag",IntegerType)
66 | .add("yMag",IntegerType)
67 | .add("zMag",IntegerType)
68 | .add("power",IntegerType)
69 | .add("pressure",FloatType)
70 | .add("speed",FloatType)
71 | .add("temp",IntegerType)
72 | .add("latitude",FloatType)
73 | .add("longitude",FloatType)
74 |
75 | //Import the data in AVRO format from Blob and extract the JSON payload using the above schema
76 | //Use wildcards in the path to select less data if needed
77 | val data = spark.read.format("avro").load("wasbs://YOURWATCHDATACONTAINER@YOURSTORAGEACCOUNT.blob.core.windows.net/YOURSTORAGEACCOUNT/watchdata/0/2019/*/*/*/*/*").selectExpr("cast (body as string) as json").select(from_json($"json", schema=watchSchema).as("readings"))
78 | //Select the data we need, in this case just heart rate data
79 | val data2 = data.select($"readings.heartRate")
80 | //Output the data to Blob in CSV Format
81 | data2.write.csv("wasbs://YOURTARGETDATACONTAINER@YOURSTORAGEACCOUNT.blob.core.windows.net/csv")
82 | ```
83 |
84 | 
85 |
86 | 
87 |
88 | 
89 |
90 |
91 |
92 |
93 | ## Data Factory
94 |
95 | # Machine Learning
96 |
97 | ## ML Studio
98 |
99 | 
100 |
101 | 
102 |
103 | 
104 |
105 | 
106 |
107 |
108 | # Testing
--------------------------------------------------------------------------------
/IoTWatchEH/source/IoTWatchView.mc:
--------------------------------------------------------------------------------
1 | using Toybox.WatchUi;
2 | using Toybox.Communications as Comm;
3 | using Toybox.Sensor;
4 | using Toybox.Graphics;
5 | using Toybox.System;
6 | using Toybox.Lang;
7 | using Toybox.Time.Gregorian;
8 | using Toybox.Sensor;
9 | using Toybox.Application;
10 | using Toybox.Position;
11 | using Toybox.Timer;
12 | using Toybox.Cryptography;
13 |
14 | class IoTWatchView extends WatchUi.View {
15 | //set up display variables
16 | var status = "Waiting";
17 | var field2 = "0";
18 | var field3 = "0";
19 | var field4 = "0";
20 | var positionInfo = null;
21 | //set up the text labels
22 | hidden var dispField1;
23 | hidden var dispField2;
24 | hidden var dispField3;
25 | hidden var dispField4;
26 |
27 | //Set up the timer
28 | var dataTimer = new Timer.Timer();
29 | //Fill in this variable with the time interval in ms that you want to use for submitting data. Lower values will cause more calls so will cost more!
30 | var timer = 1000;
31 |
32 | //Set up the variables for the API call. These will be overwritten by the app settings but I filled in here to show an example of correct strings
33 | var url = "https://yournamespace.servicebus.windows.net/yourhub/publishers/uniquestring/messages";
34 | var sas = "SharedAccessSignature sr=https%3a%2f%2fyournamespace.servicebus.windows.net%2fyourhub%2fpublishers%2funiquestring%2fmessages&sig=signature%3d&se=61572283137&skn=KeyName";
35 |
36 | function initialize() {
37 | View.initialize();
38 |
39 | //get unique identifier to use as publisher string
40 | var mySettings = System.getDeviceSettings();
41 | var publisher = mySettings.uniqueIdentifier;
42 |
43 | //set up the URL we will call
44 | url = "https://" + Application.Properties.getValue("eHNamespace") + ".servicebus.windows.net/" + Application.Properties.getValue("eHEventHub") + "/publishers/" + publisher + "/messages";
45 |
46 | //Set up the SAS key for HMAC encryption
47 | var mySASKey = Application.Properties.getValue("eHSASKey");
48 | var keyConvertOptions = {
49 | :fromRepresentation => StringUtil.REPRESENTATION_STRING_PLAIN_TEXT,
50 | :toRepresentation => StringUtil.REPRESENTATION_BYTE_ARRAY,
51 | :encoding => StringUtil.CHAR_ENCODING_UTF8
52 | };
53 | //Convert SAS Key to Byte Array
54 | var mySASKeyByteArray = StringUtil.convertEncodedString(mySASKey, keyConvertOptions);
55 |
56 | //Set up string to convert
57 | //current time
58 | var UTCNow = Time.now().value();
59 | //duration for key to last
60 | var duration = 2678400; //month
61 | var keyExpiry = (UTCNow + duration).toString();
62 | var stringToConvert = Comm.encodeURL(url) + "\n" + keyExpiry;
63 | var bytesToConvert = StringUtil.convertEncodedString(stringToConvert, keyConvertOptions);
64 |
65 | //Set up HMAC (HashBasedMessageAuthenticationCode)
66 | var HMACOptions = {
67 | :algorithm => Cryptography.HASH_SHA256,
68 | :key => mySASKeyByteArray
69 | };
70 |
71 | var HMAC = new Cryptography.HashBasedMessageAuthenticationCode(HMACOptions);
72 | //convert the string
73 | HMAC.update(bytesToConvert);
74 | var encryptedBytes = HMAC.digest();
75 |
76 | HMAC.initialize(HMACOptions);
77 | //convert the string
78 | HMAC.update(bytesToConvert);
79 | encryptedBytes = HMAC.digest();
80 |
81 | var keyConvertOptions2 = {
82 | :fromRepresentation => StringUtil.REPRESENTATION_BYTE_ARRAY,
83 | :toRepresentation => StringUtil.REPRESENTATION_STRING_BASE64,
84 | :encoding => StringUtil.CHAR_ENCODING_UTF8
85 | };
86 |
87 | var myoutput = StringUtil.convertEncodedString(encryptedBytes, keyConvertOptions2);
88 |
89 | sas = "SharedAccessSignature sr=" + Comm.encodeURL(url) + "&sig=" + Comm.encodeURL(myoutput) + "&se=" + keyExpiry + "&skn=" + Application.Properties.getValue("eHSASKeyName");
90 |
91 | // Set up a timer
92 | timer = Application.Properties.getValue("timer");
93 | dataTimer.start(method(:timerCallback), timer, true);
94 | }
95 |
96 | function timerCallback() {
97 | var sensorInfo = Sensor.getInfo();
98 | //var positionInfo = Position.getInfo();
99 | var xAccel = 0;
100 | var yAccel = 0;
101 | var hR = 0;
102 | var altitude = 0;
103 | var cadence = 0;
104 | var heading = 0;
105 | var xMag = 0;
106 | var yMag = 0;
107 | var zMag = 0;
108 | var power = 0;
109 | var pressure = 0;
110 | var speed = 0;
111 | var temp = 0;
112 | var latitude = 0;
113 | var longitude = 0;
114 | var userID = Application.Properties.getValue("userID");
115 |
116 |
117 | //Collect Data
118 | //Accelerometer
119 | if (sensorInfo has :accel && sensorInfo.accel != null) {
120 | var accel = sensorInfo.accel;
121 | xAccel = accel[0];
122 | yAccel = accel[1];
123 | }
124 | else {
125 | xAccel = 0;
126 | yAccel = 0;
127 | }
128 | //Heartrate
129 | if (sensorInfo has :heartRate && sensorInfo.heartRate != null) {
130 | hR = sensorInfo.heartRate;
131 | }
132 | else {
133 | hR = 0;
134 | }
135 | //Altitude
136 | if (sensorInfo has :altitude && sensorInfo.altitude != null) {
137 | altitude = sensorInfo.altitude;
138 | }
139 | else {
140 | altitude = 0;
141 | }
142 | //Cadence
143 | if (sensorInfo has :cadence && sensorInfo.cadence != null) {
144 | cadence = sensorInfo.cadence;
145 | }
146 | else {
147 | cadence = 0;
148 | }
149 | //heading
150 | if (sensorInfo has :heading && sensorInfo.heading != null) {
151 | heading = sensorInfo.heading;
152 | }
153 | else {
154 | heading = 0;
155 | }
156 | //magnetometer
157 | if (sensorInfo has :mag && sensorInfo.mag != null) {
158 | var mag = sensorInfo.mag;
159 | xMag = mag[0];
160 | yMag = mag[1];
161 | zMag = mag[2];
162 | }
163 | else {
164 | xMag = 0;
165 | yMag = 1;
166 | zMag = 2;
167 | }
168 | //Power
169 | if (sensorInfo has :power && sensorInfo.power != null) {
170 | power = sensorInfo.power;
171 | }
172 | else {
173 | power = 0;
174 | }
175 | //Pressure
176 | if (sensorInfo has :pressure && sensorInfo.pressure != null) {
177 | pressure = sensorInfo.pressure;
178 | }
179 | else {
180 | pressure = 0;
181 | }
182 | //Speed
183 | if (sensorInfo has :speed && sensorInfo.speed != null) {
184 | speed = sensorInfo.speed;
185 | }
186 | else {
187 | speed = 0;
188 | }
189 | //Temperature
190 | if (sensorInfo has :temp && sensorInfo.temp != null) {
191 | temp = sensorInfo.temp;
192 | }
193 | else {
194 | temp = 0;
195 | }
196 | //Position
197 | if( positionInfo != null ) {
198 | if (positionInfo has :position && positionInfo.position != null) {
199 | var location = positionInfo.position.toDegrees();
200 | latitude = location[0];
201 | longitude = location[1];
202 | }
203 | }
204 | else {
205 | latitude = 0;
206 | longitude = 0;
207 | }
208 |
209 | //Send the data to the REST API
210 |
211 | var params = {
212 | "userID" => userID,
213 | "heartRate" => hR.toNumber(),
214 | "xAccel" => xAccel.toNumber(),
215 | "yAccel" => yAccel.toNumber(),
216 | "altitude" => altitude.toFloat(),
217 | "cadence" => cadence.toNumber(),
218 | "heading" => heading.toFloat(),
219 | "xMag" => xMag.toNumber(),
220 | "yMag" => yMag.toNumber(),
221 | "zMag" => zMag.toNumber(),
222 | "power" => power.toNumber(),
223 | "pressure" => pressure.toFloat(),
224 | "speed" => speed.toFloat(),
225 | "temp" => temp.toNumber(),
226 | "latitude" => latitude.toFloat(),
227 | "longitude" => longitude.toFloat()
228 | };
229 |
230 | //set the display field variables
231 | field2 = xAccel.toString();
232 | field3 = yAccel.toString();
233 | field4 = hR.toString();
234 |
235 | var headers = {
236 | "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON,
237 | "Authorization" => sas
238 | };
239 | var options = {
240 | :headers => headers,
241 | :method => Communications.HTTP_REQUEST_METHOD_POST,
242 | :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
243 | };
244 | Communications.makeWebRequest(url, params, options, method(:onReceive));
245 |
246 | //Troubleshooting
247 | //System.println("URL: " + url);
248 | //System.println("SAS Token: " + sas);
249 | }
250 |
251 | function onReceive(responseCode, data) {
252 | //Uncomment for debug
253 | //System.println("Response code: " + responseCode);
254 | //System.println("Data: " + data);
255 |
256 | switch (responseCode.toString()) {
257 | case "-400":
258 | status = "OK";
259 | break;
260 | case "201":
261 | status = "OK";
262 | break;
263 | case "-101":
264 | status = "BLE Queue full";
265 | break;
266 | case "-104":
267 | status = "No Connection";
268 | break;
269 | case "--2":
270 | status = "Host Timeout";
271 | break;
272 | default:
273 | status = responseCode.toString();
274 | break;
275 | }
276 |
277 | WatchUi.requestUpdate();
278 | }
279 |
280 | // Load your resources here
281 | function onLayout(dc) {
282 | setLayout(Rez.Layouts.MainLayout(dc));
283 | }
284 |
285 | function onShow() {
286 |
287 | }
288 |
289 | // Update the view
290 | function onUpdate(dc) {
291 | View.onUpdate(dc);
292 | dc.clear();
293 | // Call the parent onUpdate function to redraw the layout
294 | dispField1 = new WatchUi.Text({
295 | :text=>status,
296 | :color=>Graphics.COLOR_BLACK,
297 | :font=>Graphics.FONT_LARGE,
298 | :locX =>WatchUi.LAYOUT_HALIGN_CENTER,
299 | :locY=>30,
300 | :justification=>Graphics.TEXT_JUSTIFY_CENTER
301 | });
302 | dispField2 = new WatchUi.Text({
303 | :text=>field2,
304 | :color=>Graphics.COLOR_BLACK,
305 | :font=>Graphics.FONT_LARGE,
306 | :locX =>60,
307 | :locY=>110,
308 | :justification=>Graphics.TEXT_JUSTIFY_CENTER
309 | });
310 | dispField3 = new WatchUi.Text({
311 | :text=>field3,
312 | :color=>Graphics.COLOR_BLACK,
313 | :font=>Graphics.FONT_LARGE,
314 | :locX =>180,
315 | :locY=>110,
316 | :justification=>Graphics.TEXT_JUSTIFY_CENTER
317 | });
318 | dispField4 = new WatchUi.Text({
319 | :text=>field4,
320 | :color=>Graphics.COLOR_BLACK,
321 | :font=>Graphics.FONT_LARGE,
322 | :locX =>WatchUi.LAYOUT_HALIGN_CENTER,
323 | :locY=>190,
324 | :justification=>Graphics.TEXT_JUSTIFY_CENTER
325 | });
326 | dispField1.draw(dc);
327 | dispField2.draw(dc);
328 | dispField3.draw(dc);
329 | dispField4.draw(dc);
330 |
331 | }
332 |
333 |
334 | function setPosition(info) {
335 | positionInfo = info;
336 | WatchUi.requestUpdate();
337 | }
338 |
339 | function onHide() {
340 | }
341 |
342 | }
343 |
--------------------------------------------------------------------------------
/IoTWatchInstructions.md:
--------------------------------------------------------------------------------
1 | # Garmin Device Connect IQ IoT App
2 | **produced by Dave Lusty**
3 |
4 | ## Introduction
5 | This guide details how to configure the IoT watch app with Azure Services.
6 | You can find videos of this demo on Youtube at the following locations:
7 | [Part 1 - Intro](https://youtu.be/_39eKRNK3UU)
8 | [Part 2 - Initial Platform Build](https://youtu.be/9llyGjfKiLo)
9 |
10 | You can also find instructions on adding machine learning to the solution at [Watch Demo HR check with machine learning](https://github.com/davedoesdemos/ConnectIQ-Watch-IoT/blob/master/MLModelTraining.md)
11 |
12 | ## Prerequisites
13 |
14 | Before getting started with this app you'll need to have a few things. Firstly you'll need a compatible (Garmin device)[https://developer.garmin.com/connect-iq/compatible-devices/]. Different devices have different capabilities so not all of them will be able to send all metrics.
15 |
16 | Next, you'll need the Connect IQ SDK, the Eclipse IDE and the Java JRE installed. Instructions for setting these up can be found in the (Garmin getting started guide)[https://developer.garmin.com/connect-iq/programmers-guide/getting-started/].
17 |
18 | # Environment 1
19 |
20 | 
21 |
22 | Of the two methods shown here, environment 1 is the simplest. This architecture uses a Logic App with HTTP endpoint to ingest data and push this to the Power BI streaming data service. This is simple to configure but not as scalable as the Event Hub version below.
23 |
24 | ## Power BI
25 |
26 | Log in to [Powerbi.microsoft.com](https://powerbi.microsoft.com/en-us/) and create a new app workspace called ConnectIQ Demo. Once created, add a streaming dataset. Choose API and click Next.
27 |
28 | 
29 |
30 | Add in the various fields we'll be collecting. All of these are numbers except the timestamp which will be datetime. Enable historical data analysis so we can use the data later as well as in real time.
31 |
32 | 
33 |
34 | Now create a dashboard and call it ConnectIQ Data. Add a tile and choose custom streaming data. Select your ConnectIQ dataset and then choose Card as the visualisation type. Select Heartrate as the field and click next. Add a title if you want one, and cilck apply to finish. Repeat this process to add a graph with timestamp as the axis and Heartrate as the value. Heartrate changes often so is a good metric to test with, but feel free to experiment.
35 |
36 | 
37 |
38 | ## Logic App
39 |
40 | Create a Logic App in your subscription, call it ConnectIQAPI and choose a suitable location.
41 |
42 | 
43 |
44 | Once created, open the Logic App and add a trigger for HTTP request received. Set the method to POST and save your Logic App. This will fill in the URL which you'll need to copy into the source code of the ConnectIQ App.
45 |
46 | 
47 |
48 | Next, add a Parse JSON task and add in the body of the request as the content. In order to fill in the schema you may like to set up the app and receive a sample payload, for instance if you've modified the app in some way. For now, fill in the schema with the below:
49 |
50 | ```JSON
51 | {
52 | "properties": {
53 | "altitude": {
54 | "type": "number"
55 | },
56 | "cadence": {
57 | "type": "integer"
58 | },
59 | "heading": {
60 | "type": "number"
61 | },
62 | "heartRate": {
63 | "type": "integer"
64 | },
65 | "latitude": {
66 | "type": "number"
67 | },
68 | "longitude": {
69 | "type": "number"
70 | },
71 | "power": {
72 | "type": "integer"
73 | },
74 | "pressure": {
75 | "type": "number"
76 | },
77 | "speed": {
78 | "type": "number"
79 | },
80 | "temp": {
81 | "type": "integer"
82 | },
83 | "xAccel": {
84 | "type": "integer"
85 | },
86 | "xMag": {
87 | "type": "integer"
88 | },
89 | "yAccel": {
90 | "type": "integer"
91 | },
92 | "yMag": {
93 | "type": "integer"
94 | },
95 | "zMag": {
96 | "type": "integer"
97 | }
98 | },
99 | "type": "object"
100 | }
101 | ```
102 |
103 | 
104 |
105 | Finally, add a Power BI task to add rows to dataset. You'll need to authenticate to your Power BI account here, and then choose your app workspace and dataset. Add in the columns from the dataset and then use the variables from the Parse JSON task to fill them. Use utcnow() to add the timestamp.
106 |
107 | 
108 |
109 | ## Create the App
110 |
111 | Clone the repository to your local machine and open the IoTWatch2 project. Paste the URL from your Logic App HTTP trigger into the variable in the IoTWatchView.mc file. This includes the security token so is all you need to do. You can optionally change the timer variable from 5000 (5 seconds) to some other value. This is a balance of cost and battery life, remember your Logic App wil charge for each run.
112 |
113 | 
114 |
115 | Run the app to test and you should see data in your Logic App runs as well as being passed through to Power BI in near real time.
116 |
117 | You can now side load the app to your device using the USB cable. Copy the file from the bin directory in your copy of the repository into the Apps directory on your device. You may need to restart the device to see the app. Start the app and ensure you have Bluetooth connectivity to the Connect mobile app and Internet access on your phone. You'll now see live data from the device in your Power BI session.
118 |
119 | # Environment 2
120 |
121 | 
122 |
123 | This environment uses an Azure Event Hub to ingest messages. As such this would be much more scalable and allows for copying data to a data lake for later analytics. Stream Analytics then takes these events and pushes them on to Power BI for the demo environment.
124 |
125 | ## Storage Account
126 |
127 | Create a storage account in a new resource group for the demo. Add a container in Blob called Watchdata and set it to Private. This container will be a sink for the incoming data from Event Hubs and will store a permanent copy of the events which can later be processed by HDInsight, SQL Data Warehouse, or Databricks.
128 |
129 | 
130 |
131 | ## Event Hubs
132 |
133 | Create a new Event Hubs namespace and give it a unique name (for the demo use ConnectIQ and put your name in there). Choose standard for the tier. Choose 1 in the throughput units. This is caable of ingesting 1MB of events, or 1000 events per second whichever comes first. As you can imagine, this is able to handle many devices so we are unlikely to hit these limits with this app. Click Create to create the namespace.
134 |
135 | 
136 |
137 | Once the namespace is created, open it in the console and click Firewalls and virtual networks then set the access to all networks. This is an IoT solution on which messages will be delivered from arbitary endpoints on the Internet so the firewall is not applicable. That's not to say the app is not secure. Connectivity is secured with encryption as well as shared access keys, so it's impossible to submit messages without this authentication and authorisation.
138 |
139 | 
140 |
141 | Click on Overview and add event hub. Name the event hub watchdata and set capture to on. Select the checkbox to avoid empty files being created when there is no data. Select your watchdata container in your storage account and then click Create.
142 |
143 | 
144 |
145 | Click the watchdata event hub and then click Shared access policies. Click Add and create a policy with the name ConnectIQApp and Send permissions. This SAS policy will be used by all devices, but each device will have a specific token and publisher.
146 |
147 | 
148 |
149 | Once created, click on the policy to access the keys. Copy the primary key and paste it into the [Event Hubs Signature Generator](https://github.com/sandrinodimattia/RedDog/releases/tag/0.2.0.1) or use the instructions [from Microsoft](https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-authentication-and-security-model-overview) to create a SAS token for the device. Note that I've used the device serial number as publisher - you can use anything you like here but a serial number ensures each device is authenticated separately and also that the data is published to a dedicated location from each device. You may also choose to just enter "watch" or the model name here.
150 |
151 | 
152 |
153 | This will give us a message URI of `https//connectiqlusty.servicebus.windows.net/watchdata/publishers/5mt000987/messages` which we will enter into the source code of the app. Ensure you replace this with the one you generate, which will be `https//.servicebus.windows.net//publishers//messages`.
154 |
155 | ## Create the App
156 |
157 | Clone the repository to your local machine and open the IoTWatchEH project. Paste the URL from your Event Hub as above into the variable in the IoTWatchView.mc file. Also copy the shared access signature you generated with the tool and paste this in to the code. You can optionally change the timer variable from 5000 (5 seconds) to some other value. This is a balance of cost and battery life.
158 |
159 | 
160 |
161 | Run the app to test and you should see messages appear in your Event Hub monitor.
162 |
163 | You can now side load the app to your device using the USB cable. Copy the file from the bin directory in your copy of the repository into the Apps directory on your device. You may need to restart the device to see the app. Start the app and ensure you have Bluetooth connectivity to the Connect mobile app and Internet access on your phone. You'll now see live data from the device arrive into your Event Hub.
164 |
165 | ## Power BI
166 |
167 | Log in to [Powerbi.microsoft.com](https://powerbi.microsoft.com/en-us/) and create a new app workspace called ConnectIQ Demo. Stream Analytics will create the data set so for now this is all we need to do.
168 |
169 | ## Stream Analytics
170 |
171 | Create a Stream Analytics job and name is ConnectIQ. Choose Cloud as the location and click Create.
172 |
173 | 
174 |
175 | Click on Inputs and select Add Stream Input. Choose Event Hubs as the source.
176 |
177 | 
178 |
179 | Fill in your event hub details and give the input a name.
180 |
181 | 
182 |
183 | Next, click Outputs and select Add, then Power BI.
184 |
185 | 
186 |
187 | You'll need to authorise the app to connect to the Power BI service. This will then allow you to choose your app workspace and create a dataset in the service.
188 |
189 | 
190 |
191 | ## Power BI Dashboard
192 |
193 | Now create a dashboard and call it ConnectIQ Data. Add a tile and choose custom streaming data. Select your ConnectIQ dataset and then choose Card as the visualisation type. Select Heartrate as the field and click next. Add a title if you want one, and cilck apply to finish. Repeat this process to add a graph with timestamp as the axis and Heartrate as the value. Heartrate changes often so is a good metric to test with, but feel free to experiment.
194 |
195 | 
196 |
197 | If you now start the app you will see real time data on this dashboard.
198 |
199 | 
200 |
201 | Note that at this point you will also see files begin to be created in your Blob storage, which we will later use for machine learning.
--------------------------------------------------------------------------------
/IoTWatchEH/bin/IoTWatchEH.prg.debug.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
--------------------------------------------------------------------------------