Home AMX User Forum Duet/Cafe Duet
Options

Prevent Duet from 'stealing' the device

Afternoon fellow code monkeys, got an interesting one I'm battling with.

I'm currently in the process of attempting to wrap the RMS v3 NetLinx module inside a duet module so that my RMS integration can play a little nicer with the rest of my project.

I've been able to successfully instantiate the AMX RMS module within my duet module stub and get the test box to register with the server via a config sent to it from Duet - e.g. the stub just instantiates the duet module as standard, and also does a DEFINE_MODULE for the netlinx one contained, everything else happens in duet. Where I run into problems is after the I add the listeners on the duet side this 'steals' the NetLinx virtual away from the AMX RMS module. I've tried using a second proxy device (paired via define_combine as well as manual event handlers) but same thing, as soon as I need to start talking in duet land this severs the link and kills the NetLinx module.

How's that for convoluted? Any help greatly appreciated.

P.S. Yes, I know RMS 2020 is on the horizon and the SDK is duet based and will solve all these issues but I've got something I'm needing to roll out prior.

Comments

  • Options
    svTechsvTech Posts: 44
    Unfortunately, that is inherent to how Duet and Snapi work. You would have to create some minimal Netlinx code to pass strings/commands/channels from Duet to RMS and back again. You have to either use devices in Netlinx or Duet but you can't use the same device in both. Other than to use "send_command" to send packets into Duet or to receive packets via a Data_Event.
  • Options
    PhreaKPhreaK Posts: 966
    Ok, I've managed to put something together that does what is required. It's pretty rough at the moment and will definitely be getting hit with the refinement stick multiple times. But as a proof of concept here we go:

    First off, this is my duet stub:
    /**
     * Module stub for RMS duet wrapper
     *
     * This instantiates the AMX built RMSEngine module then passes this in to
     * allow all the funky usefull guff to be done within Duet.
     *
     * Copyright 2011 Queensland Department of Justice and Attorney General
     *
     */
    module_name='RMSBoltOn' (DEV duetDevice, DEV RMSSocket)
    
    define_device
    
    RMSEngine = 38001:1:0
    RMSEngineProxy = 38002:1:0
    CLActions = 0:0:0
    
    
    define_constant
    
    char PROXY_PREFIX[] = 'RMS-'
    
    
    define_variable
    
    // Setup Duet Module properties
    char duet_props[10][47] =
    {
    	'Physical-Device',
    	'Duet-Device',
    	'Proxy-Prefix',
    	'Duet-Module=RMSBoltOn',
    	'Bundle-Version=1.0.0',
    	'Device-Category=other',
    	'Device-Make=None',
    	'Device-Model=None',
    	'Device-SDKClass=com.amx.duet.devicesdk.Utility',
    	'Device-Revision=1.0.0'
    }
    
    
    define_start
    
    // We have to use a proxy so that when we steal the NetLinx virtual the
    // RMSEngineMod can continue to operate
    combine_devices(RMSEngine, RMSEngineProxy)
    
    // Load up device numbers as strings
    duet_props[1] = "'Physical-Device=',
    		FORMAT('%d:',RMSEngineProxy.NUMBER),
    		FORMAT('%d:',RMSEngineProxy.PORT),
    		FORMAT('%d',RMSEngineProxy.SYSTEM)"
    duet_props[2] = "'Duet-Device=',
    		FORMAT('%d:',duetDevice.NUMBER),
    		FORMAT('%d:',duetDevice.PORT),
    		FORMAT('%d',duetDevice.SYSTEM)"
    // and make sure that duet know's what prefix to look for
    duet_props[3] = "'Proxy-Prefix=',PROXY_PREFIX"
    
    // Load Duet Module
    load_duet_module(duet_props)
    
    // Load the RMS Engine
    define_module 'RMSEngineMod' mdlRMSEng(RMSEngine, RMSSocket, CLActions)
    
    
    /** 
     * This handles all the plumbing we need to get duet and the RMS NetLinx
     * module talking. Basically anything that comes from the real RMS engine is
     * passed to the duet virtual so that it can be processed. The duet module can
     * than talk back to the RMS NetLinx module via the proxy NetLinx virtual.
     *
     *                     /----->   duetDevice    ----->\
     *    Real RMS Engine <                                 > Duet RMS Module
     *                     \<----- RMSEngineProxy <-----/
     *
     * All strings and commands from the real RMS engine are prefixed with 'RMS-'
     * so that the string/command origin can be identified within the duet module.
     */
    define_event
    
    data_event[RMSEngine] {
    	string: {
    		send_string duetDevice, "PROXY_PREFIX,data.text"
    	}
    	command: {
    		send_command duetDevice, "PROXY_PREFIX,data.text"
    	}
    }
    
    channel_event[RMSEngine, 0] {
    	on: {
    		on[duetDevice, channel.channel]
    	}
    	off: {
    		off[duetDevice, channel.channel]
    	}
    }
    

    Then within my RMS module the following passes all the relevant events to my rmsEngine class which handles all the direct communication with the real rmsEngine and abstracts it into a nice duet friendly API.
    /**
     * Forward all the relevent events onto the real rmsEngine that we've
     * received via our proxy.
     * 
     * @param e
     *            an event object containing all the juicy event info
     */
    public void handleAdvancedEvent(Event e) {
    	switch (e.type) {
    	case Event.E_COMMAND:
    	case Event.E_STRING: {
    		String msg = new String((byte[]) e.dataValue);
    		String proxyPrefix = this.getProperty("Proxy-Prefix");
    		if (msg.startsWith(proxyPrefix)) {
    			log(DEBUG, "Forwarding data event to rmsEngine via the proxy");
    			e.dataValue = msg.substring(proxyPrefix.length()).getBytes();
    			rmsEngine.handleDataEvent(e);
    		}
    		break;
    	}
    	case Event.E_ON:
    	case Event.E_OFF: {
    		log(DEBUG, "Forwarding channel event to rmsEngine via the proxy");
    		rmsEngine.handleChannelEvent(e);
    		break;
    	}
    	case Event.E_ONLINE:
    	case Event.E_OFFLINE:
    	case Event.E_LEVEL: {
    		log(DEBUG, "Forwarding data event to rmsEngine via the proxy");
    		rmsEngine.handleDataEvent(e);
    		break;
    	}
    	}
    }
    

    Any thoughts, comments, criticisms and abuse appreciated.
  • Options
    PhreaKPhreaK Posts: 966
    Righty oh, the refinement stick beatings have been successfully. This is now all up and going with everything contained in a much simplified Duet stub.
    /**
     * Module stub for RMS duet wrapper
     *
     * This instantiates the AMX built RMSEngine module then passes this in to
     * allow all the funky usefull guff to be done within Duet.
     *
     * Copyright 2011 Queensland Department of Justice and Attorney General
     *
     */
    module_name='RMSBoltOn' (DEV duetDevice, DEV RMSSocket)
    
    define_device
    
    RMSEngine = 36001:1:0
    RMSEngineProxy = 36002:1:0
    CLActions = 0:0:0
    
    
    define_variable
    
    // Setup Duet Module properties
    char duet_props[9][47] =
    {
    	'Physical-Device',
    	'Duet-Device',
    	'Duet-Module=RMSBoltOn',
    	'Bundle-Version=1.0.0',
    	'Device-Category=other',
    	'Device-Make=None',
    	'Device-Model=None',
    	'Device-SDKClass=com.amx.duet.devicesdk.Utility',
    	'Device-Revision=1.0.0'
    }
    
    
    define_start
    
    // We have to use a proxy so that when we steal the NetLinx virtual the
    // RMSEngineMod can continue to operate
    combine_devices(RMSEngineProxy, RMSEngine)
    
    // Load up device numbers as strings
    duet_props[1] = "'Physical-Device=',
    		FORMAT('%d:',RMSEngineProxy.NUMBER),
    		FORMAT('%d:',RMSEngineProxy.PORT),
    		FORMAT('%d',RMSEngineProxy.SYSTEM)"
    duet_props[2] = "'Duet-Device=',
    		FORMAT('%d:',duetDevice.NUMBER),
    		FORMAT('%d:',duetDevice.PORT),
    		FORMAT('%d',duetDevice.SYSTEM)"
    
    // Load Duet Module
    load_duet_module(duet_props)
    
    // Load the RMS Engine
    define_module 'RMSEngineMod' mdlRMSEng(RMSEngine, RMSSocket, CLActions)
    
    
    define_event
    
    channel_event[RMSEngine, 0] {
    	on: { [RMSEngineProxy, channel.channel] = [RMSEngine, channel.channel] }
    	off: { [RMSEngineProxy, channel.channel] = [RMSEngine, channel.channel] }
    }
    

    That's it. The proxy acts as intended and the Duet module utilizes it the same way as it would talk to a physical device, no tricks required.

    The thing that did it was shifting the proxy virtual to be the first argument for the device combining. This not only stopped the doubling of messages (when a command was sent from the module it would be echoed twice thanks to the way NetLinx virtuals behave) but also allows the Duet side of things to initialize the device and treat it as though the AMX RMSEngine module was a normal physical device - listeners behave properly and all comms is nicely handled via the SDK, no translation and re-writes required.

    Again, if anyone can suggest improvements I'd be keen to listen.


    *edit:
    Needed to add in a channel event so that the module can be alerted to channel based feedback from the RMSEngine NetLinx module. As I'm only after socket, database and server online status I've just set this up as a one way thing. Code above updated.

    *edit 2:
    Modified the channel event to use state assignment rather than on[] and off[] keywords so that input, output and feedback channel events remain intact.
  • Options
    PhreaKPhreaK Posts: 966
    Couple more improvements
    - added dev to be used for the RMSEngine to module parameters to remove possibilty of conflicts
    - set internally assigned devices to utilize dynamic_virtual_device rather than static allocations
    - changed proxy link to use translate_device rather than combine_device and a channel event handler
    /**
     * Module stub for RMS duet wrapper
     *
     * This instantiates the AMX built RMSEngine module then passes this in to
     * allow all the funky usefull guff to be done within Duet.
     *
     * Copyright 2011 Queensland Department of Justice and Attorney General
     *
     */
    module_name='RMSBoltOn' (dev duetDevice, dev rmsEngine, dev RMSSocket)
    
    define_device
    
    RMSEngineProxy = dynamic_virtual_device
    CLActions = dynamic_virtual_device
    
    
    define_variable
    
    // Setup Duet Module properties
    char duet_props[9][47] =
    {
    	'Physical-Device',
    	'Duet-Device',
    	'Duet-Module=RMSBoltOn',
    	'Bundle-Version=1.0.0',
    	'Device-Category=other',
    	'Device-Make=None',
    	'Device-Model=None',
    	'Device-SDKClass=com.amx.duet.devicesdk.Utility',
    	'Device-Revision=1.0.0'
    }
    
    
    define_start
    
    // We have to use a proxy so that when we steal the NetLinx virtual the
    // RMSEngineMod can continue to operate
    translate_device(RMSEngineProxy, RMSEngine)
    
    
    // Load up device numbers as strings
    duet_props[1] = "'Physical-Device=',
    		FORMAT('%d:',RMSEngineProxy.NUMBER),
    		FORMAT('%d:',RMSEngineProxy.PORT),
    		FORMAT('%d',RMSEngineProxy.SYSTEM)"
    duet_props[2] = "'Duet-Device=',
    		FORMAT('%d:',duetDevice.NUMBER),
    		FORMAT('%d:',duetDevice.PORT),
    		FORMAT('%d',duetDevice.SYSTEM)"
    
    // Load Duet Module
    load_duet_module(duet_props)
    
    // Load the RMS Engine
    define_module 'RMSEngineMod' mdlRMSEng(RMSEngine, RMSSocket, CLActions)
    
Sign In or Register to comment.