Added microsecond timestamp to sensor values for PS4 and PS5 controllers using the HIDAPI driver

This commit is contained in:
Sam Lantinga 2022-09-27 09:56:02 -07:00
parent d71df6448b
commit 2c518747b9
26 changed files with 204 additions and 63 deletions

View file

@ -474,6 +474,7 @@ typedef struct SDL_ControllerSensorEvent
SDL_JoystickID which; /**< The joystick instance id */ SDL_JoystickID which; /**< The joystick instance id */
Sint32 sensor; /**< The type of the sensor, one of the values of ::SDL_SensorType */ Sint32 sensor; /**< The type of the sensor, one of the values of ::SDL_SensorType */
float data[3]; /**< Up to 3 values from the sensor, as defined in SDL_sensor.h */ float data[3]; /**< Up to 3 values from the sensor, as defined in SDL_sensor.h */
Uint64 timestamp_us; /**< The timestamp of the sensor reading in microseconds, if the hardware provides this information. */
} SDL_ControllerSensorEvent; } SDL_ControllerSensorEvent;
/** /**
@ -565,6 +566,7 @@ typedef struct SDL_SensorEvent
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */ Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Sint32 which; /**< The instance ID of the sensor */ Sint32 which; /**< The instance ID of the sensor */
float data[6]; /**< Up to 6 values from the sensor - additional values can be queried using SDL_SensorGetData() */ float data[6]; /**< Up to 6 values from the sensor - additional values can be queried using SDL_SensorGetData() */
Uint64 timestamp_us; /**< The timestamp of the sensor reading in microseconds, if the hardware provides this information. */
} SDL_SensorEvent; } SDL_SensorEvent;
/** /**

View file

@ -895,6 +895,23 @@ extern DECLSPEC float SDLCALL SDL_GameControllerGetSensorDataRate(SDL_GameContro
*/ */
extern DECLSPEC int SDLCALL SDL_GameControllerGetSensorData(SDL_GameController *gamecontroller, SDL_SensorType type, float *data, int num_values); extern DECLSPEC int SDLCALL SDL_GameControllerGetSensorData(SDL_GameController *gamecontroller, SDL_SensorType type, float *data, int num_values);
/**
* Get the current state of a game controller sensor with the timestamp of the last update.
*
* The number of values and interpretation of the data is sensor dependent.
* See SDL_sensor.h for the details for each type of sensor.
*
* \param gamecontroller The controller to query
* \param type The type of sensor to query
* \param timestamp A pointer filled with the timestamp in microseconds of the current sensor reading if available, or 0 if not
* \param data A pointer filled with the current sensor state
* \param num_values The number of values to write to data
* \return 0 or -1 if an error occurred.
*
* \since This function is available since SDL 2.26.0.
*/
extern DECLSPEC int SDLCALL SDL_GameControllerGetSensorDataWithTimestamp(SDL_GameController *gamecontroller, SDL_SensorType type, Uint64 *timestamp, float *data, int num_values);
/** /**
* Start a rumble effect on a game controller. * Start a rumble effect on a game controller.
* *

View file

@ -269,6 +269,21 @@ extern DECLSPEC SDL_SensorID SDLCALL SDL_SensorGetInstanceID(SDL_Sensor *sensor)
*/ */
extern DECLSPEC int SDLCALL SDL_SensorGetData(SDL_Sensor *sensor, float *data, int num_values); extern DECLSPEC int SDLCALL SDL_SensorGetData(SDL_Sensor *sensor, float *data, int num_values);
/**
* Get the current state of an opened sensor with the timestamp of the last update.
*
* The number of values and interpretation of the data is sensor dependent.
*
* \param sensor The SDL_Sensor object to query
* \param timestamp A pointer filled with the timestamp in microseconds of the current sensor reading if available, or 0 if not
* \param data A pointer filled with the current sensor state
* \param num_values The number of values to write to data
* \returns 0 or -1 if an error occurred.
*
* \since This function is available since SDL 2.0.9.
*/
extern DECLSPEC int SDLCALL SDL_SensorGetDataWithTimestamp(SDL_Sensor *sensor, Uint64 *timestamp, float *data, int num_values);
/** /**
* Close a sensor previously opened with SDL_SensorOpen(). * Close a sensor previously opened with SDL_SensorOpen().
* *

View file

@ -863,3 +863,5 @@
++'_SDL_SetPrimarySelectionText'.'SDL2.dll'.'SDL_SetPrimarySelectionText' ++'_SDL_SetPrimarySelectionText'.'SDL2.dll'.'SDL_SetPrimarySelectionText'
++'_SDL_GetPrimarySelectionText'.'SDL2.dll'.'SDL_GetPrimarySelectionText' ++'_SDL_GetPrimarySelectionText'.'SDL2.dll'.'SDL_GetPrimarySelectionText'
++'_SDL_HasPrimarySelectionText'.'SDL2.dll'.'SDL_HasPrimarySelectionText' ++'_SDL_HasPrimarySelectionText'.'SDL2.dll'.'SDL_HasPrimarySelectionText'
++'_SDL_GameControllerGetSensorDataWithTimestamp'.'SDL2.dll'.'SDL_GameControllerGetSensorDataWithTimestamp'
++'_SDL_SensorGetDataWithTimestamp'.'SDL2.dll'.'SDL_SensorGetDataWithTimestamp'

View file

@ -889,3 +889,5 @@
#define SDL_SetPrimarySelectionText SDL_SetPrimarySelectionText_REAL #define SDL_SetPrimarySelectionText SDL_SetPrimarySelectionText_REAL
#define SDL_GetPrimarySelectionText SDL_GetPrimarySelectionText_REAL #define SDL_GetPrimarySelectionText SDL_GetPrimarySelectionText_REAL
#define SDL_HasPrimarySelectionText SDL_HasPrimarySelectionText_REAL #define SDL_HasPrimarySelectionText SDL_HasPrimarySelectionText_REAL
#define SDL_GameControllerGetSensorDataWithTimestamp SDL_GameControllerGetSensorDataWithTimestamp_REAL
#define SDL_SensorGetDataWithTimestamp SDL_SensorGetDataWithTimestamp_REAL

View file

@ -972,3 +972,5 @@ SDL_DYNAPI_PROC(void,SDL_GetJoystickGUIDInfo,(SDL_JoystickGUID a, Uint16 *b, Uin
SDL_DYNAPI_PROC(int,SDL_SetPrimarySelectionText,(const char *a),(a),return) SDL_DYNAPI_PROC(int,SDL_SetPrimarySelectionText,(const char *a),(a),return)
SDL_DYNAPI_PROC(char*,SDL_GetPrimarySelectionText,(void),(),return) SDL_DYNAPI_PROC(char*,SDL_GetPrimarySelectionText,(void),(),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_HasPrimarySelectionText,(void),(),return) SDL_DYNAPI_PROC(SDL_bool,SDL_HasPrimarySelectionText,(void),(),return)
SDL_DYNAPI_PROC(int,SDL_GameControllerGetSensorDataWithTimestamp,(SDL_GameController *a, SDL_SensorType b, Uint64 *c, float *d, int e),(a,b,c,d,e),return)
SDL_DYNAPI_PROC(int,SDL_SensorGetDataWithTimestamp,(SDL_Sensor *a, Uint64 *b, float *c, int d),(a,b,c,d),return)

View file

@ -2440,6 +2440,15 @@ SDL_GameControllerGetSensorDataRate(SDL_GameController *gamecontroller, SDL_Sens
*/ */
int int
SDL_GameControllerGetSensorData(SDL_GameController *gamecontroller, SDL_SensorType type, float *data, int num_values) SDL_GameControllerGetSensorData(SDL_GameController *gamecontroller, SDL_SensorType type, float *data, int num_values)
{
return SDL_GameControllerGetSensorDataWithTimestamp(gamecontroller, type, NULL, data, num_values);
}
/*
* Get the current state of a game controller sensor.
*/
int
SDL_GameControllerGetSensorDataWithTimestamp(SDL_GameController *gamecontroller, SDL_SensorType type, Uint64 *timestamp, float *data, int num_values)
{ {
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller); SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
int i; int i;
@ -2454,6 +2463,9 @@ SDL_GameControllerGetSensorData(SDL_GameController *gamecontroller, SDL_SensorTy
if (sensor->type == type) { if (sensor->type == type) {
num_values = SDL_min(num_values, SDL_arraysize(sensor->data)); num_values = SDL_min(num_values, SDL_arraysize(sensor->data));
SDL_memcpy(data, sensor->data, num_values*sizeof(*data)); SDL_memcpy(data, sensor->data, num_values*sizeof(*data));
if (timestamp) {
*timestamp = sensor->timestamp_us;
}
return 0; return 0;
} }
} }

