package uncanny import ( "errors" "fmt" "log" "github.com/brutella/can" "time" ) const queueSize = 10 type Can struct { bus *can.Bus queue chan func() swstate FeedbackStatus dispstate int } func NewCan(intf string) (*Can, error) { bus, err := can.NewBusForInterfaceWithName(intf) if err != nil { return nil, errors.New(fmt.Sprintf("Can't open %s: %v", intf, err)) } can := &Can{ bus: bus, queue: make(chan func(), queueSize), dispstate: -1, } bus.Subscribe(can) return can, nil } func (c *Can) Start() error { go c.process() log.Printf("Starting bus listener") return c.bus.ConnectAndPublish() } func (c *Can) process() { running := true log.Printf("Starting processing queue") for running { select { case fn, ok := <-c.queue: if ok { fn() } else { running = false } } } for range c.queue {} log.Printf("Processing queue shut down") } func (c *Can) Stop() error { log.Printf("Shutting down processing queue and bus listener") close(c.queue) return c.bus.Disconnect() } func (c *Can) Handle(frame can.Frame) { c.queue <- func() { log.Printf("Got CAN frame to 0x%08x", frame.ID) typ, obj, err := DecodeMessage(frame) if err != nil { if err == UnsupportedMessageType { log.Printf("Unsupported message type: 0x%08x", frame.ID) } else { log.Printf("Cannot decode message: %v", err) } } else { switch typ { case MessageTypeFeedbackStatus: log.Printf("Got feedback status message: %+v", obj.(FeedbackStatus)) log.Printf("Data: 0x%02x 0x%02x", frame.Data[0], frame.Data[1]) c.swstate = obj.(FeedbackStatus) // when all end switches are open (i.e. true): send stop command if c.swstate.EndD && c.swstate.EndE && c.swstate.EndF && c.swstate.EndG && c.swstate.EndH { log.Printf("All end switches off, stopping dispenser") c.Cancel() } case MessageTypePowerStatus: log.Printf("Got power status message: %+v", obj.(PowerStatus)) // ignore case MessageTypeDispenseCommand: log.Printf("Got dispense command message: %+v", obj.(DispenseCommand)) c.dispstate = obj.(DispenseCommand).Slot case MessageTypeDisplayStatus: log.Printf("Got display status message: %+v", obj.(DisplayStatus)) } } } } func (c *Can) IsEmpty(slot int) bool { ret := make(chan bool) c.queue <- func() { switch (slot) { case 0: ret <- c.swstate.EmptyD case 1: ret <- c.swstate.EmptyE case 2: ret <- c.swstate.EmptyF case 3: ret <- c.swstate.EmptyG case 4: ret <- c.swstate.EmptyH } ret <- false } return <-ret } func (c *Can) IsDispensing(slot int) bool { ret := make(chan bool) c.queue <- func() { switch (slot) { case 0: ret <- c.swstate.EndD case 1: ret <- c.swstate.EndE case 2: ret <- c.swstate.EndF case 3: ret <- c.swstate.EndG case 4: ret <- c.swstate.EndH } ret <- false } return <-ret } func (c *Can) ActiveDispenser() int { ret := make(chan int) c.queue <- func() { ret <- c.dispstate } return <- ret } func (c *Can) Dispense(slot int) { c.queue <- func() { log.Printf("Starting dispense on slot %d", slot) err := c.bus.Publish(DispenseCommand{slot}.Encode()) if err != nil { log.Printf("Error sending dispense command: %v", err) } } } func (c *Can) Cancel() { c.queue <- func() { log.Printf("Stopping dispense") err := c.bus.Publish(DispenseCommand{DispenseSlotOff}.Encode()) if err != nil { log.Printf("Error sending stop command: %v", err) } } } func (c *Can) Initialize() { c.queue <- func() { log.Printf("Initializing devices") // enable automatic status updates c.bus.Publish(AutoCommand{true}.Encode()) c.bus.Publish(EncodeFeedbackRequest()) c.bus.Publish(EncodePowerRequest()) c.bus.Publish(EncodeDisplayRequest()) c.DisplayPrint(0, "Hello world") go func() { t := time.Tick(100 * time.Millisecond) var d rune for { select { case <-t: c.DisplayPrint(1, string([]rune{d})) d = (d + 1) & 0xff } } }() } }