With the ENC28J60 EtherShield, it is possible to create packets other than the regular HTTP and web packets. The UDP packet is one of these. It is simple and connectionless making minimum overhead needed to create packets. In developing the UDP broadcasts I’ve had to expose a number of functions in my EtherShield library, these are for building the packets and calculating checksums.
The broadcast example featured here is included as an example in the library. The new library is compatible with my previous versions available from this site.
Setting up the ethernet requires a number of addresses to be configured. The broadcast address used is the network broadcast for the local LAN, in this example is it 10.0.0.255:
// Define mac and IP addresses to use static uint8_t mymac[6] = { 0x54,0x55,0x58,0x10,0x00,0x28 }; static uint8_t myip[4] = { 10,0,0,28 }; static uint8_t broadcastip[4] = { 10,0,0,255 }; // Destination Port 52240 #define DEST_PORT_L 0x10 #define DEST_PORT_H 0xCC
The UDP Payload in the example is stored in a simple struct, this is then copied into the packet and broadcast to all those that are listening on the LAN:
struct UDPPayload { uint32_t time; // Time uint16_t temperature; // Temp in 1/10 degree uint16_t data[10]; //watts data uint16_t errorCount; // count of errors in the XML. uint16_t timeout_count; // count of all protocol lockups }; UDPPayload udpPayload;
Initialise the payload:
// Initialise data in the payload structure for( int i=0; i<10; i++ ) { udpPayload.data[i] = i; } udpPayload.time = millis(); udpPayload.temperature = 0;
The final step is to build and send the broadcast message:
void broadcastData( void ) { uint8_t i=0; uint16_t ck; // Setup the MAC addresses for ethernet header while(i<6){ buf[ETH_DST_MAC +i]= 0xff; // Broadcsat address buf[ETH_SRC_MAC +i]=mymac[i]; i++; } buf[ETH_TYPE_H_P] = ETHTYPE_IP_H_V; buf[ETH_TYPE_L_P] = ETHTYPE_IP_L_V; es.ES_fill_buf_p(&buf[IP_P],9,iphdr); // IP Header buf[IP_TOTLEN_L_P]=28+sizeof(UDPPayload); buf[IP_PROTO_P]=IP_PROTO_UDP_V; i=0; while(i<4){ buf[IP_DST_P+i]=broadcastip[i]; buf[IP_SRC_P+i]=myip[i]; i++; } es.ES_fill_ip_hdr_checksum(buf); buf[UDP_DST_PORT_H_P]=DEST_PORT_H; buf[UDP_DST_PORT_L_P]=DEST_PORT_L; buf[UDP_SRC_PORT_H_P]=10; buf[UDP_SRC_PORT_L_P]=srcport; // lower 8 bit of src port buf[UDP_LEN_H_P]=0; buf[UDP_LEN_L_P]=8+sizeof(UDPPayload); // fixed len // zero the checksum buf[UDP_CHECKSUM_H_P]=0; buf[UDP_CHECKSUM_L_P]=0; // copy the data: i=0; // most fields are zero, here we zero everything and fill later uint8_t* b = (uint8_t*)&udpPayload; while(i< sizeof( UDPPayload ) ){ buf[UDP_DATA_P+i]=*b++; i++; } // Create correct checksum ck=es.ES_checksum(&buf[IP_SRC_P], 16 + sizeof( UDPPayload ),1); buf[UDP_CHECKSUM_H_P]=ck>>8; buf[UDP_CHECKSUM_L_P]=ck& 0xff; es.ES_enc28j60PacketSend(42 + sizeof( UDPPayload ), buf); }
The payload could be a structure or a text string, just replace the sizeof() functions with strlen(). With using text strings of the correct format is is then possible to create Syslog messages. The only issue here is that you would need a real time clock to provide the correct timestamp header. In practice I have seen my linux syslog daemon accept syslog messages with invalid or missing timestamps and replace it with its own.
Receiving broadcasts using Perl on Linux
Using a simple perl script the broadcast messages can be received and processed. The example below is receiving the broadcast packets and extracting currentcost data. The format of the payload is similar to the example shown above.
#!/usr/bin/perl -w # cclisten - UDP Currentcost listener use strict; use IO::Socket; my($msglen,$sock, $newmsg, $MAXLEN, $PORTNO); my($lasttimestamp,$timestamp,$tempr,$watts,$errcount,$timeouts); # Pachube.com values my $pachubeApiKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; my $pachubeFeed = "nnnn"; # Keep track of how many sample and total for updating pachube once a minute my $sampleCount = 0; my $totalWatts = 0; $MAXLEN = 1024; $PORTNO = 52240; $sock = IO::Socket::INET->new(LocalPort => $PORTNO, Proto => 'udp', LocalAddr => '10.0.0.255', Broadcast => 1) or die "socket: $@"; print "Awaiting UDP messages on port $PORTNO\n"; $lasttimestamp = 0; while ($sock->recv($newmsg, $MAXLEN)) { $msglen = length($newmsg); $timestamp = unpack("L", substr( $newmsg, 0, 4)); $tempr = unpack("S", substr( $newmsg, 4, 2)) / 10; $watts = unpack("S", substr( $newmsg, 6, 2)); $errcount = unpack("S", substr( $newmsg, 26, 2)); $timeouts = unpack("S", substr( $newmsg, 28, 2)); if ( $timestamp != $lasttimestamp ) { $lasttimestamp = $timestamp; $sampleCount++; $totalWatts += $watts; print "Time $timestamp temp = $tempr errors $errcount Timeouts $timeouts Ch0 Watts $watts\n"; # Update RRD system( "/usr/bin/rrdtool update currentcost.rrd N:$watts:0"); # Update pachube once a minute with a minute's worth of averaged data if ( $sampleCount > 10 ) { # Do a little formatting on the output my $averageWatts = sprintf("%.2f",$totalWatts / $sampleCount); system("curl -s --request PUT --header 'X-PachubeApiKey: $pachubeApiKey' --data $averageWatts 'http://www.pachube.com/api/$pachubeFeed.csv'"); # Reset the counts $totalWatts = 0; $sampleCount = 0; } } } die "recv: $!";
Downloads
Hi Andy,
Thanks for posting this improved library. I now have no excuse for not finishing my xAP automation node stuff I previously posted about now 🙂
-Cheers Max.
Hello.
Can you help me? I’m trying to start with a similar project but i have some problems. I download your library but when i compile (ethershieldclient)i got an error
in this line: buf[TCP_DATA_P+plen]=client_ip[i]; The error is:
In function ‘uint16_t gen_client_request(uint8_t*)’:
error: ‘TCP_DATA_P’ was not declared in this scope In function ‘void client_process()’:
Can you help me please? I use arduino 17 because 16 don’t start.
jagsilva
Andy,
I’ve been using your library as the stock one that comes with it is a little lacking. I’m able to get a web server up and working and processing different URLs no problem. What I’d really like is to be able to output the status of various pins. Something like:
for (int i = 0; i <=5 ; i++) {
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("Pin #"));
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR(i));
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR(" is status "));
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR(digitalRead(i)));
}
The compiler complains about error: initializer fails to determine size of '__c'
PSTR() seems to ONLY want strings. Is there a way to get it to output anything else? Where is PSTR defined, I can't find reference to it to see what it expects.
Hi Scott,
Looks like your problem is in the line:
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR(i));
and
plen=es.ES_fill_tcp_data_p(buf,plen,PSTR(digitalRead(i)));
Here you are trying to access program memory. The PSTR references program memory and is where the static strings are stored in the code above, but for dynamic values, e.g. i in this case it wornt work.
As the code doesnt implement the print class you’d need to format the value if i into a small string buffer then add it to the ether shield packet buffer:
plen=es.ES_fill_tcp_data_p(buf,plen, numberBuffer );
Where numberBuffer is a small character buffer that the value of i has been formatted into. Note here, there is no PSTR as numberBuffer is in data memory. There should be examples of this in the examples included in the library.
Hope this helps if you’ve not already figured it out!
Andy
[…] Arduino reading a CSV pachube feed with the ENC28J60 The question of how to do GET requests with the ENC28J60 arose recently so here is an example. This is using the etherShield from Nuelectronics and @andrewdlindsay’s etherShield library. […]
Thank you very much!
??
no puedo publicar
h?ello, how I can send and resivir UDP packets from the program
processing 1.5?
I found an example, but not as to figure it with this library for arduino.
from already thank you very much