Garmin cycling computers

Let me start by stating that I am writing this without any commercial interest whatsoever. I have just blindly bought a product due to a brand’s reputation and learnt my lesson. My aim is to make people aware that the Garmin cycling computers are really not the greatest product out there, neither is their support.

the story
I am into cycling with my road bike since a couple of years. It is an old hobby that I revived back in 2014. I discovered all the great enhancements since my youth and one of them was how cycle computers had evolved, or actually also cycling apps. My bike was initially installed with a wireless cycling computer from Cateye. I felt this was already a huge improvement over the wired cycling computers that I remembered. Then I discovered that there were heart rate sensors, speed and cadence sensors (2 in 1) and a very powerful app called Strava. Suddenly I could analyse my rides and see how to improve. On youtube many video’s where explaining a lot of helpful things, how to read the data that was collected during my rides. In 2016 I took my hobby to the next level and decided that I needed a serious cycling computer instead of an app on my phone. I heard about Garmin and I started to define my requirements, they were very simple: reliable connectivity to Strava and navigation. Particularly navigation seemed to be a great benefit, so that I could commute to work, a 55km ride. I had my eyes on the Edge 810 and started to do my typical homework when I want to purchase something: reading reviews on the Internet, basically a little bit of due diligence. Reviews on the Internet basically told two stories, one that there is virtually no competition for Garmin and that the Garmin Edge 810 (and Edge 1000) were both great products, yes even flawless. Wow, that is an easy choice then! Was it that easy, that easy as for instance picking a real great tire for your road bike, e.g. the Continental Grand Prix 4000S II? Were the reviews really that honest and did they take the devices really through its paces? I don’t think so….

the first weeks
The first weeks were really exciting and I really believed that I had purchased one of these products that you will never regret. It appeared flawless and solid like a brick. During my ride all data was captured, the battery lasted really long. I didn’t have to worry about losing data while taking a selfie with my phone or that the battery would die on me. And wow did that device look great with the out-front mount. I felt like a pro!
I did notice however a few quirks, but I took them for granted at that time.

the commute to work
My commute is 55km’s and I am not very familiar with the villages and roads in between, so I would need to rely on navigation, hence it was a requirement for the device. Even if I would be familiar with the roads you would like to avoid as many traffic lights or any other form of unnecessary stops, and you would like to drive as much as possible on tarmac or concrete. In the morning my time is precious. So there I went: I logged into Garmin’s online portal, called Garmin connect, and started drafting a route on my map. It took me a while, but there it was, a nearly perfect route. This nearly perfected route I perfected even more after my first ride.
On my second ride things didn’t go that smooth. I was cycling for a while and suddenly discovered that I was ‘off course’, had I missed a turn? how could that be possible with the audible alert? I stopped and looked at the map, here is where trouble started. The display has a actually a very low resolution depending on the light the display is too dim and colours are very pale. Despite this, I was able to make out that I had missed a few turns and that I really was a couple of km’s away from the course. I had to continue my ride by looking at the display and making out the route. This slowed me down considerably and caused me being late at work
Investigating the issue I found that many people on forums had found this problem. It was there for at least two years and people did not find workarounds or whatsoever. Instead Garmin had released many firmware releases but non of them tackled the problem. Contact with Garmin was reported to be futile and in fact Garmin never acknowledged problems missing turns.

Fate
The problem for me was not limited to a single incident, but it happened every ride from now on. It was very annoying, you end up in the middle of nowhere and looking at the device on the very poorly displayed maps slows you down considerably. Did I say that in the morning time is precious?
I contacted Garmin and they advised to do a full factory reset. That didn’t help, but then fate struck. I fell with my bike and broke my hip. I was grounded and did not pursue the problem with the device anymore.

A new season
This year I started cycling early in March, making the most of it. Of course I expected the turn by turn problem still to be there, but in fact it had gotten worse. Turn by turn navigation would still stop somewhere along the ride, but also the device would crash by switching itself of. It meant that I had to stop and boot up the device. After that the best I could expect is that the device started to behave normally again; but missing the information that I had already cycled. At worse the device would not display speed etc, but it would not display a route on map. Nevertheless I found that almost always I was able to recover data from my ride by looking for .fit files on the device when connecting with USB.
Once again I contact Garmin and stated that the problem is so severe that I had lost trust in the device and requested the device to be swapped for a different model, when necessary I would pay extra, as long as I would get a different model. I proposed the Edge 820 (new) or the Edge 1000. From here on a couple of emails went back and forth resulting me to believe that Garmin does not take their customers serious, nor are they reading their mails very well. After all they offered me to swap my device for a refurbished device. Same device = same problems, a different model was not possible.

More issues
I started to live with the issues. After all, having a technical background I also believe that I should limit the system resources as much as possible. I deleted as much as possible stored information in the device, only necessary courses stored and yes it did help. But then other funny things started to happen. I had downloaded a public course in Garmin Connect for a holiday in France. I had made a new course based upon this course and when I sent it to the device it reported the correct course length with all its correct details, but when starting the course it took the public course without my changes. Sigh, it never stops.
I had made more small quirks, such as displaying an Arabic name and not my own name when logging in into Garmin Connect. Drawing routes in Garmin Connect also causes problems, some roads are not visible when using the Google maps, and vice versa with the Openstreet maps. Also sometimes the course drawing ‘hangs’ and no more waypoints can be made.
I have given up on Garmin, I feel they are a company that had a couple of great ideas (in the past), but now lacking the right implementation.

