This commit is contained in:
Ian Hollier 2021-02-03 13:50:07 -08:00
Родитель 4aa3dcbecd
Коммит 3b6144cde0
11 изменённых файлов: 3350 добавлений и 0 удалений

Просмотреть файл

@ -1,2 +1,48 @@
# iot-central-web-mqtt-device
Create a simple IoT device in a web browser that communicates with Azure IoT Central
## Introduction
This project is based in large part on the work done by Github user [ridomin](https://github.com/ridomin) and the repository [iothub-webclient](https://github.com/ridomin/iothub-webclient). In this project I have focused the sample on connecting to an IoT Central application and it exclusively uses the Device Provisioning Service (DPS) for the provisioning and connection of the device. IoT Central does not support connection strings and requires the device to use DPS to obtain it the location of it's IoT hub, this allows a device to be moved between hubs and in the near futrure for IoT Central to support IoT Hub redundancy. The device running in the browser is also setup to run as an autonomous device very much like if the device was running in the real world. The devices two way communication between IoT Central and itsel;f can be observed in the console output on the web page once sending telemetry has been started.
The device supports the following functionality:
* Connection to IoT Central using DPS. The device does not need to be registered in the application prior to connecting
* The device can be authenticated via either the device SAS token or the Group SAS token for the application (in this case the device SAS token is automatically generated)
* The device is automatically associated with the model identity provided by the user in the form
* Sending the telemetry values for temperature and humidity (randomly generated values in a range) ever ~5 seconds
* Sending the reported property of fan speed to IoT Central every ~10 seconds
* Accepting a desired (writable) property setTemp to set the temperature of a thermostat on the device
* Accepting the direct method call sendTextMessage from IoT Central with a text message and responding with a boolean return value if the message is read
* Accepting a cloud to device message startVacuumCleaner that takes a time value as a parameter (this is additional functionality over iothub-webclient version)
All communication and payloads are displayed on the web browser console output with the last 200 transmissions held in history.
## Hosting locally
To host this locally clone this repository to your computer and run the following:
```
npm install
npm start
```
The following should be seen after running the last command
```
> webmqtt@1.0.0 start D:\github\iot-central-web-mqtt-device
> node server.js
Listening at http//localhost:8080
```
Open your browser of choice and go to the URL http//localhost:8080. You should see the following in you browser:
![Initial browser screen](https://github.com/iot-for-all/iot-central-web-mqtt-device/raw/master/assets/initialscreen.png "Initial browser screen")
## Hosting on Azure
## Running the sample

Двоичные данные
assets/initialscreen.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 24 KiB

45
content/AzDpsClient.js Normal file
Просмотреть файл

@ -0,0 +1,45 @@
const REGISTRATION_TOPIC = '$dps/registrations/res/#'
const REGISTER_TOPIC = '$dps/registrations/PUT/iotdps-register/?$rid='
/**
*
* @param {String} key
* @param {String} msg
* @returns {Promise<string>}
*/
export const createHmac = async (key, msg) => {
const keyBytes = Uint8Array.from(window.atob(key), c => c.charCodeAt(0))
const msgBytes = Uint8Array.from(msg, c => c.charCodeAt(0))
const cryptoKey = await window.crypto.subtle.importKey(
'raw', keyBytes, { name: 'HMAC', hash: 'SHA-256' },
true, ['sign']
)
const signature = await window.crypto.subtle.sign('HMAC', cryptoKey, msgBytes)
return window.btoa(String.fromCharCode(...new Uint8Array(signature)))
}
export class AzDpsClient {
constructor (scopeId, deviceId, deviceKey, modelId) {
this.host = 'global.azure-devices-provisioning.net'
this.scopeId = scopeId
this.deviceId = deviceId
this.deviceKey = deviceKey
this.modelId = modelId
}
async registerDevice () {
const endpoint = 'https://dps-proxy.azurewebsites.net/register'
const url = `${endpoint}?scopeId=${this.scopeId}&deviceId=${this.deviceId}&deviceKey=${encodeURIComponent(this.deviceKey)}&modelId=${this.modelId}`
console.log(url)
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Content-Encoding': 'utf-8'
}
})
const resp = await response.json()
console.log(resp)
return resp
}
}

283
content/AzIotHubClient.js Normal file
Просмотреть файл

