diff --git a/README.md b/README.md
index d60b505..0141d5a 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,15 @@ Run one of the library examples in the Arduino IDE by going to `File -> Examples
## Supported Devices
-* [Logitech Three Pedal Peripheral (Gas, Brake, Clutch)](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_pedals.html)
+### Generic Devices
+* [Two pedal peripherals (gas + brake)](https://dmadison.github.io/Sim-Racing-Arduino/docs/class_sim_racing_1_1_two_pedals.html)
+* [Three pedal peripherals (gas, brake, clutch)](https://dmadison.github.io/Sim-Racing-Arduino/docs/class_sim_racing_1_1_three_pedals.html)
+* [Analog shifters](https://dmadison.github.io/Sim-Racing-Arduino/docs/class_sim_racing_1_1_analog_shifter.html)
+* [Analog handbrakes](https://dmadison.github.io/Sim-Racing-Arduino/docs/class_sim_racing_1_1_handbrake.html)
+
+### Commercial Devices
* [Logitech Two Pedal Peripheral (Gas, Brake)](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_pedals.html)
+* [Logitech Three Pedal Peripheral (Gas, Brake, Clutch)](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_pedals.html)
* [Logitech Driving Force Shifter](http://dmadison.github.io/Sim-Racing-Arduino/docs/logitech_shifter.html)
## License
diff --git a/docs/pages/supported_devices.md b/docs/pages/supported_devices.md
index 8afa60c..9a40dee 100644
--- a/docs/pages/supported_devices.md
+++ b/docs/pages/supported_devices.md
@@ -1,19 +1,13 @@
# Supported Devices
-### Commercial Devices:
-
-- @subpage logitech_pedals
-- @subpage logitech_shifter
-
### Generic Devices
-#### Pedals
+* Two pedal peripherals (gas + brake) using `SimRacing::TwoPedals`
+* Three pedal peripherals (gas, brake, clutch) using `SimRacing::ThreePedals`
+* Analog shifters using `SimRacing::AnalogShifter`
+* Analog handbrakes using `SimRacing::Handbrake`
-The library supports generic pedal devices that connect via the microcontroller's analog to digital converter (ADC).
+### Commercial Devices
-* Two pedal setups (gas + brake) use the `SimRacing::TwoPedals` class.
-* Three pedal setups (gas, brake, clutch) use the `SimRacing::ThreePedals` class.
-
-#### Shifters
-
-The library supports generic shifting devices that record gear position using a pair of potentiometers. These are supported as part of the `SimRacing::AnalogShifter` class.
+- @subpage logitech_pedals
+- @subpage logitech_shifter
diff --git a/examples/Handbrake/HandbrakeJoystick/HandbrakeJoystick.ino b/examples/Handbrake/HandbrakeJoystick/HandbrakeJoystick.ino
new file mode 100644
index 0000000..a49ec4d
--- /dev/null
+++ b/examples/Handbrake/HandbrakeJoystick/HandbrakeJoystick.ino
@@ -0,0 +1,67 @@
+/*
+ * Project Sim Racing Library for Arduino
+ * @author David Madison
+ * @link github.com/dmadison/Sim-Racing-Arduino
+ * @license LGPLv3 - Copyright (c) 2022 David Madison
+ *
+ * This file is part of the Sim Racing Library for Arduino.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+ /**
+ * @brief Emulates the handbrake as a joystick over USB.
+ * @example HandbrakeJoystick.ino
+ */
+
+// This example requires the Arduino Joystick Library
+// Download Here: https://github.com/MHeironimus/ArduinoJoystickLibrary
+
+#include
+#include
+
+const int Pin_Handbrake = A2;
+
+SimRacing::Handbrake handbrake(Pin_Handbrake);
+
+Joystick_ Joystick(
+ JOYSTICK_DEFAULT_REPORT_ID, // default report (no additional pages)
+ JOYSTICK_TYPE_JOYSTICK, // so that this shows up in Windows joystick manager
+ 0, // number of buttons (none)
+ 0, // number of hat switches (none)
+ false, false, // no X and Y axes
+ true, // include Z axis for the handbrake
+ false, false, false, false, false, false, false, false); // no other axes
+
+const int ADC_Max = 1023; // max value of the analog inputs, 10-bit on AVR boards
+
+
+void setup() {
+ handbrake.begin(); // initialize handbrake pins
+
+ // if you have one, your calibration line should go here
+
+ Joystick.begin(false); // 'false' to disable auto-send
+
+ Joystick.setZAxisRange(0, ADC_Max);
+}
+
+void loop() {
+ handbrake.update();
+
+ int pos = handbrake.getPosition(0, ADC_Max);
+ Joystick.setZAxis(pos);
+
+ Joystick.sendState();
+}
diff --git a/examples/Handbrake/HandbrakePrint/HandbrakePrint.ino b/examples/Handbrake/HandbrakePrint/HandbrakePrint.ino
new file mode 100644
index 0000000..870727c
--- /dev/null
+++ b/examples/Handbrake/HandbrakePrint/HandbrakePrint.ino
@@ -0,0 +1,63 @@
+/*
+ * Project Sim Racing Library for Arduino
+ * @author David Madison
+ * @link github.com/dmadison/Sim-Racing-Arduino
+ * @license LGPLv3 - Copyright (c) 2022 David Madison
+ *
+ * This file is part of the Sim Racing Library for Arduino.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+ /**
+ * @brief Prints handbrake position percentage over Serial.
+ * @example HandbrakePrint.ino
+ */
+
+#include
+
+const int Pin_Handbrake = A2;
+
+SimRacing::Handbrake handbrake(Pin_Handbrake);
+
+
+void setup() {
+ handbrake.begin(); // initialize handbrake pins
+
+ // if you have one, your calibration line should go here
+
+ Serial.begin(115200);
+ while (!Serial); // wait for connection to open
+
+ Serial.println("Starting...");
+}
+
+void loop() {
+ // send some serial data to run conversational calibration
+ if (Serial.read() != -1) {
+ handbrake.serialCalibration();
+ delay(2000);
+ }
+
+ handbrake.update();
+
+ Serial.print("Handbrake: ");
+
+ int pos = handbrake.getPosition();
+ Serial.print(pos);
+ Serial.print("%");
+ Serial.println();
+
+ delay(100);
+}
diff --git a/keywords.txt b/keywords.txt
index 932f403..f80890b 100644
--- a/keywords.txt
+++ b/keywords.txt
@@ -35,6 +35,9 @@ AnalogShifter KEYWORD1
LogitechShifter KEYWORD1
+# Handbrake Classes
+Handbrake KEYWORD1
+
#######################################
# Peripheral Class Methods and Functions (KEYWORD2)
#######################################
@@ -65,6 +68,9 @@ setCalibration KEYWORD2
# Pedal Methods and Functions (KEYWORD2)
#######################################
+getPosition KEYWORD2
+getPositionRaw KEYWORD2
+
hasPedal KEYWORD2
getNumPedals KEYWORD2
@@ -84,11 +90,24 @@ gearChanged KEYWORD2
getGearMin KEYWORD2
getGearMax KEYWORD2
+getPosition KEYWORD2
+getPositionRaw KEYWORD2
+
getReverseButton KEYWORD2
setCalibration KEYWORD2
serialCalibration KEYWORD2
+#######################################
+# Handbrake Methods and Functions (KEYWORD2)
+#######################################
+
+getPosition KEYWORD2
+getPositionRaw KEYWORD2
+
+setCalibration KEYWORD2
+serialCalibration KEYWORD2
+
#######################################
# Instances (KEYWORD2)
#######################################
diff --git a/library.properties b/library.properties
index 76b22a9..3164f7c 100644
--- a/library.properties
+++ b/library.properties
@@ -2,7 +2,7 @@ name=Sim Racing Library
version=1.0.0
author=David Madison
maintainer=David Madison
-sentence=Interface with sim racing peripherals including pedals and gear shifters.
+sentence=Interface with sim racing peripherals including pedals, gear shifters, and handbrakes.
paragraph=Works out of the box with the Logitech pedals and Logitech Driving Force gear shifter. Supports easy USB joystick output.
category=Signal Input/Output
url=https://github.com/dmadison/Sim-Racing-Arduino
diff --git a/src/SimRacing.cpp b/src/SimRacing.cpp
index c05134d..2994aa4 100644
--- a/src/SimRacing.cpp
+++ b/src/SimRacing.cpp
@@ -902,4 +902,106 @@ LogitechShifter::LogitechShifter(uint8_t pinX, uint8_t pinY, uint8_t pinRev, uin
this->setCalibration({ 490, 440 }, { 253, 799 }, { 262, 86 }, { 460, 826 }, { 470, 76 }, { 664, 841 }, { 677, 77 }, 0.70, 0.50, 0.70);
}
+//#########################################################
+// Handbrake #
+//#########################################################
+
+Handbrake::Handbrake(uint8_t pinAx, uint8_t detectPin)
+ : analogAxis(pinAx), detector(detectPin)
+{}
+
+void Handbrake::begin() {
+ update(); // set initial handbrake position
+}
+
+bool Handbrake::update() {
+ bool changed = false;
+
+ detector.poll();
+ if (detector.getState() == DeviceConnection::Connected) {
+ changed = analogAxis.read();
+ }
+ else if (detector.getState() == DeviceConnection::Unplug) {
+ analogAxis.setPosition(analogAxis.getMin());
+ }
+
+ return changed;
+}
+
+long Handbrake::getPosition(long rMin, long rMax) const {
+ return analogAxis.getPosition(rMin, rMax);
+}
+
+int Handbrake::getPositionRaw() const {
+ return analogAxis.getPositionRaw();
+}
+
+void Handbrake::setCalibration(AnalogInput::Calibration newCal) {
+ analogAxis.setCalibration(newCal);
+ analogAxis.setPosition(analogAxis.getMin()); // reset to min
+}
+
+void Handbrake::serialCalibration(Stream& iface) {
+ if (isConnected() == false) {
+ iface.print(F("Error! Cannot perform calibration, "));
+ iface.print(F("handbrake"));
+ iface.println(F(" is not connected."));
+ return;
+ }
+
+ const char* separator = "------------------------------------";
+
+ iface.println();
+ iface.println(F("Sim Racing Library Handbrake Calibration"));
+ iface.println(separator);
+ iface.println();
+
+ AnalogInput::Calibration newCal;
+
+ // read minimum
+ iface.println(F("Keep your hand off of the handbrake to record its resting position"));
+ iface.println(F("Send any character to continue."));
+ waitClient(iface);
+
+ analogAxis.read();
+ newCal.min = analogAxis.getPositionRaw();
+ iface.println();
+
+ // read maximum
+ iface.println(F("Now pull on the handbrake and hold it at the end of its range"));
+ iface.println(F("Send any character to continue."));
+ waitClient(iface);
+
+ analogAxis.read();
+ newCal.max = analogAxis.getPositionRaw();
+ iface.println();
+
+ // set new calibration
+ this->setCalibration(newCal);
+
+ // print finished calibration
+ iface.println(F("Here is your calibration:"));
+ iface.println(separator);
+ iface.println();
+
+ iface.print(F("handbrake.setCalibration("));
+ iface.print('{');
+ iface.print(newCal.min);
+ iface.print(F(", "));
+ iface.print(newCal.max);
+ iface.print("});");
+ iface.println();
+
+ iface.println();
+ iface.println(separator);
+ iface.println();
+
+ iface.print(F("Paste this line into the setup() function. The "));
+ iface.print(F("handbrake"));
+ iface.print(F(" will be calibrated with these values on startup."));
+ iface.println(F("\nCalibration complete! :)\n\n"));
+
+ flushClient(iface);
+}
+
}; // end SimRacing namespace
diff --git a/src/SimRacing.h b/src/SimRacing.h
index b67a647..80943ea 100644
--- a/src/SimRacing.h
+++ b/src/SimRacing.h
@@ -662,6 +662,67 @@ namespace SimRacing {
/// @} Shifters
+ /**
+ * @brief Interface with analog handbrakes that use hall effect sensors
+ */
+ class Handbrake : public Peripheral {
+ public:
+ /**
+ * Class constructor
+ *
+ * @param pinAx analog pin number for the handbrake axis
+ * @param detectPin the digital pin for device detection (high is detected)
+ */
+ Handbrake(uint8_t pinAx, uint8_t detectPin = NOT_A_PIN);
+
+ /**
+ * Initializes the pin for reading from the handbrake.
+ */
+ virtual void begin();
+
+ /**
+ * Polls the handbrake to update its position.
+ *
+ * @return 'true' if the gear has changed, 'false' otherwise
+ */
+ virtual bool update();
+
+ /**
+ * Retrieves the buffered position for the handbrake axis, rescaled to a
+ * nominal range using the calibration values.
+ *
+ * By default this is rescaled to an integer percentage (0 - 100)
+ *
+ * @param rMin the minimum output value
+ * @param rMax the maximum output value
+ *
+ * @return the handbrake position, buffered and rescaled
+ */
+ long getPosition(long rMin = 0, long rMax = 100) const;
+
+ /**
+ * Retrieves the buffered position for the handbrake, ignoring the
+ * calibration data.
+ *
+ * @return the handbrake position, buffered
+ */
+ int getPositionRaw() const;
+
+ /// @copydoc AnalogInput::setCalibration()
+ void setCalibration(AnalogInput::Calibration newCal);
+
+ /// @copydoc AnalogShifter::serialCalibration()
+ void serialCalibration(Stream& iface = Serial);
+
+ /** @copydoc Peripheral::isConnected() */
+ bool isConnected() const { return detector.isConnected(); }
+
+ private:
+ AnalogInput analogAxis; ///< axis data for the handbrake's position
+ DeviceConnection detector; ///< detector instance for checking if the handbrake is connected
+ };
+
+
/**
* @brief Interface with the Logitech pedals (Gas, Brake, and Clutch)
* @ingroup Pedals