My point
Don’t buy Garmin, really don’t. They are bad guys that try to steal your money and then leave you to your own devices. They even try to sell you expensive maps for top dollar. These maps are opensource and downloadable for free on the Internet. They have lost the plot big time, their newest device, the edge 820 even switches of the display during a ride to conserve battery power. Think about that, how ridiculous is that, you can’t even see your ride information unless you start pressing buttons.
There are new kids around the block, they offer more sophisticated portals/tools, have better integration with online platforms. They are more eager and provide you simply with a better product. And with features that do in fact work and don’t let you pay for things that are freely available on the Internet. Don’t buy Garmin.

Navigon is a bad company!

I own a Navigon device for navigation in my car, it was the top of the line device and it works quite well. However connectivity with my Apple computer broke when upgrading to an upgrade of the operating system. After installation of Mountain Lion the Navigon Fresh application cannot connect anymore with the server hence failing to work, the error raised is CG2009.

Screen Shot 2013-05-16 at 18.55.54

Navigon acknowledged the problem but has made little efforts to fix the problem since 2011, yes you read that well, 2011. That is almost 2 years now, I run Mountain Lion for about 1.5 years! A beta was released of the Fresh application but it did not fix the problem. What I read in the forums is that the beta does not work for anyone. It is highly disappointing and proves that Navigon does not take their Apple customers seriously. Moreover I wonder if Navigon actually takes their customers serious at all, while every other Navigation device received updates every now and then, there has not been no new firmware update since I bought the device. Occasionally the device wants to send you of the highway and then immediately back on the highway, at every exit, that is clearly a major bug, yet it is not being fixed. Furthermore there is lots of room for improvement for the user interface, which could also be addressed in firmware updates.

So no wonder that people use their iPhone etc to navigate to a destination. I just feel that Navigon is a bad company and that they don’t have a right to exists if this is the way customers are treated.

The Arduino doorbell project: Ethernet shield and Multitasking

After the basic functionality has been implemented (see my previous post about the doorbell project), the next step of the doorbell project is to implement the Ethernet shield TCP/IP functionality with some extra features, such as NTP time synchronization, logging of doorbell events in an SQL server and a telnet service that allows to control the doorbell. One could for instance think about an API that makes the doorbell ring for incoming Skype calls.

a97251_g173_8-butt

Soon I will show you the source code for the doorbell project on my Arduino with the integration of the Ethernet shield. You will see that I gave up using the DCF77 hardware (for now) and use NTP time synchronisation instead. At this moment the time is not really necessary, but at a later stage I want to add functionality for quiet hours where the doorbell will not ring. I also decided to make the doorbell project more than just a doorbell. The doorbell can now ring in different patterns, indicating different type of events, i.e. when an email arrives or when something else happens that deserves some priority. To realize this the Arduino needs to be approachable for instance in the form of a TCP service. In this blog I will explain about

  • The Ethernet shield and TCP/IP integration
  • The NTP service and what one needs to keep in mnd
  • Logging the doorbell event into an external database
  • A telnet TCP service for handling external events that make the doorbell ring (in a different pattern)
  • Simple multitasking

 

TCP/IP: To get the Ethernet shield to work is real simple, first a few includes are required:

 #include <SPI.h>  
 #include <Ethernet.h>  
 #include <EthernetUdp.h>  

Now we need to set up TCP/IP, this has to happen only once, so it is part of setup

 void setup()   
 {  
  // Enter a MAC address for your controller below.  
  // Newer Ethernet shields have a MAC address printed on a sticker on the shield  
  byte mac[] = {   
   0x90, 0xA2, 0xDA, 0x01, 0x01, 0x01  };  

  IPAddress ip(192,168,2,202);  
  unsigned int localPort = 8888;   // local port to listen for UDP packets  

  // IP & UDP settings   
  Ethernet.begin(mac, ip);  // Set MAC and IP address, netmask is default 255.255.255.0  
}

The Wiznet chip on the Ethernet shield is a pretty nice chip, it does a lot automatically for us. The only thing we have to do is set up the MAC address, you can make up something like in the example or you can find a MAC address on the label on the shield. Furthermore in my example I chose to have a static IP address. DHCP is real easy too: just call Ethernet.begin(mac), so without ip-argument.

