/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * Copyright 2022 Raritan Inc. All rights reserved.
 */

#ifndef __PERIPHERAL_GATEWAY_SENSOR_MANAGER_IDL__
#define __PERIPHERAL_GATEWAY_SENSOR_MANAGER_IDL__

#include <Sensor.idl>
#include <NumericSensor.idl>
#include <Event.idl>
#include <ModbusCfg.idl>

/**
 * Peripheral Devices
 */
module peripheral {

    /** Gateway Sensor Configuration Interface */
    interface GatewaySensorManager {

        /*
            To define a gateway sensor the following steps are required:

            1) Create a [Numeric|State|Switch]SensorClass with a unique
               *classId* and metadata.
               This specification can be used for one or more gateway sensors.

            2) Create a Remote[ModbusRTU|ModbusTCP|...]Device with a unique
               *deviceId* and all information required to connect a remote
               device that provides access to sensors and actuators to be associated
               to one or more gateway sensors.

            3) Create a ValueEncoding, e.g. ModbusValueEncoding[Bit|8|16|32|64],
               with a unique *encodingId* and all information needed to correctly
               interpret a received byte sequence.

               Besides word width, masking and encoding (bool/int/uint/float) there
               is also the possibility to define rules that interpret error codes and
               special values.

               A rule consists of a recognition feature (e.g. InterpretationRuleRAW,
               InterpretationRuleIEEE754INF) and an interpretation (e.g. IGNORE,
               NUMERIC_INVALID).

               This specification can be used for one or more gateway sensors.

            4) Create a [Modbus|...]Sensor, with references to configurations described
               above: *classId*, *deviceId* and *encodingId*. The unique *sensorId* will be
               generated if not set. Additionaly, [Modbus|...]Sensor has information necessary
               to address the sensor on the referenced RemoteDevice.

            5) Store all created classes using setConfiguration()


            Example A) PMMC-1000

                1) Create NumericSensorClass with
                        classId = "Temperature"
                        metaData = {
                            type = { NUMERIC, TEMPERATURE, DEGREE_CELSIUS },
                            decdigits = 1,
                            range = { -55, +85 },
                            accuracy = 0.1,
                            resolution = 0.1,
                            tolerance = 0,
                            noiseThreshold = -55,
                            thresholdCaps = { true, true, true, true }
                        }
                        defaultThresholds = {
                            upperCriticalActive = true,
                            upperCritical = 44.0,
                            upperWarningActive = true,
                            upperWarning = 37.0,
                            lowerWarningActive = true,
                            lowerWarning = 1.0,
                            lowerCriticalActive = true,
                            lowerCritical = -11,
                            assertionTimeout = 3,
                            deassertionHysteresis = 2.0F
                        }
                        preferCommonThresholds = false

                2) Create RemoteModbusRTUDevice with
                        deviceId = "PMMC"
                        busInterface = "sensorhub1-rs485"
                        busSettings = {38400, NONE, 8, 1}
                        unitId = 247;
                        detectionIdentifiers = [ { VENDOR_NAME, "Raritan" },
                                                 { PRODUCT_CODE, "iPDU" }
                                                 { REVISION, "3\\.6.*"}
                                               ]

                3) Create ModbusValueEncoding32 with
                        encodingId = "IEEE754_32"
                        endianness = MODBUS_BIG_ENDIAN
                        type = IEEE754
                        interpretationRules = [
                            InterpretationRuleIEEE754NAN {
                                interpretation = INVALID
                            }
                        ]

                4) Create ModbusSensor with
                        classId = "Temperature"
                        deviceId = "PMMC"
                        encodingId = "IEEE754_32"
                        function = HOLDING_REGISTER;
                        regAddr = 0x812
                        defaultName = "Room temperature"


           Example B) Current sensor and check product code in INPUT_REGISTER 0

                1) Create NumericSensorClass with
                        classId = "Current"
                        metaData = {
                            type = { NUMERIC, CURRENT, AMPERE },
                            decdigits = 2,
                            range = { 0, 100 },
                            accuracy = 0.01,
                            resolution = 0.01,
                            tolerance = 0,
                            noiseThreshold = 0.01,
                            thresholdCaps = { true, true, true, true }
                        }

                2) Create RemoteModbusRTUDevice with
                        deviceId = "GW"
                        busInterface = "sensorhub1-rs485"
                        busSettings = {9600, NONE, 8, 1}
                        unitId = 1;

                3) Create ModbusValueEncoding32 with
                        encodingId = "IEEE754_32_MIXED_LE"
                        endianness = MODBUS_LITTLE_ENDIAN
                        type = IEEE754
                        interpretationRules = [
                            InterpretationRuleRAW {
                                value = 0x00800000         # FLT_MIN
                                interpretation = IGNORE
                                ignoreTimeout = 60         # unavailable after 60 seconds
                            },
                            InterpretationRuleRAW {
                                value = 0x7f7fffff         # FLT_MAX
                                interpretation = UNAVAILABLE
                            }
                        ]

                   Create ModbusValueEncoding16 with
                        encodingId = "GW_PRODUCT_UINT_16"
                        type = UINT
                        interpretationRules = [
                            InterpretationRuleRAW {
                                value = 1200                    # product code
                                invertCondition = true
                                interpretation = REJECT_DEVICE  # device detection failed
                            },
                        ]

                4) Create ModbusSensor with
                        classId = "Current"
                        deviceId = "GW"
                        encodingId = "IEEE754_32_MIXED_LE"
                        function = INPUT_REGISTER;
                        regAddr = 0xD
                        defaultName = "AC Current ch2"

                   Create ModbusSensor with
                        classId = ""                 # used for device detection only
                        deviceId = "GW"
                        encodingId = "GW_PRODUCT_UINT_16"
                        function = INPUT_REGISTER;
                        regAddr = 0


           Example C) Energy Meter

                1) Create NumericSensorClass with
                        classId = "Energy"
                        metaData = {
                            type = { NUMERIC, POWER, WATT },
                            decdigits = 1,
                            range = { 0, 9999999 },
                            accuracy = 0.1,
                            resolution = 0.1,
                            tolerance = 0,
                            noiseThreshold = 0,
                            thresholdCaps = { }
                        }

                2) Create RemoteModbusRTUDevice with
                        deviceId = "EX4"
                        busInterface = "sensorhub0-rs485"
                        busSettings = {9600, NONE, 8, 1}
                        unitId = 4;

                3) Create ModbusValueEncoding32 with
                        encodingId = "UINT_24_MIXED_LE"
                        endianness = MODBUS_LITTLE_ENDIAN
                        mask = 0x00FFFFFF
                        type = UINT
                        scalingFactor = 10.0;

                4) Create ModbusSensor with
                        classId = "Energy"
                        deviceId = "EX4"
                        encodingId = "UINT_24_MIXED_LE"
                        function = HOLDING_REGISTER;
                        regAddr = 0
                        defaultName = "Energy Meter"
        */


