Repository: MSOpenTech/connectthedots Branch: master Commit: fd72e90a2962 Files: 407 Total size: 3.5 MB Directory structure: gitextract_ovnh2m6b/ ├── .deployment ├── .gitattributes ├── .gitignore ├── .gitmodules ├── Azure/ │ ├── ARMTemplate/ │ │ ├── CustomizeTemplate.md │ │ ├── Readme.md │ │ └── azuredeploy.json │ ├── EHConsole/ │ │ ├── EHConsole/ │ │ │ ├── App.config │ │ │ ├── Microsoft.ConnectTheDots.EHConsole.csproj │ │ │ ├── Program.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ └── packages.config │ │ ├── EHConsole.sln │ │ └── nuget.config │ ├── MachineLearning/ │ │ ├── MachineLearningCloudService.md │ │ ├── SQL/ │ │ │ ├── SQL.sqlproj │ │ │ ├── StoredProcedures/ │ │ │ │ └── InsertAlertsData.sql │ │ │ ├── TableType/ │ │ │ │ └── AlertsDataTableType.sql │ │ │ └── Tables/ │ │ │ └── AlertsData.sql │ │ ├── WorkerHost/ │ │ │ ├── Analyzer.cs │ │ │ ├── AnomalyRecord.cs │ │ │ ├── App.config │ │ │ ├── CircularBuffer.cs │ │ │ ├── Data.Outputs/ │ │ │ │ ├── BlobWriter.cs │ │ │ │ ├── SQLOutputRepository.cs │ │ │ │ └── Utils/ │ │ │ │ └── SqlDBReaderSafeParser.cs │ │ │ ├── EventHubReader.cs │ │ │ ├── Program.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── SensorDataContract.cs │ │ │ ├── WorkerHost.csproj │ │ │ └── packages.config │ │ ├── WorkerHost.sln │ │ └── WorkerRole/ │ │ ├── ServiceConfiguration.Cloud.cscfg │ │ ├── ServiceConfiguration.Local.cscfg │ │ ├── ServiceDefinition.csdef │ │ ├── WorkerHostContent/ │ │ │ └── diagnostics.wadcfgx │ │ └── WorkerRole.ccproj │ ├── PowerBI/ │ │ └── PBI_setup.md │ ├── StreamAnalyticsQueries/ │ │ ├── Aggregates.sql │ │ ├── Alert.sql │ │ ├── HumidityAlert.sql │ │ ├── LightSensor.sql │ │ ├── SA_setup.md │ │ └── cg4pbi.sql │ └── WebSite/ │ ├── WebsiteDetails.md │ ├── WebsitePublish.md │ ├── site/ │ │ ├── Default.aspx │ │ ├── Docs/ │ │ │ └── license.txt │ │ ├── Global.asax │ │ ├── Web.config │ │ ├── css/ │ │ │ └── connectthedots.css │ │ ├── js/ │ │ │ ├── d3CTD.js │ │ │ ├── d3CTDDataSourceFilter.js │ │ │ ├── d3CTDDataSourceSocket.js │ │ │ ├── d3Chart.js │ │ │ ├── d3ChartControl.js │ │ │ ├── d3DataFlow.js │ │ │ ├── d3DataSourceSocket.js │ │ │ ├── d3utils.js │ │ │ ├── devicesList.js │ │ │ ├── jquery.ui-contextmenu.js │ │ │ └── qrcode.js │ │ └── packages.config │ └── source/ │ ├── ConnectTheDotsWebSite/ │ │ ├── ConnectTheDotsWebSite.csproj │ │ ├── Default.aspx │ │ ├── Default.aspx.cs │ │ ├── Default.aspx.designer.cs │ │ ├── Docs/ │ │ │ └── license.txt │ │ ├── Global.asax │ │ ├── Global.asax.cs │ │ ├── Helpers/ │ │ │ ├── BlobHelper.cs │ │ │ └── IoTHubHelper.cs │ │ ├── Properties/ │ │ │ ├── AssemblyInfo.cs │ │ │ └── PublishProfiles/ │ │ │ └── LocalDeploy.pubxml │ │ ├── SensorInventory.cs │ │ ├── Web.Debug.config │ │ ├── Web.PublishTemplate.config │ │ ├── Web.Release.config │ │ ├── Web.config │ │ ├── WebSocketEventProcessor.cs │ │ ├── WebSocketHandler.cs │ │ ├── css/ │ │ │ └── connectthedots.css │ │ ├── js/ │ │ │ ├── d3CTD.js │ │ │ ├── d3CTDDataSourceFilter.js │ │ │ ├── d3CTDDataSourceSocket.js │ │ │ ├── d3Chart.js │ │ │ ├── d3ChartControl.js │ │ │ ├── d3DataFlow.js │ │ │ ├── d3DataSourceSocket.js │ │ │ ├── d3utils.js │ │ │ ├── devicesList.js │ │ │ ├── jquery.ui-contextmenu.js │ │ │ └── qrcode.js │ │ └── packages.config │ └── ConnectTheDotsWebSite.sln ├── Contribute.md ├── Devices/ │ ├── DirectlyConnectedDevices/ │ │ ├── Common/ │ │ │ ├── csharp/ │ │ │ │ └── ConnectTheDotsHelper.cs │ │ │ └── javascript/ │ │ │ ├── connectthedots.js │ │ │ ├── package.json │ │ │ └── readme.md │ │ ├── ESP8266/ │ │ │ ├── ESP8266_setup.md │ │ │ └── connect_the_dots/ │ │ │ ├── connect_the_dots.cpp │ │ │ ├── connect_the_dots.h │ │ │ └── connect_the_dots.ino │ │ ├── NodeJS/ │ │ │ ├── BeagleBoneBlack/ │ │ │ │ ├── BeagleBone_Black_setup.md │ │ │ │ ├── Hardware.md │ │ │ │ ├── beagleboneblackctd.js │ │ │ │ ├── package.json │ │ │ │ └── settings.json │ │ │ ├── Desktop/ │ │ │ │ ├── Desktop_setup.md │ │ │ │ ├── desktop.js │ │ │ │ ├── package.json │ │ │ │ └── settings.json │ │ │ ├── IntelEdisonGrove/ │ │ │ │ ├── Hardware.md │ │ │ │ ├── Intel_Edison_setup.md │ │ │ │ ├── inteledisonctd.js │ │ │ │ ├── package.json │ │ │ │ └── settings.json │ │ │ ├── IntelEdisonSensorTag/ │ │ │ │ ├── Hardware.md │ │ │ │ ├── Intel_Edison_setup.md │ │ │ │ ├── inteledisonsensortagctd.js │ │ │ │ ├── lib/ │ │ │ │ │ ├── cc2540.js │ │ │ │ │ ├── cc2650.js │ │ │ │ │ ├── common.js │ │ │ │ │ └── sensortag.js │ │ │ │ ├── package.json │ │ │ │ └── settings.json │ │ │ ├── IntelEdisonXadow/ │ │ │ │ ├── Hardware.md │ │ │ │ ├── Intel_Edison_setup.md │ │ │ │ ├── inteledisonctd.js │ │ │ │ ├── package.json │ │ │ │ └── settings.json │ │ │ ├── SensorTag/ │ │ │ │ ├── Setup.md │ │ │ │ ├── index.js │ │ │ │ ├── package.json │ │ │ │ ├── sensorWorker.js │ │ │ │ └── settings.json │ │ │ └── Tessel2/ │ │ │ ├── Hardware.md │ │ │ ├── Tessel2_setup.md │ │ │ ├── package.json │ │ │ ├── settings.json │ │ │ └── tessel2ctd.js │ │ ├── UWPMSBand/ │ │ │ ├── UWPMSBand/ │ │ │ │ ├── App.xaml │ │ │ │ ├── App.xaml.cs │ │ │ │ ├── MainPage.xaml │ │ │ │ ├── MainPage.xaml.cs │ │ │ │ ├── Package.appxmanifest │ │ │ │ ├── Properties/ │ │ │ │ │ ├── AssemblyInfo.cs │ │ │ │ │ └── Default.rd.xml │ │ │ │ ├── UWPMSBand.csproj │ │ │ │ ├── UWPMSBand_TemporaryKey.pfx │ │ │ │ ├── project.json │ │ │ │ └── project.lock.json │ │ │ └── UWPMSBand.sln │ │ ├── UWPSimulatedSensors/ │ │ │ ├── UWPSimulatedSensors/ │ │ │ │ ├── App.xaml │ │ │ │ ├── App.xaml.cs │ │ │ │ ├── ApplicationInsights.config │ │ │ │ ├── MainPage.xaml │ │ │ │ ├── MainPage.xaml.cs │ │ │ │ ├── Package.appxmanifest │ │ │ │ ├── Properties/ │ │ │ │ │ ├── AssemblyInfo.cs │ │ │ │ │ └── Default.rd.xml │ │ │ │ ├── UWPSimulatedSensors.csproj │ │ │ │ ├── UWPSimulatedSensors_TemporaryKey.pfx │ │ │ │ ├── project.json │ │ │ │ └── project.lock.json │ │ │ └── UWPSimulatedSensors.sln │ │ └── XamarinSimulatedSensors/ │ │ ├── XamarinSimulatedSensors/ │ │ │ ├── XamarinSimulatedSensors/ │ │ │ │ ├── Helpers/ │ │ │ │ │ └── Settings.cs │ │ │ │ ├── MyClass.cs │ │ │ │ ├── Properties/ │ │ │ │ │ └── AssemblyInfo.cs │ │ │ │ ├── XamarinSimulatedSensors.csproj │ │ │ │ ├── app.config │ │ │ │ └── packages.config │ │ │ ├── XamarinSimulatedSensors.Droid/ │ │ │ │ ├── Assets/ │ │ │ │ │ └── AboutAssets.txt │ │ │ │ ├── Helpers/ │ │ │ │ │ └── Settings.cs │ │ │ │ ├── MainActivity.cs │ │ │ │ ├── Properties/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── AssemblyInfo.cs │ │ │ │ ├── Resources/ │ │ │ │ │ ├── AboutResources.txt │ │ │ │ │ ├── Resource.designer.cs │ │ │ │ │ ├── layout/ │ │ │ │ │ │ └── Main.axml │ │ │ │ │ └── values/ │ │ │ │ │ └── Strings.xml │ │ │ │ ├── XamarinSimulatedSensors.Droid.csproj │ │ │ │ ├── app.config │ │ │ │ └── packages.config │ │ │ ├── XamarinSimulatedSensors.Windows/ │ │ │ │ ├── App.config │ │ │ │ ├── Form1.Designer.cs │ │ │ │ ├── Form1.cs │ │ │ │ ├── Form1.resx │ │ │ │ ├── Helpers/ │ │ │ │ │ └── Settings.cs │ │ │ │ ├── Program.cs │ │ │ │ ├── Properties/ │ │ │ │ │ ├── AssemblyInfo.cs │ │ │ │ │ ├── Resources.Designer.cs │ │ │ │ │ ├── Resources.resx │ │ │ │ │ ├── Settings.Designer.cs │ │ │ │ │ └── Settings.settings │ │ │ │ ├── XamarinSimulatedSensors.Windows.csproj │ │ │ │ └── packages.config │ │ │ └── XamarinSimulatedSensors.iOS/ │ │ │ ├── AppDelegate.cs │ │ │ ├── Entitlements.plist │ │ │ ├── Helpers/ │ │ │ │ └── Settings.cs │ │ │ ├── Info.plist │ │ │ ├── Main.cs │ │ │ ├── Main.storyboard │ │ │ ├── Resources/ │ │ │ │ ├── ITunesArtwork │ │ │ │ ├── ITunesArtwork@2x │ │ │ │ ├── Images.xcassets/ │ │ │ │ │ ├── AppIcons.appiconset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Images.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── LaunchImage.launchimage/ │ │ │ │ │ └── Contents.json │ │ │ │ └── LaunchScreen.storyboard │ │ │ ├── ViewController.cs │ │ │ ├── ViewController.designer.cs │ │ │ ├── XamarinSimulatedSensors.iOS.csproj │ │ │ ├── app.config │ │ │ └── packages.config │ │ └── XamarinSimulatedSensors.sln │ ├── GatewayConnectedDevices/ │ │ ├── Arduino DUE/ │ │ │ └── Temperature/ │ │ │ └── DS18B20/ │ │ │ ├── DS18B20.ino │ │ │ └── Libraries.md │ │ ├── Arduino UNO/ │ │ │ ├── Accelerometer/ │ │ │ │ └── Memsic2125Json/ │ │ │ │ ├── Libraries.md │ │ │ │ └── Memsic2125Json.ino │ │ │ ├── AnalogReadSerial/ │ │ │ │ └── AnalogReadSerial.ino │ │ │ ├── Combo_accelerometer_and_temperature/ │ │ │ │ └── Memsic2125_plus_DS18B20/ │ │ │ │ ├── Libraries.md │ │ │ │ └── Memsic2125_plus_DS18B20.ino │ │ │ ├── Sound/ │ │ │ │ └── SimpleSoundSensor/ │ │ │ │ └── SimpleSoundSensor.ino │ │ │ ├── Temperature/ │ │ │ │ └── DS18B20/ │ │ │ │ ├── DS18B20.ino │ │ │ │ └── Libraries.md │ │ │ └── Weather/ │ │ │ └── WeatherShieldJson/ │ │ │ ├── Arduino-and-Weather-Shield-setup.md │ │ │ ├── Hardware.md │ │ │ ├── Libraries.md │ │ │ ├── SCPRPI.cmd │ │ │ └── WeatherShieldJson.ino │ │ ├── Arduino Zero/ │ │ │ └── Weather/ │ │ │ └── SeeedGroveJson/ │ │ │ ├── SeeedGroveJson.ino │ │ │ └── readme.md │ │ ├── BLEMoisture/ │ │ │ ├── BLEMoistureSensor.py │ │ │ ├── DeviceConfig.csv │ │ │ ├── RedBear_BLE_nano/ │ │ │ │ ├── Configure.md │ │ │ │ └── main.cpp │ │ │ ├── SensorAgent.py │ │ │ ├── SensorAgentConfig.csv │ │ │ ├── autorun2.sh │ │ │ ├── configuration.md │ │ │ └── deploy_next.cmd │ │ ├── BluetoothUARTExample/ │ │ │ ├── BluetoothUARTExample.py │ │ │ ├── SetupSerialBaudRate.py │ │ │ └── readme │ │ ├── BtUSB_2_BtUART_Example/ │ │ │ ├── ArduinoSensorMock/ │ │ │ │ └── ArduinoSensorMock.ino │ │ │ ├── BtUSB_2_BtUART_Example.py │ │ │ ├── TestServer.py │ │ │ └── readme │ │ ├── Hydrology/ │ │ │ ├── DO2Sensor.py │ │ │ ├── Documentation/ │ │ │ │ └── DeviceSetup.md │ │ │ ├── ECSensor.py │ │ │ ├── MoistureSensor.py │ │ │ ├── SensorAgent.py │ │ │ ├── autorun2.sh │ │ │ └── deploy_next.cmd │ │ └── WensnSoundLevelMeter/ │ │ └── WensnPiVS01/ │ │ ├── WensnPiVS01.py │ │ ├── autorun2.sh │ │ └── deploy_next.cmd │ ├── Gateways/ │ │ └── GatewayService/ │ │ ├── Common/ │ │ │ ├── Adapter/ │ │ │ │ └── SensorEndpoint.cs │ │ │ ├── ILogger.cs │ │ │ ├── IPAddressHelper.cs │ │ │ ├── Logger/ │ │ │ │ ├── NLogEventLogger.cs │ │ │ │ ├── SafeLogger.cs │ │ │ │ └── TunableLogger.cs │ │ │ ├── Microsoft.ConnectTheDots.Common.csproj │ │ │ ├── Platform.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── SafeAction.cs │ │ │ ├── SafeFunction.cs │ │ │ ├── Threading/ │ │ │ │ └── TaskWrapper.cs │ │ │ ├── Utils/ │ │ │ │ └── Loader.cs │ │ │ └── packages.config │ │ ├── DeviceAdapters/ │ │ │ ├── Bluetooth/ │ │ │ │ ├── BluetoothUARTAdapter.cs │ │ │ │ ├── BluetoothUARTAdapter.csproj │ │ │ │ └── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── SerialPort/ │ │ │ │ ├── Properties/ │ │ │ │ │ └── AssemblyInfo.cs │ │ │ │ ├── SerialPortAdapter.cs │ │ │ │ └── SerialPortAdapter.csproj │ │ │ └── Socket/ │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── SocketAdapter.cs │ │ │ └── SocketAdapter.csproj │ │ ├── Gateway/ │ │ │ ├── Adapter/ │ │ │ │ ├── DeviceAdapter.cs │ │ │ │ └── IDeviceAdapter.cs │ │ │ ├── App.config │ │ │ ├── Constants.cs │ │ │ ├── DeviceAdapter/ │ │ │ │ ├── DeviceAdapter.cs │ │ │ │ ├── IDeviceAdapter.cs │ │ │ │ └── SensorEndpoint.cs │ │ │ ├── EventProcessor.cs │ │ │ ├── GatewayService.cs │ │ │ ├── GatewayService.csproj │ │ │ ├── IGatewayService.cs │ │ │ ├── Microsoft.ConnectTheDots.Gateway.csproj │ │ │ ├── Models/ │ │ │ │ ├── QueuedItem.cs │ │ │ │ └── SensorDataContract.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── ServiceInstantiation/ │ │ │ │ ├── IService.cs │ │ │ │ ├── ServiceBehavior.cs │ │ │ │ └── ServiceInstanceProvider.cs │ │ │ ├── Utils/ │ │ │ │ ├── Loader/ │ │ │ │ │ └── DataIntakeLoader.cs │ │ │ │ ├── MessageSender/ │ │ │ │ │ ├── IMessageSender.cs │ │ │ │ │ └── MessageSender.cs │ │ │ │ ├── OperationStatus/ │ │ │ │ │ ├── ErrorCode.cs │ │ │ │ │ ├── OperationStatus.cs │ │ │ │ │ └── OperationStatusFactory.cs │ │ │ │ └── Queue/ │ │ │ │ ├── BatchSenderThread.cs │ │ │ │ ├── GatewayQueue.cs │ │ │ │ └── IAsyncQueue.cs │ │ │ └── packages.config │ │ ├── GatewayService.sln │ │ ├── Hardware.md │ │ ├── RaspberryPi-Gateway-setup.md │ │ ├── Scripts/ │ │ │ ├── RaspberryPi/ │ │ │ │ ├── GetLogFile.cmd │ │ │ │ ├── Modified/ │ │ │ │ │ ├── autorun_install.sh │ │ │ │ │ ├── certificate_update.sh │ │ │ │ │ ├── deploy_and_start_ctd_on_boot.sh │ │ │ │ │ └── kill_all.sh │ │ │ │ ├── autorunUart2UsbBt.sh │ │ │ │ ├── autorunUartBT.sh │ │ │ │ ├── autorunWensnSoundSensor.sh │ │ │ │ ├── autorun_install.sh │ │ │ │ ├── certificate_update.sh │ │ │ │ ├── deploy.cmd │ │ │ │ ├── deploy_and_start_ctd_on_boot.sh │ │ │ │ ├── deploy_next.cmd │ │ │ │ ├── kill_all.sh │ │ │ │ ├── setupWifi.py │ │ │ │ └── setup_autostart.sh │ │ │ └── ScriptConverter/ │ │ │ ├── App.config │ │ │ ├── Program.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ └── ScriptConverter.csproj │ │ ├── ServiceMonitor/ │ │ │ ├── App.config │ │ │ ├── IMonitor.cs │ │ │ ├── Microsoft.ConnectTheDots.ServiceMonitor.csproj │ │ │ ├── Monitor.cs │ │ │ ├── MonitorProgram.cs │ │ │ ├── NLog.config │ │ │ ├── NLog.xsd │ │ │ ├── ProcessMonitor.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── ServiceMonitor.cs │ │ │ ├── Utils/ │ │ │ │ └── Logger/ │ │ │ │ └── Logger.cs │ │ │ └── packages.config │ │ ├── Setup/ │ │ │ ├── WindowsExeSetup/ │ │ │ │ ├── Product.wxs │ │ │ │ └── WindowsExeSetup.wixproj │ │ │ └── WindowsServiceSetup/ │ │ │ ├── Product.wxs │ │ │ └── WindowsServiceSetup.wixproj │ │ ├── Tests/ │ │ │ ├── BatchSenderThreadTest/ │ │ │ │ ├── App.config │ │ │ │ ├── BatchSenderThreadTest.cs │ │ │ │ ├── BatchSenderThreadTest.csproj │ │ │ │ ├── Properties/ │ │ │ │ │ └── AssemblyInfo.cs │ │ │ │ ├── TestRunner.cs │ │ │ │ └── Utils/ │ │ │ │ └── MessageSender/ │ │ │ │ ├── MockSenderAsyncQueue.cs │ │ │ │ └── MockSenderMap.cs │ │ │ ├── CoreTest/ │ │ │ │ ├── App.config │ │ │ │ ├── CoreTest.cs │ │ │ │ ├── CoreTest.csproj │ │ │ │ ├── Devices/ │ │ │ │ │ ├── SocketClientTestDevice.cs │ │ │ │ │ └── SocketServiceTestDevice.cs │ │ │ │ ├── ITest.cs │ │ │ │ ├── Properties/ │ │ │ │ │ └── AssemblyInfo.cs │ │ │ │ ├── RealDataTest.cs │ │ │ │ ├── SocketTest.cs │ │ │ │ ├── TestRunner.cs │ │ │ │ ├── Utils/ │ │ │ │ │ ├── Generators/ │ │ │ │ │ │ └── RandomSensorDataGenerator.cs │ │ │ │ │ ├── Loader/ │ │ │ │ │ │ └── Loader.cs │ │ │ │ │ ├── Logger/ │ │ │ │ │ │ └── TestLogger.cs │ │ │ │ │ └── MessageSender/ │ │ │ │ │ └── MockSender.cs │ │ │ │ ├── WebServiceTest.cs │ │ │ │ └── packages.config │ │ │ ├── DeviceAdapterTestMock/ │ │ │ │ ├── DeviceAdapterTestMock.cs │ │ │ │ ├── DeviceAdapterTestMock.csproj │ │ │ │ ├── Properties/ │ │ │ │ │ └── AssemblyInfo.cs │ │ │ │ └── packages.config │ │ │ └── SocketServiceDeviceMock/ │ │ │ ├── App.config │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── SocketServiceDeviceMock.cs │ │ │ ├── SocketServiceDeviceMock.csproj │ │ │ ├── Utils/ │ │ │ │ └── Logger/ │ │ │ │ └── ConsoleLogger.cs │ │ │ └── packages.config │ │ ├── UpgradeLog.htm │ │ ├── WiFi-Configuration.md │ │ ├── WindowsEXE/ │ │ │ ├── App.config │ │ │ ├── Microsoft.ConnectTheDots.GatewayExe.csproj │ │ │ ├── NLog.config │ │ │ ├── NLog.xsd │ │ │ ├── Program.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ └── packages.config │ │ └── WindowsService/ │ │ ├── App.config │ │ ├── Microsoft.ConnectTheDots.GatewayService.csproj │ │ ├── NLog.config │ │ ├── NLog.xsd │ │ ├── ProjectInstaller.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── Utils/ │ │ │ └── Logger/ │ │ │ └── EventLogger.cs │ │ ├── WindowsService.cs │ │ └── packages.config │ └── readme.md ├── GettingStarted.md ├── SupportedDevices.md ├── license.txt ├── notice.txt └── readme.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .deployment ================================================ [config] project = Azure/WebSite/site ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### # Prevent from further automatic changes * text=binary ############################################################################### # We assume following configuration # Some sh files and other could be changed from Windows too ############################################################################### #* text=auto #*.sln text eol=crlf #*.csproj text eol=crlf #*.cs text eol=crlf #*.cpp text eol=crlf #*.h text eol=crlf #*.config text eol=crlf #*.sql text eol=crlf #*.asax text eol=crlf #*.css text eol=crlf #*.js text eol=crlf #*.xsd text eol=crlf # #*.wxs text eol=crlf #*.wixproj text eol=crlf # #*.cmd text eol=crlf # #*.ino text eol=lf #*.py text eol=lf #*.sh text eol=lf # #Devices\DirectlyConnectedDevices\NodeJS\*.* text=binary #Devices\DirectlyConnectedDevicesParticleCore\*.* text=binary ############################################################################### ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .gitignore ================================================ # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) [Bb]in/ [Oo]bj/ # mstest test results TestResults ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. AzurePrep.sln.ide/ ConnectTheDotsWebSite.sln.ide/ GatewayService.sln.ide/ # User-specific files *.suo *.user *.sln.docstates # Build results [Dd]ebug/ [Rr]elease/ x64/ *_i.c *_p.c *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.log *.vspscc *.vssscc .builds # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf # Visual Studio profiler *.psess *.vsp *.vspx # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper* # NCrunch *.ncrunch* .*crunch*.local.xml # Installshield output folder [Ee]xpress # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish # Publish Web Output *.Publish.xml # NuGet Packages Directory packages # Windows Azure Build Output csx *.build.csdef # Windows Store app package directory AppPackages/ # Others [Bb]in [Oo]bj sql TestResults [Tt]est[Rr]esult* *.Cache ClientBin [Ss]tyle[Cc]op.* ~$* *.dbmdl Generated_Code #added for RIA/Silverlight projects # Backup & report files from converting an old project file to a newer # Visual Studio version. Backup files are not needed, because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML # NuGet Packages *.nupkg .nuget #Thumbs.db Thumbs.db Azure/MachineLearning/UpgradeLog.htm # Node modules directory node_modules/ # Visual Studio config files .vs/ # VS Code config files .vscode # Website exception for bin !/Azure/Website/site/bin/ ================================================ FILE: .gitmodules ================================================ [submodule "Devices/DirectlyConnectedDevices/WindowsIoTCorePi2WeatherShield"] path = Devices/DirectlyConnectedDevices/WindowsIoTCorePi2WeatherShield url = https://github.com/ms-iot/iot-build-lab.git branch = ConnectTheDotsSample ================================================ FILE: Azure/ARMTemplate/CustomizeTemplate.md ================================================ You can adapt the deployment of services in order to extend, optimize and customize your own ConnectTheDots solution. The deployment of the ConnectTheDots solution is driven by the Azure Resource Manager. Azure Resource Manager simplifies the deployment, maintenance and management of Azure resources. You can read about it extensively [here](https://azure.microsoft.com/en-us/documentation/articles/resource-group-overview/). The ConnectTheDots.json file (in the same folder as this document) is the template used for ConnectTheDots deployment. Below you will find a description of what's in the template and [here](https://azure.microsoft.com/en-us/documentation/articles/resource-group-authoring-templates/) you can read details about ARM templates syntax and formatting. ## Resources in the template ## Here is what you will find in the template that will get deployed ![](images/Resources.png) - Storage: - The storage account is necessary for reading alerts from the Event Hub in the Website. - If you remove it then you won't be able to read alerts any longer. - IoTHub: - The Azure IoT Hub service is your cloud gateway for devices. - This is the service that is used to securely connect devices to Azure IoT. When deploying the sevice using the ARM template, you will be prompted to select a SKU. You can choose to pick the free SKU which should be plenty enough for your ConnectTheDots solution (note that you can only deploy one of the free SKU of IoTHub per Azure subscription) - You cannot remove this service as it is core to the ConnectTheDots architecture. - Service Bus: - This is the Service Bus instance that hosts the Event Hub used for alerts flow. - If you don't want to implement alerts or other alerts, you don't need this service and can comment it out. If you do so you will also have to customize the Website code that assumes this service is deployed. - Stream Analytics Job: - This is the Stream Analytics job that does near real time analytics on the stream of data coming from devices (computing averages and triggering alerts) - You can customize the query as described in [this document](../StreamAnalyticsQueries/SA_Setup.md). - You can remove this service from the template with no impact on the rest of the solution (other than not having alerts triggered any more) - Web Plan: - This is the App Services plan for the Web dashboard. - If you remove this one, you won't be able to deploy the Website any longer - DashboardWebApp - This is the Website dashboard - The site itself is deployed using a Webdeploy package. Read [here](../WebSite/WebsitePublish.md) if you want to customize and redeploy the site. ## Template walk through ``` { "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json", "contentVersion": "1.0.0.0", // The parameters below are used to configure the deployment "parameters": { "iotHubSku": { // The SKU for the IoT Hub service is to be selected during deployment. F1 is for the free SKU (only one instance of the F1 SKU can be deployed per subscription) ... }, "iotHubTier": { // The tier for the IoT Hub instance is set by default to "Standard" ... }, "packageUri": { // This URI is for the website WebDeploy package. The package is published by the ConnectTheDots team, but if you want your own package to be deployed, you can create it locally and change the below URI to point to it. "type": "string", "defaultValue": "http://aka.ms/connectthedotswebsitepackage" }, "sbSku": { // The Service Bus SKU is set the cheapest available one by default ... }, "solutionName": { // During deployment, you will be prompted for a solution name. This name is used as a prefix for naming the resource group and the various services. Use a unique name! ... }, "storageAccountSku": { // The Storage account SKU is set the cheapest available one by default ... }, "webSku": { // The Storage account SKU is set the cheapest available one by default ... }, "webWorkerCount": { // This is used to scale the web plan. we only have one worker ... }, "webWorkerSize": { // Set to the minimum available } }, // The variables below are used whithin the template deployment process and don't need to be changed... "variables": { ... }, // Below is the list of resources that will be deployed "resources": [ { // Storage account "apiVersion": "[variables('storageVersion')]", "location": "[variables('location')]", "name": "[variables('storageName')]", ... }, { // IoT Hub "apiVersion": "[variables('iotHubVersion')]", "type": "Microsoft.Devices/Iothubs", "name": "[variables('iotHubName')]", ... }, { // Service Bus + Event Hub (forthe alerts Event Hub) "apiVersion": "[variables('sbVersion')]", "name": "[variables('sbName')]", ... "resources": [ { // Event Hub "apiVersion": "[variables('sbVersion')]", "name": "[variables('ehOutName')]", "type": "eventHubs", ... } ] }, { // Stream Analytics job "apiVersion": "[variables('saVersion')]", "type": "Microsoft.StreamAnalytics/streamingjobs", "name": "[concat(parameters('solutionName'), 'alerts')]", ... "Transformation": { "Name": "AllToBlob", // Here is the inline default query that you can customize "Properties": { "Query": "SELECT \r\n 'TempSpike' AS alerttype, \r\n 'Temperature over 80F' AS message, \r\n displayname,\r\n guid,\r\n measurename,\r\n unitofmeasure,\r\n location,\r\n organization,\r\n MIN(timecreated) AS timecreated,\r\n MAX(value) AS tempMax,\r\n MAX(value) AS value\r\nINTO\r\n DeviceInfoEvents\r\nFROM \r\n IoTHubStream TIMESTAMP BY timecreated\r\nWHERE\r\n measurename = 'temperature' OR measurename = 'Temperature'\r\nGROUP BY \r\n displayname, guid, measurename, unitofmeasure, location, organization,\r\n TumblingWindow(Second, 5)\r\n HAVING\r\n tempMax > 80", "StreamingUnits": 1 } } } }, { // Webplan "apiVersion": "[variables('webVersion')]", "name": "[variables('webPlanName')]", "type": "Microsoft.Web/serverfarms", ... }, { // WebSite (deployed using webdeploy package "name": "[variables('webSiteName')]", "type": "Microsoft.Web/sites", "location": "[variables('location')]", ... } ], // The below are outputs printed at the end of the deployment for the user to have references such as connection strings "outputs": { ... } } ``` ================================================ FILE: Azure/ARMTemplate/Readme.md ================================================ ## How to deploy Azure services for ConnectTheDots.io To deploy the ConnectTheDots solution to your Azure subscription you will need to follow the below instructions. We are using Azure Resource Manager to deploy the needed services and connect them to one another. We are also using the Azure cross platform CLI tool which will allow you to deploy the services from your favorite development machine, running Windows, Linux or OSX. The below services will be deployed in your Azure subscription: - 1 instance of Azure IoT Hub (using the SKU of your choice, considering you can only deploy 1 instance of the free SKU per subscription) - 1 Storage account (Standard performance) - 1 Service Bus instance (Basic tier) with 1 Event Hub (1 throughput Unit) - 1 Stream Analytics Job (1 streaming unit) - 1 App Service plan (Standard: 2 Small SKU) with 1 Web app You can edit the [ARM template](azuredeploy.json) if you want to add more services or edit the parameters. If you edit the ARM template manually, you will have to deploy it using the command line instructions, not the "Azure Deploy Button". The ConnectTheDots website can allow you to manage your IoTHub instance (get devices connection strings, add/remove devices). This feature is NOT enabled by default and requires manually configuration in Active Directory described [below](#enable-iothub-management). ## Your connect the dots resource groups All the services will be deployed under a single resource group in Azure. The [Azure resource groups](https://azure.microsoft.com/en-us/updates/resource-groups-in-azure-preview-portal/) are a concept allowing to manage a set of resources all together. This allows you to easily find the various resources for your ConnectTheDots solution in the Azure portal. ## Editing the deployment ARM template The default Azure Resource Manager template doesn't require editing unless you want to change the architecture of your solution to go from the default ConnectTheDots one to your own version of it. You can find information on how to customize the ARM template for ConnectTheDots [here](CustomizeTemplate.md). If you edit the template file, then you will have to deploy it using the command line instructions, not the "Azure Deploy Button" ## Deploy using the Azure Deploy Button There are several ways to deploy Azure resources to your subscription using Azure Resourre Manager templates. The simplest one is to click on the button below and follow instructions. If you prefer command line tools, skip to the next chapter. [![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](http://azuredeploy.net/?repository=https://github.com/Azure/connectthedots/raw/master/Azure/ARMTemplate) Once you have clicked on the button, login to your subscription. You will be directed to the first step of the deployment: the setup. ![](images/AzureDeploy1.png) Select the appropriate fields: - Directory: this is the Azure Active Directory your account has access to and that you want to use - Subscription: if you have several subscriptions, you can pick the one you want to deploy your solution to - Resource group/Resource Group Name: the Azure resources/services will all be deployed to a single resource group, allowing for better management of the resources once deployed. You can choose to create a new resource group for your solution or deploy to an existing one. - Region: pick the region you want to deploy your services to. - Solution Name: this is the name for your connectthedots solution. This name has to be **all lower case** and **less than 16 characters** - IoT Hub Sku: you can select F1 (free), S1 or S2 (see [here](https://azure.microsoft.com/en-us/pricing/details/iot-hub/) for details on pricing for these skus). Note that only 1 instance of the free SKU of IoT Hub is allowed per Azure subscription. - AdminName: this is the user email you want to use as an admin if you want to activate the IoTHub management features in your site. **Use the email address you used to login into your Azure Subscription** **Important**: the IoT Hub SKU selected by default is an S1 which has a cost (see pricing details link above). If you want to use the free tier, remember to switch the selection to F1. You will then be limited to 8K messages/day. Once you have made your selections, click on **Next** ![](images/AzureDeploy2.png) You can see the list of services that will be deployed. Click on **Deploy**. You will see the progress of the deployment and if everything goes well, you will see this: ![](images/AzureDeploy3.png) To access the resource group in the Azure portal directly, you can click on the green **Manage** link. At this point you can go a start setting up your devices. Read the chapter after next for instructions on how to clean up your Azure subscription once you are done playing with ConnectTheDots. ## Deploy using Azure CLI tool and the ARM template Now here is how to deploy the whole ConnectTheDots solution in a few command lines: 1. Install the Azure CLI tool following the instructions [here](https://azure.microsoft.com/en-us/documentation/articles/xplat-cli-install/). 1. Once you have installed the Azure CLI, you will need to connect to your Azure account. To do so, follow the instructions [here](https://azure.microsoft.com/en-us/documentation/articles/xplat-cli-connect/). 1. If you have multiple subscriptions, select the one you want to deploy the solution to following the instructions [here](https://azure.microsoft.com/en-us/documentation/articles/xplat-cli-connect/#multiple-subscriptions) 1. Set the Resource Azure Manager mode typing the following command: ``` azure config mode arm ``` 1. Create a new resource group typing the following command (you can replace "ConnectTheDotsRG" with the name of your choice for the resource group): ``` azure group create -n "ConnectTheDotsRG" -l "East US" ``` 1. Navigate to the Azure\ARMTemplate folder in the repo ``` cd C:\My\Repo\Location\Azure\ARMTemplate ``` 1. Deploy the solution typing the below command. You will actually be prompted for the following: * region: you can use one of the following: "East US", "West US", "North Europe", "West Europe", "East Asia", "South East Asia", "Japan East", "Japan West", "Australia East", "Australia SouthEast", "North Europe" * iothub SKU: you can select F1 (free), S1 or S2 (see [here](https://azure.microsoft.com/en-us/pricing/details/iot-hub/) for details on pricing for these skus). Note that only 1 instance of the free SKU of IoT Hub is allowed per Azure subscription. * solution name: this name has to be **all lower case** and **less than 16 characters** * AdminName: this is the user email you want to use as an admin if you want to activate the IoTHub management features in your site. ``` azure group deployment create -f "azuredeploy.json" ConnectTheDotsRG ConnectTheDotsDeploy ``` 1. If you are seeing errors during the deployment, you can diagnose following instructions on how to debug ARM deployments: [http://aka.ms/arm-debug](http://aka.ms/arm-debug). ## Enable IoTHub Management The ConnectTheDots website can allow you to manage your IoT Hub instance: create/delete devices in the IoT Hub device registry, retreive connection strings. In order to ensure this is a secured process, you will need to create a new Azure Active Directory application and register your instance of the website to enable the features. But keep cool, there is a simple process for this: 1. Go to the [Azure portal](https://portal.azure.com) 1. Look for the resource group created during the deployment of the solution 1. Select the website in the list of resources to bring up the website blade 1. In the Settings section, look for and click on "Authentication/Authorization" 1. Turn "App Service Authentication" to on 1. Select "Log in with Azure Active Directory" in the "Action to take when request is not authenticated" 1. Click on the Azure Active Directory provider to configure it 1. Select "Express" and "Create new AD App", then click OK 1. Save the new Authentication/Authorization setting Once you have done so you can go back to the site where you will be prompted to login. Use the same account as your Azure Subscription's one. There you should see a new collumn in the devices list as well as a couple buttons allowing you to create a new device or delete an existing one. No more need to toy around with IoTHub in the portal! ## Deleting a ConnectTheDots solution from your Azure subscription You can easily delete all the Azure resources at once when you are done with your project and want to clean up your Azure subscirption. You can do this using a command line in the Azure CLI tool or in the Azure portal. ## Delete the resources using Azure CLI If you are already logged in in the Azure CLI tool, go directly to step #4 1. Connect to Azure following the instructions [here](https://azure.microsoft.com/en-us/documentation/articles/xplat-cli-connect/). 1. If you have multiple subscriptions, select the one you want to deploy the solution to following the instructions [here](https://azure.microsoft.com/en-us/documentation/articles/xplat-cli-connect/#multiple-subscriptions) 1. Set the Resource Azure Manager mode typing the following command: ``` azure config mode arm ``` 1. Delete the resource group typing the following command (you need to replace "ConnectTheDotsRG" with the name you used in step 5 of the deployment if you changed it): ``` azure group delete -n "ConnectTheDotsRG" ``` ================================================ FILE: Azure/ARMTemplate/azuredeploy.json ================================================ { "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json", "contentVersion": "1.0.0.0", "parameters": { "region": { "type": "string", "allowedValues": [ "East US", "West US", "North Europe", "West Europe", "East Asia", "South East Asia", "Japan East", "Japan West", "Australia East", "Australia SouthEast", "North Europe" ], "metadata": { "description": "The Region to deploy the solution to" } }, "solutionName": { "type": "string", "maxLength": 16, "metadata": { "description": "The name of your ConnectTheDots solution (will be used to prefix the services names)" } }, "iotHubSku": { "type": "string", "allowedValues": [ "F1", "S1", "S2" ], "defaultValue": "S1", "metadata": { "description": "The Iothub Sku" } }, "adminName": { "type": "string" }, "repoUrl": { "type": "string", "defaultValue": "https://github.com/Azure/connectthedots" }, "branch": { "type": "string", "defaultValue": "master" } }, "variables": { "location": "[parameters('region')]", "storageVersion": "2015-06-15", "storageName": "[concat(parameters('solutionName'), 'storage')]", "storageId": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageName'))]", "storageAccountSku": "Standard_LRS", "ehOutName": "ehalerts", "sbKeyName": "RootManageSharedAccessKey", "sbSku": 1, "sbName": "[concat(parameters('solutionName'),'sb')]", "sbVersion": "2017-04-01", "sbResourceId": "[resourceId('Microsoft.Eventhub/namespaces/authorizationRules', variables('sbName'), variables('sbKeyName'))]", "saVersion": "2015-10-01", "saName": "[concat(parameters('solutionName'), 'alerts')]", "webVersion": "2015-04-01", "webPlanName": "[concat(parameters('solutionName'), 'plan')]", "webSiteName": "[parameters('solutionName')]", "iotHubVersion": "2016-02-03", "iotHubTier": "Standard", "iotHubName": "[concat(parameters('solutionName'), 'hub')]", "iotHubResourceId": "[resourceId('Microsoft.Devices/Iothubs', variables('iotHubName'))]", "iotHubKeyName": "iothubowner", "iotHubKeyResource": "[resourceId('Microsoft.Devices/Iothubs/Iothubkeys', variables('iotHubName'), variables('iotHubKeyName'))]", "saCGName": "streamanalyticscg", "websiteCGName": "websitecg", "webSku": "Standard", "webWorkerCount": 2, "webWorkerSize": 0 }, "resources": [ { "apiVersion": "[variables('storageVersion')]", "location": "[variables('location')]", "name": "[variables('storageName')]", "properties": { "accountType": "[variables('storageAccountSku')]" }, "type": "Microsoft.Storage/storageAccounts", "tags": { "displayName": "storage" } }, { "apiVersion": "[variables('iotHubVersion')]", "type": "Microsoft.Devices/Iothubs", "name": "[variables('iotHubName')]", "location": "[variables('location')]", "sku": { "name": "[parameters('iotHubSku')]", "tier": "[variables('iotHubTier')]", "capacity": 1 }, "properties": { "location": "[variables('location')]" }, "tags": { "displayName": "IoTHub" }, "resources": [ { "apiVersion": "[variables('iotHubVersion')]", "name": "[concat(variables('iotHubName'), '/events/', variables('websiteCGName'))]", "type": "Microsoft.Devices/Iothubs/eventhubEndpoints/ConsumerGroups", "dependsOn": [ "[concat('Microsoft.Devices/Iothubs/', variables('iotHubName'))]" ], "tags": { "displayName": "Website Consumer Group" } }, { "apiVersion": "[variables('iotHubVersion')]", "name": "[concat(variables('iotHubName'), '/events/', 'debug')]", "type": "Microsoft.Devices/Iothubs/eventhubEndpoints/ConsumerGroups", "dependsOn": [ "[concat('Microsoft.Devices/Iothubs/', variables('iotHubName'))]" ], "tags": { "displayName": "Debug Consumer Group" } }, { "apiVersion": "[variables('iotHubVersion')]", "name": "[concat(variables('iotHubName'), '/events/', variables('saCGName'))]", "type": "Microsoft.Devices/Iothubs/eventhubEndpoints/ConsumerGroups", "dependsOn": [ "[concat('Microsoft.Devices/Iothubs/', variables('iotHubName'))]" ], "tags": { "displayName": "Stream Analytics Consumer Group" } } ] }, { "apiVersion": "[variables('sbVersion')]", "name": "[variables('sbName')]", "type": "Microsoft.Eventhub/namespaces", "location": "[variables('location')]", "properties": { "messagingSku": "[variables('sbSku')]", "region": "[variables('location')]" }, "tags": { "displayName": "Service Bus" }, "resources": [ { "apiVersion": "[variables('sbVersion')]", "name": "[variables('ehOutName')]", "type": "eventHubs", "location": "[variables('location')]", "dependsOn": [ "[concat('Microsoft.Eventhub/namespaces/', variables('sbName'))]" ], "properties": { "path": "[variables('ehOutName')]", "MessageRetentionInDays": 1 }, "tags": { "displayName": "Alerts Event Hub" }, "resources": [ { "apiVersion": "[variables('sbVersion')]", "name": "[variables('websiteCGName')]", "type": "ConsumerGroups", "dependsOn": [ "[variables('ehOutName')]" ], "tags": { "displayName": "Website Consumer Group" } }, { "apiVersion": "[variables('sbVersion')]", "name": "debug", "type": "ConsumerGroups", "dependsOn": [ "[variables('ehOutName')]" ], "tags": { "displayName": "Debug Consumer Group" } } ] } ] }, { "apiVersion": "[variables('saVersion')]", "type": "Microsoft.StreamAnalytics/streamingjobs", "name": "[variables('saName')]", "location": "[variables('location')]", "dependsOn": [ "[concat('Microsoft.Storage/storageAccounts/', variables('storageName'))]", "[concat('Microsoft.Devices/Iothubs/', variables('iotHubName'))]", "[concat('Microsoft.Eventhub/namespaces/', variables('sbName'))]" ], "tags": { "displayName": "Stream Analytics Job" }, "properties": { "sku": { "name": "standard" }, "EventsOutOfOrderMaxDelayInSeconds": 10, "EventsOutOfOrderPolicy": "adjust", "outputStartMode": "JobStartTime", "outputStartTime": null, "Inputs": [ { "Name": "IoTHubStream", "Properties": { "DataSource": { "Properties": { "consumerGroupName": "[variables('saCGName')]", "iotHubNamespace": "[variables('iotHubName')]", "sharedAccessPolicyKey": "[listkeys(variables('iotHubKeyResource'), variables('iotHubVersion')).primaryKey]", "sharedAccessPolicyName": "[variables('iotHubKeyName')]" }, "Type": "Microsoft.Devices/IotHubs" }, "Serialization": { "Properties": { "Encoding": "UTF8" }, "Type": "Json" }, "Type": "Stream" } } ], "Outputs": [ { "Name": "DeviceInfoEvents", "Properties": { "DataSource": { "Properties": { "EventHubName": "[variables('ehOutName')]", "ServiceBusNamespace": "[variables('sbName')]", "SharedAccessPolicyKey": "[listkeys(variables('sbResourceId'), variables('sbVersion')).primaryKey]", "SharedAccessPolicyName": "[variables('sbKeyName')]", "PartitionKey": "PartitionId" }, "Type": "Microsoft.ServiceBus/EventHub" }, "Serialization": { "Properties": { "Encoding": "UTF8", "Format": "Array" }, "Type": "Json" } } } ], "Transformation": { "Name": "AllToBlob", "Properties": { "Query": "SELECT \r\n 'TempSpike' AS alerttype, \r\n 'Temperature over 80F' AS message, \r\n displayname,\r\n guid,\r\n measurename,\r\n unitofmeasure,\r\n location,\r\n organization,\r\n MIN(timecreated) AS timecreated,\r\n MAX(value) AS tempMax,\r\n MAX(value) AS value\r\nINTO\r\n DeviceInfoEvents\r\nFROM \r\n IoTHubStream TIMESTAMP BY timecreated\r\nWHERE\r\n measurename = 'temperature' OR measurename = 'Temperature'\r\nGROUP BY \r\n displayname, guid, measurename, unitofmeasure, location, organization,\r\n TumblingWindow(Second, 5)\r\n HAVING\r\n tempMax > 80", "StreamingUnits": 1 } } } }, { "apiVersion": "[variables('webVersion')]", "name": "[variables('webPlanName')]", "type": "Microsoft.Web/serverfarms", "location": "[variables('location')]", "tags": { "displayName": "Web Plan" }, "properties": { "name": "[variables('webPlanName')]", "sku": "[variables('webSku')]", "workerSize": "[variables('webWorkerSize')]", "numberOfWorkers": "[variables('webWorkerCount')]" } }, { "name": "[variables('webSiteName')]", "type": "Microsoft.Web/sites", "location": "[variables('location')]", "apiVersion": "[variables('webVersion')]", "dependsOn": [ "[concat('Microsoft.Storage/storageAccounts/', variables('storageName'))]", "[concat('Microsoft.Devices/Iothubs/', variables('iotHubName'))]", "[concat('Microsoft.Eventhub/namespaces/', variables('sbName'))]", "[variables('ehOutName')]", "[variables('websiteCGName')]", "[concat('Microsoft.Web/serverfarms/', variables('webPlanName'))]", "[concat('Microsoft.StreamAnalytics/streamingjobs/', variables('saName'))]" ], "tags": { "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', variables('webPlanName'))]": "Resource", "displayName": "DashboardWebApp" }, "properties": { "name": "[variables('WebSiteName')]", "serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('webPlanName'))]", "siteConfig": { "AlwaysOn": true, "WebsocketsEnabled": true, "appSettings": [ { "name": "Azure.IoT.IoTHub.ConnectionString", "value": "[concat('HostName=', reference(variables('iotHubResourceId')).hostName, ';SharedAccessKeyName=', variables('iotHubKeyName'), ';SharedAccessKey=', listkeys(variables('iotHubKeyResource'), variables('iotHubVersion')).primaryKey)]" }, { "name": "Azure.IoT.IoTHub.EventHub.Name", "value": "[reference(variables('iotHubResourceId')).eventHubEndpoints.events.path]" }, { "name": "Azure.IoT.IoTHub.EventHub.ConnectionString", "value": "[concat('Endpoint=', reference(variables('iotHubResourceId')).eventHubEndpoints.events.endpoint, ';SharedAccessKeyName=', variables('iotHubKeyName'), ';SharedAccessKey=', listkeys(variables('iotHubKeyResource'), variables('iotHubVersion')).primaryKey)]" }, { "name": "Azure.IoT.IoTHub.EventHub.ConsumerGroup", "value": "[variables('websiteCGName')]" }, { "name": "Azure.ServiceBus.EventHub.Name", "value": "[variables('ehOutName')]" }, { "name": "Azure.ServiceBus.EventHub.ConnectionString", "value": "[listkeys(variables('sbResourceId'), variables('sbVersion')).primaryConnectionString]" }, { "name": "Azure.ServiceBus.EventHub.ConsumerGroup", "value": "[variables('websiteCGName')]" }, { "name": "Azure.Storage.ConnectionString", "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';AccountKey=', listkeys(variables('storageId'), variables('storageVersion')).key1)]" }, { "name": "AdminName", "value": "[parameters('adminName')]" }, { "name": "ObjectTypePrefix", "value": "" }, { "name": "SolutionName", "value": "[variables('webSiteName')]" } ] } }, "resources": [ { "apiVersion":"[variables('webVersion')]", "name":"web", "type":"sourcecontrols", "dependsOn":[ "[concat('Microsoft.Web/sites/', variables('webSiteName'))]" ], "properties":{ "repoUrl":"[parameters('repoUrl')]", "branch":"[parameters('branch')]", "isManualIntegration": true } } ] } ], "outputs": { "iotHubConnectionString": { "type": "string", "value": "[concat('HostName=', reference(variables('iotHubResourceId')).hostName, ';SharedAccessKeyName=', variables('iotHubKeyName'), ';SharedAccessKey=', listkeys(variables('iotHubKeyResource'), variables('iotHubVersion')).primaryKey)]" }, "Azure.IoT.IoTHub.EventHub.Name": { "type": "string", "value": "[reference(variables('iotHubResourceId')).eventHubEndpoints.events.path]" }, "Azure.IoT.IoTHub.EventHub.ConnectionString": { "type": "string", "value": "[concat('Endpoint=', reference(variables('iotHubResourceId')).eventHubEndpoints.events.endpoint, ';SharedAccessKeyName=', variables('iotHubKeyName'), ';SharedAccessKey=', listkeys(variables('iotHubKeyResource'), variables('iotHubVersion')).primaryKey)]" }, "Azure.IoT.IoTHub.EventHub.ConsumerGroup": { "type": "string", "value": "[variables('websiteCGName')]" }, "Azure.ServiceBus.EventHub.Name": { "type": "string", "value": "[variables('ehOutName')]" }, "Azure.ServiceBus.EventHub.ConnectionString": { "type": "string", "value": "[listkeys(variables('sbResourceId'), variables('sbVersion')).primaryConnectionString]" }, "Azure.ServiceBus.EventHub.ConsumerGroup": { "type": "string", "value": "[variables('websiteCGName')]" }, "Azure.Storage.ConnectionString": { "type": "string", "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';AccountKey=', listkeys(variables('storageId'), variables('storageVersion')).key1)]" } } } ================================================ FILE: Azure/EHConsole/EHConsole/App.config ================================================  ================================================ FILE: Azure/EHConsole/EHConsole/Microsoft.ConnectTheDots.EHConsole.csproj ================================================  Debug AnyCPU {AAAAA552-F973-4683-8CF9-8B719A69E49C} Exe Properties Microsoft.ConnectTheDots.EHConsole EHConsole v4.5 512 ..\ true 1610c4c5 AnyCPU true full false bin\Debug\ DEBUG;TRACE prompt 4 AnyCPU pdbonly true bin\Release\ TRACE prompt 4 ..\..\AzurePrep\packages\Hyak.Common.1.0.2\lib\net45\Hyak.Common.dll ..\..\AzurePrep\packages\Microsoft.Azure.Common.2.1.0\lib\net45\Microsoft.Azure.Common.dll ..\..\AzurePrep\packages\Microsoft.Azure.Common.2.1.0\lib\net45\Microsoft.Azure.Common.NetFramework.dll ..\..\AzurePrep\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.5.207081303-alpha\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll ..\..\AzurePrep\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.5.207081303-alpha\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll False ..\..\AzurePrep\packages\WindowsAzure.ServiceBus.2.7.5\lib\net40-full\Microsoft.ServiceBus.dll ..\..\AzurePrep\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll ..\..\AzurePrep\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll ..\..\AzurePrep\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll ..\..\AzurePrep\packages\Microsoft.WindowsAzure.Common.1.4.1\lib\net45\Microsoft.WindowsAzure.Common.dll ..\..\AzurePrep\packages\Microsoft.WindowsAzure.Common.1.4.1\lib\net45\Microsoft.WindowsAzure.Common.NetFramework.dll False ..\..\AzurePrep\packages\Microsoft.WindowsAzure.ConfigurationManager.3.1.0\lib\net40\Microsoft.WindowsAzure.Configuration.dll ..\..\AzurePrep\packages\Microsoft.WindowsAzure.Management.4.1.1\lib\net40\Microsoft.WindowsAzure.Management.dll ..\..\AzurePrep\packages\Microsoft.WindowsAzure.Management.Compute.12.2.0-preview\lib\net40\Microsoft.WindowsAzure.Management.Compute.dll ..\..\AzurePrep\packages\Microsoft.WindowsAzure.Management.MediaServices.4.1.0\lib\net40\Microsoft.WindowsAzure.Management.MediaServices.dll ..\..\AzurePrep\packages\Microsoft.WindowsAzure.Management.Monitoring.4.1.0\lib\net40\Microsoft.WindowsAzure.Management.Monitoring.dll ..\..\AzurePrep\packages\Microsoft.WindowsAzure.Management.Network.7.0.3\lib\net40\Microsoft.WindowsAzure.Management.Network.dll ..\..\AzurePrep\packages\Microsoft.WindowsAzure.Management.Scheduler.6.1.0\lib\net40\Microsoft.WindowsAzure.Management.Scheduler.dll ..\..\AzurePrep\packages\Microsoft.WindowsAzure.Management.ServiceBus.0.17.1-preview\lib\net40\Microsoft.WindowsAzure.Management.ServiceBus.dll ..\..\AzurePrep\packages\Microsoft.WindowsAzure.Management.Sql.5.2.0\lib\net40\Microsoft.WindowsAzure.Management.Sql.dll ..\..\AzurePrep\packages\Microsoft.WindowsAzure.Management.Storage.5.1.1\lib\net40\Microsoft.WindowsAzure.Management.Storage.dll ..\..\AzurePrep\packages\Microsoft.WindowsAzure.Management.WebSites.4.4.2-prerelease\lib\net40\Microsoft.WindowsAzure.Management.WebSites.dll False ..\..\AzurePrep\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll ..\..\AzurePrep\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll ..\..\AzurePrep\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll Designer Designer Docs\license.txt {987E2AAB-AC91-4781-89B7-C1AD0AF01D8A} Microsoft.ConnectTheDots.CloudDeploy.Common ================================================ FILE: Azure/EHConsole/EHConsole/Program.cs ================================================ // --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // --------------------------------------------------------------------------------- namespace Microsoft.ConnectTheDots.EHConsole { using System; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; //--// using Microsoft.Azure; using Microsoft.ServiceBus; using Microsoft.ServiceBus.Messaging; using Microsoft.WindowsAzure.Management.ServiceBus; using Microsoft.WindowsAzure.Management.ServiceBus.Models; using Microsoft.WindowsAzure.Management.Storage; //--// using Microsoft.ConnectTheDots.CloudDeploy.Common; //--// class Program { internal class CloudWebDeployInputs { public string NamePrefix; public string SBNamespace; public string Location; public string StorageAccountName; public SubscriptionCloudCredentials Credentials; } //--// private static readonly LogBuffer _ConsoleBuffer = new LogBuffer( ( m ) => { Console.WriteLine( m ); } ); //--// public bool GetInputs( out CloudWebDeployInputs result ) { result = new CloudWebDeployInputs( ); result.Credentials = AzureConsoleHelper.GetUserSubscriptionCredentials( ); if( result.Credentials == null ) { result = null; return false; } ServiceBusNamespace selectedNamespace = AzureConsoleHelper.SelectNamespace( result.Credentials ); if( selectedNamespace == null ) { result = null; Console.WriteLine( "Quiting..." ); return false; } result.NamePrefix = selectedNamespace.Name; if( result.NamePrefix.EndsWith( "-ns" ) ) { result.NamePrefix = result.NamePrefix.Substring( 0, result.NamePrefix.Length - 3 ); } result.SBNamespace = selectedNamespace.Name; result.StorageAccountName = result.NamePrefix.ToLowerInvariant( ) + "storage"; result.Location = selectedNamespace.Region; return true; } bool Run( ) { CloudWebDeployInputs inputs = null; if( !GetInputs( out inputs ) ) { return false; } Console.WriteLine( "Retrieving namespace metadata..." ); // Create Namespace ServiceBusManagementClient sbMgmt = new ServiceBusManagementClient( inputs.Credentials ); var nsDescription = sbMgmt.Namespaces.GetNamespaceDescription( inputs.SBNamespace ); string nsConnectionString = nsDescription.NamespaceDescriptions.First( ( d ) => String.Equals( d.AuthorizationType, "SharedAccessAuthorization" ) ).ConnectionString; NamespaceManager nsManager = NamespaceManager.CreateFromConnectionString( nsConnectionString ); EventHubDescription ehDevices = AzureConsoleHelper.SelectEventHub( nsManager, inputs.Credentials ); var serviceNamespace = inputs.SBNamespace; var hubName = ehDevices.Path; var sharedAccessAuthorizationRule = ehDevices.Authorization.FirstOrDefault( ( d ) => d.Rights.Contains(AccessRights.Listen)) as SharedAccessAuthorizationRule; if( sharedAccessAuthorizationRule == null ) { Console.WriteLine( "Cannot locate Authorization rule for WebSite key." ); return false; } var receiverKeyName = sharedAccessAuthorizationRule.KeyName; var receiverKey = sharedAccessAuthorizationRule.PrimaryKey; //Console.WriteLine("Starting temperature processor with {0} partitions.", partitionCount); CancellationTokenSource cts = new CancellationTokenSource( ); int closedReceivers = 0; AutoResetEvent receiversStopped = new AutoResetEvent( false ); MessagingFactory factory = MessagingFactory.Create(ServiceBusEnvironment.CreateServiceUri( "sb", serviceNamespace, "" ), new MessagingFactorySettings { TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider( receiverKeyName, receiverKey ), TransportType = TransportType.Amqp } ); EventHubClient eventHubClient = factory.CreateEventHubClient( hubName ); EventHubConsumerGroup eventHubConsumerGroup = eventHubClient.GetDefaultConsumerGroup( ); int partitionCount = ehDevices.PartitionCount; for( int i = 0; i < partitionCount; i++ ) { Task.Factory.StartNew( ( state ) => { try { _ConsoleBuffer.Add( string.Format( "Starting worker to process partition: {0}", state ) ); var receiver = eventHubConsumerGroup.CreateReceiver( state.ToString( ), DateTime.UtcNow ); _ConsoleBuffer.Add( string.Format( "Waiting for start receiving messages: {0} ...", state ) ); while( true ) { // Receive could fail, I would need a retry policy etc... var messages = receiver.Receive( 10 ); foreach( var message in messages ) { //var eventBody = Newtonsoft.Json.JsonConvert.DeserializeObject(Encoding.Default.GetString(message.GetBytes())); //Console.WriteLine("{0} [{1}] Temperature: {2}", DateTime.Now, message.PartitionKey, eventBody.Temperature); _ConsoleBuffer.Add( message.PartitionKey + " sent message:" + Encoding.Default.GetString( message.GetBytes( ) ) ); } if( cts.IsCancellationRequested ) { Console.WriteLine( "Stopping: {0}", state ); receiver.Close( ); if( Interlocked.Increment( ref closedReceivers ) >= partitionCount ) { receiversStopped.Set(); } break; } } } catch( Exception ex ) { _ConsoleBuffer.Add( ex.Message ); } }, i ); } Console.ReadLine( ); cts.Cancel( ); //waiting for all receivers to stop receiversStopped.WaitOne( ); bool saveToFile; for( ;; ) { Console.WriteLine( "Do you want to save received data to file? (y/n)" ); string answer = Console.ReadLine( ); string request = "do not"; saveToFile = false; if( !string.IsNullOrEmpty( answer ) && answer.ToLower( ).StartsWith( "y" ) ) { saveToFile = true; request = ""; } if( ConsoleHelper.Confirm( "Are you sure you " + request + " want to save received data?" ) ) { break; } } if( saveToFile ) { string fileName = inputs.SBNamespace + DateTime.UtcNow.ToString( "_d_MMM_h_mm" ) + ".log"; string filePath = Environment.GetFolderPath( Environment.SpecialFolder.Desktop ); string fileFullName = filePath + @"\" + fileName; if( _ConsoleBuffer.FlushToFile( fileFullName ) ) { Console.WriteLine( "Output was saved to your desktop, at " + fileFullName + " file." ); } } Console.WriteLine( "Wait for all receivers to close and then press ENTER." ); Console.ReadLine( ); return true; } static int Main( string[] args ) { var p = new Program( ); try { bool result = p.Run( ); return result ? 0 : 1; } catch ( Exception e ) { Console.WriteLine( "Exception {0} at {1}", e.Message, e.StackTrace ); return 0; } } } } ================================================ FILE: Azure/EHConsole/EHConsole/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("EHConsole")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("EHConsole")] [assembly: AssemblyCopyright("Copyright © 2015")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("54c0c07c-bea0-423c-b360-03c11e593049")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] ================================================ FILE: Azure/EHConsole/EHConsole/packages.config ================================================  ================================================ FILE: Azure/EHConsole/EHConsole.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 VisualStudioVersion = 12.0.30723.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.ConnectTheDots.EHConsole", "EHConsole\Microsoft.ConnectTheDots.EHConsole.csproj", "{AAAAA552-F973-4683-8CF9-8B719A69E49C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.ConnectTheDots.CloudDeploy.Common", "..\AzurePrep\Common\Microsoft.ConnectTheDots.CloudDeploy.Common.csproj", "{987E2AAB-AC91-4781-89B7-C1AD0AF01D8A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {AAAAA552-F973-4683-8CF9-8B719A69E49C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AAAAA552-F973-4683-8CF9-8B719A69E49C}.Debug|Any CPU.Build.0 = Debug|Any CPU {AAAAA552-F973-4683-8CF9-8B719A69E49C}.Release|Any CPU.ActiveCfg = Release|Any CPU {AAAAA552-F973-4683-8CF9-8B719A69E49C}.Release|Any CPU.Build.0 = Release|Any CPU {987E2AAB-AC91-4781-89B7-C1AD0AF01D8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {987E2AAB-AC91-4781-89B7-C1AD0AF01D8A}.Debug|Any CPU.Build.0 = Debug|Any CPU {987E2AAB-AC91-4781-89B7-C1AD0AF01D8A}.Release|Any CPU.ActiveCfg = Release|Any CPU {987E2AAB-AC91-4781-89B7-C1AD0AF01D8A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ================================================ FILE: Azure/EHConsole/nuget.config ================================================  ================================================ FILE: Azure/MachineLearning/MachineLearningCloudService.md ================================================ Once you have setup devices and Azure Event Hubs to receive data from your devices, you can do some analytics on the data. Azure Machine Learning offers a wide range of APIs, along with a platform to create advanced machine learning algorithm. In this example we use the Anomaly Detection API that is available on the [Azure Data Market](http://datamarket.azure.com/dataset/aml_labs/anomalydetection). In order to implement the sample you will need to do one of the following (the instructions below assume you are doing the first one): * [Sign up](http://datamarket.azure.com/checkout/f33b2da0-af7c-42dd-85eb-d625e688f876?ctpa=False) for the free trial (that gives you up to 25,000 transactions per month) * Create your own Machine Learning endpoint (we are not covering this here, but you can learn how to do so [here](http://azure.microsoft.com/en-us/services/machine-learning/)) The sample consist in a Cloud Service that reads real time data from ehDevices Event Hub and calls the Machine Learning Anomaly Detection API, then sends alerts triggered by the API to the ehAlerts Event Hub so that Web sites and clients can display the alerts. ## Prerequisites ## Make sure you have all software installed and necessary subscriptions as indicated in the Readme.md file for the project. To repeat them here, you need 1. Microsoft Azure subscription ([free trial subscription](http://azure.microsoft.com/en-us/pricing/free-trial/) is sufficient) 1. If you have not done so already, [Sign up](http://datamarket.azure.com/checkout/f33b2da0-af7c-42dd-85eb-d625e688f876?ctpa=False) for the Anomaly Detection API service. 1. Visual Studio 2013 – [Community Edition](http://www.visualstudio.com/downloads/download-visual-studio-vs) In addition, you must have run the AzurePrep program discussed in that section, as it creates the event hubs from which the Cloud service pulls data. If you already have the event hubs, you can find information needed below in your Azure portal (see below) ## Deploy the Cloud Service ## * Open the ConnectTheDots\Azure\AzurePrep\AzurePrep.sln solution in Visual Studio. * Open and edit the configuration file WorkerHost\app.config: * Find the lines for the Connection Strings (look for Microsoft.ServiceBus.ConnectionString, Microsoft.ServiceBus.ConnectionStringDevices and Microsoft.ServiceBus.ConnectionStringAlerts) * Replace the connection strings with the appropriate values for your subscription, found in [https://manage.windowsazure.com](https://manage.windowsazure.com) as follows: * **ServiceBus.ConnectionString**. Select Service Bus from the left nav menu, highlight the Namespace Name created earlier, click on Connection Information at the bottom of the screen, and copy the RootManagedSharedAccessKey. * **ServiceBus.ConnectionStringDevices**. Select Service Bus from the left nav menu, select the Namespace Name created earlier, highlight ehdevices, click on Connection information at the bottom of the screen, and copy the WebSite Connection string. * **ServiceBus.ConnectionStringAlerts**. Select Service Bus from the left nav menu, select the Namespace Name created earlier, highlight ehalerts, click on Connection information at the bottom of the screen, and copy the WebSite Connection string. * Find the lines for the Anomaly Detection settings and edit appropriately * **AnomalyDetectionApiUrl**: * if you are using the data market API, keep the URL unchanged (https://api.datamarket.azure.com/aml_labs/anomalydetection/v1/) * If you are using your own Machine Learning endpoint use its URL * **AnomalyDetectionAuthKey**: * If you are using the data market API, go to [https://datamarket.azure.com/account](https://datamarket.azure.com/account) (login in with the account used to subscribe to the Anomaly Detection API) and search for "Primary Account Key" * If you are using your own Machine Learning endpoint use the authentication key provided. * **LiveId**: * If you are using the data market API, use the Windows ID you used to subscribe to the API * If you are using your own Machine Learning endpoint this parameter is ignored * **UseMarketApi**: * If you are using the data market API, leave this one unchanged ("true") * If you are using your own Machine Learning endpoint, set to "false". * The settings **TukeyThresh** and **ZScoreThresh** can be adjusted to fine tune the Anomaly Detection algorithm. * The **AlertsIntervalSec** sets the minimum time in seconds between 2 alerts from the Anomaly Detection algorithm * You can test the service locally by hitting F5 in Visual Studio * If you have created and published a website using the samples in this project, you should see anomalies detected in the charts and in the alerts table when sensors data changes. * To deploy the Cloud Service, right click on the WorkerRole project, click on "Publish" and follow the prompts ================================================ FILE: Azure/MachineLearning/SQL/SQL.sqlproj ================================================  Debug AnyCPU SQL 2.0 4.1 {37fa4922-ac28-474c-9eb9-1544437997f8} Microsoft.Data.Tools.Schema.Sql.SqlAzureDatabaseSchemaProvider Database SQL SQL 1033, CI BySchemaAndSchemaType True v4.5 CS Properties False True True bin\Release\ $(MSBuildProjectName).sql False pdbonly true false true prompt 4 bin\Debug\ $(MSBuildProjectName).sql false true full false true true prompt 4 10.0 True 10.0 ================================================ FILE: Azure/MachineLearning/SQL/StoredProcedures/InsertAlertsData.sql ================================================ CREATE PROCEDURE [dbo].[InsertAlertsData] @dataList [dbo].[AlertsDataTableType] READONLY AS insert into [dbo].[AlertsData] ([value], [guid], [displayname], [organization], [unitofmeasure], [measurename], [location], [timecreated]) select [value], [guid], [displayname], [organization], [unitofmeasure], [measurename], [location], [timecreated] from @dataList RETURN 0 ================================================ FILE: Azure/MachineLearning/SQL/TableType/AlertsDataTableType.sql ================================================ CREATE TYPE [dbo].[AlertsDataTableType] AS TABLE( [value] FLOAT NULL, [guid] VARCHAR(40) NULL, [organization] VARCHAR(20) NULL, [displayname] VARCHAR(50) NULL, [unitofmeasure] VARCHAR(10) NULL, [measurename] VARCHAR(20) NULL, [location] VARCHAR(120) NULL, [timecreated] DATETIME NULL ) ================================================ FILE: Azure/MachineLearning/SQL/Tables/AlertsData.sql ================================================ CREATE TABLE [dbo].[AlertsData] ( [Id] INT NOT NULL PRIMARY KEY IDENTITY, [value] FLOAT NULL, [guid] VARCHAR(40) NULL, [organization] VARCHAR(20) NULL, [displayname] VARCHAR(50) NULL, [unitofmeasure] VARCHAR(10) NULL, [measurename] VARCHAR(20) NULL, [location] VARCHAR(120) NULL, [timecreated] DATETIME NULL ) ================================================ FILE: Azure/MachineLearning/WorkerHost/Analyzer.cs ================================================ //#define DEBUG_LOG using System; using System.Collections.Generic; using System.Data.Services.Client; using System.Diagnostics; using System.Linq; using System.Net; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; using System.Web.Script.Serialization; using Microsoft.WindowsAzure.Diagnostics; namespace WorkerHost { class ADResult { [DataMember] public string table { get; set; } public List GetADResults() { var rowDelim = ";"; var colDelim = ","; var rows = table.Split(new string[] { rowDelim }, StringSplitOptions.RemoveEmptyEntries); List series = new List(); for (int i = 0; i < rows.Length; i++) { var row = rows[i].Replace("\"", "").Trim(); if (i == 0 || row.Length == 0) { continue; // ignore headers and empty rows } var cols = row.Split(new string[] { colDelim }, StringSplitOptions.RemoveEmptyEntries); series.Add(AnomalyRecord.Parse(cols)); } return series; } } class Analyzer { private static string _detectorUrl; private static string _detectorAuthKey; private static string _liveId; private static string _spikeDetectorParams; private static bool _useMarketApi; public Analyzer(string anomalyDetectionApiUrl, string anomalyDetectionAuthKey, string liveId, bool useMarketApi, string tukeyThresh, string zscoreThresh) { _detectorUrl = anomalyDetectionApiUrl; _detectorAuthKey = anomalyDetectionAuthKey; _liveId = liveId; _useMarketApi = useMarketApi; _spikeDetectorParams = string.Format("SpikeDetector.TukeyThresh={0}; SpikeDetector.ZscoreThresh={1}", tukeyThresh, zscoreThresh); } public Task Analyze(SensorDataContract[] data) { var timeSeriesData = GetTimeseriesData(data); #if DEBUG_LOG Trace.TraceInformation("AzureML request: {0}", timeSeriesData); #endif if (_useMarketApi) { return Task.Run(() => GetAlertsFromAnomalyDetectionAPI(timeSeriesData)); } return GetAlertsFromRRS(timeSeriesData); } private static string GetTimeseriesData(SensorDataContract[] data) { var step = 1; var prevTime = DateTime.MinValue; var prevVal = 0d; List newData = new List(); foreach (var d in data.OrderBy(dd => dd.TimeCreated)) { d.TimeCreated = d.TimeCreated.AddTicks(-(d.TimeCreated.Ticks % TimeSpan.TicksPerSecond)); // round off the millisecs if (prevTime != DateTime.MinValue) { for (; prevTime.AddSeconds(step) < d.TimeCreated; ) { prevTime = prevTime.AddSeconds(step); newData.Add(new SensorDataContract { TimeCreated = prevTime, Value = prevVal }); } } newData.Add(d); prevTime = d.TimeCreated; prevVal = d.Value; } var sb = new StringBuilder(); foreach (var d in newData) { sb.Append(string.Format("{0}={1};", d.TimeCreated.ToString("O"), d.Value)); } return sb.ToString(); } public static double FindMaxValue(IEnumerable values) { var avg = values.Average(); var sd = Math.Sqrt(values.Average(v => Math.Pow(v - avg, 2))); return avg + 5 * sd; } private AnomalyRecord[] filterAnomaly(IEnumerable analyzedRecords) { return analyzedRecords.Where(ar => ar.Spike1 == 1 || ar.Spike2 == 1 || ar.LevelScore > 3).ToArray(); } public AnomalyRecord[] GetAlertsFromAnomalyDetectionAPI(string timeSeriesData) { var acitionUri = new Uri(_detectorUrl); var cred = new NetworkCredential(_liveId, _detectorAuthKey); // your Microsoft live Id here var cache = new CredentialCache(); cache.Add(acitionUri, "Basic", cred); DataServiceContext ctx = new DataServiceContext(acitionUri); ctx.Credentials = cache; var query = ctx.Execute(acitionUri, "POST", true, new BodyOperationParameter("data", timeSeriesData), new BodyOperationParameter("params", _spikeDetectorParams)); var resultTable = query.FirstOrDefault(); var results = resultTable.GetADResults(); var presults = results; return filterAnomaly(presults); } private Task GetAlertsFromRRS(string timeSeriesData) { var rrs = _detectorUrl; var apikey = _detectorAuthKey; using (var wb = new WebClient()) { wb.Headers[HttpRequestHeader.ContentType] = "application/json"; wb.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + apikey); var featureVector = string.Format("\"data\": \"{0}\",\"params\": \"{1}\"", timeSeriesData, _spikeDetectorParams); string jsonData = "{\"Id\":\"scoring0001\", \"Instance\": {\"FeatureVector\": {" + featureVector + "}, \"GlobalParameters\":{\"level_mhist\": 300, \"level_shist\": 100, \"trend_mhist\": 300, \"trend_shist\": 100 }}}"; var jsonBytes = Encoding.Default.GetBytes(jsonData); return wb.UploadDataTaskAsync(rrs, "POST", jsonBytes) .ContinueWith( resp => { var response = Encoding.Default.GetString(resp.Result); #if DEBUG_LOG Trace.TraceInformation("AzureML response: {0}...", response.Substring(0, Math.Min(100, response.Length))); #endif JavaScriptSerializer ser = new JavaScriptSerializer { MaxJsonLength = int.MaxValue }; var results = ser.Deserialize>(response); var presults = results.Select(AnomalyRecord.Parse); return filterAnomaly(presults); } ); } } } } ================================================ FILE: Azure/MachineLearning/WorkerHost/AnomalyRecord.cs ================================================ using System; namespace WorkerHost { public class AnomalyRecord { public DateTime Time { get; set; } public double Data { get; set; } public int Spike1 { get; set; } public int Spike2 { get; set; } public double LevelScore { get; set; } public int LevelAlert { get; set; } public double TrendScore { get; set; } public int TrendAlert { get; set; } public static AnomalyRecord Parse(string[] values) { if (values.Length < 8) throw new ArgumentException("Anomaly Record expects 8 values."); return new AnomalyRecord() { Time = DateTime.Parse(values[0]), Data = double.Parse(values[1]), Spike1 = int.Parse(values[2]), Spike2 = int.Parse(values[3]), LevelScore = double.Parse(values[4]), LevelAlert = int.Parse(values[5]), TrendScore = double.Parse(values[6]), TrendAlert = int.Parse(values[7]), }; } public override string ToString() { return Time.ToLocalTime() + ", " + Data + ", " + Spike1 + ", " + Spike2 + ", " + LevelScore + ", " + LevelAlert + ", " + TrendScore + ", " + TrendAlert; } } } ================================================ FILE: Azure/MachineLearning/WorkerHost/App.config ================================================  ================================================ FILE: Azure/MachineLearning/WorkerHost/CircularBuffer.cs ================================================ using System.Collections.Generic; namespace WorkerHost { public class CircularBuffer { private readonly Queue _queue; private readonly int _size; public CircularBuffer(int size) { _queue = new Queue(size); _size = size; } public int Count { get { return _queue.Count; } } public void Add(T obj) { if (_queue.Count == _size) { _queue.Dequeue(); } _queue.Enqueue(obj); } public T[] GetAll() { return _queue.ToArray(); } } } ================================================ FILE: Azure/MachineLearning/WorkerHost/Data.Outputs/BlobWriter.cs ================================================ using System; using System.IO; using System.Text; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Blob; namespace WorkerHost.Data.Outputs { public class BlobWriter { private CloudBlobContainer _ContainerReference; private CloudBlockBlob _Blob; private Stream _StreamWriter; public bool Connect(string blobNamePrefix, string containerName, string storageConnectionString) { try { _ContainerReference = SetUpContainer(storageConnectionString, containerName); _ContainerReference.CreateIfNotExists(); _Blob = _ContainerReference.GetBlockBlobReference(blobNamePrefix + DateTime.UtcNow.Ticks); _StreamWriter = _Blob.OpenWrite(); } catch (Exception) { return false; } return true; } public void WriteLine(string line) { if (_StreamWriter == null) { return; } try { var jsonBytes = Encoding.UTF8.GetBytes(line); _StreamWriter.Write(jsonBytes, 0, jsonBytes.Length); } catch (Exception) { } } public void Flush() { try { _StreamWriter.Flush(); } catch (Exception) { } } private CloudBlobContainer SetUpContainer(string storageConnectionString, string containerName) { CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(storageConnectionString); CloudBlobClient cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient(); CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference(containerName); return cloudBlobContainer; } } } ================================================ FILE: Azure/MachineLearning/WorkerHost/Data.Outputs/SQLOutputRepository.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; namespace WorkerHost.Data.Outputs { public class SQLOutputRepository { private const string ValueTableField = "value"; private const string GuidTableField = "guid"; private const string OrganizationTableField = "organization"; private const string DisplayNameTableField = "displayname"; private const string UnitOfMeasureTableField = "unitofmeasure"; private const string MeasureNameTableField = "measurename"; private const string LocationTableField = "location"; private const string TimeCreatedTableField = "timecreated"; private const string InsertStoreProcedure = "InsertAlertsData"; private const string InsertParameter = "@dataList"; private const string TableType = "AlertsDataTableType"; private readonly string _sqlDatabaseConnectionString; public SQLOutputRepository(string sqlDatabaseConnectionString) { _sqlDatabaseConnectionString = sqlDatabaseConnectionString; } public void ProcessEvents(IList eventDataList) { if (eventDataList == null) { return; } try { using (var sqlConnection = new SqlConnection(_sqlDatabaseConnectionString)) { sqlConnection.Open(); var table = new DataTable(); // Add columns to the table table.Columns.Add(ValueTableField, typeof(double)); table.Columns.Add(GuidTableField, typeof(string)); table.Columns.Add(OrganizationTableField, typeof(string)); table.Columns.Add(DisplayNameTableField, typeof(string)); table.Columns.Add(UnitOfMeasureTableField, typeof(string)); table.Columns.Add(MeasureNameTableField, typeof(string)); table.Columns.Add(LocationTableField, typeof(string)); table.Columns.Add(TimeCreatedTableField, typeof(DateTime)); // Add rows to the table foreach (var eventData in eventDataList) { table.Rows.Add(eventData.Value, eventData.Guid, eventData.Organization, eventData.DisplayName, eventData.UnitOfMeasure, eventData.MeasureName, eventData.Location, eventData.TimeCreated); } // Create command var sqlCommand = new SqlCommand(InsertStoreProcedure, sqlConnection) { CommandType = CommandType.StoredProcedure }; // Add table-valued parameter sqlCommand.Parameters.Add(new SqlParameter { ParameterName = InsertParameter, SqlDbType = SqlDbType.Structured, TypeName = TableType, Value = table, }); // Execute the query sqlCommand.ExecuteNonQuery(); } } catch (Exception ex) { } } } } ================================================ FILE: Azure/MachineLearning/WorkerHost/Data.Outputs/Utils/SqlDBReaderSafeParser.cs ================================================ using System; using System.Data.SqlClient; using System.Reflection; namespace WorkerHost.Data.Outputs.Utils { public static class SqlDBReaderSafeParser { public static T SafeParse(this SqlDataReader reader, string columnName, T defaultValue = default(T)) { object dataValue = reader[columnName]; T convertedValue = dataValue.SafeParse(defaultValue); return convertedValue; } public static T SafeParse(this object dataValue, T defaultValue) { if (null == dataValue || DBNull.Value == dataValue) return defaultValue; Type t = typeof(T); if (t.IsInstanceOfType(dataValue)) { T convertedValue = (T)dataValue; return convertedValue; } MethodInfo method = t.GetMethod("op_Implicit", new[] { dataValue.GetType() }); T result; if (method != null) { result = (T)method.Invoke(null, new[] { dataValue }); } else { result = defaultValue; } return result; } } } ================================================ FILE: Azure/MachineLearning/WorkerHost/EventHubReader.cs ================================================ //#define DEBUG_LOG using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.ServiceBus; using Microsoft.ServiceBus.Messaging; using Newtonsoft.Json; using Microsoft.WindowsAzure.Diagnostics; namespace WorkerHost { class EventHubReader { private const int DEFAULT_BUFFER_SIZE = 200; private const int MIN_COUNT_FOR_ANALYSIS = 10; private static int _bufferSize; private EventHubReceiver[] _receivers = null; private string _consumerGroupPrefix; //DateTime[] _receiversLastUpdate = null; private Task[] _tasks = null; private Dictionary> _buffers; private object _lock = new object(); //object _lockNoData = new object(); private string _measureNameFilter; internal ManualResetEvent FailureEvent = new ManualResetEvent(false); public EventHubReader(int messagesBufferSize, string consumerGroupPrefix = "Local") { if (messagesBufferSize == 0) { _bufferSize = DEFAULT_BUFFER_SIZE; } else { _bufferSize = messagesBufferSize; } _consumerGroupPrefix = consumerGroupPrefix; } public void Close() { if (_receivers != null) { foreach (var r in _receivers) { r.CloseAsync(); } } } public void Run(string connectionString, string hubName, string measureNameFilter) { NamespaceManager nsmgr = NamespaceManager.CreateFromConnectionString(connectionString); EventHubDescription desc = nsmgr.GetEventHub(hubName); string consumerGroupName = _consumerGroupPrefix + DateTime.UtcNow.Ticks.ToString(); ConsumerGroupDescription consumerGroupDesc = nsmgr.CreateConsumerGroupIfNotExists(new ConsumerGroupDescription(hubName, consumerGroupName)); EventHubClient client = EventHubClient.CreateFromConnectionString(connectionString, hubName); int numPartitions = desc.PartitionCount; _receivers = new EventHubReceiver[numPartitions]; //_receiversLastUpdate = new DateTime[numPartitions]; //for (int i = 0; i < numPartitions; i++) //{ // _receiversLastUpdate[i] = DateTime.UtcNow; //} _tasks = new Task[numPartitions]; _buffers = new Dictionary>(); _measureNameFilter = measureNameFilter; for (int iPart = 0; iPart < desc.PartitionCount; iPart++) { EventHubReceiver receiver = client.GetConsumerGroup(consumerGroupName).CreateReceiver( desc.PartitionIds[iPart], DateTime.UtcNow - TimeSpan.FromMinutes(2)); _receivers[iPart] = receiver; Task> task = receiver.ReceiveAsync(1000, TimeSpan.FromSeconds(1)); int thisPart = iPart; task.ContinueWith(new Action>>((t) => OnTaskComplete(t, thisPart))); _tasks[iPart] = task; } } //void ProcessNoData() //{ // lock (_lockNoData) // { // DateTime now = DateTime.UtcNow; // if (_receiversLastUpdate.All(d => now - d > TimeSpan.FromMinutes(3))) // { // Trace.TraceError("No data for the last 3 minutes. Reinitializing"); // FailureEvent.Set(); // } // } //} void Process(int iPart, bool firstReport, IEnumerable batch) { UTF8Encoding enc = new UTF8Encoding(); foreach (EventData e in batch) { string body = enc.GetString(e.GetBytes()); string[] lines = body.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { try { var payload = JsonConvert.DeserializeObject>(line); var sensorData = new SensorDataContract { DisplayName = (string) payload["displayname"], Guid = (string) payload["guid"], Location = (string) payload["location"], MeasureName = (string) payload["measurename"], Organization = (string) payload["organization"], TimeCreated = (DateTime)payload["timecreated"], UnitOfMeasure = (string) payload["unitofmeasure"], Value = (double) payload["value"] }; var from = sensorData.UniqueId(); // Filter on MeasureName if ((_measureNameFilter.Length == 0) || (_measureNameFilter.IndexOf(sensorData.MeasureName) >= 0)) { lock (_lock) { CircularBuffer buffer; if (!_buffers.TryGetValue(from, out buffer)) { buffer = new CircularBuffer(_bufferSize); _buffers.Add(from, buffer); } buffer.Add(sensorData); #if DEBUG_LOG Console.WriteLine("Data from device {0}, Total count: {1}", from, buffer.Count); #endif } } } catch (Exception) { #if DEBUG_LOG Trace.TraceError("Ignored invalid event data: {0}", line); #endif } } } //lock (_lockNoData) //{ // _receiversLastUpdate[iPart] = DateTime.UtcNow; //} } public Dictionary GetHistoricData() { lock (_lock) { return _buffers.Where(kvp => kvp.Value.Count > MIN_COUNT_FOR_ANALYSIS) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value.GetAll()); } } void OnTaskComplete(Task> task, int iPart) { try { if (task.IsCompleted) { IEnumerable batch = task.Result; if (batch != null && batch.Count() != 0) { #if DEBUG_LOG Debug.WriteLine("Partition {0}, {1} events", iPart, batch.Count()); #endif Process(iPart, false, batch); } else { //ProcessNoData(); } } else { #if DEBUG_LOG Trace.TraceError("Event hub reader {0} did not complete successfully : {1}", iPart, task.Exception == null ? "" : task.Exception.ToString()); #endif FailureEvent.Set(); } Task> newTask = _receivers[iPart].ReceiveAsync(1000, TimeSpan.FromSeconds(1)); int thisPart = iPart; newTask.ContinueWith(new Action>>((t) => OnTaskComplete(t, thisPart))); this._tasks[iPart] = newTask; } catch (Exception e) { #if DEBUG_LOG Trace.TraceError(e.ToString()); #endif FailureEvent.Set(); } } } } ================================================ FILE: Azure/MachineLearning/WorkerHost/Program.cs ================================================ //#define DEBUG_LOG using System; using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure; using Microsoft.ServiceBus.Messaging; using Microsoft.WindowsAzure.Diagnostics; using Microsoft.WindowsAzure.ServiceRuntime; using Newtonsoft.Json; using WorkerHost.Data.Outputs; namespace WorkerHost { public class WorkerHost : RoleEntryPoint { public class Configuration { public string AlertEHConnectionString; public string AlertEHName; public string DeviceEHConnectionString; public string DeviceEHName; public string AnomalyDetectionApiUrl; public string AnomalyDetectionAuthKey; public string LiveId; public string MeasureNameFilter; public string TukeyThresh; public string ZscoreThresh; public bool UseMarketApi; public int MessagesBufferSize; public int AlertsIntervalSec; public string StorageConnectionString; public string BlobContainerName; public string BlobNamePrefix; public string SqlDatabaseConnectionString; } private static Analyzer _analyzer; private static EventHubReader _eventHubReader; private static Timer _timer; private static BlobWriter _blobWriter; private static SQLOutputRepository _sqlOutputRepository; static void Main() { StartHost("LocalWorker"); } public override void Run() { StartHost("WorkerRole"); } private static void StartHost(string consumerGroupPrefix) { Trace.WriteLine("Starting Worker..."); #if DEBUG_LOG RoleEnvironment.TraceSource.TraceInformation("Starting Worker..."); #endif var config = new Configuration(); config.AlertEHConnectionString = ConfigurationManager.AppSettings.Get("Microsoft.ServiceBus.ConnectionStringAlerts"); config.AlertEHName = ConfigurationManager.AppSettings.Get("Microsoft.ServiceBus.EventHubAlerts"); config.DeviceEHConnectionString = ConfigurationManager.AppSettings.Get("Microsoft.ServiceBus.ConnectionStringDevices"); config.DeviceEHName = ConfigurationManager.AppSettings.Get("Microsoft.ServiceBus.EventHubDevices"); ; config.AnomalyDetectionApiUrl = ConfigurationManager.AppSettings.Get("AnomalyDetectionApiUrl"); config.AnomalyDetectionAuthKey = ConfigurationManager.AppSettings.Get("AnomalyDetectionAuthKey"); config.LiveId = ConfigurationManager.AppSettings.Get("LiveId"); config.MeasureNameFilter = ConfigurationManager.AppSettings.Get("MeasureNameFilter"); config.TukeyThresh = ConfigurationManager.AppSettings.Get("TukeyThresh"); config.ZscoreThresh = ConfigurationManager.AppSettings.Get("ZscoreThresh"); bool.TryParse(ConfigurationManager.AppSettings.Get("UseMarketApi"), out config.UseMarketApi); int.TryParse(ConfigurationManager.AppSettings.Get("MessagesBufferSize"), out config.MessagesBufferSize); int.TryParse(ConfigurationManager.AppSettings.Get("AlertsIntervalSec"), out config.AlertsIntervalSec); config.StorageConnectionString = ConfigurationManager.AppSettings.Get("Microsoft.Storage.ConnectionString"); config.BlobContainerName = ConfigurationManager.AppSettings.Get("blobContainerName"); config.BlobNamePrefix = ConfigurationManager.AppSettings.Get("blobNamePrefix"); config.SqlDatabaseConnectionString = ConfigurationManager.AppSettings.Get("sqlDatabaseConnectionString"); _analyzer = new Analyzer(config.AnomalyDetectionApiUrl, config.AnomalyDetectionAuthKey, config.LiveId, config.UseMarketApi, config.TukeyThresh, config.ZscoreThresh); _eventHubReader = new EventHubReader(config.MessagesBufferSize, consumerGroupPrefix); if (ConfigurationManager.AppSettings.Get("sendToBlob") == "true") { _blobWriter = new BlobWriter(); if (_blobWriter.Connect(config.BlobNamePrefix, config.BlobContainerName, config.StorageConnectionString)) { _blobWriter = null; } } if (ConfigurationManager.AppSettings.Get("sendToSQL") == "true") { _sqlOutputRepository = new SQLOutputRepository(config.SqlDatabaseConnectionString); } Process(config); } public static void Process(Configuration config) { var alertEventHub = EventHubClient.CreateFromConnectionString(config.AlertEHConnectionString, config.AlertEHName); Trace.TraceInformation("Starting to receive messages..."); _eventHubReader.Run(config.DeviceEHConnectionString, config.DeviceEHName, config.MeasureNameFilter); var timerInterval = TimeSpan.FromSeconds(1); var alertLastTimes = new Dictionary(); TimerCallback timerCallback = state => { var historicData = _eventHubReader.GetHistoricData(); try { var tasks = historicData.ToDictionary(kvp => kvp.Key, kvp => _analyzer.Analyze(kvp.Value)); Task.WaitAll(tasks.Values.ToArray()); List alertsToSQl = new List(); foreach (var kvp in tasks) { var key = kvp.Key; var alerts = kvp.Value.Result; DateTime alertLastTime; if (!alertLastTimes.TryGetValue(@key, out alertLastTime)) { alertLastTime = DateTime.MinValue; } foreach (var alert in alerts) { if ((alert.Time - alertLastTime).TotalSeconds >= config.AlertsIntervalSec) { Trace.TraceInformation("Alert - {0}", alert.ToString()); string eventJSON = OutputResults(key, historicData[key].LastOrDefault(), alert); alertEventHub.Send( new EventData(Encoding.UTF8.GetBytes(eventJSON))); alertLastTime = alert.Time; alertLastTimes[@key] = alertLastTime; if (historicData[key].Length > 0) { alertsToSQl.Add(historicData[key].Last()); if (_blobWriter != null) { _blobWriter.WriteLine(eventJSON); } } } } } if (_sqlOutputRepository != null) { _sqlOutputRepository.ProcessEvents(alertsToSQl); } if (_blobWriter != null) { _blobWriter.Flush(); } } catch (Exception e) { #if DEBUG_LOG Trace.TraceError(e.Message); Trace.TraceError(e.ToString()); #endif //throw; } _timer.Change((int)timerInterval.TotalMilliseconds, Timeout.Infinite); }; _timer = new Timer(timerCallback, null, Timeout.Infinite, Timeout.Infinite); _timer.Change(0, Timeout.Infinite); Trace.TraceInformation("Reading events from Event Hub (press ctrl+c to abort)"); var exitEvent = new ManualResetEvent(false); Console.CancelKeyPress += (sender, eventArgs) => { eventArgs.Cancel = true; exitEvent.Set(); }; int index = WaitHandle.WaitAny(new[] {exitEvent, _eventHubReader.FailureEvent}); Trace.TraceInformation("Exiting..."); _timer.Change(Timeout.Infinite, Timeout.Infinite); Thread.Sleep(timerInterval); _timer.Dispose(); _eventHubReader.Close(); } public static IEnumerable> Batch(IEnumerable collection, int batchSize) { List nextbatch = new List(batchSize); foreach (T item in collection) { nextbatch.Add(item); if (nextbatch.Count == batchSize) { yield return nextbatch; nextbatch = new List(); } } if (nextbatch.Count > 0) yield return nextbatch; } private static string OutputResults(string from, SensorDataContract sensorMeta, AnomalyRecord alert) { return JsonConvert.SerializeObject( new { guid = sensorMeta.Guid, displayname = sensorMeta.DisplayName, measurename = sensorMeta.MeasureName, unitofmeasure = sensorMeta.UnitOfMeasure, location = sensorMeta.Location, organization = sensorMeta.Organization, timecreated = alert.Time.ToLocalTime(), value = alert.Data, alerttype = "MLModelAlert", message = "Anomaly detected by Machine Learning model." }); } } } ================================================ FILE: Azure/MachineLearning/WorkerHost/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("WorkerHost")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("WorkerHost")] [assembly: AssemblyCopyright("Copyright © 2014")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("76fab21f-d875-451d-abf7-e8c352fc7a9f")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] ================================================ FILE: Azure/MachineLearning/WorkerHost/SensorDataContract.cs ================================================ using System; using System.Runtime.Serialization; namespace WorkerHost { public class SensorDataContract { [DataMember(Name = "value")] public double Value { get; set; } [DataMember(Name = "guid")] public string Guid { get; set; } [DataMember(Name = "organization")] public string Organization { get; set; } [DataMember(Name = "displayname")] public string DisplayName { get; set; } [DataMember(Name = "unitofmeasure")] public string UnitOfMeasure { get; set; } [DataMember(Name = "measurename")] public string MeasureName { get; set; } [DataMember(Name = "location")] public string Location { get; set; } [DataMember(Name = "timecreated")] public DateTime TimeCreated { get; set; } public string UniqueId() { //we could have devices with same DisplayName but different MeasureName etc.. return DisplayName + Guid + MeasureName; } } } ================================================ FILE: Azure/MachineLearning/WorkerHost/WorkerHost.csproj ================================================  Debug AnyCPU {25079398-1602-45EE-837C-D4195A1FBC27} Exe Properties WorkerHost WorkerHost v4.5 512 false publish\ true Disk false Foreground 7 Days false false true 1 1.0.0.%2a false true true Worker AnyCPU true full false bin\Debug\ DEBUG;TRACE prompt 4 AnyCPU pdbonly true bin\Release\ TRACE prompt 4 95CCA3EE94BEF7B07D71337320FD2BBC359DC7C6 WorkerHost_TemporaryKey.pfx true false ..\packages\Hyak.Common.1.0.2\lib\net45\Hyak.Common.dll ..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.dll ..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.NetFramework.dll ..\packages\Microsoft.Azure.KeyVault.Core.1.0.0\lib\net40\Microsoft.Azure.KeyVault.Core.dll True ..\packages\Microsoft.Data.Edm.5.6.4\lib\net40\Microsoft.Data.Edm.dll True ..\packages\Microsoft.Data.OData.5.6.4\lib\net40\Microsoft.Data.OData.dll True ..\packages\Microsoft.Data.Services.Client.5.6.4\lib\net40\Microsoft.Data.Services.Client.dll True ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.1.203031538-alpha\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.1.203031538-alpha\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll False ..\packages\WindowsAzure.ServiceBus.2.6.4\lib\net40-full\Microsoft.ServiceBus.dll ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll ..\packages\Microsoft.WindowsAzure.Common.1.4.1\lib\net45\Microsoft.WindowsAzure.Common.dll ..\packages\Microsoft.WindowsAzure.Common.1.4.1\lib\net45\Microsoft.WindowsAzure.Common.NetFramework.dll False ..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.1.0\lib\net40\Microsoft.WindowsAzure.Configuration.dll True ..\packages\Microsoft.WindowsAzure.Management.ServiceBus.0.17.1-preview\lib\net40\Microsoft.WindowsAzure.Management.ServiceBus.dll False ..\packages\WindowsAzure.Storage.5.0.2\lib\net40\Microsoft.WindowsAzure.Storage.dll True False ..\packages\Newtonsoft.Json.7.0.1-beta1\lib\net45\Newtonsoft.Json.dll ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll ..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll ..\packages\System.Spatial.5.6.4\lib\net40\System.Spatial.dll True Designer False Microsoft .NET Framework 4.5 %28x86 and x64%29 true False .NET Framework 3.5 SP1 Client Profile false False .NET Framework 3.5 SP1 false ================================================ FILE: Azure/MachineLearning/WorkerHost/packages.config ================================================  ================================================ FILE: Azure/MachineLearning/WorkerHost.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.23107.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkerHost", "WorkerHost\WorkerHost.csproj", "{25079398-1602-45EE-837C-D4195A1FBC27}" EndProject Project("{CC5FD16D-436D-48AD-A40C-5A424C6E3E79}") = "WorkerRole", "WorkerRole\WorkerRole.ccproj", "{73173D11-AA67-4089-91D5-BBE0BD4C5444}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SQL", "SQL", "{09DB6871-3FA4-4C09-B41C-CE017586DC5D}" EndProject Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "SQL", "SQL\SQL.sqlproj", "{37FA4922-AC28-474C-9EB9-1544437997F8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {25079398-1602-45EE-837C-D4195A1FBC27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {25079398-1602-45EE-837C-D4195A1FBC27}.Debug|Any CPU.Build.0 = Debug|Any CPU {25079398-1602-45EE-837C-D4195A1FBC27}.Release|Any CPU.ActiveCfg = Release|Any CPU {25079398-1602-45EE-837C-D4195A1FBC27}.Release|Any CPU.Build.0 = Release|Any CPU {73173D11-AA67-4089-91D5-BBE0BD4C5444}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {73173D11-AA67-4089-91D5-BBE0BD4C5444}.Debug|Any CPU.Build.0 = Debug|Any CPU {73173D11-AA67-4089-91D5-BBE0BD4C5444}.Release|Any CPU.ActiveCfg = Release|Any CPU {73173D11-AA67-4089-91D5-BBE0BD4C5444}.Release|Any CPU.Build.0 = Release|Any CPU {37FA4922-AC28-474C-9EB9-1544437997F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {37FA4922-AC28-474C-9EB9-1544437997F8}.Debug|Any CPU.Build.0 = Debug|Any CPU {37FA4922-AC28-474C-9EB9-1544437997F8}.Debug|Any CPU.Deploy.0 = Debug|Any CPU {37FA4922-AC28-474C-9EB9-1544437997F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {37FA4922-AC28-474C-9EB9-1544437997F8}.Release|Any CPU.Build.0 = Release|Any CPU {37FA4922-AC28-474C-9EB9-1544437997F8}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {37FA4922-AC28-474C-9EB9-1544437997F8} = {09DB6871-3FA4-4C09-B41C-CE017586DC5D} EndGlobalSection EndGlobal ================================================ FILE: Azure/MachineLearning/WorkerRole/ServiceConfiguration.Cloud.cscfg ================================================  ================================================ FILE: Azure/MachineLearning/WorkerRole/ServiceConfiguration.Local.cscfg ================================================  ================================================ FILE: Azure/MachineLearning/WorkerRole/ServiceDefinition.csdef ================================================  ================================================ FILE: Azure/MachineLearning/WorkerRole/WorkerHostContent/diagnostics.wadcfgx ================================================  false ================================================ FILE: Azure/MachineLearning/WorkerRole/WorkerRole.ccproj ================================================  Debug AnyCPU 2.7 73173d11-aa67-4089-91d5-bbe0bd4c5444 Library Properties WorkerRole WorkerRole True WorkerRole False true full false bin\Debug\ DEBUG;TRACE prompt 4 pdbonly true bin\Release\ TRACE prompt 4 WorkerHost {25079398-1602-45ee-837c-d4195a1fbc27} True Worker WorkerHost True 10.0 $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Windows Azure Tools\2.7\ ================================================ FILE: Azure/PowerBI/PBI_setup.md ================================================ The instructions below will help you setup a Power BI dashboard in the Connect The Dots starter solution, but they can be adapted as necessary for other scenarios. This document assumes you have already deployed the starter solution with at least one device set up pushing data to an Azure IoT Hub. The documentation below uses the names and fields you would have if you had set up the starter solution with simple device connected and sending Temperature and Humidity data, but can be modified as needed if you have a different sensor or named your fields and hubs differently. ## Prerequisites ## Make sure you have a working starter solution, with data showing in your Azure website. In addition, you will need a Power BI account, for which you can sign up for at [PowerBI.com](http://www.PowerBI.com). ## Create a new Consumer Group in your IoT Hub ## To make sure you do not exceed the maximum number of readers on your Connect The Dots Event Hub, create a Consumer Group first. * Open the [Azure Management Portal](https://portal.azure.com), and select the resource group of your Connect The Dots solution. * Find and select the IoT Hub instance * In the IoT Hub settings blade, click on Endpoints in the MESSAGING section * Select the "Events" built-in endpoint * In the properties blade of this endpoint, add a new Consumer Group called "cg4pbi" (remember to click on "Save" at the top to save the change) ## Create an Azure Stream Analytics (ASA) job ## * In the [Azure Management Portal](https://portal.azure.com), go back to the resource group for your Connect The Dots solution deployment. * Click on the "+Add" button on top of the Resource Group view * Click on "Stream Analytics Job" * Click on "Create" at the bottom * Type in the name for the job (we'll assume you used "CTD2PBI" as a name). Ensure you are creating the job in the same resource group (which should be the default and will make it easier to find it in the portal) * Go back to the resource group view and select the CTD2PBI Stream Analytics job. * Add IoT Hub as the Input * Click on the "Inputs" box * Click on "+Add" * Input Alias: “DevicesInput” * Source type: "Data Stream" * Source: "Iot Huyb" * Import Option: "Use IoT Hub form current subscription" * IoT hub: _The name of the IoT hub of your Connect The Dots solution_ * Enpoint: "Messaging" * Shared access policy name: "iothubowner" * Consumer Group: "cg4pbi" * Event Serialization format: JSON * Encoding: "UTF-8" * Create a query * Click on the "Query" box in the Stream Analytics blade * Copy/paste contents “cg4pbi.sql” found in the ConnectTheDots\Azure\StreamAnalyticsQueries folder of the repository * Save * Create an output to send data to PowerBI * Click on the "Outputs" box * Click on "+Add" * Output Alias: "CTDPBI" * Sink: "Power BI" * Click on "Authorize" if asked to and enter your Power BI account credentials * Group Workspace: "My Workspace" * Dataset Name: "CTDPBIDataSet" * Table Name: "CTDPBITable" * Start the Stream Analytics Job * Go back to the Stream Analytics CTD2PBI job blade * Click on "Start" (top menu) * Select "Now" and click on "Start" (bottom button) ## Create a Power BI dashboard ## ###Create a dashboard### We are going to create a Power BI dashboard for a the data coming from the Connect The Dots starter solution, of a single Arduino UNO + Weather Shield sending data to an Azure Event Hub. To create this, first create a dashboard: * Log in to [http://App.PowerBI.com](http://app.powerbi.com) * Create a Dashboard for your Connect The Dots data * Click "+" in the left menu under Dashboards * Enter a name: "ConnectTheDots" ###Create a simple line chart### The first chart on your dashboard will be a real-time timeline showing the temperature from your sensor. To create this, follow the steps below in order. Note that you need data flowing from at least one device for the Power BI dataset to be created. Start a device and wait a couple minutes before moving on to make sure you will see the dataset created and fed by the Stream Analytics job. * In the upper menu of the ConnectTheDots dashboard, click on "+ Add Tile" * Select "Custom Streaming Data" and click "Next" * Select CTDPBIDataSet data set and click "Next" * Select Following settings: * Visualization Type = "Line Chart" * Axis : "timecreated" * Legend : "measurename" * Values : "value" * Time window to display : 1 minute You now have a simple dashboard showing real time data coming from devices in PowerBI. You can now consider customizing the query in stream analytics and the dashboard to show things like average, alerts,... Enjoy! ================================================ FILE: Azure/StreamAnalyticsQueries/Aggregates.sql ================================================ /* --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // ---------------------------------------------------------------------------------*/ Select measurename, unitofmeasure, 'All Sensors' AS location, 'All Sensors' AS organization, 'ace60e7c-a6aa-4694-ba86-c3b66952558e' AS guid, 'Average' as displayname, Max(timecreated) as timecreated, Avg(value) AS value From DevicesInput TIMESTAMP BY timecreated where measurename = 'temperature' OR measurename='Temperature' OR measurename = 'Humidity' OR measurename='humidity' Group by measurename, unitofmeasure, TumblingWindow(Second, 15) ================================================ FILE: Azure/StreamAnalyticsQueries/Alert.sql ================================================ /* --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // ---------------------------------------------------------------------------------*/ SELECT 'TempSpike' AS alerttype, 'Temperature over 80F' AS message, displayname, guid, measurename, unitofmeasure, location, organization, MIN(timecreated) AS timecreated, MAX(value) AS tempMax, MAX(value) AS value FROM DevicesInput TIMESTAMP BY timecreated WHERE measurename = 'temperature' OR measurename = 'Temperature' GROUP BY displayname, guid, measurename, unitofmeasure, location, organization, TumblingWindow(Second, 5) HAVING tempMax > 80 ================================================ FILE: Azure/StreamAnalyticsQueries/HumidityAlert.sql ================================================ /* --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // ---------------------------------------------------------------------------------*/ WITH LongAverage AS ( SELECT displayname as dspl, AVG(value) AS hmdt, MAX(timecreated) AS time FROM StreamInput TIMESTAMP BY timecreated WHERE measurename='Humidity' GROUP BY HoppingWindow(DURATION(ss, 5), HOP(ss, 2)), displayname ), ShortAverage AS ( SELECT displayname AS dspl, AVG(value) AS hmdt, MAX(timecreated) AS time, guid, measurename, unitofmeasure, location, organization FROM StreamInput TIMESTAMP BY timecreated WHERE measurename='Humidity' GROUP BY TumblingWindow(ss, 2), displayname, guid, measurename, unitofmeasure, location, organization ), Compare AS ( SELECT ShortAverage.dspl AS dspl, ShortAverage.hmdt AS NewHumidity, LongAverage.hmdt AS OldHumidity, ((ShortAverage.hmdt - LongAverage.hmdt)/ ShortAverage.hmdt) * 100 AS delta, ShortAverage.time AS time, ShortAverage.guid, ShortAverage.measurename, ShortAverage.unitofmeasure, ShortAverage.location, ShortAverage.organization FROM LongAverage INNER JOIN ShortAverage On LongAverage.dspl = ShortAverage.dspl AND DATEDIFF(ss, LongAverage, ShortAverage) > 1 AND DATEDIFF(ss, LongAverage, ShortAverage) < 5 ) SELECT 'HumSpike' AS alerttype, 'Sudden increase in Humidity' AS message, dspl as displayname, guid, measurename, unitofmeasure, location, organization, NewHumidity as value, OldHumidity, delta, time AS timecreated FROM Compare WHERE delta >= 20 ================================================ FILE: Azure/StreamAnalyticsQueries/LightSensor.sql ================================================ /* --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // ---------------------------------------------------------------------------------*/ SELECT 'LightSensor' as alerttype, 'The Light is turned OFF' as message, displayname, guid, measurename, unitofmeasure, location, organization, MIN(timecreated) AS timecreated, max(value) as value FROM DevicesInput TIMESTAMP BY timecreated WHERE measurename = 'light' OR measurename = 'Light' GROUP BY displayname, guid, measurename, unitofmeasure, location, organization, TumblingWindow(Second, 5) having avg(value) < 0.02 and count(*) > 3 ================================================ FILE: Azure/StreamAnalyticsQueries/SA_setup.md ================================================ # Stream Analytics Setup # The instructions below will help you setup the Stream Analytics queries in the Connect The Dots getting started project, but they can be adapted as necessary for other scenarios. This document assumes you have all the necessary software and subscriptions and that you cloned or downloaded the ConnectTheDots.io project on your machine. ## Prerequisites ## Make sure you have all software installed and necessary subscriptions as indicated in the ReadMe.md file for the project. To repeat them here, you need 1. Microsoft Azure subscription ([free trial subscription](http://azure.microsoft.com/en-us/pricing/free-trial/) is sufficient) 1. Visual Studio – [Community Edition](http://www.visualstudio.com/downloads/download-visual-studio-vs) Note also that these queries are hard-coded to the data streams defined in the getting started walkthrough in this project, meaning the same JSON string contents, etc. Also note that the SQL queries ARE CASE SENSITIVE, so that "temperature" <> "TEMPERATURE". You should make sure that the spelling and case of the incoming measure names are the same as in the SQL queries. ## Create three Azure Stream Analytics (ASA) jobs ## * If you have used the ARM template to deploy the ConnectTheDots solution, then you can edit the Stream Analytics job directly in the portal, looking for it in the Resource Group created during the deployment of the solution. * If you are creating a new job, read this: * Open the [Azure Management Portal](http://portal.azure.com), and create a new job “Aggregates”: * "+” in top left corner > Internet Of Things > Stream Analytics > * Job name: “Aggregates”. * Subscription: same as the one used for the other parts of the solution. * Resource Group: same as the one used for the other parts of the solution. * Location: your choice, considering it is always better to have the various services of a solution in the same region. * Click on Create * Create an input * In the Resource Groups list, select your solution's resource group. * Select the stream analytics job "Aggregates" * Click on the Inputs tile in the Aggregates job. * *Inputs blade > Add >* * Input Alias: “DevicesInput” * Source Type: "Data Stream" * Source: "IoT Hub" * Subscription: pick the current subscription * IoTHub: pick the IoT Hub named out of your solution name (captured during the deployment of the ARM template) * Shared access policy name: "iothubowner" * Event serialization format: "JSON" * Encoding: "UTF-8" * Create a query * Select the Query tile in the Aggregates job blade * Copy/paste contents `Aggregates.sql` found in the `ConnectTheDots\Azure\StreamAnalyticsQueries` folder in Windows Explorer * Save * Create an output * Select the Output tile in the Aggregates job blade * *Output tile > Add >* * Output Alias: your choice * Sink: "Event Hub" * Subscription: pick the current subscription * Service bus namespace: Pick the one named after the solution name you entered during the deployment of the ARM template * Event Hub Name: "ehalerts" * Event Hub policy name: "RootManageSharedAccessKey" * Event Serialization format: "JSON" * Encoding: "UTF-8" * Click on Create * **Note** You will likely get an error just about the same container being used as input and output. This is OK, the job will still work. * Start the Job * *Dashboard > Start* on the bottom bar. * Create a second job “Alerts”: as above, but use `alert.sql` contents for the query, and use *ehalerts* for the Output Event Hub, not *ehdevices*. * Create a third job “LightSensor”: as above, but use `lightsensor.sql` contents for the query, and use *ehalerts* for the Output Event Hub. Once all three are running, go check out your site at http://``.azurewebsites.net/. ### What these streams do ### Now that you have them set up, a quick explanation of what each one does would be helpful. **Aggregates** job gets the data from the temperature sensor, and creates the average within a given window. This allows us to chart the rolling average on top of the temperature chart. **Alerts** keeps tabs on the temperature max, and creates an alert if the temperature rises above 80, on both the raw data and the average coming from the Aggregates stream. **Light Sensor** issues an alert when the lights are turned off, which in the query we have provided, is when the lumen value is under 0.02. For more details on the website and what it shows, check out the [Website Details](../WebSite/WebsiteDetails.md). ================================================ FILE: Azure/StreamAnalyticsQueries/cg4pbi.sql ================================================ /* --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // ---------------------------------------------------------------------------------*/ Select measurename, displayname, timecreated, value from DevicesInput TIMESTAMP by timecreated ================================================ FILE: Azure/WebSite/WebsiteDetails.md ================================================ # Website details # Once you have your website running, you'll notice more is being displayed on the charts than just the raw data. Here you'll learn about the features of the website that weren't discussed during setup. ![](ASAAlertsChart.jpg) #### Dots on the graph #### You'll see dots showing up on the graph if you've set up alerts for that data stream. In the getting started project, this was done with the light sensor data to register an alert whenever the light is off, as well as when the temperature rises above 80 degrees. Hovering over that dot shows you the value during the alert, and what that alert is. #### Secondary line on chart #### If you have Stream Analytics set up, such as for temperature in our getting started project, you'll see a second line on your chart, along with an added entry in the legend of that chart. If you click on that data stream name on the left, it will toggle the display of that data on the chart. #### Hover over data streams #### If you hover over the data stream on the left, you'll be presented with the location of the device. In the case of the getting started project, you'll see both the public and local IP address of your gateway. #### Alert table #### Below the charts, you'll see a table listing all the real time alerts, including the message, time, which device, and what alert was fired. ![](AlertsTable.jpg) ================================================ FILE: Azure/WebSite/WebsitePublish.md ================================================ # Website parameters and re-publish # This document explains how to build and deploy a sample website that is used to show data and alerts in the Connect The Dots project. It assumes you have all necessary software and subscriptions and that you have cloned or download the ConnectTheDots.io project on your machine. Since the version 2 of ConnectTheDots, the deployment of the site is primarily done using an ARM template (see [here](../ARMTemplate/Readme.md) for details) and there is no need for manually deploying if you are just using the default dashboard site and solution architecture. If you want to make changes to the site though or have changed the Azure services architecture (changed Event Hub or IoT Hub) you will need to redeploy the site. ## Web app parameters In order for the website to connect with the various Azure resources (IoT Hub, Event Hub), it needs to have information about these services such as connection strings and credentials. The ARM Template used to deploy the Azure services populates these parameters so you don't have to do it manually. If you are willing to change these parameters (for example if you have decided to use a different IoT Hub), you can find them in the Azure portal: - Go to [portal.azure.com](http://portal;.azure.com) - Login using your Azure account credentials - Find the resource group that was deployed for your ConnectTheDots solution when following the [instructions](../ARMTemplate/Readme.md). - Once you have selected the resource group: 1. Identify and select the Web App 1. Click on the **settings** button in the Web App blade 1. Select **Application settings** 1. Scroll down to **App settings** ![](portalsettings.png) Once you have made your edits in the settings, you can just restart the site, clicking on the **Restart** in the Web App blade ## Modifying the dashboard Website ## ### Prerequisites ### Make sure you have all software installed and necessary subscriptions as indicated in the ReadMe.md file for the project. To repeat them here, you need 1. Microsoft Azure subscription ([free trial subscription](http://azure.microsoft.com/en-us/pricing/free-trial/) is sufficient) 1. Visual Studio – [Community Edition](http://www.visualstudio.com/downloads/download-visual-studio-vs) 1. Deploy the default dashboard Website a first time using the [ARM Template](../ARMTemplate/readme.md). ## Publish the Azure Website ## * Open the `ConnectTheDots\Azure\WebSite\source\ConnectTheDotsWebSite.sln` solution in Visual Studio * In VS, Right-click on the project name and select *Publish*. * In the Profile tab, select the publish target **Microsoft Azure Web Apps** * Select your subscription * Select the resource group for your ConnectTheDots deployment * Select the Web App below then click **OK** * Click on **Publish** ##Running the site * Open the site in a browser to verify it has deployed correctly. * At the bottom of the page you should see “Connected.”. If you see “ERROR undefined” you likely didn’t enable WebSockets for the Azure Web Site (see above section). **Note** There is a chance you won't see any data coming into your site when you first stand it up. If that is the case, try rebooting your gateway. ![](WebsiteRunning.jpg) ================================================ FILE: Azure/WebSite/site/Default.aspx ================================================  <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ConnectTheDotsWebSite.Default" %> Connect The Dots
Fork me on GitHub