NTP: This code is readily available as an example in the Arduino IDE, but need to be changed a little. We basically don’t want to bother about the time synchronisation and let it happen automatically. Arduino offers this functionality via SetSyncProvider and  SetSyncInterval functions. SetSyncProvider requires an argument that points to a function that returns the time in unix format (the seconds that have passed since 1 Jan 1970). SetSyncInterval requires an argument the number of seconds between each call to the sync provider function. From the NTP sample code I took parts of the code.

 #define 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  

 /******************************************  
  * send an NTP request to the time server   
  * at the given address   
  ******************************************/  
 unsigned long sendNTPpacket(IPAddress& address)  
 {  
  Serial.print("Sending NTP packet to ");  
  for (byte thisByte = 0; thisByte < 4; thisByte++) {  
   // print the value of each byte of the IP address:  
   Serial.print(address[thisByte], DEC);  
   Serial.print(".");   
  }  
  Serial.println();  

  // 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();   
 }  

 /******************************************  
  * get the NTP time  
  ******************************************/  
 unsigned long getNTPtime()   
 {  
  Serial.println("Time synchronization starting");  
  IPAddress timeServer(64,147,116,229); // time.nist.gov NTP server  
  sendNTPpacket(timeServer); // send an NTP packet to a time server  
  delay(1000);   
  if ( Udp.parsePacket() ) {   
   // We've received a packet, read the data from it  
   Udp.read(packetBuffer,NTP_PACKET_SIZE); // read the packet into the buffer  

   //the timestamp starts at byte 40 of the received packet and is four bytes,  
   // or two words, long. First, esxtract the two words:  

   unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);  
   unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);   
   // combine the four bytes (two words) into a long integer  
   // this is NTP time (seconds since Jan 1 1900):  
   unsigned long secsSince1900 = highWord << 16 | lowWord;   
   Serial.print("Seconds since Jan 1 1900 = " );  
   Serial.println(secsSince1900);          

   // now convert NTP time into everyday time:  
   Serial.print("Unix time = ");  
   // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:  
   const unsigned long seventyYears = 2208988800UL;     
   // subtract seventy years:  
   unsigned long epoch = secsSince1900 - seventyYears;   

   //add correction for GMT+1  
   epoch = epoch + 3600;  
   //todo daylight saving time  

   // print Unix time:  
   Serial.println(epoch);                  
   return epoch;  
  }  
  return 0;  
 }  

The code is split into two functions, the function that is used as sync provider is getNTPtime. I believe the code is not too robust, also something is missing here. which is the correction for daylight savings time. But for now this is fine. In the setup function it is now possible to add two calls, after the IP address has been set up:

  // setup time synchronization, this is a call to the function that contacts the teim server  
  // synchronize time every 7200 seconds (2 hours)  
  setSyncProvider(getNTPtime);  
  setSyncInterval(7200);  

Note that 7200 (2 hours) is adjustable to anything you desire.

Logging: Now that we have TCP/IP set up logging is fairly easy, especially from the Arduino side. At my home I have a NAS from Synology. Pretty much out of the box you can run a Webserver with PHP and MySQL. A PHP-file is a script that is runs on the Webserver when somebody makes a request to the PHP-file. The PHP script usually does a couple of things in the background and produced HTML-code. In our case the script will connect to the MySQL database and make an entry to a table that captures doorbell events. The design of the SQL table is simple, it has two fields: a primary key (unique identifier) and a timestamp which records the time of the event.

Screen Shot 2013-02-07 at 11.28.03

I won’t go in much more details now since this is a blog about Arduino, but if you need help, I am happy to assist. From the Arduino site I only need to request a php file via the hypertext transfer protocol (HTTP). In your browser this would be the line HTTP://192.168.2.100/doring.php where 192.168.2.100 is the IP address of my NAS. The typical Arduino code would be:

 /******************************************  
  * make a request to a webpage on the NAS  
  * so the NAS can log a database entry  
  ******************************************/  
 void doWeblog() {  
  // Initialize the Ethernet client library  
  // with the IP address and port of the server   
  // that you want to connect to (port 80 is default for HTTP):  
  EthernetClient client;  

  if (client.connect(webServer, 80)) {  
   Serial.println("connected");  
   // Make a HTTP request:  
   client.println("GET /doring.php HTTP/1.0");  
   client.println();  
   if (client.available()) {  
    char c = client.read();  
    Serial.print(c);  
   }  
   // if the server's disconnected, stop the client:  
   if (!client.connected()) {  
    Serial.println();  
    Serial.println("disconnecting.");  

   }  
   client.stop();  
  }  
 }  

Note: I have not tested this function for robustness, but it worked just fine.

External events handling / multitasking: This is probably the most exciting part, because there are some nice challenges here. Our doorbell has now quite some extra functionality, and we are going to add one more, a telnet TCP service that can receive requests to make the doorbell ring. Implementation of this idea requires some new ideas: most of the time the Arduino will have nothing to do and if there is something to do it is mostly just a single task. It can handle a single task pretty well, but imagine a situation where an external event is triggered and somebody is opening a telnet connection. The regular code that you would use would probably involve a while loop that is waiting for characters, causing that other events will be blocked. That’s said this is a single tasking service. Let’s have a look to the simplified code as found in the Arduino Webserver example, this code is part of loop():

1:   EthernetClient client = server.available();  
2:   if (client) {  
3:      
4:    while (client.connected()) {  
5:     if (client.available()) {  
6:      char c = client.read();  
7:     
8:      }  
9:     }  
10:   }   

