<commit_before>package org.jeffpiazza.derby.devices;

import jssc.*;
import org.jeffpiazza.derby.serialport.SerialPortWrapper;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jeffpiazza.derby.LogWriter;
import org.jeffpiazza.derby.Message;

// TODO: "R" responds with "Ready", which maybe means this timer CAN be identified.
// More importantly, "V" responds with "Derby Magic v3.00" or some such.
// http://www.derbymagic.com/files/Timer.pdf
// http://www.derbymagic.com/files/GPRM.pdf
public class DerbyMagicDevice extends TimerDeviceCommon
    implements RemoteStartInterface {
  private TimerResult result = null;
  private long timeOfFirstResult = 0;

  public DerbyMagicDevice(SerialPortWrapper portWrapper) {
    // No GateWatcher, because there's no way to poll this timer
    super(portWrapper, null, /* gate is knowable */ false);

    // Once started, we expect a race result within 10 seconds; we allow an
    // extra second before considering the results overdue.
    rsm.setMaxRunningTimeLimit(11000);
  }

  public static String toHumanString() {
    return "Derby Magic timer";
  }

  private static final int MAX_LANES = 8;

  private static final String READ_VERSION = "V";
  private static final String TIMER_HAS_STARTED = "B";

  private static final String TIMER_RESET = "R";
  private static final String FORCE_DATA_SEND = "F";

  private static final String TRIGGER_START_SOLENOID = "S";

  @Override
  public boolean canBeIdentified() {
    return true;
  }

  public boolean probe() throws SerialPortException {
    for (int baudrate : new int[]{9600, 19200}) {
      if (!portWrapper.setPortParams(baudrate,
                                     SerialPort.DATABITS_8,
                                     SerialPort.STOPBITS_1,
                                     SerialPort.PARITY_NONE)) {
        continue;
      }

      portWrapper.write(READ_VERSION);

      long deadline = System.currentTimeMillis() + 1000;
      String s;
      while ((s = portWrapper.next(deadline)) != null) {
        if (s.indexOf("Derby Magic") >= 0) {
          timerIdentifier = s;
          portWrapper.writeAndDrainResponse(TIMER_RESET, 1, 200);
          setUp();
          has_ever_spoken = true;
          return true;
        }
      }
    }
    return false;
  }

  protected boolean probeAtSpeed(int baudrate) throws SerialPortException {
    if (!portWrapper.setPortParams(baudrate,
                                   SerialPort.DATABITS_8,
                                   SerialPort.STOPBITS_1,
                                   SerialPort.PARITY_NONE)) {
      return false;
    }

    portWrapper.writeAndDrainResponse(TIMER_RESET, 1, 200);
    timerIdentifier = portWrapper.writeAndWaitForResponse(READ_VERSION);

    setUp();
    return true;
  }

  // Timer sends individual lane results as each car crosses the finish line.
  // Each result is <lane>=<time><place>, where <lane> is the lane number,
  // <time> is time (0.0000-9.9999), and <place> is a punctuation charactter,
  // ! for first, " for second, # for third, etc.
  private static final Pattern singleLanePattern = Pattern.compile(
      "([1-8])=(\\d\\.\\d+)([!-/]) *");

  protected void setUp() {
    portWrapper.registerDetector(new SerialPortWrapper.Detector() {
      public String apply(String line) throws SerialPortException {
        if (line.equals(TIMER_HAS_STARTED)) {
          LogWriter.serial("Detected gate opening");
          // This will be an unexpected state change, if it ever happens
          onGateStateChange(false);
          has_ever_spoken = true;
          return "";
        }
        return line;
      }
    });
    portWrapper.registerEarlyDetector(new SerialPortWrapper.Detector() {<commit_msg>In derby-timer.jar, recognize eight-lane results from DerbyMagic timer.<commit_after>package org.jeffpiazza.derby.devices;

import jssc.*;
import org.jeffpiazza.derby.serialport.SerialPortWrapper;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jeffpiazza.derby.LogWriter;
import org.jeffpiazza.derby.Message;

// TODO: "R" responds with "Ready", which maybe means this timer CAN be identified.
// More importantly, "V" responds with "Derby Magic v3.00" or some such.
// http://www.derbymagic.com/files/Timer.pdf
// http://www.derbymagic.com/files/GPRM.pdf
public class DerbyMagicDevice extends TimerDeviceCommon
    implements RemoteStartInterface {
  private TimerResult result = null;
  private long timeOfFirstResult = 0;

  public DerbyMagicDevice(SerialPortWrapper portWrapper) {
    // No GateWatcher, because there's no way to poll this timer
    super(portWrapper, null, /* gate is knowable */ false);

    // Once started, we expect a race result within 10 seconds; we allow an
    // extra second before considering the results overdue.
    rsm.setMaxRunningTimeLimit(11000);
  }

  public static String toHumanString() {
    return "Derby Magic timer";
  }

  private static final int MAX_LANES = 8;

  private static final String READ_VERSION = "V";
  private static final String TIMER_HAS_STARTED = "B";

  private static final String TIMER_RESET = "R";
  private static final String FORCE_DATA_SEND = "F";

  private static final String TRIGGER_START_SOLENOID = "S";

  @Override
  public boolean canBeIdentified() {
    return true;
  }

  public boolean probe() throws SerialPortException {
    for (int baudrate : new int[]{9600, 19200}) {
      if (!portWrapper.setPortParams(baudrate,
                                     SerialPort.DATABITS_8,
                                     SerialPort.STOPBITS_1,
                                     SerialPort.PARITY_NONE)) {
        continue;
      }

      portWrapper.write(READ_VERSION);

      long deadline = System.currentTimeMillis() + 1000;
      String s;
      while ((s = portWrapper.next(deadline)) != null) {
        if (s.indexOf("Derby Magic") >= 0) {
          timerIdentifier = s;
          portWrapper.writeAndDrainResponse(TIMER_RESET, 1, 200);
          setUp();
          has_ever_spoken = true;
          return true;
        }
      }
    }
    return false;
  }

  protected boolean probeAtSpeed(int baudrate) throws SerialPortException {
    if (!portWrapper.setPortParams(baudrate,
                                   SerialPort.DATABITS_8,
                                   SerialPort.STOPBITS_1,
                                   SerialPort.PARITY_NONE)) {
      return false;
    }

    portWrapper.writeAndDrainResponse(TIMER_RESET, 1, 200);
    timerIdentifier = portWrapper.writeAndWaitForResponse(READ_VERSION);

    setUp();
    return true;
  }

  // Timer sends individual lane results as each car crosses the finish line.
  // Each result is <lane>=<time><place>, where <lane> is the lane number,
  // <time> is time (0.0000-9.9999), and <place> is a punctuation charactter,
  // !=1st, "=2nd, #=3rd, $=4th, %=5th, &=6th, *=7th, @=8th
  // (even though @ doesn't follow in ASCII order)
  private static final Pattern singleLanePattern = Pattern.compile(
      "([1-8])=(\\d\\.\\d+)([!-/:-@]) *");

  protected void setUp() {
    portWrapper.registerDetector(new SerialPortWrapper.Detector() {
      public String apply(String line) throws SerialPortException {
        if (line.equals(TIMER_HAS_STARTED)) {
          LogWriter.serial("Detected gate opening");
          // This will be an unexpected state change, if it ever happens
          onGateStateChange(false);
          has_ever_spoken = true;
          return "";
        }
        return line;
      }
    });
    portWrapper.registerEarlyDetector(new SerialPortWrapper.Detector() {
{'fraction_non_alphanumeric': 0.09968520461699895, 'fraction_numerical': 0.01416579223504722, 'mean_word_length': 2.6326822296331587, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1562774', 'n_tokens_mistral': 2332, 'n_tokens_neox': 2170, 'n_words': 597}