sync prod -> git

This commit is contained in:
recrof 2026-01-10 23:15:50 +01:00
parent 4d696db87f
commit ea3dae33dc
74 changed files with 4313 additions and 1073 deletions

File diff suppressed because it is too large Load diff

View file

@ -7,14 +7,59 @@ body {
height: 100vh; height: 100vh;
} }
body:has([v-cloak]):after {
content: '';
position: absolute;
top: calc(50% - 50px);
left: calc(50% - 50px);
width: 50px;
aspect-ratio: 1;
border-radius: 50%;
border: 8px solid #f0f0f0;
animation:
l20-1 0.8s infinite linear alternate,
l20-2 1.6s infinite linear;
}
@keyframes l20-1{
0% {clip-path: polygon(50% 50%,0 0, 50% 0%, 50% 0%, 50% 0%, 50% 0%, 50% 0% )}
12.5% {clip-path: polygon(50% 50%,0 0, 50% 0%, 100% 0%, 100% 0%, 100% 0%, 100% 0% )}
25% {clip-path: polygon(50% 50%,0 0, 50% 0%, 100% 0%, 100% 100%, 100% 100%, 100% 100% )}
50% {clip-path: polygon(50% 50%,0 0, 50% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100% )}
62.5% {clip-path: polygon(50% 50%,100% 0, 100% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100% )}
75% {clip-path: polygon(50% 50%,100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0% 100% )}
100% {clip-path: polygon(50% 50%,50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0% 100% )}
}
@keyframes l20-2{
0% {transform:scaleY(1) rotate(0deg)}
49.99%{transform:scaleY(1) rotate(135deg)}
50% {transform:scaleY(-1) rotate(0deg)}
100% {transform:scaleY(-1) rotate(-135deg)}
}
#app { #app {
flex-grow: 1; flex-grow: 1;
} }
#app h5.logo-top img {
height: 16px;
margin-top: -4px;
}
#app h5.logo-top span {
font-style: italic; font-weight: bold; line-height: 0.9
}
#app select { #app select {
cursor: pointer cursor: pointer
} }
#app .tooltip:has(>.device) {
background: #f0f0f055;
width: auto;
}
#app img.device { #app img.device {
width: 300px; width: 400px;
max-width: 400px;
max-height: 400px;
} }
#app div.autoscroller { #app div.autoscroller {
overflow: auto; overflow: auto;
@ -62,4 +107,23 @@ body {
} }
#app .version div.field { #app .version div.field {
min-width: 140px; min-width: 140px;
align-self: flex-start;
}
#app .version .pre {
padding-bottom: 10px;
}
#app img.icon {
height: 20px;
}
.flash-container {
padding-bottom: 150px;
}
.strong {
font-weight: 700;
}
.text-wrap {
text-wrap: auto;
} }

View file