Live Sensor Data

Select Sensor/R-PI:

Real Time Events

Time Device Alert Message

Devices List

Display Name Location IP Address IoTHub Device ID Connection String

Messages

================================================ FILE: Azure/WebSite/site/Docs/license.txt ================================================ Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. The MIT License (MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Azure/WebSite/site/Global.asax ================================================ <%@ Application Codebehind="Global.asax.cs" Inherits="ConnectTheDotsWebSite.Global" Language="C#" %> ================================================ FILE: Azure/WebSite/site/Web.config ================================================  ================================================ FILE: Azure/WebSite/site/css/connectthedots.css ================================================ body { font: 16px sans-serif; color: #333; width: 95%; } #header { width: 100%; border-bottom: 1px #cdcdcd solid; } h3 { width: 100%; background-color: #f7f7f7; border-bottom: 1px #ddd solid; padding: 10px; } h4 { width: 100%; background-color: #f7f7f7; border-bottom: 1px #ddd solid; margin-top: 0; } .big-block { margin-left: 10px; } ul { list-style-type: none; padding-left: 0; font: 12px sans-serif; color: #666; left: 0 } li { cursor: pointer; font-weight: normal; } .selected { font-weight: bold; } li.selected:before { content: "\25BA \0020"; } /*styles for D3 charts*/ .axis { shape-rendering: crispEdges; } .axis text { font: 10px sans-serif; font-weight: normal; fill: #787878; } .axis path, .axis line { fill: none; stroke: #787878; shape-rendering: crispEdges; } .y.axis { } .x.axis { } path.line { fill: none; stroke: steelblue; stroke-width: 1.5px; } .legend { font: 10px sans-serif; } /*for "loading" gif */ #loading { width: 100%; height: 100%; top: 0px; left: 0px; position: fixed; display: block; background-color: #777; background-color: rgba(155, 155, 155, 0.4); z-index: 99; text-align: center; } #loading-inner { background-color: #fff; border-style: solid; border-width: 1px; width: 400px; height: 200px; position: absolute; top: 50%; left: 50%; margin-left: -200px; margin-top: -100px; } #loading-image { position: relative; top: 10px; left: 10px; z-index: 100; } #loading-text { position: relative; top: 10px; left: 10px; z-index: 100; } #alerts { height: 15%; width: 100%; } #devices { height: 15%; width: 100%; } #Charts { height: 100%; position: relative; } #Graphics { height: 80%; width: 100%; position: absolute; } #controllersContainer { position: relative; text-align: center; } .controller { height: 300px; right : 10px; position: absolute; text-align: left; } #chartsContainer { margin-left: 200px; position: relative; } .chart { height: 300px; width: 100%; position: absolute; } #chartTwo { top: 40%; } #chartOne { top: 0; } #alertTable td { font-size: 12px; text-align: center; } #devicesTable td { font-size: 12px; text-align: center; } .d3-tip { line-height: 1; padding: 12px; background: rgba(255, 255, 255, 0.9); color: #000; border-radius: 2px; } .d3-tip .time_header { color: grey; font-size: 8px; display: block; } .d3-tip .value_circle { color: #006fc7; font-size: 20px; } .d3-tip .value { color: black; font-size: 14px; margin-left: 5px; } .d3-tip .message { color: black; font-size: 10px; display: block; } /* Creates a small triangle extender for the tooltip */ .d3-tip:after { box-sizing: border-box; display: inline; font-size: 8px; width: 100%; line-height: 1; color: rgba(255, 255, 255, 0.9); content: "\25BC"; position: absolute; text-align: center; } /* Style northward tooltips differently */ .d3-tip.n:after { margin: -1px 0 0 0; top: 100%; left: 0; } .sensorTip { z-index: 10000; position: absolute; white-space: pre; overflow: hidden; margin-top: -22px; float: right; margin-left: 200px; /*background: #eee;*/ color: #000; padding: 5px; padding-right: 10px; padding-left: 10px; pointer-events:none; border: solid; border-width: thin; border-color: #000; background: rgba(243, 233, 6, 0.9); } ================================================ FILE: Azure/WebSite/site/js/d3CTD.js ================================================ // --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // --------------------------------------------------------------------------------- var dataFlows = {}; var bulkMode = false; String.prototype.hashCode = function () { var hash = 0; if (this.length == 0) return hash; for (var i = 0, len = this.length; i < len; i++) { var chr = this.charCodeAt(i); hash = ((hash << 5) - hash) + chr; hash |= 0; } return hash; }; function clearData() { for (var id in dataFlows) { if (id == 'dataSource') continue; if (dataFlows[id].hasOwnProperty('flows')) { for (var id2 in dataFlows[id].flows) { dataFlows[id].flows[id2].destroy(); dataFlows[id].flows[id2] = null; } } if (dataFlows[id].hasOwnProperty('chart')) { dataFlows[id].chart.destroy(); dataFlows[id].chart = null; } } dataFlows = { dataSource: dataFlows.dataSource }; $('#chartsContainer').empty(); $('#chartsContainer').height(0); } function onChangeSensors(isAll) { var newGUIDs = []; dataFlows.dataSource.onUpdating(true); $('#sensorList li').each(function () { if ($(this).hasClass('selected') && this.id) { if (!isAll) newGUIDs.push(this.id.slice(4)); } else if (isAll) { $(this).addClass('selected'); } }); dataFlows.dataSource.changeDeviceGUIDs(isAll ? ['All'] : newGUIDs); clearData(); dataFlows.dataSource.onUpdating(false); } function onLoading(evt) { $('#loading').show(); if (evt.owner) { $('#loading-sensor').text(evt.owner); } } function onLoaded(evt) { $('#loading').hide(); } function onError(evt) { addOutputToConsole('ERROR ' + evt.owner); } function onOpen(evt) { addOutputToConsole('Connected.'); } function addNewDataFlow(eventObject) { var measurenameOriginal = eventObject['measurename'] + ''; var measurenameHash = measurenameOriginal.hashCode(); // create chart if necessary if (!dataFlows.hasOwnProperty(measurenameHash)) { dataFlows[measurenameHash] = { containerId: 'chart_' + measurenameHash, controllerId: 'controller_' + measurenameHash, dataSourceFilter: new d3CTDDataSourceFilter(dataFlows.dataSource, { measurename: measurenameOriginal }), flows: {} }; // create flows controller $('#controllersContainer').append('
    '); dataFlows[measurenameHash].controller = new d3ChartControl(dataFlows[measurenameHash].controllerId) .attachToDataSource(dataFlows[measurenameHash].dataSourceFilter); // add new div object $('#chartsContainer').height((Object.keys(dataFlows).length - 1) * 300 + 'px'); $('#chartsContainer').append('
    '); // create chart dataFlows[measurenameHash].chart = (new d3Chart(dataFlows[measurenameHash].containerId)) .addEventListeners({ 'loading': onLoading, 'loaded': onLoaded }) .attachToDataSource(dataFlows[measurenameHash].dataSourceFilter) .setFilter(dataFlows[measurenameHash].controller) .setBulkMode(bulkMode); }; // add new flow var newFlow = new d3DataFlow(eventObject.guid); //addNewSensorOption(newFlow, eventObject); dataFlows[measurenameHash].flows[eventObject.guid] = newFlow; dataFlows[measurenameHash].chart.addFlow(newFlow, 0); $(window).resize(); } function addNewSensorOption(newFlow, eventObject) { var found = false; for (var id in dataFlows) { if (dataFlows[id].hasOwnProperty('flows')) { for (var id2 in dataFlows[id].flows) { if (id2 == eventObject.guid) found = true; } } } if (!found) { // check old var oldOpt = document.getElementById('flow' + eventObject.guid); if (!oldOpt) { // add new $('#sensorList').append("
  • loading...
  • "); } document.getElementById('flow' + eventObject.guid) .onclick = function () { if ($(this).hasClass('selected')) { $(this).removeClass('selected'); } else { $(this).addClass('selected'); } onChangeSensors(); }; newFlow.addEventListener('change', function (evt) { document.getElementById('flow' + eventObject.guid).innerHTML = evt.owner.displayName(); }); } } function checkBulkMode(evt) { if (evt.bulkData != undefined) { bulkMode = evt.bulkData; // alert all charts for (var id in dataFlows) { if (dataFlows[id].chart) dataFlows[id].chart.setBulkMode(bulkMode); } } } function onNewEvent(evt) { var eventObject = evt.owner; var flowCnt = dataFlows.length; // check bulk mode checkBulkMode(eventObject); // check object necessary properties if (!eventObject.hasOwnProperty('guid') || !eventObject.hasOwnProperty('measurename')) return; var measurenameHash = eventObject['measurename'].hashCode(); // auto add flows if (!dataFlows.hasOwnProperty(measurenameHash) || !dataFlows[measurenameHash].flows.hasOwnProperty(eventObject['guid'])) addNewDataFlow(eventObject); if (eventObject.alerttype != null) { var table = $('#alertTable').DataTable(); var time = new Date(eventObject.timecreated); // Check if we already have this one in the table already to prevent duplicates var indexes = table.rows().eq(0).filter(function (rowIdx) { if ( table.cell(rowIdx, 0).data().getTime() == time.getTime() && table.cell(rowIdx, 1).data() == eventObject.displayname && table.cell(rowIdx, 2).data() == eventObject.alerttype) { return true; } return false; }); // The alert is a new one, lets display it if (indexes.length == 0) { // For performance reasons, we want to limit the number of items in the table to a max of 20. // We will remove the oldest from the list if (table.data().length > 19) { // Search for the oldest time in the list of alerts var minTime = table.data().sort( function (a, b) { return (a[0] > b[0]) - (a[0] < b[0]) })[0][0]; // Delete the oldest row table.rows( function (idx, data, node) { return data[0].getTime() == minTime.getTime(); }).remove(); } // Add the new alert to the table var message = 'message'; if (eventObject.message != null) message = eventObject.message; table.row.add([ time, eventObject.displayname, eventObject.alerttype, message ]).draw(); } } } function addOutputToConsole(text) { $('#messages').prepend('
    ' + text + '
    '); } var idleTime = 0; function onUserAction(e) { idleTime = 0; } function timerIncrement() { idleTime += 1; if (idleTime > 120) // 2 minutes { dataFlows.dataSource.closeSocket(); alert('Connection was closed due to user inactivity.'); location.reload(); } } var qrcode; function displayQRCode(title, URI) { // First time called, create the qrcode object if (qrcode == undefined) { qrcode = new QRCode(document.getElementById("qrcode"), URI); } else { // Clean previous code and update title qrcode.clear(); // Create new code qrcode.makeCode(URI); } // Display QRCode $("#qrcode").dialog({title: title}); } $(document).ready(function () { var globalSettings = $('.globalSettings'); var forceSocketCloseOnUserActionsTimeout = globalSettings.find('.ForceSocketCloseOnUserActionsTimeout').text().toLowerCase() == 'true'; if (forceSocketCloseOnUserActionsTimeout) { var idleInterval = setInterval(timerIncrement, 1000); // 1 second $(this).mousemove(onUserAction); $(this).keypress(onUserAction); } // create datasource var sss = (window.location.protocol.indexOf('s') > 0 ? "s" : ""); var uri = 'ws' + sss + '://' + window.location.host + '/api/websocketconnect?clientId=none'; addOutputToConsole('Connecting to ' + uri); dataFlows.dataSource = new d3CTDDataSourceSocket(uri).addEventListeners({ 'eventObject': onNewEvent, 'error': onError, 'open': onOpen }); $('#selectAllOpt').on('click', function () { onChangeSensors(true); }); // create alerts table var table = $('#alertTable').DataTable({ "bAutoWidth": false, "bFilter": true, "bInfo": true, "paging": true, "order": [ [0, "desc"] ], "columnDefs": [{ "targets": "timeFromDate", "data": function (row, type, val, meta) { if (type === 'set') { row[meta.col] = val; return; } else if (type === 'display') { return row[meta.col].toLocaleTimeString(); } return row[meta.col]; } }, { "targets": "numberFixed", "data": function (row, type, val, meta) { if (type === 'set') { row[meta.col] = val; return; } else if (type === 'display') { return row[meta.col].toFixed(1); } return row[meta.col]; } }, ] }); // create devices table var table = $('#devicesTable').DataTable({ "bAutoWidth": false, "bFilter": true, "bInfo": true, "paging": true, "order": [ [0, "desc"] ], "columnDefs": [{ "targets": "numberFixed", "data": function (row, type, val, meta) { if (type === 'set') { row[meta.col] = val; return; } else if (type === 'display') { return row[meta.col].toFixed(1); } return row[meta.col]; } }, ] }); $('#devicesTable tbody').on('dblclick', 'tr', function () { if ($('#cscolumn').is(':visible')) { displayQRCode(table.row(this).data()[3], table.row(this).data()[4]); } }); if ($('#cscolumn').is(':visible')) { $('#devicesTable tbody').contextmenu({ menu: [ { title: "Add new device", cmd: "add" }, { title: "Delete device", cmd: "delete" }, { title: "Get QRCode", cmd:"qrcode" } ], select: function (event, ui) { switch (ui.cmd) { case "add": addDeviceDialog.dialog('open'); break; case "delete": var deviceID = table.row(ui.target.parent()).data()[3]; var question = "Delete device " + deviceID + "?"; confirmDeleteDeviceDialog.data('deviceID', deviceID); confirmDeleteDeviceDialog.dialog({ title: question }); confirmDeleteDeviceDialog.dialog('open'); break; case "qrcode": displayQRCode(table.row(ui.target.parent()).data()[3], table.row(ui.target.parent()).data()[4]); break; } } }); } }); $(window).load(function () { // Update the devices list when page is loaded updateDevicesList(); }); ================================================ FILE: Azure/WebSite/site/js/d3CTDDataSourceFilter.js ================================================ // --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // --------------------------------------------------------------------------------- // events: onEventObject function d3CTDDataSourceFilter(dataSource, filter) { var self = this; // call base class contructor baseClass.call(self); self._filter = filter; // register handler dataSource.addEventListener('eventObject', function (evt) { var eventObject = evt.owner; // check filter if (self._filter) { for (var id in self._filter) { if (!eventObject.hasOwnProperty(id) || eventObject[id] != self._filter[id]) return; } } // forward message self.raiseEvent('eventObject', eventObject); }); return self; } d3CTDDataSourceFilter.prototype = { constructor: d3CTDDataSourceFilter, }; extendClass(d3CTDDataSourceFilter, baseClass); ================================================ FILE: Azure/WebSite/site/js/d3CTDDataSourceSocket.js ================================================ // --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // --------------------------------------------------------------------------------- // events: onEventObject function d3CTDDataSourceSocket(uri, handlers) { var self = this; // call base class contructor d3DataSourceSocket.call(self, uri, { message: function (event) { self._onMessage.call(self, event); } }); // register handlers if (handlers) { for (id in handlers) { self.addEventListener(id, handlers[id]); } } self._firstMessage = true; self._updatingState = false; self._deviceGUIDs = 'All'; return self; } d3CTDDataSourceSocket.prototype = { constructor: d3CTDDataSourceSocket, onUpdating: function (state) { var self = this; self._updatingState = state; }, changeDeviceGUIDs: function (newDeviceGUIDs) { var self = this; if (newDeviceGUIDs != undefined) { self._deviceGUIDs = newDeviceGUIDs; } var reqClear = { MessageType: "LiveDataSelection", DeviceGUIDs: 'clear' }; var req = { MessageType: "LiveDataSelection", DeviceGUIDs: self._deviceGUIDs.toString() }; // send request self.sendMessage(reqClear); self.sendMessage(req); return self; }, _onMessage: function (event) { var self = this; if (self._updatingState) return; if (self._firstMessage) { self._firstMessage = false; self.changeDeviceGUIDs(); // prevent next call event.handled = true; } else { // Parse the JSON package try { var eventObject = JSON.parse(event.owner.data); } catch (e) { self.raiseEvent('error', '
    Malformed message: ' + event.data + '
    '); return; } // forward message self.raiseEvent('eventObject', eventObject); } } }; extendClass(d3CTDDataSourceSocket, d3DataSourceSocket); ================================================ FILE: Azure/WebSite/site/js/d3Chart.js ================================================ // --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // --------------------------------------------------------------------------------- // create dataFlow with /* containerId : string, ]*/ function d3Chart(containerId) { var self = this; // call base class contructor baseClass.call(self); // initialize object self._flows = {}; self._flowsVisuals = {}; self._containerId = containerId; self._CONSTANTS = { MS_PER_MINUTE: 60000, WINDOW_MINUTES: 10, } self._isBulking = false; self._colors = d3.scale.category10(); self._onEventObjectHandler = function (event) { self._onMessageHandler.call(self, event); } self._onEventRemoveGuid = function (event) { self._onMessageRemoveGuid.call(self, event); } self._onEventAddGuid = function (event) { self._onMessageAddGuid.call(self, event); } // register update handler self.addEventListener('update', function (evt) { self.pruneOldData(); self.updateChart(); }); self._wasResizeHandled = true; return self; } d3Chart.prototype = { constructor: d3Chart, destroy: function () { var self = this; self.clearDataFlows(); self.removeChartVisual(); if (self._dataSource) { self._dataSource.removeEventListener('eventObject', this._onEventObjectHandler); } if (self._filter) { self._filter.removeEventListener('removeGuid', this._onEventRemoveGuid); self._filter.removeEventListener('addGuid', this._onEventAddGuid); } window['resizeCallback@' + self._containerId] = false; }, setBulkMode: function (newVal) { var self = this; self._isBulking = newVal; if (!newVal) { self.raiseEvent('update'); self.raiseEvent('loaded'); } return self; }, addFlow: function (newFlow, yAxis) { var self = this; self._flows[newFlow.getGUID()] = newFlow; self._flowsVisuals[newFlow.getGUID()] = { alerts: {} }; newFlow.yAxis(yAxis); newFlow.attachToChart(self); self._colors.domain(newFlow.getGUID()); return self; }, attachToDataSource: function (dataSource) { var self = this; // remebmer data source self._dataSource = dataSource; // register events handler dataSource.addEventListener('eventObject', self._onEventObjectHandler); return self; }, setFilter: function (filter) { var self = this; // remebmer data source self._filter = filter; // register guid handlers filter.addEventListener('removeGuid', self._onEventRemoveGuid); filter.addEventListener('addGuid', self._onEventAddGuid); return self; }, recalcFontSize: function () { //Standard height, for which the body font size is correct var preferredHeight = 768; //Base font size for the page var fontsize = 12; var displayHeight = $(window).height(); var percentage = displayHeight / preferredHeight; // remember font size this._fontSize = Math.floor(fontsize * percentage) - 1; return self; }, clearDataFlows: function () { var self = this; // remove visual elements for (var id in this._flowsVisuals) { self.removeFlowVisual(id); } // clear data for (var id in self._flows) { // clear data set self._flows[id].clearData(); } return self; }, removeChartVisual: function () { var self = this; if (self._x != null) { self._x = null; } if (self._y0 != null) { self._y0 = null; } if (self._y1 != null) { self._y1 = null; } if (self._y0Label != null) { self._y0Label = null; } if (self._svg != null) { self._svg.remove(); self._svg = null; } return self; }, removeFlowVisual: function (id) { if (!this._flowsVisuals.hasOwnProperty(id)) return; var dataFlowVisuals = this._flowsVisuals[id]; for (var idAl in dataFlowVisuals.alerts) { var alert = dataFlowVisuals.alerts[idAl]; if (alert.alertShowed) { alert.alertShowed.remove(); alert.alertShowed = null; } if (alert.alertBarShowed) { alert.alertBarShowed.remove(); alert.alertBarShowed = null; } } dataFlowVisuals.alerts = {}; if (dataFlowVisuals.path) { dataFlowVisuals.path.remove(); dataFlowVisuals.path = null; } if (dataFlowVisuals.legend) { dataFlowVisuals.legend.remove(); dataFlowVisuals.legend = null; } if (dataFlowVisuals.legend_r) { dataFlowVisuals.legend_r.remove(); dataFlowVisuals.legend_r = null; } return self; }, registerResizeHandler: function (containerId) { var self = this; if (!window['resizeCallback@' + containerId]) { window['resizeCallback@' + containerId] = true; $(window).bind('resize', function () { console.log('rezise chart: ' + containerId); self._wasResizeHandled = false; // remove visual elements for (var id in self._flowsVisuals) { self.removeFlowVisual(id); } // remove original one self.removeChartVisual(); d3.select("#" + containerId).select('svg').remove(); // create a new one w/ correct size self.updateChart(); }); } return self; }, setY0Label: function () { var self = this; if (self._y0Label) return; for (var id in self._flows) if (self._flows[id].label()) { self._y0Label = self._svg.append("text") .attr("transform", "rotate(-90)") .attr("class", "y0 label") .attr("text-anchor", "middle") .attr("y", -50) .attr("x", -self._height / 2) .attr("dy", "1em") .attr("font-size", self._fontSize + "px") .text(self._flows[id].label()); break; } }, createChart: function () { var self = this; var margin = { top: 5, right: 250, bottom: 20, left: 50 }; // remember container self._container = $('#' + self._containerId); // recalc font size self.recalcFontSize(); var dataFlowsArray = []; self._width = self._container.width() - margin.right; self._height = self._container.height() - margin.top - margin.bottom; // create dataFlows array for (var id in self._flows) { dataFlowsArray.push({ id: id, yMin: self._flows[id].yMin(), yMax: self._flows[id].yMax() }); } // seed the axes with some dummy values self._x = d3.time.scale() .domain([0, 1]) .range([0, self._width]); self._y0 = d3.scale.linear() .range([self._height, 0]); if (dataFlowsArray.length > 0 && dataFlowsArray[0].yMax != null && dataFlowsArray[0].yMin != null) self._y0.domain([dataFlowsArray[0].yMin, dataFlowsArray[0].yMax]); self._svg = d3.select("#" + self._containerId) .append("p") .append("svg") .attr("width", self._width + margin.left + margin.right) .attr("height", self._height + margin.top + margin.bottom) .style("margin-bottom", margin.bottom + "px") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); self._svg.append("g") .attr("class", "y0 axis") .call(d3.svg.axis().scale(self._y0).ticks(7).orient("left")); // check y0 label self.setY0Label(); self._svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + (self._height) + ")") .call(d3.svg.axis().scale(self._x).orient("bottom").tickFormat(d3.time.format("%X"))); // create tip self._tip = d3.tip() .attr('class', 'd3-tip') .offset([-10, 0]) .html(function (d) { return ""; }); self._svg.call(self._tip); // register resize handler self.registerResizeHandler(self._containerId); }, pruneAlerts: function (flowId, cutoff) { var self = this; var alertsToRemove = []; // cut alerts for (var t in self._flowsVisuals[flowId].alerts) { if (new Date(t) < cutoff) alertsToRemove.push(t); } for (var t in alertsToRemove) { var alert = self._flowsVisuals[flowId].alerts[alertsToRemove[t]]; if (alert.alertShowed) { alert.alertShowed.remove(); alert.alertShowed = null; } if (alert.alertBarShowed) { alert.alertBarShowed.remove(); alert.alertBarShowed = null; } delete alert; } }, pruneOldData: function () { var self = this; var now = new Date(); var cutoff = new Date(now - self._CONSTANTS.WINDOW_MINUTES * self._CONSTANTS.MS_PER_MINUTE) // cut data for (var id in self._flows) { if (self._flows[id].cutData(cutoff)) { self.pruneAlerts(id, cutoff); //self.removeFlowVisual(id); } } }, updateChart: function () { var self = this; var minDate = new Date(3015, 1, 1); var maxDate = new Date(1915, 1, 1); var minVal = [Number.MAX_VALUE, Number.MAX_VALUE]; var maxVal = [0, 0]; var displayHeight = $(window).height(); for (var id in self._flows) { var dataFlow = self._flows[id]; if (dataFlow.visible == false) continue; var data = dataFlow.getData(); if (data.length == 0 || !dataFlow.displayName()) continue; // sort data data.sort(function (a, b) { if (a.time < b.time) return -1; if (a.time > b.time) return 1; return 0; }); var y = dataFlow.yAxis(); for (var j = 0; j < data.length; j++) { var c = data[j].data; var t = data[j].time; if (c < minVal[y]) { minVal[y] = c; } if (c > maxVal[y]) { maxVal[y] = c; } if (t > maxDate) { maxDate = t; } if (t < minDate) { minDate = t; } } } // create chart on demand if (self._svg == null) { self.createChart(); } // check y0 label self.setY0Label(); var wasBoundsChanged = !self._previousBounds || self._previousBounds.maxVal0 !== maxVal[0] || self._previousBounds.minVal0 !== minVal[0]; if (!self._wasResizeHandled || wasBoundsChanged && minVal[0] < Number.MAX_VALUE) { var diff = maxVal[0] - minVal[0]; var scaleMargin = diff * 10 / 100; if (!isFinite(scaleMargin)) { scaleMargin = 0; } var v1 = Math.abs(minVal[0] - scaleMargin); var v2 = Math.abs(maxVal[0] + scaleMargin); if (!isFinite(v1)) { v1 = minVal[0]; } if (!isFinite(v2)) { v2 = maxVal[0]; } if (v2 < v1) { v2 = v1; } self._y0 = self._y0 .domain([v1, v2]); var yAxisLeft = d3.svg.axis() .scale(self._y0) .orient("left") self._svg.selectAll("g.y0.axis") .call(yAxisLeft); self._wasResizeHandled = true; } wasBoundsChanged = !self._previousBounds || self._previousBounds.maxVal1 !== maxVal[1] || self._previousBounds.minVal1 !== minVal[1]; if (!self._wasResizeHandled || wasBoundsChanged && minVal[1] < Number.MAX_VALUE) { var diff = maxVal[1] - minVal[1]; var scaleMargin = (diff) * 10 / 100; if (!isFinite(scaleMargin)) { scaleMargin = 0; } var v1 = Math.abs(minVal[1] - scaleMargin); var v2 = Math.abs(maxVal[1] + scaleMargin); if (!isFinite(v1)) { v1 = minVal[1]; } if (!isFinite(v2)) { v2 = maxVal[1]; } if (v2 < v1) { v2 = v1; } self._y1 = self._y1 .domain([v1,v2]); var yAxisRight = d3.svg.axis() .scale(self._y1) .orient("right") self._svg.selectAll("g.y1.axis") .call(yAxisRight); self._wasResizeHandled = true; } self._x = self._x .domain([minDate, maxDate]); var xAxis = d3.svg.axis() .scale(self._x) .tickFormat(d3.time.format("%X")) .orient("bottom"); self._svg.selectAll("g.x.axis") .call(xAxis); self._previousBounds = { maxVal0: maxVal[0], maxVal1: maxVal[1], minVal0: minVal[0], minVal1: minVal[1], }; if (!self._line) { self._line = [ d3.svg.line() .interpolate("monotone") .x(function (d) { return self._x(d.time); }) .y(function (d) { return self._y0(d.data); }), d3.svg.line() .interpolate("monotone") .x(function (d) { return self._x(d.time); }) .y(function (d) { return self._y1(d.data); }) ]; } try { var pos = 0; for (var id in self._flows) { var dataGUID = id; var dataFlow = self._flows[id]; if (dataFlow.visible == false) continue; var dataFlowVisuals = self._flowsVisuals[id]; var data = dataFlow.getData(); var yAxis = dataFlow.yAxis(); if (dataFlowVisuals.path == null) { dataFlowVisuals.path = self._svg.append("g") .append("path") .datum(data) .attr("class", "line") .attr("d", self._line[yAxis]) .style("stroke", function (d) { return self._colors(dataGUID); }); } dataFlowVisuals.path.datum(data) .attr("d", self._line[yAxis]); // draw alert points for (var pnt in data) { if (typeof data[pnt].alertData == 'object') { if (!dataFlowVisuals.alerts.hasOwnProperty(data[pnt].time)) { var transferData = JSON.stringify({ alertData: data[pnt].alertData, time: data[pnt].time, data: data[pnt].data }); var alertVisual = dataFlowVisuals.alerts[data[pnt].time] = {}; alertVisual.alertBarShowed = self._svg.append("g").append("rect") .attr("class", "bar") .attr("x", self._x(data[pnt].time)) .attr("y", 0) .attr("height", self._height) .attr("width", "2px") .style("fill", "#e6c9cd") alertVisual.alertShowed = self._svg.append("g").append("circle") .attr("class", "d3-dot") .attr("cx", self._x(data[pnt].time)) .attr("cy", yAxis == 0 ? self._y0(data[pnt].data) : self._y1(data[pnt].data)) .style("fill", "#e93541") .attr("r", displayHeight / 200) .on('mouseover', function () { d3.select(this).transition().attr("r", displayHeight / 130); eval("self._tip.show(" + transferData + ");") }) .on('mouseout', function () { d3.select(this).transition().attr("r", displayHeight / 200); self._tip.hide(); }); } else { var alertVisual = dataFlowVisuals.alerts[data[pnt].time]; alertVisual.alertShowed.attr("cx", self._x(data[pnt].time)) .attr("cy", yAxis == 0 ? self._y0(data[pnt].data) : self._y1(data[pnt].data)); alertVisual.alertBarShowed .attr("x", self._x(data[pnt].time)) } } } if (dataFlowVisuals.legend == null) { dataFlowVisuals.legend_r = self._svg.append("rect") .attr("class", "legend") .attr("width", 10) .attr("height", 10) .attr("x", self._width + 50) .attr("y", 20 + (20 * pos)) .style("fill", self._colors(dataGUID)) .style("stroke", self._colors(dataGUID)); dataFlowVisuals.legend = self._svg.append("text") .attr("x", self._width + 65) .attr("y", 20 + (20 * pos) + 5) .attr("class", "legend") .style("fill", self._colors(dataGUID)) .text(dataFlow.displayName()); } else { dataFlowVisuals.legend.text(dataFlow.displayName()); } pos++; } } catch (e) { console.log(e); } }, _onMessageAddGuid: function (evt) { var self = this; if (self._flows.hasOwnProperty(evt.owner)) { self._flows[evt.owner].visible = true; } }, _onMessageRemoveGuid: function (evt) { var self = this; self.removeFlowVisual(evt.owner); if (self._flows.hasOwnProperty(evt.owner)) { self._flows[evt.owner].visible = false; } }, // private members _onMessageHandler: function (eventObject) { var self = this; var evt = eventObject.owner; // the message is data for the charts. find chart for message if (evt.hasOwnProperty('guid') && self._flows.hasOwnProperty(evt.guid)) { // check filter //if (self._filter && !self._filter.checkGUID(evt.guid)) return; // check event time var now = new Date(); var cutoff = new Date(now - self._CONSTANTS.WINDOW_MINUTES * self._CONSTANTS.MS_PER_MINUTE) if (evt.time < cutoff) { return; } // add event self.raiseEvent('newData', evt); // check if nessasary to update if (!self._isBulking) { self.raiseEvent('update'); } else { self.raiseEvent('loading', evt.displayname); } } } }; extendClass(d3Chart, baseClass); ================================================ FILE: Azure/WebSite/site/js/d3ChartControl.js ================================================ // --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // --------------------------------------------------------------------------------- function d3ChartControl(containerId) { var self = this; // call base class contructor baseClass.call(self); // create ul self._containerId = containerId; self._ulOptions = {}; this._onEventObjectHandler = function (event) { self._onNewDataHandler.call(self, event); }; return self; } d3ChartControl.prototype = { constructor: d3ChartControl, destroy: function () { var self = this; if (self._dataSource) { self._dataSource.removeEventListener('eventObject', this._onEventObjectHandler); } }, /* params = { guid: 'someGUID', title: 'someTitle', selected: false, // default true allOption: true // if true, guid = 0 and it's main switcher for all } */ setOption: function (params) { var self = this; var guid = params.guid; if (guid == undefined) return; // check if exists or create new if (!self._ulOptions.hasOwnProperty(params.guid)) { self._ulOptions[guid] = {}; self._ulOptions[guid].li = $('
  • ' + params.title + "
  • ").appendTo("#" + self._containerId); // set selected class if (!params.hasOwnProperty('selected') || params.selected == true) { self._ulOptions[guid].li.addClass('selected'); } self._ulOptions[guid].state = self._ulOptions[guid].li.hasClass('selected'); // add click handler self._ulOptions[guid].li.on('click', function (evt) { if ($(this).hasClass('selected')) { $(this).removeClass('selected'); self.raiseEvent('removeGuid', guid); } else { $(this).addClass('selected'); self.raiseEvent('addGuid', guid); } self._ulOptions[guid].state = self._ulOptions[guid].li.hasClass('selected'); }); self._ulOptions[guid].li .append('
    ' + 'Location: ' + params.location + '
    '); self._ulOptions[guid].li.each(function () { $(this).data('sensorTip', $(this).find('.sensorTip')); $(this).data('sensorTip').hide(); }); self._ulOptions[guid].li.each(function() { $(this).on('mouseover', function () { $(this).data('sensorTip').show(); }); }); self._ulOptions[guid].li.each(function () { $(this).on('mouseout', function () { $(this).data('sensorTip').hide(); }); }); } return self; }, attachToDataSource: function (dataSource) { var self = this; // remebmer data source self._dataSource = dataSource; // register events handler dataSource.addEventListener('eventObject', this._onEventObjectHandler); return self; }, // private members _onNewDataHandler: function (eventObject) { var self = this; var evt = eventObject.owner; // check GUID if (!evt.guid || self._ulOptions.hasOwnProperty(evt.guid)) return; // add new option self.setOption({ guid: evt.guid, title: evt.displayname, location: evt.location ? evt.location : "Unknown" }); }, checkGUID : function(guid) { var self = this; return (self._ulOptions.hasOwnProperty(guid) && self._ulOptions[guid].state); } }; extendClass(d3ChartControl, baseClass); ================================================ FILE: Azure/WebSite/site/js/d3DataFlow.js ================================================ // --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // --------------------------------------------------------------------------------- // create dataFlow with /* flowGUID : string, params = { yMin : number, yMax : number, displayName : string, label : string, filter : {} };*/ function d3DataFlow(flowGUID, params) { var self = this; // call base class contructor baseClass.call(self); // initialize object self._guid = flowGUID; self._yMin = params ? params.yMin : undefined; self._yMax = params ? params.yMax : undefined; self._displayName = params ? params.displayName : undefined; self._label = params ? params.label : undefined; self._filter = params ? params.filter : undefined; self._CONSTANTS = { MAX_ARRAY_SIZE: 1000 }; this._onEventObjectHandler = function (event) { self._onNewDataHandler.call(self, event); }; self.clearData(); return self; } d3DataFlow.prototype = { constructor: d3DataFlow, destroy: function () { var self = this; if (self._chart) { self._chart.removeEventListener('newData', this._onEventObjectHandler); } }, attachToChart: function (chart) { var self = this; // remebmer data source self._chart = chart; // register events handler chart.addEventListener('newData', this._onEventObjectHandler); return self; }, getGUID: function () { return this._guid; }, yMin: function (yMinNew) { if (yMinNew != undefined) { this._yMin = yMinNew; } return this._yMin; }, yMax: function (yMaxNew) { if (yMaxNew != undefined) { this._yMax = yMaxNew; } return this._yMax; }, displayName: function (displayNameNew) { if (displayNameNew != undefined) { this._displayName = displayNameNew; } return this._displayName; }, label: function (labelNew) { if (labelNew != undefined) { this._label = labelNew; } return this._label; }, yAxis: function (yAxisNew) { if (yAxisNew != undefined) { this._yAxis = yAxisNew; } return this._yAxis; }, clearData: function () { this._data = []; }, cutData: function (cutoff) { var len = this._data.length; while (this._data.length >= 1 && this._data[0].time < cutoff) { this._data.shift(); } return len != this._data.length; }, getData: function () { return this._data; }, addNewPoint: function (obj) { var self = this; var t = new Date(obj.time); if (isNaN(t.getTime())) { return; } var pushObj = { data: obj.value, time: new Date(obj.time) }; if (obj.alerttype) pushObj.alertData = { message: obj.message }; self._data.push(pushObj); if (self._data.length >= self._CONSTANTS.MAX_ARRAY_SIZE) { self._data.shift(); return; } }, // private members _onNewDataHandler: function (evt) { var self = this; var object = evt.owner; // check filter if (self._filter) { for (var id in self._filter) { if (!object.hasOwnProperty(id) || object[id] != self._filter[id]) return; } } // check GUID if (object.guid != self._guid) return; // add to array self.addNewPoint(object); // update properties self._updateProperties(object); }, _updateProperties: function (eventObject) { var self = this; if (eventObject.hasOwnProperty("displayname")) { self.displayName(eventObject.displayname); } if (eventObject.hasOwnProperty("measurename") && eventObject.hasOwnProperty("unitofmeasure")) { self.label(eventObject.measurename + " (" + eventObject.unitofmeasure + ")"); } self.raiseEvent('change', self); } }; extendClass(d3DataFlow, baseClass); ================================================ FILE: Azure/WebSite/site/js/d3DataSourceSocket.js ================================================ // --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // --------------------------------------------------------------------------------- // create new source function d3DataSourceSocket(uri, handlers) { var self = this; // call base class contructor baseClass.call(self); // initialize object self._websocket = new WebSocket(uri); self._eventHandlers = {}; if (handlers) { for (id in handlers) { self.addEventListener(id, handlers[id]); } } // register handlers self._websocket.onopen = function () { self.raiseEvent.call(self, 'open'); } self._websocket.onerror = function (event) { self.raiseEvent.call(self, 'error', event); } self._websocket.onmessage = function (event) { self.raiseEvent.call(self, 'message', event); } return self; } d3DataSourceSocket.prototype = { constructor: d3DataSourceSocket, sendMessage: function (message) { this._websocket.send(JSON.stringify(message)); }, closeSocket: function () { this._websocket.close(); } }; extendClass(d3DataSourceSocket, baseClass); ================================================ FILE: Azure/WebSite/site/js/d3utils.js ================================================ // --------------------------------------------------------------------------------- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // // The MIT License (MIT) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // --------------------------------------------------------------------------------- function extendClass(Child, Parent) { var F = function () { }; F.prototype = Parent.prototype; var f = new F(); for (var prop in Child.prototype) f[prop] = Child.prototype[prop]; Child.prototype = f; Child.prototype.superclass = Parent.prototype; } function deepCopy(obj) { if (typeof obj != 'object') { return obj; } var copy = obj.constructor(); for (var key in obj) { if (typeof obj[key] == 'object' && obj[key] != null) { copy[key] = this.deepCopy(obj[key]); } else { copy[key] = obj[key]; } } return copy; }; /** * Base class initialization */ function baseClass() { // set protected properties this._eventsListeners = {}; } baseClass.prototype = { constructor: baseClass, addEventListeners: function (callbacks) { var self = this; for (var id in callbacks) { self.addEventListener(id, callbacks[id]); } return self; }, addEventListener: function (eventName, callback) { var self = this; if (!self._eventsListeners.hasOwnProperty(eventName)) { self._eventsListeners[eventName] = []; } self._eventsListeners[eventName].push(callback); return self; }, removeEventListener: function (eventName, callback) { var self = this; if (!self._eventsListeners.hasOwnProperty(eventName) || !self._eventsListeners[eventName].length) return; for (var i = this._eventsListeners[eventName].length; i > 0; --i) if (self._eventsListeners[eventName][i - 1] === callback) { self._eventsListeners[eventName].splice(i - 1, 1); break; } return self; }, raiseEvent: function (eventName, owner) { if (!this._eventsListeners.hasOwnProperty(eventName) || !this._eventsListeners[eventName].length) return; var context = { name: eventName, source: this, owner: owner, handled: false }; var evFuncs = this._eventsListeners[eventName]; for (var i = evFuncs.length; i > 0; --i) { evFuncs[i - 1].call(this, context); // if handled - stop event handling if (context.handled) break; } }, }; ================================================ FILE: Azure/WebSite/site/js/devicesList.js ================================================ function updateDevicesList() { // Get the devices list from the server PageMethods.GetDevicesList(ListSuccess, Failure); } var addDeviceDialog, addDeviceForm, confirmDeleteDeviceDialog; function addNewDevice() { var newDeviceID = $("#newdeviceid").val(); addDeviceDialog.dialog("close"); PageMethods.AddDevice(newDeviceID, AddSuccess, Failure); } function deleteDevice(deviceID) { var id = deviceID; confirmDeleteDeviceDialog.dialog("close"); PageMethods.DeleteDevice(id, DeleteSuccess, Failure); } addDeviceDialog = $("#add-device-dialog-form").dialog({ autoOpen: false, height: "auto", width: 400, modal: true, buttons: { "Ok": addNewDevice, Cancel: function () { addDeviceDialog.dialog("close"); } }, close: function () { addDeviceForm[0].reset(); } }); confirmDeleteDeviceDialog = $("#delete-device-dialog-confirm").dialog({ autoOpen: false, resizable: false, height: "auto", width: 400, modal: true, buttons: { "Delete device": function () { deleteDevice(confirmDeleteDeviceDialog.data('deviceID')); }, Cancel: function () { confirmDeleteDeviceDialog.dialog("close"); } } }); addDeviceForm = addDeviceDialog.find("form").on("submit", function (event) { event.preventDefault(); addNewDevice(); }); //function addDevice() //{ // var deviceName = prompt("Enter a unique Device Id"); // if (deviceName) // PageMethods.AddDevice(deviceName, AddSuccess, Failure); //} //function deleteDevice() { // var deviceName = prompt("Enter the IoT Hub ID of the device you want to remove"); // if (deviceName) // PageMethods.DeleteDevice(deviceName, DeleteSuccess, Failure); //} function ListSuccess(result) { if (result) { var devicesList = JSON.parse(result); var table = $('#devicesTable').DataTable(); // Check if we need to remove a row from the table var rowsToRemove=[]; for (var rowIndex = 0; rowIndex < table.rows().eq(0).length; rowIndex++) { for (var deviceIdx = 0 ; deviceIdx < devicesList.length ; deviceIdx++) { if (devicesList[deviceIdx]['guid'] == table.cell(rowIndex, 3).data()) { rowsToRemove[rowsToRemove.length] = rowIndex; break; } } } for (var idx = rowsToRemove.length; idx > 0 ; idx--) { table.rows(idx).remove().draw(); } // Check if we need to update or add a row in the table for (var deviceIndex = 0 ; deviceIndex < devicesList.length; deviceIndex++) { var device = devicesList[deviceIndex]; var location = 'unknown'; if (device['location'] != null) location = device['location']; var ipaddress = 'unknown'; if (device['ipaddress'] != null) ipaddress = device['ipaddress']; var displayname = 'unknown'; if (device['displayname'] != null) displayname = device['displayname']; var connectionstring = 'unknown'; if (device['connectionstring'] != null) connectionstring = device['connectionstring']; var addRow = true; if (table.rows().length > 0) { // Check if we already have this one in the table already to prevent duplicates var indexes = table.rows().eq(0).filter(function (rowIdx) { if ( table.cell(rowIdx, 3).data() == device['guid']) { // Update the row table.cell(rowIdx, 0).innerHTML = displayname; table.cell(rowIdx, 1).innerHTML = location; table.cell(rowIdx, 2).innerHTML = ipaddress; if ($('#cscolumn').is(':visible')) { table.cell(rowIdx, 4).innerHTML = connectionstring; } return true; } return false; }); // if the device is already in the list, return. if (indexes.length != 0) addRow = false; } // The device is a new one, lets add it to the table if (addRow == true) { if ($('#cscolumn').is(':visible')) { table.row.add([ displayname, location, ipaddress, device['guid'], connectionstring ]).draw(); } else { table.row.add([ displayname, location, ipaddress, device['guid'] ]).draw(); } } } } } function AddSuccess(result) { if (result) { var resultObject = JSON.parse(result); if (resultObject.Error) { addOutputToConsole('ERROR ' + resultObject.Error); alert(resultObject.Error); } else { addOutputToConsole('Device ' + resultObject.Device + ' added to IoT Hub'); updateDevicesList(); } } else { addOutputToConsole('An error happened while trying to add a new device'); alert("An error happened while trying to add a new device"); } } function DeleteSuccess(result) { if (result) { var resultObject = JSON.parse(result); if (resultObject.Error) { addOutputToConsole('ERROR ' + resultObject.Error); alert(resultObject.Error); } else { addOutputToConsole('Device ' + resultObject.Device + ' deleted from IoT Hub'); updateDevicesList(); } } else { addOutputToConsole('An error happened while trying to delete the device'); alert('An error happened while trying to delete the device'); } } function Failure(error) { addOutputToConsole(error); alert(error); } ================================================ FILE: Azure/WebSite/site/js/jquery.ui-contextmenu.js ================================================ /******************************************************************************* * jquery.ui-contextmenu.js plugin. * * jQuery plugin that provides a context menu (based on the jQueryUI menu widget). * * @see https://github.com/mar10/jquery-ui-contextmenu * * Copyright (c) 2013-2016, Martin Wendt (http://wwWendt.de). Licensed MIT. */ (function( factory ) { "use strict"; if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define([ "jquery", "jquery-ui/menu" ], factory ); } else { // Browser globals factory( jQuery ); } }(function( $ ) { "use strict"; var supportSelectstart = "onselectstart" in document.createElement("div"), match = $.ui.menu.version.match(/^(\d)\.(\d+)/), uiVersion = { major: parseInt(match[1], 10), minor: parseInt(match[2], 10) }, isLTE110 = ( uiVersion.major < 2 && uiVersion.minor <= 10 ), isLTE111 = ( uiVersion.major < 2 && uiVersion.minor <= 11 ); $.widget("moogle.contextmenu", { version: "@VERSION", options: { addClass: "ui-contextmenu", // Add this class to the outer
      autoFocus: false, // Set keyboard focus to first entry on open autoTrigger: true, // open menu on browser's `contextmenu` event delegate: null, // selector hide: { effect: "fadeOut", duration: "fast" }, ignoreParentSelect: true, // Don't trigger 'select' for sub-menu parents menu: null, // selector or jQuery pointing to
        , or a definition hash position: null, // popup positon preventContextMenuForPopup: false, // prevent opening the browser's system // context menu on menu entries preventSelect: false, // disable text selection of target show: { effect: "slideDown", duration: "fast" }, taphold: false, // open menu on taphold events (requires external plugins) uiMenuOptions: {}, // Additional options, used when UI Menu is created // Events: beforeOpen: $.noop, // menu about to open; return `false` to prevent opening blur: $.noop, // menu option lost focus close: $.noop, // menu was closed create: $.noop, // menu was initialized createMenu: $.noop, // menu was initialized (original UI Menu) focus: $.noop, // menu option got focus open: $.noop, // menu was opened select: $.noop // menu option was selected; return `false` to prevent closing }, /** Constructor */ _create: function() { var cssText, eventNames, targetId, opts = this.options; this.$headStyle = null; this.$menu = null; this.menuIsTemp = false; this.currentTarget = null; this.previousFocus = null; if (opts.preventSelect) { // Create a global style for all potential menu targets // If the contextmenu was bound to `document`, we apply the // selector relative to the tag instead targetId = ($(this.element).is(document) ? $("body") : this.element).uniqueId().attr("id"); cssText = "#" + targetId + " " + opts.delegate + " { " + "-webkit-user-select: none; " + "-khtml-user-select: none; " + "-moz-user-select: none; " + "-ms-user-select: none; " + "user-select: none; " + "}"; this.$headStyle = $("