rusEFI
The most advanced open source ECU
tunerstudio.cpp
Go to the documentation of this file.
1 /**
2  * @file tunerstudio.cpp
3  * @brief Binary protocol implementation
4  *
5  * This implementation would not happen without the documentation
6  * provided by Jon Zeeff (jon@zeeff.com)
7  *
8  *
9  * @brief Integration with EFI Analytics Tuner Studio software
10  *
11  * Tuner Studio has a really simple protocol, a minimal implementation
12  * capable of displaying current engine state on the gauges would
13  * require only two commands: queryCommand and ochGetCommand
14  *
15  * queryCommand:
16  * Communication initialization command. TunerStudio sends a single byte H
17  * ECU response:
18  * One of the known ECU id strings.
19  *
20  * ochGetCommand:
21  * Request for output channels state.TunerStudio sends a single byte O
22  * ECU response:
23  * A snapshot of output channels as described in [OutputChannels] section of the .ini file
24  * The length of this block is 'ochBlockSize' property of the .ini file
25  *
26  * These two commands are enough to get working gauges. In order to start configuring the ECU using
27  * tuner studio, three more commands should be implemented:
28  *
29  *
30  * @date Oct 22, 2013
31  * @author Andrey Belomutskiy, (c) 2012-2020
32  *
33  * This file is part of rusEfi - see http://rusefi.com
34  *
35  * rusEfi is free software; you can redistribute it and/or modify it under the terms of
36  * the GNU General Public License as published by the Free Software Foundation; either
37  * version 3 of the License, or (at your option) any later version.
38  *
39  * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
40  * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41  * GNU General Public License for more details.
42  *
43  * You should have received a copy of the GNU General Public License along with this program.
44  * If not, see <http://www.gnu.org/licenses/>.
45  *
46  *
47  * This file is part of rusEfi - see http://rusefi.com
48  *
49  * rusEfi is free software; you can redistribute it and/or modify it under the terms of
50  * the GNU General Public License as published by the Free Software Foundation; either
51  * version 3 of the License, or (at your option) any later version.
52  *
53  * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
54  * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
55  * GNU General Public License for more details.
56  *
57  * You should have received a copy of the GNU General Public License along with this program.
58  * If not, see <http://www.gnu.org/licenses/>.
59  *
60  */
61 
62 #include "pch.h"
63 
64 
65 #include "tunerstudio.h"
66 #include "tunerstudio_impl.h"
67 
68 #include "main_trigger_callback.h"
69 #include "flash_main.h"
70 
71 #include "tunerstudio_io.h"
72 #include "malfunction_central.h"
73 #include "console_io.h"
74 #include "bluetooth.h"
75 #include "tunerstudio_io.h"
76 #include "trigger_scope.h"
77 #include "software_knock.h"
78 #include "electronic_throttle.h"
79 #include "live_data.h"
80 #include "efi_quote.h"
81 
82 #include <string.h>
83 #include "bench_test.h"
84 #include "status_loop.h"
85 #include "mmc_card.h"
86 
87 #if EFI_SIMULATOR
88 #include "rusEfiFunctionalTest.h"
89 #endif /* EFI_SIMULATOR */
90 
91 #if EFI_TUNER_STUDIO
92 
93 static void printErrorCounters() {
94  efiPrintf("TunerStudio size=%d / total=%d / errors=%d / H=%d / O=%d / P=%d / B=%d",
97  efiPrintf("TunerStudio W=%d / C=%d / P=%d", tsState.writeValueCommandCounter,
99  efiPrintf("TunerStudio errors: underrun=%d / overrun=%d / crc=%d / unrecognized=%d / outofrange=%d / other=%d",
102 }
103 
104 static void printScatterList() {
105  efiPrintf("Scatter list (global)");
106  for (int i = 0; i < HIGH_SPEED_COUNT; i++) {
107  uint16_t packed = engineConfiguration->highSpeedOffsets[i];
108  uint16_t type = packed >> 13;
109  uint16_t offset = packed & 0x1FFF;
110 
111  if (type == 0)
112  continue;
113  size_t size = 1 << (type - 1);
114 
115  efiPrintf("%02d offset 0x%04x size %d", i, offset, size);
116  }
117 }
118 
119 /* 1S */
120 #define TS_COMMUNICATION_TIMEOUT TIME_MS2I(1000)
121 /* 10mS when receiving byte by byte */
122 #define TS_COMMUNICATION_TIMEOUT_SHORT TIME_MS2I(10)
123 
124 static void resetTs() {
125  memset(&tsState, 0, sizeof(tsState));
126 }
127 
128 static void printTsStats(void) {
129 #ifdef EFI_CONSOLE_RX_BRAIN_PIN
130  efiPrintf("Primary UART RX %s", hwPortname(EFI_CONSOLE_RX_BRAIN_PIN));
131  efiPrintf("Primary UART TX %s", hwPortname(EFI_CONSOLE_TX_BRAIN_PIN));
132 #endif /* EFI_CONSOLE_RX_BRAIN_PIN */
133 
134 #if EFI_USB_SERIAL
136 #endif // EFI_USB_SERIAL
137 
139 
141 }
142 
143 static void setTsSpeed(int value) {
145  printTsStats();
146 }
147 
148 void tunerStudioDebug(TsChannelBase* tsChannel, const char *msg) {
149 #if EFI_TUNER_STUDIO_VERBOSE
150  efiPrintf("%s: %s", tsChannel->name, msg);
151 #endif /* EFI_TUNER_STUDIO_VERBOSE */
152 }
153 
154 uint8_t* getWorkingPageAddr() {
155  return (uint8_t*)engineConfiguration;
156 }
157 
158 static void sendOkResponse(TsChannelBase *tsChannel) {
159  tsChannel->sendResponse(TS_CRC, nullptr, 0);
160 }
161 
162 void sendErrorCode(TsChannelBase *tsChannel, uint8_t code, const char *msg) {
163 //TODO uncomment once I have test it myself if (msg != DO_NOT_LOG) {
164 // efiPrintf("TS <- Err: %d [%s]", code, msg);
165 // }
166 
167  switch (code) {
168  case TS_RESPONSE_UNDERRUN:
170  break;
171  case TS_RESPONSE_OVERRUN:
173  break;
174  case TS_RESPONSE_CRC_FAILURE:
176  break;
177  case TS_RESPONSE_UNRECOGNIZED_COMMAND:
179  break;
180  case TS_RESPONSE_OUT_OF_RANGE:
182  break;
183  default:
185  break;
186  }
187 
188  tsChannel->writeCrcResponse(code);
189 }
190 
191 void TunerStudio::sendErrorCode(TsChannelBase* tsChannel, uint8_t code, const char *msg) {
192  ::sendErrorCode(tsChannel, code, msg);
193 }
194 
197 
198  efiPrintf("TS -> Set page (no-op)");
199 
200  sendOkResponse(tsChannel);
201 }
202 
203 bool validateOffsetCount(size_t offset, size_t count, TsChannelBase* tsChannel);
204 
205 extern bool rebootForPresetPending;
206 
207 /**
208  * This command is needed to make the whole transfer a bit faster
209  * @note See also handleWriteValueCommand
210  */
211 void TunerStudio::handleWriteChunkCommand(TsChannelBase* tsChannel, uint16_t offset, uint16_t count,
212  void *content) {
214  if (isLockedFromUser()) {
215  sendErrorCode(tsChannel, TS_RESPONSE_UNRECOGNIZED_COMMAND, "locked");
216  return;
217  }
218 
219  efiPrintf("TS -> Write chunk offset %d count %d (output_count=%d)", offset, count, tsState.outputChannelsCommandCounter);
220 
221  if (validateOffsetCount(offset, count, tsChannel)) {
222  return;
223  }
224 
225  // Skip the write if a preset was just loaded - we don't want to overwrite it
226  if (!rebootForPresetPending) {
227  uint8_t * addr = (uint8_t *) (getWorkingPageAddr() + offset);
228  memcpy(addr, content, count);
229  }
230  // Force any board configuration options that humans shouldn't be able to change
232 
233  sendOkResponse(tsChannel);
234 }
235 
236 void TunerStudio::handleCrc32Check(TsChannelBase *tsChannel, uint16_t offset, uint16_t count) {
238 
239  // Ensure we are reading from in bounds
240  if (validateOffsetCount(offset, count, tsChannel)) {
241  return;
242  }
243 
244 #if EFI_TS_SCATTER
245  /*
246  * highSpeedOffsets is noMsqSave, but located on settings page,
247  * zero highSpeedOffsets as TS expect all noMsqSave data to be zero during CRC matching
248  * TODO:
249  * Move highSpeedOffsets to separate page as it is done on MS devices
250  * Zero highSpeedOffsets on start and reconnect
251  * TODO:
252  * Is Crc check command good sing of new TS session?
253  * TODO:
254  * Support settings pages!
255  */
257 #endif // EFI_TS_SCATTER
258 
259  const uint8_t* start = getWorkingPageAddr() + offset;
260 
261  uint32_t crc = SWAP_UINT32(crc32(start, count));
262  tsChannel->sendResponse(TS_CRC, (const uint8_t *) &crc, 4);
263  efiPrintf("TS <- Get CRC offset %d count %d result %08x", offset, count, (unsigned int)crc);
264 }
265 
266 #if EFI_TS_SCATTER
268  int totalResponseSize = 0;
269  for (int i = 0; i < HIGH_SPEED_COUNT; i++) {
270  uint16_t packed = engineConfiguration->highSpeedOffsets[i];
271  uint16_t type = packed >> 13;
272 
273  size_t size = type == 0 ? 0 : 1 << (type - 1);
274 #if EFI_SIMULATOR
275 // printf("handleScatteredReadCommand 0x%x %d %d\n", packed, size, offset);
276 #endif /* EFI_SIMULATOR */
277  totalResponseSize += size;
278  }
279 #if EFI_SIMULATOR
280 // printf("totalResponseSize %d\n", totalResponseSize);
281 #endif /* EFI_SIMULATOR */
282 
283 
284  // Command part of CRC
285  uint32_t crc = tsChannel->writePacketHeader(TS_RESPONSE_OK, totalResponseSize);
286 
287  uint8_t dataBuffer[8];
288  for (int i = 0; i < HIGH_SPEED_COUNT; i++) {
289  uint16_t packed = engineConfiguration->highSpeedOffsets[i];
290  uint16_t type = packed >> 13;
291  uint16_t offset = packed & 0x1FFF;
292 
293  if (type == 0)
294  continue;
295  size_t size = 1 << (type - 1);
296 
297  // write each data point and CRC incrementally
298  copyRange(dataBuffer, getLiveDataFragments(), offset, size);
299  tsChannel->write(dataBuffer, size, false);
300  crc = crc32inc((void*)dataBuffer, crc, size);
301  }
302 #if EFI_SIMULATOR
303 // printf("CRC %x\n", crc);
304 #endif /* EFI_SIMULATOR */
305  // now write total CRC
306  *(uint32_t*)dataBuffer = SWAP_UINT32(crc);
307  tsChannel->write(reinterpret_cast<uint8_t*>(dataBuffer), 4, true);
308  tsChannel->flush();
309 }
310 #endif // EFI_TS_SCATTER
311 
312 /**
313  * 'Write' command receives a single value at a given offset
314  * @note Writing values one by one is pretty slow
315  */
316 void TunerStudio::handleWriteValueCommand(TsChannelBase* tsChannel, uint16_t offset, uint8_t value) {
317  UNUSED(tsChannel);
318 
320  if (isLockedFromUser()) {
321  sendErrorCode(tsChannel, TS_RESPONSE_UNRECOGNIZED_COMMAND, "locked");
322  return;
323  }
324 
325  efiPrintf("TS -> Write value offset %d value %d", offset, value);
326 
327  if (validateOffsetCount(offset, 1, tsChannel)) {
328  return;
329  }
330 
331  // Skip the write if a preset was just loaded - we don't want to overwrite it
332  if (!rebootForPresetPending) {
333  getWorkingPageAddr()[offset] = value;
334  }
335  // Force any board configuration options that humans shouldn't be able to change
337 }
338 
339 void TunerStudio::handlePageReadCommand(TsChannelBase* tsChannel, uint16_t offset, uint16_t count) {
341 
343  sendErrorCode(tsChannel, TS_RESPONSE_UNRECOGNIZED_COMMAND, "reboot");
344  return;
345  }
346 
347  efiPrintf("TS <- Read chunk offset %d count %d", offset, count);
348 
349  if (validateOffsetCount(offset, count, tsChannel)) {
350  return;
351  }
352 
353  uint8_t* addr;
354  if (isLockedFromUser()) {
355  // to have rusEFI console happy just send all zeros within a valid packet
356  addr = (uint8_t*)&tsChannel->scratchBuffer + TS_PACKET_HEADER_SIZE;
357  memset(addr, 0, count);
358  } else {
359  addr = getWorkingPageAddr() + offset;
360  }
361  tsChannel->sendResponse(TS_CRC, addr, count);
362 #if EFI_TUNER_STUDIO_VERBOSE
363 // efiPrintf("Sending %d done", count);
364 #endif
365 }
366 #endif // EFI_TUNER_STUDIO
367 
368 void requestBurn() {
369 #if !EFI_UNIT_TEST
370  onBurnRequest();
371 
372 #if EFI_CONFIGURATION_STORAGE
374 #endif /* EFI_CONFIGURATION_STORAGE */
375 #endif // !EFI_UNIT_TEST
376 }
377 
378 #if EFI_TUNER_STUDIO
379 
380 /**
381  * 'Burn' command is a command to commit the changes
382  */
383 static void handleBurnCommand(TsChannelBase* tsChannel) {
384  Timer t;
385  t.reset();
386 
388 
389  efiPrintf("TS -> Burn");
391 
392  // Skip the burn if a preset was just loaded - we don't want to overwrite it
393  if (!rebootForPresetPending) {
394  requestBurn();
395  }
396 
397  tsChannel->writeCrcResponse(TS_RESPONSE_BURN_OK);
398  efiPrintf("Burned in %.1fms", t.getElapsedSeconds() * 1e3);
399 }
400 
401 #if (EFI_PROD_CODE || EFI_SIMULATOR)
402 
403 static bool isKnownCommand(char command) {
404  return command == TS_HELLO_COMMAND || command == TS_READ_COMMAND || command == TS_OUTPUT_COMMAND
405  || command == TS_PAGE_COMMAND || command == TS_BURN_COMMAND || command == TS_SINGLE_WRITE_COMMAND
406  || command == TS_CHUNK_WRITE_COMMAND || command == TS_EXECUTE
407  || command == TS_IO_TEST_COMMAND
408 #if EFI_SIMULATOR
409  || command == TS_SIMULATE_CAN
410 #endif // EFI_SIMULATOR
411 #if EFI_TS_SCATTER
412  || command == TS_GET_SCATTERED_GET_COMMAND
413 #endif
414  || command == TS_SET_LOGGER_SWITCH
415  || command == TS_GET_COMPOSITE_BUFFER_DONE_DIFFERENTLY
416  || command == TS_GET_TEXT
417  || command == TS_CRC_CHECK_COMMAND
418  || command == TS_GET_FIRMWARE_VERSION
419 #ifdef KNOCK_SPECTROGRAM
420  || command == TS_KNOCK_SPECTROGRAM_ENABLE
421  || command == TS_KNOCK_SPECTROGRAM_DISABLE
422 #endif
423  || command == TS_PERF_TRACE_BEGIN
424  || command == TS_PERF_TRACE_GET_BUFFER
425  || command == TS_GET_CONFIG_ERROR
426  || command == TS_QUERY_BOOTLOADER;
427 }
428 
429 /**
430  * rusEfi own test command
431  */
432 static void handleTestCommand(TsChannelBase* tsChannel) {
434  char testOutputBuffer[64];
435  /**
436  * this is NOT a standard TunerStudio command, this is my own
437  * extension of the protocol to simplify troubleshooting
438  */
439  tunerStudioDebug(tsChannel, "got T (Test)");
440  tsChannel->write((const uint8_t*)QUOTE(SIGNATURE_HASH), sizeof(QUOTE(SIGNATURE_HASH)));
441 
442  chsnprintf(testOutputBuffer, sizeof(testOutputBuffer), " %d %d", engine->engineState.warnings.lastErrorCode, tsState.testCommandCounter);
443  tsChannel->write((const uint8_t*)testOutputBuffer, strlen(testOutputBuffer));
444 
445  chsnprintf(testOutputBuffer, sizeof(testOutputBuffer), " uptime=%ds ", (int)getTimeNowS());
446  tsChannel->write((const uint8_t*)testOutputBuffer, strlen(testOutputBuffer));
447 
448  chsnprintf(testOutputBuffer, sizeof(testOutputBuffer), __DATE__ " %s\r\n", PROTOCOL_TEST_RESPONSE_TAG);
449  tsChannel->write((const uint8_t*)testOutputBuffer, strlen(testOutputBuffer));
450 
451  if (hasFirmwareError()) {
452  const char* error = getCriticalErrorMessage();
453  chsnprintf(testOutputBuffer, sizeof(testOutputBuffer), "error=%s\r\n", error);
454  tsChannel->write((const uint8_t*)testOutputBuffer, strlen(testOutputBuffer));
455  }
456  tsChannel->flush();
457 }
458 
459 /**
460  * this command is part of protocol initialization
461  *
462  * Query with CRC takes place while re-establishing connection
463  * Query without CRC takes place on TunerStudio startup
464  */
467  const char *signature = getTsSignature();
468 
469  efiPrintf("TS <- Query signature: %s", signature);
470  tsChannel->sendResponse(mode, (const uint8_t *)signature, strlen(signature) + 1);
471 }
472 
473 /**
474  * handle non CRC wrapped command
475  *
476  * @return true if legacy command was processed, false otherwise
477  */
478 bool TunerStudio::handlePlainCommand(TsChannelBase* tsChannel, uint8_t command) {
479  // Bail fast if guaranteed not to be a plain command
480  if (command == 0) {
481  return false;
482  } else if (command == TS_HELLO_COMMAND || command == TS_QUERY_COMMAND) {
483  // We interpret 'Q' as TS_HELLO_COMMAND, since TS uses hardcoded 'Q' during ECU detection (scan all serial ports)
484  efiPrintf("Got naked Query command");
485  handleQueryCommand(tsChannel, TS_PLAIN);
486  return true;
487  } else if (command == TS_TEST_COMMAND || command == 'T') {
488  handleTestCommand(tsChannel);
489  return true;
490  } else if (command == TS_COMMAND_F) {
491  /**
492  * http://www.msextra.com/forums/viewtopic.php?f=122&t=48327
493  * Response from TS support: This is an optional command *
494  * "The F command is used to find what ini. file needs to be loaded in TunerStudio to match the controller.
495  * If you are able to just make your firmware ignore the command that would work.
496  * Currently on some firmware versions the F command is not used and is just ignored by the firmware as a unknown command."
497  */
498 
499  tunerStudioDebug(tsChannel, "not ignoring F");
500  tsChannel->write((const uint8_t *)TS_PROTOCOL, strlen(TS_PROTOCOL));
501  tsChannel->flush();
502  return true;
503  } else {
504  // This wasn't a valid command
505  return false;
506  }
507 }
508 
510 
511 static int tsProcessOne(TsChannelBase* tsChannel) {
512  assertStack("communication", ObdCode::STACK_USAGE_COMMUNICATION, EXPECTED_REMAINING_STACK, -1);
513 
514  if (!tsChannel->isReady()) {
515  chThdSleepMilliseconds(10);
516  return -1;
517  }
518 
520 
521  uint8_t firstByte;
522  size_t received = tsChannel->readTimeout(&firstByte, 1, TS_COMMUNICATION_TIMEOUT);
523 #if EFI_SIMULATOR
524  logMsg("received %d\r\n", received);
525 #endif // EFI_SIMULATOR
526 
527  if (received != 1) {
528 // tunerStudioError("ERROR: no command");
529 #if EFI_BLUETOOTH_SETUP
530  if (tsChannel == getBluetoothChannel()) {
531  // no data in a whole second means time to disconnect BT
532  // assume there's connection loss and notify the bluetooth init code
534  }
535 #endif /* EFI_BLUETOOTH_SETUP */
536  tsChannel->in_sync = false;
537  return -1;
538  }
539 
540  if (tsInstance.handlePlainCommand(tsChannel, firstByte)) {
541  return 0;
542  }
543 
544  uint8_t secondByte;
545  /* second byte should be received within minimal delay */
546  received = tsChannel->readTimeout(&secondByte, 1, TS_COMMUNICATION_TIMEOUT_SHORT);
547  if (received != 1) {
548  tunerStudioError(tsChannel, "TS: ERROR: no second byte");
549  tsChannel->in_sync = false;
550  return -1;
551  }
552 
553  uint16_t incomingPacketSize = firstByte << 8 | secondByte;
554  size_t expectedSize = incomingPacketSize + TS_PACKET_TAIL_SIZE;
555 
556  if ((incomingPacketSize == 0) || (expectedSize > sizeof(tsChannel->scratchBuffer))) {
557  if (tsChannel->in_sync) {
558  efiPrintf("process_ts: channel=%s invalid size: %d", tsChannel->name, incomingPacketSize);
559  tunerStudioError(tsChannel, "process_ts: ERROR: packet size");
560  /* send error only if previously we were in sync */
561  sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, "invalid size");
562  }
563  tsChannel->in_sync = false;
564  return -1;
565  }
566 
567  char command;
568  if (tsChannel->in_sync) {
569  /* we are in sync state, packet size should be correct so lets receive full packet and then check if command is supported
570  * otherwise (if abort reception in middle of packet) it will break synchronization and cause error on next packet */
571  received = tsChannel->readTimeout((uint8_t*)(tsChannel->scratchBuffer), expectedSize, TS_COMMUNICATION_TIMEOUT);
572  command = tsChannel->scratchBuffer[0];
573 
574  if (received != expectedSize) {
575  /* print and send error as we were in sync */
576  efiPrintf("Got only %d bytes while expecting %d for command 0x%02x", received,
577  expectedSize, command);
578  tunerStudioError(tsChannel, "ERROR: not enough bytes in stream");
579  // MS serial protocol spec: There was a timeout before all data was received. (25ms per character.)
580  sendErrorCode(tsChannel, TS_RESPONSE_UNDERRUN, "underrun");
581  tsChannel->in_sync = false;
582  return -1;
583  }
584 
585  if (!isKnownCommand(command)) {
586  /* print and send error as we were in sync */
587  efiPrintf("unexpected command %x", command);
588  sendErrorCode(tsChannel, TS_RESPONSE_UNRECOGNIZED_COMMAND, "unknown");
589  tsChannel->in_sync = false;
590  return -1;
591  }
592  } else {
593  /* receive only command byte to check if it is supported */
594  received = tsChannel->readTimeout((uint8_t*)(tsChannel->scratchBuffer), 1, TS_COMMUNICATION_TIMEOUT_SHORT);
595  command = tsChannel->scratchBuffer[0];
596 
597  if (!isKnownCommand(command)) {
598  /* do not report any error as we are not in sync */
599  return -1;
600  }
601 
602  received = tsChannel->readTimeout((uint8_t*)(tsChannel->scratchBuffer) + 1, expectedSize - 1, TS_COMMUNICATION_TIMEOUT);
603  if (received != expectedSize - 1) {
604  /* do not report any error as we are not in sync */
605  return -1;
606  }
607  }
608 
609 #if EFI_SIMULATOR
610  logMsg("command %c\r\n", command);
611 #endif
612 
613  uint32_t expectedCrc = *(uint32_t*) (tsChannel->scratchBuffer + incomingPacketSize);
614 
615  expectedCrc = SWAP_UINT32(expectedCrc);
616 
617  uint32_t actualCrc = crc32(tsChannel->scratchBuffer, incomingPacketSize);
618  if (actualCrc != expectedCrc) {
619  /* send error only if previously we were in sync */
620  if (tsChannel->in_sync) {
621  efiPrintf("TunerStudio: command %c actual CRC %x/expected %x", tsChannel->scratchBuffer[0],
622  (unsigned int)actualCrc, (unsigned int)expectedCrc);
623  tunerStudioError(tsChannel, "ERROR: CRC issue");
624  sendErrorCode(tsChannel, TS_RESPONSE_CRC_FAILURE, "crc_issue");
625  tsChannel->in_sync = false;
626  }
627  return -1;
628  }
629 
630  /* we were able to receive known command with correct crc and size! */
631  tsChannel->in_sync = true;
632 
633  int success = tsInstance.handleCrcCommand(tsChannel, tsChannel->scratchBuffer, incomingPacketSize);
634 
635  if (!success) {
636  efiPrintf("got unexpected TunerStudio command %x:%c", command, command);
637  return -1;
638  }
639 
640  return 0;
641 }
642 
644  auto channel = setupChannel();
645 
646  // No channel configured for this thread, cancel.
647  if (!channel || !channel->isConfigured()) {
648  return;
649  }
650 
651  // Until the end of time, process incoming messages.
652  while (true) {
653  if (tsProcessOne(channel) == 0) {
654  onDataArrived(true);
655  } else {
656  onDataArrived(false);
657  }
658  }
659 }
660 
661 #endif // EFI_PROD_CODE || EFI_SIMULATOR
663 
664 void tunerStudioError(TsChannelBase* tsChannel, const char *msg) {
665  tunerStudioDebug(tsChannel, msg);
668 }
669 
670 #if EFI_PROD_CODE || EFI_SIMULATOR
671 
673 
674 // see also handleQueryCommand
675 // see also printVersionForConsole
676 static void handleGetVersion(TsChannelBase* tsChannel) {
677  char versionBuffer[32];
678  chsnprintf(versionBuffer, sizeof(versionBuffer), "%s v%d@%u", FRONTEND_TITLE_BAR_NAME, getRusEfiVersion(), SIGNATURE_HASH);
679  tsChannel->sendResponse(TS_CRC, (const uint8_t *) versionBuffer, strlen(versionBuffer) + 1);
680 }
681 
682 #if EFI_TEXT_LOGGING
683 static void handleGetText(TsChannelBase* tsChannel) {
685 
687 
688  size_t outputSize;
689  const char* output = swapOutputBuffers(&outputSize);
690 #if EFI_SIMULATOR
691  logMsg("get test sending [%d]\r\n", outputSize);
692 #endif
693 
694  tsChannel->writeCrcPacket(TS_RESPONSE_OK, reinterpret_cast<const uint8_t*>(output), outputSize, true);
695 #if EFI_SIMULATOR
696  logMsg("sent [%d]\r\n", outputSize);
697 #endif // EFI_SIMULATOR
698 }
699 #endif // EFI_TEXT_LOGGING
700 
701 void TunerStudio::handleExecuteCommand(TsChannelBase* tsChannel, char *data, int incomingPacketSize) {
702  data[incomingPacketSize] = 0;
703  char *trimmed = efiTrim(data);
704 #if EFI_SIMULATOR
705  logMsg("execute [%s]\r\n", trimmed);
706 #endif // EFI_SIMULATOR
707  (console_line_callback)(trimmed);
708 
709  tsChannel->writeCrcResponse(TS_RESPONSE_OK);
710 }
711 
712 int TunerStudio::handleCrcCommand(TsChannelBase* tsChannel, char *data, int incomingPacketSize) {
714 
715  char command = data[0];
716  data++;
717 
718  const uint16_t* data16 = reinterpret_cast<uint16_t*>(data);
719 
720  uint16_t offset = 0;
721  uint16_t count = 0;
722 
723  // command may not have offset field - keep safe default value
724  // not used by .ini at the moment TODO actually use that version of the command in the .ini
725  if (incomingPacketSize >= 3) {
726  offset = data16[0];
727  }
728  // command may not have count/size filed - keep safe default value
729  if (incomingPacketSize >= 5) {
730  count = data16[1];
731  }
732 
733  switch(command)
734  {
735  case TS_OUTPUT_COMMAND:
736  if (incomingPacketSize == 1) {
737  // Read command with no offset and size - read whole livedata
738  count = TS_TOTAL_OUTPUT_SIZE;
739  }
740  cmdOutputChannels(tsChannel, offset, count);
741  break;
742  case TS_HELLO_COMMAND:
743  tunerStudioDebug(tsChannel, "got Query command");
744  handleQueryCommand(tsChannel, TS_CRC);
745  break;
746  case TS_GET_FIRMWARE_VERSION:
747  handleGetVersion(tsChannel);
748  break;
749 #if EFI_TEXT_LOGGING
750  case TS_GET_TEXT:
751  handleGetText(tsChannel);
752  break;
753 #endif // EFI_TEXT_LOGGING
754  case TS_EXECUTE:
755  handleExecuteCommand(tsChannel, data, incomingPacketSize - 1);
756  break;
757  case TS_PAGE_COMMAND:
758  handlePageSelectCommand(tsChannel);
759  break;
760  case TS_CHUNK_WRITE_COMMAND:
761  handleWriteChunkCommand(tsChannel, offset, count, data + sizeof(TunerStudioWriteChunkRequest));
762  break;
763  case TS_SINGLE_WRITE_COMMAND:
764  {
765  uint8_t value = data[4];
766  handleWriteValueCommand(tsChannel, offset, value);
767  }
768  break;
769  case TS_GET_SCATTERED_GET_COMMAND:
770 #if EFI_TS_SCATTER
771  handleScatteredReadCommand(tsChannel);
772 #else
773  criticalError("Slow/wireless mode not supported");
774 #endif // EFI_TS_SCATTER
775  break;
776  case TS_CRC_CHECK_COMMAND:
777  handleCrc32Check(tsChannel, offset, count);
778  break;
779  case TS_BURN_COMMAND:
780  handleBurnCommand(tsChannel);
781  break;
782  case TS_READ_COMMAND:
783  handlePageReadCommand(tsChannel, offset, count);
784  break;
785  case TS_TEST_COMMAND:
786  [[fallthrough]];
787  case 'T':
788  handleTestCommand(tsChannel);
789  break;
790 #if EFI_SIMULATOR
791  case TS_SIMULATE_CAN:
792  void handleWrapCan(TsChannelBase* tsChannel, char *data, int incomingPacketSize);
793  handleWrapCan(tsChannel, data, incomingPacketSize - 1);
794  break;
795 #endif // EFI_SIMULATOR
796  case TS_IO_TEST_COMMAND:
797  {
798 #if EFI_PROD_CODE
799  uint16_t subsystem = SWAP_UINT16(data16[0]);
800  uint16_t index = SWAP_UINT16(data16[1]);
801 
802  executeTSCommand(subsystem, index);
803 #endif /* EFI_PROD_CODE */
804  sendOkResponse(tsChannel);
805  }
806  break;
807 #if EFI_TOOTH_LOGGER
808  case TS_SET_LOGGER_SWITCH:
809  switch(data[0]) {
810  case TS_COMPOSITE_ENABLE:
812  break;
813  case TS_COMPOSITE_DISABLE:
815  break;
816  case TS_COMPOSITE_READ:
817  {
818  auto toothBuffer = GetToothLoggerBufferNonblocking();
819 
820  if (toothBuffer) {
821  tsChannel->sendResponse(TS_CRC, reinterpret_cast<const uint8_t*>(toothBuffer->buffer), toothBuffer->nextIdx * sizeof(composite_logger_s), true);
822 
823  ReturnToothLoggerBuffer(toothBuffer);
824  } else {
825  // TS asked for a tooth logger buffer, but we don't have one to give it.
826  sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, DO_NOT_LOG);
827  }
828  }
829  break;
830 #ifdef TRIGGER_SCOPE
831  case TS_TRIGGER_SCOPE_ENABLE:
833  break;
834  case TS_TRIGGER_SCOPE_DISABLE:
836  break;
837  case TS_TRIGGER_SCOPE_READ:
838  {
839  const auto& buffer = triggerScopeGetBuffer();
840 
841  if (buffer) {
842  tsChannel->sendResponse(TS_CRC, buffer.get<uint8_t>(), buffer.size(), true);
843  } else {
844  // TS asked for a tooth logger buffer, but we don't have one to give it.
845  sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, DO_NOT_LOG);
846  }
847  }
848  break;
849 #endif // TRIGGER_SCOPE
850  default:
851  // dunno what that was, send NAK
852  return false;
853  }
854 
855  sendOkResponse(tsChannel);
856 
857  break;
858  case TS_GET_COMPOSITE_BUFFER_DONE_DIFFERENTLY:
859  {
861 
862  auto toothBuffer = GetToothLoggerBufferNonblocking();
863 
864  if (toothBuffer) {
865  tsChannel->sendResponse(TS_CRC, reinterpret_cast<const uint8_t*>(toothBuffer->buffer), toothBuffer->nextIdx * sizeof(composite_logger_s), true);
866 
867  ReturnToothLoggerBuffer(toothBuffer);
868  } else {
869  // TS asked for a tooth logger buffer, but we don't have one to give it.
870  sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, DO_NOT_LOG);
871  }
872  }
873 
874  break;
875 #else // EFI_TOOTH_LOGGER
876  case TS_GET_COMPOSITE_BUFFER_DONE_DIFFERENTLY:
877  sendErrorCode(tsChannel, TS_RESPONSE_OUT_OF_RANGE, DO_NOT_LOG);
878  break;
879 #endif /* EFI_TOOTH_LOGGER */
880 #ifdef KNOCK_SPECTROGRAM
881  case TS_KNOCK_SPECTROGRAM_ENABLE:
883  sendOkResponse(tsChannel);
884  break;
885  case TS_KNOCK_SPECTROGRAM_DISABLE:
887  sendOkResponse(tsChannel);
888  break;
889 #endif /* KNOCK_SPECTROGRAM */
890 #if ENABLE_PERF_TRACE
891  case TS_PERF_TRACE_BEGIN:
892  perfTraceEnable();
893  sendOkResponse(tsChannel);
894  break;
895  case TS_PERF_TRACE_GET_BUFFER:
896  {
897  auto trace = perfTraceGetBuffer();
898  tsChannel->sendResponse(TS_CRC, trace.get<uint8_t>(), trace.size(), true);
899  }
900 
901  break;
902 #endif /* ENABLE_PERF_TRACE */
903  case TS_GET_CONFIG_ERROR: {
904  const char* configError = getCriticalErrorMessage();
905 #if HW_CHECK_MODE
906  // analog input errors are returned as firmware error in QC mode
907  if (!hasFirmwareError()) {
908  strcpy((char*)configError, "FACTORY_MODE_PLEASE_CONTACT_SUPPORT");
909  }
910 #endif // HW_CHECK_MODE
911  tsChannel->sendResponse(TS_CRC, reinterpret_cast<const uint8_t*>(configError), strlen(configError), true);
912  break;
913  }
914  case TS_QUERY_BOOTLOADER: {
915  uint8_t bldata = TS_QUERY_BOOTLOADER_NONE;
916 #if EFI_USE_OPENBLT
917  bldata = TS_QUERY_BOOTLOADER_OPENBLT;
918 #endif
919 
920  tsChannel->sendResponse(TS_CRC, &bldata, 1, false);
921  break;
922  }
923  default:
924  sendErrorCode(tsChannel, TS_RESPONSE_UNRECOGNIZED_COMMAND, "unknown_command");
925 static char tsErrorBuff[80];
926  chsnprintf(tsErrorBuff, sizeof(tsErrorBuff), "ERROR: ignoring unexpected command %d [%c]", command, command);
927  tunerStudioError(tsChannel, tsErrorBuff);
928  return false;
929  }
930 
931  return true;
932 }
933 
934 #endif // EFI_PROD_CODE || EFI_SIMULATOR
935 
937  // Assert tune & output channel struct sizes
938  static_assert(sizeof(persistent_config_s) == TOTAL_CONFIG_SIZE, "TS datapage size mismatch");
939 // useful trick if you need to know how far off is the static_assert
940 // char (*__kaboom)[sizeof(persistent_config_s)] = 1;
941 // another useful trick
942 // static_assert(offsetof (engine_configuration_s,HD44780_e) == 700);
943 
944  memset(&tsState, 0, sizeof(tsState));
945 
946  addConsoleAction("tsinfo", printTsStats);
947  addConsoleAction("reset_ts", resetTs);
948  addConsoleActionI("set_ts_speed", setTsSpeed);
949 
950 #if EFI_BLUETOOTH_SETUP
951  // module initialization start (it waits for disconnect and then communicates to the module)
952  // Usage: "bluetooth_hc06 <baud> <name> <pincode>"
953  // Example: "bluetooth_hc06 38400 rusefi 1234"
954  // bluetooth_jdy 115200 alphax 1234
955  addConsoleActionSSS("bluetooth_hc05", [](const char *baudRate, const char *name, const char *pinCode) {
956  bluetoothStart(BLUETOOTH_HC_05, baudRate, name, pinCode);
957  });
958  addConsoleActionSSS("bluetooth_hc06", [](const char *baudRate, const char *name, const char *pinCode) {
959  bluetoothStart(BLUETOOTH_HC_06, baudRate, name, pinCode);
960  });
961  addConsoleActionSSS("bluetooth_bk", [](const char *baudRate, const char *name, const char *pinCode) {
962  bluetoothStart(BLUETOOTH_BK3231, baudRate, name, pinCode);
963  });
964  addConsoleActionSSS("bluetooth_jdy", [](const char *baudRate, const char *name, const char *pinCode) {
965  bluetoothStart(BLUETOOTH_JDY_3x, baudRate, name, pinCode);
966  });
967  addConsoleActionSSS("bluetooth_jdy31", [](const char *baudRate, const char *name, const char *pinCode) {
968  bluetoothStart(BLUETOOTH_JDY_31, baudRate, name, pinCode);
969  });
970 #endif /* EFI_BLUETOOTH_SETUP */
971 }
972 
973 #endif // EFI_TUNER_STUDIO
uint16_t channel
Definition: adc_inputs.h:105
constexpr uint8_t addr
Definition: ads1015.cpp:5
void setBoardConfigOverrides()
void executeTSCommand(uint16_t subsystem, uint16_t index)
Definition: bench_test.cpp:539
Utility methods related to bench testing.
void bluetoothSoftwareDisconnectNotify(SerialTsChannelBase *tsChannel)
Definition: bluetooth.cpp:338
uint8_t code
Definition: bluetooth.cpp:40
void bluetoothStart(bluetooth_module_e moduleType, const char *baudRate, const char *name, const char *pinCode)
Definition: bluetooth.cpp:275
@ BLUETOOTH_HC_05
Definition: bluetooth.h:22
@ BLUETOOTH_BK3231
Definition: bluetooth.h:27
@ BLUETOOTH_JDY_3x
Definition: bluetooth.h:29
@ BLUETOOTH_JDY_31
Definition: bluetooth.h:30
@ BLUETOOTH_HC_06
Definition: bluetooth.h:23
size_t size() const
Definition: big_buffer.h:43
const TBuffer * get() const
Definition: big_buffer.h:34
EngineState engineState
Definition: engine.h:312
TunerStudioOutputChannels outputChannels
Definition: engine.h:99
WarningCodeState warnings
Definition: engine_state.h:43
virtual bool isReady() const
virtual void flush()
const char * name
uint32_t writePacketHeader(const uint8_t responseCode, const size_t size)
char scratchBuffer[BLOCKING_FACTOR+30]
void writeCrcResponse(uint8_t responseCode)
virtual void writeCrcPacket(uint8_t responseCode, const uint8_t *buf, size_t size, bool allowLongPackets=false)
virtual void write(const uint8_t *buffer, size_t size, bool isEndOfPacket=false)=0
virtual size_t readTimeout(uint8_t *buffer, size_t size, int timeout)=0
void sendResponse(ts_response_format_e mode, const uint8_t *buffer, int size, bool allowLongPackets=false)
void sendErrorCode(TsChannelBase *tsChannel, uint8_t code, const char *msg="")
void handleScatteredReadCommand(TsChannelBase *tsChannel)
void handleWriteChunkCommand(TsChannelBase *tsChannel, uint16_t offset, uint16_t count, void *content)
bool handlePlainCommand(TsChannelBase *tsChannel, uint8_t command)
void handlePageSelectCommand(TsChannelBase *tsChannel)
void handleQueryCommand(TsChannelBase *tsChannel, ts_response_format_e mode)
void cmdOutputChannels(TsChannelBase *tsChannel, uint16_t offset, uint16_t count) override
'Output' command sends out a snapshot of current values Gauges refresh
void handleCrc32Check(TsChannelBase *tsChannel, uint16_t offset, uint16_t count)
void handleWriteValueCommand(TsChannelBase *tsChannel, uint16_t offset, uint8_t value)
void handlePageReadCommand(TsChannelBase *tsChannel, uint16_t offset, uint16_t count)
int handleCrcCommand(TsChannelBase *tsChannel, char *data, int incomingPacketSize)
void handleExecuteCommand(TsChannelBase *tsChannel, char *data, int incomingPacketSize)
void ThreadTask() override
virtual TsChannelBase * setupChannel()=0
ObdCode lastErrorCode
Definition: engine_parts.h:63
void addConsoleAction(const char *token, Void callback)
Register console action without parameters.
void addConsoleActionSSS(const char *token, VoidCharPtrCharPtrCharPtr callback)
void addConsoleActionI(const char *token, VoidInt callback)
Register a console command with one Integer parameter.
void onDataArrived(bool valid)
Definition: console_io.cpp:49
void(* CommandHandler)(char *)
Definition: console_io.h:10
void printUsbConnectorStats()
Definition: usb_console.cpp:60
char * efiTrim(char *param)
Definition: efilib.cpp:39
uint32_t SWAP_UINT32(uint32_t x)
Definition: efilib.h:27
uint16_t SWAP_UINT16(uint16_t x)
Definition: efilib.h:22
efitimesec_t getTimeNowS()
Current system time in seconds (32 bits)
Definition: efitime.cpp:42
bool isLockedFromUser()
Definition: engine2.cpp:256
void onBurnRequest()
bool validateConfigOnStartUpOrBurn()
Engine * engine
const char * getCriticalErrorMessage(void)
int getRusEfiVersion(void)
void setNeedToWriteConfiguration()
Definition: flash_main.cpp:79
UNUSED(samplingTimeSeconds)
FragmentList getLiveDataFragments()
Definition: live_data.cpp:224
const char * swapOutputBuffers(size_t *actualOutputBufferSize)
Main logic header.
This data structure holds current malfunction codes.
@ STACK_USAGE_COMMUNICATION
const BigBufferHandle perfTraceGetBuffer()
Definition: perf_trace.cpp:132
void perfTraceEnable()
Definition: perf_trace.cpp:119
@ TunerStudioHandleCrcCommand
engine_configuration_s * engineConfiguration
const char * hwPortname(brain_pin_e brainPin)
const char * getTsSignature()
Definition: signature.cpp:31
void knockSpectrogramEnable()
void knockSpectrogramDisable()
void printOverallStatus()
composite packet size
void DisableToothLogger()
void EnableToothLogger()
CompositeBuffer * GetToothLoggerBufferNonblocking()
void ReturnToothLoggerBuffer(CompositeBuffer *buffer)
void EnableToothLoggerIfNotEnabled()
composite_logger_s
Definition: tooth_logger.h:48
void triggerScopeEnable()
const BigBufferHandle & triggerScopeGetBuffer()
static BigBufferHandle buffer
void triggerScopeDisable()
static void handleBurnCommand(TsChannelBase *tsChannel)
static bool isKnownCommand(char command)
static void handleGetVersion(TsChannelBase *tsChannel)
tunerstudio_counters_s tsState
TunerStudio tsInstance
static void setTsSpeed(int value)
CommandHandler console_line_callback
Definition: console_io.cpp:53
static void resetTs()
void startTunerStudioConnectivity(void)
void sendErrorCode(TsChannelBase *tsChannel, uint8_t code, const char *msg)
static void printScatterList()
static void handleGetText(TsChannelBase *tsChannel)
bool validateOffsetCount(size_t offset, size_t count, TsChannelBase *tsChannel)
void requestBurn()
bool rebootForPresetPending
static void sendOkResponse(TsChannelBase *tsChannel)
static void handleTestCommand(TsChannelBase *tsChannel)
static int tsProcessOne(TsChannelBase *tsChannel)
void tunerStudioDebug(TsChannelBase *tsChannel, const char *msg)
static void printErrorCounters()
Definition: tunerstudio.cpp:93
uint8_t * getWorkingPageAddr()
static void printTsStats(void)
void tunerStudioError(TsChannelBase *tsChannel, const char *msg)
ts_response_format_e
@ TS_CRC
@ TS_PLAIN
SerialTsChannelBase * getBluetoothChannel()