more G-Labs products

Author Topic: SDK, plugins and more  (Read 3879 times)

March 16, 2014, 09:06:07 PM
Read 3879 times

smeghead

  • *
  • Information
  • Newbie
  • Posts: 17
Hi Gene,

Congrats on the new release, all your efforts are much appreciated.

I would like your opinion on something and please feel free to flame me  ;).   Ha Ha  ;D

Have you ever considered having a core system (for the service) and loading in 3rd party DLL that implement the MIGInterface as "plugins". You could provide a simple SDK to allow people like me to write plugins as a DLL and then load it in like this:

Code: [Select]
            string path = @"E:HomeGenie-masterHomeGenie_VS10VisonicPlugininDebugVisonicPlugin.dll";
            Assembly MyDLL = Assembly.LoadFrom(path); // DLL name of assembly
            Type[] types = MyDLL.GetTypes();

            // Loop through the types in the assembly until we find a class that implements MIGInterface.
            foreach (Type type in types)
            {
                Console.WriteLine("Type " + type);
                if (type.GetInterface("MIGInterface") != null)
                {
                    /* Create a new instance of the 'Module'. */
                    Console.WriteLine("Got one " + type);
                    MIGInterface migIF = (MIGInterface)Activator.CreateInstance(type);
                    Console.WriteLine("        Domain " + migIF.Domain);
                    // add it to the list of interfaces
                }
            }
           

All users could then put their plugins in to a common directory somewhere (part of system config). The service could load them by getting a list of all files in that directory and trying each in turn.

In this way, already built-in classes that implement MIGInterface would be no different to the externally loaded interfaces.

The SDK I mentioned could be very simple, essentially the MIGInterface and a few supporting libraries that you already have in the MIG area.

Using this, the X10 and ZWave could also be loadable DLLs (as well as my new Visonic code, that I have working by the way)

Is this something you may pursue and if so then I'm willing to give it a go as a prototype including putting together a simple SDK (and let you have all code of course). I have X10 kit (with a CM15Pro) and so would probably do that as the prototype first.

Any comment welcome, as always.......

March 25, 2014, 01:20:40 PM
Reply #1

smeghead

  • *
  • Information
  • Newbie
  • Posts: 17
Hi All,

Just a bit of an update. I've made a good start over the weekend and have a prototype SDK defined by moving several classes from the HG main build in to a new "library" DLL MS project. The HG main build and any plugins need to refer to the SDK so they use the same classes etc.

I haven't yet created an X10 plugin as the code is intricatly woven in to the HG main build and untangling it is taking me some time.  However, I do have my Visonic Alarm as a plugin. I build the plugin using only the SDK, create a DLL and then dynamically load it in using similar code as my previous post. I have used systemconfig.xml and added my visonic module with an extra "parameter" as the DLL file name. If this isn't the right way we can change it easily, it seemed like the best way to refer to a DLL file to load in dynamically rather than have a fixed directory location.

I've used (moved) MIGInterface to the SDK as the Plugin interface class but I needed to add 3 or 4 extra functions to get it to work correctly.

More details to follow........... any comments welcome

April 05, 2014, 02:08:47 PM
Reply #2

smeghead

  • *
  • Information
  • Newbie
  • Posts: 17
Hi All,

Yet another update......

I'm ready to request updates to the core of HomeGenie and I'm using my experience of trying to implement a driver.  Please also remember that I'm a Java programmer and am learning C# as I progress, so I might not use the correct terminology.

This is the start of my story to implement my visonic alarm code in to HG.  I started by looking at the ExampleDriver.cs and I worked with a copy of that for a while to get my visonic code built in and compiled.

However, I quickly realized that I couldn't do anything with it.  After searching through lots of the HG code (and learning C#) the problem I had is that I couldn't use "modules", specifically the HG module.cs class. These seem to be at the core of the HG service and nothing gets done in HG if you get them wrong, as I quickly found out.  I then had 2 choices, I implement my modules in HomeGenieService.cs in the same way as X10/ZWave OR I extend the MIGInterface to make it more generic. I thought I would try the latter because HomeGenieService.cs seems to be getting more and more detailed code for different plugins/functions and is therefore getting more complicated.