With this code a newly connected client is served pretty well, and as you see the code will loop until data is received. Receiving data is usually just a matter of a split second, but theoretically can be much longer and nothing else can happen in between (except for Interrupts and probably the time synchronization). If I’d implement this code and open a telnet session to the Arduino it would appear if the board is hanging! The answer to the solution is simple, but requires a different design:

Avoid time consuming loops (while) and delays (delay) in loop(), use if and millis() so you can do other things in between.

But how will we handle loops then you might think. Well that’s fairly easy when you consider that the loop() function is a loop in it self. so imagine about a project that involves two LED’s (with resistors) connected to analog ports of the Arduino board. without using for or a while loop you can fade them seperately.

 int i, j;  

 void setup()  
 {  
  pinMode(3,OUTPUT);  
  pinMode(5,OUTPUT);  
  i=0;  
  j=128;  
 }  

 void loop()  
 {  
  analogWrite(3,i);  
  analogWrite(5,j);  

  i = (i + 1) % 256;  
  j = (j + 1) % 256;   
  delay(10);  
 }  

This code handles two tasks at the same time, in a single cycle it sends a new value to each LED and increments the value that is send to the LEDs for the next cycle. This is in essence a multitasking program!

Back to the doorbell project,  the idea is that a telnet service is available on the Arduino board. the Arduino would greet you with a welcome message and you could start typing a sequence of rings and pauses that you want to be played by the doorbell. The Arduino would check the sequence and reply with an acknowledgement or a negative acknowledgement. When acknowledged the doorbell would start right away playing the pattern.

Screen Shot 2013-02-10 at 14.00.16

