diff --git a/vehicle/OVMS.V3/changes.txt b/vehicle/OVMS.V3/changes.txt index ba28a70c0..97c14804d 100644 --- a/vehicle/OVMS.V3/changes.txt +++ b/vehicle/OVMS.V3/changes.txt @@ -1,6 +1,11 @@ Open Vehicle Monitor System v3 - Change log ????-??-?? ??? ??????? OTA release +- Separate Polling from the Vehicle implementation + New commands: + poller status + poller pause + poller resume - Duktape support for Metric Age / Stale New Duktape methods OvmsMetrics.IsStale diff --git a/vehicle/OVMS.V3/components/poller/CMakeLists.txt b/vehicle/OVMS.V3/components/poller/CMakeLists.txt new file mode 100644 index 000000000..f36f19b90 --- /dev/null +++ b/vehicle/OVMS.V3/components/poller/CMakeLists.txt @@ -0,0 +1,14 @@ +set(srcs) +set(include_dirs) + +if (CONFIG_OVMS_COMP_POLLER) + + list(APPEND srcs "src/vehicle_poller.cpp" "src/vehicle_poller_isotp.cpp" "src/vehicle_poller_vwtp.cpp") + list(APPEND include_dirs "src") +endif () + +# requirements can't depend on config +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS ${include_dirs} + PRIV_REQUIRES "main" + WHOLE_ARCHIVE) diff --git a/vehicle/OVMS.V3/components/poller/component.mk b/vehicle/OVMS.V3/components/poller/component.mk new file mode 100644 index 000000000..629703bae --- /dev/null +++ b/vehicle/OVMS.V3/components/poller/component.mk @@ -0,0 +1,14 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# + +ifdef CONFIG_OVMS_COMP_POLLER +COMPONENT_ADD_INCLUDEDIRS:=src +COMPONENT_SRCDIRS:=src +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive +endif diff --git a/vehicle/OVMS.V3/components/poller/docs/API.rst b/vehicle/OVMS.V3/components/poller/docs/API.rst new file mode 100644 index 000000000..317bd31af --- /dev/null +++ b/vehicle/OVMS.V3/components/poller/docs/API.rst @@ -0,0 +1,54 @@ +ISOTP Poller API +================ + +The Poller runs a single command thread (replacing the Vehicle RX thread) that +handles transmitting of commands, receiving of responses and various control +commands. This accessed through the global ``MyPollers`` instance. + +Within ``MyPollers`` is a ``OvmsPoller`` instance for each active Can Bus +containing the ISOTP/VWTP protocol state as well as a named list of +``PollSeriesEntry`` instances that provide which command to send. They are +each dispatched to from the single command thread but it means that concurrent +multi-frame queries can happen across different buses. + +A separate timer now means that the frequency of the poll can be adjusted from +the default 1s provided by the timer.1 event. + +There is also the ability to set up a number Secondary ticks for each Primary +tick. This means that failed commands can be retried more quickly and also that +particular types of ``PollSeriesEntry`` can repeat commands (like fetching +speed) without holding up the main Polling tick that provides periodic +commands. + +Another feature that has been added to reduce latency is that when the response +time between multiple frames is decreased via ``PollSetTimeBetweenSuccess``, a +delay can be added between poller runs that gives more air-time to other +packets on the Bus with ``PollSetResponseSeparationTime``. + +``PollSeriesEntry`` that are added with a "!v." prefix will be automatically removed +on shutdown of the vehicle class. + +Legacy Support +-------------- + +A single special ``PollSeriesEntry`` class called ``StandardVehiclePollSeries`` +is used to provide the ``PollSetPidList`` functionality. + +The blocking poll requests are also implemented using a different internal +``BlockingOnceOffPoll`` class. + +Additional Features +------------------- + +The idea is that multiple ``PollSeriesEntry`` classes can be assigned to each +BUS with one-shot classes taking priority. + +One-shot classes mean that you can easily perform once-off operations without +blocking the main timer thread. They automatically remove themselves from the +list once they are successful. +The Ioniq 5 implementation has an example of this where the VIN is retrieved. + +Another feature is that the overall 'State' is not limited to 4. Each series +entry can nominate the block of 4 states that they occupy and states outside +that won't apply to it. + diff --git a/vehicle/OVMS.V3/components/poller/docs/Commands.rst b/vehicle/OVMS.V3/components/poller/docs/Commands.rst new file mode 100644 index 000000000..8848a610d --- /dev/null +++ b/vehicle/OVMS.V3/components/poller/docs/Commands.rst @@ -0,0 +1,16 @@ +ISOTP Poller Commands +===================== + +.. highlight:: none + +Show status + :: + + poller status + +Pause/Resume poller + :: + + poller pause + poller resume + diff --git a/vehicle/OVMS.V3/components/poller/docs/Intro.rst b/vehicle/OVMS.V3/components/poller/docs/Intro.rst new file mode 100644 index 000000000..1f96f8e63 --- /dev/null +++ b/vehicle/OVMS.V3/components/poller/docs/Intro.rst @@ -0,0 +1,10 @@ +ISOTP Poller Basics +=================== + +The Poller module has been lifted from the vehicle implementation. It supports periodic +polling of OBD addresses, both older style (type 9 like those used for HUDs) and the newer +ISOTP (and VWTP) multi-framed data protocol extensions. + +As it stands it is primarily a developer API with limited controls over the parameters. + + diff --git a/vehicle/OVMS.V3/components/poller/docs/index.rst b/vehicle/OVMS.V3/components/poller/docs/index.rst new file mode 100644 index 000000000..7437d3af4 --- /dev/null +++ b/vehicle/OVMS.V3/components/poller/docs/index.rst @@ -0,0 +1,10 @@ +ISOTP Poller +============ + +.. toctree:: + :maxdepth: 1 + + Intro + Commands + API + diff --git a/vehicle/OVMS.V3/components/poller/src/vehicle_poller.cpp b/vehicle/OVMS.V3/components/poller/src/vehicle_poller.cpp new file mode 100644 index 000000000..e8ce4f075 --- /dev/null +++ b/vehicle/OVMS.V3/components/poller/src/vehicle_poller.cpp @@ -0,0 +1,2296 @@ +/* +; Project: Open Vehicle Monitor System +; Date: 14th March 2017 +; +; Changes: +; 1.0 Initial release +; +; (C) 2011 Michael Stegen / Stegen Electronics +; (C) 2011-2017 Mark Webb-Johnson +; (C) 2011 Sonny Chen @ EPRO/DX +; +; 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 = "vehicle-poll"; + +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_OVMS_COMP_WEBSERVER +#include +#endif // #ifdef CONFIG_OVMS_COMP_WEBSERVER +#include +#include +#include "vehicle.h" +#include "can.h" + +using namespace std::placeholders; + +OvmsPollers MyPollers __attribute__ ((init_priority (7000))); + +OvmsPoller::OvmsPoller(canbus* can, uint8_t can_number, OvmsPollers *parent, + const CanFrameCallback &polltxcallback) + : m_parent(parent) + { + m_poll.bus = can; + m_poll.bus_no = can_number; + m_poll_txcallback = polltxcallback; + + m_poll_state = 0; + + m_poll.entry = {}; + m_poll_vwtp = {}; + m_poll.ticker = 0; + + m_poll.moduleid_sent = 0; + m_poll.moduleid_low = 0; + m_poll.moduleid_high = 0; + m_poll.type = 0; + m_poll.pid = 0; + m_poll.mlremain = 0; + m_poll.mloffset = 0; + m_poll.mlframe = 0; + m_poll_wait = 0; + m_poll_sequence_max = 1; + m_poll_sequence_cnt = 0; + m_poll_fc_septime = 25; // response default timing: 25 milliseconds + m_poll_ch_keepalive = 60; // channel keepalive default: 60 seconds + m_poll_repeat_count = 0; + m_poll_sent_last = 0; + m_poll_between_success = 0; + } + +void OvmsPoller::Incoming(CAN_frame_t &frame, bool success) + { + // Pass frame to poller protocol handlers: + if (frame.origin == m_poll_vwtp.bus && frame.MsgID == m_poll_vwtp.rxid) + { + PollerVWTPReceive(&frame, frame.MsgID); + } + else if (frame.origin == m_poll.bus) + { + if (!m_poll_wait) + { + ESP_LOGD(TAG, "[%" PRIu8 "]Poller: Incoming - giving up", m_poll.bus_no); + return; + } + if (m_poll.entry.txmoduleid == 0) + { + ESP_LOGD(TAG, "[%" PRIu8 "]Poller: Incoming - Poll Entry Cleared", m_poll.bus_no); + return; + } + uint32_t msgid; + if (m_poll.protocol == ISOTP_EXTADR) + msgid = frame.MsgID << 8 | frame.data.u8[0]; + else + msgid = frame.MsgID; + ESP_LOGD(TAG, "[%" PRIu8 "]Poller: FrameRx(bus=%s, msg=%" PRIx32 " )", m_poll.bus_no, frame.origin == m_poll.bus ? "Self" : "Other", msgid ); + if (msgid >= m_poll.moduleid_low && msgid <= m_poll.moduleid_high) + { + PollerISOTPReceive(&frame, msgid); + } + } + } + +OvmsPoller::~OvmsPoller() + { + } + + +/** + * IncomingPollTxCallback: poller TX callback + * This is called by PollerTxCallback() on TX success/failure for a poller request. + * You can use this to detect CAN bus issues, e.g. if the car switches off the OBD port. + * + * ATT: this is executed in the main CAN task context. Keep it simple. + * Complex processing here will affect overall CAN performance. + * + * @param job + * Status of the current Poll job + * @param success + * Frame transmission success + */ +void OvmsPoller::IncomingPollTxCallback(const OvmsPoller::poll_job_t &job, bool success) + { + m_polls.IncomingTxReply(job, success); + } + +bool OvmsPoller::Ready() + { + return m_parent->Ready(); + } + +/** + * PollSetPidList: set the default bus and the polling list to process + * Call this to install a new polling list or restart the list. + * This won't change the polling state; you can change the list while keeping the state. + * The list is changed without waiting for pending responses to finish (except PollSingleRequests). + * + * @param bus + * CAN bus to use as the default bus (for all poll entries with bus=0) or NULL to stop polling + * @param plist + * The polling list to use or NULL to stop polling + */ +void OvmsPoller::PollSetPidList(uint8_t defaultbus, const OvmsPoller::poll_pid_t* plist, VehicleSignal *signal) + { + OvmsRecMutexLock lock(&m_poll_mutex); + if (m_poll_series == nullptr) + { + if (!plist) // Don't add if not necessary. + return; + m_poll_series = std::shared_ptr(new StandardVehiclePollSeries(this, signal)); + m_polls.SetEntry("!v.standard", m_poll_series); + } + + m_poll_series->PollSetPidList(defaultbus, plist); + + m_poll.ticker = 0; + m_poll_sequence_cnt = 0; + } + +void OvmsPoller::Do_PollSetState(uint8_t state) + { + if ( state != m_poll_state) + { + m_poll_state = state; + m_poll.ticker = 0; + m_poll_sequence_cnt = 0; + m_poll_repeat_count = 0; + ResetPollEntry(false); + } + } + +/** + * PollSetThrottling: configure polling speed / niceness + * If multiple requests are due at the same poll tick (second), this controls how many of + * them will be sent in series without a delay, i.e. as soon as the response/timeout for + * the previous request occurred. + * + * @param sequence_max + * Polls allowed to be sent in sequence per time tick (second), default 1, 0 = no limit. + * + * The configuration is kept unchanged over calls to PollSetPidList() or PollSetState(). + */ +void OvmsPoller::PollSetThrottling(uint8_t sequence_max) + { + m_poll_sequence_max = sequence_max; + } + +/** + * PollSetResponseSeparationTime: configure ISO TP multi frame response timing + * See: https://en.wikipedia.org/wiki/ISO_15765-2 + * + * @param septime + * Separation Time (ST), the minimum delay time between frames. Default: 25 milliseconds + * ST values up to 127 (0x7F) specify the minimum number of milliseconds to delay between frames, + * while values in the range 241 (0xF1) to 249 (0xF9) specify delays increasing from + * 100 to 900 microseconds. + * + * The configuration is kept unchanged over calls to PollSetPidList() or PollSetState(). + */ +void OvmsPoller::PollSetResponseSeparationTime(uint8_t septime) + { + assert (septime <= 127 || (septime >= 241 && septime <= 249)); + m_poll_fc_septime = septime; + } + + +/** + * PollSetChannelKeepalive: configure keepalive timeout for channel oriented protocols + * + * The VWTP poller will keep a channel connection to a module open for this + * many seconds after the last poll data transmission has taken place, to + * minimize connection overhead in case the next poll is sent to the same + * module. + * + * Devices may stay awake as long as the channel is open, so don't disable + * the timeout except for special purposes. + * + * @param keepalive_seconds + * Channel inactivity timeout in seconds, 0 = disable, default/init = 60 + */ +void OvmsPoller::PollSetChannelKeepalive(uint16_t keepalive_seconds) + { + m_poll_ch_keepalive = keepalive_seconds; + } + +void OvmsPoller::PollSetTimeBetweenSuccess(uint16_t time_between_ms) + { + m_poll_between_success = time_between_ms / portTICK_PERIOD_MS; + } + +void OvmsPoller::ResetThrottle() + { + // Main Timer reset throttling counter, + m_poll_sequence_cnt = 0; + } + +void OvmsPoller::ResetPollEntry(bool force) + { + OvmsRecMutexLock lock(&m_poll_mutex); + if (force || !m_polls.PollIsBlocking() ) + { + m_poll_repeat_count = 0; + m_polls.RestartPoll(OvmsPoller::ResetMode::PollReset); + m_poll.entry = {}; + m_poll_txmsgid = 0; + } + } + +bool OvmsPoller::HasPollList() + { + OvmsRecMutexLock lock(&m_poll_mutex); + return m_polls.HasPollList(); + } + +void OvmsPoller::ClearPollList() + { + OvmsRecMutexLock lock(&m_poll_mutex); + return m_polls.Clear(); + } + +bool OvmsPoller::CanPoll() + { + // Check Throttle + return (!m_poll_sequence_max || m_poll_sequence_cnt < m_poll_sequence_max); + } + +void OvmsPoller::PollerNextTick(poller_source_t source) + { + // Completed checking all poll entries for the current m_poll.ticker + + ESP_LOGD(TAG, "[%" PRIu8 "]PollerNextTick(%s): cycle complete for ticker=%u", m_poll.bus_no, PollerSource(source), m_poll.ticker); + m_poll_ticked = false; + + if (source == poller_source_t::Primary) + { + // Allow POLL to restart. + { + OvmsRecMutexLock lock(&m_poll_mutex); + + m_poll_run_finished = false; + m_polls.RestartPoll(OvmsPoller::ResetMode::PollReset); + m_poll_repeat_count = 0; + } + + m_poll.ticker++; + if (m_poll.ticker > 3600) m_poll.ticker -= 3600; + } + } + +/** Called after reaching the end of available POLL entries. + */ +void OvmsPoller::PollRunFinished() + { + // Call back on vehicle PollRunFinished() with bus number / bus + m_parent->PollRunFinished(m_poll.bus); + } + +/** + * PollerSend: internal: start next due request + */ +void OvmsPoller::PollerSend(poller_source_t source) + { + + bool curIsBlocking; + { + OvmsRecMutexLock lock(&m_poll_mutex); + curIsBlocking = m_polls.PollIsBlocking(); + } + bool fromPrimaryTicker = false, fromPrimaryOrOnceOffTicker = false; + switch (source) + { + case poller_source_t::OnceOff: + fromPrimaryOrOnceOffTicker = true; + break; + case poller_source_t::Primary: + fromPrimaryOrOnceOffTicker = true; + fromPrimaryTicker = true; + break; + default: + ; + } + if (fromPrimaryTicker) + { + // Call back on vehicle PollerStateTicker() with bus number / bus + m_parent->PollerStateTicker(m_poll.bus); + } + + if (fromPrimaryTicker ) + { + if (!curIsBlocking) + { + m_poll_ticked = true; + } + } + if (fromPrimaryOrOnceOffTicker) + { + // Timer ticker call: check response timeout + if (m_poll_wait > 0) + m_poll_wait--; + + // Protocol specific ticker calls: + PollerVWTPTicker(); + } + + if (m_poll_wait > 0) + { + ESP_LOGV(TAG, "[%" PRIu8 "]PollerSend: Waiting %" PRIu8, m_poll.bus_no, m_poll_wait); + return; + } + + if (!curIsBlocking && m_poll_ticked) + { + if (!m_poll_run_finished && m_poll_repeat_count > 0) + { + ESP_LOGD(TAG, "Poller finished primary run"); + m_poll_run_finished = true; + } + m_poll_ticked = false; + + // Force a reset + if (m_poll_run_finished) + { + PollRunFinished(); + PollerNextTick(poller_source_t::Primary); + } + } + + // Check poll bus & list: + if (!HasPollList()) + { + ESP_LOGV(TAG, "[%" PRIu8 "]PollerSend: Empty", m_poll.bus_no); + return; + } + + OvmsPoller::OvmsNextPollResult res; + { + OvmsRecMutexLock lock(&m_poll_mutex); + res = m_polls.NextPollEntry(m_poll.entry, m_poll.bus_no, m_poll.ticker, m_poll_state); + } + if (res == OvmsNextPollResult::ReachedEnd && m_polls.HasRepeat()) + { + ++m_poll_repeat_count; + if (m_poll_repeat_count > max_poll_repeat) + { + ESP_LOGD(TAG, "[%" PRIu8 "]Poller Retry Exceeded - Finishing", m_poll.bus_no); + m_poll_run_finished = true; + res = OvmsNextPollResult::StillAtEnd; + } + else + { + ESP_LOGV(TAG, "[%" PRIu8 "]Poller Reset for Repeat (%s)", m_poll.bus_no, OvmsPoller::PollerSource(source)); + m_polls.RestartPoll(OvmsPoller::ResetMode::LoopReset); + // If this poll is from a ISOTP success, don't overwhelm the ECU, + // wait until a Secondary tick. + if (source == poller_source_t::Successful) + { + ESP_LOGV(TAG, "[%" PRIu8 "]Poller Restart: Wait for secondary", m_poll.bus_no); + return; + } + res = m_polls.NextPollEntry(m_poll.entry, m_poll.bus_no, m_poll.ticker, m_poll_state); + } + } + switch (res) + { + case OvmsNextPollResult::Ignore: + ESP_LOGD(TAG, "[%" PRIu8 "]PollerSend: Ignore", m_poll.bus_no); + break; + case OvmsNextPollResult::NotReady: + { + ESP_LOGV(TAG, "[%" PRIu8 "]PollerSend: Poller Not Ready", m_poll.bus_no); + m_poll_run_finished = true; + break; + } + case OvmsNextPollResult::ReachedEnd: + { + ESP_LOGV(TAG, "[%" PRIu8 "]PollerSend: Poller Reached End", m_poll.bus_no); + m_poll_run_finished = true; + break; + } + case OvmsNextPollResult::StillAtEnd: + if (!m_poll_run_finished) + { + ESP_LOGD(TAG, "[%" PRIu8 "Poller Reached End(!)", m_poll.bus_no); + m_poll_run_finished = true; + } + break; + case OvmsNextPollResult::FoundEntry: + { + ESP_LOGD(TAG, "[%" PRIu8 "]PollerSend(%s)[%" PRIu8 "]: entry at[type=%02X, pid=%X], ticker=%u, wait=%u, cnt=%u/%u", + m_poll.bus_no, PollerSource(source), m_poll_state, m_poll.entry.type, m_poll.entry.pid, + m_poll.ticker, m_poll_wait, m_poll_sequence_cnt, m_poll_sequence_max); + // We need to poll this one... + m_poll.protocol = m_poll.entry.protocol; + m_poll.type = m_poll.entry.type; + m_poll.pid = m_poll.entry.pid; + + m_poll_sent_last = monotonictime; + // Dispatch transmission start to protocol handler: + if (m_poll.protocol == VWTP_20) + PollerVWTPStart(fromPrimaryOrOnceOffTicker); + else + PollerISOTPStart(fromPrimaryOrOnceOffTicker); + + m_poll_sequence_cnt++; + break; + } + } + } + +void OvmsPoller::Outgoing(const CAN_frame_t &frame, bool success) + { + + // Check for a late callback: + if (!m_poll_wait || !m_poll.entry.txmoduleid || frame.origin != m_poll.bus || frame.MsgID != m_poll_txmsgid) + return; + + // Forward to protocol handler: + if (m_poll.protocol == VWTP_20) + PollerVWTPTxCallback(&frame, success); + + // On failure, try to speed up the current poll timeout: + if (!success) + { + m_poll_wait = 0; + OvmsRecMutexLock lock(&m_poll_mutex); + m_polls.IncomingError(m_poll, POLLSINGLE_TXFAILURE); + } + + // Forward to application: + m_poll.moduleid_rec = 0; // Not yet received + IncomingPollTxCallback(m_poll, success); + } + +/** + * PollSingleRequest: perform prioritized synchronous single OBD2/UDS request + * Pass a full OBD2/UDS request (mode/type, PID, additional payload). + * The request is sent immediately, aborting a running poll list request. The previous + * poller state is automatically restored after the request has been performed, but + * without any guarantee for repetition or omission of an aborted poll. + * On success, the response buffer will contain the response payload (may be empty). + * + * See OvmsVehicleFactory::obdii_request() for a usage example. + * + * ATT: must not be called from within the vehicle task context -- deadlock situation! + * + * @param txid CAN ID to send to (0x7df = broadcast) + * @param rxid CAN ID to expect response from (broadcast: 0) + * @param request Request to send (binary string) (type + pid + up to 4095 bytes payload) + * @param response Response buffer (binary string) (multiple response frames assembled) + * @param timeout_ms Timeout for poller/response in milliseconds + * @param protocol Protocol variant: ISOTP_STD / ISOTP_EXTADR / ISOTP_EXTFRAME + * + * @return POLLSINGLE_OK (0) -- success, response is valid + * POLLSINGLE_TIMEOUT (-1) -- timeout/poller unavailable + * POLLSINGLE_TXFAILURE (-2) -- CAN transmission failure + * else (>0) -- UDS NRC detail code + * Note: response is only valid with return value 0 + */ +int OvmsPoller::PollSingleRequest(uint32_t txid, uint32_t rxid, + std::string request, std::string& response, + int timeout_ms /*=3000*/, uint8_t protocol /*=ISOTP_STD*/) + { + if (!Ready()) + return -1; + + OvmsRecMutexLock slock(&m_poll_single_mutex, pdMS_TO_TICKS(timeout_ms)); + if (!slock.IsLocked()) + return -1; + + // prepare single poll: + OvmsPoller::poll_pid_t poll = + { txid, rxid, 0, 0, { 1, 1, 1, 1 }, 0, protocol }; + + assert(request.size() > 0); + poll.type = request[0]; + poll.xargs.tag = POLL_TXDATA; + + if (POLL_TYPE_HAS_16BIT_PID(poll.type)) + { + assert(request.size() >= 3); + poll.xargs.pid = request[1] << 8 | request[2]; + poll.xargs.datalen = LIMIT_MAX(request.size()-3, 4095); + poll.xargs.data = (const uint8_t*)request.data()+3; + } + else if (POLL_TYPE_HAS_8BIT_PID(poll.type)) + { + assert(request.size() >= 2); + poll.xargs.pid = request.at(1); + poll.xargs.datalen = LIMIT_MAX(request.size()-2, 4095); + poll.xargs.data = (const uint8_t*)request.data()+2; + } + else + { + poll.xargs.pid = 0; + poll.xargs.datalen = LIMIT_MAX(request.size()-1, 4095); + poll.xargs.data = (const uint8_t*)request.data()+1; + } + + int rx_error; + OvmsSemaphore single_rxdone; // … response done (ok/error) + std::shared_ptr poller( new BlockingOnceOffPoll(poll, &response, &rx_error, &single_rxdone)); + + // acquire poller access: + { + OvmsRecMutexLock lock(&m_poll_mutex, pdMS_TO_TICKS(timeout_ms)); + if (!lock.IsLocked()) + return -1; + // start single poll: + m_polls.SetEntry("!v.single", poller, true); + } + + ESP_LOGV(TAG, "[%" PRIu8 "]Single Request Sending", m_poll.bus_no); + Queue_PollerSend(poller_source_t::OnceOff); + + // wait for response: + ESP_LOGV(TAG, "[%" PRIu8 "]Single Request Waiting for response", m_poll.bus_no); + bool rxok = single_rxdone.Take(pdMS_TO_TICKS(timeout_ms)); + ESP_LOGV(TAG, "[%" PRIu8 "]PollSingleRequest: Response done ", m_poll.bus_no); + // Make sure if it is still sticking around that it's not accessing + // stack objects! + poller->Finished(); + return (rxok == pdFALSE) ? -1 : rx_error; + } + + +/** + * PollSingleRequest: perform prioritized synchronous single OBD2/UDS request + * Convenience wrapper for standard PID polls, see above for main implementation. + * + * @param txid CAN ID to send to (0x7df = broadcast) + * @param rxid CAN ID to expect response from (broadcast: 0) + * @param polltype OBD2/UDS poll type … + * @param pid … and PID to poll + * @param response Response buffer (binary string) (multiple response frames assembled) + * @param timeout_ms Timeout for poller/response in milliseconds + * @param protocol Protocol variant: ISOTP_STD / ISOTP_EXTADR / ISOTP_EXTFRAME + * + * @return POLLSINGLE_OK ( 0) -- success, response is valid + * POLLSINGLE_TIMEOUT (-1) -- timeout/poller unavailable + * POLLSINGLE_TXFAILURE (-2) -- CAN transmission failure + * else (>0) -- UDS NRC detail code + * Note: response is only valid with return value 0 + */ +int OvmsPoller::PollSingleRequest( uint32_t txid, uint32_t rxid, + uint8_t polltype, uint16_t pid, std::string& response, + int timeout_ms /*=3000*/, uint8_t protocol /*=ISOTP_STD*/) + { + std::string request; + request = (char) polltype; + if (POLL_TYPE_HAS_16BIT_PID(polltype)) + { + request += (char) (pid >> 8); + request += (char) (pid & 0xff); + } + else if (POLL_TYPE_HAS_8BIT_PID(polltype)) + { + request += (char) (pid & 0xff); + } + return PollSingleRequest(txid, rxid, request, response, timeout_ms, protocol); + } + +void OvmsPoller::Queue_PollerSend(OvmsPoller::poller_source_t source) + { + m_parent->QueuePollerSend(source, m_poll.bus_no); + } + +void OvmsPoller::DoPollerSendSuccess( void * pvParamCan, uint32_t ticker ) // Static + { + uint8_t can_number = uint32_t(pvParamCan); + MyPollers.QueuePollerSend(OvmsPoller::poller_source_t::Successful, can_number, ticker); + } + +void OvmsPoller::Queue_PollerSendSuccess() + { + if (m_poll_between_success == 0) + m_parent->QueuePollerSend(OvmsPoller::poller_source_t::Successful, m_poll.bus_no); + else + { + xTimerPendFunctionCall(OvmsPoller::DoPollerSendSuccess,(void *)(uint32_t)m_poll.bus_no, m_poll.ticker, m_poll_between_success); + } + } + +/** Add a polling requester to list. Replaces any existing entry with that name. + @param name Unique name/identifier of poll series. + @param series Shared pointer to poller instance. + */ +void OvmsPoller::PollRequest(const std::string &name, const std::shared_ptr &series) + { + series->SetParentPoller(this); + OvmsRecMutexLock lock(&m_poll_mutex); + series->ResetList(OvmsPoller::ResetMode::PollReset); + m_polls.SetEntry(name, series); + } + +/** Remove a polling requester from the list if it exists. + @param name Unique name/identifier of poll series. + */ +void OvmsPoller::RemovePollRequest(const std::string &name) + { + OvmsRecMutexLock lock(&m_poll_mutex); + m_polls.RemoveEntry(name); + } +void OvmsPoller::RemovePollRequestStarting(const std::string &name) + { + OvmsRecMutexLock lock(&m_poll_mutex); + m_polls.RemoveEntry(name, true); + } + +/** + * PollResultCodeName: get text representation of result code + */ +const char* OvmsPoller::PollResultCodeName(int code) + { + switch (code) + { + case 0x10: return "generalReject"; + case 0x11: return "serviceNotSupported"; + case 0x12: return "subFunctionNotSupported"; + case 0x13: return "incorrectMessageLengthOrInvalidFormat"; + case 0x14: return "responseTooLong"; + case 0x21: return "busyRepeatRequest"; + case 0x22: return "conditionsNotCorrect"; + case 0x24: return "requestSequenceError"; + case 0x25: return "noResponseFromSubnetComponent"; + case 0x26: return "failurePreventsExecutionOfRequestedAction"; + case 0x31: return "requestOutOfRange"; + case 0x33: return "securityAccessDenied"; + case 0x35: return "invalidKey"; + case 0x36: return "exceedNumberOfAttempts"; + case 0x37: return "requiredTimeDelayNotExpired"; + case 0x70: return "uploadDownloadNotAccepted"; + case 0x71: return "transferDataSuspended"; + case 0x72: return "generalProgrammingFailure"; + case 0x73: return "wrongBlockSequenceCounter"; + case 0x78: return "requestCorrectlyReceived-ResponsePending"; + case 0x7e: return "subFunctionNotSupportedInActiveSession"; + case 0x7f: return "serviceNotSupportedInActiveSession"; + default: return NULL; + } + } +const char *OvmsPoller::PollerSource(OvmsPoller::poller_source_t src) + { + switch (src) + { + case poller_source_t::Primary: return "PRI"; + case poller_source_t::Secondary: return "SEC"; + case poller_source_t::Successful: return "SRX"; + case poller_source_t::OnceOff: return "ONE"; + } + return "XXX"; + } +const char *OvmsPoller::PollerCommand(OvmsPollCommand src) + { + switch(src) + { + case OvmsPollCommand::Pause: return "Pause"; + case OvmsPollCommand::Resume: return "Resume"; + case OvmsPollCommand::Throttle: return "Throttle"; + case OvmsPollCommand::ResponseSep: return "ResponseSep"; + case OvmsPollCommand::Keepalive: return "Keepalive"; + case OvmsPollCommand::SuccessSep: return "SuccessSep"; + } + return "??"; + } + +OvmsPollers::OvmsPollers() + : m_poll_state(0), + m_poll_sequence_max(0), + m_poll_fc_septime(25), + m_poll_ch_keepalive(60), + m_poll_between_success(0), + m_poll_last(0), + m_pollqueue(nullptr), m_polltask(nullptr), + m_timer_poller(nullptr), + m_poll_subticker(0), + m_poll_tick_ms(1000), + m_poll_tick_secondary(0), + m_shut_down(false), + m_ready(false), + m_paused(false), + m_user_paused(false) + { + ESP_LOGI(TAG, "Initialising Poller (7000)"); + for (int idx = 0; idx < VEHICLE_MAXBUSSES; ++idx) + { + m_pollers[idx] = nullptr; + m_canbusses[idx].can = nullptr; + m_canbusses[idx].from_vehicle = false; + } + + m_poll_txcallback = std::bind(&OvmsPollers::PollerTxCallback, this, _1, _2); + + MyEvents.RegisterEvent(TAG, "ticker.1", std::bind(&OvmsPollers::Ticker1, this, _1, _2)); + MyCan.RegisterCallback(TAG, std::bind(&OvmsPollers::PollerRxCallback, this, _1, _2)); + MyEvents.RegisterEvent(TAG,"system.shuttingdown",std::bind(&OvmsPollers::EventSystemShuttingDown, this, _1, _2)); + + OvmsCommand* cmd_pause = MyCommandApp.RegisterCommand("poller","OBD polling framework",vehicle_poller_status); + cmd_pause->RegisterCommand("status","OBD Polling Status",vehicle_poller_status); + cmd_pause->RegisterCommand("pause","Pause OBD Polling",vehicle_pause_on); + cmd_pause->RegisterCommand("resume","Resume OBD Polling",vehicle_pause_off); + + } + +OvmsPollers::~OvmsPollers() + { + ShuttingDown(); + } + +void OvmsPollers::StartingUp() + { + if (m_shut_down) + return; + CheckStartPollTask(); + } + +void OvmsPollers::ShuttingDown() + { + if (m_shut_down) + return; + m_shut_down = true; + MyCan.DeregisterCallback(TAG); + MyEvents.DeregisterEvent(TAG); + + if (m_polltask) + { + vTaskDelete(m_polltask); + m_polltask = nullptr; + } + if (m_pollqueue) + { + vQueueDelete(m_pollqueue); + m_pollqueue = nullptr; + } + if (m_timer_poller) + { + xTimerDelete( m_timer_poller, 0); + m_timer_poller = NULL; + } + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + if (m_pollers[i]) + m_pollers[i]->ClearPollList(); + } + for (int i = 1 ; i <= VEHICLE_MAXBUSSES; ++i) + PowerDownCanBus(i); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + if (m_pollers[i]) + { + delete m_pollers[i]; + m_pollers[i] = nullptr; + } + } + } + +void OvmsPollers::ShuttingDownVehicle() + { + OvmsRecMutexLock lock(&m_poller_mutex); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + // Remove All pollers starting with "!v." + if (m_pollers[i]) + m_pollers[i]->RemovePollRequestStarting("!v."); + // Power down pollers started from the 'vehicle' + bus_info_t &info = m_canbusses[i]; + if (info.can && info.from_vehicle) + { + info.can->SetPowerMode(Off); + info.from_vehicle = false; + } + } + } + +uint8_t OvmsPollers::GetBusNo(canbus* bus) + { + for (int i = 0; i < VEHICLE_MAXBUSSES; ++i) + { + if (bus == m_canbusses[i].can) + return i+1; + } + return 0; + } +canbus* OvmsPollers::GetBus(uint8_t busno) + { + if (busno <1 || busno > VEHICLE_MAXBUSSES) + return nullptr; + return m_canbusses[busno-1].can; + } +canbus* OvmsPollers::RegisterCanBus(int busno, CAN_mode_t mode, CAN_speed_t speed, dbcfile* dbcfile, bool from_vehicle) + { + if (m_shut_down) + return nullptr; + if (busno <1 || busno > VEHICLE_MAXBUSSES) + return nullptr; + + bus_info_t &info = m_canbusses[busno-1]; + if (!info.can) + { + std::string busname = string_format("can%d", busno); + info.can = (canbus*)MyPcpApp.FindDeviceByName(busname.c_str()); + if (!info.can) + return nullptr; + } + info.from_vehicle = from_vehicle; + info.can->SetPowerMode(On); + info.can->Start(mode,speed,dbcfile); + return info.can; + } + +void OvmsPollers::PowerDownCanBus(int busno) + { + canbus *bus = GetBus(busno); + if(bus) + bus->SetPowerMode(Off); + } + +void OvmsPollers::Ticker1(std::string event, void* data) + { + PollerResetThrottle(); + } + +void OvmsPollers::EventSystemShuttingDown(std::string event, void* data) + { + ShuttingDown(); + } + +/** + * PollerTxCallback: internal: process poll request callbacks + */ +void OvmsPollers::PollerTxCallback(const CAN_frame_t* frame, bool success) + { + Queue_PollerFrame(*frame, success, true); + } +/** + * PollerRxCallback: internal: process poll request callbacks + */ +void OvmsPollers::PollerRxCallback(const CAN_frame_t* frame, bool success) + { + Queue_PollerFrame(*frame, success, false); + } + +static void OvmsVehiclePollTicker(TimerHandle_t xTimer ) + { + OvmsPollers *pollers = (OvmsPollers *)pvTimerGetTimerID(xTimer); + if (pollers) + pollers->VehiclePollTicker(); + } + +void OvmsPollers::VehiclePollTicker() + { + int32_t cur_ticker = ++m_poll_subticker; + if (cur_ticker >= m_poll_tick_secondary) + m_poll_subticker = 0; + + OvmsPoller::poller_source_t src; + + // So first tick is Primary. + src = (cur_ticker == 1) ? OvmsPoller::poller_source_t::Primary : OvmsPoller::poller_source_t::Secondary; + + QueuePollerSend(src); + } + +/** + * Make sure the Poll task is running. + * Automatically called when a poller is set. + */ +void OvmsPollers::CheckStartPollTask( bool force ) + { + + if (!force) + { + bool require = false; + OvmsRecMutexLock lock(&m_poller_mutex); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + if (m_pollers[i]) + { + require = true; + break; + } + } + + if (!require) + return; + } + + if (!m_pollqueue) + m_pollqueue = xQueueCreate(CONFIG_OVMS_VEHICLE_CAN_RX_QUEUE_SIZE,sizeof(OvmsPoller::poll_queue_entry_t)); + if (!m_polltask) + { + if (!Ready()) + return; + xTaskCreatePinnedToCore(OvmsPollerTask, "OVMS Vehicle Poll", + CONFIG_OVMS_VEHICLE_RXTASK_STACK, (void*)this, 10, &m_polltask, CORE(1)); + } + if (!m_timer_poller) + { + // default to 1 second + m_timer_poller = xTimerCreate("Vehicle OBD Poll Ticker", + m_poll_tick_ms / portTICK_PERIOD_MS,pdTRUE,this, + OvmsVehiclePollTicker); + xTimerStart(m_timer_poller, 0); + } + } + +void OvmsPollers::PollSetTicker(uint16_t tick_time_ms, uint8_t secondary_ticks) + { + ESP_LOGD(TAG, "Set Poll Ticker Timer %dms * %d", tick_time_ms, secondary_ticks); + if (!m_timer_poller) + m_poll_tick_ms = tick_time_ms; + else if (m_poll_tick_ms != tick_time_ms) + { + if (xTimerChangePeriod(m_timer_poller, tick_time_ms / portTICK_PERIOD_MS, 0) == pdPASS) + { + m_poll_tick_ms = tick_time_ms; + } + else + ESP_LOGE(TAG, "Poll Ticker Timer period not changed"); + xTimerReset(m_timer_poller, 0); + } + m_poll_tick_secondary = secondary_ticks; + m_poll_subticker = 0; + } + +void OvmsPollers::OvmsPollerTask(void *pvParameters) + { + OvmsPollers *me = (OvmsPollers*)pvParameters; + me->PollerTask(); + } + +void OvmsPollers::PollerTask() + { + OvmsPoller::poll_queue_entry_t entry; + while (!m_shut_down) + { + if (xQueueReceive(m_pollqueue, &entry, (portTickType)portMAX_DELAY)!=pdTRUE) + continue; + if (m_shut_down) + break; + m_poll_last = monotonictime; + switch (entry.entry_type) + { + case OvmsPoller::OvmsPollEntryType::FrameRx: + { + auto poller = GetPoller(entry.entry_FrameRxTx.frame.origin); + ESP_LOGV(TAG, "Pollers: FrameRx(bus=%d)", GetBusNo(entry.entry_FrameRxTx.frame.origin)); + if (poller) + poller->Incoming(entry.entry_FrameRxTx.frame, entry.entry_FrameRxTx.success); + } + break; + case OvmsPoller::OvmsPollEntryType::FrameTx: + { + auto poller = GetPoller(entry.entry_FrameRxTx.frame.origin); + ESP_LOGV(TAG, "Pollers: FrameTx(bus=%d)", GetBusNo(entry.entry_FrameRxTx.frame.origin)); + if (poller) + poller->Outgoing(entry.entry_FrameRxTx.frame, entry.entry_FrameRxTx.success); + } + break; + case OvmsPoller::OvmsPollEntryType::Poll: + if (!m_user_paused && !m_paused) + { + // Must not lock mutex while calling. + ESP_LOGV(TAG, "[%" PRIu8 "]Poller: Send(%s)", entry.entry_Poll.busno, OvmsPoller::PollerSource(entry.entry_Poll.source)); + if (entry.entry_Poll.busno != 0) + { + auto bus = GetBus(entry.entry_Poll.busno); + auto poller = GetPoller(bus); + if (poller) + { + if ((entry.entry_Poll.poll_ticker == 0) || (poller->m_poll.ticker == entry.entry_Poll.poll_ticker)) + poller->PollerSend(entry.entry_Poll.source); + } + } + else + { + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + OvmsPoller *poller; + { + OvmsRecMutexLock lock(&m_poller_mutex); + poller = m_pollers[i]; + } + if (poller) + poller->PollerSend(entry.entry_Poll.source); + } + } + } + break; + case OvmsPoller::OvmsPollEntryType::Command: + switch (entry.entry_Command.cmd) + { + case OvmsPoller::OvmsPollCommand::Pause: + { + if (entry.entry_Command.parameter) + { + ESP_LOGD(TAG, "Pollers: Command Pause (user)"); + m_user_paused = true; + } + else + { + ESP_LOGD(TAG, "Pollers: Command Pause (system)"); + m_paused = true; + } + continue; + } + case OvmsPoller::OvmsPollCommand::Resume: + { + if (entry.entry_Command.parameter) + { + ESP_LOGD(TAG, "Pollers: Command Resume (user)"); + m_user_paused = false; + } + else + { + ESP_LOGD(TAG, "Pollers: Command Resume (system)"); + m_paused = false; + } + continue; + } + case OvmsPoller::OvmsPollCommand::Throttle: + if (entry.entry_Command.parameter != m_poll_sequence_max) + { + m_poll_sequence_max = entry.entry_Command.parameter; + OvmsRecMutexLock lock(&m_poller_mutex); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + if (m_pollers[i]) + m_pollers[i]->PollSetThrottling(m_poll_sequence_max); + } + } + break; + case OvmsPoller::OvmsPollCommand::ResponseSep: + { + auto septime = entry.entry_Command.parameter; + if (septime <= 127 || (septime >= 241 && septime <= 249)) + { + if (septime != m_poll_fc_septime) + { + m_poll_fc_septime = septime; + OvmsRecMutexLock lock(&m_poller_mutex); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + if (m_pollers[i]) + m_pollers[i]->PollSetResponseSeparationTime(septime); + } + } + } + } + break; + case OvmsPoller::OvmsPollCommand::Keepalive: + if (entry.entry_Command.parameter != m_poll_ch_keepalive) + { + m_poll_ch_keepalive = entry.entry_Command.parameter; + OvmsRecMutexLock lock(&m_poller_mutex); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + if (m_pollers[i]) + m_pollers[i]->PollSetChannelKeepalive(m_poll_ch_keepalive); + } + } + break; + case OvmsPoller::OvmsPollCommand::SuccessSep: + if (entry.entry_Command.parameter != m_poll_between_success) + { + m_poll_between_success = entry.entry_Command.parameter; + OvmsRecMutexLock lock(&m_poller_mutex); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + if (m_pollers[i]) + m_pollers[i]->PollSetTimeBetweenSuccess(m_poll_between_success); + } + } + break; + } + break; + case OvmsPoller::OvmsPollEntryType::PollState: + { + if (entry.entry_PollState.bus) + { + ESP_LOGD(TAG, "Pollers: PollState(%" PRIu8 ",%" PRIu8 ")", entry.entry_PollState.new_state, GetBusNo(entry.entry_PollState.bus)); + OvmsRecMutexLock lock(&m_poller_mutex); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + auto cur = m_pollers[i]; + if (cur && cur->HasBus(entry.entry_PollState.bus) ) + cur->Do_PollSetState(entry.entry_PollState.new_state); + } + } + else + { + ESP_LOGD(TAG, "Pollers: PollState(%" PRIu8 ")", entry.entry_PollState.new_state); + OvmsRecMutexLock lock(&m_poller_mutex); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + auto cur = m_pollers[i]; + if (cur) + cur->Do_PollSetState(entry.entry_PollState.new_state); + } + } + } + break; + } + } + } + +OvmsPoller *OvmsPollers::GetPoller(canbus *can, bool force) + { + if (m_shut_down || !can) + return nullptr; + + int gap = -1; + { + OvmsRecMutexLock lock(&m_poller_mutex); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + auto cur = m_pollers[i]; + if (!cur) + { + if (gap < 0) + gap = i; + } + else if (cur->HasBus(can)) + { + return cur; + } + } + if (!force) + return nullptr; + if (gap < 0) + return nullptr; + + int busno = GetBusNo(can); + if (!busno) + return nullptr; + + ESP_LOGD(TAG, "GetPoller( busno=%" PRIu8 ")", busno); + auto newpoller = new OvmsPoller(can, busno, this, m_poll_txcallback); + newpoller->m_poll_state = m_poll_state; + newpoller->m_poll_sequence_max = m_poll_sequence_max; + newpoller->m_poll_fc_septime = m_poll_fc_septime; + newpoller->m_poll_ch_keepalive = m_poll_ch_keepalive; + m_pollers[gap] = newpoller; + } + + if (gap >= 0) + CheckStartPollTask(true); + + return (gap<0) ? nullptr : m_pollers[gap]; + } + +void OvmsPollers::QueuePollerSend(OvmsPoller::poller_source_t src, uint8_t busno, uint32_t pollticker) + { + if (m_shut_down) + return; + if (!Ready()) + return; + if (!m_pollqueue) + return; + // Sends poller send message + OvmsPoller::poll_queue_entry_t entry; + memset(&entry, 0, sizeof(entry)); + entry.entry_type = OvmsPoller::OvmsPollEntryType::Poll; + entry.entry_Poll.busno = busno; + entry.entry_Poll.source = src; + entry.entry_Poll.poll_ticker = pollticker; + + ESP_LOGV(TAG, "Pollers: Queue PollerSend(%s, %" PRIu8 ")", OvmsPoller::PollerSource(src), busno); + if (xQueueSend(m_pollqueue, &entry, 0) != pdPASS) + ESP_LOGI(TAG, "Pollers[Send]: Task Queue Overflow"); + } + +void OvmsPollers::PollSetPidList(canbus* defbus, const OvmsPoller::poll_pid_t* plist, + OvmsPoller::VehicleSignal *signal) + { + uint8_t defbusno = !defbus ? 0 : GetBusNo(defbus); + bool hasbus[1+VEHICLE_MAXBUSSES]; + for (int i = 0 ; i <= VEHICLE_MAXBUSSES; ++i) + hasbus[i] = false; + + // Load up any bus pollers required. + if (!plist) + ESP_LOGD(TAG, "PollSetPidList - Setting NULL List"); + else if (plist->txmoduleid == 0) // Check for an Empty list. + { + plist = nullptr; + ESP_LOGD(TAG, "PollSetPidList - Setting Empty List"); + } + else + { + for (const OvmsPoller::poll_pid_t *plcur = plist; plcur->txmoduleid != 0; ++plcur) + { + uint8_t usebusno = plcur->pollbus; + if ( usebusno > VEHICLE_MAXBUSSES) + continue; + if (usebusno == 0) + { + if (defbusno == 0) + continue; + usebusno = defbusno; + } + if (!hasbus[usebusno]) + { + hasbus[usebusno] = true; + GetPoller(GetBus(usebusno), true); + } + } + } + + OvmsRecMutexLock lock(&m_poller_mutex); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + auto poller = m_pollers[i]; + if (poller) + { + uint8_t busno = poller->m_poll.bus_no; + if (busno > VEHICLE_MAXBUSSES) + continue; + if (hasbus[busno]) + { + ESP_LOGD(TAG, "PollSetPidList - Setting for bus=%" PRIu8 " defbus=%" PRIu8, busno, defbusno); + poller->PollSetPidList(defbusno, plist, signal); + } + else + { + ESP_LOGD(TAG, "PollSetPidList - Clearing for bus=%" PRIu8, busno); + poller->PollSetPidList(0, nullptr, nullptr ); + } + } + } + } + +void OvmsPollers::PollRequest(canbus* defbus, const std::string &name, const std::shared_ptr &series) + { + OvmsRecMutexLock lock(&m_poller_mutex); + auto poller = GetPoller(defbus, true); + poller->PollRequest(name, series); + } + +void OvmsPollers::PollRemove(canbus* defbus, const std::string &name) + { + OvmsRecMutexLock lock(&m_poller_mutex); + if (defbus) + { + auto poller = GetPoller(defbus, false); + if (poller) + poller->RemovePollRequest(name); + } + else + { + for (int i = 0 ; i < VEHICLE_MAXBUSSES ; ++i) + { + auto poller = m_pollers[i]; + if (poller) + poller->RemovePollRequest(name); + } + } + } + +bool OvmsPollers::HasPollList(canbus* bus) + { + if (bus) + { + auto poller = GetPoller(bus, false); + return poller && poller->HasPollList(); + } + OvmsRecMutexLock lock(&m_poller_mutex); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + auto poller = m_pollers[i]; + if (poller && poller->HasPollList()) + return true; + } + return false; + } + +void OvmsPollers::Queue_Command(OvmsPoller::OvmsPollCommand cmd, uint16_t param) + { + if (!m_pollqueue) + return; + // Queues the command + OvmsPoller::poll_queue_entry_t entry; + memset(&entry, 0, sizeof(entry)); + entry.entry_type = OvmsPoller::OvmsPollEntryType::Command; + entry.entry_Command.cmd = cmd; + entry.entry_Command.parameter = param; + + ESP_LOGD(TAG, "Pollers: Queue Command()"); + if (xQueueSend(m_pollqueue, &entry, 0) != pdPASS) + ESP_LOGI(TAG, "Poller[Command]: Task Queue Overflow"); + } + +void OvmsPollers::Queue_PollerFrame(const CAN_frame_t &frame, bool success, bool istx) + { + if (m_shut_down) + return; + if (!Ready()) + return; + if (!m_pollqueue) + return; + // Queues the frame + OvmsPoller::poll_queue_entry_t entry; + memset(&entry, 0, sizeof(entry)); + entry.entry_type = istx ? OvmsPoller::OvmsPollEntryType::FrameTx : OvmsPoller::OvmsPollEntryType::FrameRx; + entry.entry_FrameRxTx.frame = frame; + entry.entry_FrameRxTx.success = success; + + ESP_LOGD(TAG, "Poller: Queue PollerFrame()"); + if (xQueueSend(m_pollqueue, &entry, 0) != pdPASS) + ESP_LOGI(TAG, "Poller[Frame]: Task Queue Overflow"); + } + +void OvmsPollers::PollSetState(uint8_t state, canbus* bus) + { + if (m_shut_down) + return; + + if (!m_pollqueue) + return; + + if (!bus) + { + m_poll_state = state; + } + + // Queues the command + OvmsPoller::poll_queue_entry_t entry; + memset(&entry, 0, sizeof(entry)); + entry.entry_type = OvmsPoller::OvmsPollEntryType::PollState; + entry.entry_PollState.new_state = state; + entry.entry_PollState.bus = bus; + + ESP_LOGD(TAG, "Pollers: Queue SetState()"); + if (xQueueSend(m_pollqueue, &entry, 0) != pdPASS) + ESP_LOGI(TAG, "Pollers[SetState]: Task Queue Overflow"); + } + +// signal poller (private) +void OvmsPollers::PollerResetThrottle() + { + if (m_shut_down) + return; + OvmsRecMutexLock lock(&m_poller_mutex); + for (int i = 0 ; i < VEHICLE_MAXBUSSES; ++i) + { + if (m_pollers[i]) + m_pollers[i]->ResetThrottle(); + } + } +void OvmsPollers::PollerStatus(int verbosity, OvmsWriter* writer) + { + auto curmon = monotonictime; + if (!HasPollTask()) + { + writer->puts("OBD Polling task is not running"); + return; + } + bool found_list = false; + for (uint8_t busno = 1; busno <= VEHICLE_MAXBUSSES; ++busno) + { + auto bus = GetBus(busno); + if (!bus) + continue; + OvmsPoller *poller = GetPoller(bus, false); + if (!poller) + continue; + bool has_active = poller->HasPollList(); + uint8_t state = poller->PollState(); + found_list = true; + writer->printf("Poller on Can%" PRIu8 "\n", busno); + writer->printf(" List: %s\n", (has_active ? "active" : "not active") ); + if (has_active) + writer->printf(" State: %" PRIu8 "\n", state); + + writer->printf(" Last Request: "); + auto last = poller->m_poll_sent_last; + if (last == 0) + writer->puts("None"); + else + writer->printf("%" PRIu8 "s (ticks)\n", (curmon - last)); + } + if (!found_list) + { + writer->puts("No OBD Pollers active."); + return; + } + writer->printf("Time between polling ticks: %" PRIu16 "ms\n", m_poll_tick_ms); + if (m_poll_tick_secondary > 0) + writer->printf("Secondary ticks: %" PRIu8 ".\n", m_poll_tick_secondary); + auto last = LastPollCmdReceived(); + writer->printf("Last Poll Received: "); + if (last == 0) + writer->puts("none"); + else + writer->printf("%" PRIu32 "s (ticks) ago\n", (curmon-last)); + + if (IsPaused() || IsUserPaused()) + writer->printf("OBD polling is Paused %s%s\n", IsPaused() ? "[system]":"", IsUserPaused() ? "[user]" : ""); + else + writer->puts("OBD polling is Resumed"); + } + +void OvmsPollers::SetUserPauseStatus(bool paused, int verbosity, OvmsWriter* writer) + { + if (paused) + { + PausePolling(true); + writer->puts("Vehicle OBD Polling Pause Requested"); + } + else + { + ResumePolling(true); + writer->puts("Vehicle OBD Polling Resume Requested"); + } + } + +void OvmsPollers::vehicle_poller_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) + { + MyPollers.PollerStatus(verbosity, writer); + } +void OvmsPollers::vehicle_pause_on(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) + { + MyPollers.SetUserPauseStatus(true, verbosity, writer); + } + +void OvmsPollers::vehicle_pause_off(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) + { + MyPollers.SetUserPauseStatus(false, verbosity, writer); + } + +static const char *PollResStr( OvmsPoller::OvmsNextPollResult res) + { + switch(res) + { + case OvmsPoller::OvmsNextPollResult::NotReady: return "NotReady"; + case OvmsPoller::OvmsNextPollResult::StillAtEnd: return "StillAtEnd"; + case OvmsPoller::OvmsNextPollResult::FoundEntry: return "FoundEntry"; + case OvmsPoller::OvmsNextPollResult::ReachedEnd: return "ReachedEnd"; + default: return "Unknown"; + } + } + +// List of Poll Series + +OvmsPoller::PollSeriesList::PollSeriesList() + : m_first(nullptr), m_last(nullptr), m_iter(nullptr) + { + } + +OvmsPoller::PollSeriesList::~PollSeriesList() + { + Clear(); + } + +void OvmsPoller::PollSeriesList::SetEntry(const std::string &name, std::shared_ptr series, bool blocking) + { + bool activate = blocking; + for (poll_series_t *it = m_first; it != nullptr; it = it->next) + { + if (it->name == name) + { + if (it->series == series) + return; + + if (it->series != nullptr) + it->series->Removing(); + it->series = series; + if (activate && it == m_first) + m_iter = it; + ESP_LOGD(TAG, "Poll List: Replaced Entry %s (%s)%s", it->name.c_str(), name.c_str(), activate ? " (active)" : ""); + return; + } + } + poll_series_t *newval = new poll_series_t; + newval->name = name; + newval->series = series; + newval->is_blocking = blocking; + newval->next = nullptr; + newval->prev = nullptr; + + poll_series_t *before = nullptr; // Default END. + if (blocking) + { + before = m_first; + while (before && before->is_blocking) + { + before = before->next; + activate = false; // not first. + } + } + + InsertBefore(newval, before); + if (activate) + m_iter = newval; + ESP_LOGD(TAG, "Poll List: Added Entry %s%s%s", newval->name.c_str(), blocking ? " (blocking)" : "", activate ? " (active)" : ""); + } + +bool OvmsPoller::PollSeriesList::IsEmpty() + { + return m_first == nullptr; + } + +void OvmsPoller::PollSeriesList::Remove( poll_series_t *iter) + { + if (!iter) + return; + auto iternext = iter->next; + auto iterprev = iter->prev; + iter->next = nullptr; + iter->prev = nullptr; + + if (m_iter == iter) + m_iter = iternext; + if (m_first == iter) + m_first = iternext; + if (m_last == iter) + m_last = iterprev; + + if (iterprev) + iterprev->next = iternext; + if (iternext) + iternext->prev = iterprev; + + ESP_LOGV(TAG, "Poll List: Removing series %s", iter->name.c_str()); + if (iter->series != nullptr) + iter->series->Removing(); + iter->series = nullptr; + ESP_LOGV(TAG, "Poll List: Deleting series %s", iter->name.c_str()); + delete iter; + } + +void OvmsPoller::PollSeriesList::InsertBefore( poll_series_t *iter, poll_series_t *before) + { + if (before == nullptr || !m_first) // End + { + iter->prev = m_last; + iter->next = nullptr; + if (m_last) + m_last->next = iter; + else + m_first = iter; + m_last = iter; + } + else + { + iter->next = before; + iter->prev = before->prev; + before->prev = iter; + + if (before == m_first) + m_first = iter; + } + } + +void OvmsPoller::PollSeriesList::RemoveEntry( const std::string &name, bool starts_with) + { + for (poll_series_t *it = m_first; it != nullptr; ) + { + bool found; + if (starts_with) + found = startsWith(it->name, name); + else + found = it->name == name; + + poll_series_t *thisone=it; + it = it->next; + if (found) + { + Remove(thisone); + if (!starts_with) + return; + } + } + } + +void OvmsPoller::PollSeriesList::Clear() + { + while (m_first != nullptr) + Remove(m_first); + } + +void OvmsPoller::PollSeriesList::RestartPoll(OvmsPoller::ResetMode mode) + { + m_iter = m_first; + for ( auto iter = m_first; iter != nullptr; iter = iter->next) + { + if (iter->series != nullptr) + iter->series->ResetList(mode); + } + } + +// Process an incoming packet. +void OvmsPoller::PollSeriesList::IncomingPacket(const OvmsPoller::poll_job_t& job, uint8_t* data, uint8_t length) + { + if ((m_iter != nullptr) && (m_iter->series != nullptr)) + { + ESP_LOGD(TAG, "Poll List:[%s] IncomingPacket TYPE:%x PID: %03x LEN: %d REM: %d ", m_iter->name.c_str(), job.type, job.pid, length, job.mlremain); + + m_iter->series->IncomingPacket(job, data, length); + } + else + { + ESP_LOGD(TAG, "Poll List:[Discard] IncomingPacket TYPE:%x PID: %03x LEN: %d REM: %d ", job.type, job.pid, length, job.mlremain); + } + } + +// Process An Error +void OvmsPoller::PollSeriesList::IncomingError(const OvmsPoller::poll_job_t& job, uint16_t code) + { + if ((m_iter != nullptr) && (m_iter->series != nullptr)) + { + ESP_LOGD(TAG, "Poll List:[%s] IncomingError TYPE:%x PID: %03x Code: %02X", m_iter->name.c_str(), job.type, job.pid, code); + m_iter->series->IncomingError(job, code); + } + else + { + ESP_LOGD(TAG, "Poll List:[Discard] IncomingError TYPE:%x PID: %03x Code: %02X", job.type, job.pid, code); + } + } + +/// Send on an imcoming TX reply +void OvmsPoller::PollSeriesList::IncomingTxReply(const OvmsPoller::poll_job_t& job, bool success) + { + if ((m_iter != nullptr) && (m_iter->series != nullptr)) + { + ESP_LOGD(TAG, "Poll List:[%s] IncomingTXReply TYPE:%x PID: %03x Success: %s", m_iter->name.c_str(), job.type, job.pid, success ? "true" : "false"); + m_iter->series->IncomingTxReply(job, success); + } + else + { + ESP_LOGD(TAG, "Poll List:[Discard] IncomingTXReply TYPE:%x PID: %03x Success %s", job.type, job.pid, success ? "true" : "false"); + } + + } + +OvmsPoller::OvmsNextPollResult OvmsPoller::PollSeriesList::NextPollEntry(poll_pid_t &entry, uint8_t mybus, uint32_t pollticker, uint8_t pollstate) + { + if (!m_first) + { + ESP_LOGV(TAG, "PollSeriesList::NextPollEntry - No List Set"); + return OvmsPoller::OvmsNextPollResult::StillAtEnd; + } + if (!m_iter) + { + ESP_LOGV(TAG, "PollSeriesList::NextPollEntry - Not Started"); + return OvmsPoller::OvmsNextPollResult::StillAtEnd; + } + + OvmsPoller::OvmsNextPollResult res = OvmsPoller::OvmsNextPollResult::StillAtEnd; + while (true) + { + int skipped = 0; + while (m_iter != nullptr && m_iter->series == nullptr) + { + m_iter = m_iter->next; + ++skipped; + } + if (skipped) + ESP_LOGV(TAG, "PollSeriesList::NextPollEntry Skipped %d", skipped); + + if (m_iter == nullptr) + return OvmsPoller::OvmsNextPollResult::ReachedEnd; + res = m_iter->series->NextPollEntry(entry, mybus, pollticker, pollstate); + + ESP_LOGD(TAG, "PollSeriesList::NextPollEntry[%s]: %s", m_iter->name.c_str(), PollResStr(res)); + + switch (res) + { + case OvmsPoller::OvmsNextPollResult::NotReady: + { + ESP_LOGV(TAG, "Poll Not Ready: '%s'", m_iter->name.c_str()); + m_iter = m_iter->next; + break; + } + case OvmsPoller::OvmsNextPollResult::StillAtEnd: + { + ESP_LOGV(TAG, "Poll Still at end: '%s'", m_iter->name.c_str()); + if (m_iter->is_blocking) + { + m_iter = nullptr; + return res; + } + + m_iter = m_iter->next; + break; + } + case OvmsPoller::OvmsNextPollResult::ReachedEnd: + { + switch (m_iter->series->FinishRun()) + { + case OvmsPoller::SeriesStatus::Next: + { + if (m_iter->is_blocking) + { + m_iter = nullptr; + return res; + } + m_iter = m_iter->next; + break;// Default .. move to next + } + case OvmsPoller::SeriesStatus::RemoveNext: + { + ESP_LOGD(TAG, "Poll Auto-Removing (next) '%s'", m_iter->name.c_str()); + Remove(m_iter); + break; + } + case OvmsPoller::SeriesStatus::RemoveRestart: + { + // Resume - skipping over 'StillAtEnd' iterators. + ESP_LOGD(TAG, "Poll Auto-Removing (restart) '%s'", m_iter->name.c_str()); + auto cur = m_iter; + m_iter = nullptr; + Remove(cur); + m_iter = m_first; + break; + } + default: + // Shouldn't happen. + if (m_iter->is_blocking) + { + m_iter = nullptr; + return res; + } + m_iter = m_iter->next; + } + break; + } + default: + return res; + } + } + } + +bool OvmsPoller::PollSeriesList::HasPollList() + { + for (auto it = m_first; it != nullptr; it = it->next) + { + if (it->series != nullptr && it->series->HasPollList()) + return true; + } + return false; + } + +bool OvmsPoller::PollSeriesList::HasRepeat() + { + for (auto it = m_first; it != nullptr; it = it->next) + { + if (it->series != nullptr && it->series->HasRepeat()) + return true; + } + return false; + } + +// Poll Series base +/// Send on an imcoming TX reply +void OvmsPoller::PollSeriesEntry::IncomingTxReply(const OvmsPoller::poll_job_t& job, bool success) + { + // ignore + } + +bool OvmsPoller::PollSeriesEntry::Ready() + { + return true; + } +// Standard Poll Series - Replaces the original functionality + +// Standard Poll Series class +OvmsPoller::StandardPollSeries::StandardPollSeries(OvmsPoller *poller, uint16_t stateoffset ) + : m_poller(poller), m_state_offset(stateoffset), m_defaultbus(0), m_poll_plist(nullptr), m_poll_plcur(nullptr) + { + } +void OvmsPoller::StandardPollSeries::SetParentPoller(OvmsPoller *poller) + { + m_poller = poller; + } + +// Set the PID List in use. +void OvmsPoller::StandardPollSeries::PollSetPidList(uint8_t defaultbus, const poll_pid_t* plist) + { + ESP_LOGV(TAG, "Standard Poll Series: PID List set"); + m_poll_plcur = nullptr; + m_poll_plist = plist; + m_defaultbus = defaultbus; + } + +void OvmsPoller::StandardPollSeries::ResetList(OvmsPoller::ResetMode mode) + { + if (mode == OvmsPoller::ResetMode::PollReset) + { + ESP_LOGV(TAG, "Standard Poll Series: List reset"); + m_poll_plcur = NULL; + } + } + +OvmsPoller::OvmsNextPollResult OvmsPoller::StandardPollSeries::NextPollEntry(poll_pid_t &entry, uint8_t mybus, uint32_t pollticker, uint8_t pollstate) + { + + entry = {}; + + if (!Ready()) + return OvmsNextPollResult::NotReady; + + // Offset pollstate and check it is within polltime bounds. + if (pollstate < m_state_offset) + return OvmsNextPollResult::StillAtEnd; + pollstate -= m_state_offset; + if (pollstate >= VEHICLE_POLL_NSTATES) + return OvmsNextPollResult::StillAtEnd; + + // Restart poll list cursor: + if (m_poll_plcur == NULL) + m_poll_plcur = m_poll_plist; + else if (m_poll_plcur->txmoduleid == 0) + return OvmsNextPollResult::StillAtEnd; + else + ++m_poll_plcur; + + while (m_poll_plcur->txmoduleid != 0) + { + uint8_t bus = m_poll_plcur->pollbus; + if (bus == 0) + bus = m_defaultbus; + if (mybus == bus) + { + uint16_t polltime = m_poll_plcur->polltime[pollstate]; + if (( polltime > 0) && ((pollticker % polltime) == 0)) + { + entry = *m_poll_plcur; + ESP_LOGD(TAG, "Found Poll Entry for Standard Poll"); + return OvmsNextPollResult::FoundEntry; + } + } + // Poll entry is not due, check next + ++m_poll_plcur; + } + return OvmsNextPollResult::ReachedEnd; + } + +void OvmsPoller::StandardPollSeries::IncomingPacket(const OvmsPoller::poll_job_t& job, uint8_t* data, uint8_t length) + { + } + +void OvmsPoller::StandardPollSeries::IncomingError(const OvmsPoller::poll_job_t& job, uint16_t code) + { + } + +OvmsPoller::SeriesStatus OvmsPoller::StandardPollSeries::FinishRun() + { + return OvmsPoller::SeriesStatus::Next; + } + +void OvmsPoller::StandardPollSeries::Removing() + { + m_poller = nullptr; + } + +bool OvmsPoller::StandardPollSeries::HasPollList() + { + return (m_defaultbus != 0) + && (m_poll_plist != NULL) + && (m_poll_plist->txmoduleid != 0); + } + +bool OvmsPoller::StandardPollSeries::HasRepeat() + { + return false; + } + +// StandardVehiclePollSeries +OvmsPoller::StandardVehiclePollSeries::StandardVehiclePollSeries(OvmsPoller *poller, + VehicleSignal *signal, + uint16_t stateoffset) + : StandardPollSeries(poller, stateoffset), m_signal(signal) + { + } +// Process an incoming packet. +void OvmsPoller::StandardVehiclePollSeries::IncomingPacket(const OvmsPoller::poll_job_t& job, uint8_t* data, uint8_t length) + { + if (m_signal) + m_signal->IncomingPollReply(job, data, length); + } + +// Process An Error. +void OvmsPoller::StandardVehiclePollSeries::IncomingError(const OvmsPoller::poll_job_t& job, uint16_t code) + { + if (m_signal) + m_signal->IncomingPollError(job, code); + } + +// Return true if this series is ok to run. +bool OvmsPoller::StandardVehiclePollSeries::Ready() + { + return (!m_signal) || m_signal->Ready(); + } + +// Send on an imcoming TX reply +void OvmsPoller::StandardVehiclePollSeries::IncomingTxReply(const OvmsPoller::poll_job_t& job, bool success) + { + if (m_signal) + m_signal->IncomingPollTxCallback(job, success); + } + +// StandardPacketPollSeries + +OvmsPoller::StandardPacketPollSeries::StandardPacketPollSeries( OvmsPoller *poller, int repeat_max, poll_success_func success, poll_fail_func fail) + : StandardPollSeries(poller), + m_repeat_max(repeat_max), + m_repeat_count(0), + m_success(success), + m_fail(fail) + { + } + +// Move list to start. +void OvmsPoller::StandardPacketPollSeries::ResetList(OvmsPoller::ResetMode mode) + { + switch (mode) + { + case OvmsPoller::ResetMode::PollReset: + m_repeat_count = 0; + break; + case OvmsPoller::ResetMode::LoopReset: + if (++m_repeat_count >= m_repeat_max) + return; // No more retries... don't reset the loop. + break; + } + // Do the base Poll reset - ignore passed-in. + OvmsPoller::StandardPollSeries::ResetList(OvmsPoller::ResetMode::PollReset); + } + +// Process an incoming packet. +void OvmsPoller::StandardPacketPollSeries::IncomingPacket(const OvmsPoller::poll_job_t& job, uint8_t* data, uint8_t length) + { + if (job.mlframe == 0) + { + m_data.clear(); + m_data.reserve(length+job.mlremain); + } + m_data.append((char*)data, length); + if (job.mlremain == 0) + { + if (m_success) + m_success(job.type, job.moduleid_sent, job.moduleid_rec, job.pid, m_data); + m_data.clear(); + } + } + +// Process An Error +void OvmsPoller::StandardPacketPollSeries::IncomingError(const OvmsPoller::poll_job_t& job, uint16_t code) + { + if (code == 0) + { + ESP_LOGD(TAG, "Packet failed with zero error %.03" PRIx32 " TYPE:%x PID: %03x", job.moduleid_rec, job.type, job.pid); + m_data.clear(); + if (m_success) + m_success(job.type, job.moduleid_sent, job.moduleid_rec, job.pid, m_data); + } + else + { + if (m_fail) + m_fail(job.type, job.moduleid_sent, job.moduleid_rec, job.pid, code); + OvmsPoller::StandardPollSeries::IncomingError(job, code); + } + } + +// Return true if this series has entries to retry/redo. +bool OvmsPoller::StandardPacketPollSeries::HasRepeat() + { + return (m_repeat_count < m_repeat_max) && HasPollList(); + } + +// OvmsPoller::OnceOffPollBase class + +OvmsPoller::OnceOffPollBase::OnceOffPollBase( const poll_pid_t &pollentry, std::string *rxbuf, int *rxerr, uint8_t retry_fail) + : m_sent(status_t::Init), m_poll(pollentry), m_poll_rxbuf(rxbuf), m_poll_rxerr(rxerr), + m_retry_fail(retry_fail) + { + } +void OvmsPoller::OnceOffPollBase::SetParentPoller(OvmsPoller *poller) + { + } + +OvmsPoller::OnceOffPollBase::OnceOffPollBase(std::string *rxbuf, int *rxerr, uint8_t retry_fail) + : m_sent(status_t::Init), + m_poll({ 0, 0, 0, 0, { 1, 1, 1, 1 }, 0, 0 }), + m_poll_rxbuf(rxbuf), m_poll_rxerr(rxerr), + m_retry_fail(retry_fail) + { + } + +void OvmsPoller::OnceOffPollBase::Done(bool success) + { + } + +void OvmsPoller::OnceOffPollBase::SetPollPid( uint32_t txid, uint32_t rxid, const std::string &request, uint8_t protocol, uint8_t pollbus, uint16_t polltime) + { + // prepare single poll: + m_poll = { txid, rxid, 0, 0, { polltime, polltime, polltime, polltime }, pollbus, protocol }; + + assert(request.size() > 0); + m_poll.type = request[0]; + m_poll.xargs.tag = POLL_TXDATA; + + m_poll_data = request; + if (POLL_TYPE_HAS_16BIT_PID(m_poll.type)) + { + assert(request.size() >= 3); + m_poll.xargs.pid = request[1] << 8 | request[2]; + m_poll.xargs.datalen = LIMIT_MAX(request.size()-3, 4095); + m_poll.xargs.data = (const uint8_t*)m_poll_data.data()+3; + } + else if (POLL_TYPE_HAS_8BIT_PID(m_poll.type)) + { + assert(request.size() >= 2); + m_poll.xargs.pid = request.at(1); + m_poll.xargs.datalen = LIMIT_MAX(request.size()-2, 4095); + m_poll.xargs.data = (const uint8_t*)m_poll_data.data()+2; + } + else + { + m_poll.xargs.pid = 0; + m_poll.xargs.datalen = LIMIT_MAX(request.size()-1, 4095); + m_poll.xargs.data = (const uint8_t*)m_poll_data.data()+1; + } + } + +void OvmsPoller::OnceOffPollBase::SetPollPid( uint32_t txid, uint32_t rxid, uint8_t polltype, uint16_t pid, uint8_t protocol, uint8_t pollbus, uint16_t polltime) + { + m_poll = { txid, rxid, polltype, pid, { polltime, polltime, polltime, polltime }, pollbus, protocol }; + } + +// Move list to start. +void OvmsPoller::OnceOffPollBase::ResetList(OvmsPoller::ResetMode mode) + { + switch(m_sent) + { + case status_t::Init: + ESP_LOGD(TAG, "Once Off Poll: List reset to start"); + break; + case status_t::Retry: + if (mode == OvmsPoller::ResetMode::PollReset) + { + if (!m_retry_fail) + { + m_sent = status_t::Stopping; + return; + } + ESP_LOGD(TAG, "Once Off Poll: Reset for retry"); + --m_retry_fail; + m_sent = status_t::RetryInit; + } + break; + default: + m_sent = status_t::Stopping; + } + } + +// Find the next poll entry. +OvmsPoller::OvmsNextPollResult OvmsPoller::OnceOffPollBase::NextPollEntry(poll_pid_t &entry, uint8_t mybus, uint32_t pollticker, uint8_t pollstate) + { + switch (m_sent) + { + case status_t::Init: + { + entry = m_poll; + m_sent = status_t::Sent; + return OvmsNextPollResult::FoundEntry; + } + case status_t::RetrySent: + case status_t::Sent: + { + if ((m_retry_fail == 0) || (!m_poll_rxerr) || *m_poll_rxerr == 0) + m_sent = status_t::Stopped; + else + { + m_sent = status_t::Retry; + ESP_LOGD(TAG, "Once Off Poll: Retries left %d", m_retry_fail); + } + return OvmsPoller::OvmsNextPollResult::ReachedEnd; + } + case status_t::Stopping: + m_sent = status_t::Stopped; + return OvmsPoller::OvmsNextPollResult::ReachedEnd; + case status_t::Stopped: + return OvmsPoller::OvmsNextPollResult::StillAtEnd; + case status_t::Retry: + return OvmsPoller::OvmsNextPollResult::StillAtEnd; + case status_t::RetryInit: + { + entry = m_poll; + m_sent = status_t::RetrySent; + return OvmsNextPollResult::FoundEntry; + } + default: + return OvmsPoller::OvmsNextPollResult::StillAtEnd; + } + } + +// Process an incoming packet. +void OvmsPoller::OnceOffPollBase::IncomingPacket(const OvmsPoller::poll_job_t& job, uint8_t* data, uint8_t length) + { + if (m_poll_rxbuf != nullptr) + { + if (job.mlframe == 0 ) + { + m_poll_rxbuf->clear(); + m_poll_rxbuf->reserve(length+job.mlremain); + } + m_poll_rxbuf->append((char*)data, length); + } + if (job.mlremain == 0) + { + if (m_poll_rxerr) + *m_poll_rxerr = 0; + + m_sent = status_t::Stopping; + Done(true); + } + } + +// Process An Error +void OvmsPoller::OnceOffPollBase::IncomingError(const OvmsPoller::poll_job_t& job, uint16_t code) + { + if (m_poll_rxerr) + *m_poll_rxerr = code; + if (m_poll_rxbuf) + m_poll_rxbuf->clear(); + if (code == 0) + m_sent = status_t::Stopping; + else + { + switch (m_sent) + { + case status_t::Retry: return; + case status_t::Sent: + case status_t::RetrySent: + { + if (m_retry_fail > 0) + { + m_sent = status_t::Retry; + return; + } + m_sent = status_t::Stopping; + } + default: break; + } + } + Done(false); + } + +bool OvmsPoller::OnceOffPollBase::HasPollList() + { + return m_poll.txmoduleid != 0; + } + +bool OvmsPoller::OnceOffPollBase::HasRepeat() + { + return false; // Don't retry in same tick. + } + +// OvmsPoller::BlockingOnceOffPoll class +OvmsPoller::BlockingOnceOffPoll::BlockingOnceOffPoll(const poll_pid_t &pollentry, std::string *rxbuf, int *rxerr, OvmsSemaphore *rxdone ) + : OvmsPoller::OnceOffPollBase(pollentry, rxbuf, rxerr), m_poll_rxdone(rxdone) + { + } + +void OvmsPoller::BlockingOnceOffPoll::Done(bool success) + { + if (m_poll_rxdone) + { + ESP_LOGD(TAG, "Blocking Poll: Done %s", success ? "success" : "fail"); + m_poll_rxdone->Give(); + Finished(); + } + } + +// Called when run is finished to determine what happens next. +OvmsPoller::SeriesStatus OvmsPoller::BlockingOnceOffPoll::FinishRun() + { + return OvmsPoller::SeriesStatus::RemoveRestart; + } + + +void OvmsPoller::BlockingOnceOffPoll::Removing() + { + Done(false); + } + +// Called To null out the call-back pointers. +void OvmsPoller::BlockingOnceOffPoll::Finished() + { + m_poll_rxbuf = nullptr; + m_poll_rxdone = nullptr; + m_poll_rxerr = nullptr; + } + +// OnceOffPoll class + +// Once off poll entry with buffer and result. +OvmsPoller::OnceOffPoll::OnceOffPoll(poll_success_func success, poll_fail_func fail, + uint32_t txid, uint32_t rxid, const std::string &request, uint8_t protocol, uint8_t pollbus, uint8_t retry_fail) + : OvmsPoller::OnceOffPollBase(nullptr, &m_error, retry_fail), + m_success(success), m_fail(fail), m_error(0) + { + m_poll_rxbuf = &m_data; + SetPollPid(txid, rxid, request, protocol, pollbus); + } + +OvmsPoller::OnceOffPoll::OnceOffPoll( poll_success_func success, poll_fail_func fail, + uint32_t txid, uint32_t rxid, uint8_t polltype, uint16_t pid, uint8_t protocol, uint8_t pollbus, uint8_t retry_fail) + : OvmsPoller::OnceOffPollBase( nullptr, &m_error, retry_fail), + m_success(success), m_fail(fail), m_error(0) + { + m_poll_rxbuf = &m_data; + SetPollPid(txid, rxid, polltype, pid, protocol, pollbus); + } + +void OvmsPoller::OnceOffPoll::Done(bool success) + { + ESP_LOGD(TAG, "Once-Off Poll: Done %s", success ? "success" : "fail"); + m_poll_rxbuf = nullptr; + if (success) + { + if (m_success) + m_success(m_poll.type, m_poll.txmoduleid, m_poll.rxmoduleid, m_poll.pid, m_data ); + } + else + { + if (m_fail) + m_fail(m_poll.type, m_poll.txmoduleid, m_poll.rxmoduleid, m_poll.pid, m_error); + } + } + +// Called when run is finished to determine what happens next. +OvmsPoller::SeriesStatus OvmsPoller::OnceOffPoll::FinishRun() + { + if (m_sent == status_t::Retry) + return OvmsPoller::SeriesStatus::Next; + return OvmsPoller::SeriesStatus::RemoveNext; + } + +void OvmsPoller::OnceOffPoll::Removing() + { + } diff --git a/vehicle/OVMS.V3/components/poller/src/vehicle_poller.h b/vehicle/OVMS.V3/components/poller/src/vehicle_poller.h new file mode 100644 index 000000000..4f581596e --- /dev/null +++ b/vehicle/OVMS.V3/components/poller/src/vehicle_poller.h @@ -0,0 +1,809 @@ +/* +; Project: Open Vehicle Monitor System +; Date: 9th April 2023 + +; +; 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. +*/ +#ifndef __VEHICLE_POLLER_H__ +#define __VEHICLE_POLLER_H__ + +#include "vehicle_common.h" + +#include + +// Number of polling states supported +#define VEHICLE_POLL_NSTATES 4 + +// VWTP_20 channel states: +typedef enum + { + VWTP_Closed = 0, // Channel not connecting/connected + VWTP_ChannelClose, // Close request has been sent + VWTP_ChannelSetup, // Setup request has been sent + VWTP_ChannelParams, // Params request has been sent + VWTP_Idle, // Connection established, idle + VWTP_StartPoll, // Transit state: begin request transmission + VWTP_Transmit, // Request transmission phase + VWTP_Receive, // Response reception phase + VWTP_AbortXfer, // Transit state: abort request/response + } vwtp_channelstate_t; + +// VWTP_20 channel: +typedef struct + { + vwtp_channelstate_t state; // VWTP channel state + canbus* bus; // CAN bus + uint16_t baseid; // Protocol base CAN MsgID (usually 0x200) + uint8_t moduleid; // Logical address (ID) of destination module + uint16_t txid; // CAN MsgID we transmit on + uint16_t rxid; // CAN MsgID we listen to + uint8_t blocksize; // Number of blocks (data frames) to send between ACKs + uint32_t acktime; // Max time [us] to wait for ACK (not yet implemented) + uint32_t septime; // Min separation time [us] between blocks (data frames) + uint32_t txseqnr; // TX packet sequence number + uint32_t rxseqnr; // RX packet sequence number + uint32_t lastused; // Timestamp of last channel access + } vwtp_channel_t; + +class OvmsPollers; + +class OvmsPoller : public InternalRamAllocated { + public: + typedef struct + { + uint32_t txmoduleid; // transmission CAN ID (address), 0x7df = OBD2 broadcast + uint32_t rxmoduleid; // expected response CAN ID or 0 for broadcasts + uint16_t type; // UDS poll type / OBD2 "mode", see VEHICLE_POLL_TYPE_… + union + { + uint16_t pid; // PID (shortcut for requests w/o payload) + struct + { + uint16_t pid; // PID for requests with additional payload + uint8_t datalen; // payload length (bytes) + uint8_t data[6]; // inline payload data (single frame request) + } args; + struct + { + uint16_t pid; // PID for requests with additional payload + uint8_t tag; // needs to be POLL_TXDATA + uint16_t datalen; // payload length (bytes, max 4095) + const uint8_t* data; // pointer to payload data (single/multi frame request) + } xargs; + }; + uint16_t polltime[VEHICLE_POLL_NSTATES]; // poll intervals in seconds for used poll states + uint8_t pollbus; // 0 = default CAN bus from PollSetPidList(), 1…4 = specific + uint8_t protocol; // ISOTP_STD / ISOTP_EXTADR / ISOTP_EXTFRAME / VWTP_20 + } poll_pid_t; + + typedef struct + { + canbus* bus; ///< Bus to poll on. + uint8_t bus_no; + + uint32_t protocol; ///< ISOTP_STD / ISOTP_EXTADR / ISOTP_EXTFRAME / VWTP_20. + uint16_t type; ///< Expected type + uint16_t pid; ///< Expected PID + uint32_t moduleid_sent; ///< ModuleID last sent + uint32_t moduleid_low; ///< Expected response moduleid low mark + uint32_t moduleid_high; ///< Expected response moduleid high mark + uint32_t moduleid_rec; ///< Actual received moduleid. + uint16_t mlframe; ///< Frame number for multi frame response + uint16_t mloffset; ///< Current Byte offset of multi frame response + uint16_t mlremain; ///< Bytes remaining for multi frame response + poll_pid_t entry; ///< Currently processed entry of poll list (copy) + uint32_t ticker; ///< Polling tick count + } poll_job_t; + + typedef enum : uint8_t { Primary, Secondary, Successful, OnceOff } poller_source_t; + +// Macro for poll_pid_t termination +#define POLL_LIST_END { 0, 0, 0x00, 0x00, { 0, 0, 0 }, 0, 0 } + + // Interface for polls generated from the Vehicle class. + class VehicleSignal + { + public: + virtual ~VehicleSignal() { } + // Signals for vehicle + virtual void IncomingPollReply(const OvmsPoller::poll_job_t &job, uint8_t* data, uint8_t length); + virtual void IncomingPollError(const OvmsPoller::poll_job_t &job, uint16_t code); + virtual void IncomingPollTxCallback(const OvmsPoller::poll_job_t &job, bool success); + virtual void IncomingPollRxFrame(canbus* bus, CAN_frame_t* frame, bool success); + virtual bool Ready() = 0; + }; + enum class OvmsNextPollResult + { + NotReady, ///< Not ready - ignore and move on. + StillAtEnd, ///< Still at end of list. + FoundEntry, ///< Found an entry + ReachedEnd, ///< Reached the end. + Ignore ///< Ignore this (don't move to next poller index) + }; + + enum class SeriesStatus + { + Next, ///< Move to Next Poll Result + RemoveNext, ///< Remove Series from list and keep going. + RemoveRestart ///< Remove series and resume from start + }; + + enum class ResetMode { + PollReset, ///< Reset for poll start + LoopReset ///< Reset for retry loop. + }; + + /// Class that defines a series list. + class PollSeriesEntry + { + public: + /// Set the parent poller + virtual void SetParentPoller(OvmsPoller *poller) = 0; + + /** Move list to start. + * @arg mode Specify Resetting for Poll-Start or for Retry + */ + virtual void ResetList(ResetMode mode) = 0; + + /// Find the next poll entry. + virtual OvmsPoller::OvmsNextPollResult NextPollEntry(poll_pid_t &entry, uint8_t mybus, uint32_t pollticker, uint8_t pollstate) = 0; + /// Process an incoming packet. + virtual void IncomingPacket(const OvmsPoller::poll_job_t& job, uint8_t* data, uint8_t length) = 0; + /// Process An Error + virtual void IncomingError(const OvmsPoller::poll_job_t& job, uint16_t code) = 0; + + /// Send on an imcoming TX reply + virtual void IncomingTxReply(const OvmsPoller::poll_job_t& job, bool success); + + /// Called when run is finished to determine what happens next. + virtual SeriesStatus FinishRun() = 0; + + /// Called before removing from list. + virtual void Removing() = 0; + + /// Return true if this series has any entries + virtual bool HasPollList() = 0; + + /** Return true if this series has entries to retry/redo. + This should mean that the list has been finished at least once, + but also that the remaining todo don't NEED to be done before moving on. + */ + virtual bool HasRepeat() = 0; + /** Return true if this series is ok to run. + */ + virtual bool Ready(); + }; + + /// Named element in the series double-linked list. + typedef struct poll_series_st + { + std::string name; + std::shared_ptr series; + + bool is_blocking; + + struct poll_series_st *prev, *next; + } poll_series_t; + + /** A collection of Poll Series entries. + * Also behaves as the iterator controlling which is the current series/entry. + * No thread safety included, so relies on the mutex blocking calls to it. + */ + class PollSeriesList + { + private: + // Each end of the list. + poll_series_t *m_first, *m_last; + // Current poll entry. + poll_series_t *m_iter; + + // Remove an item out of the linked list. + void Remove( poll_series_t *iter); + // Insert an item into the linked list (before == null means the end) + void InsertBefore( poll_series_t *iter, poll_series_t *before); + public: + PollSeriesList(); + ~PollSeriesList(); + + // List functions: + + /** Set/Add the named entry to the series specified. + * @param name Name of the entry. + * @param series Shared pointer to the series + * @param blocking This is a blocking poll entry. + */ + void SetEntry(const std::string &name, std::shared_ptr series, bool blocking = false); + /// Remove the named entry from the list. + void RemoveEntry( const std::string &name, bool starts_with = false); + /// Are there any entries/lists + bool IsEmpty(); + /// Clear the list. + void Clear(); + + // Processing functions. + + /// Process an incoming packet. + void IncomingPacket(const OvmsPoller::poll_job_t& job, uint8_t* data, uint8_t length); + + /// Process An Error + void IncomingError(const OvmsPoller::poll_job_t& job, uint16_t code); + + /// Send on an imcoming TX reply + void IncomingTxReply(const OvmsPoller::poll_job_t& job, bool success); + + /// Reset the list to beging processing + void RestartPoll(ResetMode mode); + + /// Get the next poll entry + OvmsPoller::OvmsNextPollResult NextPollEntry(poll_pid_t &entry, uint8_t mybus, uint32_t pollticker, uint8_t pollstate); + + /// Are there any lists that have active entries? + bool HasPollList(); + + /** Return true if the current item is marked as blocking. + */ + bool PollIsBlocking() + { + return (m_iter != nullptr) && (m_iter->is_blocking) && (m_iter->series != nullptr); + } + + /** Return true if this series has entries to retry/redo. + This should mean that the list has been finished at least once, + but also that the remaining todo don't NEED to be done before moving on. + */ + bool HasRepeat(); + + }; + + /** Standard series. + * The main functionality of processing an array of poll_pid_t based on the pollstate and ticker. + */ + class StandardPollSeries : public PollSeriesEntry + { + protected: + OvmsPoller *m_poller; + uint16_t m_state_offset; // Offset of 'state' entries. + + uint8_t m_defaultbus; + + const poll_pid_t* m_poll_plist; // Head of poll list + const poll_pid_t* m_poll_plcur; // Poll list loop cursor + + public: + StandardPollSeries(OvmsPoller *poller, uint16_t stateoffset = 0); + + void SetParentPoller(OvmsPoller *poller) override; + + /// Set the PID list and default bus. + void PollSetPidList(uint8_t defaultbus, const poll_pid_t* plist); + + // Move list to start. + void ResetList(ResetMode mode) override; + + // Find the next poll entry. + OvmsPoller::OvmsNextPollResult NextPollEntry(poll_pid_t &entry, uint8_t mybus, uint32_t pollticker, uint8_t pollstate) override; + + // Process an incoming packet. (pass through to m_poller) + void IncomingPacket(const OvmsPoller::poll_job_t& job, uint8_t* data, uint8_t length) override; + + // Process An Error. (pass through to m_poller) + void IncomingError(const OvmsPoller::poll_job_t& job, uint16_t code) override; + + // Called when run is finished to determine what happens next. + SeriesStatus FinishRun() override; + + void Removing() override; + + bool HasPollList() override; + + bool HasRepeat() override; + }; + + // Standard Vehicle Poll series passing through various responses. + class StandardVehiclePollSeries : public StandardPollSeries + { + private: + VehicleSignal *m_signal; + public: + StandardVehiclePollSeries(OvmsPoller *poller, VehicleSignal *signal, uint16_t stateoffset = 0); + + // Process an incoming packet. + void IncomingPacket(const OvmsPoller::poll_job_t& job, uint8_t* data, uint8_t length) override; + + // Process An Error + void IncomingError(const OvmsPoller::poll_job_t& job, uint16_t code) override; + + // Send on an imcoming TX reply + void IncomingTxReply(const OvmsPoller::poll_job_t& job, bool success) override; + + // Return true if this series is ok to run. + bool Ready() override; + }; + + typedef std::function poll_success_func; + typedef std::function poll_fail_func; + + /** Standard Poll Series that assembles packets to complete results. + */ + class StandardPacketPollSeries : public StandardPollSeries + { + protected: + std::string m_data; + int m_repeat_max, m_repeat_count; + poll_success_func m_success; + poll_fail_func m_fail; + public: + StandardPacketPollSeries( OvmsPoller *poller, int repeat_max, poll_success_func success, poll_fail_func fail); + + // Move list to start. + void ResetList(ResetMode mode) override; + + // Process an incoming packet. + void IncomingPacket(const OvmsPoller::poll_job_t& job, uint8_t* data, uint8_t length) override; + + // Process An Error + void IncomingError(const OvmsPoller::poll_job_t& job, uint16_t code) override; + + // Return true if this series has entries to retry/redo. + bool HasRepeat() override; + }; + + /** Base for Once off Poll series. + */ + class OnceOffPollBase : public PollSeriesEntry + { + protected: + enum class status_t { + Init, ///< Initialised, entry can be sent + Sent, ///< Entry is sent. (Next mode is 'finished'); + Stopping, ///< Success or reached retries. + Stopped, ///< Has been stopped. + Retry, /// Ready for retry + RetryInit, /// Reset for retry + RetrySent, /// Retry has sent + }; + status_t m_sent; + std::string m_poll_data; // Request buffer data + poll_pid_t m_poll; // Poll Entry + std::string *m_poll_rxbuf; // … response buffer + int *m_poll_rxerr; // … response error code (NRC) / TX failure code + uint8_t m_retry_fail; + + // Called when the one-off is finished (for semaphore etc). + // Called with NULL bus when on Removing + virtual void Done(bool success); + + void SetPollPid( uint32_t txid, uint32_t rxid, const std::string &request, uint8_t protocol=ISOTP_STD, uint8_t pollbus = 0, uint16_t polltime = 1); + void SetPollPid( uint32_t txid, uint32_t rxid, uint8_t polltype, uint16_t pid, uint8_t protocol=ISOTP_STD, uint8_t pollbus = 0, uint16_t polltime = 1); + public: + OnceOffPollBase(const poll_pid_t &pollentry, std::string *rxbuf, int *rxerr, uint8_t retry_fail = 0); + OnceOffPollBase(std::string *rxbuf, int *rxerr, uint8_t retry_fail = 0); + + // Move list to start. + void ResetList(ResetMode mode) override; + + void SetParentPoller(OvmsPoller *poller) override; + + // Find the next poll entry. + OvmsPoller::OvmsNextPollResult NextPollEntry(poll_pid_t &entry, uint8_t mybus, uint32_t pollticker, uint8_t pollstate) override; + + // Process an incoming packet. + void IncomingPacket(const OvmsPoller::poll_job_t& job, uint8_t* data, uint8_t length) override; + + // Process An Error + void IncomingError(const OvmsPoller::poll_job_t& job, uint16_t code) override; + + bool HasPollList() override; + + bool HasRepeat() override; + }; + + private: + /** Blocking once off poll entry used by PollSingleRequest. + * Signals Semaphore when done. Used for once-off blocking calls. + * Because this is used poentially passing in stack variables, I'm going to + * leave this as private as it really needs to have Finished() called before + * the variables go out of scope. + */ + class BlockingOnceOffPoll : public OnceOffPollBase + { + protected: + OvmsSemaphore *m_poll_rxdone; // … response done (ok/error) + void Done(bool success) override; + public: + BlockingOnceOffPoll(const poll_pid_t &pollentry, std::string *rxbuf, int *rxerr, OvmsSemaphore *rxdone ); + + // Called To null out the call-back pointers (particularly before they go out of scope). + void Finished(); + public: + // Called when run is finished to determine what happens next. + SeriesStatus FinishRun() override; + + void Removing() override; + }; + public: + /** Once off poll entry with buffer and asynchronous result call-backs. + */ + class OnceOffPoll : public OnceOffPollBase + { + protected: + poll_success_func m_success; + poll_fail_func m_fail; + std::string m_data; + int m_error; + + void Done(bool success) override; + public: + OnceOffPoll(poll_success_func success, poll_fail_func fail, + uint32_t txid, uint32_t rxid, const std::string &request, uint8_t protocol=ISOTP_STD, uint8_t pollbus = 0, uint8_t retry_fail = 0); + OnceOffPoll(poll_success_func success, poll_fail_func fail, + uint32_t txid, uint32_t rxid, uint8_t polltype, uint16_t pid, uint8_t protocol=ISOTP_STD, uint8_t pollbus = 0, uint8_t retry_fail = 0); + + // Called when run is finished to determine what happens next. + SeriesStatus FinishRun() override; + + void Removing() override; + }; + + protected: + OvmsPollers* m_parent; + + OvmsRecMutex m_poll_mutex; // Concurrency protection for recursive calls + uint8_t m_poll_state; // Current poll state + private: + // Poll state + + PollSeriesList m_polls; // User poll entries list. + int m_poll_repeat_count; // 'Extra repeats' for polling. + std::shared_ptr m_poll_series; + + const int max_poll_repeat = 5; // Maximum # of poll-repeats. + uint32_t m_poll_sent_last; + + protected: + poll_job_t m_poll; + const uint8_t* m_poll_tx_data; // Payload data for multi frame request + uint16_t m_poll_tx_remain; // Payload bytes remaining for multi frame request + uint16_t m_poll_tx_offset; // Payload offset of multi frame request + uint16_t m_poll_tx_frame; // Frame number for multi frame request + uint8_t m_poll_wait; // Wait counter for a reply from a sent poll or bytes remaining. + // Gets set = 2 when a poll is sent OR when bytes are remaining after receiving. + // Gets set = 0 when a poll is received. + // Gets decremented with every second/tick in PollerSend(). + // PollerSend() aborts when > 0. + // Why set = 2: When a poll gets send just before the next ticker occurs + // PollerSend() decrements to 1 and doesn't send the next poll. + // Only when the reply doesn't get in until the next ticker occurs + // PollserSend() decrements to 0 and abandons the outstanding reply (=timeout) + + CanFrameCallback m_poll_txcallback; // Poller CAN TxCallback + uint32_t m_poll_txmsgid; // Poller last TX CAN ID (frame MsgID) + + + private: + uint8_t m_poll_sequence_max; // Polls allowed to be sent in sequence per time tick (second), default 1, 0 = no limit + uint8_t m_poll_sequence_cnt; // Polls already sent in the current time tick (second) + uint8_t m_poll_fc_septime; // Flow control separation time for multi frame responses + uint16_t m_poll_ch_keepalive; // Seconds to keep an inactive channel (e.g. VWTP) alive (default: 60) + uint16_t m_poll_between_success; + bool m_poll_ticked; + bool m_poll_run_finished; + + private: + OvmsRecMutex m_poll_single_mutex; // PollSingleRequest() concurrency protection + + protected: + vwtp_channel_t m_poll_vwtp; // VWTP channel state + + protected: + + // Signals for vehicle + void PollRunFinished(); + + void IncomingPollError(const OvmsPoller::poll_job_t &job, uint16_t code) + { + m_polls.IncomingError(job, code); + } + void IncomingPollTxCallback(const OvmsPoller::poll_job_t &job, bool success); + + bool Ready(); + private: + void PollerSend(poller_source_t source); + + void PollerISOTPStart(bool fromTicker); + bool PollerISOTPReceive(CAN_frame_t* frame, uint32_t msgid); + + void PollerVWTPStart(bool fromTicker); + bool PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid); + void PollerVWTPEnter(vwtp_channelstate_t state); + void PollerVWTPTicker(); + void PollerVWTPTxCallback(const CAN_frame_t* frame, bool success); + + static void DoPollerSendSuccess( void * pvParameter1, uint32_t ulParameter2 ); + + public: + bool HasBus(canbus* bus) { return bus == m_poll.bus;} + uint8_t CanBusNo() { return m_poll.bus_no;} + + uint8_t PollState() { return m_poll_state;} + + protected: + + // Poll entry manipulation: Must be called under lock of m_poll_mutex + + void ResetPollEntry(bool force = false); + void PollerNextTick(poller_source_t source); + + void Incoming(CAN_frame_t &frame, bool success); + void Outgoing(const CAN_frame_t &frame, bool success); + + // Check for throttling. + bool CanPoll(); + + enum class OvmsPollEntryType : uint8_t + { + Poll, + FrameRx, + FrameTx, + Command, + PollState + }; + enum class OvmsPollCommand : uint8_t + { + Pause, + Resume, + Throttle, + ResponseSep, + Keepalive, + SuccessSep + }; + typedef struct { + CAN_frame_t frame; + bool success; + } poll_frame_entry_t; + typedef struct { + OvmsPollCommand cmd; + uint16_t parameter; + } poll_command_entry_t; + typedef struct { + uint8_t busno ; + poller_source_t source; + uint32_t poll_ticker; + } poll_source_entry_t; + typedef struct { + uint8_t new_state; + canbus* bus; // optional + } poll_state_entry_t; + + typedef struct { + OvmsPollEntryType entry_type; + union { + poll_source_entry_t entry_Poll; + poll_frame_entry_t entry_FrameRxTx; + poll_command_entry_t entry_Command; + poll_state_entry_t entry_PollState; + }; + } poll_queue_entry_t; + + void Do_PollSetState(uint8_t state); + + public: + OvmsPoller(canbus* can, uint8_t can_number, OvmsPollers *parent, + const CanFrameCallback &polltxcallback); + ~OvmsPoller(); + + bool HasPollList(); + + void ClearPollList(); + + void ResetThrottle(); + + void Queue_PollerSend(poller_source_t source); + void Queue_PollerSendSuccess(); + + void PollSetThrottling(uint8_t sequence_max); + + void PollSetResponseSeparationTime(uint8_t septime); + void PollSetChannelKeepalive(uint16_t keepalive_seconds); + void PollSetTimeBetweenSuccess(uint16_t time_between_ms); + + // TODO - Work out how to make sure these are protected. Reduce/eliminate mutex time. + void PollSetPidList(uint8_t defaultbus, const poll_pid_t* plist, VehicleSignal *signal); + + int PollSingleRequest(uint32_t txid, uint32_t rxid, + std::string request, std::string& response, + int timeout_ms=3000, uint8_t protocol=ISOTP_STD); + int PollSingleRequest(uint32_t txid, uint32_t rxid, + uint8_t polltype, uint16_t pid, std::string& response, + int timeout_ms=3000, uint8_t protocol=ISOTP_STD); + + void PollRequest(const std::string &name, const std::shared_ptr &series); + void RemovePollRequest(const std::string &name); + void RemovePollRequestStarting(const std::string &name); + + public: + static const char *PollerCommand(OvmsPollCommand src); + static const char *PollerSource(poller_source_t src); + static const char *PollResultCodeName(int code); + friend class OvmsPollers; +}; + +#define VEHICLE_MAXBUSSES 4 +class OvmsPollers : public InternalRamAllocated { + private: + typedef struct { + canbus* can; + bool from_vehicle; + } bus_info_t; + bus_info_t m_canbusses[VEHICLE_MAXBUSSES]; + OvmsRecMutex m_poller_mutex; + OvmsPoller* m_pollers[VEHICLE_MAXBUSSES]; + uint8_t m_poll_state; // Current poll state + uint8_t m_poll_sequence_max; // Polls allowed to be sent in sequence per time tick (second), default 1, 0 = no limit + uint8_t m_poll_fc_septime; // Flow control separation time for multi frame responses + uint16_t m_poll_ch_keepalive; // Seconds to keep an inactive channel (e.g. VWTP) alive (default: 60) + uint16_t m_poll_between_success; + uint32_t m_poll_last; + + QueueHandle_t m_pollqueue; + TaskHandle_t m_polltask; + CanFrameCallback m_poll_txcallback; // Poller CAN TxCallback + + TimerHandle_t m_timer_poller; + int32_t m_poll_subticker; // Subticker count for polling + uint16_t m_poll_tick_ms; // Tick length in ms. + uint8_t m_poll_tick_secondary; // Number of secondary poll subticks per primary / zero + + bool m_shut_down; + bool m_ready; + bool m_paused; + bool m_user_paused; + + void PollerTxCallback(const CAN_frame_t* frame, bool success); + void PollerRxCallback(const CAN_frame_t* frame, bool success); + + void PollerTask(); + static void OvmsPollerTask(void *pvParameters); + + void Queue_PollerFrame(const CAN_frame_t &frame, bool success, bool istx); + + void Queue_Command(OvmsPoller::OvmsPollCommand cmd, uint16_t param = 0); + static void vehicle_poller_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv); + static void vehicle_pause_on(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv); + static void vehicle_pause_off(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv); + void PollerStatus(int verbosity, OvmsWriter* writer); + void SetUserPauseStatus(bool paused, int verbosity, OvmsWriter* writer); + public: + typedef std::function PollCallback; + private: + ovms_callback_register_t m_runfinished_callback, m_pollstateticker_callback; + public: + void RegisterRunFinished(const std::string &name, PollCallback fn) { m_runfinished_callback.Register(name, fn);} + void DeregisterRunFinished(const std::string &name) { m_runfinished_callback.Deregister(name);} + void RegisterPollStateTicker(const std::string &name, PollCallback fn) { m_pollstateticker_callback.Register(name, fn);} + void DeregisterPollStateTicker(const std::string &name) { m_pollstateticker_callback.Deregister(name);} + private: + void PollRunFinished(canbus *bus) + { + m_runfinished_callback.Call( + [bus](const std::string &name, const PollCallback &cb) + { + cb(bus, nullptr); + }); + } + void PollerStateTicker(canbus *bus) + { + m_pollstateticker_callback.Call( + [bus](const std::string &name, const PollCallback &cb) + { + cb(bus, nullptr); + }); + } + + void Ticker1(std::string event, void* data); + void EventSystemShuttingDown(std::string event, void* data); + + public: + OvmsPollers(); + ~OvmsPollers(); + + void StartingUp(); + void ShuttingDown(); + void ShuttingDownVehicle(); + + OvmsPoller *GetPoller(canbus *can, bool force = false ); + + void PollSetTicker(uint16_t tick_time_ms, uint8_t secondary_ticks); + + void QueuePollerSend(OvmsPoller::poller_source_t src, uint8_t busno = 0 , uint32_t pollticker = 0); + + void PollSetPidList(canbus* defbus, const OvmsPoller::poll_pid_t* plist, OvmsPoller::VehicleSignal *signal); + + void PollRequest(canbus* defbus, const std::string &name, const std::shared_ptr &series); + + void PollRemove(canbus* defbus, const std::string &name); + + bool HasPollList(canbus* bus = nullptr); + + void CheckStartPollTask( bool force = false); + + void PollSetThrottling(uint8_t sequence_max) + { + Queue_Command(OvmsPoller::OvmsPollCommand::Throttle, sequence_max); + } + void PollSetResponseSeparationTime(uint8_t septime) + { + Queue_Command(OvmsPoller::OvmsPollCommand::ResponseSep, septime); + } + void PollSetChannelKeepalive(uint16_t keepalive_seconds) + { + Queue_Command(OvmsPoller::OvmsPollCommand::Keepalive, keepalive_seconds); + } + void PollSetTimeBetweenSuccess(uint16_t time_between_ms) + { + Queue_Command(OvmsPoller::OvmsPollCommand::SuccessSep, time_between_ms); + } + // signal poller + void PollerResetThrottle(); + + void VehiclePollTicker(); + + void PausePolling(bool user_poll = false) + { + Queue_Command(OvmsPoller::OvmsPollCommand::Pause, (int)user_poll ); + } + void ResumePolling(bool user_poll = false) + { + Queue_Command(OvmsPoller::OvmsPollCommand::Resume, (int)user_poll); + } + bool IsUserPaused() + { + return m_user_paused; + } + bool IsPaused() + { + return m_paused; + } + void PollSetState(uint8_t state, canbus* bus = nullptr); + + uint32_t LastPollCmdReceived() + { + return m_poll_last; + } + uint8_t GetBusNo(canbus* bus); + canbus* GetBus(uint8_t busno); + canbus* RegisterCanBus(int busno, CAN_mode_t mode, CAN_speed_t speed, dbcfile* dbcfile, bool from_vehicle); + void PowerDownCanBus(int busno); + bool HasPollTask() + { + return (m_polltask != nullptr); + } + bool Ready() { return m_ready;} + void Ready(bool ready) { m_ready = ready;} + + // AutoInit stub for startup. + void AutoInit() { Ready(true); }; + + friend class OvmsPoller; + +}; +extern OvmsPollers MyPollers; + +#endif // __VEHICLE_POLLER_H__ diff --git a/vehicle/OVMS.V3/components/vehicle/vehicle_poller_isotp.cpp b/vehicle/OVMS.V3/components/poller/src/vehicle_poller_isotp.cpp similarity index 89% rename from vehicle/OVMS.V3/components/vehicle/vehicle_poller_isotp.cpp rename to vehicle/OVMS.V3/components/poller/src/vehicle_poller_isotp.cpp index f74882c92..4da0107a6 100644 --- a/vehicle/OVMS.V3/components/vehicle/vehicle_poller_isotp.cpp +++ b/vehicle/OVMS.V3/components/poller/src/vehicle_poller_isotp.cpp @@ -40,7 +40,7 @@ static const char *TAG = "vehicle-isotp"; /** * PollerISOTPStart: start ISO-TP request */ -void OvmsVehicle::PollerISOTPStart(bool fromTicker) +void OvmsPoller::PollerISOTPStart(bool fromTicker) { if (m_poll.entry.rxmoduleid != 0) { @@ -57,8 +57,9 @@ void OvmsVehicle::PollerISOTPStart(bool fromTicker) m_poll.moduleid_high = 0x7ef; } - ESP_LOGD(TAG, "PollerISOTPStart(%d): send [bus=%d, type=%02X, pid=%X], expecting %03" PRIx32 "/%03" PRIx32 "-%03" PRIx32, - fromTicker, m_poll.entry.pollbus, m_poll.type, m_poll.pid, m_poll.moduleid_sent, + ESP_LOGD(TAG, "[%" PRIu8 "]PollerISOTPStart(%s): send [bus=%" PRIu8 ", type=%02" PRIX16 ", pid=%X], expecting %03x/%03x-%03x", + m_poll.bus_no, fromTicker ? "Yes" : "No", + m_poll.entry.pollbus, m_poll.type, m_poll.pid, m_poll.moduleid_sent, m_poll.moduleid_low, m_poll.moduleid_high); // @@ -173,19 +174,23 @@ void OvmsVehicle::PollerISOTPStart(bool fromTicker) /** * PollerISOTPReceive: process ISO-TP poll response frame */ -bool OvmsVehicle::PollerISOTPReceive(CAN_frame_t* frame, uint32_t msgid) +bool OvmsPoller::PollerISOTPReceive(CAN_frame_t* frame, uint32_t msgid) { - OvmsRecMutexLock lock(&m_poll_mutex); + // OvmsRecMutexLock lock(&m_poll_mutex); char *hexdump = NULL; // After locking the mutex, check again for poll expectance match: - if (!m_poll_wait || !m_poll_plist || frame->origin != m_poll.bus || - msgid < m_poll.moduleid_low || msgid > m_poll.moduleid_high) + if (!m_poll_wait || !m_polls.HasPollList() || frame->origin != m_poll.bus) { - ESP_LOGD(TAG, "PollerISOTPReceive[%03" PRIX32 "]: dropping expired poll response", msgid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerISOTPReceive[%03" PRIX32 "]: dropping expired poll response", m_poll.bus_no, msgid); + return false; + } + if (msgid < m_poll.moduleid_low || msgid > m_poll.moduleid_high) + { + ESP_LOGD(TAG, "[%" PRIu8 "]PollerISOTPReceive[%03" PRIX32 "]: dropping out-of-range poll response %03" PRIX32 "-%03" PRIX32, + m_poll.bus_no, msgid, m_poll.moduleid_low, m_poll.moduleid_high); return false; } - // // Get & validate ISO-TP meta data // @@ -428,8 +433,8 @@ bool OvmsVehicle::PollerISOTPReceive(CAN_frame_t* frame, uint32_t msgid) if (error_code == UDS_RESP_NRC_RCRRP) { // Info: requestCorrectlyReceived-ResponsePending (server busy processing the request) - ESP_LOGD(TAG, "PollerISOTPReceive[%03" PRIX32 "]: got OBD/UDS info %02X(%X) code=%02X (pending)", - msgid, m_poll.type, m_poll.pid, error_code); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerISOTPReceive[%03" PRIX32 "]: got OBD/UDS info %02X(%X) code=%02X (pending)", + m_poll.bus_no, msgid, m_poll.type, m_poll.pid, error_code); // add some wait time: m_poll_wait++; return true; @@ -437,23 +442,17 @@ bool OvmsVehicle::PollerISOTPReceive(CAN_frame_t* frame, uint32_t msgid) else { // Error: forward to application: - ESP_LOGD(TAG, "PollerISOTPReceive[%03" PRIX32 "]: process OBD/UDS error %02X(%X) code=%02X", - msgid, m_poll.type, m_poll.pid, error_code); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerISOTPReceive[%03" PRIX32 "]: process OBD/UDS error %02X(%X) code=%02X", + m_poll.bus_no, msgid, m_poll.type, m_poll.pid, error_code); // Running single poll? - if (m_poll_single_rxbuf) - { - m_poll_single_rxerr = error_code; - m_poll_single_rxbuf = NULL; - m_poll_single_rxdone.Give(); - } - else - { - m_poll.moduleid_rec = msgid; - m_poll.mlframe = 0; - m_poll.mloffset = 0; - m_poll.mlremain = 0; - IncomingPollError(m_poll, error_code); - } + { + OvmsRecMutexLock lock(&m_poll_mutex); + m_poll.moduleid_rec = msgid; + m_poll.mlframe = 0; + m_poll.mloffset = 0; + m_poll.mlremain = 0; + IncomingPollError(m_poll, error_code); + } // abort: m_poll.mlremain = 0; } @@ -462,29 +461,14 @@ bool OvmsVehicle::PollerISOTPReceive(CAN_frame_t* frame, uint32_t msgid) { // Normal matching poll response, forward to application: m_poll.mlremain = tp_len - tp_datalen; - ESP_LOGD(TAG, "PollerISOTPReceive[%03" PRIX32 "]: process OBD/UDS response %02X(%X) frm=%u len=%u off=%u rem=%u", + ESP_LOGD(TAG, "PollerISOTPReceive[%03" PRIX32 "]: process OBD/UDS response %02" PRIX16 "(%" PRIX16 ") frm=%u len=%u off=%u rem=%u", msgid, m_poll.type, m_poll.pid, m_poll.mlframe, response_datalen, m_poll.mloffset, m_poll.mlremain); - // Running single poll? - if (m_poll_single_rxbuf) - { - if (m_poll.mlframe == 0) - { - m_poll_single_rxbuf->clear(); - m_poll_single_rxbuf->reserve(response_datalen + m_poll.mlremain); - } - m_poll_single_rxbuf->append((char*)response_data, response_datalen); - if (m_poll.mlremain == 0) - { - m_poll_single_rxerr = 0; - m_poll_single_rxbuf = NULL; - m_poll_single_rxdone.Give(); - } - } - else + { + OvmsRecMutexLock lock(&m_poll_mutex); m_poll.moduleid_rec = msgid; - IncomingPollReply(m_poll, response_data, response_datalen); + m_polls.IncomingPacket(m_poll, response_data, response_datalen); } } else @@ -569,7 +553,7 @@ bool OvmsVehicle::PollerISOTPReceive(CAN_frame_t* frame, uint32_t msgid) m_poll.moduleid_sent != 0x7df && CanPoll() ) { - PollerSend(poller_source_t::Successful); + Queue_PollerSendSuccess(); } return true; diff --git a/vehicle/OVMS.V3/components/vehicle/vehicle_poller_vwtp.cpp b/vehicle/OVMS.V3/components/poller/src/vehicle_poller_vwtp.cpp similarity index 82% rename from vehicle/OVMS.V3/components/vehicle/vehicle_poller_vwtp.cpp rename to vehicle/OVMS.V3/components/poller/src/vehicle_poller_vwtp.cpp index d4cf7e4fe..ac780a2ac 100644 --- a/vehicle/OVMS.V3/components/vehicle/vehicle_poller_vwtp.cpp +++ b/vehicle/OVMS.V3/components/poller/src/vehicle_poller_vwtp.cpp @@ -40,7 +40,7 @@ static const char *TAG = "vehicle-vwtp"; /** * PollerVWTPStart: start next VW-TP request (internal method) */ -void OvmsVehicle::PollerVWTPStart(bool fromTicker) +void OvmsPoller::PollerVWTPStart(bool fromTicker) { m_poll_vwtp.lastused = monotonictime; @@ -54,12 +54,10 @@ void OvmsVehicle::PollerVWTPStart(bool fromTicker) PollerVWTPEnter(VWTP_ChannelClose); else if (m_poll.entry.rxmoduleid != 0) PollerVWTPEnter(VWTP_ChannelSetup); - else if (m_poll_single_rxbuf) + else { - m_poll_single_rxbuf->clear(); - m_poll_single_rxbuf = NULL; - m_poll_single_rxerr = POLLSINGLE_OK; - m_poll_single_rxdone.Give(); + OvmsRecMutexLock lock(&m_poll_mutex); + m_polls.IncomingError(m_poll, POLLSINGLE_OK); } return; } @@ -91,7 +89,7 @@ void OvmsVehicle::PollerVWTPStart(bool fromTicker) /** * PollerVWTPEnter: channel state transition (internal method) */ -void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) +void OvmsPoller::PollerVWTPEnter(vwtp_channelstate_t state) { CAN_frame_t txframe = {}; txframe.callback = &m_poll_txcallback; @@ -106,7 +104,7 @@ void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) // Open TP20 channel for current polling entry: if (m_poll.protocol != VWTP_20) { - ESP_LOGD(TAG, "PollerVWTPEnter/ChannelSetup: m_poll.protocol mismatch, abort"); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPEnter/ChannelSetup: m_poll_protocol mismatch, abort", m_poll.bus_no); } else { @@ -119,10 +117,11 @@ void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) // txid & rxid will be changed by the setup response frame, we offer a standard rxid // matching observed client behaviour with baseid=0x200 → rxid=0x300: uint16_t offer_rxid = m_poll_vwtp.baseid + 0x100; - - ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: channel setup request bus=%d txid=%03X rxid=%03X", + + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPEnter[%02X]: channel setup request bus=%d txid=%03X rxid=%03X", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll.entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid); - + txframe.MsgID = m_poll_vwtp.txid; txframe.FIR.B.DLC = 7; txframe.data.u8[0] = m_poll_vwtp.moduleid; @@ -148,8 +147,8 @@ void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) case VWTP_ChannelParams: { - ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: channel params request bus=%d txid=%03X rxid=%03X", - m_poll_vwtp.moduleid, m_poll.entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPEnter[%02X]: channel params request bus=%d txid=%03X rxid=%03X", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll.entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid); txframe.MsgID = m_poll_vwtp.txid; txframe.FIR.B.DLC = 6; @@ -173,8 +172,8 @@ void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) case VWTP_ChannelClose: { - ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: close channel bus=%d txid=%03X rxid=%03X", - m_poll_vwtp.moduleid, m_poll.entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPEnter[%02X]: close channel bus=%d txid=%03X rxid=%03X", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll.entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid); txframe.MsgID = m_poll_vwtp.txid; txframe.FIR.B.DLC = 1; @@ -193,8 +192,8 @@ void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) case VWTP_Closed: { - ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: channel closed bus=%d txid=%03X rxid=%03X", - m_poll_vwtp.moduleid, m_poll.entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPEnter[%02X]: channel closed bus=%d txid=%03X rxid=%03X", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll.entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid); m_poll_vwtp = {}; m_poll_vwtp.state = VWTP_Closed; m_poll_wait = 0; @@ -203,8 +202,8 @@ void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) case VWTP_Idle: { - ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: idle bus=%d txid=%03X rxid=%03X", - m_poll_vwtp.moduleid, m_poll.entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPEnter[%02X]: idle bus=%d txid=%03X rxid=%03X", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll.entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid); m_poll_vwtp.state = VWTP_Idle; m_poll_vwtp.lastused = monotonictime; m_poll_wait = 0; @@ -213,8 +212,8 @@ void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) case VWTP_StartPoll: { - ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: start poll type=%02X pid=%X", - m_poll_vwtp.moduleid, m_poll.entry.type, m_poll.entry.pid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPEnter[%02X]: start poll type=%02X pid=%X", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll.entry.type, m_poll.entry.pid); if (m_poll.entry.xargs.tag == POLL_TXDATA) { @@ -238,9 +237,9 @@ void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) FALLTHROUGH; case VWTP_Transmit: { - ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: transmit frame=%u remain=%u", - m_poll_vwtp.moduleid, m_poll_tx_frame, m_poll_tx_remain); - + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPEnter[%02X]: transmit frame=%u remain=%u", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll_tx_frame, m_poll_tx_remain); + // Transmit next block of frames: txframe.MsgID = m_poll_vwtp.txid; int i; @@ -312,8 +311,8 @@ void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) case VWTP_Receive: { - ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: receive frame=%u remain=%u", - m_poll_vwtp.moduleid, m_poll.mlframe, m_poll.mlremain); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPEnter[%02X]: receive frame=%u remain=%u", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll.mlframe, m_poll.mlremain); m_poll_vwtp.state = VWTP_Receive; m_poll_vwtp.lastused = monotonictime; m_poll_wait = 2; @@ -322,8 +321,8 @@ void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) case VWTP_AbortXfer: { - ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: abort xfer", - m_poll_vwtp.moduleid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPEnter[%02X]: abort xfer", + m_poll.bus_no, m_poll_vwtp.moduleid); txframe.MsgID = m_poll_vwtp.txid; txframe.FIR.B.DLC = 1; @@ -352,8 +351,8 @@ void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) } default: - ESP_LOGD(TAG, "PollerVWTPEnter[%02X]: idle bus=%d txid=%03X rxid=%03X", - m_poll_vwtp.moduleid, m_poll.entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPEnter[%02X]: idle bus=%d txid=%03X rxid=%03X", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll.entry.pollbus, m_poll_vwtp.txid, m_poll_vwtp.rxid); m_poll_vwtp.state = VWTP_Idle; m_poll_wait = 0; break; @@ -364,7 +363,7 @@ void OvmsVehicle::PollerVWTPEnter(vwtp_channelstate_t state) /** * PollerVWTPTxCallback: CAN transmission result (internal) */ -void OvmsVehicle::PollerVWTPTxCallback(const CAN_frame_t* frame, bool success) +void OvmsPoller::PollerVWTPTxCallback(const CAN_frame_t* frame, bool success) { if (!success) { @@ -384,9 +383,9 @@ void OvmsVehicle::PollerVWTPTxCallback(const CAN_frame_t* frame, bool success) /** * PollerVWTPReceive: process VW-TP frame received (internal) */ -bool OvmsVehicle::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid) +bool OvmsPoller::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid) { - OvmsRecMutexLock lock(&m_poll_mutex); + // OvmsRecMutexLock lock(&m_poll_mutex); // Log utility: auto logFrameDump = [= CAP_THIS](const char* msg) @@ -448,7 +447,8 @@ bool OvmsVehicle::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid) else if (opcode != 0xD0) { // Setup failed: - ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: channel setup failed opcode=%02X", m_poll_vwtp.moduleid, opcode); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPReceive[%02X]: channel setup failed opcode=%02X", + m_poll.bus_no, m_poll_vwtp.moduleid, opcode); m_poll_vwtp.bus = NULL; m_poll_vwtp.state = VWTP_Closed; m_poll_wait = 0; @@ -458,8 +458,8 @@ bool OvmsVehicle::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid) // Setup success, configure txid & rxid: m_poll_vwtp.rxid = (uint16_t)frame->data.u8[3] << 8 | frame->data.u8[2]; m_poll_vwtp.txid = (uint16_t)frame->data.u8[5] << 8 | frame->data.u8[4]; - ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: channel setup OK, assigned txid=%03X rxid=%03X", - m_poll_vwtp.moduleid, m_poll_vwtp.txid, m_poll_vwtp.rxid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPReceive[%02X]: channel setup OK, assigned txid=%03X rxid=%03X", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll_vwtp.txid, m_poll_vwtp.rxid); // …and send channel parameters: PollerVWTPEnter(VWTP_ChannelParams); } @@ -477,8 +477,8 @@ bool OvmsVehicle::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid) m_poll_vwtp.blocksize = frame->data.u8[1]; m_poll_vwtp.acktime = (frame->data.u8[2] & 0b00111111) * timeunit[(frame->data.u8[2] & 0b11000000) >> 6]; m_poll_vwtp.septime = (frame->data.u8[4] & 0b00111111) * timeunit[(frame->data.u8[4] & 0b11000000) >> 6]; - ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: channel params OK: bs=%d acktime=%" PRIu32 "us septime=%" PRIu32 "us", - m_poll_vwtp.moduleid, m_poll_vwtp.blocksize, m_poll_vwtp.acktime, m_poll_vwtp.septime); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPReceive[%02X]: channel params OK: bs=%d acktime=%" PRIu32 "us septime=%" PRIu32 "us", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll_vwtp.blocksize, m_poll_vwtp.acktime, m_poll_vwtp.septime); // …and proceed to data transmission: PollerVWTPEnter(VWTP_StartPoll); } @@ -508,12 +508,10 @@ bool OvmsVehicle::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid) // Check if we shall open another channel: if (m_poll.protocol == VWTP_20 && m_poll.entry.rxmoduleid != 0) PollerVWTPEnter(VWTP_ChannelSetup); - else if (m_poll_single_rxbuf) + else { - m_poll_single_rxbuf->clear(); - m_poll_single_rxbuf = NULL; - m_poll_single_rxerr = POLLSINGLE_OK; - m_poll_single_rxdone.Give(); + OvmsRecMutexLock lock(&m_poll_mutex); + m_polls.IncomingError(m_poll, POLLSINGLE_OK); } } else @@ -567,8 +565,8 @@ bool OvmsVehicle::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid) else if ((opcode & 0xf0) == 0x90) { // ACK, abort: - ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: got abort request during transmission at frame=%u remain=%u", - m_poll_vwtp.moduleid, m_poll_tx_frame, m_poll_tx_remain); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPReceive[%02X]: got abort request during transmission at frame=%u remain=%u", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll_tx_frame, m_poll_tx_remain); m_poll_tx_remain = 0; PollerVWTPEnter(VWTP_Idle); // TODO: application error callback @@ -694,8 +692,8 @@ bool OvmsVehicle::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid) if (error_code == UDS_RESP_NRC_RCRRP) { // Info: requestCorrectlyReceived-ResponsePending (server busy processing the request) - ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: got OBD/UDS info %02X(%X) code=%02X (pending)", - m_poll_vwtp.moduleid, m_poll.type, m_poll.pid, error_code); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPReceive[%02X]: got OBD/UDS info %02X(%X) code=%02X (pending)", + m_poll.bus_no, m_poll_vwtp.moduleid, m_poll.type, m_poll.pid, error_code); // reset wait time: m_poll_wait = 2; return true; @@ -705,20 +703,11 @@ bool OvmsVehicle::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid) // Error: forward to application: ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: process OBD/UDS error %02X(%X) code=%02X", m_poll_vwtp.moduleid, m_poll.type, m_poll.pid, error_code); - // Running single poll? - if (m_poll_single_rxbuf) - { - m_poll_single_rxerr = error_code; - m_poll_single_rxbuf = NULL; - m_poll_single_rxdone.Give(); - } - else + { - m_poll.moduleid_rec = msgid; - m_poll.mlframe = 0; - m_poll.mloffset = 0; - m_poll.mlremain = 0; - IncomingPollError(m_poll, error_code); + OvmsRecMutexLock lock(&m_poll_mutex); + m_poll.moduleid_rec = m_poll.moduleid_sent; + m_polls.IncomingError(m_poll, error_code); } // abort receive: m_poll.mlremain = 0; @@ -736,29 +725,10 @@ bool OvmsVehicle::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid) ESP_LOGD(TAG, "PollerVWTPReceive[%02X]: process OBD/UDS response %02X(%X) frm=%u len=%u off=%u rem=%u", m_poll_vwtp.moduleid, m_poll.type, m_poll.pid, m_poll.mlframe, response_datalen, m_poll.mloffset, m_poll.mlremain); - // Running single poll? - if (m_poll_single_rxbuf) - { - if (m_poll.mlframe == 0) - { - m_poll_single_rxbuf->clear(); - m_poll_single_rxbuf->reserve(response_datalen + m_poll.mlremain); - } - m_poll_single_rxbuf->append((char*)response_data, response_datalen); - if (m_poll.mlremain == 0) - { - m_poll_single_rxerr = 0; - m_poll_single_rxbuf = NULL; - m_poll_single_rxdone.Give(); - } - } - else { + OvmsRecMutexLock lock(&m_poll_mutex); m_poll.moduleid_rec = msgid; - m_poll.mlframe = m_poll.mlframe; - m_poll.mloffset = m_poll.mloffset; - m_poll.mlremain = m_poll.mlremain; - IncomingPollReply(m_poll, response_data, response_datalen); + m_polls.IncomingPacket(m_poll, response_data, response_datalen); } } else @@ -804,7 +774,7 @@ bool OvmsVehicle::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid) // - poll throttling is unlimited or limit isn't reached yet if (m_poll_wait == 0 && CanPoll()) { - PollerSend(poller_source_t::Successful); + Queue_PollerSendSuccess(); } return true; @@ -814,19 +784,21 @@ bool OvmsVehicle::PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid) /** * PollerVWTPTicker: per second channel maintenance (internal) */ -void OvmsVehicle::PollerVWTPTicker() +void OvmsPoller::PollerVWTPTicker() { if (m_poll_wait > 0) { // State timeout? if (m_poll_vwtp.state == VWTP_ChannelSetup || m_poll_vwtp.state == VWTP_ChannelParams) { - ESP_LOGD(TAG, "PollerVWTPTicker[%02X]: setup/params timeout", m_poll_vwtp.moduleid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPTicker[%02X]: setup/params timeout", + m_poll.bus_no, m_poll_vwtp.moduleid); PollerVWTPEnter(VWTP_Closed); } else if (m_poll_vwtp.state == VWTP_ChannelClose) { - ESP_LOGD(TAG, "PollerVWTPTicker[%02X]: close timeout", m_poll_vwtp.moduleid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPTicker[%02X]: close timeout", + m_poll.bus_no, m_poll_vwtp.moduleid); PollerVWTPEnter(VWTP_Closed); if (m_poll.protocol == VWTP_20) PollerVWTPStart(true); @@ -837,7 +809,8 @@ void OvmsVehicle::PollerVWTPTicker() // Poll timeout: if (m_poll_vwtp.state != VWTP_Closed && m_poll_vwtp.state != VWTP_Idle) { - ESP_LOGD(TAG, "PollerVWTPTicker[%02X]: poll timeout", m_poll_vwtp.moduleid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPTicker[%02X]: poll timeout", + m_poll.bus_no, m_poll_vwtp.moduleid); PollerVWTPEnter(VWTP_ChannelClose); } } @@ -846,7 +819,8 @@ void OvmsVehicle::PollerVWTPTicker() if (m_poll_vwtp.state == VWTP_Idle && m_poll_ch_keepalive > 0 && m_poll_vwtp.lastused + m_poll_ch_keepalive < monotonictime) { - ESP_LOGD(TAG, "PollerVWTPTicker[%02X]: channel inactivity timeout", m_poll_vwtp.moduleid); + ESP_LOGD(TAG, "[%" PRIu8 "]PollerVWTPTicker[%02X]: channel inactivity timeout", + m_poll.bus_no, m_poll_vwtp.moduleid); PollerVWTPEnter(VWTP_ChannelClose); } } diff --git a/vehicle/OVMS.V3/components/vehicle/vehicle.cpp b/vehicle/OVMS.V3/components/vehicle/vehicle.cpp index 95f6f131d..f2d4ab3b3 100644 --- a/vehicle/OVMS.V3/components/vehicle/vehicle.cpp +++ b/vehicle/OVMS.V3/components/vehicle/vehicle.cpp @@ -30,6 +30,7 @@ #include "ovms_log.h" static const char *TAG = "vehicle"; +// static const char *TAGRX = "vehicle-rx"; #include #include @@ -49,6 +50,10 @@ static const char *TAG = "vehicle"; #include #include "vehicle.h" +#ifdef bind +#undef bind +#endif +using namespace std::placeholders; OvmsVehicleFactory MyVehicleFactory __attribute__ ((init_priority (2000))); @@ -150,13 +155,7 @@ OvmsVehicleFactory::OvmsVehicleFactory() OvmsVehicleFactory::~OvmsVehicleFactory() { - if (m_currentvehicle) - { - m_currentvehicle->m_ready = false; - delete m_currentvehicle; - m_currentvehicle = NULL; - m_currentvehicletype.clear(); - } + DoClearVehicle(false, false); } OvmsVehicle* OvmsVehicleFactory::NewVehicle(const char* VehicleType) @@ -170,36 +169,45 @@ OvmsVehicle* OvmsVehicleFactory::NewVehicle(const char* VehicleType) } void OvmsVehicleFactory::ClearVehicle() + { + DoClearVehicle(true, true); + } +void OvmsVehicleFactory::DoClearVehicle( bool clearName, bool sendEvent) { if (m_currentvehicle) { - m_currentvehicle->m_ready = false; - delete m_currentvehicle; + m_currentvehicle->ShuttingDown(); + auto vehicle = m_currentvehicle; m_currentvehicle = NULL; + m_currentvehicletype.clear(); - StandardMetrics.ms_v_type->SetValue(""); - MyEvents.SignalEvent("vehicle.type.cleared", NULL); + if (clearName) + StandardMetrics.ms_v_type->SetValue(""); + if (sendEvent) + MyEvents.SignalEvent("vehicle.type.cleared", NULL); + + delete vehicle; } } void OvmsVehicleFactory::SetVehicle(const char* type) { + DoClearVehicle(false, true); + m_currentvehicle = NewVehicle(type); if (m_currentvehicle) { - m_currentvehicle->m_ready = false; - delete m_currentvehicle; - m_currentvehicle = NULL; - m_currentvehicletype.clear(); - MyEvents.SignalEvent("vehicle.type.cleared", NULL); + std::string new_type(type); + m_currentvehicletype = new_type; + StandardMetrics.ms_v_type->SetValue(m_currentvehicletype); + + m_currentvehicle->StartingUp(); + + MyEvents.SignalEvent("vehicle.type.set", (void*)new_type.c_str(), new_type.size()+1); + } + else + { + StandardMetrics.ms_v_type->SetValue(""); } - m_currentvehicle = NewVehicle(type); - if (m_currentvehicle) - { - m_currentvehicle->m_ready = true; - } - m_currentvehicletype = std::string(type); - StandardMetrics.ms_v_type->SetValue(m_currentvehicle ? type : ""); - MyEvents.SignalEvent("vehicle.type.set", (void*)type, strlen(type)+1); } void OvmsVehicleFactory::AutoInit() @@ -232,16 +240,12 @@ const char* OvmsVehicleFactory::ActiveVehicleShortName() return m_currentvehicle ? m_currentvehicle->VehicleShortName() : ""; } -static void OvmsVehicleRxTask(void *pvParameters) - { - OvmsVehicle *me = (OvmsVehicle*)pvParameters; - me->RxTask(); - } +static const char *PollerRegister="Vehicle Listener"; OvmsVehicle::OvmsVehicle() { - using std::placeholders::_1; - using std::placeholders::_2; + + m_is_shutdown = false; m_can1 = NULL; m_can2 = NULL; @@ -271,38 +275,20 @@ OvmsVehicle::OvmsVehicle() m_vehicleon_ticker = 0; m_vehicleoff_ticker = 0; m_idle_ticker = 0; - m_registeredlistener = false; m_autonotifications = true; m_ready = false; +#ifdef CONFIG_OVMS_COMP_POLLER m_poll_state = 0; - m_poll_bus_default = NULL; - m_poll_txcallback = std::bind(&OvmsVehicle::PollerTxCallback, this, _1, _2); - m_poll_plist = NULL; - m_poll_plcur = NULL; - m_poll_paused = false; - - m_poll_vwtp = {}; - - m_poll.bus = NULL; - m_poll.entry = {}; - m_poll.ticker = 0; - m_poll_single_rxbuf = NULL; - m_poll_single_rxerr = 0; - m_poll.moduleid_sent = 0; - m_poll.moduleid_low = 0; - m_poll.moduleid_high = 0; - m_poll.type = 0; - m_poll.pid = 0; - m_poll.mlremain = 0; - m_poll.mloffset = 0; - m_poll.mlframe = 0; - - m_poll_wait = 0; - m_poll_sequence_max = 1; - m_poll_sequence_cnt = 0; - m_poll_fc_septime = 25; // response default timing: 25 milliseconds - m_poll_ch_keepalive = 60; // channel keepalive default: 60 seconds + m_pollsignal = nullptr; + + // Poll parameters. + PollSetThrottling(1); + // response default timing: 25 milliseconds + PollSetResponseSeparationTime(25); + // channel keepalive default: 60 seconds + PollSetChannelKeepalive(60); +#endif m_bms_voltages = NULL; m_bms_vmins = NULL; @@ -368,9 +354,10 @@ OvmsVehicle::OvmsVehicle() m_inv_energyused = 0; m_inv_energyrecd = 0; - m_rxqueue = xQueueCreate(CONFIG_OVMS_VEHICLE_CAN_RX_QUEUE_SIZE,sizeof(CAN_frame_t)); - xTaskCreatePinnedToCore(OvmsVehicleRxTask, "OVMS Vehicle", - CONFIG_OVMS_VEHICLE_RXTASK_STACK, (void*)this, 10, &m_rxtask, CORE(1)); +#ifdef CONFIG_OVMS_COMP_POLLER + MyPollers.RegisterRunFinished(TAG, std::bind(&OvmsVehicle::PollRunFinishedNotify, this, _1, _2)); + MyPollers.RegisterPollStateTicker(TAG, std::bind(&OvmsVehicle::PollerStateTickerNotify, this, _1, _2)); +#endif MyEvents.RegisterEvent(TAG, "ticker.1", std::bind(&OvmsVehicle::VehicleTicker1, this, _1, _2)); @@ -379,15 +366,12 @@ OvmsVehicle::OvmsVehicle() VehicleConfigChanged("config.mounted", NULL); MyMetrics.RegisterListener(TAG, "*", std::bind(&OvmsVehicle::MetricModified, this, _1)); - + MyCan.RegisterCallback(PollerRegister, std::bind(&OvmsVehicle::IncomingPollRxFrame, this, _1, _2)); } OvmsVehicle::~OvmsVehicle() { - if (m_can1) m_can1->SetPowerMode(Off); - if (m_can2) m_can2->SetPowerMode(Off); - if (m_can3) m_can3->SetPowerMode(Off); - if (m_can4) m_can4->SetPowerMode(Off); + ShuttingDown(); if (m_bms_voltages != NULL) { @@ -440,18 +424,38 @@ OvmsVehicle::~OvmsVehicle() delete [] m_bms_talerts; m_bms_talerts = NULL; } + } - if (m_registeredlistener) - { - MyCan.DeregisterListener(m_rxqueue); - m_registeredlistener = false; - } - - vQueueDelete(m_rxqueue); - vTaskDelete(m_rxtask); +void OvmsVehicle::StartingUp() + { + m_ready = true; +#ifdef CONFIG_OVMS_COMP_POLLER + MyPollers.StartingUp(); +#endif + } +void OvmsVehicle::ShuttingDown() + { + if (m_is_shutdown) + return; + m_is_shutdown = true; + m_ready = false; +#ifdef CONFIG_OVMS_COMP_POLLER + MyPollers.ShuttingDownVehicle(); + MyPollers.DeregisterRunFinished(TAG); + MyPollers.DeregisterPollStateTicker(TAG); + + if (m_pollsignal) + delete m_pollsignal; +#else + if (m_can1) m_can1->SetPowerMode(Off); + if (m_can2) m_can2->SetPowerMode(Off); + if (m_can3) m_can3->SetPowerMode(Off); + if (m_can4) m_can4->SetPowerMode(Off); +#endif MyEvents.DeregisterEvent(TAG); MyMetrics.DeregisterListener(TAG); + MyCan.DeregisterCallback(PollerRegister); } const char* OvmsVehicle::VehicleShortName() @@ -464,55 +468,69 @@ const char* OvmsVehicle::VehicleType() return MyVehicleFactory.ActiveVehicleType(); } -const char *OvmsVehicle::PollerSource(OvmsVehicle::poller_source_t src) +canbus *OvmsVehicle::GetBus(uint8_t busno) { - switch (src) + switch (busno) { - case poller_source_t::Primary: return "PRI"; - case poller_source_t::Successful: return "SRX"; - case poller_source_t::OnceOff: return "ONE"; + case 1: return m_can1; + case 2: return m_can2; + case 3: return m_can3; + case 4: return m_can4; + default: return nullptr; } - return "XXX"; } -void OvmsVehicle::RxTask() +uint8_t OvmsVehicle::GetBusNo(canbus* bus) { - CAN_frame_t frame; + if (bus == m_can1) + return 1; + if(bus == m_can2) + return 2; + if (bus == m_can3) + return 3; + if (bus == m_can4) + return 4; + return 0; + } +void OvmsVehicle::PollRunFinished(canbus* bus) + { + } - while(1) - { - if (xQueueReceive(m_rxqueue, &frame, (portTickType)portMAX_DELAY)==pdTRUE) - { - if (!m_ready) - continue; +#ifdef CONFIG_OVMS_COMP_POLLER +OvmsVehicle::OvmsVehicleSignal::OvmsVehicleSignal( OvmsVehicle *parent) + { + m_parent = parent; + } +// Signals for vehicle +void OvmsVehicle::OvmsVehicleSignal::IncomingPollReply(const OvmsPoller::poll_job_t &job, uint8_t* data, uint8_t length) + { + if (Ready()) + m_parent->IncomingPollReply(job, data, length); + } - // Pass frame to poller protocol handlers: - if (frame.origin == m_poll_vwtp.bus && frame.MsgID == m_poll_vwtp.rxid) - { - PollerVWTPReceive(&frame, frame.MsgID); - } - else if (m_poll_wait && frame.origin == m_poll.bus && HasPollList()) - { - uint32_t msgid; - if (m_poll.protocol == ISOTP_EXTADR) - msgid = frame.MsgID << 8 | frame.data.u8[0]; - else - msgid = frame.MsgID; - if (msgid >= m_poll.moduleid_low && msgid <= m_poll.moduleid_high) - { - PollerISOTPReceive(&frame, msgid); - } - } +void OvmsVehicle::OvmsVehicleSignal::IncomingPollError(const OvmsPoller::poll_job_t &job, uint16_t code) + { + if (Ready()) + m_parent->IncomingPollError(job, code); + } - // Pass frame to standard handlers: - if (m_can1 == frame.origin) IncomingFrameCan1(&frame); - else if (m_can2 == frame.origin) IncomingFrameCan2(&frame); - else if (m_can3 == frame.origin) IncomingFrameCan3(&frame); - else if (m_can4 == frame.origin) IncomingFrameCan4(&frame); - } - } +void OvmsVehicle::OvmsVehicleSignal::IncomingPollTxCallback(const OvmsPoller::poll_job_t &job, bool success) + { + if (Ready()) + m_parent->IncomingPollTxCallback(job, success); } +void OvmsVehicle::OvmsVehicleSignal::IncomingPollRxFrame(canbus* bus, CAN_frame_t *frame, bool success) + { + if (Ready()) + m_parent->IncomingPollRxFrame(frame, success); + } +bool OvmsVehicle::OvmsVehicleSignal::Ready() + { + return m_parent->m_ready; + } +#endif + void OvmsVehicle::IncomingFrameCan1(CAN_frame_t* p_frame) { } @@ -524,7 +542,6 @@ void OvmsVehicle::IncomingFrameCan2(CAN_frame_t* p_frame) void OvmsVehicle::IncomingFrameCan3(CAN_frame_t* p_frame) { } - void OvmsVehicle::IncomingFrameCan4(CAN_frame_t* p_frame) { } @@ -536,36 +553,23 @@ void OvmsVehicle::Status(int verbosity, OvmsWriter* writer) void OvmsVehicle::RegisterCanBus(int bus, CAN_mode_t mode, CAN_speed_t speed, dbcfile* dbcfile) { + canbus *can; +#ifdef CONFIG_OVMS_COMP_POLLER + can = MyPollers.RegisterCanBus(bus, mode, speed, dbcfile, true); +#else + { + std::string busname = string_format("can%d", bus); + can = (canbus*)MyPcpApp.FindDeviceByName(busname.c_str()); + can->SetPowerMode(On); + can->Start(mode,speed,dbcfile); + } +#endif switch (bus) { - case 1: - m_can1 = (canbus*)MyPcpApp.FindDeviceByName("can1"); - m_can1->SetPowerMode(On); - m_can1->Start(mode,speed,dbcfile); - break; - case 2: - m_can2 = (canbus*)MyPcpApp.FindDeviceByName("can2"); - m_can2->SetPowerMode(On); - m_can2->Start(mode,speed,dbcfile); - break; - case 3: - m_can3 = (canbus*)MyPcpApp.FindDeviceByName("can3"); - m_can3->SetPowerMode(On); - m_can3->Start(mode,speed,dbcfile); - break; - case 4: - m_can4 = (canbus*)MyPcpApp.FindDeviceByName("can4"); - m_can4->SetPowerMode(On); - m_can4->Start(mode,speed,dbcfile); - break; - default: - break; - } - - if (!m_registeredlistener) - { - m_registeredlistener = true; - MyCan.RegisterListener(m_rxqueue); + case 1: m_can1 = can; break; + case 2: m_can2 = can; break; + case 3: m_can3 = can; break; + case 4: m_can4 = can; break; } } @@ -577,6 +581,15 @@ bool OvmsVehicle::PinCheck(const char* pin) return (strcmp(vpin.c_str(),pin)==0); } +void OvmsVehicle::PollRunFinishedNotify(canbus* bus, void *data) + { + PollRunFinished(bus); + } +void OvmsVehicle::PollerStateTickerNotify(canbus* bus, void *data) + { + PollerStateTicker(bus); + } + void OvmsVehicle::VehicleTicker1(std::string event, void* data) { if (!m_ready) @@ -584,15 +597,26 @@ void OvmsVehicle::VehicleTicker1(std::string event, void* data) m_ticker++; - PollerStateTicker(); - PollerSend(poller_source_t::Primary); Ticker1(m_ticker); - if ((m_ticker % 10) == 0) Ticker10(m_ticker); - if ((m_ticker % 60) == 0) Ticker60(m_ticker); - if ((m_ticker % 300) == 0) Ticker300(m_ticker); - if ((m_ticker % 600) == 0) Ticker600(m_ticker); - if ((m_ticker % 3600) == 0) Ticker3600(m_ticker); + if ((m_ticker % 10) == 0) + { + Ticker10(m_ticker); + if ((m_ticker % 60) == 0) + { + Ticker60(m_ticker); + if ((m_ticker % 300) == 0) + { + Ticker300(m_ticker); + if ((m_ticker % 600) == 0) + { + Ticker600(m_ticker); + if ((m_ticker % 3600) == 0) + Ticker3600(m_ticker); + } + } + } + } if (StandardMetrics.ms_v_env_on->AsBool()) { @@ -2164,7 +2188,7 @@ void OvmsVehicle::NotifyTripLog() void OvmsVehicle::NotifyTripReport() { // Send trip report notification - // Notification type "info", subtype "drive.trip.report" + // Notification type "info", subtype "drive.trip.report" bool send_report = MyConfig.GetParamValueBool("notify", "report.trip.enable", false); if (send_report) { @@ -2242,6 +2266,202 @@ OvmsVehicle::vehicle_command_t OvmsVehicle::ProcessMsgCommand(std::string &resul } +/** + * PollerStateTicker: check for state changes (stub, override with vehicle implementation) + * This is called by VehicleTicker1() just before the next PollerSend(). + * Implement your poller state transition logic in this method, so the changes + * will get applied immediately. + */ +void OvmsVehicle::PollerStateTicker(canbus* bus) + { + } + +// Signal poller +void OvmsVehicle::PausePolling() + { +#ifdef CONFIG_OVMS_COMP_POLLER + MyPollers.PausePolling(); +#endif + } +void OvmsVehicle::ResumePolling() + { +#ifdef CONFIG_OVMS_COMP_POLLER + MyPollers.ResumePolling(); +#endif + } + +#ifdef CONFIG_OVMS_COMP_POLLER +OvmsPoller::VehicleSignal *OvmsVehicle::GetPollerSignal() + { + if (!m_pollsignal) + m_pollsignal = new OvmsVehicle::OvmsVehicleSignal(this); + return m_pollsignal; + } +void OvmsVehicle::PollSetPidList(canbus* bus, const OvmsPoller::poll_pid_t* plist) + { + m_poll_bus_default = bus; + MyPollers.PollSetPidList(bus, plist, GetPollerSignal()); + } +#endif + +/** + * PollSetState: set the polling state + * Call this to change the polling state and restart the current polling list. + * This won't do anything if the state is already active. The state is changed without + * waiting for pending responses to finish (except PollSingleRequests). + * + * @param state + * The polling state to activate (0 … VEHICLE_POLL_NSTATES) + */ +void OvmsVehicle::PollSetState(uint8_t state, canbus* bus) + { +#ifdef CONFIG_OVMS_COMP_POLLER + if (!bus) + m_poll_state = state; + MyPollers.PollSetState(state, bus); +#endif + } + +#ifdef CONFIG_OVMS_COMP_POLLER +int OvmsVehicle::PollSingleRequest(canbus* bus, uint32_t txid, uint32_t rxid, + std::string request, std::string& response, + int timeout_ms, uint8_t protocol) + { + + if (!m_ready) + return POLLSINGLE_TXFAILURE; + auto poller = MyPollers.GetPoller(bus, true); + if (!poller) + return POLLSINGLE_TXFAILURE; + return poller->PollSingleRequest(txid, rxid, request, response, timeout_ms, protocol); + } + +int OvmsVehicle::PollSingleRequest(canbus* bus, uint32_t txid, uint32_t rxid, + uint8_t polltype, uint16_t pid, std::string& response, + int timeout_ms, uint8_t protocol) + { + if (!m_ready) + return POLLSINGLE_TXFAILURE; + auto poller = MyPollers.GetPoller(bus, true); + if (!poller) + return POLLSINGLE_TXFAILURE; + return poller->PollSingleRequest(txid, rxid, polltype, pid, response, timeout_ms, protocol); + } + +/** Set the 'tick' interval for the poller. + * @param tick_time_ms The interval in ms between poll 'ticks' + * @param secondary_ticks The number of ticks making up a primary tick (0/1 means no secondary ticks) + * Only Primary ticks will allow starting the poll-queue again once it has reached the end. + * Either secondary or primary ticks allow fetching of the next entry in the queue. + */ +void OvmsVehicle::PollSetTicker(uint16_t tick_time_ms, uint8_t secondary_ticks) + { + MyPollers.PollSetTicker(tick_time_ms, secondary_ticks); + } + +void OvmsVehicle::PollSetResponseSeparationTime(uint8_t septime) + { + MyPollers.PollSetResponseSeparationTime(septime); + } +void OvmsVehicle::PollSetChannelKeepalive(uint16_t keepalive_seconds) + { + MyPollers.PollSetChannelKeepalive(keepalive_seconds); + } +void OvmsVehicle::PollSetTimeBetweenSuccess(uint16_t time_between_ms) + { + MyPollers.PollSetTimeBetweenSuccess(time_between_ms); + } + +/** + * IncomingPollReply: poll response handler (stub, override with vehicle implementation) + * This is called by PollerReceive() on each valid response frame for the current request. + * Be aware responses may consist of multiple frames, detectable e.g. by mlremain > 0. + * A typical pattern is to collect frames in a buffer until mlremain == 0. + * + * @param job + * Status of the current Poll job + * @param data + * Payload + * @param length + * Payload size + */ +void OvmsVehicle::IncomingPollReply(const OvmsPoller::poll_job_t &job, uint8_t* data, uint8_t length) + { + } + +/** + * IncomingPollError: Calls Vehicle poll response error handler + * This is called by PollerReceive() on reception of an OBD/UDS Negative Response Code (NRC), + * except if the code is requestCorrectlyReceived-ResponsePending (0x78), which is handled + * by the poller. See ISO 14229 Annex A.1 for the list of NRC codes. + * + * @param job + * Status of the current Poll job + * @param code + * NRC detail code + */ +void OvmsVehicle::IncomingPollError(const OvmsPoller::poll_job_t &job, uint16_t code) + { + } + +/** + * IncomingPollTxCallback: poller TX callback (stub, override with vehicle implementation) + * This is called by PollerTxCallback() on TX success/failure for a poller request. + * You can use this to detect CAN bus issues, e.g. if the car switches off the OBD port. + * + * ATT: this is executed in the main CAN task context. Keep it simple. + * Complex processing here will affect overall CAN performance. + * + * @param job + * Status of the current Poll job + * @param success + * Frame transmission success + */ +void OvmsVehicle::IncomingPollTxCallback(const OvmsPoller::poll_job_t &job, bool success) + { + } +#endif + +void OvmsVehicle::IncomingPollRxFrame(const CAN_frame_t *frame, bool success) + { + if (!m_ready) + return; + + auto bus = frame->origin; + + // Pass frame to standard handlers: + CAN_frame_t tmp_frame = *frame; + if (m_can1 == bus) IncomingFrameCan1(&tmp_frame); + else if (m_can2 == bus) IncomingFrameCan2(&tmp_frame); + else if (m_can3 == bus) IncomingFrameCan3(&tmp_frame); + else if (m_can4 == bus) IncomingFrameCan4(&tmp_frame); + } + +#ifdef CONFIG_OVMS_COMP_POLLER +void OvmsVehicle::PollRequest(canbus* bus, const std::string &name, const std::shared_ptr &series) + { + auto poller = MyPollers.GetPoller(bus, true); + poller->PollRequest(name, series); + } + +void OvmsVehicle::RemovePollRequest(canbus* bus, const std::string &name) + { + auto poller = MyPollers.GetPoller(bus, false); + if (poller) + poller->RemovePollRequest(name); + } +#endif + +#ifdef CONFIG_OVMS_COMP_POLLER +/** Does the specified bus have a non-empty PollList ? + * @param bus Canbus to check or null for any bus + */ +bool OvmsVehicle::HasPollList(canbus* bus) + { + return MyPollers.HasPollList(bus); + } +#endif + #ifdef CONFIG_OVMS_COMP_WEBSERVER /** * GetDashboardConfig: template / default configuration diff --git a/vehicle/OVMS.V3/components/vehicle/vehicle.h b/vehicle/OVMS.V3/components/vehicle/vehicle.h index a7285d5ad..41d0ef401 100644 --- a/vehicle/OVMS.V3/components/vehicle/vehicle.h +++ b/vehicle/OVMS.V3/components/vehicle/vehicle.h @@ -34,6 +34,7 @@ #include #include #include +#include #include "can.h" #include "ovms_events.h" #include "ovms_config.h" @@ -42,30 +43,15 @@ #include "metrics_standard.h" #include "ovms_mutex.h" #include "ovms_semaphore.h" +#include "vehicle_common.h" +#ifdef CONFIG_OVMS_COMP_POLLER +#include "vehicle_poller.h" +#endif using namespace std; struct DashboardConfig; -// ISO TP: -// (see https://en.wikipedia.org/wiki/ISO_15765-2) - -#define ISOTP_FT_SINGLE 0 -#define ISOTP_FT_FIRST 1 -#define ISOTP_FT_CONSECUTIVE 2 -#define ISOTP_FT_FLOWCTRL 3 - -// Protocol variant: -#define ISOTP_STD 0 // standard addressing (11 bit IDs) -#define ISOTP_EXTADR 1 // extended addressing (19 bit IDs) -#define ISOTP_EXTFRAME 2 // extended frame mode (29 bit IDs) -#define VWTP_16 16 // VW/VAG Transport Protocol 1.6 (placeholder, unsupported) -#define VWTP_20 20 // VW/VAG Transport Protocol 2.0 - -// Argument tag: -#define POLL_TXDATA 0xff // poll_pid_t using xargs for external payload up to 4095 bytes - - // OBD2/UDS Polling types supported: // (see https://en.wikipedia.org/wiki/OBD-II_PIDs // and https://en.wikipedia.org/wiki/Unified_Diagnostic_Services) @@ -188,12 +174,6 @@ struct DashboardConfig; #define UDS_RESP_TYPE_NRC 0x7F // see ISO 14229 Annex A.1 #define UDS_RESP_NRC_RCRRP 0x78 // … requestCorrectlyReceived-ResponsePending -// Number of polling states supported -#define VEHICLE_POLL_NSTATES 4 - -// Macro for poll_pid_t termination -#define POLL_LIST_END { 0, 0, 0x00, 0x00, { 0, 0, 0 }, 0, 0 } - // Poll list PID xargs utility (see info above): #define POLL_PID_DATA(pid, datastring) \ {.xargs={ (pid), POLL_TXDATA, sizeof(datastring)-1, reinterpret_cast(datastring) }} @@ -243,38 +223,6 @@ struct DashboardConfig; #define BMS_DEFTHR_TWARN 2.00 // [°C] #define BMS_DEFTHR_TALERT 3.00 // [°C] - -// VWTP_20 channel states: -typedef enum - { - VWTP_Closed = 0, // Channel not connecting/connected - VWTP_ChannelClose, // Close request has been sent - VWTP_ChannelSetup, // Setup request has been sent - VWTP_ChannelParams, // Params request has been sent - VWTP_Idle, // Connection established, idle - VWTP_StartPoll, // Transit state: begin request transmission - VWTP_Transmit, // Request transmission phase - VWTP_Receive, // Response reception phase - VWTP_AbortXfer, // Transit state: abort request/response - } vwtp_channelstate_t; - -// VWTP_20 channel: -typedef struct - { - vwtp_channelstate_t state; // VWTP channel state - canbus* bus; // CAN bus - uint16_t baseid; // Protocol base CAN MsgID (usually 0x200) - uint8_t moduleid; // Logical address (ID) of destination module - uint16_t txid; // CAN MsgID we transmit on - uint16_t rxid; // CAN MsgID we listen to - uint8_t blocksize; // Number of blocks (data frames) to send between ACKs - uint32_t acktime; // Max time [us] to wait for ACK (not yet implemented) - uint32_t septime; // Min separation time [us] between blocks (data frames) - uint32_t txseqnr; // TX packet sequence number - uint32_t rxseqnr; // RX packet sequence number - uint32_t lastused; // Timestamp of last channel access - } vwtp_channel_t; - enum class OvmsStatus : short { OK = 0, Warn = 1, @@ -284,56 +232,10 @@ inline bool operator<(OvmsStatus lhs, OvmsStatus rhs) { return static_cast(lhs) < static_cast(rhs); } -namespace OvmsPoller - { - typedef struct - { - uint32_t txmoduleid; // transmission CAN ID (address), 0x7df = OBD2 broadcast - uint32_t rxmoduleid; // expected response CAN ID or 0 for broadcasts - uint16_t type; // UDS poll type / OBD2 "mode", see VEHICLE_POLL_TYPE_… - union - { - uint16_t pid; // PID (shortcut for requests w/o payload) - struct - { - uint16_t pid; // PID for requests with additional payload - uint8_t datalen; // payload length (bytes) - uint8_t data[6]; // inline payload data (single frame request) - } args; - struct - { - uint16_t pid; // PID for requests with additional payload - uint8_t tag; // needs to be POLL_TXDATA - uint16_t datalen; // payload length (bytes, max 4095) - const uint8_t* data; // pointer to payload data (single/multi frame request) - } xargs; - }; - uint16_t polltime[VEHICLE_POLL_NSTATES]; // poll intervals in seconds for used poll states - uint8_t pollbus; // 0 = default CAN bus from PollSetPidList(), 1…4 = specific - uint8_t protocol; // ISOTP_STD / ISOTP_EXTADR / ISOTP_EXTFRAME / VWTP_20 - } poll_pid_t; - - typedef struct - { - canbus* bus; ///< Bus to poll on. - uint32_t protocol; ///< ISOTP_STD / ISOTP_EXTADR / ISOTP_EXTFRAME / VWTP_20. - uint16_t type; ///< Expected type - uint16_t pid; ///< Expected PID - uint32_t moduleid_sent; ///< ModuleID last sent - uint32_t moduleid_low; ///< Expected response moduleid low mark - uint32_t moduleid_high; ///< Expected response moduleid high mark - uint32_t moduleid_rec; ///< Actual received moduleid. - uint16_t mlframe; ///< Frame number for multi frame response - uint16_t mloffset; ///< Current Byte offset of multi frame response - uint16_t mlremain; ///< Bytes remaining for multi frame response - OvmsPoller::poll_pid_t entry; ///< Currently processed entry of poll list (copy) - uint32_t ticker; ///< Polling tick count - } poll_job_t; - } - class OvmsVehicle : public InternalRamAllocated { friend class OvmsVehicleFactory; + friend class OvmsVehicleSignal; public: OvmsVehicle(); @@ -342,9 +244,6 @@ class OvmsVehicle : public InternalRamAllocated virtual const char* VehicleType(); protected: - QueueHandle_t m_rxqueue; - TaskHandle_t m_rxtask; - bool m_registeredlistener; bool m_autonotifications; bool m_ready; @@ -357,14 +256,45 @@ class OvmsVehicle : public InternalRamAllocated private: void VehicleTicker1(std::string event, void* data); void VehicleConfigChanged(std::string event, void* data); - void PollerResetThrottle(); + void PollRunFinishedNotify(canbus* bus, void *data); + void PollerStateTickerNotify(canbus* bus, void *data); + protected: + // Signal poller + void PausePolling(); + void ResumePolling(); + void PollSetState(uint8_t state, canbus* bus = nullptr); +#ifdef CONFIG_OVMS_COMP_POLLER + void PollSetPidList(canbus* bus, const OvmsPoller::poll_pid_t* plist); + void PollSetPidList( const OvmsPoller::poll_pid_t* plist) + { + PollSetPidList(m_poll_bus_default, plist); + } + OvmsPoller::VehicleSignal *GetPollerSignal(); + + int PollSingleRequest(canbus* bus, uint32_t txid, uint32_t rxid, + std::string request, std::string& response, + int timeout_ms=3000, uint8_t protocol=ISOTP_STD); + int PollSingleRequest(canbus* bus, uint32_t txid, uint32_t rxid, + uint8_t polltype, uint16_t pid, std::string& response, + int timeout_ms=3000, uint8_t protocol=ISOTP_STD); + + // Poller configuration - typedef enum { Primary, Successful, OnceOff } poller_source_t; - void PollerSend(poller_source_t source); - static const char *PollerSource(OvmsVehicle::poller_source_t src); + void PollSetThrottling(uint8_t sequence_max) + { + MyPollers.PollSetThrottling(sequence_max); + } + void PollSetTicker(uint16_t tick_time_ms, uint8_t secondary_ticks = 0); + + void PollSetResponseSeparationTime(uint8_t septime); + void PollSetChannelKeepalive(uint16_t keepalive_seconds); + void PollSetTimeBetweenSuccess(uint16_t tick_between_ms); +#endif + uint8_t GetBusNo(canbus* bus); + canbus *GetBus(uint8_t busno); protected: - virtual void PollRunFinished(); + virtual void PollRunFinished(canbus* bus); virtual void IncomingFrameCan1(CAN_frame_t* p_frame); virtual void IncomingFrameCan2(CAN_frame_t* p_frame); @@ -372,7 +302,7 @@ class OvmsVehicle : public InternalRamAllocated virtual void IncomingFrameCan4(CAN_frame_t* p_frame); protected: - virtual void PollerStateTicker(); + virtual void PollerStateTicker(canbus *bus); protected: int m_minsoc; // The minimum SOC level before alert @@ -423,6 +353,7 @@ class OvmsVehicle : public InternalRamAllocated float m_inv_refpower; // last inverter(motor) power protected: + uint32_t m_ticker; int m_12v_ticker; int m_12v_low_ticker; @@ -522,9 +453,6 @@ class OvmsVehicle : public InternalRamAllocated void RegisterCanBus(int bus, CAN_mode_t mode, CAN_speed_t speed, dbcfile* dbcfile = NULL); bool PinCheck(const char* pin); - public: - virtual void RxTask(); - public: typedef enum { @@ -589,110 +517,46 @@ class OvmsVehicle : public InternalRamAllocated virtual bool SetFeature(int key, const char* value); virtual const std::string GetFeature(int key); - enum class OvmsNextPollResult - { - StillAtEnd, - FoundEntry, - ReachedEnd - }; - - protected: - OvmsRecMutex m_poll_mutex; // Concurrency protection for recursive calls - uint8_t m_poll_state; // Current poll state - canbus* m_poll_bus_default; // Bus default to poll on - private: - bool m_poll_paused; // Processing the poll list is paused - - const OvmsPoller::poll_pid_t* m_poll_plist; // Head of poll list - const OvmsPoller::poll_pid_t* m_poll_plcur; // Poll list loop cursor - // Poll state for received data. - OvmsPoller::poll_job_t m_poll; - - const uint8_t* m_poll_tx_data; // Payload data for multi frame request - uint16_t m_poll_tx_remain; // Payload bytes remaining for multi frame request - uint16_t m_poll_tx_offset; // Payload offset of multi frame request - uint16_t m_poll_tx_frame; // Frame number for multi frame request - uint8_t m_poll_wait; // Wait counter for a reply from a sent poll or bytes remaining. - // Gets set = 2 when a poll is sent OR when bytes are remaining after receiving. - // Gets set = 0 when a poll is received. - // Gets decremented with every second/tick in PollerSend(). - // PollerSend() aborts when > 0. - // Why set = 2: When a poll gets send just before the next ticker occurs - // PollerSend() decrements to 1 and doesn't send the next poll. - // Only when the reply doesn't get in until the next ticker occurs - // PollserSend() decrements to 0 and abandons the outstanding reply (=timeout) +#ifdef CONFIG_OVMS_COMP_POLLER + /** Provide link from the poller back to the parent OvmsVehicle implementation. + */ + class OvmsVehicleSignal : public OvmsPoller::VehicleSignal { + private: + OvmsVehicle *m_parent; + public: + OvmsVehicleSignal( OvmsVehicle *parent); + // Signals for vehicle. - protected: - // Poll entry manipulation: Must be called under lock of m_poll_mutex - void ResetPollEntry(); - bool HasPollList(); + void IncomingPollReply(const OvmsPoller::poll_job_t &job, uint8_t* data, uint8_t length) override; + void IncomingPollError(const OvmsPoller::poll_job_t &job, uint16_t code) override; + void IncomingPollTxCallback(const OvmsPoller::poll_job_t &job, bool success) override; - OvmsNextPollResult NextPollEntry(OvmsPoller::poll_pid_t *entry); - void PollerNextTick(poller_source_t source); + void IncomingPollRxFrame(canbus* bus, CAN_frame_t *frame, bool success) override; + bool Ready() override; + }; +#endif - // Check for throttling. - bool CanPoll(); +#ifdef CONFIG_OVMS_COMP_POLLER + protected: + bool HasPollList(canbus* bus = nullptr); - void PausePolling(); - void ResumePolling(); + canbus* m_poll_bus_default; // Bus default to poll on // Polling Response virtual void IncomingPollReply(const OvmsPoller::poll_job_t &job, uint8_t* data, uint8_t length); virtual void IncomingPollError(const OvmsPoller::poll_job_t &job, uint16_t code); virtual void IncomingPollTxCallback(const OvmsPoller::poll_job_t &job, bool success); - - private: - uint8_t m_poll_sequence_max; // Polls allowed to be sent in sequence per time tick (second), default 1, 0 = no limit - uint8_t m_poll_sequence_cnt; // Polls already sent in the current time tick (second) - uint8_t m_poll_fc_septime; // Flow control separation time for multi frame responses - uint16_t m_poll_ch_keepalive; // Seconds to keep an inactive channel (e.g. VWTP) alive (default: 60) - - private: - OvmsRecMutex m_poll_single_mutex; // PollSingleRequest() concurrency protection - std::string* m_poll_single_rxbuf; // … response buffer - int m_poll_single_rxerr; // … response error code (NRC) / TX failure code - OvmsSemaphore m_poll_single_rxdone; // … response done (ok/error) - - protected: - vwtp_channel_t m_poll_vwtp; // VWTP channel state +#endif protected: - void PollSetPidList(canbus* bus, const OvmsPoller::poll_pid_t* plist); - void PollSetPidList( const OvmsPoller::poll_pid_t* plist) - { - PollSetPidList(m_poll.bus, plist); - } - void PollSetState(uint8_t state); - void PollSetThrottling(uint8_t sequence_max); - void PollSetResponseSeparationTime(uint8_t septime); - void PollSetChannelKeepalive(uint16_t keepalive_seconds); - int PollSingleRequest(canbus* bus, uint32_t txid, uint32_t rxid, - std::string request, std::string& response, - int timeout_ms=3000, uint8_t protocol=ISOTP_STD); - int PollSingleRequest(canbus* bus, uint32_t txid, uint32_t rxid, - uint8_t polltype, uint16_t pid, std::string& response, - int timeout_ms=3000, uint8_t protocol=ISOTP_STD); - const char* PollResultCodeName(int code); - - private: - void PollerISOTPStart(bool fromTicker); - bool PollerISOTPReceive(CAN_frame_t* frame, uint32_t msgid); - - private: - void PollerVWTPStart(bool fromTicker); - bool PollerVWTPReceive(CAN_frame_t* frame, uint32_t msgid); - void PollerVWTPEnter(vwtp_channelstate_t state); - void PollerVWTPTicker(); - void PollerVWTPTxCallback(const CAN_frame_t* frame, bool success); - - private: - CanFrameCallback m_poll_txcallback; // Poller CAN TxCallback - uint32_t m_poll_txmsgid; // Poller last TX CAN ID (frame MsgID) - - private: - void PollerTxCallback(const CAN_frame_t* frame, bool success); - +#ifdef CONFIG_OVMS_COMP_POLLER + OvmsVehicleSignal*m_pollsignal; + uint8_t m_poll_state; // Current poll state + void PollRequest(canbus* bus, const std::string &name, const std::shared_ptr &series); + void RemovePollRequest(canbus* bus, const std::string &name); +#endif + void IncomingPollRxFrame(const CAN_frame_t *frame, bool success); // BMS helpers protected: @@ -759,6 +623,12 @@ class OvmsVehicle : public InternalRamAllocated virtual bool FormatBmsAlerts(int verbosity, OvmsWriter* writer, bool show_warnings); bool BmsCheckChangeCellArrangementVoltage(int readings, int readingspermodule = 0); bool BmsCheckChangeCellArrangementTemperature(int readings, int readingspermodule = 0); + + protected: + bool m_is_shutdown; + public: + void StartingUp(); + void ShuttingDown(); }; template OvmsVehicle* CreateVehicle() @@ -785,6 +655,7 @@ class OvmsVehicleFactory std::string m_currentvehicletype; map_vehicle_t m_vmap; + void DoClearVehicle( bool clearName, bool sendEvent); public: template short RegisterVehicle(const char* VehicleType, const char* VehicleName = "") diff --git a/vehicle/OVMS.V3/components/vehicle/vehicle_common.h b/vehicle/OVMS.V3/components/vehicle/vehicle_common.h new file mode 100644 index 000000000..8a7f3be56 --- /dev/null +++ b/vehicle/OVMS.V3/components/vehicle/vehicle_common.h @@ -0,0 +1,49 @@ +/* +; Project: Open Vehicle Monitor System +; Date: 19th March 2024 + +; +; 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. +*/ +#ifndef __VEHICLE_COMMON_H__ +#define __VEHICLE_COMMON_H__ + +// ISOTP Defines needed by the Poller as well as the CanOpen code. +// Required if polling is not enabled in the configuration. + +// ISO TP: +// (see https://en.wikipedia.org/wiki/ISO_15765-2) + +#define ISOTP_FT_SINGLE 0 +#define ISOTP_FT_FIRST 1 +#define ISOTP_FT_CONSECUTIVE 2 +#define ISOTP_FT_FLOWCTRL 3 + +// Protocol variant: +#define ISOTP_STD 0 // standard addressing (11 bit IDs) +#define ISOTP_EXTADR 1 // extended addressing (19 bit IDs) +#define ISOTP_EXTFRAME 2 // extended frame mode (29 bit IDs) +#define VWTP_16 16 // VW/VAG Transport Protocol 1.6 (placeholder, unsupported) +#define VWTP_20 20 // VW/VAG Transport Protocol 2.0 + +// Argument tag: +#define POLL_TXDATA 0xff // poll_pid_t using xargs for external payload up to 4095 bytes + + +#endif diff --git a/vehicle/OVMS.V3/components/vehicle/vehicle_duktape.cpp b/vehicle/OVMS.V3/components/vehicle/vehicle_duktape.cpp index ad1b5737b..953bbb2d3 100644 --- a/vehicle/OVMS.V3/components/vehicle/vehicle_duktape.cpp +++ b/vehicle/OVMS.V3/components/vehicle/vehicle_duktape.cpp @@ -44,6 +44,7 @@ #include #include #include "vehicle.h" +#include "vehicle_common.h" #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE @@ -408,7 +409,9 @@ duk_ret_t OvmsVehicleFactory::DukOvmsVehicleObdRequest(duk_context *ctx) uint32_t txid = 0, rxid = 0; std::string request, response; int timeout = 3000; +#ifdef CONFIG_OVMS_COMP_POLLER uint8_t protocol = ISOTP_STD; +#endif if (!MyVehicleFactory.m_currentvehicle) { @@ -429,8 +432,10 @@ duk_ret_t OvmsVehicleFactory::DukOvmsVehicleObdRequest(duk_context *ctx) if (duk_get_prop_string(ctx, 0, "timeout")) timeout = duk_to_int(ctx, -1); duk_pop(ctx); +#ifdef CONFIG_OVMS_COMP_POLLER if (duk_get_prop_string(ctx, 0, "protocol")) protocol = duk_to_int(ctx, -1); +#endif duk_pop(ctx); if (duk_get_prop_string(ctx, 0, "txid")) txid = duk_to_int(ctx, -1); @@ -474,6 +479,7 @@ duk_ret_t OvmsVehicleFactory::DukOvmsVehicleObdRequest(duk_context *ctx) // Execute request: if (error == 0) { +#ifdef CONFIG_OVMS_COMP_POLLER error = MyVehicleFactory.m_currentvehicle->PollSingleRequest( device, txid, rxid, request, response, timeout, protocol); @@ -484,13 +490,17 @@ duk_ret_t OvmsVehicleFactory::DukOvmsVehicleObdRequest(duk_context *ctx) else if (error) { errordesc = "Request failed with response error code " + int_to_hex((uint8_t)error); - const char* errname = MyVehicleFactory.m_currentvehicle->PollResultCodeName(error); + const char* errname = OvmsPoller::PollResultCodeName(error); if (errname) { errordesc += ' '; errordesc += errname; } } +#else + error = -1; + errordesc = "Polling not implemented"; +#endif } } diff --git a/vehicle/OVMS.V3/components/vehicle/vehicle_poller.cpp b/vehicle/OVMS.V3/components/vehicle/vehicle_poller.cpp deleted file mode 100644 index 19ef3eb0e..000000000 --- a/vehicle/OVMS.V3/components/vehicle/vehicle_poller.cpp +++ /dev/null @@ -1,610 +0,0 @@ -/* -; Project: Open Vehicle Monitor System -; Date: 14th March 2017 -; -; Changes: -; 1.0 Initial release -; -; (C) 2011 Michael Stegen / Stegen Electronics -; (C) 2011-2017 Mark Webb-Johnson -; (C) 2011 Sonny Chen @ EPRO/DX -; -; 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 = "vehicle-poll"; - -#include -#include -#include -#include -#include -#include -#include -#ifdef CONFIG_OVMS_COMP_WEBSERVER -#include -#endif // #ifdef CONFIG_OVMS_COMP_WEBSERVER -#include -#include -#include "vehicle.h" - - -/** - * PollerStateTicker: check for state changes (stub, override with vehicle implementation) - * This is called by VehicleTicker1() just before the next PollerSend(). - * Implement your poller state transition logic in this method, so the changes - * will get applied immediately. - */ -void OvmsVehicle::PollerStateTicker() - { - } - - -/** - * IncomingPollReply: poll response handler (stub, override with vehicle implementation) - * This is called by PollerReceive() on each valid response frame for the current request. - * Be aware responses may consist of multiple frames, detectable e.g. by mlremain > 0. - * A typical pattern is to collect frames in a buffer until mlremain == 0. - * - * @param job - * Status of the current Poll job - * @param data - * Payload - * @param length - * Payload size - */ -void OvmsVehicle::IncomingPollReply(const OvmsPoller::poll_job_t &job, uint8_t* data, uint8_t length) - { - } - -/** - * IncomingPollError: poll response error handler (stub, override with vehicle implementation) - * This is called by PollerReceive() on reception of an OBD/UDS Negative Response Code (NRC), - * except if the code is requestCorrectlyReceived-ResponsePending (0x78), which is handled - * by the poller. See ISO 14229 Annex A.1 for the list of NRC codes. - * - * @param job - * Status of the current Poll job - * @param code - * NRC detail code - */ -void OvmsVehicle::IncomingPollError(const OvmsPoller::poll_job_t &job, uint16_t code) - { - } - -/** - * IncomingPollTxCallback: poller TX callback (stub, override with vehicle implementation) - * This is called by PollerTxCallback() on TX success/failure for a poller request. - * You can use this to detect CAN bus issues, e.g. if the car switches off the OBD port. - * - * ATT: this is executed in the main CAN task context. Keep it simple. - * Complex processing here will affect overall CAN performance. - * - * @param job - * Status of the current Poll job - * @param success - * Frame transmission success - */ -void OvmsVehicle::IncomingPollTxCallback(const OvmsPoller::poll_job_t &job, bool success) - { - } - - -/** - * PollSetPidList: set the default bus and the polling list to process - * Call this to install a new polling list or restart the list. - * This won't change the polling state; you can change the list while keeping the state. - * The list is changed without waiting for pending responses to finish (except PollSingleRequests). - * - * @param bus - * CAN bus to use as the default bus (for all poll entries with bus=0) or NULL to stop polling - * @param plist - * The polling list to use or NULL to stop polling - */ -void OvmsVehicle::PollSetPidList(canbus* bus, const OvmsPoller::poll_pid_t* plist) - { - OvmsRecMutexLock slock(&m_poll_single_mutex); - OvmsRecMutexLock lock(&m_poll_mutex); - m_poll.bus = bus; - m_poll_bus_default = bus; - m_poll_plist = plist; - m_poll.ticker = 0; - m_poll_sequence_cnt = 0; - m_poll_wait = 0; - ResetPollEntry(); - } - - -/** - * PollSetState: set the polling state - * Call this to change the polling state and restart the current polling list. - * This won't do anything if the state is already active. The state is changed without - * waiting for pending responses to finish (except PollSingleRequests). - * - * @param state - * The polling state to activate (0 … VEHICLE_POLL_NSTATES) - */ -void OvmsVehicle::PollSetState(uint8_t state) - { - if ((state < VEHICLE_POLL_NSTATES)&&(state != m_poll_state)) - { - OvmsRecMutexLock slock(&m_poll_single_mutex); - OvmsRecMutexLock lock(&m_poll_mutex); - m_poll_state = state; - m_poll.ticker = 0; - m_poll_sequence_cnt = 0; - m_poll_wait = 0; - m_poll_plcur = NULL; - m_poll.entry = {}; - m_poll_txmsgid = 0; - } - } - - -/** - * PollSetThrottling: configure polling speed / niceness - * If multiple requests are due at the same poll tick (second), this controls how many of - * them will be sent in series without a delay, i.e. as soon as the response/timeout for - * the previous request occurred. - * - * @param sequence_max - * Polls allowed to be sent in sequence per time tick (second), default 1, 0 = no limit. - * - * The configuration is kept unchanged over calls to PollSetPidList() or PollSetState(). - */ -void OvmsVehicle::PollSetThrottling(uint8_t sequence_max) - { - OvmsRecMutexLock lock(&m_poll_mutex); - m_poll_sequence_max = sequence_max; - } - - -/** - * PollSetResponseSeparationTime: configure ISO TP multi frame response timing - * See: https://en.wikipedia.org/wiki/ISO_15765-2 - * - * @param septime - * Separation Time (ST), the minimum delay time between frames. Default: 25 milliseconds - * ST values up to 127 (0x7F) specify the minimum number of milliseconds to delay between frames, - * while values in the range 241 (0xF1) to 249 (0xF9) specify delays increasing from - * 100 to 900 microseconds. - * - * The configuration is kept unchanged over calls to PollSetPidList() or PollSetState(). - */ -void OvmsVehicle::PollSetResponseSeparationTime(uint8_t septime) - { - assert (septime <= 127 || (septime >= 241 && septime <= 249)); - OvmsRecMutexLock lock(&m_poll_mutex); - m_poll_fc_septime = septime; - } - - -/** - * PollSetChannelKeepalive: configure keepalive timeout for channel oriented protocols - * - * The VWTP poller will keep a channel connection to a module open for this - * many seconds after the last poll data transmission has taken place, to - * minimize connection overhead in case the next poll is sent to the same - * module. - * - * Devices may stay awake as long as the channel is open, so don't disable - * the timeout except for special purposes. - * - * @param keepalive_seconds - * Channel inactivity timeout in seconds, 0 = disable, default/init = 60 - */ -void OvmsVehicle::PollSetChannelKeepalive(uint16_t keepalive_seconds) - { - m_poll_ch_keepalive = keepalive_seconds; - } - -void OvmsVehicle::PollerResetThrottle() - { - // Main Timer reset throttling counter, - m_poll_sequence_cnt = 0; - } - -void OvmsVehicle::ResetPollEntry() - { - m_poll_plcur = NULL; - m_poll.entry = {}; - m_poll_txmsgid = 0; - } - -bool OvmsVehicle::HasPollList() - { - return (m_poll_bus_default != NULL) - && (m_poll_plist != NULL) - && (m_poll_plist->txmoduleid != 0); - } - -bool OvmsVehicle::CanPoll() - { - // Check Throttle - return (!m_poll_sequence_max || m_poll_sequence_cnt < m_poll_sequence_max); - } -/** Pause polling - don't progress through the poll list. - */ -void OvmsVehicle::PausePolling() - { - OvmsRecMutexLock slock(&m_poll_single_mutex); - OvmsRecMutexLock lock(&m_poll_mutex); - m_poll_paused = true; - } -/** Resume polling. - */ -void OvmsVehicle::ResumePolling() - { - OvmsRecMutexLock lock(&m_poll_mutex); - m_poll_paused = false; - } - -OvmsVehicle::OvmsNextPollResult OvmsVehicle::NextPollEntry(OvmsPoller::poll_pid_t *entry) - { - *entry = {}; - // Restart poll list cursor: - if (m_poll_plcur == NULL) - m_poll_plcur = m_poll_plist; - else if (m_poll_plcur->txmoduleid == 0) - return OvmsNextPollResult::StillAtEnd; - else - ++m_poll_plcur; - - while (m_poll_plcur->txmoduleid != 0) - { - if ((m_poll_plcur->polltime[m_poll_state] > 0) && - ((m_poll.ticker % m_poll_plcur->polltime[m_poll_state]) == 0)) - { - *entry = *m_poll_plcur; - return OvmsNextPollResult::FoundEntry; - } - // Poll entry is not due, check next - ++m_poll_plcur; - } - return OvmsNextPollResult::ReachedEnd; - } - -void OvmsVehicle::PollerNextTick(poller_source_t source) - { - // Completed checking all poll entries for the current m_poll_ticker - ESP_LOGD(TAG, "PollerSend(%s): cycle complete for ticker=%u", PollerSource(source), m_poll.ticker); - - // Allow POLL to restart. - m_poll_plcur = NULL; - - m_poll.ticker++; - if (m_poll.ticker > 3600) m_poll.ticker -= 3600; - } - -/** Called after reaching the end of available POLL entries. - */ -void OvmsVehicle::PollRunFinished() - { - } - -/** - * PollerSend: internal: start next due request - */ -void OvmsVehicle::PollerSend(poller_source_t source) - { - OvmsRecMutexLock lock(&m_poll_mutex); - - // ESP_LOGD(TAG, "PollerSend(%d): entry at[type=%02X, pid=%X], ticker=%u, wait=%u, cnt=%u/%u", - // fromTicker, m_poll_plcur->type, m_poll_plcur->pid, - // m_poll_ticker, m_poll_wait, m_poll_sequence_cnt, m_poll_sequence_max); - - bool fromPrimaryTicker = false, fromOnceOffTicker = false; - switch (source) - { - case poller_source_t::OnceOff: - fromOnceOffTicker = true; - break; - case poller_source_t::Primary: - fromPrimaryTicker = true; - break; - default: - ; - } - if (fromPrimaryTicker) - { - // Timer ticker call: reset throttling counter - PollerResetThrottle(); - - // Only reset the list when 'from Ticker' and it's at the end. - if (m_poll_plcur && m_poll_plcur->txmoduleid == 0) - { - PollerNextTick(source); - } - } - if (fromPrimaryTicker || fromOnceOffTicker) - { - // Timer ticker call: check response timeout - if (m_poll_wait > 0) m_poll_wait--; - - // Protocol specific ticker calls: - PollerVWTPTicker(); - } - if (m_poll_wait > 0) return; - - // Check poll bus & list: - if (!HasPollList()) return; - - if (m_poll_paused) return; - - switch (NextPollEntry(&m_poll.entry)) - { - case OvmsNextPollResult::ReachedEnd: - PollRunFinished(); - // fall through - case OvmsNextPollResult::StillAtEnd: - { - PollerNextTick(source); - break; - } - case OvmsNextPollResult::FoundEntry: - { - ESP_LOGD(TAG, "PollerSend(%s)[%d]: entry at[type=%02X, pid=%X], ticker=%u, wait=%u, cnt=%u/%u", - PollerSource(source), m_poll_state, m_poll.entry.type, m_poll.entry.pid, - m_poll.ticker, m_poll_wait, m_poll_sequence_cnt, m_poll_sequence_max); - // We need to poll this one... - m_poll.protocol = m_poll.entry.protocol; - m_poll.type = m_poll.entry.type; - m_poll.pid = m_poll.entry.pid; - - switch (m_poll.entry.pollbus) - { - case 1: - m_poll.bus = m_can1; - break; - case 2: - m_poll.bus = m_can2; - break; - case 3: - m_poll.bus = m_can3; - break; - case 4: - m_poll.bus = m_can4; - break; - default: - m_poll.bus = m_poll_bus_default; - } - - // Dispatch transmission start to protocol handler: - if (m_poll.protocol == VWTP_20) - PollerVWTPStart(fromPrimaryTicker); - else - PollerISOTPStart(fromPrimaryTicker); - - m_poll_sequence_cnt++; - break; - } - } - } - - -/** - * PollerTxCallback: internal: process poll request callbacks - */ -void OvmsVehicle::PollerTxCallback(const CAN_frame_t* frame, bool success) - { - OvmsRecMutexLock lock(&m_poll_mutex); - - // Check for a late callback: - if (!m_poll_wait || !HasPollList() || frame->origin != m_poll.bus || frame->MsgID != m_poll_txmsgid) - return; - - // Forward to protocol handler: - if (m_poll.protocol == VWTP_20) - PollerVWTPTxCallback(frame, success); - - // On failure, try to speed up the current poll timeout: - if (!success) - { - m_poll_wait = 0; - if (m_poll_single_rxbuf) - { - m_poll_single_rxerr = POLLSINGLE_TXFAILURE; - m_poll_single_rxbuf = NULL; - m_poll_single_rxdone.Give(); - } - } - - // Forward to application: - m_poll.moduleid_rec = 0; // Not yet received - IncomingPollTxCallback(m_poll, success); - } - -/** - * PollSingleRequest: perform prioritized synchronous single OBD2/UDS request - * Pass a full OBD2/UDS request (mode/type, PID, additional payload). - * The request is sent immediately, aborting a running poll list request. The previous - * poller state is automatically restored after the request has been performed, but - * without any guarantee for repetition or omission of an aborted poll. - * On success, the response buffer will contain the response payload (may be empty). - * - * See OvmsVehicleFactory::obdii_request() for a usage example. - * - * ATT: must not be called from within the vehicle task context -- deadlock situation! - * - * @param bus CAN bus to use for the request - * @param txid CAN ID to send to (0x7df = broadcast) - * @param rxid CAN ID to expect response from (broadcast: 0) - * @param request Request to send (binary string) (type + pid + up to 4095 bytes payload) - * @param response Response buffer (binary string) (multiple response frames assembled) - * @param timeout_ms Timeout for poller/response in milliseconds - * @param protocol Protocol variant: ISOTP_STD / ISOTP_EXTADR / ISOTP_EXTFRAME - * - * @return POLLSINGLE_OK (0) -- success, response is valid - * POLLSINGLE_TIMEOUT (-1) -- timeout/poller unavailable - * POLLSINGLE_TXFAILURE (-2) -- CAN transmission failure - * else (>0) -- UDS NRC detail code - * Note: response is only valid with return value 0 - */ -int OvmsVehicle::PollSingleRequest(canbus* bus, uint32_t txid, uint32_t rxid, - std::string request, std::string& response, - int timeout_ms /*=3000*/, uint8_t protocol /*=ISOTP_STD*/) - { - if (!m_ready) - return -1; - - if (!m_registeredlistener) - { - m_registeredlistener = true; - MyCan.RegisterListener(m_rxqueue); - } - - OvmsRecMutexLock slock(&m_poll_single_mutex, pdMS_TO_TICKS(timeout_ms)); - if (!slock.IsLocked()) - return -1; - - // prepare single poll: - OvmsPoller::poll_pid_t poll[] = - { - { txid, rxid, 0, 0, { 999, 999, 999, 999 }, 0, protocol }, - POLL_LIST_END - }; - - assert(request.size() > 0); - poll[0].type = request[0]; - poll[0].xargs.tag = POLL_TXDATA; - - if (POLL_TYPE_HAS_16BIT_PID(poll[0].type)) - { - assert(request.size() >= 3); - poll[0].xargs.pid = request[1] << 8 | request[2]; - poll[0].xargs.datalen = LIMIT_MAX(request.size()-3, 4095); - poll[0].xargs.data = (const uint8_t*)request.data()+3; - } - else if (POLL_TYPE_HAS_8BIT_PID(poll[0].type)) - { - assert(request.size() >= 2); - poll[0].xargs.pid = request.at(1); - poll[0].xargs.datalen = LIMIT_MAX(request.size()-2, 4095); - poll[0].xargs.data = (const uint8_t*)request.data()+2; - } - else - { - poll[0].xargs.pid = 0; - poll[0].xargs.datalen = LIMIT_MAX(request.size()-1, 4095); - poll[0].xargs.data = (const uint8_t*)request.data()+1; - } - - // acquire poller access: - if (!m_poll_mutex.Lock(pdMS_TO_TICKS(timeout_ms))) - return -1; - - // save poller state: - canbus* p_bus = m_poll_bus_default; - const OvmsPoller::poll_pid_t* p_list = m_poll_plist; - const OvmsPoller::poll_pid_t* p_plcur = m_poll_plcur; - uint32_t p_ticker = m_poll.ticker; - - // start single poll: - PollSetPidList(bus, poll); - m_poll_single_rxdone.Take(0); - m_poll_single_rxbuf = &response; - PollerSend(poller_source_t::OnceOff); - m_poll_mutex.Unlock(); - - // wait for response: - bool rxok = m_poll_single_rxdone.Take(pdMS_TO_TICKS(timeout_ms)); - - // restore poller state: - m_poll_mutex.Lock(); - PollSetPidList(p_bus, p_list); - m_poll_plcur = p_plcur; - m_poll.ticker = p_ticker; - m_poll_single_rxbuf = NULL; - m_poll_mutex.Unlock(); - - return (rxok == pdFALSE) ? -1 : m_poll_single_rxerr; - } - - -/** - * PollSingleRequest: perform prioritized synchronous single OBD2/UDS request - * Convenience wrapper for standard PID polls, see above for main implementation. - * - * @param bus CAN bus to use for the request - * @param txid CAN ID to send to (0x7df = broadcast) - * @param rxid CAN ID to expect response from (broadcast: 0) - * @param polltype OBD2/UDS poll type … - * @param pid … and PID to poll - * @param response Response buffer (binary string) (multiple response frames assembled) - * @param timeout_ms Timeout for poller/response in milliseconds - * @param protocol Protocol variant: ISOTP_STD / ISOTP_EXTADR / ISOTP_EXTFRAME - * - * @return POLLSINGLE_OK ( 0) -- success, response is valid - * POLLSINGLE_TIMEOUT (-1) -- timeout/poller unavailable - * POLLSINGLE_TXFAILURE (-2) -- CAN transmission failure - * else (>0) -- UDS NRC detail code - * Note: response is only valid with return value 0 - */ -int OvmsVehicle::PollSingleRequest(canbus* bus, uint32_t txid, uint32_t rxid, - uint8_t polltype, uint16_t pid, std::string& response, - int timeout_ms /*=3000*/, uint8_t protocol /*=ISOTP_STD*/) - { - std::string request; - request = (char) polltype; - if (POLL_TYPE_HAS_16BIT_PID(polltype)) - { - request += (char) (pid >> 8); - request += (char) (pid & 0xff); - } - else if (POLL_TYPE_HAS_8BIT_PID(polltype)) - { - request += (char) (pid & 0xff); - } - return PollSingleRequest(bus, txid, rxid, request, response, timeout_ms, protocol); - } - - -/** - * PollResultCodeName: get text representation of result code - */ -const char* OvmsVehicle::PollResultCodeName(int code) - { - switch (code) - { - case 0x10: return "generalReject"; - case 0x11: return "serviceNotSupported"; - case 0x12: return "subFunctionNotSupported"; - case 0x13: return "incorrectMessageLengthOrInvalidFormat"; - case 0x14: return "responseTooLong"; - case 0x21: return "busyRepeatRequest"; - case 0x22: return "conditionsNotCorrect"; - case 0x24: return "requestSequenceError"; - case 0x25: return "noResponseFromSubnetComponent"; - case 0x26: return "failurePreventsExecutionOfRequestedAction"; - case 0x31: return "requestOutOfRange"; - case 0x33: return "securityAccessDenied"; - case 0x35: return "invalidKey"; - case 0x36: return "exceedNumberOfAttempts"; - case 0x37: return "requiredTimeDelayNotExpired"; - case 0x70: return "uploadDownloadNotAccepted"; - case 0x71: return "transferDataSuspended"; - case 0x72: return "generalProgrammingFailure"; - case 0x73: return "wrongBlockSequenceCounter"; - case 0x78: return "requestCorrectlyReceived-ResponsePending"; - case 0x7e: return "subFunctionNotSupportedInActiveSession"; - case 0x7f: return "serviceNotSupportedInActiveSession"; - default: return NULL; - } - } diff --git a/vehicle/OVMS.V3/components/vehicle/vehicle_shell.cpp b/vehicle/OVMS.V3/components/vehicle/vehicle_shell.cpp index 970690602..68ab54ed4 100644 --- a/vehicle/OVMS.V3/components/vehicle/vehicle_shell.cpp +++ b/vehicle/OVMS.V3/components/vehicle/vehicle_shell.cpp @@ -44,7 +44,7 @@ #include #include #include "vehicle.h" - +#include "vehicle_common.h" int OvmsVehicleFactory::vehicle_validate(OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv, bool complete) { @@ -496,6 +496,9 @@ void OvmsVehicleFactory::obdii_request(int verbosity, OvmsWriter* writer, OvmsCo writer->printf("ERROR: CAN bus %s not in active mode\n", busname); return; } +#ifndef CONFIG_OVMS_COMP_POLLER + writer->puts("Poller not implemented"); +#else uint32_t txid = 0, rxid = 0; uint8_t protocol = ISOTP_STD; @@ -636,7 +639,7 @@ void OvmsVehicleFactory::obdii_request(int verbosity, OvmsWriter* writer, OvmsCo } else if (err) { - const char* errname = MyVehicleFactory.m_currentvehicle->PollResultCodeName(err); + const char* errname = OvmsPoller::PollResultCodeName(err); writer->printf("ERROR: request failed with response error code %02X%s%s\n", err, errname ? " " : "", errname ? errname : ""); return; @@ -656,4 +659,5 @@ void OvmsVehicleFactory::obdii_request(int verbosity, OvmsWriter* writer, OvmsCo if (buf) free(buf); +#endif } diff --git a/vehicle/OVMS.V3/components/vehicle_boltev/src/vehicle_boltev.cpp b/vehicle/OVMS.V3/components/vehicle_boltev/src/vehicle_boltev.cpp index 61fec58e2..a26cbf664 100644 --- a/vehicle/OVMS.V3/components/vehicle_boltev/src/vehicle_boltev.cpp +++ b/vehicle/OVMS.V3/components/vehicle_boltev/src/vehicle_boltev.cpp @@ -783,7 +783,7 @@ void OvmsVehicleBoltEV::Ticker10(uint32_t ticker) } } -void OvmsVehicleBoltEV::PollRunFinished() +void OvmsVehicleBoltEV::PollRunFinished(canbus *bus) { if (m_poll_state == 2) { // polling complete one time for state 2. Switch to state 1. diff --git a/vehicle/OVMS.V3/components/vehicle_boltev/src/vehicle_boltev.h b/vehicle/OVMS.V3/components/vehicle_boltev/src/vehicle_boltev.h index 86eafeeb0..37be57eb9 100644 --- a/vehicle/OVMS.V3/components/vehicle_boltev/src/vehicle_boltev.h +++ b/vehicle/OVMS.V3/components/vehicle_boltev/src/vehicle_boltev.h @@ -110,7 +110,7 @@ class OvmsVehicleBoltEV : public OvmsVehicle void NotifyFuel(); void NotifyMetrics(); - void PollRunFinished() override; + void PollRunFinished(canbus *bus) override; protected: char m_vin[18]; char m_type[6]; diff --git a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/docs/index.rst b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/docs/index.rst index 30c95c720..f33cedf68 100644 --- a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/docs/index.rst +++ b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/docs/index.rst @@ -23,7 +23,7 @@ Vehicle Cable OBD-II to DB9 Data Cable for OVMS (1441200 right, or GSM Antenna 1000500 Open Vehicles OVMS GSM Antenna (or any compatible antenna) GPS Antenna 1020200 Universal GPS Antenna (SMA Connector) (or any compatible antenna) SOC Display Yes -Range Display No +Range Display Yes GPS Location Yes (from modem module GPS) Speed Display Yes Temperature Display Partial @@ -49,7 +49,6 @@ Metric name Example value Description ======================================== ======================== ============================================ xiq.m.version 0.0.1 10/09/2022 10:23 Version of Module xiq.b.bms.soc 78.5% Internal BMS SOC - xiq.v.b.c.voltage.max 10.0V Battery Cell Volt Max xiq.v.b.c.voltage.min 10.0V Battery Cell Volt Min xiq.v.b.c.voltage.max.no 123450 Battery Cell Volt Max No @@ -102,6 +101,17 @@ xiq.v.c.current.req 45A Requested char ======================================== ======================== ============================================ +-------------- +Custom Configs +-------------- + +======================================== ============== ========= ============================================ +Config name Default value …unit Description +======================================== ============== ========= ============================================ +xiq leftDrive true This car is left-hand-drive +xiq notify.charge.delay.ccs 15 Seconds Wait time for DC charge power to ramp up before sending the notification +xiq notify.charge.delay.type2 10 Seconds … same for AC charging +======================================== ============== ========= ============================================ ---------- Debug Logs diff --git a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/hif_can_poll.cpp b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/hif_can_poll.cpp index b8e835db8..fa0a508b3 100644 --- a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/hif_can_poll.cpp +++ b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/hif_can_poll.cpp @@ -29,6 +29,11 @@ #include "ovms_utils.h" #include +#ifdef bind +#undef bind +#endif +using namespace std::placeholders; + /** * Incoming poll reply messages */ @@ -136,42 +141,63 @@ void OvmsHyundaiIoniqEv::IncomingPollReply(const OvmsPoller::poll_job_t &job, ui return; } - ESP_LOGD(TAG, "IoniqISOTP: IPR %03" PRIx32 " TYPE:%x PID: %03x Frames: %d Message Size: %d", - job.moduleid_rec, job.type, job.pid, obd_frame, rxbuf.size()); - ESP_BUFFER_LOGD(TAG, rxbuf.data(), rxbuf.size()); - switch (job.moduleid_rec) { - case 0x778: - IncomingIGMP_Full(job.bus, job.type, job.pid, rxbuf); - break; - case 0x7ce: - IncomingCM_Full(job.bus, job.type, job.pid, rxbuf); - break; - case 0x7ec: - IncomingBMC_Full(job.bus, job.type, job.pid, rxbuf); - break; - // ****** BCM ****** - case 0x7a8: - IncomingBCM_Full(job.bus, job.type, job.pid, rxbuf); - break; - // ****** ?? Misc inc speed ****** - case 0x7bb: - IncomingOther_Full(job.bus, job.type, job.pid, rxbuf); - break; - // ******* VMCU ****** - case 0x7ea: - IncomingVMCU_Full(job.bus, job.type, job.pid, rxbuf); - break; - } + Incoming_Full(job.type, job.moduleid_sent, job.moduleid_rec, job.pid, rxbuf); obd_frame = 0xffff; // Received all - drop until we have a new frame 0 rxbuf.clear(); } XDISARM; } +void OvmsHyundaiIoniqEv::Incoming_Full(uint16_t type, uint32_t module_sent, uint32_t module_rec, uint16_t pid, const std::string &data) +{ + ESP_LOGD(TAG, "IoniqISOTP: IPR %03" PRIx32 " TYPE:%x PID: %03x Message Size: %d", + module_rec, type, pid, data.size()); + ESP_BUFFER_LOGV(TAG, data.data(), data.size()); + switch (type) { + case VEHICLE_POLL_TYPE_READDATA: + switch (module_rec) { + case 0x778: + IncomingIGMP_Full(type, pid, data); + break; + case 0x7ce: + IncomingCM_Full(type, pid, data); + break; + case 0x7ec: + IncomingBMC_Full(type, pid, data); + break; + // ****** BCM ****** + case 0x7a8: + IncomingBCM_Full(type, pid, data); + break; + // ****** ?? Misc inc speed ****** + case 0x7bb: + IncomingOther_Full(type, pid, data); + break; + // ******* VMCU ****** + case 0x7ea: + IncomingVMCU_Full(type, pid, data); + break; + } + break; + case VEHICLE_POLL_TYPE_OBDIIVEHICLE: + switch (pid) { + case 2: // VIN + ProcessVIN(data); + break; + } + break; + } +} +void OvmsHyundaiIoniqEv::Incoming_Fail(uint16_t type, uint32_t module_sent, uint32_t module_rec, uint16_t pid, int errorcode) +{ + ESP_LOGE(TAG, "IoniqISOTP: IPR %03" PRIx32 " TYPE:%x PID: %03x Error: %d", + module_rec, type, pid, errorcode); +} + /** * Handle incoming messages from cluster. */ -void OvmsHyundaiIoniqEv::IncomingCM_Full(canbus *bus, uint16_t type, uint16_t pid, const std::string &data) +void OvmsHyundaiIoniqEv::IncomingCM_Full(uint16_t type, uint16_t pid, const std::string &data) { XARM("OvmsHyundaiIoniqEv::IncomingCM_Full"); switch (pid) { @@ -192,7 +218,7 @@ void OvmsHyundaiIoniqEv::IncomingCM_Full(canbus *bus, uint16_t type, uint16_t pi /** * Handle incoming messages from Aircon poll. */ -void OvmsHyundaiIoniqEv::IncomingOther_Full(canbus *bus, uint16_t type, uint16_t pid, const std::string &data) +void OvmsHyundaiIoniqEv::IncomingOther_Full(uint16_t type, uint16_t pid, const std::string &data) { // 0x7b3->0x7bb XARM("OvmsHyundaiIoniqEv::IncomingOther_Full"); @@ -220,7 +246,7 @@ void OvmsHyundaiIoniqEv::IncomingOther_Full(canbus *bus, uint16_t type, uint16_t * * - Aux battery SOC, Voltage and current */ -void OvmsHyundaiIoniqEv::IncomingVMCU_Full(canbus *bus, uint16_t type, uint16_t pid, const std::string &data) +void OvmsHyundaiIoniqEv::IncomingVMCU_Full(uint16_t type, uint16_t pid, const std::string &data) { // 0x7ea @@ -310,7 +336,7 @@ void OvmsHyundaiIoniqEv::IncomingVMCU_Full(canbus *bus, uint16_t type, uint16_t * - Cell voltage max / min + cell # * + more */ -void OvmsHyundaiIoniqEv::IncomingBMC_Full(canbus *bus, uint16_t type, uint16_t pid, const std::string &data) +void OvmsHyundaiIoniqEv::IncomingBMC_Full(uint16_t type, uint16_t pid, const std::string &data) { // 0x7e4->0x7ec XARM("OvmsHyundaiIoniqEv::IncomingBMC_Full"); @@ -645,7 +671,7 @@ void OvmsHyundaiIoniqEv::IncomingBMC_Full(canbus *bus, uint16_t type, uint16_t p * Handle incoming messages from BCM-poll * */ -void OvmsHyundaiIoniqEv::IncomingBCM_Full(canbus *bus, uint16_t type, uint16_t pid, const std::string &data) +void OvmsHyundaiIoniqEv::IncomingBCM_Full(uint16_t type, uint16_t pid, const std::string &data) { XARM("OvmsHyundaiIoniqEv::IncomingBCM_Full"); //0x7a8 @@ -728,7 +754,7 @@ void OvmsHyundaiIoniqEv::IncomingBCM_Full(canbus *bus, uint16_t type, uint16_t p * Handle incoming messages from IGMP-poll * */ -void OvmsHyundaiIoniqEv::IncomingIGMP_Full(canbus *bus, uint16_t type, uint16_t pid, const std::string &data) +void OvmsHyundaiIoniqEv::IncomingIGMP_Full(uint16_t type, uint16_t pid, const std::string &data) { XARM("OvmsHyundaiIoniqEv::IncomingIGMP_Full"); // 0x778 @@ -894,74 +920,112 @@ void OvmsHyundaiIoniqEv::IncomingIGMP_Full(canbus *bus, uint16_t type, uint16_t XDISARM; } -int OvmsHyundaiIoniqEv::RequestVIN() +IqVinStatus OvmsHyundaiIoniqEv::ProcessVIN(const std::string &response) { - //ESP_LOGD(TAG, "RequestVIN: Sending Request"); - if (!StdMetrics.ms_v_env_awake->AsBool()) { - ESP_LOGD(TAG, "RequestVIN: Not Awake Request not sent"); - return -3; + uint32_t byte; + if (!get_uint_buff_be<1>(response, 0, byte)) { + ESP_LOGE(TAG, "ProcessVIN: Bad Buffer"); + return IqVinStatus::BadBuffer; } - std::string response; - int res = PollSingleRequest( m_can1, - VEHICLE_OBD_BROADCAST_MODULE_TX, VEHICLE_OBD_BROADCAST_MODULE_RX, - VEHICLE_POLL_TYPE_OBDIIVEHICLE, 2, response, 1000); - if (res != POLLSINGLE_OK) { - switch (res) { - case POLLSINGLE_TIMEOUT: - ESP_LOGE(TAG, "RequestVIN: Request Timeout"); - break; - case POLLSINGLE_TXFAILURE: - ESP_LOGE(TAG, "RequestVIN: Request TX Failure"); - break; - default: - ESP_LOGE(TAG, "RequestVIN: UDC Error %d", res); + else if (byte == 1) + { + std::string vin; + if ( !get_buff_string(response, 1, 17, vin)) { + return IqVinStatus::BadBuffer; + } + if (vin.length() > 5 && vin[4] == '-') { + vin = vin.substr(0, 3) + vin.substr(10) + "-------"; } + StandardMetrics.ms_v_vin->SetValue(vin); + ESP_BUFFER_LOGD(TAG, response.data(), response.size()); - return res; + vin.copy(m_vin, sizeof(m_vin) - 1); + m_vin[sizeof(m_vin) - 1] = '\0'; + ESP_LOGD(TAG, "ProcessVIN: Success: '%s'", vin.c_str()); + return IqVinStatus::Success; + } + else if (!get_uint_buff_be<1>(response, 4, byte)) { + ESP_LOGE(TAG, "ProcessVIN: Bad Buffer"); + return IqVinStatus::BadBuffer; + } + else if (byte != 1) { + ESP_LOGI(TAG, "ProcessVIN: Ignore Response"); + return IqVinStatus::BadFormat; } else { - uint32_t byte; - if (!get_uint_buff_be<1>(response, 4, byte)) { - ESP_LOGE(TAG, "RequestVIN: Bad Buffer"); - return POLLSINGLE_TIMEOUT; - } - else if (byte != 1) { - ESP_LOGI(TAG, "RequestVIN: Ignore Response"); - return POLLSINGLE_TIMEOUT; + std::string vin; + if ( get_buff_string(response, 5, 17, vin)) { + if (vin.length() > 5 && vin[4] == '-') { + vin = vin.substr(0, 3) + vin.substr(10) + "-------"; + } + StandardMetrics.ms_v_vin->SetValue(vin); + ESP_BUFFER_LOGD(TAG, response.data(), response.size()); + + vin.copy(m_vin, sizeof(m_vin) - 1); + m_vin[sizeof(m_vin) - 1] = '\0'; + ESP_LOGD(TAG, "ProcessVIN: Success: '%s'->'%s'", vin.c_str(), m_vin); + return IqVinStatus::Success; } else { - std::string vin; - if ( get_buff_string(response, 5, 17, vin)) { - if (vin.length() > 5 && vin[4] == '-') { - vin = vin.substr(0, 3) + vin.substr(10) + "-------"; - } - StandardMetrics.ms_v_vin->SetValue(vin); - ESP_BUFFER_LOGD(TAG, response.data(), response.size()); - - vin.copy(m_vin, sizeof(m_vin) - 1); - m_vin[sizeof(m_vin) - 1] = '\0'; - ESP_LOGD(TAG, "RequestVIN: Success: '%s'->'%s'", vin.c_str(), m_vin); - return POLLSINGLE_OK; - } - else { - ESP_LOGE(TAG, "RequestVIN.String: Bad Buffer"); - return POLLSINGLE_TIMEOUT; - } + ESP_LOGE(TAG, "ProcessVIN: Bad VIN Buffer"); + return IqVinStatus::BadBuffer; } } } +bool OvmsHyundaiIoniqEv::PollRequestVIN() +{ + if (!StdMetrics.ms_v_env_awake->AsBool()) { + ESP_LOGV(TAG, "PollRequestVIN: Not Awake Request not sent"); + return false; + } + auto poll_entry = std::shared_ptr( + new OvmsPoller::OnceOffPoll( + std::bind(&OvmsHyundaiIoniqEv::Incoming_Full, this, _1, _2, _3, _4, _5), + std::bind(&OvmsHyundaiIoniqEv::Incoming_Fail, this, _1, _2, _3, _4, _5), + 0x7e2, 0x7ea, + VEHICLE_POLL_TYPE_OBDIIVEHICLE, 2, + ISOTP_STD, 0, 3/*retries*/ )); + PollRequest(m_can1, "!xiq.vin", poll_entry); + return true; +} + +IqVinStatus OvmsHyundaiIoniqEv::RequestVIN() +{ + //ESP_LOGD(TAG, "RequestVIN: Sending Request"); + + if (!StdMetrics.ms_v_env_awake->AsBool()) { + ESP_LOGD(TAG, "RequestVIN: Not Awake Request not sent"); + return IqVinStatus::NotAwake; + } + + std::string response; + int res = PollSingleRequest( m_can1, + 0x7e2, 0x7ea, + VEHICLE_POLL_TYPE_OBDIIVEHICLE, 2, response, 1000); + switch (res) { + case POLLSINGLE_OK: + return ProcessVIN(response); + case POLLSINGLE_TIMEOUT: + ESP_LOGE(TAG, "RequestVIN: Request Timeout"); + return IqVinStatus::Timeout; + case POLLSINGLE_TXFAILURE: + ESP_LOGE(TAG, "RequestVIN: Request TX Failure"); + return IqVinStatus::TxFail; + default: + ESP_LOGE(TAG, "RequestVIN: UDC Error %d: %s", res, OvmsPoller::PollResultCodeName(res)); + } + return IqVinStatus::ProtocolErr; +} + /** * Get console ODO units * - * Currently from configuration */ metric_unit_t OvmsHyundaiIoniqEv::GetConsoleUnits() { - XARM("OvmsHyundaiIoniqEv::GetConsoleUnits"); - metric_unit_t res = MyConfig.GetParamValueBool("xkn", "consoleKilometers", true) ? Kilometers : Miles; - XDISARM; - return res; + // Always KM. + return Kilometers; } /** @@ -972,7 +1036,7 @@ metric_unit_t OvmsHyundaiIoniqEv::GetConsoleUnits() bool OvmsHyundaiIoniqEv::IsLHD() { XARM("OvmsHyundaiIoniqEv::IsLHD"); - bool res = MyConfig.GetParamValueBool("xkn", "leftDrive", true); + bool res = MyConfig.GetParamValueBool("xiq", "leftDrive", true); XDISARM; return res; } diff --git a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/hif_commands.cpp b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/hif_commands.cpp index f5c7a81ec..5d43791ba 100644 --- a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/hif_commands.cpp +++ b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/hif_commands.cpp @@ -171,12 +171,37 @@ void xiq_vin(int verbosity, OvmsWriter *writer, OvmsCommand *cmd, int argc, cons } OvmsHyundaiIoniqEv *hif = (OvmsHyundaiIoniqEv *) MyVehicleFactory.ActiveVehicle(); - if (hif->m_vin[0] == 0) { - hif->RequestVIN(); + writer->printf("Requesting VIN ... "); + switch (hif->RequestVIN()) { + case IqVinStatus::Success: + writer->puts("OK"); + break; + case IqVinStatus::BadBuffer: + writer->puts("Bad Buffer"); + return; + case IqVinStatus::TxFail: + writer->puts("Transmit Fail"); + return; + case IqVinStatus::Timeout: + writer->puts("Timeout"); + return; + case IqVinStatus::ProtocolErr: + writer->puts("Protocol Error"); + return; + case IqVinStatus::BadFormat: + writer->puts("Unrecognised"); + return; + case IqVinStatus::NotAwake: + writer->puts("Car Not Awake"); + return; + default: + writer->puts("Failed"); + return; + } } writer->printf("VIN\n"); - writer->printf("Vin: %s \n", hif->m_vin); + writer->printf("Vin: %s\n", hif->m_vin); if (hif->m_vin[0] == 0) { return; } diff --git a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/hif_web.cpp b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/hif_web.cpp index f60f95f7d..456530dd2 100644 --- a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/hif_web.cpp +++ b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/hif_web.cpp @@ -74,25 +74,41 @@ void OvmsHyundaiIoniqEv::WebCfgFeatures(PageEntry_t &p, PageContext_t &c) #ifdef XIQ_CAN_WRITE bool canwrite; #endif - bool consoleKilometers; bool leftDrive; + int charge_delay_ccs, charge_delay_type2; if (c.method == "POST") { // process form submission: #ifdef XIQ_CAN_WRITE canwrite = (c.getvar("canwrite") == "yes"); #endif - consoleKilometers = (c.getvar("consoleKilometers") == "yes"); leftDrive = (c.getvar("leftDrive") == "yes"); + charge_delay_ccs = atoi(c.getvar("delayccs").c_str()); + if (charge_delay_ccs < 0 || charge_delay_ccs > 60) + error = "CSS Delay Out of Range"; + charge_delay_type2 = atoi(c.getvar("delaytype2").c_str()); + if (charge_delay_type2 < 0 || charge_delay_type2 > 60) + { + if (error == "") + error = "Type2 Delay Out of Range"; + } if (error == "") { // store: #ifdef XIQ_CAN_WRITE MyConfig.SetParamValueBool("xiq", "canwrite", canwrite); #endif - MyConfig.SetParamValueBool("xiq", "consoleKilometers", consoleKilometers); MyConfig.SetParamValueBool("xiq", "leftDrive", leftDrive); + if (charge_delay_ccs == 0) + MyConfig.SetParamValue("xiq", "notify.charge.delay.ccs", ""); + else + MyConfig.SetParamValueInt("xiq", "notify.charge.delay.ccs", charge_delay_ccs); + if (charge_delay_type2 == 0) + MyConfig.SetParamValue("xiq", "notify.charge.delay.type2", ""); + else + MyConfig.SetParamValueInt("xiq", "notify.charge.delay.type2", charge_delay_type2); + c.head(200); c.alert("success", "

