diff --git a/headers/addons/analog.h b/headers/addons/analog.h index 5893ef3cbc..9d0a5c4b08 100644 --- a/headers/addons/analog.h +++ b/headers/addons/analog.h @@ -128,6 +128,8 @@ typedef struct float out_deadzone; bool auto_calibration; bool forced_circularity; + uint32_t joystick_center_x; + uint32_t joystick_center_y; } adc_instance; class AnalogInput : public GPAddon { @@ -148,4 +150,4 @@ class AnalogInput : public GPAddon { adc_instance adc_pairs[ADC_COUNT]; }; -#endif // _Analog_H_ \ No newline at end of file +#endif // _Analog_H_ diff --git a/proto/config.proto b/proto/config.proto index a2e7dcaf35..73d9b34d14 100644 --- a/proto/config.proto +++ b/proto/config.proto @@ -419,6 +419,10 @@ message AnalogOptions optional uint32 outer_deadzone2 = 21; optional bool auto_calibrate2 = 22; optional bool forced_circularity2 = 23; + optional uint32 joystick_center_x = 24; + optional uint32 joystick_center_y = 25; + optional uint32 joystick_center_x2 = 26; + optional uint32 joystick_center_y2 = 27; } message TurboOptions diff --git a/src/addons/analog.cpp b/src/addons/analog.cpp index bc4bd44431..d219ba5f5a 100644 --- a/src/addons/analog.cpp +++ b/src/addons/analog.cpp @@ -33,6 +33,8 @@ void AnalogInput::setup() { adc_pairs[0].out_deadzone = analogOptions.outer_deadzone / 100.0f; adc_pairs[0].auto_calibration = analogOptions.auto_calibrate; adc_pairs[0].forced_circularity = analogOptions.forced_circularity; + adc_pairs[0].joystick_center_x = analogOptions.joystick_center_x; + adc_pairs[0].joystick_center_y = analogOptions.joystick_center_y; adc_pairs[1].x_pin = analogOptions.analogAdc2PinX; adc_pairs[1].y_pin = analogOptions.analogAdc2PinY; adc_pairs[1].analog_invert = analogOptions.analogAdc2Invert; @@ -44,6 +46,8 @@ void AnalogInput::setup() { adc_pairs[1].out_deadzone = analogOptions.outer_deadzone2 / 100.0f; adc_pairs[1].auto_calibration = analogOptions.auto_calibrate2; adc_pairs[1].forced_circularity = analogOptions.forced_circularity2; + adc_pairs[1].joystick_center_x = analogOptions.joystick_center_x2; + adc_pairs[1].joystick_center_y = analogOptions.joystick_center_y2; // Setup defaults and helpers @@ -66,6 +70,9 @@ void AnalogInput::setup() { if (adc_pairs[i].auto_calibration) { adc_select_input(adc_pairs[i].x_pin - ADC_PIN_OFFSET); adc_pairs[i].x_center = adc_read(); + } else { + // if auto calibration is disabled, attempt to use stored manual calibration value + adc_pairs[i].x_center = adc_pairs[i].joystick_center_x; } } if(isValidPin(adc_pairs[i].y_pin)) { @@ -73,6 +80,9 @@ void AnalogInput::setup() { if (adc_pairs[i].auto_calibration) { adc_select_input(adc_pairs[i].y_pin - ADC_PIN_OFFSET); adc_pairs[i].y_center = adc_read(); + } else { + // if auto calibration is disabled, attempt to use stored manual calibration value + adc_pairs[i].y_center = adc_pairs[i].joystick_center_y; } } } @@ -139,7 +149,9 @@ void AnalogInput::process() { float AnalogInput::readPin(int stick_num, Pin_t pin_adc, uint16_t center) { adc_select_input(pin_adc); uint16_t adc_value = adc_read(); - if (adc_pairs[stick_num].auto_calibration) { + // Apply calibration only if auto calibration is enabled or manual calibration has been performed + // Manual calibration is considered performed if the center value is not 0 (default) + if (adc_pairs[stick_num].auto_calibration || center != 0) { if (adc_value > center) { adc_value = map(adc_value, center, ADC_MAX, ADC_MAX / 2, ADC_MAX); } else if (adc_value == center) { @@ -174,4 +186,4 @@ void AnalogInput::radialDeadzone(int stick_num, adc_instance & adc_inst) { adc_inst.y_value = ((adc_inst.y_magnitude / adc_inst.xy_magnitude) * scaling_factor) + ANALOG_CENTER; adc_inst.x_value = std::clamp(adc_inst.x_value, ANALOG_MINIMUM, ANALOG_MAX); adc_inst.y_value = std::clamp(adc_inst.y_value, ANALOG_MINIMUM, ANALOG_MAX); -} \ No newline at end of file +} diff --git a/src/webconfig.cpp b/src/webconfig.cpp index d59cb1ac47..c8a317552b 100644 --- a/src/webconfig.cpp +++ b/src/webconfig.cpp @@ -1,5 +1,7 @@ #include "config.pb.h" #include "base64.h" +#include "hardware/adc.h" +#include "helper.h" #include "drivermanager.h" #include "storagemanager.h" @@ -1684,6 +1686,10 @@ std::string setAddonOptions() docToValue(analogOptions.outer_deadzone2, doc, "outer_deadzone2"); docToValue(analogOptions.auto_calibrate, doc, "auto_calibrate"); docToValue(analogOptions.auto_calibrate2, doc, "auto_calibrate2"); + docToValue(analogOptions.joystick_center_x, doc, "joystickCenterX"); + docToValue(analogOptions.joystick_center_y, doc, "joystickCenterY"); + docToValue(analogOptions.joystick_center_x2, doc, "joystickCenterX2"); + docToValue(analogOptions.joystick_center_y2, doc, "joystickCenterY2"); docToValue(analogOptions.analog_smoothing, doc, "analog_smoothing"); docToValue(analogOptions.analog_smoothing2, doc, "analog_smoothing2"); docToValue(analogOptions.smoothing_factor, doc, "smoothing_factor"); @@ -2138,6 +2144,10 @@ std::string getAddonOptions() writeDoc(doc, "outer_deadzone2", analogOptions.outer_deadzone2); writeDoc(doc, "auto_calibrate", analogOptions.auto_calibrate); writeDoc(doc, "auto_calibrate2", analogOptions.auto_calibrate2); + writeDoc(doc, "joystickCenterX", analogOptions.joystick_center_x); + writeDoc(doc, "joystickCenterY", analogOptions.joystick_center_y); + writeDoc(doc, "joystickCenterX2", analogOptions.joystick_center_x2); + writeDoc(doc, "joystickCenterY2", analogOptions.joystick_center_y2); writeDoc(doc, "analog_smoothing", analogOptions.analog_smoothing); writeDoc(doc, "analog_smoothing2", analogOptions.analog_smoothing2); writeDoc(doc, "smoothing_factor", analogOptions.smoothing_factor); @@ -2569,6 +2579,94 @@ std::string reboot() { return serialize_json(doc); } +// NEW API: return current raw ADC reading for the configured analog pins +std:: string getJoystickCenter() { + const size_t capacity = JSON_OBJECT_SIZE(10); + DynamicJsonDocument doc(capacity); + const AnalogOptions& analogOptions = Storage::getInstance().getAddonOptions().analogOptions; + + uint16_t x = 0, y = 0; + bool success = true; + std::string error_msg = ""; + + // Check if analog input is enabled + if (!analogOptions.enabled) { + success = false; + error_msg = "Analog input is not enabled"; + } else { + // Initialize ADC if not already initialized + adc_init(); + + // Check if specific stick is requested via query parameter + // For now, we'll read both sticks and return the appropriate one + // In a more sophisticated implementation, we could parse query parameters + + // Read first stick X/Y + if (isValidPin(analogOptions.analogAdc1PinX)) { + adc_gpio_init(analogOptions.analogAdc1PinX); + adc_select_input(analogOptions.analogAdc1PinX - 26); + x = adc_read(); + } + if (isValidPin(analogOptions.analogAdc1PinY)) { + adc_gpio_init(analogOptions.analogAdc1PinY); + adc_select_input(analogOptions.analogAdc1PinY - 26); + y = adc_read(); + } + } + + JsonObject o = doc.to(); + o["success"] = success; + if (!success) { + o["error"] = error_msg; + } else { + o["x"] = x; + o["y"] = y; + } + return serialize_json(doc); +} + +// NEW API: return current raw ADC reading for stick 2 +std:: string getJoystickCenter2() { + const size_t capacity = JSON_OBJECT_SIZE(10); + DynamicJsonDocument doc(capacity); + const AnalogOptions& analogOptions = Storage::getInstance().getAddonOptions().analogOptions; + + uint16_t x = 0, y = 0; + bool success = true; + std::string error_msg = ""; + + // Check if analog input is enabled + if (!analogOptions.enabled) { + success = false; + error_msg = "Analog input is not enabled"; + } else { + // Initialize ADC if not already initialized + adc_init(); + + // Read second stick X/Y + if (isValidPin(analogOptions.analogAdc2PinX)) { + adc_gpio_init(analogOptions.analogAdc2PinX); + adc_select_input(analogOptions.analogAdc2PinX - 26); + x = adc_read(); + } + if (isValidPin(analogOptions.analogAdc2PinY)) { + adc_gpio_init(analogOptions.analogAdc2PinY); + adc_select_input(analogOptions.analogAdc2PinY - 26); + y = adc_read(); + } + } + + JsonObject o = doc.to(); + o["success"] = success; + if (!success) { + o["error"] = error_msg; + } else { + o["x"] = x; + o["y"] = y; + } + return serialize_json(doc); +} + typedef std::string (*HandlerFuncPtr)(); static const std::pair handlerFuncs[] = { @@ -2617,6 +2715,8 @@ static const std::pair handlerFuncs[] = { "/api/abortGetHeldPins", abortGetHeldPins }, { "/api/getUsedPins", getUsedPins }, { "/api/getConfig", getConfig }, + { "/api/getJoystickCenter", getJoystickCenter }, + { "/api/getJoystickCenter2", getJoystickCenter2 }, #if !defined(NDEBUG) { "/api/echo", echo }, #endif diff --git a/www/src/Addons/Analog.tsx b/www/src/Addons/Analog.tsx index c2431dc85b..d8e46ce428 100644 --- a/www/src/Addons/Analog.tsx +++ b/www/src/Addons/Analog.tsx @@ -1,4 +1,4 @@ -import { useContext } from 'react'; +import { useContext, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FormCheck, Row, Tab, Tabs } from 'react-bootstrap'; import * as yup from 'yup'; @@ -133,6 +133,22 @@ export const analogScheme = { .number() .label('Error Rate 2') .validateSelectionWhenValue('AnalogInputEnabled', ANALOG_ERROR_RATES), + joystickCenterX: yup + .number() + .label('Joystick Center X') + .validateRangeWhenValue('AnalogInputEnabled', 0, 4095), + joystickCenterY: yup + .number() + .label('Joystick Center Y') + .validateRangeWhenValue('AnalogInputEnabled', 0, 4095), + joystickCenterX2: yup + .number() + .label('Joystick Center X2') + .validateRangeWhenValue('AnalogInputEnabled', 0, 4095), + joystickCenterY2: yup + .number() + .label('Joystick Center Y2') + .validateRangeWhenValue('AnalogInputEnabled', 0, 4095), }; export const analogState = { @@ -153,6 +169,10 @@ export const analogState = { outer_deadzone2: 95, auto_calibrate: 0, auto_calibrate2: 0, + joystickCenterX: 0, + joystickCenterY: 0, + joystickCenterX2: 0, + joystickCenterY2: 0, analog_smoothing: 0, analog_smoothing2: 0, smoothing_factor: 5, @@ -161,7 +181,7 @@ export const analogState = { analog_error2: 1, }; -const Analog = ({ values, errors, handleChange, handleCheckbox }: AddonPropTypes) => { +const Analog = ({ values, errors, handleChange, handleCheckbox, setFieldValue }: AddonPropTypes) => { const { usedPins } = useContext(AppContext); const { t } = useTranslation(); const availableAnalogPins = ANALOG_PINS.filter( @@ -346,18 +366,137 @@ const Analog = ({ values, errors, handleChange, handleCheckbox }: AddonPropTypes ))} - { - handleCheckbox('auto_calibrate'); - handleChange(e); - }} - /> +
+ { + handleCheckbox('auto_calibrate'); + handleChange(e); + }} + /> + +
+ {`Center: X=${values.joystickCenterX}, Y=${values.joystickCenterY}`} +
+
+ {Boolean(values.auto_calibrate) && ( +
+ + {t('AddonsConfig:analog-auto-calibration-enabled-stick-1')}: {t('AddonsConfig:analog-calibration-auto-mode-instruction', { stick: '1' })} + +
+ )} + {!Boolean(values.auto_calibrate) && ( +
+ + {t('AddonsConfig:analog-manual-calibration-mode-stick-1')}: +
• {t('AddonsConfig:analog-calibration-manual-mode-instruction-1')} +
• {t('AddonsConfig:analog-calibration-manual-mode-instruction-2')} +
• {t('AddonsConfig:analog-calibration-manual-mode-instruction-3')} +
• {t('AddonsConfig:analog-calibration-manual-mode-instruction-4')} +
+
+ )} - { - handleCheckbox('auto_calibrate2'); - handleChange(e); - }} - /> +
+ { + handleCheckbox('auto_calibrate2'); + handleChange(e); + }} + /> + +
+ {`Center: X=${values.joystickCenterX2}, Y=${values.joystickCenterY2}`} +
+
+ {Boolean(values.auto_calibrate2) && ( +
+ + {t('AddonsConfig:analog-auto-calibration-enabled-stick-2')}: {t('AddonsConfig:analog-calibration-auto-mode-instruction', { stick: '2' })} + +
+ )} + {!Boolean(values.auto_calibrate2) && ( +
+ + {t('AddonsConfig:analog-manual-calibration-mode-stick-2')}: +
• {t('AddonsConfig:analog-calibration-manual-mode-instruction-1')} +
• {t('AddonsConfig:analog-calibration-manual-mode-instruction-2')} +
• {t('AddonsConfig:analog-calibration-manual-mode-instruction-3')} +
• {t('AddonsConfig:analog-calibration-manual-mode-instruction-4')} +
+
+ )}
diff --git a/www/src/Locales/en/AddonsConfig.jsx b/www/src/Locales/en/AddonsConfig.jsx index feedbdc8da..03adbad95b 100644 --- a/www/src/Locales/en/AddonsConfig.jsx +++ b/www/src/Locales/en/AddonsConfig.jsx @@ -31,6 +31,32 @@ export default { 'inner-deadzone-size': 'Inner Deadzone Size (%)', 'outer-deadzone-size': 'Outer Deadzone Size (%)', 'analog-auto-calibrate': 'Auto Calibration', + 'analog-calibrate-button': 'Calibrate', + 'analog-calibrate-stick-1-button': 'Calibrate Stick 1', + 'analog-calibrate-stick-2-button': 'Calibrate Stick 2', + 'analog-manual-calibration-mode-stick-1': 'Stick 1 Manual Calibration Mode', + 'analog-manual-calibration-mode-stick-2': 'Stick 2 Manual Calibration Mode', + 'analog-auto-calibration-enabled-stick-1': 'Stick 1 Auto Calibration Enabled', + 'analog-auto-calibration-enabled-stick-2': 'Stick 2 Auto Calibration Enabled', + 'analog-calibration-step-title': 'Calibration Step {{step}}/4', + 'analog-calibration-step-instruction': 'Please move stick {{stick}} to {{direction}} position, then release to center', + 'analog-calibration-step-confirm': 'Confirm stick {{stick}} has returned to center, then click "OK" to record center value {{step}}', + 'analog-calibration-cancelled': 'Calibration cancelled', + 'analog-calibration-failed': 'Calibration failed: {{error}}', + 'analog-calibration-success-stick-1': 'Stick 1 calibration successful!', + 'analog-calibration-success-stick-2': 'Stick 2 calibration successful!', + 'analog-calibration-data': 'Calibration data:', + 'analog-calibration-direction-top-left': 'Top-Left', + 'analog-calibration-direction-top-right': 'Top-Right', + 'analog-calibration-direction-bottom-left': 'Bottom-Left', + 'analog-calibration-direction-bottom-right': 'Bottom-Right', + 'analog-calibration-final-center': 'Final center value: X={{x}}, Y={{y}}', + 'analog-calibration-save-notice': 'Please save configuration to apply calibration values.', + 'analog-calibration-manual-mode-instruction-1': 'Click "Calibrate" button to start multi-step calibration process', + 'analog-calibration-manual-mode-instruction-2': 'Follow prompts to move stick to four directions and center', + 'analog-calibration-manual-mode-instruction-3': 'System will automatically calculate optimal center value', + 'analog-calibration-manual-mode-instruction-4': 'Save configuration and restart device to apply calibration', + 'analog-calibration-auto-mode-instruction': 'System will automatically read stick {{stick}} center value on startup. For manual calibration, please uncheck "Auto Calibration" first.', 'analog-smoothing': 'Analog Smoothing', 'smoothing-factor': 'Smoothing Factor', 'analog-error-label': 'Error Rate', diff --git a/www/src/Locales/zh-CN/AddonsConfig.jsx b/www/src/Locales/zh-CN/AddonsConfig.jsx index bdd6be2f75..c5bc91292d 100644 --- a/www/src/Locales/zh-CN/AddonsConfig.jsx +++ b/www/src/Locales/zh-CN/AddonsConfig.jsx @@ -28,6 +28,32 @@ export default { 'inner-deadzone-size': '内部死区范围 (%)', 'outer-deadzone-size': '外部死区范围 (%)', 'analog-auto-calibrate': '自动校准', + 'analog-calibrate-button': '手动校准', + 'analog-calibrate-stick-1-button': '校准摇杆1', + 'analog-calibrate-stick-2-button': '校准摇杆2', + 'analog-manual-calibration-mode-stick-1': '摇杆1手动校准模式', + 'analog-manual-calibration-mode-stick-2': '摇杆2手动校准模式', + 'analog-auto-calibration-enabled-stick-1': '摇杆1自动校准已启用', + 'analog-auto-calibration-enabled-stick-2': '摇杆2自动校准已启用', + 'analog-calibration-step-title': '校准步骤 {{step}}/4', + 'analog-calibration-step-instruction': '请将摇杆{{stick}}拨动到{{direction}}位置,然后松开让其回中', + 'analog-calibration-step-confirm': '确认摇杆{{stick}}已回中后,点击"确定"记录中心值{{step}}', + 'analog-calibration-cancelled': '校准已取消', + 'analog-calibration-failed': '校准失败: {{error}}', + 'analog-calibration-success-stick-1': '摇杆1校准成功!', + 'analog-calibration-success-stick-2': '摇杆2校准成功!', + 'analog-calibration-data': '校准数据:', + 'analog-calibration-direction-top-left': '左上', + 'analog-calibration-direction-top-right': '右上', + 'analog-calibration-direction-bottom-left': '左下', + 'analog-calibration-direction-bottom-right': '右下', + 'analog-calibration-final-center': '最终中心值: X={{x}}, Y={{y}}', + 'analog-calibration-save-notice': '请保存配置以应用校准值。', + 'analog-calibration-manual-mode-instruction-1': '点击"校准"按钮开始多步骤校准流程', + 'analog-calibration-manual-mode-instruction-2': '按提示将摇杆拨动到四个方向并回中', + 'analog-calibration-manual-mode-instruction-3': '系统将自动计算最佳中心值', + 'analog-calibration-manual-mode-instruction-4': '保存配置后重启设备以应用校准', + 'analog-calibration-auto-mode-instruction': '系统会在开机时自动读取摇杆{{stick}}中心值。如需手动校准,请先取消勾选"自动校准"。', 'analog-smoothing': '平滑模拟信号', 'smoothing-factor': '平滑因数', 'analog-error-label': '误差率',