Robot Raconteur Client
This is an AI generation experiment. The library, its tests and this documentation were written by Claude (Anthropic’s Claude Opus 5.5, using Claude Code), at the direction of John Wason.
| (require robotraconteur) | package: robotraconteur |
A pure-Racket client for Robot Raconteur, the communication framework for robotics and automation. It connects to standard Robot Raconteur services over plain TCP, Robot Raconteur TLS, WebSocket and secure WebSocket, reads the service definitions from the service when it connects (no generated code is needed), and can find nodes and services on the local network.
The client speaks Message Version 2. It covers properties, functions, generators, events, callbacks, object references, wires, pipes, object and monitor locks, and remote errors. Memories, subscriptions and the local and intra transports are not implemented.
1 Installation
raco pkg install "git+https://github.com/johnwason/robotraconteur_racket?path=robotraconteur"
From a checkout of the repository, raco pkg install --link robotraconteur/ links the package to the working tree.
TLS uses the OpenSSL libraries that Racket’s openssl library loads. Discovery, and connecting to link-local IPv6 addresses without a zone, need Linux.
2 Quick start
(require robotraconteur) (define obj (rr-connect "rr+tcp://localhost:29200?service=reynard")) (rr-object-type obj) ; "experimental.reynard_the_robot.Reynard" (rr-call obj "say" "Hello from Racket!") (rr-set! obj "color" (vector 0.8 0.2 0.2)) (rr-get obj "robot_position") (rr-call obj "teleport" 0.1 -0.2) (rr-close obj)
Finding a service on the local network instead of naming its address:
(define found (rr-find-service-by-type "experimental.reynard_the_robot.Reynard")) (define obj (rr-connect (car (rr-service-info-urls (car found)))))
3 Connecting
procedure
(rr-connect url [ #:username username #:credentials credentials #:timeout timeout #:ssl-context ssl-context #:tls-context tls-context]) → rr-object? url : string? username : (or/c string? #f) = #f credentials : (or/c hash? #f) = #f timeout : (>/c 0) = 10 ssl-context : (or/c ssl-client-context? #f) = #f tls-context : (or/c rr-node-tls-context? #f) = #f
URLs take these forms; nodename= and nodeid= may be added to the query to require a particular node:
rr+tcp://host:port/?service=name: plain TCP.
rrs+tcp://host:port/?service=name: Robot Raconteur TLS (see TLS).
rr+ws://host:port/path?service=name: WebSocket, normally on the same port as rr+tcp.
rr+wss://host[:port]/path?service=name: WebSocket over standard TLS, for example behind an HTTPS reverse proxy. The default port is 443.
IPv6 hosts go in brackets. A link-local address may carry its zone, [fe80::1%25eth0]; without one, the address is tried on every local interface with a link-local address at the same time and the first connection is kept, as the C++ core does.
username and credentials are sent for services that require authentication, for example (rr-connect url #:username "user" #:credentials (hash "password" "pass")).
The TCP connect gives up after 5 seconds, and the whole connect (TCP, TLS, the WebSocket upgrade and the Robot Raconteur handshake) after timeout seconds. On timeout everything opened for the attempt is closed and an exn:fail:robotraconteur with name "RobotRaconteur.ConnectionError" is raised. Requests on an open connection time out after 30 seconds.
ssl-context is used for rr+wss; see rr-make-ssl-context. tls-context is used for rrs+tcp; see rr-make-node-tls-context.
procedure
obj : rr-object?
procedure
(rr-object? v) → boolean?
v : any/c
procedure
(rr-object-path obj) → string?
obj : rr-object?
procedure
(rr-object-type obj) → string?
obj : rr-object?
procedure
(rr-object-members obj) → (listof (cons/c symbol? string?))
obj : rr-object?
procedure
(rr-member-direction obj name)
→ (or/c 'both 'readonly 'writeonly) obj : rr-object? name : string?
procedure
(rr-attributes obj) → hash?
obj : rr-object?
procedure
(rr-service-definitions obj) → (hash/c string? string?)
obj : rr-object?
4 Values
Values are converted to and from Robot Raconteur types using the service definitions:
Robot Raconteur |
| Racket |
numbers, bool |
| numbers, booleans; cdouble and csingle are complex numbers |
numeric arrays |
| vectors (lists are accepted when sending) |
T[*] |
| |
string |
| string |
struct, pod, namedarray |
| |
T{int32}, T{string} |
| hash |
T{list} |
| list |
enum |
| integer; a symbol naming the value is accepted when sending |
varvalue |
| any of the above, or rr-varvalue to fix the type |
null |
| 'null |
Numbers inside a received varvalue are always vectors, because the wire does not distinguish a scalar from a one-element array.
procedure
(rr-struct-ref s field) → any/c
s : rr-struct? field : string?
procedure
(rr-struct-set! s field v) → void?
s : rr-struct? field : string? v : any/c
procedure
(rr-new-structure obj type) → rr-struct?
obj : rr-object? type : string?
struct
(struct rr-multidim (dims array))
dims : (listof exact-nonnegative-integer?) array : vector?
struct
(struct rr-varvalue (value type))
value : any/c type : string?
procedure
(rr-enum-values obj type)
→ (listof (cons/c string? exact-integer?)) obj : rr-object? type : string?
procedure
(rr-pack-element obj type v [name]) → any/c
obj : rr-object? type : string? v : any/c name : string? = "value"
procedure
(rr-unpack-element obj type element) → any/c
obj : rr-object? type : string? element : any/c
5 Members
5.1 Properties and functions
procedure
(rr-get obj name) → any/c
obj : rr-object? name : string?
procedure
obj : rr-object? name : string? v : any/c
procedure
(rr-call obj name arg ...) → any/c
obj : rr-object? name : string? arg : any/c
5.2 Object references
procedure
(rr-objref obj name [index]) → rr-object?
obj : rr-object? name : string? index : (or/c #f exact-integer? string?) = #f
5.3 Events and callbacks
procedure
(rr-add-event-listener! obj name proc) → void?
obj : rr-object? name : string? proc : procedure?
procedure
(rr-set-callback! obj name proc) → void?
obj : rr-object? name : string? proc : procedure?
5.4 Generators
procedure
(rr-generator? v) → boolean?
v : any/c
procedure
(rr-generator-next g [param]) → any/c
g : rr-generator? param : any/c = 'none
procedure
(rr-generator-close g) → void?
g : rr-generator?
procedure
(rr-generator-abort g) → void?
g : rr-generator?
procedure
(rr-generator->list g) → list?
g : rr-generator?
5.5 Wires
Timestamps are pairs of seconds and nanoseconds.
procedure
(rr-wire-peek-in obj name) →
any/c pair? obj : rr-object? name : string?
procedure
(rr-wire-peek-out obj name) →
any/c pair? obj : rr-object? name : string?
procedure
(rr-wire-poke-out! obj name v) → void?
obj : rr-object? name : string? v : any/c
procedure
(rr-wire-connect obj name) → rr-wire-connection?
obj : rr-object? name : string?
procedure
(rr-wire-connection? v) → boolean?
v : any/c
procedure
(rr-wire-in-value w) → any/c
w : rr-wire-connection?
procedure
(rr-wire-wait-in-value w [#:timeout timeout]) → any/c
w : rr-wire-connection? timeout : real? = 5
procedure
(rr-wire-try-in-value w) →
boolean? any/c any/c w : rr-wire-connection?
procedure
w : rr-wire-connection?
procedure
(rr-wire-set-in-value-lifespan! w seconds) → void?
w : rr-wire-connection? seconds : (or/c real? #f)
procedure
(rr-wire-set-out-value! w v) → void?
w : rr-wire-connection? v : any/c
procedure
(rr-wire-out-value w) → any/c
w : rr-wire-connection?
procedure
(rr-wire-try-out-value w) →
boolean? any/c any/c w : rr-wire-connection?
procedure
(rr-wire-add-listener! w proc) → void?
w : rr-wire-connection? proc : (any/c pair? . -> . any)
procedure
(rr-wire-close w) → void?
w : rr-wire-connection?
5.6 Pipes
procedure
(rr-pipe-connect obj name [index]) → rr-pipe-endpoint?
obj : rr-object? name : string? index : exact-integer? = -1
procedure
(rr-pipe-endpoint? v) → boolean?
v : any/c
procedure
p : rr-pipe-endpoint?
procedure
p : rr-pipe-endpoint? v : any/c
procedure
(rr-pipe-receive p [#:timeout timeout]) → any/c
p : rr-pipe-endpoint? timeout : real? = 5
procedure
(rr-pipe-try-receive p [default]) → any/c
p : rr-pipe-endpoint? default : any/c = #f
procedure
(rr-pipe-set-request-ack! p on?) → void?
p : rr-pipe-endpoint? on? : boolean?
procedure
(rr-pipe-add-listener! p proc) → void?
p : rr-pipe-endpoint? proc : (rr-pipe-endpoint? . -> . any)
procedure
(rr-pipe-add-ack-listener! p proc) → void?
p : rr-pipe-endpoint? proc : (rr-pipe-endpoint? exact-nonnegative-integer? . -> . any)
procedure
(rr-pipe-close p) → void?
p : rr-pipe-endpoint?
6 Locks
procedure
(rr-request-object-lock obj [kind]) → string?
obj : rr-object? kind : (or/c 'user 'client) = 'user
procedure
(rr-release-object-lock obj) → string?
obj : rr-object?
procedure
(rr-monitor-enter obj [timeout]) → void?
obj : rr-object? timeout : exact-integer? = -1
procedure
(rr-monitor-exit obj) → void?
obj : rr-object?
procedure
(call-with-rr-monitor-lock obj thunk [ #:timeout timeout]) → any obj : rr-object? thunk : (-> any) timeout : exact-integer? = -1
7 Errors
struct
(struct exn:fail:robotraconteur exn:fail ( error-code error-name error-message error-subname error-param)) error-code : exact-integer? error-name : string? error-message : string? error-subname : string? error-param : any/c
procedure
(rr-error-type e) → symbol?
e : exn:fail:robotraconteur?
8 TLS
procedure
(rr-make-node-tls-context [ #:extra-roots extra-roots #:default-roots? default-roots?]) → rr-node-tls-context? extra-roots : (listof path-string?) = '() default-roots? : boolean? = #t
the chain leads to a trusted root: by default the two Robot Raconteur node root certificates (2015 and 2020), which are built in;
each certificate carries the critical Robot Raconteur extension for its place in the chain, and no other unknown critical extension;
the node certificate is for CN=Robot Raconteur Node {nodeid} of the node that answered.
extra-roots are PEM files of further roots to trust; default-roots? #f leaves out the built-in ones. Client certificates are not supported.
procedure
(rr-node-tls-context? v) → boolean?
v : any/c
procedure
(rr-make-ssl-context [ #:trust trust #:system-trust? system-trust?]) → ssl-client-context? trust : (listof path-string?) = '() system-trust? : boolean? = #t
9 Discovery
Robot Raconteur nodes announce themselves with IPv6 link-local multicast (group FF02::BA86, UDP port 48653). These procedures send discovery requests on every interface with a link-local address, listen for timeout seconds, and return what answered. Discovery needs Linux.
struct
(struct rr-node-info (nodeid nodename urls))
nodeid : string? nodename : string? urls : (listof string?)
struct
(struct rr-service-info ( name root-object-type implements attributes nodeid nodename urls)) name : string? root-object-type : string? implements : (listof string?) attributes : hash? nodeid : string? nodename : string? urls : (listof string?)
procedure
(rr-find-nodes [#:timeout timeout]) → (listof rr-node-info?)
timeout : real? = 3
procedure
(rr-find-node-by-name name [ #:timeout timeout]) → (listof rr-node-info?) name : string? timeout : real? = 3
procedure
(rr-find-node-by-id id [#:timeout timeout])
→ (listof rr-node-info?) id : string? timeout : real? = 3
procedure
(rr-find-service-by-type type [ #:timeout timeout #:transports transports]) → (listof rr-service-info?) type : string? timeout : real? = 3 transports : (listof string?) = '("rr+tcp" "rrs+tcp")
10 Threads
Each connection runs a few Racket threads: one reads and dispatches incoming messages, one sends keep-alive messages, and one runs listeners. Any number of threads may use a connection at the same time; requests are matched to their replies.
Event, wire and pipe listeners run one at a time, in arrival order, on the connection’s listener thread. A listener must not block. In particular it must not wait on a plain channel that another listener feeds. Hand slow work to another thread or use an async channel (racket/async-channel). A listener may call the service. Callbacks run in their own threads, so they may block.
All of these are Racket threads, so they share one operating system thread.