/*@!Encoding:1252*/ includes { #include "include/DeviceInformation.cin" #include "include/ModbusUdp.cin" #include "include/ModbusClient.cin" } variables { char[16] gIps[long]; // List of 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 be scanned byte skip255; // Whether the IP address .255 (broadcast in /24 nets) shall be skipped char fnSysvar[100]; // Filename of Sysvars char fnDbc[100]; // Filename of DBC dword ips[50]; // detected IPs. We need this array for enumeration with integers (assoc. arrays cannot be enumerated with integers :( ) file f; // The file we are writing to struct device gIpsSorted[long]; // The final array with the devices dword gScanCurr, gScanLast; // The first and last IP address as dword word ADi, ADn, ADl; // Some variables for AnalyzeDevices() (event driven, so global) byte ggMaxTransmissionCount; // temp var for gMaxTransmissionCount enum eNodeName {WholeIp, LastByte, TwoLastBytes, ThreeLastBytes}; enum eNodeName NodeNameStyle; // The style of the node name } 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)); // Scan a range of IPs for devices (if nothing was set above). Start and Stop go here strncpy(gScanFirstIp, "192.168.1.2", elCount(gScanFirstIp)); strncpy(gScanLastIp, "192.168.1.255", elCount(gScanLastIp)); // How the Node name shall be formatted // LastByte: 192.168.12.34 --> Client_34 // TwoLastBytes: 192.168.12.34 --> Client_12_34 // ThreeLastBytes: 192.168.12.34 --> Client_168_12_34 // WholeIp: 192.168.12.34 --> Client_192_168_12_34 NodeNameStyle = LastByte; // Whether the IP address .255 (broadcast in /24) shall be skipped skip255 = 1; // Paths to the generated files relative to MakeConfig.cfg strncpy(fnSysvar, "include/SysVars/Modbus.vsysvar", elCount(fnSysvar)); strncpy(fnDbc, "include/DBC/Modbus.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); gScanCurr = ipGetAddressAsNumber(gScanFirstIp); // We have to use big endian here gScanLast = swapDWord(ipGetAddressAsNumber(gScanLastIp)); // But not here :) writeLineEx(0, 0, "%d.%d.%d.%d ", gScanCurr & 0xFF, (gScanCurr >> 8) & 0xFF, (gScanCurr >> 16) & 0xFF, gScanCurr >> 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() { gScanCurr = swapDWord(gScanCurr); // Swap to increment gScanCurr++; // Increment if ((gScanCurr & 0xFF) == 0xFF) // If .255 { if (skip255) // If we shall skip .255 gScanCurr++; writeLineEx(0, 0, "%d.%d.%d.%d ", gScanCurr >> 24, (gScanCurr >> 16) & 0xFF, (gScanCurr >> 8) & 0xFF, gScanCurr & 0xFF); } if (gScanCurr > gScanLast) // If we are beyond the last ip address { @sysvar::Config::Modbus::MaxTransmissionCount = ggMaxTransmissionCount; // reset MakeIpNets(); // Continue with Step 2 return; } gScanCurr = swapDWord(gScanCurr); // Swap back writeEx(0, 0, "."); // Write something so the user knows something is happening gRemoteIP = gScanCurr; // Don't open new socket, it takes too much time. This means we should use UDP or EIL here! ModbusReadBits(0, 1); // Scan the next device } /// void OnModbusReadBitsFailed(enum ModbusRequestError error, enum ModbusException ex, struct ModbusApHeader mbap) { DetectDevicesNext(); // Timeout, NotSent or Exception! We will go to the next device } /// void OnModbusReadBitsSuccess(struct ModbusResReceiveBits mbres, byte bitStatus[], struct ModbusReqRead mbreq) { ipGetAddressAsString(gScanCurr, gIps[gScanCurr], 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 /// 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) // Iterate all devices { ipNum = ipGetAddressAsNumber(gIps[i]); // convert IP to dword 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 ) & 0xFF, gIpsSorted[ipNum].Ip1, 10); // set .Ip1 ltoa((ipNum >> 8) & 0xFF, gIpsSorted[ipNum].Ip2, 10); // set .Ip2 ltoa((ipNum >> 16) & 0xFF, gIpsSorted[ipNum].Ip3, 10); // set .Ip3 ltoa((ipNum >> 24) & 0xFF, gIpsSorted[ipNum].Ip4, 10); // set .Ip4 gIps.Remove(i); } AnalyzeDevices(); // Continue with step 3 } // Step 3: Retreive configuration of devices /// void AnalyzeDevices() { // Init counters ADn = 1; // expect 1 response 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() { // clean up the string of the previous devices if (strlen(gIpsSorted[ips[ADi]].DeviceIOs.Modules) > 0) // If we do have some Modules in this string gIpsSorted[ips[ADi]].DeviceIOs.Modules[strlen(gIpsSorted[ips[ADi]].DeviceIOs.Modules)-1] = 0; // Remove the last comma (set the char to NUL) // print the result 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) // continue with the next device. If we have analyzed all devices { MakeFiles(); // go to Step4 return; } ADn = 1; // expect 1 response again 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 Exception: memcpy_n2h(mbreq, gQueueAck[mbap.TxID].Buffer); if (mbreq.Address == 0x1000 && ex == IllegalDataAddress) // We requested B&R MAC and it didn't work --> Not B&R --> Wago. 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 Timeout: return; // Timeout is unimportant, it means the request will be resent 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 (from MAC address) { gIpsSorted[ips[ADi]].Vendor = BuR; // request further information ADn = _DeviceGetInformation(BuR); return; } // else parse the received data _DeviceParseRegister(gIpsSorted[ips[ADi]], mbreq.Address, mbres.Data, mbreq.Count); if (--ADn == 0) // If we received all registers AnalyzeDevicesNext(); // Continue with the next device } // 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 ipN : gIpsSorted) { 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"); 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_"); switch (NodeNameStyle) // Add the IP bytes depending on the style. Don't break anywhere. { case WholeIp: PutString(gIpsSorted[ipN].Ip1); PutString("_"); case ThreeLastBytes: PutString(gIpsSorted[ipN].Ip2); PutString("_"); case TwoLastBytes: PutString(gIpsSorted[ipN].Ip3); PutString("_"); case LastByte: PutString(gIpsSorted[ipN].Ip4); break; default: writeDbg(MbError, "The NodeNameStyle %d is unknown, please use a value of the enum!", NodeNameStyle); runError(1001, 0); return; } } 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\" \"Modbus\";\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){}