/// <summary> /// Parses a telegram received by ReceiveTelegram. /// </summary> /// <param name="buffer">Buffer containing the data.</param> /// <param name="telegramLength">Total length of the telegram in bytes.</param> /// <param name="isResponse">true if the telegram is a response telegram; false if the telegram is a request telegram.</param> /// <param name="telegramContext"> /// If isResponse == true: pass the telegramContext returned by CreateTelegram from the request. /// If isResponse == false: returns the telegramContext from the received request. It must pe passed to the CreateTelegram method for the response. /// </param> /// <param name="address">Returns the device address.</param> /// <param name="fkt">Returns the function code.</param> /// <param name="dataPos">Returns the offset in buffer of the function code specific data.</param> /// <param name="dataLength">Returns the length of the function code specific data.</param> /// <returns>Returns true if this is the matching response according to the telegramContext; else false. If isResponse == false this method should return always true.</returns> public bool ParseTelegram(byte[] buffer, short telegramLength, bool isResponse, ref object telegramContext, out byte address, out byte fkt, out short dataPos, out short dataLength) { if (telegramLength < 8) { throw new ModbusException(ModbusErrorCode.ResponseTooShort); } if (isResponse) { if (telegramContext is ushort) { ushort transactionId = ModbusUtils.ExtractUShort(buffer, 0); if (transactionId != (ushort)telegramContext) { // telegram does not match address = 0; fkt = 0; dataPos = 0; dataLength = 0; return(false); } } } else { telegramContext = ModbusUtils.ExtractUShort(buffer, 0); } address = buffer[6]; fkt = buffer[7]; dataPos = 8; dataLength = (short)(telegramLength - 8); return(true); }
protected override void Finished(string sid, string portName, string receivedData) { if (ModbusUtils.CRC("01", "02", "01 00").Equals(receivedData)) { this.handleResult(sid, portName, 0); return; } if (ModbusUtils.CRC("01", "02", "01 01").Equals(receivedData)) { this.handleResult(sid, portName, 1); return; } if (ModbusUtils.CRC("01", "02", "01 02").Equals(receivedData)) { this.handleResult(sid, portName, 2); return; } if (ModbusUtils.CRC("01", "02", "01 03").Equals(receivedData)) { this.handleResult(sid, portName, 3); return; } // TODO 记录异常日志 this.handleResult(sid, portName, -1); }
/// <summary> /// Создать узел группы элементов /// </summary> private TreeNode NewElemGroupNode(ElemGroup elemGroup) { string name = elemGroup.Name == "" ? KpPhrases.DefGrName : elemGroup.Name; TreeNode grNode = new TreeNode(name + " (" + ModbusUtils.GetTableTypeName(elemGroup.TableType) + ")"); grNode.ImageKey = grNode.SelectedImageKey = elemGroup.Active ? "group.png" : "group_inactive.png"; grNode.Tag = elemGroup; ushort elemAddr = elemGroup.Address; int elemSig = elemGroup.StartKPTagInd + 1; foreach (Elem elem in elemGroup.Elems) { ElemInfo elemInfo = new ElemInfo() { Elem = elem, ElemGroup = elemGroup, Settings = template.Sett, Address = elemAddr, Signal = elemSig++ }; grNode.Nodes.Add(NewElemNode(elemInfo)); elemAddr += (ushort)elem.Quantity; } return(grNode); }
/// <summary> /// Получить обозначение команды в дереве /// </summary> private string GetCmdCaption(ModbusCmd modbusCmd) { return((string.IsNullOrEmpty(modbusCmd.Name) ? KpPhrases.DefCmdName : modbusCmd.Name) + " (" + ModbusUtils.GetTableTypeName(modbusCmd.TableType) + ", " + ModbusUtils.GetAddressRange(modbusCmd.Address, modbusCmd.ElemCnt, template.Sett.ZeroAddr, template.Sett.DecAddr) + ")"); }
/// <summary> /// Waits and receives a telegram. /// </summary> /// <param name="buffer">Buffer to write data into.</param> /// <param name="desiredDataLength">Desired length of the function code specific data in bytes. -1 if length is unknown.</param> /// <param name="timeout">Timeout in milliseconds to wait for the telegram.</param> /// <param name="telegramLength">Returns the total length of the telegram in bytes.</param> /// <returns>Returns true if the telegram was received successfully; false on timeout.</returns> public bool ReceiveTelegram(byte[] buffer, short desiredDataLength, int timeout, out short telegramLength) { if (_Socket == null) { throw new ObjectDisposedException("ModbusTcp interface"); } if (!_Socket.Poll(timeout * 1000, SelectMode.SelectRead) || _Socket.Available == 0) { telegramLength = 0; return(false); } // get the 1st 6 bytes, which includes the remaining length int pos = 0; while (pos < 6) { pos += _Socket.Receive(buffer, pos, 6 - pos, SocketFlags.None); } telegramLength = (short)(ModbusUtils.ExtractUShort(buffer, 4) + 6); // receive remaining data while (pos < telegramLength) { pos += _Socket.Receive(buffer, pos, telegramLength - pos, SocketFlags.None); } return(true); }
/// <summary> /// Gets the appropriate default byte order as array. /// </summary> public int[] GetDefaultByteOrder(int byteCnt) { switch (byteCnt) { case 2: if (byteOrderArr2 == null) { byteOrderArr2 = ModbusUtils.ParseByteOrder(DefByteOrder2); } return(byteOrderArr2); case 4: if (byteOrderArr4 == null) { byteOrderArr4 = ModbusUtils.ParseByteOrder(DefByteOrder4); } return(byteOrderArr4); case 8: if (byteOrderArr8 == null) { byteOrderArr8 = ModbusUtils.ParseByteOrder(DefByteOrder8); } return(byteOrderArr8); default: return(null); } }
/// <summary> /// Обновить узел выбранной группы элементов /// </summary> private void UpdateElemGroupNode() { if (selElemGroup != null) { selNode.ImageKey = selNode.SelectedImageKey = selElemGroup.Active ? "group.png" : "group_inactive.png"; selNode.Text = (selElemGroup.Name == "" ? KpPhrases.DefGrName : selElemGroup.Name) + " (" + ModbusUtils.GetTableTypeName(selElemGroup.TableType) + ")"; } }
public void SwapsEndiannessGeneric <T>(T[] dataset, T[] expected) where T : unmanaged { // Act ModbusUtils.SwitchEndianness(dataset.AsSpan()); var actual = dataset; // Assert Assert.Equal(actual, expected); }
public void CanParseEndpoint(string endpoint) { // Arrange var expected = IPEndPoint.Parse(endpoint); // Act var success = ModbusUtils.TryParseEndpoint(endpoint, out var actual); // Assert Assert.True(success); Assert.Equal(expected, actual); }
/// <summary> /// Shows the function code of the element group. /// </summary> private void ShowFuncCode(ElemGroupConfig elemGroup) { if (elemGroup == null) { txtGrFuncCode.Text = ""; } else { byte funcCode = ModbusUtils.GetReadFuncCode(elemGroup.DataBlock); txtGrFuncCode.Text = string.Format("{0} ({1}h)", funcCode, funcCode.ToString("X2")); } }
public void CalculatesCrcCorrectly() { // Arrange var data = new byte[] { 0xA0, 0xB1, 0xC2 }; // Act var expected = 0x2384; var actual = ModbusUtils.CalculateCRC(data); // Assert Assert.Equal(actual, expected); }
public void SwapsEndiannessUShort() { // Arrange var data = (ushort)512; // Act var expected = (ushort)2; var actual = ModbusUtils.SwitchEndianness(data); // Assert Assert.Equal(actual, expected); }
public void SwapsEndiannessMidLittleEndian() { // Arrange var data = (uint)0x01020304; // Act var expected = (uint)0x02010403; var actual1 = ModbusUtils.ConvertBetweenLittleEndianAndMidLittleEndian(data); var actual2 = ModbusUtils.ConvertBetweenLittleEndianAndMidLittleEndian(actual1); // Assert Assert.Equal(expected, actual1); Assert.Equal(data, actual2); }
/// <summary> /// Sends the given telegram. /// If necessary additional information like a checksum can be inserted here. /// </summary> /// <param name="buffer">Buffer containing the data.</param> /// <param name="telegramLength">Length of the telegram in bytes.</param> public void SendTelegram(byte[] buffer, short telegramLength) { var crc = ModbusUtils.CalcCrc(buffer, telegramLength - 2); buffer[telegramLength - 2] = (byte)(crc & 0x00ff); buffer[telegramLength - 1] = (byte)((crc & 0xff00) >> 8); // ticks and _NextSend are multiples of 100 ns var dt = _nextSend - DateTime.Now.Ticks; if (dt > 0) { Thread.Sleep(Math.Max(1, (int)dt / 10000)); } // clear buffers _serial.DiscardInBuffer(); _serial.DiscardOutBuffer(); // next send is 3.5 chars after the end of this telegram _nextSend = DateTime.Now.Ticks + (telegramLength * 2 + 7) * _halfCharLength; #if NETMF try { _serial.Write(buffer, 0, telegramLength); // make sure all bytes are sent out _serial.Flush(); } catch (ArgumentException) { // happens some times -> Close -> Open -> try again try { _serial.Close(); } catch { // ignored } _serial.Open(); _serial.Write(buffer, 0, telegramLength); _serial.Flush(); } #else _serial.Write(buffer, 0, telegramLength); #endif }
/// <summary> /// Gets the command node text. /// </summary> private string GetCmdNodeText(CmdConfig cmd) { string cmdName = string.IsNullOrEmpty(cmd.Name) ? ModbusDriverPhrases.UnnamedCommand : cmd.Name; string blockName = ModbusUtils.GetDataBlockName(cmd.DataBlock); if (cmd.DataBlock == DataBlock.Custom) { return(string.Format("{0} ({1})", cmdName, blockName)); } else { string addrRange = ModbusUtils.GetAddressRange(cmd.Address, cmd.ElemCnt * ModbusUtils.GetQuantity(cmd.ElemType), template.Options.ZeroAddr, template.Options.DecAddr); return(string.Format("{0} ({1}, {2})", cmdName, blockName, addrRange)); } }
/// <summary> /// Creates a new Modbus command based on the element configuration. /// </summary> private ModbusCmd CreateModbusCmd(DeviceTemplateOptions options, ElemGroupConfig elemGroupConfig, ElemConfig elemConfig, int elemIndex) { ModbusCmd modbusCmd = deviceModel.CreateModbusCmd(elemGroupConfig.DataBlock, false); modbusCmd.Name = elemConfig.Name; modbusCmd.Address = (ushort)(elemGroupConfig.Address + elemIndex); modbusCmd.ElemType = elemConfig.ElemType; modbusCmd.ElemCnt = 1; modbusCmd.ByteOrder = ModbusUtils.ParseByteOrder(elemConfig.ByteOrder) ?? options.GetDefaultByteOrder(ModbusUtils.GetDataLength(elemConfig.ElemType)); modbusCmd.CmdNum = 0; modbusCmd.CmdCode = elemConfig.TagCode; modbusCmd.InitReqPDU(); modbusCmd.InitReqADU(deviceModel.Addr, transMode); return(modbusCmd); }
/// <summary> /// Shows the function code of the command. /// </summary> private void ShowFuncCode(CmdConfig cmd) { if (cmd == null) { numCmdFuncCode.Value = 0; numCmdFuncCode.ReadOnly = true; } else if (cmd.DataBlock == DataBlock.Custom) { numCmdFuncCode.Value = (byte)cmd.CustomFuncCode; numCmdFuncCode.ReadOnly = false; } else { numCmdFuncCode.Value = ModbusUtils.GetWriteFuncCode(cmd.DataBlock, cmd.Multiple); numCmdFuncCode.ReadOnly = true; } }
/// <summary> /// Parses a telegram received by ReceiveTelegram. /// </summary> /// <param name="buffer">Buffer containing the data.</param> /// <param name="telegramLength">Total length of the telegram in bytes.</param> /// <param name="isResponse">true if the telegram is a response telegram; false if the telegram is a request telegram.</param> /// <param name="telegramContext"> /// If isResponse == true: pass the telegramContext returned by CreateTelegram from the request. /// If isResponse == false: returns the telegramContext from the received request. It must pe passed to the CreateTelegram method for the response. /// </param> /// <param name="address">Returns the device address.</param> /// <param name="fkt">Returns the function code.</param> /// <param name="dataPos">Returns the offset in buffer of the function code specific data.</param> /// <param name="dataLength">Returns the length of the function code specific data.</param> /// <returns>Returns true if this is the matching response according to the telegramContext; else false. If isResponse == false this method should return always true.</returns> public bool ParseTelegram(byte[] buffer, short telegramLength, bool isResponse, ref object telegramContext, out byte address, out byte fkt, out short dataPos, out short dataLength) { if (telegramLength < 4) { throw new ModbusException(ModbusErrorCode.ResponseTooShort); } var crc = ModbusUtils.CalcCrc(buffer, telegramLength - 2); if (buffer[telegramLength - 2] != (byte)(crc & 0x00ff) || buffer[telegramLength - 1] != (byte)((crc & 0xff00) >> 8)) { throw new ModbusException(ModbusErrorCode.CrcError); } address = buffer[0]; fkt = buffer[1]; dataPos = 2; dataLength = (short)(telegramLength - 4); return(true); }
private void cbCmdElemType_SelectedIndexChanged(object sender, EventArgs e) { // изменение типа элементов if (modbusCmd != null) { ElemType newElemType = (ElemType)cbCmdElemType.SelectedIndex; if (modbusCmd.TableType == TableType.HoldingRegisters && newElemType == ElemType.Bool) { // отмена выбора типа Bool для регистров хранения cbCmdElemType.SelectedIndexChanged -= cbCmdElemType_SelectedIndexChanged; cbCmdElemType.SelectedIndex = (int)modbusCmd.ElemType; cbCmdElemType.SelectedIndexChanged += cbCmdElemType_SelectedIndexChanged; } else { modbusCmd.ElemType = newElemType; numCmdElemCnt.Value = ModbusUtils.GetElemCount(modbusCmd.ElemType); OnObjectChanged(TreeUpdateTypes.None); } } }
/// <summary> /// Creates a new Modbus command based on the command configuration. /// </summary> private ModbusCmd CreateModbusCmd(DeviceTemplateOptions options, CmdConfig cmdConfig) { ModbusCmd modbusCmd = deviceModel.CreateModbusCmd(cmdConfig.DataBlock, cmdConfig.Multiple); modbusCmd.Name = cmdConfig.Name; modbusCmd.Address = (ushort)cmdConfig.Address; modbusCmd.ElemType = cmdConfig.ElemType; modbusCmd.ElemCnt = cmdConfig.ElemCnt; modbusCmd.ByteOrder = ModbusUtils.ParseByteOrder(cmdConfig.ByteOrder) ?? options.GetDefaultByteOrder(ModbusUtils.GetDataLength(cmdConfig.ElemType) * cmdConfig.ElemCnt); modbusCmd.CmdNum = cmdConfig.CmdNum; modbusCmd.CmdCode = cmdConfig.CmdCode; if (cmdConfig.DataBlock == DataBlock.Custom) { modbusCmd.SetFuncCode((byte)cmdConfig.CustomFuncCode); } modbusCmd.InitReqPDU(); modbusCmd.InitReqADU(deviceModel.Addr, transMode); return(modbusCmd); }
/// <summary> /// Creates a new telegram for a modbus request or response. /// All data except the function code specific user data is written into the given buffer. /// </summary> /// <param name="addr">Device address. 0 = Breadcast, 1..247 are valid device addresses.</param> /// <param name="fkt">Function code. <see cref="ModbusFunctionCode"/></param> /// <param name="dataLength">Number of bytes for function code sspecific user data.</param> /// <param name="buffer">Buffer to write data into. The buffer must be at least MaxTelegramLength - MaxDataLength + dataLength bytes long.</param> /// <param name="telegramLength">Returns the total length of the telegram in bytes.</param> /// <param name="dataPos">Returns the offset of the function code specific user data in buffer.</param> /// <param name="isResponse">true if this is a response telegram; false if this is a request telegram.</param> /// <param name="telegramContext"> /// If isResponse == false, this parameter returns the interface implementation specific data which must be passed to the ParseTelegram method of the received response. /// If isResponse == true, this parameter must be called with the telegramContext parameter returned by ParseTelegram of the request telegram.</param> public void CreateTelegram(byte addr, byte fkt, short dataLength, byte[] buffer, out short telegramLength, out short dataPos, bool isResponse, ref object telegramContext) { telegramLength = (short)(8 + dataLength); if (isResponse) { // in a response we insert the given telegramContext as transaction id if (telegramContext is ushort) { ModbusUtils.InsertUShort(buffer, 0, (ushort)telegramContext); } else { ModbusUtils.InsertUShort(buffer, 0, 0); } } else { // insert new transaction id into request telegramContext = _NextTransactionId; if (_NextTransactionId == 0xffff) { ModbusUtils.InsertUShort(buffer, 0, 0xffff); _NextTransactionId = 0; } else { ModbusUtils.InsertUShort(buffer, 0, _NextTransactionId++); } } ModbusUtils.InsertUShort(buffer, 2, 0); ModbusUtils.InsertUShort(buffer, 4, (ushort)(telegramLength - 6)); buffer[6] = addr; buffer[7] = fkt; dataPos = 8; }
/// <summary> /// Sends the given telegram. /// If necessary additional information like a checksum can be inserted here. /// </summary> /// <param name="buffer">Buffer containing the data.</param> /// <param name="telegramLength">Length of the telegram in bytes.</param> public void SendTelegram(byte[] buffer, short telegramLength) { var crc = ModbusUtils.CalcCrc(buffer, telegramLength - 2); buffer[telegramLength - 2] = (byte)(crc & 0x00ff); buffer[telegramLength - 1] = (byte)((crc & 0xff00) >> 8); // ticks and _NextSend are multiples of 100 ns var dt = this.nextSend - DateTime.Now.Ticks; if (dt > 0) { Thread.Sleep(Math.Max(1, (int)dt / 10000)); } // clear buffers this.serial.ClearReadBuffer(); this.serial.ClearWriteBuffer(); // next send is 3.5 chars after the end of this telegram this.nextSend = DateTime.Now.Ticks + (telegramLength * 2 + 7) * this.halfCharLength; this.serial.Write(buffer, 0, telegramLength); }
/// <summary> /// 入队一条指令(MODBUS协议) /// </summary> /// <param name="allowDiscard">是否允许丢弃该指令</param> /// <param name="cmdEventHandler">串口指令事件处理器</param> /// <param name="sendCMDTimeout">发送指令超时时长(毫秒)</param> /// <param name="reciveDataTimeout">接收数据超时时长(毫秒)</param> /// <param name="address">指令-地址码</param> /// <param name="func">指令-功能码</param> /// <param name="data">指令-数据</param> public void EnqueueModbus(Boolean allowDiscard, SerialPortCMDEventHandler cmdEventHandler, int sendCMDTimeout, int reciveDataTimeout, String address, String func, String data) { EnqueueHexString(allowDiscard, cmdEventHandler, sendCMDTimeout, reciveDataTimeout, ModbusUtils.CRC(address, func, data)); }
/// <summary> /// Gets the command node text. /// </summary> private static string GetElemGroupNodeText(ElemGroupConfig elemGroup) { return(string.Format("{0} ({1})", string.IsNullOrEmpty(elemGroup.Name) ? ModbusDriverPhrases.UnnamedElemGroup : elemGroup.Name, ModbusUtils.GetDataBlockName(elemGroup.DataBlock))); }
/// <summary> /// Gets the channel prototypes for the device. /// </summary> public override ICollection <CnlPrototype> GetCnlPrototypes() { if (LoadDeviceTemplate() is not DeviceTemplate deviceTemplate) { return(null); } List <CnlPrototype> cnlPrototypes = new(); int tagNum = 1; foreach (ElemGroupConfig elemGroupConfig in deviceTemplate.ElemGroups) { foreach (ElemConfig elemConfig in elemGroupConfig.Elems) { // create channel for element bool isBool = elemConfig.ElemType == ElemType.Bool; int eventMask = new EventMask { Enabled = true, DataChange = isBool, StatusChange = !isBool, Command = !elemConfig.ReadOnly }.Value; cnlPrototypes.Add(new CnlPrototype { Active = elemGroupConfig.Active, Name = elemConfig.Name, CnlTypeID = elemConfig.ReadOnly ? CnlTypeID.Input : CnlTypeID.InputOutput, TagNum = string.IsNullOrEmpty(elemConfig.TagCode) ? tagNum : null, TagCode = elemConfig.TagCode, FormatCode = isBool ? FormatCode.OffOn : elemConfig.IsBitMask ? FormatCode.X : null, EventMask = eventMask }); // create channels for bit mask if (elemConfig.IsBitMask && elemConfig.ElemType != ElemType.Bool) { eventMask = new EventMask { Enabled = true, DataChange = true, Command = !elemConfig.ReadOnly }.Value; for (int bit = 0, bitCnt = ModbusUtils.GetDataLength(elemConfig.ElemType) * 8; bit < bitCnt; bit++) { cnlPrototypes.Add(new CnlPrototype { Active = elemGroupConfig.Active, Name = elemConfig.Name + "[" + bit + "]", CnlTypeID = elemConfig.ReadOnly ? CnlTypeID.Calculated : CnlTypeID.CalculatedOutput, FormatCode = FormatCode.OffOn, FormulaEnabled = true, InFormula = $"GetBit(DataRel({-bit - 1}), {bit})", OutFormula = elemConfig.ReadOnly ? null : $"SetBit(DataRel({-bit - 1}), {bit}, Cmd)", EventMask = eventMask }); } } tagNum++; } } // create channels for commands int cmdEventMask = new EventMask { Enabled = true, Command = true }.Value; foreach (CmdConfig cmdConfig in deviceTemplate.Cmds) { cnlPrototypes.Add(new CnlPrototype { Name = cmdConfig.Name, CnlTypeID = CnlTypeID.Output, TagNum = string.IsNullOrEmpty(cmdConfig.CmdCode) ? cmdConfig.CmdNum : null, TagCode = cmdConfig.CmdCode, FormatCode = cmdConfig.DataBlock == DataBlock.Coils && !cmdConfig.Multiple ? FormatCode.OffOn : null, EventMask = cmdEventMask }); } return(cnlPrototypes); }
private int byteLength = 0; // 部分响应信息字节长度 /// <summary> /// 开始打开串口 /// </summary> /// <param name="SerialDataReceivedEventHandlerX">串口数据接收事件处理器扩展</param> /// <param name="BeforeOpen">尝试打开串口前操作</param> /// <param name="OpenSuccess">尝试打开串口成功操作</param> /// <param name="OpenError">尝试打开串口失败操作</param> /// <param name="StillOpen">仍处于打开状态操作</param> /// <param name="Disconnect">已经断开操作</param> /// <param name="BeforeReOpen">尝试重新打开串口前操作</param> /// <param name="ReOpenSuccess">尝试重新打开串口成功操作</param> /// <param name="ReOpenError">尝试重新打开串口失败操作</param> /// <param name="AfterOpen">串口打开后操作</param> internal void Start( SerialDataReceivedEventHandlerX SerialDataReceivedEventHandlerX, BeforeOpen BeforeOpen, OpenSuccess OpenSuccess, OpenError OpenError, StillOpen StillOpen, Disconnect Disconnect, BeforeReOpen BeforeReOpen, ReOpenSuccess ReOpenSuccess, ReOpenError ReOpenError, AfterOpen AfterOpen ) { // 设置串口数据接收事件处理器 serialPort.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler((sender, e) => { if (IsClosed) { return; } byte[] readedBytes = new byte[serialPort.BytesToRead]; int byteLength = readedBytes.Length; if (byteLength == 0) { CMDQueue.CancelCurrentCMD(); return; } serialPort.Read(readedBytes, 0, byteLength); String hexString = HexUtils.BytesToHexString(readedBytes, true).Trim(); if ("".Equals(hexString)) { CMDQueue.CancelCurrentCMD(); return; } if (ModbusUtils.IsFullCmd(hexString)) { if (useCMDQueue) { CMDQueue.FinishCurrentCMD(hexString); } SerialDataReceivedEventHandlerX?.Invoke(this.sid, this.portName, hexString); this.hexString = ""; this.byteLength = 0; } else { this.hexString += " " + hexString; this.hexString = this.hexString.Trim(); this.byteLength += byteLength; if (ModbusUtils.IsFullCmd(this.hexString)) { if (useCMDQueue) { CMDQueue.FinishCurrentCMD(hexString); } SerialDataReceivedEventHandlerX?.Invoke(this.sid, this.portName, this.hexString); this.hexString = ""; this.byteLength = 0; } } }); // 每100毫秒执行一次指令队列 if (useCMDQueue) { CMDQueue = new SerialPortCMDQueue(this.sid, this.portName, serialPort); CMDQueue.Start(); } BeforeOpen?.Invoke(this.sid, this.portName); // 尝试打开串口前操作 if (this.Open()) // 尝试打开串口 { OpenSuccess?.Invoke(this.sid, this.portName); // 尝试打开串口成功操作 OpenStatus = true; if (AfterOpen != null) { AfterOpenEventHandlerListenerTimer = new Timer((obj) => { AfterOpen(this.sid, this.portName);// 串口打开后操作 }, null, 0, this.afterOpenEventHandlerPeriod); } } else { OpenError?.Invoke(this.sid, this.portName);// 尝试打开串口失败操作 OpenStatus = null; if (AfterOpen != null) { AfterOpenEventHandlerListenerTimer = new Timer((obj) => { if (IsClosed) { return; } AfterOpen(this.sid, this.portName);// 串口打开后操作 }, null, Timeout.Infinite, Timeout.Infinite); } } // 无论打开成功与否,后续都需要监听该串口是否处于打开状态。 OpenStatusListenerTimer = new Timer((o) => { if (IsClosed) { return; } switch (OpenStatus) // 上一次的串口打开状态 { case null: // 首次打开串口失败 case false: // 后续打开串口失败 { BeforeReOpen?.Invoke(this.sid, this.portName); // 尝试重新打开串口前操作 if (this.Open()) // 尝试重新打开串口 { ReOpenSuccess?.Invoke(this.sid, this.portName); // 尝试重新打开串口成功操作 OpenStatus = true; if (AfterOpen != null) { AfterOpenEventHandlerListenerTimer.Change(0, this.afterOpenEventHandlerPeriod); // 重新开启计时器 } } else { ReOpenError?.Invoke(this.sid, this.portName); // 尝试重新打开串口失败 OpenStatus = false; } } break; case true: // 首次或后续打开串口成功 { if (serialPort.IsOpen) // 仍处于打开状态 { StillOpen?.Invoke(this.sid, this.portName); // 仍处于打开状态操作 OpenStatus = true; } else // 已经断开 { Disconnect?.Invoke(this.sid, this.portName); // 已经断开操作 OpenStatus = false; if (AfterOpen != null) { AfterOpenEventHandlerListenerTimer.Change(Timeout.Infinite, Timeout.Infinite); // 暂停计时器 } } } break; } }, null, this.checkOpenStatusPeriod, this.checkOpenStatusPeriod); }
/// <summary> /// Initializes the device tags. /// </summary> public override void InitDeviceTags() { DeviceTemplate deviceTemplate = GetDeviceTemplate(); if (deviceTemplate == null) { return; } // create device model deviceModel = CreateDeviceModel(); deviceModel.Addr = (byte)NumAddress; // add model elements and device tags foreach (ElemGroupConfig elemGroupConfig in deviceTemplate.ElemGroups) { bool groupActive = elemGroupConfig.Active; bool groupCommands = groupActive && elemGroupConfig.ReadOnlyEnabled; ElemGroup elemGroup = null; TagGroup tagGroup = new TagGroup(elemGroupConfig.Name) { Hidden = !groupActive }; int elemIndex = 0; if (groupActive) { elemGroup = deviceModel.CreateElemGroup(elemGroupConfig.DataBlock); elemGroup.Name = elemGroupConfig.Name; elemGroup.Address = (ushort)elemGroupConfig.Address; elemGroup.StartTagIdx = DeviceTags.Count; } foreach (ElemConfig elemConfig in elemGroupConfig.Elems) { // add model element if (groupActive) { Elem elem = elemGroup.CreateElem(); elem.Name = elemConfig.Name; elem.ElemType = elemConfig.ElemType; elem.ByteOrder = ModbusUtils.ParseByteOrder(elemConfig.ByteOrder) ?? deviceTemplate.Options.GetDefaultByteOrder(ModbusUtils.GetDataLength(elemConfig.ElemType)); elemGroup.Elems.Add(elem); } // add model command if (groupCommands && !elemConfig.ReadOnly && !string.IsNullOrEmpty(elemConfig.TagCode)) { deviceModel.Cmds.Add( CreateModbusCmd(deviceTemplate.Options, elemGroupConfig, elemConfig, elemIndex)); } // add device tag tagGroup.AddTag(elemConfig.TagCode, elemConfig.Name).SetFormat(GetTagFormat(elemConfig)); elemIndex++; } if (groupActive) { elemGroup.InitReqPDU(); elemGroup.InitReqADU(deviceModel.Addr, transMode); deviceModel.ElemGroups.Add(elemGroup); } DeviceTags.AddGroup(tagGroup); } // add model commands foreach (CmdConfig cmdConfig in deviceTemplate.Cmds) { deviceModel.Cmds.Add(CreateModbusCmd(deviceTemplate.Options, cmdConfig)); } deviceModel.InitCmdMap(); CanSendCommands = deviceModel.Cmds.Count > 0; InitModbusPoll(); }
/// <summary> /// Waits and receives a telegram. /// </summary> /// <param name="buffer">Buffer to write data into.</param> /// <param name="desiredDataLength">Desired length of the function code specific data in bytes. -1 if length is unknown.</param> /// <param name="timeout">Timeout in milliseconds to wait for the telegram.</param> /// <param name="telegramLength">Returns the total length of the telegram in bytes.</param> /// <returns>Returns true if the telegram was received successfully; false on timeout.</returns> public bool ReceiveTelegram(byte[] buffer, short desiredDataLength, int timeout, out short telegramLength) { short desiredLength; if (desiredDataLength >= 0) { desiredLength = (short)(desiredDataLength + 4); if (desiredLength > buffer.Length) { throw new ArgumentException(string.Concat("buffer size (", buffer.Length, ") must be at least 4 byte larger than desiredDataLength (", desiredDataLength, ")")); } } else { desiredLength = -1; } var n = 0; var tOut = DateTime.Now.AddMilliseconds(timeout); long nextRead = 0; var errorChecked = false; while (true) { //if ((desiredLength > 0 || n == 0) && DateTime.Now > tOut) if (DateTime.Now > tOut) { break; } if (this.serial.BytesToRead > 0) { if (desiredLength > 0) { n += this.serial.Read(buffer, n, desiredLength - n); } else { n += this.serial.Read(buffer, n, buffer.Length - n); } // a delay of more than 1.5 chars means end of telegram /////, but since this is a extreme short time, we extend it by factor 2 nextRead = DateTime.Now.Ticks + 6 * this.halfCharLength; } if (!errorChecked && n >= 2) { errorChecked = true; if ((buffer[1] & 0x80) != 0) { // modbus error, so desired length is 5 desiredLength = 5; } } if (desiredLength > 0 && n == desiredLength) { telegramLength = (short)n; return(true); } if (desiredLength <= 0 && n >= 2 && DateTime.Now.Ticks > nextRead && this.serial.BytesToRead == 0) { var crc = ModbusUtils.CalcCrc(buffer, n - 2); if (buffer[n - 2] != (byte)(crc & 0x00ff) || buffer[n - 1] != (byte)((crc & 0xff00) >> 8)) { // read a little bit longer Thread.Sleep(1); nextRead = DateTime.Now.Ticks + 6 * this.halfCharLength; } else { telegramLength = (short)n; return(true); } } } telegramLength = 0; return(false); }
/// <summary> /// 写指令(MODBUS协议) /// </summary> /// <param name="address">指令-地址码</param> /// <param name="func">指令-功能码</param> /// <param name="data">指令-数据</param> public void WriteModbus(String address, String func, String data) { WriteHexString(ModbusUtils.CRC(address, func, data)); }