From 6a18b50fabf01fd9d8642298a132d1c84035ec5e Mon Sep 17 00:00:00 2001 From: mbthiery Date: Sat, 8 Jun 2024 13:22:02 -0400 Subject: [PATCH 01/16] Create Edu device + display events --- .gitignore | 1 + .../heltec/cubecell-dev-board/EduDevice.tsx | 330 ++++++++++++++++++ .../cubecell-dev-board/platformio-test.mdx | 38 ++ docusaurus.config.js | 5 + package.json | 4 + yarn.lock | 14 + 6 files changed, 392 insertions(+) create mode 100644 docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx create mode 100644 docs/network-iot/devices/development/heltec/cubecell-dev-board/platformio-test.mdx diff --git a/.gitignore b/.gitignore index 136293a84..7fb431e21 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # Misc .DS_Store +.env .env.local .env.development.local .env.test.local diff --git a/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx b/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx new file mode 100644 index 000000000..1881e3ab0 --- /dev/null +++ b/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx @@ -0,0 +1,330 @@ +import { ApplicationServiceClient } from '@chirpstack/chirpstack-api-grpc-web/api/application_grpc_web_pb' +import { ListApplicationsRequest } from '@chirpstack/chirpstack-api-grpc-web/api/application_pb' +import { DeviceServiceClient } from '@chirpstack/chirpstack-api-grpc-web/api/device_grpc_web_pb' +import { + CreateDeviceKeysRequest, + CreateDeviceRequest, + Device, + DeviceKeys, +} from '@chirpstack/chirpstack-api-grpc-web/api/device_pb' +import { DeviceProfileServiceClient } from '@chirpstack/chirpstack-api-grpc-web/api/device_profile_grpc_web_pb' +import { ListDeviceProfilesRequest } from '@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb' +import { InternalServiceClient } from '@chirpstack/chirpstack-api-grpc-web/api/internal_grpc_web_pb' +import { + LogItem, + LoginRequest, + StreamDeviceEventsRequest, +} from '@chirpstack/chirpstack-api-grpc-web/api/internal_pb' +import { Region } from '@chirpstack/chirpstack-api-grpc-web/common/common_pb' +import useDocusaurusContext from '@docusaurus/useDocusaurusContext' +import { EventEmitter } from 'events' +import google_protobuf_empty_pb from 'google-protobuf/google/protobuf/empty_pb' +import * as grpcWeb from 'grpc-web' +import React, { useCallback, useEffect, useState } from 'react' + +const deviceEmitter = new EventEmitter() + +const generateRandomKey = () => { + let cryptoObj = window.crypto || window.Crypto + let b = new Uint8Array(8) + cryptoObj.getRandomValues(b) + let hexString = '' + b.forEach((num, index) => { + let hexVal = num.toString(16) + if (hexVal.length === 1) hexVal = '0' + hexVal + hexString += hexVal + }) + + return hexString +} + +const getHexArray = (key: string = '') => { + return key + .match(/[A-Fa-f0-9]{2}/g)! + .join(', ') + .toUpperCase() + .replace(/[A-Fa-f0-9]{2}/g, '0x$&') +} + +const DeviceInfoRow = ({ label, value }) => { + if (!value) return

{label}: Pending - Hex Value: Pending

+ return ( +

+ {label}: {value} - {getHexArray(value)} +

+ ) +} + +const useLogin = () => { + const { + siteConfig: { customFields }, + } = useDocusaurusContext() + const interalServiceClient = new InternalServiceClient(customFields.EDU_API_URL) + + return () => + new Promise((resolve, reject) => { + let req = new LoginRequest() + req.setEmail(customFields.EDU_EMAIL) + req.setPassword(customFields.EDU_PW) + interalServiceClient.login(req, {}, (error, resp) => { + if (error !== null) { + return reject(error) + } + return resolve(resp.getJwt()) + }) + }) +} + +const getRequestMeta = (jwt: string) => { + return { authorization: `Bearer ${jwt}` } +} + +export const CreateDevice = () => { + const { + siteConfig: { customFields }, + } = useDocusaurusContext() + const login = useLogin() + const [deviceInfo, setDeviceInfo] = useState({ devEui: '', joinEui: '', networkKey: '' }) + + const createDevice = () => { + let jwt = '' + let tenants = [] + let applicationId = '' + let deviceProfileId = '' + const interalServiceClient = new InternalServiceClient(customFields.EDU_API_URL) + + const getTenantId = () => { + return tenants[0].getTenantId() + } + const loginWrapper = () => + login().then((loginJwt) => { + jwt = loginJwt as string + }) + + const getTenants = () => { + return new Promise((resolve, reject) => { + interalServiceClient.profile( + new google_protobuf_empty_pb.Empty(), + getRequestMeta(jwt), + (error, resp) => { + if (error !== null) { + reject(error) + } + tenants = resp.getTenantsList() + resolve(tenants) + }, + ) + }) + } + + const getDeviceApplications = () => + new Promise((resolve, reject) => { + const client = new ApplicationServiceClient(customFields.EDU_API_URL) + let req = new ListApplicationsRequest() + req.setTenantId(getTenantId()) + req.setLimit(10) + req.setOffset(0) + + client.list(req, getRequestMeta(jwt), (error, resp) => { + if (error !== null) { + return reject(error) + } + const deviceApplications = resp.toObject() + const targetApplication = deviceApplications.resultList.find( + (application) => application.name === 'Mapper 2', + ) + if (!targetApplication) return reject('Target application not found') + applicationId = targetApplication.id + return resolve(applicationId) + }) + }) + + const getDeviceProfilesPage = () => + new Promise((resolve, reject) => { + const client = new DeviceProfileServiceClient(customFields.EDU_API_URL) + + let req = new ListDeviceProfilesRequest() + req.setTenantId(getTenantId()) + req.setLimit(10) + req.setOffset(0) + + client.list(req, getRequestMeta(jwt), (error, resp) => { + if (error !== null) { + return reject(error) + } + + const deviceProfiles = resp.toObject() + const targetProfile = deviceProfiles.resultList.find( + (deviceProfile) => deviceProfile.region === Region.US915, + ) + if (!targetProfile) return reject('Target profile not found') + + deviceProfileId = targetProfile.id + return resolve(deviceProfileId) + }) + }) + + const createDevice = () => + new Promise((resolve, reject) => { + const client = new DeviceServiceClient(customFields.EDU_API_URL) + let device = new Device() + + const devEui = generateRandomKey() + const joinEui = generateRandomKey() + + device.setApplicationId(applicationId) + device.setName(devEui) + device.setDescription('Randomly generated device') + device.setDevEui(devEui) + device.setDeviceProfileId(deviceProfileId) + device.setIsDisabled(false) + device.setSkipFcntCheck(false) + device.setJoinEui(joinEui) + + let req = new CreateDeviceRequest() + req.setDevice(device) + + client.create(req, getRequestMeta(jwt), (error) => { + if (error !== null) { + return reject(error) + } + + let req = new CreateDeviceKeysRequest() + let deviceKeys = new DeviceKeys() + const networkKey = generateRandomKey() + generateRandomKey() + deviceKeys.setDevEui(devEui) + deviceKeys.setNwkKey(networkKey) + req.setDeviceKeys(deviceKeys) + + client.createKeys(req, getRequestMeta(jwt), (error) => { + if (error !== null) { + return reject(error) + } + + setDeviceInfo({ + devEui, + joinEui, + networkKey, + }) + deviceEmitter.emit('deviceInfo', { devEui: devEui, jwt }) + + return resolve(device) + }) + }) + }) + + return loginWrapper() + .then(getTenants) + .then(() => Promise.all([getDeviceApplications(), getDeviceProfilesPage()])) + .then(createDevice) + } + + return ( + <> + +
+

Device Info

+ + + +
+ + ) +} + +export function DeviceEventLogger() { + const { + siteConfig: { customFields }, + } = useDocusaurusContext() + const login = useLogin() + const [jwt, setJwt] = useState('') + const [devEui, setDevEui] = useState('') + const [events, setEvents] = useState([]) + + useEffect(() => { + const setStreamInfo = ({ devEui, jwt }) => { + setJwt(jwt) + setDevEui(devEui) + } + deviceEmitter.on('deviceInfo', setStreamInfo) + return deviceEmitter.removeListener('deviceInfo', setStreamInfo) + }, []) + + const onMessage = useCallback((logItem: LogItem) => { + setEvents((e) => { + if ( + e.length === 0 || + parseInt(logItem.getId().replace('-', '')) > parseInt(e[0].id.replace('-', '')) + ) { + return [logItem.toObject(), ...e] + } + return e + }) + }, []) + + // get jwt when manual devEui entry + useEffect(() => { + if (devEui.length === 16 && !jwt) { + login().then(setJwt) + } + }, [jwt, devEui]) + + useEffect(() => { + if (devEui.length === 16 && !!jwt) { + const streamDeviceEvents = (callbackFunc: (resp: LogItem) => void): (() => void) => { + let req = new StreamDeviceEventsRequest() + req.setDevEui(devEui) + var stream: grpcWeb.ClientReadableStream | undefined = undefined + + let setup = () => { + const client = new InternalServiceClient(customFields.EDU_API_URL) + stream = client.streamDeviceEvents(req, getRequestMeta(jwt)) + + stream = stream.on('data', (resp) => { + callbackFunc(resp) + }) + + stream = stream.on('end', function () { + setTimeout(setup, 1000) + }) + } + + setup() + + return () => { + if (stream) { + stream.cancel() + } + } + } + + let cancelFunc = streamDeviceEvents(onMessage) + + return () => { + cancelFunc() + } + } + }, [jwt, devEui, onMessage]) + + return ( + <> +

Device Event Logger

+

jwt: {jwt}

+ setDevEui(event.target.value)} + /> +
+