@ -0,0 +1,283 @@
const WEB_SOCKET = '/$iothub/websocket?iothub-no-client-cert=true'
const DEVICE_TWIN_RES_TOPIC = '$iothub/twin/res/#'
const DEVICE_TWIN_GET_TOPIC = '$iothub/twin/GET/?$rid='
const DEVICE_TWIN_PUBLISH_TOPIC = '$iothub/twin/PATCH/properties/reported/?$rid='
const DIRECT_METHOD_TOPIC = '$iothub/methods/POST/#'
const DEVICE_TWIN_DESIRED_PROP_RES_TOPIC = '$iothub/twin/PATCH/properties/desired/#'
const DIRECT_METHOD_RESPONSE_TOPIC = '$iothub/methods/res/{status}/?$rid='
const DEVICE_C2D_COMMAND_TOPIC = 'devices/{deviceid}/messages/devicebound/#'
/**
*
* @param {String} key
* @param {String} msg
* @returns {Promise<string>}
*/
const createHmac = async (key, msg) => {
const keyBytes = Uint8Array.from(window.atob(key), c => c.charCodeAt(0))
const msgBytes = Uint8Array.from(msg, c => c.charCodeAt(0))
const cryptoKey = await window.crypto.subtle.importKey(
'raw', keyBytes, { name: 'HMAC', hash: 'SHA-256' },
true, ['sign']
)
const signature = await window.crypto.subtle.sign('HMAC', cryptoKey, msgBytes)
return window.btoa(String.fromCharCode(...new Uint8Array(signature)))
}
/**
* @param {string} resourceUri
* @param {string} signingKey
* @param {string | null} policyName
* @param {number} expiresInMins
* @returns {Promise<string>}
*/
async function generateSasToken (resourceUri, signingKey, policyName, expiresInMins) {
resourceUri = encodeURIComponent(resourceUri)
let expires = (Date.now() / 1000) + expiresInMins * 60
expires = Math.ceil(expires)
const toSign = resourceUri + '\n' + expires
const hmac = await createHmac(signingKey, toSign)
const base64UriEncoded = encodeURIComponent(hmac)
let token = 'SharedAccessSignature sr=' + resourceUri + '&sig=' + base64UriEncoded + '&se=' + expires
if (policyName) token += '&skn=' + policyName
return token
}
export class AzIoTHubClient {
/**
* @param {string} host
* @param {string} deviceId
* @param {string} key
* @param {string} [modelId]
*/
constructor (host, deviceId, key, modelId) {
this.connected = false
this.host = host
this.deviceId = deviceId
this.key = key
this.modelId = modelId
this.rid = 0
this.client = new Paho.MQTT.Client(this.host, Number(443), WEB_SOCKET, this.deviceId)
/**
* @description Callback when a direct method invocation is received
* @param {string} method
* @param {string} payload
* @param {number} rid
*/
this.directMethodCallback = (method, payload, rid) => { }
/**
* @description Callback when a C2D command invocation is received
* @param {string} methodName
* @param {string} payload
*/
this.c2dCallback = (methodName, payload) => { }
/**
* @description Callback for desired properties upadtes
* @param {string} desired
*/
this.desiredPropCallback = (desired) => { }
/**
* @param {any} err
*/
this.disconnectCallback = (err) => { console.log(err) }
/**
* @param {any} twin
*/
this._onReadTwinCompleted = (twin) => { }
this._onUpdateTwinCompleted = () => { }
}
/**
* @description Connects to Azure IoT Hub using MQTT over websockets
*/
async connect () {
let userName = `${this.host}/${this.deviceId}/?api-version=2020-05-31-preview`
if (this.modelId) userName += `&model-id=${this.modelId}`
const password = await generateSasToken(`${this.host}/devices/${this.deviceId}`, this.key, null, 60)
return new Promise((resolve, reject) => {
this.client.onConnectionLost = (err) => {
console.log(err)
this.connected = false
this.disconnectCallback(err)
reject(err)
}
const willMsg = new Paho.MQTT.Message('')
willMsg.destinationName = 'willMessage'
this.client.onMessageArrived = (/** @type {Paho.MQTT.Message} */ m) => {
const destinationName = m.destinationName
const payloadString = m.payloadString
// console.log('On Msg Arrived to ' + destinationName)
// console.log(payloadString)
if (destinationName === '$iothub/twin/res/200/?$rid=' + this.rid) {
this._onReadTwinCompleted(payloadString)
}
if (destinationName.startsWith('$iothub/twin/res/204/?$rid=' + this.rid)) {
this._onUpdateTwinCompleted()
}
if (destinationName.indexOf('methods/POST') > 1) {
const destParts = destinationName.split('/') // $iothub/methods/POST/myCommand/?$rid=2
const methodName = destParts[3]
const ridPart = destParts[4]
const rid = parseInt(ridPart.split('=')[1])
this.directMethodCallback(methodName, payloadString, rid)
}
if (destinationName.indexOf('twin/PATCH/properties/desired') > 1) {
this.desiredPropCallback(payloadString)
}
if (destinationName.startsWith(DEVICE_C2D_COMMAND_TOPIC.slice(0, -1).replace('{deviceid}', this.deviceId))) {
const url = decodeURIComponent(destinationName)
const methodName = url.substring(url.indexOf("&method-name=")+13)
this.c2dCallback(methodName, payloadString)
}
}
this.client.connect({
useSSL: true,
userName: userName,
timeout: 120,
cleanSession: true,
invocationContext: {},
keepAliveInterval: 120,
willMessage: willMsg,
password: password,
onSuccess: () => {
this.connected = true
console.log('Connected !!')
this.client.subscribe(DEVICE_TWIN_RES_TOPIC, {
qos: 0,
invocationContext: {},
onSuccess: () => { },
onFailure: (err) => { throw err },
timeout: 120
})
this.client.subscribe(DIRECT_METHOD_TOPIC, {
qos: 0,
invocationContext: {},
onSuccess: () => { },
onFailure: (err) => { throw err },
timeout: 120
})
this.client.subscribe(DEVICE_TWIN_DESIRED_PROP_RES_TOPIC, {
qos: 0,
invocationContext: {},
onSuccess: () => { },
onFailure: (err) => { throw err },
timeout: 120
})
this.client.subscribe(DEVICE_C2D_COMMAND_TOPIC.replace('{deviceid}', this.deviceId), {
qos: 0,
invocationContext: {},
onSuccess: () => { },
onFailure: (err) => { throw err },
timeout: 120
})
resolve(this.deviceId)
}
})
})
}
/**
* @return {Promise<DeviceTwin>}
*/
getTwin () {
this.rid = Date.now()
// console.log(this.rid)
const readTwinMessage = new Paho.MQTT.Message('')
readTwinMessage.destinationName = DEVICE_TWIN_GET_TOPIC + this.rid
this.client.send(readTwinMessage)
return new Promise((resolve, reject) => {
/**
* @param {string} twin
*/
this._onReadTwinCompleted = (twin) => {
resolve(JSON.parse(twin))
}
})
}
/**
* @param {string} reportedProperties
*/
updateTwin (reportedProperties) {
this.rid = Date.now()
// console.log(this.rid)
const reportedTwinMessage = new Paho.MQTT.Message(reportedProperties)
reportedTwinMessage.destinationName = DEVICE_TWIN_PUBLISH_TOPIC + this.rid
this.client.send(reportedTwinMessage)
return new Promise((resolve, reject) => {
this._onUpdateTwinCompleted = () => {
resolve(204)
}
})
}
/**
* @param {string} payload
*/
sendTelemetry (payload) {
const telemetryMessage = new Paho.MQTT.Message(payload)
telemetryMessage.destinationName = `devices/${this.deviceId}/messages/events/`
this.client.send(telemetryMessage)
}
/**
* @param {{ (methodName: string, payload:string, rid:number): void}} directMethodCallback
*/
setDirectMethodCallback (directMethodCallback) {
this.directMethodCallback = directMethodCallback
}
/**
* @param {{ (methodName: string, payload:string, rid:number): void}} c2dCallback
*/
setCloudToDeviceCallback (c2dCallback) {
this.c2dCallback = c2dCallback
}
/**
* @param {string} methodName
* @param {string} payload
* @param {number} rid
* @param {number} status
*/
commandResponse (methodName, payload, rid, status) {
const response = new Paho.MQTT.Message(payload)
response.destinationName = DIRECT_METHOD_RESPONSE_TOPIC.replace('{status}', status.toString()) + rid.toString()
this.client.send(response)
}
/**
* @param {{ (desired: string): void}} desiredPropCallback
*/
setDesiredPropertyCallback (desiredPropCallback) {
this.desiredPropCallback = desiredPropCallback
}
}
export /**
* @param {{ [x: string]: any; }} propValues
* @param {number} ac
* @param {number} av
*/
const ackPayload = (propValues, ac, av) => {
const isObject = o => o === Object(o)
const payload = {}
Object.keys(propValues).filter(k => k !== '$version').forEach(k => {
const value = propValues[k]
if (isObject(value)) {
Object.keys(value).filter(k => k !== '__t').forEach(p => {
const desiredValue = value[p]
propValues[k][p] = { ac, av, value: desiredValue }
payload[k] = propValues[k]
})
} else {
payload[k] = { ac, av, value }
}
})
return payload
}

