Raw Thrills I/O: Add support for the physical coin counter inside the coin door

This commit is contained in:
x0rbl
2019-10-04 17:44:32 -07:00
parent b4806b0038
commit 77e2776dad
2 changed files with 175 additions and 60 deletions
+161 -59
View File
@@ -9,6 +9,24 @@
#include "RageLog.h"
#include "RageInputDevice.h"
// The coin counter won't accept an increment command immediately after acking
// an older increment command. This delay is the minimum amount of time to wait
// between receiving an ack for an increment command and sending a new
// increment command.
const static float COUNTER_MINIMUM_SEND_DELAY = 0.15;
// The longest amount of time to wait for an ack to the increment command
// before moving on with the coin counter increment sequence.
const static float COUNTER_MAXIMUM_RECV_DELAY = 3.0;
// If RTIO does not initialize within this amount of time, exit the RTIO input
// loop to free resources.
const static float RTIO_INIT_TIME_MAX = 10.0;
// If there are this many failures, something has most likely gone very wrong,
// so just exit the RTIO input loop.
const static int RTIO_MAX_READ_FAILURES = 50;
REGISTER_INPUT_HANDLER_CLASS2(Rtio, Win32_RTIO);
InputHandler_Win32_RTIO::InputHandler_Win32_RTIO()
@@ -77,42 +95,42 @@ RString InputHandler_Win32_RTIO::GetDeviceSpecificInputString(const DeviceInput
bool InputHandler_Win32_RTIO::Initialize() {
if (!rtio_.Connect()) {
LOG->Trace("RTIO: Initialize: Cannot connect to COM1");
LOG->Warn("RTIO: Initialize: Cannot connect to COM1");
return false;
}
// The following messages are not required to receive game/operator inputs,
// but they replicate the initialization sequence in DDR. We'll use the
// response from the version message to determine that the board is good.
if (!rtio_.WriteMsg("C0000")) {
LOG->Trace("RTIO: Initialize: Cannot write init message");
LOG->Warn("RTIO: Initialize: Cannot write init message");
return false;
}
if (!rtio_.WriteMsg("v")) {
LOG->Trace("RTIO: Initialize: Cannot write version message");
LOG->Warn("RTIO: Initialize: Cannot write version message");
return false;
}
if (!rtio_.WriteMsg("D0020008")) {
LOG->Trace("RTIO: Initialize: Cannot send dongle message (1/6)");
LOG->Warn("RTIO: Initialize: Cannot send dongle message (1/6)");
return false;
}
if (!rtio_.WriteMsg("D0010008")) {
LOG->Trace("RTIO: Initialize: Cannot send dongle message (2/6)");
LOG->Warn("RTIO: Initialize: Cannot send dongle message (2/6)");
return false;
}
if (!rtio_.WriteMsg("D0000020")) {
LOG->Trace("RTIO: Initialize: Cannot send dongle message (3/6)");
LOG->Warn("RTIO: Initialize: Cannot send dongle message (3/6)");
return false;
}
if (!rtio_.WriteMsg("D1020008")) {
LOG->Trace("RTIO: Initialize: Cannot send dongle message (4/6)");
LOG->Warn("RTIO: Initialize: Cannot send dongle message (4/6)");
return false;
}
if (!rtio_.WriteMsg("D1010008")) {
LOG->Trace("RTIO: Initialize: Cannot send dongle message (5/6)");
LOG->Warn("RTIO: Initialize: Cannot send dongle message (5/6)");
return false;
}
if (!rtio_.WriteMsg("D1000020")) {
LOG->Trace("RTIO: Initialize: Cannot send dongle message (6/6)");
LOG->Warn("RTIO: Initialize: Cannot send dongle message (6/6)");
return false;
}
return true;
@@ -128,13 +146,13 @@ void InputHandler_Win32_RTIO::InputThread()
{
RageTimer start_time;
std::vector<std::string> msgs;
int failures = 0;
int read_failures = 0;
while (!shutdown_) {
if (!rtio_.ReadMsgs(&msgs)) {
failures++;
LOG->Warn("RTIO: HandlerLoop: Failed to read (%d)", failures);
if (failures == 10) {
read_failures++;
LOG->Warn("RTIO: HandlerLoop: Failed to read (%d failures)", read_failures);
if (read_failures == RTIO_MAX_READ_FAILURES) {
return;
}
continue;
@@ -142,7 +160,7 @@ void InputHandler_Win32_RTIO::InputThread()
RageTimer now;
if (!initialized_ && now - start_time > 10.0) {
if (!initialized_ && now - start_time > RTIO_INIT_TIME_MAX) {
LOG->Warn("RTIO: Device failed to initialize; exiting input loop");
return;
}
@@ -170,12 +188,53 @@ void InputHandler_Win32_RTIO::InputThread()
HandleOperatorInput(msg, now);
continue;
}
if (msg[0] == 'h' && msg.length() == 3) {
HandleCounterAck(msg);
continue;
}
LOG->Warn("RTIO: Unrecognized response: %s", msg.c_str());
}
if (counter_cycles_pending_ > 0) {
switch (counter_state_) {
case COUNTER_STATE_SEND_1:
if (last_counter_recv_.Ago() > COUNTER_MINIMUM_SEND_DELAY) {
if (!rtio_.WriteMsg("H10")) {
LOG->Warn("RTIO: Could not send coin counter increment (1/2)");
}
counter_state_ = COUNTER_STATE_RECV_1;
last_counter_send_.Touch();
}
break;
case COUNTER_STATE_SEND_2:
if (last_counter_recv_.Ago() > COUNTER_MINIMUM_SEND_DELAY) {
if (!rtio_.WriteMsg("H00")) {
LOG->Warn("RTIO: Could not send coin counter increment (2/2)");
}
counter_state_ = COUNTER_STATE_RECV_2;
last_counter_send_.Touch();
}
break;
}
if (last_counter_send_.Ago() > COUNTER_MAXIMUM_RECV_DELAY) {
switch (counter_state_) {
case COUNTER_STATE_RECV_1:
LOG->Warn("RTIO: Never received coin counter increment acknowledgement (1/2)");
counter_state_ = COUNTER_STATE_SEND_2;
break;
case COUNTER_STATE_RECV_2:
LOG->Warn("RTIO: Never received coin counter increment acknowledgement (2/2)");
counter_state_ = COUNTER_STATE_SEND_1;
counter_cycles_pending_--;
break;
}
}
}
}
}
int HexToChar(char ch)
int HexCharToInt(char ch)
{
if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
@@ -186,12 +245,12 @@ void InputHandler_Win32_RTIO::HandleGameInput(const std::string &msg, const Rage
{
InputDevice id = InputDevice(DEVICE_JOY1);
int pad1 = HexToChar(msg[1]);
int pad2 = HexToChar(msg[2]);
int menu1 = HexToChar(msg[3]);
int menu2 = HexToChar(msg[4]);
int start1 = HexToChar(msg[5]);
int start2 = HexToChar(msg[6]);
int pad1 = HexCharToInt(msg[1]);
int pad2 = HexCharToInt(msg[2]);
int menu1 = HexCharToInt(msg[3]);
int menu2 = HexCharToInt(msg[4]);
int start1 = HexCharToInt(msg[5]);
int start2 = HexCharToInt(msg[6]);
GAME_INPUT input_new;
input_new.P1_PadUp = (pad1 >> 3) & 1;
@@ -214,58 +273,58 @@ void InputHandler_Win32_RTIO::HandleGameInput(const std::string &msg, const Rage
input_new.P2_MenuStart = start2 & 1;
if (input_new.P1_PadLeft != last_game_input_.P1_PadLeft) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_1, input_new.P1_PadLeft, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_1, (float)input_new.P1_PadLeft, now));
}
if (input_new.P1_PadDown != last_game_input_.P1_PadDown) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_2, input_new.P1_PadDown, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_2, (float)input_new.P1_PadDown, now));
}
if (input_new.P1_PadUp != last_game_input_.P1_PadUp) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_3, input_new.P1_PadUp, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_3, (float)input_new.P1_PadUp, now));
}
if (input_new.P1_PadRight != last_game_input_.P1_PadRight) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_4, input_new.P1_PadRight, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_4, (float)input_new.P1_PadRight, now));
}
if (input_new.P1_MenuLeft != last_game_input_.P1_MenuLeft) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_5, input_new.P1_MenuLeft, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_5, (float)input_new.P1_MenuLeft, now));
}
if (input_new.P1_MenuDown != last_game_input_.P1_MenuDown) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_6, input_new.P1_MenuDown, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_6, (float)input_new.P1_MenuDown, now));
}
if (input_new.P1_MenuUp != last_game_input_.P1_MenuUp) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_7, input_new.P1_MenuUp, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_7, (float)input_new.P1_MenuUp, now));
}
if (input_new.P1_MenuRight != last_game_input_.P1_MenuRight) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_8, input_new.P1_MenuRight, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_8, (float)input_new.P1_MenuRight, now));
}
if (input_new.P1_MenuStart != last_game_input_.P1_MenuStart) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_9, input_new.P1_MenuStart, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_9, (float)input_new.P1_MenuStart, now));
}
if (input_new.P2_PadLeft != last_game_input_.P2_PadLeft) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_10, input_new.P2_PadLeft, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_10, (float)input_new.P2_PadLeft, now));
}
if (input_new.P2_PadDown != last_game_input_.P2_PadDown) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_11, input_new.P2_PadDown, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_11, (float)input_new.P2_PadDown, now));
}
if (input_new.P2_PadUp != last_game_input_.P2_PadUp) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_12, input_new.P2_PadUp, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_12, (float)input_new.P2_PadUp, now));
}
if (input_new.P2_PadRight != last_game_input_.P2_PadRight) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_13, input_new.P2_PadRight, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_13, (float)input_new.P2_PadRight, now));
}
if (input_new.P2_MenuLeft != last_game_input_.P2_MenuLeft) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_14, input_new.P2_MenuLeft, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_14, (float)input_new.P2_MenuLeft, now));
}
if (input_new.P2_MenuDown != last_game_input_.P2_MenuDown) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_15, input_new.P2_MenuDown, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_15, (float)input_new.P2_MenuDown, now));
}
if (input_new.P2_MenuUp != last_game_input_.P2_MenuUp) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_16, input_new.P2_MenuUp, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_16, (float)input_new.P2_MenuUp, now));
}
if (input_new.P2_MenuRight != last_game_input_.P2_MenuRight) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_17, input_new.P2_MenuRight, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_17, (float)input_new.P2_MenuRight, now));
}
if (input_new.P2_MenuStart != last_game_input_.P2_MenuStart) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_18, input_new.P2_MenuStart, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_18, (float)input_new.P2_MenuStart, now));
}
/*
if (memcmp(&last_game_input_, &input_new, sizeof(GAME_INPUT)) != 0) {
@@ -283,12 +342,12 @@ void InputHandler_Win32_RTIO::HandleOperatorInput(const std::string &msg, const
{
InputDevice id = InputDevice(DEVICE_JOY1);
int coin1 = HexToChar(msg[3]);
int coin2 = HexToChar(msg[4]);
int vol_up = HexToChar(msg[5]);
int vol_dn = HexToChar(msg[6]);
int test = HexToChar(msg[7]);
int select = HexToChar(msg[8]);
int coin1 = HexCharToInt(msg[3]);
int coin2 = HexCharToInt(msg[4]);
int vol_up = HexCharToInt(msg[5]);
int vol_dn = HexCharToInt(msg[6]);
int test = HexCharToInt(msg[7]);
int select = HexCharToInt(msg[8]);
OPERATOR_INPUT input_new;
input_new.P1_InsertCoin = coin1 & 1;
@@ -299,22 +358,28 @@ void InputHandler_Win32_RTIO::HandleOperatorInput(const std::string &msg, const
input_new.SelectSwitch = select & 1;
if (input_new.TestSwitch != last_operator_input_.TestSwitch) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_19, input_new.TestSwitch, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_19, (float)input_new.TestSwitch, now));
}
if (input_new.SelectSwitch != last_operator_input_.SelectSwitch) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_20, input_new.SelectSwitch, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_20, (float)input_new.SelectSwitch, now));
}
if (input_new.P1_InsertCoin != last_operator_input_.P1_InsertCoin) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_21, input_new.P1_InsertCoin, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_21, (float)input_new.P1_InsertCoin, now));
if (input_new.P1_InsertCoin) {
counter_cycles_pending_++;
}
}
if (input_new.P2_InsertCoin != last_operator_input_.P2_InsertCoin) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_22, input_new.P2_InsertCoin, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_22, (float)input_new.P2_InsertCoin, now));
if (input_new.P2_InsertCoin) {
counter_cycles_pending_++;
}
}
if (input_new.VolumeDown != last_operator_input_.VolumeDown) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_23, input_new.VolumeDown, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_23, (float)input_new.VolumeDown, now));
}
if (input_new.VolumeUp != last_operator_input_.VolumeUp) {
ButtonPressed(DeviceInput(id, JOY_BUTTON_24, input_new.VolumeUp, now));
ButtonPressed(DeviceInput(id, JOY_BUTTON_24, (float)input_new.VolumeUp, now));
}
/*
if (memcmp(&last_operator_input_, &input_new, sizeof(OPERATOR_INPUT)) != 0) {
@@ -326,6 +391,27 @@ void InputHandler_Win32_RTIO::HandleOperatorInput(const std::string &msg, const
}
void InputHandler_Win32_RTIO::HandleCounterAck(const std::string &msg)
{
int ack_num = HexCharToInt(msg[1]);
last_counter_recv_.Touch();
if (ack_num == 1 && counter_state_ == COUNTER_STATE_RECV_1) {
counter_state_ = COUNTER_STATE_SEND_2;
return;
}
if (ack_num == 0 && counter_state_ == COUNTER_STATE_RECV_2) {
counter_state_ = COUNTER_STATE_SEND_1;
counter_cycles_pending_ = max(counter_cycles_pending_ - 1, 0);
return;
}
LOG->Warn("RTIO: Received stray coin counter increment acknowledgement: state=%d, msg=%s", counter_state_, msg.c_str());
}
RtioDevice::~RtioDevice()
{
@@ -552,6 +638,8 @@ void ResetOverlapped(OVERLAPPED *overlapped)
}
// Reads up to buffer_size bytes from the serial device. Returns as fast as
// possible by only reading bytes that are already available in the queue.
int SerialDevice::Read(char *buffer, int buffer_size)
{
DWORD errors;
@@ -570,19 +658,22 @@ int SerialDevice::Read(char *buffer, int buffer_size)
ResetOverlapped(&read_overlapped_);
int copy_size = min((int)stat.cbInQue, read_buffer_size_);
copy_size = min(copy_size, buffer_size);
DWORD read_size = min(stat.cbInQue, (DWORD)read_buffer_size_);
read_size = min(read_size, (DWORD)buffer_size);
DWORD bytes_transferred;
if (!ReadFile(com_handle_, buffer, (DWORD)copy_size, &bytes_transferred, &read_overlapped_)) {
if (!ReadFile(com_handle_, buffer, read_size, &bytes_transferred, &read_overlapped_)) {
DWORD err = GetLastError();
if (err != ERROR_IO_PENDING) {
LOG->Warn("RTIO: SerialDevice: ReadFile failed: %n", err);
LOG->Warn("RTIO: SerialDevice: ReadFile failed: %d", err);
return -1;
}
// Wait for the read operation to finish
GetOverlappedResult(com_handle_, &read_overlapped_, &bytes_transferred, true);
if (!GetOverlappedResult(com_handle_, &read_overlapped_, &bytes_transferred, true)) {
LOG->Warn("RTIO: SerialDevice: GetOverlappedResult failed on read: %d\n", GetLastError());
return -1;
}
}
return bytes_transferred;
@@ -591,12 +682,23 @@ int SerialDevice::Read(char *buffer, int buffer_size)
int SerialDevice::Write(const char *buffer, int buffer_size)
{
DWORD bytes_transferred;
DWORD write_size = min(buffer_size, write_buffer_size_);
DWORD write_size = min((DWORD)buffer_size, (DWORD)write_buffer_size_);
ResetOverlapped(&write_overlapped_);
WriteFile(com_handle_, buffer, (DWORD)write_size, nullptr, &write_overlapped_);
GetOverlappedResult(com_handle_, &write_overlapped_, &bytes_transferred, true);
if (!WriteFile(com_handle_, buffer, write_size, &bytes_transferred, &write_overlapped_)) {
DWORD err = GetLastError();
if (err != ERROR_IO_PENDING) {
LOG->Warn("RTIO: SerialDevice: WriteFile failed: %d", err);
return -1;
}
// Wait for the write operation to finish
if (!GetOverlappedResult(com_handle_, &write_overlapped_, &bytes_transferred, true)) {
LOG->Warn("RTIO: SerialDevice: GetOverlappedResult failed on write: %d\n", GetLastError());
return -1;
}
}
return bytes_transferred;
}
@@ -38,6 +38,13 @@ typedef struct {
int SelectSwitch;
} OPERATOR_INPUT;
enum COUNTER_STATE {
COUNTER_STATE_SEND_1 = 0, // Ready to send "H10" (first coin counter increment command)
COUNTER_STATE_RECV_1, // Ready to recv "h10" (ack for first coin counter increment command)
COUNTER_STATE_SEND_2, // Ready to send "H00" (second coin counter increment command)
COUNTER_STATE_RECV_2, // Ready to recv "h00" (ack for second coin counter increment command)
};
class SerialDevice {
public:
SerialDevice(int read_buffer_size = 0x1000, int write_buffer_size = 0x1000) : read_buffer_size_(read_buffer_size), write_buffer_size_(write_buffer_size) {}
@@ -65,6 +72,7 @@ public:
bool WriteMsg(const std::string &msg);
private:
int ParseMsg(char *buffer, int buffer_size);
SerialDevice serial_;
char read_buffer_[0x2000];
int read_offset_ = 0;
@@ -84,13 +92,18 @@ private:
void InputThread();
void HandleGameInput(const std::string &msg, const RageTimer &now);
void HandleOperatorInput(const std::string &msg, const RageTimer &now);
void HandleCounterAck(const std::string &msg);
RtioDevice rtio_;
RageThread input_thread_;
bool initialized_ = false;
bool shutdown_ = false;
GAME_INPUT last_game_input_ = {};
OPERATOR_INPUT last_operator_input_ = {};
bool initialized_ = false;
int counter_cycles_pending_ = 0;
COUNTER_STATE counter_state_ = COUNTER_STATE_SEND_1;
RageTimer last_counter_send_;
RageTimer last_counter_recv_;
};
#endif