Project tutorial

Poke-e-mom: Send a Poke Reminder over LoRa Wireless

A Universal Windows poke notification App connected over LoRa wireless to Windows Remote Virtual Arduino virtualized and prototype hardware.

  • 4,241 views
  • 4 comments
  • 14 respects

Components and supplies

Necessary tools and machines

Apps and online services

About this project

This project is an extension of my previous Windows Remote Virtual Arduino  which itself is an extension of the Windows Remote Arduino project by the Windows IoT Team. In this project we enable long range wireless remoting over LoRa wireless and apply it to a fun poke notification use case with a Windows UWP App as the user interface.

LoRa Overview

LoRa ( Low Power Radio ) is an emerging low energy wireless physical (PHY) layer which features the spread spectrum technology used in modern mobile phone networks to enable very high noise immunity, very long range and very low power Internet of Things (IoT) applications. LoRa itself is an patented technology aquired and commercialized by Semtech and is gaining traction through the formation the LoRa Alliance spearheaded by industry giant microchip as their primary IoT offering.

While industry is focusing on telecom network partners to roll out LoRa side by side with other mobile network M2M offerings. Perhaps more interestingly LoRa has breached the cost barrier enabling private and DIY LoRa networks to form.

This is enabling new idea's to start bubbling up such as the exceptionally interesting idea of a global network Peer-to-Peer LoRA network which recently resulted in a successful kickstarter for the The Things Network (TTN) which I backed and have become a community member. TTN is now busy manufacturing a low cost LoRa gateway and establishing an open community driven network enabling global roaming and access to LoRa connectivity for the hackster type innovators and application of the world. Applications like poke-e-mom !

Poke-e-mom Explainer Video

Poke-e-mom

Poke-e-mom is a practical LoRa enabled application invented to solve the problem our ever forgetful disappearing son. Having tried watches and stern reminders we now figure the only way to get him home for dinner is to send him a poke.

It's an ideal application for LoRa because he is typically not far away, usually playing football in the nearby court or gaming with a neighbour.  Much too far for Wifi but well within LoRa's multi kilometer range. 

We could send him out with a mobile phone but that would just lead to non-stop gaming so really we need a low cost wearable gadget he can take with him. 

Prototyping

It's almost unbelievable how much technology goes into replacing simply stepping out the door and yelling 'Dinner time'. To minimise the number of things that can go wrong prototyping is best done with as many off-the-shelf as possible.

This is a good reason to use libraries like windows-remote-arduino as a first pass proof of concept implementation. By following the ideas of my previous windows remote virtual arduino project it's possible to fully virtualize the prototype before moving to real hardware further streamlining development. 

Poke by LoRa Remote

The Windows Remote Virtual Arduino library defines the IStream serial interface which can be used to extend the remoting library with alternative transports. In this project what we essentially do is encapsule REST API access to the virtualbreadboard.io LoRaWAN network server as an IStream enabling remoting over LoRaWAN.

Architecture

In a Nutshell

The UWP poke-e-mom App instantiates a VbbIoTLoraStream implementation of the Windows-Remote-Arduino IStream interface initializing it with the encryption keys required for secure communications with the poke-e-mom LoRa gadget. The IStream is used to initialize an instance of the Windows-Remote-Arduino arduino API. When the App poke button is pressed a digitalWrite irmata message is encoded and enqueued via the REST API with the virtualbreadboard.io LoRa server hosted in Azure. When the poke-e-mom LoRa gadget RN2483 transciever next communicates with the network server the Firmata message is dequeued and sent as a LoRa downlink message. The Serial Bridge decodes this message and streams it to the serial port. The Firmata firmware running on the Arduino UNO receives the Firmata message, decodes it and executes the embeddeddigitalWrite command. This activates the notification hardware which is just a L.E.D in the prototype but could be a buzzer, vibrator or other notification hardware.

Universal Windows App (UWP) App : This is the user interface application which can be run on any Windows UWP device. It connects the button press actions with the remoting libraries and manages the encryption keys specific to the end user device to be poked.

Windows-Remote-Arduino: Is the remoting API that enables the control of remote Arduino devices hosting Firmata firmware with stream connections.

IStream: Is the serial interface exposed by the windows-remote-arduino to be implemented when adding new data transport layers such as LoRa in this case.