74
content/device.html Normal file
Просмотреть файл

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Azure IoT Central Device Example</title>
<!-- This is a development version of Vue.js! -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="lib/paho-mqtt.js"></script>
<link rel="stylesheet" href="s.css">
</head>
<body>
<div id="app">
<h1>Device in a browser connecting to IoT Central using MQTT</h1>
<div v-show="!connectionInfo.connected">
<div class="header">
Enter in the connection information for your IoT Central application and device
</div>
<div class="header">
Get the model used by this device <a href="simple_device_model.json" download>here</a>
then head to your Azure IoT Central applications by clicking <a href="https://apps.azureiotcentral.com/myapps" target="_blank">here</a>
</div>
<p>
<label for="input Id Scope">Scope Identity</label>
<input type="text" id="inputIdScope" size="55" v-model='connectionInfo.scopeId' />
</p>
<p>
<label for="inputDeviceId">Device Identity</label>
<input type="text" id="inputDeviceId" size="55"
v-model='connectionInfo.deviceId'
@change='updateDeviceKey()'>
<a v-show="viewDpsForm" href="#" @click="refreshDeviceId()">refresh</a>
</p>
<p>
<label for="inputDeviceKey">Device SAS Token</label>
<input type="text" id="inputDeviceKey" size="55"
:disabled='disableDeviceKey'
v-model='connectionInfo.deviceKey'> <span style="font-size: smaller;">(Auto calculated if Group SAS Token provided)</span>
</p>
<p>
<label for="inputMasterKey">Group SAS Token</label>
<input type="text" id="inputMasterKey" size="55"
@change='updateDeviceKey()'
v-model='connectionInfo.masterKey'>
</p>
<p>
<label for="inputModelId">Model Identity</label>
<input type="text" id="inputModelId" size="55" v-model='connectionInfo.modelId'>
</p>
<div class="right" v-show="!runningProvision">
<input type="button" value="Clear Form" @click="clearForm()">
<input type="button" id="btnDPS" value="Provision and Connect" @click="provision()">
</div>
</div>
<div v-show="connectionInfo.connected" class="connected">
Device <strong>{{ connectionInfo.deviceId }}</strong> connected to IoT Central
</div>
<div class="transport" v-show="connectionInfo.connected">
<button @click="startTelemetry()" v-show="!isTelemetryRunning">Start Sending Telemetry</button>
<button @click="stopTelemetry()" v-show="isTelemetryRunning">Stop Sending Telemetry</button>
<button @click="fetchTwin()">Fetch Full Twin</button>
<button @click="clearConsole()">Clear Console</button>
</div>
<div id="console" v-show="connectionInfo.connected" class="console">
<span v-html="statusConsole"></span>
</div>
</div>
<script src="device.js" type="module"></script>
</body>
</html>

