410 lines
12 KiB
C++
410 lines
12 KiB
C++
/*
|
|
; Project: Open Vehicle Monitor System
|
|
; Date: 23rd July 2019
|
|
;
|
|
; Changes:
|
|
; 1.0 Initial release
|
|
;
|
|
; (C) 2019 Marko Juhanne
|
|
;
|
|
; 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.
|
|
*/
|
|
|
|
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
|
|
|
|
#define DEFAULT_PUSHOVER_RETRY 30 // Default retry interval in secs for unacknowledged emergency priority messages
|
|
#define DEFAULT_PUSHOVER_EXPIRE 1800 // Default expiration time in secs for unacknowledged emergency priority messages
|
|
#define MG_CONN_MUTEX_TIMEOUT (30*1000 / portTICK_PERIOD_MS) // 30s
|
|
|
|
#include "ovms_log.h"
|
|
static const char *TAG = "pushover";
|
|
|
|
#include <string.h>
|
|
#include "pushover.h"
|
|
#include "metrics_standard.h"
|
|
#include "ovms_netmanager.h"
|
|
#include "ovms_config.h"
|
|
#include "ovms_events.h"
|
|
#include "ovms_tls.h"
|
|
|
|
|
|
#include "mongoose.h"
|
|
|
|
Pushover MyPushoverClient __attribute__ ((init_priority (8800)));
|
|
|
|
|
|
void pushover_send_message(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
int priority;
|
|
std::string sound;
|
|
if (argc<1)
|
|
{
|
|
writer->puts("Error: No message given");
|
|
return;
|
|
}
|
|
if (argc >= 2)
|
|
{
|
|
priority = atoi(argv[1]);
|
|
|
|
if ((priority < -2) || (priority > 2))
|
|
{
|
|
writer->puts("Error: priority level should be between -2 and 2");
|
|
return;
|
|
}
|
|
|
|
if (argc >= 3)
|
|
sound = argv[2];
|
|
} else
|
|
{
|
|
priority = 0;
|
|
sound = "pushover";
|
|
}
|
|
|
|
writer->printf("Sending PUSHOVER message \"%s\" with priority %d and sound %s\n",argv[0],priority,sound.c_str());
|
|
MyPushoverClient.SendMessage(argv[0], priority, sound);
|
|
}
|
|
|
|
|
|
bool PushoverReaderCallback(OvmsNotifyType* type, OvmsNotifyEntry* entry)
|
|
{
|
|
return MyPushoverClient.IncomingNotification(type, entry);
|
|
}
|
|
|
|
|
|
bool PushoverReaderFilterCallback(OvmsNotifyType* type, const char* subtype)
|
|
{
|
|
return MyPushoverClient.NotificationFilter(type, subtype);
|
|
}
|
|
|
|
|
|
Pushover::Pushover()
|
|
{
|
|
ESP_LOGI(TAG, "Initialising Pushover client (8800)");
|
|
|
|
MyConfig.RegisterParam("pushover", "Pushover client configuration", true, true);
|
|
|
|
m_buffer = new OvmsBuffer(1024);
|
|
OvmsCommand* cmd_pushover = MyCommandApp.RegisterCommand("pushover","pushover notification framework");
|
|
cmd_pushover->RegisterCommand("msg","message",pushover_send_message,"<\"message\"> [<priority>] [<sound>]",1,3);
|
|
|
|
using std::placeholders::_1;
|
|
using std::placeholders::_2;
|
|
MyEvents.RegisterEvent(TAG, "*", std::bind(&Pushover::EventListener, this, _1, _2));
|
|
|
|
reader = MyNotify.RegisterReader("pushover", COMMAND_RESULT_NORMAL, std::bind(PushoverReaderCallback, _1, _2),
|
|
true, std::bind(PushoverReaderFilterCallback, _1, _2));
|
|
|
|
}
|
|
|
|
|
|
Pushover::~Pushover()
|
|
{
|
|
if (m_buffer)
|
|
{
|
|
delete m_buffer;
|
|
m_buffer = NULL;
|
|
}
|
|
MyNotify.ClearReader(reader);
|
|
}
|
|
|
|
|
|
bool Pushover::NotificationFilter(OvmsNotifyType* type, const char* subtype)
|
|
{
|
|
if (strcmp(type->m_name, "info") == 0 ||
|
|
strcmp(type->m_name, "error") == 0 ||
|
|
strcmp(type->m_name, "alert") == 0 ||
|
|
strcmp(type->m_name, "data") == 0)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Pushover::IncomingNotification(OvmsNotifyType* type, OvmsNotifyEntry* entry)
|
|
{
|
|
std::string nfy;
|
|
std::string priority;
|
|
std::string sound;
|
|
std::string msg;
|
|
|
|
if (MyConfig.GetParamValueBool("pushover","enable", false) == false)
|
|
{
|
|
//ESP_LOGD(TAG,"IncomingNotification: Ignore notification (%s:%s:%s) (pushover not enabled)",type->m_name,entry->GetSubType(),entry->GetValue().c_str());
|
|
return true;
|
|
}
|
|
if (!MyNetManager.m_connected_any)
|
|
{
|
|
ESP_LOGD(TAG,"IncomingNotification: Ignore notification (%s:%s:%s) because no network conn",type->m_name,entry->GetSubType(),entry->GetValue().c_str());
|
|
return true;
|
|
}
|
|
ESP_LOGD(TAG,"IncomingNotification: Handling notification (%s:%s:%s)",type->m_name,entry->GetSubType(),entry->GetValue().c_str());
|
|
|
|
OvmsConfigParam* param = MyConfig.CachedParam("pushover");
|
|
ConfigParamMap pmap;
|
|
pmap = param->m_map;
|
|
|
|
// try to find configuration for specific type/subtype
|
|
nfy = "np.";
|
|
nfy.append(type->m_name);
|
|
nfy.append("/");
|
|
nfy.append(entry->GetSubType());
|
|
priority = pmap[nfy];
|
|
if (priority == "")
|
|
{
|
|
// try more general type
|
|
nfy = "np.";
|
|
nfy.append(type->m_name);
|
|
priority = pmap[nfy];
|
|
if (priority == "")
|
|
{
|
|
ESP_LOGD(TAG,"IncomingNotification: No priority set -> ignore notification");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
msg = std::string(entry->GetValue().c_str());
|
|
if (priority == "0")
|
|
sound = pmap["sound.normal"];
|
|
else if (priority == "1")
|
|
sound = pmap["sound.high"];
|
|
else if (priority == "2")
|
|
sound = pmap["sound.emergency"];
|
|
|
|
SendMessage(msg, atoi(priority.c_str()), sound);
|
|
return true;
|
|
}
|
|
|
|
|
|
void Pushover::EventListener(std::string event, void* data)
|
|
{
|
|
std::string name, setting, pri, msg, sound;
|
|
if ( (event == "ticker.1") || (event == "ticker.10") )
|
|
return;
|
|
|
|
if (MyConfig.GetParamValueBool("pushover","enable", false) == false)
|
|
{
|
|
//ESP_LOGD(TAG,"EventListener: Ignore event (%s) (pushover not enabled)",event.c_str());
|
|
return;
|
|
}
|
|
if (!MyNetManager.m_connected_any)
|
|
{
|
|
ESP_LOGD(TAG,"EventListener: Ignore event (%s) because no network conn",event.c_str());
|
|
return;
|
|
}
|
|
ESP_LOGD(TAG,"EventListener: Handling event (%s)",event.c_str());
|
|
|
|
OvmsConfigParam* param = MyConfig.CachedParam("pushover");
|
|
ConfigParamMap pmap;
|
|
pmap = param->m_map;
|
|
|
|
name = "ep.";
|
|
name.append(event);
|
|
setting = pmap[name];
|
|
if (setting == "")
|
|
{
|
|
ESP_LOGD(TAG,"EventListener: No priority set -> ignore notification");
|
|
return;
|
|
}
|
|
// Priority and message is saved as "priority/message" tuple (eg. "-1/this is a message")
|
|
if (setting[1]=='/')
|
|
{
|
|
pri = setting.substr(0,1);
|
|
msg = setting.substr(2);
|
|
}
|
|
else if (setting[2]=='/')
|
|
{
|
|
pri = setting.substr(0,2);
|
|
msg = setting.substr(3);
|
|
}
|
|
|
|
// lower priorities don't make sound
|
|
if (pri == "0")
|
|
sound = pmap["sound.normal"];
|
|
else if (pri == "1")
|
|
sound = pmap["sound.high"];
|
|
else if (pri == "2")
|
|
sound = pmap["sound.emergency"];
|
|
SendMessage(msg, atoi(pri.c_str()), sound);
|
|
|
|
}
|
|
|
|
|
|
size_t Pushover::IncomingData(uint8_t* data, size_t len)
|
|
{
|
|
if (m_buffer->Push(data, len))
|
|
{
|
|
while ((m_buffer->HasLine()>=0)&&(m_mgconn))
|
|
{
|
|
std::string line = m_buffer->ReadLine();
|
|
m_reply.append(line);
|
|
m_reply.append("\r\n");
|
|
ESP_LOGV(TAG, "Reply: %s",line.c_str());
|
|
}
|
|
return len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void PushoverMongooseCallback(struct mg_connection *nc, int ev, void *p)
|
|
{
|
|
switch (ev)
|
|
{
|
|
case MG_EV_CONNECT:
|
|
{
|
|
int *success = (int*)p;
|
|
ESP_LOGI(TAG, "PushoverMongooseCallback(MG_EV_CONNECT=%d:%s)",*success, strerror(*success));
|
|
if (*success == 0)
|
|
{
|
|
ESP_LOGD(TAG, "Connection successful");
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGE(TAG, "Connection failed");
|
|
if (MyPushoverClient.sendReplyNotification)
|
|
{
|
|
MyNotify.NotifyString("info","pushover","Connection failed");
|
|
}
|
|
MyPushoverClient.m_mgconn_mutex.Unlock();
|
|
}
|
|
}
|
|
break;
|
|
case MG_EV_CLOSE:
|
|
ESP_LOGD(TAG, "PushoverMongooseCallback(MG_EV_CLOSE)");
|
|
if (MyPushoverClient.sendReplyNotification)
|
|
{
|
|
MyNotify.NotifyString("info","pushover",MyPushoverClient.m_reply.c_str());
|
|
}
|
|
MyPushoverClient.m_mgconn->flags |= MG_F_SEND_AND_CLOSE;
|
|
MyPushoverClient.m_mgconn = 0;
|
|
MyPushoverClient.m_buffer->EmptyAll();
|
|
MyPushoverClient.m_mgconn_mutex.Unlock();
|
|
break;
|
|
case MG_EV_SEND:
|
|
ESP_LOGD(TAG, "PushoverMongooseCallback(MG_EV_SEND) %d bytes",*(uint32_t*)p);
|
|
break;
|
|
case MG_EV_RECV:
|
|
ESP_LOGD(TAG, "PushoverMongooseCallback(MG_EV_RECV) %d bytes",*(uint32_t*)p);
|
|
mbuf_remove(&nc->recv_mbuf, MyPushoverClient.IncomingData((uint8_t*)nc->recv_mbuf.buf, nc->recv_mbuf.len));
|
|
break;
|
|
case MG_EV_POLL:
|
|
break;
|
|
default:
|
|
ESP_LOGE(TAG, "PushoverMongooseCallback - unhandled case: %d",ev);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool Pushover::SendMessage( const std::string message, int priority, const std::string sound, bool replyNotification )
|
|
{
|
|
return SendMessageOpt(MyConfig.GetParamValue("pushover", "user_key"), MyConfig.GetParamValue("pushover", "token"),
|
|
message, priority, sound,
|
|
MyConfig.GetParamValueInt("pushover", "retry", DEFAULT_PUSHOVER_RETRY),
|
|
MyConfig.GetParamValueInt("pushover", "expire", DEFAULT_PUSHOVER_EXPIRE),
|
|
replyNotification);
|
|
}
|
|
|
|
bool Pushover::SendMessageOpt( const std::string user_key, const std::string token,
|
|
const std::string message, int priority, const std::string sound, int retry, int expire, bool replyNotification )
|
|
{
|
|
std::string _server = "api.pushover.net:443";
|
|
bool parse_html = false;
|
|
|
|
ESP_LOGI(TAG,"Sending message %s with priority %d", message.c_str(), priority);
|
|
|
|
extram::ostringstream* post = new extram::ostringstream();
|
|
extram::ostringstream* http = new extram::ostringstream();
|
|
|
|
// construct body
|
|
*post << "token=" << token
|
|
<< "&user=" << user_key
|
|
<< "&message=" << message
|
|
<< "&priority=" << priority
|
|
<< "&title=" << MyConfig.GetParamValue("vehicle", "id")
|
|
<< "&sound=" << sound;
|
|
if (priority==2)
|
|
{
|
|
*post << "&retry=" << retry
|
|
<< "&expire=" << expire;
|
|
}
|
|
if (parse_html == true)
|
|
*post << "&html=1";
|
|
|
|
// construct header
|
|
uint16_t len = post->str().length();
|
|
*http << "POST /1/messages.json HTTP/1.1\r\nHost: api.pushover.net\r\nConnection: close\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nContent-Length: "
|
|
<< len << "\r\n\r\n" << post->str();
|
|
|
|
if (!m_mgconn_mutex.Lock(MG_CONN_MUTEX_TIMEOUT))
|
|
{
|
|
ESP_LOGE(TAG,"Transmit pending (mg_conn_mutex timeout)! Not sending msg..");
|
|
return false;
|
|
}
|
|
|
|
m_reply = "";
|
|
sendReplyNotification = replyNotification;
|
|
struct mg_mgr* mgr = MyNetManager.GetMongooseMgr();
|
|
struct mg_connect_opts opts;
|
|
const char* err;
|
|
memset(&opts, 0, sizeof(opts));
|
|
opts.error_string = &err;
|
|
|
|
opts.ssl_ca_cert = MyOvmsTLS.GetTrustedList();
|
|
|
|
if ((m_mgconn = mg_connect_opt(mgr, _server.c_str(), PushoverMongooseCallback, opts)) == NULL)
|
|
{
|
|
m_mgconn_mutex.Unlock();
|
|
ESP_LOGE(TAG, "mg_connect_opt(%s) failed: %s", _server.c_str(), err);
|
|
return false;
|
|
}
|
|
|
|
ESP_LOGV(TAG,"Msg: %s",http->str().c_str());
|
|
mg_send(m_mgconn, http->str().c_str(), http->str().length());
|
|
|
|
delete http;
|
|
delete post;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Pushover::SendMessageBlocking( const std::string message, std::string * reply, int priority, const std::string sound )
|
|
{
|
|
if (!SendMessage(message,priority,sound))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// wait for the reply..
|
|
if (!m_mgconn_mutex.Lock(MG_CONN_MUTEX_TIMEOUT))
|
|
{
|
|
ESP_LOGE(TAG,"Reply timeout..!");
|
|
// TODO: Do we need to unlock the mutex here or can will MG_EV_CLOSE be signaled eventually?
|
|
return false;
|
|
}
|
|
if (reply)
|
|
reply->append(m_reply);
|
|
m_reply = "";
|
|
|
|
m_mgconn_mutex.Unlock();
|
|
return true;
|
|
}
|
|
|