        /* ======================= SENSOR CLASSES ========================= */

        /**
         * @brief Specification of sensor class that stores sensor properties
         *
         * Before an instance of Sensor can be created,
         * an instance of SensorClass must be present.
         * The Sensor locates the corresponding SensorClass
         * using the sensor class id `classId`.
         */
        valueobject SensorClass {
            string classId;                            ///< sensor class id
        };

        /**
         * @brief Sensor class that stores numeric sensor properties
         */
        valueobject NumericSensorClass extends SensorClass {
            /**
             * numeric sensor type, ranges, etc.
             */
            sensors.NumericSensor_4_0_6.MetaData metadata;

            /**
             * threshold values may be used initially and as default threshold values,
             * depending on the value of the preferCommonThresholds field
             */
            sensors.NumericSensor_4_0_6.Thresholds defaultThresholds;

            boolean preferCommonThresholds;  ///< if true, use common default thresholds if available, otherwise use the value of the defaultThresholds field
        };

        /**
         * @brief Sensor class that stores state sensor properties
         */
        valueobject StateSensorClass extends SensorClass {
            sensors.Sensor_4_0_5.TypeSpec type;              ///< state sensor type
        };

        /**
         * @brief Sensor class that switch properties
         */
        valueobject SwitchSensorClass extends StateSensorClass { };


        /* ======================= REMOTE DEVICES ========================= */

        /**
         * @brief Specification of a remote device that provides access to sensors
         *
         * It contains all the information required to connect a remote
         * device that provides access to sensors and actuators to be connected
         * to one or more gateway sensors.
         *
         * Before an instance of Sensor can be created, an instance
         * of RemoteDevice must be present. The Sensor locates the
         * corresponding RemoteDevice using the remote device id `deviceId`.
         */
        valueobject RemoteDevice {
            string deviceId;   ///< remote device id
            boolean disabled;  ///< prevents usage of all sensor specifications which reference this device
            string name;       ///< human readable remote device name
            int timeoutMs;     ///< communication access timeout, 0 = default
            int retry;         ///< communication retry count, 0 = default
        };

