This commit is contained in:
Sri Harsha Kalavala 2021-10-26 20:42:35 -07:00
Родитель 6eb724e086
Коммит 31c107e2b3
15 изменённых файлов: 3769 добавлений и 368 удалений

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

@ -0,0 +1,222 @@
{
"env": {
"es6": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"sourceType": "module"
},
"plugins": [
"eslint-plugin-import",
"eslint-plugin-jsdoc",
"@typescript-eslint"
],
"rules": {
"no-shadow": "off",
"@typescript-eslint/no-shadow": "error",
"no-unused-expressions": "off",
"@typescript-eslint/no-unused-expressions": "error",
"@typescript-eslint/ban-ts-comment": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off",
"no-unneeded-ternary": "error",
"array-bracket-newline": [
"error",
"consistent"
],
"brace-style": [
"error",
"stroustrup"
],
"comma-dangle": "error",
"eqeqeq": [
"error",
"always"
],
"id-blacklist": [
"error",
"any",
"Number",
"number",
"String",
"string",
"Boolean",
"boolean",
"Undefined",
"undefined"
],
"max-classes-per-file": [
"off",
1
],
"max-len": [
"error",
{
"code": 200
}
],
"@typescript-eslint/quotes": [
"error",
"single",
{
"allowTemplateLiterals": true
}
],
"@typescript-eslint/semi": [
"error",
"always"
],
"@typescript-eslint/ban-types": [
"error",
{
"types": {
"Object": {
"message": "Avoid using the `Object` type. Did you mean `object`?"
},
"Function": {
"message": "Avoid using the `Function` type. Prefer a specific function type, like `() => void`."
},
"Boolean": {
"message": "Avoid using the `Boolean` type. Did you mean `boolean`?"
},
"Number": {
"message": "Avoid using the `Number` type. Did you mean `number`?"
},
"String": {
"message": "Avoid using the `String` type. Did you mean `string`?"
},
"Symbol": {
"message": "Avoid using the `Symbol` type. Did you mean `symbol`?"
},
"{}": false
}
}
],
"indent": "off",
"@typescript-eslint/indent": "error",
"@typescript-eslint/member-delimiter-style": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-explicit-any": [
"off",
{
"ignoreRestArgs": true
}
],
"@typescript-eslint/array-type": [
"error",
{
"default": "array"
}
],
"@typescript-eslint/consistent-type-assertions": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/explicit-member-accessibility": [
"error",
{
"accessibility": "explicit",
"overrides": {
"constructors": "no-public"
}
}
],
"@typescript-eslint/member-ordering": "off",
"@typescript-eslint/naming-convention": [
"error",
{
"selector": [
"enumMember",
"enum"
],
"format": null
}
],
"@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-this-alias": "error",
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unified-signatures": "error",
"@typescript-eslint/explicit-module-boundary-types": [
"error",
{
"allowArgumentsExplicitlyTypedAsAny": true
}
],
"object-shorthand": "error",
"arrow-parens": [
"off",
"always"
],
"constructor-super": "error",
"curly": "error",
"eol-last": "error",
"guard-for-in": "error",
"import/no-extraneous-dependencies": "error",
"import/no-internal-modules": "error",
"import/order": "off",
"jsdoc/check-alignment": "error",
"jsdoc/check-indentation": "error",
"jsdoc/newline-after-description": "error",
"new-parens": "error",
"no-bitwise": "error",
"no-caller": "error",
"no-cond-assign": "error",
"no-console": "error",
"no-debugger": "error",
"no-duplicate-case": "error",
"no-duplicate-imports": "error",
"no-empty": "error",
"no-eval": "error",
"no-extra-bind": "error",
"no-fallthrough": "off",
"no-invalid-this": "off",
"no-irregular-whitespace": "error",
"no-multiple-empty-lines": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-redeclare": "error",
"no-return-await": "error",
"no-sequences": "error",
"no-sparse-arrays": "error",
"no-template-curly-in-string": "error",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef-init": "error",
"no-underscore-dangle": "error",
"no-unsafe-finally": "error",
"no-var": "error",
"one-var": [
"error",
"never"
],
"prefer-const": "error",
"prefer-object-spread": "error",
"prefer-template": "error",
"quote-props": [
"error",
"as-needed"
],
"radix": "error",
"space-before-function-paren": [
"error",
{
"named": "never",
"asyncArrow": "always"
}
],
"spaced-comment": [
"error",
"always"
]
}
}

