mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-13 03:13:43 +08:00
- 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.
122 lines
3.5 KiB
Go
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)
|
|
}
|