new library UIPEthernet: uIP adapted to Arduino for ENC28J60

did manage to squeeze another few hundred bytes of flash out of the code stabilizing UDPs memory-usage a bit at the same time by restricting the number of unparsed (unread) inbound packets to 1 (instead of 5, which could just fill up enc28j60 internal memory).

this refactoring currently is in master only.

  • Norbert

Just tested
Amazing job.
Didn't know about "Udp.stop()"

Thanks

ntruchsess:

lukedan:
I Think I have same problem - code preaty the same: ntp and webserver.

I think that is an issue of insufficient resources.
At first I enbraced all Strings being written with 'F()'-macro to free the ram being used of this. After that change the arduino responds to ping after having received the time from NTP, but still hangs on receival of http-request when trying to process the very first packet containing data.

Then I added propper cleanup of UDP to getNtpTime-method, like so:

#include <Time.h>

#include <UIPEthernet.h>
#include <UIPUdp.h>

//EthernetServer server = EthernetServer(80);
 byte mac[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
 IPAddress  myIP(192, 168, 0, 11);
 IPAddress  gwIP(192, 168, 0, 1);
 unsigned int localPort = 8888;  // local port to listen for UDP packets
 EthernetServer server = EthernetServer(80);
 EthernetUDP Udp;  // An EthernetUDP instance to let us send and receive packets over UDP

// NTP Servers:
IPAddress timeServer(80, 96, 196, 58); //timp.mcsi.ro
const int timeZone = 2;     // Central European Time

void setup()
{ // ----------------------------- VOID SETUP <<<<<<<<<<<<<<<<<<<<<<<<<<<<<  //
 Serial.begin(9600);
 Serial.println(F("Start ntp_uipeth_test Example"));
 Ethernet.begin(mac, myIP);
 Serial.print(F("IP = "));
 Serial.println(Ethernet.localIP());
 server.begin();
 Serial.println(F("waiting for sync"));
 setSyncInterval(600);    // Set the number of seconds between re-sync
 setSyncProvider(getNtpTime);

} // >>>>>>>>>>>>>>>>>>>>>>>>>>>> VOID SETUP -----------------------------  //
time_t prevDisplay = 0; // when the digital clock was displayed

void loop()
{  // ----------------------------- VOID LOOP <<<<<<<<<<<<<<<<<<<<<<<<<<<<<  //
 if (timeStatus() != timeNotSet)
 {
   if (now() != prevDisplay)
   { //update the display only if time has changed
     prevDisplay = now();
     digitalClockDisplay();
   }
 }

// ---------------- listen for HTTP incoming clients ------------------//
 EthernetClient client = server.available();
 if (client) {
   Serial.println(F("new client"));
   // an http request ends with a blank line
   boolean currentLineIsBlank = true;
   while (client.connected()) {
     if (client.available()) {
       char c = client.read();
       Serial.write(c);
       // if you've gotten to the end of the line (received a newline
       // character) and the line is blank, the http request has ended,
       // so you can send a reply
       if (c == '\n' && currentLineIsBlank) {
         // send a standard http response header
         client.println(F("HTTP/1.1 200 OK"));
         client.println(F("Content-Type: text/html"));
         client.println(F("Connection: close"));  // the connection will be closed after completion of the response
         client.println(F("Refresh: 15"));  // refresh the page automatically every 15 sec
         client.println();
         client.println(F(""));
         client.println(F(""));

// output the value of each analog input pin

for (int analogChannel = 0 ; analogChannel < 6; analogChannel++) {
           int sensorReading = analogRead(analogChannel);
           client.print(F("analog input "));
           client.print(analogChannel);
           client.print(F(" is "));
           client.print(sensorReading);
           client.println(F("
"));
         }
         client.println(F(""));
         break;
       }
       if (c == '\n') {
         // you're starting a new line
         currentLineIsBlank = true;
       }
       else if (c != '\r') {
         // you've gotten a character on the current line
         currentLineIsBlank = false;
       }
     }
   }
   // give the web browser time to receive the data
   delay(1);
   // close the connection:
   client.stop();
   Serial.println(F("client disonnected"));
 }
// ---------------- listen for HTTP incoming clients ------------------//
}

// >>>>>>>>>>>>>>>>>>>>>>>>>>>> VOID LOOP -----------------------------  //
// ----------------------------- VOID Functions <<<<<<<<<<<<<<<<<<<<<<<<<<<<<  //

void digitalClockDisplay(){
 // digital clock display of the time
 Serial.print(hour());
 printDigits(minute());
 printDigits(second());
 Serial.print(" ");
 Serial.print(day());
 Serial.print(" ");
 Serial.print(month());
 Serial.print(" ");
 Serial.print(year());
 Serial.println();
}

void printDigits(int digits){
 // utility for digital clock display: prints preceding colon and leading 0
 Serial.print(":");
 if(digits < 10)
   Serial.print('0');
 Serial.print(digits);
}

/-------- NTP code ----------/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
 time_t retval = 0;
 Udp.begin(localPort);
 while (Udp.parsePacket() > 0) ; // discard any previously received packets
 Serial.println(F("Transmit NTP Request"));
 sendNTPpacket(timeServer);
 uint32_t beginWait = millis();
 while (millis() - beginWait < 1500) {
   int size = Udp.parsePacket();
   if (size >= NTP_PACKET_SIZE) {
     Serial.println(F("Receive NTP Response"));
     Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
     unsigned long secsSince1900;
     // convert four bytes starting at location 40 to a long integer
     secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
     secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
     secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
     secsSince1900 |= (unsigned long)packetBuffer[43];
     retval = secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
     break;
   }
 }
 Udp.flush();
 Udp.stop();
 if (retval == 0)
   Serial.println(F("No NTP Response :-("));
 return retval; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
 // set all bytes in the buffer to 0
 memset(packetBuffer, 0, NTP_PACKET_SIZE);
 // Initialize values needed to form NTP request
 // (see URL above for details on the packets)
 packetBuffer[0] = 0b11100011;   // LI, Version, Mode
 packetBuffer[1] = 0;     // Stratum, or type of clock
 packetBuffer[2] = 6;     // Polling Interval
 packetBuffer[3] = 0xEC;  // Peer Clock Precision
 // 8 bytes of zero for Root Delay & Root Dispersion
 packetBuffer[12]  = 49;
 packetBuffer[13]  = 0x4E;
 packetBuffer[14]  = 49;
 packetBuffer[15]  = 52;
 // all NTP fields have been given values, now
 // you can send a packet requesting a timestamp:
 Udp.beginPacket(address, 123); //NTP requests are to port 123
 Udp.write(packetBuffer, NTP_PACKET_SIZE);
 Udp.endPacket();
}
// >>>>>>>>>>>>>>>>>>>>>>>>>>>> VOID Functions -----------------------------  //




Udp.begin is also moved from setup() into getNtpTime() and Udp is stopped when getting the time did succeed. Receival of Udp-packets to just queue them unread to discard them 10 Minutes later obviously is a waste of resources. Keep Udp-socket open only if packets are processed instantaniously.

After that the Arduino responds to request on port 80 as well. Though rather slowly as it endet up sending the response 1 byte per packet at a time as the println()-methods being used in the sketch do call write once for each character which results into an outgoing packet in case there's allready an ACK from the previous one. But besides of that it was running successful.

I guess I'll have to check how to avoid this '1-byte per request' behaviour....

- Norbert

EDIT:
just noticed that I don't get this '1-byte-per packet' behaviour without debug-output enabled in UIPEthernet.h and without Serial being attached. Serial comm at 9600baud seems to slow down the sketch in such a way that there's allways an ACK when the next byte is written. So running above Sketch without Serial-console being attached the response in Browser was as quick as usual.

Static IP is not working with NTP now - with your attached code.
First today library was ok.
Can you please recheck

Thanks

ntruchsess:
did manage to squeeze another few hundred bytes of flash out of the code stabilizing UDPs memory-usage a bit at the same time by restricting the number of unparsed (unread) inbound packets to 1 (instead of 5, which could just fill up enc28j60 internal memory).

this refactoring currently is in master only.

  • Norbert

cannot reproduce that - the attached code from this post:

ntruchsess:

uses static ip and runs here with latest master. You might want to check the fixes I did commit today if you havn't already done so.
Turn on debugging in UIPEthernet.h to see what is happening.

  • Norbert

Does the uIP Library run on this ENC28J60 board :

sorry for the question, but I have just programed my arduino Uno with the "EchoServer" and I can´ping or connect to the IP in the example (its an IP in my LAN), also checked the wires and all is good, so I'm starting to think I don't have the rigth board.

Please confirm that this board is supported or not.

at least this board looks the same as one of those I use with UIPEthernet.
(whatever that means...)

ok here is the code... this checks the time - and displays- then waits for commands from a browser to turn up to 3 ports on and off....

So - mostly it gets the time - a more sophisticated version will check various time servers, but that seems ok... once it's done you can use a browser to get to the server on port 1000... it may work.. it may not. Once it's working until you reboot or power off it seems to continue to work.. but quite often you cannot after reboot or powerup - CANNOT access the web server. Surely the startup conditions should always be the same??? Not tried that UDP stop and flush - will of course put tha in but the fact that the web server sometimes works perfectly - to me, indicates some random condition at start time - I hope you can reproduce this - and thank you so much for spending time on this...

cannot see any code?

regarding:

scargill:
Not tried that UDP stop and flush

Calling stop on UDP after the code has finished processing UDP is mandatory. Otherwise your ENC28J60 inbound buffer might get blocked by unprocessed UDP-packets. UIPEthernet will not drop packets as long they match an active UDP-socket.

released latest bugfixes as version UIPEthernet_v1.06 and UIPEthernet_v1.56 (for Arduino-IDE 1.5.5 and Due):

  • have fun

Norbert

Sorry I missed the code off last time...

I made a change as recommended by LUKEDAN - adding udp.flush etc... so now every time I tried my code it FAILED to get the time - BUT the web server worked every time on boot up - so I changed the NTTP address to the one used by LUKEDAN... and that gives the time - but now, the web server won't respond... this is with the latest code I downloaded this morning...

#include <UIPEthernet.h>
#include <UIPUdp.h>
#include <Time.h>
IPAddress timeServer(80, 96, 196, 58);

#define OUT1 7
#define OUT2 2
#define OUT3 16

EthernetServer server = EthernetServer(1000);
EthernetUDP Udp;
unsigned int localPort = 2390; // local port to listen for UDP packets
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

time_t x;

void setup()
{
Serial.begin(57600);
uint8_t mac[6] = {0x00,0x01,0x02,0x03,0x04,0x04};
IPAddress myIP(192,168,0,123);
Ethernet.begin(mac,myIP);

x=getNtpTime(timeServer);
setTime(x);
Serial.print(hour()); Serial.print(":"); Serial.print(minute()); Serial.print(":"); Serial.println(second());

server.begin();
Serial.print("server is at ");
Serial.println(Ethernet.localIP());

pinMode(OUT1, OUTPUT); digitalWrite(OUT1,HIGH);
pinMode(OUT2, OUTPUT); digitalWrite(OUT2,HIGH);
pinMode(OUT3, OUTPUT); digitalWrite(OUT3,HIGH);
}

char buffer[128];
int bp;

// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer,NTP_PACKET_SIZE);
Udp.endPacket();
}

time_t getNtpTime(IPAddress& address)
{
Udp.begin(localPort);
while (Udp.parsePacket() > 0) ; // discard any previously received packets
sendNTPpacket(address);;
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
return secsSince1900 - 2208988800UL;
}
}
Udp.flush();
Udp.stop();
return 0; // return 0 if unable to get the time
}