Hyundai Ioniq 5 / Kia EV6 feature configuration saved.

"); MyWebServer.OutputHome(p, c); @@ -111,9 +127,11 @@ void OvmsHyundaiIoniqEv::WebCfgFeatures(PageEntry_t &p, PageContext_t &c) #ifdef XIQ_CAN_WRITE canwrite = MyConfig.GetParamValueBool("xiq", "canwrite", false); #endif - consoleKilometers = MyConfig.GetParamValueBool("xiq", "consoleKilometers", true); leftDrive = MyConfig.GetParamValueBool("xiq", "leftDrive", true); + charge_delay_ccs = MyConfig.GetParamValueInt("xiq", "notify.charge.delay.ccs",0); + charge_delay_type2 = MyConfig.GetParamValueInt("xiq", "notify.charge.delay.type2",0); + c.head(200); } @@ -127,16 +145,16 @@ void OvmsHyundaiIoniqEv::WebCfgFeatures(PageEntry_t &p, PageContext_t &c) c.input_checkbox("Enable CAN writes", "canwrite", canwrite, "

Controls overall CAN write access, all control functions depend on this.

"); #endif - c.input_checkbox("Console odometer in Kilometers", "consoleKilometers", consoleKilometers, - "

Enable for cars with console odometer in Kilometers, disable for Miles

