public static async Task PublishStats() { string[] stats = new string[] { GetStatsUptime(), GetStatsSignal(), GetStatsCPUtemp(), GetStatsCPUload(), GetStatsBattery(), GetStatsFreeHeap(), GetStatsSupply() }; foreach (Device device in devices) { //int index = 0; await MQTT.SendMQTTMessageAsync($"{device.Name}/$stats/uptime", stats[0], true); //await MQTT.SendMQTTMessageAsync($"{device.Name}/$stats/signal", stats[1], true); //await MQTT.SendMQTTMessageAsync($"{device.Name}/$stats/cputemp", stats[2], true); //await MQTT.SendMQTTMessageAsync($"{device.Name}/$stats/cpuload", stats[3], true); //await MQTT.SendMQTTMessageAsync($"{device.Name}/$stats/battery", stats[4], true); //await MQTT.SendMQTTMessageAsync($"{device.Name}/$stats/freeheap", stats[5], true); //await MQTT.SendMQTTMessageAsync($"{device.Name}/$stats/supply", stats[6], true); } // These must be sent with the interval given in homie/unitname/$interval }
public static async Task UpdateDeviceData(Device device, Protocol.PT_RX.Packet packet) { device.Datapoint.LatestDataValues = packet; device.Datapoint.LastUpdate = DateTime.Now; //string newData = CI.GetDataFromPacket(packet.MGW_RX_DATA, packet.MGW_RX_DATA_TYPE, ""); foreach (Node node in device.Node) { switch (node.Type) { case "RSSI": { node.Value = packet.MGW_RX_RSSI.ToString(); break; } case "Battery": { node.Value = packet.MGW_RX_BATTERY.ToString(); break; } case "Dimmer": { node.Value = packet.MGW_RX_INFO_SHORT.ToString(); break; } default: { node.Value = CI.GetDataFromPacket(packet.MGW_RX_DATA, packet.MGW_RX_DATA_TYPE, ""); break; } } MyLogger.DoLog($"Updating data for {device.Name}'s {node.Name}: {node.Value}...", 4); await MQTT.SendMQTTMessageAsync($"{device.Name}/{node.PathName}/{node.PropertyList[0].PathName}", node.Value, true); } }
private static async Task MqttClient_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e) { // These are the topics we are subscribing to: //$"homie/DimKitchen/Lights/DimLevel/set" // Used to set a new value for that node/property //$"homie/DimKitchen/Lights/DimLevel/poll" // This will allow us to request an update from the device //$"homie/$broadcast/#" // Example: homie/$broadcast/alert ← "Intruder detected" //$"homie/xComfort/get" // Used for get-/setting data that's not part of the Homie specs, like //$"homie/xComfort/set" // config files, config and such. //$"homie/xComfort/RAW/in" // Allows the user to send raw bytes to the CI //$"homie/xComfort/raw/in" // Same as RAW, but the bytes are now human readable strings "06 C1 04 ..." //$"homie/xComfort/cmd" // A way to receive simple commands like "exit" //$"homie/xComfort/shell" // Runs shell commands on the system. Potensial SECURITY HOLE // Technically, it's these formats, but they are not as nice to read: //$"{Program.Settings.MQTT_BASETOPIC}/{device.Name}/{node.PathName}/{property.PathName}/set" (and poll) //$"{Program.Settings.MQTT_BASETOPIC}/$broadcast/#" //$"{BasePublishingTopic}/get/#" (and set/# and RAW/in and raw/in and cmd/# and shell/#) string[] topics = e.ApplicationMessage.Topic.Split("/"); // Split the topic into levels string payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); // e.ApplicationMessage.ConvertPayloadToString(); if (Program.Settings.GENERAL_DEBUGMODE) { DoLog($"Incomming MQTT message: {e.ApplicationMessage.Topic}={payload}", 2); } // (We trust that the MQTT subscription makes sure that we don't get any unwanted suff, so we don't have to check that ourselves.) switch (topics[1]) // This will always be "$broadcast", "xComfort" or the name of a device. { case "$broadcast": // It's a broadcast message for all devices { // We have no real use for this at the moment, but we'll display and log it anyway. DoLog($"Broadcasted message: {e.ApplicationMessage.Topic.ToString()}={payload}", 4); break; } case "xComfort": // It's one of six possible things { switch (topics[2]) { case "get": { if (topics.Length > 3 && topics[3] == "result") { DoLog("MQTT bug workaround!", 2); break; } // A bug in the library. It doesn't properly unsubscribe to topics... // Get and settable if (payload == "config") { await SendMQTTMessageAsync($"{topics[1]}/{topics[2]}/result", Program.Settings.GetSettingsAsJSON(), false); } if (payload == "datapoints") { await SendMQTTMessageAsync($"{topics[1]}/{topics[2]}/result", Program.GetDatapointFile(), false); } if (payload == "debug") { await SendMQTTMessageAsync($"{topics[1]}/{topics[2]}/result", Program.Settings.GENERAL_DEBUGMODE.ToString(), false); } // Gettable only if (payload == "temperature") { await SendMQTTMessageAsync($"{topics[1]}/{topics[2]}/result", Homie.GetStatsCPUload(), false); } if (payload == "status") { string reply = "--- STAUTS ---\n"; reply += $"Debug mode: {Program.Settings.GENERAL_DEBUGMODE}\n"; reply += $"StayAlive: {Program.StayAlive}\n"; reply += $"Datapoints: {CI.datapoints.Count}\n"; reply += $"Devices: {Homie.devices.Count}\n"; reply += $"Temperature: {Homie.GetStatsCPUtemp()}"; reply += $"Publications: {PublicationCounter}\n"; reply += "--- EOT ---\n"; await SendMQTTMessageAsync($"{topics[1]}/{topics[2]}/result", reply, false); } break; } case "set": { if (topics.Length > 3 && topics[3] == "result") { DoLog("MQTT bug workaround!", 2); break; } // A bug in the library. It doesn't properly unsubscribe to topics... // Get and settable if (payload == "config") { await SendMQTTMessageAsync($"{topics[1]}/{topics[2]}/result", Program.Settings.WriteSettingsToFile(payload).ToString(), false); } if (payload == "datapoints") { await SendMQTTMessageAsync($"{topics[1]}/{topics[2]}/result", Program.SetDatapointFile(payload).ToString(), false); } if (payload == "debug") { Program.Settings.GENERAL_DEBUGMODE = (payload == "true"); await SendMQTTMessageAsync($"{topics[1]}/{topics[2]}/result", Program.Settings.GENERAL_DEBUGMODE.ToString(), false); } // Settable only if (payload == "datapoint") { await SendMQTTMessageAsync($"{topics[1]}/{topics[2]}/result", Program.ImportDatapointsOneByOne(payload).ToString(), false); } break; } case "RAW": { await CI.SendData(e.ApplicationMessage.Payload); break; } case "raw": { // Convert the human readable string back into the actualy bytes it represents, then sending it to the CI. payload = payload.Replace(" ", ""); List <byte> byteList = new List <byte>(); for (int i = 0; i < payload.Length; i += 2) { byteList.Add(Convert.ToByte(payload.Substring(i, 2), 16)); } await CI.SendData(byteList.ToArray()); break; } case "cmd": { if (payload == "exit") { Program.StayAlive = false; } if (payload == "update") { DoLog("Re-publishing all devices...", false); foreach (Homie.Device device in Homie.devices) { await MQTT.PublishDeviceAsync(device); } DoLog("Done", 3, true, 10); } if (payload == "pollall") { DoLog("Polling all relevant devices...", true); foreach (Homie.Device device in Homie.devices) { if (device.Datapoint.Class == 0) { await CI.RequestUpdateAsync(device.Datapoint.DP); //System.Threading.Thread.Sleep(500); } } DoLog("Polling complete!", 3, true, 10); } break; } case "shell": { // This is just a tad too much of a security risk to implement at this time. // It might be a very useful feature, but there must be some security in place first! // Have a look at https://loune.net/2017/06/running-shell-bash-commands-in-net-core/ for en example of how the execution can be implemented. DoLog("Ignored request for shell: " + payload, 3); break; } default: { break; } } break; } default: // It's a device's name, we must be more dynamic in our approach. { try { if (Program.Settings.GENERAL_DEBUGMODE) { DoLog($"Processing as datapoint related", 2); } Homie.Device device = Homie.devices.Find(x => x.Name == topics[1]); if (Program.Settings.GENERAL_DEBUGMODE) { DoLog($"...found device: {device.Name}", 2); } Homie.Node node = device.Node.Find(x => x.PathName == topics[2]); if (Program.Settings.GENERAL_DEBUGMODE) { DoLog($"...found node: {node.Name}", 2); } Homie.Property property = node.PropertyList.Find(x => x.PathName == topics[3]); if (Program.Settings.GENERAL_DEBUGMODE) { DoLog($"...found property: {property.Name}", 2); } switch (topics[4]) { case "set": { await CI.SendNewValueToDatapointAsync(device.Datapoint.DP, Convert.ToDouble(payload)); break; } //await Homie.UpdateSingleProperty($"{device.Name}/{node.PathName}/{property.PathName}", property, payload); case "poll": { await CI.RequestUpdateAsync(device.Datapoint.DP); break; } default: { DoLog($"Unknown action: {e.ApplicationMessage.Topic}"); break; } } //if (topics[4] == "set") { await Homie.UpdateSingleProperty($"{device.Name}/{node.PathName}/{property.PathName}",property, payload); } //if (topics[4] == "poll") { await CI.RequestUpdateAsync(device.Datapoint.DP); } //if (topics[4] != "set" && topics[4] != "poll") { DoLog($"Unknown action: {e.ApplicationMessage.Topic}"); } } catch (Exception exception) { DoLog($"Error processing MQTT message: {e.ApplicationMessage.Topic.ToString()}={payload}", 5); LogException(exception); } break; } } }
private static async Task MqttClient_Connected(object sender, MqttClientConnectedEventArgs e) { try { ReconnectionFailures = 0; //Reset errorcounter // Suscribe to our default topics List <string> topics = new List <string>() { $"{Program.Settings.MQTT_BASETOPIC}/$broadcast/#", // Example: homie/$broadcast/alert ← "Intruder detected" $"{BasePublishingTopic}/get/", // Used for get-/setting data that's not part of the Homie specs, like $"{BasePublishingTopic}/set/", // config files, config and such. $"{BasePublishingTopic}/RAW/in/", // Allows the user to send raw bytes to the CI $"{BasePublishingTopic}/raw/in/", // Same as RAW, but the bytes are now human readable strings "06 C1 04 ..." $"{BasePublishingTopic}/cmd/" // A way to receive simple commands like "exit" //$"{BasePublishingTopic}/shell/#" // Runs shell commands on the system. Potensial SECURITY HOLE }; // Time to tell everyone about our devices, and set up a subscription for that device if needed. foreach (Homie.Device device in Homie.devices) { await MQTT.PublishDeviceAsync(device); // Publishes a LOT of information foreach (Homie.Node node in device.Node) { foreach (Homie.Property property in node.PropertyList) { if (property.Settable == "true") // If this is a settable value, we need to subscribe to a topic for it { //await mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic($"{Program.Settings.MQTT_BASETOPIC}/{device.Name}/{node.PathName}/{property.PathName}/set".Replace("//","/")).Build()); topics.Add($"{Program.Settings.MQTT_BASETOPIC}/{device.Name}/{node.PathName}/{property.PathName}/set"); } if (device.Datapoint.Class == 0) // If this is a pollable value, we need to subscribe to a topic for that as well { //await mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic($"{Program.Settings.MQTT_BASETOPIC}/{device.Name}/{node.PathName}/{property.PathName}/poll".Replace("//", "/")).Build()); topics.Add($"{Program.Settings.MQTT_BASETOPIC}/{device.Name}/{node.PathName}/{property.PathName}/poll"); // This will allow us to request an update from the device } } } } foreach (string unsafetopic in topics) { string topic = unsafetopic.Replace("//", "/"); // Depending on what the user has put in the settings file, this might contain //, so we remove them just in case. await mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build()); if (topic.EndsWith("/")) { await mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic.Remove(topic.Length - 1)).Build()); } // This allows us to subscribe to both topics at once. //if (Program.Settings.GENERAL_DEBUGMODE) DoLog($"Subscribing to {topic}", 2); } while (mqttClient.IsConnected && Program.StayAlive) // As long as we are connected, we need to send the stats periodically { System.Threading.Thread.Sleep(Convert.ToInt32(Program.Settings.HOMIE_STATS_INTERVAL) * 1000); await Homie.PublishStats(); } } catch (Exception exception) { LogException(exception); } }
static void Main(string[] args) { // Handling CLI arguments foreach (string arg in args) { bool mm = false; bool im = false; switch (arg) { case "-?": case "--?": case "--h": case "/h": case "/?": case "-m": { mm = true; break; }; case "-h": { im = true; IllegalArguments = true; break; }; case "-def": { Settings.DefaultSettings(); break; }; case "-nope": {; break; }; case "-debug": { Settings.GENERAL_DEBUGMODE = true; break; }; default: { Console.WriteLine("Unknown argument: " + arg); Console.WriteLine("Try -h for a list of available arguments."); IllegalArguments = true; break; } } if (mm) { Menu.MainMenu(); } if (im) { Menu.InfoMenu(); } } if (IllegalArguments) { return; } Menu.MainMenu(); DoLog("Starting BachelorPad...", 4); if (Settings.GENERAL_FROM_FILE == false) { DoLog("Using default settings!", 4); } //if(Settings.DEBUGMODE) { Console.WriteLine(Settings.GetSettingsAsJSON()); Console.ReadLine(); } // For easier switching between developer machine and the Raspberry Pi meant for production use. if (System.Net.Dns.GetHostName().ToUpper() == "ORION") { Console.WriteLine("YOU ARE NOW RUNNING ON THE DEVELOPER MACHINE!"); Settings.MQTT_BASETOPIC = "debugdata"; if (ImportDatapointsFromFile("C:\\misc\\" + Settings.GENERAL_DATAPOINTS_FILENAME)) { CreateDevicesOutOfDatapoints(); MQTT.RunMQTTClientAsync().Wait(); MQTT.PublishDeviceAsync(Homie.CreateDeviceFromDatapoint(CI.datapoints[4])).Wait(); Console.WriteLine($"Publications: {MQTT.PublicationCounter}"); CI.FakeData(new byte[] { 0x0D, 0xC1, 0x05, 0x70, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x32, 0x10, 0x0B }).Wait(); //CI.FakeData(new byte[] { 0x0D, 0xC1, 0x31, 0x62, 0x17, 0x00, 0x00, 0xC9, 0x00, 0x00, 0x44, 0x24, 0x01 }).Wait(); while (StayAlive) { // Nada! } ; } ; Console.WriteLine("---------------------------------------------------------"); while (StayAlive) { // Nada! } ; return; } ; BootWithoutError = ImportDatapointsFromFile(Settings.GENERAL_DATAPOINTS_FILENAME); if (BootWithoutError) { CreateDevicesOutOfDatapoints(); } if (BootWithoutError) { MQTT.RunMQTTClientAsync().Wait(); } if (BootWithoutError) { CI.ConnectToCI().Wait(); } if (BootWithoutError) { while (StayAlive) { // Just chill, other threads are monitoring communications... } // If this point in the code is reached, it means that a shutdown command has been given via MQTT. MQTT.DisconnectMQTTClientAsync().Wait(); } else { DoLog("Something failed during the program startup! Please check the logs for more info.", 5); } DoLog("Terminating...", 4); }
public static async Task UpdateSingleProperty(string topic, Property property, string newDataValue) { MyLogger.DoLog($"Updating {topic} with new value: {newDataValue}", 2); property.DataValue = newDataValue; await MQTT.SendMQTTMessageAsync(topic, newDataValue, true); }