|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
using System.IO.Ports;
|
|
|
|
|
|
|
|
|
|
namespace BirdyFlash.Lib {
|
|
|
|
|
public enum BirdyType {
|
|
|
|
|
Invalid = -1,
|
|
|
|
|
Slim = 0,
|
|
|
|
|
BirdyWP_Atmega1281 = 1,
|
|
|
|
|
BirdyE = 2,
|
|
|
|
|
}
|
|
|
|
|
public class BirdyComm : IDisposable {
|
|
|
|
|
public BirdyType type = BirdyType.Invalid;
|
|
|
|
|
public CommPort commPort;
|
|
|
|
|
public BirdyComm() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public BirdyComm SetPort(CommPort port) {
|
|
|
|
|
this.commPort = port;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
public static byte Checksum(byte[] data, int length) {
|
|
|
|
|
byte r = 0x00;
|
|
|
|
|
for (int i = 0; i < length; i++)
|
|
|
|
|
r = (byte)((r + data[i] % 0x100) & 0xFF);
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
public async Task<bool> AskHighspeed() {
|
|
|
|
|
Console.WriteLine("AskHighspeed {0}", this.commPort);
|
|
|
|
|
return await this.commPort.SwitchHighspeed();
|
|
|
|
|
}
|
|
|
|
|
public async Task<bool> AskLowspeed() {
|
|
|
|
|
Console.WriteLine("AskLowspeed {0}", this.commPort);
|
|
|
|
|
return await this.commPort.SwitchLowspeed();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task ForceGotoProg() {
|
|
|
|
|
Console.WriteLine("ForceGotoProg {0}", this.commPort);
|
|
|
|
|
await Task.Run(() => this.commPort.ForceGotoProg());
|
|
|
|
|
}
|
|
|
|
|
public async Task<DeviceInfo> TryGetDeviceInfo() {
|
|
|
|
|
var task = this.GetDeviceInfo();
|
|
|
|
|
if (await Task.WhenAny(task, Task.Delay(2000)) == task)
|
|
|
|
|
return task.Result;
|
|
|
|
|
else {
|
|
|
|
|
this.Dispose();
|
|
|
|
|
throw new TimeoutException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<bool> TryWriteE2P(DeviceE2PContents e2pContents) {
|
|
|
|
|
var task = this.GetDeviceInfo();
|
|
|
|
|
if (await Task.WhenAny(task, Task.Delay(2000)) == task) {
|
|
|
|
|
//if (task.Result.model != e2pContents.deviceInfo.model) throw new Exception("Wrong Device Data");
|
|
|
|
|
switch (type) {
|
|
|
|
|
case BirdyType.Slim:
|
|
|
|
|
for (int i = 0; i < 0xCD; i++) {
|
|
|
|
|
var block = e2pContents.data.Skip(i * 0x80).Take(0x80);
|
|
|
|
|
await this.SetE2PBlock((ushort)(i * 0x80), block.ToArray());
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
case BirdyType.BirdyE:
|
|
|
|
|
case BirdyType.BirdyWP_Atmega1281:
|
|
|
|
|
for (int i = 0; i < 0xF2; i++) {
|
|
|
|
|
var block = e2pContents.data.Skip(i * 0x20).Take(0x20);
|
|
|
|
|
await this.SetE2PBlock((ushort)(i * 0x20), block.ToArray());
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
default:
|
|
|
|
|
throw new Exception("Pager Model not yet supported");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.Dispose();
|
|
|
|
|
throw new TimeoutException();
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
public async Task<DeviceE2PContents> TryReadE2P() {
|
|
|
|
|
var task = this.GetDeviceInfo();
|
|
|
|
|
if (await Task.WhenAny(task, Task.Delay(2000)) == task) {
|
|
|
|
|
var deviceE2PContents = new DeviceE2PContents
|
|
|
|
|
{
|
|
|
|
|
data = new List<byte>(),
|
|
|
|
|
deviceInfo = task.Result,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
case BirdyType.Slim:
|
|
|
|
|
for (int i = 0; i < 0xCD; i++) {
|
|
|
|
|
var block = await this.GetE2PBlock((ushort)(i * 0x80), 0x80);
|
|
|
|
|
deviceE2PContents.data.AddRange(block.data.Skip(5).Take(0x80));
|
|
|
|
|
//Console.WriteLine(BitConverter.ToString(block.data));
|
|
|
|
|
}
|
|
|
|
|
return deviceE2PContents;
|
|
|
|
|
case BirdyType.BirdyE:
|
|
|
|
|
for (int i = 0; i < 1953; i++) {
|
|
|
|
|
var block = await this.GetE2PBlock((ushort)(i * 0x20), 0x20);
|
|
|
|
|
deviceE2PContents.data.AddRange(block.data.Skip(5).Take(0x20));
|
|
|
|
|
//Console.WriteLine(BitConverter.ToString(block.data));
|
|
|
|
|
}
|
|
|
|
|
return deviceE2PContents;
|
|
|
|
|
case BirdyType.BirdyWP_Atmega1281:
|
|
|
|
|
for (int i = 0; i < 0xF2; i++) {
|
|
|
|
|
var block = await this.GetE2PBlock((ushort)(i * 0x20), 0x20);
|
|
|
|
|
deviceE2PContents.data.AddRange(block.data.Skip(5).Take(0x20));
|
|
|
|
|
//Console.WriteLine(BitConverter.ToString(block.data));
|
|
|
|
|
}
|
|
|
|
|
return deviceE2PContents;
|
|
|
|
|
default:
|
|
|
|
|
throw new Exception("Pager Model not yet supported");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.Dispose();
|
|
|
|
|
throw new TimeoutException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public async Task<bool> TryE2PResetViaBootloader() {
|
|
|
|
|
var task = this.GetDeviceInfo();
|
|
|
|
|
if (await Task.WhenAny(task, Task.Delay(2000)) == task) {
|
|
|
|
|
var deviceE2PContents = new DeviceE2PContents
|
|
|
|
|
{
|
|
|
|
|
data = new List<byte>(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
case BirdyType.Slim:
|
|
|
|
|
Console.WriteLine("E2PResetViaBootloader {0}", this.commPort);
|
|
|
|
|
this.commPort.SendCommand(new byte[] { 0x18, 0x08 });
|
|
|
|
|
return true;
|
|
|
|
|
/*case BirdyType.BirdyWP_Atmega1281:
|
|
|
|
|
Console.WriteLine("E2PResetViaBootloader {0}", this.commPort);
|
|
|
|
|
this.commPort.SendCommand(new byte[] { 0x18, 0x08 });
|
|
|
|
|
return true;*/
|
|
|
|
|
default:
|
|
|
|
|
throw new Exception("Pager Model not yet supported");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.Dispose();
|
|
|
|
|
throw new TimeoutException();
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
public async Task<bool> TryChangeSerialNumber(string newSerial) {
|
|
|
|
|
var task = this.GetDeviceInfo();
|
|
|
|
|
if (await Task.WhenAny(task, Task.Delay(2000)) == task) {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case BirdyType.Slim:
|
|
|
|
|
Console.WriteLine("ChangeSerialNumber {0}", this.commPort);
|
|
|
|
|
this.commPort.SendCommand(Encoding.ASCII.GetBytes('\x13' + newSerial));
|
|
|
|
|
return true;
|
|
|
|
|
/*case BirdyType.BirdyWP_Atmega1281:
|
|
|
|
|
Console.WriteLine("E2PResetViaBootloader {0}", this.commPort);
|
|
|
|
|
this.commPort.SendCommand(new byte[] { 0x18, 0x08 });
|
|
|
|
|
return true;*/
|
|
|
|
|
default:
|
|
|
|
|
throw new Exception("Pager Model not yet supported");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.Dispose();
|
|
|
|
|
throw new TimeoutException();
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
public async Task<bool> TryRestart() {
|
|
|
|
|
var task = this.GetDeviceInfo();
|
|
|
|
|
if (await Task.WhenAny(task, Task.Delay(2000)) == task) {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case BirdyType.Slim:
|
|
|
|
|
Console.WriteLine("Restart {0}", this.commPort);
|
|
|
|
|
this.commPort.SendCommand(new byte[] { 0x08 });
|
|
|
|
|
return true;
|
|
|
|
|
/*case BirdyType.BirdyWP_Atmega1281:
|
|
|
|
|
Console.WriteLine("E2PResetViaBootloader {0}", this.commPort);
|
|
|
|
|
this.commPort.SendCommand(new byte[] { 0x18, 0x08 });
|
|
|
|
|
return true;*/
|
|
|
|
|
default:
|
|
|
|
|
throw new Exception("Pager Model not yet supported");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.Dispose();
|
|
|
|
|
throw new TimeoutException();
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
public async Task<DeviceInfo> GetDeviceInfo() {
|
|
|
|
|
Console.WriteLine("GetDeviceInfo {0}", this.commPort);
|
|
|
|
|
var task = this.commPort.SendCommand(new byte[] { 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff });
|
|
|
|
|
await task.Task;
|
|
|
|
|
var res = task.Task.Result;
|
|
|
|
|
if (res.data.Length < 10) throw new DataMisalignedException();
|
|
|
|
|
var resStr = Encoding.UTF8.GetString(res.data, 0x01, res.dataLength - 1);
|
|
|
|
|
var resParts = resStr.Split(',');
|
|
|
|
|
|
|
|
|
|
Console.WriteLine(resStr);
|
|
|
|
|
switch (resParts[0]) {
|
|
|
|
|
case "BirdyIOT":
|
|
|
|
|
this.type = BirdyType.Slim;
|
|
|
|
|
return new DeviceInfo
|
|
|
|
|
{
|
|
|
|
|
firstIDK = res.data[0],
|
|
|
|
|
model = resParts[0],
|
|
|
|
|
firmwareVersion = resParts[1],
|
|
|
|
|
hardwareVersion = resParts[2],
|
|
|
|
|
e2pVersion = resParts[3],
|
|
|
|
|
idk1 = resParts[4],
|
|
|
|
|
deviceSerial = resParts[5],
|
|
|
|
|
};
|
|
|
|
|
case "BirdyWP-128BOS":
|
|
|
|
|
case "BirdyWP-128r3BOS":
|
|
|
|
|
case "BirdyW":
|
|
|
|
|
this.type = BirdyType.BirdyWP_Atmega1281;
|
|
|
|
|
return new DeviceInfo
|
|
|
|
|
{
|
|
|
|
|
firstIDK = res.data[0],
|
|
|
|
|
model = resParts[0],
|
|
|
|
|
firmwareVersion = resParts[1],
|
|
|
|
|
hardwareVersion = resParts[2],
|
|
|
|
|
e2pVersion = resParts[3],
|
|
|
|
|
idk1 = resParts[4],
|
|
|
|
|
deviceSerial = resParts[5],
|
|
|
|
|
};
|
|
|
|
|
case "BirdyE":
|
|
|
|
|
this.type = BirdyType.BirdyE;
|
|
|
|
|
return new DeviceInfo
|
|
|
|
|
{
|
|
|
|
|
firstIDK = 0x00,
|
|
|
|
|
model = resParts[0],
|
|
|
|
|
firmwareVersion = resParts[1],
|
|
|
|
|
hardwareVersion = resParts[2],
|
|
|
|
|
e2pVersion = "",
|
|
|
|
|
idk1 = "",
|
|
|
|
|
deviceSerial = "-UNKNOWN-",
|
|
|
|
|
};
|
|
|
|
|
default:
|
|
|
|
|
this.type = BirdyType.Invalid;
|
|
|
|
|
return new DeviceInfo
|
|
|
|
|
{
|
|
|
|
|
firstIDK = 0x00,
|
|
|
|
|
model = "",
|
|
|
|
|
firmwareVersion = "",
|
|
|
|
|
hardwareVersion = "",
|
|
|
|
|
e2pVersion = "",
|
|
|
|
|
idk1 = "",
|
|
|
|
|
deviceSerial = "",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public async Task<RXPacket> GetE2PBlock(UInt16 address, byte size = 0x80) {
|
|
|
|
|
Console.WriteLine("GetE2PBlock {0} {1} {2}", this.commPort, address, size);
|
|
|
|
|
byte[] cmd = null;
|
|
|
|
|
switch(type) {
|
|
|
|
|
case BirdyType.Slim:
|
|
|
|
|
cmd = new byte[] { 0x29, 0x00, 0x00, (byte)(address >> 8), (byte)address, size };
|
|
|
|
|
break;
|
|
|
|
|
case BirdyType.BirdyE:
|
|
|
|
|
case BirdyType.BirdyWP_Atmega1281:
|
|
|
|
|
cmd = new byte[] { 0x05, (byte)(address >> 8), (byte)address, size };
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
var task = this.commPort.SendCommand(cmd);
|
|
|
|
|
await task.Task;
|
|
|
|
|
var res = task.Task.Result;
|
|
|
|
|
if (type == BirdyType.Slim) {
|
|
|
|
|
if (!res.data.Take(5).SequenceEqual(cmd.Take(5))) throw new DataMisalignedException(string.Format("Response Memory Address is not what requested\ntx>{0}\nrx<{1}", BitConverter.ToString(cmd.Take(5).ToArray()), BitConverter.ToString(res.data.Take(5).ToArray())));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (res.crc != res.rcrc) throw new DataMisalignedException("CRC Mismatch");
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
public async Task<RXPacket> SetE2PBlock(UInt16 address, byte[] data) {
|
|
|
|
|
Console.WriteLine("SetE2PBlock {0} {1} [{2}]", this.commPort, address, data.Length);
|
|
|
|
|
List<byte> cmd = null;
|
|
|
|
|
switch (type) {
|
|
|
|
|
case BirdyType.Slim:
|
|
|
|
|
cmd = new List<byte> { 0x2A, 0x00, 0x00, (byte)(address >> 8), (byte)address };
|
|
|
|
|
break;
|
|
|
|
|
case BirdyType.BirdyE:
|
|
|
|
|
case BirdyType.BirdyWP_Atmega1281:
|
|
|
|
|
cmd = new List<byte> { 0x07, (byte)(address >> 8), (byte)address };
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
cmd.AddRange(data);
|
|
|
|
|
var task = this.commPort.SendCommand(cmd.ToArray());
|
|
|
|
|
await task.Task;
|
|
|
|
|
var res = task.Task.Result;
|
|
|
|
|
if (type == BirdyType.Slim) {
|
|
|
|
|
if (!res.data.Take(5).SequenceEqual(cmd.Take(5))) throw new DataMisalignedException(string.Format("Response Memory Address is not what requested\ntx>{0}\nrx<{1}", BitConverter.ToString(cmd.Take(5).ToArray()), BitConverter.ToString(res.data.Take(5).ToArray())));
|
|
|
|
|
if (res.data[5] != res.data.Length - 6) throw new DataMisalignedException(string.Format("length mismatch {0} != {1}", res.data[5], res.data.Length - 6));
|
|
|
|
|
if (!res.data.Skip(6).SequenceEqual(cmd.Skip(5))) throw new DataMisalignedException(string.Format("Response Copy is not what sent\ntx>{0}\nrx<{1}", BitConverter.ToString(cmd.Skip(5).ToArray()), BitConverter.ToString(res.data.Skip(6).ToArray())));
|
|
|
|
|
}
|
|
|
|
|
if (type == BirdyType.BirdyWP_Atmega1281 || type == BirdyType.BirdyE) {
|
|
|
|
|
if (!res.data.Take(3).SequenceEqual(cmd.Take(3))) throw new DataMisalignedException(string.Format("Response Memory Address is not what requested\ntx>{0}\nrx<{1}", BitConverter.ToString(cmd.Take(3).ToArray()), BitConverter.ToString(res.data.Take(3).ToArray())));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (res.crc != res.rcrc) throw new DataMisalignedException("CRC Mismatch");
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<RXPacket> GetBKIdentities(int offset) {
|
|
|
|
|
Console.WriteLine("GetBKIdentities {0} {1}", this.commPort, offset);
|
|
|
|
|
byte[] cmd = null;
|
|
|
|
|
switch (type) {
|
|
|
|
|
case BirdyType.Slim:
|
|
|
|
|
cmd = new byte[] { 0xF3, (byte)(offset * 0x20) };
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
var task = this.commPort.SendCommand(cmd);
|
|
|
|
|
await task.Task;
|
|
|
|
|
var res = task.Task.Result;
|
|
|
|
|
if (type == BirdyType.Slim) {
|
|
|
|
|
if (!res.data.Take(5).SequenceEqual(cmd.Take(5))) throw new DataMisalignedException(string.Format("Response Memory Address is not what requested\ntx>{0}\nrx<{1}", BitConverter.ToString(cmd.Take(5).ToArray()), BitConverter.ToString(res.data.Take(5).ToArray())));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (res.crc != res.rcrc) throw new DataMisalignedException("CRC Mismatch");
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
public void Dispose() {
|
|
|
|
|
Console.WriteLine("Dispose {0}", this.commPort);
|
|
|
|
|
this.commPort.Dispose();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|