From: Jonathan McDowell <noodles@earth.li>
Date: Wed, 2 May 2018 17:47:22 +0000 (+0100)
Subject: Add initial TEMPerV1.2 clone skeleton
X-Git-Url: https://the.earth.li/gitweb/?a=commitdiff_plain;h=3c727aad6ec52918a14a6550349a2e2ca35d0645;p=temper-clone.git

Add initial TEMPerV1.2 clone skeleton

This is a clone of the pcsensors.com TEMPerV1.2 device. At present
we just lie and claim 15°C rather than supporting a temperature
sensor. Tested with pcsensor-temper, temper-python + TEMPered.
---

diff --git a/main.c b/main.c
new file mode 100644
index 0000000..fff2ae8
--- /dev/null
+++ b/main.c
@@ -0,0 +1,346 @@
+/*
+ * Basic firmware to emulate a USB TEMPer device (which uses an FM75 I2C
+ * sensor internally) using a Digispark + a DS18B20 1-wire sensor.
+ *
+ * Copyright 2018 Jonathan McDowell <noodles@earth.li>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <avr/eeprom.h>
+#include <avr/interrupt.h>
+#include <avr/io.h>
+#include <avr/wdt.h>
+#include <util/delay.h>
+
+#include <avr/pgmspace.h>
+#include "usbdrv.h"
+#include "libs-device/osccal.h"
+
+typedef struct {
+	uint8_t modifier;
+	uint8_t reserved;
+	uint8_t keycode[6];
+} keyboard_report_t;
+keyboard_report_t keyboard_report;
+
+uint8_t temp_report[8];
+bool have_temp_int = false;
+
+#define NUM_LOCK	1
+#define CAPS_LOCK	2
+#define SCROLL_LOCK	4
+volatile static uchar ledstate = 0xff;
+static uchar idlerate;
+
+/* We populate this with the 1-wire device ROM ID when we get it */
+int serno_str[] = {
+	USB_STRING_DESCRIPTOR_HEADER(16),
+	'F', 'F', 'F', 'F', 'F', 'F', 'F', 'F',
+	'F', 'F', 'F', 'F', 'F', 'F', 'F', 'F',
+};
+
+#define USB_INDEX_KEYBOARD	0
+#define USB_INDEX_MOUSE		1
+
+PROGMEM const char usbHidKeyboardReportDescriptor[] = {
+	0x05, 0x01,		// USAGE_PAGE (Generic Desktop)
+	0x09, 0x06,		// USAGE (Keyboard)
+	0xa1, 0x01,		// COLLECTION (Application)
+	0x85, 0x01,		//   REPORT_ID (1)
+	0x05, 0x07,		//   USAGE_PAGE (Keyboard) (Key Codes)
+	0x19, 0xe0,		//   USAGE_MINIMUM (Keyboard LeftControl)(224)
+	0x29, 0xe7,		//   USAGE_MAXIMUM (Keyboard Right GUI)(231)
+	0x15, 0x00,		//   LOGICAL_MINIMUM (0)
+	0x25, 0x01,		//   LOGICAL_MAXIMUM (1)
+	0x75, 0x01,		//   REPORT_SIZE (1)
+	0x95, 0x08,		//   REPORT_COUNT (8)
+	0x81, 0x02,		//   INPUT (Data,Var,Abs) ; Modifier byte
+	0x95, 0x01,		//   REPORT_COUNT (1)
+	0x75, 0x08,		//   REPORT_SIZE (8)
+	0x81, 0x01,		//   INPUT (Cnst,Arr,Abs) ; Reserved byte
+	0x95, 0x03,		//   REPORT_COUNT (3)
+	0x75, 0x01,		//   REPORT_SIZE (1)
+	0x05, 0x08,		//   USAGE_PAGE (LEDs)
+	0x19, 0x01,		//   USAGE_MINIMUM (Num Lock)
+	0x29, 0x03,		//   USAGE_MAXIMUM (Scroll Lock)
+	0x91, 0x02,		//   OUTPUT (Data,Var,Abs) ; LED report
+	0x95, 0x05,		//   REPORT_COUNT (5)
+	0x75, 0x01,		//   REPORT_SIZE (1)
+	0x91, 0x01,		//   OUTPUT (Cnst,Arr,Abs)
+	0x95, 0x05,		//   REPORT_COUNT (5)
+	0x75, 0x08,		//   REPORT_SIZE (8)
+	0x15, 0x00,		//   LOGICAL_MINIMUM (0)
+	0x25, 0xff,		//   LOGICAL_MAXIMUM (255)
+	0x05, 0x07,		//   USAGE_PAGE (Keyboard)(Key Codes)
+	0x19, 0x00,		//   USAGE_MINIMUM (Reserved (no event))(0)
+	0x29, 0xff,		//   USAGE_MAXIMUM (Keyboard Application)(null)
+	0x81, 0x00,		//   INPUT (Data,Ary,Abs)
+	0xc0			// END_COLLECTION
+};
+
+PROGMEM const char usbHidMouseReportDescriptor[] = {
+	0x06, 0x00, 0xff,	// USAGE_PAGE (65280)
+	0x09, 0x01,		// USAGE (1)
+	0xa1, 0x01,		// COLLECTION (Application)
+	0x09, 0x01,		// USAGE (1)
+	0x15, 0x00,		//   LOGICAL_MINIMUM (0)
+	0x26, 0xff, 0x00,	//   LOGICAL_MAXIMUM (255)
+	0x75, 0x08,		//   REPORT_SIZE (8)
+	0x95, 0x08,		//   REPORT_COUNT (8)
+	0x81, 0x02,		//   INPUT (Data,Var,Abs) ; Modifier byte
+	0x09, 0x01,		// USAGE (Pointer)
+	0x95, 0x08,		//   REPORT_COUNT (8)
+	0x91, 0x02,		//   OUTPUT (Data,Var,Abs) ; Modifier byte
+	0x05, 0x0c,		// USAGE_PAGE (Consumer)
+	0x09, 0x00,		//   USAGE (0)
+	0x15, 0x80,		//   LOGICAL_MINIMUM (128)
+	0x25, 0x7f,		//   LOGICAL_MAXIMUM (127)
+	0x75, 0x08,		//   REPORT_SIZE (8)
+	0x95, 0x08,		//   REPORT_COUNT (8)
+	0xb1, 0x02,		//   FEATURE (Data,Var,Abs)
+	0xc0			// END_COLLECTION
+};
+
+/* USB configuration descriptor */
+PROGMEM const char usbDescriptorConfiguration[] = {
+	9,				/* sizeof(usbDescriptorConfiguration) */
+	USBDESCR_CONFIG,		/* descriptor type */
+	/* total length of data returned (including inlined descriptors) */
+	18 + 9 + 9 + 7 + 9 + 7, 0,
+	2,				/* number of interfaces in this cfg */
+	1,				/* index of this configuration */
+	0,				/* configuration name string index */
+	(1 << 7) | USBATTR_REMOTEWAKE,	/* attributes */
+	USB_CFG_MAX_BUS_POWER/2,	/* max USB current in 2mA units */
+
+	/* Keyboard interface descriptor follows inline: */
+
+	/* sizeof(usbDescrInterface): length of descriptor in bytes */
+	9,
+	USBDESCR_INTERFACE,	/* descriptor type */
+	0,			/* index of this interface */
+	0,			/* alternate setting for this interface */
+	/* endpoints excl 0: number of endpoint descriptors to follow */
+	1,
+	3,			/* Interface class: HID */
+	1,			/* SubClass: Boot Interface */
+	1,			/* Interface protocol: Keyboard */
+	0,			/* string index for interface */
+	9,			/* sizeof(usbDescrHID) */
+	USBDESCR_HID,		/* descriptor type: HID */
+	0x10, 0x01,		/* BCD representation of HID version */
+	0x00,			/* target country code */
+	/* number of HID Report (or other HID class) Descriptors to follow */
+	0x01,
+	0x22,			/* descriptor type: report */
+	sizeof(usbHidKeyboardReportDescriptor),
+	0,		/* total length of report descriptor */
+
+	/* endpoint descriptor for endpoint 1 */
+	7,				/* sizeof(usbDescrEndpoint) */
+	USBDESCR_ENDPOINT,		/* descriptor type = endpoint */
+	(char)0x81,			/* IN endpoint number 1 */
+	0x03,				/* attrib: Interrupt endpoint */
+	8, 0,				/* maximum packet size */
+	USB_CFG_INTR_POLL_INTERVAL,	/* in ms */
+
+	/* Mouse interface descriptor follows inline: */
+
+	/* sizeof(usbDescrInterface): length of descriptor in bytes */
+	9,
+	USBDESCR_INTERFACE,	/* descriptor type */
+	1,			/* index of this interface */
+	0,			/* alternate setting for this interface */
+	/* endpoints excl 0: number of endpoint descriptors to follow */
+	1,
+	3,			/* Interface class: HID */
+	1,			/* SubClass: Boot Interface */
+	2,			/* Interface protocol: Mouse */
+	0,			/* string index for interface */
+	9,			/* sizeof(usbDescrHID) */
+	USBDESCR_HID,		/* descriptor type: HID */
+	0x10, 0x01,		/* BCD representation of HID version */
+	0x00,			/* target country code */
+	/* number of HID Report (or other HID class) Descriptors to follow */
+	0x01,
+	0x22,			/* descriptor type: report */
+	/* total length of report descriptor */
+	sizeof(usbHidMouseReportDescriptor), 0,
+
+	/* endpoint descriptor for endpoint 2 */
+	7,				/* sizeof(usbDescrEndpoint) */
+	USBDESCR_ENDPOINT,		/* descriptor type = endpoint */
+	(char)0x82,			/* IN endpoint number 2 */
+	0x03,				/* attrib: Interrupt endpoint */
+	8, 0,				/* maximum packet size */
+	USB_CFG_INTR_POLL_INTERVAL,	/* in ms */
+};
+
+usbMsgLen_t usbFunctionDescriptor(usbRequest_t *rq)
+{
+	if (rq->wValue.bytes[1] == USBDESCR_STRING &&
+			rq->wValue.bytes[0] == 3) {
+		usbMsgPtr = (usbMsgPtr_t) serno_str;
+		return sizeof(serno_str);
+	} else if (rq->wValue.bytes[1] == USBDESCR_HID) {
+		if (rq->wIndex.word == USB_INDEX_MOUSE) {
+			/* "Mouse" */
+			usbMsgPtr = (usbMsgPtr_t)
+				&usbDescriptorConfiguration[43];
+		} else {
+			/* "Keyboard" */
+			usbMsgPtr = (usbMsgPtr_t)
+				&usbDescriptorConfiguration[18];
+		}
+	} else if (rq->wValue.bytes[1] == USBDESCR_HID_REPORT) {
+		if (rq->wIndex.word == USB_INDEX_MOUSE) {
+			usbMsgPtr = (usbMsgPtr_t)
+				&usbHidMouseReportDescriptor[0];
+			return sizeof(usbHidMouseReportDescriptor);
+		} else {
+			usbMsgPtr = (usbMsgPtr_t)
+				&usbHidKeyboardReportDescriptor[0];
+			return sizeof(usbHidKeyboardReportDescriptor);
+		}
+	}
+
+	return 0;
+}
+
+usbMsgLen_t usbFunctionSetup(uchar data[8])
+{
+	usbRequest_t *rq = (void *) data;
+
+	if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
+		if (rq->bRequest == USBRQ_HID_GET_REPORT) {
+			/* No keys pressed, no mouse event */
+			usbMsgPtr = (void *) &keyboard_report;
+			keyboard_report.modifier = 0;
+			keyboard_report.reserved = 0;
+			keyboard_report.keycode[0] = 0;
+			return sizeof(keyboard_report);
+		} else if (rq->bRequest == USBRQ_HID_SET_REPORT) {
+			if (rq->wIndex.word == USB_INDEX_MOUSE) {
+				return (rq->wLength.word == 8) ?
+						USB_NO_MSG : 0;
+			} else {
+				/* Keyboard */
+				return (rq->wLength.word == 1) ?
+						USB_NO_MSG : 0;
+			}
+		} else if (rq->bRequest == USBRQ_HID_GET_IDLE) {
+			usbMsgPtr = &idlerate;
+			return 1;
+		} else if (rq->bRequest == USBRQ_HID_SET_IDLE) {
+			idlerate = rq->wValue.bytes[1];
+		}
+	}
+
+	return 0;
+}
+
+usbMsgLen_t usbFunctionWrite(uint8_t * data, uchar len)
+{
+	if (len == 1) {
+		if (data[0] != ledstate) {
+			ledstate = data[0];
+
+			if (ledstate & CAPS_LOCK) {
+				PORTB |= 1 << PB1; // LED on
+			} else {
+				PORTB &= ~(1 << PB1); // LED off
+			}
+		}
+		return 1;
+	} else if (len == 8) {
+		/* Silently consume if unexpected*/
+		if (data[0] != 1)
+			return 8;
+		if ((data[4] | data[5] | data[6] | data[7]) != 0)
+			return 8;
+
+		if (data[1] == 0x80 && data[2] == 0x33 && data[3] == 1) {
+			/* Temperature query */
+			have_temp_int = true;
+			temp_report[0] = 0x80;
+			temp_report[1] = 2;
+			/* Fake 15°C */
+			temp_report[2] = 0xF;
+			temp_report[3] = 0x0;
+
+		} else if (data[1] == 0x82 && data[2] == 0x77 &&
+				data[3] == 1) {
+			/* Initialisation Query #1 */
+			have_temp_int = true;
+			temp_report[0] = 0x82;
+			temp_report[1] = 1;
+			temp_report[2] = 0;
+		} else if (data[1] == 0x86 && data[2] == 0xff &&
+				data[3] == 1) {
+			/* Initialisation Query #2 */
+			have_temp_int = true;
+			memcpy(temp_report, "TEMPerF1", 8);
+		}
+
+		return 8;
+	}
+
+	return 0;
+}
+
+int main(void)
+{
+	unsigned char i;
+
+	wdt_enable(WDTO_1S);
+
+	usbInit();
+	usbDeviceDisconnect();
+
+	i = 0;
+	while (--i) {
+		wdt_reset();
+		_delay_ms(1);
+	}
+
+	usbDeviceConnect();
+
+	/* PB1 as output for LED */
+	DDRB |= 1 << PB1;
+
+	sei(); /* We're ready to go; enable interrupts */
+
+	keyboard_report.modifier = 0;
+	keyboard_report.reserved = 0;
+	keyboard_report.keycode[0] = 0;
+
+	while (1) {
+		wdt_reset();
+		usbPoll();
+
+		if (have_temp_int && usbInterruptIsReady3()) {
+			usbSetInterrupt3((void *) temp_report,
+					sizeof(temp_report));
+			if (temp_report[0] == 'T') {
+				/* Hack up second response for 0x86 query */
+				memcpy(temp_report, ".2", 2);
+			} else {
+				have_temp_int = false;
+			}
+		}
+	}
+}
diff --git a/usbconfig.h b/usbconfig.h
index 207af65..d6166f4 100644
--- a/usbconfig.h
+++ b/usbconfig.h
@@ -76,18 +76,18 @@ section at the end of this file).
 
 /* --------------------------- Functional Range ---------------------------- */
 
