2023-08-31 17:10:30 +02:00

389 lines
19 KiB
C#

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<Expose> GetDeviceExposes(Device device)
{
List<Expose> exposes = new List<Expose>();
if (device.SupportedOperations != null) {
// Get Exposes for the zigbee device
foreach (var supportedOperation in device.SupportedOperations)
{
exposes.Add(JsonConvert.DeserializeObject<Expose>(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<AutomationState>();
if (actionDeviceToTest.LastState != null)
{
dic = JsonConvert.DeserializeObject<List<AutomationState>>(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;
actionDeviceToTest.LastMessage = JsonConvert.SerializeObject(states);
actionDeviceToTest.LastMessageDate = 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<List<int>>(channels.Value);
List<ToggleStatus> lastStatePlug = new List<ToggleStatus>(); // 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<List<ToggleStatus>>(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<ToggleStatus> statuses = new List<ToggleStatus>();
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.LastMessage = JsonConvert.SerializeObject(statuses);
actionDeviceToTest.LastMessageDate = DateTime.Now;
_DeviceDatabaseService.Update(actionDeviceToTest);
}
else
{
throw new Exception("Meross: Request action invalid");
}
}
}
public static void ActionOnZigbee2Mqtt(Device device, Interfaces.Models.Action action, string deviceOrGroupNameForAction)
{
var request = "";
var buildRequest = new Dictionary<string, object>();
System.Console.WriteLine($"zigbee2mqtt type !");
var actionDeviceExpose = DevicesHelper.GetDeviceExposes(device);
// Get device last state
var dic = new Dictionary<string, object>();
if (device.LastState != null)
{
dic = JsonConvert.DeserializeObject<Dictionary<string, object>>(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/{deviceOrGroupNameForAction}/set/{request}");
MqttClientService.PublishMessage($"zigbee2mqtt/{deviceOrGroupNameForAction}/set", request);
}
catch (Exception ex)
{
System.Console.WriteLine($"Not a valid action !");
}
}
#endregion
}
}