Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add web support #120

Closed
wants to merge 62 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
6e2eafc
feat: add support for web
pimlie Aug 19, 2023
e4d5949
Merge branch 'main' into feat-add-web-support
pimlie Aug 19, 2023
a1ec39f
chore: revert unneeded change
pimlie Aug 19, 2023
53f5982
fix: tests
pimlie Aug 19, 2023
d685750
chore: remove debug logs
pimlie Aug 19, 2023
9325ae0
refactor: refactor file picking & saving to support web
pimlie Aug 19, 2023
4f7dadf
feat: support key recovery using dictionary
pimlie Aug 19, 2023
bc23bdd
feat: set portName for web serial
pimlie Aug 19, 2023
a1f36a0
fix: if releases is a list then containsKey doesnt exists
pimlie Aug 20, 2023
ebbe746
chore: rename preform to perform
pimlie Aug 20, 2023
113b31a
fix: set disabled on nav items instead of manually changing text color
pimlie Aug 20, 2023
d9918b2
fix: dont show reader mode toggle for chameleon lites
pimlie Aug 20, 2023
3574b82
feat: show generic/both chameleon devices on connect page instead of …
pimlie Aug 20, 2023
a559778
fix: refactor reading data from serial connection
pimlie Aug 20, 2023
6dea3ea
feat: update favicons
pimlie Aug 20, 2023
eac89a8
fix: remove old code
pimlie Aug 20, 2023
cbdecdb
feat: dont try to update the fw automatically on web and show an unsu…
pimlie Aug 20, 2023
d672cfb
Merge branch 'main' into feat-add-web-support
Foxushka Aug 20, 2023
10ce49e
Correct merge
Foxushka Aug 20, 2023
0116136
chore: add some improvements to try to prevent hanging on device disc…
pimlie Aug 20, 2023
b3ed064
Merge remote-tracking branch 'refs/remotes/origin/feat-add-web-suppor…
pimlie Aug 20, 2023
baaabf9
feat: refactor chameleon ultra detection
pimlie Aug 20, 2023
44f1036
feat: add device name getter on abstract serial
pimlie Aug 20, 2023
c6a0b11
feat: make firmware flashing using zip work on web
pimlie Aug 20, 2023
ba7ceeb
feat: display dfu specific cards on connect page with both flash late…
pimlie Aug 22, 2023
a7066de
Merge branch 'main' into feat-add-web-support
pimlie Aug 22, 2023
9a1f59d
chore: resolve 'dart fix' issues
pimlie Aug 22, 2023
2cb5d56
fix: remove main app bar
pimlie Aug 22, 2023
82775df
fix: move mode & settings icons back to bottom on home
pimlie Aug 22, 2023
6151727
fix: revert moving actions to appbar on home & connect page
pimlie Aug 22, 2023
05b5cdc
fix: resolve merge issue, use custom tag color
pimlie Aug 22, 2023
f302fe5
fix: dark mode cards
pimlie Aug 22, 2023
6dbd85b
fix: extract correct sak/atqa for uid7 cards
pimlie Aug 22, 2023
81f499b
Merge branch 'main' into feat-add-web-support
pimlie Aug 24, 2023
0b3d493
chore: update readme
pimlie Aug 24, 2023
82bdbed
fix: performDisconnect should not be protected
pimlie Aug 24, 2023
ed3a1d4
fix: add some page headers back
pimlie Aug 24, 2023
2cb9106
chore: remove unneeded import
pimlie Aug 24, 2023
8bcd7dc
feat: add a/b button mode back to device settings dialog
pimlie Aug 24, 2023
8e0ae33
feat: add poc for cloudflare proxy
pimlie Aug 25, 2023
d6178f7
feat: refactor firmware flashing using composition
pimlie Aug 26, 2023
106960a
chore: remove duplicate layout builder
pimlie Aug 26, 2023
4cdd6f6
fix: disable/remove auto rebuilding on screen resize
pimlie Aug 26, 2023
4bafe48
chore: remove unused dependency
pimlie Aug 26, 2023
05dc1d0
fix: add reset factory settings back
pimlie Aug 26, 2023
fa283b5
chore: update readme
pimlie Aug 26, 2023
1583a4b
fix: add showConfirmDialog helper to ensure proper return type preven…
pimlie Aug 26, 2023
adc95ca
Merge branch 'main' into feat-add-web-support
pimlie Aug 27, 2023
3b671d1
Merge branch 'main' into feat-add-web-support
pimlie Aug 27, 2023
dde2427
fix: merge issues
pimlie Aug 27, 2023
2743a6b
chore: remove old/unused file
pimlie Aug 27, 2023
0b04957
fix: dont use gridview on connect page when no results
pimlie Aug 27, 2023
b940c37
fix: update instructions for updating firmware on web after entering dfu
pimlie Aug 27, 2023
a279d33
fix: add missing scrollview back in device settings
pimlie Aug 27, 2023
aeb4ebf
fix: add viaBLE prop back & improve error handling during firmware flash
pimlie Aug 27, 2023
55a6816
fix: try fix crc error by increasing receive timeout
pimlie Aug 27, 2023
5be7cd1
fix: only use async/sleep for web
pimlie Aug 27, 2023
226755b
fix: change button type
pimlie Aug 28, 2023
c054a9a
feat: ask user which device type is connected if unknown and user wan…
pimlie Aug 28, 2023
7f88aca
feat: add setting so users can disable mobile/vertical navigation
pimlie Aug 28, 2023
2a1be2d
chore: wording
pimlie Aug 28, 2023
db49a85
chore: switch sizer dependency
pimlie Aug 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ Key:
You might need to add your user to the `dialout` or, on Arch Linux, to the `uucp` group for the app to talk to the device. If your user is not in this group, you may get serial or permission errors.
It is also highly recommended to either uninstall or disable ModemManager (`sudo systemctl disable --now modemmanager`) as many distros ship ModemManager and it may interfere with communication.

