Meross Service Mqtt publish message toggle works ! Toggle-X plugs + Read current consumption + get abilities..

This commit is contained in:
Thomas Fransolet 2020-01-20 22:51:08 +01:00
parent b9599ec357
commit 0441baa6b2
6 changed files with 296 additions and 50 deletions

Binary file not shown.

View File

@ -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
}
}

View File

@ -14,6 +14,7 @@ using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using static MyCore.Models.Meross.DeviceAbilities;
namespace MyCore.Services namespace MyCore.Services
{ {
@ -31,6 +32,8 @@ namespace MyCore.Services
private static ResultToken resultToken; private static ResultToken resultToken;
private List<MerossDevice> allDevices;
// API MQTT // API MQTT
private const int port = 2001; private const int port = 2001;
private const string domain = "iot.meross.com"; private const string domain = "iot.meross.com";
@ -80,7 +83,7 @@ namespace MyCore.Services
if (deviceTask.Result != "") if (deviceTask.Result != "")
{ {
data = ((JObject)JsonConvert.DeserializeObject(deviceTask.Result))["data"]; data = ((JObject)JsonConvert.DeserializeObject(deviceTask.Result))["data"];
var test = JsonConvert.DeserializeObject<List<MerossDevice>>(data.ToString()); allDevices = JsonConvert.DeserializeObject<List<MerossDevice>>(data.ToString());
} }
var logTask = Task.Run(() => PostURI(new Uri(_logUrl), RequestType.Log)); var logTask = Task.Run(() => PostURI(new Uri(_logUrl), RequestType.Log));
@ -189,6 +192,74 @@ namespace MyCore.Services
return nonceString.ToString(); 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() private void ConnexionToMqtt()
{ {
try try
@ -199,8 +270,13 @@ namespace MyCore.Services
string stringForMD5 = resultToken.userid + resultToken.key; string stringForMD5 = resultToken.userid + resultToken.key;
string hashed_password = CreateMD5(stringForMD5).ToLower(); 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() _options = new MqttClientOptionsBuilder()
.WithClientId("app:ddddd9eb14407b666f559af5af9c1840") .WithClientId(clientId)
.WithTcpServer(domain, port) .WithTcpServer(domain, port)
.WithCredentials(resultToken.userid, hashed_password) .WithCredentials(resultToken.userid, hashed_password)
.WithCleanSession() .WithCleanSession()
@ -239,8 +315,37 @@ namespace MyCore.Services
{ {
Console.WriteLine("### CONNECTED WITH SERVER ###"); Console.WriteLine("### CONNECTED WITH SERVER ###");
// Subscribe to a topic var topic = $"/app/{resultToken.userid}-{appId.ToLower()}/subscribe";
await _client.SubscribeAsync(new TopicFilterBuilder().WithTopic("#").Build()); 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 ###"); Console.WriteLine("### SUBSCRIBED ###");
}); });
@ -254,7 +359,30 @@ namespace MyCore.Services
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
Console.WriteLine(); 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) catch (Exception e)
{ {
@ -262,53 +390,110 @@ namespace MyCore.Services
} }
} }
private static bool CertificateValidationCallBack( private void GenerateClientAndAppId(string uuid)
object sender,
System.Security.Cryptography.X509Certificates.X509Certificate certificate,
System.Security.Cryptography.X509Certificates.X509Chain chain,
System.Net.Security.SslPolicyErrors sslPolicyErrors)
{ {
// If the certificate is a valid, signed certificate, return true. var md5Result = CreateMD5($"API{uuid}");
if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None) appId = md5Result;
{ clientId = $"app:{md5Result}";
return true;
} }
// If there are errors in the certificate chain, look at each error to determine the cause. private void ExecuteCommand(string uuid, Method method, CommandMqtt command, string payload = null, string callback = null, ToggleStatus toggleStatus = 0,int channelChosen = 0)
if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{ {
if (chain != null && chain.ChainStatus != null) var topic = $"/appliance/{uuid}/subscribe";
var namespaceVar = "";
var payloadVar = payload;
bool multisocket = false;
// TODO Check device type.. and add switch case
switch (command)
{ {
foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus status in chain.ChainStatus) case CommandMqtt.ABILITY:
{ namespaceVar = Common.ABILITY;
if ((certificate.Subject == certificate.Issuer) && break;
(status.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot)) case CommandMqtt.CONSUMPTIONX:
{ namespaceVar = PlugsAndBulbs.TOGGLEX;
// Self-signed certificates with an untrusted root are valid. break;
continue; case CommandMqtt.TOGGLE:
} namespaceVar = PlugsAndBulbs.TOGGLE;
else break;
{ case CommandMqtt.TOGGLEX:
if (status.Status != System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError) multisocket = true;
{ namespaceVar = PlugsAndBulbs.TOGGLEX;
// If there are any other errors in the certificate chain, the certificate is invalid, break;
// so the method returns false. case CommandMqtt.ELECTRICITY:
return false; namespaceVar = PlugsAndBulbs.ELECTRICITY;
} break;
}
}
} }
// When processing reaches this line, the only errors in the certificate chain are // if a plug
// untrusted root errors for self-signed certificates. These certificates are valid // and if a toggleX ability
// for default Exchange server installations, so return true. if (method == Method.SET && multisocket)
return true;
}
else
{ {
// In all other cases, return false. PayLoadToggleX payLoadToggleX = new PayLoadToggleX();
return false; 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);
} }
} }
} }