View file

@ -2983,7 +2983,7 @@ int SDL_PrivateJoystickTouchpad(SDL_Joystick *joystick, int touchpad, int finger
return posted; return posted;
} }
int SDL_PrivateJoystickSensor(SDL_Joystick *joystick, SDL_SensorType type, const float *data, int num_values) int SDL_PrivateJoystickSensor(SDL_Joystick *joystick, SDL_SensorType type, Uint64 timestamp_us, const float *data, int num_values)
{ {
int i; int i;
int posted = 0; int posted = 0;
@ -3004,6 +3004,7 @@ int SDL_PrivateJoystickSensor(SDL_Joystick *joystick, SDL_SensorType type, const
/* Update internal sensor state */ /* Update internal sensor state */
SDL_memcpy(sensor->data, data, num_values*sizeof(*data)); SDL_memcpy(sensor->data, data, num_values*sizeof(*data));
sensor->timestamp_us = timestamp_us;
/* Post the event, if desired */ /* Post the event, if desired */
#if !SDL_EVENTS_DISABLED #if !SDL_EVENTS_DISABLED
@ -3015,6 +3016,7 @@ int SDL_PrivateJoystickSensor(SDL_Joystick *joystick, SDL_SensorType type, const
num_values = SDL_min(num_values, SDL_arraysize(event.csensor.data)); num_values = SDL_min(num_values, SDL_arraysize(event.csensor.data));
SDL_memset(event.csensor.data, 0, sizeof(event.csensor.data)); SDL_memset(event.csensor.data, 0, sizeof(event.csensor.data));
SDL_memcpy(event.csensor.data, data, num_values*sizeof(*data)); SDL_memcpy(event.csensor.data, data, num_values*sizeof(*data));
event.csensor.timestamp_us = timestamp_us;
posted = SDL_PushEvent(&event) == 1; posted = SDL_PushEvent(&event) == 1;
} }
#endif /* !SDL_EVENTS_DISABLED */ #endif /* !SDL_EVENTS_DISABLED */

View file

@ -160,7 +160,7 @@ extern int SDL_PrivateJoystickButton(SDL_Joystick *joystick,
extern int SDL_PrivateJoystickTouchpad(SDL_Joystick *joystick, extern int SDL_PrivateJoystickTouchpad(SDL_Joystick *joystick,
int touchpad, int finger, Uint8 state, float x, float y, float pressure); int touchpad, int finger, Uint8 state, float x, float y, float pressure);
extern int SDL_PrivateJoystickSensor(SDL_Joystick *joystick, extern int SDL_PrivateJoystickSensor(SDL_Joystick *joystick,
SDL_SensorType type, const float *data, int num_values); SDL_SensorType type, Uint64 timestamp_us, const float *data, int num_values);
extern void SDL_PrivateJoystickBatteryLevel(SDL_Joystick *joystick, extern void SDL_PrivateJoystickBatteryLevel(SDL_Joystick *joystick,
SDL_JoystickPowerLevel ePowerLevel); SDL_JoystickPowerLevel ePowerLevel);

View file

@ -64,6 +64,7 @@ typedef struct _SDL_JoystickSensorInfo
SDL_bool enabled; SDL_bool enabled;
float rate; float rate;
float data[3]; /* If this needs to expand, update SDL_ControllerSensorEvent */ float data[3]; /* If this needs to expand, update SDL_ControllerSensorEvent */
Uint64 timestamp_us;
} SDL_JoystickSensorInfo; } SDL_JoystickSensorInfo;
struct _SDL_Joystick struct _SDL_Joystick

View file

@ -487,7 +487,7 @@ HIDAPI_DriverPS3_HandleStatePacket(SDL_Joystick *joystick, SDL_DriverPS3_Context
sensor_data[0] = HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[41], data[42])); sensor_data[0] = HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[41], data[42]));
sensor_data[1] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[45], data[46])); sensor_data[1] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[45], data[46]));
sensor_data[2] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[43], data[44])); sensor_data[2] = -HIDAPI_DriverPS3_ScaleAccel(LOAD16(data[43], data[44]));
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, sensor_data, SDL_arraysize(sensor_data)); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, 0, sensor_data, SDL_arraysize(sensor_data));
} }
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));