So to fix this what I am requesting is 2 things:
1. That the following be added to the MIGInterface.cs interface:
Code: [Select]
       
        /// <summary>
        /// update the modules
        /// </summary>
        /// <returns></returns>
        TsList<Module> performModuleUpdate(TsList<Module> currentList);
        /// <summary>
        /// get response
        /// </summary>
        /// <returns></returns>
        string getResponse(MIGInterfaceCommand command, Module module);
        /// <summary>
        /// request module update
        /// </summary>
        /// <returns></returns>
        event Action<bool> requestModuleUpdate;
There is an impact on this and I had to move a few classes in to MIG such as module.cs, moduleparameter.cs and valuestatistics.cs.  I also had to give TsList its own class file TsList.cs. There are lots of build errors but all are due to the "using" being wrong now for those classes.
I also had to relocate:
Code: [Select]
        public static ModuleParameter ModuleParameterGet(Module module, string propertyName)
        public static ModuleParameter ModuleParameterSet(Module module, string propertyName, string propertyValue)
        public static string WaitModuleParameterChange(Module module, string parameterName)
And put these in all classes that implement MIGInterface:
Code: [Select]
        public TsList<Module> performModuleUpdate(TsList<Module> currentList) { return currentList; }
        public string getResponse(MIGInterfaceCommand command, Module module) { return ""; }
        public event Action<bool> requestModuleUpdate;
with appropriate "using" statements
The bool parameter for requestModuleUpdate will be used later to request an update for this module or for all modules.
Also, more on the use of getResponse later.......

2. That the code in HomeGenieService.cs call these functions, specifically performModuleUpdate for the interface to recreate the modules lists. Also, that HomeGenieService.cs responds to events for "requestModuleUpdate" when a module calls it. Essentially, that is for a MIG to request its modules to be updated.

I can take the latest svn from sourceforge, only make the above changes and then upload/attach them (or PM them) if you would like me to.

Also in my latest build I have added this to MIGInterface:
void Configure(SystemConfiguration config);
This is used to configure the plugin. I have also separated MIGInterface and the associated classes to a separate DLL library and can use that to build standalone plugins. So more to follow if you are interested.
In my build I have made several more changes to implement X10 and ZWave using this new interface.

Please let me know if you are interested, that's all for now.....


April 05, 2014, 04:48:02 PM
Reply #3

smeghead

  • *
  • Information
  • Newbie
  • Posts: 17
Hi Again,

Thought I would add my visonic version of performModuleUpdate as an example

Code: [Select]
        public TsList<Module> performModuleUpdate(TsList<Module> currentList)
        {
            foreach( string key in controller.ModulesStatus.Keys )
            {
                SensorState sensor = controller.ModulesStatus[key];
                Module module = null;
                try
                {
                    module = currentList.Find(delegate(Module o)
                    {
                        return o.Domain == Domain && o.Address == sensor.identifier.ToString();
                    });
                }
                catch
                {
                }
                // add new module
                if (module == null)
                {
                    module = new Module();
                    module.Domain = Domain;
                    if (key == stateMessage)
                    {
                        module.Name = stateMessage;
                        module.Address = stateMessage;
                        module.DeviceType = HGModule.DeviceTypes.Generic;
                        module.Description = "Visonic Panel Status";
                    }
                    else if (key == configMessage)
                    {
                        module.Name = configMessage;
                        module.Address = configMessage;
                        module.DeviceType = HGModule.DeviceTypes.Generic;
                        module.Description = "Visonic Panel Config";
                        ModuleParameter.ModuleParameterSet(module, "ipaddress", _tcpaddress);
                        ModuleParameter.ModuleParameterSet(module, "port", ""+_tcpport);
                    }
                    else
                    {
                        module.Address = sensor.identifier.ToString();
                        module.DeviceType = Module.DeviceTypes.Sensor;
                        module.Description = "Visonic Panel Sensor";
                    }
                    currentList.Add(module);
                }
                if (module != null)
                {
                    if (key != stateMessage && key != configMessage)
                    {
                        module.Name = sensor.Name;
                        module.Description = sensor.message;
                        ModuleParameter.ModuleParameterSet(module, "Active", "" + sensor.active);
                        ModuleParameter.ModuleParameterSet(module, "BatteryLow", "" + sensor.batteryLow);
                        ModuleParameter.ModuleParameterSet(module, "Bypassed", "" + sensor.bypassed);
                        //ModuleParameter.ModuleParameterSet(module, "identifier", "" + sensor.identifier);
                        ModuleParameter.ModuleParameterSet(module, "Message", "" + sensor.message);
                        ModuleParameter.ModuleParameterSet(module, "Tamper", "" + sensor.tamper);
                        ModuleParameter.ModuleParameterSet(module, "Tripped", "" + sensor.tripped);
                        ModuleParameter.ModuleParameterSet(module, "ZoneName", sensor.zoneName);
                        ModuleParameter.ModuleParameterSet(module, "ZoneType", sensor.zoneType);
                    }
                }
            }

            return currentList;
        }


