diff --git a/TestConcoreHpp.cpp b/TestConcoreHpp.cpp index 54df52b..64c7722 100644 --- a/TestConcoreHpp.cpp +++ b/TestConcoreHpp.cpp @@ -97,6 +97,20 @@ static void test_read_FM_missing_file_uses_initstr() { check("missing_file fallback val==3.0", approx(v[0], 3.0)); } +static void test_read_result_missing_file_status() { + setup_dirs(); + rm("in1/no_port_status"); + + Concore c; + c.delay = 0; + c.simtime = 0.0; + + Concore::ReadResult r = c.read_result(1, "no_port_status", "[9.0,3.0]"); + check("read_result missing status FILE_NOT_FOUND", + r.status == Concore::ReadStatus::FILE_NOT_FOUND); + check("read_result missing uses initstr", r.data.size() == 1 && approx(r.data[0], 3.0)); +} + // ------------- write_FM -------------------------------------------------- static void test_write_FM_creates_file() { @@ -274,6 +288,20 @@ static void test_tryparam_missing_key_uses_default() { c.tryparam("no_key", "def_val") == "def_val"); } +static void test_load_params_semicolon_format() { + setup_dirs(); + write_file("in/1/concore.params", "a=1;b=2"); + + Concore c; + c.delay = 0; + c.load_params(); + + check("load_params semicolon parses a", c.tryparam("a", "") == "1"); + check("load_params semicolon parses b", c.tryparam("b", "") == "2"); + + rm("in/1/concore.params"); +} + // ------------- main ------------------------------------------------------- int main() { @@ -282,6 +310,7 @@ int main() { // read_FM / write_FM test_read_FM_file(); test_read_FM_missing_file_uses_initstr(); + test_read_result_missing_file_status(); test_write_FM_creates_file(); // unchanged() @@ -307,6 +336,7 @@ int main() { // tryparam() test_tryparam_found(); test_tryparam_missing_key_uses_default(); + test_load_params_semicolon_format(); std::cout << "\n=== Results: " << passed << " passed, " << failed << " failed out of " << (passed + failed) << " tests ===\n"; diff --git a/concore.hpp b/concore.hpp index 5eb2b7e..eb88108 100644 --- a/concore.hpp +++ b/concore.hpp @@ -54,6 +54,19 @@ class Concore{ #endif public: + enum class ReadStatus { + SUCCESS, + TIMEOUT, + PARSE_ERROR, + FILE_NOT_FOUND, + RETRIES_EXCEEDED + }; + + struct ReadResult { + ReadStatus status; + vector data; + }; + double delay = 1; int retrycount = 0; double simtime; @@ -61,6 +74,7 @@ class Concore{ map iport; map oport; map params; + ReadStatus last_read_status = ReadStatus::SUCCESS; /** * @brief Constructor for Concore class. @@ -372,6 +386,14 @@ class Concore{ return read_FM(port, name, initstr); } + ReadResult read_result(int port, string name, string initstr) + { + ReadResult result; + result.data = read(port, name, initstr); + result.status = last_read_status; + return result; + } + /** * @brief Reads data from a specified port and name using the FM (File Method) communication protocol. * @param port The port number. @@ -383,6 +405,7 @@ class Concore{ chrono::milliseconds timespan((int)(1000*delay)); this_thread::sleep_for(timespan); string ins; + ReadStatus status = ReadStatus::SUCCESS; try { ifstream infile; infile.open(inpath+to_string(port)+"/"+name, ios::in); @@ -393,10 +416,13 @@ class Concore{ infile.close(); } else { + status = ReadStatus::FILE_NOT_FOUND; throw 505;} } catch (...) { ins = initstr; + if (status == ReadStatus::SUCCESS) + status = ReadStatus::FILE_NOT_FOUND; } int retry = 0; @@ -424,14 +450,24 @@ class Concore{ } retry++; } + if ((int)ins.length()==0) + status = ReadStatus::RETRIES_EXCEEDED; s += ins; vector inval = parser(ins); - if(inval.empty()) + if(inval.empty()) { + if (status == ReadStatus::SUCCESS) + status = ReadStatus::PARSE_ERROR; inval = parser(initstr); - if(inval.empty()) + } + if(inval.empty()) { + if (status == ReadStatus::SUCCESS) + status = ReadStatus::PARSE_ERROR; + last_read_status = status; return inval; + } simtime = simtime > inval[0] ? simtime : inval[0]; + last_read_status = status; //returning a string with data excluding simtime inval.erase(inval.begin()); @@ -450,6 +486,7 @@ class Concore{ chrono::milliseconds timespan((int)(1000*delay)); this_thread::sleep_for(timespan); string ins = ""; + ReadStatus status = ReadStatus::SUCCESS; try { if (shmId_get != -1) { if (sharedData_get && sharedData_get[0] != '\0') { @@ -463,10 +500,13 @@ class Concore{ } else { + status = ReadStatus::FILE_NOT_FOUND; throw 505; } } catch (...) { ins = initstr; + if (status == ReadStatus::SUCCESS) + status = ReadStatus::FILE_NOT_FOUND; } int retry = 0; @@ -490,14 +530,24 @@ class Concore{ } retry++; } + if ((int)ins.length()==0) + status = ReadStatus::RETRIES_EXCEEDED; s += ins; vector inval = parser(ins); - if(inval.empty()) + if(inval.empty()) { + if (status == ReadStatus::SUCCESS) + status = ReadStatus::PARSE_ERROR; inval = parser(initstr); - if(inval.empty()) + } + if(inval.empty()) { + if (status == ReadStatus::SUCCESS) + status = ReadStatus::PARSE_ERROR; + last_read_status = status; return inval; + } simtime = simtime > inval[0] ? simtime : inval[0]; + last_read_status = status; //returning a string with data excluding simtime inval.erase(inval.begin()); @@ -674,15 +724,26 @@ class Concore{ * @return a vector of double values */ vector read_ZMQ(string port_name, string name, string initstr) { + ReadStatus status = ReadStatus::SUCCESS; auto it = zmq_ports.find(port_name); if (it == zmq_ports.end()) { cerr << "read_ZMQ: port '" << port_name << "' not initialized" << endl; + status = ReadStatus::FILE_NOT_FOUND; + last_read_status = status; return parser(initstr); } vector inval = it->second->recv_with_retry(); - if (inval.empty()) + if (inval.empty()) { + status = ReadStatus::TIMEOUT; inval = parser(initstr); - if (inval.empty()) return inval; + } + if (inval.empty()) { + if (status == ReadStatus::SUCCESS) + status = ReadStatus::PARSE_ERROR; + last_read_status = status; + return inval; + } + last_read_status = status; simtime = simtime > inval[0] ? simtime : inval[0]; s += port_name; inval.erase(inval.begin()); @@ -736,6 +797,13 @@ class Concore{ return read_ZMQ(port_name, name, initstr); } + ReadResult read_result(string port_name, string name, string initstr) { + ReadResult result; + result.data = read(port_name, name, initstr); + result.status = last_read_status; + return result; + } + /** * @brief deviate the write to ZMQ communication protocol when port identifier is a string key. * @param port_name The ZMQ port name. diff --git a/concore_base.hpp b/concore_base.hpp index b4d9689..f38b6ed 100644 --- a/concore_base.hpp +++ b/concore_base.hpp @@ -345,10 +345,11 @@ inline std::map load_params(const std::string& params_ // Otherwise convert semicolon-separated key=value to dict format // e.g. "a=1;b=2" -> {"a":"1","b":"2"} + std::string normalized = std::regex_replace(sparams, std::regex(";"), ","); std::string converted = "{\"" + std::regex_replace( std::regex_replace( - std::regex_replace(sparams, std::regex(","), ",\""), + std::regex_replace(normalized, std::regex(","), ",\""), std::regex("="), "\":"), std::regex(" "), "") + "}"; diff --git a/concoredocker.hpp b/concoredocker.hpp index 651e5b3..51d6ca5 100644 --- a/concoredocker.hpp +++ b/concoredocker.hpp @@ -34,6 +34,19 @@ class Concore { int communication_oport = 0; // oport refers to output port public: + enum class ReadStatus { + SUCCESS, + TIMEOUT, + PARSE_ERROR, + FILE_NOT_FOUND, + RETRIES_EXCEEDED + }; + + struct ReadResult { + ReadStatus status; + std::vector data; + }; + std::unordered_map iport; std::unordered_map oport; std::string s, olds; @@ -44,6 +57,7 @@ class Concore { double simtime = 0; double maxtime = 100; std::unordered_map params; + ReadStatus last_read_status = ReadStatus::SUCCESS; #ifdef CONCORE_USE_ZMQ std::map zmq_ports; #endif @@ -268,6 +282,7 @@ class Concore { if (communication_iport == 1) return read_SM(port, name, initstr); #endif + ReadStatus status = ReadStatus::SUCCESS; std::this_thread::sleep_for(std::chrono::seconds(delay)); std::string file_path = inpath + "/" + std::to_string(port) + "/" + name; std::ifstream infile(file_path); @@ -275,7 +290,10 @@ class Concore { if (!infile) { std::cerr << "File " << file_path << " not found, using default value.\n"; - return concore_base::parselist_double(initstr); + status = ReadStatus::FILE_NOT_FOUND; + std::vector fallback = concore_base::parselist_double(initstr); + last_read_status = status; + return fallback; } std::getline(infile, ins); @@ -291,22 +309,38 @@ class Concore { if (ins.empty()) { std::cerr << "Max retries reached for " << file_path << ", using default value.\n"; - return concore_base::parselist_double(initstr); + status = ReadStatus::RETRIES_EXCEEDED; + std::vector fallback = concore_base::parselist_double(initstr); + last_read_status = status; + return fallback; } s += ins; std::vector inval = concore_base::parselist_double(ins); - if (inval.empty()) + if (inval.empty()) { + status = ReadStatus::PARSE_ERROR; inval = concore_base::parselist_double(initstr); - if (inval.empty()) + } + if (inval.empty()) { + last_read_status = status; return inval; + } + last_read_status = status; simtime = simtime > inval[0] ? simtime : inval[0]; inval.erase(inval.begin()); return inval; } + ReadResult read_result(int port, const std::string& name, const std::string& initstr) { + ReadResult result; + result.data = read(port, name, initstr); + result.status = last_read_status; + return result; + } + #ifdef __linux__ std::vector read_SM(int port, const std::string& name, const std::string& initstr) { + ReadStatus status = ReadStatus::SUCCESS; std::this_thread::sleep_for(std::chrono::seconds(delay)); std::string ins; try { @@ -315,6 +349,7 @@ class Concore { else throw 505; } catch (...) { + status = ReadStatus::FILE_NOT_FOUND; ins = initstr; } @@ -335,13 +370,21 @@ class Concore { } retry++; } + if ((int)ins.length() == 0) + status = ReadStatus::RETRIES_EXCEEDED; s += ins; std::vector inval = concore_base::parselist_double(ins); - if (inval.empty()) + if (inval.empty()) { + if (status == ReadStatus::SUCCESS) + status = ReadStatus::PARSE_ERROR; inval = concore_base::parselist_double(initstr); - if (inval.empty()) + } + if (inval.empty()) { + last_read_status = status; return inval; + } + last_read_status = status; simtime = simtime > inval[0] ? simtime : inval[0]; inval.erase(inval.begin()); return inval; @@ -403,15 +446,26 @@ class Concore { } std::vector read_ZMQ(const std::string& port_name, const std::string& name, const std::string& initstr) { + ReadStatus status = ReadStatus::SUCCESS; auto it = zmq_ports.find(port_name); if (it == zmq_ports.end()) { std::cerr << "read_ZMQ: port '" << port_name << "' not initialized\n"; + status = ReadStatus::FILE_NOT_FOUND; + last_read_status = status; return concore_base::parselist_double(initstr); } std::vector inval = it->second->recv_with_retry(); - if (inval.empty()) + if (inval.empty()) { + status = ReadStatus::TIMEOUT; inval = concore_base::parselist_double(initstr); - if (inval.empty()) return inval; + } + if (inval.empty()) { + if (status == ReadStatus::SUCCESS) + status = ReadStatus::PARSE_ERROR; + last_read_status = status; + return inval; + } + last_read_status = status; simtime = simtime > inval[0] ? simtime : inval[0]; s += port_name; inval.erase(inval.begin()); @@ -433,6 +487,13 @@ class Concore { return read_ZMQ(port_name, name, initstr); } + ReadResult read_result(const std::string& port_name, const std::string& name, const std::string& initstr) { + ReadResult result; + result.data = read(port_name, name, initstr); + result.status = last_read_status; + return result; + } + void write(const std::string& port_name, const std::string& name, std::vector val, int delta = 0) { return write_ZMQ(port_name, name, val, delta); }