#### Note for Web users:

You need to pair your Chameleon first before it shows up on the connect page, click on the handshake icon and select the relevant serial devices.

*Known issues*
- Chameleon Lite's are displayed as Ultra's on the connect page (but are correct after connecting)
> This is because the Web Serial API is quite limited with the [device information](https://developer.mozilla.org/en-US/docs/Web/API/SerialPort/getInfo) it returns as it only returns an usb vendor id & product id (which are the same for Ultra's & Lite's). So on the connect page any device will be displayed as an Ultra, after you connect to a specific device the correct device type will be detected by checking if the device supports reader mode (=Ultra) or not (=Lite)
- key recovery is not supported (yet)
- cannot download firmware due to CORS issue with nightly.link

## Contributing
Contributions are welcome, most stuff that needs to be done can either be found in our [issues](https://github.com/GameTec-live/ChameleonUltraGUI/issues) or on the [Project board](https://github.com/users/GameTec-live/projects/2)

Expand Down
29 changes: 27 additions & 2 deletions chameleonultragui/lib/bridge/chameleon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -343,12 +343,37 @@ class ChameleonCom {
Future<int> getFirmwareVersion() async {
var resp = await sendCmdSync(ChameleonCommand.getAppVersion, 0x00);
if (resp!.data.length != 2) throw ("Invalid data length");
return (resp.data[1] << 8) | resp.data[0];
final result = (resp.data[1] << 8) | resp.data[0];
log.d("ChamelonCom.getFirmwareVersion: $result");
pimlie marked this conversation as resolved.
Show resolved Hide resolved
return result;
}

Future<String> getDeviceChipID() async {
var resp = await sendCmdSync(ChameleonCommand.getDeviceChipID, 0x00);
return bytesToHex(resp!.data);
final result = bytesToHex(resp!.data);
log.d("ChamelonCom.getDeviceChipID: $result");
Foxushka marked this conversation as resolved.
Show resolved Hide resolved
return result;
}

Future<bool> detectChameleonUltra() async {
// Check if device is a ChameleonUltra by trying to switch to reader mode,
pimlie marked this conversation as resolved.
Show resolved Hide resolved
// a ChameleonLite will return success on setting to reader but the
// actual device mode will always be emulator
var isReader = await isReaderDeviceMode();
if (isReader) {
return true;
}

await setReaderDeviceMode(true);

isReader = await isReaderDeviceMode();
if (isReader) {
// Reset device mode back to emulator
await setReaderDeviceMode(false);
return true;
}

return false;
}

Future<String> getDeviceBLEAddress() async {
Expand Down
2 changes: 2 additions & 0 deletions chameleonultragui/lib/connector/serial_abstract.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class AbstractSerial {
return false;
}

Future pairDevices() async {} // For web only

Future<List> availableDevices() async {
return [];
}
Expand Down
10 changes: 5 additions & 5 deletions chameleonultragui/lib/connector/serial_android.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:chameleonultragui/connector/serial_abstract.dart';
import 'package:chameleonultragui/connector/serial_ble.dart';
import 'package:chameleonultragui/connector/serial_mobile.dart';
import 'package:chameleonultragui/connector/serial_ble.dart' as ble;
import 'package:chameleonultragui/connector/serial_mobile.dart' as mobile;
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';

// Class combines Android OTG and BLE serial
class AndroidSerial extends AbstractSerial {
BLESerial bleSerial = BLESerial();
MobileSerial mobileSerial = MobileSerial();
class SerialConnector extends AbstractSerial {
ble.SerialConnector bleSerial = ble.SerialConnector();
mobile.SerialConnector mobileSerial = mobile.SerialConnector();

@override
Future<bool> preformDisconnect() async {
Expand Down
2 changes: 1 addition & 1 deletion chameleonultragui/lib/connector/serial_ble.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Uuid dfuUUID = Uuid.parse("FE59");
Uuid dfuControl = Uuid.parse("8EC90001-F315-4F60-9FB8-838830DAEA50");
Uuid dfuFirmware = Uuid.parse("8EC90002-F315-4F60-9FB8-838830DAEA50");

class BLESerial extends AbstractSerial {
class SerialConnector extends AbstractSerial {
FlutterReactiveBle flutterReactiveBle = FlutterReactiveBle();
QualifiedCharacteristic? txCharacteristic;
QualifiedCharacteristic? rxCharacteristic;
Expand Down
2 changes: 1 addition & 1 deletion chameleonultragui/lib/connector/serial_mobile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:flutter/services.dart';
import 'package:usb_serial/usb_serial.dart';

// Class for Android Serial Communication
class MobileSerial extends AbstractSerial {
class SerialConnector extends AbstractSerial {
Map<String, UsbDevice> deviceMap = {};
List<Uint8List> messagePool = []; // TODO: Fix or rewrite on release
UsbPort? port;
Expand Down
2 changes: 1 addition & 1 deletion chameleonultragui/lib/connector/serial_native.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_libserialport/flutter_libserialport.dart';
import 'serial_abstract.dart';

class NativeSerial extends AbstractSerial {
class SerialConnector extends AbstractSerial {
// Class for PC Serial Communication
SerialPort? port;
SerialPort? checkPort;
Expand Down
7 changes: 7 additions & 0 deletions chameleonultragui/lib/connector/serial_stub.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'serial_abstract.dart';

class SerialConnector extends AbstractSerial {
SerialConnector() {
log.e("The serial stub class should not be used");
}
}
267 changes: 267 additions & 0 deletions chameleonultragui/lib/connector/serial_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
import 'dart:async';
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
import 'dart:typed_data';
import 'package:chameleonultragui/connector/serial_abstract.dart';
import 'package:chameleonultragui/helpers/general.dart';
import 'package:serial/serial.dart';

class DeviceInfo {
final SerialPort port;
final SerialPortInfo info;
final ChameleonConnectType connection = ChameleonConnectType.usb;
ChameleonDevice _type = ChameleonDevice.ultra;

DeviceInfo(this.port, this.info);

get deviceName {
if (type == ChameleonDevice.ultra) {
return 'Ultra';
}
if (type == ChameleonDevice.lite) {
return 'Lite';
}

return 'Unknown';
}

ChameleonDevice get type {
return _type;
}

set type(ChameleonDevice dev) => {
_type = dev
};
}

// Class for WebUSB API Serial Communication
class SerialConnector extends AbstractSerial {
Map<String, DeviceInfo> deviceMap = {};
List<Uint8List> messagePool = []; // TODO: Fix or rewrite on release
DeviceInfo? currentDevice;

ReadableStreamReader? reader;
bool keepReading = false;

@override
get device {
if (currentDevice != null) {
return currentDevice!.type;
}

return ChameleonDevice.none;
}

@override
get connectionType {
if (currentDevice != null) {
return currentDevice!.connection;
}

return ChameleonConnectType.none;
}

@override
Future<void> pairDevices() async {
try {
await window.navigator.serial.requestPort();
} catch (_) {
// ignore error
}
}

@override
Future<bool> preformDisconnect() async {
device = ChameleonDevice.none;
connectionType = ChameleonConnectType.none;
if (currentDevice != null) {
final readable = currentDevice!.port.readable;

if (readable.locked) {
if (reader != null) {
await reader!.cancel();
reader = null;
}

await readable.cancel();
}

await currentDevice!.port.close();

connected = false;
currentDevice = null;
return true;
}

connected = false; // For debug button
return false;
}

@override
Future<List> availableDevices() async {
device = ChameleonDevice.none;
connectionType = ChameleonConnectType.none;
deviceMap = {};

List output = [];

final pairedDevices = await window.navigator.serial.getPorts();
for (var deviceValue in pairedDevices) {
SerialPortInfo deviceInfo = deviceValue.getInfo();
// log.d('deviceInfo ${deviceInfo.hashCode} ${deviceInfo.usbVendorId} ${deviceInfo.usbProductId}');
if (deviceInfo.usbVendorId == 0x6868 || deviceInfo.usbVendorId == 0x1915) {
var portId = '${deviceInfo.usbVendorId}x${deviceInfo.usbProductId}';

deviceMap[portId] = DeviceInfo(deviceValue, deviceInfo);
output.add(portId);
}
}

return output;
}

@override
Future<List> availableChameleons(bool onlyDFU) async {
List output = [];

for (var devicePort in await availableDevices()) {
var device = deviceMap[devicePort];

if (device!.info.usbProductId == 1) { //}"Proxgrind") {
log.d("Found Chameleon ${device.deviceName}!");

if (device.info.usbVendorId == 0x1915) {
connectionType = ChameleonConnectType.dfu;
log.w("Chameleon is in DFU mode!");
}
}

if (onlyDFU) {
if (connectionType == ChameleonConnectType.dfu) {
output.add(
{'port': devicePort, 'device': device.type, 'type': connectionType});
}
} else {
output.add(
{'port': devicePort, 'device': device.type, 'type': connectionType});
}
}

return output;
}

@override
Future<bool> connectSpecific(devicePort) async {
await availableDevices();
connected = false;

if (deviceMap.containsKey(devicePort)) {
var deviceValue = deviceMap[devicePort] as DeviceInfo;
var serialPort = deviceValue.port;

try {
await serialPort.open(
baudRate: 115200,
dataBits: DataBits.eight,
stopBits: StopBits.one,
parity: Parity.none,
);

await serialPort.setSignals(
dataTerminalReady: true, // DTS
requestToSend: true, // RTS
// break: false, // Doesnt work
);
} catch (e) {
// ignore already connected/disconnected errors
var ignoreError = e.toString().contains('already');
if (!ignoreError) {
log.d('open port error: $e');
return false;
}
}

connected = true;
device = deviceValue.type;
currentDevice = deviceValue;

listen();

if (deviceValue.info.usbVendorId == 0x1915) {
connectionType = ChameleonConnectType.dfu;
log.w("Chameleon is in DFU mode!");
} else {
connectionType = ChameleonConnectType.usb;
}

return connected;
}
return false;
}

@override
Future<bool> write(Uint8List command, {bool firmware = false}) async {
try {
final writer = currentDevice!.port.writable.writer;

await writer.ready;
await writer.write(command);
await writer.close();

return true;
} catch (e) {
log.e('write error: $e');
}

return false;
}

Future listen () async {
final readable = currentDevice!.port.readable;
if (!readable.locked && reader == null) {
reader = currentDevice!.port.readable.reader;
}

keepReading = true;

while (connected && keepReading) {
try {
var result = await reader!.read();
if (result.done) {
keepReading = false;
// reader.cancel() has been called.
break;
}
// value is a Uint8Array.
messagePool.add(result.value);
} catch (e) {
log.e('read error: $e');
keepReading = false;
}
}

// Allow the serial port to be closed later.
reader!.releaseLock();
}

@override
Future<Uint8List> read(int length) async {
final completer = Completer<Uint8List>();
while (true) {
if (messagePool.isNotEmpty) {
var message = messagePool[0];
messagePool.removeWhere((item) => item == message);
completer.complete(message);
break;
}
await asyncSleep(100);
}

return completer.future;
}

@override
Future<void> finishRead() async {
messagePool = [];
}
}
Loading