Showing posts with label GPS. Show all posts
Showing posts with label GPS. Show all posts

Sunday, August 21, 2016

On the GPS features of WiFiChron

The WiFiChron GPS-synchronized clock was begging for some documentation on the menu options and some details on its features. Here they are.

The GPS-WiFiChron uses TinyGPS library to read the time from NMEA sentences (specifically GPRMC and GPGGA strings) from the uBlox Neo GPS module. The clock starts reading the data from the GPS module (using SoftwareSerial library, receiving on pin D7) 2 minutes after power up.

Once a valid GPRMC or GPGGA sentence is received, the minutes and seconds (but not the hours) are automatically re-set from the satellite UTC time. The successful synchronization is indicated by the "up arrow" at the end of the scrolling date (e.g. Sunday August 21, 2016 ^).

In order to automatically set the hours as well, I added a new menu option, GPS? (Yes/No). When this is selected (set to "Yes"), the local hour is calculated based on the longitude, by estimating the timezone using a simple formula:

// estimate time zone from longitude;
int8_t gpstimezone = round(floatLongitude / 15);
...
hour = gpshour + gpstimezone;

This formula will not work everywhere in the world, since not all time offsets are 15 degrees apart (take for example Newfoundland). So, if you live in one of these places, the GPS auto-sync will not work for you. In this case, make sure that the GPS option is set to "No".

The second GPS-specific menu item is DST? (Yes/No). Its purpose is to adjust the hour according to the North American DST rules (that is, moving one hour ahead in the spring and one hour back in the fall, at specified dates and times). When DST is set to "Yes" (meaning Daylight Saving Time is in effect), the hour is incremented by 1 if the date is between the second Sunday in March and first Sunday in November. The DST hour adjustment works based on the date set up by the user (from the buttons, menu option "Date").
If DST is not observed in the place you live (most Equatorial countries, but also some Canadian provinces like Saskatchewan), then select "No" for DST.

To recap:
  • regardless of the GPS and DST menu settings, the minutes and the seconds are always synchronized with the satellite time;
  • if you want the hours synchronized as well, then set GPS menu option to "Yes";
  • if you want the clock to adjust itself with DST changes (in spring and fall), then set the DST to "Yes";
  • if you want the clock to set its complete time (hours, minutes, seconds) automatically from the satellite, then set the GPS menu option to "Yes", then restart it (power on) and wait a few minutes until a valid sentence is received from the satellite;
  • the date must be set correctly for the DST to adjust the hours (if the DST menu option is set to "Yes").

As an interesting side note, I should mention that AdamM, a customer who bought a WiFiChron GPS-synchronized clock, pointed out that the time is off by tens of seconds compared to any other GPS clock he had (this one, for example). After investigation, I found a few problems in the code (which are now fixed), the main one being that DS1307 library I was using reset the seconds in the start() function (!).

For those interested, below is the relevant (GPS-specific) code, which could be adapted for any other GPS clock.

boolean checkGPS()
{
  boolean newData = false;

  // for one second, parse GPS data for relevant time values;
  for (unsigned long start = millis(); millis() - start < 1000;)
  {
    while (ss.available())
    {
      char c = ss.read();
#ifdef _DEBUG_
      Serial.write(c); // uncomment this line if you want to see the GPS data flowing
#endif
      if (gps.encode(c)) // Did a new valid sentence come in?
        newData = true;
    }
  }

  if (newData)
  {
    float flat, flon;
    unsigned long age, fix_age;
    int gpsyear;
    byte gpsmonth, gpsday, gpshour, gpsminute, gpssecond, hundredths;

    gps.f_get_position(&flat, &flon, &age);
    gps.crack_datetime(&gpsyear, &gpsmonth, &gpsday, &gpshour, &gpsminute, &gpssecond, &hundredths, &fix_age);

    // estimate time zone from longitude;
    int8_t gpstimezone = round(flon/15);

#ifdef _DEBUG_
    char buf[50] = {0};
    sprintf(buf, "GPS TIME=%02d:%02d:%02d, GPS DATE=%4d/%02d/%02d", gpshour, gpsminute, gpssecond+1, gpsyear, gpsmonth, gpsday);
    Serial.println();
    Serial.println(buf);
    Serial.print("Timezones from GPS data is ");
    Serial.println(gpstimezone);
#endif

    // determine if the time is off (and therefore must be set);
    if (gpsminute != minute || gpssecond != second)
    {
      minute = gpsminute;
      second = gpssecond;

      if (gpsAutoHour)
      {
        // automatically set the hour based on timezone/longitude;
        hour = gpshour + gpstimezone;

        // set to summer time if DST is selected;
        if (doDST && isSummerTime())
        {
          hour++;
        }
        // make sure hours stays between 0 and 23, otherwise setTime() will fail;
        if (hour < 0)
          hour = hour + 24;
        else if (hour > 24)
          hour = hour - 24;
      }

      setTime(hour, minute, second);

#ifdef _DEBUG_
      Serial.print("RTC synced from GPS to ");
      Serial.print(hour);
      Serial.print(":");
      Serial.print(minute);
      Serial.print(":");
      Serial.println(second);

      Serial.print(">>>Checking if the time was set correctly... ");
      getTimeFromRTC();
#endif
    }
  }

  return newData;
}