@ -41,6 +41,10 @@ const commandReference = {
'get lon': 'Get the advertisement map longitude', 'get lon': 'Get the advertisement map longitude',
}; };
async function delay(milis) {
return await new Promise((resolve) => setTimeout(resolve, milis));
}
function getGithubReleases(roleType, files) { function getGithubReleases(roleType, files) {
const versions = {}; const versions = {};
for(const [fileType, matchRE] of Object.entries(files)) { for(const [fileType, matchRE] of Object.entries(files)) {
@ -78,25 +82,62 @@ function addGithubFiles() {
} }
} }
config.device = config.device.filter(device => device.firmware.some(firmware => Object.keys(firmware.version).length > 0 ));
return config; return config;
} }
async function digestMessage(message) {
const msgUint8 = new TextEncoder().encode(message); // encode as (utf-8) Uint8Array
const hashBuffer = await window.crypto.subtle.digest("SHA-256", msgUint8); // hash the message
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, "0"))
.join(""); // convert bytes to hex string
return hashHex;
}
async function blobToBinaryString(blob) {
const bytes = new Uint8Array(await blob.arrayBuffer())
let binString = '';
for (let i = 0; i < bytes.length; i++) {
binString += String.fromCharCode(bytes[i]);
}
return binString;
}
console.log(addGithubFiles()); console.log(addGithubFiles());
function setup() { function setup() {
const consoleEditBox = ref(); const consoleEditBox = ref();
const consoleWindow = ref(); const consoleWindow = ref();
const deviceFilterText = ref('');
const snackbar = reactive({
text: '',
class: '',
icon: '',
});
const selected = reactive({ const selected = reactive({
device: null, device: null,
firmware: null, firmware: null,
version: null, version: null,
wipe: false, wipe: false,
nrfEraserFlashingPercent: 0,
nrfEraserFlashing: false,
port: null, port: null,
}); });
const getRoleFwValue = (firmware, key) => { const getRoleFwValue = (firmware, key) => {
return firmware[key] || config.role[firmware.role][key] || ''; const role = config.role[firmware.role] ?? {};
return firmware[key] ?? role[key] ?? '';
} }
const getSelFwValue = (key) => { const getSelFwValue = (key) => {
@ -108,8 +149,8 @@ function setup() {
const flashing = reactive({ const flashing = reactive({
supported: 'Serial' in window, supported: 'Serial' in window,
instance: null, instance: null,
active: false, locked: false,
percentage: 0, percent: 0,
log: '', log: '',
error: '', error: '',
dfuComplete: false, dfuComplete: false,
@ -130,8 +171,20 @@ function setup() {
writeLine(data) { flashing.log += data + '\n' } writeLine(data) { flashing.log += data + '\n' }
}; };
const refresh = () => { const retry = async() => {
location.reload(); flashing.active = false;
flashing.log = '';
flashing.error = '';
flashing.dfuComplete = false;
flashing.percent = 0;
if(flashing.instance instanceof ESPLoader) {
await flashing.instance?.hr.reset();
await flashing.instance?.transport?.disconnect();
}
}
const close = () => {
location.reload()
} }
const getFirmwarePath = (file) => { const getFirmwarePath = (file) => {
@ -172,15 +225,25 @@ function setup() {
flashing.log = ''; flashing.log = '';
flashing.error = ''; flashing.error = '';
flashing.dfuComplete = false; flashing.dfuComplete = false;
flashing.percentage = 0; flashing.percent = 0;
selected.firmware = null; selected.firmware = null;
selected.version = null; selected.version = null;
selected.wipe = false; selected.wipe = false;
selected.device = null; selected.device = null;
selected.nrfEraserFlashingPercent = 0;
selected.nrfEraserFlashing = false;
if(flashing.instance instanceof ESPLoader) { if(flashing.instance instanceof ESPLoader) {
await flashing.instance?.hr.reset(); await flashing.instance?.hr.reset();
await flashing.instance?.transport?.disconnect(); await flashing.instance?.transport?.disconnect();
} }
else if(flashing.instance instanceof Dfu) {
try {
flashing.instance.port.close()
}
catch(e) {
console.error(e);
}
}
flashing.instance = null; flashing.instance = null;
} }
@ -191,12 +254,12 @@ function setup() {
const openSerialCon = async() => { const openSerialCon = async() => {
const port = selected.port = await navigator.serial.requestPort(); const port = selected.port = await navigator.serial.requestPort();
const serialConsole = serialCon.instance = new SerialConsole(port); const serialConsole = serialCon.instance = new SerialConsole(port);
serialCon.content = 'Welcome to MeshCore serial console.\n'
serialCon.content += '-------------------------------------------------------------------------\n'; serialCon.content = '-------------------------------------------------------------------------\n';
serialCon.content += 'WARNING: This console only works with *Repeater* and *Room server* roles.\n'; serialCon.content += 'Welcome to MeshCore serial console.\n'
serialCon.content += '-------------------------------------------------------------------------\n'; serialCon.content += 'Click on the cursor to get all supported commands.\n';
serialCon.content += 'If you came here right after flashing, please restart your device.\n'; serialCon.content += '-------------------------------------------------------------------------\n\n';
serialCon.content += 'Click on the cursor to get all supported commands.\n\n';
serialConsole.onOutput = (text) => { serialConsole.onOutput = (text) => {
serialCon.content += text; serialCon.content += text;
}; };
@ -227,10 +290,19 @@ function setup() {
const customFirmwareLoad = async(ev) => { const customFirmwareLoad = async(ev) => {
const firmwareFile = ev.target.files[0]; const firmwareFile = ev.target.files[0];
const type = firmwareFile.name.endsWith('.bin') ? 'esp32' : 'nrf52'; const type = firmwareFile.name.endsWith('.bin') ? 'esp32' : 'nrf52';
selected.device = { selected.device = {
name: 'Custom device', name: 'Custom device',
type, type,
}; };
if(firmwareFile.name.endsWith('-merged.bin')) {
alert(
'You selected custom file that ends with "merged.bin".'+
'This will erase your flash! Proceed with caution.'+
'If you want just to update your firmware, please use non-merged bin.'
);
selected.wipe = true;
}
selected.firmware = { selected.firmware = {
icon: 'unknown_document', icon: 'unknown_document',
@ -244,55 +316,100 @@ function setup() {
} }
} }
const espReset = async(t) => {
await t.setRTS(true);
await delay(100)
await t.setRTS(false);
}
const nrfErase = async() => {
if(!(selected.device.type === 'nrf52' && selected.device.erase)) {
console.error('nRF erase called for non-nrf device or device.erase is not defined')
return;
}
const url = `${config.staticPath}/${selected.device.erase}`;
console.log('downloading: ' + url);
const resp = await fetch(url);
if(resp.status !== 200) {
alert(`Could not download the firmware file from the server, reported: HTTP ${resp.status}.\nPlease try again.`)
return;
}
const flashData = await resp.blob();
const port = selected.port = await navigator.serial.requestPort({});
const dfu = new Dfu(port);
try {
selected.nrfEraserFlashing = true;
await dfu.dfuUpdate(flashData, async (progress) => {
selected.nrfEraserFlashingPercent = progress;
if(progress === 100 && selected.nrfEraserFlashing) {
selected.nrfEraserFlashing = false;
selected.dfuComplete = false;
setTimeout(() => {
alert('Device erase firmware has been flashed and flash has been erased.\nYou can flash MeshCore now.');
}, 200);
}
}, 60000);
}
catch(e) {
alert(`nRF flashing erase firmware failed: ${e}.\nDid you put the device into DFU mode before attempting erasing?`);
selected.nrfEraserFlashing = false;
selected.nrfEraserFlashingPercent = 0;
return;
}
}
const canFlash = (device) => {
return device.type !== 'noflash'
}
const flashDevice = async() => { const flashDevice = async() => {
const device = selected.device; const device = selected.device;
const firmware = selected.firmware.version[selected.version]; const firmware = selected.firmware.version[selected.version];
let flashFile;
flashFile = firmware.files.find(f => f.type === 'flash'); const flashFiles = firmware.files.filter(f => f.type.startsWith('flash'));
if(!flashFile) { if(!flashFiles[0]) {
alert('Cannot find configuration for flash file! please report this to Discord.') alert('Cannot find configuration for flash file! please report this to Discord.')
flasherCleanup(); flasherCleanup();
return; return;
} }
console.log({flashFile, instanceFile: flashFile instanceof File}); console.log({flashFiles});
if(flashFile.file) { let flashData;
flashFile = flashFile.file; if(flashFiles[0].file) {
flashData = flashFiles[0].file;
} else { } else {
let flashFile;
if(device.type === 'esp32') {
flashFile = flashFiles.find(f => f.type === (selected.wipe ? 'flash-wipe' : 'flash-update'));
}
else {
flashFile = flashFiles[0];
}
console.log({flashFiles, flashFile});
const url = getFirmwarePath(flashFile); const url = getFirmwarePath(flashFile);
console.log('downloading: ' + url); console.log('downloading: ' + url);
const resp = await fetch(url); const resp = await fetch(url);
flashFile = await resp.blob(); if(resp.status !== 200) {
alert(`Could not download the firmware file from the server, reported: HTTP ${resp.status}.\nPlease try again.`)
return;
}
flashData = await resp.blob();
} }
const port = selected.port = await navigator.serial.requestPort({}); const port = selected.port = await navigator.serial.requestPort({});
if(device.type === 'esp32') { if(device.type === 'esp32') {
let esploader; let esploader;
let fileData;
let transport; let transport;
try {
const reader = new FileReader();
fileData = await new Promise((resolve, reject) => {
reader.addEventListener('error', () => {
reader.abort();
reject(new DOMException('Problem parsing input file.'));
});
reader.addEventListener('load', () => resolve(reader.result));
reader.readAsBinaryString(flashFile);
});
}
catch(e) {
console.error(e);
flashing.error = `Cannot read flash file: ${e}`;
return;
}
const flashOptions = { const flashOptions = {
terminal: log, terminal: log,
compress: true, compress: true,
@ -304,18 +421,18 @@ function setup() {
romBaudrate: 115200, romBaudrate: 115200,
enableTracing: false, enableTracing: false,
fileArray: [{ fileArray: [{
data: fileData, data: await blobToBinaryString(flashData),
address: 0 address: selected.wipe ? 0x00000 : 0x10000
}], }],
reportProgress: async (_, written, total) => { reportProgress: async (_, written, total) => {
flashing.percentage = (written / total) * 100; flashing.percent = (written / total) * 100;
}, },
}; };
try { try {
flashing.active = true; flashing.active = true;
transport = new Transport(port, true); transport = new Transport(port, true);
flashOptions.transport = transport flashOptions.transport = transport;
flashing.instance = esploader = new ESPLoader(flashOptions); flashing.instance = esploader = new ESPLoader(flashOptions);
esploader.hr = new HardReset(transport); esploader.hr = new HardReset(transport);
await esploader.main(); await esploader.main();
@ -330,44 +447,87 @@ function setup() {
try { try {
await esploader.writeFlash(flashOptions); await esploader.writeFlash(flashOptions);
await esploader.after(); await delay(100);
await esploader.after('hard_reset');
await delay(100);
await espReset(transport);
await transport.disconnect();
} }
catch(e) { catch(e) {
console.error(e); console.error(e);
flashing.error = `ESP32 flashing failed: ${e}`; flashing.error = `ESP32 flashing failed: ${e}`;
await esploader.hardReset(); await espReset(transport);
await transport.disconnect(); await transport.disconnect();
return; return;
} }
} }
else if(device.type === 'nrf52') { else if(device.type === 'nrf52') {
const dfu = flashing.instance = new Dfu(port, selected.wipe); const dfu = flashing.instance = new Dfu(port);
flashing.active = true; flashing.active = true;
try { try {
await dfu.dfuUpdate(flashFile, async (progress) => { await dfu.dfuUpdate(flashData, async (progress) => {
flashing.percentage = progress; flashing.percent = progress;
}); }, 60000);
} }
catch(e) { catch(e) {
console.error(e); console.error(e);
flashing.error = `nRF flashing failed: ${e}`; flashing.error = `nRF flashing failed: ${e}. Please reset the device and try again.`;
return; return;
} }
} }
}; };
const devices = computed(() => {
const classSortPrefix = (d) => d.class === 'ripple' ? '1' : '2';
const deviceGroups = {};
for(const cls of ['ripple', 'community']) {
const devices = config.device.toSorted(
(a, b) => (classSortPrefix(a) + a.maker + a.name).localeCompare(classSortPrefix(b) + b.maker + b.name)
).filter(
d => d.class === cls && (deviceFilterText.value == '' || d.name.toLowerCase().includes(deviceFilterText.value?.toLowerCase()))
)
if(devices.length > 0) deviceGroups[cls] = devices;
}
return deviceGroups;
});
const showMessage = (text, icon, displayMs) => {
snackbar.class = 'active';
snackbar.text = text;
snackbar.icon = icon || '';
setTimeout(() => {
snackbar.icon = '';
snackbar.text = '';
snackbar.class = '';
}, displayMs || 2000);
}
const consoleMouseUp = (ev) => {
if(window.getSelection().toString().length) {
navigator.clipboard.writeText(window.getSelection().toString())
showMessage('text copied to clipboard');
}
consoleEditBox.value.focus();
}
return { return {
consoleEditBox, consoleWindow, snackbar,
config, selected, flashing, consoleEditBox, consoleWindow, consoleMouseUp,
config, devices, selected, flashing, deviceFilterText,
flashDevice, flasherCleanup, dfuMode, flashDevice, flasherCleanup, dfuMode,
serialCon, closeSerialCon, openSerialCon, serialCon, closeSerialCon, openSerialCon,
sendCommand, openSerialGUI, sendCommand, openSerialGUI,
refresh, commandReference, retry, close, commandReference,
stepBack, stepBack,
customFirmwareLoad, getFirmwarePath, getSelFwValue, getRoleFwValue, customFirmwareLoad, getFirmwarePath, getSelFwValue, getRoleFwValue,
firmwareHasData firmwareHasData,
canFlash, nrfErase
} }
} }

1
img/esp32.svg Normal file
View file

@ -0,0 +1 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300" width="300" height="300"><style>.a{fill:#e7352c}</style><path class="a" d="m99.4 223.2c0 3.1-0.6 6.2-2 9-1.3 2.8-3.2 5.3-5.6 7.3-2.5 2-5.3 3.4-8.3 4.1-3 0.8-6.1 0.9-9.2 0.3-2-0.4-4-1.1-5.8-2.1-1.8-1-3.5-2.2-5-3.7-1.4-1.4-2.7-3.1-3.6-4.9-1-1.9-1.7-3.8-2.1-5.9-0.9-4.4-0.3-9 1.6-13.1 1.9-4 5.1-7.4 9.1-9.5 4-2.1 8.5-2.9 13-2.3 4.5 0.7 8.6 2.8 11.8 6q1.4 1.4 2.6 3.1 1.1 1.8 1.9 3.7 0.8 1.9 1.2 3.9 0.4 2.1 0.4 4.1zm183.5-21.9c-3.3-23.1-10.3-45.6-20.8-66.5-10.5-21-24.3-40.1-40.8-56.6-16.5-16.6-35.6-30.3-56.6-40.8-20.9-10.5-43.3-17.5-66.5-20.9q-4.2 2.2-8.2 4.7-4 2.5-7.8 5.3-3.9 2.8-7.5 5.8-3.6 3-7.1 6.3v20.3c22.7 0 45.2 4.5 66.2 13.2 20.9 8.7 40 21.4 56 37.5 16.1 16 28.8 35.1 37.5 56 8.7 21 13.2 43.5 13.3 66.2h20.3q3.2-3.5 6.2-7.1 3-3.6 5.8-7.4 2.8-3.9 5.3-7.8 2.5-4 4.7-8.2z"/><path class="a" d="m299.4 137.5c0-13.5-2-26.9-6-39.8-3.9-12.9-9.7-25.2-17.2-36.4-7.5-11.2-16.7-21.2-27.1-29.8-10.5-8.5-22.1-15.4-34.6-20.5q-6.2-2.6-12.7-4.5-6.4-1.9-13.1-3.2-6.6-1.3-13.3-1.9-6.7-0.7-13.4-0.6c-4.9 0-9.6 0-14.2 0.7l-3.2 9.1c16.6 5.8 32.5 13.4 47.4 22.8 14.9 9.3 28.6 20.3 41.1 32.8 12.4 12.4 23.4 26.2 32.8 41 9.3 14.9 17 30.8 22.8 47.4l9.1-3.3c0-4.5 0.9-9.1 0.9-14.2m-135.2 162.2c-21.2 0-42.3-4.2-62-12.3-19.7-8.2-37.6-20.1-52.7-35.1-15-15-27-32.9-35.2-52.5-8.2-19.7-12.5-40.7-12.6-62 0-10.7 1-21.3 3-31.8 2.1-10.4 5.2-20.7 9.3-30.5 4-9.9 9.1-19.3 15-28.1 5.9-8.9 12.7-17.1 20.3-24.6l9.2 8.6c-14 14-25.2 30.6-32.8 48.9-7.7 18.3-11.6 37.9-11.6 57.8 0 19.8 3.9 39.4 11.6 57.7 7.6 18.3 18.8 34.9 32.8 48.9 14 14.1 30.6 25.2 48.9 32.9 18.3 7.6 37.9 11.5 57.7 11.5 19.8 0 39.4-3.9 57.7-11.5 18.3-7.7 34.9-18.8 48.9-32.9l8.6 8.6c-7.6 7.6-16 14.3-24.9 20.1-9 5.9-18.5 10.9-28.4 14.8-10 4-20.3 7-30.8 8.9-10.6 1.9-21.3 2.8-32 2.6z"/><path class="a" d="m161.7 241.1c0.7-7.9 0.5-15.9-0.8-23.8-1.3-7.9-3.6-15.6-6.8-23-3.3-7.3-7.4-14.2-12.4-20.4-5-6.3-10.7-11.9-17.1-16.8q-5.1-4-10.7-7.3-5.7-3.3-11.7-5.7-6-2.5-12.4-4-6.3-1.6-12.8-2.3-1.6-0.2-3-1-1.5-0.8-2.5-2.1-1.1-1.2-1.6-2.8-0.5-1.6-0.4-3.2 0.1-1.3 0.6-2.4 0.4-1.2 1.2-2.2 0.8-1 1.9-1.7 1-0.7 2.2-1.1 0.4-0.1 0.8-0.2 0.4 0 0.9-0.1 0.4 0 0.8 0 0.4 0 0.8 0c14.6 1.5 28.6 5.9 41.5 12.8 12.8 7 24.2 16.4 33.4 27.7 9.2 11.3 16.2 24.3 20.4 38.3 4.2 14 5.6 28.7 4.2 43.2q-0.2 2.4-0.6 4.9-0.3 2.4-0.8 4.8-0.4 2.4-1 4.8-0.6 2.3-1.3 4.7l24.7 7q2.8-0.8 5.5-1.7 2.7-0.9 5.3-2 2.7-1 5.3-2.1 2.7-1.1 5.2-2.3 0.7-3.6 1.2-7.3 0.6-3.6 0.9-7.3 0.3-3.6 0.5-7.3 0.2-3.6 0.1-7.3c0-18.5-3.3-36.9-9.7-54.2-6.5-17.4-15.9-33.5-28-47.5-12.1-14.1-26.5-25.9-42.7-34.8-16.2-9-33.8-15.1-52.1-17.9q-3.1-0.5-6.2-0.8-3.1-0.3-6.3-0.3-3.1 0-6.2 0.3-3.1 0.3-6.2 0.8-3.9 1.1-7.6 2.6-3.7 1.6-7.1 3.7-3.4 2.2-6.4 4.7-3.1 2.6-5.8 5.7c-5.7 6.6-9.7 14.5-11.6 23.1-1.9 8.5-1.7 17.4 0.7 25.9 2.3 8.4 6.7 16.1 12.8 22.5 6 6.3 13.5 11 21.9 13.8q1.6 0.3 3.3 0.7 1.6 0.3 3.3 0.6 1.7 0.3 3.4 0.6 1.6 0.3 3.3 0.5c5.8 1 11.3 2.9 16.4 5.8 5.1 2.9 9.6 6.7 13.4 11.2 3.7 4.4 6.7 9.5 8.7 15 2 5.5 3 11.3 3 17.2q0 3.5-0.5 7-0.5 3.6-1.5 7-1 3.4-2.5 6.6-1.5 3.2-3.4 6.2l17 11q3.1 0.8 6.3 1.5 3.2 0.7 6.4 1.3 3.2 0.5 6.4 0.9 3.3 0.3 6.5 0.5 1.9-3.8 3.4-7.8 1.6-4 2.8-8.1 1.2-4.1 2-8.3 0.8-4.2 1.2-8.5"/></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

31
img/esp_now.svg Normal file
View file

@ -0,0 +1,31 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 26" width="100%" height="100%">
<style>
.a {
fill: #fff
}
.b {
fill: #c00000
}
</style>
<path
d="m5 25.3c-2.1-0.4-3.9-2.3-4.1-4.3-0.1-0.3-0.1-4-0.1-8.2 0-8.3 0-8.2 0.5-9.2 0.3-0.6 0.4-0.9 1-1.4 0.6-0.6 0.7-0.6 1.4-1 0.7-0.3 0.8-0.4 1.5-0.5 0.2 0 20.5 0 45.1 0 45.8 0 45.1 0 45.5 0.3q0.1 0 0.1 0c0.1 0 1 0.5 1.1 0.5 0.1 0.1 0.2 0.2 0.4 0.3 0.2 0.2 0.6 0.6 0.8 0.9 0.2 0.3 0.5 0.7 0.4 0.7q0 0 0.1 0.1c0 0.1 0.1 0.2 0.1 0.2 0 0.1 0.1 0.3 0.1 0.5 0.1 0.2 0.2 0.6 0.2 0.9 0.1 0.7 0.1 15.8 0 16.3 0 0.1-0.1 0.3-0.1 0.4q-0.1 0.1-0.1 0.1c0.1 0 0 0.3-0.2 0.6-0.7 1.4-1.9 2.3-3.5 2.7-0.3 0.1-89.7 0.1-90.2 0.1zm38.6-2.3c0-0.1-0.4-0.9-0.9-1.9l-0.8-1.7v-6.7c0-5.8 0-6.8-0.2-7.2-0.2-0.8-0.7-1.6-1.6-2.1-0.8-0.5 0.3-0.5-17.5-0.5-17.9 0-16.7 0-17.6 0.5-0.8 0.5-1.5 1.4-1.6 2.3-0.1 0.3-0.1 3.1-0.1 7.4 0 7.6 0 7.4 0.5 8.2 0.2 0.5 0.9 1.1 1.3 1.3 0.1 0.1 0.4 0.3 0.6 0.3 0.4 0.2 1.3 0.2 19.2 0.2 14.9 0 18.7 0 18.7-0.1zm27.5-3.2q3.1-0.8 4.2-3.9c0.9-2.4 0.7-5.6-0.5-7.7-0.4-0.6-1.4-1.6-2.1-1.9-2.1-1.2-5.1-1.2-7.3-0.1-0.3 0.2-0.8 0.6-1.2 1-0.6 0.6-0.7 0.8-1.1 1.5-0.6 1.4-0.9 2.8-0.8 4.5 0.2 3.7 2 6.1 5.2 6.7 0.9 0.2 2.8 0.1 3.6-0.1zm-22.4-4.6c0-2.5-0.1-5-0.1-5.5v-1h0.3c0.2 0 0-0.4 2.8 4.5 0.5 0.8 1.5 2.6 2.3 4.1l1.5 2.5 1.8 0.1h1.8v-7.2-7.1h-1.2-1.2l0.1 4.8c0 2.7 0 5.2 0 5.6l0.1 0.6h-0.3-0.3l-3.2-5.5-3.2-5.5h-1.7-1.8v7.1 7.2h1.2 1.2zm36 2.2c1.2-4.9 1.6-6.4 1.8-7.5l0.2-1.2h0.2c0.3 0 0.3 0 0.4 0.6 0.2 1.1 0.7 3.2 1.6 6.9l0.9 3.6h1.6 1.6l1.7-7c1-3.9 1.8-7.1 1.9-7.2 0-0.1-0.3-0.1-1.2-0.1h-1.3l-0.1 0.4c-0.7 2.8-2.2 9.5-2.4 10.8l-0.1 0.6h-0.3c-0.2 0-0.2 0-0.3-0.4-0.1-1.1-0.6-3.1-1.6-7.1l-1.1-4.3h-1.3-1.2l-0.7 2.7c-1.5 5.7-1.8 6.8-1.9 7.9l-0.2 1.1h-0.3c-0.2 0-0.2 0-0.3-0.2 0-0.1-0.1-0.8-0.3-1.7-0.1-0.8-0.7-3.3-1.2-5.5-0.5-2.1-0.9-4-1-4.1 0-0.2 0-0.2-1.3-0.2-1.1 0-1.2 0-1.2 0.1 0.1 0.2 0.7 2.5 2.3 8.8 0.7 2.9 1.3 5.3 1.3 5.3 0.1 0.1 0.4 0.1 1.6 0.1h1.5z" />
<path
d="m67.7 17.8c-1.3-0.4-2.1-1.3-2.4-2.9-0.2-0.9-0.2-3.5 0-4.4 0.2-1.2 0.9-2.2 1.7-2.6 1.1-0.6 3-0.6 4.1 0 0.8 0.4 1.5 1.4 1.8 2.6 0.1 0.5 0.1 1 0.1 2.2 0 1.8-0.1 2.5-0.6 3.4-0.2 0.5-0.8 1.2-1.3 1.4-0.8 0.4-2.4 0.5-3.4 0.3z" />
<path class="a"
d="m19.5 20.5c-0.8-0.1-1.5-0.3-2.1-0.5l-0.4-0.2v-1.7-1.7h0.2c0.1 0 0.7 0.2 1.1 0.4 1.4 0.5 2.3 0.8 3.2 0.8 0.7 0 0.9-0.1 1.2-0.2 0.6-0.3 0.9-0.9 0.7-1.4-0.2-0.5-0.7-0.9-2-1.5-2.4-1.2-3-1.7-3.6-2.6-0.5-0.7-0.6-1.3-0.6-2.5-0.1-1.1 0.1-1.6 0.5-2.3q1.2-2.1 4.7-2.1c1.5 0 2.8 0.3 4.2 0.9 0.3 0.2 0.4 0.2 0.4 0.4 0 0.2-0.3 0.8-0.5 1.5l-0.5 1.2-0.4-0.1c-0.2 0-0.7-0.2-1.1-0.3-0.8-0.3-2.1-0.6-2.4-0.5-0.5 0.1-0.7 0.2-1 0.5q-0.8 0.7 0 1.5c0.3 0.3 0.8 0.6 1.8 1.1 2.6 1.3 3.5 2 3.9 3.3 0.2 0.6 0.3 2.3 0.1 3-0.4 1.5-1.5 2.5-3.2 2.9-0.8 0.2-3.3 0.3-4.2 0.1z" />
<path class="a"
d="m5.7 12.8v-7.6h4.4c2.4 0 4.4 0 4.5 0 0.1 0.1 0.1 0.5 0.1 1.6l-0.1 1.5h-2.6-2.7v1.4 1.3h2.5 2.5v1.6 1.5h-2.5-2.5v1.6 1.7h2.7 2.6v1.5 1.6h-4.4-4.5z" />
<path class="a"
d="m29.9 12.8v-7.6h2.4c3.8 0 5 0.1 6.1 0.7 1 0.5 1.8 1.5 2 2.6 0.1 0.3 0.1 0.9 0.1 1.6 0 1.8-0.3 2.7-1.2 3.6q-0.9 0.9-2.2 1.3c-0.5 0.1-1 0.1-2.1 0.2h-1.5v2.7 2.6h-1.8-1.8zm5.8-1c0.7-0.3 1-0.7 1.2-1.3 0.1-0.5 0-1-0.4-1.5-0.3-0.5-1.2-0.8-2.5-0.8h-0.5v2 1.9h0.9c0.7 0 0.9-0.1 1.3-0.3z" />
<path class="a"
d="m67.3 20.1c-2.1-0.3-3.7-1.6-4.5-3.3-0.5-1.1-0.7-2.1-0.7-3.6-0.1-2 0.1-3.3 0.8-4.7 0.4-0.7 0.5-0.9 1.1-1.5 1.4-1.4 3-2 5.5-1.9 1.4 0.1 2.4 0.3 3.5 0.9 0.6 0.4 1.6 1.4 2 2 0.8 1.4 1.2 3.2 1.1 5.3q-0.2 3.4-2 5.2c-0.8 0.8-1.6 1.2-2.8 1.5-0.8 0.3-3.1 0.3-4 0.1zm2.7-2.5c0.7-0.2 1.1-0.4 1.6-0.9 0.5-0.5 0.8-1.1 1-2 0.2-0.9 0.2-3 0-3.9-0.3-1.3-0.9-2.3-1.8-2.7-0.4-0.2-1.2-0.4-1.7-0.4-0.6 0-1.4 0.2-1.8 0.4-0.9 0.4-1.5 1.4-1.8 2.6-0.2 0.9-0.2 3.1 0 4 0.3 1.2 0.8 2.1 1.6 2.5 0.8 0.4 1.9 0.6 2.9 0.4z" />
<path class="a"
d="m46.2 12.7v-7.4l2 0.1h2l3.1 5.5c1.8 3 3.3 5.5 3.3 5.5 0 0 0-0.2 0-0.4 0-0.2-0.1-2.7-0.1-5.5v-5.2h1.4 1.4v7.4 7.4h-2-2l-1.5-2.6c-0.8-1.4-1.9-3.3-2.3-4.1-2.8-4.8-2.6-4.5-2.7-4.2 0 0.1 0.1 0.7 0.1 1.3 0 0.6 0.1 3 0.1 5.4v4.2h-1.4-1.4z" />
<path class="a"
d="m80.7 20c0 0-0.6-2.4-1.3-5.3-0.8-2.9-1.6-6.1-1.9-7.2-0.2-1.1-0.5-2-0.5-2.1 0 0 0.5-0.1 1.6-0.1 1.5 0 1.5 0 1.5 0.2 0 0.1 0.5 2 1 4.2 0.5 2.2 1 4.7 1.2 5.5 0.2 0.8 0.3 1.6 0.3 1.7 0.1 0.1 0.1-0.2 0.3-0.9 0.2-1.1 0.4-2.3 1.9-8l0.7-2.7h1.5 1.5l1.1 4.3c1.2 4.5 1.2 4.9 1.5 6.5l0.2 1.1 0.1-0.6c0.2-1.3 0.5-2.7 1.4-6.5 0.5-2.2 1-4.2 1-4.4l0.1-0.4h1.5c0.9 0 1.6 0.1 1.6 0.1 0 0-0.9 3.4-1.9 7.4l-1.9 7.3h-1.7-1.8l-1-3.9c-1-3.7-1.4-5.3-1.6-6.6l-0.1-0.6-0.2 1.2c-0.2 1.1-0.6 2.6-1.9 7.5l-0.6 2.4h-1.7c-1.4 0-1.8 0-1.9-0.1z" />
<path class="b"
d="m5.5 23.2c-0.2-0.1-0.5-0.2-0.6-0.3-0.5-0.2-1.1-0.9-1.4-1.3-0.5-0.9-0.5-0.7-0.5-8.5 0-4.5 0.1-7.3 0.1-7.6 0.2-0.9 0.8-1.8 1.7-2.3 0.9-0.5-0.3-0.5 17.8-0.5 18.1 0 16.9 0 17.8 0.5 0.8 0.5 1.4 1.2 1.6 2.1 0.1 0.4 0.1 1.4 0.1 7.2v6.6l1 2.1c0.5 1.1 1 2.1 1 2.1 0 0-8.6 0-19.1 0-18.2 0-19.1 0-19.5-0.1zm18-3c1.7-0.5 2.8-1.4 3.2-2.9 0.2-0.7 0.1-2-0.1-2.6-0.5-1.2-1.3-1.9-3.9-3.2-1.5-0.8-1.9-1.1-2.1-1.5-0.2-0.3-0.2-1 0-1.2 0.1-0.3 0.4-0.7 0.7-0.8 0.7-0.3 2.1-0.1 3.4 0.4 0.5 0.1 0.9 0.3 0.9 0.3 0.1 0 0.3-0.3 0.6-1.2 0.3-0.6 0.5-1.2 0.5-1.2-0.1-0.1-1.2-0.5-1.8-0.7-1-0.2-1.5-0.3-2.6-0.3-2.1 0-3.6 0.7-4.4 2-0.4 0.7-0.5 1.2-0.5 2.1 0 1.3 0.4 2.1 1.3 3.1 0.5 0.4 1.1 0.8 2.9 1.7 1.8 0.9 2.2 1.4 2.1 2.3q-0.1 0.8-0.8 1.1c-0.3 0.2-0.5 0.2-1.4 0.2-1.3 0-2-0.2-4.2-1.1-0.1 0-0.1 0.3-0.1 1.4v1.5l0.5 0.2c1.2 0.5 2.2 0.6 3.9 0.6 1 0 1.4-0.1 1.9-0.2zm-9.1-1.3v-1.3h-2.7-2.6v-1.8-1.9h2.5 2.5v-1.3-1.3h-2.5-2.5v-1.6-1.7h2.6 2.7v-1.2c0-0.7 0-1.3 0-1.3 0 0-2 0-4.3 0h-4.2v7.4 7.4l4.3-0.1h4.2zm18.9-1.3v-2.6l1.5-0.1c1.6 0 2.3-0.2 3.2-0.6 0.7-0.4 1.5-1.2 1.8-1.8 0.5-0.9 0.7-2.6 0.4-3.8-0.3-1.1-1-2-2-2.5-1.2-0.6-1.8-0.7-5.2-0.7l-2.8-0.1v7.4 7.5h1.5 1.6z" />
<path class="b"
d="m33.3 10.2v-2.2h0.7c2 0 2.8 0.4 3.1 1.6 0.1 0.4 0.1 1.2-0.1 1.6-0.3 0.5-0.9 0.9-1.6 1.1-0.2 0-0.8 0.1-1.2 0.1h-0.9z" />
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

1
img/faketec.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 182 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 242 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

1
img/heltec_paper.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

1
img/heltec_t114.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 104 KiB

1
img/heltec_v2.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

1
img/heltec_v3.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 77 KiB

1
img/heltec_v4.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 123 KiB

1
img/heltec_wp.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

1
img/heltec_wsl3.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 124 KiB

1
img/heltec_wt3.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 157 KiB

1
img/ikoka_nano.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 231 KiB

1
img/ikoka_stick.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 192 KiB

1
img/keepteen_lt1.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

1
img/lilygo_pager.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

1
img/lilygo_t3s3.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

1
img/lilygo_t5_pro.svg Normal file
View file

@ -0,0 +1 @@
<svg width="752pt" height="1408pt" viewBox="0 0 752 1408" xmlns="http://www.w3.org/2000/svg"><path fill="#888b94" d="M107.77 0h528.71c17.3 1.49 34.61 5.38 50.12 13.37 17.57 8.72 32.62 22.13 44.12 37.95 10.87 15 17.82 32.63 21.28 50.78v1204.22c-5.62 31.38-23.45 60.63-49.71 78.92-19.12 14.09-42.69 21.1-66.2 22.76H106.6c-18.76-2.28-37.44-7.76-53.22-18.4-15.3-9.66-28.12-23.02-37.77-38.29-7.76-12.35-12.48-26.33-15.61-40.5V96.78C2.81 84.89 6.53 73.11 12.48 62.4 26.44 36.77 50.02 16.6 77.44 6.66 87.2 3.1 97.48 1.18 107.77 0m.71 17.65c-14.96 1.63-29.39 6.86-42.34 14.46-30.82 18.54-51.06 53.76-50.61 89.84-.16 53.35-.02 106.7-.07 160.05.02 334.31-.01 668.63.01 1002.94-.25 15.23 3.03 30.43 9.27 44.31 5.58 13.35 14.91 24.78 25.28 34.72 13.19 12.28 29.89 20.4 47.32 24.62 11.02 2.67 22.44 2.71 33.71 2.73 164-.08 328-.06 492-.06 12.32.13 24.79-.83 36.59-4.6 24.31-7.14 45.99-23.09 59.44-44.62 11.24-17.8 16.97-39.01 16.22-60.05-.11-381.66 0-763.33-.06-1144.99-.09-9.1.43-18.25-.77-27.3-3.4-31.62-22.54-60.95-49.83-77.18-17-10.27-36.79-15.57-56.62-15.71-166.67-.09-333.34.03-500.01-.02-6.52-.1-13.05.18-19.53.86z"/><path fill="#cad3d4" d="M108.48 17.65c6.48-.68 13.01-.96 19.53-.86 166.67.05 333.34-.07 500.01.02 19.83.14 39.62 5.44 56.62 15.71 27.29 16.23 46.43 45.56 49.83 77.18 1.2 9.05.68 18.2.77 27.3.06 381.66-.05 763.33.06 1144.99.75 21.04-4.98 42.25-16.22 60.05-13.45 21.53-35.13 37.48-59.44 44.62-11.8 3.77-24.27 4.73-36.59 4.6-164 0-328-.02-492 .06-11.27-.02-22.69-.06-33.71-2.73-17.43-4.22-34.13-12.34-47.32-24.62-10.37-9.94-19.7-21.37-25.28-34.72-6.24-13.88-9.52-29.08-9.27-44.31-.02-334.31.01-668.63-.01-1002.94.05-53.35-.09-106.7.07-160.05-.45-36.08 19.79-71.3 50.61-89.84 12.95-7.6 27.38-12.83 42.34-14.46m-47.99 108.3c-.05 371.96-.07 743.94.01 1115.9 44.14-.37 88.29-.03 132.44-.15 165.88-.04 331.76.15 497.63-.1-.21-49.19-.05-98.38-.08-147.56-.03-322.66 0-645.32-.01-967.98-90.81.04-181.63-.11-272.43-.03-119.19-.17-238.37.17-357.56-.08M368.3 1263.34c-20.49 2.64-37.11 21.1-38.3 41.6-1.48 17.69 8.72 35.64 24.62 43.53 15.67 8.3 36.04 6.17 49.75-5.07 10.84-8.23 16.75-21.96 16.81-35.41.15-14.48-7.28-28.68-19.09-37.01-9.68-6.82-22.14-9.42-33.79-7.64z"/><path fill="#4e4e54" d="M60.49 125.95c119.19.25 238.37-.09 357.56.08 90.8-.08 181.62.07 272.43.03.01 322.66-.02 645.32.01 967.98.03 49.18-.13 98.37.08 147.56-165.87.25-331.75.06-497.63.1-44.15.12-88.3-.22-132.44.15-.08-371.96-.06-743.94-.01-1115.9m7.93 8.21c.31 12.63.09 25.25.04 37.88.02 199.66-.03 399.32.03 598.98-.08 154.25.1 308.51-.1 462.77 151.55.16 303.11.04 454.66.02 53.17-.17 106.35.26 159.52-.13.13-268.56.03-537.13.07-805.7-.08-97.86.13-195.73-.12-293.59-1.15-1.06-3.05-.35-4.47-.59-89.36.71-178.7-.06-268.03.26-109.02 0-218.04.13-327.05-.08-4.85-.16-9.72-.34-14.55.18z"/><path fill="#747474" d="M68.42 134.16c4.83-.52 9.7-.34 14.55-.18 109.01.21 218.03.08 327.05.08 89.33-.32 178.67.45 268.03-.26 1.42.24 3.32-.47 4.47.59.25 97.86.04 195.73.12 293.59-.04 268.57.06 537.14-.07 805.7-53.17.39-106.35-.04-159.52.13-151.55.02-303.11.14-454.66-.02.2-154.26.02-308.52.1-462.77-.06-199.66-.01-399.32-.03-598.98.05-12.63.27-25.25-.04-37.88z"/><path fill="#b1b9bb" d="M368.3 1263.34c11.65-1.78 24.11.82 33.79 7.64 11.81 8.33 19.24 22.53 19.09 37.01-.06 13.45-5.97 27.18-16.81 35.41-13.71 11.24-34.08 13.37-49.75 5.07-15.9-7.89-26.1-25.84-24.62-43.53 1.19-20.5 17.81-38.96 38.3-41.6z"/></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

1
img/lilygo_tbeam.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

1
img/lilygo_tdeck.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 81 KiB

1
img/lilygo_tdeck_pro.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 70 KiB

1
img/lilygo_tdisplay.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

1
img/lilygo_techo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

1
img/lilygo_tlora_1.6.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 58 KiB

1
img/lilygo_tlora_c6.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 64 KiB

28
img/lora.svg Normal file
View file

@ -0,0 +1,28 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 405" width="100%" height="100%">
<style>
.a { fill: #fff }
</style>
<path class="a"
d="m206.6 22.5c8.7 0 17.4 0.6 26 2 8.5 1.3 17 3.2 25.3 5.8 8.3 2.5 16.4 5.7 24.2 9.5 7.9 3.8 15.4 8.1 22.6 13l13.8-18.6c-8.3-5.4-16.9-10.2-25.9-14.4-8.9-4.3-18.2-7.8-27.6-10.7-9.5-2.9-19.1-5.1-28.9-6.6-9.7-1.6-19.6-2.4-29.5-2.5-9.9 0.1-19.7 0.9-29.5 2.5-9.8 1.5-19.4 3.7-28.9 6.6-9.4 2.9-18.6 6.5-27.6 10.7-8.9 4.2-17.6 9-25.8 14.4l13.7 18.6c7.2-4.9 14.8-9.2 22.6-13 7.8-3.8 15.9-7 24.2-9.5 8.3-2.6 16.8-4.5 25.4-5.8 8.5-1.4 17.2-2 25.9-2z" />
<path class="a"
d="m260 116.6l8.2-11.1q-6.8-4.3-14.1-7.7-7.4-3.4-15.1-5.7-7.7-2.4-15.7-3.7-7.9-1.3-16-1.5h-1.2-0.1q-8.1 0.2-16.1 1.5-7.9 1.3-15.7 3.7-7.7 2.3-15 5.7-7.4 3.4-14.2 7.7l8.3 11.1q6-3.8 12.4-6.7 6.4-2.9 13.2-4.9 6.7-2 13.7-3.1 7-1.2 14-1.4 7.1 0.2 14 1.4 7 1.1 13.7 3.1 6.8 2 13.2 4.9 6.5 2.9 12.5 6.7z" />
<path class="a"
d="m206.6 43.9q-11.4 0.2-22.7 1.9-11.3 1.8-22.3 5.2-10.9 3.3-21.3 8.2-10.3 4.8-19.9 11l10.9 15c11.1-7.3 23.2-13 36-16.9 12.7-3.8 26-5.8 39.3-5.8 13.3 0 26.6 2 39.3 5.8 12.8 3.9 24.9 9.6 36 16.9l10.9-15q-9.6-6.2-19.9-11-10.4-4.9-21.3-8.2-11-3.4-22.3-5.2-11.2-1.7-22.7-1.9z" />
<path class="a"
d="m206.6 379.9c-8.7 0-17.4-0.6-25.9-1.9-8.6-1.3-17.1-3.3-25.4-5.9-8.3-2.5-16.4-5.7-24.2-9.5-7.8-3.8-15.4-8.1-22.6-13l-13.7 18.6c8.2 5.4 16.9 10.3 25.8 14.5 9 4.2 18.2 7.7 27.6 10.6 9.5 2.9 19.1 5.1 28.9 6.7 9.8 1.5 19.6 2.3 29.5 2.4 9.9-0.1 19.8-0.9 29.5-2.4 9.8-1.6 19.4-3.8 28.9-6.7 9.4-2.9 18.7-6.4 27.6-10.6 9-4.2 17.6-9.1 25.9-14.5l-13.8-18.6c-7.2 4.9-14.7 9.2-22.6 13-7.8 3.8-15.9 7-24.2 9.5-8.3 2.6-16.8 4.6-25.3 5.9-8.6 1.3-17.3 1.9-26 1.9z" />
<path class="a"
d="m153.3 285.9l-8.3 11q6.8 4.4 14.2 7.7 7.3 3.4 15 5.7 7.7 2.4 15.7 3.7 8 1.3 16 1.5h0.1 1.2q8.1-0.2 16.1-1.5 8-1.3 15.7-3.7 7.7-2.3 15.1-5.7 7.3-3.3 14.1-7.7l-8.2-11q-6 3.7-12.5 6.6-6.4 2.9-13.1 4.9-6.8 2-13.8 3.2-6.9 1.1-14 1.3-7-0.2-14-1.3-7-1.2-13.7-3.2-6.8-2-13.2-4.9-6.4-2.9-12.4-6.6z" />
<path class="a"
d="m120.4 332.2q9.6 6.3 19.9 11.1 10.4 4.8 21.3 8.2 11 3.3 22.3 5.1 11.3 1.8 22.7 2 11.5-0.2 22.7-2 11.3-1.8 22.3-5.1 10.9-3.4 21.3-8.2 10.3-4.8 19.9-11.1l-10.9-15c-11.1 7.4-23.2 13.1-36 16.9-12.7 3.9-26 5.8-39.3 5.8-13.3 0-26.6-1.9-39.3-5.8-12.8-3.8-24.9-9.5-36-16.9z" />
<path fill-rule="evenodd" class="a" d="m118.8 231.6h-72.2v-146.5h-46.6v185.3h118.8v-38.8z" />
<path class="a"
d="m445.2 188c3.5-2.4 6.7-5.2 9.5-8.4 2.8-3.3 5.1-6.8 7-10.7 1.9-3.8 3.3-7.8 4.1-12 0.8-4.2 1.1-8.5 0.9-12.7 0.3-4.2 0.1-8.4-0.5-12.6-0.7-4.1-1.8-8.2-3.4-12-1.6-3.9-3.6-7.6-6-11.1-2.4-3.4-5.2-6.5-8.3-9.3q-5-3.7-10.5-6.5-5.6-2.8-11.5-4.6-6-1.8-12.2-2.5-6.1-0.7-12.3-0.4h-94.5v185h46v-66.5h29.5l36.3 66.5h51.7l-42.3-74.2q2.2-0.6 4.3-1.4 2.2-0.8 4.3-1.8 2-1.1 4-2.2 2-1.2 3.9-2.6zm-47.1-18.3h-44.6v-50.5h42c3.4-0.4 6.9 0 10.1 1.1 3.3 1.1 6.2 3 8.6 5.4 2.4 2.4 4.3 5.4 5.4 8.6 1.1 3.2 1.5 6.7 1.1 10.1 0.4 3.2 0.1 6.4-0.8 9.5-1 3.1-2.6 5.9-4.7 8.3-2.2 2.4-4.8 4.3-7.7 5.6-3 1.3-6.2 1.9-9.4 1.9z" />
<path class="a"
d="m206.7 274c29 0 73.5-13.8 73.5-74.1 0-60.2-48.8-71.5-73.5-71.5-29.7 0-73.7 14.8-73.7 72.8 0 58 44.7 72.8 73.7 72.8zm-0.4-114.7c14.6 0 33.3 7.9 33.3 41.3 0 33.3-16.4 42.6-32.5 42.6-16.2 0-33.4-10.2-33.4-42 0-31.7 17.1-41.9 32.6-41.9z" />
<path class="a"
d="m610.3 170c0-20.4-7.7-41.8-58.9-41.8-51.3 0-59.6 24.1-59.6 43.4h39c0.1-2.7 0.7-5.3 1.8-7.7 1.2-2.5 2.8-4.6 4.8-6.4 2-1.7 4.4-3 7-3.8 2.5-0.7 5.2-1 7.9-0.6 8.2 0 18.6 2.9 18.6 13 0 10-0.2 13.1-0.2 13.1-78.9 9.7-85.5 30-85.5 55.7 0 25.7 23.7 39.2 45.6 39.2 4 0.2 8 0 12-0.8 3.9-0.7 7.7-1.8 11.4-3.4 3.7-1.6 7.1-3.7 10.3-6.1 3.2-2.4 6.1-5.2 8.7-8.2q0.2 1.8 0.4 3.7 0.3 1.8 0.7 3.6 0.4 1.9 0.8 3.7 0.5 1.8 1.1 3.5h37.4q-0.9-2.5-1.6-5.1-0.6-2.6-1-5.2-0.4-2.6-0.6-5.3-0.2-2.7-0.1-5.3zm-40 46.8c0.1 3.8-0.6 7.4-2 10.9-1.4 3.4-3.5 6.5-6.2 9.1-2.6 2.6-5.8 4.6-9.3 6-3.4 1.3-7.1 1.8-10.9 1.7-2.3 0.3-4.6 0.1-6.8-0.6-2.2-0.7-4.3-1.9-6-3.5-1.7-1.5-3-3.5-3.8-5.7-0.9-2.1-1.2-4.5-1-6.8 0-3.8 3.1-13.2 21.5-17.8q3.1-0.7 6.2-1.5 3.1-0.8 6.1-1.7 3.1-0.9 6.1-1.9 3.1-1 6-2.1z" />
<path class="a"
d="m629.7 97.8v-0.1q0.6-0.1 1-0.4 0.5-0.2 0.9-0.6 0.3-0.5 0.5-0.9 0.2-0.5 0.3-1.1 0-0.4-0.1-0.8 0-0.4-0.2-0.7-0.2-0.4-0.4-0.7-0.3-0.3-0.6-0.6-0.5-0.3-1-0.5-0.5-0.2-1.1-0.3-0.6-0.1-1.2-0.2-0.5 0-1.1 0.1-0.6-0.1-1.2 0-0.7 0-1.3 0-0.6 0.1-1.2 0.2-0.6 0-1.2 0.1v12.8h3v-5.1h1.4c1.6 0 2.4 0.6 2.6 2q0.1 0.4 0.2 0.8 0.1 0.4 0.2 0.8 0.1 0.4 0.3 0.8 0.1 0.4 0.3 0.7h3.2q-0.2-0.3-0.3-0.7-0.2-0.4-0.3-0.8-0.1-0.4-0.2-0.8-0.1-0.4-0.1-0.9-0.1-0.5-0.3-1-0.2-0.4-0.5-0.8-0.3-0.5-0.7-0.8-0.4-0.3-0.9-0.5zm-3.5-0.9h-1.4v-3.7q0.2 0 0.4 0 0.2-0.1 0.4-0.1 0.2 0 0.5 0 0.2 0 0.4 0c1.8 0 2.7 0.8 2.7 1.9 0 1.4-1.3 1.9-3 1.9z" />
<path class="a"
d="m626.8 84.7c-5.2 0-9.8 3.3-11.8 8.1-1.9 4.8-0.7 10.3 3 13.9 3.7 3.7 9.3 4.7 14 2.7 4.8-2.1 7.9-6.8 7.9-11.9-0.1-1.7-0.4-3.4-1.1-5-0.6-1.6-1.6-3-2.8-4.2-1.2-1.2-2.7-2.1-4.3-2.7-1.5-0.7-3.2-1-4.9-0.9zm0 23c-4.1 0.2-8-2.2-9.8-6-1.7-3.8-0.9-8.3 2-11.3 2.9-3.1 7.3-4 11.2-2.4 3.9 1.5 6.4 5.3 6.4 9.5 0 1.3-0.2 2.6-0.7 3.9-0.4 1.2-1.1 2.3-2 3.3-1 0.9-2 1.7-3.3 2.2-1.2 0.5-2.5 0.8-3.8 0.8z" />
</svg>

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

12
img/meshcore.svg Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 134 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M3.277,0.053C2.829,0.053 2.401,0.41 2.321,0.851L0.013,13.623C-0.067,14.064 0.232,14.421 0.681,14.421L3.13,14.421C3.578,14.421 4.006,14.064 4.086,13.623L5.004,8.54L6.684,13.957C6.766,14.239 7.02,14.421 7.337,14.421L10.58,14.421C10.897,14.421 11.217,14.239 11.401,13.957L15.043,8.513L14.119,13.623C14.038,14.064 14.338,14.421 14.787,14.421L17.236,14.421C17.684,14.421 18.112,14.064 18.192,13.623L20.5,0.851C20.582,0.41 20.283,0.053 19.834,0.053L16.69,0.053C16.373,0.053 16.053,0.235 15.87,0.517L9.897,9.473C9.803,9.616 9.578,9.578 9.528,9.41L7.074,0.517C6.992,0.235 6.738,0.053 6.421,0.053L3.277,0.053Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M21.146,14.421C21.146,14.421 33.257,14.421 33.257,14.421C33.526,14.421 33.784,14.205 33.831,13.942L34.337,11.128C34.385,10.863 34.206,10.649 33.936,10.649L25.519,10.649C25.429,10.649 25.37,10.576 25.385,10.488L25.635,9.105C25.65,9.017 25.736,8.944 25.826,8.944L32.596,8.944C32.865,8.944 33.123,8.728 33.171,8.465L33.621,5.974C33.669,5.709 33.49,5.495 33.221,5.495L26.45,5.495C26.361,5.495 26.301,5.423 26.317,5.335L26.584,3.852C26.599,3.764 26.685,3.691 26.775,3.691L35.192,3.691C35.462,3.691 35.719,3.476 35.767,3.21L36.258,0.498C36.306,0.235 36.126,0.019 35.857,0.019L23.746,0.019C23.297,0.019 22.867,0.378 22.788,0.819L20.474,13.621C20.396,14.062 20.695,14.421 21.146,14.421Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M45.926,14.419L45.926,14.421L46.346,14.421C48.453,14.421 50.465,12.742 50.839,10.67L51.081,9.327C51.456,7.256 50.05,5.576 47.943,5.576L41.455,5.576C41.186,5.576 41.007,5.363 41.054,5.097L41.218,4.192C41.266,3.927 41.524,3.713 41.793,3.713L50.569,3.713C51.018,3.713 51.446,3.356 51.526,2.915L51.9,0.85C51.98,0.407 51.68,0.05 51.232,0.05L41.638,0.05C39.531,0.05 37.519,1.73 37.145,3.801L36.88,5.267C36.505,7.339 37.91,9.018 40.018,9.018L46.506,9.018C46.775,9.018 46.954,9.231 46.907,9.497L46.785,10.176C46.737,10.441 46.479,10.655 46.21,10.655L37.189,10.655C36.741,10.655 36.313,11.012 36.233,11.453L35.841,13.621C35.761,14.062 36.061,14.419 36.51,14.419L45.926,14.419Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M68.008,0.046C68.008,0.046 65.296,0.046 65.296,0.046C64.847,0.046 64.42,0.403 64.34,0.844L63.532,5.31C63.517,5.398 63.431,5.469 63.341,5.469L58.085,5.469C57.995,5.469 57.936,5.398 57.951,5.31L58.758,0.844C58.837,0.403 58.539,0.046 58.09,0.046L55.378,0.046C54.93,0.046 54.502,0.403 54.422,0.844L52.112,13.623C52.032,14.064 52.331,14.421 52.78,14.421L55.492,14.421C55.941,14.421 56.369,14.064 56.449,13.623L57.272,9.074C57.287,8.986 57.373,8.914 57.462,8.914L62.719,8.914C62.809,8.914 62.868,8.985 62.853,9.074L62.032,13.623C61.952,14.064 62.252,14.421 62.7,14.421L65.413,14.421C65.861,14.421 66.289,14.064 66.369,13.623L68.678,0.844C68.755,0.403 68.457,0.046 68.008,0.046Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M72.099,14.421C72.099,14.421 80.066,14.421 80.066,14.421C80.515,14.421 80.943,14.064 81.022,13.623L81.414,11.453C81.494,11.012 81.194,10.655 80.746,10.655L73.828,10.655C73.559,10.655 73.38,10.441 73.427,10.176L74.51,4.215C74.558,3.951 74.815,3.736 75.082,3.736L82,3.736C82.448,3.736 82.876,3.379 82.956,2.938L83.34,0.817C83.42,0.376 83.12,0.019 82.672,0.019L74.724,0.019C72.622,0.019 70.614,1.691 70.236,3.757L68.965,10.665C68.587,12.738 69.99,14.421 72.099,14.421Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M97.176,-0C97.176,0 88.882,0 88.882,0C86.775,0 84.763,1.68 84.389,3.751L83.139,10.67C82.765,12.741 84.169,14.421 86.277,14.421L94.571,14.421C96.678,14.421 98.69,12.741 99.064,10.67L100.314,3.751C100.689,1.68 99.284,-0 97.176,-0ZM94.798,10.178C94.75,10.443 94.492,10.657 94.223,10.657L87.978,10.657C87.709,10.657 87.529,10.443 87.577,10.178L88.659,4.192C88.707,3.927 88.964,3.713 89.234,3.713L95.477,3.713C95.747,3.713 95.926,3.927 95.878,4.192L94.798,10.178Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M101.284,14.421L103.995,14.421C104.443,14.421 104.871,14.065 104.951,13.624L105.43,10.97C105.446,10.882 105.531,10.81 105.621,10.81L108.902,10.806C109.064,10.806 109.2,10.886 109.267,11.018L110.813,14.035C110.992,14.392 111.319,14.434 112.303,14.419C112.88,14.426 113.756,14.382 115.169,14.382C115.623,14.382 115.902,13.907 115.678,13.51L113.989,10.569C113.945,10.491 113.993,10.386 114.086,10.34C115.39,9.707 116.423,8.477 116.681,7.055L117.27,3.785C117.646,1.713 116.242,0.033 114.134,0.033L103.884,0.033C103.436,0.033 103.008,0.39 102.928,0.831L100.616,13.623C100.536,14.064 100.836,14.421 101.284,14.421L101.284,14.421ZM106.73,3.791C106.745,3.703 106.831,3.631 106.921,3.631L112.225,3.631C112.626,3.631 112.891,3.949 112.821,4.343L112.431,6.494C112.359,6.885 111.979,7.204 111.58,7.204L106.276,7.204C106.186,7.204 106.127,7.133 106.142,7.043L106.73,3.791Z" style="fill:white;fill-rule:nonzero;"/>
<path d="M118.277,14.421C118.277,14.421 130.388,14.421 130.388,14.421C130.657,14.421 130.915,14.205 130.963,13.942L131.468,11.128C131.516,10.863 131.337,10.649 131.068,10.649L122.65,10.649C122.56,10.649 122.501,10.576 122.516,10.488L122.766,9.105C122.781,9.017 122.867,8.944 122.957,8.944L129.728,8.944C129.997,8.944 130.254,8.728 130.302,8.465L130.753,5.974C130.801,5.709 130.621,5.495 130.352,5.495L123.581,5.495C123.492,5.495 123.432,5.423 123.448,5.335L123.715,3.852C123.73,3.764 123.816,3.691 123.906,3.691L132.324,3.691C132.593,3.691 132.851,3.476 132.898,3.21L133.389,0.498C133.437,0.235 133.257,0.019 132.988,0.019L120.877,0.019C120.428,0.019 119.999,0.378 119.919,0.819L117.605,13.621C117.527,14.062 117.827,14.421 118.277,14.421Z" style="fill:white;fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

1
img/nano_g2.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

1
img/nrf52.svg Normal file
View file

@ -0,0 +1 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 342 300" width="342" height="300"><style>.a{fill:none}.b{opacity:.6;fill:#00a9ce}.c{opacity:.8;fill:#00a9ce}.d{opacity:.5;fill:#00a9ce}.e{fill:#00a9ce}</style><path class="a" d=""/><path class="b" d="m243.6 1.9c0 0-0.5 0 0 0-1.4 0.9-1.9 1.4-2.8 1.4l-72.6 41.7 84.3 48.8v-93.8c-2.8 0-6.1 0.9-8.9 1.9z"/><path class="c" d="m95.1 295.8l76.8-44.5-84.3-48.8v95.2c2.4 0.4 4.7-0.5 7.5-1.9z"/><path class="d" d="m338.7 246.1l-172.9-99.9-7.9-4.6-156-90.5c-1 2.8-1.9 5.6-1.9 8.9 0 0.5 0 1.4 0 1.9v179c0 5.7 3.3 10.8 8 13.6l71.2 41.8c2.3 1.4 5.1 1.8 7.9 1.8v-91.4-3.7l161.7 93.3c0.4 0.4 0.9 0.4 1.4 0.4h0.4c4.7 1.9 8.5 0.5 12.7-2.3l27.2-15.5 43.1-25.8c2.3-1.4 4.2-4.2 5.1-7z"/><path class="e" d="m339.7 128v-68c0-8.9-4.7-16.9-12.2-21.1l-62.8-36.1c-3.7-1.9-8-2.8-12.2-2.8v94.2l-141-82-15.9-8.9c-7.1-3.8-13.1-4.2-20.2-1 0 0-0.4 0-0.4 0.5-1 0.5-1.4 1-2.4 1.4l-24.8 14.5-35.6 20.7c-4.7 2.8-8.5 7-10.8 12.2l156 90 8 4.6 172.9 99.9c0.9-3.3 0.9-8.9 0.9-8.9v-109.2z"/></svg>

After

Width:  |  Height:  |  Size: 1,022 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

1
img/rak_11300.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

1
img/rak_4631.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 134 KiB

1
img/rak_wismesh_tag.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

1
img/rpi.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

1
img/rpi_picow.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

1
img/sensecap_solar.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

1
img/sensecap_t1000e.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

1
img/station_g2.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 58 KiB

1
img/thinknode_m1.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 41 KiB

1
img/thinknode_m2.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

1
img/thinknode_m3.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

1
img/thinknode_m5.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 42 KiB

1
img/thinknode_m6.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

1
img/wio_tracker_l1.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 46 KiB

1
img/xiao_esp32c3.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 62 KiB

1
img/xiao_esp32c6.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 78 KiB

1
img/xiao_esp32s3.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

1
img/xiao_nrf52.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

View file

@ -9,9 +9,14 @@
<link href="./css/flasher.css" rel="stylesheet"> <link href="./css/flasher.css" rel="stylesheet">
<script type="module" src="./flasher.js"></script> <script type="module" src="./flasher.js"></script>
</head> </head>
<body class="dark"> <body class="dark">
<div id="app" v-cloak> <div id="app" v-cloak>
<div class="flash-container"> <div class="snackbar" :class="snackbar.class" popover>
<i v-if="snackbar.icon">{{ snackbar.icon }}</i>
<span v-html="snackbar.text"></span>
</div>
<div class="flash-container" data-iframe-height>
<div v-if="flashing.active"> <div v-if="flashing.active">
<header> <header>
<nav> <nav>
@ -19,7 +24,7 @@
<span class="small">{{ selected.device.name }}</span> <span class="small">{{ selected.device.name }}</span>
<i>chevron_right</i> <i>chevron_right</i>
<i>{{ selected.firmware.icon || config.role[selected.firmware.role].icon }}</i> <i>{{ selected.firmware.icon || config.role[selected.firmware.role].icon }}</i>
<span class="small">{{ selected.firmware.title || config.role[selected.firmware.role].title }}</span> <span class="small strong">{{ selected.firmware.title || config.role[selected.firmware.role].title }}</span>
</nav> </nav>
</header> </header>
<article v-if="flashing.error"> <article v-if="flashing.error">
@ -27,31 +32,49 @@
<div class="max"> <div class="max">
<h6>Flashing failed!</h6> <h6>Flashing failed!</h6>
<p><span>{{ flashing.error }}</span></p> <p><span>{{ flashing.error }}</span></p>
<p><button @click="refresh()">Retry</button></p>
</div> </div>
</div> </div>
<nav class="small-margin">
<button @click="retry">
<i>bolt</i>
<span>Retry</span>
</button>
<button @click="close">
<i>arrow_back</i>
<span>Close</span>
</button>
</nav>
</article> </article>
<article v-else> <article v-else>
<div class="row"> <div class="row">
<div class="max" v-if="flashing.percentage < 100"> <div class="max" v-if="flashing.percent < 100">
<h6><progress class="circle small"></progress> Flashing...</h6> <h6><progress class="circle small"></progress> Flashing...</h6>
<p>Please do not disconnect the device</p> <p>Please do not disconnect the device</p>
</div> </div>
<div class="max" v-else> <div class="max" v-else>
<h6>Flashing complete!</h6> <h6>Flashing complete!</h6>
<p v-if="selected.firmware.role === 'roomServer' || selected.firmware.role === 'repeater'"> <nav class="small-margin">
Before you use <b><a target="_blank" href="https://config.meshcore.dev/">Repeater/Room server setup</a></b>, please restart your device. <button @click="openSerialGUI()" v-if="selected.firmware.role === 'roomServer' || selected.firmware.role === 'repeater'">
</p> <i>manufacturing</i>
<p> <span>Configure via USB</span>
<button @click="flasherCleanup()">Close</button> <div class="tooltip left max">Open GUI to configure Repeaters and Room servers via USB serial</div>
</p> </button>
<button @click="close">
<i>arrow_back</i>
<span>Close</span>
</button>
<button @click="retry">
<i>bolt</i>
<span>Flash Again</span>
</button>
</nav>
</div> </div>
</div> </div>
<div class="autoscroller"> <div class="autoscroller">
<pre class="term" v-if="flashing.log">{{ flashing.log }}</pre> <pre class="term" v-if="flashing.log">{{ flashing.log }}</pre>
</div> </div>
<nav> <nav>
<progress :value="flashing.percentage" max="100"></progress> <progress :value="flashing.percent" max="100"></progress>
</nav> </nav>
</article> </article>
</div> </div>
@ -62,9 +85,18 @@
<i>developer_board</i> <i>developer_board</i>
<a class="small" href="javascript:;" @click="stepBack">{{ selected.device.name }}</a> <a class="small" href="javascript:;" @click="stepBack">{{ selected.device.name }}</a>
<i>chevron_right</i> <i>chevron_right</i>
<i>{{ getRoleFwValue(selected.firmware, 'icon') }}</i>
<span class="small">{{ getRoleFwValue(selected.firmware, 'title') }}</span>
</nav> </nav>
<article class="no-margin">
<ul class="list">
<li>
<i>{{ getRoleFwValue(selected.firmware, 'icon') }}</i>&nbsp;
<div class="max">
<h6 class="small strong">{{ getRoleFwValue(selected.firmware, 'title') }} {{ getRoleFwValue(selected.firmware, 'subTitle') }}</h6>
<span class="text-wrap">{{ getRoleFwValue(selected.firmware, 'tooltip') }}</span>
</div>
</li>
</ul>
</article>
<nav class="version"> <nav class="version">
<div class="field label suffix border small"> <div class="field label suffix border small">
<select v-model="selected.version"> <select v-model="selected.version">
@ -75,8 +107,8 @@
<label>Version</label> <label>Version</label>
<i>arrow_drop_down</i> <i>arrow_drop_down</i>
</div> </div>
<div class="max small-padding pre" v-if="getSelFwValue('notes')"> <div class="max pre" v-if="getSelFwValue('notes')">
{{ getSelFwValue('notes') }} <b>Changelog:</b><br>{{ getSelFwValue('notes').replace('Change log:\r\n', '') }}
</div> </div>
</nav> </nav>
</header> </header>
@ -94,34 +126,54 @@
</ul> </ul>
<template v-if="selected.device.type === 'nrf52'"> <template v-if="selected.device.type === 'nrf52'">
<div class="small-space"></div> <div class="small-space"></div>
<button @click="dfuMode" :disabled="flashing.dfuComplete || !flashing.supported"> <button @click="dfuMode" :disabled="selected.nrfEraserFlashing || !flashing.supported">
<i>{{ flashing.dfuComplete ? 'check' : 'code' }}</i> <i>{{ flashing.dfuComplete ? 'check' : 'code' }}</i>
<span>{{ flashing.dfuComplete ? 'DFU mode active' : 'Enter DFU mode' }}</span> <span>{{ flashing.dfuComplete ? 'DFU mode active' : 'Enter DFU mode' }}</span>
<div class="tooltip right max" v-if="flashing.supported"> <div class="tooltip right max" v-if="flashing.supported">
Enter DFU mode - this mode enables you to flash your firmware. Enter DFU mode - this mode enables you to flash your firmware.
If you did not trigger the DFU mode manually, please click this button. If you did not trigger the DFU mode manually, please click this button.
</div> </div>
<div class="tooltip right max" v-else> <div class="tooltip right max" v-else>
Your browser doesn't support Web Serial API. Please use Chrome or Edge on Desktop Your browser doesn't support Web Serial API. Please use Chrome or Edge on Desktop
</div> </div>
</button> </button>
<button @click="nrfErase" :disabled="!flashing.supported || selected.nrfEraserFlashing || selected.nrfEraserFlashingPercent > 0">
<i v-if="selected.nrfEraserFlashingPercent === 100">check</i>
<progress v-else-if="selected.nrfEraserFlashing" class="circle small"></progress>
<i v-else>delete_forever</i>
<span v-if="selected.nrfEraserFlashingPercent === 100">Flash Erased!</span>
<span v-if="selected.nrfEraserFlashingPercent > 0">Flashing erase firmware: {{selected.nrfEraserFlashingPercent}}%</span>
<span v-else-if="selected.nrfEraserFlashing">Connecting...</span>
<span v-else>Erase Flash</span>
<div class="tooltip right max" v-if="flashing.supported">
Flash special firmware that will format your flash and erase all user data.<br>
Click <b>Enter DFU mode</b> before erasing, if you did not trigger the DFU mode manually
</div>
<div class="tooltip right max" v-else>
Your browser doesn't support Web Serial API. Please use Chrome or Edge on Desktop
</div>
</button>
</template> </template>
<div class="medium-space"></div> <div class="medium-space"></div>
<nav class="small-margin"> <nav class="small-margin">
<button @click="flashDevice" :disabled="!flashing.supported"> <template v-if="canFlash(selected.device)">
<i>bolt</i> <button @click="flashDevice" :disabled="selected.nrfEraserFlashing || !flashing.supported">
<span>Flash!</span> <i>bolt</i>
<div class="tooltip right max" v-if="flashing.supported"> <span>Flash!</span>
Upload the firmware into your device. Existing firwmare will get overwritten. <div class="tooltip right max" v-if="flashing.supported">
<span v-if="selected.device.type === 'nrf52'"> Upload the firmware into your device. Existing firwmare will get overwritten.
If you did not trigger DFU mode manually, use the <b>Enter DFU mode</b> before flashing <span v-if="selected.device.type === 'nrf52'">
</span> If you did not trigger DFU mode manually, use the <b>Enter DFU mode</b> before flashing
</div> </span>
<div class="tooltip right max" v-else> </div>
Your browser doesn't support Web Serial API. Please use Chrome or Edge on Desktop <div class="tooltip right max" v-else>
</div> Your browser doesn't support Web Serial API. Please use Chrome or Edge on Desktop
</button> </div>
<div class="max"></div> </button>
<div class="max"></div>
</template>
<button data-ui="#down" class="active" v-if="!getSelFwValue('customFile')"> <button data-ui="#down" class="active" v-if="!getSelFwValue('customFile')">
<i>download</i> <i>download</i>
<span>Download</span><i>arrow_drop_down</i> <span>Download</span><i>arrow_drop_down</i>
@ -129,6 +181,9 @@
<li v-for="file in getSelFwValue('files')"> <li v-for="file in getSelFwValue('files')">
<a data-ui="menu-selector" :href="getFirmwarePath(file)" download>{{ file.title }}</a> <a data-ui="menu-selector" :href="getFirmwarePath(file)" download>{{ file.title }}</a>
</li> </li>
<li v-if="selected.device.type === 'nrf52' && selected.device.erase">
<a data-ui="menu-selector" :href="`${config.staticPath}/${selected.device.erase.replace('.zip', '.uf2')}`" download>{{ selected.device.erase.replace('.zip', '.uf2') }}</a>
</li>
</menu> </menu>
<div class="tooltip left max">Download a copy of the firmware files for use with other flashers</div> <div class="tooltip left max">Download a copy of the firmware files for use with other flashers</div>
</button> </button>
@ -139,7 +194,7 @@
<nav> <nav>
<button class="circle transparent" @click="stepBack"><i>arrow_back</i></button> <button class="circle transparent" @click="stepBack"><i>arrow_back</i></button>
<i>developer_board</i> <i>developer_board</i>
<span>{{ selected.device.name }}</span> <span class="small strong">{{ selected.device.name }}</span>
</nav> </nav>
<nav class="no-margin"> <nav class="no-margin">
<h6 class="small max">Choose role</h6> <h6 class="small max">Choose role</h6>
@ -161,7 +216,9 @@
<header> <header>
<nav> <nav>
<i>bolt</i> <i>bolt</i>
<h5 class="small max">MeshCore flasher</h5> <h5 class="small max logo-top">
<img src="img/meshcore.svg"> <span>flasher</span>
</h5>
<button class="transparent hide-mobile" @click="openSerialGUI()"> <button class="transparent hide-mobile" @click="openSerialGUI()">
<i>manufacturing</i> <i>manufacturing</i>
<span>Repeater Setup</span> <span>Repeater Setup</span>
@ -177,24 +234,54 @@
<h6 class="small max">Choose device</h6> <h6 class="small max">Choose device</h6>
</nav> </nav>
</header> </header>
<div class="field label prefix suffix border w-100 no-margin">
<i>search</i>
<input type="text" placeholder=" " v-model="deviceFilterText" autofocus>
<label>Filter</label>
</div>
<ul class="list border"> <ul class="list border">
<li v-for="device in config.device"> <li v-for="(deviceGroup, type) in devices">
<button class="transparent" @click="selected.device = device"> <details open>
<i>developer_board</i> <summary style="background-color: var(--surface-container)">
<span>{{ device.name }}</span> <div>
<div class="tooltip right max" v-if="device.tooltip" v-html="device.tooltip"></div> <strong>{{ type === 'community' ? 'Community Firmware' : 'Ripple Firmware' }}</strong>
</button> <div class="tooltip right max">
<template v-if="type === 'ripple'">
Freemium firmware provided by <a class="inverse-link" target="_blank" href="https://buymeacoffee.com/ripplebiz">Ripple Radios</a>
</template>
<template v-else>
Open Source <a class="inverse-link" target="_blank" href="https://github.com/meshcore-dev/MeshCore">Community firmware</a>
</template>
</div>
</div>
</summary>
<ul class="list border">
<li v-for="device in deviceGroup">
<template v-if="device.firmware.length > 0">
<button class="device-select transparent" @click="selected.device = device">
<img class="icon" :title="device.type" :src="`/img/${device.type}.svg`" v-if="device.type !== 'noflash'">
<i v-else>developer_board</i>
<span>{{ device.name }}</span>
<div class="tooltip right max" v-if="device.tooltip" v-html="device.tooltip"></div>
<div class="max"></div>
</button>
<div class="max"></div>
<img class="icon" :src="device.icon" v-if="device.icon" >
</template>
</li>
</ul>
</details>
</li> </li>
<li> <li>
<button class="transparent"> <label>
<i>unknown_document</i> <i>unknown_document</i>&nbsp;
<span>Custom Firmware</span> <strong style="font-size: small;">Custom Firmware</strong>
<div class="tooltip right max"> <div class="tooltip right max">
Flash custom firmware file from your computer. Flash custom firmware file from your computer.
Flasher supports <b>.bin</b> files for esp32 devices and OTA <b>.zip</b> files for nRF52 devices. Flasher supports <b>.bin</b> files for esp32 devices and OTA <b>.zip</b> files for nRF52 devices.
</div> </div>
<input type="file" accept=".zip,.bin" @change="customFirmwareLoad"> <input type="file" accept=".zip,.bin" @change="customFirmwareLoad">
</button> </label>
</li> </li>
</ul> </ul>
</div> </div>
@ -209,7 +296,7 @@
<h6 class="small max">Serial Console</h6> <h6 class="small max">Serial Console</h6>
</nav> </nav>
</header> </header>
<pre class="console" @click="consoleEditBox.focus()" ref="consoleWindow"> <pre class="console" ref="consoleWindow" tabindex="0" @mouseup="consoleMouseUp">
<code>{{ serialCon.content }}</code> <code>{{ serialCon.content }}</code>
<div class="holder"> <div class="holder">
<span>&gt;</span> <span>&gt;</span>
@ -217,8 +304,7 @@
</div> </div>
</pre> </pre>
</div> </div>
</div> </div>
<script src="./lib/iframeResizer.contentWindow.min.js"></script>
</body> </body>
<script type="module" src="https://cdn.jsdelivr.net/npm/@open-iframe-resizer/core@latest/dist/index.js"></script>
</html> </html>

View file

@ -1,6 +1,7 @@
function delay(msecs) { function delay(msecs) {
return new Promise((resolve) => setTimeout(resolve, msecs)); return new Promise((resolve) => setTimeout(resolve, msecs));
} }
class LineBreakTransformer { class LineBreakTransformer {
chunks = ''; chunks = '';
port = null; port = null;

View file

@ -80,7 +80,30 @@ function calcCrc16(data, crc = 0xFFFF) {
} }
function sleep(milliseconds) { function sleep(milliseconds) {
return new Promise((resolve) => setTimeout(resolve, milliseconds)) return new Promise((resolve) => {
const startTime = performance.now();
const targetTime = startTime + milliseconds;
function checkTime() {
if (performance.now() >= targetTime) {
resolve();
} else {
requestAnimationFrame(checkTime);
}
}
const initialDelay = Math.max(0, milliseconds - 16); // Wake up ~1 frame early to start polling
if (initialDelay > 0) {
setTimeout(() => {
// Now start the more precise polling
checkTime();
}, initialDelay);
} else {
// If the desired sleep is very short, go straight to polling
checkTime();
}
});
} }
// --- HciPacket Class (adapted from dfu/dfu_transport_serial.py) --- // --- HciPacket Class (adapted from dfu/dfu_transport_serial.py) ---
@ -128,6 +151,29 @@ export class Dfu {
this.eraseBeforeUpdate = eraseBeforeUpdate; // Store the erase flag this.eraseBeforeUpdate = eraseBeforeUpdate; // Store the erase flag
} }
getReader() {
const reader = this.port.readable.getReader();
return {
read() {
return new Promise((resolve, reject) => {
const timeoutHandle = setTimeout(() => {
reader.releaseLock();
reject(new Error("Read timeout"));
}, DEFAULT_SERIAL_PORT_TIMEOUT * 1000 * 5)
reader.read().then(result => {
clearTimeout(timeoutHandle);
resolve(result);
});
});
},
releaseLock() {
return reader.releaseLock();
}
}
}
async sendPacket(pkt) { async sendPacket(pkt) {
if (!this.port || !this.port.writable) { if (!this.port || !this.port.writable) {
throw new Error("Serial port not open or not writable."); throw new Error("Serial port not open or not writable.");
@ -149,7 +195,7 @@ export class Dfu {
throw new Error("Serial port not open or not readable."); throw new Error("Serial port not open or not readable.");
} }
const reader = this.port.readable.getReader(); const reader = this.getReader();
let buffer = []; let buffer = [];
let c0Count = 0; let c0Count = 0;
@ -169,13 +215,12 @@ export class Dfu {
} }
} }
} }
if (Date.now() - startTime > DEFAULT_SERIAL_PORT_TIMEOUT * 1000 * 5) { // Increased timeout for safety
HciPacket.sequenceNumber = 0; // Reset sequence number on timeout.
throw new Error("Timeout waiting for ACK.");
}
} }
} finally { }
catch(e) {
HciPacket.sequenceNumber = 0;
}
finally {
reader.releaseLock(); reader.releaseLock();
} }
// Decode SLIP // Decode SLIP
@ -216,7 +261,7 @@ export class Dfu {
} else if (data[i] === 0xC0) { } else if (data[i] === 0xC0) {
// Ignore 0xC0 (start/end of packet) // Ignore 0xC0 (start/end of packet)
} }
else { else {
result.push(data[i]); result.push(data[i]);
} }
i++; i++;

File diff suppressed because one or more lines are too long