void loop()
{
size_t size;
EthernetClient client = server.available();
if (client)
{
if (client.connected())
{
bp=0;
while((size = client.available()) > 0)
{
buffer[bp]=client.read();
if ((buffer[bp]<' ')||(bp>=126)) { buffer[bp]=0; client.flush(); } else bp++;
}
client.println("ok");
if (strstr(buffer,"HTTP")) client.stop(); // differentiate browser or SOCKET - and scrap line with HTTP on - so only have GET line

if (strstr(buffer,"OUT1ON")) digitalWrite(OUT1,HIGH);
if (strstr(buffer,"OUT1OFF")) digitalWrite(OUT1,LOW);
if (strstr(buffer,"OUT2ON")) digitalWrite(OUT2,HIGH);
if (strstr(buffer,"OUT2OFF")) digitalWrite(OUT2,LOW);
if (strstr(buffer,"OUT3ON")) digitalWrite(OUT3,HIGH);
if (strstr(buffer,"OUT3OFF")) digitalWrite(OUT3,LOW);
Serial.println(buffer);

}
}
}

you miss calling Udp.stop() in case getting time by ntp succeeds.

see my example new library UIPEthernet: uIP adapted to Arduino for ENC28J60 - #78 by system - Networking, Protocols, and Devices - Arduino Forum that does 'break' from the loop instead of directly returning from getNtpTime().

  • Norbert

