Files
MengMengCode 22487dbb1f Add PC/SC backend support for USB SIM readers
- Implemented a new `unsupportedBackend` in `backend_stub.go` to handle unsupported platforms.
- Created a `Service` struct in `service.go` to manage interactions with smart cards, including session management and identity reading.
- Added methods for reading identity, checking readiness, and authenticating with USIM applications.
- Introduced a `scriptedCard` for testing purposes in `service_test.go` to simulate card responses.
- Defined necessary types and error handling in `types.go` for better clarity and usability.
- Developed a `PCSCAdapter` in `vowifi/pcsc_adapter.go` to integrate PC/SC service with Wi-Fi calling functionalities.
- Added a new SVG icon for USB SIM readers in `public/sim-reader.svg`.
- Implemented tests to ensure correct functionality and error handling in various scenarios.
2026-08-12 02:33:32 +08:00

122 lines
3.5 KiB
Go

package modem
import (
"context"
"errors"
"fmt"
"strings"
"time"
)
var (
ErrUnsupportedPlatform = errors.New("modem: platform is not supported")
ErrSessionClosed = errors.New("modem: AT session is closed")
ErrCommandTimeout = errors.New("modem: AT command timed out")
ErrPromptNotReceived = errors.New("modem: command completed without a prompt")
)
// PortRole describes the conventional role of a Quectel USB serial interface.
// The role is a discovery hint; only a successful AT probe proves that a port is
// usable.
type PortRole string
const (
PortRoleUnknown PortRole = "unknown"
PortRoleDiagnostic PortRole = "diagnostic"
PortRoleNMEA PortRole = "nmea"
PortRoleAT PortRole = "at"
PortRoleModem PortRole = "modem"
)
type Port struct {
Path string `json:"path"`
StablePath string `json:"stablePath,omitempty"`
Name string `json:"name"`
InterfaceNumber int `json:"interfaceNumber"`
Role PortRole `json:"role"`
}
func (p Port) OpenPath() string {
if strings.TrimSpace(p.StablePath) != "" {
return p.StablePath
}
return p.Path
}
type Candidate struct {
HardwareKind string `json:"hardwareKind,omitempty"`
ReaderName string `json:"readerName,omitempty"`
ID string `json:"id"`
VendorID string `json:"vendorId"`
ProductID string `json:"productId"`
Manufacturer string `json:"manufacturer,omitempty"`
Product string `json:"product,omitempty"`
SerialNumber string `json:"serialNumber,omitempty"`
USBPath string `json:"usbPath"`
ATPort Port `json:"atPort"`
Ports []Port `json:"ports"`
QMIControl string `json:"qmiControl,omitempty"`
NetworkInterface string `json:"networkInterface,omitempty"`
}
func (c Candidate) HasATPort() bool {
return strings.TrimSpace(c.ATPort.OpenPath()) != ""
}
type Discoverer interface {
Discover(context.Context) ([]Candidate, error)
}
// Response is the response belonging to exactly one AT command. URCs that
// arrived between the command echo and final result are returned separately and
// also retained by the session's URC queue.
type Response struct {
Command string `json:"command"`
Lines []string `json:"lines"`
Final string `json:"final"`
URCs []string `json:"urcs,omitempty"`
Duration time.Duration `json:"duration"`
}
func (r Response) Text() string {
return strings.Join(r.Lines, "\n")
}
func (r Response) OK() bool {
return strings.EqualFold(strings.TrimSpace(r.Final), "OK")
}
type CommandError struct {
Command string
Final string
Lines []string
}
func (e *CommandError) Error() string {
detail := strings.TrimSpace(strings.Join(e.Lines, "; "))
if detail == "" {
return fmt.Sprintf("%s failed: %s", e.Command, e.Final)
}
return fmt.Sprintf("%s failed: %s (%s)", e.Command, e.Final, detail)
}
// Client is the device layer's narrow AT dependency. Session implements it;
// tests can supply a deterministic transcript without opening hardware.
type Client interface {
Execute(context.Context, string) (Response, error)
WaitURC(context.Context, func(string) bool) (string, error)
Close() error
}
// PromptClient performs the two-phase interaction used by AT+CMGS. The
// implementation must hold the same command serialization lock while waiting
// for the prompt, writing payload+Ctrl-Z, and reading the final result.
type PromptClient interface {
Client
ExecutePrompt(context.Context, string, []byte) (Response, error)
}
type Opener interface {
Open(context.Context, Port) (Client, error)
}