Esempio n. 1
0
        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;
            }
            }
        }