Created a devices service and moved the CDashDisplay service to the sahdow realm
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
package cdashdisplay
|
||||
|
||||
import (
|
||||
"esdi/peripheral/communication"
|
||||
"esdi/peripheral/communication/packets"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/tarm/serial"
|
||||
portp "go.bug.st/serial"
|
||||
)
|
||||
|
||||
func listPorts() ([]string, error) {
|
||||
ports, err := portp.GetPortsList()
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
func probe(WT *communication.WalkieTalkie) error {
|
||||
err := WT.TurnOn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send the identification command
|
||||
cmd := communication.CmdRequestID
|
||||
var response packets.IdentificationPacket
|
||||
err = WT.SendCommand(cmd, []byte{0x06, 0x07, 0x08, 0x09}, &response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if response.DeviceID != 0x01 {
|
||||
return fmt.Errorf("wrong ID")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findDisplayPort() (*communication.WalkieTalkie, error) {
|
||||
ports, err := listPorts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pLogger.Info(fmt.Sprintf("Looking into %v", ports))
|
||||
|
||||
var wt *communication.WalkieTalkie
|
||||
for _, port := range ports {
|
||||
pLogger.Info(fmt.Sprintf("Trying port %s", port))
|
||||
|
||||
wt = &communication.WalkieTalkie{
|
||||
Cfg: &serial.Config{
|
||||
Name: port,
|
||||
Baud: 115200,
|
||||
ReadTimeout: 500 * time.Millisecond,
|
||||
},
|
||||
}
|
||||
|
||||
pLogger.Info(fmt.Sprintf("Started probing port %s", port))
|
||||
|
||||
probeResult := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
probeResult <- probe(wt)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err = <-probeResult:
|
||||
// Probe completed normally (could be success or error)
|
||||
case <-time.After(2 * time.Second):
|
||||
// Hard timeout reached
|
||||
err = fmt.Errorf("probe completely hung/timed out: %s", port)
|
||||
}
|
||||
|
||||
pLogger.Info(fmt.Sprintf("Finished probing port %s", port))
|
||||
|
||||
if err == nil {
|
||||
pLogger.Info(fmt.Sprintf("Success probing port %s: %+v", port, err))
|
||||
break
|
||||
}
|
||||
|
||||
pLogger.Info(fmt.Sprintf("wasn't port %s", port))
|
||||
wt = nil
|
||||
}
|
||||
|
||||
if wt == nil {
|
||||
return nil, fmt.Errorf("couldn't find cdashdisplay")
|
||||
}
|
||||
|
||||
pLogger.Info(fmt.Sprintf("found cdashdisplay on port: %s", wt.Cfg.Name))
|
||||
return wt, nil
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
// Package cdashdisplay will handle the USB connections
|
||||
package cdashdisplay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
helper "esdi/helpers"
|
||||
"esdi/peripheral/communication"
|
||||
"esdi/peripheral/communication/packets"
|
||||
"esdi/peripheral/types"
|
||||
"esdi/telemetry"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var pLogger *slog.Logger
|
||||
|
||||
func SetLogger(l *slog.Logger) {
|
||||
pLogger = l
|
||||
}
|
||||
|
||||
// I have to move this to some kind of configuration place
|
||||
const (
|
||||
layoutsDir = "./layouts/"
|
||||
)
|
||||
|
||||
const (
|
||||
newWindowCMDID types.Command = 3
|
||||
destroyWindowCMDID types.Command = 4
|
||||
updateWindowDimsCMDID types.Command = 5
|
||||
updateWindowCMDID types.Command = 6 // Change this to a move cmd instead
|
||||
sendDataCMDID types.Command = 7
|
||||
newLayoutCMDID types.Command = 8
|
||||
)
|
||||
|
||||
const (
|
||||
MoveLeft = iota
|
||||
MoveDown
|
||||
MoveUp
|
||||
MoveRight
|
||||
)
|
||||
|
||||
var DefaultDecorations = UIDecorations{
|
||||
HasBorder: 1,
|
||||
BGColour: 0x1041, // Look in the eslabsCurses library for these colours
|
||||
FGColour: 0xffff,
|
||||
TitleColour: 0xffff,
|
||||
BorderColour: 0xf800,
|
||||
TitleSize: 2,
|
||||
TextSize: 4,
|
||||
Padding: 0x00,
|
||||
}
|
||||
|
||||
type UpdateDimsPacket struct {
|
||||
ID int16
|
||||
Dims UIDimensions
|
||||
}
|
||||
|
||||
type FString32 [32]byte
|
||||
|
||||
func (s FString32) String() string {
|
||||
n := bytes.IndexByte(s[:], 0)
|
||||
if n == -1 {
|
||||
n = len(s)
|
||||
}
|
||||
return string(s[:n])
|
||||
}
|
||||
|
||||
func (s FString32) MarshalYAML() (any, error) {
|
||||
// trim trailing zero bytes
|
||||
n := bytes.IndexByte(s[:], 0)
|
||||
if n == -1 {
|
||||
n = len(s)
|
||||
}
|
||||
return string(s[:n]), nil
|
||||
}
|
||||
|
||||
func (s *FString32) UnmarshalYAML(value *yaml.Node) error {
|
||||
if value.Kind != yaml.ScalarNode {
|
||||
return fmt.Errorf("expected YAML scalar for FixedString32")
|
||||
}
|
||||
|
||||
b := []byte(value.Value)
|
||||
|
||||
if len(b) > len(s) {
|
||||
return fmt.Errorf("string too long (max %d bytes)", len(s))
|
||||
}
|
||||
|
||||
// zero-fill first
|
||||
for i := range s {
|
||||
s[i] = 0
|
||||
}
|
||||
|
||||
copy(s[:], b)
|
||||
return nil
|
||||
}
|
||||
|
||||
type CDashState struct {
|
||||
Layout *LayoutTree
|
||||
}
|
||||
|
||||
func NewCDashState() *CDashState {
|
||||
return &CDashState{
|
||||
Layout: NewLayoutTree(),
|
||||
}
|
||||
}
|
||||
|
||||
type CDashDisplay struct {
|
||||
WT *communication.WalkieTalkie
|
||||
State *CDashState
|
||||
}
|
||||
|
||||
// Connect will try to find and connect to the CDashDisplay
|
||||
func Discover() (*CDashDisplay, error) {
|
||||
// Look for the port
|
||||
p, err := findDisplayPort()
|
||||
if err != nil {
|
||||
pLogger.Info("failed to find cdashdisplay port: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &CDashDisplay{
|
||||
WT: p,
|
||||
State: NewCDashState(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *CDashDisplay) SendCommand() {
|
||||
}
|
||||
|
||||
func (d *CDashDisplay) CreateWindow(win *DesktopUIWindow) (*DesktopUIWindow, error) {
|
||||
bytes, err := helper.StructToBytes(win.UIWindow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send the command
|
||||
var wID packets.NewWindowID
|
||||
err = d.WT.SendCommand(newWindowCMDID, bytes, &wID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
win.UIData.IDX = wID.ID
|
||||
|
||||
pLogger.Info(fmt.Sprintf("Recived ID message: %v", wID))
|
||||
|
||||
d.State.Layout.AddWindow(win)
|
||||
|
||||
return win, nil
|
||||
}
|
||||
|
||||
func (d *CDashDisplay) UpdateWindow(win *DesktopUIWindow) error {
|
||||
data := UIWindowUpdatePacket{
|
||||
WinID: win.UIData.IDX,
|
||||
Window: win.UIWindow,
|
||||
}
|
||||
|
||||
bytes, err := helper.StructToBytes(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.WT.SendCommand(updateWindowCMDID, bytes, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// NOTE: need to check the flow of this because:
|
||||
// windows has a pointer to a UIWindow stored
|
||||
// I get it and update it in the controller
|
||||
// I send the pointer here
|
||||
// -> it should be the same pointer then right?
|
||||
pLogger.Debug(fmt.Sprintf("PreUpdate ID: %p", win))
|
||||
d.State.Layout.Windows[win.UIData.IDX] = win
|
||||
pLogger.Debug(fmt.Sprintf("PostUpdate ID: %p", win))
|
||||
// Yeah, same address as suspected
|
||||
// I can't think about it right now. I'll think about that tomorrow
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CDashDisplay) DestroyWindow(wID int16) error {
|
||||
type UIWindowDestructPacket struct {
|
||||
WinID int16
|
||||
}
|
||||
|
||||
packet := UIWindowDestructPacket{
|
||||
WinID: wID,
|
||||
}
|
||||
|
||||
bytes, err := helper.StructToBytes(packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.WT.SendCommand(destroyWindowCMDID, bytes, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// NODE: add this
|
||||
d.State.Layout.RemoveWindow(wID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CDashDisplay) updateWindowDimensions(win *UIWindow, packet UpdateDimsPacket) error {
|
||||
pLogger.Debug(fmt.Sprintf("UPDATE: %v", packet))
|
||||
|
||||
bytes, err := helper.StructToBytes(packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// var ack packets.AckPacket
|
||||
err = d.WT.SendCommand(updateWindowDimsCMDID, bytes, nil)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
// Nothing bad happened afaik
|
||||
pLogger.Debug(fmt.Sprintf("cur dims: %v", win.Dims))
|
||||
win.Dims = packet.Dims
|
||||
pLogger.Debug(fmt.Sprintf("new dims: %v", win.Dims))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CDashDisplay) MoveWindow(wID int16, delta *helper.Vector) error {
|
||||
// Get the window from the layout
|
||||
window, ok := d.State.Layout.Windows[wID]
|
||||
if !ok {
|
||||
return fmt.Errorf("window with ID '%d' doesn't exist", wID)
|
||||
}
|
||||
|
||||
// Now we will create new dimensions
|
||||
newDimensions := window.Dims
|
||||
|
||||
newDimensions.X0 += delta.DX
|
||||
newDimensions.Y0 += delta.DY
|
||||
|
||||
packet := UpdateDimsPacket{
|
||||
ID: wID,
|
||||
Dims: newDimensions,
|
||||
}
|
||||
|
||||
err := d.updateWindowDimensions(&window.UIWindow, packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
window.Dims = newDimensions
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CDashDisplay) ResizeWindow(wID int16, delta *helper.Vector) error {
|
||||
// Get the window from the layout
|
||||
window, ok := d.State.Layout.Windows[wID]
|
||||
if !ok {
|
||||
return fmt.Errorf("window with ID '%d' doesn't exist", wID)
|
||||
}
|
||||
|
||||
// Now we will create new dimensions
|
||||
newDimensions := window.Dims
|
||||
|
||||
if delta.DX > 0 {
|
||||
newDimensions.Width += delta.DX
|
||||
} else {
|
||||
newDimensions.Width -= delta.DX
|
||||
newDimensions.X0 -= 2 * delta.DX
|
||||
}
|
||||
|
||||
if delta.DY > 0 {
|
||||
newDimensions.Height += delta.DY
|
||||
} else {
|
||||
newDimensions.Height -= delta.DY
|
||||
newDimensions.Y0 -= 2 * delta.DY
|
||||
}
|
||||
|
||||
packet := UpdateDimsPacket{
|
||||
ID: wID,
|
||||
Dims: newDimensions,
|
||||
}
|
||||
|
||||
err := d.updateWindowDimensions(&window.UIWindow, packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
window.Dims = newDimensions
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CDashDisplay) SaveLayout(outputPath string) error {
|
||||
file, err := os.OpenFile(path.Join(layoutsDir, outputPath),
|
||||
os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := yaml.Marshal(d.State.Layout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = file.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CDashDisplay) LoadLayout(layoutName string) error {
|
||||
data, err := os.ReadFile(path.Join(layoutsDir, layoutName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
layout := NewLayoutTree()
|
||||
err = yaml.Unmarshal(data, layout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, w := range layout.Windows {
|
||||
_, err = d.CreateWindow(w)
|
||||
if err != nil {
|
||||
// NOTE: Add a way to handle multiple errors ?
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CDashDisplay) UnloadLayout() error {
|
||||
var err error
|
||||
for _, w := range d.State.Layout.Windows {
|
||||
pLogger.Debug(fmt.Sprintf("= Removing %d ==============================================",
|
||||
w.UIData.IDX))
|
||||
|
||||
err = d.DestroyWindow(w.UIData.IDX)
|
||||
time.Sleep(75 * time.Millisecond)
|
||||
if err != nil {
|
||||
pLogger.Error(fmt.Sprintf("failed to destroy window: %+v", err))
|
||||
// NOTE: Add a way to handle multiple errors ?
|
||||
return err
|
||||
}
|
||||
|
||||
pLogger.Debug(fmt.Sprintf("= Removing %d ==============================================",
|
||||
w.UIData.IDX))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CDashDisplay) SendData(data *telemetry.TelemetryData) {
|
||||
packet := data.Pack()
|
||||
|
||||
bytes, err := helper.StructToBytes(packet)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
curStr := ""
|
||||
byteCount := 0
|
||||
for _, byte := range bytes {
|
||||
byteCount++
|
||||
curStr += fmt.Sprintf("%02x ", byte)
|
||||
|
||||
if byteCount == 8 {
|
||||
pLogger.Debug(curStr)
|
||||
curStr = ""
|
||||
byteCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
// var ack packets.AckPacket
|
||||
err = d.WT.SendCommand(sendDataCMDID, bytes, nil)
|
||||
if err != nil && err != io.EOF {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cdashdisplay
|
||||
|
||||
import "esdi/peripheral/devices"
|
||||
|
||||
// TODO: I believe we don't need the #esdi/peripheral/devices thing anymore
|
||||
const (
|
||||
ID = devices.CDashDisplayDevID
|
||||
Name = devices.CDashDisplayDevName
|
||||
)
|
||||
@@ -0,0 +1,25 @@
|
||||
package cdashdisplay
|
||||
|
||||
import "fmt"
|
||||
|
||||
type LayoutTree struct {
|
||||
Windows map[int16]*DesktopUIWindow `yaml:"Windows"`
|
||||
}
|
||||
|
||||
func NewLayoutTree() *LayoutTree {
|
||||
return &LayoutTree{
|
||||
Windows: make(map[int16]*DesktopUIWindow),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LayoutTree) AddWindow(w *DesktopUIWindow) {
|
||||
pLogger.Debug(fmt.Sprintf("adding window '%d' - %v", w.UIData.IDX))
|
||||
l.Windows[w.UIData.IDX] = w
|
||||
pLogger.Debug(fmt.Sprintf("new map - %v", l.Windows))
|
||||
}
|
||||
|
||||
func (l *LayoutTree) RemoveWindow(idx int16) {
|
||||
pLogger.Debug(fmt.Sprintf("removing window '%d'", idx))
|
||||
delete(l.Windows, idx)
|
||||
pLogger.Debug(fmt.Sprintf("new map - %v", l.Windows))
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package cdashdisplay
|
||||
|
||||
// In this file we will place all structs that are 1:1 representation of the
|
||||
// types in the device transport layer ->
|
||||
|
||||
const (
|
||||
ShowIDFalse uint8 = 0
|
||||
ShowIDTrue uint8 = 1
|
||||
)
|
||||
|
||||
const (
|
||||
WinTypeBASE uint8 = iota
|
||||
WinTypeBAR
|
||||
WinTypeSTRING
|
||||
WinTypeTABLE
|
||||
)
|
||||
|
||||
var WinTYPES = []string{"BASE", "BAR", "STRING", "TABLE"}
|
||||
|
||||
type UIDimensions struct {
|
||||
X0 uint16 `yaml:"X0"`
|
||||
Y0 uint16 `yaml:"Y0"`
|
||||
Width uint16 `yaml:"Width"`
|
||||
Height uint16 `yaml:"Height"`
|
||||
}
|
||||
|
||||
type UIDecorations struct {
|
||||
BGColour uint16 `yaml:"BGColour"`
|
||||
FGColour uint16 `yaml:"FGColour"`
|
||||
TitleColour uint16 `yaml:"TitleColour"`
|
||||
BorderColour uint16 `yaml:"BorderColour"`
|
||||
TitleSize uint8 `yaml:"TitleSize"`
|
||||
TextSize uint8 `yaml:"TextSize"`
|
||||
HasBorder uint8 `yaml:"HasBorder"`
|
||||
Padding uint8 `yaml:"Padding"`
|
||||
}
|
||||
|
||||
// NOTE: we can't use FString32 for this - too many bytes
|
||||
// NOTE: use a bit flags for this options instead
|
||||
type UIWindowOpts struct {
|
||||
ShowID uint8 `yaml:"ShowID"`
|
||||
WinType uint8 `yaml:"WinType"`
|
||||
PreviewValue FString32 `yaml:"PreviewValue"`
|
||||
}
|
||||
|
||||
type UIWindow struct {
|
||||
Dims UIDimensions `yaml:"Dims"`
|
||||
Decor UIDecorations `yaml:"Decor"`
|
||||
Opts UIWindowOpts `yaml:"Opts"`
|
||||
Title FString32 `yaml:"Title"`
|
||||
}
|
||||
|
||||
type DesktopUIWindow struct {
|
||||
UIWindow
|
||||
UIData DesktopUIData
|
||||
}
|
||||
|
||||
type DesktopUIData struct {
|
||||
IDX int16 `yaml:"WID"`
|
||||
TelemetryField string `yaml:"TelemetryField"`
|
||||
}
|
||||
|
||||
type UIWindowUpdatePacket struct {
|
||||
WinID int16
|
||||
Window UIWindow
|
||||
}
|
||||
|
||||
// func getUIWindowDTO(w *DesktopWindowData) *UIWindow {
|
||||
//
|
||||
// }
|
||||
Reference in New Issue
Block a user