271
content/device.js Normal file
Просмотреть файл

@ -0,0 +1,271 @@
import {
AzDpsClient,
createHmac
} from './AzDpsClient.js'
import {
AzIoTHubClient,
ackPayload
} from './AzIoTHubClient.js'
const createApp = () => {
let telemetryInterval
let reportedInterval
let consoleEntries = 0
const maxConsoleEntries = 200
/** @type {AzIoTHubClient} client */
let client
// @ts-ignore
const app = new Vue({
el: '#app',
data: {
saveConfig: true,
viewDpsForm: false,
disableDeviceKey: false,
runningProvision: false,
/** @type {ConnectionInfo} */
connectionInfo: {
scopeId: '',
hubName: '',
deviceId: 'SimpleDevice01',
deviceKey: '',
modelId: 'dtmi:simpleModel:simplesample;1',
status: 'Disconnected',
connected: false
},
/** @type {Array<CommandInfo>} */
isTelemetryRunning: false,
statusConsole:''
},
async created() {
/** @type { ConnectionInfo } connInfo */
const connInfo = JSON.parse(window.localStorage.getItem('connectionInfo') || '{}')
connInfo.deviceId = connInfo.deviceId
if (connInfo.scopeId) {
this.connectionInfo.scopeId = connInfo.scopeId
if (connInfo.masterKey) {
this.connectionInfo.masterKey = connInfo.masterKey
this.connectionInfo.deviceKey = await createHmac(this.connectionInfo.masterKey, this.connectionInfo.deviceId)
}
}
if (connInfo.hubName) {
this.connectionInfo.hubName = connInfo.hubName
this.connectionInfo.deviceId = connInfo.deviceId
this.connectionInfo.deviceKey = connInfo.deviceKey
this.connectionInfo.modelId = connInfo.modelId
}
},
methods: {
writeToConsole(content, color) {
if (consoleEntries >= maxConsoleEntries) {
this.statusConsole = this.statusConsole.substring(this.statusConsole.indexOf('</div>')+6)
consoleEntries--
}
this.statusConsole += '<div style="color: ' + color + ';">' + content + '</div>'
consoleEntries++
},
async provision() {
window.localStorage.setItem('connectionInfo',
JSON.stringify({
scopeId: this.connectionInfo.scopeId,
hubName: this.connectionInfo.hubName,
deviceId: this.connectionInfo.deviceId,
deviceKey: this.connectionInfo.deviceKey,
masterKey: this.connectionInfo.masterKey,
modelId: this.connectionInfo.modelId
}))
const dpsClient = new AzDpsClient(this.connectionInfo.scopeId, this.connectionInfo.deviceId, this.connectionInfo.deviceKey, this.connectionInfo.modelId)
this.runningProvision = true
const result = await dpsClient.registerDevice()
this.runningProvision = false
if (result.status === 'assigned') {
this.connectionInfo.hubName = result.registrationState.assignedHub
this.connect()
} else {
console.log(result)
this.connectionInfo.hubName = result.status
}
this.viewDpsForm = false
},
async refreshDeviceId() {
this.com
this.connectionInfo.deviceId = 'device' + Date.now()
await this.updateDeviceKey()
},
async desiredPropertyAck(patch, status, statusMsg) {
// acknowledge the desired property back to IoT Central
const patchJSON = JSON.parse(patch)
const keyName = Object.keys(patchJSON)[0]
let reported_payload = {}
reported_payload[keyName] = {"value": patchJSON[keyName], "ac":status,"ad":statusMsg,"av":patchJSON['$version']}
await client.updateTwin(JSON.stringify(reported_payload))
this.writeToConsole("Desired property patch acknowledged: " + "<pre>" + this.syntaxHighlight(patch) + "</pre>", "cyan")
},
async processDirectMethods(method, payload, rid) {
this.writeToConsole("Direct Method received: <pre>" + method + "(" + payload + ")</pre>", "green")
let response = 'unknown command'
let status = 404
if (method == 'sendTextMessage') {
response = {"messageRead": true}
status = 200
}
this.writeToConsole("Direct Method response: <pre>(status: " + status + ", payload: " + "<pre>" + this.syntaxHighlight(response) + "</pre>" + ")</pre>", "green")
await client.commandResponse(method, JSON.stringify(response), rid, status)
},
async processDesiredPropertyPatch(patch) {
this.writeToConsole("Desired property patch received: " + "<pre>" + this.syntaxHighlight(patch) + "</pre>", "cyan")
await this.desiredPropertyAck(patch, 200, "completed")
},
async processCloudToDeviceMessage(methodName, payload) {
this.writeToConsole("Cloud to Device message received: <pre>" + methodName + "(" + payload + ")</pre>", "red")
},
async connect() {
if (this.saveConfig) {
window.localStorage.setItem('connectionInfo',
JSON.stringify({
scopeId: this.connectionInfo.scopeId,
hubName: this.connectionInfo.hubName,
deviceId: this.connectionInfo.deviceId,
deviceKey: this.connectionInfo.deviceKey,
masterKey: this.connectionInfo.masterKey,
modelId: this.connectionInfo.modelId
}))
}
let host = this.connectionInfo.hubName
if (host.indexOf('.azure-devices.net') === -1) {
host += '.azure-devices.net'
}
client = new AzIoTHubClient(host,
this.connectionInfo.deviceId,
this.connectionInfo.deviceKey,
this.connectionInfo.modelId)
client.setDirectMethodCallback(this.processDirectMethods)
client.setDesiredPropertyCallback(this.processDesiredPropertyPatch)
client.setCloudToDeviceCallback(this.processCloudToDeviceMessage)
client.disconnectCallback = (err) => {
console.log(err)
this.connectionInfo.connected = false
this.connectionInfo.status = 'Disconnected'
}
await client.connect()
this.connectionInfo.status = 'Connected'
this.connectionInfo.connected = true
},
getRandomArbitrary(min, max, decimals) {
return +((Math.random() * (max - min) + min).toFixed(decimals));
},
startTelemetry() {
telemetryInterval = setInterval(() => {
const telemetryMessage = {"temp": this.getRandomArbitrary(32, 110, 2), "humidity": this.getRandomArbitrary(0, 100, 2)}
this.writeToConsole("Sending telemetry: " + "<pre>" + this.syntaxHighlight(telemetryMessage) + "</pre>", "#DEFFFF")
client.sendTelemetry(JSON.stringify(telemetryMessage))
}, this.getRandomArbitrary(4500, 5500, 0))
reportedInterval = setInterval(() => {
const reportedMessage = {"fanspeed": this.getRandomArbitrary(0, 1000, 0)}
this.writeToConsole("Sending reported property: " + "<pre>" + this.syntaxHighlight(reportedMessage) + "</pre>", "orange")
client.updateTwin(JSON.stringify(reportedMessage))
}, this.getRandomArbitrary(9500, 10500, 0))
this.isTelemetryRunning = true
},
stopTelemetry() {
clearInterval(telemetryInterval)
clearInterval(reportedInterval)
this.isTelemetryRunning = false
},
clearConsole() {
this.statusConsole = ''
consoleEntries = 0
},
clearForm() {
window.localStorage.removeItem('connectionInfo')
this.connectionInfo = {
scopeId: '',
hubName: '',
deviceId: 'SimpleDevice01',
deviceKey: '',
modelId: 'dtmi:simpleModel:simplesample;1',
status: 'Disconnected',
connected: false
}
},
syntaxHighlight(json) {
if (typeof json != 'string') {
json = JSON.stringify(json, undefined, 2)
}
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
var cls = 'number'
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key'
} else {
cls = 'string'
}
} else if (/true|false/.test(match)) {
cls = 'boolean'
} else if (/null/.test(match)) {
cls = 'null'
}
return '<span class="' + cls + '">' + match + '</span>'
})
},
async fetchTwin() {
if (client.connected) {
const twin = await client.getTwin()
this.writeToConsole('<div style="color: white;">Current full twin:<pre>' + this.syntaxHighlight(twin) + '</pre></div>', 'white')
}
},
async updateDeviceKey() {
this.disableDeviceKey = true
this.connectionInfo.deviceKey = await createHmac(this.connectionInfo.masterKey, this.connectionInfo.deviceId)
}
},
computed: {
connectionString() {
return `HostName=${this.connectionInfo.hubName}.azure-devices.net;DeviceId=${this.connectionInfo.deviceId};SharedAccessKey=${this.connectionInfo.deviceKey}`
}
},
})
return app
}
(() => {
createApp()
})()

