public static List<KnownNXT> GetAvailableSamanthaConnectedNXTs()
        // Samantha's implement the 'Microchip discovery standard': we broadcast a UDP packet
        // to port 30303 that has (at least) a capital 'D' as its first byte. Samantha's see
        // this and respond with a unicast packet back to us containing a payload that we 
        // ignore (but see comment in DoReceive below). But the fact that they respond makes
        // us aware of their existence.
        //
        // To communicate with a Samantha, one uses TCP over port 2901 (FWIW: 2901 is the FTC
        // team number of the mentor who wrote the Samantha software). However, before one 
        // does so, one must acquire exclusive access to the module using a series of http
        // requests (see AcquireExclusiveUse below).
        //
        // An interesting (apparent) artifact of how the exclusivity is implemented is that a
        // Samantha which is locked to a particular host IP address will no longer be discoverable
        // (on port 30303) by subsequent requests from that IP host until the host relinquishes 
        // the lock. This is goodness, for otherwise, there'd be issues between two different 
        // applications on that host simultaneously attempting to acquire exclusivity.
        //
        // Useful impl notes: http://www.microchip.com/forums/m475301-print.aspx
            {
            Program.Trace("GetAvailableSamanthaConnectedNXTs");

            List<KnownNXT> result = new List<KnownNXT>();

            IPEndPoint epSend = new IPEndPoint(IPAddress.Broadcast, samanthaDiscoveryPort);
            IPEndPoint epRecv = new IPEndPoint(IPAddress.Any,       samanthaDiscoveryPort);
            byte[] rgbSend = System.Text.Encoding.ASCII.GetBytes("D");

            UdpClient udpClient         = new UdpClient();
            udpClient.EnableBroadcast   = true;
            udpClient.MulticastLoopback = false;
            udpClient.ExclusiveAddressUse = false;
            udpClient.Client.Bind(epRecv);

            int msSendInterval        = 250;
            int msWaitForNXTDiscovery = 1500 - msSendInterval;

            SamanthaDiscovery disc = new SamanthaDiscovery();
            DoReceive(udpClient, rgbSend, result, disc);

            udpClient.Send(rgbSend, rgbSend.Length, epSend);
            System.Threading.Thread.Sleep(msSendInterval);
            udpClient.Send(rgbSend, rgbSend.Length, epSend);

            // Wait a while for NXTs to get back to us. The duration used here has been 
            // only heuristically determined.
            System.Threading.Thread.Sleep(msWaitForNXTDiscovery);

            // Shut down the UDP client, synchronizing with the DoReceive
            lock (disc)
                {
                disc.fCanceled = true;
                udpClient.Close();
                Program.Trace("udpClient closed");
                }
            
            return result;
            }
        static void DoReceive(UdpClient udpClient, byte[] rgbSent, List<KnownNXT> result, SamanthaDiscovery discParam)
            {
            udpClient.BeginReceive((IAsyncResult ar) => { 
                //
                // Program.Trace("IPConnection.DoReceive");
                //
                SamanthaDiscovery disc = ar.AsyncState as SamanthaDiscovery;
                lock (disc)
                    {
                    if (!disc.fCanceled)
                        {
                        // Get the bytes for the incoming packet
                        IPEndPoint epSender = null;
                        byte[] rgbReceived = udpClient.EndReceive(ar, ref epSender);
                
                        // Post another request so we see all responses
                        DoReceive(udpClient, rgbSent, result, discParam);
                
                        // Note that we see our own initial transmission as well as responses from 
                        // actual Samantha modules. We'd like to distinguish the former as being 
                        // send to the broadcast address, but it doesn't appear that we can get that
                        // info here. So we just compare to the packet we sent.

                        if (rgbSent.IsEqualTo(rgbReceived))
                            {
                            // It's just us 
                            Program.Trace("samantha: saw our packet");
                            }
                        else
                            {
                            // It's a Samantha. The address of the Samantha we get from udpClient. The 
                            // payload, though isn't especially useful to us: it appears to be two text lines
                            // followed by a one-byte status code. The first line seems to be the Netbios name
                            // by which we could locate the module, and the second is a text form of the module's
                            // MAC address. Observed values for the status are 'A' for active, and 'O' for
                            // offline. So we'll just snarf the address away and go through the ProbeForNXT logic later
                            // like the other connection types.
                            //
                            // Update: we actually want to dig the name string out and parse it. May as well, just 
                            // in case the ProbeForNXT doesn't find anything.
                            //
                            Program.Trace("samantha: saw {0}", epSender.Address.ToString());

                            string sReceived = (new System.Text.ASCIIEncoding()).GetString(rgbReceived);
                            string[] lines   = sReceived.Lines(StringSplitOptions.RemoveEmptyEntries);

                            // First three chars are 'NXT' prepended to the actual NXT name. So a brick named 'Foo'
                            // will show up as 'NXTFoo'.
                            string sNXT = lines[0].SafeSubstring(3).Trim();

                            lock (result)   // the delegate is called on a worker thread with (to us) unknown concurrency
                                {
                                KnownNXT.AddKnownNXT(result, new KnownNXT(KnownNXT.CONNECTIONTYPE.IP, epSender.Address.ToString(), sNXT));
                                }
                            }
                        }
                    }
                }, discParam);
            }