class ProcessingConfiguration(configbase.ProcessingConfig):
    VERSION = 1

    show_data_plot = configbase.BoolParameter(
        label="Show data",
        default_value=True,
        updateable=True,
        order=0,
    )

    show_speed_plot = configbase.BoolParameter(
        label="Show speed on FFT y-axis",
        default_value=False,
        updateable=True,
        order=10,
    )
class BaseDenseServiceConfig(BaseServiceConfig):
    noise_level_normalization = cb.BoolParameter(
        label="Noise level normalization",
        default_value=True,
        order=2010,
        category=cb.Category.ADVANCED,
        help=r"""
            With the SW version 2 release, a sensor signal normalization functionality is activated
            by default for the Power Bins, Envelope, and IQ Service. This results in a more
            constant signal for different temperatures and sensors. The radar sweeps are normalized
            to have similar amplitude independent of sensor gain and hardware averaging, resulting
            in only minor visible effect in the sweeps when adjusting these parameters.

            We recommend this setting especially for applications, where absolute radar amplitudes
            are important, such as when comparing to a previously recorded signal or to a fixed
            threshold.

            More technically, the functionality is implemented to collect data when starting the
            service, but not transmitting pulses. This data is then used to determine the current
            sensitivity of receiving part of the radar by estimating the power level of the noise,
            which then is used to normalize the collected sweeps. In the most low-power systems,
            where a service is created to collect just a single short sweep before turning off, the
            sensor normalization can add a non-negligible part to the power consumption.

            Please note, that due to the nature of Sparse data, the Sparse service does not support
            noise level normalization. Instead, normalization during processing is recommended,
            such as done in the Presence detector.

        """,
    )

    def check(self):
        alerts = super().check()

        if self.range_length < 1e-6:
            alerts.append(
                cb.Warning("range_interval",
                           "Will only return a single point"))

        if self.downsampling_factor not in [1, 2, 4]:
            alerts.append(cb.Error("downsampling_factor",
                                   "Must be 1, 2, or 4"))

        if self.repetition_mode == __class__.RepetitionMode.SENSOR_DRIVEN:
            chunks = self.range_length / 0.06
            chunks += 2 if self.mode == Mode.IQ else 0
            points_per_chunk = 124 / self.downsampling_factor
            if points_per_chunk * chunks > 2048:
                alerts.append(
                    cb.Error("range_interval", "Too large for buffer"))

        if self.power_save_mode == __class__.PowerSaveMode.HIBERNATE:
            alerts.append(
                cb.Error("power_save_mode", "Not supported for this service"))

        return alerts
class ProcessingConfig(configbase.ProcessingConfig):
    VERSION = 1

    class BackgroundMode(Enum):
        SUBTRACT = "Subtract"
        LIMIT = "Limit"

    show_peak_depths = configbase.BoolParameter(
        label="Show peak distances",
        default_value=True,
        updateable=True,
        order=-10,
    )

    bg_buffer_length = configbase.IntParameter(
        default_value=50,
        limits=(1, 200),
        label="Background buffer length",
        order=0,
    )

    bg = configbase.ReferenceDataParameter(
        label="Background",
        order=10,
    )

    bg_mode = configbase.EnumParameter(
        label="Background mode",
        default_value=BackgroundMode.SUBTRACT,
        enum=BackgroundMode,
        updateable=True,
        order=20,
    )

    history_length = configbase.IntParameter(
        default_value=100,
        limits=(10, 1000),
        label="History length",
        order=30,
    )
