libsmoco
Low-level interface library for Segment Motion Controller system
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
libsmoco: SMOCO Communication Library

Introduction

The libsmoco library provides a C API for communicating with the Segment Motion Control (SMOCO) stepper actuator control system developed by the University of Munich (Universitäts-Sternwarte München, USM).


SMOCO Hardware Overview

SMOCO is a stepper motor controller system designed for the purpose of driving high-precision actuators for optics control. A SMOCO installation comprises a collection of up to 256 separately addressable stepper motor controllers or "nodes" which communicate over a common CAN bus (a type of device communication network). Also present on this bus is a gateway device which provides a communication connection to an ethernet PC network.

Nodes and Communication

SMOCO nodes each consist of a motor driver chipset and an ARM-based microcontroller running the SMOCO firmware. The SMOCO design provides no central processor or coordinating hardware facility of any kind. Regardless of their physical implementation on shared circuit boards, the nodes operate independently and do not communicate with each other. The controllers and gateway are all connected as simple peers on the bus; the latter provides a means by which a PC application can communicate via a TCP socket to one or more nodes. The gateway device (an IXXAT NT 200) has two bus controllers; both are wired to the bus and the second is used for fault-tolerant communication flow control.

Actuators

Each SMOCO node can control up to three stepper motor actuators, numbered 0-2 and commonly labeled I, J and K. Wiring connections are provided from each controller for these three actuators, for motor phases and limit switches. Power control, acceleration, limit monitoring and other actuator functions are managed by the SMOCO node.

Concurrent Operation

Due to this parallel architecture as well as a limited parallel processing capability in the SMOCO firmware, operation of actuators can be highly concurrent in nature, subject to some key limitations described below. Multiple motors can be made to move nearly simultaneously, and some operations can be performed while motors are in motion.

Gateway communication

The first limitation on concurrency is the gateway itself, which acts as a sort of bottleneck through which all communication must pass, one message at a time. Contention between messages is handled efficiently via CAN arbitration rules but occasionally messages can get lost, typically in the gateway device's transmit buffer during high-volume communication. For this reason, the lowest-level communication is protected by a fault-tolerant software layer provided by a separate process called smocand (SMOCO CAN daemon) described below. In practice, the high performance of smocand and the design of libsmoco have minimized the effect of this communication bottleneck.

Node operation concurrency

Somewhat more important is the threading model of the SMOCO node firmware. Most operations are performed in a single foreground thread with the exception of motor movement, which takes place in a single background thread for each actuator. This means that each node can only process a single command at a time, and the application must wait for a response to that command before issuing another, even if these commands are directed to different actuators. Successful movement commands will respond with a "movement started" status that indicates the control of the movement is being handled in a background thread, so the node's single foreground thread is then available to process other commands while that actuator is in motion. The design of libsmoco allows for independent threads to issue actuator commands without concern for the ongoing activities of other actuators on the same node.


Libsmoco Design Rationale

Libsmoco was spun off from the development of "smocod", a component of the Segment Control System for the segmented primary mirror of the Hobby-Eberly Telescope (HET). During the development of smocod, a need was identified to separate the SMOCO-specific code from the application logic particular to the SCS software environment, and code was refactored into a separate collection of routines that became libsmoco.

Libsmoco provides a C API representation of SMOCO's CAN interface protocol, with some specific goals in mind:

Command-oriented interface

The libsmoco API is primarily designed to give access to SMOCO's command vocabulary to PC application developers. There are no composite operations in libsmoco; there are only functions for sending SMOCO messages and processing responses.

Parallel execution

Given the above, it may seem intuitive that libsmoco should be an extremely small, lightweight library. This is not the case. Libsmoco was developed with highly-parallel operation in mind (given the need in SCS to drive up to 273 actuators simultaneously) and much of the bulk of libsmoco's complexity involves providing transparent actuator control from a client application with a single thread, or hundreds. Libsmoco internally enforces the rules for concurrent access to SMOCO nodes and actuators, and (via smocand) sharing of the single network connection to the SMOCO CAN bus, so application threads need not be concerned with these details.

CAN communication and smocand