BTW: just see: no need to call Udp.flush() before Udp.stop(). flush() is implicitly done in stop().

Almost there. IT WORKS! Wheee.

Right - well it does work - but something has appeared that I didn't notice it before. I've cleaned up the code a little as I realised there was another silly mistake..

On power up I now get the time as expected and the IP address.

14:14:29
server is at 192.168.0.123

So now if I fire this from a browser.. http://192.168.0.123:1000/?OUT1ON

I get this..

GET /?OUT1ON HTTP/1.1
%d
GET /favicon.ico HTTP/1.1
%d

I get the favicon line - that's fine - but where is the "%d" coming from - never seen that in previous versions??? Could this be a bug perhaps? I've started at my code over and over and I don't see anything wrong.. I've tried another browser... made no difference- I get %d after every line coming back into the server???

#include <UIPEthernet.h>
#include <UIPUdp.h>
#include <Time.h>
IPAddress timeServer(80, 96, 196, 58);

#define OUT1 7
#define OUT2 2
#define OUT3 16

EthernetServer server = EthernetServer(1000);
EthernetUDP Udp;
unsigned int localPort = 2390; // local port to listen for UDP packets
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

time_t x;

void setup()
{
Serial.begin(57600);
uint8_t mac[6] = {0x00,0x01,0x02,0x03,0x04,0x04};
IPAddress myIP(192,168,0,123);
Ethernet.begin(mac,myIP);

x=getNtpTime(timeServer);
setTime(x);
Serial.print(hour()); Serial.print(":"); Serial.print(minute()); Serial.print(":"); Serial.println(second());

server.begin();
Serial.print("server is at ");
Serial.println(Ethernet.localIP());

pinMode(OUT1, OUTPUT); digitalWrite(OUT1,HIGH);
pinMode(OUT2, OUTPUT); digitalWrite(OUT2,HIGH);
pinMode(OUT3, OUTPUT); digitalWrite(OUT3,HIGH);
}

