# Monday, December 11, 2006

Over the last week of so I've noticed several questions in the newsgroups about how to synchronize a CE device's time with a time server.  Once again, it's not something I'd done, but I knew there were servers out there that provide the time publicly, so it couldn't bee too tough.

First stop was to see if Wikipedia gave a simple explanation.  Useful, but not exactly what we need - we want to know exactly what the server expects, and what exactly it returns.  It does give us the RFCs.

So a little looking at RFCs and we see that RFC 2030 is very applicable, and gives us all the info we need about the protocol.

A little more looking with Google found a public server domain name from NTP.org that actually rotates through public NTP servers, so we don't have to worry about one being up or not.

Armed with nothing but the second two links I wrote a little code.  This could be far more robust, with fractional seconds. mode, stratum, precision info and all that, but I just wanted to get a reasonable time - so to the second is all I was after.

public DateTime GetNTPTime()
{
   // 0x1B == 0b11011 == NTP version 3, client - see RFC 2030
   byte[] ntpPacket = new byte[] { 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

   IPAddress[] addressList = Dns.GetHostEntry("pool.ntp.org").AddressList;

   if (addressList.Length == 0)
   {
      // error
      return DateTime.MinValue;
   }

   IPEndPoint ep = new IPEndPoint(addressList[0], 123);
   UdpClient client = new UdpClient();
   client.Connect(ep);
   client.Send(ntpPacket, ntpPacket.Length);
   byte[] data = client.Receive(ref ep);

   // receive date data is at offset 32
   // Data is 64 bits - first 32 is seconds - we'll toss the fraction of a second
   // it is not in an endian order, so we must rearrange
   byte[] endianSeconds = new byte[4];
   endianSeconds[0] = (byte)(data[32 + 3] & (byte)0x7F); // turn off MSB (some servers set it)
   endianSeconds[1] = data[32 + 2];
   endianSeconds[2] = data[32 + 1];
   endianSeconds[3] = data[32 + 0];
   uint seconds = BitConverter.ToUInt32(endianSeconds, 0);

   return (new DateTime(1900, 1, 1)).AddSeconds(seconds);
}

Monday, December 11, 2006 9:59:10 AM (Central Standard Time, UTC-06:00)  #     | 
# Thursday, December 07, 2006

I was doing a search for some info just now and came across a really well done article describing drivers in Windows CE.  Check it out.

Thursday, December 07, 2006 5:17:45 PM (Central Standard Time, UTC-06:00)  #     | 
# Wednesday, December 06, 2006

We got a request today from someone wondering if the SDF would help send a Wake-on-LAN or Magic Packet.  Well I've never had to do it before, so I looked it up on Wikipedia. The short answer is no, but that's because all of the required pieces are already there in the CF.

Here's my guess on it - keep in mind that I don't have a WOL-capable PC lying around to test this with (if you test it and can confirm if it does or does not work, by all means let me know).

UPDATE (Dec 7, 06): Alex Feinman took the time to test the original code and the broadcast didn't work.  The code has been updated with working, tested code.

/// <summary>
/// Wakes a remote PC
/// </summary>
/// <param name="targetMAC">MAC address of target. Must be 6 bytes and MUST be in network order (reversed)</param>
/// <param name="password">Optional password. Must be null or 4 or 6 bytes.</param>
public static void WOL(byte[] targetMAC, byte[] password)
{
  // target mac must be 6-bytes!
  if (targetMAC.Length != 6)
  {
    throw new ArgumentException();
  }

  // check password
  if((password != null) && 
      (password.Length != 4) && 
      (password.Length != 6))
  {
    throw new ArgumentException();
  }

  int packetLength = 6 + (20 * 6);
  if (password != null)
  {
    packetLength += password.Length;
  }

 
byte
[] magicPacket = new byte[packetLength];

  // has a 6-byte header of 0xFF
  byte[] header = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
  Buffer.BlockCopy(header, 0, magicPacket, 0, header.Length);

  // repeat the destination MAC 16 times

  // your MAC *is* in network (reverse) order, right??
 
int
offset = 6;
  for(int i = 0 ; i < 16 ; i++)
  {
    Buffer.BlockCopy(targetMAC, 0, magicPacket, offset, targetMAC.Length);
    offset += 6;
  }

  if (password != null)
  {
    Buffer.BlockCopy(password, 0, magicPacket, offset, password.Length);
  }

  IPEndPoint ep = new IPEndPoint(IPAddress.Broadcast, 9);
  UdpClient c = new UdpClient();
 
c.Send(magicPacket, magicPacket.Length, ep);
}

Wednesday, December 06, 2006 4:29:35 PM (Central Standard Time, UTC-06:00)  #     | 
# Wednesday, November 08, 2006

If you attended one of my presentations at the MobileConnections conference, below are direct links to the PPTs and the Sample Applications I showed (if the links aren't active try again in a while as I FTP them up).

Compact Framework 2.0 and the Smart Device Framework 2.0
Presentation PPT
Sample Apps
Get the SDF here

Memory Management in the Compact Framework 2
Presentation PPT
Sample App
Blog Entry on Bitmaps and OOM

Sharing Assets between the CF and the Full Framework
Presentation PPT
ADT CodePlex project

Update (Nov 13, 06): The 'Sharing Assets..." presentation material originally came from Daniel Moth.  Unfortunately several very useful links in the footer of a few slides did not make the transition into the template for this show.  This omission does a disservice to those looking at the material, as they point to far deeper looks at some of the individual topics discussed.  Below are the links that I highly recommend that you visit, as they give much better insight than just the slides.

Slide 3: Visual Studio For Devices - See Resx compatibility for Smart Device projects
Slide 9: CF-Specific classes v2.0 - See Not a Strict subset
Slide 10: Windows Mobile 5.0 - See Microsoft.Windows.Mobile
Slide 11: CF CLR - See JIT
Slide 12: PPC versus Desktop - See Desktop to PPC (Part A)
Slide 16: CF Assemblies on PC - See Retagetable (=256)
Slide 17: Share the code, not the binary - See Share Code (#if FULL_FRAME)
Slide 20: How VS2005 & CF v2.0 improved the Everett story - See Deploy to My Computer

Wednesday, November 08, 2006 11:42:08 AM (Central Standard Time, UTC-06:00)  #     | 
# Tuesday, October 31, 2006

Some info that's worth reading:

Tuesday, October 31, 2006 10:45:58 AM (Central Standard Time, UTC-06:00)  #     | 

NETCFRPM is a great tool, but it's kind of obvious that it was a late addition to the SP1 SDK.  One big problem is the process of deploying.  There are a couple files that must be pushed to the device in order to run, and these are documented only in Steven Pratchner's blog (maybe elsewhere too, but they aren't in the RPM readme deployed with the SDK). Don't get me wrong, I'd rather have it as an afterthought than not at all.

At any rate, without these files you'll launch RPM, but a device connected via ActiveSync won't show up in the Device ComboBox. Once deployed, RPM pushes uses RAPI to do it's work.  What surprises me is that RPM uses RAPI for data transfer, so why doesn't it use RAPI at the start to find the device and push these files to begin with?  The likely answer is that the team just didn't have time to get it done.  Well, since my general philosophy is any time that I do the same operation more than three times, it needs to be automated.  This is especially true when I have to go hunting for files, (or even the list of files that I can't always recall).

So I created a simple bootstrap that does all this for you.  It requires the OpenNETCF.Desktop.Communication library, but that's about it.  It could be extended to correctly detect target processor architecture (currently just uses ARM), and even do the RAPI provisioning (see Steven Blog entry) for WM 5.0 devices that need it, but at the current moment it works with my devices, so it's unlikely that I'll make changes unless I start testing another device.

At any rate, here's the first cut (download the binary here):

namespace OpenNETCF.RPMBootstrap
{
  class Program
  {
    static void Main(string[] args)
    {
      RapiBootstrap();
    }

    private static OpenNETCF.Desktop.Communication.RAPI m_rapi;

    private static string[] m_filnames = new string[] 
   
      @"netcfrtl.dll",
      @"netcflaunch.exe"
    };

    private const string SDK_REG_KEY = @"SOFTWARE\Microsoft\.NETCompactFramework\v2.0.0.0\InstallRoot";
    private const string SOURCE_PATH = @"\WindowsCE\wce400\armv4";
    private const string TARGET_PATH = @"\Windows";
    private const string CE4_ARM_PATH = @"WindowsCE\wce400\armv4";
    private const string RPM_PATH = @"bin\NetCFRPM.exe";

    public static void RapiBootstrap()
    {
      System.Console.WriteLine("OpenNETCF RPMBootstrap Bootstrap");
      System.Console.WriteLine("================================");

      m_rapi = new OpenNETCF.Desktop.Communication.RAPI();

      System.Console.WriteLine("Connecting via ActiveSync...");
      m_rapi.Connect(true, 10000);

      if (!m_rapi.Connected)
      {
        System.Console.WriteLine("Connection FAILED");
        return;
      }
      System.Console.WriteLine("Connected");

      // determine SDK install folder
      Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(SDK_REG_KEY);
      string sdkPath = (string)key.GetValue(null);
      key.Close();
      System.Console.WriteLine("CF 2.0 SDK is installed at:\n\t" + sdkPath);
      string localFilePath = System.IO.Path.Combine(sdkPath, CE4_ARM_PATH);

      foreach (string fileName in m_filnames)
      {
        string targetFile = System.IO.Path.Combine(TARGET_PATH, fileName);

        if (!m_rapi.DeviceFileExists(targetFile))
        {
            System.Console.WriteLine(string.Format("{0} not on device", fileName));
            System.Console.Write("Copying...");
            string localFile = System.IO.Path.Combine(localFilePath, fileName);
            m_rapi.CopyFileToDevice(localFile, targetFile);
            System.Console.WriteLine("ok");
        }
        else
        {
            System.Console.WriteLine(string.Format("{0} already on device", fileName));
        }
      }
      System.Console.Write("Disconnecting...");
      m_rapi.Disconnect();
      System.Console.WriteLine("ok");

      // find RPM
      string rpmPath = System.IO.Path.Combine(sdkPath, RPM_PATH);

      if (!System.IO.File.Exists(rpmPath))
      {
        System.Console.WriteLine("Local RPM Binary not found at:\r\t" + rpmPath);
        return;
      }

      // launch
      System.Console.WriteLine("Launching RPM...");
      System.Diagnostics.Process p = System.Diagnostics.Process.Start(rpmPath);
    }
  }
}

Tuesday, October 31, 2006 10:35:18 AM (Central Standard Time, UTC-06:00)  #     |