katcp 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +110 -0
- data/examples/listdev.rb +33 -0
- data/lib/katcp/client/roach.rb +141 -0
- data/lib/katcp/client.rb +258 -0
- data/lib/katcp/irb.rb +3 -0
- data/lib/katcp/response.rb +209 -0
- data/lib/katcp/util.rb +73 -0
- data/lib/katcp/version.rb +7 -0
- data/lib/katcp.rb +2 -0
- metadata +91 -0
data/README
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--
|
2
|
+
|
3
|
+
This file includes an RDoc hack so that links can be made to open up into a
|
4
|
+
new frame rather then the current frame.
|
5
|
+
|
6
|
+
The "trick" is to append ``"target="_top'' to the URL.
|
7
|
+
|
8
|
+
++
|
9
|
+
|
10
|
+
= Ruby/KATCP
|
11
|
+
|
12
|
+
== Introduction
|
13
|
+
|
14
|
+
<b>Ruby/KATCP</b> is a Ruby extension for communicating via the Karoo Array
|
15
|
+
Telescope Control Protocol (KATCP). More information about KATCP can be
|
16
|
+
found at the {CASPER
|
17
|
+
Wiki}[http://casper.berkeley.edu/wiki/KATCP"target="_top], including the {KATCP
|
18
|
+
specification
|
19
|
+
}[http://casper.berkeley.edu/wiki/images/1/11/NRF-KAT7-6.0-IFCE-002-Rev4.pdf"target="_top]
|
20
|
+
in PDF format.
|
21
|
+
|
22
|
+
== Features
|
23
|
+
|
24
|
+
* Provides a base KATCP client supporting the functionality described in the
|
25
|
+
KATCP specification.
|
26
|
+
|
27
|
+
* Provides an extended KATCP client to support the <tt>tcpborphserver2</tt>
|
28
|
+
KATCP server implementation on {ROACH
|
29
|
+
}[http://casper.berkeley.edu/wiki/ROACH"target="_top] boards.
|
30
|
+
|
31
|
+
* Automatically provides support for all current and any future KATCP request
|
32
|
+
types through the use of Ruby's +method_missing+ feature.
|
33
|
+
|
34
|
+
* Handles all escaping and unescaping of text transferred over KATCP.
|
35
|
+
|
36
|
+
* Provides convenient and flexible ways of converting packed binary data to
|
37
|
+
various numerical formats, including Array and {NArray
|
38
|
+
}[http://narray.rubyforge.org/"target="_top] objects.
|
39
|
+
|
40
|
+
* Plays nicely with +irb+, the interactive Ruby shell.
|
41
|
+
|
42
|
+
== Installation
|
43
|
+
|
44
|
+
{Ruby/KATCP}[http://rb-katcp.rubyforge.org/"target="_top] can be installed
|
45
|
+
via RubyGems[http://www.rubygems.org/"target="_top]:
|
46
|
+
|
47
|
+
gem install katcp
|
48
|
+
|
49
|
+
Ruby/KAT<b></b>CP was developed using Ruby 1.8.6 and 1.8.7. It has not been
|
50
|
+
tested against Ruby 1.9 (yet).
|
51
|
+
|
52
|
+
== Documentation
|
53
|
+
|
54
|
+
Documentation is available on the {Ruby/KATCP
|
55
|
+
Homepage}[http://rb-katcp.rubyforge.org/"target="_top] and is also installed
|
56
|
+
locally as part of the gem-based installation. To view the documentation
|
57
|
+
locally, run
|
58
|
+
|
59
|
+
gem server
|
60
|
+
|
61
|
+
then point your browser to <tt>http</tt><tt>://localhost:8808/</tt> and
|
62
|
+
navigate to (and follow) the katcp <b>[rdoc]</b> link. You can get more
|
63
|
+
information about RubyGem's documenation server by running:
|
64
|
+
|
65
|
+
gem help server
|
66
|
+
|
67
|
+
== Example
|
68
|
+
|
69
|
+
This example script, <tt>listdev.rb</tt>, can be used to list the devices on a
|
70
|
+
given ROACH host...
|
71
|
+
|
72
|
+
#!/usr/bin/env ruby
|
73
|
+
|
74
|
+
# listdev.rb - list devices on a ROACH
|
75
|
+
#
|
76
|
+
# $ listdev.rb roach030111
|
77
|
+
# XAUI0
|
78
|
+
# acc_len
|
79
|
+
# ant_levels
|
80
|
+
# ctrl
|
81
|
+
# gbe0
|
82
|
+
# gbe_ip0
|
83
|
+
# gbe_port
|
84
|
+
# [...]
|
85
|
+
|
86
|
+
require 'rubygems'
|
87
|
+
require 'katcp'
|
88
|
+
|
89
|
+
# Abort if no hostname is given
|
90
|
+
raise "\nusage: #{$0} HOSTNAME" unless ARGV[0]
|
91
|
+
|
92
|
+
# Create RoachClient object
|
93
|
+
roach = KATCP::RoachClient.new(ARGV[0])
|
94
|
+
|
95
|
+
# Get list of devices via "?listdev" KATCP request.
|
96
|
+
# Save returned KATCP::Response object in "resp".
|
97
|
+
resp = roach.listdev
|
98
|
+
|
99
|
+
# Extract first "word" of each line except the last
|
100
|
+
# one, which will be a "!listdev ok" reply line.
|
101
|
+
devices = resp.lines[0..-2].map {|l| l[1]}
|
102
|
+
|
103
|
+
# Output device list
|
104
|
+
puts devices
|
105
|
+
|
106
|
+
== Current Limitations
|
107
|
+
|
108
|
+
* Only the client side is implemented.
|
109
|
+
* Only TCP communication is supported (i.e. no RS/232 support).
|
110
|
+
* KATCP::RoachClient#uploadbof is not yet implemented.
|
data/examples/listdev.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# listdev.rb - list devices on a ROACH
|
4
|
+
#
|
5
|
+
# $ listdev.rb roach030111
|
6
|
+
# XAUI0
|
7
|
+
# acc_len
|
8
|
+
# ant_levels
|
9
|
+
# ctrl
|
10
|
+
# gbe0
|
11
|
+
# gbe_ip0
|
12
|
+
# gbe_port
|
13
|
+
# [...]
|
14
|
+
|
15
|
+
require 'rubygems'
|
16
|
+
require 'katcp'
|
17
|
+
|
18
|
+
# Abort if no hostname is given
|
19
|
+
raise "\nusage: #{$0} HOSTNAME" unless ARGV[0]
|
20
|
+
|
21
|
+
# Create RoachClient object
|
22
|
+
roach = KATCP::RoachClient.new(ARGV[0])
|
23
|
+
|
24
|
+
# Get list of devices via "?listdev" KATCP request.
|
25
|
+
# Save returned KATCP::Response object in "resp".
|
26
|
+
resp = roach.listdev
|
27
|
+
|
28
|
+
# Extract first "word" of each line except the last
|
29
|
+
# one, which will be a "!listdev ok" reply line.
|
30
|
+
devices = resp.lines[0..-2].map {|l| l[1]}
|
31
|
+
|
32
|
+
# Output device list
|
33
|
+
puts devices
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'katcp/client'
|
2
|
+
|
3
|
+
# Holds KATCP related classes etc.
|
4
|
+
module KATCP
|
5
|
+
|
6
|
+
# Facilitates talking to <tt>tcpborphserver2</tt>, a KATCP server
|
7
|
+
# implementation that runs on ROACH boards. Mostly just adds convenience
|
8
|
+
# methods for tab completion in irb.
|
9
|
+
#
|
10
|
+
# TODO: Add conveneince methods for converting binary payload data.
|
11
|
+
class RoachClient < Client
|
12
|
+
|
13
|
+
# call-seq:
|
14
|
+
# delbof(image_file) -> KATCP::Response
|
15
|
+
#
|
16
|
+
# Deletes gateware image file named by +image_file+.
|
17
|
+
def delbof(image_file)
|
18
|
+
request(:delbof, image_file)
|
19
|
+
end
|
20
|
+
|
21
|
+
# call-seq:
|
22
|
+
# echotest(ip_address, echo_port, byte_count) -> KATCP::Response
|
23
|
+
#
|
24
|
+
# Basic network echo tester.
|
25
|
+
def echotest(ip_address, echo_port, byte_count)
|
26
|
+
request(:echotest, ip_address, echo_port, byte_count)
|
27
|
+
end
|
28
|
+
|
29
|
+
# call-seq:
|
30
|
+
# listbof -> KATCP::Response
|
31
|
+
#
|
32
|
+
# Lists available gateware images.
|
33
|
+
def listbof
|
34
|
+
request(:listbof).sort!
|
35
|
+
end
|
36
|
+
|
37
|
+
# call-seq:
|
38
|
+
# listdev -> KATCP::Response
|
39
|
+
#
|
40
|
+
# Lists available registers.
|
41
|
+
def listdev
|
42
|
+
request(:listdev, :size).sort!
|
43
|
+
end
|
44
|
+
|
45
|
+
# call-seq:
|
46
|
+
# progdev -> KATCP::Response
|
47
|
+
# progdev(image_file) -> KATCP::Response
|
48
|
+
#
|
49
|
+
# Programs a gateware image specified by +image_file+. If +image_file+ is
|
50
|
+
# omitted, de-programs the FPGA.
|
51
|
+
def progdev(*args)
|
52
|
+
request(:progdev, *args)
|
53
|
+
end
|
54
|
+
|
55
|
+
# call-seq:
|
56
|
+
# read(register_name) -> KATCP::Response
|
57
|
+
# read(register_name, register_offset) -> KATCP::Response
|
58
|
+
# read(register_name, register_offset, byte_count) -> KATCP::Response
|
59
|
+
#
|
60
|
+
# Reads a +byte_count+ bytes starting at +register_offset+ offset from
|
61
|
+
# register (or block RAM) named by +register_name+. Binary data can be
|
62
|
+
# obtained from the Response via the Response#payload method.
|
63
|
+
def read(register_name, *args)
|
64
|
+
request(:read, register_name, *args)
|
65
|
+
end
|
66
|
+
|
67
|
+
# call-seq:
|
68
|
+
# status -> KATCP::Response
|
69
|
+
#
|
70
|
+
# Reports if gateware has been programmed.
|
71
|
+
def status
|
72
|
+
request(:status)
|
73
|
+
end
|
74
|
+
|
75
|
+
# call-seq:
|
76
|
+
# sysinit -> KATCP::Response
|
77
|
+
#
|
78
|
+
# Writes the timing ctl register that resets the entire system.
|
79
|
+
#
|
80
|
+
# [Presumably this depends on a certain register naming convention?]
|
81
|
+
def sysinit
|
82
|
+
request(:sysinit)
|
83
|
+
end
|
84
|
+
|
85
|
+
# call-seq:
|
86
|
+
# tap_start(tap_device register_name, ip_address) -> KATCP::Response
|
87
|
+
# tap_start(tap_device register_name, ip_address, port) -> KATCP::Response
|
88
|
+
# tap_start(tap_device register_name, ip_address, port, mac) -> KATCP::Response
|
89
|
+
#
|
90
|
+
# Start a tgtap instance.
|
91
|
+
def tap_start(tap_device, register_name, ip_address, *args)
|
92
|
+
request(:tap_start, tap_device, register_name, ip_address, *args)
|
93
|
+
end
|
94
|
+
|
95
|
+
# call-seq:
|
96
|
+
# tap_stop(register_name) -> KATCP::Response
|
97
|
+
#
|
98
|
+
# Stop a tgtap instance.
|
99
|
+
def tap_stop(register_name)
|
100
|
+
reqest(:tap_stop, register_name)
|
101
|
+
end
|
102
|
+
|
103
|
+
# call-seq:
|
104
|
+
# uploadbof(net_port, filename) -> KATCP::Response
|
105
|
+
# uploadbof(net_port, filename, size) -> KATCP::Response
|
106
|
+
#
|
107
|
+
# Upload a gateware image.
|
108
|
+
#
|
109
|
+
# NOT YET IMPLEMENTED
|
110
|
+
def uploadbof(net_port, filename, *args)
|
111
|
+
raise NotImplementedError.new('uploadbof not yet implemented')
|
112
|
+
end
|
113
|
+
|
114
|
+
# call-seq:
|
115
|
+
# wordread(register_name) -> KATCP::Response
|
116
|
+
# wordread(register_name, word_offset) -> KATCP::Response
|
117
|
+
# wordread(register_name, word_offset, length) -> KATCP::Response
|
118
|
+
#
|
119
|
+
# Reads word(s) from +register_name+.
|
120
|
+
def wordread(register_name, *args)
|
121
|
+
request(:wordread, register_name, *args)
|
122
|
+
end
|
123
|
+
|
124
|
+
# call-seq:
|
125
|
+
# wordwrite(register_name, word_offset, payload[, ...]) -> KATCP::Response
|
126
|
+
#
|
127
|
+
# Writes one or more words to a register (or block RAM).
|
128
|
+
def wordwrite(register_name, word_offset, *args)
|
129
|
+
request(:wordwrite, register_name, word_offset, *args)
|
130
|
+
end
|
131
|
+
|
132
|
+
# call-seq:
|
133
|
+
# write(register_name, register_offset, data_payload) -> KATCP::Response
|
134
|
+
#
|
135
|
+
# Write a given payload to an offset in a register.
|
136
|
+
def write(register_name, register_offset, data_payload)
|
137
|
+
request(:write, register_name, register_offset, data_payload)
|
138
|
+
end
|
139
|
+
|
140
|
+
end # class RoachClient
|
141
|
+
end # module KATCP
|
data/lib/katcp/client.rb
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
require 'katcp/response'
|
5
|
+
require 'katcp/util'
|
6
|
+
|
7
|
+
# Holds KATCP related classes etc.
|
8
|
+
module KATCP
|
9
|
+
|
10
|
+
# Facilitates talking to a KATCP server.
|
11
|
+
class Client
|
12
|
+
|
13
|
+
# Creates a KATCP client that connects to a KATCP server at +remote_host+
|
14
|
+
# on +remote_port+. If +local_host+ and +local_port+ are specified, then
|
15
|
+
# those parameters are used on the local end to establish the connection.
|
16
|
+
def initialize(remote_host, remote_port=7147, local_host=nil, local_port=nil)
|
17
|
+
|
18
|
+
# Save remote_host and remote_port for #inspect
|
19
|
+
@remote_host = remote_host
|
20
|
+
@remote_port = remote_port
|
21
|
+
|
22
|
+
# @socket is the socket connecting to the KATCP server
|
23
|
+
@socket = TCPSocket.new(remote_host, remote_port, local_host, local_port)
|
24
|
+
|
25
|
+
# Init attribute(s)
|
26
|
+
@informs = []
|
27
|
+
|
28
|
+
# @reqlock is the Monitor object used to serialize requests sent to the
|
29
|
+
# KATCP server. Threads should not write to @socket or read from @rxq
|
30
|
+
# or change @reqname unless they have acquired (i.e. synchonized on)
|
31
|
+
# @reqlock.
|
32
|
+
@reqlock = Monitor.new
|
33
|
+
|
34
|
+
# @reqname is the request name currently being processed (nil if no
|
35
|
+
# current request).
|
36
|
+
@reqname = nil
|
37
|
+
|
38
|
+
# @rxq is an inter-thread queue
|
39
|
+
@rxq = Queue.new
|
40
|
+
|
41
|
+
# Start thread that reads data from server.
|
42
|
+
Thread.new do
|
43
|
+
# TODO: Monkey-patch gets so that it recognizes "\r" or "\n" as line
|
44
|
+
# endings. Currently only recognizes fixed strings, so for now go with
|
45
|
+
# "\n".
|
46
|
+
while line = @socket.gets("\n") do
|
47
|
+
# Split line into words and unescape each word
|
48
|
+
words = line.split.map! {|w| w.katcp_unescape!}
|
49
|
+
# Handle requests, replies, and informs based on first character of
|
50
|
+
# first word.
|
51
|
+
case words[0][0,1]
|
52
|
+
# Request
|
53
|
+
when '?'
|
54
|
+
# TODO Send 'unsupported' reply (or support requests from server?)
|
55
|
+
# Reply
|
56
|
+
when '!'
|
57
|
+
# TODO: Raise exception if name is not same as @reqname?
|
58
|
+
# TODO: Raise exception on non-ok?
|
59
|
+
# Enqueue words to @rxq
|
60
|
+
@rxq.enq(words)
|
61
|
+
# Inform
|
62
|
+
when '#'
|
63
|
+
# If the name is same as @reqname
|
64
|
+
if @reqname && @reqname == words[0][1..-1]
|
65
|
+
# Enqueue words to @rxq
|
66
|
+
@rxq.enq(words)
|
67
|
+
else
|
68
|
+
# Must be asynchronous inform message, add to list.
|
69
|
+
line.katcp_unescape!
|
70
|
+
line.chomp!
|
71
|
+
@informs << line
|
72
|
+
end
|
73
|
+
else
|
74
|
+
# Malformed line
|
75
|
+
# TODO: Log error better?
|
76
|
+
warn "malformed line: #{line.inspect}"
|
77
|
+
end
|
78
|
+
|
79
|
+
end # @socket.each_line block
|
80
|
+
|
81
|
+
warn "Read on socket returned EOF"
|
82
|
+
#TODO Close socket? Push EOF flag into @rxq?
|
83
|
+
end # Thread.new block
|
84
|
+
end
|
85
|
+
|
86
|
+
# TODO: Have a log_handler?
|
87
|
+
|
88
|
+
# call-seq:
|
89
|
+
# request(name, *arguments) -> KATCP::Response
|
90
|
+
#
|
91
|
+
# Sends request +name+ with +arguments+ to server. Returns KATCP::Response
|
92
|
+
# object.
|
93
|
+
#
|
94
|
+
# TODO: Raise exception if reply is not OK?
|
95
|
+
def request(name, *arguments)
|
96
|
+
# Massage name to allow Symbols and to allow '_' between words (since
|
97
|
+
# that is more natural for Symbols) in place of '-'
|
98
|
+
name = name.to_s.gsub('_','-')
|
99
|
+
|
100
|
+
# Escape arguments
|
101
|
+
arguments.map! {|arg| arg.to_s.katcp_escape}
|
102
|
+
|
103
|
+
# Create response
|
104
|
+
resp = Response.new
|
105
|
+
|
106
|
+
# Get lock on @reqlock
|
107
|
+
@reqlock.synchronize do
|
108
|
+
# Store request name
|
109
|
+
@reqname = name
|
110
|
+
# Send request
|
111
|
+
req = "?#{[name, *arguments].join(' ')}\n"
|
112
|
+
@socket.print req
|
113
|
+
# Loop on reply queue until done or error
|
114
|
+
begin
|
115
|
+
words = @rxq.deq
|
116
|
+
resp << words
|
117
|
+
end until words[0][0,1] == '!'
|
118
|
+
# Clear request name
|
119
|
+
@reqname = nil
|
120
|
+
end
|
121
|
+
resp
|
122
|
+
end
|
123
|
+
|
124
|
+
# call-seq:
|
125
|
+
# inform(clear=false) -> Array
|
126
|
+
#
|
127
|
+
# Returns Array of inform messages. If +clear+ is +true+, clear messages.
|
128
|
+
def informs(clear=false)
|
129
|
+
msgs = @informs
|
130
|
+
@informs = [] if clear
|
131
|
+
msgs
|
132
|
+
end
|
133
|
+
|
134
|
+
# Translates calls to missing methods into KATCP requests.
|
135
|
+
def method_missing(sym, *args)
|
136
|
+
request(sym, *args)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Provides terse string representation of +self+.
|
140
|
+
def to_s
|
141
|
+
s = "#{@remote_host}:#{@remote_port}"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Provides more detailed String representation of +self+
|
145
|
+
def inspect
|
146
|
+
"#<#{self.class.name} #{to_s} (#{@informs.length} inform messages)>"
|
147
|
+
end
|
148
|
+
|
149
|
+
# call-seq:
|
150
|
+
# client_list -> KATCP::Response
|
151
|
+
#
|
152
|
+
# Issues a client_list request to the server.
|
153
|
+
def client_list
|
154
|
+
request(:client_list)
|
155
|
+
end
|
156
|
+
|
157
|
+
# call-seq:
|
158
|
+
# configure(*args) -> KATCP::Response
|
159
|
+
#
|
160
|
+
# Issues a configure request to the server.
|
161
|
+
def configure(*args)
|
162
|
+
request(:configure, *args)
|
163
|
+
end
|
164
|
+
|
165
|
+
# call-seq:
|
166
|
+
# halt -> KATCP::Response
|
167
|
+
#
|
168
|
+
# Issues a halt request to the server. Shuts down the system.
|
169
|
+
def halt
|
170
|
+
request(:halt)
|
171
|
+
end
|
172
|
+
|
173
|
+
# call-seq:
|
174
|
+
# help -> KATCP::Response
|
175
|
+
# help(name) -> KATCP::Response
|
176
|
+
#
|
177
|
+
# Issues a help request to the server. If +name+ is a Symbol, all '_'
|
178
|
+
# characters are changed to '-'. Response inform lines are sorted.
|
179
|
+
def help(*args)
|
180
|
+
# Change '_' to '-' in Symbol args
|
181
|
+
args.map! do |arg|
|
182
|
+
arg = arg.to_s.gsub('-', '_') if Symbol === arg
|
183
|
+
arg
|
184
|
+
end
|
185
|
+
request(:help, *args).sort!
|
186
|
+
end
|
187
|
+
|
188
|
+
# call-seq:
|
189
|
+
# log_level -> KATCP::Response
|
190
|
+
# log_level(priority) -> KATCP::Response
|
191
|
+
#
|
192
|
+
# Query or set the minimum reported log priority.
|
193
|
+
def log_level(*args)
|
194
|
+
request(:log_level, *args)
|
195
|
+
end
|
196
|
+
|
197
|
+
# call-seq:
|
198
|
+
# mode -> KATCP::Response
|
199
|
+
# mode(new_mode) -> KATCP::Response
|
200
|
+
#
|
201
|
+
# Query or set the current mode.
|
202
|
+
def mode(*args)
|
203
|
+
request(:mode, *args)
|
204
|
+
end
|
205
|
+
|
206
|
+
# call-seq:
|
207
|
+
# restart -> KATCP::Response
|
208
|
+
#
|
209
|
+
# Issues a restart request to the server to restart the remote system.
|
210
|
+
def restart
|
211
|
+
request(:restart)
|
212
|
+
end
|
213
|
+
|
214
|
+
# call-seq:
|
215
|
+
# sensor_dump(*args) -> KATCP::Response
|
216
|
+
#
|
217
|
+
# Dumps the sensor tree.
|
218
|
+
def sensor_dump(*args)
|
219
|
+
request(:sensor_dump, *args)
|
220
|
+
end
|
221
|
+
|
222
|
+
# call-seq:
|
223
|
+
# sensor_list(*args) -> KATCP::Response
|
224
|
+
#
|
225
|
+
# Queries for list of available sensors. Response inform lines are sorted.
|
226
|
+
def sensor_list(*args)
|
227
|
+
request(:sensor_list, *args).sort!
|
228
|
+
end
|
229
|
+
|
230
|
+
# call-seq:
|
231
|
+
# sensor_sampling(sensor) -> KATCP::Response
|
232
|
+
# sensor_sampling(sensor, strategy, *parameters) -> KATCP::Response
|
233
|
+
#
|
234
|
+
# Quesry or set sampling parameters for a sensor.
|
235
|
+
def sensor_sampling(sensor, *args)
|
236
|
+
request(:sensor_sampling, sensor, *args)
|
237
|
+
end
|
238
|
+
|
239
|
+
# call-seq:
|
240
|
+
# sensor_value(sensor) -> KATCP::Response
|
241
|
+
#
|
242
|
+
# Query a sensor.
|
243
|
+
def sensor_value(sensor)
|
244
|
+
request(:sensor_value, sensor)
|
245
|
+
end
|
246
|
+
|
247
|
+
# call-seq:
|
248
|
+
# watchdog -> KATCP::Response
|
249
|
+
#
|
250
|
+
# Issues a watchdog request to the server.
|
251
|
+
def watchdog
|
252
|
+
request(:watchdog)
|
253
|
+
end
|
254
|
+
|
255
|
+
alias :ping :watchdog
|
256
|
+
|
257
|
+
end # class Client
|
258
|
+
end # module KATCP
|
data/lib/katcp/irb.rb
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'katcp/util'
|
2
|
+
require 'katcp/version'
|
3
|
+
|
4
|
+
# Holds KATCP related classes etc.
|
5
|
+
module KATCP
|
6
|
+
|
7
|
+
# Class that holds response to request. Bascially like an Array with each
|
8
|
+
# element representing a line of the server's response. Each "line" is
|
9
|
+
# stored as an Array (of Strings) whose elements are the unescaped "words"
|
10
|
+
# (which may contain embedded spaces) of the reply.
|
11
|
+
class Response
|
12
|
+
|
13
|
+
# Default inspect_mode for new Response objects. When running in IRB,
|
14
|
+
# default is :to_s, otherwise, default is nil.
|
15
|
+
@@inspect_mode = (defined?(IRB) ? :to_s : nil)
|
16
|
+
|
17
|
+
# Returns default inspect mode for new Response objects.
|
18
|
+
def self.inspect_mode
|
19
|
+
@@inspect_mode
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets default inspect mode for new Response objects.
|
23
|
+
def self.inspect_mode=(mode)
|
24
|
+
@@inspect_mode = mode
|
25
|
+
end
|
26
|
+
|
27
|
+
# Inspect mode for +self+. +nil+ or <tt>:inspect</tt> means terse form.
|
28
|
+
# Other symbol (typically <tt>:to_s</tt>) means call that method
|
29
|
+
attr :inspect_mode
|
30
|
+
|
31
|
+
# Creates a new Response object with given inspect_mode and lines Array
|
32
|
+
def initialize(inspect_mode=@@inspect_mode, lines_array=[])
|
33
|
+
raise TypeError.new('lines must be an Array') unless Array === lines_array
|
34
|
+
@lines = lines_array
|
35
|
+
@inspect_mode = inspect_mode
|
36
|
+
end
|
37
|
+
|
38
|
+
# call-seq:
|
39
|
+
# lines -> Array of Arrays
|
40
|
+
#
|
41
|
+
# Returns a copy contained lines. Each line is itself an Array of words.
|
42
|
+
def lines
|
43
|
+
@lines.map {|words| words.map {|word| word.dup}}
|
44
|
+
end
|
45
|
+
|
46
|
+
# call-seq:
|
47
|
+
# grep(pattern, join=' ') -> Array or Strings
|
48
|
+
# grep(pattern, nil) -> Array of Arrays
|
49
|
+
# grep(pattern, join=' ') {|line_array| ...}-> Array of Strings
|
50
|
+
# grep(pattern, nil) {|line_array| ...}-> Array of ...
|
51
|
+
#
|
52
|
+
# Greps through lines for +pattern+. If a block is given, each line will
|
53
|
+
# be passed to the block and replaced in the returned Array by the block's
|
54
|
+
# return value. If +join+ is non-nil (default is a space character), then
|
55
|
+
# each element of the returned Array (i.e. each line) will have its
|
56
|
+
# elements (i.e. words) joined together using +join+ as a delimiter.
|
57
|
+
def grep(pattern, join=' ', &block)
|
58
|
+
matches = @lines.find_all {|l| !l.grep(pattern).empty?}
|
59
|
+
matches.map!(&block) if block_given?
|
60
|
+
matches.map! {|l| l.join(join)} if join
|
61
|
+
matches
|
62
|
+
end
|
63
|
+
|
64
|
+
# call-seq:
|
65
|
+
# dup -> KATCP::Response
|
66
|
+
#
|
67
|
+
# Returns a deep copy of self
|
68
|
+
def dup
|
69
|
+
self.class.new(@inspect_mode, lines)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Pushes +line+ onto +self+. +line+ must be an Array of words (each of
|
73
|
+
# which may contain embedded spaces).
|
74
|
+
def <<(line)
|
75
|
+
raise TypeError.new('expected Array') unless Array === line
|
76
|
+
@lines << line
|
77
|
+
end
|
78
|
+
|
79
|
+
# call-seq:
|
80
|
+
# length -> Integer
|
81
|
+
#
|
82
|
+
# Returns number of lines in +self+ including all inform lines and the
|
83
|
+
# reply line, if present.
|
84
|
+
def length
|
85
|
+
@lines.length
|
86
|
+
end
|
87
|
+
|
88
|
+
# call-seq:
|
89
|
+
# response[index] -> Array or nil
|
90
|
+
# response[start, length] -> Array_of_Arrays or nil
|
91
|
+
# response[range] -> Array_of_Arrays or nil
|
92
|
+
#
|
93
|
+
# Returns subset of lines. Similar to Array#[].
|
94
|
+
def [](*args)
|
95
|
+
@lines[*args]
|
96
|
+
end
|
97
|
+
|
98
|
+
# call-seq:
|
99
|
+
# reqname -> String
|
100
|
+
#
|
101
|
+
# Returns name of request corresponding to +self+ if complete, otherwise
|
102
|
+
# nil.
|
103
|
+
def reqname
|
104
|
+
# All but first character of first word of last line, if complete
|
105
|
+
@lines[-1][0][1..-1] if complete?
|
106
|
+
end
|
107
|
+
|
108
|
+
# call-seq:
|
109
|
+
# complete? -> true_or_false
|
110
|
+
#
|
111
|
+
# Returns true if at least one line exists and the most recently added line
|
112
|
+
# is a reply line.
|
113
|
+
def complete?
|
114
|
+
# If at least one line exists, return true if last line, first word, first
|
115
|
+
# character is '!'.
|
116
|
+
@lines[-1] && @lines[-1][0][0,1] == '!'
|
117
|
+
end
|
118
|
+
|
119
|
+
# call-seq:
|
120
|
+
# status -> String
|
121
|
+
#
|
122
|
+
# Returns status from reply line if complete, otherwise
|
123
|
+
# <tt>'incomplete'</tt>.
|
124
|
+
def status
|
125
|
+
complete? ? @lines[-1][1] : 'incomplete'
|
126
|
+
end
|
127
|
+
|
128
|
+
# call-seq:
|
129
|
+
# ok? -> true_or_false
|
130
|
+
#
|
131
|
+
# Returns true if status is <tt>'ok'</tt>.
|
132
|
+
def ok?
|
133
|
+
'ok' == status
|
134
|
+
end
|
135
|
+
|
136
|
+
# call-seq:
|
137
|
+
# sort! -> self
|
138
|
+
#
|
139
|
+
# Sorts the list of inform lines in-place and returns +self+
|
140
|
+
def sort!
|
141
|
+
n = complete? ? length-1 : length
|
142
|
+
@lines[0,n] = @lines[0,n].sort if n > 0
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
# call-seq:
|
147
|
+
# sort -> KATCP::Response
|
148
|
+
#
|
149
|
+
# Returns a copy of +self+ with inform lines sorted
|
150
|
+
def sort
|
151
|
+
dup.sort!
|
152
|
+
end
|
153
|
+
|
154
|
+
# call-seq:
|
155
|
+
# to_s -> String
|
156
|
+
#
|
157
|
+
# Rejoins words into lines and lines into one String
|
158
|
+
def to_s
|
159
|
+
@lines.map do |line|
|
160
|
+
line.join(' ')
|
161
|
+
end.join("\n")
|
162
|
+
end
|
163
|
+
|
164
|
+
# call-seq:
|
165
|
+
# payload -> String or nil
|
166
|
+
# payload(:to_i) -> Integer or nil
|
167
|
+
# payload(:unpack, 'N*') -> Array_of_Integers or nil
|
168
|
+
# payload(:to_na, NArray::INT) -> NArray_of_ints or nil
|
169
|
+
# etc...
|
170
|
+
#
|
171
|
+
# Returns contents of reply line ("words" joined by spaces) after status
|
172
|
+
# word if <tt>ok?</tt> returns true. Returns +nil+ if <tt>ok?</tt> is
|
173
|
+
# false or no payload exists. If +args+ are given, they are sent to the
|
174
|
+
# payload String (via String#send) and the results are returned.
|
175
|
+
#
|
176
|
+
# For example, passing <tt>:to_i</tt> will result in conversion of payload
|
177
|
+
# String to Integer via String#to_i. The String class can be
|
178
|
+
# monkey-patched as needed for additional conversion options.
|
179
|
+
def payload(*args)
|
180
|
+
if ok?
|
181
|
+
s = @lines[-1][2..-1].join(' ')
|
182
|
+
return s if args.empty?
|
183
|
+
s.send(*args)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# call-seq:
|
188
|
+
# inspect -> String
|
189
|
+
# inspect(mode) -> String
|
190
|
+
#
|
191
|
+
# Provides a terse (or not so terse) summary of +self+ depending on value
|
192
|
+
# of +mode+. If given, +mode+ will be treated as inspect_mode.
|
193
|
+
def inspect(mode=@inspect_mode)
|
194
|
+
if mode && mode != :inspect?
|
195
|
+
send(mode) rescue inspect(nil)
|
196
|
+
else
|
197
|
+
s = "#<KATCP::Response:0x#{object_id.to_s(16)}> "
|
198
|
+
if complete?
|
199
|
+
s += "#{status}"
|
200
|
+
if @lines.length > 1
|
201
|
+
s += ", #{length-1} lines"
|
202
|
+
end
|
203
|
+
else
|
204
|
+
s += "#{length} lines, incomplete"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end # class Response
|
209
|
+
end # module KATCP
|
data/lib/katcp/util.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'narray'
|
2
|
+
|
3
|
+
#TODO: Create ntoh! (etc) methods for narray
|
4
|
+
|
5
|
+
# String add-ons for KATCP
|
6
|
+
class String
|
7
|
+
# In-place escapes +self+ into KATCP format. Always returns +self+.
|
8
|
+
def katcp_escape!
|
9
|
+
empty? ? self[0..-1] = '\@' : self.gsub!(/[\\ \0\n\r\e\t]/) do |s|
|
10
|
+
case s
|
11
|
+
when "\\": '\\'
|
12
|
+
when " " : '\_'
|
13
|
+
when "\0": '\0'
|
14
|
+
when "\n": '\n'
|
15
|
+
when "\r": '\r'
|
16
|
+
when "\e": '\e'
|
17
|
+
when "\t": '\t'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
# Escapes +self+ into KATCP format and returns new String.
|
24
|
+
def katcp_escape
|
25
|
+
dup.katcp_escape!
|
26
|
+
end
|
27
|
+
|
28
|
+
# In-place unescapes +self+ from KATCP format. Always returns +self+.
|
29
|
+
def katcp_unescape!
|
30
|
+
self == '\@' ? self[0..-1] = '' : self.gsub!(/\\[\\_0nret]/) do |s|
|
31
|
+
case s
|
32
|
+
when '\\': "\\"
|
33
|
+
when '\_': " "
|
34
|
+
when '\0': "\0"
|
35
|
+
when '\n': "\n"
|
36
|
+
when '\r': "\r"
|
37
|
+
when '\e': "\e"
|
38
|
+
when '\t': "\t"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# Unescapes +self+ from KATCP format and returns new String.
|
45
|
+
def katcp_unescape
|
46
|
+
dup.katcp_unescape!
|
47
|
+
end
|
48
|
+
|
49
|
+
# call-seq:
|
50
|
+
# to_na(typecode) -> NArray
|
51
|
+
# to_na(typecode, byteswap) -> NArray
|
52
|
+
# to_na(typecode,size[, ...]) -> NArray
|
53
|
+
# to_na(typecode,size[, ...], byteswap) -> NArray
|
54
|
+
#
|
55
|
+
# Convert String to NArray accoring to +typecode+ and call byte swap method
|
56
|
+
# given by Symbol +byteswap+. Pass +nil+ for +byteswap+ to skip the byte
|
57
|
+
# swap conversion.
|
58
|
+
#
|
59
|
+
# Because, as of this writing, KATCP servers typically run on big endian
|
60
|
+
# systems which return binary payload data in network byte order, the default
|
61
|
+
# +byteswap+ is <tt>:ntoh</tt>, which converts from network byte order to
|
62
|
+
# host (i.e. native) order.
|
63
|
+
def to_na(typecode, *args)
|
64
|
+
# Default to :ntoh
|
65
|
+
byteswap = :ntoh
|
66
|
+
if !args.empty? && (Symbol === args[-1] || args[-1].nil?)
|
67
|
+
byteswap = args.pop
|
68
|
+
end
|
69
|
+
na = NArray.to_na(self, typecode, *args)
|
70
|
+
na = na.send(byteswap) if byteswap
|
71
|
+
na
|
72
|
+
end
|
73
|
+
end
|
data/lib/katcp.rb
ADDED
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: katcp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- David MacMahon
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-07-20 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: narray
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
- 5
|
31
|
+
- 9
|
32
|
+
version: 0.5.9
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description: " Provides KATCP client library for Ruby. KATCP is the Karoo Array Telescope\n Control Protocol.\n"
|
36
|
+
email: davidm@astro.berkeley.edu
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README
|
43
|
+
files:
|
44
|
+
- README
|
45
|
+
- lib/katcp.rb
|
46
|
+
- lib/katcp/client.rb
|
47
|
+
- lib/katcp/client/roach.rb
|
48
|
+
- lib/katcp/irb.rb
|
49
|
+
- lib/katcp/response.rb
|
50
|
+
- lib/katcp/util.rb
|
51
|
+
- lib/katcp/version.rb
|
52
|
+
- examples/listdev.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://rb-katcp.rubyforge.org/
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options:
|
59
|
+
- -m
|
60
|
+
- README
|
61
|
+
- --title
|
62
|
+
- Ruby/KATCP 0.0.2 Documentation
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 1
|
72
|
+
- 8
|
73
|
+
- 1
|
74
|
+
version: 1.8.1
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project: rb-katcp
|
86
|
+
rubygems_version: 1.3.7
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: KATCP library for Ruby
|
90
|
+
test_files: []
|
91
|
+
|