Skip to content

tgu/RosSwift

Repository files navigation

RosSwift

ROS Robotic Operating System - Swift implementation of core client libraries. Based on roscpp.

RosSwift is a Swift implementation of ROS. It provides a Swift library based on Swift-NIO that enables Swift programmers to interface to ROS. It is tested on macOS and Linux and should also work on all platforms supported by Swift-NIO. There is also an iOS example app included.

There are some unimplemented features, basic publishing, subscription, services and parameters should work.

Compatibility

  • Swift 6.2+
  • macOS 15.4+, iOS 18.4+, tvOS 18.4+
  • Linux (Ubuntu)

It can be necessary to allow arbitrary loads in the Info.plist file for iOS and macOS:

<key>NSAppTransportSecurity</key>
<dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
</dict>

Installation

Add RosSwift to your Package.swift:

dependencies: [
    .package(url: "https://github.com/tgu/RosSwift.git", from: "2.0.0")
],
targets: [
    .executableTarget(name: "MyNode", dependencies: [
        .product(name: "RosSwift", package: "RosSwift")
    ])
]

Concurrency

RosSwift uses Swift concurrency (async/await) throughout. Node creation, publishing, subscribing, parameters, and service calls are all async operations, and the core types are Sendable. Shared state is protected with Synchronization.Mutex and atomics; the master handler is an actor.

Service calls return their response directly:

let response: AddTwoInts.Response = try await Service.call(
    node: node, serviceName: "/add_two_ints", req: request)

Structured lifecycle

Ros conforms to ServiceLifecycle.Service, so a node can be run inside a ServiceGroup that owns signal handling and orchestrates graceful startup/shutdown:

import ServiceLifecycle

let ros = try Ros(argv: CommandLine.arguments, name: "talker", options: [.noSigintHandler])
let node = await ros.createNode()
// ... advertise / subscribe ...

let group = ServiceGroup(
    services: [ros],
    gracefulShutdownSignals: [.sigterm, .sigint],
    logger: logger)

try await withThrowingTaskGroup(of: Void.self) { tg in
    tg.addTask { try await group.run() }   // runs until SIGINT/SIGTERM
    tg.addTask { await ros.spin() }        // process callbacks
    _ = try await tg.next()
    tg.cancelAll()
}

On shutdown the node awaits its in-flight master unregister* calls (shutdownAsync()) so the master sees a clean teardown. Without a ServiceGroup, the first NodeHandle you create starts the node lazily, and releasing the last one (or calling ros.shutdown()) tears it down.

Messages

The package includes msgbuilder to generate code for custom messages.

Master

The package includes roscore, a Swift implementation of the ROS master. The master advertises its presence with Bonjour on Apple devices and with Avahi on Linux devices. A client will search for the master automatically unless the URL is specified with remapping or with the environment variable ROS_MASTER_URI:

let ros = try Ros(name: "phone", remappings: ["__master": "http://10.0.1.23:11311"])

For service discovery in an iOS client app, add NSBonjourServices to your Info.plist:

<key>NSBonjourServices</key>
<array>
  <string>_ros._tcp</string>
</array>

Examples

Publisher

import RosSwift
import Foundation

let ros = try Ros(argv: CommandLine.arguments, name: "talker")
guard let node = await ros.createNode(ns: "", remappings: [:]) else {
    exit(1)
}

guard let chatter_pub = await node.advertise(topic: "/chatter", message: std_msgs.string.self) else {
    exit(1)
}

var rate = Rate(frequency: 1.0)

var j: Int32 = 0
while ros.ok {
    j += 1
    await chatter_pub.publish(message: std_msgs.string("Hello \(j)"))
    await rate.sleep()
}

Listener

import RosSwift

let ros = try Ros(argv: CommandLine.arguments, name: "listener", options: [.anonymousName])
let n = await ros.createNode()

let vab = await n.subscribe(topic: "/chatter", queueSize: 0) { (msg: String) in
    print("I saw: [\(msg)]")
}

// Subscribe using AsyncStream
let stream = n.subscribe(topic: "/chatter", queueSize: 10, msg: String.self)
Task {
    for await msg in stream {
        print("stream: \(msg)")
    }
}

await n.spinThread()

Services

// Advertise a service
let srv = await custom_msgs.AddTwoInts.advertise(service: "/add_two_ints", node: node) {
    .init(sum: $0.a + $0.b)
}

// Call a service
let request = custom_msgs.AddTwoInts.Request(a: 34, b: 22)
if let response = await request.call(name: "/add_two_ints", node: node) {
    print("\(request.a) + \(request.b) = \(response.sum)")
}

iOS App

RosSwift works with SwiftUI. See the included example app in Examples/iOSApp/ for a full implementation. The basic pattern uses an @Observable wrapper:

import SwiftUI
import RosSwift

@Observable class NodeWrapper {
    var node: NodeHandle?

    var isRunning: Bool {
        node?.isOK ?? false
    }

    var task: Task<Void, Never>?

    func connect(_ master: String) async throws {
        let ros = try Ros(name: "iPhone", remappings: ["__master": master])
        node = await ros.createNode()

        task = Task.detached(priority: .background) { [weak self] in
            await self?.node?.spinThread()
        }
    }

    func disconnect() {
        if isRunning {
            task?.cancel()
            task = nil
            node = nil
        }
    }
}

About

ROS Robotic Operating System - Swift implementation of core client libraries and rosmaster. Based on roscpp.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors