Skip to content

File syn_devicenet.c

File List > proto > syn_devicenet.c

Go to the documentation of this file

#include "syn_devicenet.h"

#include "../util/syn_pack.h"

#include <string.h>

/* Group 2 CAN Message ID offsets */
#define GROUP2_EXPLICIT_RESP_MSG_ID 3U
#define GROUP2_EXPLICIT_REQ_MSG_ID 4U
#define GROUP2_POLLED_CMD_MSG_ID 5U
#define GROUP2_POLLED_RESP_MSG_ID 6U

/* Group 4 CAN Message IDs */
#define GROUP4_DUP_MAC_REQ_MSG_ID 5U
#define GROUP4_DUP_MAC_RESP_MSG_ID 6U

static uint32_t make_group2_can_id(uint8_t mac_id, uint8_t msg_id)
{
    return (uint32_t)0x400U | ((uint32_t)(mac_id & 0x3FU) << 3) | (uint32_t)(msg_id & 0x07U);
}

static uint32_t make_group4_can_id(uint8_t mac_id, uint8_t msg_id)
{
    return (uint32_t)0x7C0U | ((uint32_t)(mac_id & 0x3FU) << 3) | (uint32_t)(msg_id & 0x07U);
}

bool syn_devicenet_init(SYN_DeviceNet_Node *node, uint8_t mac_id, SYN_DeviceNet_Baud baud)
{
    if ((node == NULL) || (mac_id > SYN_DEVICENET_MAX_MAC_ID)) {
        return false;
    }

    memset(node, 0, sizeof(*node));
    node->state = SYN_DEVICENET_STATE_DUP_MAC_CHECK;
    node->devicenet_obj.mac_id = mac_id;
    node->devicenet_obj.baud_rate = baud;
    node->devicenet_obj.bus_off_action = 0U;
    node->devicenet_obj.allocation_choice = 0x03U; /* Explicit + Polled I/O */

    /* CIP Identity defaults */
    node->identity.vendor_id = 0x005AU;   /* SyntropicOS Vendor ID */
    node->identity.device_type = 0x000CU; /* Generic Discrete I/O */
    node->identity.product_code = 0x0001U;
    node->identity.rev_major = 1U;
    node->identity.rev_minor = 0U;
    node->identity.status = 0x0000U;
    node->identity.serial_number = 0x12345678U;
    memcpy(node->identity.product_name, "Syntropic DeviceNet", 20);
    node->identity.quick_connect_enabled = false;

    node->dup_mac_timer_ms = SYN_DEVICENET_DUP_MAC_TIMEOUT_MS;
    node->dup_mac_failed = false;
    node->explicit_connected = false;
    node->polled_connected = false;

    return true;
}

void syn_devicenet_set_serial_number(SYN_DeviceNet_Node *node, uint32_t serial_number)
{
    if (node != NULL) {
        node->identity.serial_number = serial_number;
    }
}

bool syn_devicenet_set_assembly(SYN_DeviceNet_Node *node, uint8_t *in_buf, uint8_t in_len,
                                uint8_t *out_buf, uint8_t out_len)
{
    if (node == NULL) {
        return false;
    }

    if ((in_len > SYN_DEVICENET_MAX_PAYLOAD) || (out_len > SYN_DEVICENET_MAX_PAYLOAD)) {
        return false;
    }

    node->assembly.input_buf = in_buf;
    node->assembly.input_len = in_len;
    node->assembly.output_buf = out_buf;
    node->assembly.output_len = out_len;

    return true;
}

bool syn_devicenet_set_quickconnect(SYN_DeviceNet_Node *node, bool enable)
{
    if (node == NULL) {
        return false;
    }

    node->identity.quick_connect_enabled = enable;
    if (enable && (node->state == SYN_DEVICENET_STATE_DUP_MAC_CHECK)) {
        /* QuickConnect shortens Dup MAC startup check to 100ms */
        node->dup_mac_timer_ms = 100U;
    }

    return true;
}

void syn_devicenet_poll(SYN_DeviceNet_Node *node, uint32_t dt_ms)
{
    if ((node == NULL) || (node->state != SYN_DEVICENET_STATE_DUP_MAC_CHECK)) {
        return;
    }

    if (dt_ms >= node->dup_mac_timer_ms) {
        node->dup_mac_timer_ms = 0U;
        if (!node->dup_mac_failed) {
            node->state = SYN_DEVICENET_STATE_ONLINE;
        }
    } else {
        node->dup_mac_timer_ms -= dt_ms;
    }
}

