AziothubDeviceStreaming: Under the hood-The API Options
azure azure iothub csharp uwp dnetcore device service streaming devicestreaming
In a previous post a .Net Standard library that can be used in .Net Core, .Net Standard, UWP and Xamarin apps (last yet to be tested) was presented that implements the device and service functionality of IoT Hub Device. The previous blog in this series covered the use of thelibrary’s API when used in its simplest mode, the default single-shot mode. This blog discusses the use of the optional features of the library.
How to use the Updated UWPXaml app
API calls (recap).
To use the library an appp makes a call to instantiate the device (client) or the service:
- The Service app initiates communication by sending a message via the IoT Hub to a device
- The Device app is the IoT device connected to the IoT Hub that responds to message from the hub. Nb The UWP Xaml app is able to simultaneously invoke both ends of the pipe.
In this default mode the device and server operate in single shot mode. That is the device listens for a connection via IoT Hub, receives the message, processes it and sends it back. The service connects to the device via the same Iot Hub, sends it message then awaits the reply. A socket it created at each end only for the duration of each communication.
Calls are made to the DeviceStream_Device, DeviceStream_Service and DeviceStreamingCommon classes in the library. All of the Azure IoT Hub Device Streaming functionality, as in these classes in the IoT Hub SDK Echo sample, has been refactored (and extended) into these classes in the library. Where there is app specific functionality required by the classes, a delegate is used.
The calls to the API to instantiate the device and service from a host app are the static calls:
- Device_Stream_Device.RunDevice()
- DeviceStream_Svc.RunSvc()
Each call generates an instance of the required entity (Device or Svc). In default mode, there are some mandatory parameters to these calls. There are also some optional parameters for more extended calls. The use of these optional features is discussed below:
C# Optional Parameters 101
The optional parameters for method calls are indicated by being given default values in the method parameter. The default values are used when not specified in the method call. Optional method parameters need to be on the end of the method parameter list. You may use or not use any of the optional parameters but if one is used all of the optional parameters before it (on its left) must be used. For example:
public void RunDevice(string device_cs, bool keepDeviceListening = false, int count=0)
{
}
can be called with
RunDevice("constring");
or
RunDevice("constring", true);
In the first case, keepDeviceListening will be false in the first method and true in the second case.
The following would be invalid
RunDevice("constring", 27);
whereas the following is valid:
RunDevice("constring", true, 27);
API calls to make use of the Options
Settings
- ResponseExpected: If set, once a message has been received by the device and processed the response is sent back to the service.
- Keep Alive: Applies to both ends. If true, the sockets at both end are be kept alive between messages until the user signals otherwise.
- KeepDeviceListening: If set, once a message has been received and processed by the device and the connection is closed, the device will continue to listen for more connections. Otherwise it stops listening.
- AutoStart: This tells the Device app that when it first starts, immediately go into to listening for connections mode.
These settings can be under the command of the service and be transmitted along with the message to the device. Alternatively, the last two settings can be set directly in the device app.
The default for all of these settings is false, except for ResponseExpected which defaults to true;
The first two settings need to be sent with each message from the service to the device. If not, their defaults are used from then on until received again. The second two messages are set until a countermanding message is sent.
RunDevice API
public static async Task RunDevice(string device_cs,
ActionReceivedTextIO onRecvdTextD,
ActionReceivedText onDeviceStatusUpdateD = null,
ActionCommandD actionCommandD =null,
bool keepDeviceListening = false,
DeviceAndSvcCurrentSettings deviceCurrentSettings = null)
The last 4 parameters are optional. and so were not specified in the call to instantiate the Device in single-shot mode, (see the previous blog) .
- onDeviceStatusUpdateD A delegate. Used by the device app to display or log changes of its status and to show errors.
- actionCommandD A delegate. Called after the incoming message is processed by the device. The device app can use it to get updated keepDeviceListening and AutoStartDevice values and consequently update them (if changed) in its UI (eg to CheckBoxes).
- keepDeviceListening A flag. Device to stay connected after the next is received so as to be ready to accept more messages from the service. Can be set by incoming message as well.
- deviceCurrentSettings An instantiated class. Normally this is null. This functionality is built into the library as
DeviceAndSvcCurrentSettings
but can be overridden by providing a custom extension class to implement the required functionality. (See later)
RunSvc API
public static async Task RunSvc(string s_connectionString, String deviceId, string msgOut,
ActionReceivedText onRecvdTextD,
int devKeepListening=2,
int devAutoStart=2,
ActionReceivedText onStatusUpdateD=null,
bool keepAlive = false,
bool responseExpected = true,
DeviceAndSvcCurrentSettings deviceAndSvcCurrentSettings = null )
The last 6 parameters are optional. and so were not specified in the call to instantiate the Service in single-shot mode, (see the previous blog) .
- devKeepListening 0-false, 1-true, 2-Don’t send(default) Send KeepListening after disconnection flag to device.
- devAutoStart 0-false, 1-true, 2-Don’t send(default) Send AutoStart flag to device.
- onStatusUpdateD A delegate. Used by the service app to display or log changes of its status and to show errors.
- keepAlive A flag. Option for the service to tell the device to stay connected after this message is received so as to be ready to accept more messages from the service.
- responseExpected A flag. If true, then Device to send the result of the
onRecvdTextD()
back to the service. - deviceAndSvcCurrentSettings An instantiated class. Normally this is null. This functionality is built into the library as
DeviceAndSvcCurrentSettings
but can be overridden by providing a custom extension class to implement the required functionality. (See later)
About KeepAlive
The sockets at both end can be kept alive between messages until the user end signals otherwise. The API has been constructed so that the user service app dictates if the connection is to be kept alive and therefore the message it sends needs to have embedded in it a flag to indicate this. The device message reception, OnDeviceRecvText() needs to mirror this and decipher if the connection is to be kept alive. The inbuilt class DeviceAndSvcCurrentSettings
(used by both the device and service) prepends some coded information (characters) that if present indicate whether to set the device in KeepAlive mode or whether to reset it. That class can be overridden by user code so as to implement the coding in another way.
About KeepListening
Not to be confused with KeepAlive. If set in the device to true, then the device will continue to listen for connections once the connection is closed.
DeviceAndSvcCurrentSettings
As stated, this class is embedded in the AziothubStreaming
library.
public DeviceAndSvcCurrentSettings
{
//Properties:
/////////////
public bool AutoStartDevice { get; set; }
public bool KeepAlive { get; set; }
public bool KeepDeviceListening { get; set; }
public bool ResponseExpected { get; set; }
//Methods:
///////////
//Called by Service before transmitting message. Embed property flags in message
public virtual string ProcessMsgOut(string msgOut, bool keepAlive = false, bool responseExpected = true,
int DevKeepListening=2,int DevAutoStart=2)
//Called by Device on message reception to get flags
public virtual string ProcessMsgIn(string msgIn);
}
ProcessMsgOut()
takes the message to be sent from the service to the device and embeds the flags within it. As implemented, it prepends characters to do so.
The last two parameters, as integers, are optional. 0-false, 1-true, 2-ignored. If ignored, then that flag isn’t embedded in the message.
ProcessMsgIn()
takes the received message on the device and extracts those flags. As implemented, it gets them from the prepended characters (and removes those characters from the message). The properties in the class are set from the flags.
This class can be implemented in user code. A class prototype follows:
public class DeviceSvcCurrentSettings_Example : AzIoTHubDeviceStreams.DeviceAndSvcCurrentSettings
{
public string Name { get; set; } = "MainPage_DeviceSvcCurrentSettingsExample";
public override string ProcessMsgIn(string msgIn)
{
if (msgIn[0] == Info.KeepAliveChar)
KeepAlive = true;
else
KeepAlive = false;
//etc.
throw new NotImplementedException("NotImplemented: DeviceSvcCurrentSettingsExample.ProcessMsgIn()");
}
public override string ProcessMsgOut(string msgOut, bool keepAlive = false, bool responseExpected = true,
int DevKeepListening = 2, int DevAutoStart = 2)
{
KeepAlive = keepAlive;
ResponseExpected = responseExpected;
//etc.
throw new NotImplementedException("Not Implemented: DeviceSvcCurrentSettingsExample.ProcessMsgOut()");
}
}
You then generate an instance of it in the device and service apps and use it as the deviceAndSvcCurrentSettings parameter in the RunDevice()
and RunSvc()
calls.
In the UWPXaml sample app there is a partial implementation of this class and an option to use it. As this class is not fully implemented though (as above), the NotImplmented Exceptions are thrown.
The Device
private async void Button_Click_Device(object sender, RoutedEventArgs e)
{
bool useCustomClass = (chkUseCustomClassDevice.IsChecked == true);
await Task.Run(() =>
{
try
{
if (!useCustomClass)
DeviceStream_Device.RunDevice(device_cs, OnDeviceRecvText, OnDeviceStatusUpdate, ActionCommand,
KeepDeviceListening ).GetAwaiter().GetResult();
else
DeviceStream_Device.RunDevice(device_cs, OnDeviceRecvText, OnDeviceStatusUpdate, ActionCommand,
KeepDeviceListening , new DeviceSvcCurrentSettings_Example()).GetAwaiter().GetResult();
}
}
The KeepDeviceListening flag here is provided by the Device app and can come from the app’s stored settings which can be set by that app’s UI. It also can be embedded in the message sent from the service. The AutoStartDevice flag is read from the app’s stored settings at startup. It too can be set by the Device app or sent from the service with a message.
The useCustomClass value, in this code, is set or cleared by a UI CheckBox and determines if a custom class is used or not for flag encryption and decryption (DeviceSvcCurrentSettings).
A example of the OnDeviceRecvText method follows:
private string OnDeviceRecvText(string msgIn)
{
Console.WriteLine(msgIn);
string msgOut = msgIn.ToUpper();
Console.WriteLine(msgOut);
return msgOut;
}
Nb: There is a mechanism to not create a new socket and use the existing one for the Device when in KeepAlive mode implicit in the Device code (not required to be called in the Device code above.).
The Service
private async void Button_Click_Svc(object sender, RoutedEventArgs e)
{
string msgOut = tbSvcMsgOut.Text;
rbNoChangeListening.IsChecked = true;
rbNoChangeAutoStart.IsChecked = true;
bool keepAlive = (chkKeepAlive.IsChecked == true);
bool responseExpected = (chkExpectResponse.IsChecked == true);
bool useCustomClass = (ChkUseCustomClassSvc.IsChecked == true);
if (!DeviceStream_Svc.SignalSendMsgOut(msgOut, keepAlive,responseExpected))
{
await Task.Run(() =>
{
try
{
if (!useCustomClass)
DeviceStream_Svc.RunSvc(service_cs, device_id, msgOut, OnSvcRecvText, DevKeepListening, DevAutoStart, OnDeviceSvcUpdate, keepAlive, responseExpected).GetAwaiter().GetResult();
else
DeviceStream_Svc.RunSvc(service_cs, device_id, msgOut, OnSvcRecvText, DevKeepListening, DevAutoStart, OnDeviceSvcUpdate, keepAlive, responseExpected, new DeviceSvcCurrentSettings_Example()).GetAwaiter().GetResult();
}
}
}
}
private void OnSvcRecvText(string msgIn)
{
Console.WriteLine(msgIn);
}
The SignalSendMsgOut
method bypasses the call if a previous message has put the system into a KeepAlive mode. It will send the message using the existing socket. The AutoStartDevice and KeepDeviceListening flags can only be updated with a new connection.
Example ActionCommand delegate
In the Device, after a message is received, the following sequence of code runs:
msgIn = DeviceCurrentSettings.ProcessMsgIn(msgIn);
respond = DeviceCurrentSettings.ResponseExpected;
keepAlive = DeviceCurrentSettings.KeepAlive;
if (DeviceCurrentSettings.AutoStartDeviceChanged)
ActionCmdD?.Invoke(DeviceCurrentSettings.AutoStartDevice, "", 0, 0);
if (DeviceCurrentSettings.KeepDeviceListeningChanged)
ActionCmdD?.Invoke(DeviceCurrentSettings.KeepDeviceListening, "", 0, 1);
The first line strips the flags from the message and interprets them (updating the flags). The next two lines read two of those flags for use in later code that follows. The next two actions cause an update in the Device app UI for the other two flags. An example for ActionCommand delegate as in the UWPXaml app follows:
private void ActionCommand(bool isChecked,string val, int value, int cmd )
{
Task.Run(async () => {
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
switch (cmd)
{
case 0:
if (chkAutoStart.IsChecked != isChecked)
chkAutoStart.IsChecked = isChecked;
break;
case 1:
if (chKeepDeviceListening.IsChecked != isChecked)
chKeepDeviceListening.IsChecked = isChecked;
break;
}
});
});
}
Next: How to use the Updated UWPXaml app
Topic | Subtopic | |
This Category Links | ||
Category: | Azure Device Streaming Index: | Azure Device Streaming |
Next: > | AziothubDeviceStreaming | How to use the Updated UWP-Xaml app |
< Prev: | AziothubDeviceStreaming | Under the hood-The API |