Как сделать добавление новых модулей контроллеров устройств для умного дома удобным для программиста?

304
02 октября 2021, 21:40

Пишу программу для управления умным домом.

Просьба помочь с архитектурой.

В конечном итоге программа будет:

1)Получать команды из UI, от мониторить некоторые виды уведомлений в windows, возможно сигналы с умных датчиков, но это все уже будет реализовываться на уровне главного контроллера, это сейчас не важно;

2) Главный контроллер будет мониторить глобальные события вроде "пришло уведомление", "нажалась кнопка" и сообщать контроллерам устройств.

3) Отдельные контроллеры устройств будут либо отдавать устройствам в сети команды, либо игнорить ивент. Пришел Email, лампочка мигнула, пылесосу пофиг. Вопрос как переделать так, чтобы можно было добавлять новый класс устройства не вмешиваясь в основной код? Сейчас для каждого устройства создается несколько классов. Не факт, что они все нужны, и не факт, что они делают то что нужно.

Появились более конкретные вопросы.

Исходя из выше указанного требования к программе:

1) Где разместить логику поиска устройства в сети? Она однотипная, но каждый класс имеет свой вариант приветственного сообщения для SSDP.

2) Где создавать и хранить контроллеры устройств?

3) Где создавать и хранить кэш устройств? Может для всего этого статические классы написать?

Это пример моего кода с одним из устройств - (SmartBulb) умная лампочка. Просьба не указывать на не архитектурные ошибки, лишний код типа try/catch я тоже удалил.

public partial class MainWindow : Window
{
    MainDevicesController DevicesController = new MainDevicesController();
    private void Button_FindAllDevices_Click(object sender, RoutedEventArgs e)
        {
            DevicesController.SearchForDevices();
        }
}

Это тот самый главный контроллер

class MainDevicesController
{
    SmartBulbController BulbController = new SmartBulbController();
    Hashtable Devices = null;
    List<Device> Bulbs = null;
    public MainDevicesController()
    {
    }
    public bool SearchForDevices()
    {
        Bulbs = BulbController.FindDevices();
        Devices = new Hashtable();
        Devices.Add("Bulbs", Bulbs);
        return true;
    }
}

Этот класс должен добавлять программист, правда не в таком виде

class SmartBulbController
{
    private SmartBulbDiscoverer BulbDiscoverer = new SmartBulbDiscoverer();
    private List<Device> SmartBulbs = null;
    private Bulb Bulb = null;
    public List<Device> FindDevices()
    {
        return SmartBulbs = BulbDiscoverer.DiscoverAllBulbs();
    }
    public bool IncreaseBulbBrightness()
    {
        Bulb.IncreaseBrigtness();
        return true;
    }
    public bool DecreaseBulbBrightness(){\\ну то есть дальше все в том же духе}
    public bool ToggleBulbPower(){}
    public bool ToggleBulbMusicMode(bool value){}
    public bool ToggleAmbientLight(){}
    public string[] ShowFoundDevices()
    {
     эта функция пробрасывает "наверх" ответы от лампочек просто для вывода в консоль
    }
}

А вот этот уже не должен

class SmartBulbDiscoverer
{
    private List<Device> Bulbs = null;
    private UdpClient McastClient;
    private IPEndPoint McastEndpoint;
    public SmartBulbDiscoverer()
    {
        McastClient = new UdpClient(AddressFamily.InterNetwork);
        McastEndpoint = new IPEndPoint(Bulb.McastAddr, Bulb.UdpPort);
    }
    public List<Device> DiscoverAllBulbs()
    {
        Bulbs = new List<Device>();
        var discoveredBulb = DiscoverBulb();
        Bulbs.Add(discoveredBulb);
        return Bulbs;
    }
    public Bulb DiscoverBulb()
    {
        #region Trying To Join Multicast Group
        McastClient.JoinMulticastGroup(Bulb.McastAddr);
        #endregion
        #region Sending Handshake Message
        byte[] data = Encoding.UTF8.GetBytes(Bulb.InviteMessage);
        McastClient.Send(data, data.Length, McastEndpoint);
        #endregion
        #region Trying to receive answer from the device
        McastClient.Client.ReceiveTimeout = 3000;
        byte[] recData = null;                                    
        recData = McastClient.Receive(ref McastEndpoint);           
        string answer = Encoding.UTF8.GetString(recData);
        #endregion
        Bulb bulb = new Bulb(answer);
        return bulb;
    }
}

А этот класс, мне кажется, делает слишком много, разве что сам свои объекты))