class ProcessingConfiguration(configbase.ProcessingConfig):
    class ThresholdType(Enum):
        FIXED = "Fixed"
        RECORDED = "Recorded"
        CFAR = "CFAR"

    class PeakSorting(Enum):
        STRONGEST = "Strongest signal"
        CLOSEST = "Closest signal"
        STRONGEST_REFLECTOR = "Strongest reflector"
        STRONGEST_FLAT_REFLECTOR = "Strongest flat reflector"

    VERSION = 1

    nbr_average = configbase.FloatParameter(
        label="Sweep averaging",
        default_value=5,
        limits=(1, 100),
        logscale=True,
        decimals=0,
        updateable=True,
        order=0,
        visible=True,
        help=(
            "The number of envelope sweeps to be average into one then used for"
            " distance detection."),
    )

    threshold_type = configbase.EnumParameter(
        label="Threshold type",
        default_value=ThresholdType.FIXED,
        enum=ThresholdType,
        updateable=True,
        order=5,
        help="Setting the type of threshold",
    )

    fixed_threshold = configbase.FloatParameter(
        label="Fixed threshold level",
        default_value=800,
        limits=(1, 20000),
        decimals=0,
        updateable=True,
        order=10,
        visible=lambda conf: conf.threshold_type == conf.ThresholdType.FIXED,
        help=
        ("Sets the value of fixed threshold. The threshold has this constant value over"
         " the full sweep."),
    )

    sc_nbr_sweep_for_bg = configbase.FloatParameter(
        label="Number of sweeps for background estimation",
        default_value=20,
        limits=(2, 200),
        decimals=0,
        visible=lambda conf: conf.threshold_type == conf.ThresholdType.
        RECORDED,
        updateable=True,
        order=20,
        help=
        ("The number of (non-averaged) sweeps collected for calculating the Stationary"
         " Clutter threshold."),
    )

    sc_load_save_bg = configbase.ReferenceDataParameter(
        label="Recorded threshold",
        visible=lambda conf: conf.threshold_type == conf.ThresholdType.
        RECORDED,
        order=23,
        help=("Load/Save a recorded threshold from/to disk."),
    )

    sc_sensitivity = configbase.FloatParameter(
        label="Stationary clutter sensitivity",
        default_value=0.3,
        limits=(0.01, 1),
        logscale=True,
        visible=lambda conf: conf.threshold_type == conf.ThresholdType.
        RECORDED,
        decimals=4,
        updateable=True,
        order=24,
        help=
        ("Value between 0 and 1 that sets the threshold. A low sensitivity will set a "
         "high threshold, resulting in only few false alarms but might result in "
         "missed detections."),
    )

    cfar_sensitivity = configbase.FloatParameter(
        label="CFAR sensitivity",
        default_value=0.5,
        limits=(0.01, 1),
        logscale=True,
        visible=lambda conf: conf.threshold_type == conf.ThresholdType.CFAR,
        decimals=4,
        updateable=True,
        order=40,
        help=
        ("Value between 0 and 1 that sets the threshold. A low sensitivity will set a "
         "high threshold, resulting in only few false alarms but might result in "
         "missed detections."),
    )

    cfar_guard_cm = configbase.FloatParameter(
        label="CFAR guard",
        default_value=12,
        limits=(1, 20),
        unit="cm",
        decimals=1,
        visible=lambda conf: conf.threshold_type == conf.ThresholdType.CFAR,
        updateable=True,
        order=41,
        help=
        ("Range around the distance of interest that is omitted when calculating "
         "CFAR threshold. Can be low, ~4 cm, for Profile 1, and should be "
         "increased for higher Profiles."),
    )

    cfar_window_cm = configbase.FloatParameter(
        label="CFAR window",
        default_value=3,
        limits=(0.1, 20),
        unit="cm",
        decimals=1,
        visible=lambda conf: conf.threshold_type == conf.ThresholdType.CFAR,
        updateable=True,
        order=42,
        help=
        ("Range next to the CFAR guard from which the threshold level will be calculated."
         ),
    )

    cfar_one_sided = configbase.BoolParameter(
        label="Use only lower distance to set threshold",
        default_value=False,
        visible=lambda conf: conf.threshold_type == conf.ThresholdType.CFAR,
        updateable=True,
        order=43,
        help=
        ("Instead of determining the CFAR threshold from sweep amplitudes from "
         "distances both closer and a farther, use only closer. Helpful e.g. for "
         "fluid level in small tanks, where many multipath signal can apprear "
         "just after the main peak."),
    )

    peak_sorting_type = configbase.EnumParameter(
        label="Peak sorting",
        default_value=PeakSorting.STRONGEST,
        enum=PeakSorting,
        updateable=True,
        order=100,
        help="Setting the type of peak sorting method.",
    )

    history_length_s = configbase.FloatParameter(
        default_value=10,
        limits=(3, 1000),
        updateable=True,
        logscale=True,
        unit="s",
        label="History length",
        order=198,
        help="Length of time history for plotting.")

    show_first_above_threshold = configbase.BoolParameter(
        label="Show first distance above threshold",
        default_value=False,
        updateable=True,
        order=199,
        help=
        ("When detect in the presence of object very close to the sensor, the "
         "strong direct leakage might cause that no well shaped peaks are detected, "
         "even though the envelope signal is above the threshold. Therefore the "
         "first distace where the signal is above the threshold can be used as an "
         "alternative to peak detection."),
    )

    def check_sensor_config(self, sensor_config):
        alerts = []

        if sensor_config.update_rate is None:
            alerts.append(configbase.Error("update_rate", "Must be set"))

        if not sensor_config.noise_level_normalization:
            if self.threshold_type == self.ThresholdType.FIXED:
                alerts.append(
                    configbase.Warning(
                        "noise_level_normalization",
                        ("Enabling noise level normalization is "
                         "recommended with Fixed threshold")))

        return alerts