View file

@ -87,7 +87,8 @@ typedef struct
Uint8 rgucButtonsHatAndCounter[3]; Uint8 rgucButtonsHatAndCounter[3];
Uint8 ucTriggerLeft; Uint8 ucTriggerLeft;
Uint8 ucTriggerRight; Uint8 ucTriggerRight;
Uint8 _rgucPad0[ 3 ]; Uint8 rgucTimestamp[2];
Uint8 _rgucPad0[1];
Uint8 rgucGyroX[2]; Uint8 rgucGyroX[2];
Uint8 rgucGyroY[2]; Uint8 rgucGyroY[2];
Uint8 rgucGyroZ[2]; Uint8 rgucGyroZ[2];
@ -147,6 +148,8 @@ typedef struct {
Uint8 led_red; Uint8 led_red;
Uint8 led_green; Uint8 led_green;
Uint8 led_blue; Uint8 led_blue;
Uint16 last_timestamp;
Uint64 timestamp;
PS4StatePacket_t last_state; PS4StatePacket_t last_state;
} SDL_DriverPS4_Context; } SDL_DriverPS4_Context;
@ -807,6 +810,7 @@ HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joysti
HIDAPI_DriverPS4_LoadCalibrationData(device); HIDAPI_DriverPS4_LoadCalibrationData(device);
} }
ctx->report_sensors = enabled; ctx->report_sensors = enabled;
ctx->timestamp = 0;
return 0; return 0;
} }
@ -944,17 +948,38 @@ HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev,
} }
if (ctx->report_sensors) { if (ctx->report_sensors) {
Uint16 timestamp;
Uint64 timestamp_us;
float data[3]; float data[3];
timestamp = LOAD16(packet->rgucTimestamp[0], packet->rgucTimestamp[1]);
if (ctx->timestamp) {
Uint16 delta;
if (ctx->last_timestamp > timestamp) {
delta = (SDL_MAX_UINT16 - ctx->last_timestamp + timestamp + 1);
} else {
delta = (timestamp - ctx->last_timestamp);
}
ctx->timestamp += delta;
} else {
ctx->timestamp = timestamp;
}
ctx->last_timestamp = timestamp;
/* Sensor timestamp is in 5.33us units */
timestamp_us = (ctx->timestamp * 16) / 3;
data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1])); data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1]));
data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1])); data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1]));
data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1])); data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1]));
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, data, 3); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, timestamp_us, data, 3);
data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1])); data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1]));
data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1])); data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1]));
data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1])); data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1]));
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, data, 3); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, timestamp_us, data, 3);
} }
SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state)); SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));

