├── mxdevice ├── Device │ ├── .iotworkbenchproject │ ├── .vscode │ │ ├── settings.json │ │ ├── arduino.json │ │ └── c_cpp_properties.json │ ├── utility.h │ ├── config.h │ ├── utility.cpp │ └── MXDeviceDemo.ino ├── .vscode │ ├── arduino.json │ └── c_cpp_properties.json ├── project.code-workspace └── .azurecomponent │ └── azureconfig.json ├── frontend ├── common │ ├── test │ │ ├── value.mx.js │ │ └── index.js │ ├── package.json │ ├── icons │ │ ├── close.svg │ │ ├── accelerometer.js │ │ ├── accelerometer.svg │ │ └── microsoft_logo.svg │ ├── components │ │ ├── header.js │ │ ├── reset-app.js │ │ ├── styled │ │ │ └── button.js │ │ ├── modal.js │ │ └── round-gauge.js │ ├── styles │ │ ├── base.css │ │ ├── fonts.css │ │ └── normalize.css │ └── containers │ │ └── modal.js ├── package-lock.json ├── mx │ ├── .env │ ├── public │ │ ├── favicon.ico │ │ ├── manifest.json │ │ └── index.html │ ├── src │ │ ├── videos │ │ │ └── dps_animation.mp4 │ │ ├── images │ │ │ ├── mx_chip_angled.png │ │ │ ├── mx_chip_angled_shaking.gif │ │ │ └── DPS_animation_placeholder.png │ │ ├── reducers │ │ │ ├── step.js │ │ │ ├── index.js │ │ │ └── devices.js │ │ ├── app │ │ │ └── store.js │ │ ├── icons │ │ │ ├── sound.js │ │ │ ├── pressure.js │ │ │ ├── twitter.js │ │ │ ├── magnetism.js │ │ │ ├── temperature_symbol.svg │ │ │ ├── radio-button.js │ │ │ ├── humidity.js │ │ │ ├── temperature.js │ │ │ └── temperature_scale.svg │ │ ├── index.js │ │ ├── actions │ │ │ └── index.js │ │ ├── components │ │ │ ├── humidity-level-gauge.js │ │ │ ├── accelerometer.js │ │ │ ├── temperature-gauge.js │ │ │ ├── magnetometer-gauge.js │ │ │ ├── sound-level-gauge.js │ │ │ ├── atmospheric-pressure-gauge.js │ │ │ ├── app.js │ │ │ ├── trigger-overlay.js │ │ │ ├── round-gauge.js │ │ │ ├── temperature-monitor.js │ │ │ ├── dashboard-gauge.js │ │ │ └── device-dashboard.js │ │ ├── containers │ │ │ ├── device-provider.js │ │ │ ├── threshold-notifications.js │ │ │ ├── footer.js │ │ │ ├── dps-animation.js │ │ │ ├── send-message.js │ │ │ ├── app.js │ │ │ └── set-rule.js │ │ ├── common │ │ │ └── constants.js │ │ └── mock │ │ │ └── events.js │ ├── .vscode │ │ ├── arduino.json │ │ └── c_cpp_properties.json │ ├── .gitignore │ └── package.json ├── package.json └── README.md ├── backend ├── MXApi │ ├── Models │ │ ├── DeviceDetails.cs │ │ ├── Command.cs │ │ ├── BroadcastMessage.cs │ │ ├── TextMessage.cs │ │ ├── MetricsPayload.cs │ │ ├── MxPayload.cs │ │ └── AuthenticationFailureResult.cs │ ├── SigR │ │ └── MessageHub.cs │ ├── Services │ │ ├── ITwitterService.cs │ │ ├── IIoTHubService.cs │ │ ├── TwitterService.cs │ │ ├── IoTHubService.cs │ │ └── MessagingService.cs │ ├── Connected Services │ │ └── Application Insights │ │ │ └── ConnectedService.json │ ├── Constants.cs │ ├── Config │ │ └── NinjectConfig.cs │ ├── Web.Debug.config │ ├── Web.Release.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Resolvers │ │ └── NinjectResolver.cs │ ├── Startup.cs │ ├── Jobs │ │ └── EventHubProcessingJob.cs │ ├── Helpers │ │ └── Utilities.cs │ ├── Controllers │ │ └── MXController.cs │ ├── packages.config │ ├── Filters │ │ └── BasicAuthorizeFilter.cs │ └── Web.config └── MxDialBackend.sln ├── LICENSE ├── SECURITY.md ├── .gitignore ├── resources └── mxchip-demo.json └── README.md /mxdevice/Device/.iotworkbenchproject: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/common/test/value.mx.js: -------------------------------------------------------------------------------- 1 | export default 'value for mx'; 2 | -------------------------------------------------------------------------------- /frontend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /frontend/common/test/index.js: -------------------------------------------------------------------------------- 1 | // import value from './value' 2 | export default 47; 3 | -------------------------------------------------------------------------------- /frontend/mx/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_MX_API= 2 | # REACT_APP_SIMULATE=local 3 | # REACT_APP_SIMULATE=api 4 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": [ 4 | "common", 5 | "mx" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /frontend/mx/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/MXDial-IoT-Sample/HEAD/frontend/mx/public/favicon.ico -------------------------------------------------------------------------------- /frontend/mx/src/videos/dps_animation.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/MXDial-IoT-Sample/HEAD/frontend/mx/src/videos/dps_animation.mp4 -------------------------------------------------------------------------------- /frontend/mx/src/images/mx_chip_angled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/MXDial-IoT-Sample/HEAD/frontend/mx/src/images/mx_chip_angled.png -------------------------------------------------------------------------------- /frontend/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "cross-env": "5.1.3" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /frontend/mx/src/images/mx_chip_angled_shaking.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/MXDial-IoT-Sample/HEAD/frontend/mx/src/images/mx_chip_angled_shaking.gif -------------------------------------------------------------------------------- /frontend/mx/.vscode/arduino.json: -------------------------------------------------------------------------------- 1 | { 2 | "board": "AZ3166:stm32f4:MXCHIP_AZ3166", 3 | "configuration": "upload_method=OpenOCDMethod", 4 | "port": "COM3" 5 | } -------------------------------------------------------------------------------- /frontend/mx/src/images/DPS_animation_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/MXDial-IoT-Sample/HEAD/frontend/mx/src/images/DPS_animation_placeholder.png -------------------------------------------------------------------------------- /mxdevice/.vscode/arduino.json: -------------------------------------------------------------------------------- 1 | { 2 | "board": "AZ3166:stm32f4:MXCHIP_AZ3166", 3 | "configuration": "upload_method=OpenOCDMethod", 4 | "port": "COM3" 5 | } -------------------------------------------------------------------------------- /backend/MXApi/Models/DeviceDetails.cs: -------------------------------------------------------------------------------- 1 | namespace MXApi.Models 2 | { 3 | public class DeviceDetails 4 | { 5 | public string ConnectionString { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /mxdevice/Device/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | ".build": true, 4 | ".iotworkbenchproject": true 5 | }, 6 | "C_Cpp.intelliSenseEngine": "Tag Parser" 7 | } 8 | -------------------------------------------------------------------------------- /backend/MXApi/Models/Command.cs: -------------------------------------------------------------------------------- 1 | namespace MXApi.Models 2 | { 3 | public class Command 4 | { 5 | public int CommandType { get; set; } 6 | public string CommandMessage { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend/MXApi/Models/BroadcastMessage.cs: -------------------------------------------------------------------------------- 1 | namespace MXApi.Models 2 | { 3 | public class BroadcastMessage 4 | { 5 | public string DeviceId { get; set; } 6 | public string MessageContents { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /mxdevice/Device/.vscode/arduino.json: -------------------------------------------------------------------------------- 1 | { 2 | "board": "AZ3166:stm32f4:MXCHIP_AZ3166", 3 | "configuration": "upload_method=OpenOCDMethod", 4 | "sketch": "MXDeviceDemo.ino", 5 | "output": "./.build", 6 | "port": "COM4" 7 | } -------------------------------------------------------------------------------- /backend/MXApi/SigR/MessageHub.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNet.SignalR; 2 | 3 | namespace MXApi.SigR 4 | { 5 | public class MessageHub : Hub 6 | { 7 | // we dont need any client to server comms - however - we still need a hub 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /mxdevice/project.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "Device" 5 | } 6 | ], 7 | "settings": { 8 | "IoTWorkbench.BoardId": "devkit", 9 | "IoTWorkbench.DevicePath": "Device" 10 | } 11 | } -------------------------------------------------------------------------------- /backend/MXApi/Models/TextMessage.cs: -------------------------------------------------------------------------------- 1 | namespace MXApi.Models 2 | { 3 | public class TextMessage 4 | { 5 | public string DeviceId { get; set; } 6 | public string Number { get; set; } 7 | public string MessageContents { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/MXApi/Services/ITwitterService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace MXApi.Services 7 | { 8 | public interface ITwitterService 9 | { 10 | void Post(string message); 11 | } 12 | } -------------------------------------------------------------------------------- /backend/MXApi/Connected Services/Application Insights/ConnectedService.json: -------------------------------------------------------------------------------- 1 | { 2 | "ProviderId": "Microsoft.ApplicationInsights.ConnectedService.ConnectedServiceProvider", 3 | "Version": "8.11.10212.1", 4 | "GettingStartedDocument": { 5 | "Uri": "https://go.microsoft.com/fwlink/?LinkID=613413" 6 | } 7 | } -------------------------------------------------------------------------------- /mxdevice/.azurecomponent/azureconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "componentConfigs": [ 3 | { 4 | "id": "ec119776-2ba4-430f-90f9-083bc91baa39", 5 | "folder": "", 6 | "name": "", 7 | "dependencies": [], 8 | "type": "IoTHub" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /backend/MXApi/Services/IIoTHubService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.Azure.Devices; 3 | 4 | namespace MXApi.Services 5 | { 6 | public interface IIoTHubService 7 | { 8 | Task InvokeDeviceMethod(string deviceId, string message, string methodName); 9 | Task GetDeviceAsync(string deviceId); 10 | } 11 | } -------------------------------------------------------------------------------- /frontend/mx/src/reducers/step.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions'; 2 | 3 | export const get = state => state; 4 | 5 | const reducer = handleActions( 6 | { 7 | 'STEP/INCREMENT': (state) => (state + 1), 8 | 'STEP/SET': (state, action) => (action.payload), 9 | RESET: () => 1, 10 | }, 11 | 1 12 | ); 13 | 14 | export default reducer; 15 | -------------------------------------------------------------------------------- /frontend/mx/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /frontend/mx/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /backend/MXApi/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace MXApi 2 | { 3 | public static class Constants 4 | { 5 | public const string MessageMethodName = "message"; 6 | public const string DefaultDeviceId = "AZ3166"; 7 | public const int MaxMessageLength = 14; 8 | public const string DeviceIdHeader = "DeviceId"; 9 | public const double LowSpeakingDbValue = 50.0; 10 | public const double HighSpeakingDbValue = 80.0; 11 | } 12 | } -------------------------------------------------------------------------------- /backend/MXApi/Config/NinjectConfig.cs: -------------------------------------------------------------------------------- 1 | using MXApi.Services; 2 | using Ninject; 3 | 4 | namespace MXApi.Config 5 | { 6 | public static class NinjectConfig 7 | { 8 | public static IKernel CreateKernel() 9 | { 10 | var kernel = new StandardKernel(); 11 | 12 | kernel.Bind().To(); 13 | kernel.Bind().To(); 14 | 15 | return kernel; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mxdevice/Device/utility.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. 3 | 4 | #include "AzureIotHub.h" 5 | 6 | #ifndef UTILITY_H 7 | #define UTILITY_H 8 | 9 | void parseTwinMessage(DEVICE_TWIN_UPDATE_STATE, const char *); 10 | float * setMessage(int, char *, bool); 11 | void sensorInit(void); 12 | void blinkLED(void); 13 | void blinkSendConfirmation(void); 14 | int getInterval(void); 15 | 16 | #endif /* UTILITY_H */ -------------------------------------------------------------------------------- /frontend/mx/src/app/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux' 2 | import { createLogger } from 'redux-logger'; 3 | import thunk from 'redux-thunk' 4 | import reducers from '../reducers' 5 | 6 | export const configure = () => { 7 | const middlewares = [ thunk ]; 8 | 9 | if (process.env.NODE_ENV !== 'production') { 10 | middlewares.push(createLogger()); 11 | } 12 | 13 | return createStore(reducers, undefined, applyMiddleware(...middlewares)); 14 | } 15 | -------------------------------------------------------------------------------- /frontend/mx/src/icons/sound.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const SoundIcon = props => { 4 | return ( 5 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default SoundIcon; 16 | -------------------------------------------------------------------------------- /frontend/mx/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import devices, * as deviceSelectors from './devices'; 3 | import step, * as stepSelectors from './step'; 4 | 5 | export const getAllDevices = state => deviceSelectors.getAll(state.devices); 6 | export const getDevice = (state, id) => deviceSelectors.get(state.devices, id); 7 | export const getCurrentStep = state => stepSelectors.get(state.step); 8 | 9 | const reducers = combineReducers({ 10 | devices, 11 | step, 12 | }); 13 | 14 | export default reducers; 15 | -------------------------------------------------------------------------------- /frontend/mx/src/icons/pressure.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const PressureIcon = props => { 4 | return ( 5 | 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | export default PressureIcon; 17 | -------------------------------------------------------------------------------- /frontend/mx/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import * as Store from './app/store'; 5 | import DeviceProvider from './containers/device-provider'; 6 | import App from './containers/app'; 7 | 8 | import 'common/styles/normalize.css'; 9 | import 'common/styles/fonts.css'; 10 | import 'common/styles/base.css'; 11 | 12 | ReactDOM.render( 13 | 14 | {(deviceId) => ( 15 | 16 | 17 | 18 | )} 19 | , document.getElementById('root')); 20 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | FitBit Demo 2 | ===================== 3 | 4 | This project is found in the `mx` package. 5 | - `ionic` 6 | - `mx` 7 | 8 | Reusable components are found in the `common` package. 9 | 10 | [Yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/) are being used to manage different packages in the same repo. 11 | 12 | To install the monorepo: `yarn install` 13 | 14 | Then, to start a package, move the specific package folder (e.g. `mv mx`) and run `yarn start`. 15 | 16 | The `next` branch of [create-react-app supports workspaces](https://github.com/facebook/create-react-app/pull/3741) 17 | which is what was used to bootstrap both react apps. 18 | -------------------------------------------------------------------------------- /frontend/mx/src/icons/twitter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const TwitterIcon = props => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | }; 10 | 11 | export default TwitterIcon; 12 | -------------------------------------------------------------------------------- /mxdevice/.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Win32", 5 | "includePath": [ 6 | "C:\\Users\\jamiewilbraham\\AppData\\Local\\Arduino15\\packages\\AZ3166\\tools\\**", 7 | "C:\\Users\\jamiewilbraham\\AppData\\Local\\Arduino15\\packages\\AZ3166\\hardware\\stm32f4\\1.4.0\\**" 8 | ], 9 | "forcedInclude": [ 10 | "C:\\Users\\jamiewilbraham\\AppData\\Local\\Arduino15\\packages\\AZ3166\\hardware\\stm32f4\\1.4.0\\cores\\arduino\\Arduino.h" 11 | ], 12 | "intelliSenseMode": "msvc-x64" 13 | } 14 | ], 15 | "version": 4 16 | } -------------------------------------------------------------------------------- /frontend/mx/.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Win32", 5 | "includePath": [ 6 | "${env:USERPROFILE}\\Local\\Arduino15\\packages\\AZ3166\\hardware\\stm32f4\\1.3.2\\cores\\arduino" 7 | ], 8 | "browse": { 9 | "limitSymbolsToIncludedHeaders": false, 10 | "path": [ 11 | "${env:USERPROFILE}\\Local\\Arduino15\\packages\\AZ3166\\hardware\\stm32f4\\1.3.2\\cores\\arduino", 12 | "${workspaceRoot}" 13 | ] 14 | }, 15 | "intelliSenseMode": "msvc-x64" 16 | } 17 | ], 18 | "version": 3 19 | } -------------------------------------------------------------------------------- /frontend/common/icons/close.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 9 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/mx/src/icons/magnetism.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const MagnetismIcon = props => { 4 | return ( 5 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default MagnetismIcon; 16 | -------------------------------------------------------------------------------- /frontend/common/components/header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const imageLogo = require('../icons/microsoft_logo.svg'); 5 | 6 | const Styled = styled.div` 7 | display: flex; 8 | padding: 2vw 60px 0; 9 | 10 | img { 11 | width: 300px; 12 | margin-right: 50px; 13 | } 14 | 15 | h1 { 16 | font-size: 2.4rem; 17 | font-weight: normal; 18 | font-family: SegoeUISemilight, "Helvetica Neue", Helvetica, Arial, sans-serif; 19 | color: #c7c7c7; 20 | line-height: 1.2; 21 | } 22 | `; 23 | 24 | const Header = ({ title }) => ( 25 | 26 | 27 |