static bool handle_explicit_message(SYN_DeviceNet_Node *node, const uint8_t *data, uint8_t len,
                                    uint8_t *tx_data, uint8_t *tx_len)
{
    if (len < 4U) {
        return false;
    }

    uint8_t mac = data[0] & 0x3FU;
    uint8_t service = data[1] & 0x7FU;
    uint8_t class_id = data[2];
    uint8_t attr_id = data[3];

    (void)mac;

    /* Single Fragment Response Header */
    tx_data[0] = node->devicenet_obj.mac_id;
    tx_data[1] = service | 0x80U; /* Success Response Service Code */

    uint8_t out_idx = 2U;

    if (service == 0x0EU) {      /* Get_Attribute_Single */
        if (class_id == 0x01U) { /* Identity Object */
            switch (attr_id) {
            case 1U: /* Vendor ID */
                syn_poke_u16_le(node->identity.vendor_id, tx_data, out_idx);
                out_idx += 2U;
                break;
            case 2U: /* Device Type */
                syn_poke_u16_le(node->identity.device_type, tx_data, out_idx);
                out_idx += 2U;
                break;
            case 3U: /* Product Code */
                syn_poke_u16_le(node->identity.product_code, tx_data, out_idx);
                out_idx += 2U;
                break;
            case 4U: /* Revision */
                tx_data[out_idx++] = node->identity.rev_major;
                tx_data[out_idx++] = node->identity.rev_minor;
                break;
            case 6U: /* Serial Number */
                syn_poke_u32_le(node->identity.serial_number, tx_data, out_idx);
                out_idx += 4U;
                break;
            case 9U: /* QuickConnect attribute */
                tx_data[out_idx++] = node->identity.quick_connect_enabled ? 1U : 0U;
                break;
            default:
                tx_data[1] = 0x94U; /* Error response */
                tx_data[2] = 0x14U; /* Attribute not supported */
                out_idx = 3U;
                break;
            }
        } else if (class_id == 0x03U) { /* DeviceNet Object */
            switch (attr_id) {
            case 1U: /* MAC ID */
                tx_data[out_idx++] = node->devicenet_obj.mac_id;
                break;
            case 2U: /* Baud Rate */
                tx_data[out_idx++] = (uint8_t)node->devicenet_obj.baud_rate;
                break;
            default:
                tx_data[1] = 0x94U;
                tx_data[2] = 0x14U;
                out_idx = 3U;
                break;
            }
        } else if (class_id == 0x04U) { /* Assembly Object */
            if ((node->assembly.input_buf != NULL) && (node->assembly.input_len > 0U)) {
                uint8_t copy_len = (node->assembly.input_len > (8U - out_idx))
                                       ? (8U - out_idx)
                                       : node->assembly.input_len;
                memcpy(&tx_data[out_idx], node->assembly.input_buf, copy_len);
                out_idx += copy_len;
            }
        } else {
            tx_data[1] = 0x94U;
            tx_data[2] = 0x05U; /* Class not supported */
            out_idx = 3U;
        }
    } else if (service == 0x10U) { /* Set_Attribute_Single */
        if (len < 5U) {
            tx_data[1] = 0x94U;
            tx_data[2] = 0x13U; /* Too little data */
            out_idx = 3U;
        } else if ((class_id == 0x01U) && (attr_id == 9U)) {
            node->identity.quick_connect_enabled = (data[4] != 0U);
        } else if ((class_id == 0x03U) && (attr_id == 1U)) {
            if (data[4] <= SYN_DEVICENET_MAX_MAC_ID) {
                node->devicenet_obj.mac_id = data[4];
            }
        }
    } else {
        tx_data[1] = 0x94U;
        tx_data[2] = 0x08U; /* Service not supported */
        out_idx = 3U;
    }

    *tx_len = out_idx;
    return true;
}

bool syn_devicenet_on_can_rx(SYN_DeviceNet_Node *node, uint32_t can_id, const uint8_t *data,
                             uint8_t len, uint32_t *tx_can_id, uint8_t *tx_data, uint8_t *tx_len)
{
    if ((node == NULL) || (data == NULL) || (tx_can_id == NULL) || (tx_data == NULL) ||
        (tx_len == NULL)) {
        return false;
    }

    uint8_t mac = node->devicenet_obj.mac_id;

    /* 1. Group 4 Duplicate MAC ID Check Request */
    uint32_t dup_req_id = make_group4_can_id(mac, GROUP4_DUP_MAC_REQ_MSG_ID);
    if (can_id == dup_req_id) {
        if (node->state == SYN_DEVICENET_STATE_ONLINE) {
            /* Respond to defend MAC ID */
            *tx_can_id = make_group4_can_id(mac, GROUP4_DUP_MAC_RESP_MSG_ID);
            syn_poke_u32_le(node->identity.serial_number, tx_data, 0);
            *tx_len = 4U;
            return true;
        } else if (node->state == SYN_DEVICENET_STATE_DUP_MAC_CHECK) {
            /* Duplicate MAC ID collision detected during startup! */
            node->dup_mac_failed = true;
            node->state = SYN_DEVICENET_STATE_OFFLINE;
            return false;
        }
    }

    if (node->state != SYN_DEVICENET_STATE_ONLINE) {
        return false;
    }

    /* 2. Group 2 Explicit Request */
    uint32_t exp_req_id = make_group2_can_id(mac, GROUP2_EXPLICIT_REQ_MSG_ID);
    if (can_id == exp_req_id) {
        node->explicit_connected = true;
        *tx_can_id = make_group2_can_id(mac, GROUP2_EXPLICIT_RESP_MSG_ID);
        return handle_explicit_message(node, data, len, tx_data, tx_len);
    }

    /* 3. Group 2 Polled I/O Command */
    uint32_t poll_cmd_id = make_group2_can_id(mac, GROUP2_POLLED_CMD_MSG_ID);
    if (can_id == poll_cmd_id) {
        node->polled_connected = true;

        /* Write Master outputs to local assembly output buffer */
        if ((node->assembly.output_buf != NULL) && (len > 0U)) {
            uint8_t copy_len = len > node->assembly.output_len ? node->assembly.output_len : len;
            memcpy(node->assembly.output_buf, data, copy_len);
        }

        /* Build Polled I/O Response frame with local assembly inputs */
        *tx_can_id = make_group2_can_id(mac, GROUP2_POLLED_RESP_MSG_ID);
        if ((node->assembly.input_buf != NULL) && (node->assembly.input_len > 0U)) {
            memcpy(tx_data, node->assembly.input_buf, node->assembly.input_len);
            *tx_len = node->assembly.input_len;
        } else {
            *tx_len = 0U;
        }
        return true;
    }

    return false;
}