From d3c84350354934d5e8e7cf2c873568392e83997d Mon Sep 17 00:00:00 2001 From: William Toohey Date: Sat, 17 Jun 2017 01:51:50 +1000 Subject: [PATCH] eAmuse Cloud support --- Firmware/PocketVoltex/Config.c | 31 +++++++------ Firmware/PocketVoltex/Config.h | 21 ++++++--- .../PocketVoltex/DFU/control_bootloader.py | 6 +-- Firmware/PocketVoltex/Descriptors.c | 33 ++++++++++++-- Firmware/PocketVoltex/PocketVoltex.c | 28 ++++++------ Software/WebConfig/css/style.css | 2 +- Software/WebConfig/firmwares/latest.json | 5 ++- Software/WebConfig/index.html | 20 +++++++-- Software/WebConfig/js/config.js | 43 +++++++++++++------ Software/WebConfig/js/settings.js | 20 +++++++++ Software/WebConfig/js/usbWrapper.js | 14 ++++-- 11 files changed, 161 insertions(+), 62 deletions(-) diff --git a/Firmware/PocketVoltex/Config.c b/Firmware/PocketVoltex/Config.c index a0fd12b..bf01eaf 100644 --- a/Firmware/PocketVoltex/Config.c +++ b/Firmware/PocketVoltex/Config.c @@ -4,21 +4,22 @@ #include "Config.h" #include "Macro.h" -uint8_t firstRun EEMEM; // init to 255 sdvx_config_t eeConfig EEMEM; sdvx_config_t sdvxConfig; static const PROGMEM sdvx_config_t defaults = { + .configVersion = CONFIG_VERSION, .switches = { + HID_KEYBOARD_SC_ENTER, HID_KEYBOARD_SC_Z, HID_KEYBOARD_SC_X, HID_KEYBOARD_SC_DOT_AND_GREATER_THAN_SIGN, HID_KEYBOARD_SC_SLASH_AND_QUESTION_MARK, HID_KEYBOARD_SC_C, HID_KEYBOARD_SC_M, - HID_KEYBOARD_SC_ENTER, HID_KEYBOARD_SC_KEYPAD_PLUS}, // macro + .controlMode = CONTROL_JOYSTICK, .btColour = {BRIGHTNESS_MAX, BRIGHTNESS_MAX, BRIGHTNESS_MAX}, // white .fxColour = {BRIGHTNESS_MAX, BRIGHTNESS_MAX/4, 0}, // orange .breatheColour = {BRIGHTNESS_MAX/2, BRIGHTNESS_MAX/8, 0}, // dim orange @@ -39,30 +40,34 @@ static const PROGMEM sdvx_config_t defaults = { .action = PIN_ENTRY, .keypress = 0 }, + .macroLen = 4, .macroPin = {HID_KEYBOARD_SC_KEYPAD_0_AND_INSERT, HID_KEYBOARD_SC_KEYPAD_0_AND_INSERT, HID_KEYBOARD_SC_KEYPAD_0_AND_INSERT, HID_KEYBOARD_SC_KEYPAD_0_AND_INSERT}, - .joystickMode = 1, }; void InitConfig(void) { - if (eeprom_read_byte(&firstRun) != CONFIG_VERSION) { // store defaults - memcpy_P(&sdvxConfig, &defaults, sizeof(sdvx_config_t)); - eeprom_update_block(&sdvxConfig, &eeConfig, sizeof(sdvx_config_t)); - eeprom_update_byte(&firstRun, CONFIG_VERSION); // defaults set + // TODO: if config version changes, migrate settings + switch(eeprom_read_word(&eeConfig.configVersion)) { + case CONFIG_VERSION: // nothing needs to change + break; + default: + memcpy_P(&sdvxConfig, &defaults, CONFIG_SIZE); + eeprom_update_block(&sdvxConfig, &eeConfig, CONFIG_SIZE); + break; } - eeprom_read_block(&sdvxConfig, &eeConfig, sizeof(sdvx_config_t)); + eeprom_read_block(&sdvxConfig, &eeConfig, CONFIG_SIZE); } void SetConfig(sdvx_config_t* config) { - memcpy(&sdvxConfig, config, sizeof(sdvx_config_t)); - - eeprom_update_block(&sdvxConfig, &eeConfig, sizeof(sdvx_config_t)); + memcpy(&sdvxConfig, config, CONFIG_SIZE); + sdvxConfig.configVersion = CONFIG_VERSION; + eeprom_update_block(&sdvxConfig, &eeConfig, CONFIG_SIZE); } void UpdateConfig(void) { - eeprom_update_block(&sdvxConfig, &eeConfig, sizeof(sdvx_config_t)); + eeprom_update_block(&sdvxConfig, &eeConfig, CONFIG_SIZE); } command_response_t HandleConfig(uint8_t* buffer) { @@ -73,7 +78,7 @@ command_response_t HandleConfig(uint8_t* buffer) { command->data.version.serial = 0xDEAD; // TODO set properly later return RESPOND; case GETCONFIG: - memcpy(&command->data.config, &sdvxConfig, CONFIG_BYTES); + memcpy(&command->data.config, &sdvxConfig, CONFIG_SIZE); return RESPOND; case SETCONFIG: SetConfig(&command->data.config); diff --git a/Firmware/PocketVoltex/Config.h b/Firmware/PocketVoltex/Config.h index 0bafbfd..f460167 100644 --- a/Firmware/PocketVoltex/Config.h +++ b/Firmware/PocketVoltex/Config.h @@ -12,16 +12,25 @@ #define JOYSTICK_PPR (24 * 4) #define MAGIC_RESET_NUMBER 42 // divide by 10 for actual version -#define FIRMWARE_VERSION 12 +#define FIRMWARE_VERSION 14 // increment whenever config structure has breaking changes -#define CONFIG_VERSION 1 +#define CONFIG_VERSION 2 // not configurable since they're all the same switches #define SWITCH_DEBOUNCE 30 +typedef enum { + CONTROL_KEYBOARD_MOUSE = 0, + CONTROL_JOYSTICK = 1, + CONTROL_EAC = 2 +} control_mode_t; + typedef struct { - // SWITCH ORDER: A-D, FXL-R, START + // So we can detect and upgrade without losing settings + uint16_t configVersion; + // SWITCH ORDER: START, A-D, FXL-R uint8_t switches[SWITCH_COUNT]; + uint8_t controlMode; RGB_t btColour; RGB_t fxColour; RGB_t breatheColour; @@ -34,12 +43,12 @@ typedef struct { // When tapping or long-pressing the macro key Macro_t macroClick; Macro_t macroHold; - // Reused for both + // Reused for both tap or hold, at the end in case I decide to extend + uint8_t macroLen; // placeholder not used yet uint8_t macroPin[4]; - uint8_t joystickMode; } ATTR_PACKED sdvx_config_t; -#define CONFIG_BYTES sizeof(sdvx_config_t) +#define CONFIG_SIZE sizeof(sdvx_config_t) typedef struct { uint16_t version; diff --git a/Firmware/PocketVoltex/DFU/control_bootloader.py b/Firmware/PocketVoltex/DFU/control_bootloader.py index 68b5881..6bf8281 100644 --- a/Firmware/PocketVoltex/DFU/control_bootloader.py +++ b/Firmware/PocketVoltex/DFU/control_bootloader.py @@ -36,7 +36,7 @@ COMMAND_VERSION = 0x03 COMMAND_STARTAPPLICATION = 0x04 COMMAND_PROGRAM = 0x05 -BOOTLOADER_VERSION = 65000 +BOOTLOADER_VERSION = 11 # Device information table device_info_map = dict() @@ -86,10 +86,6 @@ def program_device(hex_data, device_info): print("No valid device found.") sys.exit(1) - if get_version(device) != BOOTLOADER_VERSION: - print("Device not in bootloader. TODO: reboot") - sys.exit(1) - try: print("Connected to bootloader.") diff --git a/Firmware/PocketVoltex/Descriptors.c b/Firmware/PocketVoltex/Descriptors.c index c777dd7..a79bf4e 100644 --- a/Firmware/PocketVoltex/Descriptors.c +++ b/Firmware/PocketVoltex/Descriptors.c @@ -182,7 +182,7 @@ const uint8_t PROGMEM BOSDescriptor[] = ) }; -const USB_Descriptor_Device_t PROGMEM DeviceDescriptor = +const USB_Descriptor_Device_t PROGMEM DeviceDescriptor_Standard = { .Header = {.Size = sizeof(USB_Descriptor_Device_t), .Type = DTYPE_Device}, @@ -205,6 +205,29 @@ const USB_Descriptor_Device_t PROGMEM DeviceDescriptor = .NumberOfConfigurations = FIXED_NUM_CONFIGURATIONS }; +const USB_Descriptor_Device_t PROGMEM DeviceDescriptor_EAC = +{ + .Header = {.Size = sizeof(USB_Descriptor_Device_t), .Type = DTYPE_Device}, + + .USBSpecification = VERSION_BCD(2,1,0), + .Class = USB_CSCP_NoDeviceClass, + .SubClass = USB_CSCP_NoDeviceSubclass, + .Protocol = USB_CSCP_NoDeviceProtocol, + + .Endpoint0Size = FIXED_CONTROL_ENDPOINT_SIZE, + // to pretend we are someone else + .VendorID = 0x1CCF, + .ProductID = 0x1014, + // To provide uniqueness + .ReleaseNumber = VERSION_BCD(0,0,3), + + .ManufacturerStrIndex = STRING_ID_Manufacturer, + .ProductStrIndex = STRING_ID_Product, + .SerialNumStrIndex = USE_INTERNAL_SERIAL, + + .NumberOfConfigurations = FIXED_NUM_CONFIGURATIONS +}; + const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = { .Config = @@ -433,8 +456,12 @@ uint16_t CALLBACK_USB_GetDescriptor(const uint16_t wValue, switch (DescriptorType) { case DTYPE_Device: - Address = &DeviceDescriptor; - Size = sizeof(USB_Descriptor_Device_t); + if(sdvxConfig.controlMode == CONTROL_EAC) { + Address = &DeviceDescriptor_EAC; + } else { + Address = &DeviceDescriptor_Standard; + } + Size = sizeof(USB_Descriptor_Device_t); break; case DTYPE_Configuration: Address = &ConfigurationDescriptor; diff --git a/Firmware/PocketVoltex/PocketVoltex.c b/Firmware/PocketVoltex/PocketVoltex.c index b7ffed8..2a615e4 100644 --- a/Firmware/PocketVoltex/PocketVoltex.c +++ b/Firmware/PocketVoltex/PocketVoltex.c @@ -105,16 +105,16 @@ uint8_t load_switches(void) { uint8_t result = 0; tmp = PINB; - LOAD_SWITCH(tmp, 1, result, 0); // PINB1, A - LOAD_SWITCH(tmp, 0, result, 4); // PINB0, FX L + LOAD_SWITCH(tmp, 1, result, 1); // PINB1, A + LOAD_SWITCH(tmp, 0, result, 5); // PINB0, FX L tmp = PINC; - LOAD_SWITCH(tmp, 2, result, 6); // PINC2, START + LOAD_SWITCH(tmp, 2, result, 0); // PINC2, START LOAD_SWITCH(tmp, 1, result, 7); // PINC1, MACRO tmp = PIND; - LOAD_SWITCH(tmp, 7, result, 1); // PIND7, B - LOAD_SWITCH(tmp, 5, result, 2); // PIND5, C - LOAD_SWITCH(tmp, 4, result, 3); // PIND4, D - LOAD_SWITCH(tmp, 6, result, 5); // PIND6, FX L + LOAD_SWITCH(tmp, 7, result, 2); // PIND7, B + LOAD_SWITCH(tmp, 5, result, 3); // PIND5, C + LOAD_SWITCH(tmp, 4, result, 4); // PIND4, D + LOAD_SWITCH(tmp, 6, result, 6); // PIND6, FX R return result; } @@ -180,15 +180,15 @@ int main(void) if(sdvxConfig.keyLights) { // Keep normal lights but override when we get flashes on BT or FX // BT LEDs - for(uint8_t i = 0; i < 4; i++) { + for(uint8_t i = 1; i < 5; i++) { if(switches[i].state) { - led_set_rgb(ledMap[i], &sdvxConfig.btColour); + led_set_rgb(ledMap[i-1], &sdvxConfig.btColour); } } // FX LEDs - for(uint8_t i = 4; i < 6; i++) { + for(uint8_t i = 5; i < 7; i++) { if(switches[i].state) { - led_set_rgb(ledMap[i], &sdvxConfig.fxColour); + led_set_rgb(ledMap[i-1], &sdvxConfig.fxColour); } } } @@ -263,7 +263,7 @@ bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDIn if(sendKeyboard) { *ReportID = HID_REPORTID_KeyboardReport; } else { - if(sdvxConfig.joystickMode) { + if(sdvxConfig.controlMode != CONTROL_KEYBOARD_MOUSE) { *ReportID = HID_REPORTID_JoystickReport; } else { *ReportID = HID_REPORTID_MouseReport; @@ -277,12 +277,12 @@ bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDIn // Only the first 4 bytes are read by bemanitools, so let's use the first uint8_t macroUpdated = macro_make_report(&KeyboardReport->KeyCode[0]); - if(!macroUpdated && (sdvxConfig.joystickMode || !switchesChanged)) { + if(!macroUpdated && (sdvxConfig.controlMode != CONTROL_KEYBOARD_MOUSE || !switchesChanged)) { *ReportSize = 0; return false; } - if(!sdvxConfig.joystickMode) { + if(sdvxConfig.controlMode == CONTROL_KEYBOARD_MOUSE) { // NOTE: using SWITCH_COUNT-1 so we don't write the report for the macro pin for(uint8_t i = 0; i < SWITCH_COUNT-1; i++) { // i+1 to take care of the shift from above diff --git a/Software/WebConfig/css/style.css b/Software/WebConfig/css/style.css index 5b0ffb4..b157619 100644 --- a/Software/WebConfig/css/style.css +++ b/Software/WebConfig/css/style.css @@ -27,7 +27,7 @@ body, html { } .connect { - height: 100%; + margin-top: 30px; display: flex; align-items: center; justify-content: center; diff --git a/Software/WebConfig/firmwares/latest.json b/Software/WebConfig/firmwares/latest.json index a3685df..b4c98e9 100644 --- a/Software/WebConfig/firmwares/latest.json +++ b/Software/WebConfig/firmwares/latest.json @@ -1,4 +1,5 @@ { - "version": 12, - "md5": "6620eb96cde1ecdcc0c5caefac7f9d03" + "version": 14, + "md5": "a07ab17b2ac21c9e0f165eac30f40e99", + "changelog" : "Add support for e-AMUSEMENT CLOUD native input" } \ No newline at end of file diff --git a/Software/WebConfig/index.html b/Software/WebConfig/index.html index be56927..03d8f56 100644 --- a/Software/WebConfig/index.html +++ b/Software/WebConfig/index.html @@ -17,11 +17,25 @@ document.getElementById("connectBtn").classList.remove("hidden"); config = new Config(); + + navigator.usb.getDevices() + .then(devices => { + if(devices.length) { + config.connect(devices[0]); + } + }); + + navigator.usb.addEventListener('connect', event => { + config.connect(event.device); + }); + navigator.usb.addEventListener('disconnect', event => { + config.disableUI(); + }); } }); - - + +
@@ -33,7 +47,7 @@
- +
diff --git a/Software/WebConfig/js/config.js b/Software/WebConfig/js/config.js index 547a0fa..bd0f609 100644 --- a/Software/WebConfig/js/config.js +++ b/Software/WebConfig/js/config.js @@ -1,8 +1,9 @@ (function(window, document) { "use strict"; -var vid = 0x16D0; -var pid = 0x0A6D; +// could be monim, could be pretending +var vids = [0x16D0, 0x1CCF]; +var pids = [0x0A6D, 0x1014]; var UsbWrapper = window.UsbWrapper; var device; @@ -21,9 +22,11 @@ var commands = { }; var configDisplay = [ - {id: 'joystickMode', name: 'Input mode', - options : [{name: 'Keyboard/Mouse', val: 0}, {name: 'Joystick', val: 1}]}, - {id: 'switches', name: 'Keyboard bindings', labels: ['BT-A','BT-B','BT-C','BT-D','FX-L','FX-R','START']}, + {id: 'controlMode', name: 'Input mode', + options : [{name: 'Keyboard/Mouse', val: 0}, + {name: 'Joystick', val: 1}, + {name: 'e-AMUSEMENT CLOUD (reconnect after change)', val: 2}]}, + {id: 'switches', name: 'Keyboard bindings', labels: ['START','BT-A','BT-B','BT-C','BT-D','FX-L','FX-R']}, {id: 'macroClick', name: 'Macro click'}, {id: 'macroHold', name: 'Macro longpress'}, {id: 'macroPin', name: 'Macro PIN'}, @@ -48,7 +51,9 @@ var configDisplay = [ // to match with firmware var settingOrder = [ + 'version', 'switches', + 'controlMode', 'btColour', 'fxColour', 'breatheColour', @@ -61,8 +66,8 @@ var settingOrder = [ 'lightPattern', 'macroClick', 'macroHold', + 'macroLen', 'macroPin', - 'joystickMode' ]; var defaultConfig = { @@ -117,7 +122,9 @@ class BinaryBuffer { class ConfigValues { constructor() { + this.version = new SettingPlaceholder(2); this.switches = new SettingKeys(SWITCH_COUNT, true); + this.controlMode = new SettingRadio(); this.btColour = new SettingRGB(BRIGHTNESS_MAX); this.fxColour = new SettingRGB(BRIGHTNESS_MAX); this.breatheColour = new SettingRGB(BRIGHTNESS_MAX); @@ -130,8 +137,8 @@ class ConfigValues { this.lightPattern = new SettingRadio(); this.macroClick = new SettingMacro(); this.macroHold = new SettingMacro(); + this.macroLen = new SettingPlaceholder(1); this.macroPin = new SettingKeys(4); - this.joystickMode = new SettingRadio(); } read(view, writeCallback) { @@ -189,16 +196,21 @@ class Config { //this.enableUI(); } - connect() { - return UsbWrapper.connect(vid, pid) + connectNew() { + return UsbWrapper.connect(vids, pids) .then(selectedDevice => { if(!selectedDevice) { return Promise.reject('No device selected'); } - device = selectedDevice; - visibleLog("Opening device..."); - return device.open(); + this.connect(selectedDevice); }) + } + + connect(selectedDevice) { + console.log(selectedDevice); + device = selectedDevice; + visibleLog("Opening device..."); + return device.open() .then(() => { console.log("Opened, selecting configuration..."); return device.selectConfiguration(1) @@ -248,6 +260,7 @@ class Config { .then(latestInfo => { if(latestInfo.version > this.version) { alert("Firmware update required. Device will be rebooted. Connect again to update"); + visibleLog("Firmware update required. Connect again to update"); this.rebootToBootloader(); return Promise.reject("Rebooted to bootloader, config read aborted"); } @@ -334,6 +347,12 @@ class Config { document.getElementById('connect').classList.add('hidden'); this.populateSettings(this.optionsDiv, configDisplay); } + + disableUI() { + visibleLog('Device disconnected'); + document.getElementById('connect').classList.remove('hidden'); + this.optionsDiv.innerHTML = ''; + } }; window.Config = Config; diff --git a/Software/WebConfig/js/settings.js b/Software/WebConfig/js/settings.js index d8aa581..6979ed3 100644 --- a/Software/WebConfig/js/settings.js +++ b/Software/WebConfig/js/settings.js @@ -14,6 +14,25 @@ class Setting { // All settings must have createUI, read and write. // When the UI changes, call fireCallback to update the device config +class SettingPlaceholder extends Setting { + constructor(bytes) { + super(); + this.bytes = bytes; + } + + read(buffer) { + this.val = buffer.read(this.bytes); + } + + write(buffer) { + buffer.write(this.val); + } + + createUI(setting) { + return null; + } +} + class SettingBool extends Setting { read(buffer) { this.box.checked = !!buffer.read(1); @@ -341,6 +360,7 @@ var scancodes = [ {name: "NUM .", value: 0x63} ]; +window.SettingPlaceholder = SettingPlaceholder; window.SettingBool = SettingBool; window.SettingRGB = SettingRGB; window.SettingRadio = SettingRadio; diff --git a/Software/WebConfig/js/usbWrapper.js b/Software/WebConfig/js/usbWrapper.js index 932909b..03bfde5 100644 --- a/Software/WebConfig/js/usbWrapper.js +++ b/Software/WebConfig/js/usbWrapper.js @@ -7,15 +7,23 @@ var UsbWrapper = new function() { this.hasUSB = navigator && navigator.usb; + if(!this.hasUSB) + return; + // returns a promise - this.connect = (vid, pid) => { - return navigator.usb.requestDevice({ filters: [ - { vendorId: vid, productId: pid }] }) + this.connect = (vids, pids) => { + var filters = []; + for(let i = 0; i < vids.length; i++) { + filters.push({ vendorId: vids[i], productId: pids[i] }); + } + return navigator.usb.requestDevice({filters: filters}) .catch(error => { console.log(error); return null; }); }; + + this.usb = navigator.usb; }; window.UsbWrapper = UsbWrapper;