2413
content/lib/paho-mqtt.js Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

56
content/s.css Normal file
Просмотреть файл

@ -0,0 +1,56 @@
body {
font-family: Arial, Helvetica, sans-serif
}
label {
display: inline-block;
width: 140px;
text-align: right
}
table,tr,td {
padding: 5px;
margin: 5px;
vertical-align: top;
}
.right {
margin-left: 330px;
}
.footer {
padding:20px;
width: 100%;
}
.connected {
background-color: lightgreen;
top:0px;
left:0px;
position: fixed;
width: 100%;
}
.console {
background-color: #1E1E1E;
display: flex;
flex-direction: column-reverse;
padding-top: 10px;
border-width: 2px;
height: 400px;
width: 100%;
border-color: black;
border-style: solid;
overflow-y: scroll;
overflow-x: hidden;
resize: vertical;
padding-left: 10px;
}
.transport {
padding-top: 50px;
padding-bottom: 15px;
}
.header {
display: inline-block;
margin-left: 50px;
width: 100%;
}
.string { color: #3B8334; }
.number { color: darkorange; }
.boolean { color: #6BCFFE; }
.null { color: whitesmoke; }
.key { color: #C586C0; }

Просмотреть файл

@ -0,0 +1,122 @@
[
{
"@id": "dtmi:simpleModel:simplesample;1",
"@type": "Interface",
"contents": [
{
"@id": "dtmi:simpleModel:simplesample:temp;1",
"@type": "Telemetry",
"comment": "This is a telemetry element",
"description": {
"en": "This is a telemetry element"
},
"displayName": {
"en": "Temperature"
},
"name": "temp",
"schema": "double",
"unit": "farad"
},
{
"@id": "dtmi:simpleModel:simplesample:Humidity;1",
"@type": "Telemetry",
"comment": "This is a telemetry element",
"description": {
"en": "This is a telemetry element"
},
"displayName": {
"en": "Humidity"
},
"name": "humidity",
"schema": "double",
"unit": "percent"
},
{
"@id": "dtmi:simpleModel:simplesample:fanspeed;1",
"@type": "Property",
"comment": "This is a reported property",
"description": {
"en": "This is a reported property"
},
"displayName": {
"en": "Fan Speed"
},
"name": "fanspeed",
"schema": "integer",
"unit": "revolutionPerMinute"
},
{
"@id": "dtmi:simpleModel:simplesample:setTemp;1",
"@type": "Property",
"comment": "This is a desired property",
"description": {
"en": "This is a desired property"
},
"displayName": {
"en": "Set Thermostat Temperature"
},
"name": "setTemp",
"schema": "double",
"unit": "farad",
"writable": true
},
{
"@id": "dtmi:simpleModel:simplesample:sendTextMessage;1",
"@type": "Command",
"commandType": "synchronous",
"comment": "This is a direct Method",
"description": {
"en": "This is a direct Method"
},
"displayName": {
"en": "Send Text Message"
},
"name": "sendTextMessage",
"request": {
"@type": "CommandPayload",
"displayName": {
"en": "Message"
},
"name": "message",
"schema": "string"
},
"response": {
"@type": "CommandPayload",
"displayName": {
"en": "Message Read"
},
"name": "messageRead",
"schema": "boolean"
}
},
{
"@id": "dtmi:simpleModel:simplesample:startVacuumCleaner;1",
"@type": "Command",
"durable": true,
"comment": "This is a cloud to device message",
"description": {
"en": "This is a cloud to device message"
},
"displayName": {
"en": "Start Vacuum cleaner"
},
"name": "startVacuumCleaner",
"request": {
"@type": "CommandPayload",
"displayName": {
"en": "Time to start"
},
"name": "timeToStart",
"schema": "time"
}
}
],
"displayName": {
"en": "simple_sample"
},
"@context": [
"dtmi:iotcentral:context;2",
"dtmi:dtdl:context;2"
]
}
]

16
package.json Normal file
Просмотреть файл

@ -0,0 +1,16 @@
{
"name": "webmqtt",
"version": "1.0.0",
"description": "Run an Azure IoT device in a browser using MQTT over websockets",
"main": "server.js",
"dependencies": {
"mime-types": "^2.1.28"
},
"devDependencies": {},
"scripts": {
"test": "node server.js",
"start": "node server.js"
},
"author": "Ian Hollier",
"license": "ISC"
}

24
server.js Normal file
Просмотреть файл

@ -0,0 +1,24 @@
var fs = require('fs'),
http = require('http');
var mime = require('mime-types')
http.createServer(function (req, res) {
if (fs.existsSync(__dirname + '/content/' + req.url)) {
fs.readFile(__dirname + '/content/' + req.url, function (err,data) {
if (err) {
res.writeHead(404);
res.end(JSON.stringify(err));
return;
}
var contentType = mime.lookup(req.url)
var headers = {};
res.setHeader('content-type', contentType);
res.writeHead(200);
res.end(data);
});
} else {
res.writeHead(404);
res.end();
}
}).listen(8080);
console.log('Listening at http//localhost:8080');