Пример #1
0
        private static string GetNodeList()
        {
            string nodes = "";

            foreach (Datapoint dp in CI.datapoints)
            {
                nodes += $"{ Homie.SanitiseString(dp.Name) },";
            }

            if (nodes.EndsWith(','))
            {
                nodes = nodes.Remove(nodes.Length - 1);
            }
            return(nodes);
        }
Пример #2
0
        private static void CreateDevicesOutOfDatapoints()
        {
            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();
            DoLog("Creating devices from datapoints...", false);
            foreach (Datapoint datapoint in CI.datapoints)
            {
                Homie.devices.Add(Homie.CreateDeviceFromDatapoint(datapoint));
            }
            DoLog("OK", 3, false, 10);
            DoLog($"{stopwatch.ElapsedMilliseconds}ms", 3, true, 14);
            stopwatch.Reset();
            DoLog($"Total number of devices: ", false);
            DoLog($"{ Homie.devices.Count}", 3, true, 10);
        }
Пример #3
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;
            }
            }
        }
Пример #4
0
        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);
            }
        }
Пример #5
0
        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);
        }