mirror of
https://github.com/mon/PocketVoltex.git
synced 2026-09-29 02:08:01 +03:00
Better macros, better config, initial updater
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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 = ¯o->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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef _KEYBOARD_H_
|
||||
#define _KEYBOARD_H_
|
||||
#ifndef _POCKETVOLTEX_H_
|
||||
#define _POCKETVOLTEX_H_
|
||||
|
||||
/* Includes: */
|
||||
#include <avr/io.h>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user