class Bulb : Device
{   // > Invite initialization properties
    public static readonly string InviteMessage = "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1982\r\nMAN: \"ssdp:discover\"\r\nST: wifi_bulb";
    public static readonly IPAddress McastAddr = IPAddress.Parse("239.255.255.250");
    public static readonly int UdpPort = 1982;
    public Bulb(string answer)
    {
        ParseProperties(answer);
        Client = new TcpClient(AddressFamily.InterNetwork);
        LocalServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        MusicMode = false;
        AmbientLightMode = false;
        TcpEndpoint = new IPEndPoint(IPDevice, Port);
        Client = new TcpClient(IPDevice.ToString(), Port);
        Client.Connect(TcpEndpoint);
        Stream = Client.GetStream();
        Writer = new StreamWriter(Stream);
        Reader = new StreamReader(Stream);
        IsConnected = true;
    }
    // > Parsing
    public void ParseProperties(string answer)
    {
        Answer = answer;
        string[] Prop = SplitBulbMessege();
        IPDevice = ParseIPAddress(Prop);
        Port = ParsePort(Prop);
        Properties = new BulbProperties(Prop); //Свойства сами себя инициализируют
    }
    private string[] SplitBulbMessege(){}
    private IPAddress ParseIPAddress(string[] Prop){}
    private int ParsePort(String[] Prop)
    {
        String iP = IPDevice.ToString() + ';';
        int Port = int.Parse(Prop[4].Remove(0, iP.Length));
        return Port;
    }
    // > Bulb toolbox
    public void PowerToggle()
    {
        string commandJson =  "{\"id\":" + Properties.Id + ",\"method 
                               \":\"toggle\",\"params\":[]}";
        Writer.WriteLine(commandJson);
        Writer.Flush();
        Properties.Power = !Properties.Power;
    }
    public bool MusicModeToggle(bool value){//тут и дальше в таком же духе}
    public void TurnOn(){}
    public void TurnOff(){}
    public void IncreaseBrigtness(){}
    public void SetColor(){}
    public void AmbientLightToggle(){}
    /*Лампочка умеет работать как в режиме сервера, так и клиента*/
    // > Client mode tcp connection properties
    public IPAddress IPDevice { get; set; }
    private int Port { get; set; }
    private IPEndPoint TcpEndpoint { get; set; }
    private TcpClient Client { get; set; }
    private NetworkStream Stream { get; set; }
    private StreamWriter Writer { get; set; }
    private StreamReader Reader { get; set; }
    // > Server mode tcp connection properties
    private string LocalIP { get; set; }
    private Socket LocalServer { get; set; }
    private Socket ServerModeClient { get; set; }
    private NetworkStream ServerStream { get; set; }
    private StreamWriter ServerClientWriter { get; set; }
    private StreamReader ServerClientReader { get; set; }
    private bool AmbientLightMode { get; set; }
    public bool MusicMode { get; set; }
    public bool IsConnected { get; set; }
    public string Answer { get; set; }
    // > Bulb visible properties
    public BulbProperties Properties;
}

Ну и еще один странный класс. Возможный ответ от устройства программист при добавлении устройства должен обрабатывать сам, кому как не ему знать как их парсить и сохранять. Но не факт что в отдельном классе.

class BulbProperties
{
    public BulbProperties(string[] Prop)
    {
        Id = Convert.ToInt32(Prop[6], 16);
        Model = Prop[7];
        FwVer = int.Parse(Prop[8]);
        Power = (Prop[10] == "on") ? Power = true : Power = false;
        Brightness = int.Parse(Prop[11]);
        ColorMode = int.Parse(Prop[12]);
        ColorTemperature = int.Parse(Prop[13]);
        RGB = int.Parse(Prop[14]);
        Hue = int.Parse(Prop[15]);
        Saturation = int.Parse(Prop[16]);
    }
    public int Id { get; set; }
    public string Model { get; set; }
    public int FwVer { get; set; }
    public bool Power { get; set; }
    public int Brightness {get; set;} 
    public int ColorMode { get; set; }
    public int ColorTemperature { get; set; }
    public int RGB { get; set; }
    public int Hue { get; set; }
    public int Saturation { get; set; }
    public string Name { get; set; }
}
Answer 1

Я нашел для себя решение https://docs.microsoft.com/ru-ru/archive/msdn-magazine/2014/january/wpf-build-fault-tolerant-composite-applications Здесь плагины существуют в виде отдельных процессов и бесшовно встраиваются в UI. Взаимодействие необходимой логики плагина с хостом можно прикрутить с помощью интерфейсов. Для общего стиля можно подготовить шаблоны контролов.

READ ALSO
Как программно определить на какой странице находится определенный элемент в документе word?

Как программно определить на какой странице находится определенный элемент в документе word?

Задача программно сгенирировать документ Word, а именно договорДля договора есть определенные правила того что если заголовок начинается...

161
Docker + IdentityServer4. Ошибка подключения

Docker + IdentityServer4. Ошибка подключения

Есть настроенный IdentityServer4 на AspNet core 3

153
обновление базы данных после изменений у конечного пользователя

обновление базы данных после изменений у конечного пользователя

Такой вот вопрос, может есть тут разработчики, которые подскажут, как можно выйти с такой ситуации

89
Для чего использовать кеш второго уровня

Для чего использовать кеш второго уровня

Вопрос теоретический, изучаю ОРМ, Понимаю , что есть кеш первого уровня, он привязан к объекту сессииА есть кеш второго уровня, и он привязан...

109