Below you find the code to this multitasking service. In the code you will see that I defined different states that can be reached, to make this primitive multitasking as fair as possible I ordered the states in the reverse order. This means that in one cycle only one new state of the telnet TCP service is handled (except for the situation when the user disconnects). Note that this code makes use of only the piezo speaker for the telnet service, else it drives people mad in the house. I use SVN for version control, soon this blog will be updated and you can checkout the code from there.

 /******************************************  
  * Doorbell project  
  *  
  * $Revision: 12 $:  
  * $Author: hans $:  
  * $LastChangedDate: 2013-02-10 13:56:18 +0100 (Sun, 10 Feb 2013) $:  
  *  
  ******************************************/  
 #include <Time.h>  
 #include <SPI.h>  
 #include <Ethernet.h>  
 #include <EthernetUdp.h>  

 #define DOORBELL_PIN 3  
 #define DOORBELL_INT 1        // only UNO and MEGA2560  
 #define DOORBELL_TIME 5000      // Minimal 5 seconds between the rings  

 #define RELAY1_PIN  5  
 #define RELAY_DOORBELL 5  
 #define RELAY2_PIN  6  
 #define RELAY3_PIN  7  
 #define RELAY4_PIN  8  
 #define PIEZO_PIN  9  

 #define NTP_PACKET_SIZE 48      // NTP time stamp is in the first 48 bytes of the message  

 #define STATE_DISCONNECTED 0  
 #define STATE_AVAILABLE 1   
 #define STATE_CONNECTED 2  
 #define STATE_DATAAVAIL 3  

 #define APP_VERSION "0.3"  

 boolean ring_doorbell = false;  
 // Initialize the Ethernet server library  
 // with the IP address and port you want to use   
 // (port 23 is default for telnet):  
 EthernetServer server(23);  
 EthernetUDP Udp;           // A UDP instance to let us send and receive packets over UDP  
 byte packetBuffer[ NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets  
 IPAddress webServer(192,168,2,100); // The IP address of my NAS  
 EthernetClient telnetClient;        // telnetClient that will be connected  

 String message = "";  
 String buzz_pattern = "";  

 void setup()   
 {  
  // Enter a MAC address for your controller below.  
  // Newer Ethernet shields have a MAC address printed on a sticker on the shield  
  byte mac[] = {   
   0x90, 0xA2, 0xDA, 0x0D, 0xC2, 0x93  };  
  IPAddress ip(192,168,2,202);  
  unsigned int localPort = 8888;   // local port to listen for UDP packets  

  // Enable serial communication for debugging   
  Serial.begin(57600);  
  Serial.print("Arduino doorbell service v");  
  Serial.println(APP_VERSION);  

  // IP & UDP settings   
  Ethernet.begin(mac, ip);  // Set MAC and IP address, netmask is default 255.255.255.0  
  Udp.begin(localPort);   // local port to listen for UDP packets  

  // setup time synchronization, this is a call to the function that contacts the teim server  
  // synchronize time every 7200 seconds (2 hours)  
  setSyncProvider(getNTPtime);  
  setSyncInterval(7200);  

  // Attach interrupt for PIN 3. On rising edge interrupt service routine intDoorbell is called  
  attachInterrupt(DOORBELL_INT, intDoorbell, RISING);  

  // Set Piezo PIN to output mode  
  pinMode(PIEZO_PIN, OUTPUT);  

  // Set Doorbell relay to output mode  
  pinMode(RELAY_DOORBELL, OUTPUT);  

  //digitalWrite(9,HIGH);  
  analogWrite(PIEZO_PIN, 20);   // Almost any value can be used except 0 and 255  
  delay(650);        // wait for a delayms ms  
  //digitalWrite(9,LOW);  
  analogWrite(PIEZO_PIN, 0);    // 0 turns it off  
 }  

 void loop()  
 {  
  static byte srv_state = STATE_DISCONNECTED;  
  static unsigned int count = 0;  
  static unsigned int piezo_delay, doorbell_delay;  
  static boolean finish_ring = 0;   
  unsigned int cur_time = millis();  

  // Check whether ring_doorbell is set. typically it is set by the intDoorbell function that is called  
  // when the doorbell button is pressed.  
  if (ring_doorbell)  
  {  
   Serial.println("Doorbell button pressed - ring ring!");  
   ring_doorbell = false;      // clear the ring_doorbell  
   ctlDoorbell(true);        // start to ring the doorbell  
   doorbell_delay=cur_time + 100;  // calculate the time till when the doorbell has to ring  
   finish_ring = true;        // indicate that the doorbell has to be stopped after delay has been reached  
   logDoorbell();          // log to the webserver  

   if (buzz_pattern=="");      // if the piezo is not performing a buzz  
    buzz_pattern="r50p100r50p100"; // insert a new buzz_pattern  

  }  

  // check whether the doorbell is still ringing and the time has reached to switch it of again  
  if ((doorbell_delay<=cur_time) && (finish_ring))  
  {  
   ctlDoorbell(false);        // stop the doorbell  
   finish_ring = false;       // finish, the doorbell doesn't need to be stopped now anymore  
  }  

  // check whether a new tone has to be processed    
  if ((piezo_delay<=cur_time) && (buzz_pattern!=""))  
  {  
   ctlPiezo(procTone());       // process the tone (r for ring, p for pause)  
   piezo_delay = cur_time + procDelay();  
                     // calculate the time for the delay given in the buzz pattern   
  }  

  // Telnet stage 4.  
  // data is available!  
  if (srv_state==STATE_DATAAVAIL)   // reaching this state means data is available  
  {  
   message = checkMsg();       // check if received message is valid  
   if ((message!="") && (buzz_pattern=="")) {  
                     // the message is not empty & no other buzz_pattern is in use  
    buzz_pattern = message;     // the new buzz_pattern is install  
    telnetClient.println("ACK");      // Acknowledge to the telnetClient  
    Serial.print("Telnet - Message acknowledged, ring pattern: ");  
    Serial.println(buzz_pattern);  
    ctlPiezo(procTone());      // start immediately processing the pattern  
    piezo_delay= cur_time + procDelay();  
                     // calculate the time for the delay given in the buzz pattern   

   }  
   else  
   {  
    telnetClient.println("NAK");      // negative acknowledgement, invalid message or another buzz pattern is ongoing  
    Serial.println("Telnet - Message discarded");  
    Serial.print(message);  
   }  
   message = "";           // the buzz_pattern is installed or discarded, anyway, clear the message   
   telnetClient.stop();           // and disconnect the telnetClient  
   srv_state = STATE_DISCONNECTED;  // set the appropriate state.  
  }  

  // Telnet stage 3.  
  // check if data is available  
  if (srv_state==STATE_CONNECTED)   
  {  
   if (!telnetClient.connected())   
   {  
    srv_state=STATE_DISCONNECTED;  // Was the connection dropped intermediate?  
    message="";           // clear the message (cleanup)  
    telnetClient.stop();          // stop the telnetClient (cleanup)  

    Serial.println("Telnet - Stage 3 telnetClient disconnected");  
   }  
   else   
   {  
    if (telnetClient.available())   
    {  
     //data available  
     char c = telnetClient.read();    // read the character that is available  
     message = message + c;     // add to the existing message  
    }   
    else  
    {   
     if (message!="")        // no more data available and the message is not empty  
      srv_state=STATE_DATAAVAIL;  // so a new state has been reached  
    }  
   }   
  }  

  // Telnet stage 2.  
  // check if a telnetClient connection is connected and say hello  
  if (srv_state==STATE_AVAILABLE)   
  {  
   if (telnetClient.connected())  
   {  
    srv_state=STATE_CONNECTED;    // update the server state  
    telnetClient.print("Welcome to Arduino doorbell service ");   
    telnetClient.println(APP_VERSION);   // send the welcome message  
    telnetClient.flush();         // flush any data that is received too early  
    Serial.println("Telnet - Stage 2 telnetClient connected");  
   }  
   else  
    srv_state=STATE_DISCONNECTED;  
  }  

  // Telnet stage 1.  
  // create a telnetClient object  
  if (srv_state==STATE_DISCONNECTED)   
  {  
   srv_state=STATE_AVAILABLE;  
   telnetClient = server.available();  
  }  

 }  

 /******************************************  
  * interrupt handler when somebody presses  
  * the doorbell switch  
  ******************************************/  
 void intDoorbell()  
 {  
  byte doorbellValue;  
  unsigned long current_time = millis();  
  static unsigned long last_time = 0;  

  // wait 50ms and read the pin. if it is still high then this is not a spike  
  delay(50);   
  doorbellValue=digitalRead(DOORBELL_PIN);  

  // test whether the last doorbell press was long enough ago  
  if ((doorbellValue) && (current_time - last_time >= DOORBELL_TIME))  
  {  
   ring_doorbell = true;  
   last_time = current_time;  
  }  
 }  

 /******************************************  
  * Ring the doorbell  
  * rings the bell and buzz the piezo speaker  
  ******************************************/  
 void ctlDoorbell(boolean state)  
 {  
  bitWrite(PORTD,RELAY_DOORBELL,state);  
 }   

 /******************************************  
  * Buzz the piezo  
  * rings the bell and buzz the piezo speaker  
  ******************************************/  
 void ctlPiezo(boolean state){  
  //digitalWrite(PIEZO_PIN, state);  
  if (state) {  
   analogWrite(PIEZO_PIN,20);  
   return;  
  }  
  analogWrite(PIEZO_PIN,0);  
  return;  
 }   

 /******************************************  
  * Read the tone (ring or pause)  
  * and remove it from the buzz pattern  
  ******************************************/  
 boolean procTone()  
 {  
  char tone = buzz_pattern[0];  
  buzz_pattern = buzz_pattern.substring(1, buzz_pattern.length());  

  if (tone=='r')  
   return true;  
  return false;  
 }  

 /******************************************  
  * Read the delay  
  * and remove it from the buzz pattern  
  ******************************************/  
 unsigned int procDelay()  
 {  
  unsigned int ipause;  
  char pause[6];  
  byte i;  

  for (i=1; i<5; i++) {  
   if ((buzz_pattern[i]=='r') || (buzz_pattern[i]=='p'))  
    break;  
  }  

  buzz_pattern.toCharArray(pause,i+1);  
  ipause = atoi((char *)&pause);   

  buzz_pattern = buzz_pattern.substring(i, buzz_pattern.length());  
  return ipause;  

 }  

 /******************************************  
  * make a request to a webpage on the NAS  
  * so the NAS can log a database entry  
  ******************************************/  
 void logDoorbell() {  
  // Initialize the Ethernet telnetClient library  
  // with the IP address and port of the server   
  // that you want to connect to (port 80 is default for HTTP):  
  EthernetClient webClient;  

  if (webClient.connect(webServer, 80)) {  
   Serial.println("connected to Logserver");  
   // Make a HTTP request:  
   webClient.println("GET /doring.php HTTP/1.0");  
   webClient.println();  
   if (webClient.available()) {  
    char c = webClient.read();  
    Serial.print(c);  
   }  
   // if the server's disconnected, stop the telnetClient:  
   if (!webClient.connected()) {  
    Serial.println();  
    Serial.println("disconnecting.");  

   }  
   webClient.stop();  
  }  
 }  

 /******************************************  
  * send an NTP request to the time server   
  * at the given address   
  ******************************************/  
 unsigned long sendNTPpacket(IPAddress& address)  
 {  
  Serial.print("Sending NTP packet to ");  
  for (byte thisByte = 0; thisByte < 4; thisByte++) {  
   // print the value of each byte of the IP address:  
   Serial.print(address[thisByte], DEC);  
   Serial.print(".");   
  }  
  Serial.println();  

  // 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();   
 }  

 /******************************************  
  * get the NTP time  
  ******************************************/  
 unsigned long getNTPtime()   
 {  
  Serial.println("Time synchronization starting");  
  IPAddress timeServer(64,147,116,229); // time.nist.gov NTP server  
  sendNTPpacket(timeServer); // send an NTP packet to a time server  
  delay(1000);   
  if ( Udp.parsePacket() ) {   
   // We've received a packet, read the data from it  
   Udp.read(packetBuffer,NTP_PACKET_SIZE); // read the packet into the buffer  

   //the timestamp starts at byte 40 of the received packet and is four bytes,  
   // or two words, long. First, esxtract the two words:  

   unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);  
   unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);   
   // combine the four bytes (two words) into a long integer  
   // this is NTP time (seconds since Jan 1 1900):  
   unsigned long secsSince1900 = highWord << 16 | lowWord;   
   Serial.print("Seconds since Jan 1 1900 = " );  
   Serial.println(secsSince1900);          

   // now convert NTP time into everyday time:  
   Serial.print("Unix time = ");  
   // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:  
   const unsigned long seventyYears = 2208988800UL;     
   // subtract seventy years:  
   unsigned long epoch = secsSince1900 - seventyYears;   

   //add correction for GMT+1  
   epoch = epoch + 3600;  
   //todo daylight saving time  

   // print Unix time:  
   Serial.println(epoch);                  
   return epoch;  
  }  
  return 0;  
 }  

 String checkMsg() {  
  String retstring = "";  
  if (message.length()>2)  
  {  
   for (int i=0; i<message.length(); i++) {  
    if (message[i]=='!')  
    {  
     //retval = true;  
     retstring = message.substring(0,i);  
     break;  
    }  
    if ((message[i]!='p') && (message[i]!='r') && ((message[i]<'0') || (message[i]>'9')))  
     break;  
   }  
  }  
  return retstring;  
 }  