char buffer[128];
int bp;

// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer,NTP_PACKET_SIZE);
Udp.endPacket();
}

time_t getNtpTime(IPAddress& address)
{
Udp.begin(localPort);
while (Udp.parsePacket() > 0) ; // discard any previously received packets
sendNTPpacket(address);;
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
Udp.stop(); return secsSince1900 - 2208988800UL;
}
}
Udp.stop(); return 0; // return 0 if unable to get the time
}

void loop()
{
byte gotsome=0;
EthernetClient client = server.available();
if (client)
{
if (client.connected())
{
bp=0;
while(client.available())
{
buffer[bp]=client.read(); gotsome=1;
if ((buffer[bp]<' ')||(bp>=126)) { buffer[bp]=0; client.flush(); } else bp++;
}
if (gotsome)
{
gotsome=0;
client.println("ok");
if (strstr(buffer,"HTTP")) client.stop(); // differentiate browser or SOCKET
if (strstr(buffer,"OUT1ON")) digitalWrite(OUT1,HIGH);
if (strstr(buffer,"OUT1OFF")) digitalWrite(OUT1,LOW);
if (strstr(buffer,"OUT2ON")) digitalWrite(OUT2,HIGH);
if (strstr(buffer,"OUT2OFF")) digitalWrite(OUT2,LOW);
if (strstr(buffer,"OUT3ON")) digitalWrite(OUT3,HIGH);
if (strstr(buffer,"OUT3OFF")) digitalWrite(OUT3,LOW);
Serial.println(buffer);
}
}
}
}

i'm not getting this %d. Maybe it's something that is leftover in the buffer from loop before, or maybe it's browser-dependent? Doublecheck what is transmitted on the wire using e.g. wireshark.

  • Norbert

