Commit 709ec17e 顾剑亮

coding

1 个父辈 9a7eb2f5
此文件类型无法预览
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
namespace Asa.IOModule
{
/// <summary>
/// AIOBOX操作类
/// 零点IO模块操作类
/// </summary>
public class AIOBOX
{
private Socket _client; //客户端
private bool _quit;
private bool _loop;
private string _logPath; //日志的文件夹路径
private LogFile _log; //日志的类
private LogType _logType; //日志输出类型
private bool _frontInputOutput; //True:Input False:Output
private int countInput; //输入总数
private int countOutput; //输出总数
private int sleepInput; //自动读取DI间隔
private int sleepOutput; //自动读取DO间隔
private Box_Type _typeInput; //输入类型
private Box_Type _typeOutput; //输出类型
private byte[] _addressInput; //输入地址
private byte[] _addressOutput; //输出地址
private bool _autoReadInput; //自动读取DI
private bool _autoReadOutput; //自动读取DO
private bool _manualReadOutput; //手动读取DO
private bool _changeDI; //DI收到数据触发事件
private bool _changeDO; //DO收到数据触发事件
private bool _changeAI; //AI收到数据触发事件
private bool _changeAO; //AO收到数据触发事件
private Box_Sta[] _stateDI; //状态
private Box_Sta[] _stateDO; //状态
private int[] _valueAI; //模拟量
private int[] _valueAO; //模拟量
private System.Collections.Concurrent.ConcurrentQueue<ushort> _flag;
private System.Collections.Concurrent.ConcurrentQueue<byte[]> _send;
private System.Collections.Concurrent.ConcurrentQueue<byte[]> _receive;
private Thread tRecon;
private Thread tSend; //发送命令处理
private Thread tReceive; //接收信息处理
private Thread tListen; //监听网络
private Thread tTrigger; //触发DI、DO改变事件
private Thread tReadInput; //自动读取DI线程
private Thread tReadOutput; //自动读取DO线程
private Thread tFlag; //ModBusTCP标识
private UdpClient broadcastClient; //广播客户端
private IPEndPoint broadcastEndPoint; //广播远程节点
private byte[] broadcastBuffer; //远程返回数据
/// <summary>
/// 每条命令发送的间隔
/// 不能小于15,会出现IO接收不到的情况
/// 小于30时,会出现接收数据连包的情况
/// </summary>
private const int SEND_SLEEP = 30;
/// <summary>
/// ModBus端口
/// </summary>
public const int PORT = 502;
/// <summary>
/// 自动读取DI委托
/// </summary>
......@@ -82,180 +25,99 @@ namespace Asa.IOModule
/// 自动读取DO事件触发
/// </summary>
public event DIO_Changed DO_Changed_Event;
/// <summary>
/// 自动读取AI委托
/// </summary>
/// <param name="box">AIOBOX</param>
/// <param name="val">所有AI的值</param>
public delegate void AIO_Changed(AIOBOX box, int[] val);
/// <summary>
/// 自动读取AI事件触发
/// </summary>
public event AIO_Changed AI_Changed_Event;
/// <summary>
/// 自动读取AO事件触发
/// </summary>
public event AIO_Changed AO_Changed_Event;
/// <summary>
/// AIOBOX
/// </summary>
public AIOBOX()
{
_log = null;
_logPath = null;
_autoReadInput = false;
_autoReadOutput = false;
_manualReadOutput = false;
sleepInput = 100;
sleepOutput = 100;
}
/// <summary>
/// IP地址
/// </summary>
public string IP { set; get; } = "192.168.1.100";
private Socket _client; //客户端
private bool _loop;
private ushort _uid;
private Box_Sta[] _stateDI; //DI状态
private Box_Sta[] _stateDO; //DO状态
private int[] _valueAI; //AI模拟量
private int[] _valueAO; //AO模拟量
private List<byte> _receive;
private System.Collections.Concurrent.ConcurrentQueue<byte[]> _writeDO;
private readonly Box_Type _typeInput; //输入类型
private readonly Box_Type _typeOutput; //输出类型
private readonly byte[] _addressInput; //输入地址
private readonly byte[] _addressOutput; //输出地址
private const int SEND_SLEEP = 50; //每条命令发送的间隔,不能小于15,会出现IO接收不到的情况,小于30时,会出现接收数据连包的情况
private const int PORT = 502; //端口
private const int UPLOAD_TIME = 8000; //8秒
private Thread tRecon; //重连线程
private Thread tSend; //发送命令处理
private Thread tListen; //监听网络
/// <summary>
/// 是否连接
/// </summary>
public bool IsConn { get; private set; } = false;
private UdpClient broadcastClient; //广播客户端
private IPEndPoint broadcastEndPoint; //广播远程节点
private byte[] broadcastBuffer; //远程返回数据
/// <summary>
/// 错误信息
/// 零点IO模块操作类
/// </summary>
public string ErrInfo { get; private set; } = "";
/// <param name="input">输入类型</param>
/// <param name="inputCount">输入数量</param>
/// <param name="output">输出类型</param>
/// <param name="outputCount">输出数量</param>
public AIOBOX(Box_Type input, int inputCount, Box_Type output, int outputCount)
{
_typeInput = input;
_typeOutput = output;
byte n = 0;
_addressInput = new byte[inputCount];
for (int i = 0; i < inputCount; i++)
_addressInput[i] = n++;
_addressOutput = new byte[outputCount];
for (int i = 0; i < outputCount; i++)
_addressOutput[i] = n++;
_stateDI = new Box_Sta[inputCount];
_stateDO = new Box_Sta[outputCount];
_valueAI = new int[inputCount];
_valueAO = new int[outputCount];
}
/// <summary>
/// 日志路径,连接前设置路径会自动保存日志
/// IP地址
/// </summary>
/// <param name="path">文件夹路径</param>
/// <param name="type">输出类型</param>
public void LogPath(string path, LogType type)
{
_logPath = path;
_logType = type;
}
public string IP { set; get; } = "192.168.1.100";
/// <summary>
/// 设置输入端
/// 输入主动上传
/// </summary>
/// <param name="type">类型</param>
/// <param name="count">数量</param>
public void SetInput(Box_Type type, int count)
{
_typeInput = type;
countInput = count;
_frontInputOutput = false;
}
public bool Upload { set; get; } = false;
/// <summary>
/// 设置输出端
/// </summary>
/// <param name="type">类型</param>
/// <param name="count">数量</param>
public void SetOutput(Box_Type type, int count)
{
_typeOutput = type;
countOutput = count;
_frontInputOutput = true;
}
/// <summary>
/// 自动获取IP地址,未连接前使用,必须在同一网段
/// 是否连接
/// </summary>
/// <param name="localIP">本地IP地址</param>
/// <returns></returns>
public bool AutoIP(string localIP)
{
try
{
IPEndPoint local = new IPEndPoint(IPAddress.Parse(localIP), 55654);
broadcastClient = new UdpClient(local);
broadcastBuffer = null;
Thread tTemp = new Thread(new ThreadStart(GetIP));
tTemp.Start();
broadcastEndPoint = new IPEndPoint(IPAddress.Parse("255.255.255.255"), 1024);
byte[] dgram = new byte[] { 0x05, 0x00, 0x07, 0x00, 0x00, 0xC1, 0x59 };
broadcastClient.Send(dgram, dgram.Length, broadcastEndPoint);
Thread.Sleep(1000);
tTemp.Abort();
broadcastClient.Close();
ErrInfo = "无法访问";
if (broadcastBuffer == null) return false;
if (broadcastBuffer[0] != 0x3B) return false;
byte[] buff = new byte[broadcastBuffer[7]];
Array.Copy(broadcastBuffer, 9, buff, 0, buff.Length);
IP = buff[buff.Length - 12] + "." + buff[buff.Length - 11] + "." + buff[buff.Length - 10] + "." + buff[buff.Length - 9];
ErrInfo = "OK";
return true;
}
catch (Exception ex)
{
ErrInfo = ex.Message;
return false;
}
}
public bool IsConn { get; private set; } = false;
/// <summary>
/// 检查IP地址
/// 错误信息
/// </summary>
/// <param name="ip"></param>
/// <returns></returns>
public bool CheckIP(string ip)
{
//IP合法
string pattern = @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$";
bool rtn = System.Text.RegularExpressions.Regex.IsMatch(ip, pattern);
if (!rtn)
{
ErrInfo = "非法的IP地址";
return false;
}
//Ping服务端
System.Net.NetworkInformation.Ping ping = new System.Net.NetworkInformation.Ping();
System.Net.NetworkInformation.PingReply result = ping.Send(ip, 1000);
ping.Dispose();
if (result.Status != System.Net.NetworkInformation.IPStatus.Success)
{
ErrInfo = "Ping " + ip + " 请求没有响应";
return false;
}
return true;
}
public string ErrInfo { get; private set; } = "";
/// <summary>
/// 连接
/// </summary>
public void Connect()
{
if (_logPath == null)
_log = null;
else
_log = new LogFile(_logPath, IP);
_loop = true;
_uid = 0;
IsConn = false;
_receive = new List<byte>();
_writeDO = new System.Collections.Concurrent.ConcurrentQueue<byte[]>();
_quit = false;
tRecon = new Thread(new ThreadStart(Reconn));
tSend = new Thread(new ThreadStart(Send));
tListen = new Thread(new ThreadStart(Listen));
tRecon.Start();
tSend.Start();
tListen.Start();
int time = 0;
while (time < 3000)
{
if (IsConn) break;
else time += 50;
Thread.Sleep(50);
}
}
/// <summary>
......@@ -263,82 +125,26 @@ namespace Asa.IOModule
/// </summary>
public void Close()
{
_quit = true;
Exit();
_loop = false;
IsConn = false;
if (_log != null)
try
{
_log.OutData("Close OK");
_log.Close();
_log = null;
if (_client != null)
{
_client.Shutdown(SocketShutdown.Both);
_client.Close();
}
}
}
/// <summary>
/// 自动读取输入端并触发事件(主动上传数据 = 禁止)
/// </summary>
/// <param name="read">是否自动读取</param>
/// <param name="sleep">间隔,必须大于等于15ms</param>
public void AutoReadInput(bool read, int sleep)
{
if (countInput < 1)
_autoReadInput = false;
else if (sleep < 15)
_autoReadInput = false;
else if (read)
catch (Exception ex)
{
_autoReadInput = true;
sleepInput = sleep;
ErrInfo = ex.Message;
}
else
_autoReadInput = false;
}
/// <summary>
/// 自动读取输入端并触发事件(主动上传数据 = 使能)
/// </summary>
/// <param name="read">是否自动读取</param>
public void AutoReadInput(bool read)
{
if (countInput < 1)
_autoReadInput = false;
else if (read)
finally
{
_autoReadInput = true;
sleepInput = 8000; //IO模块最大10s,避免超过时间,每8秒发送一次心跳包
_client = null;
}
else
_autoReadInput = false;
}
/// <summary>
/// 自动读取输出端,触发事件
/// </summary>
/// <param name="read">是否自动读取</param>
/// <param name="sleep">间隔,必须大于等于15ms</param>
public void AutoReadOutput(bool read, int sleep)
{
if (countOutput < 1)
{
_autoReadOutput = false;
sleepOutput = 100;
}
else if (sleep < 15)
{
_autoReadOutput = false;
sleepOutput = 100;
}
else if (read)
{
_autoReadOutput = true;
sleepOutput = sleep;
}
else
{
_autoReadOutput = false;
_manualReadOutput = true; //用于第一次连接的时候读取一次状态
sleepOutput = sleep;
}
}
/// <summary>
......@@ -471,9 +277,7 @@ namespace Asa.IOModule
buff[7] = 5; //功能码
buff[9] = _addressOutput[add]; //地址
buff[10] = (byte)sta; //写入值
_send.Enqueue(buff);
if (!_autoReadOutput)
_manualReadOutput = true;
_writeDO.Enqueue(buff);
return true;
}
catch (Exception ex)
......@@ -483,523 +287,274 @@ namespace Asa.IOModule
}
}
/// <summary>
/// 获取本地IPv4地址
/// </summary>
/// <returns></returns>
public string[] GetLocalIP()
{
List<string> str = new List<string>();
IPAddress[] add = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
foreach (IPAddress ip in add)
{
if (ip.AddressFamily.ToString() == "InterNetwork")
str.Add(ip.ToString());
}
return str.ToArray();
}
/// <summary>
/// 发送命令
/// 发送命令线程
/// </summary>
private void Send()
{
bool shift = true; //切换
int time = 0; //上传时间
int writeTimer = 0; //写入DO次数
bool writeStop = false; //写入命令停止
while (_loop)
{
bool rtn = _send.TryDequeue(out byte[] result);
if (rtn)
{
try
{
_client.Send(result);
if (_log != null)
{
if (_logType == LogType.All)
_log.OutData("Send ", result);
}
}
catch (Exception ex)
{
IsConn = false;
_loop = false;
if (_log != null)
_log.OutError(ex.Message);
}
}
Thread.Sleep(SEND_SLEEP);
}
}
if (!IsConn) continue;
byte[] buff;
/// <summary>
/// 接收命令
/// </summary>
private void Receive()
{
while (_loop)
{
if (_receive.TryDequeue(out byte[] buff))
if (Upload) //主动上传
{
try
if (time >= UPLOAD_TIME)
{
if (_log != null)
{
if (_logType == LogType.All)
_log.OutData("Receive ", buff);
}
if (buff.Length < 7)
continue;
if (buff[7] == 1)
{
ReadDO(buff);
_changeDO = true;
}
else if (buff[7] == 2)
{
ReadDI(buff);
_changeDI = true;
}
else if (buff[7] == 4)
buff = GetReadDI_Command();
time = 0;
}
else
{
if (writeTimer >= 3) //连续发送WriteDO3次,强制发送DO
{
ReadAI(buff);
_changeAI = true;
buff = GetReadDO_Command();
writeTimer = 0;
}
else if (buff[7] == 5)
else
{
//ReadSingle(buff);
if (_writeDO.TryDequeue(out buff))
{
writeTimer++;
}
else
{
buff = GetReadDO_Command();
writeTimer = 0;
}
}
}
catch (Exception ex)
{
if (_log != null)
_log.OutError(ex.Message);
}
time += SEND_SLEEP;
}
Thread.Sleep(10);
}
}
/// <summary>
/// 读取写入单个DO,功能码5
/// </summary>
/// <param name="buff"></param>
private void ReadSingle(byte[] buff)
{
string s;
//if (LogOut)
//{
// byte[] bb = new byte[2];
// bb[0] = buff[1];
// bb[1] = buff[0];
// ushort flag = BitConverter.ToUInt16(bb, 0);
// s = string.Format("{0:HH:mm:ss:fff} WriteDO {1} fun={2} len={3}", DateTime.Now, flag, buff[7], buff.Length);
// _log.Add(s);
//}
// int n = 0;
// int move = 0;
// byte val = _receive[0][9];
// for (int i = 0; i < 8; i++)
// {
// n = (val & Convert.ToInt32(Math.Pow(2, move))) >> move;
// _sta[i + 16] = n == 1 ? Box_Sta.On : Box_Sta.Off;
// move++;
// }
// if (_receive[0][8] == 2)
// {
// move = 0;
// val = _receive[0][10];
// for (int i = 8; i < 16; i++)
// {
// n = (val & Convert.ToInt32(Math.Pow(2, move))) >> move;
// _sta[i + 16] = n == 1 ? Box_Sta.On : Box_Sta.Off;
// move++;
// }
// }
// ErrInfo = "OK";
//}
//catch (Exception ex)
//{
// ErrInfo = ex.Message;
//}
}
/// <summary>
/// 读取所有DO状态,功能码1
/// </summary>
/// <param name="buff"></param>
private void ReadDO(byte[] buff)
{
try
{
int idx = 0;
int count = buff[8];
for (int i = 1; i <= count; i++)
else
{
int n = 0;
int move = 0;
byte val = buff[8 + i];
for (int j = 0; j < 8; j++)
if (writeTimer >= 3) //连续发送WriteDO3次,强制发送DI和DO
{
n = (val & Convert.ToInt32(Math.Pow(2, move))) >> move;
_stateDO[idx++] = n == 1 ? Box_Sta.On : Box_Sta.Off;
move++;
if (shift)
buff = GetReadDI_Command();
else
buff = GetReadDO_Command();
shift = !shift;
if (writeStop)
writeTimer = 0;
else
writeStop = true;
}
else
{
if (_writeDO.TryDequeue(out buff))
{
writeTimer++;
writeStop = false;
}
else
{
if (shift)
buff = GetReadDI_Command();
else
buff = GetReadDO_Command();
shift = !shift;
writeTimer = 0;
}
}
}
if (_log != null)
{
if (_logType == LogType.All)
_log.OutData("Read All DO");
}
}
catch (Exception ex)
{
if (_log != null)
_log.OutError(ex.Message);
}
}
/// <summary>
/// 读取所有DI状态,功能码2
/// </summary>
private void ReadDI(byte[] buff)
{
try
{
int idx = 0;
int count = buff[8];
for (int i = 1; i <= count; i++)
try
{
int n = 0;
int move = 0;
byte val = buff[8 + i];
for (int j = 0; j < 8; j++)
{
n = (val & Convert.ToInt32(Math.Pow(2, move))) >> move;
_stateDI[idx++] = n == 1 ? Box_Sta.On : Box_Sta.Off;
move++;
}
_client.Send(buff);
}
if (_log != null)
catch (Exception ex)
{
if (_logType == LogType.All)
_log.OutData("Read All DI");
ErrInfo = ex.Message;
IsConn = false;
}
//Test 方法锁
TestMethod();
}
catch (Exception ex)
{
if (_log != null)
_log.OutError(ex.Message);
}
}
/// <summary>
/// 读取所有AI的值,功能码4
/// 获取ReadDI的命令
/// </summary>
/// <param name="buff"></param>
private void ReadAI(byte[] buff)
/// <returns></returns>
private byte[] GetReadDI_Command()
{
try
{
if (countInput * 2 == buff[8])
{
_valueAI[0] = buff[9] * 256 + buff[10];
_valueAI[1] = buff[11] * 256 + buff[12];
_valueAI[2] = buff[13] * 256 + buff[14];
_valueAI[3] = buff[15] * 256 + buff[16];
}
if (_log != null)
{
if (_logType == LogType.All)
_log.OutData("Read All AI");
}
}
catch (Exception ex)
{
if (_log != null)
_log.OutError(ex.Message);
}
byte[] data = Command();
byte[] buff = new byte[12];
Array.Copy(data, 0, buff, 0, data.Length);
buff[5] = 6; //后面字节数
//功能码
if (_typeInput == Box_Type.DI)
buff[7] = 2;
else if (_typeInput == Box_Type.AI)
buff[7] = 4;
buff[9] = _addressInput[0]; //地址
buff[11] = (byte)_addressInput.Length; //个数
return buff;
}
/// <summary>
/// 命令,前7个字节
/// 获取ReadDO的命令
/// </summary>
/// <returns></returns>
private byte[] Command()
private byte[] GetReadDO_Command()
{
_flag.TryDequeue(out ushort result);
byte[] flag = BitConverter.GetBytes(result);
byte[] data = new byte[7];
data[0] = flag[1];
data[1] = flag[0];
data[2] = 0;
data[3] = 0;
data[4] = 0;
data[5] = 0;
data[6] = 255;
return data;
byte[] data = Command();
byte[] buff = new byte[12];
Array.Copy(data, 0, buff, 0, data.Length);
buff[5] = 6; //后面字节数
buff[7] = 1; //功能码
buff[9] = _addressOutput[0]; //地址
buff[11] = (byte)_addressOutput.Length; //个数
return buff;
}
/// <summary>
/// 触发DIO改变事件
/// 监听网络线程
/// </summary>
private void TriggerDIO()
private void Listen()
{
while (_loop)
{
if (_typeInput == Box_Type.DI && DI_Changed_Event != null)
{
//if (_changeDI)
//{
// _changeDI = false;
// Box_Sta[] staDI = new Box_Sta[countInput];
// Array.Copy(_stateDI, 0, staDI, 0, staDI.Length);
// if (_log != null)
// {
// if (_logType == LogType.All)
// _log.OutData("Trigger DI Event");
// }
// lock (staDI)
// System.Threading.Tasks.Task.Run(() => DI_Changed_Event.Invoke(this, staDI));
//}
}
else if (_typeInput == Box_Type.AI && AI_Changed_Event != null)
{
if (_changeAI)
{
_changeAI = false;
int[] valAI = new int[countInput];
Array.Copy(_valueAI, 0, valAI, 0, valAI.Length);
if (_log != null)
{
if (_logType == LogType.All)
_log.OutData("Trigger AI Event");
}
lock (valAI)
System.Threading.Tasks.Task.Run(() => AI_Changed_Event.Invoke(this, valAI));
}
}
Thread.Sleep(10);
if (!IsConn) continue;
if (_client == null) continue;
if (_typeOutput == Box_Type.DO && DO_Changed_Event != null)
try
{
if (_changeDO)
if (_client.Available > 0)
{
_changeDO = false;
Box_Sta[] staDO = new Box_Sta[countOutput];
Array.Copy(_stateDO, 0, staDO, 0, staDO.Length);
if (_log != null)
byte[] buff = new byte[_client.ReceiveBufferSize];
int count = _client.Receive(buff);
byte[] temp = new byte[count];
Array.Copy(buff, 0, temp, 0, count);
lock (_receive)
{
if (_logType == LogType.All)
_log.OutData("Trigger DO Event");
_receive.AddRange(temp);
while (_receive.Count > 6) //分解连包
{
int len = _receive[5] + 6;
if (len > _receive.Count) break;
byte[] cmd = new byte[len];
_receive.CopyTo(0, cmd, 0, len);
_receive.RemoveRange(0, len);
System.Threading.Tasks.Task.Run(() => CommandProcess(cmd));
}
}
lock (staDO)
System.Threading.Tasks.Task.Run(() => DO_Changed_Event.Invoke(this, staDO));
}
}
else if (_typeOutput == Box_Type.AO && AO_Changed_Event != null)
catch (Exception ex)
{
if (_changeAO)
{
_changeAO = false;
int[] valAO = new int[countOutput];
Array.Copy(_valueAO, 0, valAO, 0, valAO.Length);
if (_log != null)
{
if (_logType == LogType.All)
_log.OutData("Trigger AO Event");
}
lock (valAO)
System.Threading.Tasks.Task.Run(() => AO_Changed_Event.Invoke(this, valAO));
}
ErrInfo = ex.Message;
IsConn = false;
}
Thread.Sleep(10);
}
}
/// <summary>
/// 自动读取输入端线程
/// 接收到的命令处理方法,(task多线程)
/// </summary>
private void AutoReadInput()
/// <param name="cmd"></param>
private void CommandProcess(byte[] cmd)
{
while (_loop)
{
if (IsConn && _autoReadInput)
{
//if (suspendDI && suspend > 0)
//{
// suspendDI = false;
// suspend--;
//}
//else
//{
byte[] data = Command();
byte[] buff = new byte[12];
Array.Copy(data, 0, buff, 0, data.Length);
buff[5] = 6; //后面字节数
//功能码
if (_typeInput == Box_Type.DI)
buff[7] = 2;
else if (_typeInput == Box_Type.AI)
buff[7] = 4;
buff[9] = _addressInput[0]; //地址
buff[11] = (byte)countInput; //个数
_send.Enqueue(buff);
//suspendDI = true;
//}
}
Thread.Sleep(sleepInput);
}
}
int idx = 0;
int count = cmd[8];
/// <summary>
/// 自动读取输出端线程
/// </summary>
private void AutoReadOutput()
{
while (_loop)
try
{
Thread.Sleep(sleepOutput);
if (!IsConn) continue;
if (!_autoReadOutput)
if (cmd[7] == 1) //ReadDO
{
if (_manualReadOutput)
_manualReadOutput = false;
else
continue;
Box_Sta[] staDO = new Box_Sta[_stateDO.Length];
lock (_stateDO)
{
for (int i = 1; i <= count; i++)
{
int move = 0;
byte val = cmd[8 + i];
for (int j = 0; j < 8; j++) //字节的0-7位
{
int n = (val & Convert.ToInt32(Math.Pow(2, move))) >> move;
_stateDO[idx++] = n == 1 ? Box_Sta.On : Box_Sta.Off;
move++;
}
}
Array.Copy(_stateDO, 0, staDO, 0, staDO.Length);
}
System.Threading.Tasks.Task.Run(() => DO_Changed_Event?.Invoke(this, staDO));
}
byte[] data = Command();
byte[] buff = new byte[12];
Array.Copy(data, 0, buff, 0, data.Length);
buff[5] = 6; //后面字节数
buff[7] = 1; //功能码
buff[9] = _addressOutput[0]; //地址
buff[11] = (byte)countOutput; //个数
_send.Enqueue(buff);
}
}
/// <summary>
/// 监听结果线程
/// </summary>
private void Listen()
{
byte[] temp = new byte[200];
int start, len;
while (_loop)
{
try
else if (cmd[7] == 2) //ReadDI
{
if (_client == null) break;
if (_client.Available > 0)
Box_Sta[] staDI = new Box_Sta[_stateDI.Length];
lock (_stateDI)
{
start = 0;
int count = _client.Receive(temp);
while (count - start > 6)
for (int i = 1; i <= count; i++)
{
len = temp[start + 5];
len += 6;
if (start + len > count) break;
byte[] aa = new byte[len];
Array.Copy(temp, start, aa, 0, len);
_receive.Enqueue(aa);
start += len;
int move = 0;
byte val = cmd[8 + i];
for (int j = 0; j < 8; j++) //字节的0-7位
{
int n = (val & Convert.ToInt32(Math.Pow(2, move))) >> move;
_stateDI[idx++] = n == 1 ? Box_Sta.On : Box_Sta.Off;
move++;
}
}
Array.Copy(_stateDI, 0, staDI, 0, staDI.Length);
}
System.Threading.Tasks.Task.Run(() => DI_Changed_Event?.Invoke(this, staDI));
}
catch (Exception ex)
else if (cmd[7] == 5)
{
IsConn = false;
_loop = false;
if (_log != null)
_log.OutError(ex.Message);
}
Thread.Sleep(10);
}
}
private void InitAddr()
{
byte n = 0;
if (_frontInputOutput)
{
_addressInput = new byte[countInput];
for (int i = 0; i < countInput; i++)
_addressInput[i] = n++;
_addressOutput = new byte[countOutput];
for (int i = 0; i < countOutput; i++)
_addressOutput[i] = n++;
}
else
catch (Exception ex)
{
_addressOutput = new byte[countOutput];
for (int i = 0; i < countOutput; i++)
_addressOutput[i] = n++;
_addressInput = new byte[countInput];
for (int i = 0; i < countInput; i++)
_addressInput[i] = n++;
ErrInfo = ex.Message;
}
_stateDI = new Box_Sta[countInput];
_stateDO = new Box_Sta[countOutput];
_valueAI = new int[countInput];
_valueAO = new int[countOutput];
}
private void GetIP()
{
broadcastBuffer = broadcastClient.Receive(ref broadcastEndPoint);
}
private void Flag()
/// <summary>
/// 重连线程
/// </summary>
private void Reconn()
{
ushort n = 0;
while (_loop)
{
if (_flag.Count < 10)
if (IsConn)
{
Thread.Sleep(1000);
}
else
{
_flag.Enqueue(++n);
if (n == ushort.MaxValue) n = 0;
Thread.Sleep(100);
if (_loop) Open();
}
Thread.Sleep(5);
}
}
/// <summary>
/// 打开socket建立连接
/// </summary>
private void Open()
{
try
{
//初始化地址
IsConn = false;
InitAddr();
//检查IP地址
bool rtn = CheckIP(IP);
if (!rtn) return;
_send = new System.Collections.Concurrent.ConcurrentQueue<byte[]>();
_receive = new System.Collections.Concurrent.ConcurrentQueue<byte[]>();
_flag = new System.Collections.Concurrent.ConcurrentQueue<ushort>();
//建立连接
_client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 500);
......@@ -1007,108 +562,118 @@ namespace Asa.IOModule
_client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1);
_client.Connect(IPAddress.Parse(IP), PORT);
Thread.Sleep(100); //需要等待一会才能获取连接状态
tFlag = new Thread(new ThreadStart(Flag));
tFlag.Start();
Thread.Sleep(10);
tSend = new Thread(new ThreadStart(Send));
tReceive = new Thread(new ThreadStart(Receive));
tListen = new Thread(new ThreadStart(Listen));
tTrigger = new Thread(new ThreadStart(TriggerDIO));
tReadInput = new Thread(new ThreadStart(AutoReadInput));
tReadOutput = new Thread(new ThreadStart(AutoReadOutput));
IsConn = true;
_loop = true;
tListen.Start();
tTrigger.Start();
tSend.Start();
tReceive.Start();
tReadInput.Start();
tReadOutput.Start();
ErrInfo = "";
if (_log != null) _log.OutData("Connect OK");
return;
}
catch (Exception ex)
{
IsConn = false;
ErrInfo = ex.Message;
if (_log != null) _log.OutError(ex.Message);
return;
}
}
private void Exit()
/// <summary>
/// 检查IP地址
/// </summary>
/// <param name="ip"></param>
/// <returns></returns>
private bool CheckIP(string ip)
{
IsConn = false;
_loop = false;
tListen = null;
tReadInput = null;
tReadOutput = null;
tTrigger = null;
tSend = null;
tReceive = null;
tFlag = null;
//IP合法
string pattern = @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$";
bool rtn = System.Text.RegularExpressions.Regex.IsMatch(ip, pattern);
if (!rtn)
{
ErrInfo = "非法的IP地址";
return false;
}
try
//Ping服务端
System.Net.NetworkInformation.Ping ping = new System.Net.NetworkInformation.Ping();
System.Net.NetworkInformation.PingReply result = ping.Send(ip, 1000);
ping.Dispose();
if (result.Status != System.Net.NetworkInformation.IPStatus.Success)
{
if (_client != null)
{
_client.Shutdown(SocketShutdown.Both);
_client.Close();
}
ErrInfo = "Ping " + ip + " 请求没有响应";
return false;
}
catch (Exception ex)
return true;
}
/// <summary>
/// 自动获取IP地址,未连接前使用,必须在同一网段
/// </summary>
/// <param name="localIP">本地IP地址</param>
/// <returns></returns>
private bool AutoIP(string localIP)
{
try
{
if (_log != null)
_log.OutError(ex.Message);
IPEndPoint local = new IPEndPoint(IPAddress.Parse(localIP), 55654);
broadcastClient = new UdpClient(local);
broadcastBuffer = null;
Thread tTemp = new Thread(new ThreadStart(GetIP));
tTemp.Start();
broadcastEndPoint = new IPEndPoint(IPAddress.Parse("255.255.255.255"), 1024);
byte[] dgram = new byte[] { 0x05, 0x00, 0x07, 0x00, 0x00, 0xC1, 0x59 };
broadcastClient.Send(dgram, dgram.Length, broadcastEndPoint);
Thread.Sleep(1000);
tTemp.Abort();
broadcastClient.Close();
ErrInfo = "无法访问";
if (broadcastBuffer == null) return false;
if (broadcastBuffer[0] != 0x3B) return false;
byte[] buff = new byte[broadcastBuffer[7]];
Array.Copy(broadcastBuffer, 9, buff, 0, buff.Length);
IP = buff[buff.Length - 12] + "." + buff[buff.Length - 11] + "." + buff[buff.Length - 10] + "." + buff[buff.Length - 9];
ErrInfo = "OK";
return true;
}
finally
catch (Exception ex)
{
_client = null;
ErrInfo = ex.Message;
return false;
}
}
/// <summary>
/// 测试方法锁
/// 获取IO模块IP地址
/// </summary>
private void GetIP()
{
broadcastBuffer = broadcastClient.Receive(ref broadcastEndPoint);
}
/// <summary>
/// 命令,前7个字节
/// </summary>
/// <returns></returns>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
private void TestMethod()
private byte[] Command()
{
Box_Sta[] staDI = new Box_Sta[countInput];
Array.Copy(_stateDI, 0, staDI, 0, staDI.Length);
if (_log != null)
{
if (_logType == LogType.All)
_log.OutData("Trigger DI Event");
}
lock (staDI)
System.Threading.Tasks.Task.Run(() => DI_Changed_Event.Invoke(this, staDI));
byte[] flag = BitConverter.GetBytes(++_uid);
if (_uid == ushort.MaxValue) _uid = 0;
byte[] data = new byte[7];
data[0] = flag[1];
data[1] = flag[0];
data[2] = 0;
data[3] = 0;
data[4] = 0;
data[5] = 0;
data[6] = 255;
return data;
}
private void Reconn()
{
while (!_quit)
{
if (IsConn)
{
Thread.Sleep(1000);
}
else
{
Exit();
Thread.Sleep(100);
if (!_quit) Open();
}
}
}
}
/// <summary>
/// IO模块类型
/// </summary>
......@@ -1147,23 +712,5 @@ namespace Asa.IOModule
On = 255
}
/// <summary>
/// 日志类型
/// </summary>
public enum LogType : int
{
/// <summary>
/// 仅错误信息
/// </summary>
OnlyError,
/// <summary>
/// 所有
/// </summary>
All
}
}
\ No newline at end of file
}
......@@ -64,7 +64,6 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AIOBOX.cs" />
<Compile Include="Log.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
......
using System;
using System.Reflection;
using System.Diagnostics;
namespace Asa.IOModule
{
/// <summary>
/// 日志操作类
/// </summary>
internal class LogFile
{
//private string args;
private bool loop;
private System.IO.FileStream fs;
private StackTrace trace;
private StackFrame frame;
private MethodBase method;
private System.Threading.Thread tSave;
private System.Collections.Concurrent.ConcurrentQueue<string> info;
private readonly string PATH;
/// <summary>
/// 日志
/// </summary>
/// <param name="path">文件夹路径</param>
/// <param name="ip"></param>
internal LogFile(string path, string ip)
{
if (path == null) return;
//this.args = ip;
PATH = path.EndsWith("\\") ? path : path + "\\";
if (!System.IO.Directory.Exists(PATH))
System.IO.Directory.CreateDirectory(PATH);
loop = true;
tSave = new System.Threading.Thread(new System.Threading.ThreadStart(SaveLog));
info = new System.Collections.Concurrent.ConcurrentQueue<string>();
tSave.Start();
string file = string.Format("{0}{1:yyyy-MM-dd}_({2}).log", PATH, DateTime.Now, ip);
fs = System.IO.File.OpenWrite(file);
fs.Position = fs.Length;
}
/// <summary>
/// 关闭文件
/// </summary>
internal void Close()
{
loop = false;
System.Threading.Thread.Sleep(5);
if (fs != null)
fs.Close();
}
/// <summary>
/// 输出错误
/// </summary>
/// <param name="s"></param>
internal void OutError(string s)
{
if (string.IsNullOrWhiteSpace(s)) return;
trace = new StackTrace(true);
frame = trace.GetFrame(1);
string name = frame.GetFileName();
name = System.IO.Path.GetFileName(name);
string log = string.Format("[{0:HH:mm:ss.fff}] ERROR {1}({2},{3})\r\n",
DateTime.Now, name, frame.GetFileLineNumber(), frame.GetFileColumnNumber());
string[] arr = new string[trace.FrameCount - 1];
for (int i = 1; i < trace.FrameCount; i++) //0是本身Out
{
method = trace.GetFrame(i).GetMethod();
arr[arr.Length - i] = " " + method.DeclaringType.FullName + " -> " + method.ToString();
}
log += string.Join("\r\n", arr) + string.Format("\r\n {0}\r\n", s);
//byte[] array = System.Text.Encoding.UTF8.GetBytes(log);
//fs.Write(array, 0, array.Length);
info.Enqueue(log);
}
/// <summary>
/// 输出信息
/// </summary>
/// <param name="s"></param>
internal void OutInfo(string s)
{
if (string.IsNullOrWhiteSpace(s)) return;
trace = new StackTrace(true);
frame = trace.GetFrame(1);
method = trace.GetFrame(1).GetMethod();
string name = frame.GetFileName();
name = System.IO.Path.GetFileName(name);
string s1 = method.DeclaringType.FullName;
string s2 = method.Name;
string log = string.Format("[{0:HH:mm:ss.fff}] INFO {1}({2},{3}) {4} -> {5}\r\n {6}\r\n",
DateTime.Now, name, frame.GetFileLineNumber(), frame.GetFileColumnNumber(), s1, s2, s);
//byte[] array = System.Text.Encoding.UTF8.GetBytes(log);
//fs.Write(array, 0, array.Length);
info.Enqueue(log);
}
/// <summary>
/// 输出数据
/// </summary>
/// <param name="tr"></param>
/// <param name="buff"></param>
internal void OutData(string tr, byte[] buff)
{
string log = string.Format("{0:HH:mm:ss.fff} {1} ", DateTime.Now, tr);
for (int i = 0; i < buff.Length; i++)
log += buff[i].ToString("X2") + " ";
log += "\r\n";
//byte[] array = System.Text.Encoding.UTF8.GetBytes(log);
//fs.Write(array, 0, array.Length);
info.Enqueue(log);
}
/// <summary>
/// 输出数据
/// </summary>
/// <param name="s"></param>
internal void OutData(string s)
{
string log = string.Format("{0:HH:mm:ss.fff} {1}\r\n", DateTime.Now, s);
//byte[] array = System.Text.Encoding.UTF8.GetBytes(log);
//fs.Write(array, 0, array.Length);
info.Enqueue(log);
}
internal void SaveLog()
{
while (loop)
{
try
{
if (info.TryDequeue(out string result))
{
byte[] array = System.Text.Encoding.UTF8.GetBytes(result);
fs.Write(array, 0, array.Length);
}
}
catch (Exception)
{ }
System.Threading.Thread.Sleep(2);
}
}
}
}
......@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.3.0.4")]
[assembly: AssemblyFileVersion("2.3.0.4")]
[assembly: AssemblyVersion("2.3.0.6")]
[assembly: AssemblyFileVersion("2.3.0.6")]
......@@ -6,19 +6,7 @@
<members>
<member name="T:Asa.IOModule.AIOBOX">
<summary>
AIOBOX操作类
</summary>
</member>
<member name="F:Asa.IOModule.AIOBOX.SEND_SLEEP">
<summary>
每条命令发送的间隔
不能小于15,会出现IO接收不到的情况
小于30时,会出现接收数据连包的情况
</summary>
</member>
<member name="F:Asa.IOModule.AIOBOX.PORT">
<summary>
ModBus端口
零点IO模块操作类
</summary>
</member>
<member name="T:Asa.IOModule.AIOBOX.DIO_Changed">
......@@ -38,31 +26,23 @@
自动读取DO事件触发
</summary>
</member>
<member name="T:Asa.IOModule.AIOBOX.AIO_Changed">
<summary>
自动读取AI委托
</summary>
<param name="box">AIOBOX</param>
<param name="val">所有AI的值</param>
</member>
<member name="E:Asa.IOModule.AIOBOX.AI_Changed_Event">
<member name="M:Asa.IOModule.AIOBOX.#ctor(Asa.IOModule.Box_Type,System.Int32,Asa.IOModule.Box_Type,System.Int32)">
<summary>
自动读取AI事件触发
零点IO模块操作类
</summary>
<param name="input">输入类型</param>
<param name="inputCount">输入数量</param>
<param name="output">输出类型</param>
<param name="outputCount">输出数量</param>
</member>
<member name="E:Asa.IOModule.AIOBOX.AO_Changed_Event">
<summary>
自动读取AO事件触发
</summary>
</member>
<member name="M:Asa.IOModule.AIOBOX.#ctor">
<member name="P:Asa.IOModule.AIOBOX.IP">
<summary>
AIOBOX
IP地址
</summary>
</member>
<member name="P:Asa.IOModule.AIOBOX.IP">
<member name="P:Asa.IOModule.AIOBOX.Upload">
<summary>
IP地址
输入主动上传
</summary>
</member>
<member name="P:Asa.IOModule.AIOBOX.IsConn">
......@@ -75,41 +55,6 @@
错误信息
</summary>
</member>
<member name="M:Asa.IOModule.AIOBOX.LogPath(System.String,Asa.IOModule.LogType)">
<summary>
日志路径,连接前设置路径会自动保存日志
</summary>
<param name="path">文件夹路径</param>
<param name="type">输出类型</param>
</member>
<member name="M:Asa.IOModule.AIOBOX.SetInput(Asa.IOModule.Box_Type,System.Int32)">
<summary>
设置输入端
</summary>
<param name="type">类型</param>
<param name="count">数量</param>
</member>
<member name="M:Asa.IOModule.AIOBOX.SetOutput(Asa.IOModule.Box_Type,System.Int32)">
<summary>
设置输出端
</summary>
<param name="type">类型</param>
<param name="count">数量</param>
</member>
<member name="M:Asa.IOModule.AIOBOX.AutoIP(System.String)">
<summary>
自动获取IP地址,未连接前使用,必须在同一网段
</summary>
<param name="localIP">本地IP地址</param>
<returns></returns>
</member>
<member name="M:Asa.IOModule.AIOBOX.CheckIP(System.String)">
<summary>
检查IP地址
</summary>
<param name="ip"></param>
<returns></returns>
</member>
<member name="M:Asa.IOModule.AIOBOX.Connect">
<summary>
连接
......@@ -120,26 +65,6 @@
关闭连接
</summary>
</member>
<member name="M:Asa.IOModule.AIOBOX.AutoReadInput(System.Boolean,System.Int32)">
<summary>
自动读取输入端并触发事件(主动上传数据 = 禁止)
</summary>
<param name="read">是否自动读取</param>
<param name="sleep">间隔,必须大于等于15ms</param>
</member>
<member name="M:Asa.IOModule.AIOBOX.AutoReadInput(System.Boolean)">
<summary>
自动读取输入端并触发事件(主动上传数据 = 使能)
</summary>
<param name="read">是否自动读取</param>
</member>
<member name="M:Asa.IOModule.AIOBOX.AutoReadOutput(System.Boolean,System.Int32)">
<summary>
自动读取输出端,触发事件
</summary>
<param name="read">是否自动读取</param>
<param name="sleep">间隔,必须大于等于15ms</param>
</member>
<member name="M:Asa.IOModule.AIOBOX.ReverseStatus(Asa.IOModule.Box_Sta)">
<summary>
相反状态(ON/OFF)
......@@ -221,75 +146,68 @@
<param name="sta"></param>
<returns></returns>
</member>
<member name="M:Asa.IOModule.AIOBOX.GetLocalIP">
<summary>
获取本地IPv4地址
</summary>
<returns></returns>
</member>
<member name="M:Asa.IOModule.AIOBOX.Send">
<summary>
发送命令
</summary>
</member>
<member name="M:Asa.IOModule.AIOBOX.Receive">
<summary>
接收命令
发送命令线程
</summary>
</member>
<member name="M:Asa.IOModule.AIOBOX.ReadSingle(System.Byte[])">
<member name="M:Asa.IOModule.AIOBOX.GetReadDI_Command">
<summary>
读取写入单个DO,功能码5
获取ReadDI的命令
</summary>
<param name="buff"></param>
<returns></returns>
</member>
<member name="M:Asa.IOModule.AIOBOX.ReadDO(System.Byte[])">
<member name="M:Asa.IOModule.AIOBOX.GetReadDO_Command">
<summary>
读取所有DO状态,功能码1
获取ReadDO的命令
</summary>
<param name="buff"></param>
<returns></returns>
</member>
<member name="M:Asa.IOModule.AIOBOX.ReadDI(System.Byte[])">
<member name="M:Asa.IOModule.AIOBOX.Listen">
<summary>
读取所有DI状态,功能码2
监听网络线程
</summary>
</member>
<member name="M:Asa.IOModule.AIOBOX.ReadAI(System.Byte[])">
<member name="M:Asa.IOModule.AIOBOX.CommandProcess(System.Byte[])">
<summary>
读取所有AI的值,功能码4
接收到的命令处理方法,(task多线程)
</summary>
<param name="buff"></param>
<param name="cmd"></param>
</member>
<member name="M:Asa.IOModule.AIOBOX.Command">
<member name="M:Asa.IOModule.AIOBOX.Reconn">
<summary>
命令,前7个字节
重连线程
</summary>
<returns></returns>
</member>
<member name="M:Asa.IOModule.AIOBOX.TriggerDIO">
<member name="M:Asa.IOModule.AIOBOX.Open">
<summary>
触发DIO改变事件
打开socket建立连接
</summary>
</member>
<member name="M:Asa.IOModule.AIOBOX.AutoReadInput">
<member name="M:Asa.IOModule.AIOBOX.CheckIP(System.String)">
<summary>
自动读取输入端线程
检查IP地址
</summary>
<param name="ip"></param>
<returns></returns>
</member>
<member name="M:Asa.IOModule.AIOBOX.AutoReadOutput">
<member name="M:Asa.IOModule.AIOBOX.AutoIP(System.String)">
<summary>
自动读取输出端线程
自动获取IP地址,未连接前使用,必须在同一网段
</summary>
<param name="localIP">本地IP地址</param>
<returns></returns>
</member>
<member name="M:Asa.IOModule.AIOBOX.Listen">
<member name="M:Asa.IOModule.AIOBOX.GetIP">
<summary>
监听结果线程
获取IO模块IP地址
</summary>
</member>
<member name="M:Asa.IOModule.AIOBOX.TestMethod">
<member name="M:Asa.IOModule.AIOBOX.Command">
<summary>
测试方法锁
命令,前7个字节
</summary>
<returns></returns>
</member>
<member name="T:Asa.IOModule.Box_Type">
<summary>
......@@ -331,62 +249,5 @@
闭合,打开,高电平
</summary>
</member>
<member name="T:Asa.IOModule.LogType">
<summary>
日志类型
</summary>
</member>
<member name="F:Asa.IOModule.LogType.OnlyError">
<summary>
仅错误信息
</summary>
</member>
<member name="F:Asa.IOModule.LogType.All">
<summary>
所有
</summary>
</member>
<member name="T:Asa.IOModule.LogFile">
<summary>
日志操作类
</summary>
</member>
<member name="M:Asa.IOModule.LogFile.#ctor(System.String,System.String)">
<summary>
日志
</summary>
<param name="path">文件夹路径</param>
<param name="ip"></param>
</member>
<member name="M:Asa.IOModule.LogFile.Close">
<summary>
关闭文件
</summary>
</member>
<member name="M:Asa.IOModule.LogFile.OutError(System.String)">
<summary>
输出错误
</summary>
<param name="s"></param>
</member>
<member name="M:Asa.IOModule.LogFile.OutInfo(System.String)">
<summary>
输出信息
</summary>
<param name="s"></param>
</member>
<member name="M:Asa.IOModule.LogFile.OutData(System.String,System.Byte[])">
<summary>
输出数据
</summary>
<param name="tr"></param>
<param name="buff"></param>
</member>
<member name="M:Asa.IOModule.LogFile.OutData(System.String)">
<summary>
输出数据
</summary>
<param name="s"></param>
</member>
</members>
</doc>
9428b515b65d7e804ec8e058ca7770707ab7571d
6b9117074f0ff97736a34c4c150b6b18b33edbb4
......@@ -39,3 +39,10 @@ D:\OneDrive - 上海挚锦科技有限公司\SMD\DLL\AIOBOX\obj\Debug\AIOBOX.csp
D:\OneDrive - 上海挚锦科技有限公司\SMD\DLL\AIOBOX\obj\Debug\AIOBOX.csproj.CoreCompileInputs.cache
D:\OneDrive - 上海挚锦科技有限公司\SMD\DLL\AIOBOX\obj\Debug\Asa.IOModule.AIOBOX.dll
D:\OneDrive - 上海挚锦科技有限公司\SMD\DLL\AIOBOX\obj\Debug\Asa.IOModule.AIOBOX.pdb
D:\OneDrive - 上海挚锦科技有限公司\SMD\AIOBOX\AIOBOX\bin\Debug\Asa.IOModule.AIOBOX.xml
D:\OneDrive - 上海挚锦科技有限公司\SMD\AIOBOX\AIOBOX\bin\Debug\Asa.IOModule.AIOBOX.dll
D:\OneDrive - 上海挚锦科技有限公司\SMD\AIOBOX\AIOBOX\bin\Debug\Asa.IOModule.AIOBOX.pdb
D:\OneDrive - 上海挚锦科技有限公司\SMD\AIOBOX\AIOBOX\obj\Debug\AIOBOX.csproj.CoreCompileInputs.cache
D:\OneDrive - 上海挚锦科技有限公司\SMD\AIOBOX\AIOBOX\obj\Debug\Asa.IOModule.AIOBOX.dll
D:\OneDrive - 上海挚锦科技有限公司\SMD\AIOBOX\AIOBOX\obj\Debug\Asa.IOModule.AIOBOX.pdb
D:\OneDrive - 上海挚锦科技有限公司\SMD\AIOBOX\AIOBOX\obj\Debug\AIOBOX.csprojAssemblyReference.cache
支持 Markdown 格式
你添加了 0 到此讨论。请谨慎行事。
Finish editing this message first!