nilFM

nerdblog - musings, art, etc. of Derek Stevens: drkste@zoho.com

Milestone Integration Platform SDK Part III: Heavy Wizardry

2021-03-02

This is part 3 of my deep dive into Milestone Systems' MIP SDK, in my attempts to create and edit AlarmDefinitions in the same capacity that the Milestone Management Client offers. See part 1 and part 2 for more... wierdness.

First, a bit of background on the types of objects that can be the sources of alarms. Among other things, they can be devices like Cameras, Servers, Relay Outputs, etc; They can be `UserDefinedEvent`s which are fired based on rules or by other alarms, or they can be `GenericEvent`s which are primitive network messages in plaintext matched against certain patterns (sort of the lowest common denominator to integrate with legacy hardware or other operating systems).

So while the draft pull request waits for the release cycle to catch up with it, I've been testing my Alarm Editor and I found a bug: where the Management Client shows GenericEvents as potential or actual sources of an alarm, my implementation doesn't.

The SourceList property of an AlarmDefinition is a string which is a list of Paths separated by commas. A path looks like: ItemType[GUID] where ItemType is the type of item of course, and GUID is a 48-bit hexadecimal identifier in the UUID format (Microsoft and their followers call them GUIDs instead of UUIDs). What I found was that if I pulled an AlarmDefinition off the server with a GenericEvent in its SourceList, the Path for the GenericEvent didn't match the corresponding Path obtained by traversing the tree of ConfigurationItems. They aren't even in the same form. Where the one obtained from the tree is GenericEvent[GUID], the one in the AlarmDefinition.SourceList is UserDefinedEvent[GUID'], where GUID' != GUID

And what's weird is that in a situation where my Alarm Editor should show a GenericEvent as a potential source (eg, in a dropdown box), if I force load options using the canonical Path of form GenericEvent[GUID] the AlarmDefinition is rejected by the server!

The Management Client actually... conforms? to this behavior, as it lists the GenericEvents in the same group as the UserDefinedEvents. But I have two questions I need to resolve:

1. Given one of these weird Paths, how do I translate it into a human-readable name to put in the user-interface?
2. How do I get one of these funky paths in the form UserDefinedEvent[GUID'] for each GenericEvent?

The first question was rather easy to answer. I used the constructor VideoOS.Platform.ConfigurationItems.GenericEvent(serverId, path) and passed it the UserDefinedEvent[GUID'] path given to me by an AlarmDefinition that already had a GenericEvent as a source. It actually gave me a valid GenericEvent back, and all the properties checked out, despite its misleading Path.

The second question was a lot harder to figure out...

As I mentioned, traversing the tree of ConfigurationItems on the server doesn't yield anything with a Path that looks like UserDefinedEvent[GUID']. But there is at least on other way to probe the server for its precious goodies.

The function that yielded no results was VideoOS.ConfigurationApi.ClientService.ClientProxy.GetChildren(path) starting on the root path / and called recursively on every child until we have the whole tree.

There is another function from a completely different namespace: VideoOS.Platform.Configuration.GetItems() which gives the top-level item (the server), and you can call Item.GetChildren() in a similar fashion and get a complete tree that way.

The Item class has a property FQID which can also be passed into a constructor in the VideoOS.Platform.ConfigurationItems namespace. So my thought was to try VideoOS.Platform.ConfigurationItems.GenericEvent(Item.FQID) on all the Items and see what comes back valid... What is weird is that a LOT of things came back valid, with all kinds of Paths, but none of them actually real GenericEvents. When I did some diagnostic logging, I noticed there were a few at the end of the tree that never got properly constructed. And there were four... Which is the same number of GenericEvents as our test server has.

So I checked the Item.Properties and found out that Item.Properties["EventType"] == "Generic". So OK, weird. So I whipped out the best debugger in the .NET world, try/catch/Console.WriteLine:


try {
GenericEvent g = new GenericEvent(i.FQID);
}
catch (Exception ex) {
Console.WriteLine(ex);
}


Turns out it throws a PathNotFoundMIPException and the Exception.Message claims the Path in the form InputEvent[GUID'] is invalid.

The fact that this Path claims to belong to an InputEvent is really weird, but we have an amazing piece of information: GUID' is exactly the same GUID' that we are looking for! It turns out this GUID' is present in the FQID and we can construct a valid Path by some old fashioned string-building to get our desired UserDefinedEvent[GUID'].

So my new procedure is, when constructing my dropdown options for a SourceList, if the ValueTypeInfoList claims that a UserDefinedEvent is a valid source for our selected categories, I traverse the Item tree and check if the Item.Properties["EventType"] == "Generic", and if so I dig in the FQID for the GUID' therein and build the path UserDefinedEvent[GUID'], and pass that to the two-argument constructor that takes the ServerId and the Path.

I had thought for a little bit in my experiments that I might actually have to parse the Exception.Message to get the accursed GUID', but luckily the FQID happens to expose it directly. I still posted on the Milestone developer forum regarding the incorrect item type in the FQID and they expressed curiosity with regards to the issue. Hopefully they can fix the library so I don't have to waste cycles re-traversing the server every time I want to fill my dropdown box...