EVENTS BELOW HERE vvv

+ {events.map((event) => ( +

+ {`${new Date(event.time?.seconds! * 1000)} ${event.description} ${event.propertiesMap}`} +

+ ))} +
+ + ) +} diff --git a/docs/network-iot/devices/development/heltec/cubecell-dev-board/platformio-test.mdx b/docs/network-iot/devices/development/heltec/cubecell-dev-board/platformio-test.mdx new file mode 100644 index 000000000..af05ea23c --- /dev/null +++ b/docs/network-iot/devices/development/heltec/cubecell-dev-board/platformio-test.mdx @@ -0,0 +1,38 @@ +--- +id: platformio +title: PlatformIO +pagination_label: PlatformIO +sidebar_label: PlatformIO +description: Helium Documentation +image: https://docs.helium.com/img/link-image.png +slug: /network-iot/devices/development/heltec/cubecell-dev-board/platformio-test +--- + +import useBaseUrl from '@docusaurus/useBaseUrl' +import { DocslabCodeBlock } from 'docslab-docusaurus/lib/theme/DocslabCodeBlock' +import { CreateDevice, DeviceEventLogger } from './EduDevice.tsx' + +## Getting Started With Helium, PlatformIO and Heltec CubeCell + +The Heltec CubeCell HTCC AB0x are one of Heltec's line of popular ASR605x(ARM® Cortex® M0+ Core) +based LoRa development boards with built in USB and battery charging. + +## Complete example {#htcc-complete-example} + + + +Consider the following code from +[the helium/longfi-platformio repository](https://github.com/helium/longfi-platformio/blob/master/Heltec-CubeCell-Board/examples/cubecell-helium-us915-basic/src/main.cpp): + + + +Some more text over here maybe + + diff --git a/docusaurus.config.js b/docusaurus.config.js index 4550317c4..4045bdb1c 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -2,6 +2,8 @@ import { themes } from 'prism-react-renderer' import katex from 'rehype-katex' import math from 'remark-math' import webpack from 'webpack' +require('dotenv').config() + const darkCodeTheme = themes.dracula const lightCodeTheme = themes.github @@ -96,6 +98,9 @@ module.exports = { MIGRATION_SERVICE_URL: 'https://migration.web.helium.io', SOLANA_URL: 'https://solana-rpc.web.helium.io/?session-key=Pluto', TOKENS_TO_RENT_SERVICE_URL: 'https://tokens-to-rent.web.helium.io', + EDU_EMAIL: process.env.EDU_EMAIL, + EDU_PW: process.env.EDU_PW, + EDU_API_URL: process.env.EDU_API_URL, }, plugins: [ function (context, options) { diff --git a/package.json b/package.json index 57a48c283..cccfc5957 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "serve": "docusaurus serve" }, "dependencies": { + "@chirpstack/chirpstack-api-grpc-web": "^4.8.1", "@clockwork-xyz/sdk": "^0.3.0", "@coral-xyz/anchor": "^0.27.0", "@docusaurus/core": "^3.7.0", @@ -64,5 +65,8 @@ }, "engines": { "node": ">=18.0" + }, + "devDependencies": { + "dotenv": "^16.4.5" } } diff --git a/yarn.lock b/yarn.lock index 5721e6f93..c48c4a3ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1096,6 +1096,15 @@ resolved "https://registry.yarnpkg.com/@chevrotain/utils/-/utils-11.0.3.tgz#e39999307b102cff3645ec4f5b3665f5297a2224" integrity sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ== +"@chirpstack/chirpstack-api-grpc-web@^4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@chirpstack/chirpstack-api-grpc-web/-/chirpstack-api-grpc-web-4.8.1.tgz#4740cbf274da3e6d7ac41b80af13753de59aec65" + integrity sha512-eIwHp1pXriBX4fKT4Np8taW8BXFrCEqdqIJvp3L5pNueh3JGe6qFOpRUxoL9uXxIO8PUqAzBrJfSMNqDy9ub5w== + dependencies: + "@types/google-protobuf" "^3.15.12" + google-protobuf "^3.21.2" + grpc-web "^1.5.0" + "@clockwork-xyz/sdk@^0.3.0": version "0.3.4" resolved "https://registry.yarnpkg.com/@clockwork-xyz/sdk/-/sdk-0.3.4.tgz#fba9efd4183172c560f2566e42126e021a1e5f4d" @@ -6350,6 +6359,11 @@ gray-matter@^4.0.3: section-matter "^1.0.0" strip-bom-string "^1.0.0" +grpc-web@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/grpc-web/-/grpc-web-1.5.0.tgz#154e4007ab59a94bf7726b87ef6c5bd8815ecf6e" + integrity sha512-y1tS3BBIoiVSzKTDF3Hm7E8hV2n7YY7pO0Uo7depfWJqKzWE+SKr0jvHNIJsJJYILQlpYShpi/DRJJMbosgDMQ== + gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" From 3f3c0f59b91b200fc49cb8ea54878d8c34215736 Mon Sep 17 00:00:00 2001 From: mbthiery Date: Mon, 10 Jun 2024 10:40:15 -0400 Subject: [PATCH 02/16] Fix premature cleanup --- .../devices/development/heltec/cubecell-dev-board/EduDevice.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx b/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx index 1881e3ab0..387b773b0 100644 --- a/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx +++ b/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx @@ -249,7 +249,7 @@ export function DeviceEventLogger() { setDevEui(devEui) } deviceEmitter.on('deviceInfo', setStreamInfo) - return deviceEmitter.removeListener('deviceInfo', setStreamInfo) + return () => deviceEmitter.removeListener('deviceInfo', setStreamInfo) }, []) const onMessage = useCallback((logItem: LogItem) => { From 034cfe5b1be89a7c95df6398d155e1fa1b83aa56 Mon Sep 17 00:00:00 2001 From: mbthiery Date: Tue, 11 Jun 2024 14:50:30 -0400 Subject: [PATCH 03/16] Update for new edu account --- .../devices/development/heltec/cubecell-dev-board/EduDevice.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx b/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx index 387b773b0..7f83daeab 100644 --- a/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx +++ b/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx @@ -131,7 +131,7 @@ export const CreateDevice = () => { } const deviceApplications = resp.toObject() const targetApplication = deviceApplications.resultList.find( - (application) => application.name === 'Mapper 2', + (application) => application.name === 'Docs', ) if (!targetApplication) return reject('Target application not found') applicationId = targetApplication.id From aa8a0f07a785eb1dac3bad2eee0c0cca2c07ee4a Mon Sep 17 00:00:00 2001 From: Joey Hiller <1965053+jthiller@users.noreply.github.com> Date: Wed, 12 Jun 2024 17:14:48 -0700 Subject: [PATCH 04/16] WIP styling --- .../EduDevice.tsx | 23 ++++--- .../build-a-device.mdx} | 27 +++++---- .../learn/build-a-device.module.css | 60 +++++++++++++++++++ 3 files changed, 91 insertions(+), 19 deletions(-) rename docs/network-iot/{devices/development/heltec/cubecell-dev-board => learn}/EduDevice.tsx (94%) rename docs/network-iot/{devices/development/heltec/cubecell-dev-board/platformio-test.mdx => learn/build-a-device.mdx} (65%) create mode 100644 docs/network-iot/learn/build-a-device.module.css diff --git a/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx b/docs/network-iot/learn/EduDevice.tsx similarity index 94% rename from docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx rename to docs/network-iot/learn/EduDevice.tsx index 7f83daeab..2e9cf0d81 100644 --- a/docs/network-iot/devices/development/heltec/cubecell-dev-board/EduDevice.tsx +++ b/docs/network-iot/learn/EduDevice.tsx @@ -21,6 +21,7 @@ import { EventEmitter } from 'events' import google_protobuf_empty_pb from 'google-protobuf/google/protobuf/empty_pb' import * as grpcWeb from 'grpc-web' import React, { useCallback, useEffect, useState } from 'react' +import styles from "./build-a-device.module.css" const deviceEmitter = new EventEmitter() @@ -47,10 +48,17 @@ const getHexArray = (key: string = '') => { } const DeviceInfoRow = ({ label, value }) => { - if (!value) return

