// tera-serial-line-check temporarily applies and restores CH01 line settings. package main import ( "context" "encoding/json" "flag" "fmt" "io" "os" "path/filepath" "strings" "time" "tera-clone/services/tera-api/internal/serial" ) const ( representativeChannel = "CH01" representativeDevice = "/dev/ttyWCH4" representativePCPort = "COM3" ) type probeClient interface { Probe(context.Context, serial.ChannelConfig) (serial.ProbeResult, error) } type checkReport struct { Kind string `json:"kind"` Status string `json:"status"` CheckedAt string `json:"checked_at"` Channel string `json:"channel"` Device string `json:"device"` PCPort string `json:"pc_port"` Requested serial.LineSettings `json:"requested"` Probe *serial.ProbeResult `json:"probe,omitempty"` Findings []string `json:"findings,omitempty"` DataSent bool `json:"data_sent"` } func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr, time.Now)) } func run(args []string, stdout, stderr io.Writer, now func() time.Time) int { flags := flag.NewFlagSet("tera-serial-line-check", flag.ContinueOnError) flags.SetOutput(stderr) socketPath := flags.String("socket", serial.DefaultSocketPath, "tera-seriald Unix socket") baudRate := flags.Int("baud", 115200, "temporary baud rate") dataBits := flags.Int("data-bits", 8, "temporary data bits") parity := flags.String("parity", "none", "temporary parity") stopBits := flags.Int("stop-bits", 1, "temporary stop bits") flowControl := flags.String("flow-control", "none", "temporary flow control") timeout := flags.Duration("timeout", 10*time.Second, "overall probe timeout") if err := flags.Parse(args); err != nil { return 2 } if flags.NArg() != 0 || !filepath.IsAbs(*socketPath) || *timeout <= 0 { fmt.Fprintln(stderr, "invalid arguments: require an absolute socket path and positive timeout") return 2 } requested := serial.ChannelConfig{ Channel: representativeChannel, BaudRate: *baudRate, DataBits: *dataBits, Parity: strings.ToLower(strings.TrimSpace(*parity)), StopBits: *stopBits, FlowControl: strings.ToLower(strings.TrimSpace(*flowControl)), } if _, err := serial.NormalizeLineConfig(serial.LineConfig{ Device: representativeDevice, BaudRate: requested.BaudRate, DataBits: requested.DataBits, Parity: requested.Parity, StopBits: requested.StopBits, FlowControl: requested.FlowControl, }); err != nil { fmt.Fprintln(stderr, err) return 2 } ctx, cancel := context.WithTimeout(context.Background(), *timeout) defer cancel() report := inspect(ctx, serial.NewSocketClient(*socketPath), requested, now().UTC()) encoder := json.NewEncoder(stdout) encoder.SetIndent("", " ") if err := encoder.Encode(report); err != nil { fmt.Fprintln(stderr, "write serial line check report:", err) return 1 } if report.Status != "pass" { return 1 } return 0 } func inspect(ctx context.Context, client probeClient, requested serial.ChannelConfig, checkedAt time.Time) checkReport { wanted := serial.LineSettings{ BaudRate: requested.BaudRate, DataBits: requested.DataBits, Parity: requested.Parity, StopBits: requested.StopBits, FlowControl: requested.FlowControl, } report := checkReport{ Kind: "tera-serial-line-check", Status: "fail", CheckedAt: checkedAt.UTC().Format(time.RFC3339Nano), Channel: representativeChannel, Device: representativeDevice, PCPort: representativePCPort, Requested: wanted, DataSent: false, } result, err := client.Probe(ctx, requested) if err != nil { report.Findings = append(report.Findings, err.Error()) return report } report.Probe = &result if result.Channel != representativeChannel { report.Findings = append(report.Findings, "probe returned a non-representative channel") } if result.Device != representativeDevice { report.Findings = append(report.Findings, "probe returned an unexpected target device") } if result.Applied != wanted { report.Findings = append(report.Findings, "applied readback does not match requested line settings") } if !result.Restored { report.Findings = append(report.Findings, "prior line settings were not restored") } if len(report.Findings) == 0 { report.Status = "pass" } return report }