VbbIoTLoRaStream : Is the implementation of IStream which enables the windows-remote-arduino to operate over LoRa. VbbIoTLoRaStream encapsulates access to virtualbreadboard.io Azure REST API and exposes it as an IStream

Azure REST API : Is a Azure Cloud API Web App which exposes data access to the virtualbreadboard.io LoRa Server

VBB.IoT LoRa Server : Is a collection of Azure Cloud Worker Roles servicing LoRa gateway UDP endpoint's and providing LoRa Network and Application services accessed via the virtualbreadboard.io dashboard.

LoRa Gateway : Edge interface for the LoRa PHY which listens for LoRa broadcast messages and forwards the messages to one or more LoRa network servers. Also delivers response messages as scheduled by the LoRa network server responsible for the end device.

Microchip RN2483 :  Is a LoRaWAN module including LoRa PHY transceiver and certified LoRaWAN stack with an easy to access serial command set.

VirtualBreadboard (VBB) has new support for RN2483 which includes a virtualized LoRa gateway making the RN2483 ideal for experimenting with LoRa

LoRa Serial Bridge : A RN2483 client which drives the RN2483 command set to send and receive serial messages over a UART. VirtualBreadboard Runtime (VbbRT) is used to implement the Serial bridge. VbbRT is supported in VBB.

Arduino UNO + Firmata : An Arduino UNO hosts the Firmata firmware to drive the  notification Hardware. It's task is to interpret the Firmata encoded messages sent by the windows-virtual-arduino library. Firmata is supported in VBB.

Notification Hardware : The notification hardware can be anything from a buzzer, a vibration or MP3 voice player saying the word Poke. For the proof of concept a simple L.E.D is used. L.E.D's are supported in VBB.

Virtualization

No doubt it's because I am the author of VirtualBreadboard (VBB) but I vastly prefer to virtualize my solutions before committing to hardware. This is especially true when there is such a heavy software and infrastructure component to the solution. Poke-e-mom is absolutely no exception. In fact I spent a lot of time building the extra LoRa client and server components just so I can virtualize LoRa applications and help others do the same.

Top Level Walk Through

As solutions become increasingly connected, integration of a multitude of technology elements becomes the primary task. One of the great things about virtualization is you can put all of those elements on one screen aiding in understanding and troubleshooting. Let's take a look at the elements of this virtualized 'one page' explainer of the solution. 

Power up Solution Virtualization: I have included the RN2483Stream as a standard LoRa example in VBB. Everyone can open the example in preview mode and even better if you have licensed the LoRa and Firmata VBB modules you will be able to just press the Power Up button to run the virtualized solution.

LoRa Join Request Accepted : The first thing that happens is the serial bridge App ( implemented as a VbbRT script ) initializes the RN2483 driver and requests a LoRa Over-the-air-activation (Otaa) Join with the LoRa server. When accepted the Yellow L.E.D is driven from a GPIO pin of the RN2483 

    _sp = SerialPort(self,0) #Capture Serial Port 0 to bridge with RN2483
    ..
    if _rn.JoinOTAA(): 
        _rn.ToHigh(JOINED_LORA_OUTPUT_PIN)

The virtual RN2483 includes an embedded virtual gateway emulator so you don't need access to an actual gateway. The virtual gateway connects to the LoRa network server assigned in the RN2483 modules properties. By default this is set to virtualbreadboard.io but you can change this to point to your own server.

The default encryption keys are also read from virtual RN2483 module properties. By default the keys are automatically calculated from the LoRa license GUID but you can can override these default in the properties or in code.

Poke Button Transmits Firmata Commands : In the same screenshot the UWP Poke-e-mom App is also running Visual Studio 2015. I have included the full application in the GIT Code section below. When you run the App and press the Poke button the virtual Arduino code is invoked in the Button Press Event.

   arduino.pinMode(13, PinMode.OUTPUT);
   arduino.digitalWrite(13, PinState.LOW);
   arduino.digitalWrite(13, PinState.HIGH);

This is the familiar Arduino code to send a LOW to HIGH trigger edge to PIN 13. The trigger edge then activates the notification hardware. 

What happens next is where it gets interesting. The generated firmata commands are captured by the IStream interface of the VbbIoTLoraStream class which encrypts the message using the unique device specific LoRa AppKey and then calls the Azure VBB.IOT API to add the message to the device downlink queue

    string azureCloud = " http://vbbiotapi.azurewebsites.net/api/Stream"; 
    ..
    HttpResponseMessage msg = await client.PostAsync(azureCloud, firmataMsg);

