auto-sync

This commit is contained in:
rusEfi 2016-07-13 21:03:05 -04:00
parent a74f74f0c4
commit 38b3771a4b
24 changed files with 155 additions and 43 deletions

View File

@ -62,7 +62,7 @@ bool isStep1Condition(int rpm DECLARE_ENGINE_PARAMETER_S) {
static angle_t getRunningAdvance(int rpm, float engineLoad DECLARE_ENGINE_PARAMETER_S) {
engine->m.beforeAdvance = GET_TIMESTAMP();
if (cisnan(engineLoad)) {
warning(OBD_PCM_Processor_Fault, "NaN engine load");
warning(CUSTOM_OBD_0, "NaN engine load");
return NAN;
}
efiAssert(!cisnan(engineLoad), "invalid el", NAN);
@ -182,7 +182,7 @@ float getInitialAdvance(int rpm, float map, float advanceMax) {
void buildTimingMap(float advanceMax DECLARE_ENGINE_PARAMETER_S) {
if (engineConfiguration->algorithm != LM_SPEED_DENSITY &&
engineConfiguration->algorithm != LM_MAP) {
warning(OBD_PCM_Processor_Fault, "wrong algorithm for MAP-based timing");
warning(CUSTOM_OBD_1, "wrong algorithm for MAP-based timing");
return;
}
/**

View File

@ -932,7 +932,7 @@ void resetConfigurationExt(Logging * logger, engine_type_e engineType DECLARE_EN
break;
default:
warning(OBD_PCM_Processor_Fault, "Unexpected engine type: %d", engineType);
warning(CUSTOM_OBD_3, "Unexpected engine type: %d", engineType);
}
applyNonPersistentConfiguration(logger PASS_ENGINE_PARAMETER);
// todo: eliminate triggerShape.operationMode?

View File

@ -185,7 +185,7 @@ float getIatCorrection(float iat DECLARE_ENGINE_PARAMETER_S) {
*/
floatms_t getBaseTableFuel(engine_configuration_s *engineConfiguration, int rpm, float engineLoad) {
if (cisnan(engineLoad)) {
warning(OBD_PCM_Processor_Fault, "NaN engine load");
warning(CUSTOM_OBD_2, "NaN engine load");
return NAN;
}
return fuelMap.getValue(rpm, engineLoad);

View File

@ -1664,6 +1664,117 @@ typedef enum {
//P3492 Cyl12 Deactivation/Intake Valve Ctrl Circ High
//P3493 Cyl12 Exhaust Valve Ctrl Circ/Open
CUSTOM_OBD_0 = 6000,
CUSTOM_OBD_1 = 6001,
CUSTOM_OBD_2 = 6002,
CUSTOM_OBD_3 = 6003,
CUSTOM_OBD_4 = 6004,
CUSTOM_OBD_5 = 6005,
CUSTOM_OBD_6 = 6006,
CUSTOM_OBD_7 = 6007,
CUSTOM_OBD_8 = 6008,
CUSTOM_OBD_9 = 6009,
CUSTOM_OBD_10 = 6010,
CUSTOM_OBD_11 = 6011,
CUSTOM_OBD_12 = 6012,
CUSTOM_OBD_13 = 6013,
CUSTOM_OBD_14 = 6014,
CUSTOM_OBD_15 = 6015,
CUSTOM_OBD_16 = 6016,
CUSTOM_OBD_17 = 6017,
CUSTOM_OBD_18 = 6018,
CUSTOM_OBD_19 = 6019,
CUSTOM_OBD_20 = 6020,
CUSTOM_OBD_21 = 6021,
CUSTOM_OBD_22 = 6022,
CUSTOM_OBD_23 = 6023,
CUSTOM_OBD_24 = 6024,
CUSTOM_OBD_25 = 6025,
CUSTOM_OBD_26 = 6026,
CUSTOM_OBD_27 = 6027,
CUSTOM_OBD_28 = 6028,
CUSTOM_OBD_29 = 6029,
CUSTOM_OBD_30 = 6030,
CUSTOM_OBD_31 = 6031,
CUSTOM_OBD_32 = 6032,
CUSTOM_OBD_33 = 6033,
CUSTOM_OBD_34 = 6034,
CUSTOM_OBD_35 = 6035,
CUSTOM_OBD_36 = 6036,
CUSTOM_OBD_37 = 6037,
CUSTOM_OBD_38 = 6038,
CUSTOM_OBD_39 = 6039,
CUSTOM_OBD_40 = 6040,
CUSTOM_OBD_41 = 6041,
CUSTOM_OBD_42 = 6042,
CUSTOM_OBD_43 = 6043,
CUSTOM_OBD_44 = 6044,
CUSTOM_OBD_45 = 6045,
CUSTOM_OBD_46 = 6046,
CUSTOM_OBD_47 = 6047,
CUSTOM_OBD_48 = 6048,
CUSTOM_OBD_49 = 6049,
CUSTOM_OBD_50 = 6050,
CUSTOM_OBD_51 = 6051,
CUSTOM_OBD_52 = 6052,
CUSTOM_OBD_53 = 6053,
CUSTOM_OBD_54 = 6054,
CUSTOM_OBD_55 = 6035,
CUSTOM_OBD_56 = 6056,
CUSTOM_OBD_57 = 6057,
CUSTOM_OBD_58 = 6058,
CUSTOM_OBD_59 = 6059,
CUSTOM_OBD_60 = 6060,
CUSTOM_OBD_61 = 6061,
CUSTOM_OBD_62 = 6062,
CUSTOM_OBD_63 = 6063,
CUSTOM_OBD_64 = 6064,
CUSTOM_OBD_65 = 6065,
CUSTOM_OBD_66 = 6066,
CUSTOM_OBD_67 = 6067,
CUSTOM_OBD_68 = 6068,
CUSTOM_OBD_69 = 6069,
CUSTOM_OBD_70 = 6070,
CUSTOM_OBD_71 = 6071,
CUSTOM_OBD_72 = 6072,
CUSTOM_OBD_73 = 6073,
CUSTOM_OBD_74 = 6074,
CUSTOM_OBD_75 = 6075,
CUSTOM_OBD_76 = 6076,
CUSTOM_OBD_77 = 6077,
CUSTOM_OBD_78 = 6078,
CUSTOM_OBD_79 = 6079,
CUSTOM_OBD_80 = 6080,
CUSTOM_OBD_81 = 6081,
CUSTOM_OBD_82 = 6082,
CUSTOM_OBD_83 = 6083,
CUSTOM_OBD_84 = 6084,
CUSTOM_OBD_85 = 6085,
CUSTOM_OBD_86 = 6086,
CUSTOM_OBD_87 = 6087,
CUSTOM_OBD_88 = 6088,
CUSTOM_OBD_89 = 6089,
CUSTOM_OBD_90 = 6090,
CUSTOM_OBD_91 = 6091,
CUSTOM_OBD_92 = 6092,
CUSTOM_OBD_93 = 6093,
CUSTOM_OBD_94 = 6094,
CUSTOM_OBD_95 = 6095,
CUSTOM_OBD_96 = 6096,
CUSTOM_OBD_97 = 6097,
CUSTOM_OBD_98 = 6098,
CUSTOM_OBD_99 = 6099,
// this is needed for proper enum size, this matters for malfunction_central
Internal_ForceMyEnumIntSize_cranking_obd_code = ENUM_32_BITS,
} obd_code_e;

View File

@ -119,11 +119,11 @@ int getRevolutionCounter(void);
void scheduleOutput(OutputSignal *signal, efitimeus_t nowUs, float delayUs, float durationUs, NamedOutputPin *output) {
#if EFI_GPIO || defined(__DOXYGEN__)
if (durationUs < 0) {
warning(OBD_PCM_Processor_Fault, "duration cannot be negative: %d", durationUs);
warning(CUSTOM_OBD_3, "duration cannot be negative: %d", durationUs);
return;
}
if (cisnan(durationUs)) {
warning(OBD_PCM_Processor_Fault, "NaN in scheduleOutput", durationUs);
warning(CUSTOM_OBD_4, "NaN in scheduleOutput", durationUs);
return;
}

View File

@ -122,7 +122,7 @@ static bool float2bool(float v) {
float LECalculator::pop(le_action_e action) {
if (stack.size() == 0) {
warning(OBD_PCM_Processor_Fault, "empty stack for action=%d", action);
warning(CUSTOM_OBD_5, "empty stack for action=%d", action);
return NAN;
}
return stack.pop();
@ -286,7 +286,7 @@ bool LECalculator::processElement(Engine *engine, LEElement *element) {
push(element->action, engine->knockCount);
break;
case LE_UNDEFINED:
warning(OBD_PCM_Processor_Fault, "FSIO undefined action");
warning(CUSTOM_OBD_6, "FSIO undefined action");
return true;
default:
push(element->action, getLEValue(engine, &stack, element->action));
@ -301,7 +301,7 @@ float LECalculator::getValue2(float selfValue, LEElement *fistElementInList, Eng
float LECalculator::getValue(float selfValue, Engine *engine) {
if (first == NULL) {
warning(OBD_PCM_Processor_Fault, "no FSIO code");
warning(CUSTOM_OBD_7, "no FSIO code");
return NAN;
}
LEElement *element = first;
@ -325,7 +325,7 @@ float LECalculator::getValue(float selfValue, Engine *engine) {
counter++;
}
if (stack.size() != 1) {
warning(OBD_PCM_Processor_Fault, "unexpected FSIO stack size: %d", stack.size());
warning(CUSTOM_OBD_8, "unexpected FSIO stack size: %d", stack.size());
return NAN;
}
return stack.pop();

View File

@ -97,7 +97,7 @@ float getLEValue(Engine *engine, calc_stack_t *s, le_action_e action) {
case LE_METHOD_VBATT:
return getVBatt(PASS_ENGINE_PARAMETER_F);
default:
warning(OBD_PCM_Processor_Fault, "FSIO unexpected %d", action);
warning(CUSTOM_OBD_9, "FSIO unexpected %d", action);
return NAN;
}
}
@ -171,7 +171,7 @@ void applyFsioConfiguration(DECLARE_ENGINE_PARAMETER_F) {
const char *formula = config->le_formulas[i];
LEElement *logic = userPool.parseExpression(formula);
if (logic == NULL) {
warning(OBD_PCM_Processor_Fault, "parsing [%s]", formula);
warning(CUSTOM_OBD_10, "parsing [%s]", formula);
}
fsioLogics[i] = logic;
@ -271,7 +271,7 @@ static const char * action2String(le_action_e action) {
static void setPinState(const char * msg, OutputPin *pin, LEElement *element, Engine *engine) {
if (element == NULL) {
warning(OBD_PCM_Processor_Fault, "invalid expression for %s", msg);
warning(CUSTOM_OBD_11, "invalid expression for %s", msg);
} else {
int value = calc.getValue2(pin->getLogicValue(), element, engine);
if (pin->isInitialized() && value != pin->getLogicValue()) {

View File

@ -108,7 +108,7 @@ float interpolateMsg(const char *msg, float x1, float y1, float x2, float y2, fl
/**
* we could end up here for example while resetting bins while changing engine type
*/
warning(OBD_PCM_Processor_Fault, "interpolate%s: Same x1 and x2 in interpolate: %f/%f", msg, x1, x2);
warning(CUSTOM_OBD_12, "interpolate%s: Same x1 and x2 in interpolate: %f/%f", msg, x1, x2);
return NAN;
}

View File

@ -30,11 +30,11 @@ int needInterpolationLogging(void);
template<typename vType>
float interpolate3d(float x, float xBin[], int xBinSize, float y, float yBin[], int yBinSize, vType* map[]) {
if (cisnan(x)) {
warning(OBD_PCM_Processor_Fault, "%f: x is NaN in interpolate3d", x);
warning(CUSTOM_OBD_14, "%f: x is NaN in interpolate3d", x);
return NAN;
}
if (cisnan(y)) {
warning(OBD_PCM_Processor_Fault, "%f: y is NaN in interpolate3d", y);
warning(CUSTOM_OBD_13, "%f: y is NaN in interpolate3d", y);
return NAN;
}

View File

@ -56,7 +56,7 @@ void Table2D<SIZE>::preCalc(float *bin, float *values) {
float x1 = bin[i];
float x2 = bin[i + 1];
if (x1 == x2) {
warning(OBD_PCM_Processor_Fault, "preCalc: Same x1 and x2 in interpolate: %f/%f", x1, x2);
warning(CUSTOM_OBD_15, "preCalc: Same x1 and x2 in interpolate: %f/%f", x1, x2);
return;
}
@ -89,7 +89,7 @@ template<int RPM_BIN_SIZE, int LOAD_BIN_SIZE, typename vType>
float Map3D<RPM_BIN_SIZE, LOAD_BIN_SIZE, vType>::getValue(float xRpm, float y) {
efiAssert(initialized, "map not initialized", NAN);
if (cisnan(y)) {
warning(OBD_PCM_Processor_Fault, "%s: y is NaN", name);
warning(CUSTOM_OBD_16, "%s: y is NaN", name);
return NAN;
}
// todo: we have a bit of a mess: in TunerStudio, RPM is X-axis

View File

@ -53,7 +53,7 @@ float getEngineLoadT(DECLARE_ENGINE_PARAMETER_F) {
switch (engineConfiguration->algorithm) {
case LM_PLAIN_MAF:
if (!hasMafSensor(PASS_ENGINE_PARAMETER_F)) {
warning(OBD_PCM_Processor_Fault, "MAF sensor needed for current fuel algorithm");
warning(CUSTOM_OBD_17, "MAF sensor needed for current fuel algorithm");
return NAN;
}
return getMafT(engineConfiguration);
@ -67,7 +67,7 @@ float getEngineLoadT(DECLARE_ENGINE_PARAMETER_F) {
return getRealMaf(PASS_ENGINE_PARAMETER_F);
}
default:
warning(OBD_PCM_Processor_Fault, "Unexpected engine load parameter: %d", engineConfiguration->algorithm);
warning(CUSTOM_OBD_18, "Unexpected engine load parameter: %d", engineConfiguration->algorithm);
return -1;
}
}
@ -96,7 +96,7 @@ static void addIgnitionEvent(angle_t localAdvance, angle_t dwellAngle, IgnitionE
if (!isPinAssigned(output)) {
// todo: extact method for this index math
warning(OBD_PCM_Processor_Fault, "no_pin_cl #%s", output->name);
warning(CUSTOM_OBD_19, "no_pin_cl #%s", output->name);
}
event->output = output;
event->advance = localAdvance;
@ -139,7 +139,7 @@ void FuelSchedule::registerInjectionEvent(int injectorIndex, float angle,
if (!isSimultanious && !isPinAssigned(output)) {
// todo: extact method for this index math
warning(OBD_PCM_Processor_Fault, "no_pin_inj #%s", output->name);
warning(CUSTOM_OBD_20, "no_pin_inj #%s", output->name);
}
InjectionEvent *ev = injectionEvents.add();
@ -234,7 +234,7 @@ void FuelSchedule::addFuelEvents(injection_mode_e mode DECLARE_ENGINE_PARAMETER_
}
break;
default:
warning(OBD_PCM_Processor_Fault, "Unexpected injection mode %d", mode);
warning(CUSTOM_OBD_21, "Unexpected injection mode %d", mode);
}
}
@ -298,7 +298,7 @@ void findTriggerPosition(event_trigger_position_s *position, angle_t angleOffset
int index = TRIGGER_SHAPE(triggerIndexByAngle[(int)angleOffset]);
angle_t eventAngle = TRIGGER_SHAPE(eventAngles[index]);
if (angleOffset < eventAngle) {
warning(OBD_PCM_Processor_Fault, "angle constraint violation in findTriggerPosition(): %f/%f", angleOffset, eventAngle);
warning(CUSTOM_OBD_22, "angle constraint violation in findTriggerPosition(): %f/%f", angleOffset, eventAngle);
return;
}
@ -369,7 +369,7 @@ int getCylinderId(firing_order_e firingOrder, int index) {
return order_1_5_4_2_6_3_7_8[index];
default:
warning(OBD_PCM_Processor_Fault, "getCylinderId not supported for %d", firingOrder);
warning(CUSTOM_OBD_23, "getCylinderId not supported for %d", firingOrder);
}
return 1;
}
@ -389,7 +389,7 @@ static int getIgnitionPinForIndex(int i DECLARE_ENGINE_PARAMETER_S
break;
default:
warning(OBD_PCM_Processor_Fault, "unsupported ignitionMode %d in initializeIgnitionActions()", engineConfiguration->ignitionMode);
warning(CUSTOM_OBD_24, "unsupported ignitionMode %d in initializeIgnitionActions()", engineConfiguration->ignitionMode);
return 0;
}
}

View File

@ -99,7 +99,7 @@ float decodePressure(float voltage, air_pressure_sensor_config_s * mapConfig DEC
*/
float validateMap(float mapKPa DECLARE_ENGINE_PARAMETER_S) {
if (cisnan(mapKPa) || mapKPa < CONFIG(mapErrorDetectionTooLow) || mapKPa > CONFIG(mapErrorDetectionTooHigh)) {
warning(OBD_PCM_Processor_Fault, "unexpected MAP value: %f", mapKPa);
warning(CUSTOM_OBD_25, "unexpected MAP value: %f", mapKPa);
return 0;
}
return mapKPa;

View File

@ -109,7 +109,7 @@ float getCoolantTemperature(DECLARE_ENGINE_PARAMETER_F) {
if (!isValidCoolantTemperature(temperature)) {
efiAssert(engineConfiguration!=NULL, "NULL engineConfiguration", NAN);
if (engineConfiguration->hasCltSensor) {
warning(OBD_PCM_Processor_Fault, "unrealistic CLT %f", temperature);
warning(CUSTOM_OBD_26, "unrealistic CLT %f", temperature);
}
return LIMPING_MODE_CLT_TEMPERATURE;
}
@ -172,7 +172,7 @@ float getIntakeAirTemperature(DECLARE_ENGINE_PARAMETER_F) {
if (!isValidIntakeAirTemperature(temperature)) {
efiAssert(engineConfiguration!=NULL, "NULL engineConfiguration", NAN);
if (engineConfiguration->hasIatSensor) {
warning(OBD_PCM_Processor_Fault, "unrealistic IAT %f", temperature);
warning(CUSTOM_OBD_27, "unrealistic IAT %f", temperature);
}
return LIMPING_MODE_IAT_TEMPERATURE;
}

View File

@ -59,7 +59,7 @@ float getTpsRateOfChange(void) {
* */
percent_t getTpsValue(int adc DECLARE_ENGINE_PARAMETER_S) {
if (engineConfiguration->tpsMin == engineConfiguration->tpsMax) {
warning(OBD_PCM_Processor_Fault, "Invalid TPS configuration: same value %d", engineConfiguration->tpsMin);
warning(CUSTOM_OBD_28, "Invalid TPS configuration: same value %d", engineConfiguration->tpsMin);
return NAN;
}
float result = interpolate(TPS_TS_CONVERSION * engineConfiguration->tpsMax, 100, TPS_TS_CONVERSION * engineConfiguration->tpsMin, 0, adc);

View File

@ -473,7 +473,7 @@ static void setCrankingFuel(float timeMs) {
static void setGlobalTriggerAngleOffset(float value) {
if (cisnan(value)) {
warning(OBD_PCM_Processor_Fault, "Invalid argument");
warning(CUSTOM_OBD_37, "Invalid argument");
return;
}
engineConfiguration->globalTriggerAngleOffset = value;

View File

@ -35,7 +35,7 @@ bool assertNotInList(T *head, T*element) {
* was not scheduled by angle but was scheduled by time. In case of scheduling
* by time with slow RPM the whole next fast revolution might be within the wait period
*/
warning(OBD_PCM_Processor_Fault, "re-adding element into event_queue");
warning(CUSTOM_OBD_29, "re-adding element into event_queue");
return true;
}
}

View File

@ -277,7 +277,7 @@ void TriggerState::decodeTriggerEvent(trigger_event_e const signal, efitime_t no
errorDetection.add(isDecodingError);
if (isTriggerDecoderError()) {
warning(OBD_PCM_Processor_Fault, "trigger decoding issue. expected %d/%d/%d got %d/%d/%d",
warning(CUSTOM_OBD_35, "trigger decoding issue. expected %d/%d/%d got %d/%d/%d",
TRIGGER_SHAPE(expectedEventCount[0]), TRIGGER_SHAPE(expectedEventCount[1]),
TRIGGER_SHAPE(expectedEventCount[2]), currentCycle.eventCount[0], currentCycle.eventCount[1],
currentCycle.eventCount[2]);
@ -300,7 +300,7 @@ void TriggerState::decodeTriggerEvent(trigger_event_e const signal, efitime_t no
toothed_previous_time = nowNt;
}
if (!isValidIndex(PASS_ENGINE_PARAMETER_F) && !isInitializingTrigger) {
warning(OBD_PCM_Processor_Fault, "sync error: index #%d above total size %d", currentCycle.current_index, TRIGGER_SHAPE(size));
warning(CUSTOM_OBD_36, "sync error: index #%d above total size %d", currentCycle.current_index, TRIGGER_SHAPE(size));
lastDecodingErrorTime = getTimeNowNt();
someSortOfTriggerError = true;
}

View File

@ -361,7 +361,7 @@ static void hipStartupCode(void) {
chThdSleepMilliseconds(10);
if (correctResponse == 0) {
warning(OBD_PCM_Processor_Fault, "TPIC/HIP does not respond");
warning(CUSTOM_OBD_41, "TPIC/HIP does not respond");
}
if (boardConfiguration->useTpicAdvancedMode) {

View File

@ -200,7 +200,7 @@ static void pwmpcb_fast(PWMDriver *pwmp) {
int getInternalAdcValue(const char *msg, adc_channel_e hwChannel) {
if (hwChannel == EFI_ADC_NONE) {
warning(OBD_PCM_Processor_Fault, "ADC: %s input is not configured", msg);
warning(CUSTOM_OBD_38, "ADC: %s input is not configured", msg);
return -1;
}
#if EFI_ENABLE_MOCK_ADC || EFI_SIMULATOR
@ -218,7 +218,7 @@ int getInternalAdcValue(const char *msg, adc_channel_e hwChannel) {
return value;
}
if (adcHwChannelEnabled[hwChannel] != ADC_SLOW) {
warning(OBD_PCM_Processor_Fault, "ADC is off [%s] index=%d", msg, hwChannel);
warning(CUSTOM_OBD_39, "ADC is off [%s] index=%d", msg, hwChannel);
}
return slowAdc.getAdcValueByHwChannel(hwChannel);

View File

@ -230,7 +230,7 @@ static msg_t canThread(void *arg) {
canRead(); // todo: since this is a blocking operation, do we need a separate thread for 'write'?
if (engineConfiguration->canSleepPeriod < 10) {
warning(OBD_PCM_Processor_Fault, "%d too low CAN", engineConfiguration->canSleepPeriod);
warning(CUSTOM_OBD_40, "%d too low CAN", engineConfiguration->canSleepPeriod);
engineConfiguration->canSleepPeriod = 50;
}

View File

@ -52,7 +52,7 @@ void setHardwareUsTimer(int32_t timeUs) {
*/
if (timeUs <= 0) {
timerFreezeCounter++;
warning(OBD_PCM_Processor_Fault, "local freeze cnt=%d", timerFreezeCounter);
warning(CUSTOM_OBD_42, "local freeze cnt=%d", timerFreezeCounter);
}
if (timeUs < 2)
timeUs = 2; // for some reason '1' does not really work

View File

@ -283,7 +283,7 @@ static void MMCmount(void) {
// Performs the initialization procedure on the inserted card.
lockSpi(SPI_NONE);
if (mmcConnect(&MMCD1) != CH_SUCCESS) {
warning(OBD_PCM_Processor_Fault, "Can't connect or mount MMC/SD");
warning(CUSTOM_OBD_43, "Can't connect or mount MMC/SD");
unlockSpi();
return;

View File

@ -57,7 +57,7 @@ static ALWAYS_INLINE bool validateBuffer(Logging *logging, uint32_t extraLen) {
if (remainingSize(logging) < extraLen + 1) {
#if EFI_PROD_CODE
warning(OBD_PCM_Processor_Fault, "output overflow %s", logging->name);
warning(CUSTOM_OBD_44, "output overflow %s", logging->name);
#endif
return true;
}

View File

@ -43,13 +43,14 @@ void testPidController(void) {
pidS.pFactor = 50;
pidS.iFactor = 0.5;
pidS.dFactor = 0;
pidS.offset = 0;
Pid pid(&pidS, 10, 90);
assertEquals(90, pid.getValue(14, 12, 0.1));
assertEqualsM("getValue#90", 90, pid.getValue(14, 12, 0.1));
assertEquals(10, pid.getValue(14, 16, 0.1));
assertEqualsM("getValue#10", 10, pid.getValue(14, 16, 0.1));
assertEquals(10, pid.getValue(14, 16, 1));
pid.updateFactors(29, 0, 0);