View file

@ -94,6 +94,7 @@ typedef struct
Uint8 rgucAccelX[2]; /* 21 */ Uint8 rgucAccelX[2]; /* 21 */
Uint8 rgucAccelY[2]; /* 23 */ Uint8 rgucAccelY[2]; /* 23 */
Uint8 rgucAccelZ[2]; /* 25 */ Uint8 rgucAccelZ[2]; /* 25 */
Uint8 rgucSensorTimestamp[4]; /* 27 - 32 bit little endian */
} PS5StatePacketCommon_t; } PS5StatePacketCommon_t;
@ -114,8 +115,8 @@ typedef struct
Uint8 rgucAccelX[2]; /* 21 */ Uint8 rgucAccelX[2]; /* 21 */
Uint8 rgucAccelY[2]; /* 23 */ Uint8 rgucAccelY[2]; /* 23 */
Uint8 rgucAccelZ[2]; /* 25 */ Uint8 rgucAccelZ[2]; /* 25 */
Uint8 rgucTimer1[4]; /* 27 - 32 bit little endian */ Uint8 rgucSensorTimestamp[4]; /* 27 - 32 bit little endian */
Uint8 ucBatteryTemp; /* 31 */ Uint8 ucSensorTemp; /* 31 */
Uint8 ucTouchpadCounter1; /* 32 - high bit clear + counter */ Uint8 ucTouchpadCounter1; /* 32 - high bit clear + counter */
Uint8 rgucTouchpadData1[3]; /* 33 - X/Y, 12 bits per axis */ Uint8 rgucTouchpadData1[3]; /* 33 - X/Y, 12 bits per axis */
Uint8 ucTouchpadCounter2; /* 36 - high bit clear + counter */ Uint8 ucTouchpadCounter2; /* 36 - high bit clear + counter */
@ -145,7 +146,7 @@ typedef struct
Uint8 rgucAccelX[2]; /* 21 */ Uint8 rgucAccelX[2]; /* 21 */
Uint8 rgucAccelY[2]; /* 23 */ Uint8 rgucAccelY[2]; /* 23 */
Uint8 rgucAccelZ[2]; /* 25 */ Uint8 rgucAccelZ[2]; /* 25 */
Uint8 rgucUnknown1[4]; /* 27 */ Uint8 rgucSensorTimestamp[4]; /* 27 - 32 bit little endian */
Uint8 ucTouchpadCounter1; /* 31 - high bit clear + counter */ Uint8 ucTouchpadCounter1; /* 31 - high bit clear + counter */
Uint8 rgucTouchpadData1[3]; /* 32 - X/Y, 12 bits per axis */ Uint8 rgucTouchpadData1[3]; /* 32 - X/Y, 12 bits per axis */
Uint8 ucTouchpadCounter2; /* 35 - high bit clear + counter */ Uint8 ucTouchpadCounter2; /* 35 - high bit clear + counter */
@ -225,6 +226,8 @@ typedef struct {
Uint8 led_green; Uint8 led_green;
Uint8 led_blue; Uint8 led_blue;
EDS5LEDResetState led_reset_state; EDS5LEDResetState led_reset_state;
Uint32 last_timestamp;
Uint64 timestamp;
union union
{ {
PS5SimpleStatePacket_t simple; PS5SimpleStatePacket_t simple;
@ -706,21 +709,21 @@ HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_HIDAPI_Device *device)
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
SDL_bool led_reset_complete = SDL_FALSE; SDL_bool led_reset_complete = SDL_FALSE;
if (ctx->use_alternate_report) { if (ctx->sensors_supported) {
/* We don't know how to check the timer, just assume it's complete for now */ const PS5StatePacketCommon_t *packet = &ctx->last_state.state;
led_reset_complete = SDL_TRUE;
} else {
const PS5StatePacket_t *packet = &ctx->last_state.full_state;
/* Check the timer to make sure the Bluetooth connection LED animation is complete */ /* Check the timer to make sure the Bluetooth connection LED animation is complete */
const Uint32 connection_complete = 10200000; const Uint32 connection_complete = 10200000;
Uint32 timer = ((Uint32)packet->rgucTimer1[0] << 0) | Uint32 timestamp = LOAD32(packet->rgucSensorTimestamp[0],
((Uint32)packet->rgucTimer1[1] << 8) | packet->rgucSensorTimestamp[1],
((Uint32)packet->rgucTimer1[2] << 16) | packet->rgucSensorTimestamp[2],
((Uint32)packet->rgucTimer1[3] << 24); packet->rgucSensorTimestamp[3]);
if (SDL_TICKS_PASSED(timer, connection_complete)) { if (SDL_TICKS_PASSED(timestamp, connection_complete)) {
led_reset_complete = SDL_TRUE; led_reset_complete = SDL_TRUE;
} }
} else {
/* We don't know how to check the timer, just assume it's complete for now */
led_reset_complete = SDL_TRUE;
} }
if (led_reset_complete) { if (led_reset_complete) {
@ -759,9 +762,15 @@ HIDAPI_DriverPS5_SetEnhancedMode(SDL_HIDAPI_Device *device, SDL_Joystick *joysti
ctx->report_touchpad = SDL_TRUE; ctx->report_touchpad = SDL_TRUE;
} }
if (ctx->sensors_supported) { if (ctx->sensors_supported) {
if (device->is_bluetooth) {
/* Bluetooth sensor update rate appears to be 1000 Hz */
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 1000.0f);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 1000.0f);
} else {
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
} }
}
/* Switch into enhanced report mode */ /* Switch into enhanced report mode */
HIDAPI_DriverPS5_UpdateEffects(device, 0); HIDAPI_DriverPS5_UpdateEffects(device, 0);
@ -989,6 +998,7 @@ HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joysti
HIDAPI_DriverPS5_LoadCalibrationData(device); HIDAPI_DriverPS5_LoadCalibrationData(device);
} }
ctx->report_sensors = enabled; ctx->report_sensors = enabled;
ctx->timestamp = 0;
return 0; return 0;
} }
@ -1180,17 +1190,40 @@ HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL_hid_device
SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis); SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis);
if (ctx->report_sensors) { if (ctx->report_sensors) {
Uint32 timestamp;
Uint64 timestamp_us;
float data[3]; float data[3];
timestamp = LOAD32(packet->rgucSensorTimestamp[0],
packet->rgucSensorTimestamp[1],
packet->rgucSensorTimestamp[2],
packet->rgucSensorTimestamp[3]);
if (ctx->timestamp) {
Uint32 delta;
if (ctx->last_timestamp > timestamp) {
delta = (SDL_MAX_UINT32 - ctx->last_timestamp + timestamp + 1);
} else {
delta = (timestamp - ctx->last_timestamp);
}
ctx->timestamp += delta;
} else {
ctx->timestamp = timestamp;
}
ctx->last_timestamp = timestamp;
/* Sensor timestamp is in 0.33us units */
timestamp_us = ctx->timestamp / 3;
data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1])); data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1]));
data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1])); data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1]));
data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1])); data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1]));
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, data, 3); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, timestamp_us, data, 3);
data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1])); data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1]));
data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1])); data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1]));
data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1])); data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1]));
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, data, 3); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, timestamp_us, data, 3);
} }
} }

