Jonny007-MKD
eb6d28c71b
DeviceInformation.cin struct device now contains all 4 bytes of the IP as fields MakeConfig.can Improved code and comments MakeConfig.can PollingModbusClient.can Removed the subnetting stuff. We simply can add the net to the node name
290 lines
No EOL
12 KiB
Text
290 lines
No EOL
12 KiB
Text
/*@!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("%BUS_TYPE%::%NODE_NAME%::Config", "IP", ip, elCount(ip)); // Get IP address of device from sysvars config
|
|
|
|
DeviceInit(@sysvar::%BUS_TYPE%::%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::%BUS_TYPE%::%NODE_NAME%::Info::OutputBits); // Read the start status of the output bits
|
|
ModbusReadOutRegisters(thisDev.Addr.Read.OutputRegisters, @sysvar::%BUS_TYPE%::%NODE_NAME%::Info::OutputRegisters); // Read the start status of the output registers
|
|
|
|
if (@sysvar::%BUS_TYPE%::%NODE_NAME%::Config::Interval > 0) // Start the polling timer
|
|
setTimerCyclic(gtRead, 1, @sysvar::%BUS_TYPE%::%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("%BUS_TYPE%::%NODE_NAME%::Data", "InputBits");
|
|
for (i = 0; i < @sysvar::%BUS_TYPE%::%NODE_NAME%::Info::InputBits; i++)
|
|
@sysvar::%BUS_TYPE%::%NODE_NAME%::Data::InputBits[i] = -1;
|
|
sysEndVariableStructUpdate("%BUS_TYPE%::%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("%BUS_TYPE%::%NODE_NAME%::Data", "InputRegisters");
|
|
for (i = 0; i < @sysvar::%BUS_TYPE%::%NODE_NAME%::Info::InputRegisters; i++)
|
|
@sysvar::%BUS_TYPE%::%NODE_NAME%::Data::InputRegisters[i] = -1;
|
|
sysEndVariableStructUpdate("%BUS_TYPE%::%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("%BUS_TYPE%::%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::%BUS_TYPE%::%NODE_NAME%::Data::OutputBits[i + offset] = bitStatus[i];
|
|
|
|
sysEndVariableStructUpdate("%BUS_TYPE%::%NODE_NAME%::Data", "OutputBits");
|
|
break;
|
|
|
|
|
|
case ReadBitsIn: // Read input bits
|
|
sysBeginVariableStructUpdate("%BUS_TYPE%::%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::%BUS_TYPE%::%NODE_NAME%::Data::InputBits[i + offset] = bitStatus[i];
|
|
|
|
sysEndVariableStructUpdate("%BUS_TYPE%::%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("%BUS_TYPE%::%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::%BUS_TYPE%::%NODE_NAME%::Data::OutputRegisters[i + offset] = mbres.Data[i];
|
|
|
|
sysEndVariableStructUpdate("%BUS_TYPE%::%NODE_NAME%::Data", "OutputRegisters");
|
|
break;
|
|
|
|
|
|
case ReadRegistersIn: // Read input registers
|
|
sysBeginVariableStructUpdate("%BUS_TYPE%::%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::%BUS_TYPE%::%NODE_NAME%::Data::InputRegisters[i + offset] = mbres.Data[i];
|
|
|
|
sysEndVariableStructUpdate("%BUS_TYPE%::%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::%BUS_TYPE%::%NODE_NAME%::Info::InputRegisters);
|
|
ModbusReadBits(thisDev.Addr.Read.InputBits, @sysvar::%BUS_TYPE%::%NODE_NAME%::Info::InputBits);
|
|
}
|
|
|
|
// If Data::OutputBits is changed we will send this update to the device
|
|
on sysvar %BUS_TYPE%::%NODE_NAME%::Data::OutputBits
|
|
{
|
|
word count, i;
|
|
byte bitStatus[1968];
|
|
|
|
count = @sysvar::%BUS_TYPE%::%NODE_NAME%::Info::OutputBits;
|
|
|
|
for (i = 0; i < count; i++) // Copy the data from SysVars to byte[]
|
|
bitStatus[i] = @sysvar::%BUS_TYPE%::%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 %BUS_TYPE%::%NODE_NAME%::Data::OutputRegisters
|
|
{
|
|
word count, i;
|
|
word regValues[123];
|
|
|
|
count = @sysvar::%BUS_TYPE%::%NODE_NAME%::Info::OutputRegisters;
|
|
|
|
for (i = 0; i < count; i++) // Copy the data from SysVars to word[]
|
|
regValues[i] = @sysvar::%BUS_TYPE%::%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 %BUS_TYPE%::%NODE_NAME%::Config::Interval
|
|
{
|
|
if (@this <= 0)
|
|
gtRead.Cancel();
|
|
else
|
|
setTimerCyclic(gtRead, @this);
|
|
} |