mirror of https://github.com/jetkvm/kvm.git
288 lines
5.6 KiB
Go
288 lines
5.6 KiB
Go
package kvm
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pion/webrtc/v4"
|
|
"go.bug.st/serial"
|
|
)
|
|
|
|
const serialPortPath = "/dev/ttyS3"
|
|
|
|
var port serial.Port
|
|
|
|
func mountATXControl() error {
|
|
_ = port.SetMode(defaultMode)
|
|
go runATXControl()
|
|
|
|
return nil
|
|
}
|
|
|
|
func unmountATXControl() error {
|
|
_ = reopenSerialPort()
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
ledHDDState bool
|
|
ledPWRState bool
|
|
btnRSTState bool
|
|
btnPWRState bool
|
|
)
|
|
|
|
func runATXControl() {
|
|
scopedLogger := serialLogger.With().Str("service", "atx_control").Logger()
|
|
|
|
reader := bufio.NewReader(port)
|
|
for {
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
scopedLogger.Warn().Err(err).Msg("Error reading from serial port")
|
|
return
|
|
}
|
|
|
|
// Each line should be 4 binary digits + newline
|
|
if len(line) != 5 {
|
|
scopedLogger.Warn().Int("length", len(line)).Msg("Invalid line length")
|
|
continue
|
|
}
|
|
|
|
// Parse new states
|
|
newLedHDDState := line[0] == '0'
|
|
newLedPWRState := line[1] == '0'
|
|
newBtnRSTState := line[2] == '1'
|
|
newBtnPWRState := line[3] == '1'
|
|
|
|
if currentSession != nil {
|
|
writeJSONRPCEvent("atxState", ATXState{
|
|
Power: newLedPWRState,
|
|
HDD: newLedHDDState,
|
|
}, currentSession)
|
|
}
|
|
|
|
if newLedHDDState != ledHDDState ||
|
|
newLedPWRState != ledPWRState ||
|
|
newBtnRSTState != btnRSTState ||
|
|
newBtnPWRState != btnPWRState {
|
|
scopedLogger.Debug().
|
|
Bool("hdd", newLedHDDState).
|
|
Bool("pwr", newLedPWRState).
|
|
Bool("rst", newBtnRSTState).
|
|
Bool("pwr", newBtnPWRState).
|
|
Msg("Status changed")
|
|
|
|
// Update states
|
|
ledHDDState = newLedHDDState
|
|
ledPWRState = newLedPWRState
|
|
btnRSTState = newBtnRSTState
|
|
btnPWRState = newBtnPWRState
|
|
}
|
|
}
|
|
}
|
|
|
|
func pressATXPowerButton(duration time.Duration) error {
|
|
_, err := port.Write([]byte("\n"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = port.Write([]byte("BTN_PWR_ON\n"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
time.Sleep(duration)
|
|
|
|
_, err = port.Write([]byte("BTN_PWR_OFF\n"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func pressATXResetButton(duration time.Duration) error {
|
|
_, err := port.Write([]byte("\n"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = port.Write([]byte("BTN_RST_ON\n"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
time.Sleep(duration)
|
|
|
|
_, err = port.Write([]byte("BTN_RST_OFF\n"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func mountDCControl() error {
|
|
_ = port.SetMode(defaultMode)
|
|
go runDCControl()
|
|
return nil
|
|
}
|
|
|
|
func unmountDCControl() error {
|
|
_ = reopenSerialPort()
|
|
return nil
|
|
}
|
|
|
|
var dcState DCPowerState
|
|
|
|
func runDCControl() {
|
|
scopedLogger := serialLogger.With().Str("service", "dc_control").Logger()
|
|
reader := bufio.NewReader(port)
|
|
for {
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
scopedLogger.Warn().Err(err).Msg("Error reading from serial port")
|
|
return
|
|
}
|
|
|
|
// Split the line by semicolon
|
|
parts := strings.Split(strings.TrimSpace(line), ";")
|
|
if len(parts) != 4 {
|
|
scopedLogger.Warn().Str("line", line).Msg("Invalid line")
|
|
continue
|
|
}
|
|
|
|
// Parse new states
|
|
powerState, err := strconv.Atoi(parts[0])
|
|
if err != nil {
|
|
scopedLogger.Warn().Err(err).Msg("Invalid power state")
|
|
continue
|
|
}
|
|
dcState.IsOn = powerState == 1
|
|
milliVolts, err := strconv.ParseFloat(parts[1], 64)
|
|
if err != nil {
|
|
scopedLogger.Warn().Err(err).Msg("Invalid voltage")
|
|
continue
|
|
}
|
|
volts := milliVolts / 1000 // Convert mV to V
|
|
|
|
milliAmps, err := strconv.ParseFloat(parts[2], 64)
|
|
if err != nil {
|
|
scopedLogger.Warn().Err(err).Msg("Invalid current")
|
|
continue
|
|
}
|
|
amps := milliAmps / 1000 // Convert mA to A
|
|
|
|
milliWatts, err := strconv.ParseFloat(parts[3], 64)
|
|
if err != nil {
|
|
scopedLogger.Warn().Err(err).Msg("Invalid power")
|
|
continue
|
|
}
|
|
watts := milliWatts / 1000 // Convert mW to W
|
|
|
|
dcState.Voltage = volts
|
|
dcState.Current = amps
|
|
dcState.Power = watts
|
|
|
|
if currentSession != nil {
|
|
writeJSONRPCEvent("dcState", dcState, currentSession)
|
|
}
|
|
}
|
|
}
|
|
|
|
func setDCPowerState(on bool) error {
|
|
_, err := port.Write([]byte("\n"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
command := "PWR_OFF\n"
|
|
if on {
|
|
command = "PWR_ON\n"
|
|
}
|
|
_, err = port.Write([]byte(command))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var defaultMode = &serial.Mode{
|
|
BaudRate: 115200,
|
|
DataBits: 8,
|
|
Parity: serial.NoParity,
|
|
StopBits: serial.OneStopBit,
|
|
}
|
|
|
|
func initSerialPort() {
|
|
_ = reopenSerialPort()
|
|
switch config.ActiveExtension {
|
|
case "atx-power":
|
|
_ = mountATXControl()
|
|
case "dc-power":
|
|
_ = mountDCControl()
|
|
}
|
|
}
|
|
|
|
func reopenSerialPort() error {
|
|
if port != nil {
|
|
port.Close()
|
|
}
|
|
var err error
|
|
port, err = serial.Open(serialPortPath, defaultMode)
|
|
if err != nil {
|
|
serialLogger.Error().
|
|
Err(err).
|
|
Str("path", serialPortPath).
|
|
Interface("mode", defaultMode).
|
|
Msg("Error opening serial port")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func handleSerialChannel(d *webrtc.DataChannel) {
|
|
scopedLogger := serialLogger.With().
|
|
Uint16("data_channel_id", *d.ID()).Logger()
|
|
|
|
d.OnOpen(func() {
|
|
go func() {
|
|
buf := make([]byte, 1024)
|
|
for {
|
|
n, err := port.Read(buf)
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
scopedLogger.Warn().Err(err).Msg("Failed to read from serial port")
|
|
}
|
|
break
|
|
}
|
|
err = d.Send(buf[:n])
|
|
if err != nil {
|
|
scopedLogger.Warn().Err(err).Msg("Failed to send serial output")
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
})
|
|
|
|
d.OnMessage(func(msg webrtc.DataChannelMessage) {
|
|
if port == nil {
|
|
return
|
|
}
|
|
_, err := port.Write(msg.Data)
|
|
if err != nil {
|
|
scopedLogger.Warn().Err(err).Msg("Failed to write to serial")
|
|
}
|
|
})
|
|
|
|
d.OnError(func(err error) {
|
|
scopedLogger.Warn().Err(err).Msg("Serial channel error")
|
|
})
|
|
|
|
d.OnClose(func() {
|
|
scopedLogger.Info().Msg("Serial channel closed")
|
|
})
|
|
}
|