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