In Response to the Next Uplink: The RN2483 is currently a LoRa 'Class A' device which means it is a polled bi-directional communication device. This basically means the end-point ( commonly called a mote ) must initiate communications. In LoRa speak this is called an uplink. How often a mote can initiate an uplink, and hence the delay before your message arrives is calculated from many considerations. The primary factor is that LoRa lives in the 868Mhz/915Mhz unlicensed radio spectrum. This a free but regulated radio band with a 1% duty cycle requirement among others. This means your device can only be 'on the air' for at most 1% of the time and since the required 'on the air' time depends on how far from the gateway you are you can see it's all a bit 'up in the air'.

The upshot though is LoRa is not a general purpose real-time bidirectional high availability solution so design accordingly.

The RN2483 is a pre-certified device and enforces these requirements on your behalf so when testing on real hardware you are likely to see many 'busy' messages and enforced time delays. This can get in the way of development so a nice feature of the Virtual RN2483 is these limitations do not apply. Actually in a future version I will additional models of real-world limitations as a testing tool and you will certainly want to do extensive testing of these scenarios before rolling out commercial solutions.

In summary: You have to wait until the next uplink before your message will be delivered.

The downlink contains the firmata commands :  LoRa applications are typically very 'sleepy' low power application. This means they spend most of their time in the ultra low power configurations needed to extend their battery lives to the order of 10 years. One of the clever power saving techniques used by LoRa to minimise the 'radio on' time is the scheduled response window. After a uplink message is sent the LoRa network server and gateway synchronize with the mote to schedule a series of tight downlink response windows. Typically the mote will go back to sleep after sending the uplink and wake up again in the response windows to peek for a response. If a response is 'in the air' it will stay awake and receive the full downlink message.

Causing the firmata firmware to invoke a Poke : In this application the Serial Bridge then decodes the downlink message to unpack the firmata commands and then writes those commands to a UART connected to the Arduino UNO running the firmata firmware. The firmata application then translates those commands into hardware actions as per the firmata command protocol. In this case a L.E.D is activated but it could be a timed buzzer or other one-shot Poke sensitive to the rising edge trigger.

Virtualization Step for Step

If you want to virtualize this this exact solution step for step you need

  • VBB
  • VBB LoRa Module License
  • VBB Firmata Module License
  • Visual Studio 2015
Note: When you purchase the LoRa Module license a virtualbreadboard.io account and device will have been automatically created for you based on the LoRa Module GUID license key.

Steps :

1. Download and Install VBB

2. Activate the LoRa and Firmata Module Licenses 

3. Download and Install Visual Studio 2015

4. Clone the Poke-e-mom git repository and run the App

5. Open the RN2483Stream example in VBB

6. Press 'Power Up'

7. Click the Poke-e-mom App

8. You should see Pin 13 L.E.D activate as per animated screenshot above.

Realizing the Virtualization

Virtualized solutions are most useful when they are easily converted into physical solutions. I spend a lot of time trying to reduce the gap between virtual and real and this has led to many innovations including the new VbbRT runtime platform and language. 

If you want to realize this exact solution step for step you need the following

  • Arduino UNO with Firmata application loaded
  • Microchip LoRa Mote
  • LoRa Gateway
  • VbbRT8-Prog USB programming dongle
  • VbbRT8-28 Microcontroller 

1. Create or log onto your exising virtualbreadboard.io account

Note: When you purchase the LoRa Module license a virtualbreadboard.io account and device will have been automatically created for you based on the LoRa Module GUID license key but this is optional for physical realisation in which case you need to manually create an account.

2. Register or access a registered LoRa device to retrieve the AppEUI, DevEUI, AppKey.

Note: The previous virtualization steps assumed the keys were automatically assigned from the license but to physically realize the application you need to write the keys to the actual device.

3. Register vbbiot.cloudapp.net on port 1680 as one of your network servers for your LoRa gateway

4. Wire up the breadboard as per the diagram in the schematic section

Note: The prototype is using the battery power at 3.3V from the Mote

5. Program the Serial Bridge application into the VbbRT-28 device

6. Run the Poke-e-mom App

7. Power Up and press the Poke Button!

Arduino UNO 