View file

@ -1241,12 +1241,12 @@ HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device)
values[0] = (ctx->m_state.sGyroX / 32768.0f) * (2000.0f * (M_PI / 180.0f)); values[0] = (ctx->m_state.sGyroX / 32768.0f) * (2000.0f * (M_PI / 180.0f));
values[1] = (ctx->m_state.sGyroZ / 32768.0f) * (2000.0f * (M_PI / 180.0f)); values[1] = (ctx->m_state.sGyroZ / 32768.0f) * (2000.0f * (M_PI / 180.0f));
values[2] = (ctx->m_state.sGyroY / 32768.0f) * (2000.0f * (M_PI / 180.0f)); values[2] = (ctx->m_state.sGyroY / 32768.0f) * (2000.0f * (M_PI / 180.0f));
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, values, 3); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, 0, values, 3);
values[0] = (ctx->m_state.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; values[0] = (ctx->m_state.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
values[1] = (ctx->m_state.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; values[1] = (ctx->m_state.sAccelZ / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
values[2] = (-ctx->m_state.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; values[2] = (-ctx->m_state.sAccelY / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY;
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, values, 3); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, 0, values, 3);
} }
ctx->m_last_state = ctx->m_state; ctx->m_last_state = ctx->m_state;

View file

@ -1797,7 +1797,7 @@ static void SendSensorUpdate(SDL_Joystick *joystick, SDL_DriverSwitch_Context *c
data[0] = -tmp; data[0] = -tmp;
} }
SDL_PrivateJoystickSensor(joystick, type, data, 3); SDL_PrivateJoystickSensor(joystick, type, 0, data, 3);
} }
static void HandleCombinedControllerStateL(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) static void HandleCombinedControllerStateL(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet)

View file

