Class: EmuPower::Api

Inherits:
Object
  • Object
show all
Defined in:
lib/emu_power/api.rb

Constant Summary collapse

LINE_TERMINATOR =
"\r\n"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tty, debug: false) ⇒ Api

Initialize the serial connection and set up internal structures.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 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

Instance Attribute Details

#debug_modeObject

Returns the value of attribute debug_mode.



12
13
14
# File 'lib/emu_power/api.rb', line 12

def debug_mode
  @debug_mode
end

Instance Method Details

#callback(klass, &block) ⇒ Object

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.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 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) ⇒ Object

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.



62
63
64
65
66
67
68
69
70
71
# 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!Object

Reset all callbacks to the default no-op state.



53
54
55
56
57
# File 'lib/emu_power/api.rb', line 53

def reset_callbacks!
	@global_callback = nil
	@fallback_callback = nil
	@callbacks = {}
end

#start_serial(blocking = true) ⇒ Object

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.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# 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_serialObject

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.



146
147
148
149
150
151
152
153
154
155
# File 'lib/emu_power/api.rb', line 146

def stop_serial

	return false if @thread.nil?

	@thread.terminate
	@thread = nil

	return true

end