/*@!Encoding:1252*/ // This file is the Modbus Client for Airbus CIDS // It automatically and periodically reads all input bits and registers and writes them to SysVars %BUSTYPE%::%NODE_NAME%::Data // It also reacts on changes in the output SysVars and write those to the Modbus device. includes { #include "include\ModbusUdp.cin" // Use UDP as Layer 4 #include "include\ModbusClient.cin" // Use Modbus as Application Layer #include "include\DeviceInformation.cin" // Handle several vendors differently } variables { msTimer gtRead; // The timer that keeps on polling all the time } on preStart { writeClear(0); // Clear write window in CANoe setStartdelay(10); // Wait for Ethernet device to be ready OutputDebugLevel = Warning; // The debug level (messages in write window) } // Connect to Modbus server, read the status of output registers and bits and start the cyclic timer on start { char ip[16]; sysGetVariableString("Modbus::%NODE_NAME%::Config", "IP", ip, elCount(ip)); // Get IP address of device from sysvars config DeviceInit(@sysvar::Modbus::%NODE_NAME%::Info::Vendor); // Set all device specific parameters (Wago / B&R) writeDbg(MbInfo, "Connecting to %s:%d", ip, @sysvar::Config::Modbus::Port); ModbusInit(ip, @sysvar::Config::Modbus::Port, @sysvar::Config::Modbus::RequestTimeout, @sysvar::Config::Modbus::MaxTransmissionCount); // Connect to device. Opens socket and connection or what ever if (gSocketState < CONNECTING) // We are not connecting and not connected return; ModbusReadOutBits(thisDev.Addr.Read.OutputBits, @sysvar::Modbus::%NODE_NAME%::Info::OutputBits); // Read the start status of the output bits ModbusReadOutRegisters(thisDev.Addr.Read.OutputRegisters, @sysvar::Modbus::%NODE_NAME%::Info::OutputRegisters); // Read the start status of the output registers if (@sysvar::Modbus::%NODE_NAME%::Config::Interval > 0) // Start the polling timer setTimerCyclic(gtRead, 1, @sysvar::Modbus::%NODE_NAME%::Config::Interval); } // Stop all transactions and close connection on preStop { ModbusEnd(); } // Modbus events ---------------------------------------------------------------------- /// All these events will be called by functions out of ModbusClientCommon.cin // -- Modbus Failures ----------------------------------------------------------------- /// Several reasons are possible: /// error == Timeout: The packet will be resent /// error == FinalTimeout: The packet will not be resent /// error == Exception: The client responded with an exception (which again can have several reasons, see enum ModbusException) // This method gets called when an error occured while trying to read some bits (ModbusReadBits()) void OnModbusReadBitsFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap) { word i; switch (error) { case Exception: case Timeout: case NotSent: break; case FinalTimeout: sysBeginVariableStructUpdate("Modbus::%NODE_NAME%::Data", "InputBits"); for (i = 0; i < @sysvar::Modbus::%NODE_NAME%::Info::InputBits; i++) @sysvar::Modbus::%NODE_NAME%::Data::InputBits[i] = -1; sysEndVariableStructUpdate("Modbus::%NODE_NAME%::Data", "InputBits"); break; default: writeDbg(MbError, "OnModbusReadBitsFailed: Unkown error: %d", error); OnModbusClientPanics(SwitchArgumentInvalid); return; } } // This method gets called when an error occured while trying to read some registers (ModbusReadRegisters()) void OnModbusReadRegistersFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap) { byte i; switch (error) { case Exception: case Timeout: case NotSent: break; case FinalTimeout: sysBeginVariableStructUpdate("Modbus::%NODE_NAME%::Data", "InputRegisters"); for (i = 0; i < @sysvar::Modbus::%NODE_NAME%::Info::InputRegisters; i++) @sysvar::Modbus::%NODE_NAME%::Data::InputRegisters[i] = -1; sysEndVariableStructUpdate("Modbus::%NODE_NAME%::Data", "InputRegisters"); break; default: writeDbg(MbError, "OnModbusReadBitsFailed: Unkown error: %d", error); OnModbusClientPanics(SwitchArgumentInvalid); return; } } // This method gets called when an error occured while trying to set a bit (ModbusWriteBit()) void OnModbusWriteBitFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){} // This method gets called when an error occured while trying to set a register (ModbusWriteRegister()) void OnModbusWriteRegisterFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){} // This method gets called when an error occured while trying to apply a mask on a register (ModbusWriteMask()) void OnModbusWriteMasksFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){} // This method gets called when an error occured while trying to read and write registers (ModbusReadWriteRegisters()) void OnModbusReadWriteRegistersFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){} // This method gets called when an error occured while trying to set multiple bits (ModbusWriteBits()) void OnModbusWriteBitsFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){} // This method gets called when an error occured while trying to set multiple registers (ModbusWriteRegisters()) void OnModbusWriteRegistersFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){} // -- Modbus Success ------------------------------------------------------------------ /// These functions get called when a device responds that a request was fulfilled successfully /// Normally the Reponse as well the Request will be handed over // This method gets called when some bits were read successfully. See 'bitStatus' for their values and 'mbreq.Address' for their start address void OnModbusReadBitsSuccess(struct ModbusResReceiveBits mbres, byte bitStatus[], struct ModbusReqRead mbreq) { word i, offset; switch (mbres.Header.FuncCode) // We assume that we separate between 0x01 and 0x02 even though the address space may be the same { case ReadBitsOut: // Read output bits sysBeginVariableStructUpdate("Modbus::%NODE_NAME%::Data", "OutputBits"); offset = mbreq.Address - thisDev.Addr.Read.OutputBits; // Get the offset to the base output bit address for (i = 0; i < mbreq.Count; i++) @sysvar::Modbus::%NODE_NAME%::Data::OutputBits[i + offset] = bitStatus[i]; sysEndVariableStructUpdate("Modbus::%NODE_NAME%::Data", "OutputBits"); break; case ReadBitsIn: // Read input bits sysBeginVariableStructUpdate("Modbus::%NODE_NAME%::Data", "InputBits"); offset = mbreq.Address - thisDev.Addr.Read.InputBits; // Get the offset to the base input bit address for (i = 0; i < mbreq.Count; i++) @sysvar::Modbus::%NODE_NAME%::Data::InputBits[i + offset] = bitStatus[i]; sysEndVariableStructUpdate("Modbus::%NODE_NAME%::Data", "InputBits"); break; default: writeDbg(MbError, "OnModbusReadBitsSuccess: Unexpected function code: 0x%02X", mbreq.Header.FuncCode); OnModbusClientPanics(FuncCodeIncorrect); return; } } // This method gets called when some bits were read successfully. See 'mbres.Data' for their values and 'mbreq.Address' for their start address void OnModbusReadRegistersSuccess(struct ModbusResReceiveRegisters mbres, struct ModbusReqRead mbreq) { word i, offset; switch (mbres.Header.FuncCode) // We assume that we separate between 0x03 and 0x04 even though the address space may be the same { case ReadRegistersOut: // Read output registers sysBeginVariableStructUpdate("Modbus::%NODE_NAME%::Data", "OutputRegisters"); offset = mbreq.Address - thisDev.Addr.Read.OutputRegisters; // Get the offset to the base output register address for (i = 0; i < mbreq.Count; i++) @sysvar::Modbus::%NODE_NAME%::Data::OutputRegisters[i + offset] = mbres.Data[i]; sysEndVariableStructUpdate("Modbus::%NODE_NAME%::Data", "OutputRegisters"); break; case ReadRegistersIn: // Read input registers sysBeginVariableStructUpdate("Modbus::%NODE_NAME%::Data", "InputRegisters"); offset = mbreq.Address - thisDev.Addr.Read.InputRegisters; // Get the offset to the base input bit address for (i = 0; i < mbreq.Count; i++) @sysvar::Modbus::%NODE_NAME%::Data::InputRegisters[i + offset] = mbres.Data[i]; sysEndVariableStructUpdate("Modbus::%NODE_NAME%::Data", "InputRegisters"); break; default: writeDbg(MbError, "OnModbusReadBitsSuccess: Unexpected function code: 0x%02X", mbreq.Header.FuncCode); OnModbusClientPanics(FuncCodeIncorrect); return; } } // This method gets called when a bit was set successfully. void OnModbusWriteBitSuccess(struct ModbusResConfirmSingle mbres){} // This method gets called when a register was set successsfully. void OnModbusWriteRegisterSuccess(struct ModbusResConfirmSingle mbres){} // This method gets called when multiple bits were set successfully. void OnModbusWriteBitsSuccess(struct ModbusResConfirmMultiple mbres){} // This method gets called when multiple registers were set successfully. void OnModbusWriteRegistersSuccess(struct ModbusResConfirmMultiple mbres){} // This method gets called when a mask was applied successfully. void OnModbusWriteMasksSuccess(struct ModbusResConfirmMasks mbres){} // This method gets called when the Modbus Client panics (saying a fatal error occured). // It will pass as argument what happened. Please see the log (increase debug level in preStart) for more details. void OnModbusClientPanics(enum FatalErrors reason) { switch(reason) { case ParsingBuffer: writeLineEx(0, 4, "<%NODE_NAME%> Fatal Error while parsing the received buffer"); runError(1001, reason); break; case ModbusPackageWasSplit: writeLineEx(0, 4, "<%NODE_NAME%> Fatal Error while parsing the received buffer: The Modbus package was split", reason); runError(1001, reason); break; case VendorIdUnknown: writeLineEx(0, 4, "<%NODE_NAME%> Fatal Error: Vendor ID unknown"); runError(1001, reason); break; case FuncCodeIncorrect: writeLineEx(0, 4, "<%NODE_NAME%> Fatal Error: Function code is incorrect"); runError(1001, reason); break; case AddressFailure: writeLineEx(0, 4, "<%NODE_NAME%> Fatal Error: Start address is incorrect"); runError(1001, reason); break; case ConnectionError: writeLineEx(0, 4, "<%NODE_NAME%> Fatal Connection Error"); gtRead.Cancel(); break; case SwitchArgumentInvalid: writeLineEx(0, 4, "<%NODE_NAME%> Fatal Error: A argument of a switch statement is incorrect"); runError(1001, reason); break; } stop(); } // ------------------------------------------------------------------------- // The timer will continuously poll the input registers and intput bits on timer gtRead { ModbusReadRegisters(thisDev.Addr.Read.InputRegisters, @sysvar::Modbus::%NODE_NAME%::Info::InputRegisters); ModbusReadBits(thisDev.Addr.Read.InputBits, @sysvar::Modbus::%NODE_NAME%::Info::InputBits); } // If Data::OutputBits is changed we will send this update to the device on sysvar Modbus::%NODE_NAME%::Data::OutputBits { word count, i; byte bitStatus[1968]; count = @sysvar::Modbus::%NODE_NAME%::Info::OutputBits; for (i = 0; i < count; i++) // Copy the data from SysVars to byte[] bitStatus[i] = @sysvar::Modbus::%NODE_NAME%::Data::OutputBits[i]; ModbusWriteBitsB(0, count, bitStatus); // Send update command } // If Data::OutputRergisters is changed we will send this update to the device on sysvar Modbus::%NODE_NAME%::Data::OutputRegisters { word count, i; word regValues[123]; count = @sysvar::Modbus::%NODE_NAME%::Info::OutputRegisters; for (i = 0; i < count; i++) // Copy the data from SysVars to word[] regValues[i] = @sysvar::Modbus::%NODE_NAME%::Data::OutputRegisters[i]; ModbusWriteRegisters(0, count, regValues); // Send update command } // Config::Interval is changed we will update the timer gtRead accordingly on sysvar Modbus::%NODE_NAME%::Config::Interval { if (@this <= 0) gtRead.Cancel(); else setTimerCyclic(gtRead, @this); }