@ -1151,7 +1151,7 @@ static void HandleNunchuckButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *j
values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL_L, values, 3); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL_L, 0, values, 3);
} }
} }
@ -1191,7 +1191,7 @@ static void HandleMotionPlusData(SDL_DriverWii_Context *ctx, SDL_Joystick *joyst
values[0] = -((float)z / GYRO_RES_PER_DEGREE) * (float)M_PI / 180.0f; values[0] = -((float)z / GYRO_RES_PER_DEGREE) * (float)M_PI / 180.0f;
values[1] = ((float)x / GYRO_RES_PER_DEGREE) * (float)M_PI / 180.0f; values[1] = ((float)x / GYRO_RES_PER_DEGREE) * (float)M_PI / 180.0f;
values[2] = ((float)y / GYRO_RES_PER_DEGREE) * (float)M_PI / 180.0f; values[2] = ((float)y / GYRO_RES_PER_DEGREE) * (float)M_PI / 180.0f;
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, values, 3); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, 0, values, 3);
} }
} }
@ -1212,7 +1212,7 @@ static void HandleWiiRemoteAccelData(SDL_DriverWii_Context *ctx, SDL_Joystick *j
values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; values[0] = -((float)x / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; values[1] = ((float)z / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY; values[2] = ((float)y / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, values, 3); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, 0, values, 3);
} }
static void HandleButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, WiiButtonData *data) static void HandleButtonData(SDL_DriverWii_Context *ctx, SDL_Joystick *joystick, WiiButtonData *data)

View file

@ -1064,14 +1064,14 @@ IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
data[0] = rate.x; data[0] = rate.x;
data[1] = rate.z; data[1] = rate.z;
data[2] = -rate.y; data[2] = -rate.y;
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, data, 3); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, 0, data, 3);
} }
if (motion.hasGravityAndUserAcceleration) { if (motion.hasGravityAndUserAcceleration) {
GCAcceleration accel = motion.acceleration; GCAcceleration accel = motion.acceleration;
data[0] = -accel.x * SDL_STANDARD_GRAVITY; data[0] = -accel.x * SDL_STANDARD_GRAVITY;
data[1] = -accel.y * SDL_STANDARD_GRAVITY; data[1] = -accel.y * SDL_STANDARD_GRAVITY;
data[2] = -accel.z * SDL_STANDARD_GRAVITY; data[2] = -accel.z * SDL_STANDARD_GRAVITY;
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, data, 3); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, 0, data, 3);
} }
} }
} }

View file

@ -378,6 +378,15 @@ SDL_SensorGetInstanceID(SDL_Sensor * sensor)
*/ */
int int
SDL_SensorGetData(SDL_Sensor *sensor, float *data, int num_values) SDL_SensorGetData(SDL_Sensor *sensor, float *data, int num_values)
{
return SDL_SensorGetDataWithTimestamp(sensor, NULL, data, num_values);
}
/*
* Get the current state of this sensor
*/
int
SDL_SensorGetDataWithTimestamp(SDL_Sensor *sensor, Uint64 *timestamp, float *data, int num_values)
{ {
if (!SDL_PrivateSensorValid(sensor)) { if (!SDL_PrivateSensorValid(sensor)) {
return -1; return -1;
@ -385,6 +394,9 @@ SDL_SensorGetData(SDL_Sensor * sensor, float *data, int num_values)
num_values = SDL_min(num_values, SDL_arraysize(sensor->data)); num_values = SDL_min(num_values, SDL_arraysize(sensor->data));
SDL_memcpy(data, sensor->data, num_values*sizeof(*data)); SDL_memcpy(data, sensor->data, num_values*sizeof(*data));
if (timestamp) {
*timestamp = sensor->timestamp_us;
}
return 0; return 0;
} }
@ -478,7 +490,7 @@ SDL_SensorQuit(void)
/* These are global for SDL_syssensor.c and SDL_events.c */ /* These are global for SDL_syssensor.c and SDL_events.c */
int int
SDL_PrivateSensorUpdate(SDL_Sensor *sensor, float *data, int num_values) SDL_PrivateSensorUpdate(SDL_Sensor *sensor, Uint64 timestamp_us, float *data, int num_values)
{ {
int posted; int posted;
@ -487,6 +499,7 @@ SDL_PrivateSensorUpdate(SDL_Sensor *sensor, float *data, int num_values)
/* Update internal sensor state */ /* Update internal sensor state */
num_values = SDL_min(num_values, SDL_arraysize(sensor->data)); num_values = SDL_min(num_values, SDL_arraysize(sensor->data));
SDL_memcpy(sensor->data, data, num_values*sizeof(*data)); SDL_memcpy(sensor->data, data, num_values*sizeof(*data));
sensor->timestamp_us = timestamp_us;
/* Post the event, if desired */ /* Post the event, if desired */
posted = 0; posted = 0;
@ -498,6 +511,7 @@ SDL_PrivateSensorUpdate(SDL_Sensor *sensor, float *data, int num_values)
num_values = SDL_min(num_values, SDL_arraysize(event.sensor.data)); num_values = SDL_min(num_values, SDL_arraysize(event.sensor.data));
SDL_memset(event.sensor.data, 0, sizeof(event.sensor.data)); SDL_memset(event.sensor.data, 0, sizeof(event.sensor.data));
SDL_memcpy(event.sensor.data, data, num_values*sizeof(*data)); SDL_memcpy(event.sensor.data, data, num_values*sizeof(*data));
event.sensor.timestamp_us = timestamp_us;
posted = SDL_PushEvent(&event) == 1; posted = SDL_PushEvent(&event) == 1;
} }
#endif /* !SDL_EVENTS_DISABLED */ #endif /* !SDL_EVENTS_DISABLED */

View file

@ -37,7 +37,7 @@ extern int SDL_SensorInit(void);
extern void SDL_SensorQuit(void); extern void SDL_SensorQuit(void);
/* Internal event queueing functions */ /* Internal event queueing functions */
extern int SDL_PrivateSensorUpdate(SDL_Sensor *sensor, float *data, int num_values); extern int SDL_PrivateSensorUpdate(SDL_Sensor *sensor, Uint64 timestamp_us, float *data, int num_values);
#endif /* SDL_sensor_c_h_ */ #endif /* SDL_sensor_c_h_ */

View file

@ -37,6 +37,7 @@ struct _SDL_Sensor
SDL_SensorType type; /* Type of the sensor */ SDL_SensorType type; /* Type of the sensor */
int non_portable_type; /* Platform dependent type of the sensor */ int non_portable_type; /* Platform dependent type of the sensor */
Uint64 timestamp_us; /* The timestamp of the last sensor update */
float data[16]; /* The current state of the sensor */ float data[16]; /* The current state of the sensor */
struct _SDL_SensorDriver *driver; struct _SDL_SensorDriver *driver;

View file

@ -174,7 +174,7 @@ SDL_ANDROID_SensorUpdate(SDL_Sensor *sensor)
if (ALooper_pollAll(0, NULL, &events, (void**)&source) == LOOPER_ID_USER) { if (ALooper_pollAll(0, NULL, &events, (void**)&source) == LOOPER_ID_USER) {
SDL_zero(event); SDL_zero(event);
while (ASensorEventQueue_getEvents(sensor->hwdata->eventqueue, &event, 1) > 0) { while (ASensorEventQueue_getEvents(sensor->hwdata->eventqueue, &event, 1) > 0) {
SDL_PrivateSensorUpdate(sensor, event.data, SDL_arraysize(event.data)); SDL_PrivateSensorUpdate(sensor, 0, event.data, SDL_arraysize(event.data));
} }
} }
} }

