@@ -250,23 +250,58 @@ class BG95_base | |||||
// cmd: "AT+CREG?" | // cmd: "AT+CREG?" | ||||
// expected: "\r\n+CREG: 0,%\r\n\r\nOK\r\n" | // expected: "\r\n+CREG: 0,%\r\n\r\nOK\r\n" | ||||
/*! | |||||
* \brief | |||||
* Send a command to modem and check if the response matches to | |||||
* \c expected. If so read any token inside response marked with | |||||
* \c Marker, convert the value into type \c T and write it to \c t | |||||
* | |||||
* \param cmd The comand to send (null terminated) | |||||
* \param expected The expected response | |||||
* \param t The value to return | |||||
* \param timeout The timeout in CPU time (leave it for 0 - no timeout) | |||||
* | |||||
* \tparam T The type of the value to read from response marked with \c Marker | |||||
* \tparam Marker The marker character | |||||
* \return True on success | |||||
* | |||||
* example | |||||
* \code | |||||
* BG95<256> modem; | |||||
* std::thread th1 ([&](){ | |||||
* modem.inetd(false); | |||||
* }); | |||||
* int status; | |||||
* bool done = modem.command("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n\r\nOK\r\n", status); | |||||
* if (done && status == 1) | |||||
* std::cout << "Connected to home network\n" | |||||
* \endcode | |||||
*/ | |||||
template <typename T, char Marker = '%'> | template <typename T, char Marker = '%'> | ||||
bool command (const char* cmd, const str_view_t expected, T& t) { | |||||
bool command (const char* cmd, const str_view_t expected, T& t, clock_t timeout =0) { | |||||
char buffer[N]; | char buffer[N]; | ||||
transmit(cmd); | transmit(cmd); | ||||
for (size_t pos =0, i=0, j=0 ; pos < expected.size(); pos += j) { | for (size_t pos =0, i=0, j=0 ; pos < expected.size(); pos += j) { | ||||
str_view_t ex = expected.substr(pos); // get starting point of expected | str_view_t ex = expected.substr(pos); // get starting point of expected | ||||
size_t sz = receive(buffer, 1); // load the answer | |||||
size_t sz =0; | |||||
clock_t mark = clock(); | |||||
do { | |||||
sz = receive(buffer); // load the answer with timeout | |||||
if ((timeout != 0 )&& ((clock() - mark) >= timeout)) { | |||||
return false; | |||||
} | |||||
} while (!sz); | |||||
for (i=0, j=0 ; i<sz ; ) { | for (i=0, j=0 ; i<sz ; ) { | ||||
if (ex[j] == Marker) { | if (ex[j] == Marker) { | ||||
i += parse(&buffer[i], sz, ex[j+1], t); // parse and convert | |||||
i += parse(&buffer[i], sz, ex[j+1], t); // parse and convert | |||||
++j; | ++j; | ||||
} | } | ||||
else if (ex[j] == buffer[i]) { | else if (ex[j] == buffer[i]) { | ||||
++i; | |||||
++j; // consume current character | |||||
++i; // if same consume current character | |||||
++j; | |||||
} | } | ||||
else | else | ||||
return false; // Fail to parse | return false; // Fail to parse | ||||
@@ -313,8 +348,8 @@ class BG95_base | |||||
//! @} | //! @} | ||||
private: | private: | ||||
equeue<char, N> rx_q{}; | |||||
std::atomic<size_t> streams_{}; | |||||
equeue<char, N, true> rx_q{}; | |||||
std::atomic<size_t> streams_{}; | |||||
}; | }; | ||||
} // namespace tbx; | } // namespace tbx; | ||||
@@ -221,6 +221,19 @@ build-clang: $(BUILD_DIR)/$(TARGET) | |||||
.PHONY: debug | .PHONY: debug | ||||
debug: $(BUILD_DIR)/$(TARGET) | debug: $(BUILD_DIR)/$(TARGET) | ||||
.PHONY: test_asan | |||||
test_asan: CFLAGS += -fsanitize=address -fsanitize=leak -fsanitize=bounds-strict | |||||
test_asan: LDFLAGS += -fsanitize=address -fsanitize=leak -fsanitize=bounds-strict | |||||
test_asan: $(BUILD_DIR)/$(TARGET) | |||||
.PHONY: test_tsan | |||||
test_tsan: CFLAGS += -fsanitize=thread | |||||
test_tsan: LDFLAGS += -fsanitize=thread | |||||
test_tsan: $(BUILD_DIR)/$(TARGET) | |||||
.PHONY: release | .PHONY: release | ||||
release: CFLAGS := $(REL_CFLAGS) | release: CFLAGS := $(REL_CFLAGS) | ||||
release: $(BUILD_DIR)/$(TARGET) | release: $(BUILD_DIR)/$(TARGET) | ||||
@@ -28,185 +28,6 @@ | |||||
#include <gtest/gtest.h> | #include <gtest/gtest.h> | ||||
#include <exception> | #include <exception> | ||||
//#include <drv/BG95_base.h> | |||||
//#include <gtest/gtest.h> | |||||
//#include <cont/equeue.h> | |||||
////#include <map> | |||||
// | |||||
////#include <iostream> | |||||
//#include <cstring> | |||||
//#include <utility> | |||||
// | |||||
//#ifndef WIN_TRHEADS | |||||
//#include <mutex> | |||||
//#include <thread> | |||||
//#else | |||||
//#include <mingw.thread.h> | |||||
//#include <mingw.mutex.h> | |||||
//#endif | |||||
// | |||||
//using namespace tbx; | |||||
// | |||||
//using Q = equeue<char, 512, true>; | |||||
// | |||||
//// BG95 implementer mock | |||||
//class BG95 : public BG95_base<BG95, Q, 256> { | |||||
// using base_type = BG95_base<BG95, Q, 256>; | |||||
// | |||||
// public: | |||||
// enum class event { | |||||
// MQTT_DISCONNECT, MQTT_RXDATA | |||||
// }; | |||||
// // simulated modem operation | |||||
// private: | |||||
// struct cmd_pair { | |||||
// const char *cmd; | |||||
// const char *resp; | |||||
// }; | |||||
// struct event_pair { | |||||
// event e; | |||||
// const char* resp; | |||||
// }; | |||||
// | |||||
// std::array<cmd_pair, 19> cmd_map = {{ | |||||
// {"ERROR", "\r\nERROR\r\n"}, | |||||
// {"ATE0\r\n", "\r\nATE0\r\nOK\r\n"}, | |||||
// {"AT\r\n", "\r\nOK\r\n"}, | |||||
// {"AT+QCFG=\"nwscanseq\"\r\n", "\r\n+QCFG: \"nwscanseq\",020301\r\n"}, | |||||
// {"AT+QCFG=\"nwscanseq\",010302\r\n", "\r\nOK\r\n"}, | |||||
// {"AT+CREG?\r\n", "\r\n+CREG: 0,5\r\n\r\nOK\r\n"}, | |||||
// {"AT+CSQ\r\n", "\r\n+CSQ: 19,99\r\n\r\nOK\r\n"}, | |||||
// {"AT+QNWINFO\r\n", "\r\n+QNWINFO: \"EDGE\",\"20201\",\"GSM 1800\",865\r\n\r\nOK\r\n"}, | |||||
// // Files | |||||
// {"AT+QFLST\r\n", "\r\n+QFLST: \"cacert.pem\",1220\r\n+QFLST: \"security/\",2\r\nOK\r\n"}, | |||||
// // MQTT config | |||||
// {"AT+QSSLCFG=\"ignorelocaltime\",2,1\r\n", "\r\nOK\r\n"}, | |||||
// {"AT+QSSLCFG=\"seclevel\",2,1\r\n", "\r\nOK\r\n"}, | |||||
// {"AT+QSSLCFG=\"sslversion\",2,4\r\n", "\r\nOK\r\n"}, | |||||
// {"AT+QSSLCFG=\"ciphersuite\",2\r\n", "\r\n+QSSLCFG: \"ciphersuite\",2,0XFFFF\r\n\r\nOK\r\n"}, | |||||
// {"AT+QMTCFG=\"ssl\",0,1,2\r\n", "\r\nOK\r\n"}, | |||||
// {"AT+QMTCFG=\"keepalive\",0,3600\r\n", "\r\nOK\r\n"}, | |||||
// // MQTT | |||||
// {"AT+QMTOPEN=0,\"server.com\",8883\r\n", "\r\nOK\r\n\r\n+QMTOPEN: 0,0\r\n"}, | |||||
// {"AT+QMTCONN=0,\"myID\",\"user\",\"pass\"\r\n", "\r\nOK\r\n\r\n+QMTCONN: 0,0,0\r\n"}, | |||||
// {"AT+QMTSUB=0,1,\"/path/topic1\",2\r\n", "\r\nOK\r\n\r\n+QMTSUB: 0,1,0,2\r\n"}, | |||||
// {"AT+QMTPUB=0,0,0,0,\"/path/topic2\",9\r\n", "\r\n> \r\nOK\r\n\r\n+QMTPUB: 0,0,0\r\n"}, | |||||
// }}; | |||||
// std::array<event_pair, 2> event_map {{ | |||||
// {event::MQTT_DISCONNECT, "\r\n+QMTSTAT: 0,1\r\n"}, | |||||
// {event::MQTT_RXDATA, "\r\n+QMTRECV: 0,1,\"/path/topic1\",\"BR: hello to all of my subscribers\""} | |||||
// }}; | |||||
// const char* cmd_responce (const char* cmd) { | |||||
// for (auto& it : cmd_map) { | |||||
// if (!strcmp(it.cmd, cmd)) | |||||
// return it.resp; | |||||
// } | |||||
// return cmd_map[0].resp; | |||||
// } | |||||
// const char* event_responce (const event e) { | |||||
// for (auto& it : event_map) { | |||||
// if (e == it.e) | |||||
// return it.resp; | |||||
// } | |||||
// return nullptr; // non reachable | |||||
// } | |||||
// | |||||
// // data | |||||
// Q rx_q{}; | |||||
// std::atomic<size_t> lines{}; | |||||
// public: | |||||
// using range_t = typename Q::range_t; | |||||
// | |||||
// public: | |||||
// // BG95_base driver requirements | |||||
// BG95() : | |||||
// rx_q(Q::data_match::MATCH_PUSH, base_type::delimiter, [&](){ | |||||
// lines.fetch_add(1, std::memory_order_acq_rel); | |||||
// }), lines(0) { } | |||||
// | |||||
// size_t get(char* data, bool wait =false) { | |||||
// do { | |||||
// if (lines.load(std::memory_order_acquire)) { | |||||
// size_t n =0; | |||||
// do{ | |||||
// *data << rx_q; | |||||
// ++n; | |||||
// } while (*data++ != base_type::delimiter); | |||||
// lines.fetch_sub(1, std::memory_order_acq_rel); | |||||
// return n; | |||||
// } | |||||
// } while (wait); | |||||
// return 0; | |||||
// } | |||||
// size_t put (const char* data, size_t n) { | |||||
// const char* reply = cmd_responce (data); | |||||
// while (*reply) | |||||
// rx_q << *reply++; | |||||
// return n; | |||||
// } | |||||
// const range_t contents() const { | |||||
// return range_t {rx_q.begin(), rx_q.end()}; | |||||
// } | |||||
// clock_t clock() { static clock_t t=0; return ++t; } | |||||
// | |||||
// // extra helper for testing purposes | |||||
// void async (event e) { | |||||
// const char* reply =event_responce (e); | |||||
// while (*reply) | |||||
// rx_q << *reply++; | |||||
// } | |||||
//}; | |||||
// | |||||
//// Behavior flag | |||||
//bool handler_flag = false; | |||||
//void handler (const char* data, size_t n) { | |||||
// (void)*data; | |||||
// (void)n; | |||||
//// std::cout << "* handler called\n"; | |||||
// handler_flag = true; | |||||
//} | |||||
//void clear_flag () { | |||||
// handler_flag = false; | |||||
//} | |||||
// | |||||
//int main(int argc, char **argv) try { | |||||
// BG95 modem; | |||||
// | |||||
// const BG95::async_handlers<2> async = {{ | |||||
// {"+QMTOPEN:", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, | |||||
// {"+QMT", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, | |||||
// }}; | |||||
// const BG95::script_t<5, 2> script = {{ | |||||
// /* 0 */{BG95::control_t::NOP, {"", BG95::match_t::NO, nullptr, BG95::action_t::GOTO, 1}, 1000}, | |||||
// /* 1 */{BG95::control_t::SEND, {"ATE0\r\n", BG95::match_t::NO, nullptr, BG95::action_t::NEXT, 0}, 0}, | |||||
// /* 2 */{BG95::control_t::EXPECT, {{ | |||||
// {"OK\r\n", BG95::match_t::ENDS_WITH, nullptr, BG95::action_t::NEXT, 0}, | |||||
// {"ERROR", BG95::match_t::CONTAINS, nullptr, BG95::action_t::EXIT_ERROR, 0} }}, | |||||
// 1000 | |||||
// }, | |||||
// /* 3 */{BG95::control_t::SEND, {"AT+CSQ\r\n", BG95::match_t::NO, nullptr, BG95::action_t::NEXT, 0}, 0}, | |||||
// /* 4 */{BG95::control_t::EXPECT, {{ | |||||
// {"OK\r\n", BG95::match_t::ENDS_WITH, nullptr, BG95::action_t::EXIT_OK, 0}, | |||||
// {"ERROR", BG95::match_t::CONTAINS, nullptr, BG95::action_t::EXIT_ERROR, 0} }}, | |||||
// 1000 | |||||
// }, | |||||
// }}; | |||||
// | |||||
// std::atomic<bool> lock(true); | |||||
// std::thread th1 ([&](){ | |||||
// do | |||||
// modem.inetd(async, false); | |||||
// while (lock.load(std::memory_order_acquire)); | |||||
// }); | |||||
// EXPECT_EQ (modem.run(script), true); | |||||
// lock.store(false, std::memory_order_acq_rel); | |||||
// th1.join(); | |||||
// | |||||
//} | |||||
//catch (std::exception& e) { | |||||
// std::cout << "Exception: " << e.what() << '\n'; | |||||
//} | |||||
GTEST_API_ int main(int argc, char **argv) try { | GTEST_API_ int main(int argc, char **argv) try { | ||||
testing::InitGoogleTest(&argc, argv); | testing::InitGoogleTest(&argc, argv); | ||||
return RUN_ALL_TESTS(); | return RUN_ALL_TESTS(); | ||||
@@ -46,11 +46,14 @@ | |||||
namespace test_bg95_base { | namespace test_bg95_base { | ||||
using namespace tbx; | using namespace tbx; | ||||
using Q = equeue<char, 512, true>; | |||||
// BG95 implementer mock | // BG95 implementer mock | ||||
class BG95 : public BG95_base<BG95, Q, 256> { | |||||
using base_type = BG95_base<BG95, Q, 256>; | |||||
template<size_t N> | |||||
class BG95 : public BG95_base<BG95<N>, equeue<char, N, true>, N> { | |||||
using Q = equeue<char, N, true>; | |||||
using base_type = BG95_base<BG95<N>, Q, N>; | |||||
public: | public: | ||||
enum class event { | enum class event { | ||||
@@ -172,18 +175,18 @@ namespace test_bg95_base { | |||||
* Test inetd in non blocking mode | * Test inetd in non blocking mode | ||||
*/ | */ | ||||
TEST(TBG95_base, inetd_non_blocking) { | TEST(TBG95_base, inetd_non_blocking) { | ||||
BG95 modem; | |||||
BG95<256> modem; | |||||
char buffer[256]; | char buffer[256]; | ||||
const BG95::async_handlers<2> async = {{ | |||||
{"+QMTOPEN:", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, | |||||
{"+QMT", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, | |||||
const BG95<256>::async_handlers<2> async = {{ | |||||
{"+QMTOPEN:", BG95<256>::match_t::STARTS_WITH, handler, BG95<256>::action_t::NO, 0}, | |||||
{"+QMT", BG95<256>::match_t::STARTS_WITH, handler, BG95<256>::action_t::NO, 0}, | |||||
}}; | }}; | ||||
clear_flag(); | clear_flag(); | ||||
modem.inetd(false, &async); | modem.inetd(false, &async); | ||||
EXPECT_EQ (handler_flag, false); | EXPECT_EQ (handler_flag, false); | ||||
modem.async(BG95::event::MQTT_DISCONNECT); | |||||
modem.async(BG95<256>::event::MQTT_DISCONNECT); | |||||
modem.inetd(false, &async); // parse "\r\n" | modem.inetd(false, &async); // parse "\r\n" | ||||
EXPECT_EQ (handler_flag, false); | EXPECT_EQ (handler_flag, false); | ||||
modem.inetd(false, &async); // parse "+QMT*\r\n" and dispatch to handler() | modem.inetd(false, &async); // parse "+QMT*\r\n" and dispatch to handler() | ||||
@@ -220,47 +223,51 @@ namespace test_bg95_base { | |||||
} | } | ||||
TEST(TBG95_base, run) { | TEST(TBG95_base, run) { | ||||
BG95 modem; | |||||
BG95<256> modem; | |||||
const BG95::async_handlers<2> async = {{ | |||||
{"+QMTOPEN:", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, | |||||
{"+QMT", BG95::match_t::STARTS_WITH, handler, BG95::action_t::NO, 0}, | |||||
const BG95<256>::async_handlers<2> async = {{ | |||||
{"+QMTOPEN:", BG95<256>::match_t::STARTS_WITH, handler, BG95<256>::action_t::NO, 0}, | |||||
{"+QMT", BG95<256>::match_t::STARTS_WITH, handler, BG95<256>::action_t::NO, 0}, | |||||
}}; | }}; | ||||
const BG95::script_t<5> script = {{ | |||||
/* 0 */{BG95::control_t::NOP, {"", BG95::match_t::NO, nullptr, BG95::action_t::GOTO, 1}, 1000}, | |||||
/* 1 */{BG95::control_t::SEND, {"ATE0\r\n", BG95::match_t::NO, nullptr, BG95::action_t::NEXT, 0}, 0}, | |||||
/* 2 */{BG95::control_t::EXPECT, {{ | |||||
{"OK\r\n", BG95::match_t::ENDS_WITH, nullptr, BG95::action_t::NEXT, 0}, | |||||
{"ERROR", BG95::match_t::CONTAINS, nullptr, BG95::action_t::EXIT_ERROR, 0} }}, | |||||
const BG95<256>::script_t<5> script = {{ | |||||
/* 0 */{BG95<256>::control_t::NOP, {"", BG95<256>::match_t::NO, nullptr, BG95<256>::action_t::GOTO, 1}, 1000}, | |||||
/* 1 */{BG95<256>::control_t::SEND, {"ATE0\r\n", BG95<256>::match_t::NO, nullptr, BG95<256>::action_t::NEXT, 0}, 0}, | |||||
/* 2 */{BG95<256>::control_t::EXPECT, {{ | |||||
{"OK\r\n", BG95<256>::match_t::ENDS_WITH, nullptr, BG95<256>::action_t::NEXT, 0}, | |||||
{"ERROR", BG95<256>::match_t::CONTAINS, nullptr, BG95<256>::action_t::EXIT_ERROR, 0} }}, | |||||
1000 | 1000 | ||||
}, | }, | ||||
/* 3 */{BG95::control_t::SEND, {"AT+CSQ\r\n", BG95::match_t::NO, nullptr, BG95::action_t::NEXT, 0}, 0}, | |||||
/* 4 */{BG95::control_t::EXPECT, {{ | |||||
{"OK\r\n", BG95::match_t::ENDS_WITH, nullptr, BG95::action_t::EXIT_OK, 0}, | |||||
{"ERROR", BG95::match_t::CONTAINS, nullptr, BG95::action_t::EXIT_ERROR, 0} }}, | |||||
/* 3 */{BG95<256>::control_t::SEND, {"AT+CSQ\r\n", BG95<256>::match_t::NO, nullptr, BG95<256>::action_t::NEXT, 0}, 0}, | |||||
/* 4 */{BG95<256>::control_t::EXPECT, {{ | |||||
{"OK\r\n", BG95<256>::match_t::ENDS_WITH, nullptr, BG95<256>::action_t::EXIT_OK, 0}, | |||||
{"ERROR", BG95<256>::match_t::CONTAINS, nullptr, BG95<256>::action_t::EXIT_ERROR, 0} }}, | |||||
1000 | 1000 | ||||
}, | }, | ||||
}}; | }}; | ||||
std::atomic<bool> lock(true); | |||||
std::mutex m; | |||||
m.lock(); | |||||
std::thread th1 ([&](){ | std::thread th1 ([&](){ | ||||
do | do | ||||
modem.inetd(false, &async); | modem.inetd(false, &async); | ||||
while (lock.load(std::memory_order_acquire)); | |||||
while (!m.try_lock()); | |||||
m.unlock(); | |||||
}); | }); | ||||
EXPECT_EQ (modem.run(script), true); | EXPECT_EQ (modem.run(script), true); | ||||
lock.store(false, std::memory_order_acq_rel); | |||||
m.unlock(); // stop and join inetd | |||||
th1.join(); | th1.join(); | ||||
} | } | ||||
TEST(TBG95_base, command) { | TEST(TBG95_base, command) { | ||||
BG95 modem; | |||||
BG95<256> modem; | |||||
std::atomic<bool> lock(true); | |||||
std::mutex m; | |||||
m.lock(); | |||||
std::thread th1 ([&](){ | std::thread th1 ([&](){ | ||||
do | do | ||||
modem.inetd(false); | modem.inetd(false); | ||||
while (lock.load(std::memory_order_acquire)); | |||||
while (!m.try_lock()); | |||||
m.unlock(); | |||||
}); | }); | ||||
int status; | int status; | ||||
EXPECT_EQ (modem.command("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n\r\nOK\r\n", status), true); | EXPECT_EQ (modem.command("AT+CREG?\r\n", "\r\n+CREG: 0,%\r\n\r\nOK\r\n", status), true); | ||||
@@ -270,7 +277,7 @@ namespace test_bg95_base { | |||||
EXPECT_EQ (modem.command("AT+CREG?\r\n", "\r\n%\r\n\r\nOK\r\n", substr), true); | EXPECT_EQ (modem.command("AT+CREG?\r\n", "\r\n%\r\n\r\nOK\r\n", substr), true); | ||||
EXPECT_EQ (strcmp("+CREG: 0,5", substr), 0); | EXPECT_EQ (strcmp("+CREG: 0,5", substr), 0); | ||||
lock.store(false, std::memory_order_acq_rel); // stop and join inetd | |||||
m.unlock(); // stop and join inetd | |||||
th1.join(); | th1.join(); | ||||
} | } | ||||