386
.gitignore поставляемый
Просмотреть файл

@ -1,350 +1,84 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
# Logs
logs
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Chutzpah Test files
_Chutzpah*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Coverage directory used by tools like istanbul
coverage
# Visual Studio Trace Files
*.e2e
# nyc test coverage
.nyc_output
# TFS 2012 Local Workspace
$tf/
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Guidance Automation Toolkit
*.gpState
# Bower dependency directory (https://bower.io/)
bower_components
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# node-waf configuration
.lock-wscript
# TeamCity is a build add-in
_TeamCity*
# Compiled binary addons (http://nodejs.org/api/addons.html)
build
build/Release
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# 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
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# 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
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Dependency directories
node_modules/
jspm_packages/
# Visual Studio 6 build log
*.plg
# TSD Typings
typings/
# Visual Studio 6 workspace options file
*.opt
# npm related
.npm
.npmrc*
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Optional eslint cache
.eslintcache
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Optional REPL history
.node_repl_history
# Paket dependency manager
.paket/paket.exe
paket-files/
# Output of 'npm pack'
*.tgz
# FAKE - F# Make
.fake/
# Yarn Integrity file
.yarn-integrity
# CodeRush personal settings
.cr/personal
# dotenv environment variables file
.env
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Build output
dist
server_dist
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Ignore Mac DS_Store files
.DS_Store
# Tabs Studio
*.tss
debug.json
# Telerik's JustMock configuration file
*.jmconfig
*.swp
**/bundle-*.js
**/bundle-*.js.map
**/common.js
**/common.js.map
coverage.html
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# node profiler
profile**.txt
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# project
client
configs/**
storage*/**

26
.vscode/launch.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,26 @@
{
// Use IntelliSense to learn about possible Node.js debug attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "IoT Central File Upload Device",
"console": "integratedTerminal",
"program": "${workspaceFolder}/index.ts",
"protocol": "inspector",
"smartStep": true,
"showAsyncStacks": true,
"sourceMaps": true,
"preLaunchTask": "ts-watch",
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
],
"skipFiles": [
"<node_internals>/**"
]
}
]
}

43
.vscode/tasks.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,43 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "typescript",
"label": "build",
"tsconfig": "tsconfig.json",
"group": "build",
"problemMatcher": [
"$tsc"
]
},
{
"type": "typescript",
"label": "ts-watch",
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"panel": "new",
"reveal": "never"
},
"tsconfig": "tsconfig.json",
"option": "watch",
"problemMatcher": [
"$tsc-watch"
]
},
{
"type": "npm",
"script": "eslint",
"problemMatcher": [
"$eslint-stylish"
],
"label": "npm: lint",
"detail": "eslint -c .eslintrc.json --ext .ts ."
}
]
}

123
README.md
Просмотреть файл