class BaseServiceConfig(BaseSessionConfig):
    class RepetitionMode(ConfigEnum):
        HOST_DRIVEN = ("Host driven", "on_demand")
        SENSOR_DRIVEN = ("Sensor driven", "streaming")

    class Profile(ConfigEnum):
        PROFILE_1 = ("1 (max resolution)", 1, 0.10)
        PROFILE_2 = ("2", 2, 0.12)
        PROFILE_3 = ("3", 3, 0.18)
        PROFILE_4 = ("4", 4, 0.36)
        PROFILE_5 = ("5 (max SNR)", 5, 0.60)

        @property
        def approx_direct_leakage_length(self):
            return self.value[2]

    class PowerSaveMode(ConfigEnum):
        ACTIVE = ("Active", "active")
        READY = ("Ready", "ready")
        SLEEP = ("Sleep", "sleep")
        OFF = ("Off", "off")

    range_interval = cb.FloatRangeParameter(
        label="Range interval",
        unit="m",
        default_value=[0.18, 0.78],
        limits=(-0.7, 7.0),
        order=10,
        help=r"""
            The measured depth range. The start and end values will be rounded to the closest
            measurement point available.
        """,
    )

    range_start = cb.get_virtual_parameter_class(cb.FloatParameter)(
        label="Range start",
        get_fun=lambda conf: conf.range_interval[0],
        visible=False,
    )

    range_length = cb.get_virtual_parameter_class(cb.FloatParameter)(
        label="Range length",
        get_fun=lambda conf: conf.range_interval[1] - conf.range_interval[0],
        visible=False,
    )

    range_end = cb.get_virtual_parameter_class(cb.FloatParameter)(
        label="Range end",
        get_fun=lambda conf: conf.range_interval[1],
        visible=False,
    )

    repetition_mode = cb.EnumParameter(
        label="Repetition mode",
        enum=RepetitionMode,
        default_value=RepetitionMode.HOST_DRIVEN,
        order=1010,
        category=cb.Category.ADVANCED,
        help=r"""
            The RSS supports two different repetition modes. They determine how and when data
            acquisition occurs. They are:

            * **On demand / host driven**: The sensor produces data when requested by the
              application. Hence, the application is responsible for timing the data acquisition.
              This is the default mode, and may be used with all power save modes.

            * **Streaming / sensor driven**: The sensor produces data at a fixed rate, given by a
              configurable accurate hardware timer. This mode is recommended if exact timing
              between updates is required.

            The Exploration Tool is capable of setting the update rate also in *on demand (host
            driven)* mode. Thus, the difference between the modes becomes subtle. This is why *on
            demand* and *streaming* are called *host driven* and *sensor driven* respectively in
            Exploration Tool.
        """,
    )

    update_rate = cb.FloatParameter(
        label="Update rate",
        unit="Hz",
        default_value=None,
        limits=(0.1, None),
        decimals=1,
        optional=True,
        optional_label="Limit",
        optional_default_set_value=50.0,
        order=30,
        help=r"""
            The rate :math:`f_f` at which the sensor sends frames to the host MCU.

            .. attention::

               Setting the update rate too high might result in missed data frames.

            In sparse, the maximum possible update rate depends on the *sweeps per frame*
            :math:`N_s` and *sweep rate* :math:`f_s`:

            .. math::

               \frac{1}{f_f} > N_s \cdot \frac{1}{f_s} + \text{overhead*}

            \* *The overhead largely depends on data frame size and data transfer speeds.*
        """,
    )

    gain = cb.FloatParameter(
        label="Gain",
        default_value=0.5,
        limits=(0.0, 1.0),
        decimals=2,
        order=1040,
        category=cb.Category.ADVANCED,
        help=r"""
            The receiver gain used in the sensor. If the gain is too low, objects may not be
            visible, or it may result in poor signal quality due to quantization errors. If the
            gain is too high, strong reflections may result in saturated data. We recommend not
            setting the gain higher than necessary due to signal quality reasons.

            Must be between 0 and 1 inclusive, where 1 is the highest possible gain.

            .. note::
               When Sensor normalization is active, the change in the data due to changing gain is
               removed after normalization. Therefore, the data might seen unaffected by changes
               in the gain, except very high (receiver saturation) or very low (quantization
               error) gain.

               Sensor normalization is not available for the Sparse service, but is enabled by
               default for the other services - Envelope, IQ, and Power Bins.
        """,
    )

    hw_accelerated_average_samples = cb.IntParameter(
        label="HW accel. average samples",
        default_value=10,
        limits=(1, 63),
        order=1030,
        category=cb.Category.ADVANCED,
        help=r"""
            Number of samples taken to obtain a single point in the data. These are averaged
            directly in the sensor hardware - no extra computations are done in the MCU.

            The time needed to measure a sweep is roughly proportional to the HWAAS. Hence, if
            there's a need to obtain a higher sweep rate, HWAAS could be decreased. Note that
            HWAAS does not affect the amount of data transmitted from the sensor over SPI.

            Must be at least 1 and not greater than 63.
        """,
    )

    maximize_signal_attenuation = cb.BoolParameter(
        label="Max signal attenuation",
        default_value=False,
        order=2000,
        category=cb.Category.ADVANCED,
        help=r"""
            When measuring in the direct leakage (around 0m), this setting can be enabled to
            minimize saturation in the receiver. We do not recommend using this setting under
            normal operation.
        """,
    )

    profile = cb.EnumParameter(
        label="Profile",
        enum=Profile,
        default_value=Profile.PROFILE_2,
        order=20,
        help=r"""
            The main configuration of all the services are the profiles, numbered 1 to 5. The
            difference between the profiles is the length of the radar pulse and the way the
            incoming pulse is sampled. Profiles with low numbers use short pulses while the higher
            profiles use longer pulses.

            Profile 1 is recommended for:

            - measuring strong reflectors, to avoid saturation of the received signal
            - close range operation (<20 cm), due to the reduced direct leakage

            Profile 2 and 3 are recommended for:

            - operation at intermediate distances, (20 cm to 1 m)
            - where a balance between SNR and depth resolution is acceptable

            Profile 4 and 5 are recommended for:

            - for Sparse service only
            - operation at large distances (>1 m)
            - motion or presence detection, where an optimal SNR ratio is preferred over a high
              resolution distance measurement

            The previous profile Maximize Depth Resolution and Maximize SNR are now profile 1 and
            2. The previous Direct Leakage Profile is obtained by the use of the Maximize Signal
            Attenuation parameter.
        """,
    )

    downsampling_factor = cb.IntParameter(
        label="Downsampling factor",
        default_value=1,
        limits=(1, None),
        order=1020,
        category=cb.Category.ADVANCED,
        help=r"""
            The range downsampling by an integer factor. A factor of 1 means no downsampling, thus
            sampling with the smallest possible depth interval. A factor 2 samples every other
            point, and so on. In Envelope and IQ, the finest interval is ~0.5 mm. In Power Bins,
            it is the same but then further downsampled in post-processing.
            In sparse, it is ~6 cm.

            The downsampling is performed by skipping measurements in the sensor, and therefore
            gives lower memory usage, lower power consumption, and lower duty cycle.

            In sparse, setting a too large factor might result in gaps in the data where moving
            objects "disappear" between sampling points.

            In Envelope, IQ, and Power Bins, the factor must be 1, 2, or 4.
            In sparse, it must be at least 1.
            Setting a factor greater than 1 might affect the range end point and for IQ and
            Envelope, also the first point.
        """,
    )

    tx_disable = cb.BoolParameter(
        label="Disable TX",
        default_value=False,
        order=3000,
        category=cb.Category.ADVANCED,
        help=r"""
            Disable the radio transmitter. If used to measure noise, we recommended also switching
            off noise level normalization (if applicable).
        """,
    )

    power_save_mode = cb.EnumParameter(
        label="Power save mode",
        enum=PowerSaveMode,
        default_value=PowerSaveMode.ACTIVE,
        order=3100,
        category=cb.Category.ADVANCED,
    )

    def check(self):
        alerts = []

        if self.repetition_mode == __class__.RepetitionMode.SENSOR_DRIVEN:
            if self.update_rate is None:
                alerts.append(
                    cb.Error("update_rate", "Must be set when sensor driven"))

        if self.gain > 0.9:
            alerts.append(
                cb.Warning("gain", "Too high gain causes degradation"))

        if self.range_start < self.profile.approx_direct_leakage_length:
            alerts.append(
                cb.Info("range_interval", "Direct leakage might be seen"))

        return alerts