The Arduino UNO in this application hosts the firmata firmware which is shipped as the standard UNO firmware so should work 'out of the box'. Otherwise you will need to program the firmata firmware from the Arduino IDE. Firmata is included in the standard Arduino IDE release.

Microchip LoRa Mote

The microchip LoRa Mote development board is a battery powered breakout board for the RN2483 module. It features a USB serial interface, an OLED and demo firmware. For this project though I only use it as a convenient breakout and hold the onboard microcontroller in reset. I also use the battery power to power up the circuit.

LoRa Gateway

Getting access to a LoRa gateway is probably the trickiest part because it's all still quite new. There are several commercial gateways in the $1000-$2000 range but there is a stream of low cost gateways becoming available. For testing I built a gateway by following these MAKE your own 200€ LoRa gateway instructions by Mirakonta. That project won a silver IoT peoples choice award so respect for that effort.

There are 2 tips during the build.

  • Make sure you use a heavy duty supply or externally powered USB hub as the Multiech LoRa module uses a fair amount of power 
  • Set the LoRa server as : vbbiot.cloudapp.net, 1680

In the future you might be able to access a public LoRa network or be in a network that has roaming access to the virtualbreadboard.io server. See the further thoughts section at the bottom of the project.

LoRa Serial Bridge

The serial bridge is implemented in the Vbb scripting language and deployed to the VbbRT8-28 low power 8-bit microcontroller. The Serial Bridge is responsible for managing the LoRa encryption keys (see LoRa Security) below and negotiating with the RN2483 and serial ports to bridge the LoRa messages and serial UART data.

The VbbRT8-28 is programmed directly from VBB using the USB VbbRT8-PROG programmer. I am creating another Hackster tutorial to explain all the necessary steps but it's simply a matter of wiring up the programmer and clicking program from VBB to cloud compile and program the code into a VbbRT micro.

Future Tip: In addition to VbbRT for VBB Desktop and VbbRT8 micro's. Vbb Script will soon also run in the VbbRT container for Windows 10 IoT Core, Azure Cloud and Windows Store version of VBB. 

LoRa Security

LoRa has two integrated layers of security which will often be sufficient for most applications. Internally uses a Network key secures communicate between the network server and the LoRa endpoint ( RN2483 ). This key is generated dynamically when a mote makes a join request during the Over-the-air-activate negotiation. A second Application Key is used to secure the user data and this is a secret key shared between the application server and the mote. In addition we use this AppKey to secure communications between the poke-e-mom UWP App and the virtualbreadboard.io API.

The virtualbreadboard.io LoRa server is acting as both the network and application server so is managing the application key for you so you need to logon and access the keys for your device.

When you register a device you can set your own keys or generate random keys. These keys need to be copied over to the Serial Bridge code to enable the RN2483 to communicate with the virtualbreadboard.io LoRa network server.

	#Automatically assigned by license
	_rn.AppEUI = "f8dc017950d84896"
	_rn.DevEUI = "a73590bdf4fcd407"
	_rn.AppKey = "f8dc017950d84896a73590bdf4fcd407"
Note: The keys shown here are just examples. You need to register your own device and generate your own keys.

First Contact

Putting it all together you should get the most amazing, earth-shattering result..

A L.E.D. turns. on. OMG!

Ok.. so maybe it's a bit underwhelming when that's all there is to see but keep in mind you have setup the infrastructure to control and monitor just about anything anywhere on the planet.. not too bad.

Further Thoughts: Public LoRa Networks

It's still early days with LoRa and the opportunities are only just starting to be explored. Already major telecom operators such KPN in The Netherlands are rolling out LoRa networks. LoRa is a natural fit for existing mobile network operators who can locate LoRa gateways on existing mobile towers creating a new connectivity offering for a very low cost.

Even more exciting is the growing possibility of a shared public network. This was the motivation behind a recent successful kickstarter campaign for the The Things Network (TTN) who are developing low cost professional quality gateways and attracted considerable support for this decentralised vision.

Today virtualbreadboard.io stands alone as a testing environment to support the virtual RN2483 VBB modules and virtual gateway along with experiments with your own physical gateways.

Tomorrow the plan is to partner with other LoRa networks such as TTN and KPN to enable where possible roaming between the respective networks reducing the friction for innovative new products and Hackster type startups entering the IoT space.

This is an extremely exciting future aspect of LoRa and one I hope to participate in fully. 

