package uncanny import ( "errors" "fmt" "log" "github.com/brutella/can" ) const queueSize = 10 type Can struct { bus *can.Bus queue chan func() swstate FeedbackMessage 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 MessageTypeFeedback: log.Printf("Got feedback message: %+v", obj.(FeedbackMessage)) log.Printf("Data: 0x%02x 0x%02x", frame.Data[0], frame.Data[1]) c.swstate = obj.(FeedbackMessage) // when all end switches are off: 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 MessageTypeFeedbackRequest: // ignore case MessageTypePower: log.Printf("Got power message: %+v", obj.(PowerMessage)) // ignore case MessageTypePowerRequest: // ignore case MessageTypeDispense: log.Printf("Got dispense message: %+v", obj.(DispenseMessage)) c.dispstate = obj.(DispenseMessage).Slot case MessageTypeAuto: // ignore } } } } 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(DispenseMessage{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(DispenseMessage{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(AutoMessage{true}.Encode()) c.bus.Publish(RequestFeedbackMessage()) c.bus.Publish(RequestPowerMessage()) } }