@ -1,57 +1,96 @@
# Project Name
---
page_type: sample
name: "IoT Central file upload device sample"
description: "Sample code that demonstrates how a device can use IoT Central to upload a file to cloud storage."
languages:
- typescript
products:
- azure-iot-central
urlFragment: iotc-file-upload-device
---
(short, 1-3 sentenced, description of the project)
# IoT Central file upload device sample
This sample demonstrates how to use the file upload feature of IoT Hub from within an IoT Central app. For a full description of the IoT Central File Upload feature see the [documentation online](https://apps.azureiotcentral.com).
## Features
## Prerequisites
* [Node.js](https://nodejs.org/en/download/)
* [Visual Studio Code](https://code.visualstudio.com/Download) with [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) extension installed, or a command line will work.
* [Azure Blob Storage account](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal)
* [IoT Central Application](https://docs.microsoft.com/en-us/azure/iot-central/core/quick-deploy-iot-central)
This project framework provides the following features:
## Clone the repository
If you haven't already cloned the repository, use the following command to clone it to a suitable location on your local machine and install the dependent packages:
```
git clone https://github.com/iot-for-all/iotc-file-upload-device
cd iotc-file-upload-device
npm i
```
Open the cloned repository with VS Code.
* Feature 1
* Feature 2
* ...
## Create an IoT Central application
Follow the instructions to [create an IoT Central application](https://docs.microsoft.com/en-us/azure/iot-central/core/quick-deploy-iot-central) and associate the application with your Azure Storage account. Create a new device template using the included file upload device sample template.
* From the left pane in your IoT Central Application select "Device templtes"
* At the top of the device templates view select the "+ New" option to create a new template
* Select the "IoT Device" option
* Select the "Next: Customize" button
* Name your template (e.g. File Upload Device Sample)
* Select the "Next: Review" button
* Select the "Create" button
* Select the "Import capability model" option. The device template is included in the project here */setup/FileUploadDeviceDcm.json*.
## Getting Started
After importing the device template you need to create a view on the telemetry and properties the device will send and receive. See the documentation to [Create and manage dashboards in IoT Central](https://docs.microsoft.com/en-us/azure/iot-central/core/howto-create-personal-dashboards). Create a dashboard view for *Visualizing the device*
* In your new device template select the "Views" option
* Select the "Visualize the device" option
* For the "View name" enter "Dashboard"
* Select the `System Heartbeat` telemetry and then the "Add tile" button at the bottom. This telemetry is a heartbeat signal that shows that your device is alive and running. This signal will be charted on a graph.
* Select the `Upload Image` telemtry and then the "Add tile" button at the bottom. This telemetry is an event that will indicated when a file upload has occurred.
* Select the "Save" option for this view.
### Prerequisites
Create another view for *Editing device and cloud data*
* Select the "Views" option
* Select the "Editing device and cloud data" option
* For the "Form name" enter "Upload options"
* Select the `Filename Suffix` property and then the "Add section" button at the bottom. This property is the suffix to use on the uploaded file name.
* Select the "Save" option for this view.
(ideally very short, if any)
Now publish the template by selecting the "Publish" option at the top of the screen. You are now ready to create a device and run the sample code.
- OS
- Library version
- ...
## Create an IoT Device
In your IoT Central application create a new device based on your new template and get the connection information.
* From the left pane in your IoT Central Application select "Devices"
* In the device list view select the "+ New" option
* In the Create a new device screen specify your template, a Device name, and a Device ID
* Select the "Create" button
* In the device list select your new device
* In the device view select the "Connect" option at the top of the screen
* Copy the values for "ID scope", "Device ID", and "Primary key". You will use these values in the device sample code.
### Installation
To learn more, see [Add a device in IoT Central application.](https://docs.microsoft.com/en-us/azure/iot-central/core/howto-manage-devices#add-a-device)
(ideally very short)
## Run the sample code
Create a ".env" file at the root of your project and add the values you copied above. The file should look like the sample below with your own values. NOTE: the modelId is copied from the */setup/FileUploadDeviceDcm.json* file.
```
scopeId=<YOUR_SCOPE_ID>
deviceId=<YOUR_DEVICE_ID>
deviceKey=<YOUR_PRIMARY_KEY>
modelId=dtmi:IoTCentral:IotCentralFileUploadDevice;1
```
- npm install [package name]
- mvn install
- ...
Now you are ready to run the sample. Press F5 to run/debug the sample. In your terminal window you should see that the device is registered and is connected to IoT Central:
```
Starting IoT Central device...
> Machine: ...
Starting device registration...
DPS registration succeeded
Connecting the device...
IoT Central successfully connected device: file-upload-device
```
### Quickstart
(Add steps to get up and running quickly)
## Upload a file
The sample project comes with a sample file named datafile.json. This will be the file that is uploaded when you use the Upload File command in your IoT Central application. To test this open your application and select the device you created. Select the Command tab and you should see a button named "Run". When you select that button the IoT Central app will call a direct method on your device to upload the file. You can see this direct method in the sample code in the */device.ts* file. The method is named *uploadFileCommand*.
1. git clone [repository clone url]
2. cd [respository name]
3. ...
The *uploadFileCommand* calls a method named *uploadFile*. This method gets the device setting for the filename suffix to use. By default the built-in file upload feature automatically creates a folder with the same name as your deviceId. This device setting is purely to demonstrate how to communicate property changes to your device from IoT Central. After getting the file name and some information about file to upload the code calls the built-in IoT Hub method `deviceClient.uploadToBlob` on the device client interface. This uses the IoT Hub file upload feature to stream the file to the associated Azure Blob storage.
> While this sample uses the Command capability in IoT Central to call a direct method on your device in order to upload a file it it not required. For example you could have a video analytics device that automatically uploads a sample image when it detects an anomaly.
## Demo
A demo app is included to show how to use the project.
To run the demo, follow these steps:
(Add steps to start up the demo)
1.
2.
3.
## Resources
(Any additional resources or related projects)
- Link to supporting information
- Link to similar sample
- ...
This sample demonstrates the simplest way to upload generic files to an Azure Blob Storage account. The features of the IoT Hub take care of creating the SAS token for the connection. If you need more fine grained control of the features of Azure Blob Storage from your IoT Device then you will need to use the Azure Storage SDK directly in your Azure IoT Device project. To learn more about the Azure Blob Storage SDK see the [Azure Blob storage documentation](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction).

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

@ -0,0 +1,3 @@
{
"test": "field1"
}

293
device.ts Normal file
Просмотреть файл

@ -0,0 +1,293 @@
import { Mqtt } from 'azure-iot-device-mqtt';
import { SymmetricKeySecurityClient } from 'azure-iot-security-symmetric-key';
import { ProvisioningDeviceClient } from 'azure-iot-provisioning-device';
import { Mqtt as ProvisioningTransport } from 'azure-iot-provisioning-device-mqtt';
import {
Client as IoTDeviceClient,
Twin,
Message as IoTMessage,
DeviceMethodRequest,
DeviceMethodResponse
} from 'azure-iot-device';
import {
extname as pathExtName,
basename as pathBaseName
} from 'path';
import {
stat as fsStat,
createReadStream as fsCreateReadStream
} from 'fs';
import * as moment from 'moment';
import { log } from 'util';
const dpsProvisioningHost = 'global.azure-devices-provisioning.net';
const CommandUploadFile = 'COMMAND_UPLOAD_FILE';
const TelemetrySystemHeartbeat = 'TELEMETRY_SYSTEM_HEARTBEAT';
const EventUploadFile = 'EVENT_UPLOAD_FILE';
const SettingFilenameSuffix = 'SETTING_FILENAME_SUFFIX';
const CommandResponseStatusCode = 'COMMANDRESPONSE_STATUSCODE';
const CommandResponseMessage = 'COMMANDRESPONSE_MESSAGE';
const CommandResponseData = 'COMMANDRESPONSE_DATA';
interface IFileUploadResponse {
statusCode: number;
message: string;
filename: string;
}
interface IDeviceSettings {
[SettingFilenameSuffix]: string;
}
export class IoTCentralDevice {
private log: (message: string) => void;
private scopeId: string;
private deviceId: string;
private deviceKey: string;
private modelId: string;
private deviceClient: IoTDeviceClient;
private deviceTwin: Twin;
private deviceSettings: IDeviceSettings;
constructor(logFunc: (message: string) => void, scopeId: string, deviceId: string, deviceKey: string, modelId: string) {
this.log = logFunc;
this.scopeId = scopeId;
this.deviceId = deviceId;
this.deviceKey = deviceKey;
this.modelId = modelId;
this.deviceSettings = {
[SettingFilenameSuffix]: moment.utc().format('YYYYMMDD-HHmmss')
};
}
public async provisionDeviceClient(): Promise<string> {
let connectionString = '';
try {
const provisioningSecurityClient = new SymmetricKeySecurityClient(this.deviceId, this.deviceKey);
const provisioningClient = ProvisioningDeviceClient.create(
dpsProvisioningHost,
this.scopeId,
new ProvisioningTransport(),
provisioningSecurityClient
);
const provisioningPayload = {
iotcModelId: this.modelId
};
provisioningClient.setProvisioningPayload(provisioningPayload);
connectionString = await new Promise<string>((resolve, reject) => {
provisioningClient.register((dpsError, dpsResult) => {
if (dpsError) {
return reject(dpsError);
}
this.log('DPS registration succeeded');
return resolve(`HostName=${dpsResult.assignedHub};DeviceId=${dpsResult.deviceId};SharedAccessKey=${this.deviceKey}`);
});
});
}
catch (ex) {
this.log(`Failed to instantiate client interface from configuration: ${ex.message}`);
}
return connectionString;
}
public async connectDeviceClient(connectionString: string): Promise<void> {
try {
this.deviceClient = await IoTDeviceClient.fromConnectionString(connectionString, Mqtt);
if (!this.deviceClient) {
this.log(`Failed to connect device client interface from connection string - device: ${this.deviceId}`);
return;
}
setInterval(async () => {
await this.getHealth();
}, 1000 * 30);
await this.deviceClient.open();
this.deviceTwin = await this.deviceClient.getTwin();
this.deviceTwin.on('properties.desired', this.onHandleDeviceProperties.bind(this));
this.deviceClient.on('error', this.onDeviceClientError.bind(this));
this.deviceClient.onDeviceMethod(CommandUploadFile, this.uploadFileCommand.bind(this));
this.log(`IoT Central successfully connected device: ${this.deviceId}`);
}
catch (ex) {
this.log(`IoT Central connection error: ${ex.message}`);
}
}
private async getHealth(): Promise<void> {
await this.sendMeasurement({
[TelemetrySystemHeartbeat]: 1
});
}
private async onHandleDeviceProperties(desiredChangedSettings: any) {
try {
const patchedProperties = {};
for (const setting in desiredChangedSettings) {
if (!Object.prototype.hasOwnProperty.call(desiredChangedSettings, setting)) {
continue;
}
if (setting === '$version') {
continue;
}
const value = desiredChangedSettings[setting];
switch (setting) {
case SettingFilenameSuffix:
this.log(`Updating setting: ${setting} with value: ${value}`);
// NOTE: validation should be in place for legal folder names
patchedProperties[setting] = this.deviceSettings[setting] = value || moment.utc().format('YYYYMMDD-HHmmss');
break;
default:
this.log(`Received desired property change for unknown setting '${setting}'`);
break;
}
}
if (Object.keys(patchedProperties).length) {
await this.updateDeviceProperties(patchedProperties);
}
}
catch (ex) {
this.log(`Exception while handling desired properties: ${ex.message}`);
}
}
private onDeviceClientError(error: Error) {
this.log(`Device client connection error: ${error.message}`);
}
private async sendMeasurement(data: any): Promise<void> {
if (!data || !this.deviceClient) {
return;
}
try {
this.log(`Sending telemetry: ${JSON.stringify(data, null, 4)}`);
const iotcMessage = new IoTMessage(JSON.stringify(data));
await this.deviceClient.sendEvent(iotcMessage);
}
catch (ex) {
this.log(`sendMeasurement: ${ex.message}`);
}
}
private async updateDeviceProperties(properties: any): Promise<void> {
if (!properties || !this.deviceTwin) {
return;
}
this.log(`Updating twin properties: ${JSON.stringify(properties, null, 4)}`);
try {
await new Promise((resolve, reject) => {
this.deviceTwin.properties.reported.update(properties, (error) => {
if (error) {
return reject(error);
}
return resolve('');
});
});
}
catch (ex) {
this.log(`Error updating device properties: ${ex.message}`);
}
}
private async getFileStats(filePath: string): Promise<any> {
let fileStats = {};
try {
fileStats = await new Promise((resolve, reject) => {
fsStat(filePath, (err, stats) => {
if (err) {
return reject(err);
}
return resolve(stats);
});
});
}
catch (ex) {
log(`An error occurred while getting file stats: ${ex.message}`);
}
return fileStats;
}
private async uploadFile(filePath: string): Promise<IFileUploadResponse> {
const result: IFileUploadResponse = {
statusCode: 202,
message: '',
filename: ''
};
try {
const blobNameSuffix = this.deviceSettings[SettingFilenameSuffix];
const fileExtName = pathExtName(filePath);
const fileBaseName = pathBaseName(filePath, fileExtName);
const fileStats = await this.getFileStats(filePath);
const blobFilename = `${fileBaseName}-${blobNameSuffix}${fileExtName}`;
const readableStream = fsCreateReadStream(filePath);
this.log(`uploadContent - data length: ${fileStats.size}, blob filename: ${blobFilename}`);
await this.deviceClient.uploadToBlob(blobFilename, readableStream, fileStats.size);
await this.sendMeasurement({
[EventUploadFile]: `${blobFilename}`
});
result.message = `deviceId: '${this.deviceId}' uploaded a file named '${blobFilename}' to the Azure storage container`;
this.log(result.message);
result.filename = blobFilename;
}
catch (ex) {
result.message = `Error during deviceClient.uploadToBlob: ${ex.message}`;
this.log(result.message);
}
return result;
}
// @ts-ignore (commandRequest)
private async uploadFileCommand(commandRequest: DeviceMethodRequest, commandResponse: DeviceMethodResponse) {
this.log('Received upload file command');
const fileUploadResult = await this.uploadFile('./datafile.json');
await commandResponse.send(200);
await this.updateDeviceProperties({
[CommandUploadFile]: {
value: {
[CommandResponseStatusCode]: 202,
[CommandResponseMessage]: fileUploadResult.message,
[CommandResponseData]: ''
}
}
});
}
}

4
env.ts Normal file
Просмотреть файл

@ -0,0 +1,4 @@
import { resolve } from 'path';
import { config } from 'dotenv';
config({ path: resolve(__dirname, '../.env') });

53
index.ts Normal file
Просмотреть файл

@ -0,0 +1,53 @@
import './env';
import {
type as osType,
cpus as osCpus,
freemem as osFreeMem,
totalmem as osTotalMem
} from 'os';
import { IoTCentralDevice } from './device';
function log(message: string) {
// eslint-disable-next-line no-console
console.log(message);
}
async function start() {
try {
log('🚀 Starting IoT Central device...');
log(` > Machine: ${osType()}, ${osCpus().length} core, `
+ `freemem=${(osFreeMem() / 1024 / 1024).toFixed(0)}mb, totalmem=${(osTotalMem() / 1024 / 1024).toFixed(0)}mb`);
const {
scopeId,
deviceId,
deviceKey,
modelId
} = process.env;
if (!scopeId || !deviceId || !deviceKey || !modelId) {
log('Error - missing required environment variables scopeId, deviceId, deviceKey, modelId');
return;
}
const iotDevice = new IoTCentralDevice(log, scopeId, deviceId, deviceKey, modelId);
log('Starting device registration...');
const connectionString = await iotDevice.provisionDeviceClient();
if (connectionString) {
log('Connecting the device...');
await iotDevice.connectDeviceClient(connectionString);
}
else {
log(' Failed to obtain connection string for device.');
}
}
catch (error) {
log(`👹 Error starting process: ${error.message}`);
}
}
void (async () => {
await start();
})().catch();

2825
package-lock.json сгенерированный Normal file

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

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

@ -0,0 +1,36 @@
{
"name": "iotc-file-upload-device",
"version": "1.0.1",
"description": "IoT Central File Upload Device Sample",
"main": "index.js",
"scripts": {
"eslint": "eslint -c .eslintrc.json --ext .ts .",
"build": "node ./node_modules/typescript/bin/tsc -p ."
},
"author": "sseiber",
"license": "MIT",
"repository": {
"type": "git",
"url": "git@github.com:sseiber/iotc-file-upload-device.git"
},
"dependencies": {
"azure-iot-device": "1.17.3",
"azure-iot-device-mqtt": "1.15.3",
"azure-iot-provisioning-device": "^1.8.7",
"azure-iot-provisioning-device-mqtt": "^1.7.7",
"azure-iot-security-symmetric-key": "^1.7.7",
"dotenv": "^8.2.0",
"lodash.get": "^4.4.2",
"lodash.set": "^4.3.2",
"moment": "^2.29.1"
},
"devDependencies": {
"@types/node": "^14.14.27",
"@typescript-eslint/eslint-plugin": "^4.12.0",
"@typescript-eslint/parser": "^4.12.0",
"eslint": "^7.17.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsdoc": "^31.6.1",
"typescript": "^4.1.5"
}
}

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

@ -0,0 +1,95 @@
{
"@id": "dtmi:IoTCentral:IotCentralFileUploadDevice;1",
"@type": "Interface",
"contents": [
{
"@id": "dtmi:IoTCentral:IotCentralFileUploadDevice:systemHeartBeat;1",
"@type": "Telemetry",
"displayName": {
"en": "System Heartbeat"
},
"name": "TELEMETRY_SYSTEM_HEARTBEAT",
"schema": "integer"
},
{
"@id": "dtmi:IoTCentral:IotCentralFileUploadDevice:eventUploadFile;1",
"@type": [
"Telemetry",
"SemanticType/Event"
],
"displayName": {
"en": "Upload Image"
},
"name": "EVENT_UPLOAD_FILE",
"schema": "string"
},
{
"@id": "dtmi:IoTCentral:IotCentralFileUploadDevice:settingFilenameSuffix;1",
"@type": "Property",
"displayName": {
"en": "Filename Suffix"
},
"name": "SETTING_FILENAME_SUFFIX",
"writable": true,
"schema": "string"
},
{
"@id": "dtmi:IoTCentral:IotCentralFileUploadDevice:command;1",
"@type": "Command",
"displayName": {
"en": "Upload File"
},
"name": "COMMAND_UPLOAD_FILE",
"response": {
"@type": "SchemaField",
"displayName": {
"en": "Command Response"
},
"name": "CommandResponse",
"schema": "dtmi:IoTCentral:IotCentralFileUploadDevice:command:responseSchema;1"
}
}
],
"schemas": [
{
"@id": "dtmi:IoTCentral:IotCentralFileUploadDevice:command:responseSchema;1",
"@type": "Object",
"displayName": {
"en": "Object"
},
"fields": [
{
"@type": "SchemaField",
"displayName": {
"en": "Status Code"
},
"name": "COMMANDRESPONSE_STATUSCODE",
"schema": "long"
},
{
"@type": "SchemaField",
"displayName": {
"en": "Message"
},
"name": "COMMANDRESPONSE_MESSAGE",
"schema": "string"
},
{
"@type": "SchemaField",
"displayName": {
"en": "Data"
},
"name": "COMMANDRESPONSE_DATA",
"schema": "string"
}
]
}
],
"displayName": {
"en": "IoT Central File Upload Device"
},
"@context": [
"dtmi:iotcentral:context;2",
"dtmi:dtdl:context;2"
]
}

Двоичные данные
setup/iotclogo.png Normal file

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

После

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

2
setup/local.json Normal file
Просмотреть файл

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

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

@ -0,0 +1,26 @@
{
"compilerOptions": {
"outDir": "./dist",
"module": "CommonJS",
"moduleResolution": "node",
"target": "ES6",
"sourceMap": true,
"jsx": "react",
"skipLibCheck": true,
"noImplicitAny": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"typeRoots": [
"node_modules/@types"
],
"lib": [
"ES6"
]
},
"compileOnSave": true,
"include": [
"*.ts"
]
}