Where the code refers to controller and sensors, they are internal to my code.

I noticed on your last set of release notes that you plan on changing TsList anyway.

In HomeGenieService.cs I have incorporated the following code across a few places where modules seem to get updated. An example for which is in modules_RefreshAll():

Code: [Select]
            foreach (MIGServiceConfiguration.Interface iface in systemConfiguration.MIGService.Interfaces)
            {
                modules_RefreshGeneric(iface.Domain);
            }

The code for modules_RefreshGeneric is here
Code: [Select]
        internal void modules_RefreshGeneric(string domain)
        {
            try
            {
                if (systemConfiguration.GetInterface(domain).IsEnabled)
                {
                    MIGInterface mif = GetInterface(domain);
                    if (mif != null)
                    {
                        systemModules = mif.performModuleUpdate(systemModules);
                    }
                }
                else
                {
                    systemModules.RemoveAll(m => m.Domain == domain && m.RoutingNode == "");
                }
            }
            catch (Exception ex)
            {
                HomeGenieService.LogEvent(Domains.HomeAutomation_HomeGenie, "modules_RefreshGeneric(" + domain + ")", ex.Message, "Exception.StackTrace", ex.StackTrace);
            }
        }

The code in HomeGenieService.cs for requestModuleUpdate is here:
Code: [Select]
        private void requestModuleUpdate(bool v)
        {
            modules_RefreshAll();
            modules_Sort();
        }

In LoadSystemConfig() i have "attached" the event
Code: [Select]
                foreach (var iface in systemConfiguration.MIGService.Interfaces)
                {
                    var migInterface = GetInterface(iface.Domain);
                    // add event handler (when the interface requests a module update)
                    try {
                        if (migInterface != null)
                        {
                            migInterface.requestModuleUpdate += requestModuleUpdate;
                            //Thread.Sleep(1000);
                            migInterface.Configure(systemConfiguration);

                            bool isConnected = migInterface.IsConnected;
                            if (iface.IsEnabled && !isConnected)
                            {
                                migInterface.Connect();
                            }
                        }
                    }
                    catch { }
                }


