123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Net;
- using System.Net.Sockets;
- using System.Threading.Tasks;
- using NModbus;
- namespace Team.Communicate.Modbus
- {
- public class ModbusTcpMaster : IModbusTcpMaster
- {
- private readonly string _ip;
- private readonly int _port;
- public DataFormat DataFormat { get; set; } = DataFormat.CDAB;
- public int ConnectTimeOut { get; set; }
- public string Ip { get => _ip; }
- public int Port { get => _port; }
- public bool IsConnect { get; private set; }
- private IModbusMaster _modbusMaster;
- private TcpClient _client;
- public event EventHandler<bool> ConnectChanged;
- public ModbusTcpMaster(string ip, int port)
- {
- _ip = ip;
- _port = port;
- var modbusFactory = new ModbusFactory();
- _client = new TcpClient();
- _modbusMaster = modbusFactory.CreateMaster(_client);
- }
- /// <summary>
- /// client must is connected
- /// </summary>
- /// <param name="client"></param>
- public ModbusTcpMaster(TcpClient client)
- {
- if (client == null)
- throw new ArgumentNullException("client");
- if (!client.Connected) throw new ArgumentNullException("client is not connected");
- var iPEndPoint= client.Client.RemoteEndPoint as IPEndPoint;
- _ip = iPEndPoint.Address.ToString();
- _port= iPEndPoint.Port;
- //_port = port;
- var modbusFactory = new ModbusFactory();
- _client = client;
- _modbusMaster = modbusFactory.CreateMaster(_client);
-
- }
- public void Connect()
- {
- try
- {
- _client.Connect(_ip, _port);
- IsConnect = true;
- OnConnectChanged(true);
- }
- catch (Exception e)
- {
- OnConnectChanged(false);
- IsConnect = false;
- }
- }
- public byte SlaveId { get; set; } = 1;
- public async Task ConnectAsync()
- {
- try
- {
- if (_client.Connected)
- {
- return;
- }
- await _client.ConnectAsync(_ip, _port);
- OnConnectChanged(true);
- IsConnect = true;
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- OnConnectChanged(false);
- IsConnect = false;
- }
- }
-
- public async Task<bool> ReConnect()
- {
- if (_client != null)
- {
- _client.Dispose();
- }
- try
- {
- _client = new TcpClient();
- if (_client.Connected)
- {
- return true;
- }
- await _client.ConnectAsync(_ip, _port);
- var modbusFactory = new ModbusFactory();
- _modbusMaster = modbusFactory.CreateMaster(_client);
- OnConnectChanged(true);
- IsConnect = true;
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- OnConnectChanged(false);
- IsConnect = false;
- }
- return IsConnect;
- }
- public ushort[] ReadBytes(ushort start, ushort length)
- {
- var byteData = _modbusMaster.ReadHoldingRegisters(SlaveId, start, length);
- return byteData;
- }
- /// <summary>
- /// 获取单个位状态
- /// </summary>
- /// <param name="start">从PLC里读出第几个字节</param>
- /// <param name="index">第几位</param>
- /// <returns></returns>
- public bool GetBool(ushort start, ushort index)
- {
- try
- {
- var byteData = _modbusMaster.ReadHoldingRegisters(SlaveId, start, 1);
- var num = byteData[0];
- var bitGroup = new bool[16];
- for (ushort i = 0; i < 16; i++)
- {
- bitGroup[i] = ((num >> i) & 1) == 1;
- }
- return bitGroup[index];
- }
- catch (IOException)
- {
- OnConnectChanged(false);
- IsConnect = false;
- throw;
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- throw;
- }
- }
- public List<KeyValuePair<string, bool>> GetBoolList(IEnumerable<string> addresses)
- {
- var boolList = new List<KeyValuePair<string, bool>>();
- var byteData = _modbusMaster.ReadHoldingRegisters(SlaveId, 0, 50);
- foreach (var address in addresses)
- {
- var stringSplits = address.Split('.');
- var byteIndex = ushort.Parse(stringSplits[0]);
- var byteIndex1 = byteIndex % 2 == 0 ? byteIndex / 2: byteIndex-1 / 2;
- var index = int.Parse(stringSplits[1]);
- var num = byteData[byteIndex1];
- var bitGroup = new bool[16];
- for (ushort i = 0; i < 16; i++)
- {
- bitGroup[i] = ((num >> i) & 1) == 1;
- }
- var addressValue = bitGroup[index];
- boolList.Add(new KeyValuePair<string, bool>(address, addressValue));
- }
- return boolList;
- }
- /// <summary>
- /// 获取单个位状态
- /// </summary>
- /// <param name="start">从PLC里读出第几个字节</param>
- /// <param name="index" >第几位</param>
- /// <returns></returns>
- public async Task<bool> GetBoolAsync(ushort start, ushort index)
- {
- try
- {
- var byteData = await _modbusMaster.ReadHoldingRegistersAsync(SlaveId, start, 1);
- var num = byteData[0];
- var tempBytes = BitConverter.GetBytes(num);
- var revertBytes = tempBytes.Reverse().ToArray();
- var realValue = BitConverter.ToUInt16(revertBytes, 0);
- var bitGroup = new bool[16];
- for (ushort i = 0; i < 16; i++)
- {
- bitGroup[i] = ((realValue >> i) & 1) == 1;
- }
- return bitGroup[index];
- }
- catch (IOException)
- {
- OnConnectChanged(false);
- IsConnect = false;
- throw;
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- throw;
- }
- }
- /// <summary>
- /// 写modbus位
- /// </summary>
- /// <param name="start"></param>
- /// <param name="index"></param>
- /// <param name="flag"></param>
- public void WriteBool(ushort start, int index, bool flag)
- {
- try
- {
- var doCh = new bool[16];
- var currentValue = GetShort(start);
- for (ushort i = 0; i < 16; i++)
- {
- doCh[i] = ((currentValue >> i) & 1) == 1; //获取PLC当前状态
- }
- ushort outValue = 0;
- doCh[index] = flag;
- var front = new bool[8];
- var back = new bool[8];
- Array.Copy(doCh, 0, front, 0, 8);
- Array.Copy(doCh, 8, back, 0, 8);
- for (ushort i = 0; i < 16; i++)
- {
- outValue |= (ushort) (Convert.ToUInt16(doCh[i]) << i);
- }
- _modbusMaster.WriteMultipleRegisters(SlaveId, start, new[] {outValue});
- }
- catch (IOException)
- {
- OnConnectChanged(false);
- IsConnect = false;
- throw;
- }
- }
- /// <summary>
- /// 写modbus位
- /// </summary>
- /// <param name="start"></param>
- /// <param name="index"></param>
- /// <param name="flag"></param>
- public async Task WriteBoolAsync(ushort start, int index, bool flag)
- {
- try
- {
- var doCh = new bool[16];
- var currentValue = await GetShortAsync(start);
- for (ushort i = 0; i < 16; i++)
- {
- doCh[i] = ((currentValue >> i) & 1) == 1; //获取PLC当前状态
- }
- ushort outValue = 0;
- doCh[index] = flag;
- var front = new bool[8];
- var back = new bool[8];
- Array.Copy(doCh, 0, front, 0, 8);
- Array.Copy(doCh, 8, back, 0, 8);
- for (ushort i = 0; i < 16; i++)
- {
- outValue |= (ushort) (Convert.ToUInt16(doCh[i]) << i);
- }
- var reverseValue = BitConverter.GetBytes(outValue);
- var outValue1 = BitConverter.ToUInt16(reverseValue, 0);
- await _modbusMaster.WriteMultipleRegistersAsync(SlaveId, start, new[] {outValue1});
- }
- catch (IOException)
- {
- OnConnectChanged(false);
- IsConnect = false;
- throw;
- }
- }
- /// <summary>
- /// 写modbus位
- /// </summary>
- /// <param name="start"></param>
- /// <param name="index"></param>
- /// <param name="value"></param>
- public void WriteByte(ushort start, int index, byte value)
- {
- try
- {
- var currentValue = GetShort(start);
- var bytes = BitConverter.GetBytes(currentValue);
- bytes[index] = value;
- var writeValue = BitConverter.ToUInt16(bytes, 0);
- _modbusMaster.WriteMultipleRegisters(SlaveId, start, new[] {writeValue});
- }
- catch (IOException)
- {
- OnConnectChanged(false);
- IsConnect = false;
- throw;
- }
- }
- /// <summary>
- /// 写modbus位
- /// </summary>
- /// <param name="start"></param>
- /// <param name="index"></param>
- /// <param name="value"></param>
- public async Task WriteByteAsync(ushort start, int index, byte value)//todo need to test
- {
- try
- {
- var currentValue = await GetShortAsync(start);
- var bytes = BitConverter.GetBytes(currentValue);
- bytes[index] = value;
- var writeValue = BitConverter.ToUInt16(bytes, 0);
- await _modbusMaster.WriteSingleRegisterAsync(SlaveId, start, writeValue);
- }
- catch (IOException)
- {
- OnConnectChanged(false);
- IsConnect = false;
- throw;
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- throw;
- }
- }
- /// <summary>
- /// 获取浮点数值
- /// </summary>
- /// <param name="index">数据索引</param>
- /// <returns></returns>
- public float GetFloat(ushort index)
- {
- try
- {
- var shortData = _modbusMaster.ReadHoldingRegisters(SlaveId, index, 2);
- var bytes1 = BitConverter.GetBytes(shortData[0]);
- var bytes2 = BitConverter.GetBytes(shortData[1]);
- var mergeBytes = bytes1.Concat(bytes2);
- return BitConverter.ToSingle(mergeBytes.ToArray(), 0);
- }
- catch (IOException)
- {
- OnConnectChanged(false);
- IsConnect = false;
- throw;
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- throw;
- }
- }
- /// <summary>
- /// 获取浮点数值
- /// </summary>
- /// <param name="index">数据索引</param>
- /// <returns></returns>
- public async Task<float> GetFloatAsync(ushort index)
- {
- try
- {
- var shortData = await _modbusMaster.ReadHoldingRegistersAsync(SlaveId, index, 2);
- var bytes1 = BitConverter.GetBytes(shortData[0]);
- var bytes2 = BitConverter.GetBytes(shortData[1]);
- var mergeBytes = bytes1.Concat(bytes2);
- return BitConverter.ToSingle(mergeBytes.ToArray(), 0);
- }
- catch (IOException)
- {
- OnConnectChanged(false);
- IsConnect = false;
- throw;
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- throw;
- }
- }
- /// <summary>
- /// 获取字
- /// </summary>
- /// <param name="index">数据索引</param>
- /// <returns></returns>
- public ushort GetShort(ushort index)
- {
- try
- {
-
- var shortData = _modbusMaster.ReadHoldingRegisters(SlaveId, index, 1);
- var shortBytes = BitConverter.GetBytes(shortData[0]);
- var realBytes = shortBytes;
- var realValue = BitConverter.ToUInt16(realBytes, 0);
- return realValue;
- }
- catch (IOException)
- {
- OnConnectChanged(false);
- IsConnect = false;
- throw;
- }
- }
- /// <summary>
- /// 获取字
- /// </summary>
- /// <param name="index">数据索引</param>
- /// <returns></returns>
- public async Task<ushort> GetShortAsync(ushort index)
- {
- try
- {
- var shortData = await _modbusMaster.ReadHoldingRegistersAsync(SlaveId, index, 1);
- var shortBytes = BitConverter.GetBytes(shortData[0]);
- var realValue = BitConverter.ToUInt16(shortBytes, 0);
- return realValue;
- }
- catch (IOException)
- {
- IsConnect = false;
- throw;
- }
- }
- /// <summary>
- /// 获取整型数值
- /// </summary>
- /// 4字节整数
- /// <param name="index">数据索引</param>
- /// <returns></returns>
- public int GetInteger(ushort index)
- {
- try
- {
- var shortData = _modbusMaster.ReadHoldingRegisters(SlaveId, index, 2);
- var bytes1 = BitConverter.GetBytes(shortData[0]);
- var bytes2 = BitConverter.GetBytes(shortData[1]);
- var mergeBytes = bytes1.Concat(bytes2);
- return BitConverter.ToInt32(mergeBytes.ToArray(), 0);
- }
- catch (IOException)
- {
- IsConnect = false;
- throw;
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- throw;
- }
- }
- /// <summary>
- /// 异步获取整型数值
- /// </summary>
- /// 4字节整数
- /// <param name="index">数据索引</param>
- /// <returns></returns>
- public async Task<int> GetIntegerAsync(ushort index)
- {
- try
- {
- var shortData = await _modbusMaster.ReadHoldingRegistersAsync(SlaveId, index, 2);
- var bytes1 = BitConverter.GetBytes(shortData[0]);
- var bytes2 = BitConverter.GetBytes(shortData[1]);
- var mergeBytes = bytes1.Concat(bytes2);
- return BitConverter.ToInt32(mergeBytes.ToArray(), 0);
- }
- catch (IOException)
- {
- IsConnect = false;
- OnConnectChanged(false);
- throw;
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- throw;
- }
- }
- /// <summary>
- /// 读字节
- /// </summary>
- /// <param name="start">开始字索引</param>
- /// <param name="index">第几个字节</param>
- /// <returns></returns>
- public async Task<byte> GetByteAsync(ushort start, int index)
- {
- try
- {
- var shortData = await _modbusMaster.ReadHoldingRegistersAsync(SlaveId, start, 1);
- var bytes1 = BitConverter.GetBytes(shortData[0]).Reverse().ToArray();
- return bytes1[index];
- }
- catch (IOException)
- {
- IsConnect = false;
- OnConnectChanged(false);
- throw;
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- throw;
- }
- }
- /// <summary>
- /// 读字节
- /// </summary>
- /// <param name="start">开始字索引</param>
- /// <param name="index">第几个字节</param>
- /// <returns></returns>
- public byte GetByte(ushort start, int index)
- {
- try
- {
- var shortData = _modbusMaster.ReadHoldingRegisters(SlaveId, start, 1);
- var bytes1 = BitConverter.GetBytes(shortData[0]).Reverse().ToArray();
- return bytes1[index];
- }
- catch (IOException)
- {
- OnConnectChanged(false);
- IsConnect = false;
- throw;
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- throw;
- }
- }
-
- /// <summary>
- /// 反转多字节的数据信息
- /// </summary>
- /// <param name="value">数据字节</param>
- /// <param name="index">起始索引,默认值为0</param>
- /// <returns>实际字节信息</returns>
- protected byte[] ByteTransDataFormat2(byte[] value, int index = 0)
- {
- var buffer = new byte[2];
- switch (DataFormat)
- {
- case DataFormat.ABCD:
- {
- buffer[0] = value[index + 1];
- buffer[1] = value[index + 0];
- break;
- }
- case DataFormat.BADC:
- {
- buffer[0] = value[index + 0];
- buffer[1] = value[index + 1];
- break;
- }
- case DataFormat.CDAB:
- {
- buffer[0] = value[index + 1];
- buffer[1] = value[index + 0];
- break;
- }
- case DataFormat.DCBA:
- {
- buffer[0] = value[index + 0];
- buffer[1] = value[index + 1];
- break;
- }
- }
- return buffer;
- }
- /// <summary>
- /// 反转多字节的数据信息
- /// </summary>
- /// <param name="value">数据字节</param>
- /// <param name="index">起始索引,默认值为0</param>
- /// <returns>实际字节信息</returns>
- protected byte[] ByteTransDataFormat8(byte[] value, int index = 0)
- {
- byte[] buffer = new byte[8];
- switch (DataFormat)
- {
- case DataFormat.ABCD:
- {
- buffer[0] = value[index + 7];
- buffer[1] = value[index + 6];
- buffer[2] = value[index + 5];
- buffer[3] = value[index + 4];
- buffer[4] = value[index + 3];
- buffer[5] = value[index + 2];
- buffer[6] = value[index + 1];
- buffer[7] = value[index + 0];
- break;
- }
- case DataFormat.BADC:
- {
- buffer[0] = value[index + 6];
- buffer[1] = value[index + 7];
- buffer[2] = value[index + 4];
- buffer[3] = value[index + 5];
- buffer[4] = value[index + 2];
- buffer[5] = value[index + 3];
- buffer[6] = value[index + 0];
- buffer[7] = value[index + 1];
- break;
- }
- case DataFormat.CDAB:
- {
- buffer[0] = value[index + 1];
- buffer[1] = value[index + 0];
- buffer[2] = value[index + 3];
- buffer[3] = value[index + 2];
- buffer[4] = value[index + 5];
- buffer[5] = value[index + 4];
- buffer[6] = value[index + 7];
- buffer[7] = value[index + 6];
- break;
- }
- case DataFormat.DCBA:
- {
- buffer[0] = value[index + 0];
- buffer[1] = value[index + 1];
- buffer[2] = value[index + 2];
- buffer[3] = value[index + 3];
- buffer[4] = value[index + 4];
- buffer[5] = value[index + 5];
- buffer[6] = value[index + 6];
- buffer[7] = value[index + 7];
- break;
- }
- }
- return buffer;
- }
- /// <summary>
- /// 反转多字节的数据信息
- /// </summary>
- /// <param name="value">数据字节</param>
- /// <param name="index">起始索引,默认值为0</param>
- /// <returns>实际字节信息</returns>
- protected byte[] ByteTransDataFormat4(byte[] value, int index = 0)
- {
- var buffer = new byte[4];
- switch (DataFormat)
- {
- case DataFormat.ABCD:
- {
- buffer[0] = value[index + 3];
- buffer[1] = value[index + 2];
- buffer[2] = value[index + 1];
- buffer[3] = value[index + 0];
- break;
- }
- case DataFormat.BADC:
- {
- buffer[0] = value[index + 2];
- buffer[1] = value[index + 3];
- buffer[2] = value[index + 0];
- buffer[3] = value[index + 1];
- break;
- }
- case DataFormat.CDAB:
- {
- buffer[0] = value[index + 1];
- buffer[1] = value[index + 0];
- buffer[2] = value[index + 3];
- buffer[3] = value[index + 2];
- break;
- }
- case DataFormat.DCBA:
- {
- buffer[0] = value[index + 0];
- buffer[1] = value[index + 1];
- buffer[2] = value[index + 2];
- buffer[3] = value[index + 3];
- break;
- }
- }
- return buffer;
- }
- public virtual byte[] TransByte(int[] values)
- {
- if (values == null) return null;
- byte[] buffer = new byte[values.Length * 4];
- for (int i = 0; i < values.Length; i++)
- {
- ByteTransDataFormat4(BitConverter.GetBytes(values[i])).CopyTo(buffer, 4 * i);
- }
- return buffer;
- }
- public virtual byte[] TransByte(ushort[] values)
- {
- if (values == null) return null;
- byte[] buffer = new byte[values.Length * 2];
- for (int i = 0; i < values.Length; i++)
- {
- BitConverter.GetBytes(values[i]).CopyTo(buffer, 2 * i);
- }
- return buffer;
- }
- public void WriteDouble(ushort start, double value)
- {
- var convertBytes = BitConverter.GetBytes(value);
- var writeValues = new ushort[2];
- for (var i = 0; i < 4; i++)
- {
- var bytes = new[] { convertBytes[i * 2], convertBytes[i * 2 + 1] };
- writeValues[i] = BitConverter.ToUInt16(bytes, 0);
- }
- _modbusMaster.WriteMultipleRegisters(SlaveId, start, writeValues);
- }
- public async Task WriteDoubleAsync(ushort start, double value)
- {
- var convertBytes = BitConverter.GetBytes(value);
- var writeValues = new ushort[4];
- for (var i = 0; i < 4; i++)
- {
- var bytes = new[] { convertBytes[i * 2], convertBytes[i * 2 + 1] };
- writeValues[i] = BitConverter.ToUInt16(bytes, 0);
- }
- await _modbusMaster.WriteMultipleRegistersAsync(SlaveId, start, writeValues);
- }
- public void WriteFloat(ushort start, float value)
- {
- var convertBytes = BitConverter.GetBytes(value);
- var writeValues = new ushort[2];
- for (int i = 0; i < 4; i++)
- {
- var bytes = new[] { convertBytes[i * 2], convertBytes[i * 2 + 1] };
- writeValues[i] = BitConverter.ToUInt16(bytes, 0);
- }
- _modbusMaster.WriteMultipleRegisters(SlaveId, start, writeValues);
- }
- public async Task WriteFloatAsync(ushort start, float value)
- {
- var convertBytes = BitConverter.GetBytes(value);
- var writeValues = new ushort[2];
- for (var i = 0; i < 2; i++)
- {
- var bytes = new[] { convertBytes[i * 2], convertBytes[i * 2 + 1] };
- writeValues[i] = BitConverter.ToUInt16(bytes, 0);
- }
- await _modbusMaster.WriteMultipleRegistersAsync(SlaveId, start, writeValues);
- }
- public void WriteInt32(ushort start, int value)
- {
- var convertBytes = BitConverter.GetBytes(value);
- var writeValues = new ushort[2];
- for (int i = 0; i < 2; i++)
- {
- var bytes = new[] { convertBytes[i * 2], convertBytes[i * 2 + 1] };
- writeValues[i] = BitConverter.ToUInt16(bytes, 0);
- }
- _modbusMaster.WriteMultipleRegisters(SlaveId, start, writeValues);
- }
- public async Task WriteInt32Async(ushort start, int value)
- {
- var convertBytes = BitConverter.GetBytes(value);
- var writeValues = new ushort[2];
- for (var i = 0; i < 2; i++)
- {
- var bytes = new[] { convertBytes[i * 2], convertBytes[i * 2 + 1] };
- writeValues[i] = BitConverter.ToUInt16(bytes, 0);
- }
- await _modbusMaster.WriteMultipleRegistersAsync(SlaveId, start, writeValues);
- }
- public ushort[] GetBytes(ushort start, ushort length)
- {
-
- var nums= _modbusMaster.ReadInputRegisters(1,start, length);
- return nums;
- }
- public Task<ushort[]> GetBytesAsync(ushort start, ushort length)
- {
- return _modbusMaster.ReadInputRegistersAsync(SlaveId, start, length);
- }
- private void OnConnectChanged(bool state)
- {
- ConnectChanged?.Invoke(this, state);
- }
- }
- }
|