795 lines
24 KiB
C++
795 lines
24 KiB
C++
|
/*
|
||
|
; 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 = "notify";
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <stdio.h>
|
||
|
#include <sstream>
|
||
|
#include "ovms.h"
|
||
|
#include "ovms_notify.h"
|
||
|
#include "ovms_command.h"
|
||
|
#include "ovms_config.h"
|
||
|
#include "ovms_events.h"
|
||
|
#include "ovms_script.h"
|
||
|
#include "vehicle.h"
|
||
|
#include "buffered_shell.h"
|
||
|
#include "string.h"
|
||
|
#include "ovms_mutex.h"
|
||
|
|
||
|
using namespace std;
|
||
|
|
||
|
OvmsNotify MyNotify __attribute__ ((init_priority (1820)));
|
||
|
|
||
|
// Tracing:
|
||
|
// level 0 = no tracing (logging)
|
||
|
// level 1 = trace standard (text & data) notifications
|
||
|
// level 2 = also trace stream notifications
|
||
|
#define DO_TRACE(type) (MyNotify.m_trace == 2 || (MyNotify.m_trace == 1 && strcmp((type), "stream") != 0))
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// Console commands...
|
||
|
|
||
|
void notify_trace(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||
|
{
|
||
|
if (strcmp(cmd->GetName(),"all")==0)
|
||
|
MyNotify.m_trace = 2;
|
||
|
else if (strcmp(cmd->GetName(),"on")==0)
|
||
|
MyNotify.m_trace = 1;
|
||
|
else
|
||
|
MyNotify.m_trace = 0;
|
||
|
|
||
|
writer->printf("Notification tracing is now %s\n",cmd->GetName());
|
||
|
}
|
||
|
|
||
|
void notify_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&MyNotify.m_mutex);
|
||
|
writer->printf("Notification system has %d readers registered\n",
|
||
|
MyNotify.CountReaders());
|
||
|
for (OvmsNotifyCallbackMap_t::iterator itc=MyNotify.m_readers.begin(); itc!=MyNotify.m_readers.end(); itc++)
|
||
|
{
|
||
|
OvmsNotifyCallbackEntry* mc = itc->second;
|
||
|
writer->printf(" %s(%d): verbosity=%d\n", mc->m_caller, mc->m_reader, mc->m_verbosity);
|
||
|
}
|
||
|
|
||
|
if (MyNotify.m_types.size() > 0)
|
||
|
{
|
||
|
writer->puts("Notify types:");
|
||
|
for (OvmsNotifyTypeMap_t::iterator itm=MyNotify.m_types.begin(); itm!=MyNotify.m_types.end(); ++itm)
|
||
|
{
|
||
|
OvmsNotifyType* mt = itm->second;
|
||
|
OvmsRecMutexLock lock(&mt->m_mutex);
|
||
|
writer->printf(" %s: %d entries\n",
|
||
|
mt->m_name, mt->m_entries.size());
|
||
|
for (NotifyEntryMap_t::iterator ite=mt->m_entries.begin(); ite!=mt->m_entries.end(); ++ite)
|
||
|
{
|
||
|
OvmsNotifyEntry* e = ite->second;
|
||
|
writer->printf(" %d: [%d pending] %s\n",
|
||
|
ite->first, e->CountPending(), e->GetValue().c_str());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void notify_raise(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||
|
{
|
||
|
writer->printf("Raise %s notification for %s/%s as %s\n",
|
||
|
cmd->GetName(), argv[0], argv[1], argv[2]);
|
||
|
|
||
|
if (strcmp(cmd->GetName(),"text")==0)
|
||
|
MyNotify.NotifyString(argv[0],argv[1],argv[2]);
|
||
|
else if (strcmp(cmd->GetName(),"command")==0)
|
||
|
MyNotify.NotifyCommand(argv[0],argv[1],argv[2]);
|
||
|
else
|
||
|
MyNotify.NotifyErrorCode(atol(argv[0]),atol(argv[1]),(strcmp(argv[2],"yes")==0));
|
||
|
}
|
||
|
|
||
|
void notify_errorcode_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||
|
{
|
||
|
for (OvmsNotifyErrorCodeMap_t::iterator it=MyNotify.m_errorcodes.begin(); it!=MyNotify.m_errorcodes.end(); ++it)
|
||
|
{
|
||
|
writer->printf("%u(0x%04.4x) %s raised %d, updated %d, sec(s) ago\n",
|
||
|
it->first,
|
||
|
it->second->lastdata,
|
||
|
(it->second->active)?"active":"cleared",
|
||
|
monotonictime-it->second->raised,
|
||
|
monotonictime-it->second->updated);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void notify_errorcode_clear(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
||
|
{
|
||
|
for (OvmsNotifyErrorCodeMap_t::iterator it=MyNotify.m_errorcodes.begin(); it!=MyNotify.m_errorcodes.end(); ++it)
|
||
|
{
|
||
|
delete it->second;
|
||
|
}
|
||
|
MyNotify.m_errorcodes.clear();
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
|
||
|
|
||
|
static duk_ret_t DukOvmsNotifyRaise(duk_context *ctx)
|
||
|
{
|
||
|
const char *type = duk_to_string(ctx,0);
|
||
|
const char *subtype = duk_to_string(ctx,1);
|
||
|
const char *message = duk_to_string(ctx,2);
|
||
|
|
||
|
if (type && subtype && message)
|
||
|
{
|
||
|
uint32_t id = MyNotify.NotifyString(type, subtype, message);
|
||
|
duk_push_uint(ctx, id);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
duk_push_uint(ctx, 0);
|
||
|
}
|
||
|
return 1; /* one return value */
|
||
|
}
|
||
|
|
||
|
#endif //#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// OvmsNotifyEntry is the virtual object for notification entries.
|
||
|
// These are added to a particular OvmsNotifyType (as a member of a list)
|
||
|
// and readers are notified. Once a reader has processed an entry, the
|
||
|
// MarkRead function is called. The framework can test IsAllRead() to
|
||
|
// see if all readers have processed the entry and housekeep cleanup
|
||
|
// appropriately.
|
||
|
|
||
|
OvmsNotifyEntry::OvmsNotifyEntry(const char* subtype)
|
||
|
{
|
||
|
m_pendingreaders = 0;
|
||
|
m_id = 0;
|
||
|
m_created = esp_log_timestamp();
|
||
|
m_type = NULL;
|
||
|
m_subtype = strdup(subtype);
|
||
|
}
|
||
|
|
||
|
OvmsNotifyEntry::~OvmsNotifyEntry()
|
||
|
{
|
||
|
if (m_subtype) free(m_subtype);
|
||
|
}
|
||
|
|
||
|
bool OvmsNotifyEntry::IsRead(size_t reader)
|
||
|
{
|
||
|
return !(m_pendingreaders & 1ul << reader);
|
||
|
}
|
||
|
|
||
|
int OvmsNotifyEntry::CountPending()
|
||
|
{
|
||
|
int cnt = 0;
|
||
|
unsigned long pend = m_pendingreaders;
|
||
|
for (int i=0; i<NOTIFY_MAX_READERS; i++)
|
||
|
{
|
||
|
cnt += pend & 1;
|
||
|
pend >>= 1;
|
||
|
}
|
||
|
return cnt;
|
||
|
}
|
||
|
|
||
|
bool OvmsNotifyEntry::IsAllRead()
|
||
|
{
|
||
|
return (m_pendingreaders == 0);
|
||
|
}
|
||
|
|
||
|
const extram::string OvmsNotifyEntry::GetValue()
|
||
|
{
|
||
|
return extram::string("");
|
||
|
}
|
||
|
|
||
|
const char* OvmsNotifyEntry::GetSubType()
|
||
|
{
|
||
|
return m_subtype;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// OvmsNotifyEntryString is the notification entry for a constant
|
||
|
// string type.
|
||
|
|
||
|
OvmsNotifyEntryString::OvmsNotifyEntryString(const char* subtype, const char* value)
|
||
|
: OvmsNotifyEntry(subtype)
|
||
|
{
|
||
|
m_value = extram::string(value);
|
||
|
}
|
||
|
|
||
|
OvmsNotifyEntryString::~OvmsNotifyEntryString()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
const extram::string OvmsNotifyEntryString::GetValue()
|
||
|
{
|
||
|
return m_value;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// OvmsNotifyEntryCommand is the notification entry for a command
|
||
|
// callback type.
|
||
|
|
||
|
OvmsNotifyEntryCommand::OvmsNotifyEntryCommand(const char* subtype, int verbosity, const char* cmd)
|
||
|
: OvmsNotifyEntry(subtype)
|
||
|
{
|
||
|
m_cmd = new char[strlen(cmd)+1];
|
||
|
strcpy(m_cmd,cmd);
|
||
|
|
||
|
BufferedShell* bs = new BufferedShell(false, verbosity);
|
||
|
// command notifications can only be raised by the system or "notify raise" in enabled mode,
|
||
|
// so we can assume this is a secure shell:
|
||
|
bs->SetSecure(true);
|
||
|
bs->ProcessChars(m_cmd, strlen(m_cmd));
|
||
|
bs->ProcessChar('\n');
|
||
|
bs->Dump(m_value);
|
||
|
delete bs;
|
||
|
}
|
||
|
|
||
|
OvmsNotifyEntryCommand::~OvmsNotifyEntryCommand()
|
||
|
{
|
||
|
if (m_cmd)
|
||
|
{
|
||
|
delete [] m_cmd;
|
||
|
m_cmd = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const extram::string OvmsNotifyEntryCommand::GetValue()
|
||
|
{
|
||
|
return m_value;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// OvmsNotifyType is the container for an ordered list of
|
||
|
// OvmsNotifyEntry objects (being the notification data queued)
|
||
|
|
||
|
OvmsNotifyType::OvmsNotifyType(const char* name)
|
||
|
{
|
||
|
m_name = name;
|
||
|
m_nextid = 1;
|
||
|
}
|
||
|
|
||
|
OvmsNotifyType::~OvmsNotifyType()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
uint32_t OvmsNotifyType::QueueEntry(OvmsNotifyEntry* entry)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
uint32_t id = m_nextid++;
|
||
|
|
||
|
entry->m_id = id;
|
||
|
entry->m_type = this;
|
||
|
m_entries[id] = entry;
|
||
|
|
||
|
if (strcmp(m_name, "data") != 0 &&
|
||
|
strcmp(m_name, "stream") != 0)
|
||
|
{
|
||
|
std::string event("notify.");
|
||
|
event.append(m_name);
|
||
|
event.append(".");
|
||
|
event.append(entry->m_subtype);
|
||
|
MyEvents.SignalEvent(event, (void*)id);
|
||
|
}
|
||
|
|
||
|
// Dispatch the callbacks...
|
||
|
MyNotify.NotifyReaders(this, entry);
|
||
|
|
||
|
// Check if we can cleanup...
|
||
|
Cleanup(entry);
|
||
|
|
||
|
return id;
|
||
|
}
|
||
|
|
||
|
uint32_t OvmsNotifyType::AllocateNextID()
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
return m_nextid++;
|
||
|
}
|
||
|
|
||
|
void OvmsNotifyType::ClearReader(size_t reader)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
if (m_entries.size() > 0)
|
||
|
{
|
||
|
NotifyEntryMap_t::iterator next;
|
||
|
for (NotifyEntryMap_t::iterator ite=m_entries.begin(); ite!=m_entries.end(); )
|
||
|
{
|
||
|
OvmsNotifyEntry* e = ite->second;
|
||
|
++ite;
|
||
|
e->m_pendingreaders &= ~(1ul << reader);
|
||
|
Cleanup(e, &ite);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
OvmsNotifyEntry* OvmsNotifyType::FirstUnreadEntry(size_t reader, uint32_t floor)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
for (NotifyEntryMap_t::iterator ite=m_entries.begin(); ite!=m_entries.end(); ++ite)
|
||
|
{
|
||
|
OvmsNotifyEntry* e = ite->second;
|
||
|
if ((!e->IsRead(reader))&&(e->m_id > floor))
|
||
|
return e;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
OvmsNotifyEntry* OvmsNotifyType::FindEntry(uint32_t id)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
auto k = m_entries.find(id);
|
||
|
if (k == m_entries.end())
|
||
|
return NULL;
|
||
|
else
|
||
|
return k->second;
|
||
|
}
|
||
|
|
||
|
void OvmsNotifyType::MarkRead(size_t reader, OvmsNotifyEntry* entry)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
entry->m_pendingreaders &= ~(1ul << reader);
|
||
|
Cleanup(entry);
|
||
|
}
|
||
|
|
||
|
void OvmsNotifyType::Cleanup(OvmsNotifyEntry* entry, NotifyEntryMap_t::iterator* next /*=NULL*/)
|
||
|
{
|
||
|
if (entry->IsAllRead())
|
||
|
{
|
||
|
// We can cleanup...
|
||
|
auto k = m_entries.find(entry->m_id);
|
||
|
if (k != m_entries.end())
|
||
|
{
|
||
|
NotifyEntryMap_t::iterator it = m_entries.erase(k);
|
||
|
if (next) *next = it;
|
||
|
}
|
||
|
if (DO_TRACE(m_name))
|
||
|
ESP_LOGD(TAG,"Cleanup type %s id %d",m_name,entry->m_id);
|
||
|
delete entry;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// OvmsNotifyCallbackEntry contains the callback function for a
|
||
|
// particular reader
|
||
|
|
||
|
OvmsNotifyCallbackEntry::OvmsNotifyCallbackEntry(const char* caller, size_t reader, int verbosity, OvmsNotifyCallback_t callback,
|
||
|
bool configfiltered/*=true*/, OvmsNotifyFilterCallback_t filtercallback/*=NULL*/)
|
||
|
{
|
||
|
m_caller = caller;
|
||
|
m_reader = reader;
|
||
|
m_verbosity = verbosity;
|
||
|
m_callback = callback;
|
||
|
m_configfiltered = configfiltered;
|
||
|
m_filtercallback = filtercallback;
|
||
|
}
|
||
|
|
||
|
OvmsNotifyCallbackEntry::~OvmsNotifyCallbackEntry()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
bool OvmsNotifyCallbackEntry::Accepts(OvmsNotifyType* type, const char* subtype, size_t size)
|
||
|
{
|
||
|
// Check size
|
||
|
if (size > m_verbosity)
|
||
|
return false;
|
||
|
// Check filter by config:
|
||
|
if (m_configfiltered)
|
||
|
{
|
||
|
// Config syntax options:
|
||
|
// a) explicit inclusion: e.g. 'ovmsv2,ovmsv3' (only enable these)
|
||
|
// b) explicit exclusion: e.g. '*,-ovmsv2,-ovmsv3' (only disable these)
|
||
|
// '-' to disable all, empty/'*' to enable all
|
||
|
std::string filter = MyConfig.GetParamValue("notify", subtype);
|
||
|
if (!filter.empty())
|
||
|
{
|
||
|
if (filter[0] == '*')
|
||
|
{
|
||
|
if (filter.find(std::string("-")+m_caller) != string::npos)
|
||
|
return false;
|
||
|
}
|
||
|
else if (filter.find(m_caller) == string::npos)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Check filter by callback:
|
||
|
if (m_filtercallback)
|
||
|
{
|
||
|
if (m_filtercallback(type, subtype) == false)
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// OvmsNotifyCallbackEntry contains the callback function for a
|
||
|
//
|
||
|
|
||
|
OvmsNotify::OvmsNotify()
|
||
|
{
|
||
|
ESP_LOGI(TAG, "Initialising NOTIFICATIONS (1820)");
|
||
|
|
||
|
m_nextreader = 1;
|
||
|
|
||
|
#ifdef CONFIG_OVMS_DEV_DEBUGNOTIFICATIONS
|
||
|
m_trace = 1;
|
||
|
#else
|
||
|
m_trace = 0;
|
||
|
#endif // #ifdef CONFIG_OVMS_DEV_DEBUGNOTIFICATIONS
|
||
|
|
||
|
MyConfig.RegisterParam("notify", "Notification filters", true, true);
|
||
|
|
||
|
// Register our commands
|
||
|
OvmsCommand* cmd_notify = MyCommandApp.RegisterCommand("notify","NOTIFICATION framework", notify_status, "", 0, 0, false);
|
||
|
cmd_notify->RegisterCommand("status","Show notification status",notify_status);
|
||
|
OvmsCommand* cmd_notifyraise = cmd_notify->RegisterCommand("raise","NOTIFICATION raise framework");
|
||
|
cmd_notifyraise->RegisterCommand("text","Raise a textual notification",notify_raise,"<type><subtype><message>", 3, 3);
|
||
|
cmd_notifyraise->RegisterCommand("command","Raise a command callback notification",notify_raise,"<type><subtype><command>", 3, 3);
|
||
|
cmd_notifyraise->RegisterCommand("errorcode","Raise an error code notification",notify_raise,"<code><data><raised>", 3, 3);
|
||
|
OvmsCommand* cmd_notifyerrorcode = cmd_notify->RegisterCommand("errorcode","NOTIFICATION error code framework");
|
||
|
cmd_notifyerrorcode->RegisterCommand("list","List error codes raised",notify_errorcode_list);
|
||
|
cmd_notifyerrorcode->RegisterCommand("clear","Clear error code list",notify_errorcode_clear);
|
||
|
OvmsCommand* cmd_notifytrace = cmd_notify->RegisterCommand("trace","NOTIFICATION trace framework");
|
||
|
cmd_notifytrace->RegisterCommand("on","Standard notification tracing (text, error & data)",notify_trace);
|
||
|
cmd_notifytrace->RegisterCommand("all","Full notification tracing (including streams)",notify_trace);
|
||
|
cmd_notifytrace->RegisterCommand("off","Turn notification tracing OFF",notify_trace);
|
||
|
|
||
|
RegisterType("info"); // payload: human readable text message
|
||
|
RegisterType("error"); // payload: "<vehicletype>,<errorcode>,<errordata>"
|
||
|
RegisterType("alert"); // payload: human readable text message
|
||
|
RegisterType("data"); // payload: MP historical data record (tagged CSV, see MP documentation)
|
||
|
RegisterType("stream"); // payload: subtype specific, use for high volume / short latency data streams
|
||
|
|
||
|
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
|
||
|
ESP_LOGI(TAG, "Expanding DUKTAPE javascript engine");
|
||
|
DuktapeObjectRegistration* dto = new DuktapeObjectRegistration("OvmsNotify");
|
||
|
dto->RegisterDuktapeFunction(DukOvmsNotifyRaise, 3, "Raise");
|
||
|
MyDuktape.RegisterDuktapeObject(dto);
|
||
|
#endif // CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
|
||
|
}
|
||
|
|
||
|
OvmsNotify::~OvmsNotify()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
size_t OvmsNotify::RegisterReader(const char* caller, int verbosity, OvmsNotifyCallback_t callback,
|
||
|
bool configfiltered/*=false*/, OvmsNotifyFilterCallback_t filtercallback/*=NULL*/)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
size_t reader = m_nextreader++;
|
||
|
|
||
|
m_readers[reader] = new OvmsNotifyCallbackEntry(caller, reader, verbosity, callback, configfiltered, filtercallback);
|
||
|
|
||
|
return reader;
|
||
|
}
|
||
|
|
||
|
void OvmsNotify::RegisterReader(size_t reader, const char* caller, int verbosity, OvmsNotifyCallback_t callback,
|
||
|
bool configfiltered/*=false*/, OvmsNotifyFilterCallback_t filtercallback/*=NULL*/)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
m_readers[reader] = new OvmsNotifyCallbackEntry(caller, reader, verbosity, callback, configfiltered, filtercallback);
|
||
|
}
|
||
|
|
||
|
void OvmsNotify::ClearReader(size_t reader)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
auto k = m_readers.find(reader);
|
||
|
if (k != m_readers.end())
|
||
|
{
|
||
|
for (OvmsNotifyTypeMap_t::iterator itt=m_types.begin(); itt!=m_types.end(); ++itt)
|
||
|
{
|
||
|
OvmsNotifyType* t = itt->second;
|
||
|
t->ClearReader(k->second->m_reader);
|
||
|
}
|
||
|
OvmsNotifyCallbackEntry* ec = k->second;
|
||
|
m_readers.erase(k);
|
||
|
delete ec;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
size_t OvmsNotify::CountReaders()
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
return m_readers.size();
|
||
|
}
|
||
|
|
||
|
OvmsNotifyType* OvmsNotify::GetType(const char* type)
|
||
|
{
|
||
|
auto k = m_types.find(type);
|
||
|
if (k == m_types.end())
|
||
|
return NULL;
|
||
|
else
|
||
|
return k->second;
|
||
|
}
|
||
|
|
||
|
void OvmsNotify::NotifyReaders(OvmsNotifyType* type, OvmsNotifyEntry* entry)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
for (OvmsNotifyCallbackMap_t::iterator itc=m_readers.begin(); itc!=m_readers.end(); ++itc)
|
||
|
{
|
||
|
OvmsNotifyCallbackEntry* mc = itc->second;
|
||
|
if (mc->Accepts(type, entry->GetSubType(), entry->GetValueSize()))
|
||
|
{
|
||
|
// deliver notification:
|
||
|
if (mc->m_callback(type,entry) == true)
|
||
|
entry->m_pendingreaders &= ~(1ul << mc->m_reader);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// in case the acceptance filter changed since queueing:
|
||
|
entry->m_pendingreaders &= ~(1ul << mc->m_reader);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool OvmsNotify::HasReader(const char* type, const char* subtype, size_t size)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
OvmsNotifyType* mt = GetType(type);
|
||
|
for (OvmsNotifyCallbackMap_t::iterator itc=m_readers.begin(); itc!=m_readers.end(); ++itc)
|
||
|
{
|
||
|
OvmsNotifyCallbackEntry* mc = itc->second;
|
||
|
if (mc->Accepts(mt, subtype, size))
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void OvmsNotify::RegisterType(const char* type)
|
||
|
{
|
||
|
OvmsNotifyType* mt = GetType(type);
|
||
|
if (mt == NULL)
|
||
|
{
|
||
|
mt = new OvmsNotifyType(type);
|
||
|
m_types[type] = mt;
|
||
|
ESP_LOGI(TAG,"Registered notification type %s",type);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uint32_t OvmsNotify::NotifyString(const char* type, const char* subtype, const char* value)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
OvmsNotifyType* mt = GetType(type);
|
||
|
if (mt == NULL)
|
||
|
{
|
||
|
ESP_LOGW(TAG, "Notification raised for non-existent type %s: %s", type, value);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (DO_TRACE(type))
|
||
|
ESP_LOGI(TAG, "Raise text %s/%s: %s", type, subtype, value);
|
||
|
|
||
|
// determine all currently active readers accepting the message:
|
||
|
std::bitset<NOTIFY_MAX_READERS> readers;
|
||
|
size_t size = strlen(value);
|
||
|
for (OvmsNotifyCallbackMap_t::iterator itc=m_readers.begin(); itc!=m_readers.end(); ++itc)
|
||
|
{
|
||
|
OvmsNotifyCallbackEntry* mc = itc->second;
|
||
|
if (mc->Accepts(mt, subtype, size))
|
||
|
readers.set(mc->m_reader);
|
||
|
}
|
||
|
if (readers.count() == 0)
|
||
|
{
|
||
|
ESP_LOGD(TAG, "Abort: no readers for type '%s' subtype '%s' size %d", type, subtype, size);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// create message:
|
||
|
OvmsNotifyEntry* msg = (OvmsNotifyEntry*) new OvmsNotifyEntryString(subtype, value);
|
||
|
msg->m_pendingreaders = readers.to_ulong();
|
||
|
|
||
|
ESP_LOGD(TAG, "Created entry type '%s' subtype '%s' size %d has %d readers pending", type, subtype, size, readers.count());
|
||
|
|
||
|
return mt->QueueEntry(msg);
|
||
|
}
|
||
|
|
||
|
uint32_t OvmsNotify::NotifyCommand(const char* type, const char* subtype, const char* cmd)
|
||
|
{
|
||
|
OvmsRecMutexLock lock(&m_mutex);
|
||
|
OvmsNotifyType* mt = GetType(type);
|
||
|
if (mt == NULL)
|
||
|
{
|
||
|
ESP_LOGW(TAG, "Notification raised for non-existent type %s: %s", type, cmd);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (DO_TRACE(type))
|
||
|
ESP_LOGI(TAG, "Raise command %s/%s: %s", type, subtype, cmd);
|
||
|
|
||
|
// Strategy:
|
||
|
// to minimize RAM usage and command calls we try to reuse higher verbosity messages
|
||
|
// if their result length fits for lower verbosity readers as well.
|
||
|
|
||
|
// get verbosity levels needed by readers accepting the message:
|
||
|
std::map<int, OvmsNotifyEntryCommand*> verbosity_msgs;
|
||
|
std::bitset<NOTIFY_MAX_READERS> readers;
|
||
|
for (auto itc=m_readers.begin(); itc!=m_readers.end(); itc++)
|
||
|
{
|
||
|
OvmsNotifyCallbackEntry* mc = itc->second;
|
||
|
if (mc->Accepts(mt, subtype))
|
||
|
{
|
||
|
verbosity_msgs[mc->m_verbosity] = NULL;
|
||
|
readers.set(mc->m_reader); // cache acceptance
|
||
|
}
|
||
|
}
|
||
|
if (verbosity_msgs.size() == 0)
|
||
|
{
|
||
|
if (DO_TRACE(type))
|
||
|
{
|
||
|
// no readers, but tracing enabled, so log command result:
|
||
|
const int verbosity = COMMAND_RESULT_NORMAL;
|
||
|
OvmsNotifyEntryCommand *msg = new OvmsNotifyEntryCommand(subtype, verbosity, cmd);
|
||
|
ESP_LOGI(TAG, "Raise cmdres[%d] %s/%s: %s", verbosity, type, subtype, msg->GetValue().c_str());
|
||
|
delete msg;
|
||
|
}
|
||
|
ESP_LOGD(TAG, "Abort: no readers for type '%s' subtype '%s'", type, subtype);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// fetch verbosity levels beginning at highest verbosity:
|
||
|
OvmsNotifyEntryCommand *msg = NULL;
|
||
|
size_t msglen = 0;
|
||
|
for (auto ritm=verbosity_msgs.rbegin(); ritm!=verbosity_msgs.rend(); ritm++)
|
||
|
{
|
||
|
int verbosity = ritm->first;
|
||
|
if (msg && msglen <= verbosity)
|
||
|
{
|
||
|
// reuse last verbosity level message:
|
||
|
verbosity_msgs[verbosity] = msg;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
msg = verbosity_msgs[verbosity];
|
||
|
if (!msg)
|
||
|
{
|
||
|
// create verbosity level message:
|
||
|
msg = new OvmsNotifyEntryCommand(subtype, verbosity, cmd);
|
||
|
msglen = msg->GetValueSize();
|
||
|
verbosity_msgs[verbosity] = msg;
|
||
|
if (DO_TRACE(type))
|
||
|
ESP_LOGI(TAG, "Raise cmdres[%d] %s/%s: %s", verbosity, type, subtype, msg->GetValue().c_str());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// add readers:
|
||
|
for (auto itc=m_readers.begin(); itc!=m_readers.end(); itc++)
|
||
|
{
|
||
|
OvmsNotifyCallbackEntry* mc = itc->second;
|
||
|
if (readers.test(mc->m_reader))
|
||
|
{
|
||
|
msg = verbosity_msgs[mc->m_verbosity];
|
||
|
msg->m_pendingreaders |= (1ul << mc->m_reader);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// queue all verbosity level messages beginning at lowest verbosity (fastest delivery):
|
||
|
msg = NULL;
|
||
|
uint32_t queue_id = 0;
|
||
|
for (auto itm=verbosity_msgs.begin(); itm!=verbosity_msgs.end(); itm++)
|
||
|
{
|
||
|
if (itm->second == msg)
|
||
|
continue; // already queued
|
||
|
msg = itm->second;
|
||
|
ESP_LOGD(TAG, "Created entry type '%s' subtype '%s' verbosity %d has %d readers pending",
|
||
|
type, subtype, itm->first, msg->CountPending());
|
||
|
queue_id = mt->QueueEntry(msg);
|
||
|
}
|
||
|
|
||
|
return queue_id;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* NotifyStringf: printf style API
|
||
|
*/
|
||
|
uint32_t OvmsNotify::NotifyStringf(const char* type, const char* subtype, const char* fmt, ...)
|
||
|
{
|
||
|
char *buffer = NULL;
|
||
|
uint32_t res = 0;
|
||
|
va_list args;
|
||
|
va_start(args, fmt);
|
||
|
int len = vasprintf(&buffer, fmt, args);
|
||
|
va_end(args);
|
||
|
if (len >= 0)
|
||
|
{
|
||
|
res = NotifyString(type, subtype, buffer);
|
||
|
free(buffer);
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* NotifyCommandf: printf style API
|
||
|
*/
|
||
|
uint32_t OvmsNotify::NotifyCommandf(const char* type, const char* subtype, const char* fmt, ...)
|
||
|
{
|
||
|
char *buffer = NULL;
|
||
|
uint32_t res = 0;
|
||
|
va_list args;
|
||
|
va_start(args, fmt);
|
||
|
int len = vasprintf(&buffer, fmt, args);
|
||
|
va_end(args);
|
||
|
if (len >= 0)
|
||
|
{
|
||
|
res = NotifyCommand(type, subtype, buffer);
|
||
|
free(buffer);
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
void OvmsNotify::NotifyErrorCode(uint32_t code, uint32_t data, bool raised, bool force)
|
||
|
{
|
||
|
// Raise a notification of an error code
|
||
|
// <code> error code (key field)
|
||
|
// <data> data associated with the error
|
||
|
// <raised> true if error is being raised, else false
|
||
|
// <force> true if notification should be forced, otherwise auto-suppress dupes
|
||
|
|
||
|
// Firstly, let's see if we have a notification record for it already
|
||
|
auto k = m_errorcodes.find(code);
|
||
|
OvmsNotifyErrorCodeEntry_t* entry;
|
||
|
if (k == m_errorcodes.end())
|
||
|
{
|
||
|
entry = new OvmsNotifyErrorCodeEntry_t;
|
||
|
entry->raised = monotonictime;
|
||
|
entry->updated = 0;
|
||
|
entry->lastdata = 0;
|
||
|
m_errorcodes[code] = entry;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
entry = k->second;
|
||
|
}
|
||
|
entry->active = raised;
|
||
|
if (raised) entry->lastdata = data;
|
||
|
|
||
|
// Handle auto-suppression, if necessary
|
||
|
if (!force && (entry->updated>0) && (monotonictime-entry->updated < NOTIFY_ERROR_AUTOSUPPRESS))
|
||
|
{
|
||
|
// We need to auto-suppress
|
||
|
entry->updated = monotonictime;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// OK to raise...
|
||
|
entry->updated = monotonictime;
|
||
|
if (raised)
|
||
|
{
|
||
|
NotifyStringf("error", "code", "%s,%u,%u",
|
||
|
MyVehicleFactory.ActiveVehicleType(),
|
||
|
code,
|
||
|
data);
|
||
|
}
|
||
|
}
|