mirror of
https://github.com/compute-blade-community/compute-blade-agent.git
synced 2026-04-16 15:35:42 +02:00
* chore: update repository references from uptime-industries to compute-blade-community chore: update repository references from uptime-industries to compute-blade-community for consistency and clarity across all files fix: update links in CHANGELOG.md and README.md to point to the new repository location for accurate documentation fix: update Dockerfile and systemd service file to reflect the new repository URL for proper source tracking refactor: change import paths in Go files to use the new repository name for correct package referencing * chore: Add CODEOWNERS * feat: add auto-labeling --------- Co-authored-by: Cedric Kienzler <cedric@specht-labs.de>
263 lines
7.1 KiB
Go
263 lines
7.1 KiB
Go
//go:build tinygo
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"machine"
|
|
|
|
"github.com/compute-blade-community/compute-blade-agent/pkg/events"
|
|
"github.com/compute-blade-community/compute-blade-agent/pkg/hal/led"
|
|
"github.com/compute-blade-community/compute-blade-agent/pkg/smartfanunit"
|
|
"github.com/compute-blade-community/compute-blade-agent/pkg/smartfanunit/emc2101"
|
|
"github.com/compute-blade-community/compute-blade-agent/pkg/smartfanunit/proto"
|
|
"golang.org/x/sync/errgroup"
|
|
"tinygo.org/x/drivers"
|
|
"tinygo.org/x/drivers/ws2812"
|
|
)
|
|
|
|
const (
|
|
leftBladeTopicIn = "left:in"
|
|
leftBladeTopicOut = "left:out"
|
|
rightBladeTopicIn = "right:in"
|
|
rightBladeTopicOut = "right:out"
|
|
)
|
|
|
|
type Controller struct {
|
|
DefaultFanSpeed uint8
|
|
LEDs ws2812.Device
|
|
FanController emc2101.EMC2101
|
|
ButtonPin machine.Pin
|
|
|
|
LeftUART drivers.UART
|
|
RightUART drivers.UART
|
|
|
|
eb events.EventBus
|
|
leftLed led.Color
|
|
rightLed led.Color
|
|
leftReqFanSpeed uint8
|
|
rightReqFanSpeed uint8
|
|
|
|
buttonPressed int
|
|
}
|
|
|
|
func (c *Controller) Run(parentCtx context.Context) error {
|
|
c.eb = events.New()
|
|
|
|
c.FanController.Init()
|
|
c.FanController.SetFanPercent(c.DefaultFanSpeed)
|
|
c.LEDs.Write([]byte{0, 0, 0, 0, 0, 0})
|
|
|
|
group, ctx := errgroup.WithContext(parentCtx)
|
|
|
|
// LED Update events
|
|
println("[+] Starting LED update loop")
|
|
group.Go(func() error {
|
|
return c.updateLEDs(ctx)
|
|
})
|
|
|
|
// Fan speed update events
|
|
println("[+] Starting fan update loop")
|
|
group.Go(func() error {
|
|
return c.updateFanSpeed(ctx)
|
|
})
|
|
|
|
// Metric reporting events
|
|
println("[+] Starting metric reporting loop")
|
|
group.Go(func() error {
|
|
return c.metricReporter(ctx)
|
|
})
|
|
|
|
// Left blade events
|
|
println("[+] Starting event listener (left)")
|
|
group.Go(func() error {
|
|
return c.listenEvents(ctx, c.LeftUART, leftBladeTopicIn)
|
|
})
|
|
println("[+] Starting event dispatcher (left)")
|
|
group.Go(func() error {
|
|
return c.dispatchEvents(ctx, c.LeftUART, leftBladeTopicOut)
|
|
})
|
|
|
|
// right blade events
|
|
println("[+] Starting event listener (right)")
|
|
group.Go(func() error {
|
|
return c.listenEvents(ctx, c.RightUART, rightBladeTopicIn)
|
|
})
|
|
println("[+] Starting event dispatcher (right)")
|
|
group.Go(func() error {
|
|
return c.dispatchEvents(ctx, c.RightUART, rightBladeTopicOut)
|
|
})
|
|
|
|
// Button Press events
|
|
println("[+] Starting button interrupt handler")
|
|
c.ButtonPin.SetInterrupt(machine.PinFalling, func(machine.Pin) {
|
|
c.buttonPressed += 1
|
|
})
|
|
|
|
group.Go(func() error {
|
|
ticker := time.NewTicker(20 * time.Millisecond)
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case <-ticker.C:
|
|
btnPressed := smartfanunit.ButtonPressPacket{}
|
|
if c.buttonPressed > 0 {
|
|
// Allow up to 600ms for a 2nc button press
|
|
time.Sleep(600 * time.Millisecond)
|
|
}
|
|
|
|
if c.buttonPressed == 1 {
|
|
println("[ ] Button pressed once")
|
|
c.eb.Publish(leftBladeTopicOut, btnPressed.Packet())
|
|
}
|
|
if c.buttonPressed == 2 {
|
|
println("[ ] Button pressed twice")
|
|
c.eb.Publish(rightBladeTopicOut, btnPressed.Packet())
|
|
}
|
|
c.buttonPressed = 0
|
|
}
|
|
}
|
|
})
|
|
return group.Wait()
|
|
}
|
|
|
|
// listenEvents reads events from the UART interface and dispatches them to the events
|
|
func (c *Controller) listenEvents(ctx context.Context, uart drivers.UART, targetTopic string) error {
|
|
for {
|
|
// Read packet from UART; blocks until packet is received
|
|
pkt, err := proto.ReadPacket(ctx, uart)
|
|
if err != nil {
|
|
println("[!] failed to read packet, continuing..", err.Error())
|
|
continue
|
|
}
|
|
println("[ ] received packet from UART publishing to topic", targetTopic)
|
|
c.eb.Publish(targetTopic, pkt)
|
|
}
|
|
}
|
|
|
|
// dispatchEvents reads events from the events and writes them to the UART interface
|
|
func (c *Controller) dispatchEvents(ctx context.Context, uart drivers.UART, sourceTopic string) error {
|
|
sub := c.eb.Subscribe(sourceTopic, 4, events.MatchAll)
|
|
defer sub.Unsubscribe()
|
|
for {
|
|
select {
|
|
case msg := <-sub.C():
|
|
println("[ ] dispatching event to UART from topic", sourceTopic)
|
|
pkt := msg.(proto.Packet)
|
|
err := proto.WritePacket(ctx, uart, pkt)
|
|
if err != nil {
|
|
println(err.Error())
|
|
}
|
|
case <-ctx.Done():
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Controller) metricReporter(ctx context.Context) error {
|
|
var err error
|
|
|
|
ticker := time.NewTicker(2 * time.Second)
|
|
airFlowTempRight := smartfanunit.AirFlowTemperaturePacket{}
|
|
airFlowTempLeft := smartfanunit.AirFlowTemperaturePacket{}
|
|
fanRpm := smartfanunit.FanSpeedRPMPacket{}
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case <-ticker.C:
|
|
}
|
|
|
|
airFlowTempLeft.Temperature, err = c.FanController.InternalTemperature()
|
|
if err != nil {
|
|
println("[!] failed to read internal temperature:", err.Error())
|
|
}
|
|
airFlowTempRight.Temperature, err = c.FanController.ExternalTemperature()
|
|
if err != nil {
|
|
println("[!] failed to read external temperature:", err.Error())
|
|
}
|
|
fanRpm.RPM, err = c.FanController.FanRPM()
|
|
if err != nil {
|
|
println("[!] failed to read fan RPM:", err.Error())
|
|
}
|
|
|
|
// FIXME: This is a workaround for an i2c lockup issue.
|
|
if err != nil {
|
|
println("[!] resetting CPU")
|
|
time.Sleep(100 * time.Millisecond)
|
|
machine.CPUReset()
|
|
}
|
|
|
|
// Publish metrics
|
|
c.eb.Publish(leftBladeTopicOut, airFlowTempLeft.Packet())
|
|
c.eb.Publish(rightBladeTopicOut, airFlowTempRight.Packet())
|
|
c.eb.Publish(leftBladeTopicOut, fanRpm.Packet())
|
|
c.eb.Publish(rightBladeTopicOut, fanRpm.Packet())
|
|
}
|
|
}
|
|
|
|
func (c *Controller) updateFanSpeed(ctx context.Context) error {
|
|
var pkt smartfanunit.SetFanSpeedPercentPacket
|
|
|
|
subLeft := c.eb.Subscribe(leftBladeTopicIn, 1, events.MatchAll)
|
|
defer subLeft.Unsubscribe()
|
|
subRight := c.eb.Subscribe(rightBladeTopicIn, 1, events.MatchAll)
|
|
defer subRight.Unsubscribe()
|
|
|
|
for {
|
|
// Update LED color depending on blade
|
|
select {
|
|
case msg := <-subLeft.C():
|
|
pkt.FromPacket(msg.(proto.Packet))
|
|
c.leftReqFanSpeed = pkt.Percent
|
|
case msg := <-subRight.C():
|
|
pkt.FromPacket(msg.(proto.Packet))
|
|
c.rightReqFanSpeed = pkt.Percent
|
|
case <-ctx.Done():
|
|
return nil
|
|
}
|
|
|
|
// Update fan speed with the max requested speed
|
|
if c.leftReqFanSpeed > c.rightReqFanSpeed {
|
|
c.FanController.SetFanPercent(c.leftReqFanSpeed)
|
|
} else {
|
|
c.FanController.SetFanPercent(c.rightReqFanSpeed)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Controller) updateLEDs(ctx context.Context) error {
|
|
subLeft := c.eb.Subscribe(leftBladeTopicIn, 1, smartfanunit.MatchCmd(smartfanunit.CmdSetLED))
|
|
defer subLeft.Unsubscribe()
|
|
subRight := c.eb.Subscribe(rightBladeTopicIn, 1, smartfanunit.MatchCmd(smartfanunit.CmdSetLED))
|
|
defer subRight.Unsubscribe()
|
|
|
|
var pkt smartfanunit.SetLEDPacket
|
|
for {
|
|
// Update LED color depending on blade
|
|
select {
|
|
case msg := <-subLeft.C():
|
|
pkt.FromPacket(msg.(proto.Packet))
|
|
c.leftLed = pkt.Color
|
|
case msg := <-subRight.C():
|
|
pkt.FromPacket(msg.(proto.Packet))
|
|
c.rightLed = pkt.Color
|
|
case <-time.After(500 * time.Millisecond):
|
|
case <-ctx.Done():
|
|
return nil
|
|
}
|
|
// Write to LEDs (they are in a chain -> we always have to update both)
|
|
_, err := c.LEDs.Write([]byte{
|
|
c.rightLed.Blue, c.rightLed.Green, c.rightLed.Red,
|
|
c.leftLed.Blue, c.leftLed.Green, c.leftLed.Red,
|
|
})
|
|
if err != nil {
|
|
println("[!] failed to update LEDs", err.Error())
|
|
return err
|
|
}
|
|
}
|
|
}
|