diff options
author | Richard Hughes <richard@hughsie.com> | 2017-07-24 15:13:45 +0100 |
---|---|---|
committer | Richard Hughes <richard@hughsie.com> | 2017-07-24 16:13:43 +0100 |
commit | dd22416e8119db4fe91c7d4c01a505d96c6c2e2c (patch) | |
tree | 23920e5e0ea6c7a5e5aaa291c67b2dd4cd6a3b9f /src | |
parent | 8fdf55d17437133bf6fbd23897236121f48c5e8e (diff) | |
download | colord-dd22416e8119db4fe91c7d4c01a505d96c6c2e2c.tar.gz |
trivial: Build the private sensor code into the .so itself
Diffstat (limited to 'src')
27 files changed, 4054 insertions, 15 deletions
diff --git a/src/sensors/Makefile.am b/src/sensors/Makefile.am index d369259..58a49ff 100644 --- a/src/sensors/Makefile.am +++ b/src/sensors/Makefile.am @@ -11,6 +11,7 @@ AM_CPPFLAGS = \ $(GLIB_CFLAGS) \ $(GUSB_CFLAGS) \ $(GUDEV_CFLAGS) \ + $(POLKIT_CFLAGS) \ -I$(top_srcdir)/src \ -I$(top_srcdir)/lib \ -I$(top_builddir)/lib \ @@ -33,13 +34,13 @@ noinst_PROGRAMS = \ cd-parse-beagle cd_parse_beagle_SOURCES = \ + huey/huey-enum.c \ + munki/munki-enum.c \ cd-parse-beagle.c cd_parse_beagle_LDADD = \ $(COLORD_PRIVATE_LIBS) \ - $(GLIB_LIBS) \ - $(top_builddir)/lib/huey/libhuey-private.la \ - $(top_builddir)/lib/munki/libmunki-private.la + $(GLIB_LIBS) cd_parse_beagle_CFLAGS = \ $(WARN_CFLAGS) diff --git a/src/sensors/cd-parse-beagle.c b/src/sensors/cd-parse-beagle.c index 31bda23..36a1974 100644 --- a/src/sensors/cd-parse-beagle.c +++ b/src/sensors/cd-parse-beagle.c @@ -25,8 +25,9 @@ #include <sys/types.h> #include <stdlib.h> #include <glib.h> -#include <huey/huey.h> -#include <munki/munki.h> + +#include "huey/huey-enum.h" +#include "munki/munki-enum.h" #include "cd-sensor.h" diff --git a/src/sensors/dtp94/Makefile.am b/src/sensors/dtp94/Makefile.am index 6df053b..0b688ab 100644 --- a/src/sensors/dtp94/Makefile.am +++ b/src/sensors/dtp94/Makefile.am @@ -18,11 +18,14 @@ plugin_LTLIBRARIES = \ libcolord_sensor_dtp94.la libcolord_sensor_dtp94_la_SOURCES = \ + dtp94-device.c \ + dtp94-device.h \ + dtp94-enum.c \ + dtp94-enum.h \ cd-sensor-dtp94.c libcolord_sensor_dtp94_la_LIBADD = \ $(GLIB_LIBS) \ - $(GUSB_LIBS) \ - $(top_builddir)/lib/dtp94/libdtp94-private.la + $(GUSB_LIBS) libcolord_sensor_dtp94_la_LDFLAGS = -module -avoid-version libcolord_sensor_dtp94_la_CFLAGS = $(WARN_CFLAGS) endif diff --git a/src/sensors/dtp94/cd-sensor-dtp94.c b/src/sensors/dtp94/cd-sensor-dtp94.c index cbd744b..82be057 100644 --- a/src/sensors/dtp94/cd-sensor-dtp94.c +++ b/src/sensors/dtp94/cd-sensor-dtp94.c @@ -28,7 +28,8 @@ #include "../src/cd-sensor.h" -#include <dtp94/dtp94.h> +#include "dtp94-enum.h" +#include "dtp94-device.h" typedef struct { diff --git a/src/sensors/dtp94/dtp94-device.c b/src/sensors/dtp94/dtp94-device.c new file mode 100644 index 0000000..65ee254 --- /dev/null +++ b/src/sensors/dtp94/dtp94-device.c @@ -0,0 +1,357 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * SECTION:dtp94-enum + * @short_description: Types used by dtp94 and libdtp94 + * + * These helper functions provide a way to marshal enumerated values to + * text and back again. + * + * See also: #CdClient, #CdDevice + */ + +#include "config.h" + +#include <glib.h> +#include <string.h> +#include <colord-private.h> + +#include "dtp94-device.h" +#include "dtp94-enum.h" + +#define DTP94_MAX_READ_RETRIES 5 +#define DTP94_CONTROL_MESSAGE_TIMEOUT 50000 /* ms */ + +/** + * dtp94_device_error_quark: + **/ +GQuark +dtp94_device_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("Dtp94DeviceError"); + return quark; +} + +/** + * dtp94_device_send_data: + * + * Since: 0.1.29 + **/ +gboolean +dtp94_device_send_data (GUsbDevice *device, + const guint8 *request, + gsize request_len, + guint8 *reply, + gsize reply_len, + gsize *reply_read, + GError **error) +{ + gboolean ret; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (request != NULL, FALSE); + g_return_val_if_fail (request_len != 0, FALSE); + g_return_val_if_fail (reply != NULL, FALSE); + g_return_val_if_fail (reply_len != 0, FALSE); + g_return_val_if_fail (reply_read != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* request data from device */ + cd_buffer_debug (CD_BUFFER_KIND_REQUEST, + request, request_len); + ret = g_usb_device_interrupt_transfer (device, + 0x2, + (guint8 *) request, + request_len, + NULL, + DTP94_CONTROL_MESSAGE_TIMEOUT, + NULL, + error); + if (!ret) + return FALSE; + + /* get sync response */ + ret = g_usb_device_interrupt_transfer (device, + 0x81, + (guint8 *) reply, + reply_len, + reply_read, + DTP94_CONTROL_MESSAGE_TIMEOUT, + NULL, + error); + if (!ret) + return FALSE; + if (reply_read == 0) { + g_set_error_literal (error, + DTP94_DEVICE_ERROR, + DTP94_DEVICE_ERROR_INTERNAL, + "failed to get data from device"); + return FALSE; + } + cd_buffer_debug (CD_BUFFER_KIND_RESPONSE, + reply, *reply_read); + return TRUE; +} + +static gboolean +dtp94_device_send_cmd_issue (GUsbDevice *device, + const gchar *command, + GError **error) +{ + gboolean ret; + gsize reply_read; + guint8 buffer[128]; + guint8 rc; + guint command_len; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* sent command raw */ + command_len = strlen (command); + ret = dtp94_device_send_data (device, + (const guint8 *) command, + command_len, + buffer, + sizeof (buffer), + &reply_read, + error); + if (!ret) + return FALSE; + + /* device busy */ + rc = dtp94_rc_parse (buffer, reply_read); + if (rc == DTP94_RC_BAD_COMMAND) { + g_set_error_literal (error, + DTP94_DEVICE_ERROR, + DTP94_DEVICE_ERROR_NO_DATA, + "device busy"); + return FALSE; + } + + /* no success */ + if (rc != DTP94_RC_OK) { + buffer[reply_read] = '\0'; + g_set_error (error, + DTP94_DEVICE_ERROR, + DTP94_DEVICE_ERROR_INTERNAL, + "unexpected response from device: %s [%s]", + (const gchar *) buffer, + dtp94_rc_to_string (rc)); + return FALSE; + } + return TRUE; +} + +/** + * dtp94_device_send_cmd: + * + * Since: 0.1.29 + **/ +gboolean +dtp94_device_send_cmd (GUsbDevice *device, + const gchar *command, + GError **error) +{ + gboolean ret = FALSE; + GError *error_local = NULL; + guint error_cnt = 0; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (command != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* repeat until the device is ready */ + for (error_cnt = 0; ret != TRUE; error_cnt++) { + ret = dtp94_device_send_cmd_issue (device, command, &error_local); + if (!ret) { + if (error_cnt < DTP94_MAX_READ_RETRIES && + g_error_matches (error_local, + DTP94_DEVICE_ERROR, + DTP94_DEVICE_ERROR_NO_DATA)) { + g_debug ("ignoring %s", error_local->message); + g_clear_error (&error_local); + continue; + } + g_propagate_error (error, error_local); + break; + } + }; + return ret; +} + +/** + * dtp94_device_setup: + * + * Since: 0.1.29 + **/ +gboolean +dtp94_device_setup (GUsbDevice *device, GError **error) +{ + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* reset device */ + if (!dtp94_device_send_cmd (device, "0PR\r", error)) + return FALSE; + + /* reset device again */ + if (!dtp94_device_send_cmd (device, "0PR\r", error)) + return FALSE; + + /* set color data separator to '\t' */ + if (!dtp94_device_send_cmd (device, "0207CF\r", error)) + return FALSE; + + /* set delimeter to CR */ + if (!dtp94_device_send_cmd (device, "0008CF\r", error)) + return FALSE; + + /* set extra digit resolution */ + if (!dtp94_device_send_cmd (device, "010ACF\r", error)) + return FALSE; + + /* no black point subtraction */ + if (!dtp94_device_send_cmd (device, "0019CF\r", error)) + return FALSE; + + /* set to factory calibration */ + if (!dtp94_device_send_cmd (device, "EFC\r", error)) + return FALSE; + + /* compensate for offset drift */ + if (!dtp94_device_send_cmd (device, "0117CF\r", error)) + return FALSE; + + return TRUE; +} + +/** + * dtp94_device_take_sample: + * + * Since: 0.1.29 + **/ +CdColorXYZ * +dtp94_device_take_sample (GUsbDevice *device, CdSensorCap cap, GError **error) +{ + CdColorXYZ *result = NULL; + gboolean ret = FALSE; + gchar *tmp; + gsize reply_read; + guint8 buffer[128]; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* set hardware support */ + switch (cap) { + case CD_SENSOR_CAP_CRT: + case CD_SENSOR_CAP_PLASMA: + /* CRT = 01 */ + ret = dtp94_device_send_cmd (device, "0116CF\r", error); + break; + case CD_SENSOR_CAP_LCD: + /* LCD = 02 */ + ret = dtp94_device_send_cmd (device, "0216CF\r", error); + break; + default: + g_set_error (error, + DTP94_DEVICE_ERROR, + DTP94_DEVICE_ERROR_NO_SUPPORT, + "DTP94 cannot measure in %s mode", + cd_sensor_cap_to_string (cap)); + break; + } + if (!ret) + return NULL; + + /* get sample */ + ret = dtp94_device_send_data (device, + (const guint8 *) "RM\r", 3, + buffer, sizeof (buffer), + &reply_read, + error); + if (!ret) + return NULL; + tmp = g_strstr_len ((const gchar *) buffer, reply_read, "\r"); + if (tmp == NULL || memcmp (tmp + 1, "<00>", 4) != 0) { + buffer[reply_read] = '\0'; + g_set_error (error, + DTP94_DEVICE_ERROR, + DTP94_DEVICE_ERROR_INTERNAL, + "unexpected response from device: %s", + (const gchar *) buffer); + return NULL; + } + + /* format is raw ASCII with fixed formatting: + * 'X 10.29 Y 10.33 Z 4.65\u000d<00>' */ + tmp = (gchar *) buffer; + g_strdelimit (tmp, "\t\r", '\0'); + + /* success */ + result = cd_color_xyz_new (); + cd_color_xyz_set (result, + g_ascii_strtod (tmp + 1, NULL), + g_ascii_strtod (tmp + 13, NULL), + g_ascii_strtod (tmp + 25, NULL)); + return result; +} + +/** + * dtp94_device_get_serial: + * + * Since: 0.1.29 + **/ +gchar * +dtp94_device_get_serial (GUsbDevice *device, GError **error) +{ + gboolean ret; + gchar *tmp; + gsize reply_read; + guint8 buffer[128]; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = dtp94_device_send_data (device, + (const guint8 *) "SV\r", 3, + buffer, sizeof (buffer), + &reply_read, + error); + if (!ret) + return NULL; + tmp = g_strstr_len ((const gchar *) buffer, reply_read, "\r"); + if (tmp == NULL || memcmp (tmp + 1, "<00>", 4) != 0) { + buffer[reply_read] = '\0'; + g_set_error (error, + DTP94_DEVICE_ERROR, + DTP94_DEVICE_ERROR_INTERNAL, + "unexpected response from device: %s", + (const gchar *) buffer); + return NULL; + } + tmp[0] = '\0'; + return g_strdup (tmp); +} diff --git a/src/sensors/dtp94/dtp94-device.h b/src/sensors/dtp94/dtp94-device.h new file mode 100644 index 0000000..2b29b6b --- /dev/null +++ b/src/sensors/dtp94/dtp94-device.h @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __DTP94_DEVICE_H +#define __DTP94_DEVICE_H + +#include <glib-object.h> +#include <gusb.h> +#include <colord-private.h> + +G_BEGIN_DECLS + +#define DTP94_DEVICE_ERROR dtp94_device_error_quark() + +typedef enum { + DTP94_DEVICE_ERROR_INTERNAL, + DTP94_DEVICE_ERROR_NO_DATA, + DTP94_DEVICE_ERROR_NO_SUPPORT, + DTP94_DEVICE_ERROR_LAST +} Dtp94DeviceError; + +GQuark dtp94_device_error_quark (void); +gboolean dtp94_device_send_data (GUsbDevice *device, + const guint8 *request, + gsize request_len, + guint8 *reply, + gsize reply_len, + gsize *reply_read, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gboolean dtp94_device_send_cmd (GUsbDevice *device, + const gchar *command, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +CdColorXYZ *dtp94_device_take_sample (GUsbDevice *device, + CdSensorCap cap, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gchar *dtp94_device_get_serial (GUsbDevice *device, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gboolean dtp94_device_setup (GUsbDevice *device, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; + +G_END_DECLS + +#endif /* __DTP94_DEVICE_H */ + diff --git a/src/sensors/dtp94/dtp94-enum.c b/src/sensors/dtp94/dtp94-enum.c new file mode 100644 index 0000000..4d2000e --- /dev/null +++ b/src/sensors/dtp94/dtp94-enum.c @@ -0,0 +1,122 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * SECTION:dtp94-enum + * @short_description: Types used by dtp94 and libdtp94 + * + * These helper functions provide a way to marshal enumerated values to + * text and back again. + * + * See also: #CdClient, #CdDevice + */ + +#include "config.h" + +#include <glib.h> + +#include "dtp94-enum.h" + +/** + * dtp94_rc_parse: + * + * Since: 0.1.29 + **/ +guint8 +dtp94_rc_parse (const guint8 *data, gsize length) +{ + gchar *endptr = NULL; + guint64 tmp = DTP94_RC_UNKNOWN; + + /* invalid data */ + if (length < 4 || + data[0] != '<' || + data[1] == '\0' || + data[2] == '\0' || + data[3] != '>') { + return tmp; + } + + /* parse number */ + tmp = g_ascii_strtoull ((const gchar *) data + 1, &endptr, 16); + if (endptr == (const gchar *) data + 1) + return tmp; // valid? + return tmp; +} + +/** + * dtp94_rc_to_string: + * + * Since: 0.1.29 + **/ +const gchar * +dtp94_rc_to_string (guint8 value) +{ + if (value == DTP94_RC_OK) + return "ok"; + if (value == DTP94_RC_BAD_COMMAND) + return "bad-command"; + if (value == DTP94_RC_PRM_RANGE) + return "prm-range"; + if (value == DTP94_RC_MEMORY_OVERFLOW) + return "memory-overflow"; + if (value == DTP94_RC_INVALID_BAUD_RATE) + return "invalid-baud-rate"; + if (value == DTP94_RC_TIMEOUT) + return "timeout"; + if (value == DTP94_RC_SYNTAX_ERROR) + return "syntax-error"; + if (value == DTP94_RC_NO_DATA_AVAILABLE) + return "no-data-available"; + if (value == DTP94_RC_MISSING_PARAMETER) + return "missing-parameter"; + if (value == DTP94_RC_CALIBRATION_DENIED) + return "calibration-denied"; + if (value == DTP94_RC_NEEDS_OFFSET_CAL) + return "needs-offset-cal"; + if (value == DTP94_RC_NEEDS_RATIO_CAL) + return "needs-ratio-cal"; + if (value == DTP94_RC_NEEDS_LUMINANCE_CAL) + return "needs-luminance-cal"; + if (value == DTP94_RC_NEEDS_WHITE_POINT_CAL) + return "needs-white-point-cal"; + if (value == DTP94_RC_NEEDS_BLACK_POINT_CAL) + return "needs-black-point-cal"; + if (value == DTP94_RC_INVALID_READING) + return "invalid-reading"; + if (value == DTP94_RC_BAD_COMP_TABLE) + return "bad-comp-table"; + if (value == DTP94_RC_TOO_MUCH_LIGHT) + return "too-much-light"; + if (value == DTP94_RC_NOT_ENOUGH_LIGHT) + return "not-enough-light"; + if (value == DTP94_RC_BAD_SERIAL_NUMBER) + return "bad-serial-number"; + if (value == DTP94_RC_NO_MODULATION) + return "no-modulation"; + if (value == DTP94_RC_EEPROM_FAILURE) + return "eeprom-failure"; + if (value == DTP94_RC_FLASH_WRITE_FAILURE) + return "flash-write-failure"; + if (value == DTP94_RC_INST_INTERNAL_ERROR) + return "inst-internal-error"; + return NULL; +} diff --git a/src/sensors/dtp94/dtp94-enum.h b/src/sensors/dtp94/dtp94-enum.h new file mode 100644 index 0000000..9c5f949 --- /dev/null +++ b/src/sensors/dtp94/dtp94-enum.h @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __DTP94_ENUM_H +#define __DTP94_ENUM_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define DTP94_VENDOR_ID 0x0765 +#define DTP94_PRODUCT_ID 0xd094 + +/* return values from the commands */ +#define DTP94_RC_OK 0x00 +#define DTP94_RC_BAD_COMMAND 0x01 +#define DTP94_RC_PRM_RANGE 0x02 +#define DTP94_RC_MEMORY_OVERFLOW 0x04 +#define DTP94_RC_INVALID_BAUD_RATE 0x05 +#define DTP94_RC_TIMEOUT 0x07 +#define DTP94_RC_SYNTAX_ERROR 0x08 +#define DTP94_RC_NO_DATA_AVAILABLE 0x0b +#define DTP94_RC_MISSING_PARAMETER 0x0c +#define DTP94_RC_CALIBRATION_DENIED 0x0d +#define DTP94_RC_NEEDS_OFFSET_CAL 0x16 +#define DTP94_RC_NEEDS_RATIO_CAL 0x17 +#define DTP94_RC_NEEDS_LUMINANCE_CAL 0x18 +#define DTP94_RC_NEEDS_WHITE_POINT_CAL 0x19 +#define DTP94_RC_NEEDS_BLACK_POINT_CAL 0x2a +#define DTP94_RC_INVALID_READING 0x20 +#define DTP94_RC_BAD_COMP_TABLE 0x25 +#define DTP94_RC_TOO_MUCH_LIGHT 0x28 +#define DTP94_RC_NOT_ENOUGH_LIGHT 0x29 +#define DTP94_RC_BAD_SERIAL_NUMBER 0x40 +#define DTP94_RC_NO_MODULATION 0x50 +#define DTP94_RC_EEPROM_FAILURE 0x70 +#define DTP94_RC_FLASH_WRITE_FAILURE 0x71 +#define DTP94_RC_INST_INTERNAL_ERROR 0x7f +#define DTP94_RC_UNKNOWN 0xff + +guint8 dtp94_rc_parse (const guint8 *data, + gsize length); +const gchar *dtp94_rc_to_string (guint8 value); + +G_END_DECLS + +#endif /* __DTP94_ENUM_H */ + diff --git a/src/sensors/huey/Makefile.am b/src/sensors/huey/Makefile.am index a927f53..1d1cb9e 100644 --- a/src/sensors/huey/Makefile.am +++ b/src/sensors/huey/Makefile.am @@ -18,11 +18,16 @@ plugin_LTLIBRARIES = \ libcolord_sensor_huey.la libcolord_sensor_huey_la_SOURCES = \ + huey-ctx.c \ + huey-ctx.h \ + huey-device.c \ + huey-device.h \ + huey-enum.c \ + huey-enum.h \ cd-sensor-huey.c libcolord_sensor_huey_la_LIBADD = \ $(GLIB_LIBS) \ - $(GUSB_LIBS) \ - $(top_builddir)/lib/huey/libhuey-private.la + $(GUSB_LIBS) libcolord_sensor_huey_la_LDFLAGS = -module -avoid-version libcolord_sensor_huey_la_CFLAGS = $(WARN_CFLAGS) endif diff --git a/src/sensors/huey/cd-sensor-huey.c b/src/sensors/huey/cd-sensor-huey.c index 967c17e..9a8ff87 100644 --- a/src/sensors/huey/cd-sensor-huey.c +++ b/src/sensors/huey/cd-sensor-huey.c @@ -25,10 +25,13 @@ #include <glib-object.h> #include <gusb.h> #include <colord-private.h> -#include <huey/huey.h> #include "../src/cd-sensor.h" +#include "huey-ctx.h" +#include "huey-device.h" +#include "huey-enum.h" + typedef struct { GUsbDevice *device; diff --git a/src/sensors/huey/huey-ctx.c b/src/sensors/huey/huey-ctx.c new file mode 100644 index 0000000..42feb41 --- /dev/null +++ b/src/sensors/huey/huey-ctx.c @@ -0,0 +1,555 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <glib.h> +#include <lcms2.h> +#include <stdlib.h> + +#include "huey-ctx.h" +#include "huey-device.h" +#include "huey-enum.h" + +static void huey_ctx_class_init (HueyCtxClass *klass); +static void huey_ctx_init (HueyCtx *ctx); +static void huey_ctx_finalize (GObject *object); + +#define GET_PRIVATE(o) (huey_ctx_get_instance_private (o)) + +#define HUEY_CONTROL_MESSAGE_TIMEOUT 50000 /* ms */ +#define HUEY_MAX_READ_RETRIES 5 + +/* The CY7C63001 is paired with a 6.00Mhz crystal */ +#define HUEY_CLOCK_FREQUENCY 6e6 + +/* It takes 6 clock pulses to process a single 16bit increment (INC) + * instruction and check for the carry so this is the fastest a loop + * can be processed. */ +#define HUEY_POLL_FREQUENCY 1e6 + +/* Picked out of thin air, just to try to match reality... + * I have no idea why we need to do this, although it probably + * indicates we doing something wrong. */ +#define HUEY_XYZ_POST_MULTIPLY_FACTOR 3.428 + +/** + * HueyCtxPrivate: + * + * Private #HueyCtx data + **/ +typedef struct +{ + CdMat3x3 calibration_crt; + CdMat3x3 calibration_lcd; + CdVec3 dark_offset; + gchar *unlock_string; + gfloat calibration_value; + GUsbDevice *device; +} HueyCtxPrivate; + +enum { + PROP_0, + PROP_DEVICE, + PROP_LAST +}; + +G_DEFINE_TYPE_WITH_PRIVATE (HueyCtx, huey_ctx, G_TYPE_OBJECT) + +/** + * huey_ctx_error_quark: + * + * Return value: An error quark. + * + * Since: 0.1.0 + **/ +GQuark +huey_ctx_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) { + quark = g_quark_from_static_string ("huey_ctx_error"); + } + return quark; +} + + +/** + * huey_ctx_get_device: + * + * Since: 0.1.29 + **/ +GUsbDevice * +huey_ctx_get_device (HueyCtx *ctx) +{ + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + g_return_val_if_fail (HUEY_IS_CTX (ctx), NULL); + return priv->device; +} + +/** + * huey_ctx_set_device: + * + * Since: 0.1.29 + **/ +void +huey_ctx_set_device (HueyCtx *ctx, GUsbDevice *device) +{ + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + g_return_if_fail (HUEY_IS_CTX (ctx)); + priv->device = g_object_ref (device); +} + +/** + * huey_ctx_setup: + * + * Since: 0.1.29 + **/ +gboolean +huey_ctx_setup (HueyCtx *ctx, GError **error) +{ + gboolean ret; + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + + g_return_val_if_fail (HUEY_IS_CTX (ctx), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* get matrix */ + cd_mat33_clear (&priv->calibration_lcd); + ret = huey_device_read_register_matrix (priv->device, + HUEY_EEPROM_ADDR_CALIBRATION_DATA_LCD, + &priv->calibration_lcd, + error); + if (!ret) + return FALSE; + g_debug ("device calibration LCD: %s", + cd_mat33_to_string (&priv->calibration_lcd)); + + /* get another matrix, although this one is different... */ + cd_mat33_clear (&priv->calibration_crt); + ret = huey_device_read_register_matrix (priv->device, + HUEY_EEPROM_ADDR_CALIBRATION_DATA_CRT, + &priv->calibration_crt, + error); + if (!ret) + return FALSE; + g_debug ("device calibration CRT: %s", + cd_mat33_to_string (&priv->calibration_crt)); + + /* this number is different on all three hueys */ + ret = huey_device_read_register_float (priv->device, + HUEY_EEPROM_ADDR_AMBIENT_CALIB_VALUE, + &priv->calibration_value, + error); + if (!ret) + return FALSE; + + /* this vector changes between sensor 1 and 3 */ + ret = huey_device_read_register_vector (priv->device, + HUEY_EEPROM_ADDR_DARK_OFFSET, + &priv->dark_offset, + error); + if (!ret) + return FALSE; + return TRUE; +} + +/** + * huey_ctx_get_calibration_lcd: + * + * Since: 0.1.29 + **/ +const CdMat3x3 * +huey_ctx_get_calibration_lcd (HueyCtx *ctx) +{ + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + g_return_val_if_fail (HUEY_IS_CTX (ctx), NULL); + return &priv->calibration_lcd; +} + +/** + * huey_ctx_get_calibration_crt: + * + * Since: 0.1.29 + **/ +const CdMat3x3 * +huey_ctx_get_calibration_crt (HueyCtx *ctx) +{ + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + g_return_val_if_fail (HUEY_IS_CTX (ctx), NULL); + return &priv->calibration_crt; +} + +/** + * huey_ctx_get_calibration_value: + * + * Since: 0.1.29 + **/ +gfloat +huey_ctx_get_calibration_value (HueyCtx *ctx) +{ + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + g_return_val_if_fail (HUEY_IS_CTX (ctx), -1); + return priv->calibration_value; +} + +/** + * huey_ctx_get_dark_offset: + * + * Since: 0.1.29 + **/ +const CdVec3 * +huey_ctx_get_dark_offset (HueyCtx *ctx) +{ + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + g_return_val_if_fail (HUEY_IS_CTX (ctx), NULL); + return &priv->dark_offset; +} + +/** + * huey_ctx_get_unlock_string: + * + * Since: 0.1.29 + **/ +const gchar * +huey_ctx_get_unlock_string (HueyCtx *ctx) +{ + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + g_return_val_if_fail (HUEY_IS_CTX (ctx), NULL); + return priv->unlock_string; +} + +typedef struct { + guint16 R; + guint16 G; + guint16 B; +} HueyCtxMultiplier; + +typedef struct { + guint32 R; + guint32 G; + guint32 B; +} HueyCtxDeviceRaw; + +static gboolean +huey_ctx_sample_for_threshold (HueyCtx *ctx, + HueyCtxMultiplier *threshold, + HueyCtxDeviceRaw *raw, + GError **error) +{ + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + guint8 request[] = { HUEY_CMD_SENSOR_MEASURE_RGB, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + guint8 reply[8]; + gboolean ret; + gsize reply_read; + + /* these are 16 bit gain values */ + cd_buffer_write_uint16_be (request + 1, threshold->R); + cd_buffer_write_uint16_be (request + 3, threshold->G); + cd_buffer_write_uint16_be (request + 5, threshold->B); + + /* measure, and get red */ + ret = huey_device_send_data (priv->device, + request, 8, + reply, 8, + &reply_read, + error); + if (!ret) + return FALSE; + + /* get value */ + raw->R = cd_buffer_read_uint32_be (reply+2); + + /* get green */ + request[0] = HUEY_CMD_READ_GREEN; + ret = huey_device_send_data (priv->device, + request, 8, + reply, 8, + &reply_read, + error); + if (!ret) + return FALSE; + + /* get value */ + raw->G = cd_buffer_read_uint32_be (reply+2); + + /* get blue */ + request[0] = HUEY_CMD_READ_BLUE; + ret = huey_device_send_data (priv->device, + request, 8, + reply, 8, + &reply_read, + error); + if (!ret) + return FALSE; + + /* get value */ + raw->B = cd_buffer_read_uint32_be (reply+2); + return TRUE; +} + +/** + * huey_ctx_convert_device_RGB_to_XYZ: + * + * / X \ ( / R \ / c a l \ ) + * | Y | = ( | G | * | m a t | ) x post_scale + * \ Z / ( \ B / \ l c d / ) + * + **/ +static void +huey_ctx_convert_device_RGB_to_XYZ (CdColorRGB *src, + CdColorXYZ *dest, + CdMat3x3 *calibration, + gdouble post_scale) +{ + CdVec3 *result; + + /* convolve */ + result = (CdVec3 *) dest; + cd_mat33_vector_multiply (calibration, (CdVec3 *) src, result); + + /* post-multiply */ + cd_vec3_scalar_multiply (result, + post_scale, + result); +} + + +/** + * huey_ctx_take_sample: + * + * Since: 0.1.29 + **/ +CdColorXYZ * +huey_ctx_take_sample (HueyCtx *ctx, CdSensorCap cap, GError **error) +{ + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + CdColorRGB values; + CdColorXYZ color_result; + CdMat3x3 *device_calibration; + CdVec3 *temp; + gboolean ret; + HueyCtxDeviceRaw color_native; + HueyCtxMultiplier multiplier; + + g_return_val_if_fail (HUEY_IS_CTX (ctx), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* no hardware support */ + if (cap == CD_SENSOR_CAP_PROJECTOR) { + g_set_error_literal (error, + HUEY_CTX_ERROR, + HUEY_CTX_ERROR_NO_SUPPORT, + "Huey cannot measure in projector mode"); + return NULL; + } + + /* set this to one value for a quick approximate value */ + multiplier.R = 1; + multiplier.G = 1; + multiplier.B = 1; + ret = huey_ctx_sample_for_threshold (ctx, + &multiplier, + &color_native, + error); + if (!ret) + return NULL; + g_debug ("initial values: red=%u, green=%u, blue=%u", + color_native.R, color_native.G, color_native.B); + + /* try to fill the 16 bit register for accuracy */ + multiplier.R = HUEY_POLL_FREQUENCY / color_native.R; + multiplier.G = HUEY_POLL_FREQUENCY / color_native.G; + multiplier.B = HUEY_POLL_FREQUENCY / color_native.B; + + /* don't allow a value of zero */ + if (multiplier.R == 0) + multiplier.R = 1; + if (multiplier.G == 0) + multiplier.G = 1; + if (multiplier.B == 0) + multiplier.B = 1; + g_debug ("using multiplier factor: red=%i, green=%i, blue=%i", + multiplier.R, multiplier.G, multiplier.B); + ret = huey_ctx_sample_for_threshold (ctx, + &multiplier, + &color_native, + error); + if (!ret) + return NULL; + g_debug ("raw values: red=%u, green=%u, blue=%u", + color_native.R, color_native.G, color_native.B); + + /* get DeviceRGB values */ + values.R = (gdouble) multiplier.R * 0.5f * HUEY_POLL_FREQUENCY / ((gdouble) color_native.R); + values.G = (gdouble) multiplier.G * 0.5f * HUEY_POLL_FREQUENCY / ((gdouble) color_native.G); + values.B = (gdouble) multiplier.B * 0.5f * HUEY_POLL_FREQUENCY / ((gdouble) color_native.B); + g_debug ("scaled values: red=%0.6lf, green=%0.6lf, blue=%0.6lf", + values.R, values.G, values.B); + + /* remove dark offset */ + temp = (CdVec3*) &values; + cd_vec3_subtract (temp, + &priv->dark_offset, + temp); + + g_debug ("dark offset values: red=%0.6lf, green=%0.6lf, blue=%0.6lf", + values.R, values.G, values.B); + + /* negative values don't make sense (device needs recalibration) */ + if (values.R < 0.0f) + values.R = 0.0f; + if (values.G < 0.0f) + values.G = 0.0f; + if (values.B < 0.0f) + values.B = 0.0f; + + /* we use different calibration matrices for each output type */ + switch (cap) { + case CD_SENSOR_CAP_CRT: + case CD_SENSOR_CAP_PLASMA: + g_debug ("using CRT calibration matrix"); + device_calibration = &priv->calibration_crt; + break; + default: + g_debug ("using LCD calibration matrix"); + device_calibration = &priv->calibration_lcd; + break; + } + + /* convert from device RGB to XYZ */ + huey_ctx_convert_device_RGB_to_XYZ (&values, + &color_result, + device_calibration, + HUEY_XYZ_POST_MULTIPLY_FACTOR); + g_debug ("finished values: red=%0.6lf, green=%0.6lf, blue=%0.6lf", + color_result.X, color_result.Y, color_result.Z); + + /* save result */ + return cd_color_xyz_dup (&color_result); +} + +static void +huey_ctx_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + HueyCtx *ctx = HUEY_CTX (object); + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + + switch (prop_id) { + case PROP_DEVICE: + g_value_set_object (value, priv->device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static void +huey_ctx_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + HueyCtx *ctx = HUEY_CTX (object); + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + + switch (prop_id) { + case PROP_DEVICE: + priv->device = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* + * huey_ctx_class_init: + */ +static void +huey_ctx_class_init (HueyCtxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = huey_ctx_get_property; + object_class->set_property = huey_ctx_set_property; + object_class->finalize = huey_ctx_finalize; + + /** + * HueyCtx:device: + * + * Since: 0.1.29 + **/ + g_object_class_install_property (object_class, + PROP_DEVICE, + g_param_spec_object ("device", + NULL, NULL, + G_USB_TYPE_DEVICE, + G_PARAM_READWRITE)); +} + +/* + * huey_ctx_init: + */ +static void +huey_ctx_init (HueyCtx *ctx) +{ + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + + cd_mat33_clear (&priv->calibration_lcd); + cd_mat33_clear (&priv->calibration_crt); + + /* ensure the remote errors are registered */ + huey_ctx_error_quark (); +} + +static void +huey_ctx_finalize (GObject *object) +{ + HueyCtx *ctx = HUEY_CTX (object); + HueyCtxPrivate *priv = GET_PRIVATE (ctx); + + g_return_if_fail (HUEY_IS_CTX (object)); + + g_free (priv->unlock_string); + + G_OBJECT_CLASS (huey_ctx_parent_class)->finalize (object); +} + +/** + * huey_ctx_new: + * + * Creates a new #HueyCtx object. + * + * Return value: a new HueyCtx object. + * + * Since: 0.1.29 + **/ +HueyCtx * +huey_ctx_new (void) +{ + HueyCtx *ctx; + ctx = g_object_new (HUEY_TYPE_CTX, NULL); + return HUEY_CTX (ctx); +} diff --git a/src/sensors/huey/huey-ctx.h b/src/sensors/huey/huey-ctx.h new file mode 100644 index 0000000..cf32e4d --- /dev/null +++ b/src/sensors/huey/huey-ctx.h @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __HUEY_CTX_H +#define __HUEY_CTX_H + +#include <glib-object.h> +#include <gio/gio.h> +#include <gusb.h> +#include <colord-private.h> + +G_BEGIN_DECLS + +#define HUEY_CTX_ERROR (huey_ctx_error_quark ()) +#define HUEY_CTX_TYPE_ERROR (huey_ctx_error_get_type ()) + +#define HUEY_TYPE_CTX (huey_ctx_get_type ()) +G_DECLARE_DERIVABLE_TYPE (HueyCtx, huey_ctx, HUEY, CTX, GObject) + +struct _HueyCtxClass +{ + GObjectClass parent_class; + /*< private >*/ + /* Padding for future expansion */ + void (*_huey_ctx_reserved1) (void); + void (*_huey_ctx_reserved2) (void); + void (*_huey_ctx_reserved3) (void); + void (*_huey_ctx_reserved4) (void); + void (*_huey_ctx_reserved5) (void); + void (*_huey_ctx_reserved6) (void); + void (*_huey_ctx_reserved7) (void); + void (*_huey_ctx_reserved8) (void); +}; + +/** + * HueyCtxError: + * @HUEY_CTX_ERROR_FAILED: the request failed for an unknown reason + * + * Errors that can be thrown + */ +typedef enum +{ + HUEY_CTX_ERROR_FAILED, + HUEY_CTX_ERROR_NO_SUPPORT, + HUEY_CTX_ERROR_LAST +} HueyCtxError; + +GQuark huey_ctx_error_quark (void); +HueyCtx *huey_ctx_new (void); + +CdColorXYZ *huey_ctx_take_sample (HueyCtx *ctx, + CdSensorCap cap, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +GUsbDevice *huey_ctx_get_device (HueyCtx *ctx); +void huey_ctx_set_device (HueyCtx *ctx, + GUsbDevice *device); +gboolean huey_ctx_setup (HueyCtx *ctx, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +const CdMat3x3 *huey_ctx_get_calibration_lcd (HueyCtx *ctx); +const CdMat3x3 *huey_ctx_get_calibration_crt (HueyCtx *ctx); +gfloat huey_ctx_get_calibration_value (HueyCtx *ctx); +const CdVec3 *huey_ctx_get_dark_offset (HueyCtx *ctx); +const gchar *huey_ctx_get_unlock_string (HueyCtx *ctx); + +G_END_DECLS + +#endif /* __HUEY_CTX_H */ + diff --git a/src/sensors/huey/huey-device.c b/src/sensors/huey/huey-device.c new file mode 100644 index 0000000..a070158 --- /dev/null +++ b/src/sensors/huey/huey-device.c @@ -0,0 +1,516 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <glib.h> +#include <string.h> + +#include "huey-device.h" +#include "huey-enum.h" + +#define HUEY_MAX_READ_RETRIES 5 +#define HUEY_CONTROL_MESSAGE_TIMEOUT 50000 /* ms */ + +/* fudge factor to convert the value of HUEY_CMD_GET_AMBIENT to Lux */ +#define HUEY_AMBIENT_UNITS_TO_LUX 125.0f + +/** + * huey_device_error_quark: + **/ +GQuark +huey_device_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("HueyError"); + return quark; +} + +/** + * huey_device_send_data: + * + * Since: 0.1.29 + **/ +gboolean +huey_device_send_data (GUsbDevice *device, + const guint8 *request, + gsize request_len, + guint8 *reply, + gsize reply_len, + gsize *reply_read, + GError **error) +{ + gboolean ret; + guint i; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (request != NULL, FALSE); + g_return_val_if_fail (request_len != 0, FALSE); + g_return_val_if_fail (reply != NULL, FALSE); + g_return_val_if_fail (reply_len != 0, FALSE); + g_return_val_if_fail (reply_read != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* control transfer */ + cd_buffer_debug (CD_BUFFER_KIND_REQUEST, + request, request_len); + ret = g_usb_device_control_transfer (device, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_CLASS, + G_USB_DEVICE_RECIPIENT_INTERFACE, + 0x09, + 0x0200, + 0, + (guint8 *) request, + request_len, + NULL, + HUEY_CONTROL_MESSAGE_TIMEOUT, + NULL, + error); + if (!ret) + return FALSE; + + /* some commands need to retry the read */ + for (i = 0; i < HUEY_MAX_READ_RETRIES; i++) { + + /* get sync response */ + ret = g_usb_device_interrupt_transfer (device, + 0x81, + (guint8 *) reply, + reply_len, + reply_read, + HUEY_CONTROL_MESSAGE_TIMEOUT, + NULL, + error); + if (!ret) + return FALSE; + + /* the second byte seems to be the command again */ + cd_buffer_debug (CD_BUFFER_KIND_RESPONSE, + reply, *reply_read); + if (reply[1] != request[0]) { + g_set_error (error, + HUEY_DEVICE_ERROR, + HUEY_DEVICE_ERROR_INTERNAL, + "wrong command reply, got 0x%02x, " + "expected 0x%02x", + reply[1], + request[0]); + return FALSE; + } + + /* the first byte is status */ + if (reply[0] == HUEY_RC_SUCCESS) + return TRUE; + + /* failure, the return buffer is set to "Locked" */ + if (reply[0] == HUEY_RC_LOCKED) { + g_set_error_literal (error, + HUEY_DEVICE_ERROR, + HUEY_DEVICE_ERROR_INTERNAL, + "the device is locked"); + return FALSE; + } + + /* failure, the return buffer is set to "NoCmd" */ + if (reply[0] == HUEY_RC_ERROR) { + g_set_error (error, + HUEY_DEVICE_ERROR, + HUEY_DEVICE_ERROR_INTERNAL, + "failed to issue command: %s", &reply[2]); + return FALSE; + } + + /* we ignore retry */ + if (reply[0] != HUEY_RC_RETRY) { + g_set_error (error, + HUEY_DEVICE_ERROR, + HUEY_DEVICE_ERROR_INTERNAL, + "return value unknown: 0x%02x", reply[0]); + return FALSE; + } + } + + /* no success */ + g_set_error (error, + HUEY_DEVICE_ERROR, + HUEY_DEVICE_ERROR_INTERNAL, + "gave up retrying after %i reads", + HUEY_MAX_READ_RETRIES); + return FALSE; +} + +/** + * huey_device_unlock: + * + * Since: 0.1.29 + **/ +gboolean +huey_device_unlock (GUsbDevice *device, GError **error) +{ + guint8 request[8]; + guint8 reply[8]; + gboolean ret; + gsize reply_read; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + request[0] = HUEY_CMD_UNLOCK; + request[1] = 'G'; + request[2] = 'r'; + request[3] = 'M'; + request[4] = 'b'; + request[5] = '\0'; + request[6] = '\0'; + request[7] = '\0'; + + /* no idea why the hardware gets 'locked' */ + ret = huey_device_send_data (device, + request, 8, + reply, 8, + &reply_read, + error); + if (!ret) + return FALSE; + return TRUE; +} + +/** + * huey_device_get_serial_number: + * + * Since: 0.1.29 + **/ +gchar * +huey_device_get_serial_number (GUsbDevice *device, GError **error) +{ + gboolean ret; + guint32 tmp; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = huey_device_read_register_word (device, + HUEY_EEPROM_ADDR_SERIAL, + &tmp, + error); + if (!ret) + return NULL; + return g_strdup_printf ("%" G_GUINT32_FORMAT, tmp); +} + +/** + * huey_device_get_unlock_string: + * + * Since: 0.1.29 + **/ +gchar * +huey_device_get_unlock_string (GUsbDevice *device, GError **error) +{ + gboolean ret; + gchar tmp[5]; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + ret = huey_device_read_register_string (device, + HUEY_EEPROM_ADDR_UNLOCK, + tmp, + sizeof (tmp), + error); + if (!ret) + return NULL; + return g_strndup (tmp, sizeof (tmp)); +} + +/** + * huey_device_set_leds: + * + * Since: 0.1.29 + **/ +gboolean +huey_device_set_leds (GUsbDevice *device, guint8 value, GError **error) +{ + guint8 reply[8]; + gsize reply_read; + guint8 payload[] = { HUEY_CMD_SET_LEDS, + 0x00, + ~value, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00 }; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + return huey_device_send_data (device, + payload, 8, reply, 8, + &reply_read, + error); +} + +/** + * huey_device_read_register_byte: + * + * Return value: -1 for error. + * + * Since: 0.1.29 + **/ +gdouble +huey_device_get_ambient (GUsbDevice *device, GError **error) +{ + gboolean ret; + gsize reply_read; + guint8 reply[8]; + guint8 request[] = { HUEY_CMD_GET_AMBIENT, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00 }; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), -1); + g_return_val_if_fail (error == NULL || *error == NULL, -1); + + /* just use LCD mode */ + request[2] = 0x00; + ret = huey_device_send_data (device, + request, 8, + reply, 8, + &reply_read, + error); + if (!ret) + return -1.f; + + /* parse the value */ + return (gdouble) cd_buffer_read_uint16_be (reply+5) / HUEY_AMBIENT_UNITS_TO_LUX; +} + +/** + * huey_device_read_register_byte: + * + * Since: 0.1.29 + **/ +gboolean +huey_device_read_register_byte (GUsbDevice *device, + guint8 addr, + guint8 *value, + GError **error) +{ + guint8 request[] = { HUEY_CMD_REGISTER_READ, + 0xff, + 0x00, + 0x10, + 0x3c, + 0x06, + 0x00, + 0x00 }; + guint8 reply[8]; + gboolean ret; + gsize reply_read; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* hit hardware */ + request[1] = addr; + ret = huey_device_send_data (device, + request, 8, + reply, 8, + &reply_read, + error); + if (!ret) + return FALSE; + *value = reply[3]; + return TRUE; +} + +/** + * huey_device_read_register_string: + * + * Since: 0.1.29 + **/ +gboolean +huey_device_read_register_string (GUsbDevice *device, + guint8 addr, + gchar *value, + gsize len, + GError **error) +{ + guint8 i; + gboolean ret; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* get each byte of the string */ + for (i = 0; i < len; i++) { + ret = huey_device_read_register_byte (device, + addr+i, + (guint8*) &value[i], + error); + if (!ret) + return FALSE; + } + return TRUE; +} + +/** + * huey_device_read_register_word: + * + * Since: 0.1.29 + **/ +gboolean +huey_device_read_register_word (GUsbDevice *device, + guint8 addr, + guint32 *value, + GError **error) +{ + guint8 i; + guint8 tmp[4]; + gboolean ret; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* get each byte of the 32 bit number */ + for (i = 0; i < 4; i++) { + ret = huey_device_read_register_byte (device, + addr+i, + tmp+i, + error); + if (!ret) + return FALSE; + } + + /* convert to a 32 bit integer */ + *value = cd_buffer_read_uint32_be (tmp); + return TRUE; +} + +/** + * huey_device_read_register_float: + * + * Since: 0.1.29 + **/ +gboolean +huey_device_read_register_float (GUsbDevice *device, + guint8 addr, + gfloat *value, + GError **error) +{ + gboolean ret; + guint32 tmp = 0; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* first read in 32 bit integer */ + ret = huey_device_read_register_word (device, + addr, + &tmp, + error); + if (!ret) + return FALSE; + + /* convert to float */ + *((guint32 *)value) = tmp; + return TRUE; +} + +/** + * huey_device_read_register_vector: + * + * Since: 0.1.29 + **/ +gboolean +huey_device_read_register_vector (GUsbDevice *device, + guint8 addr, + CdVec3 *value, + GError **error) +{ + gboolean ret; + guint i; + gfloat tmp = 0.0f; + gdouble *vector_data; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* get this to avoid casting */ + vector_data = cd_vec3_get_data (value); + + /* read in vec3 */ + for (i = 0; i < 3; i++) { + ret = huey_device_read_register_float (device, + addr + (i*4), + &tmp, + error); + if (!ret) + return FALSE; + + /* save in matrix */ + *(vector_data+i) = tmp; + } + return TRUE; +} + +/** + * huey_device_read_register_matrix: + * + * Since: 0.1.29 + **/ +gboolean +huey_device_read_register_matrix (GUsbDevice *device, + guint8 addr, + CdMat3x3 *value, + GError **error) +{ + gboolean ret; + guint i; + gfloat tmp = 0.0f; + gdouble *matrix_data; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* get this to avoid casting */ + matrix_data = cd_mat33_get_data (value); + + /* read in 3d matrix */ + for (i = 0; i < 9; i++) { + ret = huey_device_read_register_float (device, + addr + (i*4), + &tmp, + error); + if (!ret) + return FALSE; + + /* save in matrix */ + *(matrix_data+i) = tmp; + } + return TRUE; +} diff --git a/src/sensors/huey/huey-device.h b/src/sensors/huey/huey-device.h new file mode 100644 index 0000000..c488132 --- /dev/null +++ b/src/sensors/huey/huey-device.h @@ -0,0 +1,100 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __HUEY_DEVICE_H +#define __HUEY_DEVICE_H + +#include <glib-object.h> +#include <gusb.h> +#include <colord-private.h> + +G_BEGIN_DECLS + +#define HUEY_DEVICE_ERROR huey_device_error_quark() + +typedef enum { + HUEY_DEVICE_ERROR_INTERNAL, + HUEY_DEVICE_ERROR_NO_DATA, + HUEY_DEVICE_ERROR_NO_SUPPORT, + HUEY_DEVICE_ERROR_LAST +} HueyError; + +GQuark huey_device_error_quark (void); +gboolean huey_device_send_data (GUsbDevice *device, + const guchar *request, + gsize request_len, + guchar *reply, + gsize reply_len, + gsize *reply_read, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gboolean huey_device_set_leds (GUsbDevice *device, + guint8 value, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gchar *huey_device_get_serial_number (GUsbDevice *device, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gchar *huey_device_get_unlock_string (GUsbDevice *device, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gboolean huey_device_unlock (GUsbDevice *device, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gdouble huey_device_get_ambient (GUsbDevice *device, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gboolean huey_device_read_register_byte (GUsbDevice *device, + guint8 addr, + guint8 *value, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gboolean huey_device_read_register_string (GUsbDevice *device, + guint8 addr, + gchar *value, + gsize len, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gboolean huey_device_read_register_word (GUsbDevice *device, + guint8 addr, + guint32 *value, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gboolean huey_device_read_register_float (GUsbDevice *device, + guint8 addr, + gfloat *value, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gboolean huey_device_read_register_vector (GUsbDevice *device, + guint8 addr, + CdVec3 *value, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gboolean huey_device_read_register_matrix (GUsbDevice *device, + guint8 addr, + CdMat3x3 *value, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; + +G_END_DECLS + +#endif /* __HUEY_DEVICE_H */ + diff --git a/src/sensors/huey/huey-enum.c b/src/sensors/huey/huey-enum.c new file mode 100644 index 0000000..385aaa9 --- /dev/null +++ b/src/sensors/huey/huey-enum.c @@ -0,0 +1,108 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * SECTION:huey-enum + * @short_description: Types used by huey and libhuey + * + * These helper functions provide a way to marshal enumerated values to + * text and back again. + * + * See also: #CdClient, #CdDevice + */ + +#include "config.h" + +#include <glib.h> + +#include "huey-enum.h" + +/** + * huey_rc_to_string: + * + * Since: 0.1.29 + **/ +const gchar * +huey_rc_to_string (guchar value) +{ + if (value == HUEY_RC_SUCCESS) + return "success"; + if (value == HUEY_RC_LOCKED) + return "locked"; + if (value == HUEY_RC_ERROR) + return "error"; + if (value == HUEY_RC_RETRY) + return "retry"; + if (value == HUEY_RC_UNKNOWN_5A) + return "unknown5a"; + if (value == HUEY_RC_UNKNOWN_81) + return "unknown81"; + return NULL; +} + +/** + * huey_cmd_code_to_string: + * + * Since: 0.1.29 + **/ +const gchar * +huey_cmd_code_to_string (guchar value) +{ + if (value == HUEY_CMD_GET_STATUS) + return "get-status"; + if (value == HUEY_CMD_READ_GREEN) + return "read-green"; + if (value == HUEY_CMD_READ_BLUE) + return "read-blue"; + if (value == HUEY_CMD_SET_INTEGRATION_TIME) + return "set-integration-time"; + if (value == HUEY_CMD_GET_INTEGRATION_TIME) + return "get-integration-time"; + if (value == HUEY_CMD_REGISTER_WRITE) + return "reg-write"; + if (value == HUEY_CMD_REGISTER_READ) + return "reg-read"; + if (value == HUEY_CMD_UNLOCK) + return "unlock"; + if (value == HUEY_CMD_UNKNOWN_0F) + return "unknown0f"; + if (value == HUEY_CMD_UNKNOWN_10) + return "unknown10"; + if (value == HUEY_CMD_UNKNOWN_11) + return "unknown11"; + if (value == HUEY_CMD_UNKNOWN_12) + return "unknown12"; + if (value == HUEY_CMD_SENSOR_MEASURE_RGB_CRT) + return "measure-rgb-crt"; + if (value == HUEY_CMD_UNKNOWN_15) + return "unknown15(status?)"; + if (value == HUEY_CMD_SENSOR_MEASURE_RGB) + return "measure-rgb"; + if (value == HUEY_CMD_UNKNOWN_21) + return "unknown21"; + if (value == HUEY_CMD_GET_AMBIENT) + return "get-ambient"; + if (value == HUEY_CMD_SET_LEDS) + return "set-leds"; + if (value == HUEY_CMD_SENSOR_MEASURE_RGB_ALT) + return "measure-rgb-alt"; + return NULL; +} diff --git a/src/sensors/huey/huey-enum.h b/src/sensors/huey/huey-enum.h new file mode 100644 index 0000000..7317558 --- /dev/null +++ b/src/sensors/huey/huey-enum.h @@ -0,0 +1,321 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __HUEY_ENUM_H +#define __HUEY_ENUM_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +/* external hueys */ +#define CD_SENSOR_HUEY_VENDOR_ID 0x0971 +#define CD_SENSOR_HUEY_PRODUCT_ID 0x2005 + +/* integrated and newer devices */ +#define CD_SENSOR_HUEY_VENDOR_ID2 0x0765 +#define CD_SENSOR_HUEY_PRODUCT_ID2 0x5001 + +/* Return values from the commands */ +#define HUEY_RC_SUCCESS 0x00 +#define HUEY_RC_LOCKED 0xc0 +#define HUEY_RC_UNKNOWN_5A 0x5a /* seen in profiling */ +#define HUEY_RC_ERROR 0x80 +#define HUEY_RC_UNKNOWN_81 0x81 /* seen once in init */ +#define HUEY_RC_RETRY 0x90 + +/* + * Get the currect status of the device + * + * input: 00 00 00 00 3f 00 00 00 + * returns: 00 00 43 69 72 30 30 31 (or) + * "Cir001" --^^^^^^^^^^^^^^^^^ (Pantone Huey) + * c0 00 xx xx xx xx xx xx + * "huL002" --^^^^^^^^^^^^^^^^^ (Lenovo Huey) + * c0 00 4c 6f 63 6b 65 64 + * "locked" --^^^^^^^^^^^^^^^^^ + */ +#define HUEY_CMD_GET_STATUS 0x00 + +/* + * Read the green sample data + * + * input: 02 xx xx xx xx xx xx xx + * returns: 00 02 00 00 0a 00 00 00 (or) + * 00 02 00 0e c6 80 00 00 + * data --^^^^^ ^-- only ever 00 or 80 + * | + * \-- for RGB(00,00,00) is 09 f2 + * RGB(ff,ff,ff) is 00 00 + * RGB(ff,00,00) is 02 a5 + * RGB(00,ff,00) is 00 f1 + * RGB(00,00,ff) is 08 56 + * + * This doesn't do a sensor read, it seems to be a simple accessor. + * HUEY_CMD_SENSOR_MEASURE_RGB has to be used before this one. + */ +#define HUEY_CMD_READ_GREEN 0x02 + +/* + * Read the blue sample data + * + * input: 03 xx xx xx xx xx xx xx + * returns: 00 03 00 0f 18 00 00 00 + * data --^^^^^ ^-- only ever 00 or 80 + * | + * \-- for RGB(00,00,00) is 09 64 + * RGB(ff,ff,ff) is 08 80 + * RGB(ff,00,00) is 03 22 + * RGB(00,ff,00) is 00 58 + * RGB(00,00,ff) is 00 59 + * + * This doesn't do a sensor read, it seems to be a simple accessor. + * HUEY_CMD_SENSOR_MEASURE_RGB has to be used before this one. + */ +#define HUEY_CMD_READ_BLUE 0x03 + +/* + * Sets a 32 bit integration time register. + * + * input: 05 ?? 11 12 13 14 xx xx + * returns: 00 05 00 00 00 00 00 00 + * ^--- always the same no matter the input + * + * This is not normally used during profiling + */ +#define HUEY_CMD_SET_INTEGRATION_TIME 0x05 + +/* + * Get the value of the 32 bit integration time register. + * + * input: 06 xx xx xx xx xx xx xx + * returns: 00 06 11 12 13 14 00 00 + * 4 bytes ----^^^^^^^^^^^ (from HUEY_CMD_SET_INTEGRATION_TIME) + * + * The default value at plug-in is 00 0f 42 40, although during + * profiling it is set to 00 00 6f 00 and then 00 00 61 00. + */ +#define HUEY_CMD_GET_INTEGRATION_TIME 0x06 + +/* + * Writes a register value. + * + * (sent at startup after the unlock) + * input: 07 0b ff xx xx xx xx xx + * ^^-- data value + * ^^-- register address + * returns: 00 xx xx xx xx xx xx xx + * + * You can only write one byte at a time. + */ +#define HUEY_CMD_REGISTER_WRITE 0x07 + +/* + * Reads a register value. + * + * (sent at startup after the unlock) + * input: 08 0b xx xx xx xx xx xx + * ^^-- register address + * returns: 00 08 0b b8 00 00 00 00 + * address --^^ ^^-- value + * + * You can only read one byte at a time. + */ +#define HUEY_CMD_REGISTER_READ 0x08 + +/* + * Unlock a locked sensor. + * + * input: 0e 47 72 4d 62 6b 65 64 (Pantone Huey) + * "GrMb"--^^^^^^^^^^^ + * 0e 47 72 4d 62 6b 65 64 (Lenovo Huey) + * "huyL"--^^^^^^^^^^^ + * returns: 00 0e 00 00 00 00 00 00 + * + * Only GrMbk or huyL is needed to unlock, the rest is just junk data. + * We still don't know how to 'lock' a device, it just kinda happens. + */ +#define HUEY_CMD_UNLOCK 0x0e + +/* + * Unknown command + * + * returns: all NULL all of the time */ +#define HUEY_CMD_UNKNOWN_0F 0x0f + +/* + * Unknown command + * + * Something to do with sampling */ +#define HUEY_CMD_UNKNOWN_10 0x10 + +/* + * Unknown command + * + * Something to do with sampling (that needs a retry with code 5a) + */ +#define HUEY_CMD_UNKNOWN_11 0x11 + +/* + * Unknown command + * + * something to do with sampling + */ +#define HUEY_CMD_UNKNOWN_12 0x12 + +/* + * Measures RGB value, and return the red value (only used in CRT mode). + * + * Seems to have to retry, every single time. + * + * Gain? + * _______|_______ + * /---\ /---\ /---\ + * input: 13 02 41 00 54 00 49 00 + * returns: 00 13 00 00 01 99 02 00 + * ^^^^^ - would match HUEY_CMD_SENSOR_MEASURE_RGB + * + * The gain seems not to change for different measurements with different + * colors. This seems to be a less precise profile too. + */ +#define HUEY_CMD_SENSOR_MEASURE_RGB_CRT 0x13 + +/* + * Unknown command + * + * returns: seems to be sent, but not requested + */ +#define HUEY_CMD_UNKNOWN_15 0x15 + +/* + * Sample a color and return the red component + * + * input: 16 00 01 00 01 00 01 00 + * returns: 00 16 00 00 00 00 00 00 + * + * or: + * ,,-,,-,,-,,-,,-,,-- 'gain control' + * || || || || || || + * input: 16 00 35 00 48 00 1d 03 + * returns: 00 16 00 0b d0 00 00 00 + * data --^^^^^ ^^-- only ever 00 or 80 + * + * This is used when profiling, and all commands are followed by + * HUEY_CMD_READ_GREEN and HUEY_COMMAND_READ_BLUE. + * + * The returned values are some kind of 16 bit register count that + * indicate how much light fell on a sensor. If the sensors are + * converting light to pulses, then the 'gain' control tells the sensor + * how long to read. It's therefore quicker to read white than black. + * + * Given there exists only GREEN and BLUE accessors, and that RED comes + * first in a RGB sequence, I think it's safe to assume that this command + * does the measurement, and the others just return cached data. + * + * argyll does (for #ff0000) + * + * -> 16 00 01 00 01 00 01 00 + * <- 00 00 0b 00 00 00 + * -> 02 xx xx xx xx xx xx xx + * <- 00 00 12 00 00 00 + * -> 03 xx xx xx xx xx xx xx + * <- 00 03 41 00 00 00 + * + * then does: + * + * -> 16 01 63 00 d9 00 04 00 + * <- 00 0f ce 80 00 00 + * -> 02 xx xx xx xx xx xx xx + * <- 00 0e d0 80 00 00 + * -> 03 xx xx xx xx xx xx xx + * <- 00 0d 3c 00 00 00 + * + * then returns XYZ=87.239169 45.548708 1.952249 + */ +#define HUEY_CMD_SENSOR_MEASURE_RGB 0x16 + +/* + * Unknown command (some sort of poll?) + * + * input: 21 09 00 02 00 00 08 00 (or) + * returns: [never seems to return a value] + * + * Only when profiling, and over and over. + */ +#define HUEY_CMD_UNKNOWN_21 0x21 + +/* + * Get the level of ambient light from the sensor + * + * ,,--- The output-type, where 00 is LCD and 02 is CRT + * input: 17 03 00 xx xx xx xx xx + * returns: 90 17 03 00 00 00 00 00 then on second read: + * 00 17 03 00 00 62 57 00 in light (or) + * 00 17 03 00 00 00 08 00 in dark + * no idea --^^ ^---^ = 16bits data + */ +#define HUEY_CMD_GET_AMBIENT 0x17 + +/* + * Set the LEDs on the sensor + * + * input: 18 00 f0 xx xx xx xx xx + * returns: 00 18 f0 00 00 00 00 00 + * led mask ----^^ + */ +#define HUEY_CMD_SET_LEDS 0x18 + +/* + * Unknown command + * + * returns: all NULL for NULL input: times out for f1 f2 f3 f4 f5 f6 f7 f8 */ +#define HUEY_CMD_SENSOR_MEASURE_RGB_ALT 0x19 + +/* + * Register map: + * x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF + * 0x [serial-number.][matrix-lcd....................................| + * 1x ...............................................................| + * 2x .......] | + * 3x [calib-lcd-time][matrix-crt............................| + * 4x ...............................................................| + * 5x .......................................][calib-crt-time] | + * 6x [calib_vector......................| + * 7x ...........] [unlock-string.....] | + * 8x | + * 9x [calib_value...] | + */ +#define HUEY_EEPROM_ADDR_SERIAL 0x00 /* 4 bytes */ +#define HUEY_EEPROM_ADDR_CALIBRATION_DATA_LCD 0x04 /* 36 bytes */ +#define HUEY_EEPROM_ADDR_CALIBRATION_TIME_LCD 0x32 /* 4 bytes */ +#define HUEY_EEPROM_ADDR_CALIBRATION_DATA_CRT 0x36 /* 36 bytes */ +#define HUEY_EEPROM_ADDR_CALIBRATION_TIME_CRT 0x5a /* 4 bytes */ +#define HUEY_EEPROM_ADDR_DARK_OFFSET 0x67 /* 12 bytes */ +#define HUEY_EEPROM_ADDR_UNLOCK 0x7a /* 5 bytes */ +#define HUEY_EEPROM_ADDR_AMBIENT_CALIB_VALUE 0x94 /* 4 bytes */ + +const gchar *huey_rc_to_string (guchar value); +const gchar *huey_cmd_code_to_string (guchar value); + +G_END_DECLS + +#endif /* __HUEY_ENUM_H */ + diff --git a/src/sensors/munki/Makefile.am b/src/sensors/munki/Makefile.am index c820af7..008cac1 100644 --- a/src/sensors/munki/Makefile.am +++ b/src/sensors/munki/Makefile.am @@ -1,5 +1,8 @@ if FALSE -libcolord_sensor_munki_la_SOURCES = cd-sensor-munki.c +libcolord_sensor_munki_la_SOURCES = \ + munki-enum.c \ + munki-enum.h \ + cd-sensor-munki.c libcolord_sensor_munki_la_LIBADD = $(GLIB_LIBS) libcolord_sensor_munki_la_LDFLAGS = -module -avoid-version libcolord_sensor_munki_la_CFLAGS = $(WARN_CFLAGS) diff --git a/src/sensors/munki/cd-sensor-munki.c b/src/sensors/munki/cd-sensor-munki.c index b5f7ef0..52ce132 100644 --- a/src/sensors/munki/cd-sensor-munki.c +++ b/src/sensors/munki/cd-sensor-munki.c @@ -29,6 +29,8 @@ #include "cd-buffer.h" #include "cd-sensor.h" +#include "munki-enum.h" + typedef struct { gboolean done_startup; diff --git a/src/sensors/munki/munki-enum.c b/src/sensors/munki/munki-enum.c new file mode 100644 index 0000000..e3ae53e --- /dev/null +++ b/src/sensors/munki/munki-enum.c @@ -0,0 +1,96 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * SECTION:munki-enum + * @short_description: Types used by munki and libmunki + * + * These helper functions provide a way to marshal enumerated values to + * text and back again. + * + * See also: #CdClient, #CdDevice + */ + +#include "config.h" + +#include <glib.h> + +#include "munki-enum.h" + +/** + * munki_command_value_to_string: + **/ +const gchar * +munki_command_value_to_string (guint8 value) +{ + if (value == MUNKI_COMMAND_DIAL_ROTATE) + return "dial-rotate"; + if (value == MUNKI_COMMAND_BUTTON_PRESSED) + return "button-released"; + if (value == MUNKI_COMMAND_BUTTON_RELEASED) + return "button-released"; + return NULL; +} + +/** + * munki_button_state_to_string: + **/ +const gchar * +munki_button_state_to_string (guint8 value) +{ + if (value == MUNKI_BUTTON_STATE_RELEASED) + return "released"; + if (value == MUNKI_BUTTON_STATE_PRESSED) + return "pressed"; + return NULL; +} + +/** + * munki_dial_position_to_string: + **/ +const gchar * +munki_dial_position_to_string (guint8 value) +{ + if (value == MUNKI_DIAL_POSITION_PROJECTOR) + return "projector"; + if (value == MUNKI_DIAL_POSITION_SURFACE) + return "surface"; + if (value == MUNKI_DIAL_POSITION_CALIBRATION) + return "calibration"; + if (value == MUNKI_DIAL_POSITION_AMBIENT) + return "ambient"; + return NULL; +} + +/** + * munki_endpoint_to_string: + **/ +const gchar * +munki_endpoint_to_string (guint value) +{ + if (value == MUNKI_EP_CONTROL) + return "control"; + if (value == MUNKI_EP_DATA) + return "data"; + if (value == MUNKI_EP_EVENT) + return "event"; + return NULL; +} diff --git a/src/sensors/munki/munki-enum.h b/src/sensors/munki/munki-enum.h new file mode 100644 index 0000000..d43c15c --- /dev/null +++ b/src/sensors/munki/munki-enum.h @@ -0,0 +1,125 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __MUNKI_ENUM_H +#define __MUNKI_ENUM_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define MUNKI_COMMAND_DIAL_ROTATE 0x00 +#define MUNKI_COMMAND_BUTTON_PRESSED 0x01 +#define MUNKI_COMMAND_BUTTON_RELEASED 0x02 + +#define MUNKI_BUTTON_STATE_RELEASED 0x00 +#define MUNKI_BUTTON_STATE_PRESSED 0x01 + +#define MUNKI_DIAL_POSITION_PROJECTOR 0x00 +#define MUNKI_DIAL_POSITION_SURFACE 0x01 +#define MUNKI_DIAL_POSITION_CALIBRATION 0x02 +#define MUNKI_DIAL_POSITION_AMBIENT 0x03 +#define MUNKI_DIAL_POSITION_UNKNOWN 0xff + + +/* + * Triggers a request for a bulk transfer of EEPROM + * Length: 8 bytes + * + * address length (LE) + * ____|____ ____|____ + * / \ / \ + * 04 00 00 00 04 00 00 00 + */ +#define MUNKI_REQUEST_EEPROM_DATA 0x81 + +/* + * Gets the next hardware event + * Length: 8 bytes + * + * This blocks until the hardware sends an event, and must either be + * run in a mainloop or thread to avoid blocking. + * + * subcmd ----\ /------------ 32 bit event time + * cmd ----|\ || || || || || + * Returns: 02 00 00 00 ac 62 07 00 + * always zero ---||-|| + * + * cmd is: + * 00 dial rotate + * 01 button pressed + * 02 button released + * + * subcmd is: + * 00 button event + * 01 dial rotate + */ +#define MUNKI_REQUEST_INTERRUPT 0x83 + +/* + * Returns the major and minor version numbers + * Length: 24 bytes + */ +#define MUNKI_REQUEST_VERSION_STRING 0x85 + +/* + * Returns the chip id + * Length: 8 bytes + */ +#define MUNKI_REQUEST_FIRMWARE_PARAMS 0x86 + +/* + * Gets the device status + * Length: 2 bytes + * + * Returns: 00 00 + * |/ || + * dial pos -/ \--- button value + * - 00 = projector + * - 01 = surface + * - 02 = calibration + * - 03 = ambient + */ +#define MUNKI_REQUEST_GET_STATUS 0x87 + +/* + * Returns the version string + * Length: 36 bytes + */ +#define MUNKI_REQUEST_CHIP_ID 0x8A + +/* USB endpoints in use */ +#define MUNKI_EP_CONTROL 0x00 +#define MUNKI_EP_DATA 0x01 +#define MUNKI_EP_EVENT 0x03 + +/* EEPROM is massive */ +#define COLORMUNKI_EEPROM_OFFSET_SERIAL_NUMBER 0x0018 /* 10 bytes */ + +const gchar *munki_button_state_to_string (guint8 value); +const gchar *munki_dial_position_to_string (guint8 value); +const gchar *munki_command_value_to_string (guint8 value); +const gchar *munki_endpoint_to_string (guint value); + +G_END_DECLS + +#endif /* __MUNKI_ENUM_H */ + diff --git a/src/sensors/spark/Makefile.am b/src/sensors/spark/Makefile.am index 8962815..cb65f0e 100644 --- a/src/sensors/spark/Makefile.am +++ b/src/sensors/spark/Makefile.am @@ -18,11 +18,14 @@ plugin_LTLIBRARIES = \ libcolord_sensor_spark.la libcolord_sensor_spark_la_SOURCES = \ + osp-device.c \ + osp-device.h \ + osp-enum.c \ + osp-enum.h \ cd-sensor-spark.c libcolord_sensor_spark_la_LIBADD = \ $(GLIB_LIBS) \ - $(GUSB_LIBS) \ - $(top_builddir)/lib/ospark/libospark-private.la + $(GUSB_LIBS) libcolord_sensor_spark_la_LDFLAGS = -module -avoid-version libcolord_sensor_spark_la_CFLAGS = $(WARN_CFLAGS) endif diff --git a/src/sensors/spark/cd-sensor-spark.c b/src/sensors/spark/cd-sensor-spark.c index 62ecf07..fcddf7a 100644 --- a/src/sensors/spark/cd-sensor-spark.c +++ b/src/sensors/spark/cd-sensor-spark.c @@ -24,10 +24,12 @@ #include <glib-object.h> #include <colord-private.h> -#include <ospark/ospark.h> #include "../src/cd-sensor.h" +#include "osp-device.h" +#include "osp-enum.h" + typedef struct { GUsbDevice *device; diff --git a/src/sensors/spark/osp-device.c b/src/sensors/spark/osp-device.c new file mode 100644 index 0000000..84fcdb2 --- /dev/null +++ b/src/sensors/spark/osp-device.c @@ -0,0 +1,864 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * SECTION:ospark-enum + * @short_description: Types used by libospark + * + * See also: #CdClient, #CdDevice + */ + +#include "config.h" + +#include <glib.h> +#include <string.h> +#include <colord-private.h> + +#include "osp-device.h" +#include "osp-enum.h" + +#define OSP_USB_TIMEOUT_MS 50000 /* ms */ +#define OSP_DEVICE_MAX_MSG_LENGTH (10240 + 64) /* bytes */ +#define OSP_DEVICE_EP_SIZE 64 /* bytes */ + +/** + * osp_device_error_quark: + **/ +GQuark +osp_device_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("OspDeviceError"); + return quark; +} + +/** + * osp_device_open: + * @device: a #GUsbDevice instance + * @error: A #GError or %NULL + * + * Opens the device and claims the interface + * + * Return value: %TRUE for success + * + * Since: 1.2.11 + **/ +gboolean +osp_device_open (GUsbDevice *device, GError **error) +{ + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!g_usb_device_open (device, error)) + return FALSE; + if (!g_usb_device_claim_interface (device, 0x00, 0, error)) { + g_prefix_error (error, "Failed to claim interface: "); + return FALSE; + } + + return TRUE; +} + +/** + * osp_device_query: + * + * Since: 1.2.11 + **/ +static gboolean +osp_device_query (GUsbDevice *device, OspCmd cmd, + const guint8 *data_in, gsize data_in_length, + guint8 **data_out, gsize *data_out_length, + GError **error) +{ + OspProtocolFooter *ftr; + OspProtocolHeader *hdr; + gsize actual_length; + gsize checksum_length = 16; /* always for MD5 */ + gsize payload_length = 0; + gsize offset_rd = 0; + gsize offset_wr = 0; + guint i; + g_autoptr(GChecksum) csum = NULL; + g_autofree guint8 *buffer_in = NULL; + g_autofree guint8 *buffer_out = NULL; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (data_in_length <= 16, FALSE); //FIXME + g_assert (sizeof(OspProtocolHeader) + sizeof(OspProtocolFooter) == 64); + + /* write header to buffer */ + buffer_in = g_new0 (guint8, OSP_DEVICE_MAX_MSG_LENGTH); + hdr = (OspProtocolHeader *) buffer_in; + hdr->start_bytes = GUINT16_TO_BE (0xc1c0); + hdr->protocol_version = 0x1000; + hdr->checksum_type = OSP_HEADER_CHECKSUM_KIND_MD5; + hdr->message_type = cmd; + hdr->bytes_remaining = sizeof(OspProtocolFooter); + if (data_out == NULL) + hdr->flags = OSP_HEADER_FLAG_ACK_REQUIRED; + if (data_in_length > 0) { + if (data_in_length <= 16) { + /* avoid another USB packet if we can */ + hdr->immediate_data_length = data_in_length; + memcpy (hdr->immediate_data, data_in, data_in_length); + } else { + payload_length = data_in_length; + } + } + offset_wr += sizeof(OspProtocolHeader); + hdr->bytes_remaining = sizeof(OspProtocolFooter) + payload_length; + + /* write payload to buffer, if any */ + if (payload_length > 0) { + memcpy (buffer_in + offset_wr, data_in, data_in_length); + offset_wr += payload_length; + } + + /* write footer to buffer */ + ftr = (OspProtocolFooter *) (buffer_in + offset_wr); + ftr->end_bytes = GUINT32_TO_BE (0xc5c4c3c2); + csum = g_checksum_new (G_CHECKSUM_MD5); + g_checksum_update (csum, (const guchar *) buffer_in, offset_wr); + g_checksum_get_digest (csum, ftr->checksum, &checksum_length); + offset_wr += sizeof(OspProtocolFooter); + + /* send data */ + if (g_getenv ("SPARK_PROTOCOL_DEBUG") != NULL) + cd_buffer_debug (CD_BUFFER_KIND_REQUEST, buffer_in, offset_wr); + if (!g_usb_device_bulk_transfer (device, 0x01, + buffer_in, offset_wr, + &actual_length, + OSP_USB_TIMEOUT_MS, NULL, error)) + return FALSE; + + /* get reply */ + buffer_out = g_new0 (guint8, 64); + if (!g_usb_device_bulk_transfer (device, 0x81, + buffer_out, OSP_DEVICE_EP_SIZE, + &actual_length, + OSP_USB_TIMEOUT_MS, NULL, error)) + return FALSE; + if (g_getenv ("SPARK_PROTOCOL_DEBUG") != NULL) + cd_buffer_debug (CD_BUFFER_KIND_RESPONSE, buffer_out, actual_length); + + /* check the error code */ + hdr = (OspProtocolHeader *) buffer_out; + switch (hdr->error_code) { + case OSP_ERROR_CODE_SUCCESS: + break; + case OSP_ERROR_CODE_MESSAGE_TOO_LARGE: + case OSP_ERROR_CODE_UNKNOWN_CHECKSUM_TYPE: + case OSP_ERROR_CODE_UNSUPPORTED_PROTOCOL: + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_NO_SUPPORT, + "Failed to %s", + osp_cmd_to_string (cmd)); + return FALSE; + break; + case OSP_ERROR_CODE_COMMAND_DATA_MISSING: + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_NO_DATA, + "Failed to %s", + osp_cmd_to_string (cmd)); + return FALSE; + break; + default: + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Failed to %s: %s", + osp_cmd_to_string (cmd), + osp_error_code_to_string (hdr->error_code)); + return FALSE; + break; + } + + /* copy out the data */ + offset_rd = sizeof(OspProtocolHeader); + if (data_out != NULL && data_out_length != NULL) { + if (hdr->immediate_data_length > 0) { + *data_out_length = hdr->immediate_data_length; + *data_out = g_memdup (hdr->immediate_data, + hdr->immediate_data_length); + } else if (hdr->bytes_remaining >= sizeof(OspProtocolFooter)) { + *data_out_length = hdr->bytes_remaining - sizeof(OspProtocolFooter); + *data_out = g_new0 (guint8, hdr->bytes_remaining); + + /* copy the first chunk of data */ + offset_wr = 64 - offset_rd; + memcpy (*data_out, buffer_out + offset_rd, offset_wr); + } else { + g_assert_not_reached (); + } + } + + /* read the rest of the payload */ + payload_length = hdr->bytes_remaining - sizeof(OspProtocolFooter); + for (i = 0; i < payload_length / 64; i++) { + if (!g_usb_device_bulk_transfer (device, 0x81, + buffer_out, OSP_DEVICE_EP_SIZE, + &actual_length, + OSP_USB_TIMEOUT_MS, NULL, error)) + return FALSE; + if (data_out != NULL) { + memcpy (*data_out + offset_wr, + buffer_out, OSP_DEVICE_EP_SIZE); + } + if (g_getenv ("SPARK_PROTOCOL_DEBUG") != NULL) + cd_buffer_debug (CD_BUFFER_KIND_RESPONSE, buffer_out, OSP_DEVICE_EP_SIZE); + offset_wr += 64; + } + offset_rd += payload_length; + + /* verify the footer is intact */ + ftr = (OspProtocolFooter *) (buffer_out + OSP_DEVICE_EP_SIZE - sizeof(OspProtocolFooter)); + if (ftr->end_bytes != GUINT32_TO_BE (0xc5c4c3c2)) { + g_set_error_literal (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Footer invalid"); + return FALSE; + } + + return TRUE; +} + +/** + * osp_device_send_command: + * + * Since: 1.2.11 + **/ +static gboolean +osp_device_send_command (GUsbDevice *device, OspCmd cmd, + const guint8 *data_in, gsize data_in_length, + GError **error) +{ + return osp_device_query (device, cmd, + data_in, data_in_length, + NULL, NULL, error); +} + +/** + * osp_device_get_serial: + * @device: a #GUsbDevice instance + * @error: A #GError or %NULL + * + * Gets the device serial number. + * + * Return value: A string, or %NULL for failure + * + * Since: 1.2.11 + **/ +gchar * +osp_device_get_serial (GUsbDevice *device, GError **error) +{ + gsize data_len; + g_autofree guint8 *data = NULL; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* query hardware */ + if (!osp_device_query (device, OSP_CMD_GET_SERIAL_NUMBER, + NULL, 0, &data, &data_len, error)) + return NULL; + + /* check values */ + if (data_len == 0) { + g_set_error_literal (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Expected serial number, got nothing"); + return NULL; + } + + /* format value */ + return g_strndup ((const gchar *) data, data_len); +} + +/** + * osp_device_get_fw_version: + * @device: a #GUsbDevice instance + * @error: A #GError or %NULL + * + * Opens the device and claims the interface + * + * Return value: The firmware version, or %NULL for error + * + * Since: 1.2.11 + **/ +gchar * +osp_device_get_fw_version (GUsbDevice *device, GError **error) +{ + gsize data_len; + g_autofree guint8 *data = NULL; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* query hardware */ + if (!osp_device_query (device, OSP_CMD_GET_FIRMWARE_VERSION, + NULL, 0, &data, &data_len, error)) + return NULL; + + /* check values */ + if (data_len != 2) { + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Expected %i bytes, got %" G_GSIZE_FORMAT, + 2, data_len); + return NULL; + } + + /* format value */ + return g_strdup_printf ("%u.%u", data[1], data[0]); +} + +/** + * osp_device_get_wavelength_cal_for_idx: + * + * Since: 1.3.1 + **/ +static gboolean +osp_device_get_wavelength_cal_for_idx (GUsbDevice *device, + guint idx, + gfloat *cal, + GError **error) +{ + gsize data_len; + guint8 idx_buf[1] = { idx }; + g_autofree guint8 *data = NULL; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* query hardware */ + if (!osp_device_query (device, OSP_CMD_GET_WAVELENGTH_COEFFICIENT, + idx_buf, 1, &data, &data_len, error)) + return FALSE; + + /* check values */ + if (data_len != 4) { + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Expected %i bytes, got %" G_GSIZE_FORMAT, + 4, data_len); + return FALSE; + } + + /* convert to floating point */ + if (cal != NULL) + *cal = *((gfloat *) data); + + /* format value */ + return TRUE; +} + +/** + * osp_device_get_wavelength_start: + * @device: a #GUsbDevice instance + * @error: A #GError or %NULL + * + * Gets the starting wavelength for the sensor. + * + * Return value: A value in nm, or -1 for error + * + * Since: 1.3.1 + **/ +gdouble +osp_device_get_wavelength_start (GUsbDevice *device, GError **error) +{ + gfloat tmp = -1.f; + + /* get from hardware */ + if (!osp_device_get_wavelength_cal_for_idx (device, 0, &tmp, error)) + return -1.f; + + /* check values */ + if (tmp < 0) { + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Not a valid start, got %f", tmp); + return -1.f; + } + + return (gdouble) tmp; +} + +/** + * osp_device_get_wavelength_cal: + * @device: a #GUsbDevice instance + * @length: the size of the returned array + * @error: A #GError or %NULL + * + * Gets the wavelength coefficients for the sensor. + * + * Return value: An array of coefficients + * + * Since: 1.3.1 + **/ +gdouble * +osp_device_get_wavelength_cal (GUsbDevice *device, guint *length, GError **error) +{ + gboolean ret; + gdouble *coefs = NULL; + gfloat cx; + gsize data_len; + guint i; + g_autofree guint8 *data = NULL; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* query hardware */ + if (!osp_device_query (device, OSP_CMD_GET_WAVELENGTH_COEFFICIENT_COUNT, + NULL, 0, &data, &data_len, error)) + return NULL; + + /* check values */ + if (data_len != 1) { + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Expected 1 bytes, got %" G_GSIZE_FORMAT, + data_len); + return NULL; + } + + /* check sanity */ + if (data[0] != 4) { + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Expected 4 coefs, got %u", data[0]); + return NULL; + } + + /* get the coefs */ + coefs = g_new0 (gdouble, data[0] - 1); + for (i = 0; i < (guint) data[0] - 1; i++) { + ret = osp_device_get_wavelength_cal_for_idx (device, + i + 1, + &cx, + error); + if (!ret) + return NULL; + coefs[i] = cx; + } + + /* this is optional */ + if (length != NULL) + *length = data[0] - 1; + + /* success */ + return coefs; +} + +/** + * osp_device_get_nonlinearity_cal_for_idx: + * + * Since: 1.3.1 + **/ +static gboolean +osp_device_get_nonlinearity_cal_for_idx (GUsbDevice *device, + guint idx, + gfloat *cal, + GError **error) +{ + gsize data_len; + guint8 idx_buf[1] = { idx }; + g_autofree guint8 *data = NULL; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* query hardware */ + if (!osp_device_query (device, OSP_CMD_GET_NONLINEARITY_COEFFICIENT, + idx_buf, 1, &data, &data_len, error)) + return FALSE; + + /* check values */ + if (data_len != 4) { + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Expected %i bytes, got %" G_GSIZE_FORMAT, + 4, data_len); + return FALSE; + } + + /* convert to floating point */ + if (cal != NULL) + *cal = *((gfloat *) data); + + /* format value */ + return TRUE; +} + +/** + * osp_device_get_nonlinearity_cal: + * @device: a #GUsbDevice instance + * @length: the size of the returned array + * @error: A #GError or %NULL + * + * Gets the nonlinearity values for the sensor. + * + * Return value: An array of coefficients + * + * Since: 1.3.1 + **/ +gdouble * +osp_device_get_nonlinearity_cal (GUsbDevice *device, guint *length, GError **error) +{ + gboolean ret; + gdouble *coefs = NULL; + gfloat cx; + gsize data_len; + guint i; + g_autofree guint8 *data = NULL; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* query hardware */ + if (!osp_device_query (device, OSP_CMD_GET_NONLINEARITY_COEFFICIENT_COUNT, + NULL, 0, &data, &data_len, error)) + return NULL; + + /* check values */ + if (data_len != 1) { + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Expected 1 bytes, got %" G_GSIZE_FORMAT, + data_len); + return NULL; + } + + /* check sanity */ + if (data[0] != 8) { + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Expected 8 coefs, got %i", data[0]); + return NULL; + } + + /* get the coefs */ + coefs = g_new0 (gdouble, data[0]); + for (i = 0; i < data[0]; i++) { + ret = osp_device_get_nonlinearity_cal_for_idx (device, + i, + &cx, + error); + if (!ret) + return NULL; + coefs[i] = cx; + } + + /* this is optional */ + if (length != NULL) + *length = data[0]; + + /* success */ + return coefs; +} + +/** + * osp_device_get_irradiance_cal: + * @device: a #GUsbDevice instance + * @length: the size of the returned array + * @error: A #GError or %NULL + * + * Gets the irradiance spectrum for the sensor. + * + * Return value: An array of coefficients + * + * Since: 1.3.1 + **/ +gdouble * +osp_device_get_irradiance_cal (GUsbDevice *device, guint *length, GError **error) +{ + gdouble *coefs = NULL; + gsize data_len; + guint i; + g_autofree guint8 *data = NULL; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* query hardware */ + if (!osp_device_query (device, OSP_CMD_GET_IRRADIANCE_CALIBRATION, + NULL, 0, &data, &data_len, error)) + return NULL; + + /* check values */ + if (data_len != 4096 * 4) { + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Expected %i bytes, got %" G_GSIZE_FORMAT, + 4096 * 4, data_len); + return NULL; + } + + /* copy out the coefs */ + coefs = g_new0 (gdouble, 4096); + for (i = 0; i < 4096; i++) + coefs[i] = *((gfloat *) &data[i*4]); + + /* this is optional */ + if (length != NULL) + *length = 4096; + + /* success */ + return coefs; +} + +static CdSpectrum * +osp_device_take_spectrum_internal (GUsbDevice *device, + guint64 sample_duration, + GError **error) +{ + CdSpectrum *sp; + gdouble val; + gsize data_len; + guint i; + g_autofree guint8 *data = NULL; + g_autoptr(GTimer) t = NULL; + + /* set integral time in us */ + if (!osp_device_send_command (device, OSP_CMD_SET_INTEGRATION_TIME, + (guint8 *) &sample_duration, 4, error)) + return NULL; + + /* get spectrum */ + t = g_timer_new (); + if (!osp_device_query (device, OSP_CMD_GET_AND_SEND_RAW_SPECTRUM, + NULL, 0, &data, &data_len, error)) + return NULL; + g_debug ("For integration of %.0fms, sensor took %.0fms", + sample_duration / 1000.f, g_timer_elapsed (t, NULL) * 1000.f); + + /* check values */ + if (data_len != 2048) { + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "Expected %i bytes, got %" G_GSIZE_FORMAT, + 2048, data_len); + return NULL; + } + + /* export */ + sp = cd_spectrum_sized_new (1024); + for (i = 0; i < 1024; i++) { + val = data[i*2+1] * 256 + data[i*2+0]; + cd_spectrum_add_value (sp, val / (gdouble) 0xffff); + } + + /* the maximum value the hardware can return is 0x3fff */ + val = cd_spectrum_get_value_max (sp); + if (val > 0.25) { + g_set_error (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_INTERNAL, + "spectral max should be <= 0.25f, was %f", + val); + cd_spectrum_free (sp); + return NULL; + } + + return sp; +} + +/** + * osp_device_take_spectrum_full: + * @device: a #GUsbDevice instance + * @sample_duration: the sample duration in µs + * @error: A #GError or %NULL + * + * Returns a spectrum for a set sample duration. + * + * Return value: A #CdSpectrum, or %NULL for error + * + * Since: 1.3.1 + **/ +CdSpectrum * +osp_device_take_spectrum_full (GUsbDevice *device, + guint64 sample_duration, + GError **error) +{ + CdSpectrum *sp; + gdouble start; + guint8 bin_factor = 0; + g_autofree gdouble *cx = NULL; + g_autoptr(CdSpectrum) sp_dc = NULL; + g_autoptr(CdSpectrum) sp_raw = NULL; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* return every pixel */ + if (!osp_device_send_command (device, OSP_CMD_SET_PIXEL_BINNING_FACTOR, + &bin_factor, 1, error)) + return NULL; + + /* get spectrum */ + sp_raw = osp_device_take_spectrum_internal (device, sample_duration, error); + if (sp_raw == NULL) + return NULL; + cd_spectrum_set_id (sp_raw, "raw"); + + /* remove any DC offset from the sensor by doing a 10us reading -- + * ideally this would be 0us, but we have to use what we have */ + sp_dc = osp_device_take_spectrum_internal (device, 10, error); + if (sp_dc == NULL) + return NULL; + cd_spectrum_set_id (sp_dc, "dc"); + + /* get coefficients */ + cx = osp_device_get_wavelength_cal (device, NULL, error); + if (cx == NULL) + return NULL; + + /* get start */ + start = osp_device_get_wavelength_start (device, error); + if (start < 0) + return NULL; + + /* return the reading without a DC component */ + sp = cd_spectrum_subtract (sp_raw, sp_dc, 5); + cd_spectrum_set_start (sp, start); + cd_spectrum_set_norm (sp, 4); + cd_spectrum_set_wavelength_cal (sp, cx[0], cx[1], cx[2]); + return sp; +} + +/** + * osp_device_take_spectrum: + * @device: a #GUsbDevice instance. + * @error: A #GError or %NULL + * + * Returns a spectrum. The optimal sample duration is calculated automatically. + * + * Return value: A #CdSpectrum, or %NULL for error + * + * Since: 1.2.11 + **/ +CdSpectrum * +osp_device_take_spectrum (GUsbDevice *device, GError **error) +{ + const guint sample_duration_max_secs = 3; + gboolean relax_requirements = FALSE; + gdouble max; + gdouble scale = 0.f; + guint64 sample_duration = 10000; /* us */ + CdSpectrum *sp = NULL; + guint i; + + g_return_val_if_fail (G_USB_IS_DEVICE (device), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* loop until we're in 1/4 to 3/4 FSD */ + for (i = 0; i < 5; i++) { + g_autoptr(CdSpectrum) sp_probe = NULL; + + /* for the last try, relax what we deem acceptable so we can + * measure very black things with a long integration time */ + if (i == 4) + relax_requirements = TRUE; + + /* take a measurement */ + sp_probe = osp_device_take_spectrum_full (device, + sample_duration, + error); + if (sp_probe == NULL) + return NULL; + + /* sensor picked up nothing, take action */ + max = cd_spectrum_get_value_max (sp_probe); + if (max < 0.001f) { + sample_duration *= 100.f; + g_debug ("sensor read no data, setting duration " + "to %" G_GUINT64_FORMAT , + sample_duration); + continue; + } + + /* sensor is saturated, take action */ + if (max > 0.99f) { + sample_duration /= 100.f; + g_debug ("sensor saturated, setting duration " + "to %" G_GUINT64_FORMAT, + sample_duration); + continue; + } + + /* break out if we got valid readings */ + if (max > 0.25f && max < 0.75f) { + sp = cd_spectrum_dup (sp_probe); + break; + } + + /* be more accepting */ + if (relax_requirements && max > 0.01f) { + sp = cd_spectrum_dup (sp_probe); + break; + } + + /* aim for FSD / 2 */ + scale = (gdouble) 0.5 / max; + sample_duration *= scale; + g_debug ("for max of %f, using scale=%f for duration %" G_GUINT64_FORMAT, + max, scale, sample_duration); + + /* limit this to something sane */ + if (sample_duration / G_USEC_PER_SEC > sample_duration_max_secs) { + g_debug ("limiting duration from %us to %us", + (guint) (sample_duration / G_USEC_PER_SEC), + (guint) (sample_duration_max_secs)); + sample_duration = sample_duration_max_secs * G_USEC_PER_SEC; + relax_requirements = TRUE; + } + } + + /* no suitable readings */ + if (sp == NULL) { + g_set_error_literal (error, + OSP_DEVICE_ERROR, + OSP_DEVICE_ERROR_NO_DATA, + "Got no valid data"); + return NULL; + } + + /* scale with the new integral time */ + cd_spectrum_set_norm (sp, cd_spectrum_get_norm (sp) / scale); + g_debug ("normalised spectral max is %f", cd_spectrum_get_value_max (sp)); + return sp; +} diff --git a/src/sensors/spark/osp-device.h b/src/sensors/spark/osp-device.h new file mode 100644 index 0000000..617e9dd --- /dev/null +++ b/src/sensors/spark/osp-device.h @@ -0,0 +1,76 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __OSP_DEVICE_H +#define __OSP_DEVICE_H + +#include <glib-object.h> +#include <gusb.h> +#include <colord-private.h> + +G_BEGIN_DECLS + +#define OSP_DEVICE_ERROR osp_device_error_quark() + +typedef enum { + OSP_DEVICE_ERROR_INTERNAL, + OSP_DEVICE_ERROR_NO_DATA, + OSP_DEVICE_ERROR_NO_SUPPORT, + OSP_DEVICE_ERROR_LAST +} OspDeviceError; + +GQuark osp_device_error_quark (void); +CdSpectrum *osp_device_take_spectrum (GUsbDevice *device, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +CdSpectrum *osp_device_take_spectrum_full (GUsbDevice *device, + guint64 sample_duration, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gchar *osp_device_get_serial (GUsbDevice *device, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gchar *osp_device_get_fw_version (GUsbDevice *device, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gdouble *osp_device_get_nonlinearity_cal(GUsbDevice *device, + guint *length, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gdouble *osp_device_get_irradiance_cal (GUsbDevice *device, + guint *length, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gdouble *osp_device_get_wavelength_cal (GUsbDevice *device, + guint *length, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gdouble osp_device_get_wavelength_start (GUsbDevice *device, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; +gboolean osp_device_open (GUsbDevice *device, + GError **error) + G_GNUC_WARN_UNUSED_RESULT; + +G_END_DECLS + +#endif /* __OSP_DEVICE_H */ + diff --git a/src/sensors/spark/osp-enum.c b/src/sensors/spark/osp-enum.c new file mode 100644 index 0000000..9c92b80 --- /dev/null +++ b/src/sensors/spark/osp-enum.c @@ -0,0 +1,201 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "config.h" + +#include <glib.h> + +#include "osp-enum.h" + +/** + * osp_cmd_to_string: + * + * Since: 1.2.11 + **/ +const gchar * +osp_cmd_to_string (OspCmd cmd) +{ + if (cmd == OSP_CMD_RESET) + return "reset"; + if (cmd == OSP_CMD_RESET_TO_DEFAULTS) + return "reset-to-defaults"; + if (cmd == OSP_CMD_GET_HARDWARE_VERSION) + return "get-hardware-version"; + if (cmd == OSP_CMD_GET_FIRMWARE_VERSION) + return "get-firmware-version"; + if (cmd == OSP_CMD_GET_SERIAL_NUMBER) + return "get-serial-number"; + if (cmd == OSP_CMD_GET_SERIAL_NUMBER_LENGTH) + return "get-serial-number-length"; + if (cmd == OSP_CMD_GET_DEVICE_ALIAS) + return "get-device-alias"; + if (cmd == OSP_CMD_GET_DEVICE_ALIAS_LENGTH) + return "get-device-alias-length"; + if (cmd == OSP_CMD_SET_DEVICE_ALIAS) + return "set-device-alias"; + if (cmd == OSP_CMD_GET_NUMBER_OF_AVAILABLE_USER_STRINGS) + return "get-number-of-available-user-strings"; + if (cmd == OSP_CMD_GET_USER_STRING_LENGTH) + return "get-user-string-length"; + if (cmd == OSP_CMD_GET_USER_STRING) + return "get-user-string"; + if (cmd == OSP_CMD_SET_USER_STRING) + return "set-user-string"; + if (cmd == OSP_CMD_SET_LED) + return "configures-status-led"; + if (cmd == OSP_CMD_PUT_DEVICE_IN_REPROGRAMMING_MODE) + return "put-device-in-reprogramming-mode"; + if (cmd == OSP_CMD_GET_AND_SEND_CORRECTED_SPECTRUM) + return "get-and-send-corrected-spectrum"; + if (cmd == OSP_CMD_GET_AND_SEND_RAW_SPECTRUM) + return "get-and-send-raw-spectrum"; + if (cmd == OSP_CMD_GET_PARTIAL_SPECTRUM_MODE) + return "get-partial-spectrum-mode"; + if (cmd == OSP_CMD_SET_PARTIAL_SPECTRUM_MODE) + return "set-partial-spectrum-mode"; + if (cmd == OSP_CMD_GET_AND_SEND_PARTIAL_CORRECTED_SPECTRUM) + return "get-and-send-partial-corrected-spectrum"; + if (cmd == OSP_CMD_SET_INTEGRATION_TIME) + return "set-integration-time"; + if (cmd == OSP_CMD_GET_PIXEL_BINNING_FACTOR) + return "get-pixel-binning-factor"; + if (cmd == OSP_CMD_GET_MAXIMUM_BINNING_FACTOR) + return "get-maximum-binning-factor"; + if (cmd == OSP_CMD_GET_DEFAULT_BINNING_FACTOR) + return "get-default-binning-factor"; + if (cmd == OSP_CMD_SET_PIXEL_BINNING_FACTOR) + return "set-pixel-binning-factor"; + if (cmd == OSP_CMD_SET_DEFAULT_BINNING_FACTOR) + return "set-default-binning-factor"; + if (cmd == OSP_CMD_SET_TRIGGER_DELAY_MS) + return "set-trigger-delay-ms"; + if (cmd == OSP_CMD_GET_SCANS_TO_AVERAGE) + return "get-scans-to-average"; + if (cmd == OSP_CMD_SET_SCANS_TO_AVERAGE) + return "set-scans-to-average"; + if (cmd == OSP_CMD_GET_BOXCAR_WIDTH) + return "get-boxcar-width"; + if (cmd == OSP_CMD_SET_BOXCAR_WIDTH) + return "set-boxcar-width"; + if (cmd == OSP_CMD_GET_WAVELENGTH_COEFFICIENT_COUNT) + return "get-wavelength-coefficient-count"; + if (cmd == OSP_CMD_GET_WAVELENGTH_COEFFICIENT) + return "get-wavelength-coefficient"; + if (cmd == OSP_CMD_SET_WAVELENGTH_COEFFICIENT) + return "set-wavelength-coefficient"; + if (cmd == OSP_CMD_GET_NONLINEARITY_COEFFICIENT_COUNT) + return "get-nonlinearity-coefficient-count"; + if (cmd == OSP_CMD_GET_NONLINEARITY_COEFFICIENT) + return "get-nonlinearity-coefficient"; + if (cmd == OSP_CMD_SET_NONLINEARITY_COEFFICIENT) + return "set-nonlinearity-coefficient"; + if (cmd == OSP_CMD_GET_IRRADIANCE_CALIBRATION) + return "get-irradiance-calibration"; + if (cmd == OSP_CMD_GET_IRRADIANCE_CALIBRATION_COUNT) + return "get-irradiance-calibration-count"; + if (cmd == OSP_CMD_GET_IRRADIANCE_CALIBRATION_COLLECTION_AREA) + return "get-irradiance-calibration-collection-area"; + if (cmd == OSP_CMD_SET_IRRADIANCE_CALIBRATION) + return "set-irradiance-calibration"; + if (cmd == OSP_CMD_SET_IRRADIANCE_CALIBRATION_COLLECTION_AREA) + return "set-irradiance-calibration-collection-area"; + if (cmd == OSP_CMD_GET_NUMBER_OF_STRAY_LIGHT_COEFFICIENTS) + return "get-number-of-stray-light-coefficients"; + if (cmd == OSP_CMD_GET_STRAY_LIGHT_COEFFICIENT) + return "get-stray-light-coefficient"; + if (cmd == OSP_CMD_SET_STRAY_LIGHT_COEFFICIENT) + return "set-stray-light-coefficient"; + if (cmd == OSP_CMD_GET_HOT_PIXEL_INDICES) + return "get-hot-pixel-indices"; + if (cmd == OSP_CMD_SET_HOT_PIXEL_INDICES) + return "set-hot-pixel-indices"; + if (cmd == OSP_CMD_GET_BENCH_ID) + return "get-bench-id"; + if (cmd == OSP_CMD_GET_BENCH_SERIAL_NUMBER) + return "get-bench-serial-number"; + if (cmd == OSP_CMD_GET_SLIT_WIDTH_MICRONS) + return "get-slit-width-microns"; + if (cmd == OSP_CMD_GET_FIBER_DIAMETER_MICRONS) + return "get-fiber-diameter-microns"; + if (cmd == OSP_CMD_GET_GRATING) + return "get-grating"; + if (cmd == OSP_CMD_GET_FILTER) + return "get-filter"; + if (cmd == OSP_CMD_GET_COATING) + return "get-coating"; + if (cmd == OSP_CMD_GET_GET_TEMPERATURE_SENSOR_COUNT) + return "get-temperature-sensor-count"; + if (cmd == OSP_CMD_GET_READ_TEMPERATURE_SENSOR) + return "read-temperature-sensor"; + if (cmd == OSP_CMD_GET_READ_ALL_TEMPERATURE_SENSORS) + return "read-all-temperature-sensors"; + return NULL; +} + +/** + * osp_error_code_to_string: + * + * Since: 1.2.11 + **/ +const gchar * +osp_error_code_to_string (OspErrorCode error_code) +{ + if (error_code == OSP_ERROR_CODE_SUCCESS) + return "success"; + if (error_code == OSP_ERROR_CODE_UNSUPPORTED_PROTOCOL) + return "unsupported-protocol"; + if (error_code == OSP_ERROR_CODE_UNKNOWN_MESSAGE_TYPE) + return "unknown-message-type"; + if (error_code == OSP_ERROR_CODE_BAD_CHECKSUM) + return "bad-checksum"; + if (error_code == OSP_ERROR_CODE_MESSAGE_TOO_LARGE) + return "message-too-large"; + if (error_code == OSP_ERROR_CODE_PAYLOAD_LENGTH_INVALID) + return "payload-length-invalid"; + if (error_code == OSP_ERROR_CODE_PAYLOAD_DATA_INVALID) + return "payload-data-invalid"; + if (error_code == OSP_ERROR_CODE_DEVICE_NOT_READY) + return "device-not-ready"; + if (error_code == OSP_ERROR_CODE_UNKNOWN_CHECKSUM_TYPE) + return "unknown-checksum-type"; + if (error_code == OSP_ERROR_CODE_DEVICE_RESET) + return "device-reset"; + if (error_code == OSP_ERROR_CODE_TOO_MANY_BUSES) + return "too-many-busses"; + if (error_code == OSP_ERROR_CODE_OUT_OF_MEMORY) + return "out-of-memory"; + if (error_code == OSP_ERROR_CODE_COMMAND_DATA_MISSING) + return "command-data-missing"; + if (error_code == OSP_ERROR_CODE_INTERNAL_ERROR) + return "internal-error"; + if (error_code == OSP_ERROR_CODE_COULD_NOT_DECRYPT) + return "could-not-decrypt"; + if (error_code == OSP_ERROR_CODE_FIRMWARE_LAYOUT_INVALID) + return "firmware-layout-invalid"; + if (error_code == OSP_ERROR_CODE_DATA_PACKET_INVALID_SIZE) + return "packet-invalid-size"; + if (error_code == OSP_ERROR_CODE_HW_REVISION_INVALID) + return "hardware-revision-invalid"; + if (error_code == OSP_ERROR_CODE_FLASH_MAP_INVALID) + return "flash-map-invalid"; + if (error_code == OSP_ERROR_CODE_RESPONSE_DEFERRED) + return "response-deferred"; + return NULL; +} diff --git a/src/sensors/spark/osp-enum.h b/src/sensors/spark/osp-enum.h new file mode 100644 index 0000000..8772000 --- /dev/null +++ b/src/sensors/spark/osp-enum.h @@ -0,0 +1,163 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef OSP_ENUM_H +#define OSP_ENUM_H + +#include <glib.h> +#include <gusb.h> + +G_BEGIN_DECLS + +#define OSP_USB_VID 0x2457 +#define OSP_USB_PID 0x4200 + +typedef enum { + OSP_HEADER_FLAG_NONE = 0, + OSP_HEADER_FLAG_RESPONSE = 1 << 0, /* set by device */ + OSP_HEADER_FLAG_ACK = 1 << 1, /* set by device */ + OSP_HEADER_FLAG_ACK_REQUIRED = 1 << 2, /* set by host */ + OSP_HEADER_FLAG_NACK = 1 << 3, /* set by device */ + OSP_HEADER_FLAG_ERROR = 1 << 4, /* set by device */ + OSP_HEADER_FLAG_DEPRECATED = 1 << 5, /* set by device */ + /*< private >*/ + OSP_HEADER_FLAG_LAST +} OspHeaderFlag; + +typedef enum { + OSP_ERROR_CODE_SUCCESS = 0, + OSP_ERROR_CODE_UNSUPPORTED_PROTOCOL = 1, + OSP_ERROR_CODE_UNKNOWN_MESSAGE_TYPE = 2, + OSP_ERROR_CODE_BAD_CHECKSUM = 3, + OSP_ERROR_CODE_MESSAGE_TOO_LARGE = 4, + OSP_ERROR_CODE_PAYLOAD_LENGTH_INVALID = 5, + OSP_ERROR_CODE_PAYLOAD_DATA_INVALID = 6, + OSP_ERROR_CODE_DEVICE_NOT_READY = 7, + OSP_ERROR_CODE_UNKNOWN_CHECKSUM_TYPE = 8, + OSP_ERROR_CODE_DEVICE_RESET = 9, + OSP_ERROR_CODE_TOO_MANY_BUSES = 10, + OSP_ERROR_CODE_OUT_OF_MEMORY = 11, + OSP_ERROR_CODE_COMMAND_DATA_MISSING = 12, + OSP_ERROR_CODE_INTERNAL_ERROR = 13, + OSP_ERROR_CODE_COULD_NOT_DECRYPT = 100, + OSP_ERROR_CODE_FIRMWARE_LAYOUT_INVALID = 101, + OSP_ERROR_CODE_DATA_PACKET_INVALID_SIZE = 102, + OSP_ERROR_CODE_HW_REVISION_INVALID = 103, + OSP_ERROR_CODE_FLASH_MAP_INVALID = 104, + OSP_ERROR_CODE_RESPONSE_DEFERRED = 255, + /*< private >*/ + OSP_ERROR_CODE_LAST, +} OspErrorCode; + +typedef enum { + OSP_HEADER_CHECKSUM_KIND_NONE = 0, + OSP_HEADER_CHECKSUM_KIND_MD5 = 1, + /*< private >*/ + OSP_HEADER_CHECKSUM_KIND_LAST +} OspHeaderChecksumKind; + +typedef enum { + OSP_CMD_RESET = 0x00000000, + OSP_CMD_RESET_TO_DEFAULTS = 0x00000001, + OSP_CMD_GET_HARDWARE_VERSION = 0x00000080, + OSP_CMD_GET_FIRMWARE_VERSION = 0x00000090, + OSP_CMD_GET_SERIAL_NUMBER = 0x00000100, + OSP_CMD_GET_SERIAL_NUMBER_LENGTH = 0x00000101, + OSP_CMD_GET_DEVICE_ALIAS = 0x00000200, + OSP_CMD_GET_DEVICE_ALIAS_LENGTH = 0x00000201, + OSP_CMD_SET_DEVICE_ALIAS = 0x00000210, + OSP_CMD_GET_NUMBER_OF_AVAILABLE_USER_STRINGS = 0x00000300, + OSP_CMD_GET_USER_STRING_LENGTH = 0x00000301, + OSP_CMD_GET_USER_STRING = 0x00000302, + OSP_CMD_SET_USER_STRING = 0x00000310, + OSP_CMD_SET_LED = 0x00001010, + OSP_CMD_PUT_DEVICE_IN_REPROGRAMMING_MODE = 0x000fff00, + OSP_CMD_GET_AND_SEND_CORRECTED_SPECTRUM = 0x00101000, + OSP_CMD_GET_AND_SEND_RAW_SPECTRUM = 0x00101100, + OSP_CMD_GET_PARTIAL_SPECTRUM_MODE = 0x00102000, + OSP_CMD_SET_PARTIAL_SPECTRUM_MODE = 0x00102010, + OSP_CMD_GET_AND_SEND_PARTIAL_CORRECTED_SPECTRUM = 0x00102080, + OSP_CMD_SET_INTEGRATION_TIME = 0x00110010, + OSP_CMD_GET_PIXEL_BINNING_FACTOR = 0x00110280, + OSP_CMD_GET_MAXIMUM_BINNING_FACTOR = 0x00110281, + OSP_CMD_GET_DEFAULT_BINNING_FACTOR = 0x00110285, + OSP_CMD_SET_PIXEL_BINNING_FACTOR = 0x00110290, + OSP_CMD_SET_DEFAULT_BINNING_FACTOR = 0x00110295, + OSP_CMD_SET_TRIGGER_DELAY_MS = 0x00110510, + OSP_CMD_GET_SCANS_TO_AVERAGE = 0x00120000, + OSP_CMD_SET_SCANS_TO_AVERAGE = 0x00120010, + OSP_CMD_GET_BOXCAR_WIDTH = 0x00121000, + OSP_CMD_SET_BOXCAR_WIDTH = 0x00121010, + OSP_CMD_GET_WAVELENGTH_COEFFICIENT_COUNT = 0x00180100, + OSP_CMD_GET_WAVELENGTH_COEFFICIENT = 0x00180101, + OSP_CMD_SET_WAVELENGTH_COEFFICIENT = 0x00180111, + OSP_CMD_GET_NONLINEARITY_COEFFICIENT_COUNT = 0x00181100, + OSP_CMD_GET_NONLINEARITY_COEFFICIENT = 0x00181101, + OSP_CMD_SET_NONLINEARITY_COEFFICIENT = 0x00181111, + OSP_CMD_GET_IRRADIANCE_CALIBRATION = 0x00182001, + OSP_CMD_GET_IRRADIANCE_CALIBRATION_COUNT = 0x00182002, + OSP_CMD_GET_IRRADIANCE_CALIBRATION_COLLECTION_AREA = 0x00182003, + OSP_CMD_SET_IRRADIANCE_CALIBRATION = 0x00182011, + OSP_CMD_SET_IRRADIANCE_CALIBRATION_COLLECTION_AREA = 0x00182013, + OSP_CMD_GET_NUMBER_OF_STRAY_LIGHT_COEFFICIENTS = 0x00183100, + OSP_CMD_GET_STRAY_LIGHT_COEFFICIENT = 0x00183101, + OSP_CMD_SET_STRAY_LIGHT_COEFFICIENT = 0x00183111, + OSP_CMD_GET_HOT_PIXEL_INDICES = 0x00186000, + OSP_CMD_SET_HOT_PIXEL_INDICES = 0x00186010, + OSP_CMD_GET_BENCH_ID = 0x001b0000, + OSP_CMD_GET_BENCH_SERIAL_NUMBER = 0x001b0100, + OSP_CMD_GET_SLIT_WIDTH_MICRONS = 0x001b0200, + OSP_CMD_GET_FIBER_DIAMETER_MICRONS = 0x001b0300, + OSP_CMD_GET_GRATING = 0x001b0400, + OSP_CMD_GET_FILTER = 0x001b0500, + OSP_CMD_GET_COATING = 0x001b0600, + OSP_CMD_GET_GET_TEMPERATURE_SENSOR_COUNT = 0x00400000, + OSP_CMD_GET_READ_TEMPERATURE_SENSOR = 0x00400001, + OSP_CMD_GET_READ_ALL_TEMPERATURE_SENSORS = 0x00400002, + /*< private >*/ + OSP_CMD_LAST +} OspCmd; + +typedef struct { + guint16 start_bytes; + guint16 protocol_version; + guint16 flags; + guint16 error_code; + guint32 message_type; + guint32 regarding; + guint8 reserved[6]; + guint8 checksum_type; + guint8 immediate_data_length; + guint8 immediate_data[16]; + guint32 bytes_remaining; +} OspProtocolHeader; + +typedef struct { + guint8 checksum[16]; + guint32 end_bytes; +} OspProtocolFooter; + +const gchar *osp_error_code_to_string (OspErrorCode error_code); +const gchar *osp_cmd_to_string (OspCmd cmd); + +G_END_DECLS + +#endif diff --git a/src/sensors/spark/osp-self-test.c b/src/sensors/spark/osp-self-test.c new file mode 100644 index 0000000..0c48464 --- /dev/null +++ b/src/sensors/spark/osp-self-test.c @@ -0,0 +1,190 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2015 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU General Public License Version 2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <glib-object.h> +#include <colord.h> + +#include "osp-device.h" +#include "osp-enum.h" + +static GUsbDevice * +osp_client_get_default (GError **error) +{ + g_autoptr(GUsbContext) usb_ctx = NULL; + g_autoptr(GUsbDevice) device = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* try to find the Spark device */ + usb_ctx = g_usb_context_new (NULL); + if (usb_ctx == NULL) { + g_set_error (error, + G_USB_DEVICE_ERROR, + G_USB_DEVICE_ERROR_NOT_SUPPORTED, + "No device found; USB initialisation failed"); + return NULL; + } + device = g_usb_context_find_by_vid_pid (usb_ctx, + OSP_USB_VID, + OSP_USB_PID, + error); + if (device == NULL) + return NULL; + g_debug ("Found Spark device %s", + g_usb_device_get_platform_id (device)); + if (!osp_device_open (device, error)) + return NULL; + return g_object_ref (device); +} + +static void +osp_test_protocol_func (void) +{ + gchar *tmp; + guint32 cmd_val; + guint8 cmd[4]; + guint i; + guint j; + g_auto(GStrv) lines = NULL; + g_autofree gchar *data = NULL; + g_autoptr(GError) error = NULL; + + if (!g_file_test ("protocol-dump.csv", G_FILE_TEST_EXISTS)) + return; + g_file_get_contents ("protocol-dump.csv", &data, NULL, &error); + g_assert_no_error (error); + + lines = g_strsplit (data, "\n", -1); + for (i = 0; lines[i] != NULL; i++) { + g_auto(GStrv) tokens = NULL; + tmp = g_strstr_len (lines[i], -1, "OUT txn"); + if (tmp == NULL) + continue; + tokens = g_strsplit (tmp, " ", -1); + for (j = 0; j < 4; j++) + cmd[j] = g_ascii_strtoull (tokens[j + 9], NULL, 16); + cmd_val = *((guint32 *) cmd); + g_print ("%08x = %s\n", cmd_val, osp_cmd_to_string (cmd_val)); + } +} + +static void +osp_test_reading_xyz_func (void) +{ + guint i, j; + g_autofree gchar *fwver = NULL; + g_autofree gchar *serial = NULL; + g_autoptr(CdSpectrum) sp = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GUsbDevice) device = NULL; + + /* load the device */ + device = osp_client_get_default (&error); + if (device == NULL && g_error_matches (error, + G_USB_DEVICE_ERROR, + G_USB_DEVICE_ERROR_NO_DEVICE)) { + g_debug ("skipping tests: %s", error->message); + return; + } + g_assert_no_error (error); + g_assert (device != NULL); + + serial = osp_device_get_serial (device, &error); + g_assert_no_error (error); + g_assert_cmpstr (serial, !=, NULL); + + fwver = osp_device_get_fw_version (device, &error); + g_assert_no_error (error); + g_assert_cmpstr (fwver, ==, "0.4"); + + sp = osp_device_take_spectrum (device, &error); + g_assert_no_error (error); + g_assert (sp != NULL); + for (i = 0; i < 1024; i+=5) { + g_print ("%.1fnm: ", cd_spectrum_get_wavelength (sp, i)); + for (j = 0; j < cd_spectrum_get_value_raw (sp, i) * 1000.f; j++) + g_print ("*"); + g_print ("\n"); + } +} + +static void +osp_test_wavelength_cal_func (void) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GUsbDevice) device = NULL; + g_autofree gdouble *coefficients = NULL; + gdouble start; + + /* load the device */ + device = osp_client_get_default (&error); + if (device == NULL && g_error_matches (error, + G_USB_DEVICE_ERROR, + G_USB_DEVICE_ERROR_NO_DEVICE)) { + g_debug ("skipping tests: %s", error->message); + return; + } + g_assert_no_error (error); + g_assert (device != NULL); + + /* get coefficients */ + coefficients = osp_device_get_wavelength_cal (device, NULL, &error); + g_assert_no_error (error); + g_assert (coefficients != NULL); + g_assert_cmpfloat (ABS (coefficients[0] - 0.37f), <, 0.1f); + g_assert_cmpfloat (ABS (coefficients[1] - 0.00f), <, 0.1f); + g_assert_cmpfloat (ABS (coefficients[2] - 0.00f), <, 0.1f); + + /* get start */ + start = osp_device_get_wavelength_start (device, &error); + g_assert_no_error (error); + g_assert (start > 0); + g_assert_cmpfloat (ABS (start - 355), <, 5); + + /* get irradiance coefficients */ + coefficients = osp_device_get_irradiance_cal (device, NULL, &error); + g_assert_error (error, OSP_DEVICE_ERROR, OSP_DEVICE_ERROR_NO_DATA); + g_clear_error (&error); + + /* get nonlinearity coefficients */ + coefficients = osp_device_get_nonlinearity_cal (device, NULL, &error); + g_assert_error (error, OSP_DEVICE_ERROR, OSP_DEVICE_ERROR_NO_DATA); + g_clear_error (&error); +} + +int +main (int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + + /* only critical and error are fatal */ + g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); + + /* tests go here */ + g_test_add_func ("/Spark/protocol", osp_test_protocol_func); + g_test_add_func ("/Spark/wavelength-cal", osp_test_wavelength_cal_func); + g_test_add_func ("/Spark/reading-xyz", osp_test_reading_xyz_func); + + return g_test_run (); +} + |