You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

455 lines
17 KiB
HTML

2 years ago
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
<script src="https://unpkg.com/downloadjs@1.4.7"></script>
<script src="https://unpkg.com/tone@14.7.77/build/Tone.js" async></script>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
</head>
<body>
<div id="app">
<v-app>
<v-app-bar app color="blue darken-2 white--text">
<v-img src="birdytiger.png" max-width="48px" max-height="48px"></v-img>
<v-toolbar-title>
<b>Birdy Flash Tools 2.0</b>
<sub>by <a target="_blank" style="color: white;" href="https://t.me/spottychee">cheetah.cat</a> (<a style="color: white;" target="_blank" href="https://t.me/spottychee">telegram</a>)</sub>
2 years ago
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-content>
<v-container v-if="global.step == 0">
<v-btn color="success" raised @click="openSerial()">Open Serial-Port</v-btn>
</v-container>
<v-container v-if="global.step == 1">
<v-row>
<v-col>
<pre>Selected Device: {{ global.selectedDevice.model }}
Hardware Version: {{ global.selectedDevice.hardwareVersion }}
Firmware Version: {{ global.selectedDevice.firmwareVersion }}
E2P Version: {{ global.selectedDevice.e2pVersion }}
Serial: {{ global.selectedDevice.deviceSerial }}
</pre>
</v-col>
<v-col>
<v-btn @click="writeE2P()">Write E2P</v-btn>
<v-btn @click="dumpE2P()">Dump E2P</v-btn>
<v-dialog v-model="changeSerial.dialog" width="auto">
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" v-bind="attrs" v-on="on">Change Serial</v-btn>
</template>
<v-card>
<v-card-text>
<v-text-field readonly v-model="changeSerial.oldSerial"></v-text-field>
<v-text-field v-model="changeSerial.newSerial"></v-text-field>
</v-card-text>
<v-card-actions>
<v-btn color="primary" block @click="applyChangeSerial(); changeSerial.dialog = false">Close Dialog</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
2 years ago
</v-col>
</v-row>
<v-col v-if="global.selectedDevice.model == 'BirdyIOT'">
<h2>Birdy Slim IoT : Features</h2>
<v-col cols="12" sm="6">
<v-text-field label="AppEUI" v-model="lora.AppEUI"></v-text-field>
<v-text-field label="DevEUI" v-model="lora.DevEUI"></v-text-field>
<v-text-field label="AppSKey" v-model="lora.AppSKey"></v-text-field>
<v-text-field label="NwkSKey" v-model="lora.NwkSKey"></v-text-field>
<v-text-field label="DevAddr" v-model="lora.DevAddr"></v-text-field>
<v-btn @click="writeLoRaWAN()" >Write LoRaWAN</v-btn>
</v-col>
<v-col class="d-flex" cols="12" sm="6">
<v-btn color="success" :disabled="step1.ringtoneSelection==null" raised @click="decodeRingtone()">Load</v-btn>
<v-btn color="warning" :disabled="step1.ringtoneSelection==null || !step1.dirty" raised @click="encodeRingtone()">Store</v-btn>
<v-btn color="error" :disabled="step1.ringtoneSelection==null" raised @click="cleanRingtone()">Clear</v-btn>
</v-col>
</v-col>
</v-container>
<!--
hello to anyone, who reads this :3
-->
</v-content>
<v-footer>
<v-card flat tile width="100%" class="red lighten-1 text-center">
<v-card-text class="white--text">
{{ new Date().getFullYear() }} - <strong>cheetah.cat</strong> - disclaimer.txt
</v-card-text>
</v-card>
</v-footer>
</v-app>
</div>
<script>
// im working on a full version of this.
class TPL_BirdySlimConfig {
constructor() {
this.buffer = null
}
loadBuffer(buffer) {
this.buffer = buffer
return this
}
saveBuffer() {
return this.buffer
}
writeNotes(melody) {
console.log(melody)
let offset = 0x0AE0 + (melody.ringNumber * 90)
this.buffer[ offset++ ] = melody.melodyHeader
for(let i=0;i<45;i++) {
if (!(!!melody.notes[i])) {
this.buffer[ offset++ ] = 0x00
this.buffer[ offset++ ] = 0x00
} else {
let note = melody.notes[i] // 0note,1octave,2length
this.buffer[ offset++ ] = (note[ 1 ] << 4 & 0xf0) | (note[ 0 ] & 0xf)
this.buffer[ offset++ ] = note[ 2 ]
}
}
}
readNotesFromBuffer(ringNumber) {
const offset = 0x0AE0 + (ringNumber * 90)
let music = JSON.parse(JSON.stringify(this.buffer)).slice(offset,offset+1+(2 * 45))
//console.log(music.map(x=>x.toString(16).toUpperCase()).join(''))
/////if (typeof(Buffer)==='undefined') music = new Int8Array(music) // if in browser, we get arraybuffers
const notes = [], melodyHeader = music[0]
for (let i=1; i < music.length; i+=2) {
const note = music[i] >> 0 & 0xf,
octave = music[i] >> 4 & 0xf,
length = music[i+1] // 1 Byte for the Length
if (note == 0) break;
//console.log(`${ noteNames[note] } ${ octave } ${ length*10 }ms`)
notes.push([note, octave, length ])
}
return {
ringNumber,
melodyHeader,
notes,
}
}
}
function parseHexString(str) {
const result = []
while (str.length >= 2) {
result.push(parseInt(str.substring(0, 2), 16))
str = str.substring(2, str.length)
}
return result
}
class SerialPort {
constructor(device) {
this.device = device
this.writer = null
this.reader = null
}
connect(baudRate = 57600) {
let readLoop = async () => {
while (this.open && this.device?.readable) {
this.open = true
this.reader = this.device.readable.getReader()
console.log('starting read loop')
try {
while (this.open) {
console.log('reading...')
const { value, done } = await this.reader.read()
if (done) {
// |reader| has been canceled.
this.open = false
break;
}
console.log('read complete:', value, done)
}
} catch (error) {
console.error('reading error', error)
} finally {
this.reader.releaseLock()
}
}
}
return this.device.open({
baudRate: 57600,
stopBits: 1,
dataBits: 8,
bufferSize: 1,
parity: 'none',
flowControl: 'none'
})
.then(async _=> {
this.open = !!this.device?.readable
this.writer = this.device.writable.getWriter()
this.reader = this.device.readable.getReader()
//readLoop()
})
.catch(console.error)
}
disconnect() {
return this.device.close()
}
async send(data) {
await this.writer.write(data)
//this.writer.releaseLock()
return true
}
}
class TPLBirdy {
constructor(port) {
this.port = port
}
checksum(data) {
let cs = 0x00
for (let x of data)
cs = (cs + x % 256) & 0xFF
return cs & 0xFF
}
async sendCMD(data) {
let cmd = [0x02, data.length].concat(data, [0x03])
cmd.push(this.checksum(cmd))
cmd.push(0x04)
let bffr = new Uint8Array(cmd)
console.log('>', bffr.map(a=>a.toString(16)).join(' '))
await this.port.send(bffr)
}
responseToString(input) {
return input.map(a=>String.fromCharCode(a)).join('')
}
async wait4Response() {
{
console.log('requesting first read', this.port.reader)
let { value, done } = await this.port.reader.read()
console.log('read', value[0], done)
if (value[0] != 0x02) throw 'First byte isnt 0x02, something is off'
}
let dataLength = -1
{
let { value, done } = await this.port.reader.read()
dataLength = value[0]
}
console.log('<', dataLength, 'bytes')
let dataArray = []
{
for (let i = 0; i < dataLength; i++) {
let { value, done } = await this.port.reader.read()
dataArray.push(value[0])
}
}
{
let { value, done } = await this.port.reader.read()
if (value[0] != 0x03) throw 'Payload EOF byte isnt 0x03, something is off'
}
console.log('<', dataArray)
let dataChecksum = -1
{
let { value, done } = await this.port.reader.read()
dataChecksum = value[0]
}
const recvChecksumData = [0x02, dataLength].concat(dataArray, [0x03])
const recvChecksum = this.checksum(recvChecksumData)
{
let { value, done } = await this.port.reader.read()
if (value[0] != 0x04) throw 'Checksum EOF byte isnt 0x04, something is off'
}
if (dataChecksum != recvChecksum) throw 'Checksum mismatch'
return dataArray
}
async getDeviceInfo() {
await this.sendCMD([0x01,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff])
let deviceInfo = await this.wait4Response()
const devInfo = {
type: false,
firstIDK: null,
model: '',
firmwareVersion: '',
hardwareVersion: '',
e2pVersion: '',
idk: '',
deviceSerial: '',
}
let parts = this.responseToString(deviceInfo).substring(1).split(',')
console.log(parts)
switch (parts[0]) {
case "BirdyIOT":
devInfo.type = 'Slim'
devInfo.firstIDK = parts[0]
devInfo.model = parts[0]
devInfo.firmwareVersion = parts[1]
devInfo.hardwareVersion = parts[2]
devInfo.e2pVersion = parts[3]
devInfo.idk = parts[4]
devInfo.deviceSerial = parts[5]
break;
case "BirdyWP-128BOS":
case "BirdyWP-128r3BOS":
case "BirdyW":
devInfo.type = 'BirdyWP_Atmega1281'
devInfo.firstIDK = parts[0]
devInfo.model = parts[0]
devInfo.firmwareVersion = parts[1]
devInfo.hardwareVersion = parts[2]
devInfo.e2pVersion = parts[3]
devInfo.idk = parts[4]
devInfo.deviceSerial = parts[5]
break;
case "BirdyWP-r3":
devInfo.type = 'BirdyWP-r3'
devInfo.firstIDK = parts[0]
devInfo.model = parts[0]
devInfo.firmwareVersion = parts[1]
devInfo.hardwareVersion = parts[2]
devInfo.e2pVersion = parts[3]
devInfo.idk = parts[4]
devInfo.deviceSerial = parts[5]
break;
2 years ago
case "BirdyE":
devInfo.type = 'BirdyE'
devInfo.firstIDK = 0x00
devInfo.model = parts[0]
devInfo.firmwareVersion = parts[1]
devInfo.hardwareVersion = parts[2]
devInfo.e2pVersion = ''
devInfo.idk = ''
devInfo.deviceSerial = "-UNKNOWN-"
break;
}
this.deviceInfo = devInfo
return devInfo
}
async writeSerial(newSerial) {
switch (this.deviceInfo.type) {
case 'Slim':
if (newSerial.length != 13) throw 'insufficient serial length'
await this.sendCMD([ 0x13, ].concat(newSerial.split('').map(x => x.charCodeAt())))
console.log(this.responseToString(await this.wait4Response()))
break
case 'BirdyWP-r3': //note to myself, needs a 0 in front of the serial 1234 -> 01234
//if (newSerial.length != 13) throw 'insufficient serial length'
await this.sendCMD([ 0x13, ].concat(`0${ newSerial }`.split('').map(x => x.charCodeAt())))
console.log(this.responseToString(await this.wait4Response()))
break
2 years ago
}
}
}
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
serial: {},
sPort: null,
bPager: null,
step1: {
birdyRingtoneSelection: [],
ringtoneSelection: null,
dirty: false,
},
global: {
step: 0,
selectedDevice: { type: null },
},
lora: {
AppEUI: '00 00 00 00 00 00 00 00',
DevEUI: '00 00 00 00 00 00 00 00',
AppSKey: '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00',
NwkSKey: '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00',
DevAddr: '00 00 00 00',
},
changeSerial: {
dialog: false,
oldSerial: '',
newSerial: ''
},
2 years ago
}
},
created() {
},
methods: {
// step0
openSerial() {
const filters = [
{ 'vendorId': 0x0403, 'productId': 0x6001 }, // Future Technology Devices International, Ltd FT232 Serial (UART) IC
]
const usbVendorId = '0x0403'
return navigator.serial.requestPort({ filters: [ { usbVendorId }] })
.then(device => {
this.sPort = new SerialPort(device)
this.sPort.connect(57600).then(_ => {
this.bPager = new TPLBirdy(this.sPort)
})
.then(async _ => {
let deviceInfo = await this.bPager.getDeviceInfo()
console.log('deviceInfo', deviceInfo)
if (deviceInfo.type === false) {
this.global.step = -1
alert('unknown pager detected')
} else {
this.global.step = 1
this.global.selectedDevice = deviceInfo
this.changeSerial.oldSerial = deviceInfo.deviceSerial
2 years ago
}
})
.catch(error => {
alert(error)
})
})
},
async rawHex(cmd) {
await this.bPager.sendCMD(parseHexString(cmd))
let resp = this.bPager.wait4Response()
console.log(resp)
},
async writeLoRaWAN() {
loraProvCMD = [ 0x2B, 0x00, 0x44, 0x00 ].concat(
parseHexString(this.lora.AppSKey.replaceAll(' ', '')),
parseHexString(this.lora.AppEUI.replaceAll(' ', '')),
parseHexString(this.lora.DevEUI.replaceAll(' ', '')),
parseHexString(this.lora.AppSKey.replaceAll(' ', '')),
parseHexString(this.lora.NwkSKey.replaceAll(' ', '')),
parseHexString(this.lora.DevAddr.replaceAll(' ', '')),
)
console.log(loraProvCMD)
await this.bPager.sendCMD(loraProvCMD)
let resp = this.bPager.wait4Response()
console.log(resp)
},
async applyChangeSerial() {
await this.bPager.writeSerial(this.changeSerial.newSerial)
let resp = this.bPager.wait4Response()
console.log(resp)
let deviceInfo = await this.bPager.getDeviceInfo()
console.log('deviceInfo', deviceInfo)
if (deviceInfo.type === false) {
this.global.step = -1
alert('unknown pager detected')
} else {
this.global.step = 1
this.global.selectedDevice = deviceInfo
this.changeSerial.oldSerial = deviceInfo.deviceSerial
}
2 years ago
}
}
})
</script>
</body>
</html>