/*@!Encoding:1252*/ // This file contains functions that abstract the Ethernet Interaction Layer /// It provides following methods /// - _ModbusConnectTo() Prepare anything that sending works. Here: Create a new packet /// - _ModbusDisconnect() Gracefully disconnect from the device. Here: Invalidate the existing packet /// - _ModbusRecv() Receive data from the device. Here: Does nothing, receiving takes place automatically /// - _ModbusSnd() Send data to the device. Here: Fills the packet with payload data and sends it /// - Some function that receives packets and hands them to _OnModbusReceive() includes { #include "Common.cin" #include "TcpUdpEilCommon.cin" } variables { long gPacket; // The packet that will be send msTimer gtModbusArp; // A timer waiting for the ARP response byte gLocalMac[6]; // Storage of the local MAC address byte gRemoteMac[6]; // Storage of the remote MAC address (the one of the device) // TODO: The local IP should not have to be specified here. Where can we get it from? dword gLocalIP = 0xC0A80101; // The local IP address. } // This method prepares anything in a way that sending with ModbusSnd() is possible. // Here: The method will create an ARP telegram to get the MAC address of the device with the specified Remote_IP word _ModbusConnectTo(char Remote_IP[], word remotePort) { dword remoteIp; // Convert IP string to Number remoteIp = IpGetAddressAsNumber(Remote_IP); if (remoteIp == INVALID_IP) { writeDbg(ConnError, "EilConnectTo: invalid server Ip address!"); OnModbusClientPanics(ConnectionError); return ipGetLastError(); } return _ModbusConnectTo(remoteIp, remotePort); } // This method prepares anything in a way that sending with ModbusSnd() is possible. // Here: The method will create an ARP telegram to get the MAC address of the device with the specified remoteIp word _ModbusConnectTo(dword remoteIp, word remotePort) { long error; byte broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; if (EthGetMacId(gLocalMac) != 0) { gSocketState = ERROR; error = EthGetLastError(); writeDbg(ConnError, "EthGetMacId: Could not get local MAC! %d", error); OnModbusClientPanics(ConnectionError); return error; } // TCP/IP API gives IP in little endian but EIL uses big endian gRemotePort = remotePort; gRemoteIP = (remoteIp >> 24) | (remoteIp >> 8) & 0x0000FF00 | (remoteIp << 8) & 0x00FF0000 | (remoteIp << 24); if (gPacket != 0) _ModbusDisconnect(); // Try to create an ARP packet that gets the MAC from remote server gPacket = EthInitPacket("arp"); if (gPacket == 0) { gSocketState = ERROR; error = EthGetLastError(); writeDbg(ConnError, "EthInitPacket: Could not create ARP package! %d", error); OnModbusClientPanics(ConnectionError); return error; } gSocketState = CONNECTING; EthSetTokenData(gPacket, "eth", "source" , elCount(gLocalMac), gLocalMac); EthSetTokenData(gPacket, "eth", "destination" , elCount(broadcastMac), broadcastMac); EthSetTokenInt(gPacket, "arp", "hwType" , 1); // Ethernet EthSetTokenInt(gPacket, "arp", "protType" , 0x0800); // IPv4 EthSetTokenInt(gPacket, "arp", "hwSize" , 6); // Ethernet addr size EthSetTokenInt(gPacket, "arp", "protSize" , 4); // IP addr size EthSetTokenInt(gPacket, "arp", "operation" , 1); EthSetTokenData(gPacket, "arp", "hwSourceAddr" , elCount(gLocalMac), gLocalMac); EthSetTokenInt(gPacket, "arp", "protSourceAddr" , gLocalIP); EthSetTokenInt(gPacket, "arp", "protDestinationAddr" , gRemoteIP); EthReceivePacket("OnEthReceivePacket"); EthCompletePacket(gPacket); EthOutputPacket(gPacket); EthReleasePacket(gPacket); gSocketState = NULL; gtModbusArp.set(@sysvar::Config::Modbus::RequestTimeout); return 0; } // This method prepares anything in a way that sending with ModbusSnd() is possible. // Here: The method will be called after an ARP telegram was received and set up the EthernetIL packet. void _ModbusConnectTo2() { gPacket = EthInitPacket("udp"); if (gPacket == 0) { gSocketState = ERROR; writeDbg(ConnError, "EthInitPacket: Could not create UDP packet: %d", EthGetLastError()); OnModbusClientPanics(ConnectionError); return; } EthSetTokenData(gPacket, "eth", "source" , elCount(gLocalMac), gLocalMac); EthSetTokenData(gPacket, "eth", "destination" , elCount(gRemoteMac), gRemoteMac); EthSetTokenInt(gPacket, "ipv4", "source" , gLocalIP); EthSetTokenInt(gPacket, "ipv4", "destination" , gRemoteIP); EthSetTokenInt(gPacket, "udp", "source" , 23456); EthSetTokenInt(gPacket, "udp", "destination" , 502); gSocketState = OK; } // This method will gracefully disconnect from the remote device. // Here: The method will invalidate the packet 'gPacket' void _ModbusDisconnect() { if (gPacket != 0) { EthReleasePacket(gPacket); gPacket = 0; } gSocketState = CLOSED; } // This method will wait for data from the remote device. // Here: Nothing has to be done, EthernetIL is waiting for packets all the time void _ModbusRecv() { } // This method will send the payload 'buffer' to the device. // Here: It fills the packet 'gPacket' and sends it byte _ModbusSnd(byte buffer[], word length) { char str[20*3]; switch (gSocketState) { case CLOSED: _ModbusConnectTo(gRemoteIP, gRemotePort); if (gSocketState != OK) { writeDbg(ConnWarning, "_ModbusSnd: Reconnecting failed!"); return 1; } case OK: break; case NULL: // Delay case CONNECTING: return 1; case ERROR: writeDbg(ConnError, "_ModbusSnd: Socket status is not OK!"); OnModbusClientPanics(ConnectionError); return 1; default: writeDbg(ConnError, "_ModbusSnd: Unknown socket status: %d", gSocketState); OnModbusClientPanics(SwitchArgumentInvalid); return 1; } bin_to_strhex(buffer, str); writeDbg(ConnDebug, "_ModbusSnd: %s (Länge: %d)", str, length); EthResizeToken(gPacket, "udp", "data" , length*8); EthSetTokenData(gPacket, "udp", "data" , length, buffer); EthCompletePacket(gPacket); EthOutputPacket(gPacket); return 0; } // This Method simply combines the two EthGetLastError functions long _ModbusGetLastConnectionError(char string[]) { EthGetLastErrorText(elCount(string), string); return EthGetLastError(); } // When the ARP times out the "connection" could not be opened and we have to throw an error on timer gtModbusArp { gSocketState = ERROR; writeDbg(ConnError, "No (valid) ARP response detected. The host seems to be offline!"); OnModbusClientPanics(ConnectionError); } // This function will handle incoming packets and give them to the Modbus layer void OnEthReceivePacket(long channel, long dir, long packet) { byte buffer[gModbusMaxTelegramSize]; long size; if (dir == TX) return; if (EthGetTokenInt(packet, "arp", "protSourceAddr") == gRemoteIP) // this was our ARP package { if (EthGetTokenData(packet, "arp", "hwSourceAddr", elCount(gRemoteMac), gRemoteMac) == 6) // save it { gtModbusArp.Cancel(); writeDbg(ConnDebug, "Remote Mac: %02X:%02X:%02X:%02X:%02X:%02X", gRemoteMac[0], gRemoteMac[1], gRemoteMac[2], gRemoteMac[3], gRemoteMac[4], gRemoteMac[5]); _ModbusConnectTo2(); // create the UDP package _ModbusStartQueue(); } return; } if (EthGetTokenInt(packet, "ipv4", "protocol") == 0x11 && EthGetTokenInt(packet, "ipv4", "source") == gRemoteIP) // if this is a UDP package from our server { size = EthGetThisData(0, gModbusMaxTelegramSize, buffer); _OnModbusReceive(0, 0, EthGetTokenInt(packet, "ipv4", "source"), gRemoteIP, buffer, size); // Hand to Modbus Layer } }