573 lines
16 KiB
C++
573 lines
16 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.
|
|
*/
|
|
|
|
#ifndef __OVMS_UTILS_H__
|
|
#define __OVMS_UTILS_H__
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <iomanip>
|
|
#include <vector>
|
|
#include "ovms.h"
|
|
|
|
// Macro utils:
|
|
// see https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing
|
|
// STR(x) = string of x, x expanded if preprocessor macro
|
|
#ifndef STR
|
|
#define STRX(x) #x
|
|
#define STR(x) STRX(x)
|
|
#endif
|
|
|
|
// Math utils:
|
|
#define SQR(n) ((n)*(n))
|
|
#define ABS(n) (((n) < 0) ? -(n) : (n))
|
|
#define LIMIT_MIN(n,lim) ((n) < (lim) ? (lim) : (n))
|
|
#define LIMIT_MAX(n,lim) ((n) > (lim) ? (lim) : (n))
|
|
|
|
// Value precision utils:
|
|
#define TRUNCPREC(fval,prec) (trunc((fval) * pow(10,(prec))) / pow(10,(prec)))
|
|
#define ROUNDPREC(fval,prec) (round((fval) * pow(10,(prec))) / pow(10,(prec)))
|
|
#define CEILPREC(fval,prec) (ceil((fval) * pow(10,(prec))) / pow(10,(prec)))
|
|
|
|
// Standard array size (number of elements):
|
|
#if __cplusplus < 201703L
|
|
template <class T, std::size_t N>
|
|
constexpr std::size_t sizeof_array(const T (&array)[N]) noexcept
|
|
{
|
|
return N;
|
|
}
|
|
#else
|
|
#define sizeof_array(array) (std::size(array))
|
|
#endif
|
|
|
|
// container.insert() helper:
|
|
#define endof_array(array) ((array)+sizeof_array(array))
|
|
|
|
// C string sorting for std::map et al:
|
|
struct CmpStrOp
|
|
{
|
|
bool operator()(char const *a, char const *b)
|
|
{
|
|
return std::strcmp(a, b) < 0;
|
|
}
|
|
};
|
|
|
|
// Tolerant boolean string analyzer:
|
|
inline bool strtobool(const std::string& str)
|
|
{
|
|
return (str == "yes" || str == "1" || str == "true");
|
|
}
|
|
|
|
/**
|
|
* chargestate_code: convert legacy chargestate key to code
|
|
* chargestate_key: convert chargestate code to legacy key
|
|
*/
|
|
std::string chargestate_code(const int key);
|
|
int chargestate_key(const std::string code);
|
|
|
|
/**
|
|
* chargesubstate_code: convert legacy charge substate key to code
|
|
* chargesubstate_key: convert charge substate code to legacy key
|
|
*/
|
|
std::string chargesubstate_code(const int key);
|
|
int chargesubstate_key(const std::string code);
|
|
|
|
/**
|
|
* chargemode_code: convert legacy chargemode key to code
|
|
* chargemode_key: convert chargemode code to legacy key
|
|
*/
|
|
std::string chargemode_code(const int key);
|
|
int chargemode_key(const std::string code);
|
|
|
|
|
|
/**
|
|
* mp_encode: encode string for MP transport;
|
|
* - replace '\r\n' by '\r'
|
|
* - replace '\n' by '\r'
|
|
* - replace ',' by ';'
|
|
*/
|
|
std::string mp_encode(const std::string text);
|
|
extram::string mp_encode(const extram::string text);
|
|
|
|
/**
|
|
* stripcr:
|
|
* - replace '\r\n' by '\n'
|
|
*/
|
|
extram::string stripcr(const extram::string& text);
|
|
|
|
/**
|
|
* stripesc: remove terminal escape sequences from (log) string
|
|
*/
|
|
std::string stripesc(const char* s);
|
|
|
|
/**
|
|
* startsWith: std::string et al prefix check
|
|
*/
|
|
template <class string_t>
|
|
bool startsWith(const string_t& haystack, const std::string& needle)
|
|
{
|
|
return needle.length() <= haystack.length()
|
|
&& std::equal(needle.begin(), needle.end(), haystack.begin());
|
|
}
|
|
template <class string_t>
|
|
bool startsWith(const string_t& haystack, const char needle)
|
|
{
|
|
return !haystack.empty() && haystack.front() == needle;
|
|
}
|
|
|
|
/**
|
|
* endsWith: std::string et al suffix check
|
|
*/
|
|
template <class string_t>
|
|
bool endsWith(const string_t& haystack, const std::string& needle)
|
|
{
|
|
return needle.length() <= haystack.length()
|
|
&& std::equal(needle.begin(), needle.end(), haystack.end() - needle.length());
|
|
}
|
|
template <class string_t>
|
|
bool endsWith(const string_t& haystack, const char needle)
|
|
{
|
|
return !haystack.empty() && haystack.back() == needle;
|
|
}
|
|
|
|
/**
|
|
* HexByte: Write a single byte as two hexadecimal characters
|
|
* Returns new pointer to end of string (p + 2)
|
|
*/
|
|
char* HexByte(char* p, uint8_t byte);
|
|
|
|
/**
|
|
* FormatHexDump: create/fill hexdump buffer including printable representation
|
|
* Note: allocates buffer as necessary in *bufferp, caller must free.
|
|
* Returns new remaining length
|
|
*/
|
|
size_t FormatHexDump(char** bufferp, const char* data, size_t rlength, size_t colsize=16);
|
|
|
|
/**
|
|
* hexencode: encode a string of bytes into hexadecimal form
|
|
*/
|
|
std::string hexencode(const std::string value);
|
|
|
|
/**
|
|
* hexdecode: decode a hexadecimal encoded string of bytes
|
|
* Returns empty string on error
|
|
*/
|
|
std::string hexdecode(const std::string encval);
|
|
|
|
/**
|
|
* int_to_hex: hex encode an integer value
|
|
* Source: https://kodlogs.com/68574/int-to-hex-string-c
|
|
*/
|
|
template <typename T>
|
|
std::string int_to_hex(T i)
|
|
{
|
|
std::stringstream stream;
|
|
stream << std::setfill('0') << std::setw(sizeof(T)*2) << std::hex << (unsigned)i;
|
|
return stream.str();
|
|
}
|
|
|
|
/**
|
|
* json_encode: encode string for JSON transport (see http://www.json.org/)
|
|
*/
|
|
template <class src_string>
|
|
std::string json_encode(const src_string text)
|
|
{
|
|
std::string buf;
|
|
char hex[10];
|
|
buf.reserve(text.size() + (text.size() >> 3));
|
|
for (int i=0; i<text.size(); i++)
|
|
{
|
|
switch(text[i])
|
|
{
|
|
case '\n': buf += "\\n"; break;
|
|
case '\r': buf += "\\r"; break;
|
|
case '\t': buf += "\\t"; break;
|
|
case '\b': buf += "\\b"; break;
|
|
case '\f': buf += "\\f"; break;
|
|
case '\"': buf += "\\\""; break;
|
|
case '\\': buf += "\\\\"; break;
|
|
default:
|
|
if (iscntrl(text[i]))
|
|
{
|
|
sprintf(hex, "\\u%04x", (unsigned int)text[i]);
|
|
buf += hex;
|
|
}
|
|
else
|
|
{
|
|
buf += text[i];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* display_encode: encode string displaying unprintablel characters
|
|
* Emulates (linux) "cat -t" semantics
|
|
*/
|
|
template <class src_string>
|
|
std::string display_encode(const src_string text)
|
|
{
|
|
std::string buf;
|
|
buf.reserve(text.size() + (text.size() >> 3));
|
|
for (int i = 0; i < text.size(); i++)
|
|
{
|
|
char ch = text[i];
|
|
if (!isascii(ch))
|
|
{
|
|
buf += "M-";
|
|
ch = toascii(ch);
|
|
}
|
|
if (ch == '\177')
|
|
{
|
|
buf += "^?";
|
|
continue;
|
|
}
|
|
if (ch == '\t')
|
|
{
|
|
buf += "^I";
|
|
continue;
|
|
}
|
|
if (ch == '\n')
|
|
{
|
|
// deviation from "cat -t"
|
|
buf += "^J";
|
|
continue;
|
|
}
|
|
if (!isprint(ch))
|
|
{
|
|
ch = ch ^ 0x40;
|
|
buf += "^";
|
|
}
|
|
buf += ch;
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
|
|
/**
|
|
* mqtt_topic: convert dotted string (e.g. notification subtype) to MQTT topic
|
|
* - replace '.' by '/'
|
|
*/
|
|
std::string mqtt_topic(const std::string text);
|
|
|
|
|
|
/**
|
|
* pwgen: simple password generator
|
|
* Note: take care to seed the pseudo random generator i.e. by
|
|
* srand48(StdMetrics.ms_m_monotonic->AsInt() * StdMetrics.ms_m_freeram->AsInt());
|
|
*/
|
|
std::string pwgen(int length);
|
|
|
|
#ifdef CONFIG_FREERTOS_USE_TRACE_FACILITY
|
|
#define HAVE_TaskGetHandle
|
|
/**
|
|
* TaskGetHandle: get task handle by name
|
|
* (FreeRTOS xTaskGetHandle() is not available)
|
|
*/
|
|
TaskHandle_t TaskGetHandle(const char *name);
|
|
#endif // CONFIG_FREERTOS_USE_TRACE_FACILITY
|
|
|
|
|
|
/**
|
|
* mkpath: mkdir -p
|
|
*/
|
|
int mkpath(std::string path, mode_t mode = 0);
|
|
|
|
/**
|
|
* rmtree: rmdir -r
|
|
*/
|
|
int rmtree(const std::string path);
|
|
|
|
/**
|
|
* path_exists: check if filesystem path exists
|
|
*/
|
|
bool path_exists(const std::string path);
|
|
|
|
/**
|
|
* load & save file to/from string
|
|
* - saving creates missing directories automatically & signals system.vfs.file.changed
|
|
* - return value: 0 = ok / errno
|
|
*/
|
|
int load_file(const std::string &path, extram::string &content);
|
|
int save_file(const std::string &path, extram::string &content);
|
|
|
|
/**
|
|
* get_user_agent: create User-Agent string from OVMS versions & vehicle ID
|
|
* Scheme: "ovms/v<hw_version> (<vehicle_id> <sw_version>)"
|
|
*/
|
|
std::string get_user_agent();
|
|
|
|
/**
|
|
* float2double: minimize precision errors on float→double conversion
|
|
*/
|
|
double float2double(float f);
|
|
|
|
/**
|
|
* idtag: create object instance tag for registrations
|
|
*/
|
|
std::string idtag(const char* tag, void* instance);
|
|
#define IDTAG idtag(TAG,this)
|
|
|
|
/**
|
|
* sign_extend: Sign extend an unsigned to a signed integer of the same or bigger size.
|
|
* Sign bit is not known at compile time.
|
|
*/
|
|
template<typename UINT, typename INT>
|
|
INT sign_extend( UINT uvalue, uint8_t signbit)
|
|
{
|
|
typedef typename std::make_unsigned<INT>::type uint_t;
|
|
uint_t newuvalue = uvalue;
|
|
UINT signmask = UINT(1U) << signbit;
|
|
if ( newuvalue & signmask)
|
|
newuvalue |= ~ (static_cast<uint_t>(signmask) - 1);
|
|
return reinterpret_cast<INT &>(newuvalue);
|
|
}
|
|
|
|
/**
|
|
* sign_extend: Sign extend an unsigned to a signed integer of the same or bigger size.
|
|
* Sign bit is known at compile time.
|
|
*/
|
|
template<typename UINT, typename INT, uint8_t SIGNBIT>
|
|
INT sign_extend( UINT uvalue)
|
|
{
|
|
typedef typename std::make_unsigned<INT>::type uint_t;
|
|
uint_t newuvalue = uvalue;
|
|
if ( newuvalue & ( UINT(1U) << SIGNBIT) )
|
|
newuvalue |= ~((uint_t(1U) << SIGNBIT) - 1);
|
|
return reinterpret_cast<INT &>(newuvalue);
|
|
}
|
|
|
|
/**
|
|
* get_bit: Get at a specific (compile-time defined) bit in a byte.
|
|
*/
|
|
template<uint8_t BIT>
|
|
bool get_bit(uint8_t data)
|
|
{
|
|
return (0 != (data & (1 << BIT)));
|
|
}
|
|
/**
|
|
* get_bit: Get at a specific (run-time defined) bit in a byte.
|
|
*/
|
|
inline bool get_bit(uint8_t data, uint8_t bit)
|
|
{
|
|
return (0 != (data & (1 << bit)));
|
|
}
|
|
/**
|
|
* get_uint_bits: Get at unsigned integer values within data.
|
|
* Compile-time defined section.
|
|
*/
|
|
template<uint8_t BIT, uint8_t BITLEN>
|
|
uint16_t get_uint_bits(uint32_t data)
|
|
{
|
|
return (data >> BIT) & ((0x1U <<(BITLEN+1))-1);
|
|
}
|
|
/**
|
|
* get_uint_bits: Get at unsigned integer values within data.
|
|
* Run-time defined section.
|
|
*/
|
|
inline uint16_t get_uint_bits(uint32_t data, uint8_t bit, uint8_t bitlen)
|
|
{
|
|
return (data >> bit) & ((0x1U <<(bitlen+1))-1);
|
|
}
|
|
|
|
/**
|
|
* get_uint_bits: Get at signed integer values within data.
|
|
* Compile-time defined section.
|
|
*/
|
|
template<uint8_t BIT, uint8_t BITLEN>
|
|
int16_t get_int_bits(uint32_t data)
|
|
{
|
|
uint16_t unsigned_val = get_uint_bits<BIT,BITLEN>(data);
|
|
return sign_extend<uint16_t,int16_t, BITLEN-1>(unsigned_val);
|
|
}
|
|
/**
|
|
* get_uint_bits: Get at signed integer values within data.
|
|
* Run-time defined section.
|
|
*/
|
|
inline int16_t get_int_bits(uint32_t data, uint8_t bit, uint8_t bitlen)
|
|
{
|
|
uint16_t unsigned_val = get_uint_bits(data, bit, bitlen);
|
|
return sign_extend<uint16_t,int16_t>(unsigned_val, bitlen-1);
|
|
}
|
|
|
|
// helper to provide a big endian integer from a given number of bytes.
|
|
// The helper function checks the length before using this.
|
|
template<uint8_t BYTES, typename UINT = uint32_t>
|
|
struct ovms_bytes_impl_t
|
|
{
|
|
static UINT get_bytes_uint(const uint8_t *data, uint32_t index)
|
|
{
|
|
return (data[index + (BYTES - 1)])
|
|
| (ovms_bytes_impl_t < BYTES - 1, UINT>::get_bytes_uint(data, index) << 8);
|
|
}
|
|
};
|
|
template<typename UINT>
|
|
struct ovms_bytes_impl_t<1,UINT>
|
|
{
|
|
static UINT get_bytes_uint(const uint8_t *data, uint32_t index)
|
|
{
|
|
return (data[index]);
|
|
}
|
|
};
|
|
|
|
// Helper Class to access Little-Endian values.
|
|
template<uint8_t BYTES, typename UINT = uint32_t>
|
|
struct get_bytes_le_impl_t
|
|
{
|
|
static UINT get_bytes_uint( const uint8_t *data, uint32_t index)
|
|
{
|
|
return ((data[index + (BYTES - 1)]) << (UINT(8) * (BYTES - 1)))
|
|
| get_bytes_le_impl_t < BYTES - 1 >::get_bytes_uint(data, index);
|
|
}
|
|
};
|
|
template<typename UINT>
|
|
struct get_bytes_le_impl_t<1, UINT>
|
|
{
|
|
static UINT get_bytes_uint(const uint8_t *data, uint32_t index)
|
|
{
|
|
return (data[index]);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* get_uint_bytes_be: Access to unsigned integer (big-endian) in a data buffer.
|
|
*
|
|
* @return true If within bounds
|
|
*/
|
|
template<uint8_t BYTES, typename UINT = uint32_t>
|
|
bool get_uint_bytes_be(const uint8_t *data, uint32_t index, uint32_t length, UINT &res)
|
|
{
|
|
if ((index + (BYTES - 1)) >= length) {
|
|
return false;
|
|
}
|
|
res = ovms_bytes_impl_t<BYTES,UINT>::get_bytes_uint(data, index);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* get_int_bytes_be: Access to signed integer (big-endian) in a data buffer.
|
|
* @return true If within bounds
|
|
*/
|
|
template<uint8_t BYTES, typename INT = int32_t>
|
|
bool get_int_bytes_be(const uint8_t *data, uint32_t index, uint32_t length, INT &res)
|
|
{
|
|
if ((index + (BYTES - 1)) >= length)
|
|
return false;
|
|
typedef typename std::make_unsigned<INT>::type uint_t;
|
|
|
|
uint_t uresult = ovms_bytes_impl_t<BYTES,uint_t>::get_bytes_uint(data, index);
|
|
res = sign_extend<uint_t, INT, BYTES * 8 - 1>(uresult);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* get_uint_buff_be: Access to unsigned integer (big-endian) in a vector data buffer.
|
|
* @return true If successful
|
|
*/
|
|
template<uint8_t BYTES, typename UINT = uint32_t>
|
|
bool get_uint_buff_be(const std::string &data, uint32_t index, UINT &ures)
|
|
{
|
|
if ((index + (BYTES - 1)) >= data.size())
|
|
return false;
|
|
ures = ovms_bytes_impl_t<BYTES,UINT>::get_bytes_uint(reinterpret_cast<const uint8_t *>(data.data()), index);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* get_buff_int_be: Access to signed integer (big-endian) in a std::string data buffer.
|
|
* @return true If successful
|
|
*/
|
|
template<uint8_t BYTES, typename INT = int32_t>
|
|
bool get_buff_int_be(const std::string &data, uint32_t index, INT &res)
|
|
{
|
|
if ((index + (BYTES - 1)) >= data.size())
|
|
return false;
|
|
typedef typename std::make_unsigned<INT>::type uint_t;
|
|
uint_t ures = ovms_bytes_impl_t<BYTES,uint_t>::get_bytes_uint(reinterpret_cast<const uint8_t *>(data.data()), index);
|
|
|
|
res = sign_extend<uint_t, INT, BYTES * 8 - 1>(ures);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* get_bytes_uint_le: Access to unsigned integer (little-endian) in a data buffer.
|
|
* @return true If within bounds
|
|
*/
|
|
template<uint8_t BYTES, typename UINT = uint32_t>
|
|
bool get_bytes_uint_le(const uint8_t *data, uint32_t index, uint32_t length, UINT &res)
|
|
{
|
|
if ((index + (BYTES - 1)) >= length)
|
|
return false;
|
|
res = get_bytes_le_impl_t<BYTES,UINT>::get_bytes_uint(data, index);
|
|
return true;
|
|
}
|
|
|
|
/** Access to unsigned integer (little-endian) in a vector data buffer.
|
|
* @return true If within bounds
|
|
*/
|
|
template<uint8_t BYTES, typename UINT = uint32_t>
|
|
bool get_buff_uint_le(const std::string &data, uint32_t index, UINT &res)
|
|
{
|
|
if ((index + (BYTES - 1)) >= data.size())
|
|
return false;
|
|
res = get_bytes_le_impl_t<BYTES,UINT>::get_bytes_uint(reinterpret_cast<const uint8_t *>(data.data()), index);
|
|
return true;
|
|
}
|
|
|
|
/** Access to signed integer (little-endian) in a vector data buffer.
|
|
* @return true If within bounds
|
|
*/
|
|
template<uint8_t BYTES, typename INT = int32_t>
|
|
bool get_buff_int_le(const std::string &data, uint32_t index, INT &res)
|
|
{
|
|
if ((index + (BYTES - 1)) >= data.size())
|
|
return false;
|
|
typedef typename std::make_unsigned<INT>::type uint_t;
|
|
uint_t uresult = ovms_bytes_impl_t<BYTES,uint_t>::get_bytes_uint(reinterpret_cast<const uint8_t *>(data.data()), index);
|
|
res = sign_extend < uint_t, INT, BYTES * 8 - 1 > (uresult);
|
|
return true;
|
|
}
|
|
|
|
|
|
/** Access a string portion in a vector data buffer.
|
|
* @return true if start is within bounds.
|
|
*/
|
|
bool get_buff_string(const uint8_t *data, uint32_t size, uint32_t index, uint32_t len, std::string &strret);
|
|
|
|
inline bool get_buff_string(const std::string &data, uint32_t index, uint32_t len, std::string &strret)
|
|
{
|
|
return get_buff_string(reinterpret_cast<const uint8_t *>(data.data()), data.size(), index, len, strret);
|
|
}
|
|
|
|
#endif // __OVMS_UTILS_H__
|