class ProcessingConfiguration(configbase.ProcessingConfig):
    VERSION = 4

    detection_threshold = configbase.FloatParameter(
        label="Detection threshold",
        default_value=1.5,
        limits=(0, OUTPUT_MAX / 2),
        updateable=True,
        order=0,
        help='Level at which the detector output is considered as "present".',
    )

    inter_frame_fast_cutoff = configbase.FloatParameter(
        label="Inter fast cutoff freq.",
        unit="Hz",
        default_value=20.0,
        limits=(1, 100),
        logscale=True,
        updateable=True,
        order=10,
        help=
        ("Cutoff frequency of the low pass filter for the fast filtered sweep mean."
         " No filtering is applied if the cutoff is set over half the frame rate"
         " (Nyquist limit)."),
    )

    inter_frame_slow_cutoff = configbase.FloatParameter(
        label="Inter slow cutoff freq.",
        unit="Hz",
        default_value=0.2,
        limits=(0.01, 1),
        logscale=True,
        updateable=True,
        order=20,
        help=
        "Cutoff frequency of the low pass filter for the slow filtered sweep mean.",
    )

    inter_frame_deviation_time_const = configbase.FloatParameter(
        label="Inter deviation time const.",
        unit="s",
        default_value=0.5,
        limits=(0.01, 30),
        logscale=True,
        updateable=True,
        order=30,
        help=
        ("Time constant of the low pass filter for the (inter-frame) deviation between"
         " fast and slow."),
    )

    intra_frame_time_const = configbase.FloatParameter(
        label="Intra time const.",
        unit="s",
        default_value=0.15,
        limits=(0, 0.5),
        updateable=True,
        order=40,
        help="Time constant for the intra frame part.",
    )

    intra_frame_weight = configbase.FloatParameter(
        label="Intra weight",
        default_value=0.6,
        limits=(0, 1),
        updateable=True,
        order=50,
        help=
        ("The weight of the intra-frame part in the final output. A value of 1 corresponds"
         " to only using the intra-frame part and a value of 0 corresponds to only using"
         " the inter-frame part."),
    )

    output_time_const = configbase.FloatParameter(
        label="Output time const.",
        unit="s",
        default_value=0.5,
        limits=(0.01, 30),
        logscale=True,
        updateable=True,
        order=60,
        help="Time constant of the low pass filter for the detector output.")

    show_data = configbase.BoolParameter(
        label="Show data scatter plot",
        default_value=True,
        updateable=True,
        order=100,
        help=
        ("Show the plot of the current data frame along with the fast and slow filtered"
         " mean sweep (used in the inter-frame part)."),
    )

    show_noise = configbase.BoolParameter(
        label="Show noise",
        default_value=False,
        updateable=True,
        order=110,
        help="Show the noise estimation plot.",
        category=configbase.Category.ADVANCED,
    )

    show_depthwise_output = configbase.BoolParameter(
        label="Show depthwise presence",
        default_value=True,
        updateable=True,
        order=120,
        help="Show the depthwise presence output plot.",
    )

    show_sectors = configbase.BoolParameter(
        label="Show distance sectors",
        default_value=False,
        updateable=True,
        order=130,
    )

    history_length_s = configbase.FloatParameter(
        label="History length",
        unit="s",
        default_value=5,
        limits=(1, 20),
        decimals=0,
        order=200,
        category=configbase.Category.ADVANCED,
    )

    def check_sensor_config(self, conf):
        alerts = []

        if conf.update_rate is None:
            alerts.append(configbase.Error("update_rate", "Must be set"))

        if not conf.sweeps_per_frame > 3:
            alerts.append(configbase.Error("sweeps_per_frame", "Must be > 3"))

        return alerts
