-/* $OpenBSD: ugold.c,v 1.23 2023/04/19 04:51:53 miod Exp $ */
+/* $OpenBSD: ugold.c,v 1.24 2023/11/30 20:08:23 miod Exp $ */
/*
* Copyright (c) 2013 Takayoshi SASANO <uaa@openbsd.org>
* Copyright (c) 2013 Martin Pieuchot <mpi@openbsd.org>
* Copyright (c) 2015 Joerg Jung <jung@openbsd.org>
+ * Copyright (c) 2023 Miodrag Vallat.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
#define UGOLD_CMD_DATA 0x80
#define UGOLD_CMD_INIT 0x82
+#define UGOLD_TYPE_INVALID -1
#define UGOLD_TYPE_SI7005 1
#define UGOLD_TYPE_SI7006 2
#define UGOLD_TYPE_SHT1X 3
#define UGOLD_TYPE_GOLD 4
#define UGOLD_TYPE_TEMPERX 5
+#define UGOLD_TYPE_DS75 6
/*
* This driver uses three known commands for the TEMPer and TEMPerHUM
static uint8_t cmd_data[8] = { 0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00 };
static uint8_t cmd_init[8] = { 0x01, 0x82, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00 };
static uint8_t cmd_type[8] = { 0x01, 0x86, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00 };
+/*
+ * The following command is also recognized and reports some kind of status
+ * byte (i.e. 87 xx 00 00 00 00 00 00).
+ { 0x01, 0x87, 0xee, 0x01, 0x00, 0x00, 0x00, 0x00 };
+ */
+
+struct ugold_softc;
struct ugold_softc {
struct uhidev sc_hdev;
int sc_num_sensors;
int sc_type;
+ char sc_model[16 + 1];
+ unsigned int sc_model_len;
+
struct ksensor sc_sensor[UGOLD_MAX_SENSORS];
struct ksensordev sc_sensordev;
struct sensor_task *sc_sensortask;
+
+ void (*sc_intr)(struct ugold_softc *, uint8_t *, u_int);
};
const struct usb_devno ugold_devs[] = {
{ USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPER },
{ USB_VENDOR_MICRODIA, USB_PRODUCT_MICRODIA_TEMPERHUM },
{ USB_VENDOR_PCSENSORS, USB_PRODUCT_PCSENSORS_TEMPER },
+ { USB_VENDOR_RDING, USB_PRODUCT_RDING_TEMPER },
{ USB_VENDOR_WCH2, USB_PRODUCT_WCH2_TEMPER },
};
void ugold_attach(struct device *, struct device *, void *);
int ugold_detach(struct device *, int);
-void ugold_ds75_intr(struct uhidev *, void *, u_int);
-void ugold_si700x_intr(struct uhidev *, void *, u_int);
+void ugold_setup_sensors(struct ugold_softc *);
+void ugold_intr(struct uhidev *, void *, u_int);
+void ugold_ds75_intr(struct ugold_softc *, uint8_t *, u_int);
+void ugold_si700x_intr(struct ugold_softc *, uint8_t *, u_int);
void ugold_refresh(void *);
int ugold_issue_cmd(struct ugold_softc *, uint8_t *, int);
sc->sc_udev = uha->parent->sc_udev;
sc->sc_hdev.sc_parent = uha->parent;
sc->sc_hdev.sc_report_id = uha->reportid;
+ sc->sc_hdev.sc_intr = ugold_intr;
switch (uha->uaa->product) {
case USB_PRODUCT_MICRODIA_TEMPER:
- sc->sc_hdev.sc_intr = ugold_ds75_intr;
+ sc->sc_intr = ugold_ds75_intr;
break;
case USB_PRODUCT_MICRODIA_TEMPERHUM:
case USB_PRODUCT_PCSENSORS_TEMPER:
+ case USB_PRODUCT_RDING_TEMPER:
case USB_PRODUCT_WCH2_TEMPER:
- sc->sc_hdev.sc_intr = ugold_si700x_intr;
+ sc->sc_intr = ugold_si700x_intr;
break;
default:
printf(", unknown product\n");
strlcpy(sc->sc_sensordev.xname, sc->sc_hdev.sc_dev.dv_xname,
sizeof(sc->sc_sensordev.xname));
- switch (uha->uaa->product) {
- case USB_PRODUCT_MICRODIA_TEMPER:
- /* 2 temperature sensors */
- sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
- strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
- sizeof(sc->sc_sensor[UGOLD_INNER].desc));
- sc->sc_sensor[UGOLD_OUTER].type = SENSOR_TEMP;
- strlcpy(sc->sc_sensor[UGOLD_OUTER].desc, "outer",
- sizeof(sc->sc_sensor[UGOLD_OUTER].desc));
- break;
- case USB_PRODUCT_MICRODIA_TEMPERHUM:
- case USB_PRODUCT_PCSENSORS_TEMPER:
- case USB_PRODUCT_WCH2_TEMPER:
- /* 1 temperature and 1 humidity sensor */
- sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
- strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
- sizeof(sc->sc_sensor[UGOLD_INNER].desc));
- sc->sc_sensor[UGOLD_HUM].type = SENSOR_HUMIDITY;
- strlcpy(sc->sc_sensor[UGOLD_HUM].desc, "RH",
- sizeof(sc->sc_sensor[UGOLD_HUM].desc));
- break;
- default:
- printf(", unknown product\n");
- return;
- }
-
- /* 0.1Hz */
+ /* 0.166Hz */
sc->sc_sensortask = sensor_task_register(sc, ugold_refresh, 6);
if (sc->sc_sensortask == NULL) {
printf(", unable to register update task\n");
}
printf("\n");
+ /* speed up sensor identification */
+ ugold_refresh(sc);
+
sensordev_install(&sc->sc_sensordev);
}
sensordev_deinstall(&sc->sc_sensordev);
}
- for (i = 0; i < sc->sc_num_sensors; i++)
- sensor_detach(&sc->sc_sensordev, &sc->sc_sensor[i]);
+ if (sc->sc_type != UGOLD_TYPE_INVALID) {
+ for (i = 0; i < sc->sc_num_sensors; i++)
+ sensor_detach(&sc->sc_sensordev, &sc->sc_sensor[i]);
+ }
if (sc->sc_hdev.sc_state & UHIDEV_OPEN)
uhidev_close(&sc->sc_hdev);
return (0);
}
+void
+ugold_setup_sensors(struct ugold_softc *sc)
+{
+ int i;
+
+ switch (sc->sc_type) {
+ default:
+ return;
+ case UGOLD_TYPE_SI7005:
+ case UGOLD_TYPE_SI7006:
+ case UGOLD_TYPE_SHT1X:
+ case UGOLD_TYPE_TEMPERX:
+ /* 1 temperature and 1 humidity sensor */
+ sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
+ strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
+ sizeof(sc->sc_sensor[UGOLD_INNER].desc));
+ sc->sc_sensor[UGOLD_HUM].type = SENSOR_HUMIDITY;
+ strlcpy(sc->sc_sensor[UGOLD_HUM].desc, "RH",
+ sizeof(sc->sc_sensor[UGOLD_HUM].desc));
+ break;
+ case UGOLD_TYPE_GOLD:
+ case UGOLD_TYPE_DS75:
+ /* up to 2 temperature sensors */
+ sc->sc_sensor[UGOLD_INNER].type = SENSOR_TEMP;
+ strlcpy(sc->sc_sensor[UGOLD_INNER].desc, "inner",
+ sizeof(sc->sc_sensor[UGOLD_INNER].desc));
+ sc->sc_sensor[UGOLD_OUTER].type = SENSOR_TEMP;
+ strlcpy(sc->sc_sensor[UGOLD_OUTER].desc, "outer",
+ sizeof(sc->sc_sensor[UGOLD_OUTER].desc));
+ break;
+ }
+ for (i = 0; i < sc->sc_num_sensors; i++) {
+ sc->sc_sensor[i].flags |= SENSOR_FINVALID;
+ sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
+ }
+}
+
+static void
+strnvis(char *dst, const char *src, size_t siz)
+{
+ char *start, *end;
+ int c;
+
+ for (start = dst, end = start + siz - 1; (c = *src) && dst < end; ) {
+ if (c >= 0x20 && c <= 0x7f) {
+ if (c == '\\') {
+ /* need space for the extra '\\' */
+ if (dst + 2 > end)
+ break;
+ *dst++ = '\\';
+ }
+ *dst++ = c;
+ } else {
+ if (dst + 4 > end)
+ break;
+ *dst++ = '\\';
+ *dst++ = ((u_char)c >> 6 & 07) + '0';
+ *dst++ = ((u_char)c >> 3 & 07) + '0';
+ *dst++ = ((u_char)c & 07) + '0';
+ }
+ src++;
+ }
+ if (siz > 0)
+ *dst = '\0';
+}
+
static int
ugold_ds75_temp(uint8_t msb, uint8_t lsb)
{
}
static void
-ugold_ds75_type(struct ugold_softc *sc, uint8_t *buf, u_int len)
+ugold_ds75_type(struct ugold_softc *sc)
{
- if (memcmp(buf, "TEMPer1F", len) == 0 ||
- memcmp(buf, "TEMPer2F", len) == 0 ||
- memcmp(buf, "TEMPerF1", len) == 0)
- return; /* skip first half of the answer */
-
- printf("%s: %d sensor%s type ds75/12bit (temperature)\n",
- sc->sc_hdev.sc_dev.dv_xname, sc->sc_num_sensors,
- (sc->sc_num_sensors == 1) ? "" : "s");
+ char model[4 * sizeof(sc->sc_model) + 1];
+
+ strnvis(model, sc->sc_model, sizeof model);
+
+ if (memcmp(sc->sc_model, "TEMPer1F", 8) == 0 ||
+ memcmp(sc->sc_model, "TEMPer2F", 8) == 0 ||
+ memcmp(sc->sc_model, "TEMPerF1", 8) == 0) {
+ sc->sc_type = UGOLD_TYPE_DS75;
+ ugold_setup_sensors(sc);
+ printf("%s: \"%s\", %d sensor%s"
+ " type ds75/12bit (temperature)\n",
+ sc->sc_hdev.sc_dev.dv_xname, model, sc->sc_num_sensors,
+ (sc->sc_num_sensors == 1) ? "" : "s");
+ ugold_refresh(sc);
+ return;
+ }
- sc->sc_type = -1; /* ignore type */
+ printf("%s: unknown model \"%s\"\n",
+ sc->sc_hdev.sc_dev.dv_xname, model);
+ sc->sc_num_sensors = 0;
+ sc->sc_type = UGOLD_TYPE_INVALID;
}
void
-ugold_ds75_intr(struct uhidev *addr, void *ibuf, u_int len)
+ugold_ds75_intr(struct ugold_softc *sc, uint8_t *buf, u_int len)
{
- struct ugold_softc *sc = (struct ugold_softc *)addr;
- uint8_t *buf = ibuf;
- int i, temp;
+ int temp;
switch (buf[0]) {
case UGOLD_CMD_INIT:
- if (sc->sc_num_sensors)
+ if (sc->sc_num_sensors != 0)
break;
-
- sc->sc_num_sensors = min(buf[1], UGOLD_MAX_SENSORS) /* XXX */;
-
- for (i = 0; i < sc->sc_num_sensors; i++) {
- sc->sc_sensor[i].flags |= SENSOR_FINVALID;
- sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
- }
-
+ sc->sc_num_sensors = imin(buf[1], UGOLD_MAX_SENSORS) /* XXX */;
+ ugold_refresh(sc);
break;
case UGOLD_CMD_DATA:
switch (buf[1]) {
sc->sc_sensor[UGOLD_INNER].flags &= ~SENSOR_FINVALID;
break;
default:
+#ifdef UGOLD_DEBUG
printf("%s: invalid data length (%d bytes)\n",
sc->sc_hdev.sc_dev.dv_xname, buf[1]);
+#endif
+ break;
}
break;
default:
- if (!sc->sc_type) { /* type command returns arbitrary string */
- ugold_ds75_type(sc, buf, len);
- break;
- }
- printf("%s: unknown command 0x%02x\n",
- sc->sc_hdev.sc_dev.dv_xname, buf[0]);
+ ugold_ds75_type(sc);
+ break;
}
}
}
static void
-ugold_si700x_type(struct ugold_softc *sc, uint8_t *buf, u_int len)
+ugold_si700x_type(struct ugold_softc *sc)
{
- if (memcmp(buf, "TEMPerHu", len) == 0 ||
- memcmp(buf, "TEMPer1F", len) == 0 ||
- memcmp(buf, "TEMPerX_", len) == 0 ||
- memcmp(buf, "TEMPerGo", len) == 0)
- return; /* skip equal first half of the answer */
-
- printf("%s: %d sensor%s type ", sc->sc_hdev.sc_dev.dv_xname,
- sc->sc_num_sensors, (sc->sc_num_sensors == 1) ? "" : "s");
-
- if (memcmp(buf, "mM12V1.0", len) == 0) {
- sc->sc_type = UGOLD_TYPE_SI7005;
- printf("si7005 (temperature and humidity)\n");
- } else if (memcmp(buf, "mM12V1.2", len) == 0) {
- sc->sc_type = UGOLD_TYPE_SI7006;
- printf("si7006 (temperature and humidity)\n");
- } else if (memcmp(buf, "_H1V1.5F", len) == 0) {
+ char model[4 * sizeof(sc->sc_model) + 1];
+ const char *descr;
+ int nsensors = 0;
+
+ strnvis(model, sc->sc_model, sizeof model);
+
+ /* TEMPerHUM prefix */
+ if (sc->sc_model_len >= 9 &&
+ memcmp(sc->sc_model, "TEMPerHum", 9) == 0) {
+ if (memcmp(sc->sc_model + 9, "M12V1.0", 16 - 9) == 0) {
+ sc->sc_type = UGOLD_TYPE_SI7005;
+ descr = "si7005 (temperature and humidity)";
+ goto identified;
+ }
+ if (memcmp(sc->sc_model + 9, "M12V1.2", 16 - 9) == 0) {
+ sc->sc_type = UGOLD_TYPE_SI7006;
+ descr = "si7006 (temperature and humidity)";
+ goto identified;
+ }
+ }
+ if (sc->sc_model_len >= 9 &&
+ memcmp(sc->sc_model, "TEMPerHUM", 9) == 0) {
+ if (memcmp(sc->sc_model + 9, "_V4.0 ", 16 - 9) == 0) {
+ sc->sc_type = UGOLD_TYPE_TEMPERX;
+ descr = "temperx (temperature and humidity)";
+ goto identified;
+ }
+ }
+
+ /* TEMPerX prefix */
+ if (sc->sc_model_len >= 8 &&
+ memcmp(sc->sc_model, "TEMPerX_", 8) == 0) {
+ if (memcmp(sc->sc_model + 8, "V3.1 ", 16 - 8) == 0 ||
+ memcmp(sc->sc_model + 8, "V3.3 ", 16 - 8) == 0) {
+ sc->sc_type = UGOLD_TYPE_TEMPERX;
+ descr = "temperx (temperature and humidity)";
+ goto identified;
+ }
+ }
+
+ /* TEMPer1F or TEMPer2_ prefixes */
+ if (sc->sc_model_len >= 16 &&
+ memcmp(sc->sc_model, "TEMPer1F_H1V1.5F", 16) == 0) {
sc->sc_type = UGOLD_TYPE_SHT1X;
- printf("sht1x (temperature and humidity)\n");
- } else if (memcmp(buf, "V3.1 ", len) == 0) {
- sc->sc_type = UGOLD_TYPE_TEMPERX;
- printf("temperx (temperature and humidity)\n");
- } else if (memcmp(buf, "V3.3 ", len) == 0) {
- sc->sc_type = UGOLD_TYPE_TEMPERX;
- printf("temperx (temperature and humidity)\n");
- } else if (memcmp(buf, "ld_V3.1 ", len) == 0) {
- sc->sc_type = UGOLD_TYPE_GOLD;
- printf("gold (temperature only)\n");
- } else if (memcmp(buf, "ld_V3.4 ", len) == 0) {
+ descr = "sht1x (temperature and humidity)";
+ goto identified;
+ }
+ if (sc->sc_model_len >= 16 &&
+ (memcmp(sc->sc_model, "TEMPer1F_V4.1\0\0\0", 16) == 0 ||
+ memcmp(sc->sc_model, "TEMPer2_V4.1\0\0\0\0", 16) == 0)) {
sc->sc_type = UGOLD_TYPE_GOLD;
- printf("gold (temperature only)\n");
- } else {
- sc->sc_type = -1;
- printf("unknown\n");
+ /*
+ * TEMPer1F devices lack the internal sensor, but will never
+ * report data for it, so it will never gets marked as valid.
+ * We thus keep the value of sc_num_sensors unchanged at 2,
+ * and make sure we will only report one single sensor below.
+ */
+ if (sc->sc_model[6] == '1')
+ nsensors = 1;
+ descr = "gold (temperature only)";
+ goto identified;
}
+
+ /* TEMPerGold prefix */
+ if (sc->sc_model_len >= 11 &&
+ memcmp(sc->sc_model, "TEMPerGold_", 11) == 0) {
+ if (memcmp(sc->sc_model + 11, "V3.1 ", 16 - 11) == 0 ||
+ memcmp(sc->sc_model + 11, "V3.4 ", 16 - 11) == 0) {
+ sc->sc_type = UGOLD_TYPE_GOLD;
+ sc->sc_num_sensors = 1;
+ descr = "gold (temperature only)";
+ goto identified;
+ }
+ }
+
+ printf("%s: unknown model \"%s\"\n",
+ sc->sc_hdev.sc_dev.dv_xname, model);
+ sc->sc_num_sensors = 0;
+ sc->sc_type = UGOLD_TYPE_INVALID;
+ return;
+
+ identified:
+ ugold_setup_sensors(sc);
+ if (nsensors == 0)
+ nsensors = sc->sc_num_sensors;
+ printf("%s: \"%s\", %d sensor%s type %s\n", sc->sc_hdev.sc_dev.dv_xname,
+ model, nsensors, (nsensors == 1) ? "" : "s", descr);
+ ugold_refresh(sc);
}
void
-ugold_si700x_intr(struct uhidev *addr, void *ibuf, u_int len)
+ugold_si700x_intr(struct ugold_softc *sc, uint8_t *buf, u_int len)
{
- struct ugold_softc *sc = (struct ugold_softc *)addr;
- uint8_t *buf = ibuf;
- int i, temp, rhum;
+ int temp, sensor, rhum;
switch (buf[0]) {
case UGOLD_CMD_INIT:
- if (sc->sc_num_sensors)
+ if (sc->sc_num_sensors != 0)
break;
-
- if (sc->sc_type == UGOLD_TYPE_GOLD)
- sc->sc_num_sensors = 1;
- else
- sc->sc_num_sensors = min(buf[1],
- UGOLD_MAX_SENSORS) /* XXX */;
-
- for (i = 0; i < sc->sc_num_sensors; i++) {
- sc->sc_sensor[i].flags |= SENSOR_FINVALID;
- sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
- }
+ /* XXX some devices report 0x04 here */
+ sc->sc_num_sensors = imin(buf[1], UGOLD_MAX_SENSORS);
+ ugold_refresh(sc);
break;
case UGOLD_CMD_DATA:
- if (buf[1] != 4 && buf[1] != 64 && buf[1] != 128)
- printf("%s: invalid data length (%d bytes)\n",
+ if (sc->sc_type == UGOLD_TYPE_GOLD) {
+ if (buf[1] == 0x80)
+ sensor = UGOLD_INNER;
+ else if (buf[1] == 0x01)
+ sensor = UGOLD_OUTER;
+ else
+ sensor = -1;
+ } else {
+ if (buf[1] == 0x04 || buf[1] == 0x20 ||
+ buf[1] == 0x40 || buf[1] == 0x80)
+ sensor = UGOLD_INNER;
+ else
+ sensor = -1;
+ }
+ if (sensor < 0) {
+ /* unexpected data, ignore */
+#ifdef UGOLD_DEBUG
+ printf("%s: unexpected sensor id %02x\n",
sc->sc_hdev.sc_dev.dv_xname, buf[1]);
+#endif
+ break;
+ }
+
temp = ugold_si700x_temp(sc->sc_type, buf[2], buf[3]);
- sc->sc_sensor[UGOLD_INNER].value = (temp * 1000) + 273150000;
- sc->sc_sensor[UGOLD_INNER].flags &= ~SENSOR_FINVALID;
+ sc->sc_sensor[sensor].value = (temp * 1000) + 273150000;
+ /*
+ * TEMPer1F and TEMPer2 report 200C when the sensor probe is
+ * missing or not plugged correctly.
+ */
+ if (sc->sc_type == UGOLD_TYPE_GOLD && temp == 200000)
+ sc->sc_sensor[sensor].flags |= SENSOR_FINVALID;
+ else
+ sc->sc_sensor[sensor].flags &= ~SENSOR_FINVALID;
+
if (sc->sc_type != UGOLD_TYPE_GOLD) {
rhum = ugold_si700x_rhum(sc->sc_type, buf[4], buf[5], temp);
sc->sc_sensor[UGOLD_HUM].value = rhum;
}
break;
default:
- if (!sc->sc_type) { /* type command returns arbitrary string */
- ugold_si700x_type(sc, buf, len);
+ ugold_si700x_type(sc);
+ break;
+ }
+}
+
+void
+ugold_intr(struct uhidev *addr, void *ibuf, u_int len)
+{
+ struct ugold_softc *sc = (struct ugold_softc *)addr;
+ uint8_t *buf = ibuf;
+ unsigned long chunk;
+
+#ifdef UGOLD_DEBUG
+ {
+ printf("%s: %u bytes\n", sc->sc_hdev.sc_dev.dv_xname, len);
+ u_int i;
+ for (i = 0; i < len; i++) {
+ if (i != 0 && (i % 8) == 0)
+ printf("\n");
+ printf("%02x ", buf[i]);
+ }
+ printf("\n");
+ }
+#endif
+
+ switch (buf[0]) {
+ case UGOLD_CMD_INIT:
+ case UGOLD_CMD_DATA:
+ (*sc->sc_intr)(sc, buf, len);
+ break;
+ default:
+ if (!sc->sc_type) {
+ /*
+ * Exact sensor type is not known yet, type command
+ * returns arbitrary string.
+ */
+ chunk = ulmin(len,
+ sizeof(sc->sc_model) - 1 - sc->sc_model_len);
+ if (chunk != 0) {
+ memcpy(sc->sc_model + sc->sc_model_len, buf,
+ chunk);
+ sc->sc_model_len += chunk;
+ }
+ if (sc->sc_model_len > 8) {
+ /* should have enough data now */
+ (*sc->sc_intr)(sc, buf, len);
+ }
break;
}
printf("%s: unknown command 0x%02x\n",
sc->sc_hdev.sc_dev.dv_xname, buf[0]);
+ break;
}
}
struct ugold_softc *sc = arg;
int i;
+ /*
+ * Don't waste time talking to the device if we don't understand
+ * its language.
+ */
+ if (sc->sc_type == UGOLD_TYPE_INVALID)
+ return;
+
if (!sc->sc_num_sensors) {
ugold_issue_cmd(sc, cmd_init, sizeof(cmd_init));
return;