device: Add simple temperature model for devices

This temperature model has three states:
 * COLD
 * WARM
 * HOT

Device drivers can define the time it requires for the device to get HOT
and COLD. The underlying model assumes an exponential warming and
cooling process and enforces a cool-off time after the device has
reached the HOT state. This cool down period is however shorter than the
specified time in the driver.
This commit is contained in:
Benjamin Berg 2021-04-21 17:16:03 +02:00
parent 96e5888110
commit a2d950044d
8 changed files with 243 additions and 0 deletions

View File

@ -1121,6 +1121,7 @@ fpi_device_elanmoc_class_init (FpiDeviceElanmocClass *klass)
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
dev_class->id_table = id_table;
dev_class->nr_enroll_stages = ELAN_MOC_ENROLL_TIMES;
dev_class->temp_hot_seconds = -1;
dev_class->open = elanmoc_open;
dev_class->close = elanmoc_close;

View File

@ -1565,6 +1565,7 @@ fpi_device_goodixmoc_class_init (FpiDeviceGoodixMocClass *klass)
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
dev_class->id_table = id_table;
dev_class->nr_enroll_stages = DEFAULT_ENROLL_SAMPLES;
dev_class->temp_hot_seconds = -1;
dev_class->open = gx_fp_init;
dev_class->close = gx_fp_exit;

View File

@ -1385,6 +1385,7 @@ fpi_device_synaptics_class_init (FpiDeviceSynapticsClass *klass)
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
dev_class->id_table = id_table;
dev_class->nr_enroll_stages = ENROLL_SAMPLES;
dev_class->temp_hot_seconds = -1;
dev_class->open = dev_init;
dev_class->close = dev_exit;

View File

@ -22,6 +22,23 @@
#include "fpi-device.h"
/* Chosen so that if we turn on after WARM -> COLD, it takes exactly one time
* constant to go from COLD -> HOT.
* TEMP_COLD_THRESH = 1 / (e + 1)
*/
#define TEMP_COLD_THRESH (0.26894142136999512075)
#define TEMP_WARM_HOT_THRESH (1.0 - TEMP_COLD_THRESH)
#define TEMP_HOT_WARM_THRESH (0.5)
/* Delay updates by 100ms to avoid hitting the border exactly */
#define TEMP_DELAY_SECONDS 0.1
/* Hopefully 3min is long enough to not get in the way, while also not
* properly overheating any devices.
*/
#define DEFAULT_TEMP_HOT_SECONDS (3 * 60)
#define DEFAULT_TEMP_COLD_SECONDS (9 * 60)
typedef struct
{
FpDeviceType type;
@ -58,6 +75,15 @@ typedef struct
/* State for tasks */
gboolean wait_for_finger;
FpFingerStatusFlags finger_status;
/* Device temperature model information and state */
GSource *temp_timeout;
FpTemperature temp_current;
gint32 temp_hot_seconds;
gint32 temp_cold_seconds;
gint64 temp_last_update;
gboolean temp_last_active;
gdouble temp_current_ratio;
} FpDevicePrivate;
@ -88,3 +114,6 @@ typedef struct
} FpMatchData;
void match_data_free (FpMatchData *match_data);
void fpi_device_update_temp (FpDevice *device,
gboolean is_active);

View File

