66 bool rc =
driver->Connect();
74 if (loadCalibrationData())
77 LOG_DEBUG(
"No saved calibration found for this joystick.");
80 LOG_INFO(
"Error: cannot find Joystick device.");
88 return driver->Disconnect();
102 int nAxis =
driver->getNumOfAxes();
103 int nJoysticks =
driver->getNumOfJoysticks();
104 int nButtons =
driver->getNumrOfButtons();
110 for (
int i = 0; i < nJoysticks; i++)
112 snprintf(propName,
MAXINDINAME,
"JOYSTICK_%d", i + 1);
118 snprintf(propLabel,
MAXINDILABEL,
"Joystick %d", i + 1);
122 jp[0].fill(
"JOYSTICK_MAGNITUDE",
"Magnitude",
"%g", 0.0, 1.0, 0, 0);
123 jp[1].fill(
"JOYSTICK_ANGLE",
"Angle",
"%g", 0.0, 360.0, 0, 0);
129 for (
int i = 0; i < nAxis; i++)
133 const char *axLabel =
driver->getAxisLabel(i);
134 if (axLabel && axLabel[0] !=
'\0' && strcmp(axLabel,
"Unknown") != 0)
138 AxisNP[i].
fill(propName, propLabel,
"%.f", -32767.0, 32767.0, 0, 0);
144 for (
int i = 0; i < nAxis; i++)
147 const char *axLabel =
driver->getAxisLabel(i);
148 if (axLabel && axLabel[0] !=
'\0' && strcmp(axLabel,
"Unknown") != 0)
163 for (
int i = 0; i < nButtons; i++)
165 snprintf(propName,
MAXINDINAME,
"BUTTON_%d", i + 1);
174 for (
int i = 0; i < nAxis; i++)
176 snprintf(propName,
MAXINDINAME,
"AXIS_%d_CENTER", i + 1);
177 snprintf(propLabel,
MAXINDILABEL,
"Axis %d Center", i + 1);
180 snprintf(propName,
MAXINDINAME,
"AXIS_%d_MIN", i + 1);
181 snprintf(propLabel,
MAXINDILABEL,
"Axis %d Min", i + 1);
184 snprintf(propName,
MAXINDINAME,
"AXIS_%d_MAX", i + 1);
185 snprintf(propLabel,
MAXINDILABEL,
"Axis %d Max", i + 1);
192 m_axisCalCenter.assign(nAxis, 0);
193 m_axisCalMin.assign(nAxis, -32767);
194 m_axisCalMax.assign(nAxis, 32767);
199 for (
int i = 0; i < nAxis; i++)
201 const char *axLabel =
driver->getAxisLabel(i);
202 if (axLabel && axLabel[0] !=
'\0' && strcmp(axLabel,
"Unknown") != 0)
207 snprintf(propName,
MAXINDINAME,
"AXIS_%d_REVERSE", i + 1);
222 PortTP[0].
fill(
"PORT",
"Port",
"/dev/input/js0");
233 FilterNP[0].
fill(
"JOYSTICK_FILTER_FACTOR",
"Smoothing (α)",
"%.2f", 0.01, 1.0, 0.05, 0.5);
244 const char *home = getenv(
"HOME");
247 struct passwd *pw = getpwuid(getuid());
248 if (pw) home = pw->pw_dir;
251 m_calibrationFile = std::string(home) +
"/.indi/JoystickCalibrationData.xml";
253 m_calibrationFile =
"/tmp/JoystickCalibrationData.xml";
275 snprintf(buf,
sizeof(buf),
"%d",
driver->getVersion());
277 snprintf(buf,
sizeof(buf),
"%d",
driver->getNumOfJoysticks());
279 snprintf(buf,
sizeof(buf),
"%d",
driver->getNumOfAxes());
281 snprintf(buf,
sizeof(buf),
"%d",
driver->getNumrOfButtons());
303 applyReverseToDriver();
336 m_axisCalCenter.clear();
337 m_axisCalMin.clear();
338 m_axisCalMax.clear();
339 m_calibrating =
false;
380 return DefaultDevice::ISNewText(dev, name, texts, names, n);
418 applyCalibrationToDriver();
442 LOG_WARN(
"Cannot start calibration: joystick is not connected.");
449 const int nAxis =
static_cast<int>(
AxisNP.
size());
452 for (
int i = 0; i < nAxis; i++)
453 m_axisCalCenter[i] =
static_cast<int>(
AxisNP[i].getValue());
456 m_axisCalMin.assign(nAxis, INT_MAX);
457 m_axisCalMax.assign(nAxis, INT_MIN);
459 m_calibrating =
true;
461 LOG_INFO(
"Calibration started. Move every axis to its full extent in all directions, then click End Calibration.");
465 m_calibrating =
false;
467 const int nAxis =
static_cast<int>(
AxisNP.
size());
468 bool anyUpdated =
false;
470 for (
int i = 0; i < nAxis; i++)
473 if (m_axisCalMin[i] == INT_MAX) m_axisCalMin[i] = -32767;
474 if (m_axisCalMax[i] == INT_MIN) m_axisCalMax[i] = 32767;
477 if (m_axisCalMin[i] >= m_axisCalCenter[i])
478 m_axisCalMin[i] = m_axisCalCenter[i] - 1;
479 if (m_axisCalMax[i] <= m_axisCalCenter[i])
480 m_axisCalMax[i] = m_axisCalCenter[i] + 1;
492 applyCalibrationToDriver();
494 if (saveCalibrationData())
495 LOGF_INFO(
"Calibration complete and saved for \"%s\".",
driver->getName());
497 LOG_WARN(
"Calibration applied but could not be saved to file.");
514 applyReverseToDriver();
529 joystick->joystickEvent(joystick_n, mag, angle);
534 joystick->buttonEvent(button_n, value);
551 LOGF_DEBUG(
"joystickEvent[%d]: %g @ %g", joystick_n, mag, angle);
553 if (joystick_n < 0 || joystick_n >=
static_cast<int>(
JoyStickNP.size()))
558 const int axisIdx = joystick_n * 2;
559 const double dzNorm = (axisIdx < static_cast<int>(
DeadZoneNP.
size()))
577 const double alpha =
FilterNP[0].getValue();
600 LOGF_DEBUG(
"axisEvent[%d]: %d", axis_n, value);
602 if (axis_n < 0 || axis_n >=
static_cast<int>(
AxisNP.
size()))
606 if (m_calibrating && axis_n <
static_cast<int>(m_axisCalMin.size()))
608 if (value < m_axisCalMin[axis_n]) m_axisCalMin[axis_n] = value;
609 if (value > m_axisCalMax[axis_n]) m_axisCalMax[axis_n] = value;
624 const double alpha =
FilterNP[0].getValue();
625 if (axis_n <
static_cast<int>(
m_axisEMA.size()))
627 if (std::abs(value) < std::abs(
static_cast<int>(
m_axisEMA[axis_n])))
628 m_axisEMA[axis_n] =
static_cast<double>(value);
630 m_axisEMA[axis_n] = alpha *
static_cast<double>(value) + (1.0 - alpha) *
m_axisEMA[axis_n];
631 value =
static_cast<int>(std::round(
m_axisEMA[axis_n]));
635 if (std::abs(value) <=
static_cast<int>(
DeadZoneNP[axis_n].getValue()))
639 AxisNP[axis_n].setValue(value);
648 LOGF_DEBUG(
"buttonEvent[%d]: %s", button_n, value > 0 ?
"On" :
"Off");
650 if (button_n < 0 || button_n >=
static_cast<int>(
ButtonSP.
size()))
679void JoyStick::applyCalibrationToDriver()
681 const int nAxis =
static_cast<int>(
AxisNP.
size());
682 for (
int i = 0; i < nAxis; i++)
687 driver->setAxisCalibration(i, center, minVal, maxVal);
695void JoyStick::applyReverseToDriver()
698 for (
int i = 0; i < nAxis; i++)
706bool JoyStick::loadCalibrationData()
711 const char *hwName =
driver->getName();
712 if (!hwName || hwName[0] ==
'\0')
716 if (wordexp(m_calibrationFile.c_str(), &wexp, 0) != 0)
722 FILE *fp = fopen(wexp.we_wordv[0],
"r");
736 LOGF_DEBUG(
"loadCalibrationData: parse error: %s", errmsg);
744 if (strcmp(
tagXMLEle(devXml),
"device") != 0)
748 if (!nameAtt || strcmp(
valuXMLAtt(nameAtt), hwName) != 0)
755 if (strcmp(
tagXMLEle(axXml),
"axis") != 0)
759 if (!idxAtt)
continue;
762 const int nAxis =
static_cast<int>(
AxisNP.
size());
763 if (idx < 0 || idx >= nAxis)
770 if (!centerXml || !minXml || !maxXml)
781 m_axisCalCenter[idx] = center;
782 m_axisCalMin[idx] = minVal;
783 m_axisCalMax[idx] = maxVal;
794 applyCalibrationToDriver();
804bool JoyStick::saveCalibrationData()
809 const char *hwName =
driver->getName();
810 if (!hwName || hwName[0] ==
'\0')
815 XMLEle *existingDevXml =
nullptr;
818 if (wordexp(m_calibrationFile.c_str(), &wexp, 0) == 0)
820 FILE *fp = fopen(wexp.we_wordv[0],
"r");
834 root =
addXMLEle(
nullptr,
"joystickcalibration");
839 if (strcmp(
tagXMLEle(devXml),
"device") != 0)
842 if (nameAtt && strcmp(
valuXMLAtt(nameAtt), hwName) == 0)
844 existingDevXml = devXml;
857 const int nAxis =
static_cast<int>(
AxisNP.
size());
860 for (
int i = 0; i < nAxis; i++)
862 snprintf(buf,
sizeof(buf),
"%d", i);
870 snprintf(buf,
sizeof(buf),
"%d",
static_cast<int>(
AxisCalibrationNP[i * 3 + 0].getValue()));
873 snprintf(buf,
sizeof(buf),
"%d",
static_cast<int>(
AxisCalibrationNP[i * 3 + 1].getValue()));
876 snprintf(buf,
sizeof(buf),
"%d",
static_cast<int>(
AxisCalibrationNP[i * 3 + 2].getValue()));
881 if (wordexp(m_calibrationFile.c_str(), &wexp, 0) != 0)
885 LOGF_ERROR(
"saveCalibrationData: bad filename: %s", m_calibrationFile.c_str());
889 FILE *fp = fopen(wexp.we_wordv[0],
"w");
895 LOGF_ERROR(
"saveCalibrationData: cannot open %s: %s",
896 m_calibrationFile.c_str(), strerror(
errno));
const char * getDeviceName() const
INDI::PropertyText getText(const char *name) const
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool ISSnoopDevice(XMLEle *root)
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
virtual bool loadConfig(bool silent=false, const char *property=nullptr)
Load the last saved configuration file.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
void addDebugControl()
Add Debug control to the driver.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
bool isNameMatch(const char *otherName) const
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool update(const char *const texts[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
The JoyStickDriver class provides basic functionality to read events from supported game pads under L...
The JoyStick class provides an INDI driver that displays event data from game pads....
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
INDI::PropertyNumber FilterNP
static void buttonHelper(int button_n, int value)
virtual bool Disconnect() override
Disconnect from device.
std::vector< double > m_axisEMA
std::unique_ptr< JoyStickDriver > driver
INDI::PropertyNumber AxisCalibrationNP
AxisCalibrationNP Per-axis calibration values (IP_RW).
static void joystickHelper(int joystick_n, double mag, double angle)
bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual const char * getDefaultName() override
INDI::PropertyText JoystickInfoTP
INDI::PropertySwitch ButtonSP
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
static void axisHelper(int axis_n, int value)
std::vector< INDI::PropertyNumber > JoyStickNP
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
INDI::PropertyText PortTP
void joystickEvent(int joystick_n, double mag, double angle)
INDI::PropertySwitch CalibrationSP
CalibrationSP Start / End the calibration sweep.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
INDI::PropertySwitch AxisReverseSP
AxisReverseSP Per-axis polarity reversal toggles (IP_RW).
virtual bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
void axisEvent(int axis_n, int value)
INDI::PropertyNumber DeadZoneNP
INDI::PropertyNumber AxisNP
std::vector< double > m_joystickAngleSinEMA
std::vector< double > m_joystickAngleCosEMA
void buttonEvent(int button_n, int value)
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
#define LOGF_INFO(fmt,...)
#define LOGF_DEBUG(fmt,...)
#define LOGF_ERROR(fmt,...)
std::unique_ptr< JoyStick > joystick(new JoyStick())
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
XMLAtt * findXMLAtt(XMLEle *ep, const char *name)
Find an XML attribute within an XML element.
LilXML * newLilXML()
Create a new lilxml parser.
char * tagXMLEle(XMLEle *ep)
Return the tag of an XML element.
XMLEle * addXMLEle(XMLEle *parent, const char *tag)
add an element with the given tag to the given element. parent can be NULL to make a new root.
XMLEle * findXMLEle(XMLEle *ep, const char *tag)
Find an XML element within an XML element.
char * valuXMLAtt(XMLAtt *ap)
Return the value of an XML attribute.
void editXMLEle(XMLEle *ep, const char *pcdata)
set the pcdata of the given element
void prXMLEle(FILE *fp, XMLEle *ep, int level)
Print an XML element.
void delXMLEle(XMLEle *ep)
delXMLEle Delete XML element.
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
void delLilXML(LilXML *lp)
Delete a lilxml parser.
XMLAtt * addXMLAtt(XMLEle *ep, const char *name, const char *valu)
Add an XML attribute to an existing XML element.
XMLEle * readXMLFile(FILE *fp, LilXML *lp, char ynot[])
Handy wrapper to read one xml file.
A little DOM-style library to handle parsing and processing an XML file.
const char * DEVICE_PORT
Device serial (or bluetooth) connection port. The default value on Linux is /dev/ttyUSB0 while on Mac...
A matched pair of axis indices that together form a joystick.
const char * name
Human-readable name (e.g. "Left Stick")