OVMS3/OVMS.V3/components/ovms_location/src/ovms_location.cpp

929 lines
30 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 = "location";
#include "ovms_location.h"
#include "ovms_config.h"
#include "ovms_events.h"
#include "ovms_script.h"
#include "ovms_notify.h"
#include "ovms_command.h"
#include "vehicle.h"
#include "metrics_standard.h"
#include <math.h>
const char *LOCATIONS_PARAM = "locations";
#define LOCATION_DEFRADIUS 100
#define LOCATION_R 6371
#define LOCATION_TO_RAD (3.1415926536 / 180)
// Calculate haversine distance in meters
double OvmsLocationDistance(double th1, double ph1, double th2, double ph2)
{
double dx, dy, dz;
ph1 -= ph2;
ph1 *= LOCATION_TO_RAD, th1 *= LOCATION_TO_RAD, th2 *= LOCATION_TO_RAD;
dz = sin(th1) - sin(th2);
dx = cos(ph1) * cos(th1) - cos(th2);
dy = sin(ph1) * cos(th1);
return (asin(sqrt(dx * dx + dy * dy + dz * dz) / 2) * 2 * LOCATION_R)*1000.0;
}
OvmsLocationAction::OvmsLocationAction(bool enter, enum LocationAction action, const char* params, int len)
: m_enter(enter), m_action(action), m_params(params, len) {}
const char* OvmsLocationAction::ActionString()
{
switch (m_action)
{
case HOMELINK: return "homelink";
case ACC: return "acc";
case NOTIFY: return "notify";
default: return "INVALID";
}
}
void OvmsLocationAction::Execute(bool enter)
{
if (m_enter != enter)
return;
if (m_action == HOMELINK)
{
int homelink = m_params.at(0) - '0';
int durationms = 1000;
if (m_params.length() > 1)
durationms = atoi(m_params.substr(2).c_str());
OvmsVehicle* currentvehicle = MyVehicleFactory.m_currentvehicle;
if (currentvehicle)
{
if (!StandardMetrics.ms_v_env_on->AsBool())
ESP_LOGI(TAG, "Not activating Homelink #%d because parked",homelink);
else
switch(currentvehicle->CommandHomelink(homelink-1, durationms))
{
case OvmsVehicle::Success:
ESP_LOGI(TAG, "Homelink #%d activated for %dms",homelink,durationms);
break;
case OvmsVehicle::Fail:
ESP_LOGE(TAG, "Could not activate homelink #%d",homelink);
break;
default:
ESP_LOGE(TAG, "Vehicle does not implement homelink");
break;
}
}
}
else if (m_action == NOTIFY)
{
MyNotify.NotifyString("info", enter ? "location.enter" : "location.leave", m_params.c_str());
}
}
ActionList::~ActionList()
{
clear();
}
void ActionList::clear()
{
for (iterator i = begin(); i != end(); ++i)
delete *i;
std::list<OvmsLocationAction*>::clear();
}
ActionList::iterator ActionList::erase(iterator pos)
{
delete *pos;
return std::list<OvmsLocationAction*>::erase(pos);
}
OvmsLocation::OvmsLocation(const std::string& name)
{
m_name = name;
m_inlocation = false;
}
OvmsLocation::~OvmsLocation()
{
}
bool OvmsLocation::IsInLocation(float latitude, float longitude)
{
// This should check if we are in the location
double dist = OvmsLocationDistance((double)latitude,(double)longitude,(double)m_latitude,(double)m_longitude);
std::string event;
// ESP_LOGI(TAG, "Location %s is %0.1fm distant",m_name.c_str(),dist);
if (fabs(dist) <= m_radius)
{
// We are in the location
if (!m_inlocation)
{
m_inlocation = true;
StandardMetrics.ms_v_pos_location->SetValue(m_name);
if (StandardMetrics.ms_v_env_on->AsBool())
{
event = std::string("location.enter.");
event.append(m_name);
MyEvents.SignalEvent(event.c_str(), (void*)m_name.c_str(), m_name.size()+1);
}
for (ActionList::iterator it = m_actions.begin(); it != m_actions.end(); ++it)
(*it)->Execute(true);
}
}
else
{
// We are out of the location
if (m_inlocation)
{
m_inlocation = false;
StandardMetrics.ms_v_pos_location->SetValue("");
if (StandardMetrics.ms_v_env_on->AsBool())
{
event = std::string("location.leave.");
event.append(m_name);
MyEvents.SignalEvent(event.c_str(), (void*)m_name.c_str(), m_name.size()+1);
}
for (ActionList::iterator it = m_actions.begin(); it != m_actions.end(); ++it)
(*it)->Execute(false);
}
}
// Return current status
return m_inlocation;
}
bool OvmsLocation::Parse(const std::string& value)
{
const char *p = value.c_str();
char next = 0;
int len = 0;
m_actions.clear();
m_radius = LOCATION_DEFRADIUS;
int num = sscanf(p, "%f , %f %n%1c", &m_latitude, &m_longitude, &len, &next);
if (num < 2)
return false;
if (num < 3)
return true;
p += len;
if (next == ',')
{
num = sscanf(p, ", %u %n%1c", &m_radius, &len, &next);
if (num < 1)
return false;
if (num < 2)
return true;
p += len;
}
if (next != ';')
return false;
while (*p == ';')
{
bool enter;
enum LocationAction action;
p += strspn(++p, " ");
if (strncasecmp(p, "enter:", 6) == 0)
enter = true;
else if (strncasecmp(p, "leave:", 6) == 0)
enter = false;
else
return false;
p += 6;
const char* act = p;
int alen = strcspn(act, ":");
p += alen;
if (*p++ != ':')
return false;
len = strcspn(p, ";");
if (alen == 8 && strncasecmp(act, "homelink", alen) == 0)
{
if (len < 1 || *p < '1' || *p > '3')
{
ESP_LOGE(TAG, "homelink parameter must be 1, 2. or 3");
return false;
}
if (len > 1)
{
int durationms;
int slen;
int n = sscanf(p+1, ",%d%n", &durationms, &slen);
if (durationms < 100 || n != 1 || slen != len-1)
{
ESP_LOGE(TAG, "Minimum homelink timer duration 100ms");
return false;
}
}
action = HOMELINK;
}
else if (alen == 3 && strncasecmp(act, "acc", alen) == 0)
{
if (!enter)
{
ESP_LOGE(TAG, "Location action ACC not valid on leave");
return false;
}
action = ACC;
}
else if (alen == 6 && strncasecmp(act, "notify", alen) == 0)
action = NOTIFY;
else
{
ESP_LOGE(TAG, "Unrecognized action keyword");
return false;
}
m_actions.push_back(new OvmsLocationAction(enter, action, p, len));
p += len;
}
return true;
}
void OvmsLocation::Store(std::string& buf)
{
char val[32];
snprintf(val,sizeof(val),"%0.6f,%0.6f,%d", m_latitude, m_longitude, m_radius);
buf = val;
for (ActionList::iterator it = m_actions.begin(); it != m_actions.end(); ++it)
{
OvmsLocationAction* ola = *it;
buf.append(1, ';');
buf.append(ola->m_enter ? "enter" : "leave");
buf.append(1, ':');
buf.append(ola->ActionString());
buf.append(1, ':');
buf.append(ola->m_params);
}
MyConfig.SetParamValue(LOCATIONS_PARAM, m_name, buf.c_str());
}
void OvmsLocation::Render(std::string& buf)
{
char val[32];
snprintf(val, sizeof(val), "%0.6f,%0.6f (%dm)", m_latitude, m_longitude, m_radius);
buf = val;
bool first = true;
for (ActionList::iterator it = m_actions.begin(); it != m_actions.end(); ++it)
{
OvmsLocationAction* ola = *it;
if (!ola->m_enter)
continue;
if (first)
{
buf.append("\n Enter Actions: ");
first = false;
}
else
buf.append("; ");
buf.append(ola->ActionString());
buf.append(1, ' ');
if (ola->m_action == HOMELINK)
{
buf.append(1, ola->m_params.at(0));
if (ola->m_params.length() > 1)
{
buf.append(" (");
buf.append(ola->m_params.substr(2));
buf.append("ms)");
}
}
else
buf.append(ola->m_params);
}
first = true;
for (ActionList::iterator it = m_actions.begin(); it != m_actions.end(); ++it)
{
OvmsLocationAction* ola = *it;
if (ola->m_enter)
continue;
if (first)
{
buf.append("\n Leave Actions: ");
first = false;
}
else
buf.append("; ");
buf.append(ola->ActionString());
buf.append(1, ' ');
buf.append(ola->m_params);
}
}
void location_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
OvmsConfigParam* p = MyConfig.CachedParam(LOCATIONS_PARAM);
if (p == NULL) return;
// List all the locations that have been parsed as valid
for (LocationMap::iterator it=MyLocations.m_locations.begin(); it!=MyLocations.m_locations.end(); ++it)
{
const std::string& name = it->first;
OvmsLocation* loc = it->second;
std::string value;
loc->Render(value);
writer->printf("%s%s: %s\n", loc->m_inlocation ? "*" : "", name.c_str(), value.c_str());
}
// List any entries in the location config that are not valid
for (ConfigParamMap::iterator it=p->m_map.begin(); it!=p->m_map.end(); ++it)
{
const std::string& name = it->first;
const std::string& value = it->second;
auto k = MyLocations.m_locations.find(name);
if (k == MyLocations.m_locations.end())
{
writer->printf("Location %s is invalid: %s\n", name.c_str(), value.c_str());
}
}
writer->puts("NOTE: ACC actions are not implemented yet!"); // XXX IMPLEMENT AND REMOVE THIS!
}
void location_set(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
const char *name = argv[0];
float latitude, longitude;
int radius = LOCATION_DEFRADIUS;
if (strcmp(name, "?") == 0)
{
writer->printf("Error: ? is not a valid name\n");
return;
}
if (argc >= 3)
{
latitude = atof(argv[1]);
longitude = atof(argv[2]);
}
else
{
latitude = MyLocations.m_latitude;
longitude = MyLocations.m_longitude;
}
if (argc > 3) radius = atoi(argv[3]);
char val[32];
snprintf(val,sizeof(val),"%0.6f,%0.6f,%d",latitude,longitude,radius);
MyConfig.SetParamValue(LOCATIONS_PARAM,name,val);
writer->puts("Location defined");
}
void location_radius(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
const char *name = argv[0];
OvmsLocation* const* locp = MyLocations.m_locations.FindUniquePrefix(name);
if (locp == NULL)
{
writer->printf("Error: No location %s defined\n",name);
return;
}
std::string buf;
OvmsLocation* loc = *locp;
loc->m_radius = atoi(argv[1]);
loc->Store(buf);
writer->puts("Location radius set");
}
void location_rm(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
const char *name = argv[0];
OvmsLocation* const* locp = MyLocations.m_locations.FindUniquePrefix(name);
if (locp == NULL)
{
writer->printf("Error: No location %s defined\n",name);
return;
}
MyConfig.DeleteInstance(LOCATIONS_PARAM,(*locp)->m_name);
writer->puts("Location removed");
}
void location_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
int n;
writer->printf("Currently at %0.6f,%0.6f ",MyLocations.m_latitude,MyLocations.m_longitude);
writer->printf("(%sGPS lock", MyLocations.m_gpslock ? "" : "without ");
n = StandardMetrics.ms_v_pos_satcount->AsInt();
if (n > 0)
{
writer->printf(", %d satellite%s, %s", n, n == 1 ? "" : "s",
MyLocations.m_gpsgood ? "reliable" : "unreliable");
}
writer->puts(")");
if ((MyLocations.m_park_latitude != 0) || (MyLocations.m_park_longitude != 0))
writer->printf("Vehicle is parked%s %0.6f,%0.6f\n",
MyLocations.m_park_invalid ? ", last known coordinates:" : " at",
MyLocations.m_park_latitude,
MyLocations.m_park_longitude);
n = MyLocations.m_locations.size();
writer->printf("There %s %d location%s defined\n",
n == 1 ? "is" : "are", n, n == 1 ? "" : "s");
bool found = false;
for (LocationMap::iterator it=MyLocations.m_locations.begin(); it!=MyLocations.m_locations.end(); ++it)
{
if (it->second->m_inlocation)
{
if (!found) writer->printf("Active locations:");
writer->printf(" %s",it->second->m_name.c_str());
found = true;
}
}
if (found)
writer->puts("");
else
writer->puts("No active locations");
}
int location_validate(OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv, bool complete)
{
if (argc == 1)
return MyLocations.m_locations.Validate(writer, argc, argv[0], complete);
return -1;
}
void location_action(int verbosity, OvmsWriter* writer, enum LocationAction act, std::string& params)
{
const char* const* rargv = writer->GetArgv();
int remove = *rargv[2] == 'r' ? 1 : 0;
bool enter = *rargv[2+remove] == 'e';
const char* name = rargv[3+remove];
OvmsLocation* const* locp = MyLocations.m_locations.FindUniquePrefix(name);
if (locp == NULL)
{
writer->printf("Error: No location %s defined\n",name);
return;
}
OvmsLocation* loc = *locp;
if (!remove)
{
loc->m_actions.push_back(new OvmsLocationAction(enter, act, params.c_str(), params.length()));
loc->Store(params);
writer->puts("Location action set");
if (act == ACC)
writer->puts("NOTE: ACC actions are not implemented yet!"); // XXX IMPLEMENT AND REMOVE THIS!
}
else
{
int removed = 0;
for (ActionList::iterator it = loc->m_actions.begin(); it != loc->m_actions.end(); )
{
OvmsLocationAction* ola = *it;
if (ola->m_enter != enter)
{
++it;
continue;
}
if (act != INVALID)
{
if (ola->m_action != act)
{
++it;
continue;
}
if (!params.empty() && ola->m_params != params)
{
++it;
continue;
}
}
++removed;
it = loc->m_actions.erase(it);
if (it == loc->m_actions.end())
break;
}
if (!removed)
{
writer->printf("Error: No matching action found in location %s\n",name);
return;
}
loc->Store(params);
writer->printf("%d action%s removed from location %s\n", removed, removed > 1 ? "s" : "", name);
}
}
void location_homelink(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
std::string params = cmd->GetName();
if (argc == 1)
{
int durationms;
int slen;
int n = sscanf(argv[0], "%u%n", &durationms, &slen);
if (durationms < 100 || n != 1 || slen != strlen(argv[0]))
{
writer->puts("Error: Minimum homelink timer duration 100ms");
return;
}
params.append(1, ',');
params.append(argv[0]);
}
enum LocationAction act = HOMELINK;
location_action(verbosity, writer, act, params);
}
void location_homelink_any(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
std::string params;
enum LocationAction act = HOMELINK;
location_action(verbosity, writer, act, params);
}
void location_acc(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
std::string params = argc ? argv[0] : "";
enum LocationAction act = ACC;
location_action(verbosity, writer, act, params);
}
void location_notify(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
std::string params;
for (int i = 0; i < argc; ++i)
{
params.append(argv[i]);
if (i < argc-1)
params.append(1, ' ');
}
if (params.find(';') != string::npos)
{
writer->puts("Error: Notify text cannot include semicolon");
return;
}
enum LocationAction act = NOTIFY;
location_action(verbosity, writer, act, params);
}
void location_all(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
std::string params;
enum LocationAction act = INVALID;
location_action(verbosity, writer, act, params);
}
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
static duk_ret_t DukOvmsLocationStatus(duk_context *ctx)
{
const char *mn = duk_to_string(ctx,0);
OvmsLocation* const* locp = MyLocations.m_locations.FindUniquePrefix(mn);
if (locp && *locp)
{
duk_push_boolean(ctx, (*locp)->m_inlocation);
return 1; /* one return value */
}
else
return 0;
}
#endif //#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
OvmsLocations MyLocations __attribute__ ((init_priority (1900)));
OvmsLocations::OvmsLocations()
{
ESP_LOGI(TAG, "Initialising LOCATIONS (1900)");
m_ready = false;
m_gpslock = false;
m_gpssq = 0;
m_gpsgood = false;
m_latitude = 0;
m_longitude = 0;
m_park_latitude = 0;
m_park_longitude = 0;
m_park_distance = 0;
m_park_invalid = true;
m_last_alarm = 0;
// Register our commands
OvmsCommand* cmd_location = MyCommandApp.RegisterCommand("location","LOCATION framework", location_status, "", 0, 0, false);
cmd_location->RegisterCommand("list","Show all locations",location_list);
cmd_location->RegisterCommand("set","Set the position of a location",location_set, "<name> [<latitude> <longitude> [<radius>]]", 1, 4);
cmd_location->RegisterCommand("radius","Set the radius of a location",location_radius, "<name> <radius>", 2, 2, true, location_validate);
cmd_location->RegisterCommand("rm","Remove a defined location",location_rm, "<name>", 1, 1, true, location_validate);
cmd_location->RegisterCommand("status","Show location status",location_status);
OvmsCommand* cmd_action = cmd_location->RegisterCommand("action","Set an action for a location");
OvmsCommand* cmd_enter = cmd_action->RegisterCommand("enter","Set an action upon entering a location", NULL, "<location> $L", 1, 1, true, location_validate);
OvmsCommand* cmd_leave = cmd_action->RegisterCommand("leave","Set an action upon leaving a location", NULL, "<location> $L", 1, 1, true, location_validate);
OvmsCommand* cmd_rm_action = cmd_action->RegisterCommand("rm","Remove a location action");
OvmsCommand* cmd_rm_enter = cmd_rm_action->RegisterCommand("enter","Remove an action from entering a location", location_all, "<location> [$C]", 1, 1, true, location_validate);
OvmsCommand* cmd_rm_leave = cmd_rm_action->RegisterCommand("leave","Remove an action from leaving a location", location_all, "<location> [$C]", 1, 1, true, location_validate);
OvmsCommand* enter_homelink = cmd_enter->RegisterCommand("homelink","Transmit Homelink signal",NULL,"$C [<duration=1000ms>]");
enter_homelink->RegisterCommand("1","Transmit Homelink 1 signal",location_homelink,"[<duration=1000ms>]", 0, 1);
enter_homelink->RegisterCommand("2","Transmit Homelink 2 signal",location_homelink,"[<duration=1000ms>]", 0, 1);
enter_homelink->RegisterCommand("3","Transmit Homelink 3 signal",location_homelink,"[<duration=1000ms>]", 0, 1);
cmd_enter->RegisterCommand("acc","ACC profile",location_acc,"<profile>", 1, 1);
cmd_enter->RegisterCommand("notify","Text notification",location_notify,"<text>", 1, INT_MAX);
OvmsCommand* leave_homelink = cmd_leave->RegisterCommand("homelink","Transmit Homelink signal",NULL,"$C [<duration=1000ms>]");
leave_homelink->RegisterCommand("1","Transmit Homelink 1 signal",location_homelink,"[<duration=1000ms>]", 0, 1);
leave_homelink->RegisterCommand("2","Transmit Homelink 2 signal",location_homelink,"[<duration=1000ms>]", 0, 1);
leave_homelink->RegisterCommand("3","Transmit Homelink 3 signal",location_homelink,"[<duration=1000ms>]", 0, 1);
cmd_leave->RegisterCommand("notify","Text notification",location_notify,"<text>", 1, INT_MAX);
OvmsCommand* rm_enter_homelink = cmd_rm_enter->RegisterCommand("homelink","Remove Homelink signal",location_homelink_any);
rm_enter_homelink->RegisterCommand("1","Remove Homelink 1 signal",location_homelink);
rm_enter_homelink->RegisterCommand("2","Remove Homelink 2 signal",location_homelink);
rm_enter_homelink->RegisterCommand("3","Remove Homelink 3 signal",location_homelink);
cmd_rm_enter->RegisterCommand("acc","Remove ACC profile",location_acc,"[<profile>]", 0, 1);
cmd_rm_enter->RegisterCommand("notify","Remove text notification",location_notify,"[<text>]", 0, INT_MAX);
OvmsCommand* rm_leave_homelink = cmd_rm_leave->RegisterCommand("homelink","Remove Homelink signal",location_homelink_any);
rm_leave_homelink->RegisterCommand("1","Remove Homelink 1 signal",location_homelink);
rm_leave_homelink->RegisterCommand("2","Remove Homelink 2 signal",location_homelink);
rm_leave_homelink->RegisterCommand("3","Remove Homelink 3 signal",location_homelink);
cmd_rm_leave->RegisterCommand("notify","Remove text notification",location_notify,"[<text>]", 0, INT_MAX);
// Register our parameters
MyConfig.RegisterParam(LOCATIONS_PARAM, "Geo Locations", true, true);
// Register our callbacks
using std::placeholders::_1;
using std::placeholders::_2;
MyMetrics.RegisterListener(TAG, MS_V_POS_GPSLOCK, std::bind(&OvmsLocations::UpdatedGpsLock, this, _1));
MyMetrics.RegisterListener(TAG, MS_V_POS_GPSSQ, std::bind(&OvmsLocations::UpdatedGpsSQ, this, _1));
MyMetrics.RegisterListener(TAG, MS_V_POS_GPSTIME, std::bind(&OvmsLocations::UpdatedPosition, this, _1));
MyMetrics.RegisterListener(TAG, MS_V_ENV_ON, std::bind(&OvmsLocations::UpdatedVehicleOn, this, _1));
MyEvents.RegisterEvent(TAG,"config.mounted", std::bind(&OvmsLocations::UpdatedConfig, this, _1, _2));
MyEvents.RegisterEvent(TAG,"config.changed", std::bind(&OvmsLocations::UpdatedConfig, this, _1, _2));
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
ESP_LOGI(TAG, "Expanding DUKTAPE javascript engine");
DuktapeObjectRegistration* dto = new DuktapeObjectRegistration("OvmsLocation");
dto->RegisterDuktapeFunction(DukOvmsLocationStatus, 1, "Status");
MyDuktape.RegisterDuktapeObject(dto);
#endif //#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
}
OvmsLocations::~OvmsLocations()
{
MyMetrics.DeregisterListener(TAG);
}
void OvmsLocations::UpdatedGpsLock(OvmsMetric* metric)
{
OvmsMetricBool* m = (OvmsMetricBool*)metric;
m_gpslock = m->AsBool();
if (m_gpslock)
{
MyEvents.SignalEvent("gps.lock.acquired", NULL);
}
else
{
MyEvents.SignalEvent("gps.lock.lost", NULL);
}
}
void OvmsLocations::UpdatedGpsSQ(OvmsMetric* metric)
{
m_gpssq = StdMetrics.ms_v_pos_gpssq->AsInt();
int level_good = MyConfig.GetParamValueInt("vehicle", "gps.sq.good", 60);
int level_bad = MyConfig.GetParamValueInt("vehicle", "gps.sq.bad", 40);
if (level_bad >= level_good) level_bad = level_good - 1;
if (!m_gpsgood && m_gpssq >= level_good)
{
m_gpsgood = true;
if (!m_ready)
{
UpdateParkPosition();
m_ready = true;
}
UpdateLocations();
MyEvents.SignalEvent("gps.sq.good", NULL);
}
else if (m_gpsgood && m_gpssq <= level_bad)
{
m_gpsgood = false;
MyEvents.SignalEvent("gps.sq.bad", NULL);
}
}
void OvmsLocations::UpdatedPosition(OvmsMetric* metric)
{
m_latitude = StdMetrics.ms_v_pos_latitude->AsFloat();
m_longitude = StdMetrics.ms_v_pos_longitude->AsFloat();
if (m_gpsgood)
{
UpdateLocations();
CheckTheft();
}
}
void OvmsLocations::UpdatedVehicleOn(OvmsMetric* metric)
{
UpdateParkPosition();
}
void OvmsLocations::UpdateParkPosition()
{
OvmsRecMutexLock lock(&m_park_lock);
bool caron = StdMetrics.ms_v_env_on->AsBool();
if (caron)
{
m_park_latitude = 0;
m_park_longitude = 0;
m_park_distance = 0;
m_park_invalid = true;
m_last_alarm = 0;
ESP_LOGI(TAG, "UpdateParkPosition: vehicle is driving");
}
else
{
m_park_latitude = m_latitude;
m_park_longitude = m_longitude;
m_park_invalid = (!m_gpsgood || StdMetrics.ms_v_pos_latitude->IsStale() || StdMetrics.ms_v_pos_longitude->IsStale());
m_last_alarm = 0;
ESP_LOGI(TAG, "UpdateParkPosition: vehicle is parking @%0.6f,%0.6f gpslock=%d satcount=%d hdop=%.1f sq=%d invalid=%d",
m_park_latitude, m_park_longitude, m_gpslock,
StdMetrics.ms_v_pos_satcount->AsInt(),
StdMetrics.ms_v_pos_gpshdop->AsFloat(),
m_gpssq, m_park_invalid);
}
}
void OvmsLocations::ReloadMap()
{
OvmsConfigParam* p = MyConfig.CachedParam(LOCATIONS_PARAM);
if (p == NULL) return;
// Forward search, updating existing locations
for (ConfigParamMap::iterator it=p->m_map.begin(); it!=p->m_map.end(); ++it)
{
const std::string& name = it->first;
const std::string& value = it->second;
OvmsLocation* loc;
auto k = m_locations.find(name);
if (k == m_locations.end())
{
// Create it
loc = new OvmsLocation(name);
m_locations[name] = loc;
}
else
loc = k->second;
// Parse the parameters
if (!loc->Parse(value))
{
ESP_LOGE(TAG, "Location %s is invalid: %s", name.c_str(), value.c_str());
delete loc;
m_locations.erase(name);
}
else
{
// ESP_LOGI(TAG, "Location %s is at %f,%f (%d)", name.c_str(), loc->m_latitude, loc->m_longitude, loc->m_radius);
}
}
// Reverse search, go through existing locations looking for those to delete
for (LocationMap::iterator it=m_locations.begin(); it!=m_locations.end();)
{
auto k = p->m_map.find(it->first);
if (k == p->m_map.end())
{
// Location no longer exists
// ESP_LOGI(TAG, "Location %s is removed",it->first.c_str());
delete it->second;
it = m_locations.erase(it);
}
else
{
it++;
}
}
if (m_gpsgood) UpdateLocations();
}
void OvmsLocations::UpdateLocations()
{
if ((m_latitude == 0) && (m_longitude == 0)) return;
for (LocationMap::iterator it=m_locations.begin(); it!=m_locations.end(); ++it)
{
it->second->IsInLocation(m_latitude,m_longitude);
}
}
void OvmsLocations::CheckTheft()
{
static int last_dist = 0;
OvmsRecMutexLock lock(&m_park_lock);
if ((m_park_latitude == 0) && (m_park_longitude == 0)) return;
if (StandardMetrics.ms_v_env_on->AsBool()) return;
// Wait for first valid coordinates if we had none when we parked the car:
if (m_park_invalid)
{
UpdateParkPosition();
return;
}
int alarm = MyConfig.GetParamValueInt("vehicle", "flatbed.alarmdistance", 500);
if (alarm == 0) return;
double dist = fabs(OvmsLocationDistance((double)m_latitude,(double)m_longitude,(double)m_park_latitude,(double)m_park_longitude));
// Park distance is the smoothed version
m_park_distance = (m_park_distance * 4 + dist) / 5;
if (last_dist != round(dist/10))
{
last_dist = round(dist/10);
ESP_LOGV(TAG, "CheckTheft: vehicle parked @%0.6f,%0.6f now @%0.6f,%0.6f dist=%.0f smoothed=%.0f alarm=%d"
" gpsmode=%s satcount=%d hdop=%.1f gpsspeed=%.1f",
m_park_latitude, m_park_longitude, m_latitude, m_longitude, dist, m_park_distance, alarm,
StdMetrics.ms_v_pos_gpsmode->AsString().c_str(),
StdMetrics.ms_v_pos_satcount->AsInt(),
StdMetrics.ms_v_pos_gpshdop->AsFloat(),
StdMetrics.ms_v_pos_gpsspeed->AsFloat());
}
// Suppress false theft alerts due to a suspected SIMCOM GPS bug,
// the reported location goes from: A,B -> A,B -> 0,B -> 0,A -> A,B -> A,B
// Also seen: A,B -> A,B -> A,0 -> A,B -> A,B
int simcombugdist = MyConfig.GetParamValueInt("vehicle", "flatbed.simcombugdistance", 500 * 1000);
if (simcombugdist > 0 && (m_latitude == 0.0 || m_longitude == 0.0) &&
m_park_distance > simcombugdist)
{
ESP_LOGE(TAG, "CheckTheft: Invalid SIMCOM GPS position @%0.6f,%0.6f dist=%.0f smoothed=%.0f",
m_latitude, m_longitude, dist, m_park_distance);
return;
}
int alarm_interval = MyConfig.GetParamValueInt("vehicle", "flatbed.alarminterval", 15) * 60;
if ((m_park_distance > alarm) &&
(m_last_alarm == 0 || (alarm_interval > 0 && monotonictime > m_last_alarm + alarm_interval)))
{
MyNotify.NotifyStringf("alert", "flatbed.moved",
"Vehicle is being transported while parked - possible theft/flatbed (@%0.6f,%0.6f)",
m_latitude, m_longitude);
MyEvents.SignalEvent("location.alert.flatbed.moved", NULL);
ESP_LOGW(TAG, "CheckTheft: flatbed.moved parked @%0.6f,%0.6f now @%0.6f,%0.6f gpsmode=%s satcount=%d hdop=%.1f gpsspeed=%.1f",
m_park_latitude, m_park_longitude, m_latitude, m_longitude,
StdMetrics.ms_v_pos_gpsmode->AsString().c_str(),
StdMetrics.ms_v_pos_satcount->AsInt(),
StdMetrics.ms_v_pos_gpshdop->AsFloat(),
StdMetrics.ms_v_pos_gpsspeed->AsFloat());
// inhibit further alerts for configured interval:
m_last_alarm = monotonictime;
}
}
void OvmsLocations::UpdatedConfig(std::string event, void* data)
{
if (event.compare("config.changed")==0)
{
// Only reload if our parameter has changed
OvmsConfigParam*p = (OvmsConfigParam*)data;
if (p->GetName().compare(LOCATIONS_PARAM)!=0) return;
}
ReloadMap();
if (event == "config.mounted")
{
// Init from persistent position & vehicle state:
m_latitude = StdMetrics.ms_v_pos_latitude->AsFloat();
m_longitude = StdMetrics.ms_v_pos_longitude->AsFloat();
UpdateLocations();
UpdatedVehicleOn(StdMetrics.ms_v_env_on);
}
}