// determines if it is the moment to change the hour;
// also set the dstAdjust to either 1 or -1;
boolean isDstMoment()
{
  if (minute != 0 || second !=0)
    return false;
    
  // in the second Sunday in March, 2 -> 3;
  if (month == 3)
  {
    // check for second Sunday;
    if (dow == 1 && day >=8 && day <=14)
    {
#ifdef _DEBUG_
      Serial.println("switching to summer time; add one hour");
#endif
      dstAdjust = 1;
      return true;
    }
  }
  // in the first Sunday in November, 2 -> 1;
  else if (month == 11)
  {
    // check for first Sunday;
    if (dow == 1 && day >=1 && day <=7)
    {
#ifdef _DEBUG_
      Serial.println("switching to winter time; subtract one hour");
#endif
      dstAdjust = -1;
      return true;
    }
  }

  dstAdjust = 0;
  return false;
}


// determines if it is the moment to change the hour;
// also set the dstAdjust to either 1 or -1;
boolean isSummerTime()
{
  unsigned long summerStartTime = 0;
  unsigned long winterStartTime;

  if (month < 3 || month > 11)
    return false;

  // find the second Sunday in March; start with March 8th;
  for (int iDay=8; iDay<15 font="" iday="">
  {
    if (zellersDow(year, 3, iDay) == 1)  // is it Sunday?
    {
      summerStartTime = makeUnixTime(year, 3, iDay, 2, 0, 0);
      break;
    }
  }
  
  // find the first Sunday in November;
  for (int iDay=1; iDay<8 font="" iday="">
  {
    if (zellersDow(year, 11, iDay) == 1)  // is it Sunday?
    {
      winterStartTime = makeUnixTime(year, 11, iDay, 2, 0, 0);
      break;
    }
  }

  time_t timeNow = makeUnixTime(year, month, day, hour, minute, second);
  if (timeNow > summerStartTime && timeNow < winterStartTime)
    return true;
  else
    return false;
}


Saturday, March 26, 2016

GPS-synchronized Mondrian clock

The challenge was to add GPS to this "basic LED matrix clock".


Since I don't see the point of a GPS clock that does not show seconds, I had to figure out how to fit 6 digits on the 8x16 LED matrix. One way to do it is this:


as used by the "Matrix Clock V1.0". Kind of hard to distinguish between 0 and 8 though.

Another way is based on MixiClock, where 4 digits are crammed in a 8x8 (tri-color) matrix. (This was more than 4 years ago. Incredible how little progress I made since.)

As for the name, I settled for "Mondrian" because Kandinsky was already taken :)



The hours are shown in red, the minutes in green and the seconds in orange. After power up, the seconds blink until the GPS time is received (less than 5 minutes in my house, more than 3 meters away from the nearest window). Only the minutes and the seconds are synchronized (but not the hours).

The Mondrian clock is made of 2 boards: the wsduino with the GPS Bee plugged in (and the antenna affixed on the back with double sided tape, as shown in the next photo), and the LED-mini-display shield.


The GPS Bee module is connected on the hardware serial port (there are only a couple of pins left available, D2 and D17).
The clock has two buttons (on the LED matrix shield) used for incrementing the hours and the minutes respectively. The GPS synchronization occurs every 10 hours. That should be more than enough, considering that the highly accurate DS3231 on the wsduino board is responsible for timekeeping.
The clock, powered by 5V, consumes less than 200mA.
The sketch can be found here (compiles on Arduino IDE 1.0.6 and has dependencies on TinyGPS and DS1307 libraries).


Sunday, February 14, 2016

New Wise Clock 4 software release

The latest release of the Wise Clock 4 software can be found here.

It includes a few new features:
The WiFi settings are configured in the SD card file message.txt, and they look like this:

#### ESP8266 configuration
#
# Esp8266.ssid Wireless SSID to connect to
# Esp8266.phrase pass phrase to use (WPA authentication)
# Esp8266.sntp optional SNTP server IP address (not hostname) for time synchronization
# Esp8266.rssurl optional URL of an RSS feed, like http://w1.weather.gov/xml/current_obs/KMYF.rss

Esp8266.ssid   BELL475
Esp8266.phrase 7xyz1E9F6
Esp8266.sntp   
Esp8266.rssurl 

This feature is enabled in the code by the following line in file UserConf.h:
#define WANT_ESP8266
  • support for GPSBee (from seeedstudio): only the minutes and seconds are set from the GPS data, the hours remain as set from the buttons; synchronization is performed once a day and/or when the clock is reset (powered off-on);
To enable this feature, make sure the following line in UserConf.h is not commented out:
#define WANT_GPS
  • "Night" mode: turns the display off at night, between the hours set in message.txt file, as shown:
#### turn display off at night (in conjunction with menu "NIGHT");
#### formatted as HHMM (military time);
Night.start 2230
Night.end   600

This feature is enabled/disabled from the NIGHT+/- option in the SETUP menu. In Night mode, the display shows just a random dot. The implementation is in AppNight.class, and can be changed to display any desired effect (e.g. Bezier splines, Lissajous curve etc).
  • "Unix time" mode: displays advancing Unix time (passing seconds from Jan 1, 1970); accessed through the "UNIX" menu option.
Also, debugging (sending messages to Serial port) is now controlled by a single line in UserConf.h:
#define _DEBUG_

Before compiling and uploading the software, make sure the debug macro above is commented out (since it is not in the code you just downloaded). With the debug disabled, the code runs faster and takes less memory space.