"); c.input_checkbox("Left hand drive", "leftDrive", leftDrive, "

Enable for left hand drive cars, disable for right hand drive.

"); c.fieldset_end(); - //c.fieldset_start("Functionality"); - //c.input_checkbox("Enable open charge port with key fob", "remote_charge_port", remote_charge_port, - // "

Enable using the Hold-button on the remote key fob to open up the charge port.

"); - //c.fieldset_end(); + c.fieldset_start("Charge Notify Delays"); + c.input_slider("CCS Delay", "delayccs", 1, "seconds", charge_delay_ccs>0, charge_delay_ccs?charge_delay_ccs:15, + 15, 1, 60, 1, "

Seconds after DC charging starts before notifying user

"); + c.input_slider("Type 2 Delay", "delaytype2", 1, "seconds", charge_delay_type2>0, charge_delay_type2?charge_delay_type2:10, + 10, 1, 60, 1, "

Seconds after AC charge starts before notifying user

"); + c.fieldset_end(); c.print("
"); c.input_button("default", "Save"); diff --git a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/vehicle_hyundai_ioniq5.cpp b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/vehicle_hyundai_ioniq5.cpp index b6f302993..43cc760a9 100644 --- a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/vehicle_hyundai_ioniq5.cpp +++ b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/vehicle_hyundai_ioniq5.cpp @@ -65,15 +65,21 @@ const char *OvmsHyundaiIoniqEv::TAG = "v-ioniq5"; +#ifdef bind +#undef bind +#endif +using namespace std::placeholders; + // Pollstate 0 - car is off // Pollstate 1 - car is on // Pollstate 2 - car is charging // Pollstate 3 - ping : car is off, not charging and something triggers a wake static const OvmsPoller::poll_pid_t vehicle_ioniq_polls[] = { + // 0 1 2 3 // Off On Chrg Ping - { 0x7b3, 0x7bb, VEHICLE_POLL_TYPE_READDATA, 0x0100, { 0, 1, 10, 30}, 0, ISOTP_STD }, // AirCon and Speed + { 0x7b3, 0x7bb, VEHICLE_POLL_TYPE_READDATA, 0x0100, { 0, 2, 10, 30}, 0, ISOTP_STD }, // AirCon and Speed { 0x7e2, 0x7ea, VEHICLE_POLL_TYPE_READDATA, 0xe004, { 0, 1, 4, 4}, 0, ISOTP_STD }, // VMCU - Drive status + Accellerator - { 0x7e4, 0x7ec, VEHICLE_POLL_TYPE_READDATA, 0x0101, { 0, 3, 4, 4}, 0, ISOTP_STD }, // BMC Diag page 01 - Inc Battery Pack Temp + RPM + Charging + { 0x7e4, 0x7ec, VEHICLE_POLL_TYPE_READDATA, 0x0101, { 0, 2, 4, 4}, 0, ISOTP_STD }, // BMC Diag page 01 - Inc Battery Pack Temp + RPM { 0x7e4, 0x7ec, VEHICLE_POLL_TYPE_READDATA, 0x0102, { 0, 59, 9, 0}, 0, ISOTP_STD }, // Battery 1 - BMC Diag page 02 { 0x7e4, 0x7ec, VEHICLE_POLL_TYPE_READDATA, 0x0103, { 0, 59, 9, 0}, 0, ISOTP_STD }, // Battery 2 - BMC Diag page 03 { 0x7e4, 0x7ec, VEHICLE_POLL_TYPE_READDATA, 0x0104, { 0, 59, 9, 0}, 0, ISOTP_STD }, // Battery 3 - BMC Diag page 04 @@ -101,8 +107,27 @@ static const OvmsPoller::poll_pid_t vehicle_ioniq_polls[] = { // TODO 0x7e5 OBC - On Board Charger? - // Check again while driving only - { 0x7b3, 0x7bb, VEHICLE_POLL_TYPE_READDATA, 0x0100, { 0, 1, 0, 0}, 0, ISOTP_STD }, // AirCon and Speed + POLL_LIST_END +}; + +// Pollstate 4 - Aux Charging car is off but Auxilary is charging +static const OvmsPoller::poll_pid_t vehicle_ioniq_polls_second[] = { + // 4 5 6 7 + // AuxCh + { 0x7e4, 0x7ec, VEHICLE_POLL_TYPE_READDATA, 0x0101, { 300, 0, 0, 0}, 0, ISOTP_STD }, // BMC Diag page 01 - Inc Battery Pack Temp + RPM + { 0x7e4, 0x7ec, VEHICLE_POLL_TYPE_READDATA, 0x0102, { 300, 0, 0, 0}, 0, ISOTP_STD }, // Battery 1 - BMC Diag page 02 + { 0x7e4, 0x7ec, VEHICLE_POLL_TYPE_READDATA, 0x0103, { 300, 0, 0, 0}, 0, ISOTP_STD }, // Battery 2 - BMC Diag page 03 + { 0x7e4, 0x7ec, VEHICLE_POLL_TYPE_READDATA, 0x0104, { 300, 0, 0, 0}, 0, ISOTP_STD }, // Battery 3 - BMC Diag page 04 + { 0x7e4, 0x7ec, VEHICLE_POLL_TYPE_READDATA, 0x0105, { 300, 0, 0, 0}, 0, ISOTP_STD }, // Battery 4 - BMC Diag page 05 (Other - Battery Pack Temp) + { 0x770, 0x778, VEHICLE_POLL_TYPE_READDATA, 0xbc03, { 150, 0, 0, 0}, 0, ISOTP_STD }, // IGMP Door status + IGN1 & IGN2 - Detects when car is turned on + { 0x770, 0x778, VEHICLE_POLL_TYPE_READDATA, 0xbc04, { 150, 0, 0, 0}, 0, ISOTP_STD }, // IGMP Door status + POLL_LIST_END +}; + +static const OvmsPoller::poll_pid_t vehicle_ioniq_driving_polls[] = { + // Check again while driving with ECU only + { 0x7b3, 0x7bb, VEHICLE_POLL_TYPE_READDATA, 0x0100, { 0, 1, 20, 20}, 0, ISOTP_STD }, // For Speed + { 0x7e4, 0x7ec, VEHICLE_POLL_TYPE_READDATA, 0x0101, { 0, 1, 20, 20}, 0, ISOTP_STD }, // For RPM POLL_LIST_END }; @@ -596,10 +621,6 @@ OvmsHyundaiIoniqEv::OvmsHyundaiIoniqEv() if (StdMetrics.ms_v_bat_pack_tavg->IsDefined()) { StdMetrics.ms_v_bat_temp->SetValue(StdMetrics.ms_v_bat_pack_tavg->AsFloat()); } -#ifdef bind -#undef bind -#endif - using std::placeholders::_1; MyMetrics.RegisterListener(TAG, MS_V_BAT_PACK_TAVG, std::bind(&OvmsHyundaiIoniqEv::UpdatedAverageTemp, this, _1)); // init commands: @@ -634,14 +655,13 @@ OvmsHyundaiIoniqEv::OvmsHyundaiIoniqEv() kia_enable_write = false; #endif - - using std::placeholders::_2; MyEvents.RegisterEvent(TAG, "app.connected", std::bind(&OvmsHyundaiIoniqEv::EventListener, this, _1, _2)); MyConfig.RegisterParam("xiq", "Ioniq 5/EV6 specific settings.", true, true); ConfigChanged(NULL); - m_ecu_lockout = 0; + m_ecu_lockout = -1; + m_ecu_status_on = false; #ifdef CONFIG_OVMS_COMP_WEBSERVER WebInit(); @@ -655,6 +675,13 @@ OvmsHyundaiIoniqEv::OvmsHyundaiIoniqEv() PollState_Off(); kia_secs_with_no_client = 0; PollSetPidList(m_can1, vehicle_ioniq_polls); + + auto secondary_series = std::shared_ptr( + new OvmsPoller::StandardVehiclePollSeries(nullptr, GetPollerSignal(), 4)); + secondary_series->PollSetPidList(1, vehicle_ioniq_polls_second); + + PollRequest(m_can1, "!v.secondary", secondary_series); + // Initially throttling to 4. PollSetThrottling(4); @@ -664,10 +691,45 @@ OvmsHyundaiIoniqEv::OvmsHyundaiIoniqEv() XDISARM; } +static const char *ECU_POLL = "!v.xiq.ecu"; + void OvmsHyundaiIoniqEv::ECUStatusChange(bool run) { + if (m_ecu_status_on == run) { + return; + } + if (!run) + m_ecu_lockout = -1; + m_ecu_status_on = run; // When ECU is running - be more agressive. - PollSetThrottling(run ? 10 : 4); + int newThrottle = run ? 10 : 5; + bool subtick = run && MyConfig.GetParamValueBool("xiq", "poll_subtick", true); + ESP_LOGD(TAG, "run=%d throttle=%d subtick=%d", run, newThrottle, subtick); + PollSetThrottling(newThrottle); + + if (!run) { + RemovePollRequest(m_can1, ECU_POLL); + } else { + // Add an extra set of polling. + auto poll_series = std::shared_ptr( + new OvmsPoller::StandardPacketPollSeries(nullptr, 3/*repeats*/, + std::bind(&OvmsHyundaiIoniqEv::Incoming_Full, this, _1, _2, _3, _4, _5), + nullptr)); + poll_series->PollSetPidList(1, vehicle_ioniq_driving_polls); + + PollRequest(m_can1, ECU_POLL, poll_series); + } + if (subtick) { + PollSetTimeBetweenSuccess(80); // 80ms Gap between each successfull poll. + PollSetResponseSeparationTime(5); // Faster bursts of messages. + PollSetTicker(300, 3); + } + else { + // Defaults. + PollSetTimeBetweenSuccess(0); + PollSetResponseSeparationTime(25); + PollSetTicker(1000, 1); + } } /** @@ -686,6 +748,21 @@ OvmsHyundaiIoniqEv::~OvmsHyundaiIoniqEv() XDISARM; } +int OvmsHyundaiIoniqEv::GetNotifyChargeStateDelay(const char *state) +{ + if (!StdMetrics.ms_v_charge_inprogress->AsBool()) + return KiaVehicle::GetNotifyChargeStateDelay(state); + + std::string charge_type = StdMetrics.ms_v_charge_type->AsString(); + if (charge_type == "ccs") { + // CCS charging needs some time to ramp up the current/power level: + return MyConfig.GetParamValueInt("xiq", "notify.charge.delay.ccs", 15); + } + else { + return MyConfig.GetParamValueInt("xiq", "notify.charge.delay.type2", 10); + } +} + /** * ConfigChanged: reload single/all configuration variables */ @@ -695,7 +772,7 @@ void OvmsHyundaiIoniqEv::ConfigChanged(OvmsConfigParam *param) ESP_LOGD(TAG, "Hyundai Ioniq 5 EV reload configuration"); // Instances: - // xkn + // xiq // cap_act_kwh Battery capacity in kwH // suffsoc Sufficient SOC [%] (Default: 0=disabled) // suffrange Sufficient range [km] (Default: 0=disabled) @@ -986,9 +1063,9 @@ void OvmsHyundaiIoniqEv::Ticker1(uint32_t ticker) #endif if (hif_aux_battery_mon.state() == OvmsBatteryState::Charging) { // Maintain ping until stop charging - if (IsPollState_Off() || IsPollState_Ping() ) { - ESP_LOGD(TAG, "PollState->Ping for 30 (Charging)"); - PollState_Ping(30); + if (IsPollState_Off() || IsPollState_PingAux() ) { + ESP_LOGD(TAG, "PollState->PingAux for 30 (Charging)"); + PollState_PingAux(30); } } @@ -1132,12 +1209,19 @@ void OvmsHyundaiIoniqEv::Ticker1(uint32_t ticker) // Let the busy time of starting the car happen before we // ramp up the speed of the polls to support obd2ecu. // Otherwise we can see the car reporting system failures. - if (m_ecu_lockout > 0 && (--m_ecu_lockout == 0)) { - if (StandardMetrics.ms_v_env_on->AsBool() - && StandardMetrics.ms_m_obd2ecu_on->AsBool() - && (StdMetrics.ms_v_env_gear->AsInt() > 0)) { - ECUStatusChange(true); - } + + if (StandardMetrics.ms_v_env_on->AsBool() + && StandardMetrics.ms_m_obd2ecu_on->AsBool() + && (StdMetrics.ms_v_env_gear->AsInt() > 0)) { + + if (m_ecu_lockout > 0) + --m_ecu_lockout; + else if (m_ecu_lockout < 0) + m_ecu_lockout = 10; + + ECUStatusChange(m_ecu_lockout==0); + } else { + ECUStatusChange(false); } // Send tester present @@ -1154,7 +1238,7 @@ void OvmsHyundaiIoniqEv::Ticker10(uint32_t ticker) { if (m_vin[0] == 0 && IsPollState_Running() && (m_vin_retry < 10)) { ESP_LOGI(TAG, "Checking for VIN."); - if (RequestVIN() != -3) { + if (PollRequestVIN()) { ++m_vin_retry; } } diff --git a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/vehicle_hyundai_ioniq5.h b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/vehicle_hyundai_ioniq5.h index d79d54d40..822d10ece 100644 --- a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/vehicle_hyundai_ioniq5.h +++ b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniq5/src/vehicle_hyundai_ioniq5.h @@ -61,6 +61,15 @@ enum class IqShiftStatus { Neutral, Drive }; +enum class IqVinStatus { + Success, + BadBuffer, + TxFail, + Timeout, + ProtocolErr, + BadFormat, + NotAwake +}; void xiq_trip_since_parked(int verbosity, OvmsWriter *writer, OvmsCommand *cmd, int argc, const char *const *argv); @@ -216,12 +225,15 @@ class OvmsHyundaiIoniqEv : public KiaVehicle protected: void HandleCharging(); void HandleChargeStop(); - void IncomingVMCU_Full(canbus *bus, uint16_t type, uint16_t pid, const std::string &data); - void IncomingBMC_Full(canbus *bus, uint16_t type, uint16_t pid, const std::string &data); - void IncomingBCM_Full(canbus *bus, uint16_t type, uint16_t pid, const std::string &data); - void IncomingIGMP_Full(canbus *bus, uint16_t type, uint16_t pid, const std::string &data); - void IncomingOther_Full(canbus *bus, uint16_t type, uint16_t pid, const std::string &data); - void IncomingCM_Full(canbus *bus, uint16_t type, uint16_t pid, const std::string &data); + void Incoming_Full(uint16_t type, uint32_t module_sent, uint32_t module_rec, uint16_t pid, const std::string &data); + void Incoming_Fail(uint16_t type, uint32_t module_sent, uint32_t module_rec, uint16_t pid, int errorcode); + + void IncomingVMCU_Full(uint16_t type, uint16_t pid, const std::string &data); + void IncomingBMC_Full(uint16_t type, uint16_t pid, const std::string &data); + void IncomingBCM_Full(uint16_t type, uint16_t pid, const std::string &data); + void IncomingIGMP_Full(uint16_t type, uint16_t pid, const std::string &data); + void IncomingOther_Full(uint16_t type, uint16_t pid, const std::string &data); + void IncomingCM_Full(uint16_t type, uint16_t pid, const std::string &data); void RequestNotify(unsigned int which); void DoNotify(); void vehicle_ioniq5_car_on(bool isOn); @@ -245,6 +257,8 @@ class OvmsHyundaiIoniqEv : public KiaVehicle void SendTesterPresentMessages(); void StopTesterPresentMessages(); + int GetNotifyChargeStateDelay(const char *state) override; + // Inline functions to handle the different I5 Poll states. inline int PollGetState() { @@ -292,6 +306,19 @@ class OvmsHyundaiIoniqEv : public KiaVehicle } PollSetState(3); } + inline bool IsPollState_PingAux() + { + return m_poll_state == 4; + } + inline void PollState_PingAux(uint32_t ticks) + { + if (hif_keep_awake < ticks) { + hif_keep_awake = ticks; + } + if (!IsPollState_PingAux()) { + PollSetState(4); + } + } inline void Poll_CapAwake( uint32_t ticks) { if (hif_keep_awake > ticks) { @@ -319,6 +346,8 @@ class OvmsHyundaiIoniqEv : public KiaVehicle } void CheckResetDoorCheck(); + int m_ecu_lockout; + bool m_ecu_status_on; void NotifiedOBD2ECUStart() override { if (m_ecu_lockout == 0) @@ -330,12 +359,13 @@ class OvmsHyundaiIoniqEv : public KiaVehicle } void NotifiedVehicleOn() override { - m_ecu_lockout = 20; + if (m_ecu_lockout < 0) + m_ecu_lockout = 20; } void NotifiedVehicleOff() override { - m_ecu_lockout = 0; ECUStatusChange(false); + m_ecu_lockout = -1; } void NotifiedVehicleGear( int gear) override { @@ -345,10 +375,18 @@ class OvmsHyundaiIoniqEv : public KiaVehicle ECUStatusChange(StandardMetrics.ms_v_env_on->AsBool() && StandardMetrics.ms_m_obd2ecu_on->AsBool()); } - int m_ecu_lockout; void ECUStatusChange(bool run); public: - int RequestVIN(); + // Non-Blocking VIN Request. + bool PollRequestVIN(); + + + // Blocking VIN Request. + IqVinStatus RequestVIN(); + + // Process VIN REsult. + IqVinStatus ProcessVIN(const std::string &response); + bool DriverIndicator(bool on) { if (IsLHD()) { diff --git a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniqvfl/src/vehicle_hyundai_ioniqvfl.cpp b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniqvfl/src/vehicle_hyundai_ioniqvfl.cpp index 5f9b1a740..18c7f01e5 100644 --- a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniqvfl/src/vehicle_hyundai_ioniqvfl.cpp +++ b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniqvfl/src/vehicle_hyundai_ioniqvfl.cpp @@ -418,7 +418,7 @@ void OvmsVehicleHyundaiVFL::IncomingPollReply(const OvmsPoller::poll_job_t &job, * PollerStateTicker: framework callback: check for state changes * This is called by VehicleTicker1() just before the next PollerSend(). */ -void OvmsVehicleHyundaiVFL::PollerStateTicker() +void OvmsVehicleHyundaiVFL::PollerStateTicker(canbus *bus) { bool car_online = (m_can1->GetErrorState() < CAN_errorstate_passive && !m_xhi_charge_state->IsStale()); int charge_state = m_xhi_charge_state->AsInt(); diff --git a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniqvfl/src/vehicle_hyundai_ioniqvfl.h b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniqvfl/src/vehicle_hyundai_ioniqvfl.h index 9a1d273ca..9489d7983 100644 --- a/vehicle/OVMS.V3/components/vehicle_hyundai_ioniqvfl/src/vehicle_hyundai_ioniqvfl.h +++ b/vehicle/OVMS.V3/components/vehicle_hyundai_ioniqvfl/src/vehicle_hyundai_ioniqvfl.h @@ -56,7 +56,7 @@ class OvmsVehicleHyundaiVFL : public OvmsVehicle #endif protected: - void PollerStateTicker(); + void PollerStateTicker(canbus *bus) override; void IncomingPollReply(const OvmsPoller::poll_job_t &job, uint8_t* data, uint8_t length) override; // Trip length & SOC/energy consumption: diff --git a/vehicle/OVMS.V3/components/vehicle_maxus_edeliver3/src/vehicle_med3.cpp b/vehicle/OVMS.V3/components/vehicle_maxus_edeliver3/src/vehicle_med3.cpp index b6186c03a..815c9ba01 100644 --- a/vehicle/OVMS.V3/components/vehicle_maxus_edeliver3/src/vehicle_med3.cpp +++ b/vehicle/OVMS.V3/components/vehicle_maxus_edeliver3/src/vehicle_med3.cpp @@ -423,7 +423,7 @@ void OvmsVehicleMaxed3::Ticker1(uint32_t ticker) // PollerStateTicker: framework callback: check for state changes // This is called by VehicleTicker1() just before the next PollerSend(). -void OvmsVehicleMaxed3::PollerStateTicker() +void OvmsVehicleMaxed3::PollerStateTicker(canbus *bus) { bool charging12v = StdMetrics.ms_v_env_charging12v->AsBool(); StdMetrics.ms_v_env_charging12v->SetValue(StdMetrics.ms_v_bat_12v_voltage->AsFloat() >= 12.9); diff --git a/vehicle/OVMS.V3/components/vehicle_maxus_edeliver3/src/vehicle_med3.h b/vehicle/OVMS.V3/components/vehicle_maxus_edeliver3/src/vehicle_med3.h index e9d36089a..89ec86773 100644 --- a/vehicle/OVMS.V3/components/vehicle_maxus_edeliver3/src/vehicle_med3.h +++ b/vehicle/OVMS.V3/components/vehicle_maxus_edeliver3/src/vehicle_med3.h @@ -72,7 +72,7 @@ class OvmsVehicleMaxed3 : public OvmsVehicle protected: void ConfigChanged(OvmsConfigParam* param) override; - void PollerStateTicker(); + void PollerStateTicker(canbus *bus) override; void Ticker1(uint32_t ticker) override; void IncomingPollReply(const OvmsPoller::poll_job_t &job, uint8_t* data, uint8_t length) override; void processEnergy(); diff --git a/vehicle/OVMS.V3/components/vehicle_maxus_euniq56/src/vehicle_me56.cpp b/vehicle/OVMS.V3/components/vehicle_maxus_euniq56/src/vehicle_me56.cpp index 7e83853ea..4ccc0d119 100644 --- a/vehicle/OVMS.V3/components/vehicle_maxus_euniq56/src/vehicle_me56.cpp +++ b/vehicle/OVMS.V3/components/vehicle_maxus_euniq56/src/vehicle_me56.cpp @@ -423,7 +423,7 @@ void OvmsVehicleMaxe56::Ticker1(uint32_t ticker) // PollerStateTicker: framework callback: check for state changes // This is called by VehicleTicker1() just before the next PollerSend(). -void OvmsVehicleMaxe56::PollerStateTicker() +void OvmsVehicleMaxe56::PollerStateTicker(canbus *bus) { bool charging12v = StdMetrics.ms_v_env_charging12v->AsBool(); StdMetrics.ms_v_env_charging12v->SetValue(StdMetrics.ms_v_bat_12v_voltage->AsFloat() >= 12.9); diff --git a/vehicle/OVMS.V3/components/vehicle_maxus_euniq56/src/vehicle_me56.h b/vehicle/OVMS.V3/components/vehicle_maxus_euniq56/src/vehicle_me56.h index ea3add3f2..6a0cc9c67 100644 --- a/vehicle/OVMS.V3/components/vehicle_maxus_euniq56/src/vehicle_me56.h +++ b/vehicle/OVMS.V3/components/vehicle_maxus_euniq56/src/vehicle_me56.h @@ -72,7 +72,7 @@ class OvmsVehicleMaxe56 : public OvmsVehicle protected: void ConfigChanged(OvmsConfigParam* param) override; - void PollerStateTicker(); + void PollerStateTicker(canbus *bus); void Ticker1(uint32_t ticker) override; void IncomingPollReply(const OvmsPoller::poll_job_t &job, uint8_t* data, uint8_t length) override; void processEnergy(); diff --git a/vehicle/OVMS.V3/components/vehicle_mgev/src/mg_can_handler.cpp b/vehicle/OVMS.V3/components/vehicle_mgev/src/mg_can_handler.cpp index 51156deb4..7f48d6089 100644 --- a/vehicle/OVMS.V3/components/vehicle_mgev/src/mg_can_handler.cpp +++ b/vehicle/OVMS.V3/components/vehicle_mgev/src/mg_can_handler.cpp @@ -35,37 +35,45 @@ static const char *TAG = "v-mgev"; void OvmsVehicleMgEv::IncomingFrameCan1(CAN_frame_t* p_frame) { + /* if (m_poll_bus_default != m_can1) { return; } + */ IncomingPollFrame(p_frame); } void OvmsVehicleMgEv::IncomingFrameCan2(CAN_frame_t* p_frame) { + /* if (m_poll_bus_default != m_can2) { return; } + */ IncomingPollFrame(p_frame); } void OvmsVehicleMgEv::IncomingFrameCan3(CAN_frame_t* p_frame) { + /* if (m_poll_bus_default != m_can3) { return; } + */ IncomingPollFrame(p_frame); } void OvmsVehicleMgEv::IncomingFrameCan4(CAN_frame_t* p_frame) { + /* if (m_poll_bus_default != m_can4) { return; } + */ IncomingPollFrame(p_frame); } diff --git a/vehicle/OVMS.V3/components/vehicle_mgev/src/vehicle_mgev.cpp b/vehicle/OVMS.V3/components/vehicle_mgev/src/vehicle_mgev.cpp index dbae928bb..3cc97c309 100644 --- a/vehicle/OVMS.V3/components/vehicle_mgev/src/vehicle_mgev.cpp +++ b/vehicle/OVMS.V3/components/vehicle_mgev/src/vehicle_mgev.cpp @@ -554,7 +554,7 @@ void OvmsVehicleMgEv::ConfigurePollInterface(int bus) { // Already configured for that interface ESP_LOGI(TAG, "Already configured for interface, not re-configuring"); - if (m_pollData && !HasPollList()) + if (m_pollData && !HasPollList(newBus)) { PollSetPidList(newBus, m_pollData); } diff --git a/vehicle/OVMS.V3/components/vehicle_nissanleaf/src/vehicle_nissanleaf.cpp b/vehicle/OVMS.V3/components/vehicle_nissanleaf/src/vehicle_nissanleaf.cpp index e7fa4ba2e..94da7473a 100644 --- a/vehicle/OVMS.V3/components/vehicle_nissanleaf/src/vehicle_nissanleaf.cpp +++ b/vehicle/OVMS.V3/components/vehicle_nissanleaf/src/vehicle_nissanleaf.cpp @@ -68,9 +68,11 @@ enum poll_states static const OvmsPoller::poll_pid_t obdii_polls[] = { + // BUS 2 { CHARGER_TXID, CHARGER_RXID, VEHICLE_POLL_TYPE_OBDIIGROUP, VIN_PID, { 0, 900, 0, 0 }, 2, ISOTP_STD }, // VIN [19] { CHARGER_TXID, CHARGER_RXID, VEHICLE_POLL_TYPE_OBDIIEXTENDED, QC_COUNT_PID, { 0, 900, 0, 0 }, 2, ISOTP_STD }, // QC [2] { CHARGER_TXID, CHARGER_RXID, VEHICLE_POLL_TYPE_OBDIIEXTENDED, L1L2_COUNT_PID, { 0, 900, 0, 0 }, 2, ISOTP_STD }, // L0/L1/L2 [2] + // BUS 1 { BMS_TXID, BMS_RXID, VEHICLE_POLL_TYPE_OBDIIGROUP, 0x01, { 0, 60, 0, 60 }, 1, ISOTP_STD }, // bat [39/41] { BMS_TXID, BMS_RXID, VEHICLE_POLL_TYPE_OBDIIGROUP, 0x02, { 0, 60, 0, 60 }, 1, ISOTP_STD }, // battery voltages [196] { BMS_TXID, BMS_RXID, VEHICLE_POLL_TYPE_OBDIIGROUP, 0x06, { 0, 60, 0, 60 }, 1, ISOTP_STD }, // battery shunts [96] diff --git a/vehicle/OVMS.V3/components/vehicle_smarted/src/ed_can_poll.cpp b/vehicle/OVMS.V3/components/vehicle_smarted/src/ed_can_poll.cpp index 60a5adaef..ab9746569 100644 --- a/vehicle/OVMS.V3/components/vehicle_smarted/src/ed_can_poll.cpp +++ b/vehicle/OVMS.V3/components/vehicle_smarted/src/ed_can_poll.cpp @@ -249,7 +249,6 @@ void OvmsVehicleSmartED::ObdInitPoll() { } void OvmsVehicleSmartED::ObdModifyPoll() { - OvmsRecMutexLock lock(&m_poll_mutex); PollSetPidList(m_can1, NULL); PollSetState(0); diff --git a/vehicle/OVMS.V3/components/vehicle_smarted/src/vehicle_smarted.cpp b/vehicle/OVMS.V3/components/vehicle_smarted/src/vehicle_smarted.cpp index 9c3817b36..fae9bc48b 100644 --- a/vehicle/OVMS.V3/components/vehicle_smarted/src/vehicle_smarted.cpp +++ b/vehicle/OVMS.V3/components/vehicle_smarted/src/vehicle_smarted.cpp @@ -101,6 +101,8 @@ OvmsVehicleSmartED::OvmsVehicleSmartED() : smarted_obd_rxwait(1,1) { m_charging_timer = 0; m_last_pid = 0; + m_reboot_ticker = 0; + // init commands: cmd_xse = MyCommandApp.RegisterCommand("xse","SmartED 451 Gen.3"); cmd_xse->RegisterCommand("recu","Set recu..", xse_recu, "",1,1); diff --git a/vehicle/OVMS.V3/components/vehicle_voltampera/src/vehicle_voltampera.cpp b/vehicle/OVMS.V3/components/vehicle_voltampera/src/vehicle_voltampera.cpp index eb3c8402d..1262e318d 100644 --- a/vehicle/OVMS.V3/components/vehicle_voltampera/src/vehicle_voltampera.cpp +++ b/vehicle/OVMS.V3/components/vehicle_voltampera/src/vehicle_voltampera.cpp @@ -783,7 +783,7 @@ void OvmsVehicleVoltAmpera::Ticker10(uint32_t ticker) } } -void OvmsVehicleVoltAmpera::PollRunFinished() +void OvmsVehicleVoltAmpera::PollRunFinished(canbus* bus) { if(m_poll_state == 2) { diff --git a/vehicle/OVMS.V3/components/vehicle_voltampera/src/vehicle_voltampera.h b/vehicle/OVMS.V3/components/vehicle_voltampera/src/vehicle_voltampera.h index 2cfc255cb..0fe161864 100644 --- a/vehicle/OVMS.V3/components/vehicle_voltampera/src/vehicle_voltampera.h +++ b/vehicle/OVMS.V3/components/vehicle_voltampera/src/vehicle_voltampera.h @@ -111,7 +111,7 @@ class OvmsVehicleVoltAmpera : public OvmsVehicle void NotifyFuel(); void NotifyMetrics(); - void PollRunFinished() override; + void PollRunFinished(canbus* bus) override; protected: char m_vin[18]; char m_type[6]; diff --git a/vehicle/OVMS.V3/components/vehicle_vweup/src/vehicle_vweup.h b/vehicle/OVMS.V3/components/vehicle_vweup/src/vehicle_vweup.h index ee2366033..579026fb0 100644 --- a/vehicle/OVMS.V3/components/vehicle_vweup/src/vehicle_vweup.h +++ b/vehicle/OVMS.V3/components/vehicle_vweup/src/vehicle_vweup.h @@ -383,7 +383,7 @@ class OvmsVehicleVWeUp : public OvmsVehicle const char *statename[4] = { "OFF", "AWAKE", "CHARGING", "ON" }; return statename[state]; } - void PollerStateTicker(); + void PollerStateTicker(canbus *bus) override; void IncomingPollReply(const OvmsPoller::poll_job_t &job, uint8_t* data, uint8_t length) override; diff --git a/vehicle/OVMS.V3/components/vehicle_vweup/src/vweup_obd.cpp b/vehicle/OVMS.V3/components/vehicle_vweup/src/vweup_obd.cpp index b43e40ad0..ad76b7cf1 100644 --- a/vehicle/OVMS.V3/components/vehicle_vweup/src/vweup_obd.cpp +++ b/vehicle/OVMS.V3/components/vehicle_vweup/src/vweup_obd.cpp @@ -225,7 +225,6 @@ void OvmsVehicleVWeUp::OBDInit() // Init/reconfigure poller // - OvmsRecMutexLock lock(&m_poll_mutex); obd_state_t previous_state = m_obd_state; m_obd_state = OBDS_Config; @@ -368,13 +367,11 @@ void OvmsVehicleVWeUp::OBDInit() void OvmsVehicleVWeUp::OBDDeInit() { ESP_LOGI(TAG, "Stopping connection: OBDII"); - OvmsRecMutexLock lock(&m_poll_mutex); m_obd_state = OBDS_DeInit; PollSetPidList(m_can1, NULL); m_poll_vector.clear(); } - /** * OBDSetState: set the OBD state, log the change */ @@ -383,14 +380,12 @@ bool OvmsVehicleVWeUp::OBDSetState(obd_state_t state) if (m_obd_state == OBDS_Run && state == OBDS_Pause) { ESP_LOGW(TAG, "OBDSetState: %s -> %s", GetOBDStateName(m_obd_state), GetOBDStateName(state)); - OvmsRecMutexLock lock(&m_poll_mutex); PollSetPidList(m_can1, NULL); m_obd_state = OBDS_Pause; } else if (m_obd_state == OBDS_Pause && state == OBDS_Run) { ESP_LOGI(TAG, "OBDSetState: %s -> %s", GetOBDStateName(m_obd_state), GetOBDStateName(state)); - OvmsRecMutexLock lock(&m_poll_mutex); PollSetPidList(m_can1, m_poll_vector.data()); m_obd_state = OBDS_Run; } @@ -413,7 +408,7 @@ void OvmsVehicleVWeUp::PollSetState(uint8_t state) * PollerStateTicker: check for state changes * This is called by VehicleTicker1() just before the next PollerSend(). */ -void OvmsVehicleVWeUp::PollerStateTicker() +void OvmsVehicleVWeUp::PollerStateTicker(canbus *bus) { // T26 state management has precedence if available: if (HasT26() || m_obd_state != OBDS_Run) diff --git a/vehicle/OVMS.V3/main/Kconfig b/vehicle/OVMS.V3/main/Kconfig index b3143a721..ec664da35 100644 --- a/vehicle/OVMS.V3/main/Kconfig +++ b/vehicle/OVMS.V3/main/Kconfig @@ -340,6 +340,7 @@ config OVMS_VEHICLE_OBDII bool "Include support for OBDII vehicles" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for OBDII vehicles. @@ -382,6 +383,7 @@ config OVMS_VEHICLE_NISSANLEAF bool "Include support for Nissan Leaf vehicles" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Nissan Leaf vehicles. @@ -390,6 +392,7 @@ config OVMS_VEHICLE_RENAULTTWIZY default y depends on OVMS depends on OVMS_COMP_CANOPEN + depends on OVMS_COMP_POLLER help Enable to include support for Renault Twizy vehicles. @@ -398,6 +401,7 @@ config OVMS_VEHICLE_RENAULTZOE default y depends on OVMS depends on OVMS_COMP_CANOPEN + depends on OVMS_COMP_POLLER help Enable to include support for Renault Zoe vehicles. @@ -406,6 +410,7 @@ config OVMS_VEHICLE_RENAULTZOE_PH2_OBD default y depends on OVMS depends on OVMS_COMP_CANOPEN + depends on OVMS_COMP_POLLER help Enable to include support for Renault Zoe Phase 2 vehicles (OBD port version). @@ -413,6 +418,7 @@ config OVMS_VEHICLE_MAXED3 bool "Include support for Maxus eDeliver3 vehicle" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Maxus eDeliver3 vehicle. @@ -420,6 +426,7 @@ config OVMS_VEHICLE_MAXE56 bool "Include support for Maxus Euniq 5 6-seats vehicle" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Maxus Euniq 5 6-seats vehicle. @@ -427,6 +434,7 @@ config OVMS_VEHICLE_KIASOULEV bool "Include support for Kia Soul EV vehicles" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Kia Soul EV vehicles. @@ -434,6 +442,7 @@ config OVMS_VEHICLE_BOLTEV bool "Include support for Bolt EV/Ampera-e vehicles" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Bolt EV/Ampera-e vehicles" @@ -441,6 +450,7 @@ config OVMS_VEHICLE_VOLTAMPERA bool "Include support for Volt/Ampera vehicles" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Volt/Ampera vehicles. @@ -448,6 +458,7 @@ config OVMS_VEHICLE_THINKCITY bool "Include support for Think City vehicles" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Think City vehicles. @@ -455,6 +466,7 @@ config OVMS_VEHICLE_SMARTED bool "Include support for Smart ED vehicle" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Smart ED vehicle. @@ -462,6 +474,7 @@ config OVMS_VEHICLE_SMARTEQ bool "Include support for Smart ED/EQ Gen.4 vehicle" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Smart ED/EQ Gen.4 vehicle. @@ -469,6 +482,7 @@ config OVMS_VEHICLE_MITSUBISHI bool "Include support for Mitsubishi iMiev vehicle" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Mitsubishi iMiev vehicle. @@ -498,6 +512,7 @@ config OVMS_VEHICLE_VWEUP bool "Include support for VW e-up! vehicle" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for vehicles VW e-up! (all model years), Seat Mii electric, and Skoda Citigo-e iV. @@ -512,6 +527,7 @@ config OVMS_VEHICLE_CADILLAC_C2_CTS bool "Include support for Cadillac 2nd gen CTS vehicle" default n depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Cadillac 2nd gen CTS vehicle. @@ -519,6 +535,7 @@ config OVMS_VEHICLE_CHEVROLET_C6_CORVETTE bool "Include support for Chevrolet C6 Corvette vehicle" default n depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Chevrolet C6 Corvette vehicle. @@ -526,6 +543,7 @@ config OVMS_VEHICLE_MG_EV bool "Include support for MG ZS EV vehicles" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for MG ZS EV vehicles. @@ -533,6 +551,7 @@ config OVMS_VEHICLE_BMWI3 bool "Include support for BMW i3/i3s" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for BMW i3 or i3s vehicles @@ -540,6 +559,7 @@ config OVMS_VEHICLE_MINISE bool "Include support for Mini Cooper SE" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Mini Cooper SE vehicles @@ -547,6 +567,7 @@ config OVMS_VEHICLE_HYUNDAI_IONIQVFL bool "Include support for Hyundai Ioniq Electric 28kWh (vFL)" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Hyundai Ioniq Electric 28kWh (vFL). @@ -554,6 +575,7 @@ config OVMS_VEHICLE_HYUNDAI_IONIQ5 bool "Include support for Hyundai Ioniq 5" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Hyundai Ioniq 5. @@ -561,6 +583,7 @@ config OVMS_VEHICLE_JAGUARIPACE bool "Include support for Jaguar Ipace" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for Jaguar Ipace. @@ -574,26 +597,10 @@ config OVMS_VEHICLE_BYD_ATTO3 bool "Include support for BYD Atto 3" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable support for BYD Atto 3 -config OVMS_VEHICLE_RXTASK_STACK - int "Stack size for vehicle RX task" - default 6144 - depends on OVMS - help - Stack size for the vehicle CAN RX task ("Vrx Task"). - The RX task triggers events and metrics updates so needs to be - able to process the attached event/metrics listeners. - Standard stack usage of this task is currently around 1400 bytes. - -config OVMS_VEHICLE_CAN_RX_QUEUE_SIZE - int "Vehicle CAN queue size" - default 40 - depends on OVMS - help - The size of the CAN bus RX queue (at the vehicle component). - endmenu # Vehicle Support @@ -770,6 +777,7 @@ config OVMS_COMP_OBD2ECU bool "Include support for OBDII ECU" default y depends on OVMS + depends on OVMS_COMP_POLLER help Enable to include support for OBDII ECU @@ -831,6 +839,32 @@ config OVMS_COMP_CANOPEN_WRK_STACK updates so can run with a smaller stack than the RX task. Standard stack usage for the Twizy is currently around 1000 bytes. +menuconfig OVMS_COMP_POLLER + bool "Include ISOTP Poller framework" + default y + depends on OVMS + help + The ISOTP Poller framework provides a client API for ISOTP/VWTP + protocol address polling. + It provides some simple shell commands for status. + +config OVMS_VEHICLE_RXTASK_STACK + int "Stack size for ISOTP Poller RX task" + default 6144 + depends on OVMS_COMP_POLLER + help + Stack size for the ISOTP Poller CAN RX task ("Vrx Task"). + The RX task triggers events and metrics updates so needs to be + able to process the attached event/metrics listeners. + Standard stack usage of this task is currently around 1400 bytes. + +config OVMS_VEHICLE_CAN_RX_QUEUE_SIZE + int "Poller CAN queue size" + default 40 + depends on OVMS_COMP_POLLER + help + The size of the CAN bus RX queue (at the ISOTP Poller component). + config OVMS_COMP_PLUGINS bool "Include support for PLUGINS" default y diff --git a/vehicle/OVMS.V3/main/ovms_housekeeping.cpp b/vehicle/OVMS.V3/main/ovms_housekeeping.cpp index df6111fb9..a42af1d63 100644 --- a/vehicle/OVMS.V3/main/ovms_housekeeping.cpp +++ b/vehicle/OVMS.V3/main/ovms_housekeeping.cpp @@ -111,14 +111,26 @@ void HousekeepingTicker1( TimerHandle_t timer ) MyEvents.SignalEvent("ticker.1", NULL); tick++; - if ((tick % 10)==0) MyEvents.SignalEvent("ticker.10", NULL); - if ((tick % 60)==0) MyEvents.SignalEvent("ticker.60", NULL); - if ((tick % 300)==0) MyEvents.SignalEvent("ticker.300", NULL); - if ((tick % 600)==0) MyEvents.SignalEvent("ticker.600", NULL); - if ((tick % 3600)==0) + if ((tick % 10)==0) { - tick = 0; - MyEvents.SignalEvent("ticker.3600", NULL); + MyEvents.SignalEvent("ticker.10", NULL); + if ((tick % 60)==0) + { + MyEvents.SignalEvent("ticker.60", NULL); + if ((tick % 300)==0) + { + MyEvents.SignalEvent("ticker.300", NULL); + if ((tick % 600)==0) + { + MyEvents.SignalEvent("ticker.600", NULL); + if ((tick % 3600)==0) + { + tick = 0; + MyEvents.SignalEvent("ticker.3600", NULL); + } + } + } + } } time_t rawtime; @@ -216,6 +228,11 @@ void Housekeeping::Init(std::string event, void* data) MyPeripherals->m_cellular_modem->AutoInit(); #endif // #ifdef CONFIG_OVMS_COMP_CELLULAR +#ifdef CONFIG_OVMS_COMP_POLLER + ESP_LOGI(TAG, "Auto init Pollers (free: %zu bytes)", heap_caps_get_free_size(MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL)); + MyPollers.AutoInit(); +#endif + ESP_LOGI(TAG, "Auto init vehicle (free: %zu bytes)", heap_caps_get_free_size(MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL)); MyVehicleFactory.AutoInit(); diff --git a/vehicle/OVMS.V3/main/ovms_utils.h b/vehicle/OVMS.V3/main/ovms_utils.h index 97a1d1808..783074bc9 100644 --- a/vehicle/OVMS.V3/main/ovms_utils.h +++ b/vehicle/OVMS.V3/main/ovms_utils.h @@ -38,6 +38,8 @@ #include #include #include +#include +#include #include "ovms.h" // Macro utils: @@ -634,4 +636,75 @@ static inline std::string str_tolower(std::string s) { ); return s; } + +/** + * Call-back register for registering named call-back procedures. + * + * The list does not shrink which is fine for our use-cases. + * Can be made inexpensively threadsafe/re-entrant safe. + */ +template +class ovms_callback_register_t + { + private: + class entry_t + { + public: + entry_t(const std::string &caller, FN callback) + { + m_name = caller; + m_callback = callback; + } + ~entry_t() {} + public: + std::string m_name; + FN m_callback; + }; + typedef std::forward_list callbacklist_t; + callbacklist_t m_list; + public: + ~ovms_callback_register_t() + { + } + void Register(const std::string &nametag, FN callback) + { + // Replace + for (auto it = m_list.begin(); it != m_list.end(); ++it) + { + if ((*it).m_name == nametag) + { + (*it).m_callback = callback; + return; + } + } + if (!callback) + return; + for (auto it = m_list.begin(); it != m_list.end(); ++it) + { + if (!(*it).m_callback) + { + entry_t &entry = *it; + entry.m_name = nametag; + entry.m_callback = callback; + return; + } + } + m_list.push_front(entry_t(nametag, callback)); + } + void Deregister(const std::string &nametag) + { + Register(nametag, nullptr); + } + typedef std::function visit_fn_t; + void Call(visit_fn_t visit) + { + for (auto it = m_list.begin(); it != m_list.end(); ++it) + { + const entry_t &entry = *it; + if (entry.m_callback) + visit(entry.m_name, entry.m_callback); + } + } + }; + #endif // __OVMS_UTILS_H__