From power-up, here you see the result of 3 different browsers in a row.

8:22:56
server is at 192.168.0.123
GET /?OUT1ON HTTP/1.1
%d
GET /favicon.ico HTTP/1.1
%d
GET /?OUT1ON HTTP/1.1
%d
GET /favicon.ico HTTP/1.1
%d
GET /?OUT1ON HTTP/1.1
%d
GET /favicon.ico HTTP/1.1
%d
GET /favicon.ico HTTP/1.1
%d
GET /favicon.ico HTTP/1.1
%d

as I said before, use wireshark and capture the packets that are being transmitted. I'm pretty sure you find the cause of %d being read there. You can also paste your wireshark log to cloudshark.org and post a link to this here so I can check.

Well, I tried - cannot make head nor tail of the output of Wireshark. I've filtered the output to port 1000 only - but there is just so much guff coming out of it I can't make sense of it. (Not used Wireshark before).

If I follow the TCP stream - I get this.

GET /?OUT1ON HTTP/1.1

Host: 192.168.0.123:1000

Connection: keep-alive

Cache-Control: max-age=0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8

User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Safari/537.36

Accept-Encoding: gzip,deflate,sdch

Accept-Language: en-GB,en;q=0.8,es;q=0.6

ok

that doesn't help a lot as it's the text-representation, not the bytes themself.
But could it be that the Serial-console on your windows system displays the carridge-return as '%d'? To be 100% sure to see what the buffer really contains replace the Serial.println(buffer) with something like:

for (byte i=0;i<128;i++) { 
  Serial.print(" ");
  Serial.print(buffer[i],HEX);
}
Serial.println();

hi norbert,
first a big THANK YOU for this gem of a library :slight_smile:
everything i tried so far works like a charm.

however, when trying to compile the arduino sketch for the 'arduinoconnect' iOS app,
I get errors shown as soon as I try to compile using your lib.

this is the sketch I am talking about (I have not modified it at all yet):
https://github.com/milocreek/ArduinoConnectServer/archive/master.zip

thats the error log:

In file included from ArduinoConnectServer.ino:9:
/WebServer.h: In constructor 'WebServer::WebServer(const char*, int)':
WebServer.h:332: error: call of overloaded 'UIPClient(int)' is ambiguous
C:\arduinoIDE\libraries\UIPEthernet/UIPClient.h:81: note: candidates are: UIPClient::UIPClient(uip_userdata_t*)
C:\arduinoIDE\libraries\UIPEthernet/UIPClient.h:80: note: UIPClient::UIPClient(uip_conn*)
C:\arduinoIDE\libraries\UIPEthernet/UIPClient.h:59: note: UIPClient::UIPClient(const UIPClient&)
/WebServer.h: In member function 'virtual size_t WebServer::write(const char*)':
WebServer.h:367: error: invalid conversion from 'const char*' to 'uint8_t'
WebServer.h:367: error: initializing argument 1 of 'virtual size_t UIPClient::write(uint8_t)'
/WebServer.h: In member function 'int WebServer::read()':
WebServer.h:650: error: ambiguous overload for 'operator==' in '((WebServer*)this)->WebServer::m_client == 0'
/WebServer.h:650: note: candidates are: operator==(int, int)
C:\arduinoIDE\libraries\UIPEthernet/UIPClient.h:69: note: virtual bool UIPClient::operator==(const UIPClient&)

Any help would be greatly apreciated :slight_smile:

That will work if you replace the initialization of m_client in Webserver.h (ArduinoConnectServer/WebServer.h at master · milocreek/ArduinoConnectServer · GitHub):

instead of:
m_client(255),
use
m_client(),

I've no idea of why they used this (undocumented) constructor as the standard no-arg constructor defaults to the same value, so it wouldn't make any difference for ArduinoConnectServer. (I also have no idea of why this constructor is not decleared private in EthernetClient.h as the argument refers to a WIZ5100-internal register, but this is another story).

P.S.: I just opened an issue for ArduinoConnectServer, so the author might want to change this:

wow, you are legend!
your support for this free lib easily beats most support teams of most commercial programs. in quality, reply speed and passion...
so thnx again!

I will test this in a few hours, but I trust you blindly :slight_smile:

and thnx for posting this already in their github!