        constant int MODBUS_VENDOR_NAME = 0;
        constant int MODBUS_PRODUCT_CODE = 1;
        constant int MODBUS_REVISION = 2;
        constant int MODBUS_VENDOR_URL = 3;
        constant int MODBUS_PRODUCT_NAME = 5;
        constant int MODBUS_MODEL_NAME = 6;
        constant int MODBUS_APP_NAME = 7;

        valueobject RemoteModbusDevice extends RemoteDevice {
            map<int, string> detectionIdentifiers; ///< list of expected device identifiers
            int unitId;                            ///< modbus server address
        };

        valueobject RemoteModbusRTUDevice extends RemoteModbusDevice {
            /**
             * @brief rs485 interface used for modbus communication
             *
             * only SRC-080X ist supported, possible values are
             *   - "sensorhub0-rs485" means port "REMOTE HUB 1"
             *   - "sensorhub1-rs485" means port "REMOTE HUB 2"
             *
             */
            string busInterface;
            ModbusCfg.SerialSettings busSettings; ///< interface settings
            int interframeDelayDeciChars;         ///< (== 0) -> default, (< 0) -> no delay, (> 0) -> e.g. 35 means 3.5 chars
        };

        valueobject RemoteModbusTCPDevice extends RemoteModbusDevice {
            string          ipAddress;            ///< modbus device ip address
            int             tcpPort;              ///< modbus tcp port
        };


        /* ======================= VALUE ENCODING ========================= */

        /**
         * @brief Specification of value encoding
         *
         * Before an instance of Sensor can be created,
         * an instance of ValueEncoding must be present.
         * The Sensor locates the corresponding ValueEncoding
         * using the sensor class id `encodingId`.
         */
        enumeration EncodingType {
            BOOL,       ///< boolean
            INT,        ///< signed integer of a size specified in ValueEncoding
            UINT,       ///< unsigned integer of a size specified in ValueEncoding
            IEEE754     ///< IEEE 754 floating point of a size specified in ValueEncoding
        };

        /**
         * @brief Here you define how certain read values are to be interpreted
         *
         * The rules leading to *_IGNORE and *_UNAVAILABLE are applied to numeric values and states.
         *
         * IGNORE can be time-limited using ignoreTimeout, see InterpretationRule. After the time has expired,
         * the value is set to unavailable.
         *
         * NUMERIC_INVALID only affects NumericSensors. As DEFAULT for numeric sensors (if
         * no rule is matched, or if DEFAULT is matched) the value is converted to a floating point
         * number according to type specification.
         *
         * STATE_ON and STATE_OFF affect only StateSensors. If no such rule is specified, the state
         * becomes true if the read value is integral and not 0. If STATE_ON or STATE_OFF are present in
         * the rules, then the state is set to true or false only if a STATE_* rule matches. Otherwise
         * (DEFAULT) the state becomes unavailable.

         * REJECT_SENSOR means: do not accept this reading in any way. The sensor is treated as
         * non-existent.
         *
         * REJECT_DEVICE means: do not accept this reading in any way. Instead, this is a clear
         * indication that the device is defective, or it is a wrong device. All sensors that belong
         * to this device are treated as non-existent.
         * REJECT_DEVICE can be used in this way to detect devices. The sensor defined for this purpose
         * does not have to be imported as gateway sensor (see Sensor.classId = "").
         *
         * DEFAULT on failed receives modbus exceptions REJECT_SENSOR. DEFAULT on modbus read errors
         * (e.g. timeouts) is REJECT_DEVICE.
         */
        enumeration Interpretation {
            DEFAULT,                     ///< use default decoding
            REJECT_DEVICE,               ///< device is treated as non-existent
            REJECT_SENSOR,               ///< sensor is treated as non-existent
            IGNORE,                      ///< ignore (use previous value)
            UNAVAILABLE,                 ///< set value to unavailable
            NUMERIC_INVALID,             ///< set numeric value to invalid
            STATE_ON,                    ///< set state to ON
            STATE_OFF                    ///< set state to OFF
        };