I use SVN for version control. you can checkout the code soon from there. 

 

The Arduino doorbell project

My doorbell rings. That is what doorbells do. When it rings, it is mostly annoying, unless it is the pizza guy delivering a pizza. Some people ring the doorbell for several seconds or press it a couple of times, thinking that it makes people answer the door quicker. I just find it annoying. A simple solution would be to turn off the doorbell by installing a switch but for me that is way too simple; moreover you could forget switching the doorbell on again. Doorbells on the market that can be switched off or play a nice melody are vastly overpriced. Very often these are also wireless doorbells, leaving the wired system unused, which means that you have to give up the old doorbell, which is unlike the wireless one, maintenance free.

In my most creative moment I thought about creating a controllable doorbell with logging facility. The doorbell would ring briefly even if somebody presses it for 2 seconds, it should be silent at given hours and any doorbell button press would be logged into a database. Also the doorbell could ring on other sorts of activity, for instance when somebody wants to Skype you, when an email arrives or something else that’s important.

The answer to it is Arduino. The Arduino UNO that I have is ideal for this task. My last (and first) Arduino project was to pick up the DCF77 signal and get the time from the atomic clock. Basically the DCF77 signal is not necessary for the project, but the shield that I created for it has some room left which can be used to connect the doorbell switch and another peripheral like a piezo speaker. The piezo speaker will be nice for testing purposes. To really make this work the schematics of the current doorbell needs to be studied, I just looked for schematics on the Internet.