View file

@ -162,7 +162,7 @@ SDL_COREMOTION_SensorUpdate(SDL_Sensor *sensor)
data[1] = -acceleration.y * SDL_STANDARD_GRAVITY; data[1] = -acceleration.y * SDL_STANDARD_GRAVITY;
data[2] = -acceleration.z * SDL_STANDARD_GRAVITY; data[2] = -acceleration.z * SDL_STANDARD_GRAVITY;
if (SDL_memcmp(data, sensor->hwdata->data, sizeof(data)) != 0) { if (SDL_memcmp(data, sensor->hwdata->data, sizeof(data)) != 0) {
SDL_PrivateSensorUpdate(sensor, data, SDL_arraysize(data)); SDL_PrivateSensorUpdate(sensor, 0, data, SDL_arraysize(data));
SDL_memcpy(sensor->hwdata->data, data, sizeof(data)); SDL_memcpy(sensor->hwdata->data, data, sizeof(data));
} }
} }
@ -178,7 +178,7 @@ SDL_COREMOTION_SensorUpdate(SDL_Sensor *sensor)
data[1] = rotationRate.y; data[1] = rotationRate.y;
data[2] = rotationRate.z; data[2] = rotationRate.z;
if (SDL_memcmp(data, sensor->hwdata->data, sizeof(data)) != 0) { if (SDL_memcmp(data, sensor->hwdata->data, sizeof(data)) != 0) {
SDL_PrivateSensorUpdate(sensor, data, SDL_arraysize(data)); SDL_PrivateSensorUpdate(sensor, 0, data, SDL_arraysize(data));
SDL_memcpy(sensor->hwdata->data, data, sizeof(data)); SDL_memcpy(sensor->hwdata->data, data, sizeof(data));
} }
} }

View file

