001package jmri.jmrix.bachrus.speedmatcher.basic; 002 003import jmri.DccThrottle; 004import jmri.jmrix.bachrus.Speed; 005 006/** 007 * This is a simple speed matcher which will speed match a locomotive to a given 008 * start and top speed using the complex speed table. Speed steps 1, 10, 19, and 009 * 28 will be set according to values interpolated linearly between the given 010 * start and to speeds. Values for the remaining CVs will interpolated between 011 * these 4 CVs. This is done to reduce the time the speed match takes and to 012 * increase likelihood of success. 013 * 014 * @author Todd Wegter Copyright (C) 2024 015 */ 016public class BasicSpeedTableSpeedMatcher extends BasicSpeedMatcher { 017 018 //<editor-fold defaultstate="collapsed" desc="Constants"> 019 private final int INITIAL_STEP1 = 1; 020 private final int INITIAL_STEP28 = 255; 021 private final int INITIAL_TRIM = 128; 022 023 private final int STEP28_MAX = 255; 024 private final int STEP28_MIN = 28; 025 private final int STEP19_MIN = 19; 026 private final int STEP10_MIN = 10; 027 private final int STEP1_MIN = 1; 028 //</editor-fold> 029 030 //<editor-fold defaultstate="collapsed" desc="Enums"> 031 protected enum SpeedMatcherState { 032 IDLE, 033 WAIT_FOR_THROTTLE, 034 INIT_THROTTLE, 035 INIT_ACCEL, 036 INIT_DECEL, 037 INIT_SPEED_TABLE, 038 INIT_FORWARD_TRIM, 039 INIT_REVERSE_TRIM, 040 POST_INIT, 041 FORWARD_WARM_UP, 042 FORWARD_SPEED_MATCH_STEP28, 043 RE_INIT_SPEED_TABLE_TOP_THIRD, 044 FORWARD_SPEED_MATCH_STEP19, 045 RE_INIT_SPEED_TABLE_MIDDLE_THIRD, 046 FORWARD_SPEED_MATCH_STEP10, 047 RE_INIT_SPEED_TABLE_BOTTOM_THIRD, 048 FORWARD_SPEED_MATCH_STEP1, 049 INTERPOLATE_SPEED_TABLE, 050 POST_INTERPOLATE, 051 REVERSE_WARM_UP, 052 REVERSE_SPEED_MATCH_TRIM, 053 COMPLETE, 054 USER_STOPPED, 055 CLEAN_UP, 056 } 057 //</editor-fold> 058 059 //<editor-fold defaultstate="collapsed" desc="Instance Variables"> 060 private SpeedTableStep initSpeedTableStep; 061 private int initSpeedTableStepValue; 062 063 private SpeedTableStep interpolationSpeedTableStep; 064 065 private int speedMatchCVValue = INITIAL_STEP28; 066 private int lastSpeedMatchCVValue = INITIAL_STEP28; 067 068 private int reverseTrimValue = INITIAL_TRIM; 069 private int lastReverseTrimValue = INITIAL_TRIM; 070 071 private final float targetStep28SpeedKPH; 072 private final float targetStep19SpeedKPH; 073 private final float targetStep10SpeedKPH; 074 private final float targetStep1SpeedKPH; 075 076 private int step28CVValue; 077 private int step19CVValue; 078 private int step10CVValue; 079 private int step1CVValue; 080 081 private SpeedMatcherState speedMatcherState = SpeedMatcherState.IDLE; 082 //</editor-fold> 083 084 /** 085 * Constructs the BasicSpeedTableSpeedMatcher from a BasicSpeedMatcherConfig 086 * 087 * @param config SpeedStepScaleSpeedMatcherConfig 088 */ 089 public BasicSpeedTableSpeedMatcher(BasicSpeedMatcherConfig config) { 090 super(config); 091 092 targetStep28SpeedKPH = targetTopSpeedKPH; 093 targetStep19SpeedKPH = getSpeedForSpeedStep(SpeedTableStep.STEP19, targetStartSpeedKPH, targetTopSpeedKPH); 094 targetStep10SpeedKPH = getSpeedForSpeedStep(SpeedTableStep.STEP10, targetStartSpeedKPH, targetTopSpeedKPH); 095 targetStep1SpeedKPH = targetStartSpeedKPH; 096 } 097 098 //<editor-fold defaultstate="collapsed" desc="SpeedMatcher Overrides"> 099 /** 100 * Starts the speed matching process 101 * 102 * @return true if speed matching started successfully, false otherwise 103 */ 104 @Override 105 public boolean startSpeedMatcher() { 106 if (!validate()) { 107 return false; 108 } 109 110 //reset instance variables 111 speedMatchCVValue = INITIAL_STEP28; 112 lastSpeedMatchCVValue = INITIAL_STEP28; 113 reverseTrimValue = INITIAL_TRIM; 114 lastReverseTrimValue = INITIAL_TRIM; 115 116 speedMatcherState = SpeedMatcherState.WAIT_FOR_THROTTLE; 117 118 if (!initializeAndStartSpeedMatcher(e -> speedMatchTimeout())) { 119 cleanUpSpeedMatcher(); 120 return false; 121 } 122 123 startStopButton.setText(Bundle.getMessage("SpeedMatchStopBtn")); 124 125 return true; 126 } 127 128 /** 129 * Stops the speed matching process 130 */ 131 @Override 132 public void stopSpeedMatcher() { 133 if (!isSpeedMatcherIdle()) { 134 logger.info("Speed matching manually stopped"); 135 userStop(); 136 } else { 137 cleanUpSpeedMatcher(); 138 } 139 } 140 141 /** 142 * Indicates if the speed matcher is idle (not currently speed matching) 143 * 144 * @return true if idle, false otherwise 145 */ 146 @Override 147 public boolean isSpeedMatcherIdle() { 148 return speedMatcherState == SpeedMatcherState.IDLE; 149 } 150 151 /** 152 * Cleans up the speed matcher when speed matching is stopped or is finished 153 */ 154 @Override 155 protected void cleanUpSpeedMatcher() { 156 speedMatcherState = SpeedMatcherState.IDLE; 157 super.cleanUpSpeedMatcher(); 158 } 159 //</editor-fold> 160 161 //<editor-fold defaultstate="collapsed" desc="Speed Matcher State"> 162 /** 163 * Main speed matching timeout handler. This is the state machine that 164 * effectively does the speed matching process. 165 */ 166 private synchronized void speedMatchTimeout() { 167 switch (speedMatcherState) { 168 case WAIT_FOR_THROTTLE: 169 cleanUpSpeedMatcher(); 170 logger.error("Timeout waiting for throttle"); 171 statusLabel.setText(Bundle.getMessage("StatusTimeout")); 172 break; 173 174 case INIT_THROTTLE: 175 //set throttle to 0 for init 176 setThrottle(true, 0); 177 initNextSpeedMatcherState(SpeedMatcherState.INIT_ACCEL); 178 break; 179 180 case INIT_ACCEL: 181 //set acceleration momentum to 0 (CV 3) 182 if (programmerState == ProgrammerState.IDLE) { 183 writeMomentumAccel(INITIAL_MOMENTUM); 184 initNextSpeedMatcherState(SpeedMatcherState.INIT_DECEL); 185 } 186 break; 187 188 case INIT_DECEL: 189 //set deceleration mementum to 0 (CV 4) 190 if (programmerState == ProgrammerState.IDLE) { 191 writeMomentumDecel(INITIAL_MOMENTUM); 192 initNextSpeedMatcherState(SpeedMatcherState.INIT_SPEED_TABLE); 193 } 194 break; 195 196 case INIT_SPEED_TABLE: 197 //initialize every speed table step to 1 except speed table step 28 = 255 198 if (programmerState == ProgrammerState.IDLE) { 199 if (stepDuration == 0) { 200 initSpeedTableStepValue = INITIAL_STEP1; 201 initSpeedTableStep = SpeedTableStep.STEP1; 202 stepDuration = 1; 203 } 204 205 if (initSpeedTableStep == SpeedTableStep.STEP28) { 206 initSpeedTableStepValue = INITIAL_STEP28; 207 } 208 209 writeSpeedTableStep(initSpeedTableStep, initSpeedTableStepValue); 210 211 initSpeedTableStep = initSpeedTableStep.getNext(); 212 if (initSpeedTableStep == null) { 213 initNextSpeedMatcherState(SpeedMatcherState.INIT_FORWARD_TRIM); 214 } 215 } 216 break; 217 218 case INIT_FORWARD_TRIM: 219 //set forward trim to 128 (CV 66) 220 if (programmerState == ProgrammerState.IDLE) { 221 writeForwardTrim(INITIAL_TRIM); 222 initNextSpeedMatcherState(SpeedMatcherState.INIT_REVERSE_TRIM); 223 } 224 break; 225 226 case INIT_REVERSE_TRIM: 227 //set reverse trim to 128 (CV 95) 228 if (programmerState == ProgrammerState.IDLE) { 229 writeReverseTrim(INITIAL_TRIM); 230 initNextSpeedMatcherState(SpeedMatcherState.POST_INIT); 231 } 232 break; 233 234 case POST_INIT: { 235 statusLabel.setText(Bundle.getMessage("StatRestoreThrottle")); 236 237 //un-brick Digitrax decoders 238 setThrottle(false, 0); 239 setThrottle(true, 0); 240 241 SpeedMatcherState nextState; 242 if (warmUpForwardSeconds > 0) { 243 nextState = SpeedMatcherState.FORWARD_WARM_UP; 244 } else { 245 nextState = SpeedMatcherState.FORWARD_SPEED_MATCH_STEP28; 246 } 247 initNextSpeedMatcherState(nextState); 248 break; 249 } 250 251 case FORWARD_WARM_UP: 252 //Run 4 minutes at high speed forward 253 statusLabel.setText(Bundle.getMessage("StatForwardWarmUp", warmUpForwardSeconds - stepDuration)); 254 255 if (stepDuration >= warmUpForwardSeconds) { 256 initNextSpeedMatcherState(SpeedMatcherState.FORWARD_SPEED_MATCH_STEP28); 257 } else { 258 if (stepDuration == 0) { 259 setSpeedMatchStateTimerDuration(5000); 260 setThrottle(true, 28); 261 } 262 stepDuration += 5; 263 } 264 break; 265 266 case FORWARD_SPEED_MATCH_STEP28: 267 //Use PID Controller to adjust Speed Step 28 to the max speed 268 if (programmerState == ProgrammerState.IDLE) { 269 speedMatchSpeedStepInner(SpeedTableStep.STEP28, targetStep28SpeedKPH, STEP28_MAX, STEP28_MIN, SpeedMatcherState.RE_INIT_SPEED_TABLE_TOP_THIRD); 270 step28CVValue = speedMatchCVValue; 271 } 272 break; 273 274 case RE_INIT_SPEED_TABLE_TOP_THIRD: 275 //Re-initialize Speed Steps 19-27 based off value for Step 28 276 if (programmerState == ProgrammerState.IDLE) { 277 if (stepDuration == 0) { 278 initSpeedTableStep = SpeedTableStep.STEP27; 279 stepDuration = 1; 280 } 281 282 writeSpeedTableStep(initSpeedTableStep, step28CVValue); 283 284 if (initSpeedTableStep == SpeedTableStep.STEP19) { 285 initNextSpeedMatcherState(SpeedMatcherState.FORWARD_SPEED_MATCH_STEP19); 286 } else { 287 initSpeedTableStep = initSpeedTableStep.getPrevious(); 288 } 289 } 290 break; 291 292 case FORWARD_SPEED_MATCH_STEP19: 293 //Use PID Controller to adjust Speed Step 19 to the interpolated speed 294 if (programmerState == ProgrammerState.IDLE) { 295 speedMatchSpeedStepInner(SpeedTableStep.STEP19, targetStep19SpeedKPH, step28CVValue - 9, STEP19_MIN, SpeedMatcherState.RE_INIT_SPEED_TABLE_MIDDLE_THIRD); 296 step19CVValue = speedMatchCVValue; 297 } 298 break; 299 300 case RE_INIT_SPEED_TABLE_MIDDLE_THIRD: 301 //Re-initialize Speed Steps 10-18 based off value for Step 19 302 if (programmerState == ProgrammerState.IDLE) { 303 if (stepDuration == 0) { 304 initSpeedTableStep = SpeedTableStep.STEP18; 305 stepDuration = 1; 306 } 307 308 writeSpeedTableStep(initSpeedTableStep, step19CVValue); 309 310 if (initSpeedTableStep == SpeedTableStep.STEP10) { 311 initNextSpeedMatcherState(SpeedMatcherState.FORWARD_SPEED_MATCH_STEP10); 312 } else { 313 initSpeedTableStep = initSpeedTableStep.getPrevious(); 314 } 315 316 } 317 break; 318 319 case FORWARD_SPEED_MATCH_STEP10: 320 //Use PID Controller to adjust Speed Step 10 to the interpolated speed 321 if (programmerState == ProgrammerState.IDLE) { 322 speedMatchSpeedStepInner(SpeedTableStep.STEP10, targetStep10SpeedKPH, step19CVValue - 9, STEP10_MIN, SpeedMatcherState.RE_INIT_SPEED_TABLE_BOTTOM_THIRD); 323 step10CVValue = speedMatchCVValue; 324 } 325 break; 326 327 case RE_INIT_SPEED_TABLE_BOTTOM_THIRD: 328 //Re-initialize Speed Steps 1-9 based off value for Step 10 329 if (programmerState == ProgrammerState.IDLE) { 330 if (stepDuration == 0) { 331 initSpeedTableStep = SpeedTableStep.STEP9; 332 stepDuration = 1; 333 } 334 335 writeSpeedTableStep(initSpeedTableStep, step10CVValue); 336 337 if (initSpeedTableStep == SpeedTableStep.STEP1) { 338 initNextSpeedMatcherState(SpeedMatcherState.FORWARD_SPEED_MATCH_STEP1); 339 } else { 340 initSpeedTableStep = initSpeedTableStep.getPrevious(); 341 } 342 343 } 344 break; 345 346 case FORWARD_SPEED_MATCH_STEP1: 347 //Use PID Controller to adjust Speed Step 1 to the minimum speed 348 if (programmerState == ProgrammerState.IDLE) { 349 speedMatchSpeedStepInner(SpeedTableStep.STEP1, targetStep1SpeedKPH, step10CVValue - 9, STEP1_MIN, SpeedMatcherState.INTERPOLATE_SPEED_TABLE); 350 step1CVValue = speedMatchCVValue; 351 } 352 break; 353 354 case INTERPOLATE_SPEED_TABLE: { 355 //Interpolate the values of the intermediate speed steps 356 if (programmerState == ProgrammerState.IDLE) { 357 if (stepDuration == 0) { 358 setThrottle(true, 0); 359 interpolationSpeedTableStep = SpeedTableStep.STEP27; 360 stepDuration = 1; 361 } 362 363 int interpolatedSpeedStepCVValue = getInterpolatedSpeedTableCVValue(interpolationSpeedTableStep); 364 writeSpeedTableStep(interpolationSpeedTableStep, interpolatedSpeedStepCVValue); 365 366 do { 367 interpolationSpeedTableStep = interpolationSpeedTableStep.getPrevious(); 368 } while (interpolationSpeedTableStep == SpeedTableStep.STEP19 || interpolationSpeedTableStep == SpeedTableStep.STEP10); 369 370 if (interpolationSpeedTableStep == SpeedTableStep.STEP1) { 371 initNextSpeedMatcherState(SpeedMatcherState.POST_INTERPOLATE); 372 } 373 374 } 375 break; 376 } 377 378 case POST_INTERPOLATE: { 379 statusLabel.setText(Bundle.getMessage("StatRestoreThrottle")); 380 381 //un-brick Digitrax decoders 382 setThrottle(false, 0); 383 setThrottle(true, 0); 384 385 SpeedMatcherState nextState; 386 if (trimReverseSpeed) { 387 if (warmUpReverseSeconds > 0) { 388 nextState = SpeedMatcherState.REVERSE_WARM_UP; 389 } else { 390 nextState = SpeedMatcherState.REVERSE_SPEED_MATCH_TRIM; 391 } 392 } else { 393 nextState = SpeedMatcherState.COMPLETE; 394 } 395 initNextSpeedMatcherState(nextState); 396 break; 397 } 398 399 case REVERSE_WARM_UP: 400 //Run specified reverse warm up time at high speed in reverse 401 statusLabel.setText(Bundle.getMessage("StatReverseWarmUp", warmUpReverseSeconds - stepDuration)); 402 403 if (stepDuration >= warmUpReverseSeconds) { 404 initNextSpeedMatcherState(SpeedMatcherState.REVERSE_SPEED_MATCH_TRIM); 405 } else { 406 if (stepDuration == 0) { 407 setSpeedMatchStateTimerDuration(5000); 408 setThrottle(false, 28); 409 } 410 stepDuration += 5; 411 } 412 413 break; 414 415 case REVERSE_SPEED_MATCH_TRIM: 416 //Use PID controller logic to adjust reverse trim until high speed reverse speed matches forward 417 if (programmerState == ProgrammerState.IDLE) { 418 if (stepDuration == 0) { 419 statusLabel.setText(Bundle.getMessage("StatSettingReverseTrim")); 420 setThrottle(false, 28); 421 setSpeedMatchStateTimerDuration(8000); 422 stepDuration = 1; 423 } else { 424 setSpeedMatchError(targetTopSpeedKPH); 425 426 if (Math.abs(speedMatchError) < ALLOWED_SPEED_MATCH_ERROR) { 427 initNextSpeedMatcherState(SpeedMatcherState.COMPLETE); 428 } else { 429 reverseTrimValue = getNextSpeedMatchValue(lastReverseTrimValue, REVERSE_TRIM_MAX, REVERSE_TRIM_MIN); 430 431 if (((lastReverseTrimValue == REVERSE_TRIM_MAX) || (lastReverseTrimValue == REVERSE_TRIM_MIN)) && (reverseTrimValue == lastReverseTrimValue)) { 432 statusLabel.setText(Bundle.getMessage("StatSetReverseTrimFail")); 433 logger.info("Unable to trim reverse to match forward"); 434 abort(); 435 break; 436 } 437 438 lastReverseTrimValue = reverseTrimValue; 439 writeReverseTrim(reverseTrimValue); 440 } 441 } 442 } 443 break; 444 445 case COMPLETE: 446 if (programmerState == ProgrammerState.IDLE) { 447 statusLabel.setText(Bundle.getMessage("StatSpeedMatchComplete")); 448 setThrottle(true, 0); 449 initNextSpeedMatcherState(SpeedMatcherState.CLEAN_UP); 450 } 451 break; 452 453 case USER_STOPPED: 454 if (programmerState == ProgrammerState.IDLE) { 455 statusLabel.setText(Bundle.getMessage("StatUserStoppedSpeedMatch")); 456 setThrottle(true, 0); 457 initNextSpeedMatcherState(SpeedMatcherState.CLEAN_UP); 458 } 459 break; 460 461 case CLEAN_UP: 462 //wrap it up 463 if (programmerState == ProgrammerState.IDLE) { 464 cleanUpSpeedMatcher(); 465 } 466 break; 467 468 default: 469 cleanUpSpeedMatcher(); 470 logger.error("Unexpected speed match timeout"); 471 break; 472 } 473 474 if (speedMatcherState != SpeedMatcherState.IDLE) { 475 startSpeedMatchStateTimer(); 476 } 477 } 478 //</editor-fold> 479 480 //<editor-fold defaultstate="collapsed" desc="ThrottleListener Overrides"> 481 /** 482 * Called when a throttle is found 483 * 484 * @param t the requested DccThrottle 485 */ 486 @Override 487 public void notifyThrottleFound(DccThrottle t) { 488 super.notifyThrottleFound(t); 489 490 if (speedMatcherState == SpeedMatcherState.WAIT_FOR_THROTTLE) { 491 logger.info("Starting speed matching"); 492 // using speed matching timer to trigger each phase of speed matching 493 initNextSpeedMatcherState(SpeedMatcherState.INIT_THROTTLE); 494 startSpeedMatchStateTimer(); 495 } else { 496 cleanUpSpeedMatcher(); 497 } 498 } 499 //</editor-fold> 500 501 //<editor-fold defaultstate="collapsed" desc="Helper Functions"> 502 /** 503 * Gets the interpolated CV value for the given speed step in the speed 504 * table 505 * 506 * @param speedStep the SpeedTableStep to get the speed for 507 * @return the target speed for the given speed step in KPH 508 */ 509 private int getInterpolatedSpeedTableCVValue(SpeedTableStep speedStep) { 510 SpeedTableStep maxStep; 511 SpeedTableStep minStep; 512 int maxStepCVValue; 513 int minStepCVValue; 514 515 if (speedStep.getSpeedStep() >= SpeedTableStep.STEP19.getSpeedStep()) { 516 maxStep = SpeedTableStep.STEP28; 517 minStep = SpeedTableStep.STEP19; 518 maxStepCVValue = step28CVValue; 519 minStepCVValue = step19CVValue; 520 } else if (speedStep.getSpeedStep() >= SpeedTableStep.STEP10.getSpeedStep()) { 521 maxStep = SpeedTableStep.STEP19; 522 minStep = SpeedTableStep.STEP10; 523 maxStepCVValue = step19CVValue; 524 minStepCVValue = step10CVValue; 525 } else { 526 maxStep = SpeedTableStep.STEP10; 527 minStep = SpeedTableStep.STEP1; 528 maxStepCVValue = step10CVValue; 529 minStepCVValue = step1CVValue; 530 } 531 532 return Math.round(minStepCVValue + ((((float) (maxStepCVValue - minStepCVValue)) / (maxStep.getSpeedStep() - minStep.getSpeedStep())) * (speedStep.getSpeedStep() - minStep.getSpeedStep()))); 533 } 534 535 /** 536 * Helper function for speed matching a given speed step 537 * 538 * @param speedStep the SpeedTableStep to speed match 539 * @param targetSpeedKPH the target speed in KPH 540 * @param maxCVValue the maximum allowable value for the CV 541 * @param minCVValue the minimum allowable value for the CV 542 * @param nextState the SpeedMatcherState to advance to if speed 543 * matching is complete 544 */ 545 private void speedMatchSpeedStepInner(SpeedTableStep speedStep, float targetSpeedKPH, int maxCVValue, int minCVValue, SpeedMatcherState nextState) { 546 if (stepDuration == 0) { 547 statusLabel.setText(Bundle.getMessage("StatSettingSpeed", speedStep.getCV() + " (Speed Step " + String.valueOf(speedStep.getSpeedStep()) + ")")); 548 logger.info("Setting CV {} (speed step {}) to {} KPH ({} MPH)", speedStep.getCV(), speedStep.getSpeedStep(), String.valueOf(targetSpeedKPH), String.valueOf(Speed.kphToMph(targetSpeedKPH))); 549 setThrottle(true, speedStep.getSpeedStep()); 550 setSpeedMatchStateTimerDuration(8000); 551 stepDuration = 1; 552 } else { 553 setSpeedMatchError(targetSpeedKPH); 554 555 if (Math.abs(speedMatchError) < ALLOWED_SPEED_MATCH_ERROR) { 556 initNextSpeedMatcherState(nextState); 557 } else { 558 speedMatchCVValue = getNextSpeedMatchValue(lastSpeedMatchCVValue, maxCVValue, minCVValue); 559 560 if (((speedMatchCVValue == maxCVValue) || (speedMatchCVValue == minCVValue)) && (speedMatchCVValue == lastSpeedMatchCVValue)) { 561 statusLabel.setText(Bundle.getMessage("StatSetSpeedFail", speedStep.getCV() + " (Speed Step " + String.valueOf(speedStep.getSpeedStep()) + ")")); 562 logger.info("Unable to achieve desired speed for CV {} (Speed Step {})", speedStep.getCV(), String.valueOf(speedStep.getSpeedStep())); 563 abort(); 564 return; 565 } 566 567 lastSpeedMatchCVValue = speedMatchCVValue; 568 writeSpeedTableStep(speedStep, speedMatchCVValue); 569 } 570 } 571 } 572 573 /** 574 * Aborts the speed matching process programmatically 575 */ 576 private void abort() { 577 initNextSpeedMatcherState(SpeedMatcherState.CLEAN_UP); 578 } 579 580 /** 581 * Stops the speed matching process due to user input 582 */ 583 private void userStop() { 584 initNextSpeedMatcherState(SpeedMatcherState.USER_STOPPED); 585 } 586 587 /** 588 * Sets up the speed match state by clearing the speed match error, clearing 589 * the step duration, setting the timer duration, and setting the next state 590 * 591 * @param nextState - next SpeedMatcherState to set 592 */ 593 protected void initNextSpeedMatcherState(SpeedMatcherState nextState) { 594 resetSpeedMatchError(); 595 stepDuration = 0; 596 speedMatcherState = nextState; 597 setSpeedMatchStateTimerDuration(1800); 598 } 599 //</editor-fold> 600 601 //debugging logger 602 private final static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(BasicSpeedTableSpeedMatcher.class); 603}