Better macros, better config, initial updater

This commit is contained in:
William Toohey
2017-05-22 01:12:52 +10:00
parent 6a050a5588
commit 3caa27d33b
9 changed files with 280 additions and 103 deletions
+14 -3
View File
@@ -33,11 +33,18 @@ static const PROGMEM sdvx_config_t defaults = {
.keyLights = 1,
.knobLights = 1,
.lightPattern = FOLLOWER,
.macroClick = HID_KEYBOARD_SC_KEYPAD_PLUS,
.macroClick = {
.action = KEYPRESS,
.keypress = HID_KEYBOARD_SC_KEYPAD_PLUS
},
.macroHold = {
.action = PIN_ENTRY,
.keypress = 0
},
.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}
HID_KEYBOARD_SC_KEYPAD_0_AND_INSERT},
.joystickMode = 1,
};
@@ -53,7 +60,11 @@ void InitConfig(void) {
void SetConfig(sdvx_config_t* config) {
memcpy(&sdvxConfig, config, sizeof(sdvx_config_t));
eeprom_write_block(&sdvxConfig, &eeConfig, sizeof(sdvx_config_t));
eeprom_update_block(&sdvxConfig, &eeConfig, sizeof(sdvx_config_t));
}
void UpdateConfig(void) {
eeprom_update_block(&sdvxConfig, &eeConfig, sizeof(sdvx_config_t));
}
command_response_t HandleConfig(uint8_t* buffer) {
+6 -3
View File
@@ -5,13 +5,14 @@
#include <stdbool.h>
#include <LUFA/Drivers/USB/USB.h>
#include "LEDPatterns.h"
#include "Macro.h"
// 7 gameplay switches + macro combo switch
#define SWITCH_COUNT 8
#define JOYSTICK_PPR (24 * 4)
#define MAGIC_RESET_NUMBER 42
// divide by 10 for actual version
#define FIRMWARE_VERSION 10
#define FIRMWARE_VERSION 11
// not configurable since they're all the same switches
#define SWITCH_DEBOUNCE 30
@@ -29,8 +30,9 @@ typedef struct {
uint8_t knobLights;
uint8_t lightPattern;
// When tapping or long-pressing the macro key
uint8_t macroClick;
uint8_t macroHold;
Macro_t macroClick;
Macro_t macroHold;
// Reused for both
uint8_t macroPin[4];
uint8_t joystickMode;
} ATTR_PACKED sdvx_config_t;
@@ -69,6 +71,7 @@ extern sdvx_config_t sdvxConfig;
extern void InitConfig(void);
extern void SetConfig(sdvx_config_t* config);
void UpdateConfig(void);
extern command_response_t HandleConfig(uint8_t* buffer);
#endif
+27 -15
View File
@@ -1,9 +1,12 @@
#include <Macro.h>
#include "Macro.h"
#include "LEDPatterns.h"
// in ms
#define KEY_DELAY 30
#define LONGPRESS_DELAY 500
extern sdvx_config_t sdvxConfig;
static uint8_t countdown = 0;
static uint16_t longpress = 0;
static uint8_t *data;
@@ -20,7 +23,6 @@ uint8_t macro_make_report(uint8_t* key) {
liftoff = 0;
countdown = KEY_DELAY;
*key = 0;
return 1;
} else {
if(position >= dataLen) {
return 0;
@@ -31,20 +33,30 @@ uint8_t macro_make_report(uint8_t* key) {
position++;
liftoff = 1;
return 1;
}
return 1;
}
void macro_fire_click(void) {
data = &sdvxConfig.macroClick;
dataLen = 1;
position = 0;
}
void macro_fire_pin(void) {
data = sdvxConfig.macroPin;
dataLen = 4;
position = 0;
void macro_handle(Macro_t* macro) {
switch(macro->action) {
case KEYPRESS:
data = &macro->keypress;
dataLen = 1;
position = 0;
break;
case PIN_ENTRY:
data = sdvxConfig.macroPin;
dataLen = 4;
position = 0;
break;
case LED_PATTERN_CHANGE:
led_pattern_next();
break;
case LED_TOGGLE:
sdvxConfig.lightsOn ^= 1;
UpdateConfig();
break;
}
}
void macro_on_frame(switch_t* macro) {
@@ -59,11 +71,11 @@ void macro_on_frame(switch_t* macro) {
} else if(macro->state && macro->lastReport && longpress < LONGPRESS_DELAY) {
longpress++;
if(longpress >= LONGPRESS_DELAY) {
macro_fire_pin();
macro_handle(&sdvxConfig.macroHold);
}
// falling edge before longpress time
} else if(!macro->state && macro->lastReport && longpress < LONGPRESS_DELAY) {
macro_fire_click();
macro_handle(&sdvxConfig.macroClick);
}
macro->lastReport = macro->state;
}
+17 -5
View File
@@ -1,15 +1,27 @@
#ifndef MACRO_H_
#define MACRO_H_
#include "Config.h"
#include "asciihid.h"
#include "PocketVoltex.h"
extern sdvx_config_t sdvxConfig;
#define NUMPAD_START HID_KEYBOARD_SC_KEYPAD_1_AND_END
// because 0 is top instead of bottom
#define NUMPAD_0 HID_KEYBOARD_SC_KEYPAD_0_AND_INSERT
enum MacroAction {
KEYPRESS = 0,
PIN_ENTRY = 1,
LED_PATTERN_CHANGE = 2,
LED_TOGGLE = 3,
};
typedef struct {
uint8_t action;
// if set
uint8_t keypress;
} Macro_t;
uint8_t macro_make_report(uint8_t* key);
void macro_on_frame(switch_t* macro);
void macro_fire_click(void);
void macro_fire_pin(void);
void macro_handle(Macro_t* macro);
#endif
+2 -2
View File
@@ -1,5 +1,5 @@
#ifndef _KEYBOARD_H_
#define _KEYBOARD_H_
#ifndef _POCKETVOLTEX_H_
#define _POCKETVOLTEX_H_
/* Includes: */
#include <avr/io.h>
+11 -15
View File
@@ -1,8 +1,14 @@
body, html {
width: 100%;
height: 100%;
margin: 0;
}
.center {
position: relative;
display:flex;
align-items:center;
flex-direction: column;
justify-content: center;
height: 100%;
}
.configBox {
@@ -12,12 +18,9 @@
}
.connect {
position: absolute;
left: 0;
right: 0;
top: 285px;
display:flex;
justify-content:center;
height: 100%;
display: flex;
align-items: center;
}
.connect > button {
@@ -25,16 +28,9 @@
}
.configOptions {
margin-left: 180px;
display: flex;
flex-wrap: wrap;
flex-direction: column;
height: 400px;
width: 600px;
}
.configOptions > div, .configOptions > p {
width: 50%;
}
.colourPicker {
+1
View File
@@ -6,6 +6,7 @@
<link rel="stylesheet" href="css/style.css">
<script type="text/javascript" src="js/usbWrapper.js"></script>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript" src="js/updater.js"></script>
<script type="text/javascript" src="js/jscolor.min.js"></script>
<script type="text/javascript">
window.addEventListener("load", function() {
+111 -60
View File
@@ -21,24 +21,28 @@ var commands = {
};
var configDisplay = [
{id: 'joystickMode', name: 'Input mode', display : 'radio',
{id: 'joystickMode', name: 'Input mode',
options : [{name: 'Keyboard/Mouse', val: 0}, {name: 'Joystick', val: 1}]},
{id: 'macroClick', name: 'Macro click', display : 'key'},
{id: 'macroHold', name: 'Macro longpress', display : 'bool'},
{id: 'macroPin', name: 'Macro PIN', display : 'pin'},
{id: 'lightsOn', name: 'Enable LEDs', display : 'bool', children: [
{id: 'hidLights', name: 'HID Lights', display : 'bool'},
{id: 'keyLights', name: 'Key lights', display : 'bool', children: [
{id: 'btColour', name: 'BT colour', display: 'rgb'},
{id: 'fxColour', name: 'FX colour', display: 'rgb'},
{id: 'switches', name: 'Keyboard bindings'},
{id: 'macroClick', name: 'Macro click'},
{id: 'macroHold', name: 'Macro longpress'},
{id: 'macroPin', name: 'Macro PIN'},
{id: 'lightsOn', name: 'Enable LEDs', children: [
{id: 'hidLights', name: 'HID Lights'},
{id: 'keyLights', name: 'Key lights', children: [
{id: 'btColour', name: 'BT colour'},
{id: 'fxColour', name: 'FX colour'},
]},
{id: 'knobLights', name: 'Knob lights', display : 'bool', children: [
{id: 'knobL', name: 'VOL-L colour', display : 'rgb'},
{id: 'knobR', name: 'VOL-R colour', display : 'rgb'},
{id: 'knobLights', name: 'Knob lights', children: [
{id: 'knobL', name: 'VOL-L colour'},
{id: 'knobR', name: 'VOL-R colour'},
]},
{id: 'lightPattern', name: 'Lights pattern', display : 'radio',
options : [{name: 'Solid', val: 2}, {name: 'Follower', val: 3}, {name: 'Breathe', val: 4}]},
{id: 'breatheColour', name: 'Solid/Breathe colour', display : 'rgb'},
{id: 'lightPattern', name: 'Lights pattern',
options : [{name: 'None', val : 1},
{name: 'Solid', val: 2},
{name: 'Breathe', val: 4},
{name: 'Follower', val: 3}]},
{id: 'breatheColour', name: 'Solid/Breathe colour'},
]},
];
@@ -65,7 +69,7 @@ var defaultConfig = {
switches : [],
ledsOn : true,
//macroClick : KP_PLUS,
macroPin : [1,2,3,4],
macroPin : [0,0,0,0],
}
var visibleLog = function(html) {
@@ -123,12 +127,12 @@ class Setting {
// All settings must have createUI, read and write.
// When the UI changes, call fireCallback to update the device config
class SettingBool extends Setting {
read(reader) {
this.box.checked = !!reader.read(1);
read(buffer) {
this.box.checked = !!buffer.read(1);
}
write(reader) {
reader.write(this.box.checked ? 1 : 0);
write(buffer) {
buffer.write(this.box.checked ? 1 : 0);
}
createUI(setting) {
@@ -150,16 +154,16 @@ class SettingBool extends Setting {
}
class SettingRGB extends Setting {
read(reader) {
read(buffer) {
var rgb = [];
for(var i = 0; i < 3; i++)
rgb[i] = (reader.read(1) / BRIGHTNESS_MAX) * 255;
rgb[i] = (buffer.read(1) / BRIGHTNESS_MAX) * 255;
this.picker.fromRGB(rgb[0], rgb[1], rgb[2]);
}
write(reader) {
write(buffer) {
for(var i = 0; i < 3; i++)
reader.write((this.picker.rgb[i] / 255) * BRIGHTNESS_MAX);
buffer.write((this.picker.rgb[i] / 255) * BRIGHTNESS_MAX);
}
createUI(setting) {
@@ -237,32 +241,48 @@ class SettingRadio extends Setting {
}
class SettingKeys extends Setting {
constructor(switchCount) {
constructor(switchCount, ignoreLast = false) {
super();
this.switchCount = switchCount;
this.byteCount = switchCount;
// hacks upon hacks, this stuff
this.ignoreLast = ignoreLast;
this.switchCount = ignoreLast ? switchCount - 1 : switchCount;
}
read(reader) {
this.keys = reader.read(this.switchCount);
read(buffer) {
var keys = buffer.read(this.byteCount);
for(var i = 0; i < this.switchCount; i++) {
this.selects[i].value = keys[i];
}
}
write(reader) {
reader.write(this.keys);
write(buffer) {
for(var i = 0; i < this.selects.length; i++) {
buffer.write(this.selects[i].value);
}
if(this.ignoreLast) {
buffer.write(0);
}
}
createUI(setting) {
var entry = document.createElement('p');
entry.textContent = setting.name + ':';
var select = document.createElement("select");
scancodes.forEach(function(code) {
var option = document.createElement("option");
option.value = code.value;
option.textContent = code.name;
select.appendChild(option);
});
entry.appendChild(select);
this.selects = [];
for(var i = 0; i < this.switchCount; i++) {
var select = document.createElement("select");
scancodes.forEach(function(code) {
var option = document.createElement("option");
option.value = code.value;
option.textContent = code.name;
select.appendChild(option);
});
select.onchange = this.fireCallback.bind(this);
entry.appendChild(select);
this.selects.push(select);
}
return entry;
}
}
@@ -282,9 +302,26 @@ class SettingPin extends Setting {
}
}
class SettingMacro extends Setting {
createUI(setting) {
var entry = document.createElement('p');
entry.textContent = setting.name + ':';
return entry;
}
read(buffer) {
this.keys = buffer.read(2);
}
write(buffer) {
buffer.write(this.keys);
}
}
class ConfigValues {
constructor() {
this.switches = new SettingKeys(SWITCH_COUNT);
this.switches = new SettingKeys(SWITCH_COUNT, true);
this.btColour = new SettingRGB();
this.fxColour = new SettingRGB();
this.breatheColour = new SettingRGB();
@@ -295,10 +332,10 @@ class ConfigValues {
this.keyLights = new SettingBool();
this.knobLights = new SettingBool();
this.lightPattern = new SettingRadio();
this.macroClick = new SettingKeys(1);
this.macroHold = new SettingBool();
this.macroClick = new SettingMacro();
this.macroHold = new SettingMacro();
this.macroPin = new SettingKeys(4);
this.joystickMode = new SettingBool();
this.joystickMode = new SettingRadio();
}
read(view, writeCallback) {
@@ -367,9 +404,38 @@ class Config {
return device.selectConfiguration(1)
})
.then(() => {
console.log("Selected, claiming interface...");
return device.claimInterface(0);
if(device.productName == 'Voltex Boot') {
return this.loadFirmware();
} else {
return this.loadConfig();
}
})
.catch(error => {
//alert(error);
visibleLog(error);
if(device && device.opened)
device.close();
});
}
loadFirmware() {
visibleLog("Downloading fimware...");
return getLatest()
.then(info => {
return downloadLatest(info.version);
})
.then(firmware => {
visibleLog("Flashing firmware...");
return programLatest(device, firmware);
})
.then(() => {
visibleLog("Done!");
})
}
loadConfig() {
console.log("Selected, claiming interface...");
return device.claimInterface(0)
.then(() => {
visibleLog("Opened!");
return this.readVersion();
@@ -379,12 +445,6 @@ class Config {
this.enableUI();
return this.readConfig();
})
.catch(error => {
//alert(error);
visibleLog(error);
if(device && device.opened)
device.close();
});
}
readVersion() {
@@ -463,15 +523,6 @@ class Config {
document.getElementById('connect').classList.add('hidden');
this.populateSettings(this.optionsDiv, configDisplay);
}
createSetting(parent, setting) {
var func = this.settingsMap[setting.display];
if(func) {
func(parent, setting);
} else {
console.log('Unknown setting type', setting.display);
}
}
};
window.Config = Config;
+91
View File
@@ -0,0 +1,91 @@
(function(window, document) {
"use strict";
var pageSize = 128;
// full size - bootloader
var flashSize = (16 * 1024) - (2 * 1024);
var COMMAND_VERSION = 0x03;
var COMMAND_STARTAPPLICATION = 0x04;
var COMMAND_PROGRAM = 0x05;
var WEBUSB_ID = 0x01;
var MS_OS_ID = 0x02;
var CONFIG_ID = 0x03;
function getLatest() {
return new Promise ((resolve, reject) => {
var req = new XMLHttpRequest();
req.open("GET", "firmwares/latest.json", true);
req.responseType = "json";
req.onload = function (oEvent) {
if (req.response) {
resolve(req.response);
} else {
reject(Error(req.status + ": Could not fetch latest firmware information"));
}
};
req.send();
});
};
function downloadLatest(version) {
return new Promise ((resolve, reject) => {
var req = new XMLHttpRequest();
req.open("GET", "firmwares/PocketVoltex_" + version + ".bin", true);
req.responseType = "arraybuffer";
req.onload = function (oEvent) {
var arrayBuffer = req.response;
if (arrayBuffer) {
var view = new Uint8Array(arrayBuffer);
if(view.length <= flashSize) {
resolve(view);
} else {
reject(Error("Firmware too big! Must be under " +flashSize + ", got " + arrayBuffer.length));
}
} else {
reject(Error(req.status + ": Could not fetch firmware version " + version));
}
};
req.send();
})
};
function programLatest(device, hex) {
var p = Promise.resolve();
for(let addr = 0; addr < hex.length; addr += pageSize) {
let page = hex.subarray(addr, addr+pageSize);
p = p.then(() => {
// pad to 0s when size smaller
let packet = new Uint8Array(new ArrayBuffer(pageSize));
packet.set(page);
console.log(Math.round(addr / hex.length * 100) + '%');
return device.controlTransferOut({
requestType: 'vendor',
recipient: 'device',
request: CONFIG_ID,
value: addr,
index: COMMAND_PROGRAM}, packet);
});
}
p = p.then(() => {
console.log("Rebooting...");
return device.controlTransferOut({
requestType: 'vendor',
recipient: 'device',
request: CONFIG_ID,
value: 0,
index: COMMAND_STARTAPPLICATION}, []);
});
return p;
}
window.downloadLatest = downloadLatest;
window.getLatest = getLatest;
window.programLatest = programLatest;
})(window, document);