Exemple #7
0
class ProcessingConfiguration(cb.ProcessingConfig):
    VERSION = 1
    WINDOW_SIZE_POW_OF_2_MAX = 12
    ROLLING_HISTORY_SIZE_MAX = 1000

    show_time_domain = cb.BoolParameter(
        label="Show data in time domain",
        default_value=True,
        updateable=True,
        order=0,
    )

    show_spect_history = cb.BoolParameter(
        label="Show spectrum history",
        default_value=False,
        updateable=True,
        order=10,
    )

    show_depthwise_spect = cb.BoolParameter(
        label="Show depthwise spectrum",
        default_value=False,
        updateable=True,
        order=20,
    )

    window_size_pow_of_2 = cb.FloatParameter(
        label="Window size, power of 2",
        default_value=8,
        limits=(3, WINDOW_SIZE_POW_OF_2_MAX),
        decimals=0,
        updateable=True,
        order=100,
    )

    _window_size = cb.get_virtual_parameter_class(cb.IntParameter)(
        label="Window size",
        get_fun=lambda conf: 2**int(conf.window_size_pow_of_2),
        visible=False,
    )

    overlap = cb.FloatParameter(
        label="Overlap",
        default_value=0.95,
        limits=(0, 1),
        updateable=True,
        order=200,
    )

    rolling_history_size = cb.FloatParameter(
        label="Rolling history size",
        default_value=100,
        decimals=0,
        logscale=True,
        limits=(10, ROLLING_HISTORY_SIZE_MAX),
        updateable=True,
        order=300,
    )

    def check(self):
        alerts = super().check()

        msg = "{}".format(self._window_size)
        alerts.append(cb.Info("window_size_pow_of_2", msg))

        return alerts