During the development of libsmoco's low-level network communication routines, it became clear that a focus was needed on providing guaranteed message transmission while reducing latency and making it more consistent with extreme variations in message traffic volume to and from the SMOCO hardware. The code for managing this was eventually migrated into a separate process called smocand (SMOCO CAN daemon) which communicates with a libsmoco application (and between its own multiple internal threads) via the ZeroMQ message queue library. All of this is transparent to the client application developer, though any libsmoco-based application will need local or network access to a running instance of this daemon and its TCP ports (5000-5002 by default). In turn, smocand will need network access to the gateway interface device at its configured IP address and fixed TCP port (19228).


Application Development Overview

Developing a libsmoco-based application generally involves a set of steps:

Prerequisites

Maybe like, list some prerequisites here.

Example Usage

#include <libsmoco.h>


/* Example universal handler function, called for all messages from SMOCO */
void rsp_handler(SMOCO_CTX sc, const SMOCO_Response *p_resp) {
    printf("rsp_handler: received message: cmd %d (%s) %d:%d\n", p_resp->p_command->id, p_resp->p_command->uilabel, 
            p_resp->node_id, p_resp->p_command->is_act_cmd? p_resp->actuator_id : -1);
}

/* Example command callback, called for events that occur during long-running move commands */
int cmd_event_cb(SMOCO_Response *p_resp, SMOCO_NodeID node_id, SMOCO_ActuatorID act_id, SMOCO_TXEventID_t event_type, void *data) {
    char *evttype_msg;

    switch(event_type) {
    case SMOCO_TXEVT_RESPONSE: evttype_msg = "Normal response"; break;
    case SMOCO_TXEVT_TIMEOUT:  evttype_msg = "TIMEOUT"; break;
    case SMOCO_TXEVT_EXIT:     evttype_msg = "ABORT"; break;
    case SMOCO_TXEVT_NOSTART:  evttype_msg = "FAILED TO START"; break;
    case SMOCO_TXEVT_TIMER_TICK: evttype_msg = "TIMER TICK"; break;
    case SMOCO_TXEVT_START:    evttype_msg = "START"; break;
    default: evttype_msg = "OTHER"; break;
    }
    if(p_resp) 
        printf("Command event (%s): Node %d act %d cmd %d [%s], resp_id from smoco %d\n",
            evttype_msg, p_resp->node_id, p_resp->actuator_id, p_resp->p_command->id, p_resp->p_command->uilabel,
            p_resp->response_id);
    else 
        printf("Command event (%s), no SMOCO response data\n", evttype_msg);
    return 0;
}

int main(int argc, char *argp[]) {

    SMOCO_CTX sm_ctx = smoco_init(NULL);

    smoco_register_handler_universal(sc, rsp_handler);

    /* Overwrite current motor position in SMOCO controller to 50000 steps */
    smoco_params_set(POSOVW, &sm_params, 50000);
    smoco_send_command(sc, POSOVW, node_id, act_id, &sm_params);

    /* Set an upward motion of 40000 steps */
    smoco_params_set(SETRELUP, &sm_params, 40000);
    smoco_send_command(sc, SETRELUP, node_id, act_id, &sm_params);

    /* Perform the move, supply our event callback to handle events during motion. smoco_run_command() 
    won't return until the movement is complete. */
    SMOCO_ResponseID move_resp = smoco_run_command(sc, ACTMOVE, node_id, act_id, NULL, cmd_event_cb, NULL);

    /* Report how the move ended */
    printf("Move returned status: %s\n", smoco_response_get_uilabel(move_resp));

    /* Call status function to fetch information about the actuator. A variety of information is returned
    in a single response from SMOCO, and stored in libsmoco's cache */
    smoco_send_command(sc, GETACTSTAT, node_id, act_id, NULL);

    unsigned endposition = 0;
    /* Get current position from information just fetched, from libsmoco */
    if(smoco_cache_get_act_position(sc, &endposition, node_id, act_id) == SMOCO_ST_SUCCESS)
        printf("End position: %u\n", endposition);
    else
        printf("Error fetching end position\n");

    smoco_terminate(sm_ctx);

    return 0;
}

In the above application, something something your mom.