meshcore.js/index.html
2025-02-12 23:05:19 +13:00

353 lines
No EOL
14 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>MeshCore</title>
<style>
[v-cloak] {
display: none;
}
</style>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body class="bg-slate-300">
<div id="app" class="space-y-2 p-3" v-cloak>
<!-- header -->
<div class="flex border bg-gray-50 p-3 rounded shadow">
<div class="my-auto">
<div class="font-bold">MeshCore Client</div>
<div class="text-sm">Developed by <a target="_blank" href="https://liamcottle.com" class="text-blue-500 hover:underline">Liam Cottle</a></div>
</div>
</div>
<!-- connection state -->
<div class="border bg-gray-50 rounded shadow">
<div class="p-3 space-x-1">
<button v-if="!connection" @click="askForSerialPort" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
Connect (Serial)
</button>
<button v-if="!connection" @click="askForBleDevice" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
Connect (BLE)
</button>
<button v-if="connection" @click="disconnect" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
Disconnect
</button>
</div>
</div>
<div v-if="connection" class="space-y-2">
<!-- self info -->
<div v-if="selfInfo" class="border bg-gray-50 rounded shadow">
<div class="p-3">
<div>Connected to: {{ selfInfo.name }}</div>
</div>
</div>
<!-- actions -->
<div class="border bg-gray-50 rounded shadow">
<div class="p-3 space-x-1">
<button @click="sendCommandAppStart" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
AppStart
</button>
<button @click="sendZeroHopAdvert" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
Advert (ZeroHop)
</button>
<button @click="sendFloodAdvert" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
Advert (Flood)
</button>
<button @click="sendCommandSetAdvertName" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
SetAdvertName
</button>
<button @click="sendCommandSetAdvertLatLon" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
SetAdvertLatLon
</button>
<button @click="sendCommandAddUpdateContact" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
AddUpdateContact
</button>
<button @click="sendCommandSyncNextMessage" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
SyncNextMessage
</button>
<button @click="sendCommandGetDeviceTime" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
GetDeviceTime
</button>
<button @click="sendCommandSetDeviceTime" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
SetDeviceTime
</button>
<button @click="sendCommandSetRadioParams" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
SetRadioParams
</button>
<button @click="sendCommandSetTxPower" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
SetTxPower
</button>
</div>
</div>
<!-- contacts -->
<div class="border bg-gray-50 rounded shadow">
<div class="flex border-b p-2">
<div class="font-semibold my-auto mr-auto">Contacts</div>
<div class="my-auto">
<div @click="loadContacts" class="hover:underline cursor-pointer">Reload</div>
</div>
</div>
<div class="divide-y">
<div v-for="contact of contacts" class="flex px-2 py-1">
<div class="my-auto mr-auto">
<div class="font-semibold">{{ contact.advName }}</div>
<div class="text-sm text-gray-500"><{{ bytesToHex(contact.publicKey) }}></div>
<div class="text-sm text-gray-500">Type: {{ contactTypeToString(contact.type) }} • Last Advert: {{ contact.lastAdvert }}</div>
<div class="text-sm text-gray-500">
<span v-if="contact.outPathLen === -1">Path: ??? (Flood)</span>
<span v-else-if="contact.outPathLen === 0">Path: Direct</span>
<span v-else>Path: {{ contact.outPathLen }} hops [{{ formatOutPath(contact) }}]</span>
</div>
</div>
<div class="flex my-auto ml-2 space-x-2">
<div @click="sendMessage(contact)" class="hover:underline cursor-pointer">Message</div>
<div @click="setPath(contact)" class="hover:underline cursor-pointer">Set Path</div>
<div @click="resetPath(contact)" class="hover:underline cursor-pointer">Reset Path</div>
<div @click="removeContact(contact)" class="hover:underline cursor-pointer">Forget</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="module">
import Constants from "./src/constants.js";
import SerialConnection from "./src/connection/serial_connection.js";
import BleConnection from "./src/connection/ble_connection.js";
Vue.createApp({
data() {
return {
connection: null,
selfInfo: null,
contacts: [],
};
},
mounted() {
},
methods: {
async askForSerialPort() {
this.connection = await SerialConnection.open();
this.connection.on("connected", () => this.onConnected());
this.connection.on("disconnected", () => this.onDisconnected());
// this.connection.on("tx", (data) => console.log("tx", data));
// this.connection.on("rx", (data) => console.log("rx", data));
},
async askForBleDevice() {
this.connection = await BleConnection.open();
this.connection.on("connected", () => this.onConnected());
this.connection.on("disconnected", () => this.onDisconnected());
// this.connection.on("tx", (data) => console.log("tx", data));
// this.connection.on("rx", (data) => console.log("rx", data));
},
async disconnect() {
if(this.connection){
await this.connection.close();
this.onDisconnected();
this.connection = null;
}
},
async onConnected() {
console.log("connected");
await this.loadSelfInfo();
await this.loadContacts();
this.connection.on(Constants.PushCodes.Advert, async () => {
console.log("on advert");
await this.loadContacts();
});
this.connection.on(Constants.PushCodes.PathUpdated, async (event) => {
console.log("PathUpdated", event);
await this.loadContacts();
});
},
onDisconnected() {
console.log("disconnected");
this.connection = null;
},
async loadSelfInfo() {
this.selfInfo = await this.connection.getSelfInfo();
},
async loadContacts() {
this.contacts = await this.connection.getContacts();
},
contactTypeToString(type) {
switch(type){
case 0: return "Unknown";
case 1: return "Chat";
case 2: return "Repeater";
case 3: return "Room";
}
},
formatOutPath(contact) {
// get out path
const outPath = contact.outPath.slice(0, contact.outPathLen);
// convert each path to hex
const pathHashes = Array.from(outPath).map((path) => {
return path.toString(16);
});
// return with separator
return pathHashes.join(" -> ");
},
async sendCommandAppStart() {
const selfInfo = await this.connection.getSelfInfo();
console.log(selfInfo);
},
async sendZeroHopAdvert() {
await this.connection.sendZeroHopAdvert();
},
async sendFloodAdvert() {
await this.connection.sendFloodAdvert();
},
async sendCommandGetContacts() {
this.contacts = await this.connection.getContacts();
console.log(this.contacts);
},
async sendCommandGetDeviceTime() {
await this.connection.sendCommandGetDeviceTime();
},
async sendCommandSetDeviceTime() {
const timestamp = Math.floor(Date.now() / 1000);
await this.connection.sendCommandSetDeviceTime(timestamp);
},
async sendCommandSetTxPower() {
// ask user for tx power
const txPowerString = prompt("Please enter TX power in dBm");
if(!txPowerString){
return;
}
// update tx power
const txPower = parseInt(txPowerString);
await this.connection.sendCommandSetTxPower(txPower);
},
async sendCommandSetRadioParams() {
const radioFreq = 917375;
const radioBw = 250000;
const radioSf = 7;
const radioCr = 5;
await this.connection.sendCommandSetRadioParams(radioFreq, radioBw, radioSf, radioCr);
},
async sendCommandSetAdvertName() {
// ask user for name
const name = prompt("Please enter name");
if(!name){
return;
}
// set name
await this.connection.sendCommandSetAdvertName(name);
},
async sendCommandSetAdvertLatLon() {
const lat = Math.floor(-38.661727955271765 * 1000000);
const lon = Math.floor(178.0236810462527 * 1000000);
console.log(lat, lon);
await this.connection.sendCommandSetAdvertLatLon(lat, lon);
},
async sendCommandAddUpdateContact() {
const publicKey = new Uint8Array([148, 63, 175, 162, 88, 212, 192, 40, 214, 185, 213, 140, 42, 145, 194, 186, 70, 71, 112, 68, 0, 192, 65, 4, 105, 143, 230, 50, 162, 79, 247, 192]);
const type = Constants.AdvType.Chat;
const flags = 0;
const outPathLen = 0;
const outPath = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
const advName = "Boaty";
const lastAdvert = 1739244825;
const advLat = 0;
const advLon = 0;
await this.connection.sendCommandAddUpdateContact(publicKey, type, flags, outPathLen, outPath, advName, lastAdvert, advLat, advLon);
},
async sendCommandSyncNextMessage() {
await this.connection.sendCommandSyncNextMessage();
},
async sendMessage(contact) {
// ask user for message
const message = prompt("Enter message to send");
if(!message){
return;
}
// send message
const response = await this.connection.sendTextMessage(contact.publicKey, message);
console.log(response);
},
async resetPath(contact) {
// remove contact from device
await this.connection.sendCommandResetPath(contact.publicKey);
// reload contacts
await this.loadContacts();
},
async removeContact(contact) {
// ask user to confirm action
if(!confirm("Are you sure you want to remove this contact?")){
return;
}
// remove contact from device
await this.connection.sendCommandRemoveContact(contact.publicKey);
// reload contacts
await this.loadContacts();
},
async setPath(contact) {
const newOutPath = [
0xf4,
];
// create out path
const outPath = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
for(var i = 0; i < newOutPath.length; i++){
outPath[i] = newOutPath[i];
}
// update contact details
contact.outPathLen = newOutPath.length;
contact.outPath = outPath;
// update contact
await this.connection.sendCommandAddUpdateContact(contact.publicKey, contact.type, contact.flags, contact.outPathLen, contact.outPath, contact.advName, contact.lastAdvert, contact.advLat, contact.advLon);
},
bytesToHex(uint8Array) {
return Array.from(uint8Array).map(byte => byte.toString(16).padStart(2, '0')).join('');
},
},
}).mount('#app');
</script>
</body>
</html>