-#define USB_CFG_HAVE_INTRIN_ENDPOINT    0
+#define USB_CFG_HAVE_INTRIN_ENDPOINT    1
 /* Define this to 1 if you want to compile a version with two endpoints: The
  * default control endpoint 0 and an interrupt-in endpoint (any other endpoint
  * number).
  */
-#define USB_CFG_HAVE_INTRIN_ENDPOINT3   0
+#define USB_CFG_HAVE_INTRIN_ENDPOINT3   1
 /* Define this to 1 if you want to compile a version with three endpoints: The
  * default control endpoint 0, an interrupt-in endpoint 3 (or the number
  * configured below) and a catch-all default interrupt-in endpoint as above.
  * You must also define USB_CFG_HAVE_INTRIN_ENDPOINT to 1 for this feature.
  */
-#define USB_CFG_EP3_NUMBER              3
+#define USB_CFG_EP3_NUMBER              2
 /* If the so-called endpoint 3 is used, it can now be configured to any other
  * endpoint number (except 0) with this macro. Default if undefined is 3.
  */
@@ -111,7 +111,7 @@ section at the end of this file).
  * (e.g. HID), but never want to send any data. This option saves a couple
  * of bytes in flash memory and the transmit buffers in RAM.
  */
-#define USB_CFG_INTR_POLL_INTERVAL      10
+#define USB_CFG_INTR_POLL_INTERVAL      20
 /* If you compile a version with endpoint 1 (interrupt-in), this is the poll
  * interval. The value is in milliseconds and must not be less than 10 ms for
  * low speed devices.
@@ -125,7 +125,7 @@ section at the end of this file).
  * The value is in milliamperes. [It will be divided by two since USB
  * communicates power requirements in units of 2 mA.]
  */