{title}

28 |
29 | ); 30 | 31 | export default Header; 32 | -------------------------------------------------------------------------------- /frontend/mx/src/icons/temperature_symbol.svg: -------------------------------------------------------------------------------- 1 | AssetLayup -------------------------------------------------------------------------------- /mxdevice/Device/config.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. 3 | 4 | // Interval time(ms) for sending message to IoT Hub 5 | #define INTERVAL 2000 6 | 7 | #define MESSAGE_MAX_LEN 256 8 | 9 | #define TEMPERATURE_ALERT 30 10 | 11 | #define DIRECT_METHOD_NAME "message" 12 | 13 | #define DEVICE_CREDENTIAL_ENDPOINT "/api/mx/device-credentials/" //NOTE: You will need to update this to like the following: https://mxchip-.azurewebsites.net/api/mx/device-credentials/ 14 | 15 | #define DEVICE_CREDENTIAL_ENDPOINT_AUTH "Basic dDpBenVyZQ==" //NOTE: If you updated your password in your web.config - you will have to convert your password to Base64 16 | 17 | // How many messages get sent to the hub before user has to press A to continue 18 | #define MESSAGE_SEND_COUNT_LIMIT 350 -------------------------------------------------------------------------------- /backend/MXApi/Models/MetricsPayload.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace MXApi.Models 4 | { 5 | public class MetricsPayload 6 | { 7 | [JsonProperty(PropertyName = "id")] 8 | public string Id { get; set; } 9 | 10 | [JsonProperty(PropertyName = "gravity")] 11 | public double Gravity { get; set; } 12 | 13 | [JsonProperty(PropertyName = "temperature")] 14 | public double Temperature { get; set; } 15 | 16 | [JsonProperty(PropertyName = "humidity")] 17 | public double Humidity { get; set; } 18 | 19 | [JsonProperty(PropertyName = "pressure")] 20 | public double Pressure { get; set; } 21 | 22 | [JsonProperty(PropertyName = "decibels")] 23 | public double Decibels { get; set; } 24 | 25 | [JsonProperty(PropertyName = "magnetometer")] 26 | public double Magnetometer { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /frontend/mx/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import { createActions } from 'redux-actions'; 2 | 3 | const actions = createActions({ 4 | DEVICE: { 5 | SETUP: undefined, 6 | UPDATE: undefined, 7 | }, 8 | RESET: undefined, 9 | STEP: { 10 | INCREMENT: undefined, 11 | SET: undefined, 12 | }, 13 | }); 14 | 15 | export const setupDevice = data => dispatch => { 16 | dispatch(actions.device.setup(data)); 17 | }; 18 | 19 | export const updateDevice = data => dispatch => { 20 | dispatch(actions.device.update(data)); 21 | }; 22 | 23 | export const nextStep = () => dispatch => { 24 | dispatch(actions.step.increment()); 25 | }; 26 | 27 | export const setStep = stepNumber => dispatch => { 28 | dispatch(actions.step.set(stepNumber)); 29 | }; 30 | 31 | export const resetDemo = (devices, callback) => dispatch => { 32 | dispatch(actions.reset()); 33 | }; 34 | -------------------------------------------------------------------------------- /mxdevice/Device/.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Win32", 5 | "defines": [ 6 | "ARDUINO=10800" 7 | ], 8 | "includePath": [ 9 | "${workspaceFolder}", 10 | "${env:USERPROFILE}\\AppData\\Local\\Arduino15\\packages\\AZ3166\\tools\\**", 11 | "${env:USERPROFILE}\\Local\\Arduino15\\packages\\AZ3166\\hardware\\stm32f4\\1.4.1\\**" 12 | ], 13 | "forcedInclude": [ 14 | "${env:USERPROFILE}\\AppData\\Local\\Arduino15\\packages\\AZ3166\\hardware\\stm32f4\\1.4.1\\cores\\arduino\\Arduino.h" 15 | ], 16 | "intelliSenseMode": "clang-x64", 17 | "cStandard": "c11", 18 | "cppStandard": "c++17" 19 | } 20 | ], 21 | "version": 4 22 | } -------------------------------------------------------------------------------- /frontend/mx/src/components/humidity-level-gauge.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DashboardGauge from './dashboard-gauge'; 3 | import HumidityIcon from '../icons/humidity'; 4 | import { MAX_HUMIDITY } from '../common/constants'; 5 | 6 | const HumidityLevelGauge = ({ humidity }) => { 7 | const roundedSoundLevel = humidity.toFixed(1); 8 | const degValue = humidity * 244 / MAX_HUMIDITY - 122; 9 | const infoText = 10 | 'The sensor will display the surrounding humidity reading between 0 - 100%. ' + 11 | 'You can cup your hands and breathe onto the device to increase the values.'; 12 | 13 | return ( 14 | 24 | ); 25 | }; 26 | 27 | export default HumidityLevelGauge; 28 | -------------------------------------------------------------------------------- /frontend/mx/src/icons/radio-button.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const Styled = styled.g` 5 | &.icon { 6 | path { 7 | fill: #959595; 8 | } 9 | 10 | &.selected { 11 | path, circle { 12 | fill: #4597E9; 13 | } 14 | } 15 | &.unselected { 16 | circle { 17 | fill: transparent; 18 | } 19 | } 20 | } 21 | `; 22 | 23 | const RadioButton = props => { 24 | const selectedClass = props.selected ? 'selected' : 'unselected'; 25 | const classes = `icon ${selectedClass}` 26 | return ( 27 | 28 | 29 | 31 | 32 | 33 | 34 | ); 35 | } 36 | 37 | export default RadioButton; 38 | -------------------------------------------------------------------------------- /frontend/common/components/reset-app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import StyledButton from './styled/button'; 4 | 5 | const Styled = styled.div` 6 | &.reset-app { 7 | padding: 0 20%; 8 | text-align: center; 9 | 10 | p { 11 | color: #02a3ee; 12 | font-family: SegoeUISemilight, 'Helvetica Neue', Helvetica, Arial, 13 | sans-serif; 14 | font-size: 36px; 15 | margin: 16vh 0 10vh; 16 | } 17 | .actions { 18 | display: flex; 19 | justify-content: space-between; 20 | } 21 | } 22 | `; 23 | 24 | const ResetApp = ({ onAccept, onCancel }) => ( 25 | 26 |