@ -154,7 +154,24 @@ SDL_VITA_SensorUpdate(SDL_Sensor *sensor)
{ {
if (sensor->hwdata->counter < motionState[i].counter) if (sensor->hwdata->counter < motionState[i].counter)
{ {
unsigned int timestamp = motionState[i].timestamp;
sensor->hwdata->counter = motionState[i].counter; sensor->hwdata->counter = motionState[i].counter;
if (sensor->hwdata->timestamp_us) {
unsigned int delta;
if (sensor->hwdata->last_timestamp > timestamp) {
SDL_COMPILE_TIME_ASSERT(sizeof(timestamp) == sizeof(Uint32));
delta = (SDL_MAX_UINT32 - sensor->hwdata->last_timestamp + timestamp + 1);
} else {
delta = (timestamp - sensor->hwdata->last_timestamp);
}
sensor->hwdata->timestamp_us += delta;
} else {
sensor->hwdata->timestamp_us = timestamp;
}
sensor->hwdata->last_timestamp = timestamp;
switch (sensor->type) switch (sensor->type)
{ {
case SDL_SENSOR_ACCEL: case SDL_SENSOR_ACCEL:
@ -163,10 +180,7 @@ SDL_VITA_SensorUpdate(SDL_Sensor *sensor)
data[0] = motionState[i].accelerometer.x * SDL_STANDARD_GRAVITY; data[0] = motionState[i].accelerometer.x * SDL_STANDARD_GRAVITY;
data[1] = motionState[i].accelerometer.y * SDL_STANDARD_GRAVITY; data[1] = motionState[i].accelerometer.y * SDL_STANDARD_GRAVITY;
data[2] = motionState[i].accelerometer.z * SDL_STANDARD_GRAVITY; data[2] = motionState[i].accelerometer.z * SDL_STANDARD_GRAVITY;
if (SDL_memcmp(data, sensor->hwdata->data, sizeof(data)) != 0) { SDL_PrivateSensorUpdate(sensor, sensor->hwdata->timestamp_us, data, SDL_arraysize(data));
SDL_PrivateSensorUpdate(sensor, data, SDL_arraysize(data));
SDL_memcpy(sensor->hwdata->data, data, sizeof(data));
}
} }
break; break;
case SDL_SENSOR_GYRO: case SDL_SENSOR_GYRO:
@ -175,10 +189,7 @@ SDL_VITA_SensorUpdate(SDL_Sensor *sensor)
data[0] = motionState[i].gyro.x; data[0] = motionState[i].gyro.x;
data[1] = motionState[i].gyro.y; data[1] = motionState[i].gyro.y;
data[2] = motionState[i].gyro.z; data[2] = motionState[i].gyro.z;
if (SDL_memcmp(data, sensor->hwdata->data, sizeof(data)) != 0) { SDL_PrivateSensorUpdate(sensor, sensor->hwdata->timestamp_us, data, SDL_arraysize(data));
SDL_PrivateSensorUpdate(sensor, data, SDL_arraysize(data));
SDL_memcpy(sensor->hwdata->data, data, sizeof(data));
}
} }
break; break;
default: default:

View file

@ -23,8 +23,9 @@
/* The private structure used to keep track of a sensor */ /* The private structure used to keep track of a sensor */
struct sensor_hwdata struct sensor_hwdata
{ {
float data[3];
Uint32 counter; Uint32 counter;
unsigned int last_timestamp;
Uint64 timestamp_us;
}; };
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */

View file

@ -171,7 +171,7 @@ static HRESULT STDMETHODCALLTYPE ISensorEventsVtbl_OnDataUpdated(ISensorEvents *
values[0] = (float)valueX.dblVal * SDL_STANDARD_GRAVITY; values[0] = (float)valueX.dblVal * SDL_STANDARD_GRAVITY;
values[1] = (float)valueY.dblVal * SDL_STANDARD_GRAVITY; values[1] = (float)valueY.dblVal * SDL_STANDARD_GRAVITY;
values[2] = (float)valueZ.dblVal * SDL_STANDARD_GRAVITY; values[2] = (float)valueZ.dblVal * SDL_STANDARD_GRAVITY;
SDL_PrivateSensorUpdate(SDL_sensors[i].sensor_opened, values, 3); SDL_PrivateSensorUpdate(SDL_sensors[i].sensor_opened, 0, values, 3);
} }
break; break;
case SDL_SENSOR_GYRO: case SDL_SENSOR_GYRO:
@ -186,7 +186,7 @@ static HRESULT STDMETHODCALLTYPE ISensorEventsVtbl_OnDataUpdated(ISensorEvents *
values[0] = (float)valueX.dblVal * DEGREES_TO_RADIANS; values[0] = (float)valueX.dblVal * DEGREES_TO_RADIANS;
values[1] = (float)valueY.dblVal * DEGREES_TO_RADIANS; values[1] = (float)valueY.dblVal * DEGREES_TO_RADIANS;
values[2] = (float)valueZ.dblVal * DEGREES_TO_RADIANS; values[2] = (float)valueZ.dblVal * DEGREES_TO_RADIANS;
SDL_PrivateSensorUpdate(SDL_sensors[i].sensor_opened, values, 3); SDL_PrivateSensorUpdate(SDL_sensors[i].sensor_opened, 0, values, 3);
} }
break; break;
default: default:

View file

@ -592,12 +592,13 @@ loop(void *arg)
#define VERBOSE_SENSORS #define VERBOSE_SENSORS
#ifdef VERBOSE_SENSORS #ifdef VERBOSE_SENSORS
case SDL_CONTROLLERSENSORUPDATE: case SDL_CONTROLLERSENSORUPDATE:
SDL_Log("Controller %d sensor %s: %.2f, %.2f, %.2f\n", SDL_Log("Controller %d sensor %s: %.2f, %.2f, %.2f (%"SDL_PRIu64")\n",
event.csensor.which, event.csensor.which,
GetSensorName((SDL_SensorType)event.csensor.sensor), GetSensorName((SDL_SensorType)event.csensor.sensor),
event.csensor.data[0], event.csensor.data[0],
event.csensor.data[1], event.csensor.data[1],
event.csensor.data[2]); event.csensor.data[2],
event.csensor.timestamp_us);
break; break;
#endif /* VERBOSE_SENSORS */ #endif /* VERBOSE_SENSORS */