{label}: Pending - Hex Value: Pending

+ if (!value) + return ( +

+ + +

+ ) return ( -

- {label}: {value} - {getHexArray(value)} +

+ +

) } @@ -221,15 +229,14 @@ export const CreateDevice = () => { return ( <> - -
-

Device Info

+
+ ) } diff --git a/docs/network-iot/devices/development/heltec/cubecell-dev-board/platformio-test.mdx b/docs/network-iot/learn/build-a-device.mdx similarity index 65% rename from docs/network-iot/devices/development/heltec/cubecell-dev-board/platformio-test.mdx rename to docs/network-iot/learn/build-a-device.mdx index af05ea23c..cfb5a5bc4 100644 --- a/docs/network-iot/devices/development/heltec/cubecell-dev-board/platformio-test.mdx +++ b/docs/network-iot/learn/build-a-device.mdx @@ -1,25 +1,30 @@ --- -id: platformio -title: PlatformIO -pagination_label: PlatformIO -sidebar_label: PlatformIO +id: build-a-device +title: Build A LoRaWAN Device +pagination_label: Build A LoRaWAN Device +sidebar_label: Build A LoRaWAN Device description: Helium Documentation image: https://docs.helium.com/img/link-image.png -slug: /network-iot/devices/development/heltec/cubecell-dev-board/platformio-test +slug: /network-iot/learn/build-a-device --- import useBaseUrl from '@docusaurus/useBaseUrl' import { DocslabCodeBlock } from 'docslab-docusaurus/lib/theme/DocslabCodeBlock' import { CreateDevice, DeviceEventLogger } from './EduDevice.tsx' -## Getting Started With Helium, PlatformIO and Heltec CubeCell +How to provision a custom-built device on Helium and see the payloads: -The Heltec CubeCell HTCC AB0x are one of Heltec's line of popular ASR605x(ARM® Cortex® M0+ Core) -based LoRa development boards with built in USB and battery charging. +- background on LoRaWAN ( keys, onboarding, etc) +- Using an LNS to provision a device +- Create a Device +- Using an off-the-shelf development board. + - distinction for ready-to-use devices with pre-provisioned keys +- Send data to the Helium Network +- View the data in the Console -## Complete example {#htcc-complete-example} - - +
+ +
Consider the following code from [the helium/longfi-platformio repository](https://github.com/helium/longfi-platformio/blob/master/Heltec-CubeCell-Board/examples/cubecell-helium-us915-basic/src/main.cpp): diff --git a/docs/network-iot/learn/build-a-device.module.css b/docs/network-iot/learn/build-a-device.module.css new file mode 100644 index 000000000..5de06af84 --- /dev/null +++ b/docs/network-iot/learn/build-a-device.module.css @@ -0,0 +1,60 @@ +.DeviceKeys { + width: 100%; + padding: 0.5em; +} + +.keybox { + display: flex; + flex-direction: column; +} + +.keybox label { + font-weight: 500; + color: #53627c; + font-size: small; + background-color: #eaeff9; + padding: .5em 1rem calc(0.5em + 4px) 1rem; + border-radius: 8px 8px 0 0; +} + +.keybox input { + display: table; + background: white; + width: 100%; + table-layout: fixed; + border: none; + border-radius: 0 0 8px 8px; + padding: 1rem; + margin-top: -8px; + box-shadow: 0 3px 8px -3px rgba(0, 0, 0, 0.08); + font-size: 1rem; +} + +.keybox input:focus { + outline-color: rgb(47, 98, 192); +} + +.keybox input::placeholder { + color: #aaadb4; +} + +.keybox input:focus:placeholder { + outline: none; +} + +.createDeviceButton { + appearance: none; + cursor: pointer; + background-color: #fff; + border: none; + border-radius: 6px; + box-shadow: 0 3px 6px -3px #0000001a; + font-size: 1em; + margin-bottom: 0.5em; + padding: 0.5em; +} + + +.createDeviceButton:disabled { + background-color: beige; +} \ No newline at end of file From f6788956a22873a646666daa0b4833a537a9e319 Mon Sep 17 00:00:00 2001 From: Joey Hiller <1965053+jthiller@users.noreply.github.com> Date: Wed, 12 Jun 2024 17:57:34 -0700 Subject: [PATCH 05/16] light button styling --- docs/network-iot/learn/build-a-device.module.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/network-iot/learn/build-a-device.module.css b/docs/network-iot/learn/build-a-device.module.css index 5de06af84..004b0d98a 100644 --- a/docs/network-iot/learn/build-a-device.module.css +++ b/docs/network-iot/learn/build-a-device.module.css @@ -50,11 +50,10 @@ border-radius: 6px; box-shadow: 0 3px 6px -3px #0000001a; font-size: 1em; - margin-bottom: 0.5em; + margin-bottom: 1em; padding: 0.5em; } - .createDeviceButton:disabled { - background-color: beige; + background-color: #e8e8e8 } \ No newline at end of file From 55a09270263d4b46eb5707dce77360e03b81976c Mon Sep 17 00:00:00 2001 From: mbthiery Date: Thu, 20 Jun 2024 09:42:11 -0400 Subject: [PATCH 06/16] Add alert for bad env vars + alert cleanup --- .env.example | 3 + docs/network-iot/learn/EduDevice.tsx | 41 +++++++++-- .../learn/build-a-device.module.css | 68 +++++++++---------- src/theme/HntToFeeSol.tsx | 2 +- src/theme/HstManager.tsx | 2 +- src/theme/LedgerMigration.tsx | 12 ++-- src/theme/components/Alert.tsx | 4 +- 7 files changed, 83 insertions(+), 49 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..0fe3f6de0 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +EDU_EMAIL=SEE_README +EDU_PW=SEE_README +EDU_API_URL=SEE_README \ No newline at end of file diff --git a/docs/network-iot/learn/EduDevice.tsx b/docs/network-iot/learn/EduDevice.tsx index 2e9cf0d81..91a463b41 100644 --- a/docs/network-iot/learn/EduDevice.tsx +++ b/docs/network-iot/learn/EduDevice.tsx @@ -21,7 +21,8 @@ import { EventEmitter } from 'events' import google_protobuf_empty_pb from 'google-protobuf/google/protobuf/empty_pb' import * as grpcWeb from 'grpc-web' import React, { useCallback, useEffect, useState } from 'react' -import styles from "./build-a-device.module.css" +import { Alert, AlertIcon } from '../../../src/theme/components/Alert' +import styles from './build-a-device.module.css' const deviceEmitter = new EventEmitter() @@ -87,7 +88,23 @@ const getRequestMeta = (jwt: string) => { return { authorization: `Bearer ${jwt}` } } -export const CreateDevice = () => { +const EnvChecker = ({ children }) => { + const { + siteConfig: { customFields }, + } = useDocusaurusContext() + if (!customFields.EDU_API_URL || !customFields.EDU_EMAIL || !customFields.EDU_PW) { + return ( + + +

Please configure your .env file to use the Educational LNS features

+
+ ) + } + + return children +} + +const CreateDeviceComponent = () => { const { siteConfig: { customFields }, } = useDocusaurusContext() @@ -234,14 +251,24 @@ export const CreateDevice = () => {
- ) } -export function DeviceEventLogger() { +export const CreateDevice = () => ( + + + +) + +function DeviceEventLoggerComponent() { const { siteConfig: { customFields }, } = useDocusaurusContext() @@ -335,3 +362,9 @@ export function DeviceEventLogger() { ) } + +export const DeviceEventLogger = () => ( + + + +) diff --git a/docs/network-iot/learn/build-a-device.module.css b/docs/network-iot/learn/build-a-device.module.css index 004b0d98a..348a3d6ec 100644 --- a/docs/network-iot/learn/build-a-device.module.css +++ b/docs/network-iot/learn/build-a-device.module.css @@ -1,59 +1,59 @@ .DeviceKeys { - width: 100%; - padding: 0.5em; + width: 100%; + padding: 0.5em; } .keybox { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .keybox label { - font-weight: 500; - color: #53627c; - font-size: small; - background-color: #eaeff9; - padding: .5em 1rem calc(0.5em + 4px) 1rem; - border-radius: 8px 8px 0 0; + font-weight: 500; + color: #53627c; + font-size: small; + background-color: #eaeff9; + padding: 0.5em 1rem calc(0.5em + 4px) 1rem; + border-radius: 8px 8px 0 0; } .keybox input { - display: table; - background: white; - width: 100%; - table-layout: fixed; - border: none; - border-radius: 0 0 8px 8px; - padding: 1rem; - margin-top: -8px; - box-shadow: 0 3px 8px -3px rgba(0, 0, 0, 0.08); - font-size: 1rem; + display: table; + background: white; + width: 100%; + table-layout: fixed; + border: none; + border-radius: 0 0 8px 8px; + padding: 1rem; + margin-top: -8px; + box-shadow: 0 3px 8px -3px rgba(0, 0, 0, 0.08); + font-size: 1rem; } .keybox input:focus { - outline-color: rgb(47, 98, 192); + outline-color: rgb(47, 98, 192); } .keybox input::placeholder { - color: #aaadb4; + color: #aaadb4; } .keybox input:focus:placeholder { - outline: none; + outline: none; } .createDeviceButton { - appearance: none; - cursor: pointer; - background-color: #fff; - border: none; - border-radius: 6px; - box-shadow: 0 3px 6px -3px #0000001a; - font-size: 1em; - margin-bottom: 1em; - padding: 0.5em; + appearance: none; + cursor: pointer; + background-color: #fff; + border: none; + border-radius: 6px; + box-shadow: 0 3px 6px -3px #0000001a; + font-size: 1em; + margin-bottom: 1em; + padding: 0.5em; } .createDeviceButton:disabled { - background-color: #e8e8e8 -} \ No newline at end of file + background-color: #e8e8e8; +} diff --git a/src/theme/HntToFeeSol.tsx b/src/theme/HntToFeeSol.tsx index 3abec7198..f4d72806e 100644 --- a/src/theme/HntToFeeSol.tsx +++ b/src/theme/HntToFeeSol.tsx @@ -58,7 +58,7 @@ export const HntToFeeSolImpl = () => { return ( {error && ( - +

{error.message}

diff --git a/src/theme/HstManager.tsx b/src/theme/HstManager.tsx index d8a383790..05ccb371a 100644 --- a/src/theme/HstManager.tsx +++ b/src/theme/HstManager.tsx @@ -187,7 +187,7 @@ export const HstManagerImpl = ({ idl }: { idl: Fanout }) => { return ( {error && ( - +

{error.message}

diff --git a/src/theme/LedgerMigration.tsx b/src/theme/LedgerMigration.tsx index 7a89118a7..ffb97478f 100644 --- a/src/theme/LedgerMigration.tsx +++ b/src/theme/LedgerMigration.tsx @@ -351,7 +351,7 @@ export const LedgerMigration = () => { component: ( {errorSolana && ( - +

{errorSolana.message}. Please make sure you are connected to the Solana Ledger App @@ -398,7 +398,7 @@ export const LedgerMigration = () => { component: ( {errorHelium && ( - +

{errorHelium.message}. Please make sure you are connected to the Helium-Solana @@ -429,7 +429,7 @@ export const LedgerMigration = () => { component: ( {errorSeed && ( - + {errorSeed.message} @@ -452,7 +452,7 @@ export const LedgerMigration = () => { component: ( {errorHeliumSign && ( - + {errorHeliumSign.message}. Please make sure you are connected to the Helium-Solana Ledger App and have blind signing enabled. @@ -476,7 +476,7 @@ export const LedgerMigration = () => { component: ( {errorSolanaSign && ( - + {errorSolanaSign.message}. Please make sure you are connected to the Solana Ledger App and have blind signing enabled. @@ -500,7 +500,7 @@ export const LedgerMigration = () => { component: ( {errorSendTransactions && ( - + {errorSendTransactions.message} diff --git a/src/theme/components/Alert.tsx b/src/theme/components/Alert.tsx index 77cf1037b..b51582ac6 100644 --- a/src/theme/components/Alert.tsx +++ b/src/theme/components/Alert.tsx @@ -1,17 +1,15 @@ import React from 'react' import { FaExclamationCircle } from 'react-icons/fa' - export const AlertIcon: React.FunctionComponent = () => { return } export const Alert: React.FunctionComponent<{ children: React.ReactNode - status: 'error' | 'warning' }> = ({ children }) => { return ( -

+
{children}
) From ddfd8c0767d48ccae116d639e581892ab653c313 Mon Sep 17 00:00:00 2001 From: mbthiery Date: Tue, 16 Jul 2024 15:37:05 -0400 Subject: [PATCH 07/16] Check cors --- docs/network-iot/learn/EduDevice.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/network-iot/learn/EduDevice.tsx b/docs/network-iot/learn/EduDevice.tsx index 91a463b41..7c3f18745 100644 --- a/docs/network-iot/learn/EduDevice.tsx +++ b/docs/network-iot/learn/EduDevice.tsx @@ -108,6 +108,7 @@ const CreateDeviceComponent = () => { const { siteConfig: { customFields }, } = useDocusaurusContext() + console.log('url', customFields.EDU_API_URL) const login = useLogin() const [deviceInfo, setDeviceInfo] = useState({ devEui: '', joinEui: '', networkKey: '' }) From 6e03937c39db3f47e6506ef626169ec4cf2ca635 Mon Sep 17 00:00:00 2001 From: Joey Hiller <1965053+jthiller@users.noreply.github.com> Date: Wed, 17 Jul 2024 19:17:24 -0700 Subject: [PATCH 08/16] Couple of UI tweaks, WIP interactive docs. --- docs/network-iot/learn/EduDevice.tsx | 36 ++++++++++++++----- docs/network-iot/learn/build-a-device.mdx | 29 ++++++++++----- .../learn/build-a-device.module.css | 14 ++++++++ src/css/custom.css | 30 +++++++++------- 4 files changed, 79 insertions(+), 30 deletions(-) diff --git a/docs/network-iot/learn/EduDevice.tsx b/docs/network-iot/learn/EduDevice.tsx index 7c3f18745..e2a0fdd1a 100644 --- a/docs/network-iot/learn/EduDevice.tsx +++ b/docs/network-iot/learn/EduDevice.tsx @@ -59,7 +59,7 @@ const DeviceInfoRow = ({ label, value }) => { return (

- + e.target.select()} />

) } @@ -111,8 +111,10 @@ const CreateDeviceComponent = () => { console.log('url', customFields.EDU_API_URL) const login = useLogin() const [deviceInfo, setDeviceInfo] = useState({ devEui: '', joinEui: '', networkKey: '' }) + const [isLoading, setIsLoading] = useState(false) const createDevice = () => { + setIsLoading(true) let jwt = '' let tenants = [] let applicationId = '' @@ -212,6 +214,7 @@ const CreateDeviceComponent = () => { client.create(req, getRequestMeta(jwt), (error) => { if (error !== null) { + setIsLoading(false) return reject(error) } @@ -223,6 +226,7 @@ const CreateDeviceComponent = () => { req.setDeviceKeys(deviceKeys) client.createKeys(req, getRequestMeta(jwt), (error) => { + setIsLoading(false) if (error !== null) { return reject(error) } @@ -255,9 +259,9 @@ const CreateDeviceComponent = () => { ) @@ -345,19 +349,33 @@ function DeviceEventLoggerComponent() { return ( <> -

Device Event Logger

-

jwt: {jwt}

+ {/*

Device Event Logger

*/} + {/*

jwt: {jwt}

*/} setDevEui(event.target.value)} /> + {!events.length &&

No events yet.

}
-

EVENTS BELOW HERE vvv

{events.map((event) => ( -

- {`${new Date(event.time?.seconds! * 1000)} ${event.description} ${event.propertiesMap}`} -

+
+
+

+ {event.description === 'join' + ? '🔄 Joining' + : event.description === 'up' + ? '⬆️ Uplink' + : event.description === 'down' + ? '⬇️ Downlink' + : event.description === 'status' + ? '*️⃣ Status' + : `🛜 ${event.description}`} +

+

{new Date(event.time?.seconds! * 1000).toLocaleString()}

+
+
{JSON.stringify(event.propertiesMap)}
+
))}
diff --git a/docs/network-iot/learn/build-a-device.mdx b/docs/network-iot/learn/build-a-device.mdx index cfb5a5bc4..40e861fe1 100644 --- a/docs/network-iot/learn/build-a-device.mdx +++ b/docs/network-iot/learn/build-a-device.mdx @@ -25,19 +25,30 @@ How to provision a custom-built device on Helium and see the payloads:
+
Consider the following code from [the helium/longfi-platformio repository](https://github.com/helium/longfi-platformio/blob/master/Heltec-CubeCell-Board/examples/cubecell-helium-us915-basic/src/main.cpp): - +
+ +
+
Some more text over here maybe - +
+ +
+
+ +If uplinks are shown in the logger above, the device is successfully sending data to the Helium Network! + +With the payloads received, the next step would be to send them to an application. LNSs typically support a number of integrations like HTTP, MQTT, or bespoke integrations with platforms like AWS IoT or Azure IoT. diff --git a/docs/network-iot/learn/build-a-device.module.css b/docs/network-iot/learn/build-a-device.module.css index 348a3d6ec..554aff298 100644 --- a/docs/network-iot/learn/build-a-device.module.css +++ b/docs/network-iot/learn/build-a-device.module.css @@ -1,3 +1,4 @@ +/*CreateDevice*/ .DeviceKeys { width: 100%; padding: 0.5em; @@ -57,3 +58,16 @@ .createDeviceButton:disabled { background-color: #e8e8e8; } + +/*DeviceEventLogger*/ +.event-frame { + background: grey; +} + +.event-header { + display: flex; + justify-content: space-between; + padding: 0.5em; + background: #f5f5f5; + border-bottom: 1px solid #e0e0e0; +} diff --git a/src/css/custom.css b/src/css/custom.css index f70b4bdb1..49fe32ce1 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -849,11 +849,17 @@ html[data-theme='dark'] .badge--secondary:hover { } .screensnippet-wrapper .add-shadow { - box-shadow: 0px 1.4px 5.2px -10px rgba(0, 0, 0, 0.004), 0px 2.3px 8.2px -10px rgba(0, 0, 0, 0.006), - 0px 3px 10.5px -10px rgba(0, 0, 0, 0.008), 0px 3.5px 12.6px -10px rgba(0, 0, 0, 0.009), - 0px 4px 14.7px -10px rgba(0, 0, 0, 0.01), 0px 4.4px 16.8px -10px rgba(0, 0, 0, 0.011), - 0px 5px 19.1px -10px rgba(0, 0, 0, 0.012), 0px 5.8px 21.8px -10px rgba(0, 0, 0, 0.014), - 0px 7px 25.6px -10px rgba(0, 0, 0, 0.016), 0px 10px 36px -10px rgba(0, 0, 0, 0.02); + box-shadow: + 0px 1.4px 5.2px -10px rgba(0, 0, 0, 0.004), + 0px 2.3px 8.2px -10px rgba(0, 0, 0, 0.006), + 0px 3px 10.5px -10px rgba(0, 0, 0, 0.008), + 0px 3.5px 12.6px -10px rgba(0, 0, 0, 0.009), + 0px 4px 14.7px -10px rgba(0, 0, 0, 0.01), + 0px 4.4px 16.8px -10px rgba(0, 0, 0, 0.011), + 0px 5px 19.1px -10px rgba(0, 0, 0, 0.012), + 0px 5.8px 21.8px -10px rgba(0, 0, 0, 0.014), + 0px 7px 25.6px -10px rgba(0, 0, 0, 0.016), + 0px 10px 36px -10px rgba(0, 0, 0, 0.02); } .screensnippet-wrapper:has(.add-shadow-margin) { @@ -868,15 +874,15 @@ html[data-theme='dark'] .badge--secondary:hover { border-radius: 18px; } +.screensnippet-wrapper:has(.docslabEditor) > div { + width: 100%; +} + .docslabEditor { - width: 70%; + width: 100%; height: 300px; } .font-disable-calt { - font-feature-settings: 'calt' 0, 'calt' 0; -} - -.footer { - background-color: #13162e; -} + font-feature-settings:'calt' 0,'calt'0; +} \ No newline at end of file From 57a7fec0bd6daa028e57b2e4b82c8568988c91a6 Mon Sep 17 00:00:00 2001 From: Joey Hiller <1965053+jthiller@users.noreply.github.com> Date: Wed, 17 Jul 2024 22:16:17 -0700 Subject: [PATCH 09/16] WIP interactive docs --- docs/network-iot/learn/EduDevice.tsx | 42 ++++++++++--------- .../learn/build-a-device.module.css | 37 +++++++++++++--- 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/docs/network-iot/learn/EduDevice.tsx b/docs/network-iot/learn/EduDevice.tsx index e2a0fdd1a..f57e713c3 100644 --- a/docs/network-iot/learn/EduDevice.tsx +++ b/docs/network-iot/learn/EduDevice.tsx @@ -357,26 +357,30 @@ function DeviceEventLoggerComponent() { onChange={(event) => setDevEui(event.target.value)} /> {!events.length &&

No events yet.

} -
- {events.map((event) => ( -
-
-

- {event.description === 'join' - ? '🔄 Joining' - : event.description === 'up' - ? '⬆️ Uplink' - : event.description === 'down' - ? '⬇️ Downlink' - : event.description === 'status' - ? '*️⃣ Status' - : `🛜 ${event.description}`} -

-

{new Date(event.time?.seconds! * 1000).toLocaleString()}

+
+ {events + .filter((event) => event.description !== 'log') + .map((event) => ( +
+
+

+ {event.description === 'join' + ? '🔄 Joining' + : event.description === 'up' + ? '⬆️ Uplink' + : event.description === 'down' + ? '⬇️ Downlink' + : event.description === 'status' + ? '*️⃣ Status' + : `🛜 ${event.description}`} +

+

+ {new Date(event.time?.seconds! * 1000).toLocaleString()} +

+
+
{JSON.stringify(event.propertiesMap)}
-
{JSON.stringify(event.propertiesMap)}
-
- ))} + ))}
) diff --git a/docs/network-iot/learn/build-a-device.module.css b/docs/network-iot/learn/build-a-device.module.css index 554aff298..5d20d239d 100644 --- a/docs/network-iot/learn/build-a-device.module.css +++ b/docs/network-iot/learn/build-a-device.module.css @@ -60,14 +60,41 @@ } /*DeviceEventLogger*/ -.event-frame { - background: grey; +.eventsContainer { + width: 100%; + max-height: 90vh; + overflow: scroll; + display: flex; + flex-direction: column; +} + +.eventFrame { + box-shadow: 0 3px 8px -3px rgba(0, 0, 0, 0.08); + border-radius: 0.5em; + overflow: scroll; + margin: 0 0.5rem 1rem; + flex: 0 0 auto; } -.event-header { +.eventHeader { display: flex; justify-content: space-between; padding: 0.5em; - background: #f5f5f5; - border-bottom: 1px solid #e0e0e0; + background: #eaeff9; +} + +.eventTime, +.eventType { + color: #53627c; + font-size: small; + padding: 0; + margin: 0; +} + +.eventType { + font-weight: 500; +} + +.eventTime { + color: #6a748c; } From 035d2071185625afabd5bf206be6e095a3c3e1c4 Mon Sep 17 00:00:00 2001 From: Joey Hiller <1965053+jthiller@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:11:40 -0700 Subject: [PATCH 10/16] Stub out new intro page --- .../learn/understanding-lorawan.mdx | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 docs/network-iot/learn/understanding-lorawan.mdx diff --git a/docs/network-iot/learn/understanding-lorawan.mdx b/docs/network-iot/learn/understanding-lorawan.mdx new file mode 100644 index 000000000..ca4a70935 --- /dev/null +++ b/docs/network-iot/learn/understanding-lorawan.mdx @@ -0,0 +1,45 @@ +--- +id: understanding-lorawan +title: Understanding LoRaWAN +pagination_label: Understanding LoRaWAN +sidebar_label: Understanding LoRaWAN +description: Helium Documentation +image: https://docs.helium.com/img/link-image.png +slug: /network-iot/learn/understanding-lorawan +--- + + +LoRaWAN is a wireless communication protocol designed for long-range, low-power, and low-bandwidth applications. It is a critical technology for IoT (Internet of Things) networks, enabling devices to communicate over large distances with minimal energy consumption. + +```mermaid +graph TB; + A["LoRaWAN"] + + subgraph LR0["Long-Range"] + B1(Supports distances over several kilometers) + B2(Suitable for urban and rural areas) + end + + subgraph LB["Low-Bandwidth"] + C1(Optimized for small data transfers) + C2(Examples include sensor readings and status updates) + end + + subgraph LP["Low-Power"] + D1(Designed for battery-operated devices) + D2(Extends operational life up to several years) + end + + A --> LR0 + A --> LB + A --> LP +``` +The primary properties of LoRaWAN encompass long-range capabilities, allowing data transmission over several kilometers, making it suitable for both urban and rural deployments. It also supports low-power operations, designed for battery-operated devices, thus extending their operational life up to several years without the need for frequent recharging. Additionally, LoRaWAN is optimized for low-bandwidth applications that require small amounts of data transfer, such as sensor readings and status updates. + +LoRaWAN is versatile and can be used in various applications. In smart cities, it enables connectivity for street lighting, waste management, and parking solutions. In agriculture, it facilitates soil moisture monitoring, livestock tracking, and irrigation control. Industrial IoT benefits from LoRaWAN through predictive maintenance, asset tracking, and environmental monitoring in factories. Smart homes also utilize LoRaWAN for efficient control of home automation devices and security systems. + +Understanding the background of LoRaWAN involves several key components, including keys and security measures to ensure secure communications by using unique keys for device authentication and data encryption. The onboarding process for devices requires registering and commissioning end devices onto the network, which may involve configuring parameters and device keys. + +Provisioning a device through a LoRaWAN Network Server (LNS) is essential. The LNS manages the network by handling tasks such as device authentication, validating each device's identity to allow secure network access. It also manages data routing, directing data traffic between end devices and application servers, and optimizes network performance by handling join requests and maintaining device connectivity. + +Helium Hotspots play a fundamental role in providing coverage for LoRaWAN devices. Hotspots act as gateways that bridge the communication between end nodes (LoRaWAN devices) and the internet. By deploying Helium Hotspots, individuals and businesses contribute to a decentralized network, expanding the coverage area and enabling more devices to connect and communicate reliably. The relationship between gateways (Helium Hotspots) and end nodes (LoRaWAN devices) is that gateways transmit and receive data packets from multiple end nodes, providing the infrastructure necessary for widespread IoT applications. From 43f5698340bfd448c42143d2e7dda1815ef22e76 Mon Sep 17 00:00:00 2001 From: Joey Hiller <1965053+jthiller@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:12:40 -0700 Subject: [PATCH 11/16] stubbing out content --- docs/network-iot/learn/build-a-device.mdx | 49 +++++++++++++++++------ 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/docs/network-iot/learn/build-a-device.mdx b/docs/network-iot/learn/build-a-device.mdx index 40e861fe1..69c01df95 100644 --- a/docs/network-iot/learn/build-a-device.mdx +++ b/docs/network-iot/learn/build-a-device.mdx @@ -12,24 +12,44 @@ import useBaseUrl from '@docusaurus/useBaseUrl' import { DocslabCodeBlock } from 'docslab-docusaurus/lib/theme/DocslabCodeBlock' import { CreateDevice, DeviceEventLogger } from './EduDevice.tsx' -How to provision a custom-built device on Helium and see the payloads: +
+ Notes: // Create a Device // Using an off-the-shelf development board. // distinction for + ready-to-use devices with pre-provisioned keys // Send data to the Helium Network // View the data + in the Console +
-- background on LoRaWAN ( keys, onboarding, etc) -- Using an LNS to provision a device -- Create a Device -- Using an off-the-shelf development board. - - distinction for ready-to-use devices with pre-provisioned keys -- Send data to the Helium Network -- View the data in the Console +Activating a commercially-produced device is often as simple as scanning a QR code or pairing with +an app. However, knowing the fundamentals of how LoRaWAN devices are onboarded, programmed, and +their data delivered helps to unlock the ideas and opportunities that come with these devices. +Therefore, this guide details the less common, but important principles of creating a custom-built +device on the Helium Network. + +## Provisioning a Device + +Many devices are pre-provisioned with keys. However, for this demonstration, we will create a new +set of keys for a device. The keys are used to authenticate the device on the network and encrypt +data. The keys are generated randomly in order to be unique to each device. + +Generate a set of keys below by clicking the "Create Device" button:
-
+
+ +Behind the scenes, these keys have been submitted to a LoRaWAN Network Server (LNS) which also +shares the Join EUI with the Helium Packet Router (HPR). With the Join EUI, HPR can receieve +payloads from this device through any Hotspot in the world and route it to the appropriate LNS. Because these +keys are unique to the device and LNS, only the device and the LNS can decrypt the data transferred +between them. + +## Building a Device Consider the following code from [the helium/longfi-platformio repository](https://github.com/helium/longfi-platformio/blob/master/Heltec-CubeCell-Board/examples/cubecell-helium-us915-basic/src/main.cpp): +Copy the keys generated above into the `devEui`, `appEui`, and `appKey` variables in the code below: +
-
+
Some more text over here maybe
-
+
-If uplinks are shown in the logger above, the device is successfully sending data to the Helium Network! +If uplinks are shown in the logger above, the device is successfully sending data to the Helium +Network! -With the payloads received, the next step would be to send them to an application. LNSs typically support a number of integrations like HTTP, MQTT, or bespoke integrations with platforms like AWS IoT or Azure IoT. +With the payloads received, the next step would be to send them to an application. LNSs typically +support a number of integrations like HTTP, MQTT, or bespoke integrations with platforms like AWS +IoT or Azure IoT. From 5b11885a3c2033d560383dac5554d71bd26da200 Mon Sep 17 00:00:00 2001 From: Joey Hiller <1965053+jthiller@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:13:36 -0700 Subject: [PATCH 12/16] Styling, retry stream with exponential backoff --- docs/network-iot/learn/EduDevice.tsx | 46 +++++++++++++++---- .../learn/build-a-device.module.css | 25 ++++++++-- src/css/custom.css | 3 +- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/docs/network-iot/learn/EduDevice.tsx b/docs/network-iot/learn/EduDevice.tsx index f57e713c3..d4321af85 100644 --- a/docs/network-iot/learn/EduDevice.tsx +++ b/docs/network-iot/learn/EduDevice.tsx @@ -316,6 +316,7 @@ function DeviceEventLoggerComponent() { let req = new StreamDeviceEventsRequest() req.setDevEui(devEui) var stream: grpcWeb.ClientReadableStream | undefined = undefined + let retryCount = 0 let setup = () => { const client = new InternalServiceClient(customFields.EDU_API_URL) @@ -323,10 +324,17 @@ function DeviceEventLoggerComponent() { stream = stream.on('data', (resp) => { callbackFunc(resp) + retryCount = 0 // reset retry count on successful data reception }) stream = stream.on('end', function () { - setTimeout(setup, 1000) + retryCount++ + setTimeout(setup, Math.min(1000 * 2 ** retryCount, 30000)) // exponential backoff with max delay + }) + + stream = stream.on('error', function () { + retryCount++ + setTimeout(setup, Math.min(1000 * 2 ** retryCount, 30000)) // exponential backoff with max delay }) } @@ -351,12 +359,18 @@ function DeviceEventLoggerComponent() { <> {/*

Device Event Logger

*/} {/*

jwt: {jwt}

*/} - setDevEui(event.target.value)} - /> - {!events.length &&

No events yet.

} + + {!events.length && ( +
+ setDevEui(event.target.value)} + /> +

No events yet.

+
+ )} +
{events .filter((event) => event.description !== 'log') @@ -378,7 +392,23 @@ function DeviceEventLoggerComponent() { {new Date(event.time?.seconds! * 1000).toLocaleString()}

-
{JSON.stringify(event.propertiesMap)}
+
    + {event.propertiesMap.map(([key, value], index) => ( +
  • + + {key === 'DR' + ? 'Data Rate' + : key === 'FCnt' + ? 'Frame Count' + : key === 'FPort' + ? 'Frame Port' + : key} + :{' '} + + {value} +
  • + ))} +
))}
diff --git a/docs/network-iot/learn/build-a-device.module.css b/docs/network-iot/learn/build-a-device.module.css index 5d20d239d..f1d1ed3ac 100644 --- a/docs/network-iot/learn/build-a-device.module.css +++ b/docs/network-iot/learn/build-a-device.module.css @@ -14,7 +14,8 @@ color: #53627c; font-size: small; background-color: #eaeff9; - padding: 0.5em 1rem calc(0.5em + 4px) 1rem; + line-height: 1em; + padding: 1em 1rem calc(1em + 6px); border-radius: 8px 8px 0 0; } @@ -62,24 +63,30 @@ /*DeviceEventLogger*/ .eventsContainer { width: 100%; - max-height: 90vh; + max-height: 50vh; overflow: scroll; display: flex; flex-direction: column; + padding: 2rem 1rem 1rem; + margin: -0.5rem; + scroll-snap-type: y proximity; } .eventFrame { + background: white; box-shadow: 0 3px 8px -3px rgba(0, 0, 0, 0.08); border-radius: 0.5em; overflow: scroll; margin: 0 0.5rem 1rem; flex: 0 0 auto; + scroll-snap-align: start; + scroll-snap-margin: 1rem; } .eventHeader { display: flex; justify-content: space-between; - padding: 0.5em; + padding: 0.5em 1rem; background: #eaeff9; } @@ -98,3 +105,15 @@ .eventTime { color: #6a748c; } + +.eventProperties { + list-style: none; + padding: 0.5rem 0 0.55rem; + margin: 0; + /* display: inline-flex; */ +} + +.eventProperties li { + margin-top: 0 !important; + padding: 0 1rem; +} diff --git a/src/css/custom.css b/src/css/custom.css index 49fe32ce1..784b017b7 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -880,7 +880,8 @@ html[data-theme='dark'] .badge--secondary:hover { .docslabEditor { width: 100%; - height: 300px; + /* height: 300px; */ + height: 50vh; } .font-disable-calt { From 72ca51bf2ecc932b71f0ceed5fdf28a0ead4d2ac Mon Sep 17 00:00:00 2001 From: Joey Hiller <1965053+jthiller@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:18:35 -0700 Subject: [PATCH 13/16] move to oled example. bump docslab --- docs/network-iot/learn/build-a-device.mdx | 6 ++--- src/css/custom.css | 4 +-- yarn.lock | 30 +++++++++++------------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/network-iot/learn/build-a-device.mdx b/docs/network-iot/learn/build-a-device.mdx index 69c01df95..4d63531fd 100644 --- a/docs/network-iot/learn/build-a-device.mdx +++ b/docs/network-iot/learn/build-a-device.mdx @@ -38,8 +38,8 @@ Generate a set of keys below by clicking the "Create Device" button:
Behind the scenes, these keys have been submitted to a LoRaWAN Network Server (LNS) which also -shares the Join EUI with the Helium Packet Router (HPR). With the Join EUI, HPR can receieve -payloads from this device through any Hotspot in the world and route it to the appropriate LNS. Because these +shares the Join EUI with the Helium Packet Router (HPR). With the Join EUI, HPR can receive payloads +from this device through any Hotspot in the world and route it to the appropriate LNS. Because these keys are unique to the device and LNS, only the device and the LNS can decrypt the data transferred between them. @@ -57,7 +57,7 @@ Copy the keys generated above into the `devEui`, `appEui`, and `appKey` variable command="source .platformio/penv/bin/activate && cd m/Heltec-CubeCell-Board/examples/cubecell-helium-us915-basic && pio run -t upload && pio device monitor" destpath="m/Heltec-CubeCell-Board/examples/cubecell-helium-us915-basic/src/main.cpp" repoUrl="https://github.com/helium/longfi-platformio.git" - urlfile="https://raw.githubusercontent.com/helium/longfi-platformio/master/Heltec-CubeCell-Board/examples/cubecell-helium-us915-basic/src/main.cpp" + urlfile="https://raw.githubusercontent.com/helium/longfi-platformio/master/Heltec-CubeCell-Board/examples/cubecell-helium-us915-oled/src/main.cpp" />
diff --git a/src/css/custom.css b/src/css/custom.css index 784b017b7..e8f79aa3b 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -878,9 +878,9 @@ html[data-theme='dark'] .badge--secondary:hover { width: 100%; } -.docslabEditor { +.screensnippet-wrapper .docslabEditor { width: 100%; - /* height: 300px; */ + max-height: 550px; height: 50vh; } diff --git a/yarn.lock b/yarn.lock index c48c4a3ef..8d436367f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3754,9 +3754,14 @@ accepts@~1.3.4, accepts@~1.3.8: negotiator "0.6.3" ace-code@^1.23.4: - version "1.38.0" - resolved "https://registry.yarnpkg.com/ace-code/-/ace-code-1.38.0.tgz#e6825c6dbc97bb1a44d75bcd876cddab6a73001e" - integrity sha512-iZtpjAOBWsWka3auM7n2MM81ykCsQk0iSVRRwMiPDMOjnLBNm1bQxDVZkx82y21Oo3s3/5HJgHxf/HWcMwHAyw== + version "1.24.1" + resolved "https://registry.yarnpkg.com/ace-code/-/ace-code-1.24.1.tgz#129e070699e33d819a780c69be3b95e0a59e1342" + integrity sha512-pRdEBQpwnXcH4l9BRhevjkICdKt3m7YEN5O+XJ4xtEpqZS5fUt9ZIXlb3UmLU9wl+tYAXm+IN1p0UVUS+C9DWQ== + +acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== acorn-jsx@^5.0.0: version "5.3.2" @@ -5453,10 +5458,10 @@ docslab-docusaurus@^0.2.10: dependencies: docslab "^0.3.12" -docslab@^0.3.12: - version "0.3.12" - resolved "https://registry.yarnpkg.com/docslab/-/docslab-0.3.12.tgz#fb7ec65e2b77a9f1f61b4c1125c275adffd32abd" - integrity sha512-+E6AqnrCHlUsjPfatNk3vMEdXPbn30IpQsMU0eX5kMENtcLERja/YgKBJarm32gpOUqejn9mhby7Sk8/KijDsA== +docslab@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/docslab/-/docslab-0.3.7.tgz#7b0e25676979d3dac211412e936b391d20f3004b" + integrity sha512-7sUcsfB1do2cB/uNPJzdZAwJNuTlvsY9ivPFGUpuY89o9kXGEDIoIhJ2cmJZiDemrxBfFUnGpAqQy7WBrQ04mA== dependencies: ace-code "^1.23.4" xterm "^5.2.1" @@ -11445,14 +11450,9 @@ xterm-addon-fit@^0.7.0: integrity sha512-tQgHGoHqRTgeROPnvmtEJywLKoC/V9eNs4bLLz7iyJr1aW/QFzRwfd3MGiJ6odJd9xEfxcW36/xRU47JkD5NKQ== xterm@^5.2.1: - version "5.3.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0.tgz#867daf9cc826f3d45b5377320aabd996cb0fce46" - integrity sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg== - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + version "5.2.1" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.1.tgz#b3fea7bdb55b9be1d4b31f4cd1091f26ac42afb8" + integrity sha512-cs5Y1fFevgcdoh2hJROMVIWwoBHD80P1fIP79gopLHJIE4kTzzblanoivxTiQ4+92YM9IxS36H1q0MxIJXQBcA== yallist@^3.0.2: version "3.1.1" From 3a0a983eac96bf89f38909f9827853d64c6a52f0 Mon Sep 17 00:00:00 2001 From: Joey Hiller <1965053+jthiller@users.noreply.github.com> Date: Tue, 30 Jul 2024 15:02:54 -0700 Subject: [PATCH 14/16] practice exercises --- docs/network-iot/learn/build-a-device.mdx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/network-iot/learn/build-a-device.mdx b/docs/network-iot/learn/build-a-device.mdx index 4d63531fd..86cd616b1 100644 --- a/docs/network-iot/learn/build-a-device.mdx +++ b/docs/network-iot/learn/build-a-device.mdx @@ -73,5 +73,18 @@ If uplinks are shown in the logger above, the device is successfully sending dat Network! With the payloads received, the next step would be to send them to an application. LNSs typically -support a number of integrations like HTTP, MQTT, or bespoke integrations with platforms like AWS -IoT or Azure IoT. +support several integrations like HTTP, MQTT, or bespoke integrations with platforms like AWS IoT or +Azure IoT. + +## Practice + +Now that data is being sent to the Helium Network, experiment with the device. + +1. Try changing the data being sent by the device. How would you send the payload, "Hello, World!"? + The current payload is being sent in hexadecimal format. Tools like + [asciitohex.com](https://www.asciitohex.com/) can help convert data types. +1. LoRaWAN provides the 'port' field which helps separate data types. For instance, multiple sensors + can run on the same device under different ports or status updates like battery level can be sent + on a different port than sensor data. +1. The device is currently sending data every 30 seconds. Try changing the interval to 10 seconds. + How would this affect the battery life of the device? From 938e09bbf71ac8c0aaea8b51d7f75f3d8c318b5d Mon Sep 17 00:00:00 2001 From: Joey Hiller <1965053+jthiller@users.noreply.github.com> Date: Mon, 13 Jan 2025 16:39:34 -0800 Subject: [PATCH 15/16] post-rebase placement of Learn LoRaWAN docs in sidebar --- sidebarsDocs.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sidebarsDocs.js b/sidebarsDocs.js index b644db237..94a78935a 100644 --- a/sidebarsDocs.js +++ b/sidebarsDocs.js @@ -77,6 +77,13 @@ module.exports = { }, ], }, + { + type: 'category', + label: 'Learn LoRaWAN', + link: { type: 'doc', id: 'network-iot/learn/understanding-lorawan' }, + items: ['network-iot/learn/understanding-lorawan', 'network-iot/learn/build-a-device'], + collapsed: true, + }, { type: 'category', label: 'Use Helium LoRaWAN', From d895ad66d2f565d1275ebec979f31d4fe2e1a274 Mon Sep 17 00:00:00 2001 From: Joey Hiller <1965053+jthiller@users.noreply.github.com> Date: Thu, 10 Jul 2025 17:09:57 -0700 Subject: [PATCH 16/16] rebase and docslab update --- yarn.lock | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8d436367f..cd948fae8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3300,6 +3300,11 @@ resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a" integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg== +"@types/google-protobuf@^3.15.12": + version "3.15.12" + resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.12.tgz#eb2ba0eddd65712211a2b455dc6071d665ccf49b" + integrity sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ== + "@types/gtag.js@^0.0.12": version "0.0.12" resolved "https://registry.yarnpkg.com/@types/gtag.js/-/gtag.js-0.0.12.tgz#095122edca896689bdfcdd73b057e23064d23572" @@ -3758,11 +3763,6 @@ ace-code@^1.23.4: resolved "https://registry.yarnpkg.com/ace-code/-/ace-code-1.24.1.tgz#129e070699e33d819a780c69be3b95e0a59e1342" integrity sha512-pRdEBQpwnXcH4l9BRhevjkICdKt3m7YEN5O+XJ4xtEpqZS5fUt9ZIXlb3UmLU9wl+tYAXm+IN1p0UVUS+C9DWQ== -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== - acorn-jsx@^5.0.0: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -5458,10 +5458,10 @@ docslab-docusaurus@^0.2.10: dependencies: docslab "^0.3.12" -docslab@^0.3.7: - version "0.3.7" - resolved "https://registry.yarnpkg.com/docslab/-/docslab-0.3.7.tgz#7b0e25676979d3dac211412e936b391d20f3004b" - integrity sha512-7sUcsfB1do2cB/uNPJzdZAwJNuTlvsY9ivPFGUpuY89o9kXGEDIoIhJ2cmJZiDemrxBfFUnGpAqQy7WBrQ04mA== +docslab@^0.3.12: + version "0.3.12" + resolved "https://registry.yarnpkg.com/docslab/-/docslab-0.3.12.tgz#fb7ec65e2b77a9f1f61b4c1125c275adffd32abd" + integrity sha512-+E6AqnrCHlUsjPfatNk3vMEdXPbn30IpQsMU0eX5kMENtcLERja/YgKBJarm32gpOUqejn9mhby7Sk8/KijDsA== dependencies: ace-code "^1.23.4" xterm "^5.2.1" @@ -5552,6 +5552,11 @@ dot-prop@^6.0.1: dependencies: is-obj "^2.0.0" +dotenv@^16.4.5: + version "16.6.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== + dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -6322,6 +6327,11 @@ globby@^13.1.1: merge2 "^1.4.1" slash "^4.0.0" +google-protobuf@^3.21.2: + version "3.21.4" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.4.tgz#2f933e8b6e5e9f8edde66b7be0024b68f77da6c9" + integrity sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ== + gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" @@ -11454,6 +11464,11 @@ xterm@^5.2.1: resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.1.tgz#b3fea7bdb55b9be1d4b31f4cd1091f26ac42afb8" integrity sha512-cs5Y1fFevgcdoh2hJROMVIWwoBHD80P1fIP79gopLHJIE4kTzzblanoivxTiQ4+92YM9IxS36H1q0MxIJXQBcA== +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"