first commit
commit
fbf868b2f0
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
@ -0,0 +1,400 @@
|
||||
<!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://www.patreon.com/cheetahcat">patreon</a>)</sub>
|
||||
</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-btn color="warning" @click="changeSerialDialog()">Change Serial</v-btn>
|
||||
</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 "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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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',
|
||||
},
|
||||
}
|
||||
},
|
||||
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
|
||||
}
|
||||
})
|
||||
.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)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue