using MyCore.Interfaces.Models; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using MyCore.Interfaces.DTO; using static Mqtt.Client.AspNetCore.Services.MqttClientMerossService; using Mqtt.Client.AspNetCore.Services; using MyCore.Services; namespace MyCore.Service.Controllers.Helpers { public class DevicesHelper { #region Common methods public 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; } public 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; } } #endregion #region ACTIONS ! public async static void ActionOnYeelight(DeviceDatabaseService deviceDatabaseService, Device actionDeviceToTest, Interfaces.Models.Action action) { if (YeelightService.devices.Count <= 0) { await YeelightService.GetDevices(); } var lamp = YeelightService.devices.Where(d => d.Hostname == actionDeviceToTest.IpAddress).FirstOrDefault(); if (lamp != null) { System.Console.WriteLine($"Yeelight type !"); // Get device last state var dic = new List(); if (actionDeviceToTest.LastState != null) { dic = JsonConvert.DeserializeObject>(actionDeviceToTest.LastState); } try { switch (actionDeviceToTest.Type) { case DeviceType.Light: var states = action.States.Where(s => s.Name == "state" || s.Name == "brightness").ToList(); if (states.Count >= 0) { if (dic.Count > 0) { /*if (dic["state"].ToString().ToLower() == state.Value.ToLower() && action.States.Count <= 1) // workaround if brightness not the same { throw new Exception($"Action device is already at the good state : {state.Name} {state.Value}"); }*/ } foreach (var state in states) { switch (state.Name) { case "state": switch (state.Value.ToLower()) { case "on": Task.Run(async () => { await YeelightService.TurnOn(lamp, null); }); break; case "off": Task.Run(async () => { await YeelightService.TurnOff(lamp); }); break; case "toggle": Task.Run(async () => { await YeelightService.Toggle(lamp); }); break; } break; case "brightness": var brightness = int.Parse(state.Value); Task.Run(async () => { await YeelightService.TurnOn(lamp, brightness); }); break; } } // Update Last state actionDeviceToTest.LastState = JsonConvert.SerializeObject(states); actionDeviceToTest.LastStateDate = DateTime.Now; deviceDatabaseService.Update(actionDeviceToTest); } else { throw new Exception($"Action device light in yeelight does not have correct expose"); } break; default: break; } } catch (Exception ex) { System.Console.WriteLine($"Not a valid action !"); } } else { System.Console.WriteLine($"Yeelight - Lamp offline! - {actionDeviceToTest.Name}"); } } public static void ActionOnMeross(DeviceDatabaseService _DeviceDatabaseService, Device actionDeviceToTest, Interfaces.Models.Action action, string deviceNameForAction) { 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 // => TODO GET ALL POSSIBILITIES FROM DEVICE (number of channels) 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 p = 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[p] == 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); p++; } // Update Last state actionDeviceToTest.LastState = JsonConvert.SerializeObject(statuses); actionDeviceToTest.LastStateDate = DateTime.Now; _DeviceDatabaseService.Update(actionDeviceToTest); } else { throw new Exception("Meross: Request action invalid"); } } } public static void ActionOnZigbee2Mqtt(Device device, Interfaces.Models.Action action, string deviceNameForAction) { var request = ""; var buildRequest = new Dictionary(); System.Console.WriteLine($"zigbee2mqtt type !"); var actionDeviceExpose = DevicesHelper.GetDeviceExposes(device); // Get device last state var dic = new Dictionary(); if (device.LastState != null) { dic = JsonConvert.DeserializeObject>(device.LastState); } try { switch (device.Type) { case DeviceType.Light: case DeviceType.Switch: var actionDeviceExposeForFeatures = actionDeviceExpose.Where(e => e.type == ((DeviceType)device.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)) { // Comment this for test (ensure the request will be sent) /*if (dic.Count > 0) { //if (device.Type == DeviceType.Light) {} var test = dic["state"].ToString().ToLower(); var test0 = state.Value.ToLower(); if (dic["state"].ToString().ToLower() == state.Value.ToLower() && action.States.Count <= 1) // workaround if brightness not the same => For switch or light without brightness { throw new Exception($"Action device is already at the good state : {state.Name} {state.Value}"); } }*/ 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}"); } } // If any action ask for state update, check if device is already at the asked state if (action.States.Any(s => s.Name == "state") && !action.IsForce) { bool sendRequest = true; // If action doesn't ask for brightness just test if device is already at the asked state if (action.States.Any(s => s.Name == "brightness")) { // Check state and brightness // if we got state value (current value) if (dic.Any(d => d.Key == "state")) { var currentValue = dic["state"].ToString().ToLower(); var askedValue = action.States.FirstOrDefault(s => s.Name == "state").Value.ToLower(); if (currentValue == askedValue) { // Check brightness difference if (dic.Any(d => d.Key == "brightness")) { try { var currentValueBrightness = int.Parse(dic["brightness"].ToString().ToLower()); var askedValueBrightness = int.Parse(action.States.FirstOrDefault(s => s.Name == "brightness").Value.ToLower()); if (Math.Abs(currentValueBrightness - askedValueBrightness) <= 5) // brightness diff is lower than 5 => don't change brightness sendRequest = false; else sendRequest = true; } catch (Exception ex) { sendRequest = false; System.Console.WriteLine($"int parse error in brightness check", ex.Message); } } } } } else { // Check only state value // if we got state value (current value) if (dic.Any(d => d.Key == "state")) { var currentValue = dic["state"].ToString().ToLower(); var askedValue = action.States.FirstOrDefault(s => s.Name == "state").Value.ToLower(); if (currentValue == askedValue) sendRequest = false; } } if (!sendRequest) throw new Exception($"Action device is already at the good state"); } } 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 !"); } } #endregion } }