Bachelorthesis/Modbus-CAPL/include/CAPL/include/ModbusEil.cin

224 lines
7.6 KiB
Plaintext
Raw Blame History

/*@!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
}
}