The Yoctopuce API was designed to easily drive any Yoctopuce module with a minimum of code. It's therefore very easy to make an inventory of all the modules directly connected by USB to a machine. However, up till now, it was rather difficult, not to say impossible, to rebuild the exact tree structure of a system based on Yoctopuce modules, in particular for modules connected via the network. The latest version of the API now allows you to do this thanks to three new functions.
Question: can we detect the structure of this Yoctopuce test rig?
Local USB inventory
The functions needed to perform an inventory of all the Yoctopuce modules were there from the start. It's the functions FirstModule and nextModule which allow you to obtain the list of all the USB connected modules with 5 lines of code. Here is a C# example:
m = YModule.FirstModule();
while (m!=null)
{ Console.WriteLine(m.get_serialNumber());
m = m.nextModule();
}
The hub case
It's as easy to enumerate all the modules available by USB or by Ethernet: you only have to add a registerhub with the "net" parameter and to wait a little so that the network hubs have enough time to come forward before you start enumerating.
YAPI.RegisterHub("usb", ref errmsg)
YAPI.Sleep(2000, ref errmsg);
m = YModule.FirstModule();
while (m!=null)
{ Console.WriteLine(m.get_serialNumber());
m = m.nextModule();
}
This very simple piece of code provides the list of all the available modules, connected both by USB and on the local network. But there is a catch, it provides a flat list: we don't know which module is connected to which hub.
Listing modules connected to a hub
Up till now, the only way to enumerate the modules connected to a Yoctopuce hub was to enumerate its ports: the logical name of a hub port automatically takes the value of the serial number of the module connected to it. Here is a short piece of code enumerating all the functions of a hub, finding the ports, and using their logical name to retrieve the connected modules.
{ for (int i = 0; i < hub.functionCount(); i++)
{ string fctname = hub.functionId(i);
if (fctname.Length > 7 && fctname.Substring(0, 7) == "hubPort")
{ string logicalName = hub.functionName(i);
if (logicalName != "")
{ YModule m = YModule.FindModule(logicalName);
Console.WriteLine(m.get_serialNumber()+' '+m.get_productName());
}
}
}
}
This code is not perfect: it only provides the list of modules directly connected to the hub, you can't obtain with it the list of connected YoctoHub-Shields, and you can even less obtain the list of the modules connected to these shields. It doesn't allow you either to enumerate the modules driven by a VirtualHub, as a VirtualHub doesn't have a hubPort function.
The get_subDevices method
The YModule class offers a new method, get_subDevices, enabling you to directly obtain the serial number of all the modules connected to a hub, including the shields and the modules connected to the shields. Icing on the cake, it works with the VirtualHub as well. Here is a short usage example:
{ List<String> subDevices = hub.get_subDevices();
for (int i = 0; i < subDevices.Count; i++)
{ Console.WriteLine(subDevices[i]);
}
}
Note that the list of the sub-modules is returned as is. To know which module is connected on which port, you need to use the hubport function logical name trick, which works on the shields as well.
Building a dynamic tree structure
We saw how to build a static tree structure, that is how to make a snapshot of the tree structure of the modules available at a given time. But in real life, this tree structure can change all the time. We are dealing with plug-n-play modules which can be connected or disconnected at any time. We naturally could rebuild the tree structure at regular intervals, but this is not the most practical or efficient technique.
DeviceArrivalCallback and DeviceRemovalCallback
The API allows you to define two functions which are automatically called each time your application sees a new module appear or disappear. Here is a short example, still in C#:
{ Console.WriteLine("arrival: " + dev.get_serialNumber());
}
static void removalCallback(YModule dev)
{ Console.WriteLine("removal: " + dev.get_serialNumber());
}
static void Main(string[] args)
{ string errmsg="";
YAPI.RegisterHub("usb", ref errmsg);
YAPI.RegisterHub("net", ref errmsg);
YAPI.RegisterDeviceArrivalCallback(arrivalCallback);
YAPI.RegisterDeviceRemovalCallback(removalCallback);
while (true)
{ YAPI.UpdateDeviceList(ref errmsg);
YAPI.Sleep(1000, ref errmsg);
}
}
This method also provides a flat list of the modules as they appear. But the latest version of the Yoctopuce library contains two new methods in the YModule class, which allow your to rebuild the tree structure:
- YModule.get_url() returns a Yoctopuce module internal access path. It's a somewhat arcane piece of information which is usually of little interest for the average user, but it allows you to detect when a module is connected by USB directly: in this case, this method simply returns the "usb" character string.
- YModule.get_parentHub() returns the serial number of the parent hub of a module, or an empty string if there is no parent. Beware, in the case of a module connected to a shield, itself connected to a hub, the hub serial number is returned, not the shield's.
By using these two methods, you can easily rebuild the tree structure of a whole system by creating a tree-like data structure and by updating it each time a new module is connected or disconnected. For example, let's assume that KnownHubs is a list of objects of type KnownHub, that it's first element artificially corresponds to the list of modules connected by USB, the code for arrivalCallback would look like this, more or less:
{ string serial = dev.get_serialNumber();
if (dev.get_url() == "usb") // usb device case
{ KnownHubs[0].addSubdevice(dev);
}
else if (serial.Substring(0, 7) == "YHUBSHL") // shield
{ findHub(dev.get_parentHub()).addShield(dev);
}
else if (dev.get_parentHub() == "") // network hub or virtualhub
{ KnownHubs.Add(new KnownHub(dev));
} // regular device connected to a hub
else findHub(dev.get_parentHub()).addSubdevice(dev);
}
And you only need to use removalCallback to suppress from the tree modules which are disconnected. However, be careful, removalCallback does provide a YModule object as a parameter, but it corresponds to a module which disappeared, most likely because it was disconnected. Therefore, because you can't communicate with the module, the only information that this object can provide are the persistent information, such as the serial number or the module model. Thus, you can't ask which was the parent hub. You have to find the element to be removed from the tree by your own means, for example:
{ string serial = dev.get_serialNumber();
for (int i = KnownHubs.Count - 1; i >= 0; i--)
{ if (KnownHubs[i].get_serial() == serial)
{ KnownHubs.RemoveAt(i);
return;
}
if (KnownHubs[i].removeSubDevice(dev))
{ return;
}
}
}
Answer: Yes, we can.
Conclusion
It doesn't look like much but, thanks to the addition of these three methods to the YModule class, you can now build in real time the tree structure of all the modules connected to the network or by USB. If you want to explore the subject deeper, you can find in all the Yoctopuce libraries a directory called Prog-DevicesTree containing a full example to build this tree and to maintain it up to date.