Skip to main content

A Zenoh-based robotics framework with opinionated namespacing

Project description

Tide

A lightweight, strongly-typed framework for robotics based on Zenoh, with opinionated namespacing.

Overview

Tide wraps Zenoh's key/value-based pub-sub-query model in a set of strongly-typed "robot nodes," each running in its own thread that talks over a shared Zenoh session.

The framework enforces an opinionated namespacing pattern:

/{robot_id}/{group}/{topic}

For example:

  • /frogbot/cmd/twist - Command velocity for the "frogbot" robot
  • /frogbot/state/pose2d - 2D pose state for the robot
  • /frogbot/sensor/lidar - Lidar data from the robot See docs/namespacing.md for a full list of reserved namespaces. The tide.namespaces module exposes enums for these topics so you don't have to hardcode strings.

Features

  • Opinionated namespacing: Clear, consistent naming pattern for all messages
  • Zero-config networking: Lean on Zenoh's peer discovery for automatic device connection
  • Strongly-typed messages: Uses Pydantic models for validation and serialization
  • Pythonic, thread-based architecture: Each node runs in its own thread to keep latency low
  • Callback-based: Register callbacks for specific topics
  • Command-line interface: Easily create and manage Tide projects

Installation

pip install tide-sdk

Or with uv (recommended):

uv add tide-sdk

Command-Line Interface

Tide comes with a powerful CLI to help you create and manage projects.

Creating a New Project

tide init my_robot_project --robot-id robot1

This creates a new project directory with the following structure:

my_robot_project/
├── config/
│   └── config.yaml     # Configuration for nodes
├── nodes/
│   ├── robot_node.py   # Main robot control node
│   ├── teleop_node.py  # Node for commanding the robot
│   └── monitor_node.py # Node for monitoring state
├── main.py             # Project entry point
├── README.md           # Project documentation
└── requirements.txt    # Dependencies

Running Your Project

To start your Tide project:

cd my_robot_project
tide up

This will:

  1. Load the configuration from config/config.yaml
  2. Start all the defined nodes
  3. Display a table of running nodes

You can specify a custom configuration file:

tide up --config path/to/custom_config.yaml

Checking Node Status

To discover running Tide nodes on the network:

tide status

