You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

410 lines
16 KiB
C#

using AMWD.Modbus.Common.Interfaces;
using AMWD.Modbus.Common.Structures;
using IoTSharp.Gateways.Data;
using IoTSharp.MqttSdk;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json.Linq;
using Quartz;
using System.IO.Ports;
using System.Web;
using System.Collections.Specialized;
namespace IoTSharp.Gateways.Jobs
{
public class ModbusJob : IJob
{
private ILogger _logger;
private ApplicationDbContext _dbContext;
private MQTTClient _client;
private IMemoryCache _cache;
private readonly ILoggerFactory _factory;
private IServiceScope _serviceScope;
public ModbusJob(ILoggerFactory factory, IServiceScopeFactory scopeFactor, MQTTClient client, IMemoryCache cache)
{
_factory = factory;
_serviceScope = scopeFactor.CreateScope();
_dbContext = _serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
_client = client;
_cache = cache;
}
public async Task Execute(IJobExecutionContext context)
{
var slave_id = new Guid(context.Trigger.JobDataMap.GetString(SchedulerJob.client_id));
var slave_name = context.Trigger.JobDataMap.GetString(SchedulerJob.client_name);
var slave = await _dbContext.Clients.FirstOrDefaultAsync(m => m.Id == slave_id);
if (slave != null)
{
_logger = _factory.CreateLogger($"Slaver:{slave_name}({slave.Address})");
/// DTU: dtu://dev.ttyS0/?BaudRate=115200
/// DTU: dtu://COM1:115200
/// rtu: tcp://www.host.com:602
/// d2t: d2t://www.host.com:602
var client = CreateModbusSlave(slave);
try
{
if (!client.IsConnected) await client.Connect(context.CancellationToken);
if (client.IsConnected)
{
await ReadDatas(slave, client, context.CancellationToken);
await client.Disconnect(context.CancellationToken);
}
}
catch (Exception ex)
{
var msg = $"SlaveId:{slave_id},Device:{slave.DeviceName},IsConnected:{client?.IsConnected},Message:{ex.Message}";
_logger.LogError(ex, msg);
throw new Exception(msg, ex);
}
finally
{
client.Dispose();
}
}
else
{
_logger.LogWarning($"未能找到从机{slave_id}");
}
}
private async Task ReadDatas(Client slave, IModbusClient client, CancellationToken stoppingToken)
{
var points = await _dbContext.ModbusMappings.Include(p => p.Owner).Where(p => p.Owner == slave).ToListAsync();
foreach (var point in points)
{
try
{
switch (point.FunCode)
{
case FunCode.ReadCoils:
var _coils = await client.ReadCoils(point.Code, point.Address, point.Length, stoppingToken);
await UploadCoils(slave, point, _coils);
break;
case FunCode.ReadDiscreteInputs:
await UploadDiscreteInputs(slave, client, point, stoppingToken);
break;
case FunCode.ReadMultipleHoldingRegisters:
var _registers = await client.ReadHoldingRegisters(point.Code, point.Address, point.Length, stoppingToken);
await UploadRegisters(slave, point, _registers);
break;
case FunCode.ReadInputRegisters:
var _input_registers = await client.ReadInputRegisters(point.Code, point.Address, point.Length, stoppingToken);
await UploadRegisters(slave, point, _input_registers);
break;
default:
break;
}
_logger.LogInformation($"从{slave.Address}执行{point.FunCode} 将地址{point.Address}的长度{point.Length}的数据存储到名称{point.DataName}类型{point.DataType}完成。 ");
}
catch (Exception ex)
{
_logger.LogWarning($"从{slave.Address}执行{point.FunCode} 将地址{point.Address}的长度{point.Length}的数据存储到名称{point.DataName}类型{point.DataType}时遇到错误{ex.Message}。");
}
}
}
private async Task UploadDiscreteInputs(Client slave, IModbusClient? client, ModbusMapping point, CancellationToken stoppingToken)
{
var _discreteInputs = await client.ReadDiscreteInputs(point.Code, point.Address, point.Length, stoppingToken);
switch (point.DataType)
{
case DataType.Boolean:
await UploadData(slave, point, _discreteInputs.First().BoolValue);
break;
case DataType.Double:
case DataType.Long:
await UploadData(slave, point, _discreteInputs.First().RegisterValue + 0.0);
break;
default:
break;
}
}
private async Task UploadCoils(Client slave, ModbusMapping point, List<Coil> _coils)
{
switch (point.DataType)
{
case DataType.Boolean:
await UploadData(slave, point, _coils.First().BoolValue);
break;
case DataType.Double:
case DataType.Long:
await UploadData(slave, point, _coils.First().RegisterValue + 0.0);
break;
default:
break;
}
}
private async Task UploadRegisters(Client slave, ModbusMapping point, List<Register> _registers)
{
switch (point.DataType)
{
case DataType.String:
await UploadData(slave, point, RegistersToString(point, _registers));
break;
case DataType.Long:
if (_registers.Count == 1)
{
await UploadData(slave, point, _registers.First().RegisterValue);
}
else if (_registers.Count == 2)
{
await UploadData(slave, point, RegistersToUint32(_registers));
}
else if (_registers.Count == 4)
{
await UploadData(slave, point, RegistersToUint64(_registers));
}
break;
case DataType.Double:
if (_registers.Count == 2)
{
await UploadData(slave, point, RegistersToFloat(_registers));
}
else if (_registers.Count == 4)
{
await UploadData(slave, point, RegistersToDouble(_registers));
}
break;
case DataType.DateTime:
break;
case DataType.Boolean:
await UploadData(slave, point, RegistersToBitVector32(_registers));
break;
default:
_logger.LogWarning($"多寄存器读取方式不支持类型{point.DataType}");
break;
}
}
private static string RegistersToString(ModbusMapping point, List<Register> _registers)
{
var buffer = new List<byte>();
_registers.ForEach(p =>
{
buffer.Add(p.HiByte);
buffer.Add(p.LoByte);
});
var stringvalue = System.Text.Encoding.GetEncoding(point.CodePage).GetString(buffer.ToArray()).TrimNull();
return stringvalue;
}
private static uint RegistersToUint32(List<Register> _registers)
{
var buff = new byte[] { _registers[0].HiByte, _registers[0].LoByte, _registers[1].HiByte, _registers[1].LoByte };
var uint32 = BitConverter.ToUInt32(buff);
return uint32;
}
private static ulong RegistersToUint64(List<Register> _registers)
{
var buff = new byte[] { _registers[0].HiByte, _registers[0].LoByte, _registers[1].HiByte, _registers[1].LoByte
, _registers[2].HiByte, _registers[2].LoByte, _registers[3].HiByte, _registers[3].LoByte};
var uint64 = BitConverter.ToUInt64(buff);
return uint64;
}
private static float RegistersToFloat(List<Register> _registers)
{
var buff = new byte[] { _registers[0].HiByte, _registers[0].LoByte, _registers[1].HiByte, _registers[1].LoByte };
var uint32 = BitConverter.ToSingle(buff);
return uint32;
}
private static double RegistersToDouble(List<Register> _registers)
{
var buff = new byte[] { _registers[0].HiByte, _registers[0].LoByte, _registers[1].HiByte, _registers[1].LoByte
, _registers[2].HiByte, _registers[2].LoByte, _registers[3].HiByte, _registers[3].LoByte};
var uint64 = BitConverter.ToDouble(buff);
return uint64;
}
private static BitVector32 RegistersToBitVector32(List<Register> _registers)
{
BitVector32 vector32 = new BitVector32(0);
if (_registers.Count == 1)//16位
{
vector32 = new BitVector32(BitConverter.ToInt32(new byte[] { _registers[0].HiByte, _registers[0].LoByte, 0, 0 }));
}
else if (_registers.Count == 2)//32位
{
vector32 = new BitVector32(BitConverter.ToInt32(new byte[] { _registers[0].HiByte, _registers[0].LoByte, _registers[1].HiByte, _registers[1].LoByte }));
}
return vector32;
}
private async Task UploadData<T>(Client slave, ModbusMapping point, T? value)
{
if (value != null)
{
JObject jo = new()
{
{ point.DataName, new JValue(value) }
};
switch (point.DataCatalog)
{
case DataCatalog.AttributeData:
await _client.UploadAttributeAsync(slave.DeviceName, jo);
break;
case DataCatalog.TelemetryData:
await _client.UploadTelemetryDataAsync(slave.DeviceName, jo);
break;
default:
break;
}
}
}
private async Task UploadData(Client slave, ModbusMapping point, BitVector32 value)
{
Dictionary<string, short> lst = new Dictionary<string, short>();
2 years ago
var _format = point.DataFormat ?? $"{point.DataName}_unknow1:8;{point.DataName}_unknow2:8";
_format.Split(';').ToList().ForEach(s =>
{
var sk = s.Split(':');
lst.Add(sk[0], short.Parse(sk[1]));
});
var objx = value.ToDictionary(lst);
JObject jo = new()
{
{ point.DataName, new JValue(value.Data) }
};
objx.Keys.ToList().ForEach(s =>
{
jo.Add(s, objx[s]);
});
switch (point.DataCatalog)
{
case DataCatalog.AttributeData:
await _client.UploadAttributeAsync(slave.DeviceName, jo);
break;
case DataCatalog.TelemetryData:
await _client.UploadTelemetryDataAsync(slave.DeviceName, jo);
break;
default:
break;
}
}
private IModbusClient? CreateModbusSlave(Client slave)
{
var url = slave.Address;
IModbusClient? client = null;
switch (url.Scheme)
{
case "dtu":
string comname = ParseCOMName(url);
var dtu = new AMWD.Modbus.Serial.Client.ModbusClient(comname, _logger);
ParseDtuParam(url, dtu);
client = dtu;
break;
case "rtu":
client = new AMWD.Modbus.Tcp.Client.ModbusClient(url.Host, url.Port, _logger);
break;
case "d2t":
client = new AMWD.Modbus.SerialOverTCP.Client.ModbusClient(url.Host, url.Port, _logger);
break;
default:
break;
}
return client;
}
private static string ParseCOMName(Uri url)
{
var comname = string.Empty;
switch (Environment.OSVersion.Platform)
{
case PlatformID.Win32S:
case PlatformID.Win32Windows:
case PlatformID.Win32NT:
case PlatformID.WinCE:
case PlatformID.Xbox:
comname = url.Host;
break;
case PlatformID.Other:
case PlatformID.Unix:
case PlatformID.MacOSX:
default:
comname = url.Host.Replace('.', '/');
break;
}
return comname;
}
private void ParseDtuParam(Uri url, AMWD.Modbus.Serial.Client.ModbusClient dtu)
{
var query = HttpUtility.ParseQueryString(url.Query);
if (query.HasKeys())
{
foreach (string key in query.Keys)
{
switch (key)
{
case nameof(dtu.BaudRate):
if (Enum.TryParse(query.Get(key), true, out AMWD.Modbus.Serial.BaudRate _baudrate))
{
dtu.BaudRate = _baudrate;
}
break;
case nameof(dtu.Parity):
if (Enum.TryParse(query.Get(key), true, out Parity parity))
{
dtu.Parity = parity;
}
break;
case nameof(dtu.Handshake):
if (Enum.TryParse(query.Get(key), true, out Handshake handshake))
{
dtu.Handshake = handshake;
}
break;
case nameof(dtu.StopBits):
if (Enum.TryParse(query.Get(key), true, out StopBits stopBits))
{
dtu.StopBits = stopBits;
}
break;
case nameof(dtu.DataBits):
if (int.TryParse(query.Get(key), out int _DataBits))
{
dtu.DataBits = _DataBits;
}
break;
default:
break;
}
}
}
}
}
}