-#define USB_CFG_IMPLEMENT_FN_WRITE      0
+#define USB_CFG_IMPLEMENT_FN_WRITE      1
 /* Set this to 1 if you want usbFunctionWrite() to be called for control-out
  * transfers. Set it to 0 if you don't need it and want to save a couple of
  * bytes.
@@ -209,7 +209,7 @@ section at the end of this file).
 /* define this macro to 1 if you want the function usbMeasureFrameLength()
  * compiled in. This function can be used to calibrate the AVR's RC oscillator.
  */
-#define USB_USE_FAST_CRC                0
+#define USB_USE_FAST_CRC                1
 /* The assembler module has two implementations for the CRC algorithm. One is
  * faster, the other is smaller. This CRC routine is only used for transmitted
  * messages where timing is not critical. The faster routine needs 31 cycles
@@ -220,7 +220,7 @@ section at the end of this file).
 
 /* -------------------------- Device Description --------------------------- */
 
-#define  USB_CFG_VENDOR_ID       0xc0, 0x16 /* = 0x16c0 = 5824 = voti.nl */
+#define  USB_CFG_VENDOR_ID       0x45, 0x0c
 /* USB vendor ID for the device, low byte first. If you have registered your
  * own Vendor ID, define it here. Otherwise you may use one of obdev's free
  * shared VID/PID pairs. Be sure to read USB-IDs-for-free.txt for rules!
@@ -229,7 +229,7 @@ section at the end of this file).
  * with libusb: 0x16c0/0x5dc.  Use this VID/PID pair ONLY if you understand
  * the implications!
  */
