214 lines
7.3 KiB
Plaintext
214 lines
7.3 KiB
Plaintext
/*@!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 received packets
|
||
|
||
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;
|
||
}
|
||
|
||
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, "EilSnd: Reconnecting failed! Doing nothing.");
|
||
return 1;
|
||
}
|
||
case OK:
|
||
break;
|
||
default:
|
||
writeDbg(ConnWarning, "EilSnd: Socket status is not OK! Doing nothing.");
|
||
return 1;
|
||
}
|
||
|
||
bin_to_strhex(buffer, str);
|
||
writeDbg(ConnDebug, "EilSnd: %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
|
||
}
|
||
} |