/**
 * CANopen Layer Setting Service - slave protocol.
 *
 * @file        CO_LSSslave.h
 * @ingroup     CO_LSS
 * @author      Martin Wagner
 * @author      Janez Paternoster
 * @copyright   2017 - 2020 Neuberger Gebaeudeautomation GmbH
 *
 *
 * This file is part of <https://github.com/CANopenNode/CANopenNode>, a CANopen Stack.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */

#ifndef CO_LSSslave_H
#define CO_LSSslave_H

#include "305/CO_LSS.h"

#if (((CO_CONFIG_LSS)&CO_CONFIG_LSS_SLAVE) != 0) || defined CO_DOXYGEN

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @defgroup CO_LSSslave LSS Slave
 * CANopen Layer Setting Service - slave protocol.
 *
 * @ingroup CO_CANopen_305
 * @{
 * The slave provides the following services
 * - node selection via LSS address
 * - node selection via LSS fastscan
 * - Inquire LSS address of currently selected node
 * - Inquire node ID
 * - Configure bit timing
 * - Configure node ID
 * - Activate bit timing parameters
 * - Store configuration (bit rate and node ID)
 *
 * After CAN module start, the LSS slave and NMT slave are started and then coexist alongside each other. To achieve
 * this behaviour, the CANopen node startup process has to be controlled more detailed. Therefore, CO_LSSinit() must be
 * invoked between CO_CANinit() and CO_CANopenInit() in the communication reset section.
 *
 * Moreover, the LSS slave needs to pause the NMT slave initialization in case no valid node ID is available at start
 * up. In that case CO_CANopenInit() skips initialization of other CANopen modules and CO_process() skips processing of
 * other modules than LSS slave automatically.
 *
 * Variables for CAN-bitrate and CANopen node-id must be initialized by application from non-volatile memory or dip
 * switches. Pointers to them are passed to CO_LSSinit() function. Those variables represents pending values. If node-id
 * is valid in the moment it enters CO_LSSinit(), it also becomes active node-id and the stack initialises normally.
 * Otherwise, node-id must be configured by lss and after successful configuration stack passes reset communication
 * autonomously.
 *
 * Device with all threads can be normally initialized and running despite that node-id is not valid. Application must
 * take care, because CANopen is not initialized. In that case CO_CANopenInit() returns error condition
 * CO_ERROR_NODE_ID_UNCONFIGURED_LSS which must be handled properly. Status can also be checked with
 * CO->nodeIdUnconfigured variable.
 *
 * Some callback functions may be initialized by application with CO_LSSslave_initCkBitRateCall(),
 * CO_LSSslave_initActBitRateCall() and CO_LSSslave_initCfgStoreCall().
 */

/**
 * LSS slave object.
 */
typedef struct {
    CO_LSS_address_t lssAddress;  /**< From #CO_LSSslave_init */
    uint8_t lssState;             /**< @ref CO_LSS_STATE_state */
    CO_LSS_address_t lssSelect;   /**< Received LSS Address by select */
    CO_LSS_address_t lssFastscan; /**< Received LSS Address by fastscan */
    uint8_t fastscanPos;          /**< Current state of fastscan */
    uint16_t* pendingBitRate;     /**< Bit rate value that is temporarily configured */
    uint8_t* pendingNodeID;       /**< Node ID that is temporarily configured */
    uint8_t activeNodeID;         /**< Node ID used at the CAN interface */
    volatile void*
        sendResponse;   /**< Variable indicates, if LSS response has to be sent by mainline processing function */
    uint8_t service;    /**< Service, which will have to be processed by mainline processing function */
    uint8_t CANdata[8]; /**< Received CAN data, which will be processed by mainline processing function */

#if (((CO_CONFIG_LSS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
    void (*pFunctSignalPre)(void* object); /**< From CO_LSSslave_initCallbackPre() or NULL */
    void* functSignalObjectPre;            /**< Pointer to object */
#endif
    bool_t (*pFunctLSScheckBitRate)(void* object,
                                    uint16_t bitRate); /**< From CO_LSSslave_initCkBitRateCall() or NULL */
    void* functLSScheckBitRateObject;                  /** Pointer to object */
    void (*pFunctLSSactivateBitRate)(
        void* object, uint16_t delay);   /**< From CO_LSSslave_initActBitRateCall() or NULL. Delay is in ms */
    void* functLSSactivateBitRateObject; /** Pointer to object */
    bool_t (*pFunctLSScfgStore)(void* object, uint8_t id,
                                uint16_t bitRate); /**< From CO_LSSslave_initCfgStoreCall() or NULL */
    void* functLSScfgStoreObject;                  /** Pointer to object */
    CO_CANmodule_t* CANdevTx;                      /**< From #CO_LSSslave_init() */
    CO_CANtx_t* TXbuff;                            /**< CAN transmit buffer */
} CO_LSSslave_t;

/**
 * Initialize LSS object.
 *
 * Function must be called in the communication reset section.
 *
 * pendingBitRate and pendingNodeID must be pointers to external variables. Both variables must be initialized on
 * program startup (after #CO_NMT_RESET_NODE) from non-volatile memory, dip switches or similar. They must not change
 * during #CO_NMT_RESET_COMMUNICATION. Both variables can be changed by CO_LSSslave_process(), depending on commands
 * from the LSS master.
 *
 * If pendingNodeID is valid (1 <= pendingNodeID <= 0x7F), then this becomes valid active nodeId just after exit of this
 * function. In that case all other CANopen objects may be initialized and processed in run time.
 *
 * If pendingNodeID is not valid (pendingNodeID == 0xFF), then only LSS slave is initialized and processed in run time.
 * In that state pendingNodeID can be configured and after successful configuration reset communication with all CANopen
 * object is activated automatically.
 *
 * @remark The LSS address needs to be unique on the network. For this, the 128 bit wide identity object (1018h) is
 * used. Therefore, this object has to be fully initialized before passing it to this function (vendorID, product code,
 * revisionNo, serialNo are set to 0 by default). Otherwise, if non-configured devices are present on CANopen network,
 * LSS configuration may behave unpredictable.
 *
 * @param LSSslave This object will be initialized.
 * @param lssAddress LSS address
 * @param [in,out] pendingBitRate Pending bit rate of the CAN interface
 * @param [in,out] pendingNodeID Pending node ID or 0xFF - invalid
 * @param CANdevRx CAN device for LSS slave reception.
 * @param CANdevRxIdx Index of receive buffer in the above CAN device.
 * @param CANidLssMaster COB ID for reception.
 * @param CANdevTx CAN device for LSS slave transmission.
 * @param CANdevTxIdx Index of transmit buffer in the above CAN device.
 * @param CANidLssSlave COB ID for transmission.
 * @return #CO_ReturnError_t: CO_ERROR_NO or CO_ERROR_ILLEGAL_ARGUMENT.
 */
CO_ReturnError_t CO_LSSslave_init(CO_LSSslave_t* LSSslave, CO_LSS_address_t* lssAddress, uint16_t* pendingBitRate,
                                  uint8_t* pendingNodeID, CO_CANmodule_t* CANdevRx, uint16_t CANdevRxIdx,
                                  uint16_t CANidLssMaster, CO_CANmodule_t* CANdevTx, uint16_t CANdevTxIdx,
                                  uint16_t CANidLssSlave);

/**
 * Process LSS communication
 *
 * Object is partially pre-processed after LSS message received. Further processing is inside this function.
 *
 * In case that Node-Id is unconfigured, then this function may request CANopen communication reset. This happens, when
 * valid node-id is configured by LSS master.
 *
 * @param LSSslave This object.
 * @return True, if #CO_NMT_RESET_COMMUNICATION is requested
 */
bool_t CO_LSSslave_process(CO_LSSslave_t* LSSslave);

/**
 * Get current LSS state
 *
 * @param LSSslave This object.
 * @return @ref CO_LSS_STATE_state
 */
static inline uint8_t
CO_LSSslave_getState(CO_LSSslave_t* LSSslave) {
    return (LSSslave == NULL) ? CO_LSS_STATE_WAITING : LSSslave->lssState;
}

#if (((CO_CONFIG_LSS)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0) || defined CO_DOXYGEN
/**
 * Initialize LSSslaveRx callback function.
 *
 * Function initializes optional callback function, which should immediately start further LSS processing. Callback is
 * called after LSS message is received from the CAN bus. It should signal the RTOS to resume corresponding task.
 *
 * @param LSSslave This object.
 * @param object Pointer to object, which will be passed to pFunctSignal(). Can be NULL
 * @param pFunctSignalPre Pointer to the callback function. Not called if NULL.
 */
void CO_LSSslave_initCallbackPre(CO_LSSslave_t* LSSslave, void* object, void (*pFunctSignalPre)(void* object));
#endif

/**
 * Initialize verify bit rate callback
 *
 * Function initializes callback function, which is called when "config bit timing parameters" is used. The callback
 * function needs to check if the new bit rate is supported by the CANopen device. Callback returns "true" if supported.
 * When no callback is set the LSS slave will no-ack the request, indicating to the master that bit rate change is not
 * supported.
 *
 * @param LSSslave This object.
 * @param object Pointer to object, which will be passed to pFunctLSScheckBitRate(). Can be NULL
 * @param pFunctLSScheckBitRate Pointer to the callback function. Not called if NULL.
 */
void CO_LSSslave_initCkBitRateCall(CO_LSSslave_t* LSSslave, void* object,
                                   bool_t (*pFunctLSScheckBitRate)(void* object, uint16_t bitRate));

/**
 * Initialize activate bit rate callback
 *
 * Function initializes callback function, which is called when "activate bit timing parameters" is used. The callback
 * function gives the user an event to allow setting a timer or do calculations based on the exact time the request
 * arrived. According to DSP 305 6.4.4, the delay has to be applied once before and once after switching bit rates.
 * During this time, a device mustn't send any messages.
 *
 * @param LSSslave This object.
 * @param object Pointer to object, which will be passed to pFunctLSSactivateBitRate(). Can be NULL
 * @param pFunctLSSactivateBitRate Pointer to the callback function. Not called if NULL.
 */
void CO_LSSslave_initActBitRateCall(CO_LSSslave_t* LSSslave, void* object,
                                    void (*pFunctLSSactivateBitRate)(void* object, uint16_t delay));

/**
 * Store configuration callback
 *
 * Function initializes callback function, which is called when "store configuration" is used. The callback function
 * gives the user an event to store the corresponding node id and bit rate to NVM. Those values have to be supplied to
 * the init function as "persistent values" after reset. If callback returns "true", success is send to the LSS master.
 * When no callback is set the LSS slave will no-ack the request, indicating to the master that storing is not
 * supported.
 *
 * @param LSSslave This object.
 * @param object Pointer to object, which will be passed to pFunctLSScfgStore(). Can be NULL
 * @param pFunctLSScfgStore Pointer to the callback function. Not called if NULL.
 */
void CO_LSSslave_initCfgStoreCall(CO_LSSslave_t* LSSslave, void* object,
                                  bool_t (*pFunctLSScfgStore)(void* object, uint8_t id, uint16_t bitRate));

/** @} */ /* @defgroup CO_LSSslave */

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* (CO_CONFIG_LSS) & CO_CONFIG_LSS_SLAVE */

#endif /* CO_LSSslave_H */