-#define  USB_CFG_DEVICE_ID       0xdc, 0x05 /* = 0x05dc = 1500 */
+#define  USB_CFG_DEVICE_ID       0x01, 0x74
 /* This is the ID of the product, low byte first. It is interpreted in the
  * scope of the vendor ID. If you have registered your own VID with usb.org
  * or if you have licensed a PID from somebody else, define it here. Otherwise
@@ -243,7 +243,7 @@ section at the end of this file).
 #define USB_CFG_DEVICE_VERSION  0x00, 0x01
 /* Version number of the device: Minor number first, then major number.
  */
-#define USB_CFG_VENDOR_NAME     'o', 'b', 'd', 'e', 'v', '.', 'a', 't'
+#define USB_CFG_VENDOR_NAME     'e', 'a', 'r', 't', 'h', '.', 'l', 'i'
 #define USB_CFG_VENDOR_NAME_LEN 8
 /* These two values define the vendor name returned by the USB device. The name
  * must be given as a list of characters under single quotes. The characters
@@ -253,8 +253,8 @@ section at the end of this file).
  * obdev's free shared VID/PID pair. See the file USB-IDs-for-free.txt for
  * details.
  */
-#define USB_CFG_DEVICE_NAME     'T', 'e', 'm', 'p', 'l', 'a', 't', 'e'
-#define USB_CFG_DEVICE_NAME_LEN 8
+#define USB_CFG_DEVICE_NAME     'T', 'E', 'M', 'P', 'e', 'r', 'V', '1', '.', '2'
+#define USB_CFG_DEVICE_NAME_LEN 10
 /* Same as above for the device name. If you don't want a device name, undefine
  * the macros. See the file USB-IDs-for-free.txt before you assign a name if
  * you use a shared VID/PID.
@@ -268,12 +268,12 @@ section at the end of this file).
  * to fine tune control over USB descriptors such as the string descriptor
  * for the serial number.
  */