Next Steps for Poke-e-mom

Poke-e-mom is the result of a brainstorming session for a suitable project for the Hackster 'Worlds Largest Arduino Maker Challenge' but already I am planning to take it further as I think it's a really neat project.

I reserved www.poke-e-mom.com and have begun thinking about the design of the wearable version of the Poke gadget. 

The infrastructure created in this project is actually bidirectional so it's also possible to send Pokes back by adding one or more buttons as an OK response or maybe a You Forgot Me! poke - kids are not the only ones who can forget an appointment! These can be turned into Push Notifications cloud side. 

The plan is to start with simple poking notifications but different versions could feature GPS locators for time of arrival updates or who knows what else.

The next challenge is to research how to create a truly low cost LoRa gateway suitable for bundling with the Poke wearable gadget to breakthrough the LoRa gateway access barrier.

There has been some interesting work on low cost Single Channel Gateway's in the TTN community which look promising and a MakeFair exhibit would be an ideal way to kick off a crowdfunding campaign. So if I get that chance I think the next step for Poke-e-mom is likely to be a  LoRa Personal Gateway + Poke Wearable crowdfunding campaign.

Well it's been quite the effort - amazing what one goes through for a little respect!

Hope you enjoyed the project and have learn't a little about LoRa along the way. Maybe you even have some new ideas bubbling up in that creative mind of yours as I think LoRa opens up alot of exciting new possibilities.

Final Thanks

A big final shout out to Microsoft who are a sponsor of VirtualBreadboard through the BizSpark program without which the development of the Azure based LoRa network server wouldn't be possible.


Code

Serial BridgePython
Serial Bridge software coordinates with the RN2483 LoRa module to periodically send buffered UART serial data as LoRa uplink messages and decode and transmit on the UART LoRa dowlink messages. Implemented in Vbb Script ( syntax is similar to Python ) and executed in the 8-bit Vbb Runtime (VbbRT8) microcontroller suitable for low-power applications.
# LORA STREAM BRIDGE
# 
# Lora Virtual Serial Bridge application for streaming a serial connections over
# LORA connection
#
# Uses:
# * Remote Firmata Stream
# * Generic Serial Interfacing 

final JOINED_LORA_OUTPUT_PIN = 0
final SWITCH_INPUT_PIN = 1
final ACTIVATE_OUTPUT_PIN = 2
 
#POLL Period is influenced by distance to the gateway and cost
#Poll too frequently and bandwidth will be denied especially at long distances from gateway
#Costs also increase for higher POLL period
#For testing lower poll period more convienent
final POLL_PERIOD_MILLIS_MAX as long = 5000
final POLL_PERIOD_MILLIS_MIN as long = 1000

#LORA packets have a maximum 
final MAX_PACKET_SIZE as int = 50

_rn as RN2483
_sp as SerialPort
msg as string
nextPoll as long
buffer = array of byte(50)

def Init():
	_sp = SerialPort(self,0)  #Capture Serial Port 0 to bridge with RN2483
	_rn = RN2483(self)		  #Capture Serial Port 1 for use with RN2483
  
	_sp.Start(57600L) #Standard Firmata Serial Stream expects 57600 baud
  
	_rn.Reset()
	_rn.SetRX2(3,869525000)
	
	_rn.AsOutput(JOINED_LORA_OUTPUT_PIN)
	_rn.AsInput(SWITCH_INPUT_PIN)
	_rn.AsOutput(ACTIVATE_OUTPUT_PIN)
  
	#Automatically assigned by license for virtualization
	#Copy over from virtualbreadboard.io for realisation
	#_rn.AppEUI =  "yourAppEUI"
	#_rn.DevEUI = "yourDevEUI"
	#_rn.AppKey = "yourAppKey"
	
	if _rn.JoinOTAA(): 
		_rn.ToHigh(JOINED_LORA_OUTPUT_PIN)
		
	nextPoll = millis()
 
	_sp.Read(buffer,1,MAX_PACKET_SIZE) #Flush
	_sp.Print("\u00FF")//Inject firmata reset	 
	 
def Loop():
	if _rn.IsJoined:
		if millis() > nextPoll or _sp.Available() != 0:
			readLen = _sp.Read(buffer,1,MAX_PACKET_SIZE);
			#Can't send a zero length so stream apps insert length in header	
			buffer[0] = readLen
			fromServer = _rn.SendAndReceive(buffer,0,readLen+1)
			_sp.Print(fromServer)
			nextPoll+=POLL_PERIOD_MILLIS_MAX

	delay(POLL_PERIOD_MILLIS_MIN)