I have also rewritten  modules_Sort() to be generic:
Code: [Select]
        internal void modules_Sort()
        {

            // sort modules properties by name
            foreach (var module in systemModules)
            {
                // various normalization stuff
                module.Properties.Sort((ModuleParameter p1, ModuleParameter p2) =>
                {
                    return p1.Name.CompareTo(p2.Name);
                });
            }
            //
            // sort modules
            //
            systemModules.Sort((HGModule m1, HGModule m2) =>
            {
                try
                {
                    string l1 = "";
                    int n1 = 0;
                    int m1_index = m1.Address.IndexOfAny(new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' });
                    int m2_index = m2.Address.IndexOfAny(new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' });

                    if (m1_index >= 1)
                    {
                        l1 = m1.Address.Substring(0, m1_index);
                        int.TryParse(m1.Address.Substring(m1_index), out n1);
                    }
                    else
                    {
                        int.TryParse(m1.Address, out n1);
                    }

                    string l2 = "";
                    int n2 = 0;
                    if (m2_index >= 1)
                    {
                        l2 = m2.Address.Substring(0, m2_index);
                        int.TryParse(m2.Address.Substring(m2_index), out n2);
                    }
                    else
                    {
                        int.TryParse(m2.Address, out n2);
                    }
                    string d1 = m1.Domain;
                    if (d1.StartsWith("EmbeddedSystems."))
                    {
                        d1 = "z|" + d1;
                    }
                    string d2 = m2.Domain;
                    if (d2.StartsWith("EmbeddedSystems."))
                    {
                        d2 = "z|" + d2;
                    }
                    return ((d1 + "|" + l1 + n1.ToString("00000")).CompareTo(d2 + "|" + l2 + n2.ToString("00000")));
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Caught Sort Exception :" + ex.Message);
                }
                return 0;
            });
        }


Purely for interest, this is how I load in an external DDL as a plugin. The following goes in LoadSystemConfig().
Code: [Select]
                foreach (MIGServiceConfiguration.Interface iface in systemConfiguration.MIGService.Interfaces)
                {
                    var migInterface = GetInterface(iface.Domain);
                    if (migInterface == null)
                    {
                        string val = iface.findOption("Plugin");
                        if (val.Length > 0)
                        {
                            migService.AddPluginInterface(iface.Domain, val);
                        }
                        else
                        {
                            migService.AddInterface(iface.Domain);
                        }
                    }
                }

AddPluginInterface is
Code: [Select]
        public MIGInterface AddPluginInterface(string domain, string path)
        {
            Assembly MyDLL = Assembly.LoadFrom(path); // DLL name of assembly
            Type[] types = MyDLL.GetTypes();

            // Loop through the types in the assembly until we find a class that implements MIGInterface.
            foreach (Type type in types)
            {
                if (type.GetInterface("MIGInterface") != null)
                {
                    return createMIGInterface(domain, type);
                }
            }
            return null;
        }

And createMIGInterface is
Code: [Select]
       private MIGInterface createMIGInterface(string domain, Type type)
        {
            // add it to the list of interfaces
            MIGInterface migInterface = (MIGInterface)Activator.CreateInstance(type);
            Interfaces.Add(domain, migInterface);
            migInterface.InterfacePropertyChangedAction += new Action<InterfacePropertyChangedAction>(MigService_InterfacePropertyChanged);
            //TODO: implement eventually a RemoveInterface method containing code:
            // mif.InterfacePropertyChangedAction -= MIGService_InterfacePropertyChangedAction;
            return migInterface;
        }

That's all for now.....
« Last Edit: April 05, 2014, 04:52:39 PM by smeghead »

April 05, 2014, 05:14:03 PM
Reply #4

Gene

  • *****
  • Information
  • Administrator
  • Posts: 1472
  • Tangible is the future!
    • Yet Another Programmer
Hi smeghead,

TsList will be kept.
A generic way for refreshing Interface's modules is a good thing I've been thinking about too. But it will require more code rework as I'd lke to keep MIG as a generic library and less HomeGenie dependant.
So let's think more about it :) I don't feel like it's the right moment for such rework, but I appreciate your work and we'll find a way later to abstract this part.
I would indeed like to proceed with automatic dll loading as it was already implemented in very earlier version of MIG (2011):

https://sourceforge.net/p/mono-mig/code/HEAD/tree/MultiInputGateway/ClientLibraries/Mono/MIGlib/MIGHost.cs#l156

Cheers,
g.

April 11, 2014, 01:36:51 PM
Reply #5

smeghead

  • *
  • Information
  • Newbie
  • Posts: 17
Hi Gene,

OK, it's good to know that you're already considering improvements in these areas. I will stop what I am doing on my "experimental" build and embody minimal changes in to the latest release and use that. As new releases are created I can then take a small amount of time to re-do the minimal changes I need to make. That way I will get your latest updates combined with the changes I need for my alarm.  I may do it by creating a new interface class that extends MIGInterface with the extra functions I need, I'll take a look at it.

Do you have any idea of timescales when you would consider these kind of changes?

Cheers
Smeghead

October 19, 2014, 11:58:00 AM
Reply #6

smeghead

  • *
  • Information
  • Newbie
  • Posts: 17
Hi Gene,

I'm just wondering if you've made any changes to this area of homegenie since April. I didn't keep making the updates as each version of homegenie was rolled out and have just kept with my experimental build.  I feel that it is time that I upgraded my homegenie to the latest version and I am wondering if I should wait for a formal embodied plugin interface to homegenie or should I just take the latest source code and embody my changes in again.  Any advice would be appreciated.

Thanks