-#define USB_CFG_DEVICE_CLASS        0xff    /* set to 0 if deferred to interface */
+#define USB_CFG_DEVICE_CLASS        0
 #define USB_CFG_DEVICE_SUBCLASS     0
 /* See USB specification if you want to conform to an existing device class.
  * Class 0xff is "vendor specific".
  */
-#define USB_CFG_INTERFACE_CLASS     0   /* define class here if not at device level */
+#define USB_CFG_INTERFACE_CLASS     0 /* At device level */
 #define USB_CFG_INTERFACE_SUBCLASS  0
 #define USB_CFG_INTERFACE_PROTOCOL  0
 /* See USB specification if you want to conform to an existing device class or
@@ -346,18 +346,16 @@ section at the end of this file).
  * };
  */
 
-#define CONFIG_DESCRIPTOR_SIZE 41
-
 #define USB_CFG_DESCR_PROPS_DEVICE                  0
-#define USB_CFG_DESCR_PROPS_CONFIGURATION           0
+#define USB_CFG_DESCR_PROPS_CONFIGURATION           (9 + 2 * (9 + 9 + 7))
 #define USB_CFG_DESCR_PROPS_STRINGS                 0
 #define USB_CFG_DESCR_PROPS_STRING_0                0
 #define USB_CFG_DESCR_PROPS_STRING_VENDOR           0
 #define USB_CFG_DESCR_PROPS_STRING_PRODUCT          0
-#define USB_CFG_DESCR_PROPS_STRING_SERIAL_NUMBER    0
+#define USB_CFG_DESCR_PROPS_STRING_SERIAL_NUMBER    (USB_PROP_IS_DYNAMIC | USB_PROP_IS_RAM)
 #define USB_CFG_DESCR_PROPS_HID                     0
 #define USB_CFG_DESCR_PROPS_HID_REPORT              0
-#define USB_CFG_DESCR_PROPS_UNKNOWN                 0
+#define USB_CFG_DESCR_PROPS_UNKNOWN                 0xFFFF
 
 
 //#define usbMsgPtr_t unsigned short