Files
DevToolsAvalonia/常用工具集/Utility/Network/Omrons/Omron.cs
2025-08-26 08:37:44 +08:00

961 lines
35 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
namespace OmronLib
{
/*
*Copyright: Copyright (c) 2019
*Created on 2019-11-7
*Author: coder_li@outlook.com
*/
/// <summary>
/// 欧姆龙PLC使用FINS通讯在与PLC建立tcp连接后必须先进行读写准备获取到DA1和SA1
/// 这里默认读取200个D区地址从D5000开始到D5199读取的返回值都是高位在前低位在后
/// 写入时都是单个寄存器写入,写入的内容也是高位在前低位在后
/// </summary>
public class Omron
{
/// <summary>
/// PLCIP
/// </summary>
public IPAddress IPAddr { get; set; }
/// <summary>
/// PLC端口号
/// </summary>
public int Port { get; set; }
public int Timeout = 1000;
/// <summary>
/// 获取是否已经连接到PLC
/// </summary>
/// <returns></returns>
public bool IsConnected
{
get
{
if (PlcClient == null)
return false;
else
return PlcClient.Connected;
}
}
/// <summary>
/// PLC内存区域类型
/// </summary>
public enum AreaType
{
CIO_Bit = 0x30,
WR_Bit = 0x31,
HR_Bit = 0x32,
AR_Bit = 0x33,
DM_Bit = 0x02,
CIO_Word = 0xB0,
WR_Word = 0xB1,
HR_Word = 0xB2,
AR_Word = 0xB3,
DM_Word = 0x82
}
private readonly object locker = new object();
private TcpClient PlcClient = null;
private NetworkStream Stream = null;
private IPEndPoint PlcIP = null;
private byte DA1 = 0x00, SA1 = 0x00;
public Omron()
{ }
#region
/// <summary>
/// 写命令
/// </summary>
private readonly byte[] WriteCommand = new byte[] { 0x46, 0x49, 0x4e, 0x53, // F I N S
0x00, 0x00, 0x00, 0x1c, // 命令长度
0x00, 0x00, 0x00, 0x02, // 命令码
0x00, 0x00, 0x00, 0x00, // 错误码
0x80, 0x00, 0x02, 0x00, // ICF RSV GCT DNA
0x00, 0x00, 0x00, 0x00, // DA1 DA2 SNA SA1
0x00, 0xDA, // SA2 SID
0x01, 0x02, // FINS主命令 FINS从命令 01 02 写)
0x82, 0x00, 0x00, 0x00, // 寄存器控制位 开始字地址(从高到低) 开始位地址
0x00, 0x01 }; // 写入数量(从高到低) 后面都是写入的内容(可以加长)
/// <summary>
/// 读命令
/// </summary>
private readonly byte[] ReadCommand = new byte[] { 0x46, 0x49, 0x4e, 0x53, // F I N S
0x00, 0x00, 0x00, 0x1a, // 命令长度
0x00, 0x00, 0x00, 0x02, // 命令码
0x00, 0x00, 0x00, 0x00, // 错误码
0x80, 0x00, 0x02, 0x00, // ICF RSV GCT DNA ***************
0x00, 0x00, 0x00, 0x00, // DA1 DA2 SNA SA1 FINS Header
0x00, 0xDA, // SA2 SID ***************
0x01, 0x01, // FINS主命令 FINS从命令 01 01 读) ***************
0x82, 0x00, 0x00, 0x00, // 寄存器控制位 开始字地址(高,低) 开始位地址 FINS Command
0x00, 0x01 }; // 读取数量(高,低) ***************
/// <summary>
/// 获取节点地址的命令
/// </summary>
private readonly byte[] PrepareCmd = new byte[] { 0x46, 0x49, 0x4e, 0x53,
0x00, 0x00, 0x00, 0x0c,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };
#endregion
/// <summary>
/// 向PLC发送指令
/// </summary>
/// <param name="commands">指令</param>
/// <param name="info">报错信息</param>
/// <returns>是否发送成功</returns>
private bool DataSend(byte[] commands)
{
string info;
try
{
if (this.PlcClient != null && PlcClient.Connected)
{
//lock (locker)
{
if (Stream == null) Stream = PlcClient.GetStream();
Stream.Write(commands, 0, commands.Length);
Stream.Flush();
}
return true;
}
else
{
info = "连接已断开";
return false;
}
}
catch (Exception exp)
{
info = "发送指令到PLC失败 " + exp.Message;
return false;
}
}
/// <summary>
/// 从PLC读取数据
/// </summary>
/// <param name="isExtCmd"></param>
/// <param name="info">报错信息</param>
/// <returns></returns>
private string DataRead()
{
byte[] buffer = new byte[1024];
int bytestoread = 0;
string info;
try
{
//if (Stream.DataAvailable)
{
int start = Environment.TickCount;
do
{
bytestoread = Stream.Read(buffer, 0, buffer.Length);
if (Math.Abs(Environment.TickCount - start) >= 20000)
{
info = "PLC应答超时请确认PLC连接线路及cpu是否有报错";
break;
}
} while (bytestoread == 0);
if (buffer[11] == 3)
{
if (buffer[15] == 1)
{
info = "发送的命令不是有效的FINS命令";
return "";
}
else if (buffer[15] == 2)
{
info = "发送的命令长度超出范围!";
return "";
}
else if (buffer[15] == 3)
{
info = "PLC不支持该FINS命令";
return "";
}
}
if (buffer[25] == WriteCommand[25])
return DataTools.ByteToHexString(buffer, bytestoread);
else
{
info = "PLC返回错误返回值 " + buffer[0].ToString();
return "";
}
}
}
catch
{
return "";
}
}
/// <summary>
/// 判断是否成功写入命令到PLC
/// </summary>
/// <returns></returns>
private bool Write2PlcSuccess()
{
byte[] buffer = new byte[1024];
int bytestoread = 0;
string info;
int start = Environment.TickCount;
try
{
//lock (locker)
{
do
{
bytestoread = Stream.Read(buffer, 0, buffer.Length);
if (Math.Abs(Environment.TickCount - start) >= 1000)
{
info = "PLC应答超时请确认PLC连接线路及cpu是否有报错";
break;
}
} while (bytestoread == 0);
}
if (buffer[25] == WriteCommand[25]) return true;
else
{
info = "PLC返回错误返回值 " + buffer[1].ToString();
return false;
}
}
catch
{
return false;
}
}
/// <summary>
/// 发送连接字符串获取PLC返回的DA1和SA1值
/// </summary>
/// <returns></returns>
private bool ConnectPrepare()
{
try
{
Stream.Write(PrepareCmd, 0, PrepareCmd.Length);
byte[] res = new byte[24];
Stream.Read(res, 0, res.Length);
DA1 = res[23];
SA1 = res[19];
}
catch { return false; }
return true;
}
/// <summary>
/// 检查命令帧中的EndCode
/// </summary>
/// <param name="Main">主码</param>
/// <param name="Sub">副码</param>
/// <param name="info">错误信息</param>
/// <returns>指示程序是否可以继续进行</returns>
private bool CheckEndCode(byte Main, byte Sub, out string info)
{
info = "";
switch (Main)
{
case 0x00:
switch (Sub)
{
case 0x00: return true;//the only situation of success
case 0x01: info = "service canceled"; return false;
}
break;
case 0x01:
switch (Sub)
{
case 0x01: info = "local node not in network"; return false;
case 0x02: info = "token timeout"; return false;
case 0x03: info = "retries failed"; return false;
case 0x04: info = "too many send frames"; return false;
case 0x05: info = "node address range error"; return false;
case 0x06: info = "node address duplication"; return false;
}
break;
case 0x02:
switch (Sub)
{
case 0x01: info = "destination node not in network"; return false;
case 0x02: info = "unit missing"; return false;
case 0x03: info = "third node missing"; return false;
case 0x04: info = "destination node busy"; return false;
case 0x05: info = "response timeout"; return false;
}
break;
case 0x03:
switch (Sub)
{
case 0x01: info = "communications controller error"; return false;
case 0x02: info = "CPU unit error"; return false;
case 0x03: info = "controller error"; return false;
case 0x04: info = "unit number error"; return false;
}
break;
case 0x04:
switch (Sub)
{
case 0x01: info = "undefined command"; return false;
case 0x02: info = "not supported by model/version"; return false;
}
break;
case 0x05:
switch (Sub)
{
case 0x01: info = "destination address setting error"; return false;
case 0x02: info = "no routing tables"; return false;
case 0x03: info = "routing table error"; return false;
case 0x04: info = "too many relays"; return false;
}
break;
case 0x10:
switch (Sub)
{
case 0x01: info = "command too long"; return false;
case 0x02: info = "command too short"; return false;
case 0x03: info = "elements/data don't match"; return false;
case 0x04: info = "command format error"; return false;
case 0x05: info = "header error"; return false;
}
break;
case 0x11:
switch (Sub)
{
case 0x01: info = "area classification missing"; return false;
case 0x02: info = "access size error"; return false;
case 0x03: info = "address range error"; return false;
case 0x04: info = "address range exceeded"; return false;
case 0x06: info = "program missing"; return false;
case 0x09: info = "relational error"; return false;
case 0x0a: info = "duplicate data access"; return false;
case 0x0b: info = "response too long"; return false;
case 0x0c: info = "parameter error"; return false;
}
break;
case 0x20:
switch (Sub)
{
case 0x02: info = "protected"; return false;
case 0x03: info = "table missing"; return false;
case 0x04: info = "data missing"; return false;
case 0x05: info = "program missing"; return false;
case 0x06: info = "file missing"; return false;
case 0x07: info = "data mismatch"; return false;
}
break;
case 0x21:
switch (Sub)
{
case 0x01: info = "read-only"; return false;
case 0x02: info = "protected , cannot write data link table"; return false;
case 0x03: info = "cannot register"; return false;
case 0x05: info = "program missing"; return false;
case 0x06: info = "file missing"; return false;
case 0x07: info = "file name already exists"; return false;
case 0x08: info = "cannot change"; return false;
}
break;
case 0x22:
switch (Sub)
{
case 0x01: info = "not possible during execution"; return false;
case 0x02: info = "not possible while running"; return false;
case 0x03: info = "wrong PLC mode"; return false;
case 0x04: info = "wrong PLC mode"; return false;
case 0x05: info = "wrong PLC mode"; return false;
case 0x06: info = "wrong PLC mode"; return false;
case 0x07: info = "specified node not polling node"; return false;
case 0x08: info = "step cannot be executed"; return false;
}
break;
case 0x23:
switch (Sub)
{
case 0x01: info = "file device missing"; return false;
case 0x02: info = "memory missing"; return false;
case 0x03: info = "clock missing"; return false;
}
break;
case 0x24:
switch (Sub)
{ case 0x01: info = "table missing"; return false; }
break;
case 0x25:
switch (Sub)
{
case 0x02: info = "memory error"; return false;
case 0x03: info = "I/O setting error"; return false;
case 0x04: info = "too many I/O points"; return false;
case 0x05: info = "CPU bus error"; return false;
case 0x06: info = "I/O duplication"; return false;
case 0x07: info = "CPU bus error"; return false;
case 0x09: info = "SYSMAC BUS/2 error"; return false;
case 0x0a: info = "CPU bus unit error"; return false;
case 0x0d: info = "SYSMAC BUS No. duplication"; return false;
case 0x0f: info = "memory error"; return false;
case 0x10: info = "SYSMAC BUS terminator missing"; return false;
}
break;
case 0x26:
switch (Sub)
{
case 0x01: info = "no protection"; return false;
case 0x02: info = "incorrect password"; return false;
case 0x04: info = "protected"; return false;
case 0x05: info = "service already executing"; return false;
case 0x06: info = "service stopped"; return false;
case 0x07: info = "no execution right"; return false;
case 0x08: info = "settings required before execution"; return false;
case 0x09: info = "necessary items not set"; return false;
case 0x0a: info = "number already defined"; return false;
case 0x0b: info = "error will not clear"; return false;
}
break;
case 0x30:
switch (Sub)
{ case 0x01: info = "no access right"; return false; }
break;
case 0x40:
switch (Sub)
{ case 0x01: info = "service aborted"; return false; }
break;
}
info = "unknown exception";
return false;
}
/// <summary>
/// PLC初始化
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
private bool PlcInit()
{
bool flag = false;
PlcClient = new TcpClient();
IAsyncResult asyncResult = PlcClient.BeginConnect(IPAddr, Port, null, null);
if (!asyncResult.AsyncWaitHandle.WaitOne(Timeout))
{
return flag;
}
PlcClient.EndConnect(asyncResult);
if (PlcClient != null)
{
Stream = PlcClient.GetStream();
Stream.ReadTimeout = Timeout;
Stream.WriteTimeout = Timeout;
if (ConnectPrepare())
{
flag = true;
}
}
return flag;
}
/// <summary>
/// 连接PLC
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
public bool PlcConnect(out string info)
{
info = "";
try
{
if (PlcClient == null)
{
PlcInit();
}
if (!PlcClient.Connected) // 没连上的话重试一遍
{
PlcClient.Close();
PlcClient = null;
PlcInit();
}
return PlcClient.Connected;
}
catch (Exception exp)
{
info = "连接PLC失败\n" + exp.Message;
return false;
}
}
/// <summary>
/// 断开PLC连接
/// </summary>
/// <returns></returns>
public bool DisConnect()
{
if (this.PlcClient == null)
return true;
if (!this.PlcClient.Connected)
return true;
this.PlcClient.Close();
return true;
}
#region
/// <summary>
/// 读PLC字或位
/// 读位时从左到右是低位到高位
/// </summary>
/// <param name="type">PLC内存区类型</param>
/// <param name="startword">开始地址(字)</param>
/// <param name="startbit">开始地址(位)</param>
/// <param name="count">长度</param>
/// <param name="result">返回的字符数组,每个元素表示一个字</param>
/// <returns></returns>
public bool Read(AreaType type, int startword, int startbit, int count, out string[] result)
{
result = new string[0];
byte[] cmd = (byte[])ReadCommand.Clone();
cmd[20] = DA1; //连接时获取到的DA1
cmd[23] = SA1; //连接时获取到的SA1
// 内存类型
cmd[28] = (byte)type;
// 读取起始地址
byte[] bytesAddr = BitConverter.GetBytes((short)startword); //BitConverter转出来的Byte按从低位到高位排列
cmd[29] = bytesAddr[1];
cmd[30] = bytesAddr[0];
cmd[31] = (byte)startbit;
// 读取长度
byte[] bytesLength = BitConverter.GetBytes((short)count);
cmd[32] = bytesLength[1];
cmd[33] = bytesLength[0];
//开始读取
lock (locker)
{
if (DataSend(cmd))
{
string res = DataRead();
if (res.Length == 0)
return false;
result = DataTools.StrToStrArray(res.Substring(60));
return true;
}
}
return false;
}
/// <summary>
/// 读取DM区的字
/// </summary>
/// <param name="startword"></param>
/// <param name="count"></param>
/// <param name="result"></param>
/// <returns></returns>
public bool ReadDWords(int startword, int count, out string[] result)
{
string[] res;
bool isSucess = Read(AreaType.DM_Word, startword, 0, count, out res);
result = res;
return isSucess;
}
/// <summary>
/// 读取DM区的位
/// </summary>
/// <param name="startword"></param>
/// <param name="startbit"></param>
/// <param name="count"></param>
/// <param name="result"></param>
/// <returns></returns>
public bool ReadDBits(int startword, int startbit, int count, out string[] result)
{
string[] res;
bool isSucess = Read(AreaType.DM_Bit, startword, startbit, count, out res);
result = res;
return isSucess;
}
#endregion
#region
/// <summary>
/// 写PLC字
/// </summary>
/// <param name="type">地址类型</param>
/// <param name="startword">开始字地址</param>
/// <param name="count">字数</param>
/// <param name="paras">值</param>
/// <returns>写入成功与否</returns>
public bool WriteWords(AreaType type, int startword, int count, int[] paras)
{
byte[] hexadd = BitConverter.GetBytes(startword);
byte[] sendCmd = WriteCommand.Concat(new byte[count * 2]).ToArray(); // 这里扩容sendCmd数组不然会溢出
sendCmd[7] = (byte)(26 + count * 2);
sendCmd[20] = DA1;
sendCmd[23] = SA1;
// 内存类型
sendCmd[28] = (byte)type;
// 写入起始地址
byte[] bytesAddr = BitConverter.GetBytes((short)startword); //BitConverter转出来的Byte按从低位到高位排列
sendCmd[29] = bytesAddr[1];
sendCmd[30] = bytesAddr[0];
sendCmd[31] = 0;
// 写入长度
byte[] bytesLength = BitConverter.GetBytes((short)count);
sendCmd[32] = bytesLength[1];
sendCmd[33] = bytesLength[0];
byte[] hexPara;
for (int i = 0; i < count; i++)
{
hexPara = BitConverter.GetBytes(paras[i]);
sendCmd[34 + i * 2] = hexPara[1];
sendCmd[34 + i * 2 + 1] = hexPara[0];
}
lock (locker)
{
if (DataSend(sendCmd))
{
if (Write2PlcSuccess())
return true;
}
}
return false;
}
/// <summary>
/// 写PLC位
///
/// </summary>
/// <param name="type">地址类型</param>
/// <param name="startword">开始字地址</param>
/// <param name="startbit">开始位地址</param>
/// <param name="count">写入位数</param>
/// <param name="paras">值</param>
/// <returns>写入成功与否</returns>
public bool WriteBits(AreaType type, int startword, int startbit, int count, bool[] paras)
{
byte[] hexadd = BitConverter.GetBytes(startword);
byte[] sendCmd = WriteCommand.Concat(new byte[count]).ToArray(); // 这里扩容sendCmd数组不然会溢出
// 命令长度
sendCmd[7] = (byte)(26 + count);
sendCmd[20] = DA1;
sendCmd[23] = SA1;
// 内存类型
sendCmd[28] = (byte)type;
// 写入起始地址
byte[] bytesAddr = BitConverter.GetBytes((short)startword); //BitConverter转出来的Byte按从低位到高位排列
sendCmd[29] = bytesAddr[1];
sendCmd[30] = bytesAddr[0];
sendCmd[31] = (byte)startbit;
// 写入长度
byte[] bytesLength = BitConverter.GetBytes((short)count);
sendCmd[32] = bytesLength[1];
sendCmd[33] = bytesLength[0];
for (int i = 0; i < count; i++)
{
sendCmd[34 + i] = Convert.ToByte(paras[i]);
}
lock (locker)
{
if (DataSend(sendCmd))
{
if (Write2PlcSuccess())
return true;
}
}
return false;
}
/// <summary>
/// 写D字
/// </summary>
/// <param name="startword"></param>
/// <param name="count"></param>
/// <param name="paras"></param>
/// <returns></returns>
public bool WriteDWords(int startword, int count, int[] paras)
{
return WriteWords(AreaType.DM_Word, startword, count, paras);
}
/// <summary>
/// 写D位
/// </summary>
/// <param name="startword"></param>
/// <param name="startbit"></param>
/// <param name="count"></param>
/// <param name="paras"></param>
/// <returns></returns>
public bool WriteDBits(int startword, int startbit, int count, bool[] paras)
{
return WriteBits(AreaType.DM_Bit, startword, startbit, count, paras);
}
public bool ReadString(int address, int count, out string str)
{
try
{
string[] res;
bool isSucess = ReadDWords(address, count, out res);
if (!isSucess)
{
str = "";
return false;
}
byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray();
List<int> list = new List<int>();
for (int i = 0; i < bRes.Length; i += 2)
{
list.Add((int)(bRes[i] << 8 | bRes[i + 1]));
}
str = GetQRCodeByData(list.ToArray());
return true;
}
catch (Exception ex)
{
str = ex.Message;
return false;
}
}
public bool ReadFloat(int address, out float value)
{
try
{
string[] res;
bool isSucess = ReadDWords(address, 2, out res);
if (!isSucess)
{
value = 0;
return false;
}
byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray();
if (bRes.Length != 4)
{
value = 0;
return false;
}
int i = 0;
value = BitConverter.ToSingle(new byte[] { bRes[i + 1], bRes[i], bRes[i + 3], bRes[i + 2] }, 0);
return true;
}
catch (Exception ex)
{
value = 0;
return false;
}
}
public bool ReadFloats(int address, int count, out float[] values)
{
try
{
string[] res;
bool isSucess = ReadDWords(address, count * 2, out res);
if (!isSucess)
{
values = null;
return false;
}
byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray();
if (bRes.Length != (count * 4))
{
values = null;
return false;
}
List<float> list = new List<float>();
for (int i = 0; i < bRes.Length; i += 4)
{
list.Add(BitConverter.ToSingle(new byte[] { bRes[i + 1], bRes[i], bRes[i + 3], bRes[i + 2] }, 0));
}
values = list.ToArray();
return true;
}
catch (Exception ex)
{
values = null;
return false;
}
}
public bool WriteFloat(int address, float[] values)
{
List<int> writeValues = new List<int>();
foreach (float f in values)
{
byte[] bytes = BitConverter.GetBytes(f);
writeValues.Add(BitConverter.ToInt32(new byte[] { bytes[0], bytes[1], 0, 0 }, 0));
writeValues.Add(BitConverter.ToInt32(new byte[] { bytes[2], bytes[3], 0, 0 }, 0));
}
return WriteWords(AreaType.DM_Word, address, values.Length * 2, writeValues.ToArray());
}
public bool ReadShort(int address, out short value)
{
try
{
string[] res;
bool isSucess = ReadDWords(address, 1, out res);
if (!isSucess)
{
value = 0;
return false;
}
byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray();
if (bRes.Length != 2)
{
value = 0;
return false;
}
value = BitConverter.ToInt16(new byte[] { bRes[1], bRes[0] }, 0);
return true;
}
catch (Exception ex)
{
value = 0;
return false;
}
}
public bool ReadShorts(int address, int count, out short[] values)
{
try
{
string[] res;
bool isSucess = ReadDWords(address, count, out res);
if (!isSucess)
{
values = null;
return false;
}
byte[] bRes = res.Select(it => Convert.ToByte(it, 16)).ToArray();
if (bRes.Length != (count * 2))
{
values = null;
return false;
}
List<short> list = new List<short>();
for (int i = 0; i < bRes.Length; i += 2)
{
list.Add(BitConverter.ToInt16(new byte[] { bRes[i + 1], bRes[i] }, 0));
}
values = list.ToArray();
return true;
}
catch (Exception ex)
{
values = null;
return false;
}
}
public bool WriteShort(int address, short[] values)
{
List<int> writeValues = new List<int>();
foreach (short f in values)
{
writeValues.Add(f);
}
return WriteWords(AreaType.DM_Word, address, values.Length, writeValues.ToArray());
}
public bool WriteString(int address, string str, int count)
{
try
{
int[] aaa = GetDataByQRCode(str, count);
return WriteDWords(address, count, aaa);
}
catch (Exception ex)
{
return false;
}
}
private int[] GetDataByQRCode(string qrcode, int totalCount)
{
byte[] byteList = new byte[2 * totalCount];
byte[] bytes = Encoding.ASCII.GetBytes(qrcode);
for (int i = 0; i < byteList.Length; i++)
{
if (i >= bytes.Length)
{
break;
}
byteList[i] = bytes[i];
}
List<int> shortList = new List<int>();
for (int i = 0; i < (2 * totalCount); i += 2)
{
//将字节数组转为int存入list
shortList.Add(BitConverter.ToInt32(new byte[] { byteList[i], byteList[i + 1], 0x00, 0x00 }, 0));
}
return shortList.ToArray();
}
/// <summary>
/// 将PLC中获取到的数据转成string
/// </summary>
/// <param name="qrcode"></param>
/// <returns></returns>
private string GetQRCodeByData(int[] qrcode)
{
List<byte> list = new List<byte>();
for (int i = 0; i < qrcode.Length; i++)
{
//int中取两个字节存入list
list.AddRange(BitConverter.GetBytes(qrcode[i]).Take(2));
}
//去掉多余的/0和空格 转为string
string qrcodes = Encoding.ASCII.GetString(list.ToArray()).Trim('\0').Trim();
return qrcodes;
}
#endregion
}
}