ViciousCycle: Cadence sensors in games
There are four linked posts in this set:
- The actual setup of the bike and gaming equipment
- Problems with Cadence Sensors
- Making a much more effective speed/cadence sensor
- Using the sensor as a input for video games (this post)
So, I’ve posted before about setting up my exercise bike in front of a PlayStation. It does two things:
- Makes my exercise a bit more fun.
- stops me gaming until 4am like a teenager and thus wrecking my job, relationships, and entire life.
For some games (mostly on the PS4) I use an alarm on my watch that beeps if my heartbeat drops below a target and I’ve been working my way through the best games of, like, 2007.
However, I also have some code that converts the input from my custom speedometer into keypresses, which I use to play retro computer games.
History
Bluetooth sensor
Here’s the proof of concept:
You can see the basic code here(note: that’s a link to the commit rather than the up-to-date state) and the particular code shown in the video is this one:
from bleak import BleakClient
import asyncio
import viciouscycle
import bleak
import keyboard
def go_forward(sender, data):
print("Here")
cadence=viciouscycle.decode_and_handle_measurement(sender,data)
if cadence is None:
print("None")
return
if cadence > 60:
if cadence <150:
print("Doing great!")
keyboard.hold_key('up',2)
else:
print("No, do better")
async def play():
async with BleakClient(viciouscycle.sensor_address) as client:
print("Start to play")
await client.start_notify(viciouscycle.CSC_MEASUREMENT_UUID, go_forward)
await asyncio.sleep(60)
await client.stop_notify(viciouscycle.CSC_MEASUREMENT_UUID)
print("Stopped notifications")
if __name__ == "__main__":
print("Entering Warm up")
print("Seeking Sensor")
try:
asyncio.run(play())
except bleak.exc.BleakDeviceNotFoundError:
print("Device was NOT found")
Version 2
Since making the proof of concept I rebuilt the overall setup so that it will be a lot easier to do things like ‘fit the laptop on the desk’ and so on.
I also bought a Retrode.
I created a test system using my Retrode and Road Rash 2 (1992) (I played this game as a child, still have a copy I can put into the Retrode, and know that it’s played by having the accelerator down for the whole race, which is fairly easy to do with my script). I’m using RetroArch to actually play via the Retrode⁰
It was successful in the sense that I am much more tired by the workout that I would have been with my other gaming setup. I used some extremely simple logic (If cadence is above 80, then hold down the accelerate button) and relived my youth a little.
While doing it, I rebuild the code in general, and investigated what might be possible with the device on other games (It won’t be needed for a while, Road Rash is wearing me out at the moment). Presumably the setup of ‘hold down the accelerate button’ will work for similar retro games like Mario Kart.
I wrote up some notes on the sensor itself on this post. It sends one data pulse every 0.76 seconds or so (and quite a lot get lost, so the average time between received packets about 0.9 seconds) I get the impression that I get more regular updates the higher the cadence, but I don’t have particularly hard data on that and it certainly doesn’t get below 0.7 seconds or so.
That eliminates the ‘direct control’ option immediately. 0.7 seconds plus the sensor lag (it will take a while before it can tell I’ve sped up) is far to long for any sort of gaming response. There are other problems as well but I talk though those in the other post.
The bottom line is that these cadence sensors are good for their central use case (measuring cadence for cyclists over reasonably long periods) but far from great for my use-case (super-fast reaction times in video gaming).
Sidenote
In the 15 years or so since I last properly looked at retro games emulators they have been very much enhanced. Here’s a before and after of me finding the controls for graphic enhancement:
Version 3
I built a much more accurate speedometer and now I have vastly more sophisticated options
This is the code I now use to play Road Rash 2
void road_rash(int cadence){
if (cadence > 65){ //then acellerate
static unsigned long lastPress = 0;
Keyboard.press('s');
Serial.print("gas+ ");
if (cadence > 65 && millis() - lastPress > 2000) {
Keyboard.release('s');
//delay(50); // tap duration
Keyboard.press('s');
lastPress = millis();
}
} else{
Serial.print("gas-");
Keyboard.release('s');
}
if (cadence < 20){
Keyboard.press('a');
Serial.print("break+");
} else{
Keyboard.release('a');
Serial.print("break-");
}
}
…and it is a vast improvement in terms of responsiveness and how much I enjoy the game. I get tired quite quickly now compared to the more gentle cycling I do for other games.
Next Steps
- Reimplement Road Rash with the new Speedometer.
- Test Mario Kart 64.
Daydream Goals
- More ‘aware’ controls that recognise the game state and adapt automatically.
- The python code directly controlling the PS4 via emulating the controller.
- Porting the code to a Raspberry Pi (or whatever is current these days) so I don’t have to lug around my laptop
⁰ OpenEmu is more popular, but as detailed here really doesn’t work well for accepting inputs from python.