        /**
         * InterpretationRule defines a rule that is applied to a reading, resulting in an Interpretation.
         * A list of InterpretationRule is applied in the given order, and stopped after the first match.
         * If no rule applies, the interpretation is Interpretation.DEFAULT.
         **/
        valueobject InterpretationRule {
            Interpretation interpretation; ///< how to interpret the applied rule
            int     ignoreTimeout;         ///< if > 0, ignoring stops after this timeout (seconds), and state changes to unavailable
        };

        valueobject InterpretationRuleInvertable extends InterpretationRule {
            boolean invertCondition;       ///< negate rule condition
        };

        /** InterpretationRuleModbusException is applied to modbus read exceptions
         */
        valueobject InterpretationRuleModbusException extends InterpretationRuleInvertable {
            vector<int> exceptions;        ///< list of modbus exception codes
        };

        /** InterpretationRuleModbusSystemError is applied to standard system errors returned
         *  from libmodbus
         */
        valueobject InterpretationRuleModbusSystemError extends InterpretationRuleInvertable {
            vector<int> errnos;            ///< list of system error codes
        };

        /** InterpretationRuleModbusSpecificError is applied to specific errors from libmodbus
         */
        valueobject InterpretationRuleModbusSpecificError extends InterpretationRuleInvertable {
            vector<ModbusCfg.SpecificModbusErrors> errors;  ///< list of specific libmodbus error codes
        };

        /** InterpretationRuleRAW is applied after swap, but before masking (because it has it's own mask)
         */
        valueobject InterpretationRuleRAW extends InterpretationRuleInvertable {
            long value;                    ///< compare to value
            long mask;                     ///< (0 = not masked, the same as 0xFFFF...)
        };

        /** InterpretationRuleRangeRAW is applied after swap, but before masking (because it has it's own mask)
         */
        valueobject InterpretationRuleRangeRAW extends InterpretationRuleInvertable {
            long min;                    ///< minimum accepted value
            long max;                    ///< maximum accepted value
            long mask;                   ///< (0 = not masked, the same as 0xFFFF...)
        };

        /** InterpretationRuleIEEE* are applied after value is wapped, masked and interpreted as float / double
         */
        valueobject InterpretationRuleIEEE754INF extends InterpretationRuleInvertable {};
        valueobject InterpretationRuleIEEE754NAN extends InterpretationRuleInvertable {};
        valueobject InterpretationRuleCatchAll extends InterpretationRule {};

        valueobject ValueEncoding {
            string                      encodingId;          ///< encoding type id
            EncodingType                type;                ///< value coding type
            boolean                     invertState;         ///< invert when interpreting as state
            vector<InterpretationRule>  interpretationRules; ///< error/value inerpretation rules
        };

        valueobject NumericValueEncoding extends ValueEncoding {
            float                       scalingFactor;       ///< multiply with when interpreting as numeric value
            float                       offset;              ///< add value after applying scalingFactor
        };

        valueobject ModbusValueEncodingBit extends ValueEncoding {};

        valueobject ModbusValueEncoding8 extends NumericValueEncoding {
            /**
             * 16-bit modbus words should be transferred in big-endian byte order.
             * For modbus devices that do not comply with this, byteSwap = true must
             * be set. Another application is to use byteSwap to address a single byte in
             * a 16-bit modbus word in case 8-bit values are requested (ModbusValueEncoding8).
             */
            boolean byteSwap;
            long    mask;               ///< mask raw value before interpreting (0 = not masked, the same as 0xFFFF...)
        };

        valueobject ModbusValueEncoding16 extends ModbusValueEncoding8 {};

        enumeration ModbusEndianness {
            MODBUS_BIG_ENDIAN,          ///< use big endian modbus word order (default is big endian)
            MODBUS_LITTLE_ENDIAN        ///< use little endian modbus word order
        };

        valueobject ModbusValueEncoding32 extends ModbusValueEncoding16 {
            /**
             * Modbus 16-bit-words should be transmitted in big endian order. For modbus
             * devices that do not comply with this, endianness must be set to LITTLE_ENDIAN.
             */
            ModbusEndianness endianness;
        };


        valueobject ModbusValueEncoding64 extends ModbusValueEncoding32 {};


        /* ======================= SENSORS  ========================= */