This shows a list of all discovered Tide nodes. The discovery uses the */*/* wildcard so custom groups like the ping-pong example are also found. The output includes:

  • Robot ID
  • Group
  • Topic

For longer discovery times:

tide status --timeout 5.0

Programming API

Defining a Node

from tide.core.node import BaseNode
from tide.models import Twist2D, Pose2D, to_zenoh_value
from tide import CmdTopic, StateTopic

class MyRobotNode(BaseNode):
    ROBOT_ID = "myrobot"  # Your robot's unique ID
    GROUP = "controller"  # Group for this node
    
    def __init__(self, *, config=None):
        super().__init__(config=config)
        
        # Subscribe to command velocity using the reserved enum
        self.subscribe(CmdTopic.TWIST.value, self._on_cmd_vel)
    
    def _on_cmd_vel(self, data):
        # Process command velocity message
        # ...
    
    def step(self):
        # Called at the node's update rate
        # Publish robot state
        pose = Pose2D(x=1.0, y=2.0, theta=0.5)
        self.put(StateTopic.POSE2D.value, to_zenoh_value(pose))

Simplifying Project Imports

Standalone Tide nodes sometimes need to import other modules from the project root. Instead of manually manipulating sys.path, use the helper:

from tide.core.utils import add_project_root_to_path

add_project_root_to_path(__file__)

This mirrors the common pattern sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))).

Launching Nodes

import time
from tide.core.utils import launch_from_config
from tide.config import load_config

def main():
    # Load configuration
    config = load_config('config.yaml')

    # Launch nodes and external scripts
    nodes, processes = launch_from_config(config)

    try:
        print(f"Started {len(nodes)} nodes and {len(processes)} scripts. Press Ctrl+C to exit.")
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("Interrupted by user")
        for node in nodes:
            node.stop()
        for proc in processes:
            proc.terminate()

if __name__ == "__main__":
    main()

Configuration File

session:
  mode: peer  # Mesh network

scripts:
  - ./tools/my_helper.py

nodes:
  - type: my_package.MyRobotNode
    params:
      robot_id: "robot1"
      
  - type: my_package.TeleopNode
    params:
      robot_id: "robot1"

See docs/config_spec.md for the formal configuration specification.

Complete Example

The generated project template includes a complete working example. Here's how the nodes work together:

  1. TeleopNode - Generates simple oscillating motion commands (simulating joystick)

    def step(self):
        # Create sinusoidal motion pattern
        t = time.time()
        self.linear_vel = 0.5 * math.sin(0.2 * t)
        self.angular_vel = 0.2 * math.cos(0.1 * t)
    
        # Create and publish the command
        cmd = Twist2D(
            linear=Vector2(x=self.linear_vel),
            angular=self.angular_vel
        )
        self.put(CmdTopic.TWIST.value, to_zenoh_value(cmd))
    
  2. RobotNode - Receives commands and simulates robot movement

    def _on_cmd_vel(self, data):
        cmd = from_zenoh_value(data, Twist2D)
        self.linear_vel = cmd.linear.x
        self.angular_vel = cmd.angular
    
    def step(self):
        # Simple motion model - integrate velocity
        dt = time.time() - self.last_update
        self.theta += self.angular_vel * dt
        self.x += self.linear_vel * math.cos(self.theta) * dt
        self.y += self.linear_vel * math.sin(self.theta) * dt
    
        # Publish the current pose
        pose = Pose2D(x=self.x, y=self.y, theta=self.theta)
        self.put(StateTopic.POSE2D.value, to_zenoh_value(pose))
    
  3. MonitorNode - Displays the robot's state

    def _on_pose(self, data):
        pose = from_zenoh_value(data, Pose2D)
        print(f"Robot pose: x={pose.x:.2f}, y={pose.y:.2f}, theta={pose.theta:.2f}")
    

This demonstrates:

  • Opinionated topic naming (cmd/twist, state/pose2d)
  • Callback-based architecture for handling messages
  • Strong typing with Pydantic models
  • Serialization/deserialization with Zenoh

Common Message Types

  • Twist2D: 2D velocity command (linear x, y and angular z)
  • Pose2D: 2D pose (x, y, theta)
  • Pose3D: 3D pose (position and orientation)
  • Acceleration3D: 3D acceleration (linear and angular)
  • LaserScan: 2D laser scan data

Built-in Nodes

Tide ships with a small library of reusable nodes. PIDNode implements a basic PID controller that reads a state and reference value and publishes a command. WebcamNode captures frames from a V4L2 webcam and publishes raw images. See docs/pid_node.md and docs/webcam_node.md for configuration details.

License

MIT

Installing Latest Development Version

To try the latest features before they are released on PyPI, install Tide directly from the main branch using uv:

uv add git+https://github.com/NorthCarolinaRivalRobotics/tide.git

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tide_sdk-0.1.13.tar.gz (35.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tide_sdk-0.1.13-py3-none-any.whl (67.1 kB view details)

Uploaded Python 3

File details

Details for the file tide_sdk-0.1.13.tar.gz.

File metadata

  • Download URL: tide_sdk-0.1.13.tar.gz
  • Upload date:
  • Size: 35.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for tide_sdk-0.1.13.tar.gz
Algorithm Hash digest
SHA256 b40de8a443f1107bf74981ce7c19b79b7a5abe5846aa5577fe99dae9bf229527
MD5 daaabd6c12c4b5753cbb01bb9548a2d9
BLAKE2b-256 2a000f76555d774950f875a944b2b69906d7e6419fe615a99f53d81c7a387612

See more details on using hashes here.

Provenance

The following attestation bundles were made for tide_sdk-0.1.13.tar.gz:

Publisher: pypi.yaml on NorthCarolinaRivalRobotics/tide

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tide_sdk-0.1.13-py3-none-any.whl.

File metadata

  • Download URL: tide_sdk-0.1.13-py3-none-any.whl
  • Upload date:
  • Size: 67.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for tide_sdk-0.1.13-py3-none-any.whl
Algorithm Hash digest
SHA256 76c69f6ba11b9c3c929a2844a73d25f51eee17cc8c7d21e3c12962627c43aedb
MD5 561d26f3a7bb45867853fedd64ed9f5b
BLAKE2b-256 b5114ab2eb9052c4826f5d2051de6099561e9f97372633910bedc812e81b7815

See more details on using hashes here.

Provenance

The following attestation bundles were made for tide_sdk-0.1.13-py3-none-any.whl:

Publisher: pypi.yaml on NorthCarolinaRivalRobotics/tide

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page