using Microsoft.Azure.Devices; using System.Diagnostics; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using RIoT; using System.IO; using Microsoft.Azure.Devices.Shared; using Microsoft.ServiceBus.Messaging; using Newtonsoft.Json; using Org.BouncyCastle.Crypto.Digests; namespace RIoTDemo { /// /// Most program logic is driven from timer ticks in MainPage. ChildStatus forms /// contain the per-device UI, but they display what they're told based on waht /// goes on here. /// public partial class MainPage : Form { bool initialized = false; string DeviceIDMatch = "RIoT_Test"; string ControlDevice = "ControlDevice"; byte[] ControlDeviceKey = HashData(new byte[] { 0 } , 0,1); RegistryManager RegMgr; EventHubClient EventClient; EventHubReceiver[] Receivers; DateTime LastUpdateTime = DateTime.Now; string[] D2CPartitions; string ConnectionStringFile = "c:\\tmp\\ConnectionString.txt"; //int CurrentFWVersionNumber = 0; IEnumerable CurrentDeviceList; List StatusPanes = new List(); int TargetVersionNumber = 0; public MainPage() { InitializeComponent(); TargetVersionNumber = 0; } bool paintBusy = false; private async void MainPage_Paint(object sender, PaintEventArgs e) { if (paintBusy) return; paintBusy = true; if (initialized) return; if (!File.Exists(ConnectionStringFile)) { Notify($"Missing connection string file {ConnectionStringFile}. This file is omitted from the distribution because it contains passwords. Fix and restart."); return; } try { var connectionString = File.ReadAllText(ConnectionStringFile); RegMgr = RegistryManager.CreateFromConnectionString(connectionString); string iotHubD2cEndpoint = "messages/events"; EventClient = EventHubClient.CreateFromConnectionString(connectionString, iotHubD2cEndpoint); D2CPartitions = EventClient.GetRuntimeInformation().PartitionIds; Receivers = new EventHubReceiver[D2CPartitions.Length]; for (int j = 0; j < D2CPartitions.Length; j++) { Receivers[j] = EventClient.GetDefaultConsumerGroup().CreateReceiver(D2CPartitions[j], DateTime.UtcNow - new TimeSpan(0, 0, 10)); } // make or refresh the control device (this is just a convenient way of passing // data like the targetFWID from the GUI app to the server Device dTemplate = new Device(ControlDevice); dTemplate.Authentication = new AuthenticationMechanism(); dTemplate.Authentication.SymmetricKey = new SymmetricKey(); dTemplate.Authentication.SymmetricKey.PrimaryKey = Convert.ToBase64String(ControlDeviceKey); dTemplate.Authentication.SymmetricKey.SecondaryKey = Convert.ToBase64String(ControlDeviceKey); try { await RegMgr.AddDeviceAsync(dTemplate); } catch(Exception zz) { Helpers.Notify($"Failed to add device. Probably already exists. Error is {zz.ToString()}"); } try { // and set the FWID property in the twin var props = new TwinCollection(); props["FWID"] = GetFWIDString(TargetVersionNumber); props["VersionNumber"] = TargetVersionNumber.ToString(); var twin = await RegMgr.GetTwinAsync(ControlDevice); twin.Properties.Desired = props; await RegMgr.UpdateTwinAsync(ControlDevice, twin, twin.ETag); } catch (Exception ex) { Helpers.Notify($"Failed to add device. Error is {ex.ToString()}"); return; } Debug.Write(""); //CancellationTokenSource cts = new CancellationTokenSource(); } catch (Exception ex) { Notify($"Failed to CreateFromConnectionString: {ex.ToString()}"); return ; } // everything else happens on timer ticks this.timer1.Interval = 1000; this.timer1.Tick += Timer1_Tick; this.timer1.Start(); initialized = true; paintBusy = false; } bool executing = false; private async void Timer1_Tick(object sender, EventArgs e) { if (executing) return; executing = true; // do we have changes to the devices we're monitoring? var devices = await RegMgr.GetDevicesAsync(100); var demoDevices = devices.Where(x => x.Id.Contains(DeviceIDMatch)); if(DeviceListHasChanged(CurrentDeviceList, demoDevices)) { CurrentDeviceList = demoDevices; UpdateDevicePanes(); } // else update status of what we have // First, get a batch of messages and post them to the controls for (int j = 0; j < Receivers.Length; j++) { while (true) { var mx = Receivers[j].Receive(new TimeSpan(0, 0, 0, 0, 1)); if (mx == null) break; var senderDeviceId = mx.SystemProperties["iothub-connection-device-id"]; var messageData = mx.GetBytes(); var message = JsonConvert.DeserializeObject(Encoding.ASCII.GetString(messageData)); var device = StatusPanes.Where(x => x.Id == senderDeviceId.ToString()).First(); string colorString = Enum.GetName(typeof(KnownColor), message.ColorVal); device.NotifyNewMessage($"Count={message.MessageCount}, Color = {colorString}"); device.PicColor = (KnownColor)message.ColorVal; device.LastMessageTime = DateTime.Now; Debug.WriteLine(message.ToString()); } } // Next 1) update the background color based on what version number the device *claims* it is // 2) If device firmware is wrong AND the last firmware change was >1 min ago, revoke the // creds for the device foreach (var cc in this.Controls) { if (!(cc is DeviceStatus)) continue; var ds = (DeviceStatus)cc; string deviceId = ds.Id; var device = demoDevices.Where(x => x.Id == deviceId).First(); var twx = await RegMgr.GetTwinAsync(deviceId); CurrentState s = CurrentState.Good; DateTime nowTIme = DateTime.Now; try { if (!twx.Properties.Reported.Contains("VersionNumber")) s = CurrentState.BadFirmware; if (!twx.Properties.Desired.Contains("VersionNumber")) s = CurrentState.BadFirmware; if (s != CurrentState.BadFirmware) { var reported = (twx.Properties.Reported["VersionNumber"]); var desired = (twx.Properties.Desired["VersionNumber"]); if (reported == desired) { s = CurrentState.Good; } else { s = CurrentState.OldFirmware; } // If the device is out-of-date, and the update was requested >1 min ago, revoke the creds if(s== CurrentState.OldFirmware && nowTIme-LastUpdateTime> new TimeSpan(0,1,0)) { device.Authentication.X509Thumbprint.PrimaryThumbprint = null; s = CurrentState.BadFirmware; await RegMgr.UpdateDeviceAsync(device); } if (twx.Properties.Reported.Contains("VersionNumber")) { var repX = (twx.Properties.Reported["VersionNumber"]); ds.MyVersionNumber = repX; } } } catch (Exception ee) { Debug.WriteLine($"Error getting twin info {ee.ToString()}"); s = CurrentState.BadFirmware; } // any messages in the last minute? if (nowTIme - ds.LastMessageTime > new TimeSpan(0, 0, 1, 0)) { s = CurrentState.BadFirmware; } ds.State = s; ds.UpdateGUI(); } // Finally, tell devices that they're P0wned foreach (var cc in this.Controls) { if (!(cc is DeviceStatus)) continue; var ds = (DeviceStatus)cc; if (!ds.P0wnedStatusChanged) continue; ds.P0wnedStatusChanged = false; var twx = await RegMgr.GetTwinAsync(ds.Id); TwinCollection t = new TwinCollection(); t["POwned"] = ds.AmIPOwned; twx.Properties.Desired = t; await RegMgr.UpdateTwinAsync(ds.Id, twx, twx.ETag); } executing = false; } void UpdateDevicePanes() { // remove current foreach (var s in StatusPanes) { this.Controls.Remove(s); } StatusPanes.Clear(); // this.Controls.Clear(); // add new int xCount = 0; int yCount = 0; int yGutter = 64; foreach (var d in CurrentDeviceList) { DeviceStatus s = new DeviceStatus(); s.Id = d.Id; if ((xCount + 1) * (s.Width + 10) + 10 > this.Width) { yCount++; xCount = 0; } s.Location = new Point(xCount * (s.Width + 10) + 10, yCount * (s.Height + 10) + yGutter); xCount++; s.Show(); StatusPanes.Add(s); this.Controls.Add(s); } } bool DeviceListHasChanged(IEnumerable current, IEnumerable newList) { if (current == null) return true; if (current.Count() != newList.Count()) return true; foreach (var c in current) { if (newList.Where(x => x.Id == c.Id).Count() == 0) return true; } return false; } internal static void Notify(string s) { MessageBox.Show(s); } bool updating = false; private async void button1_Click(object sender, EventArgs e) { if (updating) return; updating = true; TargetVersionNumber++; this.VersionNumber.Text = $"Target Version Number {TargetVersionNumber}"; try { // Set the FWID property in the twin var props = new TwinCollection(); props["FWID"] = GetFWIDString(TargetVersionNumber); props["VersionNumber"] = TargetVersionNumber.ToString(); var twin = await RegMgr.GetTwinAsync(ControlDevice); twin.Properties.Desired = props; await RegMgr.UpdateTwinAsync(ControlDevice, twin, twin.ETag); } catch (Exception ex) { Helpers.Notify($"Failed to add device. Error is {ex.ToString()}"); return; } /* var devices = await RegMgr.GetDevicesAsync(100); var demoDevices = devices.Where(x => x.Id.Contains(DeviceIDMatch)); foreach(var d in demoDevices) { var twx = await RegMgr.GetTwinAsync(d.Id); TwinCollection t = new TwinCollection(); t["VersionNumber"] = TargetVersionNumber; twx.Properties.Desired = t; await RegMgr.UpdateTwinAsync(d.Id, twx, twx.ETag); } */ LastUpdateTime = DateTime.Now; updating = false; } /// /// SHA256 hash of data-fragment /// /// /// /// /// internal static byte[] HashData(byte[] data, int start, int length) { var d = new Sha256Digest(); d.BlockUpdate(data, start, length); var digest = new byte[32]; d.DoFinal(digest, 0); return digest; } /// /// Convert a byte-array to packed HEX /// /// /// public static string Hexify(byte[] t) { return BitConverter.ToString(t).Replace("-", ""); } string GetFWIDString(int versionNumber) { return Hexify(HashData(new byte[] { (byte) versionNumber }, 0, 1)); } void MakeControlDeviceTwin() { } } /* var m = new { DevID = devId, ColorVal = colorVal, MessageCount = messageCount }; */ public class MyMessageType { public string DevID; public int ColorVal; public int MessageCount; }; }