VbbIoTLoraStream : IStreamC#
Implementation of the windows-remote-arduino IStream to expose a secure LoRa connection as a serial stream connection suitable for sending and receiving firmata commands generated by the windows-remote-arduino library
using Microsoft.Maker.Serial;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;

namespace io.virtualbreadboard.api
{
    /**
       Implements the Firmata IStream with a VirtualBreadboard.io Lora Stream API

      //////////////////////////////////////////
       // VirtualBreadboard.IO LORA
       //////////////////////////////////////////
       Firmata requires synchronous so provides a synchronous buffer to asynchronous background WebClient Calls

       Class A Lora devices are 'sleepy' polling devices so this is not a real-time stream but is instread

       read/writing into a Queue maintained by VirtualBreadboard.io network server and accessed via the Stream API.

       Security - LoraWAN has integrated security. The AppKey is a shared secret key registered with the VirtualBreadboard.io Lora
       application server. The Stream API interface uses this AppKey to sign data sent and received to the device.

       //////////////////////////////////////////
       // ISTREAM
       //////////////////////////////////////////
       IStream is the stream interface to remote arduino : https://github.com/ms-iot/remote-wiring

       Serial is the transport layer, which provides the physical communication between applications and the Arduino device.
       IStream is the interface which defines the requirements of a communication stream between the Arduino and the application itself.
 
       @author James Caska , www.virtualbreadboard.com
       */

    public class VbbIoTLoraStream : IStream
    {
        private const int MAX_PACKET_SIZE = 10; //Maximum data payloa in a LORA packet.
        private const int GUID_LENGTH = 36;

        private Queue<byte> inputBuffer;
        private Queue<byte> outputBuffer;
        private Queue<byte> commandBuffer;
        private bool taskRunning;
        private bool _IsConnecting;

        public event IStreamConnectionCallback ConnectionEstablished;

        public event IStreamConnectionCallbackWithMessage ConnectionFailed;

        public event IStreamConnectionCallbackWithMessage ConnectionLost;

        private string _appEUI;
        private string _devEUI;
        private string _appKey;
        private int _pollPeriodSeconds;

        private int _sequenceNo; //Tracking the sequence no

        private Dictionary<string, string> _fixedResponses;

        ///Security
        private SymmetricKeyAlgorithmProvider _aesCbcPkcs7;

        private CryptographicKey _aesAppKey;

        /// <summary>
        /// The  AppEUI, AevEUI, AppKey are defined by the LoRa specification for Over-The-Air-Activation 
        /// These keys are obtained by creating an account and registering a device with virtualbreadboard.io network server 
        /// The AppKey is used to secure communications between the UWP App and the virtualbreadboard.io network server
        /// </summary>
        /// <param name="appEUI">Application Id</param>
        /// <param name="devEUI">Device Id</param>
        /// <param name="appKey">Unique Application Encryption Secret Key</param>
        public VbbIoTLoraStream(string appEUI, string devEUI, string appKey, int pollPeriodSeconds)
        {
            _appEUI = appEUI;
            _devEUI = devEUI;
            _appKey = appKey;
            _pollPeriodSeconds = pollPeriodSeconds;
            _fixedResponses = new Dictionary<string, string>();

            inputBuffer = new Queue<byte>();
            outputBuffer = new Queue<byte>();
            commandBuffer = new Queue<byte>();

            _aesCbcPkcs7 = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesCbcPkcs7);

            IBuffer keyBuf = CryptographicBuffer.DecodeFromHexString(appKey.ToUpper());

            // Create an AES 128-bit (16 byte) key
            _aesAppKey = _aesCbcPkcs7.CreateSymmetricKey(keyBuf);
        }

        /**
            Some queries have the same response..
        */

        public void AddFixedResponse(string request, string response)
        {
            _fixedResponses.Add(request, response);
        }

        public class StreamWrite
        {
            public string AppEUI;
            public string DevEUI;
            public string Data;
            public int SequenceNo;

            public StreamWrite(string appEUI, string devEUI, string data, int sequenceNo)
            {
                this.AppEUI = appEUI;
                this.DevEUI = devEUI;
                this.Data = data;
                this.SequenceNo = sequenceNo;
            }
        }

