Added control server
This commit is contained in:
parent
a2cccbde71
commit
d5713b5e43
7 changed files with 577 additions and 0 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -17,3 +17,5 @@ fp-info-cache
|
|||
*.lst
|
||||
*.map
|
||||
*.srec
|
||||
|
||||
uncanny/uncanny
|
||||
|
|
115
uncanny/can.go
Normal file
115
uncanny/can.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package uncanny
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/brutella/can"
|
||||
)
|
||||
|
||||
type Can struct {
|
||||
bus *can.Bus
|
||||
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,
|
||||
dispstate: -1,
|
||||
}
|
||||
bus.Subscribe(can)
|
||||
return can, nil
|
||||
}
|
||||
|
||||
func (c *Can) Start() {
|
||||
go func() {
|
||||
// send the init sequence
|
||||
c.initialize()
|
||||
// ConnectAndPublish will block, so we'll just leave the Printf hanging here
|
||||
log.Printf("CAN bus shut down: %v", c.bus.ConnectAndPublish())
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *Can) Stop() {
|
||||
c.bus.Disconnect()
|
||||
}
|
||||
|
||||
func (c *Can) Handle(frame can.Frame) {
|
||||
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:
|
||||
c.swstate = obj.(FeedbackMessage)
|
||||
case MessageTypeFeedbackRequest:
|
||||
// ignore
|
||||
case MessageTypePower:
|
||||
// ignore
|
||||
case MessageTypePowerRequest:
|
||||
// ignore
|
||||
case MessageTypeDispense:
|
||||
c.dispstate = obj.(DispenseMessage).Slot
|
||||
case MessageTypeAuto:
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Can) IsEmpty(slot int) bool {
|
||||
switch (slot) {
|
||||
case 0:
|
||||
return c.swstate.EmptyD
|
||||
case 1:
|
||||
return c.swstate.EmptyE
|
||||
case 2:
|
||||
return c.swstate.EmptyF
|
||||
case 3:
|
||||
return c.swstate.EmptyG
|
||||
case 4:
|
||||
return c.swstate.EmptyH
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Can) IsDispensing(slot int) bool {
|
||||
switch (slot) {
|
||||
case 0:
|
||||
return c.swstate.EndD
|
||||
case 1:
|
||||
return c.swstate.EndE
|
||||
case 2:
|
||||
return c.swstate.EndF
|
||||
case 3:
|
||||
return c.swstate.EndG
|
||||
case 4:
|
||||
return c.swstate.EndH
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Can) ActiveDispenser() int {
|
||||
return c.dispstate
|
||||
}
|
||||
|
||||
func (c *Can) Dispense(slot int) error {
|
||||
return c.bus.Publish(DispenseMessage{slot}.Encode())
|
||||
}
|
||||
|
||||
func (c *Can) Cancel() error {
|
||||
return c.bus.Publish(DispenseMessage{DispenseSlotOff}.Encode())
|
||||
}
|
||||
|
||||
func (c *Can) initialize() error {
|
||||
// enable automatic status updates
|
||||
return c.bus.Publish(AutoMessage{true}.Encode())
|
||||
}
|
27
uncanny/cmd/uncanny/main.go
Normal file
27
uncanny/cmd/uncanny/main.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"git.kabelsalat.ch/seto/matemat/uncanny"
|
||||
"log"
|
||||
)
|
||||
|
||||
const (
|
||||
CanInterface = "can0"
|
||||
ListenAddress = "localhost:8000"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var intf string
|
||||
var addr string
|
||||
flag.StringVar(&intf, "interface", CanInterface, "CAN interface name")
|
||||
flag.StringVar(&addr, "listen", ListenAddress, "HTTP listen address")
|
||||
flag.Parse()
|
||||
can, err := uncanny.NewCan(intf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
server := uncanny.NewServer(can)
|
||||
can.Start()
|
||||
log.Fatal(server.ListenAndServe(addr))
|
||||
}
|
5
uncanny/go.mod
Normal file
5
uncanny/go.mod
Normal file
|
@ -0,0 +1,5 @@
|
|||
module git.kabelsalat.ch/seto/matemat/uncanny
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/brutella/can v0.0.1
|
4
uncanny/go.sum
Normal file
4
uncanny/go.sum
Normal file
|
@ -0,0 +1,4 @@
|
|||
github.com/brutella/can v0.0.1 h1:Rz+2Zuje3NT79daon8wPN9+VphH3/kl1DP8Dhf/k1NI=
|
||||
github.com/brutella/can v0.0.1/go.mod h1:NYDxbQito3w4+4DcjWs/fpQ3xyaFdpXw/KYqtZFU98k=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 h1:0oC8rFnE+74kEmuHZ46F6KHsMr5Gx2gUQPuNz28iQZM=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
305
uncanny/message.go
Normal file
305
uncanny/message.go
Normal file
|
@ -0,0 +1,305 @@
|
|||
package uncanny
|
||||
|
||||
/*
|
||||
// include our message format definitions
|
||||
#cgo CPPFLAGS: -I${SRCDIR}/..
|
||||
#include <messages.h>
|
||||
|
||||
// since we can't use macros in Go directly, let's add some wrappers
|
||||
// to avoid conflicts, we'll simply lowercase the symbol names
|
||||
|
||||
const uint16_t can_msg_feedback_status = CAN_MSG_FEEDBACK_STATUS;
|
||||
const size_t can_msg_feedback_status_field_reset_sw = CAN_MSG_FEEDBACK_STATUS_FIELD_RESET_SW;
|
||||
const uint8_t can_msg_feedback_status_bit_reset_sw = CAN_MSG_FEEDBACK_STATUS_BIT_RESET_SW;
|
||||
const size_t can_msg_feedback_status_field_empty_h = CAN_MSG_FEEDBACK_STATUS_FIELD_EMPTY_H;
|
||||
const uint8_t can_msg_feedback_status_bit_empty_h = CAN_MSG_FEEDBACK_STATUS_BIT_EMPTY_H;
|
||||
const size_t can_msg_feedback_status_field_empty_g = CAN_MSG_FEEDBACK_STATUS_FIELD_EMPTY_G;
|
||||
const uint8_t can_msg_feedback_status_bit_empty_g = CAN_MSG_FEEDBACK_STATUS_BIT_EMPTY_G;
|
||||
const size_t can_msg_feedback_status_field_empty_f = CAN_MSG_FEEDBACK_STATUS_FIELD_EMPTY_F;
|
||||
const uint8_t can_msg_feedback_status_bit_empty_f = CAN_MSG_FEEDBACK_STATUS_BIT_EMPTY_F;
|
||||
const size_t can_msg_feedback_status_field_empty_e = CAN_MSG_FEEDBACK_STATUS_FIELD_EMPTY_E;
|
||||
const uint8_t can_msg_feedback_status_bit_empty_e = CAN_MSG_FEEDBACK_STATUS_BIT_EMPTY_E;
|
||||
const size_t can_msg_feedback_status_field_empty_d = CAN_MSG_FEEDBACK_STATUS_FIELD_EMPTY_D;
|
||||
const uint8_t can_msg_feedback_status_bit_empty_d = CAN_MSG_FEEDBACK_STATUS_BIT_EMPTY_D;
|
||||
const size_t can_msg_feedback_status_field_end_h = CAN_MSG_FEEDBACK_STATUS_FIELD_END_H;
|
||||
const uint8_t can_msg_feedback_status_bit_end_h = CAN_MSG_FEEDBACK_STATUS_BIT_END_H;
|
||||
const size_t can_msg_feedback_status_field_end_g = CAN_MSG_FEEDBACK_STATUS_FIELD_END_G;
|
||||
const uint8_t can_msg_feedback_status_bit_end_g = CAN_MSG_FEEDBACK_STATUS_BIT_END_G;
|
||||
const size_t can_msg_feedback_status_field_end_f = CAN_MSG_FEEDBACK_STATUS_FIELD_END_F;
|
||||
const uint8_t can_msg_feedback_status_bit_end_f = CAN_MSG_FEEDBACK_STATUS_BIT_END_F;
|
||||
const size_t can_msg_feedback_status_field_end_e = CAN_MSG_FEEDBACK_STATUS_FIELD_END_E;
|
||||
const uint8_t can_msg_feedback_status_bit_end_e = CAN_MSG_FEEDBACK_STATUS_BIT_END_E;
|
||||
const size_t can_msg_feedback_status_field_end_d = CAN_MSG_FEEDBACK_STATUS_FIELD_END_D;
|
||||
const uint8_t can_msg_feedback_status_bit_end_d = CAN_MSG_FEEDBACK_STATUS_BIT_END_D;
|
||||
|
||||
const uint16_t can_msg_power_status = CAN_MSG_POWER_STATUS;
|
||||
|
||||
const uint16_t can_msg_power_dispense = CAN_MSG_POWER_DISPENSE;
|
||||
const uint8_t can_msg_power_dispense_off = CAN_MSG_POWER_DISPENSE_OFF;
|
||||
const uint8_t can_msg_power_dispense_slot1 = CAN_MSG_POWER_DISPENSE_SLOT1;
|
||||
const uint8_t can_msg_power_dispense_slot2 = CAN_MSG_POWER_DISPENSE_SLOT2;
|
||||
const uint8_t can_msg_power_dispense_slot3 = CAN_MSG_POWER_DISPENSE_SLOT3;
|
||||
const uint8_t can_msg_power_dispense_slot4 = CAN_MSG_POWER_DISPENSE_SLOT4;
|
||||
const uint8_t can_msg_power_dispense_slot5 = CAN_MSG_POWER_DISPENSE_SLOT5;
|
||||
|
||||
const uint16_t can_msg_auto_status = CAN_MSG_AUTO_STATUS;
|
||||
const uint8_t can_msg_auto_status_disable = CAN_MSG_AUTO_STATUS_DISABLE;
|
||||
const uint8_t can_msg_auto_status_enable = CAN_MSG_AUTO_STATUS_ENABLE;
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/brutella/can"
|
||||
)
|
||||
|
||||
var (
|
||||
InvalidMessageID = errors.New("Invalid message ID, cannot decode")
|
||||
InvalidMessageLength = errors.New("Invalid message length, cannot decode")
|
||||
UnsupportedMessageType = errors.New("Unsupported message type, cannot decode")
|
||||
)
|
||||
|
||||
type MessageType int
|
||||
const (
|
||||
MessageTypeInvalid MessageType = iota
|
||||
MessageTypeFeedback
|
||||
MessageTypeFeedbackRequest
|
||||
MessageTypePower
|
||||
MessageTypePowerRequest
|
||||
MessageTypeDispense
|
||||
MessageTypeAuto
|
||||
)
|
||||
|
||||
// DecodeMessage decodes a CAN frame and returns an appropriate message
|
||||
// object and the message type.
|
||||
//
|
||||
// For request messages, only the type is returned (object and error are nil)
|
||||
//
|
||||
// When the message cannot be decoded, a nil object and MessageTypeInvalid is
|
||||
// returned, plus an appropriate error. If the message ID is unknown, error
|
||||
// will be UnsupportedMessageType.
|
||||
func DecodeMessage(frame can.Frame) (MessageType, interface{}, error) {
|
||||
switch frame.ID {
|
||||
case uint32(C.can_msg_feedback_status):
|
||||
msg, err := DecodeFeedbackMessage(frame)
|
||||
return MessageTypeFeedback, msg, err
|
||||
case uint32(C.can_msg_feedback_status) | can.MaskRtr:
|
||||
return MessageTypeFeedbackRequest, nil, nil
|
||||
case uint32(C.can_msg_power_status):
|
||||
msg, err := DecodePowerMessage(frame)
|
||||
return MessageTypePower, msg, err
|
||||
case uint32(C.can_msg_power_status) | can.MaskRtr:
|
||||
return MessageTypePowerRequest, nil, nil
|
||||
case uint32(C.can_msg_power_dispense):
|
||||
msg, err := DecodeDispenseMessage(frame)
|
||||
return MessageTypeDispense, msg, err
|
||||
case uint32(C.can_msg_auto_status):
|
||||
msg, err := DecodeAutoMessage(frame)
|
||||
return MessageTypeAuto, msg, err
|
||||
}
|
||||
return MessageTypeInvalid, nil, UnsupportedMessageType
|
||||
}
|
||||
|
||||
type FeedbackMessage struct {
|
||||
EndD bool
|
||||
EndE bool
|
||||
EndF bool
|
||||
EndG bool
|
||||
EndH bool
|
||||
EmptyD bool
|
||||
EmptyE bool
|
||||
EmptyF bool
|
||||
EmptyG bool
|
||||
EmptyH bool
|
||||
ResetSw bool
|
||||
}
|
||||
|
||||
func RequestFeedbackMessage() can.Frame {
|
||||
return can.Frame{
|
||||
ID: uint32(C.can_msg_feedback_status) | can.MaskRtr,
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeFeedbackMessage(frame can.Frame) (FeedbackMessage, error) {
|
||||
var ret FeedbackMessage
|
||||
if frame.ID != uint32(C.can_msg_feedback_status) {
|
||||
return ret, InvalidMessageID
|
||||
}
|
||||
if frame.Length != 2 {
|
||||
return ret, InvalidMessageLength
|
||||
}
|
||||
ret.EndD = frame.Data[C.can_msg_feedback_status_field_end_d] & C.can_msg_feedback_status_bit_end_d != 0
|
||||
ret.EndE = frame.Data[C.can_msg_feedback_status_field_end_e] & C.can_msg_feedback_status_bit_end_e != 0
|
||||
ret.EndF = frame.Data[C.can_msg_feedback_status_field_end_f] & C.can_msg_feedback_status_bit_end_f != 0
|
||||
ret.EndG = frame.Data[C.can_msg_feedback_status_field_end_g] & C.can_msg_feedback_status_bit_end_g != 0
|
||||
ret.EndH = frame.Data[C.can_msg_feedback_status_field_end_h] & C.can_msg_feedback_status_bit_end_h != 0
|
||||
ret.EmptyD = frame.Data[C.can_msg_feedback_status_field_empty_d] & C.can_msg_feedback_status_bit_empty_d != 0
|
||||
ret.EmptyE = frame.Data[C.can_msg_feedback_status_field_empty_e] & C.can_msg_feedback_status_bit_empty_e != 0
|
||||
ret.EmptyF = frame.Data[C.can_msg_feedback_status_field_empty_f] & C.can_msg_feedback_status_bit_empty_f != 0
|
||||
ret.EmptyG = frame.Data[C.can_msg_feedback_status_field_empty_g] & C.can_msg_feedback_status_bit_empty_g != 0
|
||||
ret.EmptyH = frame.Data[C.can_msg_feedback_status_field_empty_h] & C.can_msg_feedback_status_bit_empty_h != 0
|
||||
ret.ResetSw = frame.Data[C.can_msg_feedback_status_field_reset_sw] & C.can_msg_feedback_status_bit_reset_sw != 0
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (f FeedbackMessage) Encode() can.Frame {
|
||||
data := [8]uint8{}
|
||||
if f.EndD {
|
||||
data[C.can_msg_feedback_status_field_end_d] |= C.can_msg_feedback_status_bit_end_d
|
||||
}
|
||||
if f.EndE {
|
||||
data[C.can_msg_feedback_status_field_end_e] |= C.can_msg_feedback_status_bit_end_e
|
||||
}
|
||||
if f.EndF {
|
||||
data[C.can_msg_feedback_status_field_end_f] |= C.can_msg_feedback_status_bit_end_f
|
||||
}
|
||||
if f.EndG {
|
||||
data[C.can_msg_feedback_status_field_end_g] |= C.can_msg_feedback_status_bit_end_g
|
||||
}
|
||||
if f.EndH {
|
||||
data[C.can_msg_feedback_status_field_end_h] |= C.can_msg_feedback_status_bit_end_h
|
||||
}
|
||||
if f.EmptyD {
|
||||
data[C.can_msg_feedback_status_field_empty_d] |= C.can_msg_feedback_status_bit_empty_d
|
||||
}
|
||||
if f.EmptyE {
|
||||
data[C.can_msg_feedback_status_field_empty_e] |= C.can_msg_feedback_status_bit_empty_e
|
||||
}
|
||||
if f.EmptyF {
|
||||
data[C.can_msg_feedback_status_field_empty_f] |= C.can_msg_feedback_status_bit_empty_f
|
||||
}
|
||||
if f.EmptyG {
|
||||
data[C.can_msg_feedback_status_field_empty_g] |= C.can_msg_feedback_status_bit_empty_g
|
||||
}
|
||||
if f.EmptyH {
|
||||
data[C.can_msg_feedback_status_field_empty_h] |= C.can_msg_feedback_status_bit_empty_h
|
||||
}
|
||||
if f.ResetSw {
|
||||
data[C.can_msg_feedback_status_field_reset_sw] |= C.can_msg_feedback_status_bit_reset_sw
|
||||
}
|
||||
return can.Frame{
|
||||
ID: uint32(C.can_msg_feedback_status),
|
||||
Length: 2,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
type PowerMessage struct {
|
||||
Bits int
|
||||
}
|
||||
|
||||
func RequestPowerMessage() can.Frame {
|
||||
return can.Frame{
|
||||
ID: uint32(C.can_msg_power_status) | can.MaskRtr,
|
||||
}
|
||||
}
|
||||
|
||||
func DecodePowerMessage(frame can.Frame) (PowerMessage, error) {
|
||||
var ret PowerMessage
|
||||
if frame.ID != uint32(C.can_msg_power_status) {
|
||||
return ret, InvalidMessageID
|
||||
}
|
||||
if frame.Length != 2 {
|
||||
return ret, InvalidMessageLength
|
||||
}
|
||||
ret.Bits = (int(frame.Data[0]) << 8) | int(frame.Data[1])
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (f PowerMessage) Encode() can.Frame {
|
||||
data := [8]uint8{}
|
||||
data[0] = uint8(f.Bits >> 8)
|
||||
data[1] = uint8(f.Bits)
|
||||
return can.Frame{
|
||||
ID: uint32(C.can_msg_power_status),
|
||||
Length: 2,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
const DispenseSlotOff int = -1
|
||||
|
||||
type DispenseMessage struct {
|
||||
Slot int
|
||||
}
|
||||
|
||||
func DecodeDispenseMessage(frame can.Frame) (DispenseMessage, error) {
|
||||
var ret DispenseMessage
|
||||
if frame.ID != uint32(C.can_msg_power_dispense) {
|
||||
return ret, InvalidMessageID
|
||||
}
|
||||
if frame.Length != 1 {
|
||||
return ret, InvalidMessageLength
|
||||
}
|
||||
switch frame.Data[0] {
|
||||
case C.can_msg_power_dispense_off:
|
||||
ret.Slot = DispenseSlotOff
|
||||
case C.can_msg_power_dispense_slot1:
|
||||
ret.Slot = 0
|
||||
case C.can_msg_power_dispense_slot2:
|
||||
ret.Slot = 1
|
||||
case C.can_msg_power_dispense_slot3:
|
||||
ret.Slot = 2
|
||||
case C.can_msg_power_dispense_slot4:
|
||||
ret.Slot = 3
|
||||
case C.can_msg_power_dispense_slot5:
|
||||
ret.Slot = 4
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (f DispenseMessage) Encode() can.Frame {
|
||||
data := [8]uint8{}
|
||||
switch f.Slot {
|
||||
case DispenseSlotOff:
|
||||
data[0] = C.can_msg_power_dispense_off
|
||||
case 0:
|
||||
data[0] = C.can_msg_power_dispense_slot1
|
||||
case 1:
|
||||
data[0] = C.can_msg_power_dispense_slot2
|
||||
case 2:
|
||||
data[0] = C.can_msg_power_dispense_slot3
|
||||
case 3:
|
||||
data[0] = C.can_msg_power_dispense_slot4
|
||||
case 4:
|
||||
data[0] = C.can_msg_power_dispense_slot5
|
||||
}
|
||||
return can.Frame{
|
||||
ID: uint32(C.can_msg_power_dispense),
|
||||
Length: 1,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
type AutoMessage struct {
|
||||
AutoUpdate bool
|
||||
}
|
||||
|
||||
func DecodeAutoMessage(frame can.Frame) (AutoMessage, error) {
|
||||
var ret AutoMessage
|
||||
if frame.ID != uint32(C.can_msg_auto_status) {
|
||||
return ret, InvalidMessageID
|
||||
}
|
||||
if frame.Length != 1 {
|
||||
return ret, InvalidMessageLength
|
||||
}
|
||||
// we accept all non-null values as "enable"
|
||||
if frame.Data[0] != C.can_msg_auto_status_disable {
|
||||
ret.AutoUpdate = true
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (f AutoMessage) Encode() can.Frame {
|
||||
data := [8]uint8{}
|
||||
if f.AutoUpdate {
|
||||
data[0] = C.can_msg_auto_status_enable
|
||||
} else {
|
||||
data[0] = C.can_msg_auto_status_disable
|
||||
}
|
||||
return can.Frame{
|
||||
ID: uint32(C.can_msg_auto_status),
|
||||
Length: 1,
|
||||
Data: data,
|
||||
}
|
||||
}
|
119
uncanny/server.go
Normal file
119
uncanny/server.go
Normal file
|
@ -0,0 +1,119 @@
|
|||
package uncanny
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// highest slot number (range: 0..maxSlot)
|
||||
maxSlot = 4
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
*http.ServeMux
|
||||
can *Can
|
||||
}
|
||||
|
||||
func NewServer(can *Can) *Server {
|
||||
ret := &Server{
|
||||
ServeMux: http.NewServeMux(),
|
||||
can: can,
|
||||
}
|
||||
ret.registerHandlers()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *Server) registerHandlers() {
|
||||
s.HandleFunc("/", http.NotFound)
|
||||
s.HandleFunc("/dispense/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
if matched, _ := path.Match("/dispense/[0-9]?[0-9]*", r.URL.Path); matched {
|
||||
slot, err := strconv.ParseUint(path.Base(r.URL.Path), 10, 32)
|
||||
if err != nil {
|
||||
log.Printf("Error decoding slot number: %v", err)
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
} else if slot < 0 || slot > maxSlot {
|
||||
log.Printf("Invalid slot number: %u", slot)
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
} else {
|
||||
err = s.can.Dispense(int(slot))
|
||||
if err != nil {
|
||||
log.Printf("Error sending dispense command: %v", err)
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(http.StatusServiceUnavailable)
|
||||
fmt.Fprintf(rw, "%u error", slot)
|
||||
} else {
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(rw, "%u dispense", slot)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
}
|
||||
})
|
||||
s.HandleFunc("/stop", func(rw http.ResponseWriter, r *http.Request) {
|
||||
err := s.can.Cancel()
|
||||
if err != nil {
|
||||
log.Printf("Error sending stop command: %v", err)
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(http.StatusServiceUnavailable)
|
||||
fmt.Fprintf(rw, "error")
|
||||
} else {
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(rw, "stopped")
|
||||
}
|
||||
})
|
||||
s.HandleFunc("/level/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
if matched, _ := path.Match("/level/[0-9]?[0-9]*", r.URL.Path); matched {
|
||||
slot, err := strconv.ParseUint(path.Base(r.URL.Path), 10, 32)
|
||||
if err != nil {
|
||||
log.Printf("Error decoding slot number: %v", err)
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
} else if slot < 0 || slot > maxSlot {
|
||||
log.Printf("Invalid slot number: %u", slot)
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
} else {
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
if s.can.IsEmpty(int(slot)) {
|
||||
fmt.Fprintf(rw, "%u empty", slot)
|
||||
} else {
|
||||
fmt.Fprintf(rw, "%u full", slot)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
}
|
||||
})
|
||||
s.HandleFunc("/active/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
if matched, _ := path.Match("/active/[0-9]?[0-9]*", r.URL.Path); matched {
|
||||
slot, err := strconv.ParseUint(path.Base(r.URL.Path), 10, 32)
|
||||
if err != nil {
|
||||
log.Printf("Error decoding slot number: %v", err)
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
} else if slot < 0 || slot > maxSlot {
|
||||
log.Printf("Invalid slot number: %u", slot)
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
} else {
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
if s.can.IsDispensing(int(slot)) {
|
||||
fmt.Fprintf(rw, "%u dispensing", slot)
|
||||
} else {
|
||||
fmt.Fprintf(rw, "%u off", slot)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) ListenAndServe(addr string) error {
|
||||
return http.ListenAndServe(addr, s)
|
||||
}
|
Loading…
Reference in a new issue