diff --git a/.vs/MyCore/DesignTimeBuild/.dtbcache b/.vs/MyCore/DesignTimeBuild/.dtbcache index e9d0d9b..9720a90 100644 Binary files a/.vs/MyCore/DesignTimeBuild/.dtbcache and b/.vs/MyCore/DesignTimeBuild/.dtbcache differ diff --git a/.vs/MyCore/v15/Server/sqlite3/storage.ide b/.vs/MyCore/v15/Server/sqlite3/storage.ide index 3303ca8..031dd8d 100644 Binary files a/.vs/MyCore/v15/Server/sqlite3/storage.ide and b/.vs/MyCore/v15/Server/sqlite3/storage.ide differ diff --git a/.vs/MyCore/v15/Server/sqlite3/storage.ide-shm b/.vs/MyCore/v15/Server/sqlite3/storage.ide-shm index 6716a8b..7b530e2 100644 Binary files a/.vs/MyCore/v15/Server/sqlite3/storage.ide-shm and b/.vs/MyCore/v15/Server/sqlite3/storage.ide-shm differ diff --git a/.vs/MyCore/v15/Server/sqlite3/storage.ide-wal b/.vs/MyCore/v15/Server/sqlite3/storage.ide-wal index 6938aa0..b26556f 100644 Binary files a/.vs/MyCore/v15/Server/sqlite3/storage.ide-wal and b/.vs/MyCore/v15/Server/sqlite3/storage.ide-wal differ diff --git a/MyCore/Models/Meross/DevicesAbilities.cs b/MyCore/Models/Meross/DevicesAbilities.cs new file mode 100644 index 0000000..805df46 --- /dev/null +++ b/MyCore/Models/Meross/DevicesAbilities.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MyCore.Models.Meross +{ + public class DeviceAbilities + { + # region Common abilities + public class Common + { + public static string ALL = "Appliance.System.All"; + public static string ABILITY = "Appliance.System.Ability"; + public static string REPORT = "Appliance.System.Report"; + public static string ONLINE = "Appliance.System.Online"; + public static string WIFI_LIST = "Appliance.Config.WifiList"; + public static string DEBUG = "Appliance.System.Debug"; + public static string TRACE = "Appliance.Config.Trace"; + } + #endregion + + # region Power plug/bulbs abilities + public class PlugsAndBulbs + { + public static string TOGGLE = "Appliance.Control.Toggle"; + public static string TOGGLEX = "Appliance.Control.ToggleX"; + public static string TRIGGER = "Appliance.Control.Trigger"; + public static string TRIGGERX = "Appliance.Control.TriggerX"; + public static string ELECTRICITY = "Appliance.Control.Electricity"; + public static string CONSUMPTIONX = "Appliance.Control.ConsumptionX"; + } + #endregion + + # region Hub + public class Hub + { + public static string HUB_TOGGLEX = "Appliance.Hub.ToggleX"; + public static string HUB_ONLINE = "Appliance.Hub.Online"; + public static string HUB_MTS100_TEMPERATURE = "Appliance.Hub.Mts100.Temperature"; + public static string HUB_MTS100_MODE = "Appliance.Hub.Mts100.Mode"; + public static string HUB_MTS100_ALL = "Appliance.Hub.Mts100.All"; + public static string HUB_EXCEPTION = "Appliance.Hub.Exception"; + } + #endregion + + # region Garage opener abilities + public class Garage + { + public static string GARAGE_DOOR_STATE = "Appliance.GarageDoor.State"; + } + #endregion + + #region Bulbs-only abilities + public class Bulb + { + public static string LIGHT = "Appliance.Control.Light"; + } + #endregion + } +} diff --git a/MyCore/Services/MerossService.cs b/MyCore/Services/MerossService.cs index 41e2af5..a2ff293 100644 --- a/MyCore/Services/MerossService.cs +++ b/MyCore/Services/MerossService.cs @@ -14,6 +14,7 @@ using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; +using static MyCore.Models.Meross.DeviceAbilities; namespace MyCore.Services { @@ -31,6 +32,8 @@ namespace MyCore.Services private static ResultToken resultToken; + private List allDevices; + // API MQTT private const int port = 2001; private const string domain = "iot.meross.com"; @@ -80,7 +83,7 @@ namespace MyCore.Services if (deviceTask.Result != "") { data = ((JObject)JsonConvert.DeserializeObject(deviceTask.Result))["data"]; - var test = JsonConvert.DeserializeObject>(data.ToString()); + allDevices = JsonConvert.DeserializeObject>(data.ToString()); } var logTask = Task.Run(() => PostURI(new Uri(_logUrl), RequestType.Log)); @@ -189,6 +192,74 @@ namespace MyCore.Services return nonceString.ToString(); } + public class RequestMQTT + { + public HeaderMqtt header; + public Object payload; + } + + public class HeaderMqtt + { + public string from; + public string messageId; + public string method; + public string Namespace; + public int payloadVersion; + public string sign; + public long timestamp; + } + + public class PayLoadToggle + { + // as it's not a multi-socket + public int channel = 0; + public Toggle toggle; + } + + public class Toggle + { + // 1 = on or 0 = off + public int onoff; + } + + public class PayLoadToggleX + { + public ToggleX togglex; + } + + public class ToggleX + { + // 1 = on or 0 = off + public int onoff; + // number of the socket of the multi-socket + public int channel; + } + + // TODO Create ability class related to device + public enum CommandMqtt + { + ABILITY, + CONSUMPTIONX, + ELECTRICITY, + TOGGLE, + TOGGLEX + } + + public enum Method + { + GET, + SET + } + + public enum ToggleStatus + { + OFF, + ON + } + + public string clientId = ""; + public string appId = ""; + private void ConnexionToMqtt() { try @@ -199,8 +270,13 @@ namespace MyCore.Services string stringForMD5 = resultToken.userid + resultToken.key; string hashed_password = CreateMD5(stringForMD5).ToLower(); + // Check if we can hardcode it -> seems ok 20/01/2020 + var uuid = "01df8092-d790-4377-8481-5b73aef045c6"; + + GenerateClientAndAppId(uuid); + _options = new MqttClientOptionsBuilder() - .WithClientId("app:ddddd9eb14407b666f559af5af9c1840") + .WithClientId(clientId) .WithTcpServer(domain, port) .WithCredentials(resultToken.userid, hashed_password) .WithCleanSession() @@ -239,8 +315,37 @@ namespace MyCore.Services { Console.WriteLine("### CONNECTED WITH SERVER ###"); - // Subscribe to a topic - await _client.SubscribeAsync(new TopicFilterBuilder().WithTopic("#").Build()); + var topic = $"/app/{resultToken.userid}-{appId.ToLower()}/subscribe"; + await _client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build()); + + /*foreach (var device in allDevices) + { + topic = $"/appliance/{device.uuid}/subscribe"; + // Not really neccessary as the GETACK exists + //await _client.SubscribeAsync(new TopicFilterBuilder().WithTopic(topic).Build()); + }*/ + + MerossDevice multisocket = allDevices.Where(d => d.onlineStatus == 1 && d.deviceType == "mss425f").FirstOrDefault(); + + /*if (multisocket != null && multisocket.onlineStatus == 1) + { + //As multisocket channel 0 is all the sockets, skip 0 + int i = 0; + foreach (var channel in multisocket.channels) + { + if (i != 0) + ExecuteCommand(multisocket.uuid, Method.SET, CommandMqtt.TOGGLEX, "", "", ToggleStatus.OFF, i); + i++; + } + }*/ + + ExecuteCommand(multisocket.uuid, Method.GET, CommandMqtt.ABILITY); + + foreach (var device in allDevices.Where(d => d.deviceType == "mss310" && d.onlineStatus == 1).ToList()) + { + ExecuteCommand(device.uuid, Method.GET, CommandMqtt.ABILITY); + ExecuteCommand(device.uuid, Method.GET, CommandMqtt.ELECTRICITY); + } Console.WriteLine("### SUBSCRIBED ###"); }); @@ -254,7 +359,30 @@ namespace MyCore.Services Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); Console.WriteLine(); + // TODO GETACK and writing db + get current consumption + reliability of changing status of a plug + get current temp in bathroom + etc }); + + + /* Example : + * + * + * Toggle channel 0 multiprise + * topic = /appliance/1909206725106690802148e1e95243fd/subscribe + * + * payload = + * {"header": {"from": "/app/174744-2c2c67eab37f04625d60dc678e5a2349/subscribe", + * "messageId": "5e2ac8700552ef089da05f8543993319", + * "method": "SET", + * "namespace": "Appliance.Control.ToggleX", + * "payloadVersion": 1, + * "sign": "18469fb032695f3144f060e65f3fad26", + * "timestamp": 1579540738}, + * "payload": + * {"togglex": + * {"onoff": 1, "channel": 0} + * } + * } + */ } catch (Exception e) { @@ -262,53 +390,110 @@ namespace MyCore.Services } } - private static bool CertificateValidationCallBack( - object sender, - System.Security.Cryptography.X509Certificates.X509Certificate certificate, - System.Security.Cryptography.X509Certificates.X509Chain chain, - System.Net.Security.SslPolicyErrors sslPolicyErrors) + private void GenerateClientAndAppId(string uuid) + { + var md5Result = CreateMD5($"API{uuid}"); + appId = md5Result; + clientId = $"app:{md5Result}"; + } + + private void ExecuteCommand(string uuid, Method method, CommandMqtt command, string payload = null, string callback = null, ToggleStatus toggleStatus = 0,int channelChosen = 0) + { + var topic = $"/appliance/{uuid}/subscribe"; + var namespaceVar = ""; + var payloadVar = payload; + bool multisocket = false; + + // TODO Check device type.. and add switch case + switch (command) { - // If the certificate is a valid, signed certificate, return true. - if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None) - { - return true; - } - - // If there are errors in the certificate chain, look at each error to determine the cause. - if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0) - { - if (chain != null && chain.ChainStatus != null) - { - foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus status in chain.ChainStatus) - { - if ((certificate.Subject == certificate.Issuer) && - (status.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot)) - { - // Self-signed certificates with an untrusted root are valid. - continue; - } - else - { - if (status.Status != System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError) - { - // If there are any other errors in the certificate chain, the certificate is invalid, - // so the method returns false. - return false; - } - } - } - } - - // When processing reaches this line, the only errors in the certificate chain are - // untrusted root errors for self-signed certificates. These certificates are valid - // for default Exchange server installations, so return true. - return true; - } - else - { - // In all other cases, return false. - return false; - } + case CommandMqtt.ABILITY: + namespaceVar = Common.ABILITY; + break; + case CommandMqtt.CONSUMPTIONX: + namespaceVar = PlugsAndBulbs.TOGGLEX; + break; + case CommandMqtt.TOGGLE: + namespaceVar = PlugsAndBulbs.TOGGLE; + break; + case CommandMqtt.TOGGLEX: + multisocket = true; + namespaceVar = PlugsAndBulbs.TOGGLEX; + break; + case CommandMqtt.ELECTRICITY: + namespaceVar = PlugsAndBulbs.ELECTRICITY; + break; } + + // if a plug + // and if a toggleX ability + if (method == Method.SET && multisocket) + { + PayLoadToggleX payLoadToggleX = new PayLoadToggleX(); + ToggleX toggleX = new ToggleX(); + toggleX.onoff = toggleStatus.GetHashCode(); + toggleX.channel = channelChosen; + payLoadToggleX.togglex = toggleX; + + payloadVar = JsonConvert.SerializeObject(payLoadToggleX); + } + + // if a plug + // and if hasn't toggleX ability + if (method == Method.SET && !multisocket) + { + PayLoadToggle payLoadToggle= new PayLoadToggle(); + Toggle toggle = new Toggle(); + toggle.onoff = toggleStatus.GetHashCode(); + payLoadToggle.toggle = toggle; + + payloadVar = JsonConvert.SerializeObject(payLoadToggle); + } + + var message = BuildMQTTMessage(method, namespaceVar, payloadVar); + + PublishMessage(topic, message); + } + + private string BuildMQTTMessage(Method method, string namespaceVar, string payloadVar) + { + HeaderMqtt headerMqtt = new HeaderMqtt(); + + headerMqtt.from = $"/app/{resultToken.userid}-{appId.ToLower()}/subscribe"; + headerMqtt.messageId = CreateMD5(GenerateNonce(16).ToUpper()).ToLower(); + headerMqtt.method = method.ToString(); + headerMqtt.Namespace = namespaceVar; + headerMqtt.payloadVersion = 1; + headerMqtt.timestamp = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds(); + headerMqtt.sign = CreateMD5($"{headerMqtt.messageId}{resultToken.key}{(headerMqtt.timestamp).ToString()}").ToLower(); + + + RequestMQTT requestMQTT = new RequestMQTT(); + requestMQTT.header = headerMqtt; + if (payloadVar != null) + requestMQTT.payload = JsonConvert.DeserializeObject(payloadVar); + else + requestMQTT.payload = new Object(); + + var data = JsonConvert.SerializeObject(requestMQTT); + + // Change Namespace to namespace + data = data.Replace("Namespace", "namespace"); + + return data; + } + + public void PublishMessage(string topic, string message) + { + var mqttMessage = new MqttApplicationMessageBuilder() + .WithTopic(topic) + .WithPayload(message) + .WithExactlyOnceQoS() + .WithRetainFlag() + .Build(); + + if (_client.IsConnected) + _client.PublishAsync(mqttMessage); + } } }