        public class StreamRead
        {
            public StreamRead()
            {
            }

            public string Data { get; set; }
            public int SequenceNo { get; set; }
        }

        /// <summary>
        /// Encrypt with the AppKey
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public byte[] Encrypt(byte[] data)
        {
            // Creata a 16 byte initialization vector
            //Empty: uses prepended guuid as initialisation vector
            IBuffer iv = new byte[_aesCbcPkcs7.BlockLength].AsBuffer();

            // Encrypt the data
            byte[] encryptedData = CryptographicEngine.Encrypt(_aesAppKey, data.AsBuffer(), iv).ToArray();

            return encryptedData;
        }

        /// <summary>
        /// Decrypt with the AppKey
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public byte[] Decrypt(byte[] data)
        {
            //Empty: uses prepended guuid as initialisation vector
            IBuffer iv = new byte[_aesCbcPkcs7.BlockLength].AsBuffer();

            byte[] decryptedData = CryptographicEngine.Decrypt(_aesAppKey, data.AsBuffer(), iv).ToArray();

            return decryptedData;
        }

        /// <summary>
        /// Returns the next paypload to send.
        /// Encrypt[appkey, guuid:data]
        /// </summary>
        private string DequeueOutgoing()
        {
            MemoryStream stream = new MemoryStream();

            string nounce = Guid.NewGuid().ToString();

            stream.Write(System.Text.Encoding.UTF8.GetBytes(nounce), 0, GUID_LENGTH);

            //The first connection will flush the queue and synchronise. Don't send data during this.
            if (!_IsConnecting)
            {
                lock (outputBuffer)
                {
                    int packetSize = Math.Min(outputBuffer.Count, MAX_PACKET_SIZE);

                    while (packetSize != 0)
                    {
                        stream.WriteByte(outputBuffer.Dequeue());
                        packetSize--;
                    }
                }
            }
            byte[] payloadBytes = stream.ToArray();

            payloadBytes = Encrypt(payloadBytes);

            return Convert.ToBase64String(payloadBytes);
        }

        /// <summary>
        /// Invokes the VirtualBreadboard.io REST API interface and exchanges data
        /// </summary>
        /// <returns></returns>
        private async Task<StreamRead> InvokeVbbIoTAPI(string payload)
        {
            //string azureCloud = "http://localhost:57334/api/Stream";
            string azureCloud = " http://vbbiotapi.azurewebsites.net/api/Stream";

            string request = JsonConvert.SerializeObject(new StreamWrite(_appEUI, _devEUI, payload, _sequenceNo));

            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            StringContent firmataMsg = new StringContent(request, Encoding.UTF8, "application/json");

            HttpResponseMessage msg = await client.PostAsync(azureCloud, firmataMsg);

            byte[] response = await msg.Content.ReadAsByteArrayAsync();

            string jsonResponse = System.Text.Encoding.UTF8.GetString(response);

            StreamRead readResponse = JsonConvert.DeserializeObject<StreamRead>(jsonResponse);

            if (readResponse == null)
            {
                throw new Exception("Invalid Server Response");
            }
            _sequenceNo = readResponse.SequenceNo;  //Update the sequence number to mark data received

            return readResponse;
        }

        /// <summary>
        /// In order to seed the CBC encryption without a IV the data prepends a guid in the form guid:data
        /// When guid prefix is verified as a valid guid the data section is enqueued into the input buffer
        /// If is not a valid guid then the server is not valid and could be security risk so the connection is closed.
        /// </summary>
        /// <param name="data"></param>
        ///
        private bool EnqueueIncoming(byte[] data)
        {
            int len = data.Length;

            if (len >= GUID_LENGTH)
            {
                string guidNounce = System.Text.Encoding.UTF8.GetString(data, 0, GUID_LENGTH);
                Guid validGuid;
                if (Guid.TryParse(guidNounce, out validGuid))
                {
                    lock (inputBuffer)
                    {
                        //Is a valid guid
                        for (int i = GUID_LENGTH; i < len; i++)
                        {
                            inputBuffer.Enqueue(data[i]);
                        }
                    }
                }

                return len > GUID_LENGTH;
            }
            else
            {
                throw new Exception("Invalid Server");
            }
        }

