/*@!Encoding:1252*/ includes { #include "include/DeviceInformation.cin" #include "include/ModbusUdp.cin" #include "include/ModbusClient.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 ggMaxTransmissionCount; byte useThirdIpByteForNames; // Whether the third byte of the IP address shall be used in the node name byte skip255; // Whether the IP address .255 (broadcast in /24) shall be skipped } on preStart { byte i = 0; // List of IPs of devices goes here ///strncpy(gIps[i++], "192.168.1.100", elCount(gIps)); ///strncpy(gIps[i++], "192.168.1.101", elCount(gIps)); ///strncpy(gIps[i++], "192.168.100.2", elCount(gIps)); ///strncpy(gIps[i++], "192.168.1.8", elCount(gIps)); // 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 this address for devices strncpy(gScanFirstIp, "192.168.1.2", elCount(gScanFirstIp)); strncpy(gScanLastIp, "192.168.1.255", elCount(gScanLastIp)); // Whether the third byte of the IP address shall be used in the node name useThirdIpByteForNames = 0; // Whether the IP address .255 (broadcast in /24) shall be skipped skip255 = 1; // 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 { ggMaxTransmissionCount = @sysvar::Config::Modbus::MaxTransmissionCount; // save the value DeviceInit(All); 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() { writeLineEx(0, 1, "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); ModbusInit(gScanFirstIp, @sysvar::Config::Modbus::Port, @sysvar::Config::Modbus::RequestTimeout, 1); // Open socket and set variables ModbusReadBits(0, 1); // Start device detection } // This function will increment the IP address and continue the detection /// void DetectDevicesNext() { gScanFirst = swapDWord(gScanFirst); gScanFirst++; if (skip255 && (gScanFirst & 0xFF) == 0xFF) // skip .255 { gScanFirst++; writeLineEx(0, 0, "%d.%d.%d.%d ", gScanFirst >> 24, (gScanFirst >> 16) & 0xFF, (gScanFirst >> 8) & 0xFF, gScanFirst & 0xFF); } else if (!skip255 && (gScanFirst & 0xFF) == 0x00) { writeLineEx(0, 0, "%d.%d.%d.%d ", gScanFirst >> 24, (gScanFirst >> 16) & 0xFF, (gScanFirst >> 8) & 0xFF, gScanFirst & 0xFF); } if (gScanFirst > gScanLast) { @sysvar::Config::Modbus::MaxTransmissionCount = ggMaxTransmissionCount; 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 } /// void OnModbusReadBitsFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap) { DetectDevicesNext(); // Timeout, NotSent, Exception! 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 { writeDbg(MbError, "No devices found!"); 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 = 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 } /// 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 } /// 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; case NotSent: writeLineEx(0, 3, "Error while analyzing %s! The device was not available! Ignoring...", gIpsSorted[ips[ADi]].IP); break; default: writeLineEx(0, 3, "OnModbusReadRegistersFailed: Unknown error: %d", error); OnModbusClientPanics(SwitchArgumentInvalid); return; } gQueueAck.Clear(); // Clear all queues gQueuePending.Clear(); gQueueSent.Clear(); gIpsSorted.Remove(ips[ADi]); // Remove the IP AnalyzeDevicesNext(); // And go to the next device } /// 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 /// void MakeFiles() { GenSysvars(); GenDbc(); stop(); } // Generate the SysVars XML /// void GenSysvars() { writeLineEx(0, 1, "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; DeviceInit(gIpsSorted[ipN].Vendor); 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() { writeLineEx(0, 1, "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_"); if (useThirdIpByteForNames) { 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){}