/*@!Encoding:1252*/ includes { #include "include/ModbusUdpClientCommon.cin" #include "include/ModbusFunctions.cin" } variables { struct device // A structure that contains information about an Modbus device { char Ip[16]; // String: The IP address char IpLsb[4]; // String: The last byte of the IP address. Used as index of node name char IpNet[4]; // String: The second last byte of the IP. Used as index of net enum Vendor Vendor; // The Vendor (Wago / B&R) word SerialCode; // Serial Code word DeviceCode; // Device Code struct deviceIOs DeviceIOs; // A structure with more information about IOs }; 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. Start and Stop go here // Please note: Currently .255 will be skipped! Don't use it for devices and as stop address strncpy(gScanFirstIp, "192.168.1.2", 16); strncpy(gScanLastIp, "192.168.1.10", 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 = Info; } 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 } /// void PutString(char str[]) { f.PutString(str, strlen(str)); } /// void PutString(word d) { char str[6]; ltoa(d, str, 10); f.PutString(str, strlen(str)); } /// 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 /// void DetectDevices() { write("Scanning from %s to %s with timeout of %d ms", gScanFirstIp, gScanLastIp, @sysvar::Config::Modbus::RequestTimeout); gScanFirst = ipGetAddressAsNumber(gScanFirstIp); gScanLast = ipGetAddressAsNumber(gScanLastIp); write("%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 /// void DetectDevicesNext() { // next IP // Note: IP address is stored as big endian, comments are notated as little endian :) // 0xFE...... --> Skip xxx.xxx.xxx.255 which is broadcast address in 192.168.xxx.0 nets // If first three bytes are full (123.255.255.255), set those to 0 and increment the first byte (124.0.0.0) if ((gScanFirst & 0xFFFFFF00) == 0xFEFFFF00) { gScanFirst &= 0x000000FF; gScanFirst += 0x00000001; write("%d.%d.%d.%d ", gScanFirst & 0xFF, (gScanFirst >> 8) & 0xFF, (gScanFirst >> 16) & 0xFF, gScanFirst >> 24); } // If first two bytes are full (124.111.255.255), set those to 0 and increment the second byte (124.112.0.0) else if ((gScanFirst & 0xFFFF0000) == 0xFEFF0000) { gScanFirst &= 0x0000FFF; gScanFirst += 0x00000100; write("%d.%d.%d.%d ", gScanFirst & 0xFF, (gScanFirst >> 8) & 0xFF, (gScanFirst >> 16) & 0xFF, gScanFirst >> 24); } // If first last byte is full (124.112.222.255), set it to 0 and increment the third byte (124.112.223.0) else if ((gScanFirst & 0xFF000000) == 0xFE000000) { gScanFirst &= 0x00FFFFFF; gScanFirst += 0x00010000; write("%d.%d.%d.%d ", gScanFirst & 0xFF, (gScanFirst >> 8) & 0xFF, (gScanFirst >> 16) & 0xFF, gScanFirst >> 24); } // Else simply increment the LSB else { gScanFirst += 0x01000000; } if (gScanFirst == gScanLast) // If this is the last address we stop the detection { @sysvar::Config::Modbus::MaxTransmissionCount = gMaxTransmissionCount; MakeIpNets(); return; } writeEx(1, 1, "."); // 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 } /// void OnModbusReadBitsFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap) { DetectDevicesNext(); // Timeout! We will go to the next device } /// 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 /// 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 /// void AnalyzeDevices() { // Init counters ADn = 0; // Zero message received ADi = 0; // First IP address ADl = gIpsSorted.Size(); writeLineEx(0, 1, "Analyzing %s", gIpsSorted[ips[ADi]].Ip); gIpsSorted[ips[ADi]].Vendor = Wago; 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); // read the important registers from the device ModbusReadRegisters(0x2011, 1); // Serial Code ModbusReadRegisters(0x2012, 1); // Device Code ModbusReadRegisters(0x1022, 1); // Number of AOs ModbusReadRegisters(0x1023, 1); // Number of AIs ModbusReadRegisters(0x1024, 1); // Number of DOs ModbusReadRegisters(0x1025, 1); // Number of DIs ModbusReadRegisters(0x2030, 65); // Connected IO 1 ModbusReadRegisters(0x2031, 64); // Connected IO 2 ModbusReadRegisters(0x2032, 64); // Connected IO 3 ModbusReadRegisters(0x2033, 63); // Connected IO 4 } /// void AnalyzeDevicesNext() { 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 = 0; // Zero message received gRemoteIP = ips[ADi]; // Next IP address writeLineEx(0, 1, "Analyzing %s", gIpsSorted[ips[ADi]].Ip); gIpsSorted[ips[ADi]].Vendor = Wago; ModbusReadRegisters(0x2011, 1); // Serial Code ModbusReadRegisters(0x2012, 1); // Device Code ModbusReadRegisters(0x1022, 1); // Number of AOs ModbusReadRegisters(0x1023, 1); // Number of AIs ModbusReadRegisters(0x1024, 1); // Number of DOs ModbusReadRegisters(0x1025, 1); // Number of DIs ModbusReadRegisters(0x2030, 65); // Connected IO 1 ModbusReadRegisters(0x2031, 64); // Connected IO 2 ModbusReadRegisters(0x2032, 64); // Connected IO 3 ModbusReadRegisters(0x2033, 63); // Connected IO 4 } /// void OnModbusReadRegistersFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap) { switch (error) { case FinalTimeout: writeLineEx(0, 3, "Error while analyzing %s! The device did not respond! Ignoring...", gIpsSorted[ips[ADi]].IP); gQueueAck.Clear(); // Clear all queues gQueuePending.Clear(); gQueueSent.Clear(); gIpsSorted.Remove(ips[ADi]); // Remove the IP AnalyzeDevicesNext(); // And go to the next device break; case Exception: writeLineEx(0, 3, "Error while analyzing %s! The device respond with exception code %d! Ignoring...", gIpsSorted[ips[ADi]].IP, ex); gQueueAck.Clear(); // Clear all queues gQueuePending.Clear(); gQueueSent.Clear(); gIpsSorted.Remove(ips[ADi]); // Remove the IP AnalyzeDevicesNext(); // And go to the next device break; } } /// void OnModbusReadRegistersSuccess(struct ModbusResReceiveRegisters mbres, struct ModbusReqRead mbreq) { byte i; // Parse the received data switch (mbreq.Address) { case 0x2011: gIpsSorted[ips[ADi]].serialCode = mbres.Data[0]; break; case 0x2012: gIpsSorted[ips[ADi]].deviceCode = mbres.Data[0]; break; case 0x1022: gIpsSorted[ips[ADi]].DeviceIOs.OutputRegisters = mbres.Data[0] / 16; break; case 0x1023: gIpsSorted[ips[ADi]].DeviceIOs.InputRegisters = mbres.Data[0] / 16; break; case 0x1024: gIpsSorted[ips[ADi]].DeviceIOs.OutputBits = mbres.Data[0]; break; case 0x1025: gIpsSorted[ips[ADi]].DeviceIOs.InputBits = mbres.Data[0]; break; case 0x2030: case 0x2031: case 0x2032: case 0x2033: for (i = 0; i < mbreq.Count; i++) { if (mbres.Data[i] == 0x0000) // No more devices --> end break; ParseDeviceCode(mbres.Data[i], gIpsSorted[ips[ADi]].Vendor, gIpsSorted[ips[ADi]].DeviceIOs); } break; } if (++ADn == 10) // If we received all registers (6 messages) AnalyzeDevicesNext(); } // Step 4: Create the files with the queried data /// void MakeFiles() { GenSysvars(); GenDbc(); stop(); } // Generate the SysVars XML /// void GenSysvars() { write("GenSysvars() -> %s", fnSysvar); f.Open(fnSysvar, 0, 0); // rewrite file in ASCII PutString("\n"); PutString("\n"); PutString(" \n"); PutString(" \n"); PutString(" \n"); PutString(" \n"); PutString(" \n"); PutString(" \n"); PutString(" \n"); PutString(" \n"); PutString(" \n"); PutString(" \n"); PutString(" \n"); for (long net : gIpNets) { byte nett; nett = net; PutString(" \n"); for (long ipN : gIpsSorted) { if (((ipN >> 16) & 0xFF) != net) continue; PutString(" \n"); // Namespace Config PutString(" \n"); // IP PutString(" \n"); // Intveral PutString(" \n"); PutString(" \n"); //Namespace Info PutString(" \n"); // Vendor PutString(" \n"); PutString(" \n"); PutString(" \n"); PutString(" \n"); PutString(" \n"); PutString(" \n"); // SerialCode PutString(" \n"); // DeviceCode PutString(" \n"); // Modules PutString(" \n"); // InputRegisters PutString(" \n"); // InputBits PutString(" \n"); // OutputRegisters PutString(" \n"); // OutputBits PutString(" \n"); PutString(" \n"); // Namespace Data PutString(" \n"); // InputRegisters PutString(" \n"); // InputBits PutString(" \n"); // OutputRegisters PutString(" \n"); // OutputBits PutString(" \n"); PutString(" \n"); PutString(" \n"); } PutString(" \n"); } PutString(" \n"); PutString("\n"); f.Close(); } // Generate the Database /// 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 /// 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; } */ } /// void OnModbusWriteBitFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){} /// void OnModbusWriteRegisterFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){} /// void OnModbusWriteMasksFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){} /// void OnModbusReadWriteRegistersFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){} /// void OnModbusWriteBitsFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){} /// void OnModbusWriteRegistersFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap){} /// void OnModbusWriteBitSuccess(struct ModbusResConfirmSingle mbc){} /// void OnModbusWriteRegisterSuccess(struct ModbusResConfirmSingle mbc){} /// void OnModbusWriteBitsSuccess(struct ModbusResConfirmMultiple mbc){} /// void OnModbusWriteRegistersSuccess(struct ModbusResConfirmMultiple mbc){} /// void OnModbusWriteMasksSuccess(struct ModbusResConfirmMasks mbc){}