2949 lines
137 KiB
HTML
2949 lines
137 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
|
|
|
<title>RNode Flasher (DIY)</title>
|
|
|
|
<script src="./js/rnode.js"></script>
|
|
<script src="./js/nrf52_dfu_flasher.js"></script>
|
|
<script src="./js/zip.min.js"></script>
|
|
|
|
<!-- tailwind css -->
|
|
<script src="./js/tailwindcss/tailwind-v3.4.3-forms-v0.5.7.js"></script>
|
|
|
|
<!-- vue js -->
|
|
<script src="./js/vue@3.4.26/dist/vue.global.prod.js"></script>
|
|
|
|
<script src="./js/crypto-js@3.9.1-1/core.js"></script>
|
|
<script src="./js/crypto-js@3.9.1-1/md5.js"></script>
|
|
|
|
</head>
|
|
<body class="bg-[#1e293b]">
|
|
|
|
<div id="app" class="space-y-2 p-3">
|
|
|
|
<!-- header -->
|
|
<div class="flex border bg-[#1e293b] p-3 rounded shadow">
|
|
<div class="mr-3">
|
|
<img src="reticulum_logo_512.png" class="w-14 h-14"/>
|
|
</div>
|
|
<div class="my-auto">
|
|
<div class="font-bold text-white">RNode Flasher</div>
|
|
<div class="text-sm text-gray-300">Supports factory and DIY devices. Originally by <a target="_blank" href="https://liamcottle.com" class="text-blue-500 hover:underline">Liam Cottle</a></div>
|
|
<div class="text-sm text-gray-300">Maintained by <a target="_blank" href="https://t.me/TheLT#" class="text-blue-500 hover:underline">Nickie Deuxyeux</a></div>
|
|
<div class="text-sm text-gray-300">Firmware archive (binaries) available <a target="_blank" href="/firmware/" class="text-blue-500 hover:underline">here</a></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex border bg-[#1e293b] p-3 rounded shadow">
|
|
<div class="my-auto">
|
|
<div class="font-bold text-white">Current version: 1.86</div>
|
|
<div class="text-sm text-gray-300">Updated on: 2026-Apr-26</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="border bg-[#1e293b] rounded shadow">
|
|
|
|
<div class="border-b px-2 py-1 text-white">
|
|
1. Select your device
|
|
</div>
|
|
|
|
<div class="p-3">
|
|
|
|
<div class="mb-2">
|
|
<label class="block text-gray-300 mb-1">Product</label>
|
|
<select v-model="selectedProduct" class="w-full bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block px-2">
|
|
<option :value="null" disabled>Select a Product</option>
|
|
<option v-for="product of products" :value="product">{{ product.name }}</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div v-if="selectedProduct" class="mb-2">
|
|
<label class="block text-gray-300 mb-1">Model</label>
|
|
<select v-model="selectedModel" class="w-full bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block px-2">
|
|
<option :value="null" disabled>Select a Model</option>
|
|
<option v-if="selectedProduct" v-for="model of selectedProduct.models" :value="model">{{ model.name }}</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- button to enter dfu mode on PLATFORM_NRF52 -->
|
|
<div v-if="selectedProduct?.platform === 0x70" class="p-3 border-t space-x-1">
|
|
<button @click="enterDfuMode" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Enter DFU Mode
|
|
</button>
|
|
|
|
<button @click="downloadEraseUF2" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Download Erase UF2
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border bg-[#1e293b] rounded shadow">
|
|
|
|
<div class="border-b px-2 py-1 text-white">
|
|
2. Select firmware to flash (.zip)
|
|
</div>
|
|
|
|
<div class="p-3">
|
|
|
|
<div class="mb-2">
|
|
<input ref="file" type="file" class="bg-gray-700 text-white"/>
|
|
</div>
|
|
|
|
<div v-if="!isFlashing" class="space-x-1">
|
|
<button @click="flash" :disabled="isFlashing" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Flash Now
|
|
</button>
|
|
<button @click="eraseEsp32" :disabled="isFlashing" class="border border-red-500 px-2 bg-red-700 hover:bg-red-600 rounded text-white ml-2">
|
|
Erase Flash
|
|
</button>
|
|
</div>
|
|
|
|
<div v-else>
|
|
<span v-if="flashingProgress > 0" class="text-white">Flashing: {{flashingProgress}}%</span>
|
|
<span v-else class="text-white">Flashing: please wait...</span>
|
|
<div class="mt-1 w-[200px] overflow-hidden rounded-full bg-gray-700">
|
|
<div class="h-2 rounded-full bg-blue-600" :style="{ 'width': `${flashingProgress}%`}"></div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="border-t px-2 py-1 text-sm text-white">
|
|
<div v-if="selectedProduct && selectedModel && recommendedFirmwareFilename">
|
|
<span>Classic Firmware Download</span>
|
|
<span v-if="selectedProduct && selectedModel && recommendedFirmwareFilename">: <a target="_blank" :href="`firmware/classic/latest/${recommendedFirmwareFilename}`" class="text-blue-500 hover:underline">{{ recommendedFirmwareFilename }}</a> </span>
|
|
</div>
|
|
<div v-if="selectedProduct && selectedModel && FirmwareRepoClassic">
|
|
<span>Classic Firmware Information</span>
|
|
<span>: <a target="_blank" :href="`${FirmwareRepoClassic}`" class="text-blue-500 hover:underline">{{ FirmwareRepoClassic }}</a></span>
|
|
</div>
|
|
<div v-if="selectedProduct && selectedModel && recommendedFirmwareFilenameMicroReticulum">
|
|
<span>MicroReticulum Firmware Download</span>
|
|
<span v-if="selectedProduct && selectedModel && recommendedFirmwareFilenameMicroReticulum">: <a target="_blank" :href="`firmware/microreticulum/latest/${recommendedFirmwareFilenameMicroReticulum}`" class="text-blue-500 hover:underline">{{ recommendedFirmwareFilenameMicroReticulum }}</a> </span>
|
|
</div>
|
|
<div v-if="selectedProduct && selectedModel && FirmwareRepoMicroReticulum">
|
|
<span>MicroReticulum Firmware Information</span>
|
|
<span>: <a target="_blank" :href="`${FirmwareRepoMicroReticulum}`" class="text-blue-500 hover:underline">{{ FirmwareRepoMicroReticulum }}</a></span>
|
|
</div>
|
|
<div v-if="selectedProduct && selectedModel && recommendedFirmwareFilenameCustom">
|
|
<span>Custom Firmware Download</span>
|
|
<span v-if="selectedProduct && selectedModel && recommendedFirmwareFilenameCustom">: <a target="_blank" :href="`firmware/${recommendedFirmwareFilenameCustom}`" class="text-blue-500 hover:underline">{{ recommendedFirmwareFilenameCustom }}</a> </span>
|
|
</div>
|
|
<div v-if="selectedProduct && selectedModel && FirmwareRepoCustom">
|
|
<span>Custom Firmware Information</span>
|
|
<span>: <a target="_blank" :href="`${FirmwareRepoCustom}`" class="text-blue-500 hover:underline">{{ FirmwareRepoCustom }}</a></span>
|
|
</div>
|
|
<div v-if="selectedProduct && selectedModel && HardwareInfo">
|
|
<span>Hardware Information</span>
|
|
<span>: <a target="_blank" :href="`${HardwareInfo}`" class="text-blue-500 hover:underline">{{ HardwareInfo }}</a></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border-t px-2 py-1 text-sm text-white">
|
|
<div>Common error messages</div>
|
|
<div>• Missing Config: You need to provision the EEPROM in step 3.</div>
|
|
<div>• Firmware Corrupt: You need to set the firmware hash in step 4.</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="border bg-[#1e293b] rounded shadow">
|
|
|
|
<div class="border-b px-2 py-1 text-white">
|
|
3. Provision EEPROM (sets device info, checksum and blank signature)
|
|
</div>
|
|
|
|
<div class="p-3">
|
|
<button v-if="!isProvisioning" @click="provision" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Provision
|
|
</button>
|
|
<div v-else class="flex items-center space-x-1">
|
|
<div class="mr-1">
|
|
<svg class="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="text-white">Provisioning: please wait...</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="border bg-[#1e293b] rounded shadow">
|
|
|
|
<div class="border-b px-2 py-1 text-white">
|
|
4. Set Firmware Hash (uses hash from board, will fix later)
|
|
</div>
|
|
|
|
<div class="p-3">
|
|
<button v-if="!isSettingFirmwareHash" @click="setFirmwareHash" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Set Firmware Hash
|
|
</button>
|
|
<div v-else class="flex items-center space-x-1">
|
|
<div class="mr-1">
|
|
<svg class="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="text-white">Setting Firmware Hash: please wait...</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="border bg-[#1e293b] rounded shadow">
|
|
|
|
<div class="border-b px-2 py-1 text-white">
|
|
5. Done
|
|
</div>
|
|
|
|
<div class="px-2 py-1 text-sm text-white">
|
|
<div>• If you made it this far, and all previous steps were successful, your RNode should be ready to use.</div>
|
|
<div>• To use RNode with <a target="_blank" href="https://github.com/liamcottle/reticulum-meshchat" class="text-blue-500 hover:underline">MeshChat</a>, you will need to add an <u>RNodeInterface</u> in the <u>Interfaces → Add Interface</u> page.</div>
|
|
<div>• To use RNode with <a target="_blank" href="https://github.com/markqvist/Sideband/" class="text-blue-500 hover:underline">Sideband</a>, you will need to configure it in <u>Hardware → RNode</u> and enable <u>Connectivity → Connect via RNode</u>.</div>
|
|
<div>• You must restart MeshChat and Sideband for interface setting changes to take effect, otherwise nothing will happen!</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="border bg-[#1e293b] rounded shadow">
|
|
|
|
<div class="border-b px-2 py-1 text-white">
|
|
Advanced Tools
|
|
</div>
|
|
|
|
<div class="p-3">
|
|
|
|
<div class="space-x-1">
|
|
<button @click="detect" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Detect RNode
|
|
</button>
|
|
<button @click="reboot" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Reboot RNode
|
|
</button>
|
|
<button @click="readDisplay" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Read Display
|
|
</button>
|
|
<button @click="dumpEeprom" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Dump EEPROM
|
|
</button>
|
|
<button @click="wipeEeprom" class="border border-gray-500 px-2 bg-red-900 hover:bg-red-800 rounded text-white">
|
|
Wipe EEPROM
|
|
</button>
|
|
</div>
|
|
|
|
<!-- show rnode display if available -->
|
|
<div v-if="rnodeDisplayImage">
|
|
<img :src="rnodeDisplayImage" class="h-28"/>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="border bg-[#1e293b] rounded shadow">
|
|
|
|
<div class="border-b px-2 py-1 text-white">
|
|
Configure Bluetooth (optional)
|
|
</div>
|
|
|
|
<div class="border-b px-2 py-1 text-sm text-gray-300">
|
|
<div>• Bluetooth is not supported on all devices.</div>
|
|
<div>• Some devices use Bluetooth Classic, and some use BLE (Bluetooth Low Energy)</div>
|
|
<div>• Put the RNode into Bluetooth Pairing mode, then connect to it from Android Bluetooth settings.</div>
|
|
<div>• Once you have initiated a pair request from Android, a PIN should show on the RNode display.</div>
|
|
<div>• In Sideband you will need to enable <u>Connect using Bluetooth</u> in <u>Hardware → RNode</u>.</div>
|
|
<div>• If your device uses BLE you will also need to enable <u>Device requires BLE</u> in <u>Hardware → RNode</u>.</div>
|
|
<div>• Don't forget to restart Sideband for the setting changes to take effect, otherwise nothing will happen!</div>
|
|
</div>
|
|
|
|
<div class="p-3">
|
|
<div class="space-x-1">
|
|
<button @click="enableBluetooth" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Enable
|
|
</button>
|
|
<button @click="disableBluetooth" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Disable
|
|
</button>
|
|
<button @click="startBluetoothPairing" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Start Pairing
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="border bg-[#1e293b] rounded shadow">
|
|
|
|
<div class="border-b px-2 py-1 text-white">
|
|
Configure TNC Mode / Transport Mode
|
|
</div>
|
|
|
|
<div class="border-b px-2 py-1 text-sm text-gray-300">
|
|
<div>• TNC mode allows an RNode to be used as a KISS compatible TNC over the Serial Port.</div>
|
|
<div>• This mode makes it usable with amateur radio software that can talk to a KISS TNC over a serial port.</div>
|
|
<div>• You must leave TNC mode disabled when using RNode with apps like MeshChat or Sideband.</div>
|
|
</div>
|
|
|
|
<div class="p-3">
|
|
|
|
<div class="flex mb-1 space-x-1">
|
|
<div class="min-w-[125px] my-auto text-right text-white">Frequency (Hz)</div>
|
|
<input v-model="configFrequency" type="number" class="w-full sm:w-min sm:min-w-[250px] bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block px-2">
|
|
</div>
|
|
<div class="flex mb-1 space-x-1">
|
|
<div class="min-w-[125px] my-auto text-right text-white">Bandwidth</div>
|
|
<select v-model="configBandwidth" class="w-full sm:w-min sm:min-w-[250px] bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block pl-2 pr-8">
|
|
<option v-for="bandwidth in RNodeInterfaceDefaults.bandwidths" :value="bandwidth">{{ bandwidth / 1000 }} KHz</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex mb-1 space-x-1">
|
|
<div class="min-w-[125px] my-auto text-right text-white">Tx Power (dBm)</div>
|
|
<input v-model="configTxPower" type="number" class="w-full sm:w-min sm:min-w-[250px] bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block px-2">
|
|
</div>
|
|
<div class="flex mb-1 space-x-1">
|
|
<div class="min-w-[125px] my-auto text-right text-white">Spreading Factor</div>
|
|
<select v-model="configSpreadingFactor" class="w-full sm:w-min sm:min-w-[250px] bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block pl-2 pr-8">
|
|
<option v-for="spreadingfactor in RNodeInterfaceDefaults.spreadingfactors" :value="spreadingfactor">{{ spreadingfactor }}</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex mb-1 space-x-1">
|
|
<div class="min-w-[125px] my-auto text-right text-white">Coding Rate</div>
|
|
<select v-model="configCodingRate" class="w-full sm:w-min sm:min-w-[250px] bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block pl-2 pr-8">
|
|
<option v-for="codingrate in RNodeInterfaceDefaults.codingrates" :value="codingrate">{{ codingrate }}</option>
|
|
</select>
|
|
</div>
|
|
<div class="space-x-1">
|
|
<button @click="enableTncMode" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Enable
|
|
</button>
|
|
<button @click="disableTncMode" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Disable
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="border-b px-2 py-1 text-white">
|
|
Configure Display (optional)
|
|
</div>
|
|
|
|
<div class="p-3 space-y-2">
|
|
<div class="flex space-x-1">
|
|
<div class="my-auto text-white">Rotation</div>
|
|
<button @click="setDisplayRotation(0)" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
0
|
|
</button>
|
|
<button @click="setDisplayRotation(1)" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
1
|
|
</button>
|
|
<button @click="setDisplayRotation(2)" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
2
|
|
</button>
|
|
<button @click="setDisplayRotation(3)" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
3
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex mb-1 space-x-1 mt-2">
|
|
<div class="my-auto text-right text-gray-300">Intensity</div>
|
|
<input v-model.number="displayIntensity" type="number" min="0" max="255" class="w-full sm:w-min bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block px-2">
|
|
<div class="space-x-1 flex items-center">
|
|
<button @click="setDisplayIntensity(displayIntensity)" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Set Display Intensity
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mb-1 space-x-1 mt-2">
|
|
<div class="my-auto text-right text-gray-300">Timeout (sec)</div>
|
|
<input v-model.number="displayTimeout" type="number" min="0" max="255" class="w-full sm:w-min bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block px-2">
|
|
<div class="space-x-1 flex items-center">
|
|
<button @click="setDisplayTimeout(displayTimeout)" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Set Display Timeout
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex space-x-1">
|
|
<div class="my-auto text-white">Reconditioning</div>
|
|
<button @click="startDisplayReconditioning" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Start
|
|
</button>
|
|
<button @click="reboot" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Stop
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border bg-[#1e293b] rounded shadow">
|
|
|
|
<div class="border-b px-2 py-1 text-white">
|
|
Configure WiFi (optional)
|
|
</div>
|
|
|
|
<div class="border-b px-2 py-1 text-sm text-gray-300">
|
|
<div>• WiFi configuration is supported only on ESP32 devices.</div>
|
|
<div>• Simultaneous operation of Bluetooth and WiFi is unsupported.</div>
|
|
<div>• For static IP configuration both the address and the netmask need to be set.</div>
|
|
<div>• Unset both IP and Netmask to use DHCP.</div>
|
|
</div>
|
|
|
|
<div class="flex mb-1 space-x-1 mt-2">
|
|
<div class="min-w-[125px] my-auto text-right text-gray-300">Mode</div>
|
|
<label class="text-white">
|
|
<input type="radio" v-model="wifiMode" :value="0" class="mr-1 bg-gray-500 border-gray-100 focus:ring-blue-100">
|
|
OFF
|
|
</label>
|
|
<label class="text-white">
|
|
<input type="radio" v-model="wifiMode" :value="1" class="mr-1 bg-gray-500 border-gray-100 focus:ring-blue-100">
|
|
STATION
|
|
</label>
|
|
<label class="text-white">
|
|
<input type="radio" v-model="wifiMode" :value="2" class="mr-1 bg-gray-500 border-gray-100 focus:ring-blue-100">
|
|
AP
|
|
</label>
|
|
<div class="space-x-1 flex items-center">
|
|
<button @click="setWiFiMode" :disabled="wifiMode === null" class="border px-2 rounded text-white" :class="wifiMode === null ? 'border-gray-600 bg-gray-800 text-gray-600 cursor-not-allowed' : 'border-gray-500 bg-gray-700 hover:bg-gray-600'">
|
|
Set Mode
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mb-1 space-x-1 mt-2">
|
|
<div class="min-w-[125px] my-auto text-right text-gray-300">Channel</div>
|
|
<input v-model.number="wifiChannel" type="number" min="1" max="14" class="w-full sm:w-min sm:min-w-[250px] bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block px-2">
|
|
<div class="space-x-1 flex items-center">
|
|
<button @click="setWiFiChannel" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Set Channel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mb-1 space-x-1 mt-2">
|
|
<div class="min-w-[125px] my-auto text-right text-gray-300">SSID</div>
|
|
<input v-model="wifiSSID" type="text" placeholder="SSID" class="w-full sm:w-min sm:min-w-[250px] bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block px-2">
|
|
<div class="space-x-1 flex items-center">
|
|
<button @click="setWiFiSSID" :disabled="!isSsidValid" :class="isSsidValid ? 'border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white' : 'border border-gray-600 px-2 bg-gray-800 text-gray-500 cursor-not-allowed rounded'">
|
|
Set SSID
|
|
</button>
|
|
<button @click="unsetWiFiSSID" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Unset SSID
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mb-1 space-x-1 mt-2">
|
|
<div class="min-w-[125px] my-auto text-right text-gray-300">PSK (Password)</div>
|
|
<input v-model="wifiPSK" type="password" placeholder="PSK (8 characters minimum)" class="w-full sm:w-min sm:min-w-[250px] bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block px-2">
|
|
|
|
<div class="space-x-1 flex items-center">
|
|
<!-- Tooltip wrapper -->
|
|
<div class="relative inline-block group">
|
|
<button
|
|
@click="setWiFiPSK"
|
|
:disabled="!isPskValid"
|
|
:class="isPskValid
|
|
? 'border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white'
|
|
: 'border border-gray-600 px-2 bg-gray-800 text-gray-500 cursor-not-allowed rounded'"
|
|
>
|
|
Set PSK
|
|
</button>
|
|
<!-- Tooltip -->
|
|
<div
|
|
v-if="!isPskValid"
|
|
class="absolute left-1/2 -translate-x-1/2 -top-8
|
|
hidden group-hover:block
|
|
bg-gray-900 text-xs text-red-400
|
|
px-2 py-1 rounded border border-gray-600
|
|
whitespace-nowrap"
|
|
>
|
|
Password must be at least 8 characters.
|
|
</div>
|
|
</div>
|
|
<button
|
|
@click="unsetWiFiPSK"
|
|
class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white"
|
|
>
|
|
Unset PSK
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mb-1 space-x-1 mt-2">
|
|
<div class="min-w-[125px] my-auto text-right text-gray-300">IP Address</div>
|
|
<input v-model="wifiIP" type="text" placeholder="192.168.1.100" class="w-full sm:w-min sm:min-w-[250px] bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block px-2">
|
|
<div class="space-x-1 flex items-center">
|
|
<button @click="setWiFiIP" :disabled="!isIpValid" :class="isIpValid ? 'border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white' : 'border border-gray-600 px-2 bg-gray-800 text-gray-500 cursor-not-allowed rounded'">
|
|
Set IP
|
|
</button>
|
|
<button @click="unsetWiFiIP" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Unset IP
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mb-1 space-x-1 mt-2 items-center">
|
|
<div class="min-w-[125px] my-auto text-right text-gray-300">Netmask</div>
|
|
<input v-model="wifiNM" type="text" placeholder="255.255.255.0" class="w-full sm:w-min sm:min-w-[250px] bg-gray-700 border border-gray-600 text-gray-200 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block px-2">
|
|
<div class="space-x-1 flex items-center">
|
|
<button @click="setWiFiNM" :disabled="!isNetmaskValid" :class="isNetmaskValid ? 'border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white' : 'border border-gray-600 px-2 bg-gray-800 text-gray-500 cursor-not-allowed rounded'">
|
|
Set Netmask
|
|
</button>
|
|
<button @click="unsetWiFiNM" class="border border-gray-500 px-2 bg-gray-700 hover:bg-gray-600 rounded text-white">
|
|
Unset Netmask
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- setup web-serial-polyfill -->
|
|
<script type="module">
|
|
import { serial } from "./js/web-serial-polyfill@1.0.15/dist/serial.js";
|
|
if(!navigator.serial && navigator.usb){
|
|
navigator.serial = serial;
|
|
}
|
|
</script>
|
|
|
|
<!-- setup esptool-js -->
|
|
<script type="module">
|
|
import { ESPLoader, Transport } from "./js/esptool-js@0.5.6/bundle.js";
|
|
window.ESPLoader = ESPLoader;
|
|
window.Transport = Transport;
|
|
</script>
|
|
|
|
<script>
|
|
Vue.createApp({
|
|
data() {
|
|
return {
|
|
|
|
rnode: null,
|
|
|
|
isFlashing: false,
|
|
flashingProgress: 0,
|
|
|
|
isProvisioning: false,
|
|
isSettingFirmwareHash: false,
|
|
|
|
rnodeDisplayImage: null,
|
|
|
|
selectedProduct: null,
|
|
selectedModel: null,
|
|
products: [
|
|
{
|
|
name: "Heltec LoRa32 v2",
|
|
id: ROM.PRODUCT_H32_V2,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_C4,
|
|
name: "433 MHz",
|
|
},
|
|
{
|
|
id: ROM.MODEL_C9,
|
|
name: "868 MHz / 915 MHz / 923 MHz",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_heltec32v2.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_heltec32v2.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://heltec.org/project/wifi-lora-32v2/",
|
|
flash_config: {
|
|
flash_size: "8MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_heltec32v2.boot_app0",
|
|
"0x1000": "rnode_firmware_heltec32v2.bootloader",
|
|
"0x10000": "rnode_firmware_heltec32v2.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_heltec32v2.partitions",
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "Heltec LoRa32 v3",
|
|
id: ROM.PRODUCT_H32_V3,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_C5,
|
|
name: "433 MHz",
|
|
},
|
|
{
|
|
id: ROM.MODEL_CA,
|
|
name: "868 MHz / 915 MHz / 923 MHz",
|
|
},
|
|
{
|
|
id: ROM.MODEL_CA,
|
|
name: "RTNode Firmware",
|
|
firmware_filename_classic: "rtnode_heltec_v3.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RTNode-HeltecV4",
|
|
hardware_info: "https://heltec.org/project/wifi-lora-32-v3/",
|
|
flash_config: {
|
|
flash_size: "16MB",
|
|
flash_files: {
|
|
"0xe000": "boot_app0.bin",
|
|
"0x0": "bootloader.bin",
|
|
"0x10000": "firmware.bin",
|
|
"0x8000": "partitions.bin",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_heltec32v3.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_heltec32v3.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://heltec.org/project/wifi-lora-32-v3/",
|
|
flash_config: {
|
|
flash_size: "8MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_heltec32v3.boot_app0",
|
|
"0x0": "rnode_firmware_heltec32v3.bootloader",
|
|
"0x10000": "rnode_firmware_heltec32v3.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_heltec32v3.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Heltec LoRa32 v4",
|
|
id: ROM.PRODUCT_H32_V4,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_C8,
|
|
name: "868 MHz / 915 MHz / 923 MHz with PA (V4.2-V4.3)",
|
|
},
|
|
{
|
|
id: ROM.MODEL_C8,
|
|
name: "RTNode Firmware",
|
|
firmware_filename_classic: "rtnode_heltec_v4.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RTNode-HeltecV4",
|
|
hardware_info: "https://heltec.org/project/wifi-lora-32-v4/",
|
|
flash_config: {
|
|
flash_size: "16MB",
|
|
flash_files: {
|
|
"0xe000": "boot_app0.bin",
|
|
"0x0": "bootloader.bin",
|
|
"0x10000": "firmware.bin",
|
|
"0x8000": "partitions.bin",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_heltec32v4pa.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_heltec32v4pa.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://heltec.org/project/wifi-lora-32-v4/",
|
|
flash_config: {
|
|
flash_size: "16MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_heltec32v4pa.boot_app0",
|
|
"0x0": "rnode_firmware_heltec32v4pa.bootloader",
|
|
"0x10000": "rnode_firmware_heltec32v4pa.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_heltec32v4pa.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Heltec T114",
|
|
id: ROM.PRODUCT_HELTEC_T114,
|
|
platform: ROM.PLATFORM_NRF52,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_C6,
|
|
name: "470-510 MHz (HT-n5262-LF)",
|
|
},
|
|
{
|
|
id: ROM.MODEL_C7,
|
|
name: "863-928 MHz (HT-n5262-HF)",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_heltec_t114.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
hardware_info: "https://heltec.org/project/mesh-node-t114/",
|
|
},
|
|
{
|
|
name: "SenseCAP T1000E",
|
|
id: ROM.PRODUCT_SENSECAP_T1000E,
|
|
platform: ROM.PLATFORM_NRF52,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_25,
|
|
name: "863-928 MHz",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_t1000e.zip",
|
|
firmware_repo_classic: "https://github.com/cobraPA",
|
|
hardware_info: "https://www.seeedstudio.com/SenseCAP-Card-Tracker-T1000-E-for-Meshtastic-p-5913.html",
|
|
},
|
|
{
|
|
name: "LilyGO LoRa32 v1.0",
|
|
id: ROM.PRODUCT_T32_10,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_BA,
|
|
name: "433 MHz",
|
|
},
|
|
{
|
|
id: ROM.MODEL_BB,
|
|
name: "868 MHz / 915 MHz / 923 MHz",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_lora32v10.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_lora32v10.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/lora-v1-0-kit",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_lora32v10.boot_app0",
|
|
"0x1000": "rnode_firmware_lora32v10.bootloader",
|
|
"0x10000": "rnode_firmware_lora32v10.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_lora32v10.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "LilyGO LoRa32 v2.0",
|
|
id: ROM.PRODUCT_T32_20,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_B3,
|
|
name: "433 MHz",
|
|
},
|
|
{
|
|
id: ROM.MODEL_B8,
|
|
name: "868 MHz / 915 MHz / 923 MHz",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_lora32v20.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_lora32v20.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/lora-v1-3",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_lora32v20.boot_app0",
|
|
"0x1000": "rnode_firmware_lora32v20.bootloader",
|
|
"0x10000": "rnode_firmware_lora32v20.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_lora32v20.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "LilyGO LoRa32 v2.1",
|
|
id: ROM.PRODUCT_T32_21,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_B4,
|
|
name: "433 MHz",
|
|
firmware_filename_classic: "rnode_firmware_lora32v21.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_lora32v21.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/lora3",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_lora32v21.boot_app0",
|
|
"0x1000": "rnode_firmware_lora32v21.bootloader",
|
|
"0x10000": "rnode_firmware_lora32v21.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_lora32v21.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_B9,
|
|
name: "868/915/923 MHz",
|
|
firmware_filename_classic: "rnode_firmware_lora32v21.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_lora32v21.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/lora3",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_lora32v21.boot_app0",
|
|
"0x1000": "rnode_firmware_lora32v21.bootloader",
|
|
"0x10000": "rnode_firmware_lora32v21.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_lora32v21.partitions",
|
|
},
|
|
},
|
|
},
|
|
// The TCXO model codes are only used here to select the correct firmware,
|
|
// actual model codes in firmware is still 0xB4 and 0xB9.
|
|
// https://github.com/markqvist/Reticulum/blob/master/RNS/Utilities/rnodeconf.py#L156
|
|
// https://github.com/markqvist/Reticulum/blob/master/RNS/Utilities/rnodeconf.py#L3283
|
|
{
|
|
id: ROM.MODEL_B4_TCXO,
|
|
mapped_id: ROM.MODEL_B4, // this device uses the same model code, but different firmware file
|
|
name: "433 MHz, with TCXO",
|
|
firmware_filename_classic: "rnode_firmware_lora32v21_tcxo.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_lora32v21.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/lora3",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_lora32v21_tcxo.boot_app0",
|
|
"0x1000": "rnode_firmware_lora32v21_tcxo.bootloader",
|
|
"0x10000": "rnode_firmware_lora32v21_tcxo.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_lora32v21_tcxo.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_B9_TCXO,
|
|
mapped_id: ROM.MODEL_B9, // this device uses the same model code, but different firmware file
|
|
name: "868/915/923 MHz, with TCXO",
|
|
firmware_filename_classic: "rnode_firmware_lora32v21_tcxo.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_lora32v21.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/lora3",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_lora32v21_tcxo.boot_app0",
|
|
"0x1000": "rnode_firmware_lora32v21_tcxo.bootloader",
|
|
"0x10000": "rnode_firmware_lora32v21_tcxo.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_lora32v21_tcxo.partitions",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "LilyGO LoRa T3S3",
|
|
id: ROM.PRODUCT_RNODE,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_A5,
|
|
name: "433 MHz (with SX1278 chip)",
|
|
firmware_filename_classic: "rnode_firmware_t3s3_sx127x.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_t3s3_sx127x.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t3s3-v1-0",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_t3s3_sx127x.boot_app0",
|
|
"0x0": "rnode_firmware_t3s3_sx127x.bootloader",
|
|
"0x10000": "rnode_firmware_t3s3_sx127x.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_t3s3_sx127x.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_AA,
|
|
name: "868/915/923 MHz (with SX1276 chip)",
|
|
firmware_filename_classic: "rnode_firmware_t3s3_sx127x.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_t3s3_sx127x.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t3s3-v1-0",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_t3s3_sx127x.boot_app0",
|
|
"0x0": "rnode_firmware_t3s3_sx127x.bootloader",
|
|
"0x10000": "rnode_firmware_t3s3_sx127x.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_t3s3_sx127x.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_A1,
|
|
name: "433 MHz (with SX1268 chip)",
|
|
firmware_filename_classic: "rnode_firmware_t3s3.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_t3s3.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t3s3-v1-0",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_t3s3.boot_app0",
|
|
"0x0": "rnode_firmware_t3s3.bootloader",
|
|
"0x10000": "rnode_firmware_t3s3.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_t3s3.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_A6,
|
|
name: "868/915/923 MHz (with SX1262 chip)",
|
|
firmware_filename_classic: "rnode_firmware_t3s3.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_t3s3.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t3s3-v1-0",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_t3s3.boot_app0",
|
|
"0x0": "rnode_firmware_t3s3.bootloader",
|
|
"0x10000": "rnode_firmware_t3s3.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_t3s3.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_AC,
|
|
name: "2.4 GHz (with SX1280 chip)",
|
|
firmware_filename_classic: "rnode_firmware_t3s3_sx1280_pa.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_t3s3_sx1280_pa.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t3s3-v1-0",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_t3s3_sx1280_pa.boot_app0",
|
|
"0x0": "rnode_firmware_t3s3_sx1280_pa.bootloader",
|
|
"0x10000": "rnode_firmware_t3s3_sx1280_pa.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_t3s3_sx1280_pa.partitions",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "LilyGO T-Beam",
|
|
id: ROM.PRODUCT_TBEAM,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_E4,
|
|
name: "433 MHz (with SX1278 chip)",
|
|
firmware_filename_classic: "rnode_firmware_tbeam.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_tbeam.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t-beam",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_tbeam.boot_app0",
|
|
"0x1000": "rnode_firmware_tbeam.bootloader",
|
|
"0x10000": "rnode_firmware_tbeam.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_tbeam.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_E9,
|
|
name: "868/915/923 MHz (with SX1276 chip)",
|
|
firmware_filename_classic: "rnode_firmware_tbeam.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_tbeam.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t-beam",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_tbeam.boot_app0",
|
|
"0x1000": "rnode_firmware_tbeam.bootloader",
|
|
"0x10000": "rnode_firmware_tbeam.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_tbeam.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_E3,
|
|
name: "433 MHz (with SX1268 chip)",
|
|
firmware_filename_classic: "rnode_firmware_tbeam_sx1262.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_tbeam_sx1262.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t-beam",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_tbeam_sx1262.boot_app0",
|
|
"0x1000": "rnode_firmware_tbeam_sx1262.bootloader",
|
|
"0x10000": "rnode_firmware_tbeam_sx1262.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_tbeam_sx1262.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_E8,
|
|
name: "868/915/923 MHz (with SX1262 chip)",
|
|
firmware_filename_classic: "rnode_firmware_tbeam_sx1262.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_tbeam_sx1262.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t-beam",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_tbeam_sx1262.boot_app0",
|
|
"0x1000": "rnode_firmware_tbeam_sx1262.bootloader",
|
|
"0x10000": "rnode_firmware_tbeam_sx1262.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_tbeam_sx1262.partitions",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "LilyGO T-Beam Supreme",
|
|
id: ROM.PRODUCT_TBEAM_S_V1,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_DB,
|
|
name: "433 MHz (with SX1268 chip)",
|
|
},
|
|
{
|
|
id: ROM.MODEL_DC,
|
|
name: "868/915/923 MHz (with SX1262 chip)",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_tbeam_supreme.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_tbeam_supreme.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t-beam-supreme",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_tbeam_supreme.boot_app0",
|
|
"0x0": "rnode_firmware_tbeam_supreme.bootloader",
|
|
"0x10000": "rnode_firmware_tbeam_supreme.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_tbeam_supreme.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "LilyGO T-Beam Supreme (V3)",
|
|
id: ROM.PRODUCT_TBEAM_S_V3,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_DB,
|
|
name: "433 MHz (with SX1268 chip)",
|
|
},
|
|
{
|
|
id: ROM.MODEL_DC,
|
|
name: "868/915/923 MHz (with SX1262 chip)",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_tbeam_supreme_v3.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_tbeam_supreme_v3.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t-beam-supreme",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_tbeam_supreme_v3.boot_app0",
|
|
"0x0": "rnode_firmware_tbeam_supreme_v3.bootloader",
|
|
"0x10000": "rnode_firmware_tbeam_supreme_v3.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_tbeam_supreme_v3.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "LilyGO T-Deck",
|
|
id: ROM.PRODUCT_TDECK,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_D4,
|
|
name: "433 MHz (with SX1268 chip)",
|
|
},
|
|
{
|
|
id: ROM.MODEL_D9,
|
|
name: "868/915/923 MHz (with SX1262 chip)",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_tdeck.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_tdeck.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t-deck",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_tdeck.boot_app0",
|
|
"0x0": "rnode_firmware_tdeck.bootloader",
|
|
"0x10000": "rnode_firmware_tdeck.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_tdeck.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Xiao ESP32S3",
|
|
id: ROM.PRODUCT_XIAO_S3,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_DE,
|
|
name: "433 MHz",
|
|
},
|
|
{
|
|
id: ROM.MODEL_DD,
|
|
name: "868/915/923 MHz",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_xiao_esp32s3.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_xiao_esp32s3.zip",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://www.seeedstudio.com/XIAO-ESP32S3-p-5627.html",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_xiao_esp32s3.boot_app0",
|
|
"0x0": "rnode_firmware_xiao_esp32s3.bootloader",
|
|
"0x10000": "rnode_firmware_xiao_esp32s3.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_xiao_esp32s3.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "LilyGO T-Echo",
|
|
id: ROM.PRODUCT_TECHO,
|
|
platform: ROM.PLATFORM_NRF52,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_16,
|
|
name: "433 MHz",
|
|
},
|
|
{
|
|
id: ROM.MODEL_17,
|
|
name: "868 MHz / 915 MHz / 923 MHz",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_techo.zip",
|
|
hardware_info: "https://lilygo.cc/en-us/products/t-echo",
|
|
},
|
|
{
|
|
name: "RAK4631",
|
|
id: ROM.PRODUCT_RAK4631,
|
|
platform: ROM.PLATFORM_NRF52,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_11,
|
|
name: "433 MHz",
|
|
},
|
|
{
|
|
id: ROM.MODEL_12,
|
|
name: "868 MHz / 915 MHz / 923 MHz",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_rak4631.zip",
|
|
hardware_info: "https://docs.rakwireless.com/product-categories/wisblock/rak4631/overview/",
|
|
},
|
|
{
|
|
name: "MeshAdventurer-S3",
|
|
id: ROM.PRODUCT_HMBRW,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "433 MHz",
|
|
firmware_filename_classic: "rnode_firmware_meshadventurer_s3.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_meshadventurer_s3.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RTNode-HeltecV4",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://git.rns.moscow/deuxyeux/MeshAdventurer-S3",
|
|
flash_config: {
|
|
flash_size: "16MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_meshadventurer_s3.boot_app0",
|
|
"0x0": "rnode_firmware_meshadventurer_s3.bootloader",
|
|
"0x10000": "rnode_firmware_meshadventurer_s3.bin",
|
|
"0x8000": "rnode_firmware_meshadventurer_s3.partitions",
|
|
"0x210000": "console_image.bin",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "868/915/923 MHz",
|
|
firmware_filename_classic: "rnode_firmware_meshadventurer_s3.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_meshadventurer_s3.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RTNode-HeltecV4",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://git.rns.moscow/deuxyeux/MeshAdventurer-S3",
|
|
flash_config: {
|
|
flash_size: "16MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_meshadventurer_s3.boot_app0",
|
|
"0x0": "rnode_firmware_meshadventurer_s3.bootloader",
|
|
"0x10000": "rnode_firmware_meshadventurer_s3.bin",
|
|
"0x8000": "rnode_firmware_meshadventurer_s3.partitions",
|
|
"0x210000": "console_image.bin",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "RTNode Firmware",
|
|
firmware_filename_classic: "rtnode_meshadventurer_s3.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RTNode-HeltecV4",
|
|
hardware_info: "https://git.rns.moscow/deuxyeux/MeshAdventurer-S3",
|
|
flash_config: {
|
|
flash_size: "16MB",
|
|
flash_files: {
|
|
"0xe000": "boot_app0.bin",
|
|
"0x0": "bootloader.bin",
|
|
"0x10000": "firmware.bin",
|
|
"0x8000": "partitions.bin",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "Meshtastic Firmware",
|
|
firmware_filename_custom: "meshtastic/latest/meshtastic_firmware_meshadventurer-s3.zip",
|
|
firmware_repo_custom: "https://git.rns.moscow/deuxyeux/meshtastic_firmware",
|
|
hardware_info: "https://git.rns.moscow/deuxyeux/MeshAdventurer-S3",
|
|
flash_config: {
|
|
flash_size: "16MB",
|
|
flash_files: {
|
|
"0x0000": "bootloader.bin",
|
|
"0x8000": "partitions.bin",
|
|
"0x10000": "firmware.bin",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "MeshAdventurer",
|
|
id: ROM.PRODUCT_HMBRW,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "433 MHz",
|
|
},
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "433 MHz (Merged Binary)",
|
|
firmware_filename_classic: "rnode_firmware_meshadventurer_merged.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
hardware_info: "https://github.com/chrismyers2000/MeshAdventurer",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0x0": "rnode_firmware_meshadventurer_merged.bin",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "868/915/923 MHz (30dBm)",
|
|
},
|
|
{
|
|
id: ROM.MODEL_FD,
|
|
name: "868/915/923 MHz (33dBm)",
|
|
},
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "868/915/923 MHz (Merged Binary, 30dBm)",
|
|
firmware_filename_classic: "rnode_firmware_meshadventurer_merged.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
hardware_info: "https://github.com/chrismyers2000/MeshAdventurer",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0x0": "rnode_firmware_meshadventurer_merged.bin",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_FD,
|
|
name: "868/915/923 MHz (Merged Binary, 33dBm)",
|
|
firmware_filename_classic: "rnode_firmware_meshadventurer_merged.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
hardware_info: "https://github.com/chrismyers2000/MeshAdventurer",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0x0": "rnode_firmware_meshadventurer_merged.bin",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_meshadventurer.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
hardware_info: "https://github.com/chrismyers2000/MeshAdventurer",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_meshadventurer.boot_app0",
|
|
"0x1000": "rnode_firmware_meshadventurer.bootloader",
|
|
"0x10000": "rnode_firmware_meshadventurer.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_meshadventurer.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "FakeTec (Promicro)",
|
|
id: ROM.PRODUCT_HMBRW,
|
|
platform: ROM.PLATFORM_NRF52,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "863-928 MHz",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_promicro.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
hardware_info: "https://github.com/gargomoma/fakeTec_pcb",
|
|
},
|
|
{
|
|
name: "Aethernode",
|
|
id: ROM.PRODUCT_HMBRW,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "433 MHz",
|
|
firmware_filename_classic: "rnode_firmware_aethernode.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
hardware_info: "https://github.com/ahedproductions/aethernode",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_aethernode.boot_app0",
|
|
"0x1000": "rnode_firmware_aethernode.bootloader",
|
|
"0x10000": "rnode_firmware_aethernode.bin",
|
|
"0x8000": "rnode_firmware_aethernode.partitions",
|
|
"0x210000": "console_image.bin",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "Meshtastic Firmware",
|
|
firmware_filename_custom: "meshtastic/latest/meshtastic_firmware_aethernode.zip",
|
|
hardware_info: "https://github.com/ahedproductions/aethernode",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0x1000": "bootloader.bin",
|
|
"0x8000": "partitions.bin",
|
|
"0x10000": "firmware.bin",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "DIY-V1",
|
|
id: ROM.PRODUCT_HMBRW,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "470-510 MHz",
|
|
},
|
|
{
|
|
id: ROM.MODEL_FE,
|
|
name: "863-928 MHz",
|
|
},
|
|
],
|
|
firmware_filename_classic: "rnode_firmware_diy_v1.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
hardware_info: "https://github.com/NanoVHF/Meshtastic-DIY",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_diy_v1.boot_app0",
|
|
"0x1000": "rnode_firmware_diy_v1.bootloader",
|
|
"0x10000": "rnode_firmware_diy_v1.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_diy_v1.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "RNode",
|
|
id: ROM.PRODUCT_RNODE,
|
|
platform: ROM.PLATFORM_ESP32,
|
|
models: [
|
|
{
|
|
id: ROM.MODEL_A2,
|
|
name: "Handheld v2.0 RNode, 410 - 525 MHz",
|
|
firmware_filename_classic: "rnode_firmware_ng20.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_ng20.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://unsigned.io/hardware/Original_RNode.html",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_ng21.boot_app0",
|
|
"0x1000": "rnode_firmware_ng21.bootloader",
|
|
"0x10000": "rnode_firmware_ng21.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_ng21.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_A7,
|
|
name: "Handheld v2.0 RNode, 820 - 1020 MHz",
|
|
firmware_filename_classic: "rnode_firmware_ng20.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_ng20.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://unsigned.io/hardware/Original_RNode.html",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_ng21.boot_app0",
|
|
"0x1000": "rnode_firmware_ng21.bootloader",
|
|
"0x10000": "rnode_firmware_ng21.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_ng21.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_A2,
|
|
name: "Handheld v2.1 RNode, 410 - 525 MHz",
|
|
firmware_filename_classic: "rnode_firmware_ng21.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_ng21.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://unsigned.io/hardware/RNode.html",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_ng21.boot_app0",
|
|
"0x1000": "rnode_firmware_ng21.bootloader",
|
|
"0x10000": "rnode_firmware_ng21.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_ng21.partitions",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: ROM.MODEL_A7,
|
|
name: "Handheld v2.1 RNode, 820 - 1020 MHz",
|
|
firmware_filename_classic: "rnode_firmware_ng21.zip",
|
|
firmware_filename_microreticulum: "rnode_firmware_ng21.zip",
|
|
firmware_repo_classic: "https://git.rns.moscow/deuxyeux/RNode_Firmware",
|
|
firmware_repo_microreticulum: "https://github.com/attermann/microReticulum_Firmware",
|
|
hardware_info: "https://unsigned.io/hardware/RNode.html",
|
|
flash_config: {
|
|
flash_size: "4MB",
|
|
flash_files: {
|
|
"0xe000": "rnode_firmware_ng21.boot_app0",
|
|
"0x1000": "rnode_firmware_ng21.bootloader",
|
|
"0x10000": "rnode_firmware_ng21.bin",
|
|
"0x210000": "console_image.bin",
|
|
"0x8000": "rnode_firmware_ng21.partitions",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
|
|
// Default config
|
|
configFrequency: 868825000,
|
|
configBandwidth: 125000,
|
|
configTxPower: 22,
|
|
configSpreadingFactor: 10,
|
|
configCodingRate: 7,
|
|
displayIntensity: 255,
|
|
displayTimeout: 0,
|
|
wifiMode: null,
|
|
wifiChannel: 1,
|
|
wifiSSID: "",
|
|
wifiPSK: "",
|
|
wifiIP: "",
|
|
wifiNM: "",
|
|
|
|
RNodeInterfaceDefaults: {
|
|
bandwidths: [ // bandwidth in hz
|
|
7800, // 7.8 kHz
|
|
10400, // 10.4 kHz
|
|
15600, // 15.6 kHz
|
|
20800, // 20.8 kHz
|
|
31250, // 31.25 kHz
|
|
41700, // 41.7 kHz
|
|
62500, // 62.5 kHz
|
|
125000, // 125 kHz
|
|
250000, // 250 kHz
|
|
500000, // 500 kHz
|
|
],
|
|
codingrates: [
|
|
5, // 4:5
|
|
6, // 4:6
|
|
7, // 4:7
|
|
8, // 4:8
|
|
],
|
|
spreadingfactors: [
|
|
7,
|
|
8,
|
|
9,
|
|
10,
|
|
11,
|
|
12,
|
|
],
|
|
},
|
|
|
|
// Flag to track if we're in the initial setup
|
|
isInitializing: true,
|
|
|
|
};
|
|
},
|
|
mounted() {
|
|
// Do not set default selections automatically - keep null by default
|
|
},
|
|
methods: {
|
|
downloadEraseUF2() {
|
|
const link = document.createElement('a');
|
|
link.href = 'firmware/misc/nrf_erase2.uf2';
|
|
link.download = '';
|
|
link.click();
|
|
},
|
|
async askForSerialPort() {
|
|
|
|
if(!navigator.serial){
|
|
alert("Web Serial is not supported in this browser");
|
|
return null;
|
|
}
|
|
|
|
// close any existing rnode connection
|
|
if(this.rnode){
|
|
await this.rnode.close();
|
|
this.rnode = null;
|
|
}
|
|
|
|
// ask user to select device
|
|
return await navigator.serial.requestPort({
|
|
filters: [],
|
|
});
|
|
|
|
},
|
|
async askForRNode() {
|
|
|
|
// ask for serial port
|
|
const serialPort = await this.askForSerialPort();
|
|
if(!serialPort){
|
|
return false;
|
|
}
|
|
|
|
// check if device is an rnode
|
|
this.rnode = await RNode.fromSerialPort(serialPort);
|
|
const isRNode = await this.rnode.detect();
|
|
if(!isRNode){
|
|
await this.rnode.close();
|
|
alert("Selected device is not an RNode!");
|
|
return false;
|
|
}
|
|
|
|
return this.rnode;
|
|
|
|
},
|
|
async enterDfuMode() {
|
|
|
|
// ask for serial port
|
|
const serialPort = await this.askForSerialPort();
|
|
if(!serialPort){
|
|
return;
|
|
}
|
|
|
|
// enter dfu mode
|
|
console.log("entering dfu mode");
|
|
const flasher = new Nrf52DfuFlasher(serialPort);
|
|
await flasher.enterDfuMode();
|
|
console.log("entering dfu mode: done");
|
|
|
|
alert("Device should now be in DFU mode.");
|
|
|
|
},
|
|
async flash() {
|
|
switch(this.selectedProduct?.platform){
|
|
case ROM.PLATFORM_ESP32: {
|
|
await this.flashEsp32();
|
|
break;
|
|
}
|
|
case ROM.PLATFORM_NRF52: {
|
|
await this.flashNrf52();
|
|
break;
|
|
}
|
|
default: {
|
|
alert("Please select a device first");
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
async flashNrf52() {
|
|
|
|
// ensure firmware file selected
|
|
const file = this.$refs["file"].files[0];
|
|
if(!file){
|
|
alert("Select a firmware file first");
|
|
return;
|
|
}
|
|
|
|
// ask for serial port
|
|
const serialPort = await this.askForSerialPort();
|
|
if(!serialPort){
|
|
return;
|
|
}
|
|
|
|
// update progress
|
|
this.isFlashing = true;
|
|
this.flashingProgress = 0;
|
|
|
|
try {
|
|
|
|
// flash file
|
|
const flasher = new Nrf52DfuFlasher(serialPort);
|
|
await flasher.flash(file, (percentage, message) => {
|
|
this.flashingProgress = percentage;
|
|
});
|
|
|
|
// flashing successful
|
|
alert("Firmware has been flashed!");
|
|
|
|
} catch(e) {
|
|
alert("Firmware flashing failed: " + e);
|
|
console.log(e);
|
|
} finally {
|
|
this.isFlashing = false;
|
|
}
|
|
|
|
// close port
|
|
console.log("Closing serial port...");
|
|
try {
|
|
await serialPort.close();
|
|
} catch(e) {
|
|
console.log("failed to close serial port, ignoring...", e);
|
|
}
|
|
|
|
|
|
},
|
|
async flashEsp32() {
|
|
|
|
// ensure ESPLoader is available
|
|
if(!window.ESPLoader){
|
|
alert("esptool-js could not be loaded.");
|
|
return;
|
|
}
|
|
|
|
// ensure flash config is known
|
|
const flashConfig = this.selectedModel?.flash_config ?? this.selectedProduct?.flash_config;
|
|
if(!flashConfig){
|
|
alert("Flash config is not available for the selected device.");
|
|
return;
|
|
}
|
|
|
|
// ensure firmware file selected
|
|
const file = this.$refs["file"].files[0];
|
|
if(!file){
|
|
alert("Select a firmware file first");
|
|
return;
|
|
}
|
|
|
|
// ask for serial port
|
|
const serialPort = await this.askForSerialPort();
|
|
if(!serialPort){
|
|
return;
|
|
}
|
|
|
|
this.isFlashing = true;
|
|
this.flashingProgress = 0;
|
|
|
|
let transport;
|
|
try {
|
|
|
|
// read zip file
|
|
const blobReader = new window.zip.BlobReader(file);
|
|
const zipReader = new window.zip.ZipReader(blobReader);
|
|
const zipEntries = await zipReader.getEntries();
|
|
|
|
const filesToFlash = [];
|
|
for(const [address, filename] of Object.entries(flashConfig.flash_files)){
|
|
const zipEntry = zipEntries.find(e => e.filename === filename);
|
|
if(!zipEntry) throw filename + " not found in firmware file!";
|
|
const blob = await zipEntry.getData(new window.zip.BlobWriter());
|
|
const data = await this.readAsBinaryString(blob); // deprecated, works for now
|
|
filesToFlash.push({ address: parseInt(address), data });
|
|
}
|
|
|
|
// init transport and esploader
|
|
transport = new window.Transport(serialPort, true);
|
|
const esploader = new window.ESPLoader({
|
|
transport,
|
|
baudrate: 921600,
|
|
debugLogging: false,
|
|
enableTracing: false,
|
|
terminal: {
|
|
clean() {},
|
|
writeLine(data) { console.log(data); },
|
|
write(data) { console.log(data); },
|
|
},
|
|
});
|
|
|
|
// completely disable TRACE logging
|
|
transport.trace = () => {};
|
|
|
|
// load device info
|
|
await esploader.main();
|
|
|
|
// flash device
|
|
await esploader.writeFlash({
|
|
fileArray: filesToFlash,
|
|
flashSize: flashConfig.flash_size,
|
|
flashMode: "DIO",
|
|
flashFreq: "80MHz",
|
|
eraseAll: false,
|
|
compress: true,
|
|
calculateMD5Hash: (image) => CryptoJS.MD5(CryptoJS.enc.Latin1.parse(image)),
|
|
reportProgress: (fileIndex, written, total) => {
|
|
const percent = (written / total) * 100;
|
|
this.flashingProgress = Math.floor(percent);
|
|
},
|
|
});
|
|
|
|
// reboot device
|
|
await transport.setDTR(false);
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
await transport.setDTR(true);
|
|
|
|
alert("Firmware has been flashed!");
|
|
|
|
} catch(e) {
|
|
alert("Firmware flashing failed: " + e);
|
|
console.error(e);
|
|
} finally {
|
|
this.isFlashing = false;
|
|
|
|
// release reader/writer locks before closing port
|
|
try {
|
|
if(transport?.reader){
|
|
await transport.reader.cancel();
|
|
transport.reader.releaseLock();
|
|
}
|
|
if(transport?.writer){
|
|
transport.writer.releaseLock();
|
|
}
|
|
|
|
console.log("Closing serial port...");
|
|
await serialPort.close();
|
|
} catch(e) {
|
|
console.warn("Failed to close serial port, ignoring...", e);
|
|
}
|
|
}
|
|
},
|
|
async eraseEsp32() {
|
|
if (!window.ESPLoader) {
|
|
alert("esptool-js could not be loaded.");
|
|
return;
|
|
}
|
|
|
|
// ask for serial port
|
|
const serialPort = await this.askForSerialPort();
|
|
if (!serialPort) return;
|
|
|
|
this.isFlashing = true;
|
|
|
|
let transport, esploader;
|
|
|
|
try {
|
|
transport = new window.Transport(serialPort, true);
|
|
esploader = new window.ESPLoader({
|
|
transport: transport,
|
|
baudrate: 921600,
|
|
debugLogging: false,
|
|
enableTracing: false,
|
|
terminal: {
|
|
clean() {},
|
|
writeLine(data) { console.log(data); },
|
|
write(data) { console.log(data); },
|
|
},
|
|
});
|
|
|
|
// completely disable TRACE logging
|
|
transport.trace = () => {};
|
|
|
|
await esploader.main();
|
|
await esploader.eraseFlash();
|
|
alert("Flash erase completed!");
|
|
} catch (e) {
|
|
alert("Flash erase failed: " + e);
|
|
console.log(e);
|
|
} finally {
|
|
this.isFlashing = false;
|
|
|
|
// properly release streams before closing
|
|
try {
|
|
if (transport?.reader) {
|
|
await transport.reader.cancel();
|
|
transport.reader.releaseLock();
|
|
}
|
|
if (transport?.writer) {
|
|
transport.writer.releaseLock();
|
|
}
|
|
|
|
console.log("Closing serial port...");
|
|
await serialPort.close();
|
|
} catch (e) {
|
|
console.warn("Failed to close serial port, ignoring...", e);
|
|
}
|
|
}
|
|
},
|
|
async detect() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
const firmwareVersion = await rnode.getFirmwareVersion();
|
|
alert("Device has RNode firmware v" + firmwareVersion);
|
|
|
|
console.log({
|
|
firmware_version: await rnode.getFirmwareVersion(),
|
|
platform: await rnode.getPlatform(),
|
|
mcu: await rnode.getMcu(),
|
|
board: await rnode.getBoard(),
|
|
device_hash: await rnode.getDeviceHash(),
|
|
firmware_hash_target: await rnode.getTargetFirmwareHash(),
|
|
firmware_hash: await rnode.getFirmwareHash(),
|
|
// rom: await rnode.getRom(),
|
|
frequency: await rnode.getFrequency(),
|
|
bandwidth: await rnode.getBandwidth(),
|
|
tx_power: await rnode.getTxPower(),
|
|
spreading_factor: await rnode.getSpreadingFactor(),
|
|
coding_rate: await rnode.getCodingRate(),
|
|
radio_state: await rnode.getRadioState(),
|
|
rx_stat: await rnode.getRxStat(),
|
|
tx_stat: await rnode.getTxStat(),
|
|
rssi_stat: await rnode.getRssiStat(),
|
|
wifi_mode: await rnode.getWifiMode(),
|
|
});
|
|
|
|
await rnode.close();
|
|
|
|
},
|
|
packInt(value) {
|
|
const buffer = new ArrayBuffer(4); // 4 bytes for a 32-bit integer
|
|
const view = new DataView(buffer);
|
|
view.setUint32(0, value, false); // false for big-endian
|
|
return new Uint8Array(buffer);
|
|
},
|
|
unpackInt(byteArray) {
|
|
const buffer = new Uint8Array(byteArray).buffer; // Get the underlying ArrayBuffer from the byte array
|
|
const view = new DataView(buffer);
|
|
return view.getUint32(0, false); // false for big-endian
|
|
},
|
|
async reboot() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// reboot
|
|
await rnode.reset();
|
|
await rnode.close();
|
|
|
|
// done
|
|
alert("Board is rebooting!");
|
|
|
|
},
|
|
frameBufferToCanvas(framebuffer, width, height, backgroundColour, foregroundColour) {
|
|
|
|
// create canvas
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
|
|
// fill the canvas with background colour
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.fillStyle = backgroundColour;
|
|
ctx.fillRect(0, 0, width, height);
|
|
|
|
// draw foreground pixels where bits are 1
|
|
ctx.fillStyle = foregroundColour;
|
|
for(let y = 0; y < height; y++){
|
|
for(let x = 0; x < width; x++){
|
|
const byteIndex = Math.floor((y * width + x) / 8);
|
|
const bitIndex = x % 8;
|
|
const bit = (framebuffer[byteIndex] >> (7 - bitIndex)) & 1;
|
|
if(bit){
|
|
ctx.fillRect(x, y, 1, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return canvas;
|
|
|
|
},
|
|
rnodeDisplayBufferToPng(displayBuffer) {
|
|
|
|
// get display area and stat area
|
|
const displayArea = displayBuffer.slice(0, 512);
|
|
const statArea = displayBuffer.slice(512, 1024);
|
|
|
|
// create canvas from frame buffers
|
|
const displayCanvasOriginal = this.frameBufferToCanvas(displayArea, 64, 64, "#000000", "#FFFFFF");
|
|
const statCanvasOriginal = this.frameBufferToCanvas(statArea, 64, 64, "#000000", "#FFFFFF");
|
|
|
|
// create horizontal canvas to show display area and stat area next to each other
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = 128;
|
|
canvas.height = 64;
|
|
|
|
// draw canvases to final canvas
|
|
const canvasCtx = canvas.getContext('2d');
|
|
canvasCtx.imageSmoothingEnabled = false;
|
|
canvasCtx.drawImage(displayCanvasOriginal, 0, 0, 64, 64);
|
|
canvasCtx.drawImage(statCanvasOriginal, 64, 0, 64, 64);
|
|
|
|
// create scaled canvas
|
|
const scaleFactor = 4;
|
|
const scaledCanvas = document.createElement('canvas');
|
|
scaledCanvas.width = canvas.width * scaleFactor;
|
|
scaledCanvas.height = canvas.height * scaleFactor;
|
|
|
|
// scale original canvas onto new canvas
|
|
const scaledCtx = scaledCanvas.getContext('2d');
|
|
scaledCtx.imageSmoothingEnabled = false;
|
|
scaledCtx.drawImage(canvas, 0, 0, scaledCanvas.width, scaledCanvas.height);
|
|
|
|
// convert canvas to png
|
|
return scaledCanvas.toDataURL("image/png");
|
|
|
|
},
|
|
async readDisplay() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// read display
|
|
const displayBuffer = await rnode.readDisplay();
|
|
|
|
// disconnect from rnode
|
|
await rnode.close();
|
|
|
|
// update ui
|
|
this.rnodeDisplayImage = this.rnodeDisplayBufferToPng(displayBuffer);
|
|
|
|
},
|
|
async dumpEeprom() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// get rom
|
|
const eeprom = await rnode.getRom();
|
|
if(!eeprom){
|
|
alert("Unable to retrieve eeprom!");
|
|
return;
|
|
}
|
|
|
|
// done
|
|
console.log(Utils.bytesToHex(eeprom));
|
|
await rnode.close();
|
|
|
|
},
|
|
async wipeEeprom() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// ask user to confirm
|
|
if(!confirm("Are you sure you want to wipe the eeprom on this device? This will take about 30 seconds. An alert will show when the eeprom wipe has finished.")){
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// wipe eeprom
|
|
console.log("wiping eeprom");
|
|
await rnode.wipeRom();
|
|
console.log("wiping eeprom: done");
|
|
|
|
// must reboot device after wipe
|
|
await rnode.reset();
|
|
await rnode.close();
|
|
|
|
// done
|
|
alert("eeprom has been wiped!");
|
|
|
|
},
|
|
async provision() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(details){
|
|
console.log(details);
|
|
alert("Eeprom is already provisioned. You must wipe it to reprovision!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// ensure user has selected product
|
|
if(!this.selectedProduct){
|
|
alert("Please select a product!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// ensure user has selected model
|
|
if(!this.selectedModel){
|
|
alert("Please select a model!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
console.log("device is not provisioned yet, doing it now...");
|
|
|
|
this.isProvisioning = true;
|
|
|
|
try {
|
|
|
|
// determine device info
|
|
// todo implement ui to configure these values
|
|
const product = this.selectedProduct.id;
|
|
// use mapped_id if available, else fallback to id, some devices use the same model, but different firmware file
|
|
const model = this.selectedModel.mapped_id ?? this.selectedModel.id;
|
|
const hardwareRevision = 0x1;
|
|
const serialNumber = 1;
|
|
const timestampInSeconds = Math.floor(Date.now() / 1000);
|
|
const serialBytes = this.packInt(serialNumber);
|
|
const timestampBytes = this.packInt(timestampInSeconds);
|
|
|
|
// compute device info checksum
|
|
const checksum = Utils.md5([
|
|
product,
|
|
model,
|
|
hardwareRevision,
|
|
...serialBytes,
|
|
...timestampBytes,
|
|
]);
|
|
|
|
console.log("checksum", checksum);
|
|
|
|
// write device info to eeprom
|
|
console.log("writing device info");
|
|
await rnode.writeRom(ROM.ADDR_PRODUCT, product);
|
|
console.log(Utils.bytesToHex(await rnode.getRom()));
|
|
await rnode.writeRom(ROM.ADDR_MODEL, model);
|
|
console.log(Utils.bytesToHex(await rnode.getRom()));
|
|
await rnode.writeRom(ROM.ADDR_HW_REV, hardwareRevision);
|
|
console.log(Utils.bytesToHex(await rnode.getRom()));
|
|
await rnode.writeRom(ROM.ADDR_SERIAL, serialBytes[0]);
|
|
await rnode.writeRom(ROM.ADDR_SERIAL + 1, serialBytes[1]);
|
|
await rnode.writeRom(ROM.ADDR_SERIAL + 2, serialBytes[2]);
|
|
await rnode.writeRom(ROM.ADDR_SERIAL + 3, serialBytes[3]);
|
|
console.log(Utils.bytesToHex(await rnode.getRom()));
|
|
await rnode.writeRom(ROM.ADDR_MADE, timestampBytes[0]);
|
|
await rnode.writeRom(ROM.ADDR_MADE + 1, timestampBytes[1]);
|
|
await rnode.writeRom(ROM.ADDR_MADE + 2, timestampBytes[2]);
|
|
await rnode.writeRom(ROM.ADDR_MADE + 3, timestampBytes[3]);
|
|
console.log(Utils.bytesToHex(await rnode.getRom()));
|
|
console.log("writing device info: done");
|
|
|
|
// write checksum to eeprom
|
|
console.log("writing checksum");
|
|
for(var i = 0; i < 16; i++){
|
|
await rnode.writeRom(ROM.ADDR_CHKSUM + i, checksum[i]);
|
|
}
|
|
console.log(Utils.bytesToHex(await rnode.getRom()));
|
|
console.log("writing checksum: done");
|
|
|
|
// write signature to eeprom
|
|
// fixme: actually implement signature, for now it's just zeroed out
|
|
console.log("writing signature");
|
|
for(var i = 0; i < 128; i++){
|
|
// await rnode.writeRom(ROM.ADDR_SIGNATURE + i, signature[i]);
|
|
await rnode.writeRom(ROM.ADDR_SIGNATURE + i, 0x00); // fixme: fake signature
|
|
}
|
|
console.log(Utils.bytesToHex(await rnode.getRom()));
|
|
console.log("writing signature: done");
|
|
|
|
// write info lock byte to eeprom
|
|
console.log("writing lock byte");
|
|
await rnode.writeRom(ROM.ADDR_INFO_LOCK, ROM.INFO_LOCK_BYTE);
|
|
console.log(Utils.bytesToHex(await rnode.getRom()));
|
|
console.log("writing lock byte: done");
|
|
|
|
// todo get partition hash from release.json OR directly from the firmware.bin
|
|
// partition_filename = fw_filename.replace(".zip", ".bin")
|
|
// partition_hash = get_partition_hash(rnode.platform, UPD_DIR+"/"+selected_version+"/"+partition_filename)
|
|
|
|
// todo set firmware hash in eeprom
|
|
// RNS.log("Setting firmware checksum...")
|
|
// rnode.set_firmware_hash(partition_hash)
|
|
|
|
// RNS.log("Generating a new device signing key...")
|
|
// device_signer = RNS.Identity()
|
|
// device_signer.to_file(FWD_DIR+"/device.key")
|
|
// RNS.log("Device signing key written to "+str(FWD_DIR+"/device.key"))
|
|
|
|
// if not os.path.isfile(FWD_DIR+"/signing.key"):
|
|
// RNS.log("Generating a new EEPROM signing key...")
|
|
// private_key = rsa.generate_private_key(
|
|
// public_exponent=65537,
|
|
// key_size=1024,
|
|
// backend=default_backend()
|
|
// )
|
|
// private_bytes = private_key.private_bytes(
|
|
// encoding=serialization.Encoding.DER,
|
|
// format=serialization.PrivateFormat.PKCS8,
|
|
// encryption_algorithm=serialization.NoEncryption()
|
|
// )
|
|
// public_key = private_key.public_key()
|
|
// public_bytes = public_key.public_bytes(
|
|
// encoding=serialization.Encoding.DER,
|
|
// format=serialization.PublicFormat.SubjectPublicKeyInfo
|
|
// )
|
|
|
|
// can get partition hash from releases.json
|
|
// partition_hash = bytes.fromhex(release_info.split()[1])
|
|
|
|
// await rnode.indicateFirmwareUpdate();
|
|
// await rnode.setFirmwareHash(partition_hash);
|
|
|
|
// todo get signing.key
|
|
|
|
// file = open(key_path, "rb")
|
|
// private_bytes = file.read()
|
|
// file.close()
|
|
// private_key = serialization.load_der_private_key(
|
|
// private_bytes,
|
|
// password=None,
|
|
// backend=default_backend()
|
|
// )
|
|
// public_key = private_key.public_key()
|
|
// public_bytes = public_key.public_bytes(
|
|
// encoding=serialization.Encoding.DER,
|
|
// format=serialization.PublicFormat.SubjectPublicKeyInfo
|
|
// )
|
|
// signature = private_key.sign(
|
|
// checksum,
|
|
// padding.PSS(
|
|
// mgf=padding.MGF1(hashes.SHA256()),
|
|
// salt_length=padding.PSS.MAX_LENGTH
|
|
// ),
|
|
// hashes.SHA256()
|
|
// )
|
|
|
|
// wait a bit for eeprom writes to complete
|
|
await Utils.sleepMillis(5000);
|
|
|
|
// done
|
|
await rnode.reset();
|
|
|
|
alert("device has been provisioned!");
|
|
|
|
} catch(e) {
|
|
console.log(e);
|
|
alert("failed to provision, please try again");
|
|
}
|
|
|
|
this.isProvisioning = false;
|
|
|
|
await rnode.close();
|
|
|
|
},
|
|
async setFirmwareHash() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
this.isSettingFirmwareHash = true;
|
|
|
|
try {
|
|
|
|
// todo: this works, but we should be calculating the firmware hash from the file, and not giving the board what it already knows
|
|
console.log("setting firmware hash");
|
|
await rnode.setFirmwareHash(await rnode.getFirmwareHash());
|
|
console.log("setting firmware hash: done");
|
|
|
|
// wait a bit for eeprom writes to complete
|
|
await Utils.sleepMillis(5000);
|
|
|
|
// reset board if it didn't do it automatically
|
|
try {
|
|
await rnode.reset();
|
|
} catch(e) {
|
|
console.log("couldn't auto reset board, probably did it automatically...");
|
|
}
|
|
|
|
alert("firmware hash has been set!");
|
|
|
|
} catch(e) {
|
|
console.log(e);
|
|
alert("failed to set firmware hash, please try again")
|
|
}
|
|
|
|
this.isSettingFirmwareHash = false;
|
|
|
|
// done
|
|
await rnode.close();
|
|
|
|
},
|
|
async enableTncMode() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// todo check if firmware hashes match, as config will not save if device has invalid target hash, because radio must be able to init
|
|
|
|
// configure
|
|
console.log("configuring");
|
|
await rnode.setFrequency(this.configFrequency);
|
|
await rnode.setBandwidth(this.configBandwidth);
|
|
await rnode.setTxPower(this.configTxPower);
|
|
await rnode.setSpreadingFactor(this.configSpreadingFactor);
|
|
await rnode.setCodingRate(this.configCodingRate);
|
|
await rnode.setRadioStateOn();
|
|
console.log("configuring: done");
|
|
|
|
// save config
|
|
// fixme: for some reason, sending saveConfig ONCE doesn't write the entire config to eeprom...???
|
|
// fixme: when calling saveConfig once, it seems to miss the last 2 bytes of frequency, and doesn't set the conf ok byte...
|
|
// fixme: however, it seems to save it correctly if I send the CMD_CONF_SAVE more than once...
|
|
// fixme: note that sending the CMD_CONF_SAVE once in the python implementation seems to work fine, just not here...
|
|
console.log("saving config");
|
|
await Utils.sleepMillis(500);
|
|
await rnode.saveConfig();
|
|
await rnode.saveConfig();
|
|
console.log("saving config: done");
|
|
|
|
await Utils.sleepMillis(5000);
|
|
|
|
// done
|
|
await rnode.reset();
|
|
await rnode.close();
|
|
alert("TNC mode has been enabled!");
|
|
|
|
},
|
|
async disableTncMode() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// todo check if firmware hashes match, as config will not save if device has invalid target hash, because radio must be able to init
|
|
|
|
// configure
|
|
console.log("disabling tnc mode");
|
|
await rnode.deleteConfig();
|
|
console.log("disabling tnc mode: done");
|
|
|
|
// wait a bit for eeprom writes to complete
|
|
await Utils.sleepMillis(5000);
|
|
|
|
// done
|
|
await rnode.reset();
|
|
await rnode.close();
|
|
alert("TNC mode has been disabled!");
|
|
|
|
},
|
|
async enableBluetooth() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// enable bluetooth
|
|
console.log("enabling bluetooth");
|
|
await rnode.enableBluetooth();
|
|
console.log("enabling bluetooth: done");
|
|
|
|
alert("Bluetooth has been enabled!");
|
|
|
|
// done
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
|
|
},
|
|
async disableBluetooth() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// disable bluetooth
|
|
console.log("disabling bluetooth");
|
|
await rnode.disableBluetooth();
|
|
console.log("disabling bluetooth: done");
|
|
alert("Bluetooth has been disabled!");
|
|
|
|
// done
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
|
|
},
|
|
async startBluetoothPairing() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// start bluetooth pairing
|
|
try {
|
|
console.log("start bluetooth pairing");
|
|
await rnode.startBluetoothPairing(async (pin) => {
|
|
alert("Bluetooth Pairing Pin: " + pin);
|
|
await rnode.close();
|
|
});
|
|
console.log("start bluetooth pairing: done");
|
|
} catch(error) {
|
|
alert(error);
|
|
}
|
|
|
|
// tell user device is in pairing mode, and how to pair
|
|
alert([
|
|
"- RNode is in Bluetooth Pairing Mode for 30 seconds.",
|
|
"- Close this alert before performing the next steps.",
|
|
"- Open bluetooth settings on your Android device.",
|
|
"- Click pair on the RNode device that shows up.",
|
|
"- Bluetooth pin will shown on your RNode screen and on this page.",
|
|
].join("\n"));
|
|
|
|
},
|
|
|
|
async setWiFiMode() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if (!rnode) {
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if (!details || !details.is_provisioned) {
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// normalize mode to number (safety)
|
|
const mode = Number(this.wifiMode);
|
|
|
|
console.log("setting wifi mode to", mode);
|
|
|
|
await rnode.enableWiFiMode(mode);
|
|
|
|
console.log("setting wifi mode: done");
|
|
|
|
const modeMap = {
|
|
0: "OFF",
|
|
1: "STATION",
|
|
2: "AP"
|
|
};
|
|
|
|
const modeLabel = modeMap[mode] ?? "UNKNOWN";
|
|
|
|
alert("WiFi mode has been set to " + modeLabel + "!");
|
|
|
|
// update UI if needed
|
|
this.wifiMode = mode;
|
|
|
|
// done
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
},
|
|
|
|
async setWiFiChannel() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// set wifi channel
|
|
console.log("setting wifi channel to", this.wifiChannel);
|
|
await rnode.setWiFiChannel(this.wifiChannel);
|
|
console.log("setting wifi channel: done");
|
|
alert("WiFi channel has been set to " + this.wifiChannel + "!");
|
|
|
|
// done
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
},
|
|
async setWiFiSSID() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// set wifi SSID
|
|
console.log("setting wifi SSID to", this.wifiSSID);
|
|
await rnode.setWiFiSSID(this.wifiSSID);
|
|
console.log("setting wifi SSID: done");
|
|
alert("WiFi SSID has been set to " + this.wifiSSID + "!");
|
|
|
|
// done
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
|
|
},
|
|
async disableWiFiMode() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// disable wifi mode
|
|
console.log("disabling wifi mode");
|
|
await rnode.disableWiFiMode();
|
|
console.log("disabling wifi mode: done");
|
|
alert("WiFi mode has been disabled!");
|
|
|
|
// done
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
|
|
},
|
|
async setWiFiPSK() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// set wifi psk
|
|
console.log("setting wifi psk to", this.wifiPSK);
|
|
await rnode.setWiFiPSK(this.wifiPSK);
|
|
console.log("setting wifi psk: done");
|
|
alert("WiFi PSK has been set!");
|
|
|
|
// done
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
|
|
},
|
|
async unsetWiFiSSID() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// unset wifi SSID by setting it to "None"
|
|
console.log("unsetting wifi SSID");
|
|
await rnode.setWiFiSSID("None");
|
|
console.log("unsetting wifi SSID: done");
|
|
alert("WiFi SSID has been unset!");
|
|
|
|
// update UI
|
|
this.wifiSSID = "None";
|
|
|
|
// done
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
|
|
},
|
|
async unsetWiFiPSK() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// unset wifi psk by setting it to "None"
|
|
console.log("unsetting wifi psk");
|
|
await rnode.setWiFiPSK(null);
|
|
console.log("unsetting wifi psk: done");
|
|
alert("WiFi PSK has been unset!");
|
|
|
|
// update UI
|
|
this.wifiPSK = "";
|
|
|
|
// done
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
|
|
},
|
|
|
|
async unsetWiFiIP() {
|
|
|
|
const rnode = await this.askForRNode();
|
|
if (!rnode) return;
|
|
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if (!details || !details.is_provisioned) {
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
console.log("unsetting wifi ip");
|
|
|
|
await rnode.setWiFiIP(null);
|
|
|
|
console.log("unsetting wifi ip: done");
|
|
|
|
this.wifiIP = "";
|
|
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
},
|
|
|
|
async unsetWiFiNM() {
|
|
|
|
const rnode = await this.askForRNode();
|
|
if (!rnode) return;
|
|
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if (!details || !details.is_provisioned) {
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
console.log("unsetting wifi netmask");
|
|
|
|
await rnode.setWiFiNM(null);
|
|
|
|
console.log("unsetting wifi netmask: done");
|
|
|
|
this.wifiNM = "";
|
|
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
},
|
|
|
|
async setWiFiIP() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// set wifi ip
|
|
console.log("setting wifi ip to", this.wifiIP);
|
|
await rnode.setWiFiIP(this.wifiIP);
|
|
console.log("setting wifi ip: done");
|
|
alert("WiFi IP has been set to " + this.wifiIP + "!");
|
|
|
|
// done
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
|
|
},
|
|
async setWiFiNM() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// set wifi nm
|
|
console.log("setting wifi nm to", this.wifiNM);
|
|
await rnode.setWiFiNM(this.wifiNM);
|
|
console.log("setting wifi nm: done");
|
|
alert("WiFi Netmask has been set to " + this.wifiNM + "!");
|
|
|
|
// done
|
|
await Utils.sleepMillis(1000);
|
|
await rnode.close();
|
|
|
|
},
|
|
async setDisplayRotation(rotation) {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// configure
|
|
console.log("setting display rotation");
|
|
await rnode.setDisplayRotation(rotation);
|
|
console.log("setting display rotation: done");
|
|
|
|
// done
|
|
await rnode.close();
|
|
|
|
},
|
|
async setDisplayIntensity(intensity) {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// configure
|
|
console.log("setting display intensity");
|
|
await rnode.setDisplayIntensity(intensity);
|
|
console.log("setting display intensity: done");
|
|
|
|
// done
|
|
await rnode.close();
|
|
|
|
},
|
|
async setDisplayTimeout(timeout) {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// configure
|
|
console.log("setting display timeout");
|
|
await rnode.setDisplayTimeout(timeout);
|
|
console.log("setting display timeout: done");
|
|
|
|
// done
|
|
await rnode.close();
|
|
|
|
},
|
|
async startDisplayReconditioning() {
|
|
|
|
// ask for rnode
|
|
const rnode = await this.askForRNode();
|
|
if(!rnode){
|
|
return;
|
|
}
|
|
|
|
// check if device has been provisioned
|
|
const rom = await rnode.getRomAsObject();
|
|
const details = rom.parse();
|
|
if(!details || !details.is_provisioned){
|
|
alert("Eeprom is not provisioned. You must do this first!");
|
|
await rnode.close();
|
|
return;
|
|
}
|
|
|
|
// configure
|
|
console.log("starting display reconditioning");
|
|
await rnode.startDisplayReconditioning();
|
|
console.log("starting display reconditioning: done");
|
|
|
|
// done
|
|
await rnode.close();
|
|
|
|
},
|
|
async readAsBinaryString(blob) {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = () => {
|
|
resolve(reader.result);
|
|
};
|
|
reader.readAsBinaryString(blob);
|
|
});
|
|
},
|
|
},
|
|
computed: {
|
|
recommendedFirmwareFilename() {
|
|
return this.selectedModel?.firmware_filename_classic ?? this.selectedProduct?.firmware_filename_classic;
|
|
},
|
|
recommendedFirmwareFilenameMicroReticulum() {
|
|
return this.selectedModel?.firmware_filename_microreticulum ?? this.selectedProduct?.firmware_filename_microreticulum;
|
|
},
|
|
recommendedFirmwareFilenameCustom() {
|
|
return this.selectedModel?.firmware_filename_custom ?? this.selectedProduct?.firmware_filename_custom;
|
|
},
|
|
recommendedFirmwareFilenameRTNode() {
|
|
return this.selectedModel?.firmware_filename_rtnode ?? this.selectedProduct?.firmware_filename_rtnode;
|
|
},
|
|
FirmwareRepoClassic() {
|
|
return this.selectedModel?.firmware_repo_classic ?? this.selectedProduct?.firmware_repo_classic;
|
|
},
|
|
FirmwareRepoMicroReticulum() {
|
|
return this.selectedModel?.firmware_repo_microreticulum ?? this.selectedProduct?.firmware_repo_microreticulum;
|
|
},
|
|
FirmwareRepoCustom() {
|
|
return this.selectedModel?.firmware_repo_custom ?? this.selectedProduct?.firmware_repo_custom;
|
|
},
|
|
HardwareInfo() {
|
|
return this.selectedModel?.hardware_info ?? this.selectedProduct?.hardware_info;
|
|
},
|
|
isSsidValid() {
|
|
if (!this.wifiSSID) return false;
|
|
|
|
const encoder = new TextEncoder();
|
|
const bytes = encoder.encode(this.wifiSSID);
|
|
|
|
return bytes.length >= 1 && bytes.length <= 32;
|
|
},
|
|
isPskValid() {
|
|
if (!this.wifiPSK) return false;
|
|
|
|
const encoder = new TextEncoder();
|
|
const bytes = encoder.encode(this.wifiPSK);
|
|
|
|
return bytes.length >= 8;
|
|
},
|
|
isIpValid() {
|
|
if (!this.wifiIP) return false;
|
|
|
|
const parts = this.wifiIP.split(".");
|
|
if (parts.length !== 4) return false;
|
|
|
|
for (const part of parts) {
|
|
if (part === "") return false;
|
|
|
|
const num = Number(part);
|
|
if (!Number.isInteger(num)) return false;
|
|
if (num < 0 || num > 255) return false;
|
|
}
|
|
return true;
|
|
},
|
|
isNetmaskValid() {
|
|
if (!this.wifiNM) return false;
|
|
|
|
const parts = this.wifiNM.split(".");
|
|
if (parts.length !== 4) return false;
|
|
|
|
for (const part of parts) {
|
|
if (part === "") return false;
|
|
|
|
const num = Number(part);
|
|
if (!Number.isInteger(num)) return false;
|
|
if (num < 0 || num > 255) return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
},
|
|
watch: {
|
|
selectedProduct() {
|
|
// reset selected model when changing selected product, but not during initialization
|
|
if (!this.isInitializing) {
|
|
this.selectedModel = null;
|
|
}
|
|
},
|
|
},
|
|
}).mount('#app');
|
|
</script>
|
|
|
|
</body>
|
|
</html> |