@ -48,6 +48,7 @@ enum {
PROP_NR_ENROLL_STAGES,
PROP_SCAN_TYPE,
PROP_FINGER_STATUS,
PROP_TEMPERATURE,
PROP_FPI_ENVIRON,
PROP_FPI_USB_DEVICE,
PROP_FPI_UDEV_DATA_SPIDEV,
@ -151,6 +152,36 @@ fp_device_constructed (GObject *object)
priv->device_name = g_strdup (cls->full_name);
priv->device_id = g_strdup ("0");
if (cls->temp_hot_seconds > 0)
{
priv->temp_hot_seconds = cls->temp_hot_seconds;
priv->temp_cold_seconds = cls->temp_cold_seconds;
g_assert (priv->temp_cold_seconds > 0);
}
else if (cls->temp_hot_seconds == 0)
{
priv->temp_hot_seconds = DEFAULT_TEMP_HOT_SECONDS;
priv->temp_cold_seconds = DEFAULT_TEMP_COLD_SECONDS;
}
else
{
/* Temperature management disabled */
priv->temp_hot_seconds = -1;
priv->temp_cold_seconds = -1;
}
/* Start out at not completely cold (i.e. assume we are only at the upper
* bound of COLD).
* To be fair, the warm-up from 0 to WARM should be really short either way.
*
* Note that a call to fpi_device_update_temp() is not needed here as no
* timeout must be registered.
*/
priv->temp_current = FP_TEMPERATURE_COLD;
priv->temp_current_ratio = TEMP_COLD_THRESH;
priv->temp_last_update = g_get_monotonic_time ();
priv->temp_last_active = FALSE;
G_OBJECT_CLASS (fp_device_parent_class)->constructed (object);
}
@ -165,6 +196,8 @@ fp_device_finalize (GObject *object)
if (priv->is_open)
g_warning ("User destroyed open device! Not cleaning up properly!");
g_clear_pointer (&priv->temp_timeout, g_source_destroy);
g_slist_free_full (priv->sources, (GDestroyNotify) g_source_destroy);
g_clear_pointer (&priv->current_idle_cancel_source, g_source_destroy);
@ -204,6 +237,10 @@ fp_device_get_property (GObject *object,
g_value_set_flags (value, priv->finger_status);
break;
case PROP_TEMPERATURE:
g_value_set_enum (value, priv->temp_current);
break;
case PROP_DRIVER:
g_value_set_static_string (value, FP_DEVICE_GET_CLASS (self)->id);
break;
@ -358,6 +395,13 @@ fp_device_class_init (FpDeviceClass *klass)
FP_TYPE_FINGER_STATUS_FLAGS, FP_FINGER_STATUS_NONE,
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
properties[PROP_TEMPERATURE] =
g_param_spec_enum ("temperature",
"Temperature",
"The temperature estimation for device to prevent overheating.",
FP_TYPE_TEMPERATURE, FP_TEMPERATURE_COLD,
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
properties[PROP_DRIVER] =
g_param_spec_string ("driver",
"Driver",
@ -616,6 +660,25 @@ fp_device_get_nr_enroll_stages (FpDevice *device)
return priv->nr_enroll_stages;
}
/**
* fp_device_get_temperature:
* @device: A #FpDevice
*
* Retrieves simple temperature information for device. It is not possible
* to use a device when this is #FP_TEMPERATURE_HOT.
*
* Returns: The current temperature estimation.
*/
FpTemperature
fp_device_get_temperature (FpDevice *device)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
g_return_val_if_fail (FP_IS_DEVICE (device), -1);
return priv->temp_current;
}
/**
* fp_device_supports_identify:
* @device: A #FpDevice
@ -902,6 +965,8 @@ fp_device_enroll (FpDevice *device,
priv->current_task = g_steal_pointer (&task);
maybe_cancel_on_cancelled (device, cancellable);
fpi_device_update_temp (device, TRUE);
data = g_new0 (FpEnrollData, 1);
data->print = g_object_ref_sink (template_print);
data->enroll_progress_cb = progress_cb;
@ -995,6 +1060,8 @@ fp_device_verify (FpDevice *device,
priv->current_task = g_steal_pointer (&task);
maybe_cancel_on_cancelled (device, cancellable);
fpi_device_update_temp (device, TRUE);
data = g_new0 (FpMatchData, 1);
data->enrolled_print = g_object_ref (enrolled_print);
data->match_cb = match_cb;
@ -1114,6 +1181,8 @@ fp_device_identify (FpDevice *device,
priv->current_task = g_steal_pointer (&task);
maybe_cancel_on_cancelled (device, cancellable);
fpi_device_update_temp (device, TRUE);
data = g_new0 (FpMatchData, 1);
/* We cannot store the gallery directly, because the ptr array may not own
* a reference to each print. Also, the caller could in principle modify the
@ -1231,6 +1300,8 @@ fp_device_capture (FpDevice *device,
priv->current_task = g_steal_pointer (&task);
maybe_cancel_on_cancelled (device, cancellable);
fpi_device_update_temp (device, TRUE);
priv->wait_for_finger = wait_for_finger;
cls->capture (device);

View File

@ -81,6 +81,23 @@ typedef enum {
FP_SCAN_TYPE_PRESS,
} FpScanType;
/**
* FpTemperature:
* @FP_TEMPERATURE_COLD: Sensor is considered cold.
* @FP_TEMPERATURE_WARM: Sensor is warm, usage time may be limited.
* @FP_TEMPERATURE_HOT: Sensor is hot and cannot be used.
*
* When a device is created, it is assumed to be cold. Applications such as
* fprintd may want to ensure all devices on the system are cold before
* shutting down in order to ensure that the cool-off period is not violated
* because the internal libfprint state about the device is lost.
*/
typedef enum {
FP_TEMPERATURE_COLD,
FP_TEMPERATURE_WARM,
FP_TEMPERATURE_HOT,
} FpTemperature;
/**
* FpDeviceRetry:
* @FP_DEVICE_RETRY_GENERAL: The scan did not succeed due to poor scan quality
@ -201,6 +218,7 @@ gboolean fp_device_is_open (FpDevice *device);
FpScanType fp_device_get_scan_type (FpDevice *device);
FpFingerStatusFlags fp_device_get_finger_status (FpDevice *device);
gint fp_device_get_nr_enroll_stages (FpDevice *device);
FpTemperature fp_device_get_temperature (FpDevice *device);
FpDeviceFeature fp_device_get_features (FpDevice *device);
gboolean fp_device_has_feature (FpDevice *device,

View File

@ -19,6 +19,8 @@
*/
#define FP_COMPONENT "device"
#include <math.h>
#include "fpi-log.h"
#include "fp-device-private.h"
@ -851,6 +853,8 @@ fp_device_task_return_in_idle_cb (gpointer user_data)
priv->current_action = FPI_DEVICE_ACTION_NONE;
priv->current_task_idle_return_source = NULL;
fpi_device_update_temp (data->device, FALSE);
if (action == FPI_DEVICE_ACTION_OPEN &&
data->type != FP_DEVICE_TASK_RETURN_ERROR)
{
@ -1685,3 +1689,113 @@ fpi_device_report_finger_status_changes (FpDevice *device,
return fpi_device_report_finger_status (device, finger_status);
}
static void
update_temp_timeout (FpDevice *device, gpointer user_data)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
fpi_device_update_temp (device, priv->temp_last_active);
}
/**
* fpi_device_update_temp:
* @device: The #FpDevice
* @is_active: Whether the device is now active
*
* Purely internal function to update the temperature. Also ensure that the
* state is updated once a threshold is reached.
*/
void
fpi_device_update_temp (FpDevice *device, gboolean is_active)
{
FpDevicePrivate *priv = fp_device_get_instance_private (device);
gint64 now = g_get_monotonic_time ();
gdouble passed_seconds;
gdouble alpha;
gdouble next_threshold;
gdouble old_ratio;
FpTemperature old_temp;
g_autofree char *old_temp_str = NULL;
g_autofree char *new_temp_str = NULL;
if (priv->temp_hot_seconds < 0)
{
g_debug ("Not updating temperature model, device can run continuously!");
return;
}
passed_seconds = (now - priv->temp_last_update) / 1e6;
old_ratio = priv->temp_current_ratio;
if (priv->temp_last_active)
{
alpha = exp (-passed_seconds / priv->temp_hot_seconds);
priv->temp_current_ratio = alpha * priv->temp_current_ratio + 1 - alpha;
}
else
{
alpha = exp (-passed_seconds / priv->temp_cold_seconds);
priv->temp_current_ratio = alpha * priv->temp_current_ratio;
}
priv->temp_last_active = is_active;
priv->temp_last_update = now;
old_temp = priv->temp_current;
if (priv->temp_current_ratio < TEMP_COLD_THRESH)
{
priv->temp_current = FP_TEMPERATURE_COLD;
next_threshold = is_active ? TEMP_COLD_THRESH : -1.0;
}
else if (priv->temp_current_ratio < TEMP_HOT_WARM_THRESH)
{
priv->temp_current = FP_TEMPERATURE_WARM;
next_threshold = is_active ? TEMP_WARM_HOT_THRESH : TEMP_COLD_THRESH;
}
else if (priv->temp_current_ratio < TEMP_WARM_HOT_THRESH)
{
/* Keep HOT until we reach TEMP_HOT_WARM_THRESH */
if (priv->temp_current != FP_TEMPERATURE_HOT)
priv->temp_current = FP_TEMPERATURE_WARM;
next_threshold = is_active ? TEMP_WARM_HOT_THRESH : TEMP_HOT_WARM_THRESH;
}
else
{
priv->temp_current = FP_TEMPERATURE_HOT;
next_threshold = is_active ? -1.0 : TEMP_HOT_WARM_THRESH;
}
old_temp_str = g_enum_to_string (FP_TYPE_TEMPERATURE, old_temp);
new_temp_str = g_enum_to_string (FP_TYPE_TEMPERATURE, priv->temp_current);
g_debug ("Updated temperature model after %0.2f seconds, ratio %0.2f -> %0.2f, active %d -> %d, %s -> %s",
passed_seconds,
old_ratio,
priv->temp_current_ratio,
priv->temp_last_active,
is_active,
old_temp_str,
new_temp_str);
if (priv->temp_current != old_temp)
g_object_notify (G_OBJECT (device), "temperature");
g_clear_pointer (&priv->temp_timeout, g_source_destroy);
if (next_threshold < 0)
return;
/* Set passed_seconds to the time until the next update is needed */
if (is_active)
passed_seconds = -priv->temp_hot_seconds * log ((next_threshold - 1.0) / (priv->temp_current_ratio - 1.0));
else
passed_seconds = -priv->temp_cold_seconds * log (next_threshold / priv->temp_current_ratio);
passed_seconds += TEMP_DELAY_SECONDS;
priv->temp_timeout = fpi_device_add_timeout (device,
passed_seconds * 1000,
update_temp_timeout,
NULL, NULL);
}

View File

@ -82,6 +82,10 @@ struct _FpIdEntry
* fpi_device_set_nr_enroll_stages() from @probe if this is dynamic.
* @scan_type: The scan type of supported devices; use
* fpi_device_set_scan_type() from @probe if this is dynamic.
* @temp_hot_seconds: Assumed time in seconds for the device to become too hot
* after being mostly cold. Set to -1 if the device can be always-on.
* @temp_cold_seconds: Assumed time in seconds for the device to be mostly cold
* after having been too hot to operate.
* @usb_discover: Class method to check whether a USB device is supported by
* the driver. Should return 0 if the device is unsupported and a positive
* score otherwise. The default score is 50 and the driver with the highest
@ -142,6 +146,10 @@ struct _FpDeviceClass
gint nr_enroll_stages;
FpScanType scan_type;
/* Simple device temperature model constants */
gint32 temp_hot_seconds;
gint32 temp_cold_seconds;
/* Callbacks */
gint (*usb_discover) (GUsbDevice *usb_device);
void (*probe) (FpDevice *device);