/* ; Project: Open Vehicle Monitor System ; Date: 15th September 2020 ; ; Changes: ; 1.0 Initial release ; ; (C) 2011 Michael Stegen / Stegen Electronics ; (C) 2011-2017 Mark Webb-Johnson ; (C) 2011 Sonny Chen @ EPRO/DX ; (C) 2020 Chris Staite ; ; Permission is hereby granted, free of charge, to any person obtaining a copy ; of this software and associated documentation files (the "Software"), to deal ; in the Software without restriction, including without limitation the rights ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ; copies of the Software, and to permit persons to whom the Software is ; furnished to do so, subject to the following conditions: ; ; The above copyright notice and this permission notice shall be included in ; all copies or substantial portions of the Software. ; ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ; THE SOFTWARE. */ #include "ovms_log.h" static const char *TAG = "re-tp"; #include "retools_testerpresent.h" #include "vehicle.h" namespace { bool ReadHexString(const char* value, unsigned long& output) { char* ptr = nullptr; errno = 0; output = strtoul(value, &ptr, 16); if (ptr == value || errno != 0 || *ptr) { return false; } return true; } canbus* GetCan(int bus) { char name[] = "canx"; name[3] = '0' + bus; auto can = reinterpret_cast(MyPcpApp.FindDeviceByName(name)); if (can != nullptr && (can->GetPowerMode() != On || can->m_mode != CAN_MODE_ACTIVE)) { can = nullptr; } return can; } } // anon namespace OvmsReToolsTesterPresent::OvmsReToolsTesterPresent( canbus* bus, uint16_t ecu, int interval) : m_bus(bus), m_id(ecu), m_ticker(0u), m_interval(interval) { } void OvmsReToolsTesterPresent::Ticker1() { ++m_ticker; if ((m_ticker % m_interval) == 0) { SendTesterPresent(); } } void OvmsReToolsTesterPresent::SendTesterPresent() { CAN_frame_t testerFrame = { m_bus, nullptr, { .B = { 8, 0, CAN_no_RTR, CAN_frame_std, 0 } }, m_id, { .u8 = { (ISOTP_FT_SINGLE<<4) + 2, VEHICLE_POLL_TYPE_TESTERPRESENT, 0, 0, 0, 0, 0, 0 } } }; if (m_bus->Write(&testerFrame) == ESP_FAIL) { ESP_LOGE(TAG, "Error sending tester present to %03x", m_id); } } class OvmsReToolsTesterPresentInit { public: OvmsReToolsTesterPresentInit(); ~OvmsReToolsTesterPresentInit(); void Start(); void Stop(); private: static void Start(int, OvmsWriter*, OvmsCommand*, int, const char* const*); static void List(int, OvmsWriter*, OvmsCommand*, int, const char* const*); static void Stop(int, OvmsWriter*, OvmsCommand*, int, const char* const*); static void StopAll(int, OvmsWriter*, OvmsCommand*, int, const char* const*); void Ticker1(std::string, void*); bool m_registered; OvmsMutex m_testersMutex; std::vector m_testers; } OvmsReToolsTesterPresentInitInstance __attribute__ ((init_priority (8801))); OvmsReToolsTesterPresentInit::OvmsReToolsTesterPresentInit() : m_registered(false), m_testersMutex(), m_testers() { OvmsCommand* cmd_reobdii = MyCommandApp.FindCommandFullName("re obdii"); if (cmd_reobdii == nullptr) { ESP_LOGE(TAG, "Tester present command depends on re command"); return; } OvmsCommand* cmd_tester = cmd_reobdii->RegisterCommand( "tester", "Tester present sender" ); cmd_tester->RegisterCommand( "start", "Start sending a tester present signal to an ECU", PickOvmsCommandExecuteCallback(OvmsReToolsTesterPresentInit::Start), " ", 3, 3 ); cmd_tester->RegisterCommand( "list", "List current tester present signals", &OvmsReToolsTesterPresentInit::List ); cmd_tester->RegisterCommand( "stop", "Stop tester present for an ECU", PickOvmsCommandExecuteCallback(OvmsReToolsTesterPresentInit::Stop), " ", 2, 2 ); cmd_tester->RegisterCommand( "stopall", "Stop tester present for all ECUs", &OvmsReToolsTesterPresentInit::StopAll ); } void OvmsReToolsTesterPresentInit::Start() { if (m_registered == true) { return; } ESP_LOGD(TAG, "Starting tester present ticker"); MyEvents.RegisterEvent( TAG, "ticker.1", std::bind( &OvmsReToolsTesterPresentInit::Ticker1, this, std::placeholders::_1, std::placeholders::_2 ) ); m_registered = true; } void OvmsReToolsTesterPresentInit::Stop() { if (m_registered == false) { return; } ESP_LOGD(TAG, "Stopping tester present ticker"); MyEvents.DeregisterEvent(TAG); m_registered = false; } OvmsReToolsTesterPresentInit::~OvmsReToolsTesterPresentInit() { Stop(); } void OvmsReToolsTesterPresentInit::Ticker1(std::string, void*) { OvmsMutexLock lock(&OvmsReToolsTesterPresentInitInstance.m_testersMutex); for (auto& tester : OvmsReToolsTesterPresentInitInstance.m_testers) { tester.Ticker1(); } } void OvmsReToolsTesterPresentInit::Start( int, OvmsWriter* writer, OvmsCommand*, int, const char* const* argv) { unsigned long bus, ecu; bool valid = true; if (!ReadHexString(argv[0], bus) || bus < 1 || bus > 4) { writer->printf("Error: Invalid bus to scan %s", argv[0]); valid = false; } if (!ReadHexString(argv[1], ecu) || ecu <= 0 || ecu >= 0xfff) { writer->printf("Error: Invalid ECU Id to scan %s", argv[1]); valid = false; } int interval = atoi(argv[2]); if (interval <= 0) { writer->printf("Error: Invalid interval %s", argv[2]); valid = false; } if (!valid) { return; } canbus* can = GetCan(bus); if (can == nullptr) { writer->printf("CAN not started in active mode, please start and try again"); valid = false; } if (valid) { OvmsMutexLock lock(&OvmsReToolsTesterPresentInitInstance.m_testersMutex); for (auto& tester : OvmsReToolsTesterPresentInitInstance.m_testers) { if (tester.Bus() == can && tester.Ecu() == ecu) { writer->printf("Already sending tester present to that ECU"); return; } } if (OvmsReToolsTesterPresentInitInstance.m_testers.empty()) { OvmsReToolsTesterPresentInitInstance.Start(); } OvmsReToolsTesterPresentInitInstance.m_testers.push_back( OvmsReToolsTesterPresent(can, ecu, interval) ); } } void OvmsReToolsTesterPresentInit::List( int, OvmsWriter* writer, OvmsCommand*, int, const char* const*) { OvmsMutexLock lock(&OvmsReToolsTesterPresentInitInstance.m_testersMutex); for (auto& tester : OvmsReToolsTesterPresentInitInstance.m_testers) { writer->printf( "Tester present sending to %03x on bus %d every %d seconds", tester.Ecu(), tester.Bus()->m_busnumber + 1, tester.Interval() ); } } void OvmsReToolsTesterPresentInit::Stop( int, OvmsWriter* writer, OvmsCommand*, int, const char* const* argv) { unsigned long bus, ecu; bool valid = true; if (!ReadHexString(argv[0], bus) || bus < 1 || bus > 4) { writer->printf("Error: Invalid bus %s", argv[0]); valid = false; } if (!ReadHexString(argv[1], ecu) || ecu <= 0 || ecu >= 0xfff) { writer->printf("Error: Invalid ECU Id %s", argv[1]); valid = false; } if (!valid) { return; } OvmsMutexLock lock(&OvmsReToolsTesterPresentInitInstance.m_testersMutex); auto it = OvmsReToolsTesterPresentInitInstance.m_testers.begin(); while (it != OvmsReToolsTesterPresentInitInstance.m_testers.end()) { if (it->Bus()->m_busnumber + 1 == bus && it->Ecu() == ecu) { OvmsReToolsTesterPresentInitInstance.m_testers.erase(it); writer->puts("Stopped sending"); break; } ++it; } if (it == OvmsReToolsTesterPresentInitInstance.m_testers.end()) { writer->puts("Error: Not sending tester present to that"); } if (OvmsReToolsTesterPresentInitInstance.m_testers.empty()) { OvmsReToolsTesterPresentInitInstance.Stop(); } } void OvmsReToolsTesterPresentInit::StopAll( int, OvmsWriter* writer, OvmsCommand*, int, const char* const*) { OvmsMutexLock lock(&OvmsReToolsTesterPresentInitInstance.m_testersMutex); if (OvmsReToolsTesterPresentInitInstance.m_testers.empty()) { writer->puts("Error: Not sending tester present to anything"); } else { OvmsReToolsTesterPresentInitInstance.m_testers.clear(); OvmsReToolsTesterPresentInitInstance.Stop(); writer->puts("Stopped all"); } }