mirror of
https://github.com/wisplite/parchment.git
synced 2026-06-27 13:47:08 -05:00
162 lines
3.4 KiB
Go
162 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"time"
|
|
|
|
"go.bug.st/serial"
|
|
"go.bug.st/serial/enumerator"
|
|
)
|
|
|
|
// Device manages the serial connection to the Cardputer.
|
|
type Device struct {
|
|
PortName string
|
|
Port serial.Port
|
|
Connected bool
|
|
}
|
|
|
|
var commandAck = []byte{0xAA, 0x06, 0x00, 0x00, 0x00}
|
|
|
|
// NewDevice creates a new Device instance.
|
|
func NewDevice() *Device {
|
|
return &Device{}
|
|
}
|
|
|
|
// FindCardputer searches for a connected Cardputer by VID.
|
|
func (d *Device) FindCardputer() (string, error) {
|
|
ports, err := enumerator.GetDetailedPortsList()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
for _, port := range ports {
|
|
if port.VID == "303a" { // Cardputer VID
|
|
d.PortName = port.Name
|
|
return port.Name, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("Cardputer not found")
|
|
}
|
|
|
|
// Connect opens the serial port and requests dev mode.
|
|
func (d *Device) Connect() error {
|
|
if d.PortName == "" {
|
|
return fmt.Errorf("no port name set")
|
|
}
|
|
|
|
mode := &serial.Mode{
|
|
BaudRate: 115200,
|
|
}
|
|
port, err := serial.Open(d.PortName, mode)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open port: %w", err)
|
|
}
|
|
|
|
d.Port = port
|
|
d.Connected = true
|
|
_ = d.Port.SetReadTimeout(100 * time.Millisecond)
|
|
|
|
// Send dev mode request
|
|
// 0xAA 0x04 0x00 0x00 0x00 is the command sequence for dev mode
|
|
err = d.sendCommand([]byte{0xAA, 0x04, 0x00, 0x00, 0x00})
|
|
if err != nil {
|
|
d.Close()
|
|
return fmt.Errorf("failed to enter dev mode: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExitDevMode sends the command to exit dev mode.
|
|
func (d *Device) ExitDevMode() error {
|
|
if d.Port == nil {
|
|
return fmt.Errorf("port not open")
|
|
}
|
|
|
|
// disable dev mode
|
|
if err := d.sendCommand([]byte{0xAA, 0x05, 0x00, 0x00, 0x00}); err != nil {
|
|
return fmt.Errorf("failed to exit dev mode: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close closes the serial port.
|
|
func (d *Device) Close() error {
|
|
if d.Port != nil {
|
|
// Try to exit dev mode before closing
|
|
_ = d.ExitDevMode()
|
|
|
|
// Allow some time for the command to be processed
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
err := d.Port.Close()
|
|
d.Port = nil
|
|
d.Connected = false
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Write sends data to the device.
|
|
func (d *Device) Write(data []byte) (int, error) {
|
|
if !d.Connected || d.Port == nil {
|
|
return 0, fmt.Errorf("device not connected")
|
|
}
|
|
return d.Port.Write(data)
|
|
}
|
|
|
|
func (d *Device) sendCommand(cmd []byte) error {
|
|
if d.Port == nil {
|
|
return fmt.Errorf("port not open")
|
|
}
|
|
if _, err := d.Port.Write(cmd); err != nil {
|
|
return fmt.Errorf("failed to send command: %w", err)
|
|
}
|
|
if err := d.waitForResponse(commandAck, 2*time.Second); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Device) waitForResponse(expected []byte, timeout time.Duration) error {
|
|
if d.Port == nil {
|
|
return fmt.Errorf("port not open")
|
|
}
|
|
if len(expected) == 0 {
|
|
return fmt.Errorf("expected response is empty")
|
|
}
|
|
|
|
deadline := time.NewTimer(timeout)
|
|
defer deadline.Stop()
|
|
|
|
readBuf := make([]byte, 64)
|
|
window := make([]byte, 0, len(expected)*4)
|
|
maxWindow := len(expected) * 4
|
|
if maxWindow < 64 {
|
|
maxWindow = 64
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-deadline.C:
|
|
return fmt.Errorf("timeout waiting for response %x", expected)
|
|
default:
|
|
n, err := d.Port.Read(readBuf)
|
|
if err != nil {
|
|
return fmt.Errorf("failed while waiting for response: %w", err)
|
|
}
|
|
if n == 0 {
|
|
continue
|
|
}
|
|
window = append(window, readBuf[:n]...)
|
|
if len(window) > maxWindow {
|
|
window = window[len(window)-maxWindow:]
|
|
}
|
|
if bytes.Contains(window, expected) {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|