Bachelorthesis/Modbus-CAPL/include/CAPL/MakeConfig.can
Jonny007-MKD 93dd56469d ModbusClientCommon.cin
Introduced loops to automatically split requests that are too large for Modbus

ModbusClient.can
  Modified Modbus events so that the split requests (see above) will be written at the correct position in sys vars

ModbusFunctions.cin
DeviceInformation.cin
  Introduced new file that will handle most device specific things

ModbusStructs.cin
  Introduced new constants with maximum Modbus values

MakeConfig.can
  Increment IP address with swapDWord
  Moved detection stuff to DeviceInformation.cin
2014-06-17 14:21:45 +00:00

530 lines
22 KiB
Plaintext

/*@!Encoding:1252*/
includes
{
#include "include/DeviceInformation.cin"
#include "include/ModbusUdpClientCommon.cin"
}
variables
{
char[16] gIps[long]; // List IP addresses. These will be analysed
char gScanFirstIp[16]; // The first IP address that will be scanned
char gScanLastIp[16]; // The first IP address that will not be scanned anymore.
char fnSysvar[40]; // Filename of Sysvars
char fnDbc[40]; // Filename of DBC
char name[20]; // Name of project (not important)
dword ips[50]; // detected IPs. We need this array for enumeration with integers
file f; // The file we are writing to
byte gIpNets[long]; // A list of nets
struct device gIpsSorted[long]; // The final array with the devices
dword gScanFirst, gScanLast; // The first and last IP address as dword
word ADi, ADn, ADl; // Some variables for "AnalyzeDevices"
byte gMaxTransmissionCount;
}
on preStart
{
// List of IPs of devices go here
/*
strncpy(gIps[0], "192.168.1.3", 16);
strncpy(gIps[2], "192.168.1.4", 16);
strncpy(gIps[3], "192.168.1.8", 16);
*/
// Scan a range of IPs for devices (if nothing was set above). Start and Stop go here
// Please note: Currently .255 will be skipped! Don't use it for devices
strncpy(gScanFirstIp, "192.168.1.2", 16);
strncpy(gScanLastIp, "192.168.1.20", 16);
// Name of the project
strncpy(name, "Modbus", elCount(name));
// Paths to the generated files relative to MakeConfig.cfg
strncpy(fnSysvar, "include/SysVars/generated.vsysvar", elCount(fnSysvar));
strncpy(fnDbc, "include/DBC/generated.dbc", elCount(fnDbc));
OutputDebugLevel = Error;
}
on start
{
gMaxTransmissionCount = @sysvar::Config::Modbus::MaxTransmissionCount; // save the value
@sysvar::Config::Modbus::MaxTransmissionCount = 1; // and then don't retransmit
if (gIps.Size() == 0) // if no IP address were specified
DetectDevices(); // scan network for devices (Step1)
else
MakeIpNets(); // else continue with Step2
}
/// <PutString>
void PutString(char str[])
{
f.PutString(str, strlen(str));
}
/// <PutString>
void PutString(word d)
{
char str[6];
ltoa(d, str, 10);
f.PutString(str, strlen(str));
}
/// <PutString>
void PutString(byte d)
{
char str[4];
ltoa(d, str, 10);
f.PutString(str, strlen(str));
}
// Step 1: Detect active devices and collect IP addresses
// This function will convert the IP address, open the socket and start the detection. The rest will be done by events
/// <Step1>
void DetectDevices()
{
write("Scanning from %s to %s with timeout of %d ms", gScanFirstIp, gScanLastIp, @sysvar::Config::Modbus::RequestTimeout);
gScanFirst = ipGetAddressAsNumber(gScanFirstIp); // We have to use big endian here
gScanLast = swapDWord(ipGetAddressAsNumber(gScanLastIp)); // But not here :)
writeLineEx(0, 0, "%d.%d.%d.%d ", gScanFirst & 0xFF, (gScanFirst >> 8) & 0xFF, (gScanFirst >> 16) & 0xFF, gScanFirst >> 24);
ModbusConnectTo(gScanFirst, @sysvar::Config::Modbus::Port); // Open socket and set variables
ModbusReadBits(0, 1); // Start device detection
}
// This function will increment the IP address and continue the detection
/// <Step1>
void DetectDevicesNext()
{
gScanFirst = swapDWord(gScanFirst);
gScanFirst++;
if ((gScanFirst & 0xFF) == 0xFF) // .255
{
gScanFirst++;
writeLineEx(0, 0, "%d.%d.%d.%d ", gScanFirst >> 24, (gScanFirst >> 16) & 0xFF, (gScanFirst >> 8) & 0xFF, gScanFirst & 0xFF);
}
if (gScanFirst > gScanLast)
{
@sysvar::Config::Modbus::MaxTransmissionCount = gMaxTransmissionCount;
MakeIpNets();
return;
}
gScanFirst = swapDWord(gScanFirst);
writeEx(0, 0, "."); // Write something so the user knows something is happening
gRemoteIP = gScanFirst; // Don't open new socket, it takes too much time. This means we should use UDP here!
ModbusReadBits(0, 1); // Scan the next device
}
/// <Step1>
void OnModbusReadBitsFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap)
{
switch (error)
{
case FinalTimeout:
case Exception:
DetectDevicesNext(); // Timeout! We will go to the next device
}
}
/// <Step1>
void OnModbusReadBitsSuccess(struct ModbusResReceiveBits mbres, byte bitStatus[], struct ModbusReqRead mbreq)
{
ipGetAddressAsString(gScanFirst, gIps[gScanFirst], 16); // store the detected device's IP address
DetectDevicesNext(); // and continue
}
// Step 2: Sort into subnets and create structure
// Sort the IPs from gIps to gIpsSorted and add their subnet to gIpNets
/// <Step2>
void MakeIpNets()
{
long ipNum;
if (gIps.Size() == 0) // If no devices were specified and detected
{
stop(); // Don't do anything
return;
}
for (long i : gIps) // Go through all devices
{
ipNum = ipGetAddressAsNumber(gIps[i]); // convert IP to dword
gIpNets[(ipNum >> 16) & 0xFF] = 1; // register subnet
ips[gIpsSorted.size()] = ipNum; // add ip address to normal array
// fill the device structure array:
strncpy(gIpsSorted[ipNum].IP, gIps[i], 16); // set .IP
ltoa((ipNum >> 16) & 0xFF, gIpsSorted[ipNum].IpNet, 10); // set .IpNet
ltoa((ipNum >> 24) & 0xFF, gIpsSorted[ipNum].IpLsb, 10); // set .IpLsb
gIps.Remove(i);
}
AnalyzeDevices(); // Continue with step 3
}
// Step 3: Retreive configuration of devices
/// <Step3>
void AnalyzeDevices()
{
// Init counters
ADn = 1; // expect 10 responses
ADi = 0; // First IP address
ADl = gIpsSorted.Size();
writeLineEx(0, 1, "Analyzing %s", gIpsSorted[ips[ADi]].Ip);
if (gRemoteIP != INVALID_IP) // If we already do have a socket
gRemoteIP = ips[ADi]; // use it
else // else create a new one
ModbusConnectTo(ips[ADi], @sysvar::Config::Modbus::Port);
// request something special to get the vendor
// since there is no common register that holds the vendor
// we have to send a request that only one device responds correctly.
// At 0x1000-0x1002 B&R devices return the MAC address, whereas Wago holds the WatchdogTime.
// As the watchdog time only consists of 1 word Wago will return a IllegalDataAddress exception (0x02)
ModbusReadRegisters(0x1000, 3); // Request B&R MAC address
}
/// <Step3>
void AnalyzeDevicesNext()
{
if (strlen(gIpsSorted[ips[ADi]].DeviceIOs.Modules) > 0)
gIpsSorted[ips[ADi]].DeviceIOs.Modules[strlen(gIpsSorted[ips[ADi]].DeviceIOs.Modules)-1] = 0;
writeEx(0, 1, ": AOs: %d, AIs: %d, DOs: %d, DIs: %d --> %s", gIpsSorted[ips[ADi]].DeviceIOs.OutputRegisters, gIpsSorted[ips[ADi]].DeviceIOs.InputRegisters, gIpsSorted[ips[ADi]].DeviceIOs.OutputBits, gIpsSorted[ips[ADi]].DeviceIOs.InputBits, gIpsSorted[ips[ADi]].DeviceIOs.Modules);
if (++ADi >= ADl) // we have analyzed all devices
{
MakeFiles(); // go to Step4
return;
}
ADn = 1; // expect 10 responses
gRemoteIP = ips[ADi]; // Next IP address
writeLineEx(0, 1, "Analyzing %s", gIpsSorted[ips[ADi]].Ip);
// request something special to get the vendor
ModbusReadRegisters(0x1000, 3); // Request B&R MAC address
}
/// <Step3>
void OnModbusReadRegistersFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap)
{
struct ModbusReqRead mbreq;
switch (error)
{
case Timeout:
return;
case Exception:
memcpy_n2h(mbreq, gQueueAck[mbap.TxID].Buffer);
if (mbreq.Address == 0x1000 && ex == IllegalDataAddress) // We requested Wago SerialCode and it didn't work --> Not Wago --> B&R. Not future proof
{
gIpsSorted[ips[ADi]].Vendor = Wago;
// request information
ADn = DeviceGetInformation(Wago);
return;
}
writeLineEx(0, 3, "Error while analyzing %s! The device respond with exception code %d! Ignoring...", gIpsSorted[ips[ADi]].IP, ex);
break;
case FinalTimeout:
writeLineEx(0, 3, "Error while analyzing %s! The device did not respond! Ignoring...", gIpsSorted[ips[ADi]].IP);
break;
}
gQueueAck.Clear(); // Clear all queues
gQueuePending.Clear();
gQueueSent.Clear();
gIpsSorted.Remove(ips[ADi]); // Remove the IP
AnalyzeDevicesNext(); // And go to the next device
}
/// <Step3>
void OnModbusReadRegistersSuccess(struct ModbusResReceiveRegisters mbres, struct ModbusReqRead mbreq)
{
if (mbreq.Address == 0x1000) // We detected a B&R device
{
gIpsSorted[ips[ADi]].Vendor = BuR;
// request further information
ADn = DeviceGetInformation(BuR);
return;
}
DeviceParseRegister(gIpsSorted[ips[ADi]], mbreq.Address, mbres.Data, mbreq.Count);
if (--ADn == 0) // If we received all registers
AnalyzeDevicesNext();
}
// Step 4: Create the files with the queried data
/// <Step4>
void MakeFiles()
{
GenSysvars();
GenDbc();
stop();
}
// Generate the SysVars XML
/// <Step4>
void GenSysvars()
{
write("GenSysvars() -> %s", fnSysvar);
f.Open(fnSysvar, 0, 0); // rewrite file in ASCII
PutString("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
PutString("<systemvariables version=\"4\">\n");
PutString(" <namespace name=\"\" comment=\"\">\n");
PutString(" <namespace name=\"Config\" comment=\"\">\n");
PutString(" <namespace name=\"Modbus\" comment=\"\">\n");
PutString(" <variable anlyzLocal=\"2\" readOnly=\"false\" valueSequence=\"false\" unit=\"ms\" name=\"RequestTimeout\" comment=\"The maximum duration for a Modbus-UDP/-TCP request in milliseconds. After timeout a retransmission may be started (see MaxRetransmissionCount). Use `ping` to get the maximum latency to a device, double it and add 2-3 ms for processing.\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString((word)@sysvar::Config::Modbus::RequestTimeout);
PutString("\" minValue=\"1\" minValuePhys=\"1\" maxValue=\"1000\" maxValuePhys=\"1000\" />\n");
PutString(" <variable anlyzLocal=\"2\" readOnly=\"false\" valueSequence=\"false\" unit=\"\" name=\"Port\" comment=\"\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString((word)@sysvar::Config::Modbus::Port);
PutString("\" minValue=\"1\" minValuePhys=\"1\" maxValue=\"65535\" maxValuePhys=\"65535\" />\n");
PutString(" <variable anlyzLocal=\"2\" readOnly=\"false\" valueSequence=\"false\" unit=\"times\" name=\"MaxTransmissionCount\" comment=\"How often a retransmission of a request will be sent until it gets discarded and an error is thrown.\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString((byte)@sysvar::Config::Modbus::MaxTransmissionCount);
PutString("\" minValue=\"1\" minValuePhys=\"1\" maxValue=\"10\" maxValuePhys=\"10\" />\n");
PutString(" </namespace>\n");
PutString(" <namespace name=\"TcpIp\" comment=\"\">\n");
PutString(" <variable anlyzLocal=\"2\" readOnly=\"false\" valueSequence=\"false\" unit=\"\" name=\"AdapterIndex\" comment=\"Index of network interface to use\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"2\" minValue=\"1\" minValuePhys=\"1\" maxValue=\"20\" maxValuePhys=\"20\" />\n");
PutString(" </namespace>\n");
PutString(" </namespace>\n");
for (long net : gIpNets)
{
byte nett;
nett = net;
PutString(" <namespace name=\"Ethernet");
PutString(nett);
PutString("\" comment=\"Subnet: 192.168.");
PutString(nett);
PutString(".\">\n");
for (long ipN : gIpsSorted)
{
if (((ipN >> 16) & 0xFF) != net)
continue;
DeviceInit(gIpsSorted[ipN].Vendor);
PutString(" <namespace name=\"Client_");
//PutString(netS);
//PutString("_");
PutString(gIpsSorted[ipN].IpLsb);
PutString("\" comment=\"Server with ip address '");
PutString(gIpsSorted[ipN].Ip);
PutString("'\">\n");
// Namespace Config
PutString(" <namespace name=\"Config\" comment=\"Configuration section for this server\">\n");
// IP
PutString(" <variable anlyzLocal=\"2\" readOnly=\"false\" valueSequence=\"false\" unit=\"\" name=\"IP\" comment=\"The IP address of this server\" bitcount=\"8\" isSigned=\"true\" encoding=\"65001\" type=\"string\" startValue=\"");
PutString(gIpsSorted[ipN].Ip);
PutString("\" />\n");
// Intveral
PutString(" <variable anlyzLocal=\"2\" readOnly=\"false\" valueSequence=\"false\" unit=\"ms\" name=\"Interval\" comment=\"The interval with which the device will be queried\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"100\" minValue=\"10\" minValuePhys=\"10\" maxValue=\"10000\" maxValuePhys=\"10000\" />\n");
PutString(" </namespace>\n");
//Namespace Info
PutString(" <namespace name=\"Info\" comment=\"Some information about the device\">\n");
// Vendor
PutString(" <variable anlyzLocal=\"2\" readOnly=\"true\" valueSequence=\"false\" unit=\"\" name=\"Vendor\" comment=\"The vendor of the device\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString((byte)gIpsSorted[ipN].Vendor);
PutString("\">\n");
PutString(" <valuetable definesMinMax=\"true\">\n");
PutString(" <valuetableentry value=\"2\" description=\"BuR\" />\n");
PutString(" <valuetableentry value=\"23\" description=\"Wago\" />\n");
PutString(" </valuetable>\n");
PutString(" </variable>\n");
// SerialCode
PutString(" <variable anlyzLocal=\"2\" readOnly=\"true\" valueSequence=\"false\" unit=\"\" name=\"SerialCode\" comment=\"The serial code of the server\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString(gIpsSorted[ipN].SerialCode);
PutString("\" minValue=\"1\" minValuePhys=\"1\" maxValue=\"10000\" maxValuePhys=\"10000\" />\n");
// DeviceCode
PutString(" <variable anlyzLocal=\"2\" readOnly=\"true\" valueSequence=\"false\" unit=\"\" name=\"DeviceCode\" comment=\"The device code of the server\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString(gIpsSorted[ipN].DeviceCode);
PutString("\" minValue=\"1\" minValuePhys=\"1\" maxValue=\"10000\" maxValuePhys=\"10000\" />\n");
// Modules
PutString(" <variable anlyzLocal=\"2\" readOnly=\"true\" valueSequence=\"false\" unit=\"\" name=\"Modules\" comment=\"The type and number of inputs of modules that are connected to the server\" bitcount=\"8\" isSigned=\"true\" encoding=\"65001\" type=\"string\" startValue=\"");
PutString(gIpsSorted[ipN].DeviceIOs.Modules);
PutString("\" />\n");
// InputRegisters
PutString(" <variable anlyzLocal=\"2\" readOnly=\"true\" valueSequence=\"false\" unit=\"\" name=\"InputRegisters\" comment=\"Number of input registers\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString(gIpsSorted[ipN].DeviceIOs.InputRegisters);
PutString("\" minValue=\"0\" minValuePhys=\"0\" maxValue=\"");
PutString((word)gDevRegMaxCount);
PutString("\" maxValuePhys=\"");
PutString((word)gDevRegMaxCount);
PutString("\" />\n");
// InputBits
PutString(" <variable anlyzLocal=\"2\" readOnly=\"true\" valueSequence=\"false\" unit=\"\" name=\"InputBits\" comment=\"Number of input bits\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString(gIpsSorted[ipN].DeviceIOs.InputBits);
PutString("\" minValue=\"0\" minValuePhys=\"0\" maxValue=\"");
PutString((word)gDevBitMaxCount);
PutString("\" maxValuePhys=\"");
PutString((word)gDevBitMaxCount);
PutString("\" />\n");
// OutputRegisters
PutString(" <variable anlyzLocal=\"2\" readOnly=\"true\" valueSequence=\"false\" unit=\"\" name=\"OutputRegisters\" comment=\"Number of output registers\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString(gIpsSorted[ipN].DeviceIOs.OutputRegisters);
PutString("\" minValue=\"0\" minValuePhys=\"0\" maxValue=\"");
PutString((word)gDevRegMaxCount);
PutString("\" maxValuePhys=\"");
PutString((word)gDevRegMaxCount);
PutString("\" />\n");
// OutputBits
PutString(" <variable anlyzLocal=\"2\" readOnly=\"true\" valueSequence=\"false\" unit=\"\" name=\"OutputBits\" comment=\"Number of output bits\" bitcount=\"32\" isSigned=\"true\" encoding=\"65001\" type=\"int\" startValue=\"");
PutString(gIpsSorted[ipN].DeviceIOs.OutputBits);
PutString("\" minValue=\"0\" minValuePhys=\"0\" maxValue=\"");
PutString((word)gDevBitMaxCount);
PutString("\" maxValuePhys=\"");
PutString((word)gDevBitMaxCount);
PutString("\" />\n");
PutString(" </namespace>\n");
// Namespace Data
PutString(" <namespace name=\"Data\" comment=\"The actual process image\">\n");
// InputRegisters
PutString(" <variable anlyzLocal=\"2\" readOnly=\"false\" valueSequence=\"false\" unit=\"\" name=\"InputRegisters\" comment=\"The values of the input registers\" bitcount=\"9\" isSigned=\"true\" encoding=\"65001\" type=\"intarray\" arrayLength=\"");
PutString(gIpsSorted[ipN].DeviceIOs.InputRegisters);
PutString("\" />\n");
// InputBits
PutString(" <variable anlyzLocal=\"2\" readOnly=\"false\" valueSequence=\"false\" unit=\"\" name=\"InputBits\" comment=\"The state of the input bits\" bitcount=\"2\" isSigned=\"true\" encoding=\"65001\" type=\"intarray\" arrayLength=\"");
PutString(gIpsSorted[ipN].DeviceIOs.InputBits);
PutString("\" />\n");
// OutputRegisters
PutString(" <variable anlyzLocal=\"2\" readOnly=\"false\" valueSequence=\"false\" unit=\"\" name=\"OutputRegisters\" comment=\"The values of the output registers. Write here and the values will be sent to the device\" bitcount=\"9\" isSigned=\"true\" encoding=\"65001\" type=\"intarray\" arrayLength=\"");
PutString(gIpsSorted[ipN].DeviceIOs.OutputRegisters);
PutString("\" />\n");
// OutputBits
PutString(" <variable anlyzLocal=\"2\" readOnly=\"false\" valueSequence=\"false\" unit=\"\" name=\"OutputBits\" comment=\"The state of the output bits. Write here and the values will be sent to the device\" bitcount=\"2\" isSigned=\"true\" encoding=\"65001\" type=\"intarray\" arrayLength=\"");
PutString(gIpsSorted[ipN].DeviceIOs.OutputBits);
PutString("\" />\n");
PutString(" </namespace>\n");
PutString(" </namespace>\n");
}
PutString(" </namespace>\n");
}
PutString(" </namespace>\n");
PutString("</systemvariables>\n");
f.Close();
}
// Generate the Database
/// <Step4>
void GenDbc()
{
write("GenDbc() -> %s", fnDbc);
f.Open(fnDbc, 0, 0); // rewrite file in ASCII
PutString("VERSION \"\"\n\n\n");
PutString("NS_ :\n");
PutString(" NS_DESC_\n");
PutString(" CM_\n");
PutString(" BA_DEF_\n");
PutString(" BA_\n");
PutString(" VAL_\n");
PutString(" CAT_DEF_\n");
PutString(" CAT_\n");
PutString(" FILTER\n");
PutString(" BA_DEF_DEF_\n");
PutString(" EV_DATA_\n");
PutString(" ENVVAR_DATA_\n");
PutString(" SGTYPE_\n");
PutString(" SGTYPE_VAL_\n");
PutString(" BA_DEF_SGTYPE_\n");
PutString(" BA_SGTYPE_\n");
PutString(" SIG_TYPE_REF_\n");
PutString(" VAL_TABLE_\n");
PutString(" SIG_GROUP_\n");
PutString(" SIG_VALTYPE_\n");
PutString(" SIGTYPE_VALTYPE_\n");
PutString(" BO_TX_BU_\n");
PutString(" BA_DEF_REL_\n");
PutString(" BA_REL_\n");
PutString(" BA_DEF_DEF_REL_\n");
PutString(" BU_SG_REL_\n");
PutString(" BU_EV_REL_\n");
PutString(" BU_BO_REL_\n");
PutString(" SG_MUL_VAL_\n");
PutString("\n");
PutString("BS_:\n");
PutString("\nBU_:");
for (long ipN : gIpsSorted)
{
PutString(" Client_");
//PutString(gIpsSorted[ipN].IpNet);
//PutString("_");
PutString(gIpsSorted[ipN].IpLsb);
}
PutString("\n\n\n\n");
PutString("BA_DEF_ BU_ \"NodeLayerModules\" STRING ;\n");
PutString("BA_DEF_ \"DBName\" STRING ;\n");
PutString("BA_DEF_ \"BusType\" STRING ;\n");
PutString("BA_DEF_DEF_ \"NodeLayerModules\" \"Ethernet_IL.DLL\";\n");
PutString("BA_DEF_DEF_ \"DBName\" \"\";\n");
PutString("BA_DEF_DEF_ \"BusType\" \"Ethernet\";\n");
PutString("BA_ \"BusType\" \"Ethernet\";\n");
PutString("BA_ \"DBName\" \"");
PutString(name);
PutString("\";\n");
f.Close();
}
// The stuff below is not needed
/// <zzzModbus>
void OnModbusClientPanics(enum FatalErrors reason)
{
writeLineEx(0, 4, "<%NODE_NAME%> FATAL! %d", reason);
/* switch(reason)
{
case ParsingBuffer:
case ModbusPackageWasSplit:
case DeviceCodeUnknown:
case VendorIdUnknown:
case ConnectionError:
break;
}
*/
}
/// <zzzModbus>
void OnModbusWriteBitFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){}
/// <zzzModbus>
void OnModbusWriteRegisterFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){}
/// <zzzModbus>
void OnModbusWriteMasksFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){}
/// <zzzModbus>
void OnModbusReadWriteRegistersFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){}
/// <zzzModbus>
void OnModbusWriteBitsFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){}
/// <zzzModbus>
void OnModbusWriteRegistersFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){}
/// <zzzModbus>
void OnModbusWriteBitSuccess(struct ModbusResConfirmSingle mbc){}
/// <zzzModbus>
void OnModbusWriteRegisterSuccess(struct ModbusResConfirmSingle mbc){}
/// <zzzModbus>
void OnModbusWriteBitsSuccess(struct ModbusResConfirmMultiple mbc){}
/// <zzzModbus>
void OnModbusWriteRegistersSuccess(struct ModbusResConfirmMultiple mbc){}
/// <zzzModbus>
void OnModbusWriteMasksSuccess(struct ModbusResConfirmMasks mbc){}