protocol/time.js

/**
 * @file Implements the CANopen Time Stamp (TIME) protocol.
 * @author Wilkins White
 * @copyright 2024 Daxbot
 */

const Protocol = require('./protocol');
const { Eds, EdsError } = require('../eds');
const { DataType } = require('../types');
const rawToType = require('../functions/raw_to_type');
const typeToRaw = require('../functions/type_to_raw');
const { deprecate } = require('util');

/**
 * CANopen TIME protocol handler.
 *
 * The time stamp (TIME) protocol follows a producer-consumer structure that
 * provides a simple network clock. There should be at most one time stamp
 * producer on the network.
 *
 * @param {Eds} eds - Eds object.
 * @see CiA301 "Time stamp object (TIME)" (ยง7.2.6)
 * @implements {Protocol}
 */
class Time extends Protocol {
    constructor(eds) {
        super(eds);
        this._consume = false;
        this._produce = false;
        this._cobId = null;
    }

    /**
     * Get object 0x1012 [bit 30] - Time producer enable.
     *
     * @type {boolean}
     * @deprecated Use {@link Eds#getTimeProducerEnable} instead.
     */
    get produce() {
        return this.eds.getTimeProducerEnable();
    }

    /**
     * Set object 0x1012 [bit 30] - Time producer enable.
     *
     * @type {boolean}
     * @deprecated Use {@link Eds#setTimeProducerEnable} instead.
     */
    set produce(enable) {
        this.eds.setTimeProducerEnable(enable);
    }

    /**
     * Get object 0x1012 [bit 31] - Time consumer enable.
     *
     * @type {boolean}
     * @deprecated Use {@link Eds#getTimeConsumerEnable} instead.
     */
    get consume() {
        return this.eds.getTimeConsumerEnable();
    }

    /**
     * Set object 0x1012 [bit 31] - Time consumer enable.
     *
     * @type {boolean}
     * @deprecated Use {@link Eds#setTimeConsumerEnable} instead.
     */
    set consume(enable) {
        this.eds.setTimeConsumerEnable(enable);
    }

    /**
     * Get object 0x1012 - COB-ID TIME.
     *
     * @type {number}
     * @deprecated Use {@link Eds#getTimeCobId} instead.
     */
    get cobId() {
        return this.eds.getTimeCobId();
    }

    /**
     * Set object 0x1012 - COB-ID TIME.
     *
     * @type {number}
     * @deprecated Use {@link Eds#setTimeCobId} instead.
     */
    set cobId(cobId) {
        this.eds.setTimeCobId(cobId);
    }

    /**
     * Service: TIME write.
     *
     * @param {Date} date - date to write.
     * @fires Protocol#message
     */
    write(date) {
        if (!this._produce)
            throw new EdsError('TIME production is disabled');

        if(!this._cobId)
            throw new EdsError('COB-ID TIME may not be 0');

        if(!date)
            date = new Date();

        this.send(this._cobId, typeToRaw(date, DataType.TIME_OF_DAY));
    }

    /**
     * Start the module.
     *
     * @override
     */
    start() {
        if(!this.started) {
            const obj1012 = this.eds.getEntry(0x1012);
            if(obj1012)
                this._addEntry(obj1012);

            this.addEdsCallback('newEntry', (obj) => this._addEntry(obj));
            this.addEdsCallback('removeEntry', (obj) => this._removeEntry(obj));

            super.start();
        }
    }

    /**
     * Stop the module.
     *
     * @override
     */
    stop() {
        if(this.started) {
            this.removeEdsCallback('newEntry');
            this.removeEdsCallback('removeEntry');

            const obj1012 = this.eds.getEntry(0x1012);
            if(obj1012)
                this._removeEntry(obj1012);

            super.stop();
        }
    }

    /**
     * Call when a new CAN message is received.
     *
     * @param {object} message - CAN frame.
     * @param {number} message.id - CAN message identifier.
     * @param {Buffer} message.data - CAN message data;
     * @fires Time#time
     * @override
     */
    receive({ id, data }) {
        if (this._consume && this._cobId === id) {
            const date = rawToType(data, DataType.TIME_OF_DAY);

            /**
             * A Time object was received.
             *
             * @event Time#time
             * @type {Date}
             */
            this.emit('time', date);
        }
    }

    /**
     * Listens for new Eds entries.
     *
     * @param {DataObject} entry - new entry.
     * @private
     */
    _addEntry(entry) {
        if(entry.index === 0x1012) {
            this.addUpdateCallback(entry, (obj) => this._parse1012(obj));
            this._parse1012(entry);
        }
    }

    /**
     * Listens for removed Eds entries.
     *
     * @param {DataObject} entry - removed entry.
     * @private
     */
    _removeEntry(entry) {
        if(entry.index === 0x1012) {
            this.removeUpdateCallback(entry);
            this._clear1012();
        }
    }

    /**
     * Called when 0x1012 (COB-ID TIME) is updated.
     *
     * @param {DataObject} data - updated DataObject.
     * @private
     */
    _parse1012(data) {
        const value = data.value;
        const consume = (value >> 31) & 0x1;
        const produce = (value >> 30) & 0x1;
        const rtr = (value >> 29) & 0x1;
        const cobId = value & 0x7FF;

        if(rtr != 0x1) {
            this._consume = !!consume;
            this._produce = !!produce;
            this._cobId = cobId;
        }
        else {
            this._clear1012();
        }
    }

    /**
     * Called when 0x1012 (COB-ID TIME) is removed.
     *
     * @private
     */
    _clear1012() {
        this._consume = false;
        this._produce = false;
        this._cobId = null;
    }
}

////////////////////////////////// Deprecated //////////////////////////////////

/**
 * Initialize the device and audit the object dictionary.
 *
 * @deprecated Use {@link Time#start} instead.
 * @function
 */
Time.prototype.init = deprecate(
    function () {
        const { ObjectType, DataType } = require('../types');

        let obj1012 = this.eds.getEntry(0x1012);
        if(obj1012 === undefined) {
            obj1012 = this.eds.addEntry(0x1012, {
                parameterName:  'COB-ID TIME',
                objectType:     ObjectType.VAR,
                dataType:       DataType.UNSIGNED32,
            });
        }

        this.start();
    }, 'Time.init() is deprecated. Use Time.start() instead.');

module.exports = exports = { Time };