Devices connecting to engine - acaprojects/ruby-engine GitHub Wiki

Sometimes an external service might want to push information to Engine. Whilst this isn't explicitly supported, it is quite easy to implement.

Common Examples

A good example of a Web Hook protocol is the Johnson Controls - P2000 Remote Monitoring module (where the swipe card security system sends events as staff swipe their cards).

  • TODO:: Example of consuming Email

Annotated Example Code

class Module::Code
    include ::Orchestrator::Constants

    def on_load
        on_update
    end

    def on_update
        # Ensure server is re-started on update (maybe the port has changed etc)
        on_unload

        # Configure server - we allow the port to be defined in settings here.
        port = setting(:port) || 38000

        # We want to use evented IO (don't want to block the thread)
        #                Bind Address, bind port, connection class, arguments
        @server = UV.start_server '0.0.0.0', port, SignalServer, logger, thread, self

        logger.info "server started"
    end

    def on_unload
        # It is up to you to manually stop any servers you start
        if @server
            @server.close
            @server = nil
            logger.info "server stopped"
        end
    end

    # Process data as it is provided by the connections
    # Pass back any data you want to be returned - or make information
    #  accessible from the @driver object in the connection class.
    def parse(request)
        self[:status] = ::JSON.parse(request)
        return {result: 'success'}
    end

    # The connection 'class' that will handle new connections to the server:
    #  (It is converted to a class and decorated with helper functions)
    module SignalServer
        # I would recommend passing in at least logger and driver
        # driver is a reference to your module (passed in as self above)
        def post_init(logger, thread, driver)
            @logger = logger
            @thread = thread
            @driver = driver

            @buffer = ::UV::BufferedTokenizer.new({
                indicator: "\x02",
                delimiter: "\x03"
            })
        end

        attr_reader :logger, :thread

        # This function is called once the connection is initialised
        #  Transport is the raw TCP connection object and this is the place to
        #  grab the remote ends IP address (could whitelist IPs for security)
        def on_connect(transport)
            ip, port = transport.peername
            logger.info "Connection from: #{ip}:#{port}"
        end

        # Any data the remote sends us comes in here.
        # Buffering needs to happen at the connection level. Not within the module.
        # It's also always good to have detailed error messages too
        def on_read(data, *args)
            begin
                @buffer.extract(data).each do |request|
                    logger.debug { "remote sent: #{request}" }

                    begin
                        # This is where we pass the request back to the module
                        result = @driver.parse request

                        # We can optionally respond to the remote device using write
                        write "\x02#{result.to_json}\x03"
                    rescue => e
                        logger.print_error(e, "error parsing request: #{request.inspect} in on_read callback")
                    end
                end
            rescue => e
                logger.print_error(e, "error extracting data: #{data.inspect} in on_read callback")
            end
        end
    end
end