belaansluiting

The doorbell is connected to a power supply providing 8 Volts AC to the doorbell. In order control the doorbell separately from the doorbell switch the circuit has to be changed. Where now the doorbell switch is, the Arduino should be placed. A second circuit with the Arduino and the doorbell switch has to be created as well. The Arduino cannot handle AC power, also the current is around 1A, this is nothing for the senstive PINs of Transistor-transistor logic! The DFRobot relay shield seems to be able to do this task. It has four relays, so actually a little bit overkill.

IMGP9263-600x600

 

So now we have a little shopping list:

  • Arduino UNO (or another version, but this is the minimum)
  • Arduino Ethernet shield
  • DFRobot relay shield
  • DFRobot prototype shield, with DCF77 logic connected (not really required, but part of my existing project)
  • Piezo speaker (not really required)

The DCF77 is connected to pin 2, this is nice because we can attach an interrupt to it, which simplifies the code. Another interrupt can be attached to pin 3, so it seems good to connect the doorbell switch to that pin 3. The other boards also require some PINs and this leads to some interesting conflicts!

  • PIN 0 – unused
  • PIN 1 – unused
  • PIN 2 – relay shield, relay #1 & DCF77
  • PIN 3 – relay shield, relay #2 & Doorbell switch
  • PIN 4 – relay shield, relay #3  & Ethernet shield, SS MicroSD
  • PIN 5 – relay shield, relay #4
  • PIN 6 – unused
  • PIN 7 – unused
  • PIN 8 – unused
  • PIN 9 – Piezo speaker
  • PIN 10 – Ethernet shield, SS Ethernet
  • PIN 11 – Ethernet shield, MOSI
  • PIN 12 – Ethernet shield, MISO
  • PIN 13 – Ethernet shield, SCK

PIN 2 to PIN 5 are being used by the relay shield but also the DCF77 antenna, the doorbell switch and the Ethernet shield. So it would make sense that the relay shield moves out of the way. I found the “go between” shield that allows remapping of pins:

11002-01

I wanted relay #1 to be on PIN 5, relay #2 on PIN6, relay #3 on PIN7 and relay #4 on PIN8. After configuring it the shield looked like this:

130202_4858The only thing that now matters is the order of how the shield are stacked. The relay shield is on top, straight after the “go between” shield and after that either the Ethernet shield or the DCF77/piezo/doorbell switch shield. Hardware wise we are almost there, I attached a pull down resistor of 10K between PIN 3 and GND, the doorbell switch is placed between +5V and PIN3. The purpose of the pull down resistor is that in case the doorbell switch is open the remaining current or noise is burned and no tri-state occurs. Tri-state means that the state of the PIN 3 would be floating and the Arduino would not be able to determine the right state (which is 0).

Screen Shot 2013-02-03 at 12.09.25

Now we are ready to rock and roll. This bit of code proofs the concept:

 /******************************************  
  * Doorbell project  
  *  
  * $Revision: 3 $:  
  * $Author: hans $:  
  * $LastChangedDate: 2013-01-29 10:10:18 +0100 (Tue, 29 Jan 2013) $:  
  *  
  ******************************************/  

 #define DOORBELL_PIN 3  
 #define DOORBELL_INT 1 //only UNO and MEGA2560  

 #define RELAY1_PIN  5  
 #define RELAY2_PIN  6  
 #define RELAY3_PIN  7  
 #define RELAY4_PIN  8  
 #define PIEZO_PIN  9  

 void setup()   
 {  
  Serial.begin(57600);  
  Serial.println("Doorbell application");  
  attachInterrupt(DOORBELL_INT, ring, RISING);  
 }  

 void loop()  
 {  

 }  

 void ring()  
 {  
  Serial.println("Ring ring!");  
 }  

