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; } } }