        /**
         * Specification of a Sensor
         */
        valueobject Sensor {
            /**
             * Unique sensor id, is automatically generated if not set
             */
            string           sensorId;

            /**
             * Set disabled = true prevents any usage of the specification
             */
            boolean          disabled;

            /**
             * @brief remote device id
             *
             * deviceId is used to locate the corresponding RemoteDevice that
             * provides the physical sensor
             */
            string           deviceId;

            /**
             * @brief sensor class id
             *
             * classId is used to locate the corresponding SensorClass.
             * classId = "" prevents the sensor specification from being used
             * for an imported gateway sensor. The sensor value is read anyway, and a read
             * error or a interpretation as Interpretation.REJECT_DEVICE
             * prevents the successful detection of the device
             */
            string           classId;

            /**
             * @brief sensor encoding id
             *
             * classId is used to locate the corresponding ValueEncoding
             */
            string           encodingId;

            /**
             * @brief default sensor name
             *
             * defaultName is used as sensor slot name
             */
            string           defaultName;
        };

        valueobject ModbusSensor extends Sensor {
            ModbusCfg.ModbusFunction  function;      ///< Modbus function to be used
            int                       regAddr;       ///< Modbus server register address
        };


        /* ======================= FULL CONFIGURATION  ========================= */

        structure ConfigurationPackage {
            boolean disabled;              ///< prevents usage of all sensor specifications defined in this package
            string name;                   ///< package name
            vector<SensorClass> classes;
            vector<RemoteDevice> devices;
            vector<ValueEncoding> encodings;
            vector<Sensor> sensors;
        };

        /**
         * @brief Get all defined gateway sensors and required objects from config
         *
         * Read all Sensor Classes, Remote Devices, Value Encodings and Sensors
         * at once to ensure that the overall configuration is consistent
         *
         * @return all defined gateway sensor feature configuration objects
         */
        map<string, ConfigurationPackage> getConfiguration();

        constant int ERR_CONFIG_INCONSISTENT = 1;
        constant int ERR_CONFIG_STORAGE_FAILED = 2;

        /**
         * @brief Replace defined gateway sensor and/or other configuration objects in config
         *
         * Write all Sensor Classes, Remote Devices, Value Encodings and Sensors
         * at once to ensure that the overall configuration is consistent at every
         * moment.
         *
         * The uniqueness of all IDs and the availability of all referenced elements are
         * checked before the configuration is applied. Otherwise setConfiguration() returns
         * with an error.
         *
         * @param cfg all defined gateway sensor feature configuration objects
         * @return 0 if OK
         * @return ERR_CONFIG_INCONSISTENT if consistency check has failed (e.g. non-unique identifiers)
         * @return ERR_CONFIG_STORAGE_FAILED if configuration cannot be stored
         */
        int setConfiguration(in map<string, ConfigurationPackage> cfg);

        /** Event: The gateway sensor feature configuration has changed */
        valueobject ConfigurationChangedEvent extends idl.Event {
            map<string, ConfigurationPackage> configuration;   ///< the current gateway sensor feature configuration
        };


        /* ======================= FEEDBACK  ========================= */

        /**
         * @brief Feedback from gateway sensor configuration usage
         *
         * There is no knowledge of Feedback required to configure gateway sensors.
         * The following is only needed for special tools that monitor the usage
         * of the configurations above.
         */
        structure FeedbackObject {
            enumeration FeedbackState {
                UNSPECIFIED,
                INTENTIONALLY_UNUSED,
                FAILED_PRECONDITIONS_UNUSED,
                FAILED,
                GOOD
            };

            string key;                         ///< empty or free defined value name
            string value;                       ///< message or value
            FeedbackState stateTansitionTo;     ///< this FeedbackObject changes state, if not UNSPECIFIED
        };

        valueobject Feedback {
            FeedbackObject.FeedbackState currentState;  ///< Device or sensor state
            vector<FeedbackObject> infos;
        };

        valueobject DeviceFeedback extends Feedback {
            string packageId;             ///< the configuration package id
            string deviceId;              ///< the same id as in RemoteDevice
        };

        valueobject SensorFeedback extends Feedback {
            string packageId;             ///< the configuration package id
            string deviceId;              ///< the same id as in RemoteDevice
            string sensorId;              ///< the same id as in Sensor
        };

        /**
         * Get feedback from gateway sensor configuration usage
         *
         * @return Result: the full feedback
         */
        vector<Feedback> getFeedback();

        /** Event: The configuration usage feedback has changed */
        valueobject FeedbackChangedEvent extends idl.Event {
            vector<Feedback> feedback;  ///< the latest configuration usage feedback
        };
    };
}

#endif /* __PERIPHERAL_GATEWAY_SENSOR_MANAGER_IDL__ */