Now when I press the doorbell I can monitor the serial monitor and see something happening!

Screen Shot 2013-02-03 at 10.07.41

During this first test I noticed that sometimes pressing the doorbell switch caused the interrupt routine to be called twice, this is because one may press the switch halfway or it is caused by dust / debris in the switch. The ‘noise’ can be filtered with a small capacitor in parallel with the switch, but I decided to resolve this in software. I came up with the following solution, which also prevents multiple rings when people are impatient. The DOORBELL_TIME define is 5000, which means 5000 milliseconds, so the doorbell is only allowed to ring once every 5 seconds. the interrupt service routine is now called intDoorbell (before it was declared as ring).

update: I also noticed phantom doorbell interrupts, the doorbell would ring when nobody pressed the doorbell switch. It happened when somebody was touching metal in the proximity of the doorbell-switch or when somebody was switching on the light in house. To prevent this I added a delay of 50ms in the interrupt service and read the digital pin again. if it is not high anymore nobody pressed the doorbell and this was just a spike. This is called debounce, here you find an article about it. A hardware solution is on the way.

  #define RELAY_DOORBELL 5    
  #define DOORBELL_TIME 5000   // Minimal 5 seconds between the rings   
  unsigned char handle_ring = 0;   

 /******************************************  
  * interrupt handler when somebody presses  
  * the doorbell switch  
  ******************************************/  
 void intDoorbell()  
 {  
  byte doorbellValue;  
  unsigned long current_time = millis();  
  static unsigned long last_time = 0;  

  delay(50);   

  doorbellValue=digitalRead(DOORBELL_PIN);  

  // test whether the last doorbell press was long enough ago  
  if ((doorbellValue) && (current_time - last_time >= DOORBELL_TIME))  
  {  
    handle_ring = 1;  
    last_time = current_time;  
  }  
 }  

I declared a global variable handle_ring, the variable indicates that somebody pressed the switch, but only when meeting the criteria that a given time have passed since the last time the doorbell was pressed. It is good practice to keep interrupt service routines as short as possible, so business as usual can continue. the handle_ring variable is now handled (and cleared) by the loop() function.

 void loop()  
 {  
  if (handle_ring)  
  {  
   Serial.println("Ring ring!");  
   handle_ring = 0;  

   for (int i=0; i<2; i++)  
   {  
    bitWrite(PORTD,5,1);  
    delay(50);  
    bitWrite(PORTD,5,0);  
    delay(100);  
   }  
  }  
 }  

Although my doorbell rings now for only a split second (300 milliseconds), the project is not finished. Next is to install the Ethernet shield and communicate to my database on my NAS that somebody rang the doorbell. Please continue to follow this blog updates will be made… keep on reading in part 2 (Ethernetshield, logging in SQL and multitasking telnet service implementation)

Oh before i forget, I bought my Arduino hardware at iprototype.nl and floris.cc. The DCF77 receiver I bought via Conrad.  Special thanks to Frans Goddijn for some editorial work!

Bugatti

Dit is de Bugatti Veyron, zoals te zien in Autostadt van Volkswagen in Wolfsburg. Ik maakte de foto met mijn Fuji X100.
130131_4794
In 1988 toen we onze eerste video recorder kregen vond ik op een VHS tape een programma over Bugatti. Ik kende het merk van naam maar verder niet echt. De tape herinner ik me nog goed, er was een man die bij het horen van de naam Bugatti begon te huilen. Gelukkig heb ik hem nu terug gevonden op youtube.

How to fix Netgear GS105E when firmware update failed

Today I wanted to do a firmware update on my Netgear GS105E. This is a cheap managed switch with VLAN capabilities. It works well, the only disadvantage is that instead of a web interface it it comes with a small application to manage it. For unknown reasons Netgear wrote this application based on Adobe Air, and would only run on Windows, but it works well. Well … may be not so well, it failed during a firmware update, with a TFTP timeout occured! The switch worked after, but became un-manageable, the application reported ‘No switch exists in the local network’, very frustrating. After googling a bit I found that many people were struggling with the same problem. The answers were not really leading to a solution, but unwilling to give up, I started to investigate.

Screen Shot 2013-01-25 at 23.51.55

I found by doing a broadcast-ping that the switch had given itself the IP address 192.168.0.239. So I changed my computer’s IP address to be in the same subnet, but that didn’t help a lot. The application still could not manage the switch. On the Netgear forums I read something similar and people reported that they had to send in the switch to Netgear for repair. People reported that no ports were open and that the swich seemed to have died. but then I had a bright idea… let’s try TFTP the firmware to the switch. TFTP is a port that works over UDP, so it will never show that the port is open in a portscan. after launching the tftp program it was easy:

tftp> connect 192.168.0.239
tftp> put
(file) GS105E_V1.01.25.HEX
Sent 2588991 bytes in 66.3 seconds

Nothing happened initally after entering the put command, but then after a few second but the the activity LEDs started flickering on the switch, a very good sign! After the upload had finished I saw the lights go off for a second, indicating that the switch booted. After its reboot the device was back to its old IP and guess what… my switch was manageable again! Solved, hope this helps others running into the same issue.