using Mqtt.Client.AspNetCore.Services; using MyCore.Interfaces.DTO; using MyCore.Interfaces.Models; using MyCore.Service.Services; using MyCore.Services.MyControlPanel; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; using static Mqtt.Client.AspNetCore.Services.MqttClientMerossService; namespace MyCore.Services.Devices { public class ActionService { public static async Task HandleActionFromMQTTAsync(string topic, string message, DeviceDatabaseService _DeviceDatabaseService, GroupDatabaseService _GroupDatabaseService, ProviderDatabaseService _ProviderDatabaseService, LocationDatabaseService _LocationDatabaseService, AutomationDatabaseService _AutomationDatabaseService, string userId) { var actionTime = DateTimeOffset.Now.ToUnixTimeMilliseconds(); var providers = _ProviderDatabaseService.GetAll(userId); string[] topicSplit = topic.Split('/'); var providerFromTopic = topicSplit[0]; // TODO Sortir automation d'un type specific.. et créer un objet ? System.Console.WriteLine($"Received message {message}"); switch (providerFromTopic) { case "zigbee2mqtt": UpdateZigbee2MqttConfigAsync(topic, message, userId, _DeviceDatabaseService, _GroupDatabaseService, _ProviderDatabaseService, _LocationDatabaseService); var provider = _ProviderDatabaseService.GetByType(topicSplit[0]).Id; var automations = _AutomationDatabaseService.GetByProvider(provider); var serviceName = topicSplit[1]; var zigbeeDevice = _DeviceDatabaseService.GetByName(serviceName).FirstOrDefault(); if (zigbeeDevice != null) { try { /* // Todo Deserialize by type switch (zigbeeDevice.Type) { case DeviceType.Switch: deserializedReceivedMessage = JsonConvert.DeserializeObject(message); zigbeeDevice.LastState = message; break; case DeviceType.Light: deserializedReceivedMessage = JsonConvert.DeserializeObject(message); zigbeeDevice.LastState = message; break; case DeviceType.Motion: deserializedReceivedMessage = JsonConvert.DeserializeObject(message); zigbeeDevice.LastState = message; break; case DeviceType.Plug: zigbeeDevice.LastState = message; break; default: break; }*/ // if is not a set => check automation if (topicSplit.Length <= 2) { foreach (var automation in automations) { // todo check if not null and if more than one element if (zigbeeDevice != null && automation.Triggers.Any(t => t.DeviceId == zigbeeDevice.Id)) { System.Console.WriteLine($"Open automation {automation.Name}"); var automationTrigger = automation.Triggers.Where(t => t.DeviceId == zigbeeDevice.Id).FirstOrDefault(); List exposes = GetDeviceExposes(zigbeeDevice); var triggerStateValueCheck = (object) null; // Try to get automationTrigger.StateName of zigbee device var expose = exposes.Where(e => e.name == automationTrigger.StateName).FirstOrDefault(); bool validTrigger = expose != null; // No expose found.. => the device has not the correct state => not correct automation //System.Type typeToCheck = GetStateType(expose); try { // Try to get specific field from message var dic = JsonConvert.DeserializeObject>(message); triggerStateValueCheck = dic[automationTrigger.StateName]; // if action slide => get slide } catch (Exception ex) { validTrigger = false; } if (validTrigger && automationTrigger.StateValue.ToLower() == triggerStateValueCheck.ToString().ToLower()) { // Correct trigger ! // Todo check condition if (automation.Conditions.Count <= 0) { System.Console.WriteLine("None conditions"); foreach (var action in automation.Actions) { var DeviceNameForAction = ""; Device actionDeviceToTest = new Device(); // Retrieve action type switch (action.Type) { case Interfaces.Models.Type.DEVICE: var zigbeeDeviceAction = _DeviceDatabaseService.GetById(action.DeviceId); var providerActionTest = _ProviderDatabaseService.GetById(userId, action.ProviderId); DeviceNameForAction = zigbeeDeviceAction.Name; actionDeviceToTest = zigbeeDeviceAction; System.Console.WriteLine($"We get a zigbeeDeviceAction ! Name={zigbeeDeviceAction.Name} Type={zigbeeDeviceAction.Type}"); System.Console.WriteLine($"Check action provider type ! Type={providerActionTest.Type} Name={providerActionTest.Name}"); break; case Interfaces.Models.Type.GROUP: var zigbeeGroupAction = _GroupDatabaseService.GetById(action.GroupId); DeviceNameForAction = zigbeeGroupAction.Name; System.Console.WriteLine($"We get a zigbeeGroupAction ! Name={zigbeeGroupAction.Name} Type={zigbeeGroupAction.Type}"); System.Console.WriteLine($"Check action zigbeeGroupAction type ! Type={zigbeeGroupAction.Type}"); // Check state of first device of a group actionDeviceToTest = _DeviceDatabaseService.GetByGroup(zigbeeGroupAction.Id).FirstOrDefault(); // TODO : Send error if no device found ! break; case Interfaces.Models.Type.MQTT: // Correct way ? // TODO //requestType = Interfaces.Models.Type.MQTT; break; case Interfaces.Models.Type.HTTP: // Correct way ? // TODO break; } var providerAction = _ProviderDatabaseService.GetById(userId, action.ProviderId); // Check if device exist if (actionDeviceToTest != null) { switch (providerAction.Type) { case "zigbee2mqtt": var request = ""; var buildRequest = new Dictionary(); System.Console.WriteLine($"zigbee2mqtt type !"); var actionDeviceExpose = GetDeviceExposes(actionDeviceToTest); // Get device last state var dic = new Dictionary(); if (actionDeviceToTest.LastState != null) { dic = JsonConvert.DeserializeObject>(actionDeviceToTest.LastState); } try { switch (actionDeviceToTest.Type) { case DeviceType.Light: case DeviceType.Switch: var actionDeviceExposeForFeatures = actionDeviceExpose.Where(e => e.type == ((DeviceType)actionDeviceToTest.Type).ToString().ToLower()).FirstOrDefault(); if (actionDeviceExposeForFeatures != null) { foreach (var state in action.States) { System.Console.WriteLine($"Check Action in light|switch ! {state.Name} {state.Value}"); if (actionDeviceExposeForFeatures.features.Any(ade => ade.name == state.Name)) { if (dic.Count > 0) { if (dic["state"].ToString().ToLower() == state.Value.ToLower()) { throw new Exception($"Action device is already at the good state : {state.Name} {state.Value}"); } } // TODO clean this if (state.Name == "brightness") { try { int brightness = int.Parse(state.Value); // Add to request buildRequest.Add(state.Name, brightness); } catch (Exception ex) { System.Console.WriteLine($"zigbee - Parse to int error {ex}"); } } else { // Add to request buildRequest.Add(state.Name, state.Value); } } else { throw new Exception($"Action device light|switch does not have expose of type {state.Name}"); } } } else throw new Exception("Action device does not have expose of type light|switch"); break; default: foreach (var state in action.States) { System.Console.WriteLine($"Check Action ! {state.Name} {state.Value}"); if (!actionDeviceExpose.Any(ade => ade.name == state.Name)) { throw new Exception($"Action device does not have expose of type {state.Name}"); } } break; } request = JsonConvert.SerializeObject(buildRequest); // SEND REQUEST var requestToSend = $"Send request ! zigbee2mqtt/{DeviceNameForAction}/set/{request}"; System.Console.WriteLine($"Send request ! zigbee2mqtt/{DeviceNameForAction}/set/{request}"); MqttClientService.PublishMessage("zigbee2mqtt/" + DeviceNameForAction + "/set", request); } catch (Exception ex) { System.Console.WriteLine($"Not a valid action !"); } break; case "meross": try { var merossDevice = _DeviceDatabaseService.GetById(actionDeviceToTest.Id); if (merossDevice != null) { //As multisocket channel 0 is all the sockets, skip 0 // two state = first state = ON/OFF, second var = switch channels number (LIST) // TODO better check if (action.States.Count >= 2 && action.States.Any(s => s.Name.ToLower() == "state" && (s.Value.ToLower() == "on" || s.Value.ToLower() == "off" || s.Value.ToLower() == "toggle"))) { var state = action.States.Where(s => s.Name.ToLower() == "state").FirstOrDefault(); var channels = action.States.Where(s => s.Name.ToLower() == "channels").FirstOrDefault(); var plugIds = JsonConvert.DeserializeObject>(channels.Value); List lastStatePlug = new List(); // Off by default if (actionDeviceToTest.LastState != null) // Get last state of device { try { lastStatePlug = JsonConvert.DeserializeObject>(actionDeviceToTest.LastState); } catch (Exception ex) { System.Console.WriteLine($"Meross error: Last state ! - {ex}"); } } else { // By default set to OFF foreach (var plug in plugIds) { lastStatePlug.Add(ToggleStatus.OFF); } } List statuses = new List(); var i = 0; foreach (var plugId in plugIds) { ToggleStatus status = new ToggleStatus(); // check if device, check if multiprise switch (merossDevice.Type) { case DeviceType.Multiplug: case DeviceType.Plug: if (state.Value.ToLower() == "toggle") { status = lastStatePlug[i] == ToggleStatus.OFF ? ToggleStatus.ON : ToggleStatus.OFF; } else { status = state.Value.ToLower() == "on" ? ToggleStatus.ON : ToggleStatus.OFF; } break; } // Send request MqttClientMerossService.ExecuteCommand(actionDeviceToTest.ServiceIdentification, Method.SET, CommandMqtt.TOGGLEX, "", "", status, plugId); statuses.Add(status); i++; } // Update Last state actionDeviceToTest.LastState = JsonConvert.SerializeObject(statuses); actionDeviceToTest.LastStateDate = DateTime.Now; _DeviceDatabaseService.Update(actionDeviceToTest); } else { throw new Exception("Meross: Request action invalid"); } // TODO UPDATE STATE } } catch (Exception ex) { System.Console.WriteLine($"Meross error: {ex}"); } break; case "yeelight": if (YeelightService.devices.Count <= 0) { await YeelightService.GetDevices(); } var labLamp = YeelightService.devices.Where(d => d.Hostname == actionDeviceToTest.IpAddress).FirstOrDefault(); if (labLamp != null) { // Check action type.. Task.Run(async () => { await YeelightService.Toggle(labLamp); }); } break; } } else { System.Console.WriteLine($"Device or group found in action incorrect"); } } } } } } zigbeeDevice.LastStateDate = DateTime.Now; zigbeeDevice.LastState = message; _DeviceDatabaseService.Update(zigbeeDevice); } else { // do nothing is a set } } catch (Exception ex) { } } break; default: break; } } private static List GetDeviceExposes(Device device) { List exposes = new List(); // Get Exposes for the zigbee device foreach (var supportedOperation in device.SupportedOperations) { exposes.Add(JsonConvert.DeserializeObject(supportedOperation)); } return exposes; } private static System.Type GetStateType(Expose expose) { switch (expose.type) { // e.g.: occupancy case "binary": if (expose.value_toggle == null) { // value are boolean return true.GetType(); } else { // value are string return "".GetType(); } case "numeric": return new long().GetType(); case "enum": return expose.values.GetType(); break; /*case "text": break; case "light": break; case "composite": break; case "switch": break;*/ /*case "fan": break; case "cover": break; case "lock": break; case "climate": break;*/ default: return null; } } public static async Task UpdateZigbee2MqttConfigAsync(string topic, string message, string userId, DeviceDatabaseService _DeviceDatabaseService, GroupDatabaseService _GroupDatabaseService, ProviderDatabaseService _ProviderDatabaseService, LocationDatabaseService _LocationDatabaseService) { // update zigbee2mqqtt config switch (topic) { case "zigbee2mqtt/bridge/config/devices": try { var devices = JsonConvert.DeserializeObject>(message); var zigbee2mqttProvider = _ProviderDatabaseService.GetByType("zigbee2mqtt"); if (zigbee2mqttProvider != null) { // Retrieve existing devices List existingDevices = _DeviceDatabaseService.GetByProviderId(zigbee2mqttProvider.Id); var existingDevicesAddresses = existingDevices.Select(ed => ed.ServiceIdentification); // Filter devices and check if something new var filteredDevices = devices.Where(d => !existingDevicesAddresses.Contains(d.ieee_address)).ToList(); // Add new devices Dictionary> createdDevices = await DeviceService.CreateFromZigbeeAsync(_DeviceDatabaseService, _ProviderDatabaseService, _LocationDatabaseService, userId, filteredDevices, zigbee2mqttProvider); } System.Console.WriteLine($"Devices updated for user {userId}"); } catch (Exception ex) { Console.WriteLine($"Error during retrieving devices ! Exception: {ex}"); } break; case "zigbee2mqtt/bridge/groups": try { var groupsConvert = JsonConvert.DeserializeObject>(message); var zigbee2mqttProvider = _ProviderDatabaseService.GetByType("zigbee2mqtt"); if (zigbee2mqttProvider != null) { var groups = _GroupDatabaseService.GetByType(userId, "zigbee2mqtt"); // Compare the groups from MyCore and the group we received, if something diff in one group => Hard refresh (delete, new) // TODO : wait for new devices // GroupService.CompareGroupsFromZigbee2Mqtt(userId, groups, groupsConvert, _DeviceDatabaseService, _GroupDatabaseService); } } catch (Exception ex) { Console.WriteLine($"Error during retrieving groups ! Exception: {ex}"); } break; } } } }