You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iot/projects/IoTNode/DeviceServices/LiChuang/LCDAUE800DService.cs

666 lines
26 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using Application.Domain.Entities;
using Infrastructure.Data;
using Infrastructure.Extensions;
using IoT.Shared.Application.Models;
using IoT.Shared.Services;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
namespace IoTNode.DeviceServices.LiChuang
{
public class LCDAUE800DService : BaseDeviceService
{
private static ushort[] crcTable = new ushort[]{
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
};
private static Dictionary<string, string> Keys = new Dictionary<string, string> {
{ "1","有功电度"},
{ "2","有功功率"},
{ "3","无功电度"},
{ "4","无功功率"},
{ "5","A相电压"},
{ "6","B相电压"},
{ "7","C相电压"},
{ "8","A相电流"},
{ "9","B相电流"},
{ "10","C相电流"},
{ "11","功率因数"},
{ "12","A相功率"},
{ "13","B相功率"},
{ "14","C相功率"},
{ "15","水燃气当前流量"},
{ "16","结算日热量"},
{ "17","当前热量"},
{ "18","热功率"},
{ "19","瞬时流量"},
{ "20","当前累计流量"},
{ "21","供水温度"},
{ "22","回水温度"},
{ "23","累计时间"},
{ "24","累计时间"},
{ "25","自定义2"},
{ "26","自定义3"}
};
private List<byte> _frame = new List<byte>();
private int _frameLength = 0;
private TcpClient _client;
private TcpListener _listener;
private CancellationToken _cancellationToken;
private string _mac;
private readonly ILogger<LCDAUE800DService> _logger;
public LCDAUE800DService(IServiceProvider applicationServices, IWebHostEnvironment env, ILogger<LCDAUE800DService> logger)
: base(applicationServices, env)
{
this._logger = logger;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
this._env.Debug("~~start");
this._cancellationToken = cancellationToken;
return base.StartAsync(cancellationToken);
}
public override void Execute()
{
this._env.Debug("lc exec");
try
{
if (this._listener == null)
{
this._listener = new TcpListener(IPAddress.Any, 7000);
this._listener.Start();
Task.Run(() =>
{
try
{
while (!this._cancellationToken.IsCancellationRequested)
{
this._client = _listener.AcceptTcpClient();
Task.Run(() =>
{
this.Run();
});
}
}
catch (Exception ex)
{
ex.PrintStack();
}
});
}
}
catch (Exception ex)
{
this._listener = null;
ex.PrintStack();
}
}
public override Task StopAsync(CancellationToken cancellationToken)
{
this._env.Debug("lc end");
return base.StopAsync(cancellationToken);
}
public void Run()
{
var stream = _client.GetStream();
byte[] buffer = new byte[10240];
int bufferLength;
while (_client.Connected)
{
if (stream.DataAvailable)
{
try
{
bufferLength = stream.Read(buffer);
var rawData = buffer.Take(bufferLength).ToArray();
this._env.Debug($"from {_client.Client.RemoteEndPoint}:{rawData.Length}");
UnitHandle(stream, rawData);
}
catch (Exception ex)
{
this._env.Debug(ex);
}
}
else
{
try
{
stream.Write(buffer, 0, 0);
}
catch
{
break;
}
}
}
}
private void UnitHandle(NetworkStream stream, byte[] data)
{
var start = BitConverter.ToString(data.Take(4).ToArray()).Replace("-", "").ToLower();
var length = data.Length;
if (start == "aa55aa55")
{
var unitLength = 4 + 4 + BitConverter.ToInt32(data.Skip(4).Take(4).ToArray()) + 2 + 4;
if (length == unitLength)
{
this._env.Debug("单个包");
Handle(stream, data);
}
else if (length > unitLength)
{
this._env.Debug("多个包");
Handle(stream, data.Take(unitLength).ToArray());
UnitHandle(stream, data.Skip(unitLength).ToArray());
}
else if (length < unitLength)
{
this._env.Debug("长包开头");
_frame.Clear();
_frame.AddRange(data);
_frameLength = unitLength;
}
}
else
{
if (_frame.Count + length == _frameLength)
{
this._env.Debug("长包结尾");
_frame.AddRange(data);
Handle(stream, _frame.ToArray());
_frame.Clear();
}
else if (_frame.Count + length > _frameLength)
{
this._env.Debug("长包结尾+后续包");
_frame.AddRange(data.Take(length - _frame.Count));
Handle(stream, _frame.ToArray());
Handle(stream, data.Skip(length - _frame.Count).ToArray());
}
else if (_frame.Count + length < _frameLength)
{
this._env.Debug("长包中间");
_frame.AddRange(data);
}
}
}
private void Handle(NetworkStream stream, byte[] rawData)
{
var xml = GetXml(rawData);
this._env.Debug("收到客户端的消息");
this._env.Debug(() => FormatXml(xml));
var type = GetTypeFromXml(xml);
this._env.Debug($"type:{type}");
if (type == "request")
{
HandleRequest(stream, xml);
}
else if (type == "md5")
{
HandleMd5(stream, xml);
}
else if (type == "device")
{
HandleDevice(stream, xml);
}
else if (type == "notify")
{
HandleNotify(stream, xml);
}
else if (type == "report")
{
HandleReport(stream, xml);
}
else if (type == "continuous")
{
HandleReport(stream, xml);
}
}
private void HandleRequest(NetworkStream stream, string xml)
{
var command = "sequence";
var operation = "operation";
var doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.LoadXml(xml);
doc.SelectSingleNode("/root/common/type").InnerText = command;
var idValidate = doc.SelectSingleNode("/root/id_validate");
idValidate.Attributes[operation].Value = command;
var sequence = doc.CreateElement(command);
sequence.InnerText = "01234567";
idValidate.AppendChild(sequence);
Reply(stream, doc.OuterXml);
}
private void HandleMd5(NetworkStream stream, string xml)
{
var command = "md5";
var operation = "operation";
var doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.LoadXml(xml);
var value1 = doc.SelectSingleNode($"/root/id_validate/{command}").InnerText;
using var md5 = MD5.Create();
var value2 = BitConverter.ToString(md5.ComputeHash(Encoding.ASCII.GetBytes("01234567IJKLMNOPQRSTUVWX"))).Replace("-", "").ToLower();
doc.SelectSingleNode("/root/common/type").InnerText = command;
var idValidate = doc.SelectSingleNode("/root/id_validate");
idValidate.Attributes[operation].Value = "result";
var result = doc.CreateElement("result");
result.InnerText = value1 == value2 ? "pass" : "fail";
idValidate.ReplaceChild(result, doc.SelectSingleNode($"/root/id_validate/{command}"));
Reply(stream, doc.OuterXml);
}
private void HandleDevice(NetworkStream stream, string xml)
{
var command = "device_ack";
var operation = "operation";
var doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.LoadXml(xml);
//building_id //build_no
//var building_id = doc.SelectSingleNode("//building_id").InnerText.Trim();
//var buildArea = GetBuildName(building_id);
//var buildType = GetBuildType(building_id);
//var buildName = doc.SelectSingleNode("//build_name").InnerText.Trim();
//var period = doc.SelectSingleNode("//period").InnerText.Trim();
var mac = doc.SelectSingleNode("//mac").InnerText.Trim().Replace(":", "");
var ip = doc.SelectSingleNode("//ip").InnerText.Trim().Replace(":", "");
var period = doc.SelectSingleNode("//period").InnerText.Trim();
var factory = doc.SelectSingleNode("//factory").InnerText.Trim();
var hardware = doc.SelectSingleNode("//hardware").InnerText.Trim();
var software = doc.SelectSingleNode("//software").InnerText.Trim();
this._env.Debug($"factory:{factory},hardware{hardware},software:{software},mac{mac}");
//Console.WriteLine($"楼宇建筑编码:{building_id},行政区域:{buildArea},建筑类型:{buildType},建筑名称:{buildName}");
//add for iotnode start
try
{
this._mac = mac;
var timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds();
var product = this.UpdateProduct("LC集抄器", hardware, "", "collector");
using var scope = _applicationServices.CreateScope();
var iotNodeClient = scope.ServiceProvider.GetService<IoTNodeClient>();
var deviceNodeRepo = scope.ServiceProvider.GetService<IRepository<IoTGateway>>();
var iotNode = deviceNodeRepo.ReadOnlyTable().FirstOrDefault();
var deviceRepo = scope.ServiceProvider.GetService<IRepository<IoTDevice>>();
var device = deviceRepo.Table().FirstOrDefault(o => o.Number == mac);
if (device == null)
{
device = new IoTDevice
{
Name = "LC集抄器",
Number = mac,
DisplayName = "LC集抄器",
Icon = "collector",
Ip = ip,
IoTGatewayId = iotNode.Id,
IoTProductId = product.Id,
IsOnline = true,
};
deviceRepo.Add(device);
deviceRepo.SaveChanges();
this.UpdateIoTData(device.Id, DataKeys.Hardware, hardware);
this.UpdateIoTData(device.Id, DataKeys.Period, period);
}
}
catch (Exception ex)
{
ex.PrintStack();
}
//add for iotnode end
doc.SelectSingleNode("/root/common/type").InnerText = command;
var node = doc.SelectSingleNode("/root/device");
node.RemoveAll();
var deviceOperation = doc.CreateAttribute(operation);
deviceOperation.Value = command;
node.Attributes.Append(deviceOperation);
var deviceAck = doc.CreateElement(command);
deviceAck.InnerText = "pass";
node.AppendChild(deviceAck);
Reply(stream, doc.OuterXml);
}
//private static string GetBuildName(string building_id)
//{
// try
// {
// var doc = new XmlDocument();
// var file = Path.Combine(AppContext.BaseDirectory, "SDLC", "ZoneCode.xml");
// doc.LoadXml(File.ReadAllText(file));
// var value = doc.SelectSingleNode($"//ConfigData[Value={building_id.Substring(0, 6)}]/Value[2]").InnerText;
// return value;
// }
// catch (Exception ex)
// {
// Console.WriteLine(ex.ToString());
// return building_id;
// }
//}
//private static string GetBuildType(string building_id)
//{
// try
// {
// var doc = new XmlDocument();
// var file = Path.Combine(AppContext.BaseDirectory, "SDLC", "BuildingSet.xml");
// doc.LoadXml(File.ReadAllText(file));
// var value = doc.SelectSingleNode($"//ConfigData[Value={building_id.Skip(6).Take(1)}]/Value[1]").InnerText;
// return value;
// }
// catch (Exception ex)
// {
// Console.WriteLine(ex.ToString());
// return building_id;
// }
//}
private void HandleNotify(NetworkStream stream, string xml)
{
var command = "time";
var operation = "operation";
var doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.LoadXml(xml);
doc.SelectSingleNode("/root/common/type").InnerText = command;
var node = doc.SelectSingleNode("/root/heart_beat");
node.RemoveAll();
var deviceOperation = doc.CreateAttribute(operation);
deviceOperation.Value = command;
node.Attributes.Append(deviceOperation);
var deviceAck = doc.CreateElement(command);
deviceAck.InnerText = DateTime.Now.ToString("yyyyMMddHHmmss");
node.AppendChild(deviceAck);
Reply(stream, doc.OuterXml);
}
//private void HandleContinuous(NetworkStream stream, string xml)
//{
// //续传数据处理
// var doc = new XmlDocument();
// doc.PreserveWhitespace = true;
// doc.LoadXml(xml);
// var building_id = doc.SelectSingleNode("//building_id").InnerText.Trim();
// var gateway_id = doc.SelectSingleNode("//gateway_id").InnerText.Trim();
// //续传数据响应
// var doc2 = new XmlDocument();
// var xml2 = @$"<?xml version=""1.0"" encoding=""UTF-8"" ?><root><common><building_id>{building_id}</building_id><gateway_id>{gateway_id}</gateway_id><type>report_ack</type></common><report_config operation=""report_ack""><report_ack>pass</report_ack></report_config></root>";
// doc2.LoadXml(xml2);
// Reply(stream, doc2.OuterXml);
//}
private void HandleReport(NetworkStream stream, string xml)
{
//上传数据处理
var doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.LoadXml(xml);
var type = doc.SelectSingleNode("//type").InnerText.Trim();
var building_id = doc.SelectSingleNode("//building_id").InnerText.Trim();
var gateway_id = doc.SelectSingleNode("//gateway_id").InnerText.Trim();
var time = doc.SelectSingleNode("//time").InnerText.Trim();
try
{
var meters = doc.SelectNodes("//meter");
foreach (XmlNode meter in meters)
{
var deviceNo = meter.SelectSingleNode("//functionex");
var equipidex = deviceNo.Attributes["equipidex"].Value.Trim();
var tpex = deviceNo.Attributes["tpex"].Value.Trim();
var functions = meter.SelectNodes("//function");
foreach (XmlNode function in functions)
{
var key = function.Attributes["id"].Value.Trim();
var name = Keys[key];
var value = function.InnerText.Trim();
this._logger.LogDebug($"type:{type},time:{time},equipidex:{equipidex},item:{name},value:{value}");
//add for iotnode start
if (!string.IsNullOrEmpty(this._mac))
{
var timestamp = (type == "report" ? DateTimeOffset.Now : DateTime.ParseExact(time, "yyyyMMddHHmmss", CultureInfo.InvariantCulture)).ToUnixTimeMilliseconds();
using var scope = _applicationServices.CreateScope();
var deviceRepo = scope.ServiceProvider.GetService<IRepository<IoTDevice>>();
var dataRepo = scope.ServiceProvider.GetService<IRepository<IoTData>>();
var deviceId = this.GetIoTDeviceId(this._mac);
if (deviceId.HasValue)
{
var dataKey = GetKey(key);
var unit = this.GetUnit(name)?.ToString();
var dataName = $"{dataKey.GetDisplayName():unit}";
this.UpdateIoTData(deviceId.Value, dataKey, value, name: dataName, unit: unit);
}
}
//add for iotnode end
}
}
}
catch (Exception ex)
{
this._logger.LogError(ex, ex.Message);
}
//上传数据响应
var doc2 = new XmlDocument();
var xml2 = @$"<?xml version=""1.0"" encoding=""UTF-8"" ?><root><common><building_id>{building_id}</building_id><gateway_id>{gateway_id}</gateway_id><type>report_ack</type></common><report_config operation=""report_ack""><report_ack>pass</report_ack></report_config></root>";
doc2.LoadXml(xml2);
Reply(stream, doc2.OuterXml);
}
private DataKeys GetKey(string equipidex)
{
var value = $"Device{equipidex}";
return Enum.Parse<DataKeys>(value);
}
private object GetUnit(string name)
{
if (name.Contains("有功功率"))
{
return "W";
}
if (name.Contains("无功功率"))
{
return "Var";
}
if (name.Contains("电压"))
{
return "V";
}
if (name.Contains("电流"))
{
return "A";
}
if (name.Contains("电能") || name.Contains("有功电度"))
{
return "千瓦时";
}
if (name.Contains("无功电度"))
{
return "Kvarh";
}
if (name.Contains("流量"))
{
return "立方米";
}
return null;
}
private object GetDeviceType(string value)
{
throw new NotImplementedException();
}
private void Reply(NetworkStream stream, string xml)
{
this._env.Debug("服务端响应:");
this._env.Debug(() => FormatXml(xml));
this._env.Debug("服务端响应:");
using var ms = new MemoryStream();
ms.Write(new byte[] { 0xaa, 0x55, 0xaa, 0x55 });//header
var data = new byte[] { 0x00, 0x00, 0x00, 0x00 }.Concat(AesEncrypt(Encoding.UTF8.GetBytes(xml))).ToArray();
ms.Write(BitConverter.GetBytes(data.Length));//length
ms.Write(data);//data
ms.Write(BitConverter.GetBytes((ushort)XmlGetCrc16(data)));//crc
ms.Write(new byte[] { 0x68, 0x68, 0x16, 0x16 });
var response = ms.ToArray();
stream.Write(response);
stream.Flush();
}
private static string GetTypeFromXml(string xml)
{
var doc = new XmlDocument();
doc.LoadXml(xml);
return doc.SelectSingleNode("/root/common/type").InnerText;
}
private string GetXml(byte[] rawData)
{
var dataLength = BitConverter.ToUInt32(rawData.Skip(4).Take(4).ToArray());
var data = rawData.Skip(8).Take((int)dataLength).ToArray();
var xmlData = data.Skip(4).ToArray();
var crc = BitConverter.ToUInt16(rawData.Skip(8 + (int)dataLength).Take(2).ToArray());
var crc2 = XmlGetCrc16(data);
if (crc == crc2)
{
var result = Encoding.UTF8.GetString(AesDecrypt(xmlData));
result = result.Trim();
if (!result.EndsWith(">"))
{
this._env.Debug("xml format error");
result += "></root>";
}
return result;
}
throw new Exception("crc error");
}
private static byte[] HexStringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
private static uint XmlGetCrc16(byte[] data)
{
uint crc = 0, by;
for (int i = 0; i < data.Length; i++)
{
by = (crc >> 8) & 0xff;
crc = (crc & 0xffff) << 8;
crc = (crc ^ crcTable[(data[i] ^ by) & 0xff]) & 0xffff;
}
return crc;
}
private static byte[] AesEncrypt(byte[] plainText)
{
using var aes = CreateAes();
using var encryptor = aes.CreateEncryptor();
return encryptor.TransformFinalBlock(plainText, 0, plainText.Length);
}
private static byte[] AesDecrypt(byte[] cipherText)
{
using var aes = CreateAes();
using var decryptor = aes.CreateDecryptor();
return decryptor.TransformFinalBlock(cipherText, 0, cipherText.Length);
}
private static Aes CreateAes()
{
var key = HexStringToByteArray("0102030405060708090a0b0c0d0e0f10");
var iv = HexStringToByteArray("0102030405060708090a0b0c0d0e0f10");
var aesAlg = Aes.Create();
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.Zeros;
aesAlg.BlockSize = 128;
aesAlg.Key = key;
aesAlg.IV = iv;
return aesAlg;
}
private string FormatXml(string xml)
{
try
{
var doc = new XmlDocument();
doc.LoadXml(xml);
var xdoc = XDocument.Parse(doc.OuterXml);
return xdoc.ToString();
}
catch (Exception ex)
{
this._env.Debug(ex);
return xml;
}
}
}
}