Are you sure you want to reset the demo?

27 |
28 | 29 | Yes 30 | 31 | 32 | No 33 | 34 |
35 |
36 | ); 37 | 38 | export default ResetApp; 39 | -------------------------------------------------------------------------------- /frontend/mx/src/components/accelerometer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DashboardGauge from './dashboard-gauge'; 3 | import AccelerometerIcon from 'common/icons/accelerometer'; 4 | 5 | const Accelerometer = ({ acceleration }) => { 6 | const roundedAcceleration = acceleration.toFixed(2); 7 | const degValue = acceleration * 244 / 10 - 122; 8 | const infoText = 9 | 'We calculate the G-Force from the 3 accelerometer readings. ' + 10 | 'We first divide each value by gravity (9.81) then apply the Pythagorean ' + 11 | 'theorem. This gives us a resting G-force of around 1g. You can easily ' + 12 | 'shake your device around and you will see the amount of G\'s increase.'; 13 | 14 | return ( 15 | 25 | ); 26 | }; 27 | 28 | export default Accelerometer; 29 | -------------------------------------------------------------------------------- /frontend/mx/src/components/temperature-gauge.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DashboardGauge from './dashboard-gauge'; 3 | import TemperatureIcon from '../icons/temperature'; 4 | import { MAX_TEMPERATURE } from '../common/constants'; 5 | 6 | const TemperatureGauge = ({ temperature }) => { 7 | const temperatureInFahrenheit = Math.round(temperature); 8 | const degValue = temperatureInFahrenheit * 244 / MAX_TEMPERATURE - 122; 9 | const infoText = 10 | 'This sensor measures in Celsius - however - we convert this to Fahrenheit ' + 11 | 'in the dashboard. It will usually stay quiet stable at room temperature. ' + 12 | 'Putting the chip between both palms of your hands will increase the temperature.'; 13 | 14 | return ( 15 | 25 | ); 26 | }; 27 | 28 | export default TemperatureGauge; 29 | -------------------------------------------------------------------------------- /frontend/mx/src/reducers/devices.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { handleActions } from 'redux-actions'; 3 | 4 | /* --- SELECTORS */ 5 | export const getAll = state => state.allIds.map(id => state.byId[id]); 6 | export const get = (state, id) => state.byId[id]; 7 | 8 | /* --- REDUCERS */ 9 | const byId = handleActions( 10 | { 11 | 'DEVICE/SETUP': (state, action) => ({ 12 | ...state, 13 | [action.payload.id]: { ...action.payload }, 14 | }), 15 | 'DEVICE/UPDATE': (state, action) => ({ 16 | ...state, 17 | [action.payload.id]: { ...action.payload }, 18 | }), 19 | RESET: (state, action) => ({}), 20 | }, 21 | {} 22 | ); 23 | 24 | const allIds = handleActions( 25 | { 26 | 'DEVICE/SETUP': (state, action) => { 27 | // Do not add key if it already exists 28 | const result = new Set([...state, action.payload.id]); 29 | return [...result]; 30 | }, 31 | RESET: (state, action) => [], 32 | }, 33 | [] 34 | ); 35 | 36 | const reducers = combineReducers({ 37 | byId, 38 | allIds, 39 | }); 40 | 41 | export default reducers; 42 | -------------------------------------------------------------------------------- /frontend/common/styles/base.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-size: 100%; 3 | font-family: SegoeUI, "Helvetica Neue", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | button { 7 | border: 0 none; 8 | padding: 0; 9 | background-color: transparent; 10 | } 11 | button:focus, 12 | input[type="text"], 13 | input[type="tel"], 14 | input[type="submit"] { 15 | outline: 0 none; 16 | } 17 | 18 | input[type='text']:-webkit-autofill { 19 | -webkit-text-fill-color: #c7c7c7 !important; 20 | -webkit-box-shadow: 0 0 0px 1000px #ffffff inset; 21 | } 22 | 23 | .visually-hidden { 24 | position: absolute !important; 25 | clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ 26 | clip: rect(1px, 1px, 1px, 1px); 27 | padding: 0 !important; 28 | border: 0 !important; 29 | height: 1px !important; 30 | width: 1px !important; 31 | overflow: hidden; 32 | } 33 | 34 | @keyframes alarm { 35 | 10%, 90% { 36 | transform: translate3d(-1px, 0, 0); 37 | } 38 | 39 | 20%, 80% { 40 | transform: translate3d(2px, 0, 0); 41 | } 42 | 43 | 30%, 50%, 70% { 44 | transform: translate3d(-3px, 0, 0); 45 | } 46 | 47 | 40%, 60% { 48 | transform: translate3d(3px, 0, 0); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/mx/src/icons/humidity.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const HumidityIcon = props => { 4 | return ( 5 | 10 | 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default HumidityIcon; 18 | -------------------------------------------------------------------------------- /backend/MxDialBackend.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27906.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MXApi", "MXApi\MXApi.csproj", "{4211A13C-8390-41F1-8C47-783183913A87}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {4211A13C-8390-41F1-8C47-783183913A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {4211A13C-8390-41F1-8C47-783183913A87}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {4211A13C-8390-41F1-8C47-783183913A87}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {4211A13C-8390-41F1-8C47-783183913A87}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {3E7EB575-286A-485C-9CB2-28C467E35D92} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /frontend/mx/src/components/magnetometer-gauge.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DashboardGauge from './dashboard-gauge'; 3 | import MagnetometerIcon from '../icons/magnetism'; 4 | import { MAX_MAGNETIC_FLUX_DENSITY } from '../common/constants'; 5 | 6 | const MagnetometerGauge = ({ value }) => { 7 | const roundedMagnetLevel = value.toFixed(1); 8 | const degValue = value * 244 / MAX_MAGNETIC_FLUX_DENSITY - 122; 9 | const infoText = 10 | 'This sensor will measure the magnetization of a magnetic material like a ' + 11 | 'ferromagnet, or to measure the strength and, in some cases, the ' + 12 | 'direction of the magnetic field at a point in space. Put something made ' + 13 | 'of steel over the sensor, such as a bottle cap or your phone. You will ' + 14 | 'see the dashboard mG value update.'; 15 | 16 | return ( 17 | 27 | ); 28 | }; 29 | 30 | export default MagnetometerGauge; 31 | -------------------------------------------------------------------------------- /frontend/mx/src/containers/device-provider.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import axios from 'axios'; 3 | 4 | class DeviceProvider extends Component { 5 | state = { 6 | deviceId: null, 7 | }; 8 | 9 | componentDidMount() { 10 | // Clear any previous data 11 | window.sessionStorage.clear(); 12 | // Fetch device names 13 | axios 14 | .get(`${process.env.REACT_APP_MX_API}/api/mx`) 15 | .then(response => { 16 | let deviceId = response.headers && response.headers.deviceid; 17 | if (deviceId && typeof deviceId === 'string') { 18 | deviceId = deviceId.toLowerCase(); 19 | this.setState({ deviceId }); 20 | } 21 | }) 22 | .catch(() => { 23 | // Problem loading device id 24 | console.warn('Unable to load device id from endpoint'); // eslint-disable-line 25 | }); 26 | } 27 | 28 | setDeviceId = id => { 29 | window.sessionStorage.setItem('deviceId', id); 30 | this.setState({ deviceId: id }); 31 | }; 32 | 33 | render() { 34 | const { deviceId } = this.state; 35 | if (!deviceId) return null; 36 | return this.props.children(deviceId); 37 | } 38 | } 39 | 40 | export default DeviceProvider; 41 | -------------------------------------------------------------------------------- /frontend/mx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mx", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "facebook/create-react-app:next" 7 | }, 8 | "dependencies": { 9 | "axios": "^0.18.0", 10 | "classnames": "^2.2.5", 11 | "common": "0.0.1", 12 | "copyfiles": "^2.0.0", 13 | "cross-env": "5.1.3", 14 | "moment": "^2.22.1", 15 | "react": "^16.2.0", 16 | "react-dom": "^16.2.0", 17 | "react-redux": "^5.0.7", 18 | "redux": "^3.7.2", 19 | "redux-actions": "^2.2.1", 20 | "redux-logger": "^3.0.6", 21 | "redux-thunk": "^2.2.0", 22 | "styled-components": "^3.1.6" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test --env=jsdom", 28 | "eject": "react-scripts eject", 29 | "copyfiles": "copyfiles -u 1 build/**/*" 30 | }, 31 | "browserslist": { 32 | "development": [ 33 | "last 2 chrome versions", 34 | "last 2 firefox versions", 35 | "last 2 edge versions" 36 | ], 37 | "production": [ 38 | ">1%", 39 | "last 4 versions", 40 | "Firefox ESR", 41 | "not ie < 11" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /frontend/common/icons/accelerometer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const AccelerometerIcon = props => { 4 | return ( 5 | 10 | 11 | 17 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default AccelerometerIcon; 25 | -------------------------------------------------------------------------------- /frontend/mx/src/components/sound-level-gauge.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import DashboardGauge from './dashboard-gauge'; 3 | import SoundIcon from '../icons/sound'; 4 | import { MAX_SOUND_LEVEL } from '../common/constants'; 5 | 6 | class SoundLevelGauge extends Component { 7 | shouldComponentUpdate({ sound }) { 8 | return sound > 0; 9 | } 10 | 11 | render() { 12 | const { sound } = this.props; 13 | const roundedSoundLevel = sound.toFixed(1); 14 | const degValue = sound * 244 / MAX_SOUND_LEVEL - 122; 15 | const infoText = 16 | 'Sound is the only sensor that needs input from the MX Device directly. ' + 17 | 'It will record a sound and get the average DB reading from the raw WAV file. ' + 18 | 'Hold down the \'B\' button on your MX Chip and record sound. ' + 19 | 'Release to stop recording and the value will be sent to the hub.'; 20 | 21 | return ( 22 | 32 | ); 33 | } 34 | } 35 | 36 | export default SoundLevelGauge; 37 | -------------------------------------------------------------------------------- /backend/MXApi/Models/MxPayload.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace MXApi.Models 4 | { 5 | public class MxPayload 6 | { 7 | [JsonProperty(PropertyName = "deviceId")] 8 | public string DeviceId { get; set; } 9 | 10 | [JsonProperty(PropertyName = "messageId")] 11 | public int MessageId { get; set; } 12 | 13 | [JsonProperty(PropertyName = "temperature")] 14 | public double Temperature { get; set; } 15 | 16 | [JsonProperty(PropertyName = "humidity")] 17 | public double Humidity { get; set; } 18 | 19 | [JsonProperty(PropertyName = "pressure")] 20 | public double Pressure { get; set; } 21 | 22 | [JsonProperty(PropertyName = "accelX")] 23 | public int AccelX { get; set; } 24 | 25 | [JsonProperty(PropertyName = "accelY")] 26 | public int AccelY { get; set; } 27 | 28 | [JsonProperty(PropertyName = "accelZ")] 29 | public int AccelZ { get; set; } 30 | 31 | [JsonProperty(PropertyName = "magX")] 32 | public int MagX { get; set; } 33 | 34 | [JsonProperty(PropertyName = "magY")] 35 | public int MagY { get; set; } 36 | 37 | [JsonProperty(PropertyName = "magZ")] 38 | public int MagZ { get; set; } 39 | 40 | [JsonProperty(PropertyName = "soundRecorded")] 41 | public bool SoundRecorded { get; set; } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /frontend/mx/src/components/atmospheric-pressure-gauge.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DashboardGauge from './dashboard-gauge'; 3 | import PressureIcon from '../icons/pressure'; 4 | import { 5 | MIN_ATMOSPHERIC_PRESSURE, 6 | MAX_ATMOSPHERIC_PRESSURE, 7 | } from '../common/constants'; 8 | 9 | const AtmosphericPressureGauge = ({ pressure }) => { 10 | const roundedSoundLevel = pressure.toFixed(1); 11 | // Make sure pressure readings below the MIN threshold won't result in a negative value 12 | const degValue = 13 | (100 - 14 | (MAX_ATMOSPHERIC_PRESSURE - 15 | Math.max(pressure, MIN_ATMOSPHERIC_PRESSURE))) * 16 | 244 / 17 | 100 - 18 | 122; 19 | const infoText = 20 | 'This sensor will display the atmospheric pressure in MilliBars of your ' + 21 | 'current location. You can manipulate this value if you put it on the ' + 22 | 'floor or above your head.'; 23 | 24 | return ( 25 | 35 | ); 36 | }; 37 | 38 | export default AtmosphericPressureGauge; 39 | -------------------------------------------------------------------------------- /frontend/common/components/styled/button.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const StyledButton = styled.button` 4 | color: #02a3ee; 5 | border-style: solid; 6 | border-color: #02a3ee; 7 | font-family: SegoeUISemilight, 'Helvetica Neue', Helvetica, Arial, sans-serif; 8 | line-height: 1; 9 | border-radius: 8px; 10 | border-width: 2px; 11 | opacity: 1; 12 | transition: opacity 0.6s; 13 | 14 | &.large { 15 | font-size: 1.875rem; 16 | width: 21.25rem; 17 | height: 3.75rem; 18 | padding-bottom: 0.2rem; 19 | } 20 | 21 | &.small { 22 | font-size: 1.125rem; 23 | font-family: SegoeUISemibold, 'Helvetica Neue', Helvetica, Arial, sans-serif; 24 | padding: 5px 13px; 25 | } 26 | 27 | &.in-modal { 28 | font-family: SegoeUISemibold, 'Helvetica Neue', Helvetica, Arial, sans-serif; 29 | font-size: 24px; 30 | text-transform: uppercase; 31 | width: 180px; 32 | padding: 6px 0 8px; 33 | } 34 | 35 | &.danger { 36 | color: #f16644; 37 | border-color: #f16644; 38 | } 39 | 40 | &.optional { 41 | color: #c7c7c7; 42 | border-color: #c7c7c7; 43 | } 44 | 45 | &[disabled] { 46 | color: #c7c7c7; 47 | border-color: #c7c7c7; 48 | } 49 | 50 | &.not-visible { 51 | opacity: 0; 52 | } 53 | `; 54 | 55 | export default StyledButton; 56 | -------------------------------------------------------------------------------- /frontend/mx/src/icons/temperature.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const TemperatureIcon = props => { 4 | return ( 5 | 10 | 14 | 18 | 22 | 26 | 30 | 34 | 35 | ); 36 | }; 37 | 38 | export default TemperatureIcon; 39 | -------------------------------------------------------------------------------- /backend/MXApi/Services/TwitterService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Web; 7 | using TweetSharp; 8 | 9 | namespace MXApi.Services 10 | { 11 | public class TwitterIoTService : ITwitterService 12 | { 13 | private TwitterService _twitterService; 14 | 15 | public void Post(string message) 16 | { 17 | if (_twitterService == null) 18 | { 19 | var consumerKey = ConfigurationManager.AppSettings["TwitterConsumerKey"]; 20 | var consumerSecret = ConfigurationManager.AppSettings["TwitterConsumerSecret"]; 21 | var accessToken = ConfigurationManager.AppSettings["TwitterAccessToken"]; ; 22 | var accessTokenSecret = ConfigurationManager.AppSettings["TwitterAccessTokenSecret"]; ; 23 | CreateAuthenticatedService(consumerKey, consumerSecret, accessToken, accessTokenSecret); 24 | } 25 | 26 | _twitterService.SendTweet(new SendTweetOptions { Status = message }); 27 | } 28 | 29 | private void CreateAuthenticatedService(string consumerKey, string consumerSecret, string accessToken, string accessTokenSecret) 30 | { 31 | _twitterService = new TwitterService(consumerKey, consumerSecret); 32 | _twitterService.AuthenticateWith(accessToken, accessTokenSecret); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /backend/MXApi/Web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /frontend/common/icons/accelerometer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | 19 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /backend/MXApi/Web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /backend/MXApi/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("MXApi")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("MXApi")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("cfe8bfaa-ffde-4663-92a1-da0b6b41460d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /backend/MXApi/Resolvers/NinjectResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Web.Http.Dependencies; 5 | using Ninject; 6 | using Ninject.Syntax; 7 | 8 | namespace MXApi.Resolvers 9 | { 10 | public class NinjectDependencyScope : IDependencyScope 11 | { 12 | private IResolutionRoot _resolver; 13 | 14 | internal NinjectDependencyScope(IResolutionRoot resolver) 15 | { 16 | Contract.Assert(resolver != null); 17 | _resolver = resolver; 18 | } 19 | 20 | public void Dispose() 21 | { 22 | _resolver = null; 23 | } 24 | 25 | public object GetService(Type serviceType) 26 | { 27 | if (_resolver == null) 28 | { 29 | throw new ObjectDisposedException("this", "This scope has already been disposed"); 30 | } 31 | 32 | return _resolver.TryGet(serviceType); 33 | } 34 | 35 | public IEnumerable GetServices(Type serviceType) 36 | { 37 | if (_resolver == null) 38 | { 39 | throw new ObjectDisposedException("this", "This scope has already been disposed"); 40 | } 41 | 42 | return _resolver.GetAll(serviceType); 43 | } 44 | } 45 | 46 | public class NinjectResolver : NinjectDependencyScope, IDependencyResolver 47 | { 48 | private readonly IKernel _kernel; 49 | 50 | public NinjectResolver(IKernel kernel) 51 | : base(kernel) 52 | { 53 | _kernel = kernel; 54 | } 55 | 56 | public IDependencyScope BeginScope() 57 | { 58 | return new NinjectDependencyScope(_kernel); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /frontend/mx/src/containers/threshold-notifications.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import axios from 'axios'; 3 | import moment from 'moment'; 4 | 5 | class ThresholdNotifications extends Component { 6 | twitterAPI = `${process.env.REACT_APP_MX_API}/api/mx/twitter/messages`; 7 | 8 | getNotificationMessage = () => { 9 | const { deviceId, trigger: { message }, thresholdValue } = this.props; 10 | const time = moment() 11 | .utc() 12 | .format('h:mm A'); 13 | return `Device ${deviceId} exceeded the ${ 14 | message.threshold 15 | } threshold of ${thresholdValue}${message.unit} at ${time} UTC`; 16 | }; 17 | 18 | componentWillReceiveProps({ isThresholdExceeded }) { 19 | const { trigger, thresholdValue } = this.props; 20 | 21 | if ( 22 | trigger && 23 | thresholdValue && 24 | isThresholdExceeded && 25 | isThresholdExceeded !== this.props.isThresholdExceeded 26 | ) { 27 | const message = this.getNotificationMessage(); 28 | axios({ 29 | crossDomain: true, 30 | method: 'POST', 31 | url: this.twitterAPI, 32 | data: { 33 | messageContents: message, 34 | }, 35 | }) 36 | .then(() => { 37 | console.log('Message sent: ', message); // eslint-disable-line 38 | }) 39 | .catch(e => { 40 | if (process.env.NODE_ENV !== 'production') { 41 | console.error(e); 42 | console.log('Failed to send notification message: ', message); // eslint-disable-line 43 | } 44 | }); 45 | } 46 | } 47 | render() { 48 | return this.props.children; 49 | } 50 | } 51 | 52 | export default ThresholdNotifications; 53 | -------------------------------------------------------------------------------- /backend/MXApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web.Http; 3 | using FluentScheduler; 4 | using Microsoft.AspNet.SignalR; 5 | using Microsoft.Owin; 6 | using Microsoft.Owin.Cors; 7 | using MXApi; 8 | using MXApi.Config; 9 | using MXApi.Filters; 10 | using MXApi.Jobs; 11 | using MXApi.Resolvers; 12 | using Owin; 13 | using Swashbuckle.Application; 14 | 15 | [assembly: OwinStartup(typeof(Startup))] 16 | namespace MXApi 17 | { 18 | public class Startup 19 | { 20 | public void Configuration(IAppBuilder app) 21 | { 22 | var config = new HttpConfiguration(); 23 | 24 | // Configure routes 25 | config.MapHttpAttributeRoutes(); 26 | 27 | // Enable Swagger 28 | config.EnableSwagger(c => c.SingleApiVersion("v1", "MX API")) 29 | .EnableSwaggerUi(); 30 | 31 | // Dependency injection 32 | config.DependencyResolver = new NinjectResolver(NinjectConfig.CreateKernel()); 33 | 34 | config.Filters.Add(new BasicAuthorizeFilter()); 35 | 36 | // Refresh the service every fifteen minutes 37 | var registry = new Registry(); 38 | registry.Schedule() 39 | .WithName("read-mx-messages") 40 | .ToRunOnceAt(DateTime.Now) 41 | .AndEvery(15).Minutes(); 42 | 43 | // Initialize jobs 44 | JobManager.Initialize(registry); 45 | JobManager.JobException += (info) => JobManager.GetSchedule("read-mx-messages").Execute(); 46 | 47 | // SigR configuration 48 | var sigConfiguration = new HubConfiguration { EnableJSONP = true }; 49 | 50 | #if DEBUG 51 | app.UseCors(CorsOptions.AllowAll); 52 | #endif 53 | 54 | app.MapSignalR(sigConfiguration); 55 | app.UseWebApi(config); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /backend/MXApi/Jobs/EventHubProcessingJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Web.Hosting; 6 | using FluentScheduler; 7 | using MXApi.Services; 8 | 9 | namespace MXApi.Jobs 10 | { 11 | public class EventHubProcessingJob : IJob, IRegisteredObject, IDisposable 12 | { 13 | private static readonly object Lock = new object(); 14 | private static CancellationTokenSource _tokenSource; 15 | private static volatile MessagingService _messagingService; 16 | 17 | private bool _shuttingDown; 18 | 19 | public EventHubProcessingJob() 20 | { 21 | HostingEnvironment.RegisterObject(this); 22 | } 23 | 24 | public void Execute() 25 | { 26 | try 27 | { 28 | CancelExistingTasks(); 29 | lock (Lock) 30 | { 31 | if (_shuttingDown) 32 | { 33 | return; 34 | } 35 | 36 | CreateNewMessageService(); 37 | _messagingService.ReadMessages(_tokenSource.Token); 38 | } 39 | } 40 | catch (Exception e) 41 | { 42 | Trace.TraceError($"Failed to execute event hub processing job: {e}"); 43 | throw; 44 | } 45 | } 46 | 47 | public void Stop(bool immediate) 48 | { 49 | lock (Lock) 50 | { 51 | _shuttingDown = true; 52 | } 53 | 54 | HostingEnvironment.UnregisterObject(this); 55 | } 56 | 57 | public void Dispose() 58 | { 59 | HostingEnvironment.UnregisterObject(this); 60 | } 61 | 62 | private void CreateNewMessageService() 63 | { 64 | _messagingService = new MessagingService(); 65 | } 66 | 67 | private void CancelExistingTasks() 68 | { 69 | if (_tokenSource != null) 70 | { 71 | _tokenSource.Cancel(); 72 | _tokenSource.Dispose(); 73 | } 74 | 75 | _tokenSource = new CancellationTokenSource(); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /frontend/common/containers/modal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import ModalComponent from '../components/modal'; 4 | 5 | const MODAL_ANIMATION_LENGTH = 0.8; // seconds 6 | 7 | const ModalContainer = ContentComponent => 8 | class extends Component { 9 | state = { 10 | isModalLoaded: false, 11 | isModalVisible: false, 12 | }; 13 | 14 | componentWillReceiveProps({ show }) { 15 | if (show !== this.props.show) { 16 | // Prop controlling visibility has changed => modify state accordingly 17 | if (show) { 18 | this.showModal(); 19 | } else { 20 | this.hideModal(); 21 | } 22 | } 23 | } 24 | 25 | showModal = () => { 26 | this.setState({ isModalLoaded: true }, () => { 27 | setTimeout(() => { 28 | this.setState({ isModalVisible: true }); 29 | }, 200); 30 | }); 31 | }; 32 | 33 | hideModal = () => { 34 | this.setState({ isModalVisible: false }, () => { 35 | setTimeout(() => { 36 | this.setState({ isModalLoaded: false }); 37 | }, MODAL_ANIMATION_LENGTH * 1000); 38 | }); 39 | }; 40 | 41 | render() { 42 | const { className, onCancel } = this.props; 43 | const { isModalLoaded, isModalVisible } = this.state; 44 | const animationLength = `${MODAL_ANIMATION_LENGTH}s`; 45 | 46 | if (!isModalLoaded) return null; 47 | 48 | return ( 49 |
50 | {ReactDOM.createPortal( 51 | 57 | 58 | , 59 | document.getElementById('modal') 60 | )} 61 |
62 | ); 63 | } 64 | }; 65 | 66 | export default ModalContainer; 67 | -------------------------------------------------------------------------------- /frontend/mx/src/components/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import Header from 'common/components/header'; 4 | import Modal from 'common/containers/modal'; 5 | import DeviceDashboard from './device-dashboard'; 6 | import Footer from '../containers/footer'; 7 | import SetRule from '../containers/set-rule'; 8 | import { DEVICE_IMAGE_MAP, DEFAULT_DEVICE_TYPE } from '../common/constants'; 9 | 10 | const SetRuleModal = Modal(SetRule); 11 | 12 | const Styled = styled.div` 13 | &.mx-demo { 14 | max-width: 2000px; 15 | margin: 0 auto; 16 | height: 100vh; 17 | display: flex; 18 | flex-direction: column; 19 | 20 | main { 21 | flex: 1; 22 | position: relative; 23 | } 24 | } 25 | `; 26 | 27 | const App = ({ 28 | numDevices, 29 | device, 30 | isThresholdExceeded, 31 | nextStep, 32 | onReset, 33 | step, 34 | setStep, 35 | setThreshold, 36 | trigger, 37 | }) => { 38 | const imageObj = 39 | DEVICE_IMAGE_MAP[device.type] || DEVICE_IMAGE_MAP[DEFAULT_DEVICE_TYPE]; 40 | const image = require(`../images/${imageObj.angled}`); 41 | 42 | return ( 43 | 44 |
45 |
46 | 54 |
55 | {/*

Value is: {value}

*/} 56 |