layout | title | date | categories | tags |
---|---|---|---|---|
post |
Bring Azure IoT to Mobile Phones with React Native |
2020-06-10 16:16:06 +1000 |
code |
azure iot hub device sdk react-native android ios mobile |
TL;DR If you want to skip the blurbs, just go straight to steps to reproduce or chceckout the code on GitHub.
I'm personally interested in the IoT space and after a few experiments with Azure IoT Hub and some samples I wanted to run some code on a real device rather than simulating one from the console. I have a spare Android mobile phone and I thought it would be great to deploy and run some code on that as a PoC.
Time is precious, so I did not want to spend a lot of time on boiler plate / infrastructure set-up, so I chose React Native as a base as I'm already familiar with it and it is easy to set up and it pretty much provides you with a deployable app shell with a few commands on the CLI. Also, this would open up avenues to iOS devices later.
For the same reason as above, I did not want to write any custom code to interact with the IoT Hub but rather make use of Microsoft's official Azure IoT Hub device SDK.
The Azure IoT Hub device SDK for Node.js needs a node-js runtime, which does not exists on Android.
After some googlin' I found out there is a pretty active project called nodejs-mobile and even better - they published a React Native plugin: nodejs-mobile-react-native . It allows you to create a node-js aplication project and run it alongside your React Native project. It provides an api for inter-process communication between the React Native app and the node-js app (which is running in a different thread). Both, the node-js app and the node-js runtime are deployed alogside the React Native application bundle (because they're part of it).
Now that we've established that we can run code that requires a node-js runtime alogside a React Native app, it should be possible to have an android device talking to Azure IoT Hub via javascript sdk with only writing a few lines of code, right? So let's try it out.
-
Prerequisites
- Windows 10
- Node.js v12.18.0 x64 (or compatible)
- An Azure IoT Hub / device created and a device connection string available to use to connect the app.
-
Create the app shell React-Native project
- Follow the instructions on 'getting started with react native' - make sure you use the 'React Native CLI Quickstart', not Expo which is default.
- I chose to use TypeScript:
npx react-native init ReactNativeAndIot --template react-native-template-typescript
cd ReactNativeAndIot
- Start the Android Emulator
- Make sure you can run the app in the simulator after it has been generated:
npx react-native run-android
-
Install nodejs-mobile-react-native:
npm install --save nodejs-mobile-react-native
-
In the
nodejs-assets/nodejs-project
folder, remove thesample-
prefix from the files so that you have amain.js
and apackage.json
file. Themain.js
file becomes the entry point for the node-js application running alongside the react-native app. -
Replace the content of
App.tsx
with the following content (which will start the node-js app, listen for messages and display them in aText
component).import React, { Component } from 'react'; import { SafeAreaView, ScrollView, View, StatusBar, Text, } from 'react-native'; import nodejs from 'nodejs-mobile-react-native'; class App extends Component<{}, { logs: string[]; message: string }> { constructor(props: any) { super(props); this.state = { logs: ['starting'], message: '' }; } componentWillMount() { nodejs.start('main.js'); nodejs.channel.addListener( 'message', (msg) => this.log(msg), this, ); this.log('done, waiting for messages from channel'); } public render() { return ( <> <StatusBar barStyle="dark-content" /> <SafeAreaView> <ScrollView contentInsetAdjustmentBehavior="automatic"> <View> {this.state.logs.map((s, i) => ( <Text key={`log${i}`}>{s}</Text> ))} </View> </ScrollView> </SafeAreaView> </> ); } private log(msg: string) { this.setState({ logs: [...this.state.logs, msg] }); } } export default App;
the resulting output should be as follows:
starting done, waiting for messages from channel from node-js: Node was initialized.
-
If you see an error in the metro server console relating to a
Duplicate module name
error, please follow the instructions here. -
Now we can add the Azure IoT Hub device SDK:
-
Change to the node-js project directory:
cd ./nodejs-assets/nodejs-project
-
Install Azure IoT Hub device SDK and HTTP transport package
npm install --save azure-iot-device azure-iot-device-http
-
We're now adding code to connect to IoT hub and to send/receive messages using the Azure IoT Hub device SDK using Http transport. Please note that you need your device connection string here to connect via
Client.fromConnectionString(...)
- Please note that this is a PoC only and connection string should not be checked into source code. Also, for security considerations, I suggest to read "Understand Azure IoT Hub security" from the Microsoft Docs -
In the
nodejs-assets/nodejs-project
folder replace the content ofmain.js
with the following:const Client = require('azure-iot-device').Client; const Protocol = require('azure-iot-device-http').Http; const Message = require('azure-iot-device').Message; var rn_bridge = require('rn-bridge'); rn_bridge.channel.on('message', (msg) => { const message = generateMessage(msg); sendLog('Sending message: ' + message.getData()); client.sendEvent(message); }); function sendErr(theErr) { rn_bridge.channel.send('Err: ' + theErr); } function sendLog(theMsg) { rn_bridge.channel.send('Msg: ' + theMsg); } function messageHandler(msg) { sendLog('Received message: Id: ' + msg.messageId + ' Body: ' + msg.data); client.complete(msg); } function generateMessage(messageText) { const data = JSON.stringify({ deviceId: 'iot-brick', text: messageText, }); const message = new Message(data); return message; } const client = Client.fromConnectionString( '<YOUR DEVICE CONNECTION STRING>', Protocol, ); client.on('connect', () => sendLog('Client connected')); client.on('error', (err) => sendErr(err.message)); client.on('message', messageHandler); client.open().catch((err) => { sendErr('Could not connect: ' + err.message); });
-
In the React Native app, we'll add a text field and button to send text-based messages to IoT Hub replace the content off the
App.tsx
with the following:import React, { Component } from 'react'; import { SafeAreaView, ScrollView, View, StatusBar, Text, Button, TextInput, } from 'react-native'; import nodejs from 'nodejs-mobile-react-native'; class App extends Component<{}, { logs: string[]; message: string }> { constructor(props: any) { super(props); this.state = { logs: ['starting'], message: '' }; } componentWillMount() { nodejs.start('main.js'); nodejs.channel.addListener( 'message', (msg) => this.log('from node-js: ' + msg), this, ); this.log('done, waiting for messages from channel'); } public render() { return ( <> <StatusBar barStyle="dark-content" /> <SafeAreaView> <ScrollView contentInsetAdjustmentBehavior="automatic"> <View> {this.state.logs.map((s, i) => ( <Text key={`log${i}`}>{s}</Text> ))} </View> </ScrollView> <View> <TextInput placeholder="Type message here!" onChangeText={(text: string) => this.setState({ ...this.state, message: text }) } value={this.state.message} /> <Button title="send!" onPress={() => nodejs.channel.send(this.state.message) } /> </View> </SafeAreaView> </> ); } private log(msg: string) { this.setState({ logs: [...this.state.logs, msg] }); } } export default App;
-
Now you should be able to send messages to the IoT Hub using the text field / button on the UI. Cloud-to-Device messages should be displayed in the log.
-
I have borrowed heavily from the azure-iot-sdk sample code.
-
All code can be found in this repo on GitHub.
It's possible to run node-js alongside React Native applications and therefore make use of the Azure IoT Hub device SDK for Node.js on mobile devices.