class ProcessingConfiguration(configbase.ProcessingConfig):
    VERSION = 2

    class SpeedUnit(Enum):
        METER_PER_SECOND = ("m/s", 1)
        KILOMETERS_PER_HOUR = ("km/h", 3.6)
        MILES_PER_HOUR = ("mph", 2.237)

        @property
        def label(self):
            return self.value[0]

        @property
        def scale(self):
            return self.value[1]

    threshold = configbase.FloatParameter(
        label="Threshold",
        default_value=4.0,
        limits=(1, 100),
        decimals=2,
        updateable=True,
        logscale=True,
        order=0,
    )

    min_speed = configbase.FloatParameter(
        label="Minimum speed",
        unit="m/s",
        default_value=0.5,
        limits=(0, 5),
        decimals=1,
        updateable=True,
        order=10,
    )

    shown_speed_unit = configbase.EnumParameter(
        label="Speed unit",
        default_value=SpeedUnit.METER_PER_SECOND,
        enum=SpeedUnit,
        updateable=True,
        order=100,
    )

    show_data_plot = configbase.BoolParameter(
        label="Show data",
        default_value=False,
        updateable=True,
        order=110,
    )

    show_sd_plot = configbase.BoolParameter(
        label="Show spectral density",
        default_value=True,
        updateable=True,
        order=120,
    )

    show_vel_history_plot = configbase.BoolParameter(
        label="Show speed history",
        default_value=True,
        updateable=True,
        order=130,
    )