        /// <summary>
        /// Exchange Data with the API.
        /// </summary>
        private async Task SendReceiveTask()
        {
            _IsConnecting = true;
            taskRunning = true;

            try
            {
                while (taskRunning)
                {
                    var invokeAPI = await InvokeVbbIoTAPI(DequeueOutgoing());

                    byte[] decrypted = Decrypt(Convert.FromBase64String(invokeAPI.Data));

                    bool dataReceived = EnqueueIncoming(decrypted);

                    if (_IsConnecting)
                    {
                        RaiseConnectionEstablished();
                        _IsConnecting = false;
                    }

                    if (dataReceived)
                    {
                        //Poll faster while active to increase response time.
                        await Task.Delay(1000);
                    }
                    else
                    {
                        await Task.Delay(_pollPeriodSeconds * 1000);
                    }
                }
            }
            catch (Exception e)
            {
                if (_IsConnecting)
                {
                    RaiseConnectionFailed(e.ToString());
                }
                else
                {
                    RaiseConnectionLost();
                }
            }

            taskRunning = false;
        }

        private void RaiseConnectionFailed(string msg)
        {
            if (ConnectionFailed != null) ConnectionFailed(msg);
        }

        private void RaiseConnectionEstablished()
        {
            if (ConnectionEstablished != null) ConnectionEstablished();
        }

        private void RaiseConnectionLost()
        {
            if (ConnectionLost != null && taskRunning)
            {
                RaiseConnectionLost();
            }
            taskRunning = false;
        }

        ushort IStream.available()
        {
            return (ushort)inputBuffer.Count;
        }

        void IStream.begin(uint baud_, SerialConfig config_)
        {
            Task.Factory.StartNew(SendReceiveTask);
        }

        void IStream.end()
        {
            taskRunning = false;
        }

        ushort IStream.read()
        {
            lock (inputBuffer)
            {
                if (inputBuffer.Count == 0)
                {
                    return 0;
                }
                else
                {
                    return inputBuffer.Dequeue();
                }
            }
        }

        public bool connectionReady()
        {
            return true;
        }

        public void flush()
        {
            byte[] packet = commandBuffer.ToArray();

            commandBuffer.Clear();

            string packet64 = Convert.ToBase64String(packet);

            if (_fixedResponses.ContainsKey(packet64))
            {
                packet = Convert.FromBase64String(_fixedResponses[packet64]);
                lock (inputBuffer)
                {
                    foreach (byte b in packet)
                    {
                        inputBuffer.Enqueue(b);
                    }
                }
            }
            else
            {
                lock (outputBuffer)
                {
                    foreach (byte b in packet)
                    {
                        outputBuffer.Enqueue(b);
                    }
                }
            }
        }

        public void @lock()
        {
        }

        public ushort print(byte[] buffer_)
        {
            throw new NotImplementedException();
        }

        public ushort print(double value_, short decimal_place_)
        {
            throw new NotImplementedException();
        }

        public ushort print(double value_)
        {
            throw new NotImplementedException();
        }

        public ushort print(uint value_, Radix base_)
        {
            throw new NotImplementedException();
        }

        public ushort print(uint value_)
        {
            throw new NotImplementedException();
        }

        public ushort print(int value_, Radix base_)
        {
            throw new NotImplementedException();
        }

        public ushort print(int value_)
        {
            throw new NotImplementedException();
        }

        public ushort print(byte c_)
        {
            throw new NotImplementedException();
        }

        public ushort write(byte[] buffer_)
        {
            throw new NotImplementedException();
        }

        public ushort write(byte c_)
        {
            commandBuffer.Enqueue(c_);

            return 0;
        }

        public void unlock()
        {
        }
    }
}
Poke-e-mom
UWP App for Visual Studio 2015. Use this App to test drive the virtual or real notification hardware. Get your own keys from virtualbreadboard.io

Schematics

Prototype
Breadboard Prototype Layout
Schematic

Comments

Author

Profile800 yaemgme4da
James Caska
  • 2 projects
  • 25 followers

Additional contributors

  • Make your own 200€ lora gateway by Mirakonta

Published on

March 30, 2016

Members who respect this project

Jqp4ppvjSprint rzPasfotosander300x450Lilshredder23172573 1460724767377545 8437967682661243475 n lmt4reuxivPhotoDefaultImg 0547

and 6 others

Add projectSign up / Login