class EmuPower::Api

Constants

LINE_TERMINATOR

Attributes

debug_mode[RW]

Public Class Methods

new(tty, debug: false) click to toggle source

Initialize the serial connection and set up internal structures.

# File lib/emu_power/api.rb, line 15
def initialize(tty, debug: false)

        @port = SerialPort.new(tty, 115200, 8, 1, SerialPort::NONE)

        # Get rid of any existing buffered data - we only want to operate on
        # fresh notifications.
        @port.flush_input
        @port.flush_output

        @debug_mode = debug

        reset_callbacks!

end

Public Instance Methods

callback(klass, &block) click to toggle source

Register the callback for specific notification events. Expects either an EmuPower::Notifications::Notification subclass, or :global, or :fallback. If :global is passed, the callback will be fired on every notification. If :fallback is passed, the callback will be fired for every notification that does not have a specific callback registered already.

# File lib/emu_power/api.rb, line 35
def callback(klass, &block)

        if klass == :global || klass == 'global'
                @global_callback = block
        elsif klass == :fallback || klass == 'fallback'
                @fallback_callback = block
        elsif EmuPower::Notifications::Notification.subclasses.include?(klass)
                @callbacks[klass] = block
        else
                klass_list = EmuPower::Notifications::Notification.subclasses.map(&:name).join(', ')
                raise ArgumentError.new("Class must be :global, :fallback, or one of #{klass_list}")
        end

        return true

end
issue_command(obj) click to toggle source

Send a command to the device. Expects an instance of one of the command classes defined in commands.rb. The serial connection must be started before this can be used.

# File lib/emu_power/api.rb, line 62
def issue_command(obj)

        return false if @thread.nil? || !obj.respond_to?(:to_command)

        xml = obj.to_command
        @port.write(xml)

        return true

end
reset_callbacks!() click to toggle source

Reset all callbacks to the default no-op state.

# File lib/emu_power/api.rb, line 53
def reset_callbacks!
        @global_callback = nil
        @fallback_callback = nil
        @callbacks = {}
end
start_serial(blocking = true) click to toggle source

Begin polling for serial data. We spawn a new thread to handle this so we don't block input. This method blocks until the reader thread terminates, which in most cases is never. This should usually be called at the end of a program after all callbacks are registered. If blocking is set to false, returns immediately and lets the caller handle the spawned thread. Non-blocking mode should mostly be used for development purposes; most production scripts should use blocking mode and callbacks.

# File lib/emu_power/api.rb, line 80
def start_serial(blocking = true)

        return false unless @thread.nil?

        @thread = Thread.new do

                # Define boundary tags
                root_elements = EmuPower::Notifications.notify_roots
                start_tags = root_elements.map { |v| "<#{v}>" }
                stop_tags = root_elements.map { |v| "</#{v}>" }

                current_notify = ''

                # Build up complete XML fragments line-by-line and dispatch callbacks
                loop do

                  line = @port.readline(LINE_TERMINATOR).strip

                        if start_tags.include?(line)
                                current_notify = line

                        elsif stop_tags.include?(line)

                                xml = current_notify + line
                                current_notify = ''

                                begin
                                        obj = EmuPower::Notifications.construct(xml)
                                rescue StandardError
                                        puts "Failed to construct object for XML fragment: #{xml}" if @debug_mode
                                        next
                                end

                                if obj
                                        puts obj if @debug_mode
                                        perform_callbacks(obj)
                                else
                                        puts "Incomplete XML stream: #{xml}" if @debug_mode
                                end

                        else
                                current_notify += line
                        end

                end
        end

        if blocking

                # Block until thread is terminated, and ensure we clean up after ourselves.
                begin
                        @thread.join
                ensure
                        stop_serial if @thread
                end

        else
                return @thread
        end

end
stop_serial() click to toggle source

Terminate the reader thread. The start_serial method will return once this is called. This will usually be called from a signal trap or similar, since the main program will usually be blocked by start_serial.

# File lib/emu_power/api.rb, line 146
def stop_serial

        return false if @thread.nil?

        @thread.terminate
        @thread = nil

        return true

end

Private Instance Methods

perform_callbacks(obj) click to toggle source

Dispatch the appropriate callback

# File lib/emu_power/api.rb, line 160
def perform_callbacks(obj)

        klass = obj.class

        # Fire global callback
        @global_callback&.call(obj)

        klass_specific = @callbacks[klass]
        if klass_specific
                klass_specific.call(obj)
        else
                @fallback_callback&.call(obj)
        end

end