Skip to content

Commit cd333c4

Browse files
authored
feat(extension): ATX/DC/Serial extension support
1 parent 1973a65 commit cd333c4

20 files changed

+1535
-480
lines changed

config.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ type Config struct {
2222
LocalAuthToken string `json:"local_auth_token"`
2323
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
2424
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
25-
EdidString string `json:"hdmi_edid_string"`
26-
DisplayMaxBrightness int `json:"display_max_brightness"`
25+
EdidString string `json:"hdmi_edid_string"`
26+
ActiveExtension string `json:"active_extension"`
27+
DisplayMaxBrightness int `json:"display_max_brightness"`
2728
DisplayDimAfterSec int `json:"display_dim_after_sec"`
2829
DisplayOffAfterSec int `json:"display_off_after_sec"`
2930
}
@@ -33,6 +34,7 @@ const configPath = "/userdata/kvm_config.json"
3334
var defaultConfig = &Config{
3435
CloudURL: "https://api.jetkvm.com",
3536
AutoUpdateEnabled: true, // Set a default value
37+
ActiveExtension: "",
3638
DisplayMaxBrightness: 64,
3739
DisplayDimAfterSec: 120, // 2 minutes
3840
DisplayOffAfterSec: 1800, // 30 minutes

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
github.com/pojntfx/go-nbd v0.3.2
2323
github.com/psanford/httpreadat v0.1.0
2424
github.com/vishvananda/netlink v1.3.0
25+
go.bug.st/serial v1.6.2
2526
golang.org/x/crypto v0.28.0
2627
golang.org/x/net v0.30.0
2728
)
@@ -33,6 +34,7 @@ require (
3334
github.com/bytedance/sonic/loader v0.1.1 // indirect
3435
github.com/cloudwego/base64x v0.1.4 // indirect
3536
github.com/cloudwego/iasm v0.2.0 // indirect
37+
github.com/creack/goselect v0.1.2 // indirect
3638
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
3739
github.com/gin-contrib/sse v0.1.0 // indirect
3840
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
@@ -69,7 +71,7 @@ require (
6971
github.com/wlynxg/anet v0.0.5 // indirect
7072
golang.org/x/arch v0.8.0 // indirect
7173
golang.org/x/oauth2 v0.21.0 // indirect
72-
golang.org/x/sys v0.26.0 // indirect
74+
golang.org/x/sys v0.29.0 // indirect
7375
golang.org/x/text v0.19.0 // indirect
7476
google.golang.org/protobuf v1.34.0 // indirect
7577
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NA
1616
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
1717
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
1818
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
19+
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
20+
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
1921
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2022
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
2123
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
@@ -146,6 +148,8 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
146148
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
147149
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
148150
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
151+
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
152+
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
149153
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
150154
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
151155
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
@@ -161,8 +165,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
161165
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
162166
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
163167
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
164-
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
165-
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
168+
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
169+
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
166170
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
167171
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
168172
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=

jsonrpc.go

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ import (
1010
"os/exec"
1111
"path/filepath"
1212
"reflect"
13+
"strconv"
14+
"time"
1315

1416
"github.com/pion/webrtc/v4"
17+
"go.bug.st/serial"
1518
)
1619

1720
type JSONRPCRequest struct {
@@ -569,7 +572,172 @@ func rpcResetConfig() error {
569572
return nil
570573
}
571574

572-
// TODO: replace this crap with code generator
575+
type DCPowerState struct {
576+
IsOn bool `json:"isOn"`
577+
Voltage float64 `json:"voltage"`
578+
Current float64 `json:"current"`
579+
Power float64 `json:"power"`
580+
}
581+
582+
func rpcGetDCPowerState() (DCPowerState, error) {
583+
return dcState, nil
584+
}
585+
586+
func rpcSetDCPowerState(enabled bool) error {
587+
log.Printf("[jsonrpc.go:rpcSetDCPowerState] Setting DC power state to: %v", enabled)
588+
err := setDCPowerState(enabled)
589+
if err != nil {
590+
return fmt.Errorf("failed to set DC power state: %w", err)
591+
}
592+
return nil
593+
}
594+
595+
func rpcGetActiveExtension() (string, error) {
596+
return config.ActiveExtension, nil
597+
}
598+
599+
func rpcSetActiveExtension(extensionId string) error {
600+
if config.ActiveExtension == extensionId {
601+
return nil
602+
}
603+
if config.ActiveExtension == "atx-power" {
604+
unmountATXControl()
605+
} else if config.ActiveExtension == "dc-power" {
606+
unmountDCControl()
607+
}
608+
config.ActiveExtension = extensionId
609+
if err := SaveConfig(); err != nil {
610+
return fmt.Errorf("failed to save config: %w", err)
611+
}
612+
if extensionId == "atx-power" {
613+
mountATXControl()
614+
} else if extensionId == "dc-power" {
615+
mountDCControl()
616+
}
617+
return nil
618+
}
619+
620+
func rpcSetATXPowerAction(action string) error {
621+
logger.Debugf("[jsonrpc.go:rpcSetATXPowerAction] Executing ATX power action: %s", action)
622+
switch action {
623+
case "power-short":
624+
logger.Debug("[jsonrpc.go:rpcSetATXPowerAction] Simulating short power button press")
625+
return pressATXPowerButton(200 * time.Millisecond)
626+
case "power-long":
627+
logger.Debug("[jsonrpc.go:rpcSetATXPowerAction] Simulating long power button press")
628+
return pressATXPowerButton(5 * time.Second)
629+
case "reset":
630+
logger.Debug("[jsonrpc.go:rpcSetATXPowerAction] Simulating reset button press")
631+
return pressATXResetButton(200 * time.Millisecond)
632+
default:
633+
return fmt.Errorf("invalid action: %s", action)
634+
}
635+
}
636+
637+
type ATXState struct {
638+
Power bool `json:"power"`
639+
HDD bool `json:"hdd"`
640+
}
641+
642+
func rpcGetATXState() (ATXState, error) {
643+
state := ATXState{
644+
Power: ledPWRState,
645+
HDD: ledHDDState,
646+
}
647+
return state, nil
648+
}
649+
650+
type SerialSettings struct {
651+
BaudRate string `json:"baudRate"`
652+
DataBits string `json:"dataBits"`
653+
StopBits string `json:"stopBits"`
654+
Parity string `json:"parity"`
655+
}
656+
657+
func rpcGetSerialSettings() (SerialSettings, error) {
658+
settings := SerialSettings{
659+
BaudRate: strconv.Itoa(serialPortMode.BaudRate),
660+
DataBits: strconv.Itoa(serialPortMode.DataBits),
661+
StopBits: "1",
662+
Parity: "none",
663+
}
664+
665+
switch serialPortMode.StopBits {
666+
case serial.OneStopBit:
667+
settings.StopBits = "1"
668+
case serial.OnePointFiveStopBits:
669+
settings.StopBits = "1.5"
670+
case serial.TwoStopBits:
671+
settings.StopBits = "2"
672+
}
673+
674+
switch serialPortMode.Parity {
675+
case serial.NoParity:
676+
settings.Parity = "none"
677+
case serial.OddParity:
678+
settings.Parity = "odd"
679+
case serial.EvenParity:
680+
settings.Parity = "even"
681+
case serial.MarkParity:
682+
settings.Parity = "mark"
683+
case serial.SpaceParity:
684+
settings.Parity = "space"
685+
}
686+
687+
return settings, nil
688+
}
689+
690+
var serialPortMode = defaultMode
691+
692+
func rpcSetSerialSettings(settings SerialSettings) error {
693+
baudRate, err := strconv.Atoi(settings.BaudRate)
694+
if err != nil {
695+
return fmt.Errorf("invalid baud rate: %v", err)
696+
}
697+
dataBits, err := strconv.Atoi(settings.DataBits)
698+
if err != nil {
699+
return fmt.Errorf("invalid data bits: %v", err)
700+
}
701+
702+
var stopBits serial.StopBits
703+
switch settings.StopBits {
704+
case "1":
705+
stopBits = serial.OneStopBit
706+
case "1.5":
707+
stopBits = serial.OnePointFiveStopBits
708+
case "2":
709+
stopBits = serial.TwoStopBits
710+
default:
711+
return fmt.Errorf("invalid stop bits: %s", settings.StopBits)
712+
}
713+
714+
var parity serial.Parity
715+
switch settings.Parity {
716+
case "none":
717+
parity = serial.NoParity
718+
case "odd":
719+
parity = serial.OddParity
720+
case "even":
721+
parity = serial.EvenParity
722+
case "mark":
723+
parity = serial.MarkParity
724+
case "space":
725+
parity = serial.SpaceParity
726+
default:
727+
return fmt.Errorf("invalid parity: %s", settings.Parity)
728+
}
729+
serialPortMode = &serial.Mode{
730+
BaudRate: baudRate,
731+
DataBits: dataBits,
732+
StopBits: stopBits,
733+
Parity: parity,
734+
}
735+
736+
port.SetMode(serialPortMode)
737+
738+
return nil
739+
}
740+
573741
var rpcHandlers = map[string]RPCHandler{
574742
"ping": {Func: rpcPing},
575743
"getDeviceID": {Func: rpcGetDeviceID},
@@ -618,4 +786,12 @@ var rpcHandlers = map[string]RPCHandler{
618786
"resetConfig": {Func: rpcResetConfig},
619787
"setBacklightSettings": {Func: rpcSetBacklightSettings, Params: []string{"params"}},
620788
"getBacklightSettings": {Func: rpcGetBacklightSettings},
789+
"getDCPowerState": {Func: rpcGetDCPowerState},
790+
"setDCPowerState": {Func: rpcSetDCPowerState, Params: []string{"enabled"}},
791+
"getActiveExtension": {Func: rpcGetActiveExtension},
792+
"setActiveExtension": {Func: rpcSetActiveExtension, Params: []string{"extensionId"}},
793+
"getATXState": {Func: rpcGetATXState},
794+
"setATXPowerAction": {Func: rpcSetATXPowerAction, Params: []string{"action"}},
795+
"getSerialSettings": {Func: rpcGetSerialSettings},
796+
"setSerialSettings": {Func: rpcSetSerialSettings, Params: []string{"settings"}},
621797
}

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ func Main() {
7171
if config.CloudToken != "" {
7272
go RunWebsocketClient()
7373
}
74+
initSerialPort()
7475
sigs := make(chan os.Signal, 1)
7576
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
7677
<-sigs

0 commit comments

Comments
 (0)