|
|
|
@ -6,7 +6,6 @@ import (
|
|
|
|
"fmt"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io"
|
|
|
|
"strconv"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"sync"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
@ -161,12 +160,16 @@ func (dc *DerbyClock) Results() []*Result {
|
|
|
|
|
|
|
|
|
|
|
|
// readLoop continuously reads from the serial port
|
|
|
|
// readLoop continuously reads from the serial port
|
|
|
|
func (dc *DerbyClock) readLoop() {
|
|
|
|
func (dc *DerbyClock) readLoop() {
|
|
|
|
|
|
|
|
buffer := make([]byte, 0, 256)
|
|
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
select {
|
|
|
|
case <-dc.stopReading:
|
|
|
|
case <-dc.stopReading:
|
|
|
|
return
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
line, err := dc.reader.ReadString('\n')
|
|
|
|
// Read a single byte
|
|
|
|
|
|
|
|
b := make([]byte, 1)
|
|
|
|
|
|
|
|
_, err := dc.port.Read(b)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
if err != io.EOF {
|
|
|
|
if err != io.EOF {
|
|
|
|
// Only log if it's not EOF
|
|
|
|
// Only log if it's not EOF
|
|
|
|
@ -176,19 +179,11 @@ func (dc *DerbyClock) readLoop() {
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Process the line
|
|
|
|
// Add the byte to our buffer
|
|
|
|
dc.processLine(line)
|
|
|
|
buffer = append(buffer, b[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// processLine handles a line of text from the derby clock
|
|
|
|
|
|
|
|
func (dc *DerbyClock) processLine(line string) {
|
|
|
|
|
|
|
|
// Trim any whitespace and carriage returns
|
|
|
|
|
|
|
|
line = strings.TrimSpace(line)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if line == "C" {
|
|
|
|
// Check for race start signal "C\r\n"
|
|
|
|
// Race has started
|
|
|
|
if len(buffer) >= 3 && string(buffer[len(buffer)-3:]) == "C\r\n" {
|
|
|
|
dc.mu.Lock()
|
|
|
|
dc.mu.Lock()
|
|
|
|
dc.status = StatusRunning
|
|
|
|
dc.status = StatusRunning
|
|
|
|
dc.results = nil
|
|
|
|
dc.results = nil
|
|
|
|
@ -196,83 +191,108 @@ func (dc *DerbyClock) processLine(line string) {
|
|
|
|
|
|
|
|
|
|
|
|
// Send race start event
|
|
|
|
// Send race start event
|
|
|
|
dc.eventChan <- Event{Type: EventRaceStart}
|
|
|
|
dc.eventChan <- Event{Type: EventRaceStart}
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check if this is a results line
|
|
|
|
// Clear the buffer
|
|
|
|
if strings.Contains(line, "=") {
|
|
|
|
buffer = buffer[:0]
|
|
|
|
results := parseResults(line)
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check for lane result pattern (n=t.ttttc)
|
|
|
|
|
|
|
|
// We need to look for an equals sign followed by digits, a period, more digits, and a finish character
|
|
|
|
|
|
|
|
if b[0] == '=' || b[0] == ' ' || b[0] == '\r' || b[0] == '\n' {
|
|
|
|
|
|
|
|
// These characters could indicate a complete result or a separator
|
|
|
|
|
|
|
|
// Try to extract a result from the buffer
|
|
|
|
|
|
|
|
result := dc.tryExtractResult(buffer)
|
|
|
|
|
|
|
|
if result != nil {
|
|
|
|
dc.mu.Lock()
|
|
|
|
dc.mu.Lock()
|
|
|
|
// Add results to our stored results
|
|
|
|
// Add result to our stored results
|
|
|
|
for _, result := range results {
|
|
|
|
|
|
|
|
dc.results = append(dc.results, result)
|
|
|
|
dc.results = append(dc.results, result)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Determine finish place based on how many results we have
|
|
|
|
|
|
|
|
result.FinishPlace = len(dc.results)
|
|
|
|
|
|
|
|
dc.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
// Send lane finish event
|
|
|
|
// Send lane finish event
|
|
|
|
dc.eventChan <- Event{
|
|
|
|
dc.eventChan <- Event{
|
|
|
|
Type: EventLaneFinish,
|
|
|
|
Type: EventLaneFinish,
|
|
|
|
Result: result,
|
|
|
|
Result: result,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If we have results, the race is now finished
|
|
|
|
// If we hit a newline, this might be the end of all results
|
|
|
|
if len(results) > 0 {
|
|
|
|
if b[0] == '\n' {
|
|
|
|
|
|
|
|
// Check if we should consider the race complete
|
|
|
|
|
|
|
|
dc.mu.Lock()
|
|
|
|
|
|
|
|
if dc.status == StatusRunning {
|
|
|
|
dc.status = StatusFinished
|
|
|
|
dc.status = StatusFinished
|
|
|
|
|
|
|
|
dc.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
// Send race complete event
|
|
|
|
// Send race complete event
|
|
|
|
dc.eventChan <- Event{Type: EventRaceComplete}
|
|
|
|
dc.eventChan <- Event{Type: EventRaceComplete}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
dc.mu.Unlock()
|
|
|
|
dc.mu.Unlock()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// parseResults parses a line of results from the derby clock
|
|
|
|
// Clear the buffer after a newline
|
|
|
|
func parseResults(line string) []*Result {
|
|
|
|
buffer = buffer[:0]
|
|
|
|
parts := strings.Split(line, " ")
|
|
|
|
}
|
|
|
|
results := make([]*Result, 0, len(parts))
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for i, part := range parts {
|
|
|
|
// tryExtractResult attempts to extract a lane result from the buffer
|
|
|
|
if !strings.Contains(part, "=") {
|
|
|
|
func (dc *DerbyClock) tryExtractResult(buffer []byte) *Result {
|
|
|
|
continue
|
|
|
|
// Convert buffer to string for easier processing
|
|
|
|
|
|
|
|
bufStr := string(buffer)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Look for the pattern: n=t.ttttc
|
|
|
|
|
|
|
|
// where n is the lane number, t.tttt is the time, and c is the finish character
|
|
|
|
|
|
|
|
for i := 0; i < len(bufStr); i++ {
|
|
|
|
|
|
|
|
if bufStr[i] == '=' {
|
|
|
|
|
|
|
|
// Found an equals sign, try to extract a lane number before it
|
|
|
|
|
|
|
|
j := i - 1
|
|
|
|
|
|
|
|
for j >= 0 && bufStr[j] >= '0' && bufStr[j] <= '9' {
|
|
|
|
|
|
|
|
j--
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
j++ // Move back to the first digit
|
|
|
|
|
|
|
|
|
|
|
|
// Format is "n=0.0000c"
|
|
|
|
if j < i { // We found at least one digit
|
|
|
|
laneStr := strings.Split(part, "=")[0]
|
|
|
|
laneStr := bufStr[j:i]
|
|
|
|
timeAndPlace := strings.Split(part, "=")[1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if len(timeAndPlace) < 2 {
|
|
|
|
// Now look for the time and finish character after the equals sign
|
|
|
|
continue
|
|
|
|
k := i + 1
|
|
|
|
|
|
|
|
for k < len(bufStr) && (bufStr[k] == '.' || (bufStr[k] >= '0' && bufStr[k] <= '9')) {
|
|
|
|
|
|
|
|
k++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Extract time and place character
|
|
|
|
if k < len(bufStr) && k > i+1 { // We found a time and a finish character
|
|
|
|
timeStr := timeAndPlace[:len(timeAndPlace)-1]
|
|
|
|
timeStr := bufStr[i+1 : k]
|
|
|
|
placeChar := timeAndPlace[len(timeAndPlace)-1:]
|
|
|
|
finishChar := bufStr[k : k+1]
|
|
|
|
|
|
|
|
|
|
|
|
// Parse lane number
|
|
|
|
// Parse the lane number
|
|
|
|
lane, err := strconv.Atoi(laneStr)
|
|
|
|
lane, err := strconv.Atoi(laneStr)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Parse time
|
|
|
|
// Parse the time
|
|
|
|
timeVal, err := strconv.ParseFloat(timeStr, 64)
|
|
|
|
timeVal, err := strconv.ParseFloat(timeStr, 64)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Determine place
|
|
|
|
// Create and return the result
|
|
|
|
place := i + 1
|
|
|
|
return &Result{
|
|
|
|
|
|
|
|
|
|
|
|
// Create result
|
|
|
|
|
|
|
|
result := &Result{
|
|
|
|
|
|
|
|
Lane: lane,
|
|
|
|
Lane: lane,
|
|
|
|
Time: timeVal,
|
|
|
|
Time: timeVal,
|
|
|
|
FinishPlace: place,
|
|
|
|
FinishPlace: 0, // Will be set by the caller
|
|
|
|
FinishSymbol: placeChar,
|
|
|
|
FinishSymbol: finishChar,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
results = append(results, result)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return results
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|