From 66c53a27fc3f3515be72f5d40c590ff62a3eb872 Mon Sep 17 00:00:00 2001 From: peri4 Date: Mon, 18 Mar 2013 12:07:44 +0400 Subject: [PATCH] 18.03.2013 - Bug fixes, add in/out speed diagnostic to PIProtocol, fixed PIConsole tab switch segfault, PIObject EVENT / EVENT_HANDLER mechanism update - new EVENT macros that use EVENT_HANDLER with raiseEvent implementation. This allow compile check event for CONNECT and use EVENT as CONNECT target, also raise event now is simple execute EVENT function. --- CMakeLists.txt | 11 +- clean | 22 +- main.cpp | 264 ++++-- make.sh | 2 +- make_install.sh | 2 +- pibitarray.h | 2 +- pibytearray.cpp | 50 +- pibytearray.h | 51 +- pichar.h | 27 +- picli.cpp | 4 +- picli.h | 2 +- picodec.cpp | 2 +- picodec.h | 2 +- piconfig.cpp | 5 +- piconfig.h | 2 +- piconsole.cpp | 159 ++-- piconsole.h | 77 +- picontainers.h | 26 +- picrc.h | 216 +++++ pidir.cpp | 30 +- pidir.h | 23 +- piethernet.cpp | 351 +++++++- piethernet.h | 82 +- pievaluator.cpp | 2 +- pievaluator.h | 2 +- pifile.cpp | 24 +- pifile.h | 57 +- pigeometry.h | 2 +- piincludes.cpp | 6 +- piincludes.h | 185 +++- piiodevice.cpp | 3 +- piiodevice.h | 34 +- pikbdlistener.cpp | 3 +- pikbdlistener.h | 24 +- pimath.cpp | 1134 ++++++++++++++++++++---- pimath.h | 203 +++-- pimonitor.cpp | 2 +- pimonitor.h | 2 +- pimultiprotocol.h | 6 +- pimutex.h | 2 +- piobject.cpp | 4 +- piobject.h | 120 ++- pip.h | 4 +- pipacketextractor.cpp | 25 +- pipacketextractor.h | 9 +- pipeer.cpp | 186 ++++ pipeer.h | 81 ++ piprocess.cpp | 29 +- piprocess.h | 37 +- piprotocol.cpp | 145 ++- piprotocol.h | 102 ++- piserial.cpp | 190 +++- piserial.h | 138 ++- pisignals.cpp | 2 +- pisignals.h | 2 +- pistring.cpp | 58 +- pistring.h | 123 ++- pisystemmonitor.cpp | 4 +- pisystemmonitor.h | 14 +- pisystemtests.cpp | 44 + pimultiprotocol.cpp => pisystemtests.h | 24 +- pithread.cpp | 63 +- pithread.h | 24 +- pitimer.cpp | 368 +++++++- pitimer.h | 160 +++- pivariable.cpp | 2 +- pivariable.h | 2 +- protocols.conf | 192 ++++ system_test/CMakeLists.txt | 6 + system_test/main.cpp | 106 +++ Описание.odt | Bin 115746 -> 112704 bytes Описание.pdf | Bin 72 files changed, 4407 insertions(+), 960 deletions(-) mode change 100755 => 100644 main.cpp mode change 100755 => 100644 make.sh mode change 100755 => 100644 pibitarray.h mode change 100755 => 100644 pibytearray.cpp mode change 100755 => 100644 pibytearray.h mode change 100755 => 100644 pichar.h mode change 100755 => 100644 picli.cpp mode change 100755 => 100644 picli.h mode change 100755 => 100644 picodec.cpp mode change 100755 => 100644 picodec.h mode change 100755 => 100644 piconfig.cpp mode change 100755 => 100644 piconfig.h mode change 100755 => 100644 piconsole.cpp mode change 100755 => 100644 piconsole.h mode change 100755 => 100644 picontainers.h create mode 100644 picrc.h mode change 100755 => 100644 pidir.cpp mode change 100755 => 100644 pidir.h mode change 100755 => 100644 piethernet.cpp mode change 100755 => 100644 piethernet.h mode change 100755 => 100644 pievaluator.cpp mode change 100755 => 100644 pievaluator.h mode change 100755 => 100644 pifile.cpp mode change 100755 => 100644 pifile.h mode change 100755 => 100644 pigeometry.h mode change 100755 => 100644 piincludes.h mode change 100755 => 100644 piiodevice.cpp mode change 100755 => 100644 piiodevice.h mode change 100755 => 100644 pikbdlistener.cpp mode change 100755 => 100644 pikbdlistener.h mode change 100755 => 100644 pimath.cpp mode change 100755 => 100644 pimath.h mode change 100755 => 100644 pimonitor.cpp mode change 100755 => 100644 pimonitor.h mode change 100755 => 100644 pimultiprotocol.h mode change 100755 => 100644 pimutex.h mode change 100755 => 100644 piobject.cpp mode change 100755 => 100644 piobject.h mode change 100755 => 100644 pip.h create mode 100644 pipeer.cpp create mode 100644 pipeer.h mode change 100755 => 100644 piprocess.cpp mode change 100755 => 100644 piprocess.h mode change 100755 => 100644 piprotocol.cpp mode change 100755 => 100644 piprotocol.h mode change 100755 => 100644 piserial.cpp mode change 100755 => 100644 piserial.h mode change 100755 => 100644 pisignals.cpp mode change 100755 => 100644 pisignals.h mode change 100755 => 100644 pistring.cpp mode change 100755 => 100644 pistring.h mode change 100755 => 100644 pisystemmonitor.cpp mode change 100755 => 100644 pisystemmonitor.h create mode 100644 pisystemtests.cpp rename pimultiprotocol.cpp => pisystemtests.h (61%) mode change 100755 => 100644 mode change 100755 => 100644 pithread.cpp mode change 100755 => 100644 pithread.h mode change 100755 => 100644 pivariable.cpp mode change 100755 => 100644 pivariable.h create mode 100644 protocols.conf create mode 100644 system_test/CMakeLists.txt create mode 100644 system_test/main.cpp mode change 100755 => 100644 Описание.odt mode change 100755 => 100644 Описание.pdf diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b95cf08..036cde4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,22 +1,23 @@ project(pip) find_package(Qt4) cmake_minimum_required(VERSION 2.6) -include_directories(${CMAKE_CURRENT_SOURCE_DIR} . ${QT_INCLUDES}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${QT_INCLUDES} . ../peri4_widgets) file(GLOB CPPS "pi*.cpp") if (${WIN32}) add_definitions(-Wall -O2) else (${WIN32}) - add_definitions(-Wall -O2 -g3) + add_definitions(-Wall -O2 -g3 --fast-math) endif (${WIN32}) add_library(pip SHARED ${CPPS}) if (${WIN32}) - target_link_libraries(pip pthread ws2_32) + target_link_libraries(pip pthread ws2_32 Iphlpapi) else (${WIN32}) target_link_libraries(pip pthread rt) endif (${WIN32}) add_executable(pip_test "main.cpp") if (${WIN32}) - target_link_libraries(pip_test pthread ws2_32 pip) + target_link_libraries(pip_test pthread ws2_32 Iphlpapi pip) else (${WIN32}) - target_link_libraries(pip_test pthread rt pip ${QT_QTCORE_LIBRARY}) + target_link_libraries(pip_test pip ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} pcollection) endif (${WIN32}) +add_subdirectory(system_test) diff --git a/clean b/clean index ac8674c1..395a12c3 100755 --- a/clean +++ b/clean @@ -1,19 +1,7 @@ -#! /bin/bash +#! /bin/sh VERBOSE=1 make clean -rm -vf ./lib/* -#for i in $( ls -1 ); do -# if [ "`ls -1 --file-type | grep $i | grep -o /`" = "/" ]; then -# cd $i -# rm -rvf ./CMakeFiles -# rm -vf ./CMakeCache.txt ./Makefile ./cmake_install.cmake ./install_manifest.txt ./*~ -# cd ../ -# fi -#done rm -rvf ./CMakeFiles -rm -vf ./CMakeCache.txt ./Makefile ./cmake_install.cmake ./install_manifest.txt ./*~ ./*cxx ./moc_* ./*.o -#cd ./include -#for i in $( ls -1 ); do -# if [ "`ls -1 --file-type $i | grep -v @`" = "" ]; then -# rm -v $i -# fi -#done +rm -vf ./CMakeCache.txt ./Makefile ./cmake_install.cmake ./install_manifest.txt ./*~ ./*cxx ./moc_* ./*.o ./core +cd system_test +rm -rvf ./CMakeFiles +rm -vf ./CMakeCache.txt ./Makefile ./cmake_install.cmake ./install_manifest.txt ./*~ ./*cxx ./moc_* ./*.o ./core diff --git a/main.cpp b/main.cpp old mode 100755 new mode 100644 index c7b55c43..cca29f94 --- a/main.cpp +++ b/main.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Test program - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -63,8 +63,7 @@ void timerEvent(void * data, int delim) { cout << " tick from constuctor, delimiter = " << delim << ", data = " << data << endl; }; -bool -t2 = false; +bool t2 = false; void timerEvent2(void * data, int delim) { t2 = true; cout << " tick from delimiter " << delim << ", data = " << data << endl; @@ -86,12 +85,16 @@ public: ObjectTest2(const PIString & name = PIString()): PIObject(name) {;} void raise0(const PIString & e) {cout << " event \"" << e << "\" from \"" << name() << "\"" << endl; raiseEvent(this, e);} void raise2(const PIString & e, int i, const PIString & s) {cout << " event \"" << e << "\" from \"" << name() << "\"" << endl; raiseEvent(this, e, i, s);} + EVENT(ObjectTest2, event0) + EVENT(ObjectTest2, event2) + }; class CA: public PIObject { public: CA(const PIString & n): PIObject(n) {;} EVENT_HANDLER(CA, void, handler_ca) {a = true; cout << " handler CA" << endl;} + EVENT(CA, event_ca) bool a; }; @@ -124,60 +127,212 @@ struct Packet { int cs; }; -bool retH(void * d, uchar * src, uchar * rec, int size) { - return (*((int*)rec)) == 1; + +#pragma pack(push,1) + +struct InpuData { + uint first_number; // Номер первого отсчета + struct { + uchar packet_number: 7; // Идентификационный код пакета + uchar packet_last : 1; // Признак последнего пакета + }; + struct { + uchar antenna_number : 3; // Номер антенного канала + uchar frequency_number: 3; // Номер частотного канала + uchar data_type : 2; // Вид передаваемой информации: 0 – оцифровка, 1 – измеренные параметры + }; + uchar data[122]; // Данные +}; + +struct msgHeader { + char sign[4]; + unsigned short size; + unsigned short cnt; + unsigned char fragment; + unsigned short msg_id; + unsigned char sys_id; + unsigned char subsys_id; + unsigned char security; + bool verify_sign() + { + bool ok; + ok = sign[0] == 'B'; + ok = ok && sign[1] == 'R'; + ok = ok && sign[2] == 'K'; + ok = ok && sign[3] == 'D'; + return ok; + } +}; +struct FilterCommand { + FilterCommand() { + memset(this, 0, sizeof(FilterCommand)); + header.sign[0] = 'B'; header.sign[1] = 'R'; header.sign[2] = 'K'; header.sign[3] = 'D'; + header.size = sizeof(FilterCommand); + header.cnt = header.fragment = header.security = 0; + header.msg_id = 2102; + header.sys_id = header.subsys_id = 2; + command = 3; + } + + msgHeader header; + uchar command; // 3 + struct { + uchar fi_number: 7; // номер частотного участка, 1 - 36 + uchar bort : 1; // борт: 0 - левый, 1 - правый + }; + ushort freq_start; // начальная частота, 0 - 6100 + ushort freq_end; // конечная частота, 0 - 6100 + uchar filter_bandwith; // полоса проспускания фильтра + ushort filter_time_step; // время шага фильтра по времени + uchar reserve; + ushort record_time_start; // начальное время записи, 1 - 60 мс + ushort record_time_end; // конечное время записи, 1 - 60 мс + uchar record_channels; // номера каналов для записи, 0 - 5 биты + uchar play_channels; // номера каналов для воспроизведения, 0 - 5 биты + uchar signal_type; // тип сигнала для выдачи, 0 - С3, 1 - С4, 2 - С4Ф, 3 - выборка + ushort play_time_start; // начальное время воспроизведения, 1 - 60 мс + ushort play_time_end; // конечное время воспроизведения, 1 - 60 мс + uchar checksum; }; -bool retF(void * d, uchar * data, int size) { - cout << "rec " << size << endl; - return true; -}; +#pragma pack(pop) +void te(void*, int); +PITimer tm_(te); +PISerial ser("/dev/ttyS0"); +bool pins[9]; +void te(void*, int) { + for (int i = 1; i <= 9; ++i) + pins[i - 1] = ser.isPin(i); +} +void ke(char key, void*) { + int p = key - '0'; + if (key >= '1' && key <= '9') + ser.setPin(p, !pins[p - 1]); +} int main(int argc, char * argv[]) { - /*Packet p, mp; - p.from = mp.from = 1; - p.to = mp.to = 2; - PISerial ser("/dev/ttyS0"); - ser.setSpeed(PISerial::S115200); + /*tm_.start(10.); + PIConsole con(false, ke); + con.enableExitCapture(); + //ser.setParameter(PISerial::HardwareFlowControl); ser.open(); - ser.setReadIsBlocking(true); - PIPacketExtractor pe(&ser, &mp, 8, 8); - pe.setThreadedReadSlot(retF); - pe.setHeaderCheckSlot(retH); - pe.startThreadedRead(); - ser.write(&p, sizeof(p)); - p.from = 2; - ser.write(&p, sizeof(p)); - msleep(1000); - exit(0);*/ - PICodec codec; + con.addVariable("1 (CAR)", pins); + con.addVariable("2 (SR) ", pins + 1); + con.addVariable("3 (ST) ", pins + 2); + con.addVariable("4 (DTR)", pins + 3); + con.addVariable("5 (GND)", pins + 4); + con.addVariable("6 (DSR)", pins + 5); + con.addVariable("7 (RTS)", pins + 6); + con.addVariable("8 (CTS)", pins + 7); + con.addVariable("9 (RNG)", pins + 8); + con.start(); + con.waitForFinish(); + return 0;*/ + + /*FilterCommand fc; + PIEthernet eth(PIEthernet::TCP_SingleTCP); + eth.open("127.0.0.1:10201"); + while (1) { + eth.read(&fc, sizeof(fc)); + cout << int(fc.fi_number) << ", " << int(fc.bort) << ", " << int(fc.freq_start) << ", " << int(fc.freq_end) << ", " << int(fc.play_channels) << ", " << int(fc.record_channels) << endl; + } + return 0;*/ - exit(0); -/* - vec my_v; vector stl_v; QVector qt_v; - double el; - PITimer tm; + /*PIDir dir(argv[1]); + FILE*f = popen("cd ../ && pwd", "w"); + char fd[4096]; + int ret = fread(fd, 4096, 1, f); + pclose(f); + cout << PIString(fd, ret); + f = popen("cd ../ && pwd", "w"); + ret = fread(fd, 4096, 1, f); + pclose(f); + cout << PIString(fd, ret); + return 0; + cout << dir.path() << endl; + dir.up(); + cout << dir.path() << endl; + PIVector ent = dir.entries(); + piForeachC (PIDir::DirEntry & i, ent) + cout << i.mode << " " << PIString::readableSize(i.size).expandRightTo(10, ' ') << " " << i.name << endl;*/ + + /*PISystemTime st_time, inc_time; + PITimer tm_; + tm_.reset(); + inc_time = PISystemTime::fromMilliseconds(50); + st_time = currentSystemTime() + inc_time; + int cnt = 100; + while (cnt--) { + (st_time - currentSystemTime()).sleep(); + { + msleep(49); + cout << "tick " << cnt << endl; + } + st_time += inc_time; + } + cout << tm_.elapsed_s() - 0.049 << endl;*/ + + /*PISystemTime t0, inc_time; + PITimer tm_; + tm_.reset(); + inc_time = PISystemTime::fromMilliseconds(50); + inc_time.sleep(); + int cnt = 100; + while (cnt--) { + t0 = currentSystemTime(); + { + msleep(49); + cout << "tick " << cnt << endl; + } + (inc_time - (currentSystemTime() - t0)).sleep(); + } + cout << tm_.elapsed_s() - 0.049 << endl;*/ + + //return 0; + + /*InpuData data; + PIEthernet eth(PIEthernet::UDP); + data.first_number = 0; + data.packet_number = 0; + data.packet_last = 0; + data.antenna_number = data.frequency_number = data.data_type = 0; + PIBitArray ba_; + for (int i = 0; i < 97; ++i) { + for (int b = 0; b < 10; ++b) + ba_.push_back(((i >> b) & 1) == 1); + } + //cout << ba_.byteSize() << ", " << ba_.bitSize() << endl; + memcpy(data.data, ba_.data(), 122); + cout << ba_ << endl; + for (int i = 0; i < 122; ++i) + cout << int(data.data[i]) << ", "; + cout << endl; + eth.send("127.0.0.1:5000", &data, sizeof(data)); + return 0;*/ + + /*QApplication app(argc, argv); + PIFFT fft; + PIVector in; + in.resize(50*1000); + for (int i=400; i<404; i++) in[i] = complexd(1,0); + PITimer timer_; + fft.prepareFFT(in.size()); + timer_.reset(); + PIVector * out = fft.calcFFT(in); + cout << timer_.elapsed_m() << endl; + Graphic * g = new Graphic(); + QVector res; + fft.getAmplitude(); + for (int i=0; isize(); i++) res.append(QPointF(i,abs(out->at(i)))); + g->setGraphicData(res); + g->addGraphic("arg", Qt::darkBlue); + res.clear(); + for (int i=0; i< out->size(); i++) res.append(QPointF(i,arg(out->at(i)))); + g->setGraphicData(res, 1); + g->show(); - tm.reset(); - for (uint i = 0; i < 500000; ++i) - my_v.push_back(i); - el = tm.elapsed_m(); - cout << el << endl; - - tm.reset(); - for (uint i = 0; i < 500000; ++i) - stl_v.push_back(i); - el = tm.elapsed_m(); - cout << el << endl; - - tm.reset(); - for (uint i = 0; i < 500000; ++i) - qt_v.append(i); - el = tm.elapsed_m(); - cout << el << endl; - - exit(0); -*/ + return app.exec();*/ + bool r_string = true, r_thread = true, r_mutex = true, r_timer = true, r_file = true, r_eval = true, r_event = true; bool succ = true; cout << "== PIP test program ==" << endl; @@ -189,8 +344,10 @@ int main(int argc, char * argv[]) { cout << " to char * = \"" << string.data() << "\"" << endl; cout << " to std::string = \"" << string.stdString() << "\"" << endl; if (string.stdString().length() != 11) succ = r_string = false; +#ifdef HAS_LOCALE cout << " to std::wstring = \"" << string.stdWString() << "\"" << endl; if (string.stdWString().length() != 11) succ = r_string = false; +#endif if (succ) cout << " convertions success" << endl; else cout << " convertions fail" << endl; succ = true; @@ -199,9 +356,11 @@ int main(int argc, char * argv[]) { if (string.length() != 5) succ = r_string = false; cout << " to char * = \"" << string.data() << "\"" << endl; cout << " to std::string = \"" << string.stdString() << "\"" << endl; - if (string.stdString().length() != 10) succ = r_string = false; + if (string.stdString().length() != 11) succ = r_string = false; +#ifdef HAS_LOCALE cout << " to std::wstring = \"" << string.stdWString() << "\"" << endl; if (string.stdWString().length() != 5) succ = r_string = false; +#endif if (succ) cout << " complex convertions success" << endl; else cout << " complex convertions fail" << endl; if (r_string) cout << "== Success ==" << endl; @@ -244,6 +403,7 @@ int main(int argc, char * argv[]) { cout << " file \"" << file.path() << "\" is "; if (!file.isOpened()) cout << "not "; cout << "opened" << endl; + file.clear(); file << "test string"; cout << " write " << file.pos() << " bytes" << endl; if (file.pos() != 11) r_file = false; @@ -264,7 +424,7 @@ int main(int argc, char * argv[]) { cout << " error = \"" << evaluator.error() << '\"' << endl; cout << " \"x\" = " << evaluator.content.variable("x").value << endl; cout << " result = " << evaluator.evaluate() << endl; - r_eval = round(evaluator.lastResult()) == complexd(9., 12.); + r_eval = round(evaluator.lastResult()) == complexd(6., 9.); if (r_eval) cout << "== Success ==" << endl; else cout << "== Fail ==" << endl; diff --git a/make.sh b/make.sh old mode 100755 new mode 100644 index d218016a..64fcb2ab --- a/make.sh +++ b/make.sh @@ -1,3 +1,3 @@ -#! /bin/bash +#! /bin/sh cmake ./ make $@ diff --git a/make_install.sh b/make_install.sh index 9e65aa38..2f5f4628 100755 --- a/make_install.sh +++ b/make_install.sh @@ -1,4 +1,4 @@ -#! /bin/bash +#! /bin/sh cmake . make $@ cp -vf *.h /usr/include/ diff --git a/pibitarray.h b/pibitarray.h old mode 100755 new mode 100644 index f67bd600..807c237a --- a/pibitarray.h +++ b/pibitarray.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Bit array - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/pibytearray.cpp b/pibytearray.cpp old mode 100755 new mode 100644 index 589a4b7f..127755e9 --- a/pibytearray.cpp +++ b/pibytearray.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Byte array - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -59,29 +59,29 @@ PIByteArray & PIByteArray::convertToBase64() { if (size() == 0) return *this; int sz = (size_s() / 3) * 3; for (int i = 0; i < sz; ++i) { - hs.byte0 = hs.byte1 = hs.byte2 = 0; - hs.byte0 = at(i); - hs.byte1 = at(++i); - hs.byte2 = at(++i); - t.push_back(base64Table[hs.ascii0]); - t.push_back(base64Table[hs.ascii1]); - t.push_back(base64Table[hs.ascii2]); - t.push_back(base64Table[hs.ascii3]); + hs.byte.byte0 = hs.byte.byte1 = hs.byte.byte2 = 0; + hs.byte.byte0 = at(i); + hs.byte.byte1 = at(++i); + hs.byte.byte2 = at(++i); + t.push_back(base64Table[hs.ascii.ascii0]); + t.push_back(base64Table[hs.ascii.ascii1]); + t.push_back(base64Table[hs.ascii.ascii2]); + t.push_back(base64Table[hs.ascii.ascii3]); } - hs.byte0 = hs.byte1 = hs.byte2 = 0; sz = size() % 3; + hs.byte.byte0 = hs.byte.byte1 = hs.byte.byte2 = 0; sz = size() % 3; switch (sz) { case 1: - hs.byte0 = back(); - t.push_back(base64Table[hs.ascii0]); - t.push_back(base64Table[hs.ascii1]); + hs.byte.byte0 = back(); + t.push_back(base64Table[hs.ascii.ascii0]); + t.push_back(base64Table[hs.ascii.ascii1]); t.push_back('='); t.push_back('='); break; case 2: - hs.byte0 = at(size() - 2); hs.byte1 = back(); - t.push_back(base64Table[hs.ascii0]); - t.push_back(base64Table[hs.ascii1]); - t.push_back(base64Table[hs.ascii2]); + hs.byte.byte0 = at(size() - 2); hs.byte.byte1 = back(); + t.push_back(base64Table[hs.ascii.ascii0]); + t.push_back(base64Table[hs.ascii.ascii1]); + t.push_back(base64Table[hs.ascii.ascii2]); t.push_back('='); break; default: break; @@ -97,14 +97,14 @@ PIByteArray & PIByteArray::convertFromBase64() { uint sz = size(); if (sz == 0) return *this; for (uint i = 0; i < sz; ++i) { - hs.byte0 = hs.byte1 = hs.byte2 = 0; - hs.ascii0 = base64InvTable[at(i)]; - hs.ascii1 = base64InvTable[at(++i)]; - hs.ascii2 = base64InvTable[at(++i)]; - hs.ascii3 = base64InvTable[at(++i)]; - t.push_back(hs.byte0); - t.push_back(hs.byte1); - t.push_back(hs.byte2); + hs.byte.byte0 = hs.byte.byte1 = hs.byte.byte2 = 0; + hs.ascii.ascii0 = base64InvTable[at(i)]; + hs.ascii.ascii1 = base64InvTable[at(++i)]; + hs.ascii.ascii2 = base64InvTable[at(++i)]; + hs.ascii.ascii3 = base64InvTable[at(++i)]; + t.push_back(hs.byte.byte0); + t.push_back(hs.byte.byte1); + t.push_back(hs.byte.byte2); } if (back() == '=') t.pop_back(); if (sz > 1) if (at(sz - 2) == '=') t.pop_back(); diff --git a/pibytearray.h b/pibytearray.h old mode 100755 new mode 100644 index 3f607c97..c6342006 --- a/pibytearray.h +++ b/pibytearray.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Byte array - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -110,6 +110,15 @@ public: PIByteArray & compressHuffman() {*this = huffman.compress(*this); return *this;} + PIByteArray & append(void * data, int size) {for (int i = 0; i < size; ++i) push_back(((uchar*)data)[i]); return *this;} + /*PIByteArray & operator <<(short v) {for (uint i = 0; i < sizeof(v); ++i) push_back(((uchar*)(&v))[i]); return *this;} + PIByteArray & operator <<(ushort v) {for (uint i = 0; i < sizeof(v); ++i) push_back(((uchar*)(&v))[i]); return *this;} + PIByteArray & operator <<(int v) {for (uint i = 0; i < sizeof(v); ++i) push_back(((uchar*)(&v))[i]); return *this;} + PIByteArray & operator <<(uint v) {for (uint i = 0; i < sizeof(v); ++i) push_back(((uchar*)(&v))[i]); return *this;} + PIByteArray & operator <<(llong v) {for (uint i = 0; i < sizeof(v); ++i) push_back(((uchar*)(&v))[i]); return *this;} + PIByteArray & operator <<(ullong v) {for (uint i = 0; i < sizeof(v); ++i) push_back(((uchar*)(&v))[i]); return *this;}*/ + PIByteArray & operator <<(const PIByteArray & v) {for (uint i = 0; i < v.size(); ++i) push_back(v[i]); return *this;} + uchar checksumPlain8(); uint checksumPlain32(); uchar checksumCRC8(); @@ -125,12 +134,12 @@ private: uchar ascii1: 6; uchar ascii2: 6; uchar ascii3: 6; - }; + } ascii; struct { uchar byte0; uchar byte1; uchar byte2; - }; + } byte; }; static PIHuffman huffman; @@ -139,4 +148,40 @@ private: inline std::ostream & operator <<(std::ostream & s, const PIByteArray & ba) {for (uint i = 0; i < ba.size(); ++i) s << ba[i]; return s;} +inline PIByteArray & operator <<(PIByteArray & s, uchar v) {s.push_back(v); return s;} +inline PIByteArray & operator <<(PIByteArray & s, const short & v) {for (uint i = 0; i < sizeof(v); ++i) s.push_back(((uchar * )&v)[i]); return s;} +inline PIByteArray & operator <<(PIByteArray & s, const int & v) {for (uint i = 0; i < sizeof(v); ++i) s.push_back(((uchar * )&v)[i]); return s;} +inline PIByteArray & operator <<(PIByteArray & s, const long & v) {for (uint i = 0; i < sizeof(v); ++i) s.push_back(((uchar * )&v)[i]); return s;} +inline PIByteArray & operator <<(PIByteArray & s, const llong & v) {for (uint i = 0; i < sizeof(v); ++i) s.push_back(((uchar * )&v)[i]); return s;} +inline PIByteArray & operator <<(PIByteArray & s, const ushort & v) {for (uint i = 0; i < sizeof(v); ++i) s.push_back(((uchar * )&v)[i]); return s;} +inline PIByteArray & operator <<(PIByteArray & s, const uint & v) {for (uint i = 0; i < sizeof(v); ++i) s.push_back(((uchar * )&v)[i]); return s;} +inline PIByteArray & operator <<(PIByteArray & s, const ulong & v) {for (uint i = 0; i < sizeof(v); ++i) s.push_back(((uchar * )&v)[i]); return s;} +inline PIByteArray & operator <<(PIByteArray & s, const ullong & v) {for (uint i = 0; i < sizeof(v); ++i) s.push_back(((uchar * )&v)[i]); return s;} +inline PIByteArray & operator <<(PIByteArray & s, const float & v) {for (uint i = 0; i < sizeof(v); ++i) s.push_back(((uchar * )&v)[i]); return s;} +inline PIByteArray & operator <<(PIByteArray & s, const double & v) {for (uint i = 0; i < sizeof(v); ++i) s.push_back(((uchar * )&v)[i]); return s;} +template +inline PIByteArray & operator <<(PIByteArray & s, const PIVector & v) {s << v.size_s(); for (uint i = 0; i < v.size(); ++i) s << v[i]; return s;} +template +inline PIByteArray & operator <<(PIByteArray & s, const PIList & v) {s << v.size_s(); for (uint i = 0; i < v.size(); ++i) s << v[i]; return s;} +template +inline PIByteArray & operator <<(PIByteArray & s, const PIDeque & v) {s << v.size_s(); for (uint i = 0; i < v.size(); ++i) s << v[i]; return s;} + +inline PIByteArray & operator >>(PIByteArray & s, uchar & v) {v = s.take_front(); return s;} +inline PIByteArray & operator >>(PIByteArray & s, short & v) {for (uint i = 0; i < sizeof(v); ++i) ((uchar * )&v)[i] = s.take_front(); return s;} +inline PIByteArray & operator >>(PIByteArray & s, int & v) {for (uint i = 0; i < sizeof(v); ++i) ((uchar * )&v)[i] = s.take_front(); return s;} +inline PIByteArray & operator >>(PIByteArray & s, long & v) {for (uint i = 0; i < sizeof(v); ++i) ((uchar * )&v)[i] = s.take_front(); return s;} +inline PIByteArray & operator >>(PIByteArray & s, llong & v) {for (uint i = 0; i < sizeof(v); ++i) ((uchar * )&v)[i] = s.take_front(); return s;} +inline PIByteArray & operator >>(PIByteArray & s, ushort & v) {for (uint i = 0; i < sizeof(v); ++i) ((uchar * )&v)[i] = s.take_front(); return s;} +inline PIByteArray & operator >>(PIByteArray & s, uint & v) {for (uint i = 0; i < sizeof(v); ++i) ((uchar * )&v)[i] = s.take_front(); return s;} +inline PIByteArray & operator >>(PIByteArray & s, ulong & v) {for (uint i = 0; i < sizeof(v); ++i) ((uchar * )&v)[i] = s.take_front(); return s;} +inline PIByteArray & operator >>(PIByteArray & s, ullong & v) {for (uint i = 0; i < sizeof(v); ++i) ((uchar * )&v)[i] = s.take_front(); return s;} +inline PIByteArray & operator >>(PIByteArray & s, float & v) {for (uint i = 0; i < sizeof(v); ++i) ((uchar * )&v)[i] = s.take_front(); return s;} +inline PIByteArray & operator >>(PIByteArray & s, double & v) {for (uint i = 0; i < sizeof(v); ++i) ((uchar * )&v)[i] = s.take_front(); return s;} +template +inline PIByteArray & operator >>(PIByteArray & s, PIVector & v) {int sz; s >> sz; v.resize(sz); for (int i = 0; i < sz; ++i) s >> v[i]; return s;} +template +inline PIByteArray & operator >>(PIByteArray & s, PIList & v) {int sz; s >> sz; v.resize(sz); for (int i = 0; i < sz; ++i) s >> v[i]; return s;} +template +inline PIByteArray & operator >>(PIByteArray & s, PIDeque & v) {int sz; s >> sz; v.resize(sz); for (int i = 0; i < sz; ++i) s >> v[i]; return s;} + #endif // PIBYTEARRAY_H diff --git a/pichar.h b/pichar.h old mode 100755 new mode 100644 index 65b5d262..552acc51 --- a/pichar.h +++ b/pichar.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Unicode char - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,11 +20,13 @@ #ifndef PICHAR_H #define PICHAR_H -#include "piincludes.h" +#include "pibytearray.h" class PIChar { friend class PIString; + friend PIByteArray & operator <<(PIByteArray & s, const PIChar & v); + friend PIByteArray & operator >>(PIByteArray & s, PIChar & v); public: PIChar(const char c) {ch = c; ch &= 0xFF;} PIChar(const short c) {ch = c; ch &= 0xFFFF;} @@ -33,17 +35,17 @@ public: PIChar(const ushort c) {ch = c; ch &= 0xFFFF;} PIChar(const uint c) {ch = c;} PIChar(const char * c) {ch = *reinterpret_cast(c);} - + //inline operator const int() {return static_cast(ch);} //inline operator const char() {return toAscii();} - + PIChar & operator =(const char v) {ch = v; return *this;} /*inline PIChar & operator =(const short v) {ch = v; return *this;} inline PIChar & operator =(const int v) {ch = v; return *this;} inline PIChar & operator =(const uchar v) {ch = v; return *this;} inline PIChar & operator =(const ushort v) {ch = v; return *this;} inline PIChar & operator =(const uint v) {ch = v; return *this;}*/ - + bool operator ==(const PIChar & o) const {return strcmp(o.toCharPtr(), toCharPtr()) == 0;} /*inline bool operator ==(const PIChar & o) const {if (o.isAscii() ^ isAscii()) return false; if (isAscii()) return (o.toAscii() == toAscii()); @@ -54,7 +56,7 @@ public: inline bool operator ==(const uchar o) const {return (PIChar(o) == *this);} inline bool operator ==(const ushort o) const {return (PIChar(o) == *this);} inline bool operator ==(const uint o) const {return (PIChar(o) == *this);}*/ - + bool operator !=(const PIChar & o) const {return !(o == *this);} /*inline bool operator !=(const char o) const {return (PIChar(o) != *this);} inline bool operator !=(const short o) const {return (PIChar(o) != *this);} @@ -62,12 +64,12 @@ public: inline bool operator !=(const uchar o) const {return (PIChar(o) != *this);} inline bool operator !=(const ushort o) const {return (PIChar(o) != *this);} inline bool operator !=(const uint o) const {return (PIChar(o) != *this);}*/ - + bool operator >(const PIChar & o) const {return strcmp(o.toCharPtr(), toCharPtr()) < 0;} bool operator <(const PIChar & o) const {return strcmp(o.toCharPtr(), toCharPtr()) > 0;} bool operator >=(const PIChar & o) const {return strcmp(o.toCharPtr(), toCharPtr()) <= 0;} bool operator <=(const PIChar & o) const {return strcmp(o.toCharPtr(), toCharPtr()) >= 0;} - + bool isDigit() const {return isdigit(ch) != 0;} bool isHex() const {return isxdigit(ch) != 0;} bool isGraphical() const {return isgraph(ch) != 0;} @@ -78,7 +80,7 @@ public: bool isSpace() const {return isspace(ch) != 0;} bool isAlpha() const {return isalpha(ch) != 0;} bool isAscii() const {return isascii(ch) != 0;} - + int toInt() const {return static_cast(ch);} const wchar_t * toWCharPtr() const {return &ch;} const char * toCharPtr() const {return reinterpret_cast(&ch);} @@ -92,14 +94,17 @@ public: PIChar toUpper() const {return PIChar(toupper(ch));} PIChar toLower() const {return PIChar(tolower(ch));} //#endif - + private: wchar_t ch; - + }; inline std::ostream & operator <<(std::ostream & s, const PIChar & v) {s << v.toCharPtr(); return s;} +inline PIByteArray & operator <<(PIByteArray & s, const PIChar & v) {s << uint(v.ch); return s;} +inline PIByteArray & operator >>(PIByteArray & s, PIChar & v) {uint i; s >> i; v.ch = wchar_t(i); return s;} + inline bool operator ==(const char v, const PIChar & c) {return (PIChar(v) == c);} inline bool operator >(const char v, const PIChar & c) {return (PIChar(v) > c);} inline bool operator <(const char v, const PIChar & c) {return (PIChar(v) < c);} diff --git a/picli.cpp b/picli.cpp old mode 100755 new mode 100644 index 93ba9014..81ccae40 --- a/picli.cpp +++ b/picli.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Command-Line Parser - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,7 +25,7 @@ PICLI::PICLI(int argc, char * argv[]) { _prefix_short = "-"; _prefix_full = "--"; _count_opt = 0; - _count_mand = 1; + _count_mand = 0; for (int i = 0; i < argc; ++i) _args_raw << argv[i]; } diff --git a/picli.h b/picli.h old mode 100755 new mode 100644 index 6b835e1d..8a9051e8 --- a/picli.h +++ b/picli.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Command-Line Parser - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/picodec.cpp b/picodec.cpp old mode 100755 new mode 100644 index 564dfbd5..afd0ff05 --- a/picodec.cpp +++ b/picodec.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Text codings coder, based on "iconv" - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/picodec.h b/picodec.h old mode 100755 new mode 100644 index 6793428e..21ee8fa6 --- a/picodec.h +++ b/picodec.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Text codings coder, based on "iconv" - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/piconfig.cpp b/piconfig.cpp old mode 100755 new mode 100644 index 8e6e81b9..48554566 --- a/piconfig.cpp +++ b/piconfig.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Config parser - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -396,6 +396,7 @@ void PIConfig::writeAll() { *this << other[i]; if (i < other.size_s() - 1) *this << '\n'; } + //cout << other[i] << endl; } flush(); readAll(); @@ -433,9 +434,9 @@ void PIConfig::parse() { while (!isEnd()) { other.push_back(PIString()); src = str = readLine(); - //cout << str << endl; tab = str.left(str.find(str.trimmed().left(1))); str.trim(); + //cout << endl << str << endl << endl; all = str; ind = str.find('='); if ((ind > 0) && !(str[0] == '#')) { diff --git a/piconfig.h b/piconfig.h old mode 100755 new mode 100644 index 23de194b..e0bf7abd --- a/piconfig.h +++ b/piconfig.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Config parser - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/piconsole.cpp b/piconsole.cpp old mode 100755 new mode 100644 index f5d7e19f..70d70414 --- a/piconsole.cpp +++ b/piconsole.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Console output/input - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -56,16 +56,20 @@ PIConsole::~PIConsole() { int PIConsole::addTab(const PIString & name, char bind_key) { + if (isRunning()) lock(); tabs.push_back(Tab(name, bind_key)); cur_tab = tabs.size() - 1; + if (isRunning()) unlock(); return tabs.size(); } void PIConsole::removeTab(uint index) { if (index >= tabs.size()) return; + if (isRunning()) lock(); tabs.remove(index); if (cur_tab >= tabs.size()) cur_tab = tabs.size() - 1; + if (isRunning()) unlock(); } @@ -84,9 +88,12 @@ void PIConsole::removeTab(const PIString & name) { bool PIConsole::setTab(uint index) { if (index >= tabs.size()) return false; - cur_tab = index; - if (!isRunning()) return true; + if (!isRunning()) { + cur_tab = index; + return true; + } lock(); + cur_tab = index; clearScreen(); fillLabels(); unlock(); @@ -156,6 +163,7 @@ PIString PIConsole::fstr(PIFlags f) { if (f[PIConsole::Dec]) num_format = 0; if (f[PIConsole::Hex]) num_format = 1; if (f[PIConsole::Oct]) num_format = 2; + if (f[PIConsole::Bin]) num_format = 4; if (f[PIConsole::Scientific]) num_format = 3; #ifdef WINDOWS @@ -232,15 +240,15 @@ PIString PIConsole::fstr(PIFlags f) { #endif } -#define siprint(x) switch (num_format) {case (1): return printf("0x%.4hX", x); break; case (2): return printf("%o", x); break; default: return printf("%hd", x); break;} -#define iprint(x) switch (num_format) {case (1): return printf("0x%.8X", x); break; case (2): return printf("%o", x); break; default: return printf("%d", x); break;} -#define liprint(x) switch (num_format) {case (1): return printf("0x%.16lX", x); break; case (2): return printf("%lo", x); break; default: return printf("%ld", x); break;} -#define lliprint(x) switch (num_format) {case (1): return printf("0x%.16llX", x); break; case (2): return printf("%llo", x); break; default: return printf("%lld", x); break;} -#define cuprint(x) switch (num_format) {case (1): return printf("0x%.2X", x); break; case (2): return printf("%o", x); break; default: return printf("%u", x); break;} -#define suprint(x) switch (num_format) {case (1): return printf("0x%.4hX", x); break; case (2): return printf("%o", x); break; default: return printf("%hu", x); break;} -#define uprint(x) switch (num_format) {case (1): return printf("0x%.8X", x); break; case (2): return printf("%o", x); break; default: return printf("%u", x); break;} -#define luprint(x) switch (num_format) {case (1): return printf("0x%.16lX", x); break; case (2): return printf("%lo", x); break; default: return printf("%lu", x); break;} -#define lluprint(x) switch (num_format) {case (1): return printf("0x%.16llX", x); break; case (2): return printf("%llo", x); break; default: return printf("%llu", x); break;} +#define siprint(x) switch (num_format) {case (1): return printf("0x%.4hX", x); break; case (2): return printf("%o", x); break; case (4): return printf("%s", toBin(&x, 2)); break; default: return printf("%hd", x); break;} +#define iprint(x) switch (num_format) {case (1): return printf("0x%.8X", x); break; case (2): return printf("%o", x); break; case (4): return printf("%s", toBin(&x, 4)); break; default: return printf("%d", x); break;} +#define liprint(x) switch (num_format) {case (1): return printf("0x%.16lX", x); break; case (2): return printf("%lo", x); break; case (4): return printf("%s", toBin(&x, sizeof(x))); break; default: return printf("%ld", x); break;} +#define lliprint(x) switch (num_format) {case (1): return printf("0x%.16llX", x); break; case (2): return printf("%llo", x); break; case (4): return printf("%s", toBin(&x, sizeof(x))); break; default: return printf("%lld", x); break;} +#define cuprint(x) switch (num_format) {case (1): return printf("0x%.2X", x); break; case (2): return printf("%o", x); break; case (4): return printf("%s", toBin(&x, 1)); break; default: return printf("%u", x); break;} +#define suprint(x) switch (num_format) {case (1): return printf("0x%.4hX", x); break; case (2): return printf("%o", x); break; case (4): return printf("%s", toBin(&x, 2)); break; default: return printf("%hu", x); break;} +#define uprint(x) switch (num_format) {case (1): return printf("0x%.8X", x); break; case (2): return printf("%o", x); break; case (4): return printf("%s", toBin(&x, 4)); break; default: return printf("%u", x); break;} +#define luprint(x) switch (num_format) {case (1): return printf("0x%.16lX", x); break; case (2): return printf("%lo", x); break; case (4): return printf("%s", toBin(&x, sizeof(x))); break; default: return printf("%lu", x); break;} +#define lluprint(x) switch (num_format) {case (1): return printf("0x%.16llX", x); break; case (2): return printf("%llo", x); break; case (4): return printf("%s", toBin(&x, sizeof(x))); break; default: return printf("%llu", x); break;} #define fprint(x) switch (num_format) {case (3): return printf("%e", x); break; default: return printf("%.5g", x); break;} #define dprint(x) switch (num_format) {case (3): return printf("%le", x); break; default: return printf("%.5lg", x); break;} @@ -312,20 +320,20 @@ void PIConsole::run() { case 1: clen = printValue(tv.b != 0 ? *tv.b : false, tv.format); break; case 2: clen = printValue(tv.i != 0 ? *tv.i : 0, tv.format); break; case 3: clen = printValue(tv.l != 0 ? *tv.l : 0l, tv.format); break; - case 4: clen = printValue(tv.c != 0 ? *tv.c : 0, tv.format); break; + case 4: clen = printValue(tv.c != 0 ? *tv.c : char(0), tv.format); break; case 5: clen = printValue(tv.f != 0 ? *tv.f : 0.f, tv.format); break; case 6: clen = printValue(tv.d != 0 ? *tv.d : 0., tv.format); break; - case 7: clen = printValue(tv.sh != 0 ? *tv.sh : 0, tv.format); break; + case 7: clen = printValue(tv.sh != 0 ? *tv.sh : short(0), tv.format); break; case 8: clen = printValue(tv.ui != 0 ? *tv.ui : 0u, tv.format); break; case 9: clen = printValue(tv.ul != 0 ? *tv.ul : 0ul, tv.format); break; - case 10: clen = printValue(tv.ush != 0 ? *tv.ush : 0u, tv.format); break; - case 11: clen = printValue(tv.uc != 0 ? *tv.uc : 0u, tv.format); break; + case 10: clen = printValue(tv.ush != 0 ? *tv.ush : ushort(0), tv.format); break; + case 11: clen = printValue(tv.uc != 0 ? *tv.uc : uchar(0), tv.format); break; case 12: clen = printValue(tv.ll != 0 ? *tv.ll : 0l, tv.format); break; case 13: clen = printValue(tv.ull != 0 ? *tv.ull : 0ull, tv.format); break; case 14: clen = printValue(bitsValue(tv.ptr, tv.bitFrom, tv.bitCount), tv.format); break; } if (clen + tv.offset < (uint)col_wid) { -#ifdef QNX +#if defined(QNX) || defined(FREE_BSD) string ts = PIString(col_wid - clen - tv.offset - 1, ' ').stdString(); #else string ts = PIString(col_wid - clen - tv.offset, ' ').stdString(); @@ -340,6 +348,7 @@ void PIConsole::run() { void PIConsole::fillLabels() { + if (!isRunning()) return; uint cx, cy, my = 0, mx = 0, dx; #ifdef WINDOWS GetConsoleScreenBufferInfo(hOut, &sbi); @@ -359,7 +368,7 @@ void PIConsole::fillLabels() { if (ccol.alignment != Nothing) { mx = 0; piForeachC (Variable & j, cvars) - if (j.type != 0 && j.s != 0) + if (j.s != 0) if (mx < j.name.size()) mx = j.name.size(); mx += 2; @@ -390,25 +399,27 @@ void PIConsole::fillLabels() { cy++; continue; } - switch (ccol.alignment) { - case Nothing: - cvars[j].offset = (tv.name + ": ").length(); - cvars[j].nx += cvars[j].offset; - printValue(tv.name + ": ", tv.format); - break; - case Left: - cvars[j].offset = mx; - cvars[j].nx += cvars[j].offset; - printValue(tv.name + ": ", tv.format); - break; - case Right: - cvars[j].offset = mx; - cvars[j].nx += cvars[j].offset; - dx = mx - (tv.name + ": ").length(); - moveRight(dx); - printValue(tv.name + ": ", tv.format); - moveLeft(dx); - break; + if (tv.s != 0) { + switch (ccol.alignment) { + case Nothing: + cvars[j].offset = (tv.name + ": ").length(); + cvars[j].nx += cvars[j].offset; + printValue(tv.name + ": ", tv.format); + break; + case Left: + cvars[j].offset = mx; + cvars[j].nx += cvars[j].offset; + printValue(tv.name + ": ", tv.format); + break; + case Right: + cvars[j].offset = mx; + cvars[j].nx += cvars[j].offset; + dx = mx - (tv.name + ": ").length(); + moveRight(dx); + printValue(tv.name + ": ", tv.format); + moveLeft(dx); + break; + } } newLine(); cy++; @@ -442,78 +453,98 @@ void PIConsole::status() { } -int PIConsole::bitsValue(void * src, int offset, int count) { +int PIConsole::bitsValue(const void * src, int offset, int count) { int ret = 0, stbyte = offset / 8, cbit = offset - stbyte * 8; - char cbyte = reinterpret_cast(src)[stbyte]; + char cbyte = reinterpret_cast(src)[stbyte]; for (int i = 0; i < count; i++) { ret |= ((cbyte >> cbit & 1) << i); cbit++; if (cbit == 8) { cbit = 0; stbyte++; - cbyte = reinterpret_cast(src)[stbyte]; + cbyte = reinterpret_cast(src)[stbyte]; } } return ret; } + +const char * PIConsole::toBin(const void * d, int s) { + binstr.clear(); + uchar cc, b; + for (int i = 0; i < s; ++i) { + cc = ((const uchar *)d)[i]; + b = 1; + for (int j = 0; j < 8; ++j) { + binstr << (cc & b ? "1" : "0"); + b <<= 1; + } + if (i < s - 1) binstr << " "; + } + binstr.reverse(); + return binstr.data(); +} + + #define ADD_VAR_BODY tv.name = name; tv.bitFrom = tv.bitCount = 0; tv.format = format; checkColumn(col); void PIConsole::addString(const PIString & name, int col, PIFlags format) { ADD_VAR_BODY tv.type = 0; tv.s = 0; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, PIString* ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const PIString* ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 0; tv.s = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, bool * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const bool * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 1; tv.b = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, int * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const int * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 2; tv.i = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, long * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const long * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 3; tv.l = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, char * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const char * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 4; tv.c = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, float * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const float * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 5; tv.f = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, double * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const double * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 6; tv.d = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, short * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const short * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 7; tv.sh = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, uint * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const uint * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 8; tv.ui = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, ulong * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const ulong * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 9; tv.ul = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, ushort * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const ushort * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 10; tv.ush = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, uchar * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const uchar * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 11; tv.uc = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, llong * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const llong * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 12; tv.ll = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, ullong * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const ullong * ptr, int col, PIFlags format) { ADD_VAR_BODY tv.type = 13; tv.ull = ptr; column(col).push_back(tv);} -void PIConsole::addVariable(const PIString & name, PIProtocol * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const PIProtocol * ptr, int col, PIFlags format) { addString("protocol " + name, col, format | PIConsole::Bold); - addVariable("Rec :" + ptr->receiverDeviceName() + " ", ptr->receiverDeviceState_ptr(), col, format); - addVariable("Send:" + ptr->senderDeviceName() + " ", ptr->senderDeviceState_ptr(), col, format); + addVariable("Rec - " + ptr->receiverDeviceName(), ptr->receiverDeviceState_ptr(), col, format); + addVariable("Send - " + ptr->senderDeviceName(), ptr->senderDeviceState_ptr(), col, format); addVariable("Sended count", ptr->sendCount_ptr(), col, format); addVariable("Received count", ptr->receiveCount_ptr(), col, format); addVariable("Invalid count", ptr->wrongCount_ptr(), col, format); addVariable("Missed count", ptr->missedCount_ptr(), col, format); addVariable("Immediate Frequency, Hz", ptr->immediateFrequency_ptr(), col, format); addVariable("Integral Frequency, Hz", ptr->integralFrequency_ptr(), col, format); - addVariable("Disconnect Timeout, s", ptr->disconnectTimeout_ptr(), col, format); + addVariable("Receive speed", ptr->receiveSpeed_ptr(), col, format); + addVariable("Send speed", ptr->sendSpeed_ptr(), col, format); addVariable("Receiver history size", ptr->receiverHistorySize_ptr(), col, format); addVariable("Sender history size", ptr->senderHistorySize_ptr(), col, format); + addVariable("Disconnect Timeout, s", ptr->disconnectTimeout_ptr(), col, format); addVariable("Quality", ptr->quality_ptr(), col, format); } -void PIConsole::addVariable(const PIString & name, PISystemMonitor * ptr, int col, PIFlags format) { +void PIConsole::addVariable(const PIString & name, const PISystemMonitor * ptr, int col, PIFlags format) { addString("monitor " + name, col, format | PIConsole::Bold); - addVariable("state ", &(ptr->statistic().state), col, format); - addVariable("threads ", &(ptr->statistic().threads), col, format); + addVariable("state", &(ptr->statistic().state), col, format); + addVariable("threads", &(ptr->statistic().threads), col, format); addVariable("priority", &(ptr->statistic().priority), col, format); addVariable("memory physical", &(ptr->statistic().physical_memsize_readable), col, format); - addVariable("memory shared ", &(ptr->statistic().share_memsize_readable), col, format); + addVariable("memory shared", &(ptr->statistic().share_memsize_readable), col, format); addVariable("cpu load", &(ptr->statistic().cpu_load_system), col, format); } -void PIConsole::addBitVariable(const PIString & name, void * ptr, int fromBit, int bitCount, int col, PIFlags format) { +void PIConsole::addBitVariable(const PIString & name, const void * ptr, int fromBit, int bitCount, int col, PIFlags format) { tv.name = name; tv.bitFrom = fromBit; tv.bitCount = bitCount; tv.type = 14; tv.ptr = ptr; tv.format = format; checkColumn(col); column(col).push_back(tv);} void PIConsole::addEmptyLine(int col, uint count) { @@ -562,7 +593,7 @@ PIString PIConsole::getString(const PIString & name) { inline void PIConsole::printLine(const PIString & value, int dx, PIFlags format) { int i = width - value.length() - dx; -#ifdef QNX +#if defined(QNX) || defined(FREE_BSD) --i; #endif PIString ts = fstr(format); diff --git a/piconsole.h b/piconsole.h old mode 100755 new mode 100644 index 2afd4bbf..0b76408d --- a/piconsole.h +++ b/piconsole.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Console output/input - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,6 +28,10 @@ # include #endif +/// handlers: +/// void clearVariables(bool clearScreen = true) +/// void start(bool wait = false) +/// void stop(bool clear = false) class PIConsole: public PIThread { public: @@ -60,27 +64,28 @@ public: Dec = 0x1000000, Hex = 0x2000000, Oct = 0x4000000, - Scientific = 0x8000000}; + Bin = 0x8000000, + Scientific = 0x10000000}; enum Alignment {Nothing, Left, Right}; void addString(const PIString & name, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, PIString * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, char * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, bool * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, short * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, int * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, long * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, llong * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, uchar * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, ushort * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, uint * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, ulong * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, ullong * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, float * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, double * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, PIProtocol * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addVariable(const PIString & name, PISystemMonitor * ptr, int column = 1, PIFlags format = PIConsole::Normal); - void addBitVariable(const PIString & name, void * ptr, int fromBit, int bitCount, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const PIString * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const char * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const bool * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const short * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const int * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const long * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const llong * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const uchar * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const ushort * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const uint * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const ulong * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const ullong * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const float * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const double * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const PIProtocol * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addVariable(const PIString & name, const PISystemMonitor * ptr, int column = 1, PIFlags format = PIConsole::Normal); + void addBitVariable(const PIString & name, const void * ptr, int fromBit, int bitCount, int column = 1, PIFlags format = PIConsole::Normal); void addEmptyLine(int column = 1, uint count = 1); PIString getString(int x, int y); @@ -162,7 +167,8 @@ private: #endif void status(); void checkColumn(uint col) {while (columns().size() < col) columns().push_back(Column(def_align));} - int bitsValue(void * src, int offset, int count); + int bitsValue(const void * src, int offset, int count); + const char * toBin(const void * d, int s); inline void printLine(const PIString & str, int dx = 0, PIFlags format = PIConsole::Normal); inline int printValue(const PIString & str, PIFlags format = PIConsole::Normal); inline int printValue(const char * str, PIFlags format = PIConsole::Normal); @@ -191,21 +197,21 @@ private: int bitFrom; int bitCount; union { - PIString * s; - bool * b; - short * sh; - int * i; - long * l; - llong * ll; - float * f; - double * d; - char * c; - uchar * uc; - ushort * ush; - uint * ui; - ulong * ul; - ullong * ull; - void * ptr; + const PIString * s; + const bool * b; + const short * sh; + const int * i; + const long * l; + const llong * ll; + const float * f; + const double * d; + const char * c; + const uchar * uc; + const ushort * ush; + const uint * ui; + const ulong * ul; + const ullong * ull; + const void * ptr; }; void operator =(const Variable & src) {name = src.name; format = src.format; type = src.type; offset = src.offset; bitFrom = src.bitFrom; bitCount = src.bitCount; ptr = src.ptr; nx = src.nx; ny = src.ny;} @@ -261,6 +267,7 @@ private: struct termios sterm, vterm; #endif PIVector tabs; + PIString binstr; Variable tv; PIKbdListener * listener; Alignment def_align; diff --git a/picontainers.h b/picontainers.h old mode 100755 new mode 100644 index 635a3540..46a0957f --- a/picontainers.h +++ b/picontainers.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Generic containers, based on STL - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -157,7 +157,7 @@ public: PIFlags operator ^(PIFlags f) const {PIFlags tf(flags ^ f.flags); return tf;} PIFlags operator ^(Enum e) const {PIFlags tf(flags ^ e); return tf;} PIFlags operator ^(int i) const {PIFlags tf(flags ^ i); return tf;} - bool operator [](Enum e) {return (flags & e) == e;} + bool operator [](Enum e) const {return (flags & e) == e;} operator int() const {return flags;} private: int flags; @@ -394,12 +394,15 @@ public: _CVector & fill(const Type & t) {_stlc::assign(_stlc::size(), t); return *this;} _CVector & pop_front() {_stlc::erase(_stlc::begin()); return *this;} _CVector & push_front(const Type & t) {_stlc::insert(_stlc::begin(), t); return *this;} + Type take_front() {Type t(_stlc::front()); pop_front(); return t;} + Type take_back() {Type t(_stlc::back()); _stlc::pop_back(); return t;} _CVector & remove(uint num) {_stlc::erase(_stlc::begin() + num); return *this;} _CVector & remove(uint num, uint count) {_stlc::erase(_stlc::begin() + num, _stlc::begin() + num + count); return *this;} //_CVector & remove(const Type & t) {for (typename _stlc::iterator i = _stlc::begin(); i != _stlc::end(); ++i) if (t == *i) {_stlc::erase(i); --i;} return *this;} _CVector & removeOne(const Type & v) {for (typename _stlc::iterator i = _stlc::begin(); i != _stlc::end(); ++i) if (v == *i) {_stlc::erase(i); return *this;} return *this;} _CVector & removeAll(const Type & v) {for (typename _stlc::iterator i = _stlc::begin(); i != _stlc::end(); ++i) if (v == *i) {_stlc::erase(i); --i;} return *this;} _CVector & insert(uint pos, const Type & t) {_stlc::insert(_stlc::begin() + pos, t); return *this;} + _CVector & insert(uint pos, const _CVector & t) {_stlc::insert(_stlc::begin() + pos, t.begin(), t.end()); return *this;} _CVector & operator <<(const Type & t) {_stlc::push_back(t); return *this;} _CVector & operator <<(const _CVector & t) {for (typename _stlc::const_iterator i = t.begin(); i != t.end(); i++) _stlc::push_back(*i); return *this;} bool operator ==(const _CVector & t) {for (uint i = 0; i < _stlc::size(); ++i) if (t[i] != at(i)) return false; return true;} @@ -518,16 +521,19 @@ template inline bool operator <(const PIPair & value0, const PIPair & value1) {return value0.first < value1.first;} -template -class PIMap: public PISet > { - typedef PIMap _CMap; - typedef PISet > _CSet; +template +class PIMap: public map { + typedef PIMap _CMap; + typedef map _stlc; + typedef std::pair _stlpair; public: PIMap() {;} - PIMap(const Type & value, const Key & key) {insert(value, key);} - _CMap & insert(const Type & value, const Key & key) {_CSet::insert(PIPair(key, value)); return *this;} - Type value(Key key) const {for (typename _CMap::iterator i = _CMap::begin(); i != _CMap::end(); i++) if ((*i).first == key) return (*i).second; return Type();} - Type operator[](Key key) const {return value(key);} + PIMap(const Key & key_, const Type & value_) {insert(key_, value_);} + _CMap & insert(const Key & key_, const Type & value_) {_stlc::insert(std::pair(key_, value_)); return *this;} + _CMap & insert(PIPair entry_) {_stlc::insert(std::pair(entry_.first, entry_.second)); return *this;} + Key key(Type value_) const {for (typename _stlc::const_iterator i = _stlc::begin(); i != _stlc::end(); i++) if (i->second == value_) return i->first; return Key();} + Type & value(Key key_) {return (*this)[key_];} + Type value(Key key_) const {return (*this)[key_];} }; #endif // PICONTAINERS_H diff --git a/picrc.h b/picrc.h new file mode 100644 index 00000000..d855792f --- /dev/null +++ b/picrc.h @@ -0,0 +1,216 @@ +/* + PIP - Platform Independent Primitives + Abstract input/output device + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PICRC_H +#define PICRC_H + +#include "pistring.h" + +template +class uint_cl { +public: + uint_cl() {for (int i = 0; i < L / 8; ++i) data_[i] = 0;} + uint_cl(const uint_cl & v) {for (int i = 0; i < L / 8; ++i) data_[i] = v.data_[i];} + uint_cl(uchar v) {for (int i = 0; i < L / 8; ++i) data_[i] = (i == 0 ? v : 0);} + uint_cl(char v) {for (int i = 0; i < L / 8; ++i) data_[i] = (i == 0 ? v : 0);} + uint_cl(ushort v) {int l = piMin(L / 8, sizeof(v)); memcpy(data_, &v, l); for (int i = l; i < L / 8; ++i) data_[i] = 0;} + uint_cl(short v) {int l = piMin(L / 8, sizeof(v)); memcpy(data_, &v, l); for (int i = l; i < L / 8; ++i) data_[i] = 0;} + uint_cl(uint v) {int l = piMin(L / 8, sizeof(v)); memcpy(data_, &v, l); for (int i = l; i < L / 8; ++i) data_[i] = 0;} + uint_cl(int v) {int l = piMin(L / 8, sizeof(v)); memcpy(data_, &v, l); for (int i = l; i < L / 8; ++i) data_[i] = 0;} + uint_cl(ulong v) {int l = piMin(L / 8, sizeof(v)); memcpy(data_, &v, l); for (int i = l; i < L / 8; ++i) data_[i] = 0;} + uint_cl(long v) {int l = piMin(L / 8, sizeof(v)); memcpy(data_, &v, l); for (int i = l; i < L / 8; ++i) data_[i] = 0;} + uint_cl(ullong v) {int l = piMin(L / 8, sizeof(v)); memcpy(data_, &v, l); for (int i = l; i < L / 8; ++i) data_[i] = 0;} + uint_cl(llong v) {int l = piMin(L / 8, sizeof(v)); memcpy(data_, &v, l); for (int i = l; i < L / 8; ++i) data_[i] = 0;} + + operator bool() {for (int i = 0; i < L / 8; ++i) if (data_[i] > 0) return true; return false;} + operator char() {return (char)data_[0];} + operator short() {short t(0); int l = piMin(L / 8, sizeof(t)); memcpy(&t, data_, l); return t;} + operator int() {int t(0); int l = piMin(L / 8, sizeof(t)); memcpy(&t, data_, l); return t;} + operator long() {long t(0); int l = piMin(L / 8, sizeof(t)); memcpy(&t, data_, l); return t;} + operator llong() {llong t(0); int l = piMin(L / 8, sizeof(t)); memcpy(&t, data_, l); return t;} + operator uchar() {return data_[0];} + operator ushort() {ushort t(0); int l = piMin(L / 8, sizeof(t)); memcpy(&t, data_, l); return t;} + operator uint() {uint t(0); int l = piMin(L / 8, sizeof(t)); memcpy(&t, data_, l); return t;} + operator ulong() {ulong t(0); int l = piMin(L / 8, sizeof(t)); memcpy(&t, data_, l); return t;} + operator ullong() {ullong t(0); int l = piMin(L / 8, sizeof(t)); memcpy(&t, data_, l); return t;} + + uint_cl operator +(const uint_cl & v) { + uint_cl t; + uint cv; + bool ov = false; + for (int i = 0; i < L / 8; ++i) { + cv = v.data_[i] + data_[i]; + if (ov) ++cv; + ov = cv > 255; + t.data_[i] = ov ? cv - 256 : cv; + } + return t; + } + + uint_cl operator &(const uint_cl & v) const {uint_cl t; for (int i = 0; i < L / 8; ++i) t.data_[i] = v.data_[i] & data_[i]; return t;} + uint_cl operator &(const uchar & v) const {return *this & uint_cl(v);} + uint_cl operator &(const ushort & v) const {return *this & uint_cl(v);} + uint_cl operator &(const uint & v) const {return *this & uint_cl(v);} + uint_cl operator &(const ulong & v) const {return *this & uint_cl(v);} + uint_cl operator &(const ullong & v) const {return *this & uint_cl(v);} + uint_cl operator &(const char & v) const {return *this & uint_cl(v);} + uint_cl operator &(const short & v) const {return *this & uint_cl(v);} + uint_cl operator &(const int & v) const {return *this & uint_cl(v);} + uint_cl operator &(const long & v) const {return *this & uint_cl(v);} + uint_cl operator &(const llong & v) const {return *this & uint_cl(v);} + + uint_cl operator |(const uint_cl & v) const {uint_cl t; for (int i = 0; i < L / 8; ++i) t.data_[i] = v.data_[i] | data_[i]; return t;} + uint_cl operator |(const uchar & v) const {return *this | uint_cl(v);} + uint_cl operator |(const ushort & v) const {return *this | uint_cl(v);} + uint_cl operator |(const uint & v) const {return *this | uint_cl(v);} + uint_cl operator |(const ulong & v) const {return *this | uint_cl(v);} + uint_cl operator |(const ullong & v) const {return *this | uint_cl(v);} + uint_cl operator |(const char & v) const {return *this | uint_cl(v);} + uint_cl operator |(const short & v) const {return *this | uint_cl(v);} + uint_cl operator |(const int & v) const {return *this | uint_cl(v);} + uint_cl operator |(const long & v) const {return *this | uint_cl(v);} + uint_cl operator |(const llong & v) const {return *this | uint_cl(v);} + + uint_cl operator ^(const uint_cl & v) const {uint_cl t; for (int i = 0; i < L / 8; ++i) t.data_[i] = v.data_[i] ^ data_[i]; return t;} + uint_cl operator ^(const uchar & v) const {return *this ^ uint_cl(v);} + uint_cl operator ^(const ushort & v) const {return *this ^ uint_cl(v);} + uint_cl operator ^(const uint & v) const {return *this ^ uint_cl(v);} + uint_cl operator ^(const ulong & v) const {return *this ^ uint_cl(v);} + uint_cl operator ^(const ullong & v) const {return *this ^ uint_cl(v);} + uint_cl operator ^(const char & v) const {return *this ^ uint_cl(v);} + uint_cl operator ^(const short & v) const {return *this ^ uint_cl(v);} + uint_cl operator ^(const int & v) const {return *this ^ uint_cl(v);} + uint_cl operator ^(const long & v) const {return *this ^ uint_cl(v);} + uint_cl operator ^(const llong & v) const {return *this ^ uint_cl(v);} + + bool operator <(const uint_cl & v) const {for (int i = 0; i < L / 8; ++i) {if (v.data_[i] > data_[i]) return true; if (v.data_[i] < data_[i]) return false;} return false;} + bool operator <=(const uint_cl & v) const {for (int i = 0; i < L / 8; ++i) {if (v.data_[i] > data_[i]) return true; if (v.data_[i] < data_[i]) return false;} return true;} + bool operator >(const uint_cl & v) const {for (int i = 0; i < L / 8; ++i) {if (v.data_[i] < data_[i]) return true; if (v.data_[i] > data_[i]) return false;} return false;} + bool operator >=(const uint_cl & v) const {for (int i = 0; i < L / 8; ++i) {if (v.data_[i] < data_[i]) return true; if (v.data_[i] > data_[i]) return false;} return true;} + bool operator ==(const uint_cl & v) const {for (int i = 0; i < L / 8; ++i) if (v.data_[i] != data_[i]) return false; return true;} + bool operator !=(const uint_cl & v) const {for (int i = 0; i < L / 8; ++i) if (v.data_[i] != data_[i]) return true; return false;} + + uint_cl operator >>(const int & c) const { + uint_cl t; + int l = L - c; + bool bit; + if (l <= 0) return t; + for (int i = 0; i < l; ++i) { + bit = 1 & (data_[(i + c) / 8] >> ((i + c) % 8)); + if (bit) t.data_[i / 8] |= (1 << (i % 8)); + else t.data_[i / 8] &= ~(1 << (i % 8)); + } + return t; + } + uint_cl operator >>(const uint & c) const {return (*this << (int)c);} + uint_cl operator <<(const int & c) const { + uint_cl t; + int l = L - c; + bool bit; + if (l <= 0) return t; + for (int i = c; i < L; ++i) { + bit = 1 & (data_[(i - c) / 8] >> ((i - c) % 8)); + if (bit) t.data_[i / 8] |= (1 << (i % 8)); + else t.data_[i / 8] &= ~(1 << (i % 8)); + } + return t; + } + uint_cl operator <<(const uint & c) const {return (*this >> (int)c);} + + uint_cl & inverse() const {for (int i = 0; i < L / 8; ++i) data_[i] = ~data_[i]; return *this;} + uint_cl inversed() const {uint_cl t(*this); for (int i = 0; i < L / 8; ++i) t.data_[i] = ~t.data_[i]; return t;} + uint_cl reversed() const { + uint_cl t; + bool bit; + for (int i = 0; i < L; ++i) { + bit = 1 & (data_[(L - i - 1) / 8] >> ((L - i - 1) % 8)); + if (bit) t.data_[i / 8] |= (1 << (i % 8)); + else t.data_[i / 8] &= ~(1 << (i % 8)); + } + return t; + } + + const uchar * data() const {return data_;} + uchar * data() {return data_;} + uint length() const {return L / 8;} + +private: + uchar data_[L / 8]; + +}; + +template +inline std::ostream & operator <<(std::ostream & s, const uint_cl & v) {std::ios::fmtflags f = s.flags(); s << std::hex; for (uint i = 0; i < v.length(); ++i) {s << int(v.data()[i]); if (v.data()[i] < 0x10) s << '0'; s << ' ';} s.flags(f); return s;} + + +template +class PICRC { +public: + PICRC(const uint_cl & poly) {poly_ = poly; reverse_poly = true; init_ = uint_cl(0).inversed(); out_ = uint_cl(0).inversed(); initTable();} + PICRC(const uint_cl & poly, bool reverse, const uint_cl & initial, const uint_cl & out_xor) {poly_ = poly; reverse_poly = reverse; init_ = initial; out_ = out_xor; initTable();} + + void setInitial(const uint_cl & v) {init_ = v;} + void setOutXor(const uint_cl & v) {out_ = v;} + void setReversePolynome(bool yes) {reverse_poly = yes; initTable();} + + void initTable() { + uint_cl tmp, pol = reverse_poly ? poly_.reversed() : poly_; + //cout << std::hex << "poly " << (uint)uint_cl(poly_) << " -> " << (uint)uint_cl(pol) << endl; + for (int i = 0; i < 256; ++i) { + tmp = uchar(i); + for (int j = 0; j < 8; ++j) + tmp = ((tmp & 1) ? ((tmp >> 1) ^ pol) : (tmp >> 1)); + table[i] = tmp; + } + + } + + uint_cl calculate(const void * data, int size) { + uint_cl crc = init_; + uchar * data_ = (uchar * )data; + //cout << "process " << size << endl; + uchar nTemp; + for (int i = 0; i < size; ++i) { + nTemp = data_[i] ^ uchar(crc); + crc = crc >> 8; + crc = crc ^ table[nTemp]; + } + return crc ^ out_; + + } + uint_cl calculate(const PIByteArray & d) {return calculate(d.data(), d.size());} + uint_cl calculate(const char * str) {string s(str); return calculate((void * )s.data(), s.size());} + +private: + uint_cl table[256]; + uint_cl poly_, init_, out_; + bool reverse_poly; + +}; + +typedef PICRC<32> CRC_32; +typedef PICRC<24> CRC_24; +typedef PICRC<16> CRC_16; +typedef PICRC<8> CRC_8; + +inline CRC_32 standardCRC_32() {return CRC_32(0x04C11DB7, true, 0xFFFFFFFF, 0xFFFFFFFF);} +inline CRC_16 standardCRC_16() {return CRC_16(0x8005, true, 0x0, 0x0);} +inline CRC_8 standardCRC_8() {return CRC_8(0xD5, true, 0x0, 0x0);} + +#endif // CRC_H diff --git a/pidir.cpp b/pidir.cpp old mode 100755 new mode 100644 index bfde7fcb..36e83093 --- a/pidir.cpp +++ b/pidir.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Directory - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -78,17 +78,11 @@ PIDir & PIDir::cleanPath() { open(); return *this; } + path_.replaceAll(PIString(separator) + PIString(separator), PIString(separator)); bool isAbs = isAbsolute(); - for (uint i = p.size() - 1; i > 0; --i) { - if (p[i] == separator && p[i - 1] == separator) { - p.cutLeft(i); - isAbs = true; - break; - } - } PIStringList l = PIString(p).split(separator); - l.removeStrings("."); - l.removeStrings(""); + l.removeAll("."); + l.removeAll(""); bool found = true; while (found) { found = false; @@ -118,20 +112,19 @@ PIDir & PIDir::cleanPath() { PIDir & PIDir::cd(const PIString & path) { if (path_.size() == 0) return *this; - if (path_[path_.size() - 1] == separator) path_ << path; - else path_ << separator << path; + if (path_[path_.size() - 1] != separator) path_ << separator; + path_ << path; return cleanPath(); } bool PIDir::mkDir(bool withParents) { PIDir d = cleanedPath(); - string p = d.path_; PIString tp; int ret; bool isAbs = isAbsolute(); if (withParents) { - PIStringList l = PIString(p).split(separator); + PIStringList l = d.path_.split(separator); for (int i = l.size_s() - 1; i >= 0; --i) { if (i > 1) tp = PIStringList(l).remove(i, l.size_s() - i).join(separator); else { @@ -143,19 +136,18 @@ bool PIDir::mkDir(bool withParents) { for (int j = i + 1; j <= l.size_s(); ++j) { tp = PIStringList(l).remove(j, l.size_s() - j).join(separator); //cout << tp << endl; - p = tp.stdString(); - ret = mkdir(p.c_str(), 16877); + ret = mkdir(tp.data(), 16877); if (ret == 0) continue; - printf("[PIDir] mkDir(\"%s\") error: %s\n", p.c_str(), strerror(errno)); + printf("[PIDir] mkDir(\"%s\") error: %s\n", d.path_.data(), strerror(errno)); return false; } break; }; } } else { - ret = mkdir(p.c_str(), 16877); + ret = mkdir(d.path_.data(), 16877); if (ret == 0) return true; - printf("[PIDir] mkDir(\"%s\") error: %s\n", p.c_str(), strerror(errno)); + printf("[PIDir] mkDir(\"%s\") error: %s\n", d.path_.data(), strerror(errno)); } return false; } diff --git a/pidir.h b/pidir.h old mode 100755 new mode 100644 index c6fd7d97..81f1c13c --- a/pidir.h +++ b/pidir.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Directory - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ #define PIDIR_H #include "pifile.h" +#include "pistring.h" #ifndef WINDOWS #include #include @@ -43,23 +44,23 @@ public: bool isDir() const {return (mode & S_IFDIR);} bool isFile() const {return (mode & S_IFREG);} - bool isSymLink() const {return (mode & S_IFLNK);} - bool isBlkDevice() const {return (mode & S_IFBLK);} - bool isChrDevice() const {return (mode & S_IFCHR);} + bool isSymbolicLink() const {return (mode & S_IFLNK);} + bool isBlockDevice() const {return (mode & S_IFBLK);} + bool isCharacterDevice() const {return (mode & S_IFCHR);} bool isSocket() const {return (mode & S_IFSOCK);} }; - inline const bool isExists() {return (dir_ != 0);} - inline const bool isAbsolute() {if (path_.size() == 0) return false; return (path_[0] == separator);} - inline PIString path() {return PIString(path_);} - PIDir & cleanPath(); - inline PIDir cleanedPath() {PIDir d(path_); d.cleanPath(); return d;} + const bool isExists() {return (dir_ != 0);} + const bool isAbsolute() {if (path_.size() == 0) return false; return (path_[0] == separator);} + PIString path() {return PIString(path_);} + PIDir & cleanPath(); + PIDir cleanedPath() {PIDir d(path_); d.cleanPath(); return d;} PIString absolutePath(); bool mkDir(bool withParents = true); PIVector entries(); - PIDir & cd(const PIString & path); - inline PIDir & up() {return cd("..");} + PIDir & cd(const PIString & path); + PIDir & up() {return cd("..");} bool operator ==(const PIDir & d) const; diff --git a/piethernet.cpp b/piethernet.cpp old mode 100755 new mode 100644 index 1fd93494..bab1fc1e --- a/piethernet.cpp +++ b/piethernet.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Ethernet, UDP - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,16 +27,13 @@ PIEthernet::PIEthernet(void * data, ReadRetFunc slot): PIIODevice("", ReadWrite) ret_data_ = data; ip_ = ip_s = ""; port_ = port_s = 0; - sock = -1; + sock = sock_s = -1; ret_func_ = slot; connected_ = false; + params = PIEthernet::ReuseAddress; server_thread_.setData(this); setThreadedReadBufferSize(65536); -#ifdef WINDOWS - WSADATA wsaData; - WSAStartup(MAKEWORD(2, 2), &wsaData); -#endif - init(); + if (type_ != UDP) init(); } @@ -47,16 +44,13 @@ PIEthernet::PIEthernet(PIEthernet::Type type, void * data, ReadRetFunc slot): PI ret_data_ = data; ip_ = ip_s = ""; port_ = port_s = 0; - sock = -1; + sock = sock_s = -1; ret_func_ = slot; connected_ = false; + params = PIEthernet::ReuseAddress; server_thread_.setData(this); setThreadedReadBufferSize(65536); -#ifdef WINDOWS - WSADATA wsaData; - WSAStartup(MAKEWORD(2, 2), &wsaData); -#endif - init(); + if (type_ != UDP) init(); } @@ -66,13 +60,11 @@ PIEthernet::PIEthernet(int sock_, PIString ip_port): PIIODevice("", ReadWrite) { type_ = TCP_Client; parseAddress(ip_port, &ip_s, &port_s); sock = sock_; + sock_s = -1; server_thread_.setData(this); + params = PIEthernet::ReuseAddress; init_ = opened_ = connected_ = true; setThreadedReadBufferSize(65536); -#ifdef WINDOWS - WSADATA wsaData; - WSAStartup(MAKEWORD(2, 2), &wsaData); -#endif } @@ -80,9 +72,6 @@ PIEthernet::~PIEthernet() { piMonitor.ethernets--; if (server_thread_.isRunning()) server_thread_.terminate(); closeSocket(sock); -#ifdef WINDOWS - WSACleanup(); -#endif //if (buffer_ != 0) delete buffer_; //buffer_ = 0; } @@ -91,15 +80,29 @@ PIEthernet::~PIEthernet() { bool PIEthernet::init() { //cout << "init " << type_ << endl; closeSocket(sock); - int st = 0, so = 1; + int st = 0; +#ifdef WINDOWS + int flags = WSA_FLAG_MULTIPOINT_C_LEAF | WSA_FLAG_MULTIPOINT_D_LEAF; +#else + int so = 1; +#endif if (type_ == UDP) st = SOCK_DGRAM; - if (type_ == TCP_Client || type_ == TCP_Server) st = SOCK_STREAM; - sock = socket(PF_INET, st, 0); + else st = SOCK_STREAM; +#ifdef WINDOWS + if (params[ReuseAddress]) flags |= WSA_FLAG_OVERLAPPED; + sock = WSASocket(AF_INET, st, type_ == UDP ? IPPROTO_UDP : IPPROTO_TCP, NULL, 0, flags); +#else + sock = socket(AF_INET, st, type_ == UDP ? IPPROTO_UDP : IPPROTO_TCP); +#endif if (sock == -1) { - piCout << "[PIEthernet] Cant`t create socket, " << errorString() << endl; + piCout << "[PIEthernet] Cant`t create socket, " << EthErrorString() << endl; return false; } - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &so, sizeof(so)); +#ifndef WINDOWS + if (params[PIEthernet::ReuseAddress]) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &so, sizeof(so)); + if (params[PIEthernet::Broadcast]) setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &so, sizeof(so)); +#endif + //cout << "inited " << sock << ": bc = " << params << endl; //fcntl(sock, F_SETFL, 0/*O_NONBLOCK*/); return true; } @@ -114,15 +117,24 @@ void PIEthernet::parseAddress(const PIString & ipp, PIString * ip, int * port) { bool PIEthernet::openDevice() { if (connected_) return true; if (sock == -1) init(); - if (sock == -1) return false; - //cout << " bind to " << path_ << " ..." <= 0) received(read_to, rs); + return rs; //return ::read(sock, read_to, max_size); default: break; //return ::read(sock, (char * )read_to, max_size); @@ -201,12 +315,34 @@ int PIEthernet::write(const void * data, int max_size) { return -1; } //piCout << "[PIEthernet] sending to " << ip_s << ":" << port_s << " " << max_size << " bytes" << endl; + int ret = 0; switch (type_) { + case TCP_SingleTCP: + memset(&addr_, 0, sizeof(addr_)); + addr_.sin_port = htons(port_s); + addr_.sin_addr.s_addr = inet_addr(ip_s.data()); + addr_.sin_family = AF_INET; +#ifdef QNX + addr_.sin_len = sizeof(addr_); +#endif + if (::connect(sock, (sockaddr * )&addr_, sizeof(addr_)) != 0) { + piCout << "[PIEthernet] Cant`t connect to " << ip_s << ":" << port_s << ", " << EthErrorString() << endl; + return -1; + } + ret = ::write(sock, data, max_size); + closeSocket(sock); + init(); + return ret; case UDP: saddr_.sin_port = htons(port_s); - saddr_.sin_addr.s_addr = inet_addr(ip_s.data()); - saddr_.sin_family = PF_INET; + if (params[PIEthernet::Broadcast]) saddr_.sin_addr.s_addr = INADDR_BROADCAST; + else saddr_.sin_addr.s_addr = inet_addr(ip_s.data()); + saddr_.sin_family = AF_INET; +#ifdef WINDOWS + return sendto(sock, (const char * )data, max_size, 0, (sockaddr * )&saddr_, sizeof(saddr_)); +#else return sendto(sock, data, max_size, 0, (sockaddr * )&saddr_, sizeof(saddr_)); +#endif case TCP_Client: return ::write(sock, data, max_size); default: break; @@ -219,18 +355,139 @@ void PIEthernet::server_func(void * eth) { PIEthernet * ce = (PIEthernet * )eth; sockaddr_in client_addr; socklen_t slen = sizeof(client_addr); - int s; - s = accept(ce->sock, (sockaddr * )&client_addr, &slen); + int s = accept(ce->sock, (sockaddr * )&client_addr, &slen); if (s == -1) { - piCout << "[PIEthernet] Cant`t accept new connection, " << errorString() << endl; + piCout << "[PIEthernet] Cant`t accept new connection, " << EthErrorString() << endl; return; } PIString ip(inet_ntoa(client_addr.sin_addr)); ip += ":" + PIString::fromNumber(htons(client_addr.sin_port)); ce->clients_ << new PIEthernet(s, ip); - cout << "connected " << ip << endl; + //cout << "connected " << ip << endl; //char d[256]; //cout << " recv " << recv(s, d, 256, 0) << endl; //cout << recv(ce->clients_.back()->sock, d, 256, 0) << endl; } + + +PIStringList PIEthernet::interfaces() { +#ifdef WINDOWS + piCout << "[PIEthernet] Not implemented on Windows, use \"PIEthernet::allAddresses\" instead" << endl; + return PIStringList(); +#else +# ifdef QNX + PIStringList il, sl; + /*struct if_nameindex * ni = if_nameindex(); + for (int i = 0; ; ++i) { + if (ni[i].if_name == 0 || ni[i].if_index == 0) break; + sl << PIString(ni[i].if_name); + } + if_freenameindex(ni);*/ + PIProcess proc; + proc.setGrabOutput(true); + proc.exec(ifconfigPath.c_str(), "-l"); + if (!proc.waitForFinish(1000)) return sl; + PIString out(proc.readOutput()); + il = out.split(" "); + il.removeAll(""); + piForeachC (PIString & i, il) { + proc.exec(ifconfigPath.c_str(), i); + if (!proc.waitForFinish(1000)) return il; + sl << i.trimmed(); + out = proc.readOutput(); + int al = out.length(); + al = (al - out.replaceAll("alias", "").length()) / 5; + for (int j = 0; j < al; ++j) + sl << i.trimmed() + ":" + PIString::fromNumber(j); + } + //cout << out; + //cout << sl << endl; + return sl; +# else + PIStringList sl; + /*struct if_nameindex * ni = if_nameindex(); + for (int i = 0; ; ++i) { + if (ni[i].if_name == 0 || ni[i].if_index == 0) break; + sl << PIString(ni[i].if_name); + } + if_freenameindex(ni);*/ + PIProcess proc; + proc.setGrabOutput(true); + proc.exec(ifconfigPath.c_str(), "-s"); + if (!proc.waitForFinish(1000)) return sl; + PIString out(proc.readOutput()); + //cout << out << endl; + out.cutLeft(out.find('\n') + 1); + while (!out.isEmpty()) { + sl << out.left(out.find(' ')); + out.cutLeft(out.find('\n') + 1); + } + //cout << sl << endl; + return sl; +# endif +#endif +} + + +PIString PIEthernet::interfaceAddress(const PIString & interface_) { +#ifdef WINDOWS + piCout << "[PIEthernet] Not implemented on Windows, use \"PIEthernet::allAddresses\" instead" << endl; + return PIString(); +#else + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, interface_.data()); + int s = socket(AF_INET, SOCK_DGRAM, 0); + ioctl(s, SIOCGIFADDR, &ifr); + ::close(s); + struct sockaddr_in * sa = (struct sockaddr_in * )&ifr.ifr_addr; + return PIString(inet_ntoa(sa->sin_addr)); +#endif + +} + + +PIStringList PIEthernet::allAddresses() { +#ifdef WINDOWS + PIStringList al; + PIString ca; + PIP_ADAPTER_INFO pAdapterInfo, pAdapter = 0; + int ret = 0; + ulong ulOutBufLen = sizeof(IP_ADAPTER_INFO); + pAdapterInfo = (IP_ADAPTER_INFO * ) HeapAlloc(GetProcessHeap(), 0, (sizeof (IP_ADAPTER_INFO))); + if (pAdapterInfo == 0) { + piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersinfo" << endl; + return PIStringList(); + } + if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) { + HeapFree(GetProcessHeap(), 0, (pAdapterInfo)); + pAdapterInfo = (IP_ADAPTER_INFO *) HeapAlloc(GetProcessHeap(), 0, (ulOutBufLen)); + if (pAdapterInfo == 0) { + piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersinfo" << endl; + return PIStringList(); + } + } + if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) { + pAdapter = pAdapterInfo; + while (pAdapter) { + /*if (pAdapter->Type != MIB_IF_TYPE_ETHERNET && pAdapter->Type != MIB_IF_TYPE_LOOPBACK) { + pAdapter = pAdapter->Next; + continue; + }*/ + ca = PIString(pAdapter->IpAddressList.IpAddress.String); + if (ca != "0.0.0.0") al << ca; + pAdapter = pAdapter->Next; + } + } else + piCout << "[PIEthernet] GetAdaptersInfo failed with error: " << ret << endl; + if (pAdapterInfo) + HeapFree(GetProcessHeap(), 0, (pAdapterInfo)); + return al; +#else + PIStringList il = interfaces(), al; + piForeachC (PIString & i, il) + al << interfaceAddress(i); + return al; +#endif +} diff --git a/piethernet.h b/piethernet.h old mode 100755 new mode 100644 index 50e95db3..89fbfdd7 --- a/piethernet.h +++ b/piethernet.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Ethernet, UDP - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,61 +22,74 @@ #include "pitimer.h" #include "piiodevice.h" -#ifndef WINDOWS -# include -# include -# include -# include -#else -# ifdef CC_VC -# include -# define SHUT_RDWR 2 -# else -# include -# define SHUT_RDWR SD_BOTH -# endif -#endif +#include "piprocess.h" class PIEthernet: public PIIODevice { + friend class PIPeer; public: // slot is any function format "bool (void*, uchar*, int)" PIEthernet(void * data, ReadRetFunc slot); - enum Type {UDP, TCP_Client, TCP_Server}; + enum Type {UDP, TCP_Client, TCP_Server, TCP_SingleTCP}; + enum Parameters {ReuseAddress = 0x1, Broadcast = 0x2}; PIEthernet(Type type = UDP, void * data = 0, ReadRetFunc slot = 0); ~PIEthernet(); - void setReadAddress(PIString ip, int port) {path_ = ip + ":" + PIString::fromNumber(port);} - void setReadAddress(PIString ip_port) {path_ = ip_port;} - void setSendAddress(PIString ip, int port) {ip_s = ip; port_s = port;} - void setSendAddress(PIString ip_port) {parseAddress(ip_port, &ip_s, &port_s);} + void setReadAddress(const PIString & ip, int port) {path_ = ip + ":" + PIString::fromNumber(port);} + void setReadAddress(const PIString & ip_port) {path_ = ip_port;} + void setReadIP(const PIString & ip) {parseAddress(path_, &ip_, &port_); path_ = ip + ":" + PIString::fromNumber(port_);} + void setReadPort(int port) {parseAddress(path_, &ip_, &port_); path_ = ip_ + ":" + PIString::fromNumber(port);} + void setSendAddress(const PIString & ip, int port) {ip_s = ip; port_s = port;} + void setSendAddress(const PIString & ip_port) {parseAddress(ip_port, &ip_s, &port_s);} + void setSendIP(const PIString & ip) {ip_s = ip;} + void setSendPort(int port) {port_s = port;} + PIString readIP() {parseAddress(path_, &ip_, &port_); return ip_;} + int readPort() {parseAddress(path_, &ip_, &port_); return port_;} + PIString sendIP() {return ip_s;} + int sendPort() {return port_s;} + void setParameters(PIFlags parameters_) {params = parameters_;} + void setParameter(PIEthernet::Parameters parameter, bool on = true) {params.setFlag(parameter, on);} + bool isParameterSet(PIEthernet::Parameters parameter) const {return params[parameter];} + PIFlags parameters() const {return params;} + + //PIByteArray macAddress() {if (!init_) init(); struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); memcpy(ifr.ifr_name, "eth0", 5); ioctl(sock, SIOCSIFHWADDR, &ifr); return PIByteArray(&ifr.ifr_hwaddr.sa_data, 6);} Type type() const {return type_;} - bool connect(PIString ip, int port); - bool connect(PIString ip_port) {parseAddress(ip_port, &ip_c, &port_c); return connect(ip_c, port_c);} + bool joinMulticastGroup(const PIString & group); + bool leaveMulticastGroup(const PIString & group); + + bool connect(const PIString & ip, int port); + bool connect(const PIString & ip_port) {parseAddress(ip_port, &ip_c, &port_c); return connect(ip_c, port_c);} bool isConnected() const {return connected_;} bool listen(); - bool listen(PIString ip, int port) {setReadAddress(ip, port); return listen();} - bool listen(PIString ip_port) {setReadAddress(ip_port); return listen();} + bool listen(const PIString & ip, int port) {setReadAddress(ip, port); return listen();} + bool listen(const PIString & ip_port) {setReadAddress(ip_port); return listen();} PIEthernet * client(int index) {return clients_[index];} int clientsCount() const {return clients_.size_s();} PIVector clients() {return clients_;} - bool send(PIString ip, int port, const void * data, int size) {ip_s = ip; port_s = port; return send(data, size);} - bool send(PIString ip_port, const void * data, int size) {parseAddress(ip_port, &ip_s, &port_s); return send(data, size);} + bool send(const PIString & ip, int port, const void * data, int size) {ip_s = ip; port_s = port; return send(data, size);} + bool send(const PIString & ip_port, const void * data, int size) {parseAddress(ip_port, &ip_s, &port_s); return send(data, size);} bool send(const void * data, int size) {return (write(data, size) == size);} int read(void * read_to, int max_size); int write(const void * data, int max_size); + int write(const PIByteArray & data) {return write(data.data(), data.size_s());} + + static PIStringList interfaces(); + static PIString interfaceAddress(const PIString & interface_); + static PIStringList allAddresses(); protected: PIEthernet(int sock, PIString ip_port); + virtual void received(void * data, int size) {;} + bool init(); bool openDevice(); bool closeDevice(); @@ -87,17 +100,32 @@ protected: #endif void parseAddress(const PIString & ipp, PIString * ip, int * port); - int sock, port_, port_s, port_c, wrote; + int sock, sock_s, port_, port_s, port_c, wrote; bool connected_; sockaddr_in addr_, saddr_; PIString ip_, ip_s, ip_c; PIThread server_thread_; PIVector clients_; +#ifdef WINDOWS + PIMap leafs; +#endif + PIFlags params; Type type_; private: static void server_func(void * eth); + static std::string EthErrorString() { +#ifdef WINDOWS + char * msg; + int err = WSAGetLastError(); + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL); + return "code " + itos(err) + " - " + string(msg); +#else + return errorString(); +#endif + } + }; #endif // PIETHERNET_H diff --git a/pievaluator.cpp b/pievaluator.cpp old mode 100755 new mode 100644 index 5bef2b45..049f9ce9 --- a/pievaluator.cpp +++ b/pievaluator.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Evaluator designed for stream computing - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/pievaluator.h b/pievaluator.h old mode 100755 new mode 100644 index 545c78cd..229ee3fc --- a/pievaluator.h +++ b/pievaluator.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Evaluator designed for stream computing - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/pifile.cpp b/pifile.cpp old mode 100755 new mode 100644 index ac4d5cad..93f99a94 --- a/pifile.cpp +++ b/pifile.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives File - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,21 +33,20 @@ bool PIFile::openDevice() { bool PIFile::closeDevice() { if (!opened_) return true; - return (fclose(fd) != 0); + return (fclose(fd) == 0); } PIString PIFile::readLine() { PIString str; if (!opened_) return str; - char cc; - int cp = 0; + int cc, cp = 0; while (!isEnd() && cp < 4095) { - cc = char(fgetc(fd)); - if (cc == '\n') break; - str.push_back(cc); + cc = fgetc(fd); + if (char(cc) == '\n' || cc == EOF) break; + str.push_back(char(cc)); } - //cout << "readline: " << buff << endl; + //cout << "readline: " << str << endl; return str; } @@ -66,17 +65,20 @@ llong PIFile::readAll(void * data) { PIByteArray PIFile::readAll(bool forceRead) { - llong cp = pos(), s = size(); PIByteArray a; - if (s < 0) { - if (!forceRead) return a; + llong cp = pos(); + if (forceRead) { + seekToBegin(); while (!isEnd()) a.push_back(readChar()); seek(cp); return a; } + llong s = size(); + if (s < 0) return a; a.resize(s); s = readAll(a.data()); + seek(cp); if (s >= 0) a.resize(s); return a; } diff --git a/pifile.h b/pifile.h old mode 100755 new mode 100644 index 6c378f86..f8966976 --- a/pifile.h +++ b/pifile.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives File - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,10 +23,14 @@ #include "piiodevice.h" #include +/// handlers: +/// void clear() +/// void resize(llong new_size, char fill = 0) +/// void remove() class PIFile: public PIIODevice { public: - PIFile(const PIString & path = PIString(), DeviceMode type = ReadWrite): PIIODevice(path, type) {openDevice();} + PIFile(const PIString & path = PIString(), DeviceMode type = ReadWrite): PIIODevice(path, type) {setPrecision(5); openDevice();} //PIFile & operator =(const PIFile & f) {path_ = f.path_; type_ = f.type_; return *this;} @@ -51,6 +55,9 @@ public: bool isEnd() {return (feof(fd) || ferror(fd));} bool isEmpty() {return (size() <= 0);} + int precision() const {return prec;} + void setPrecision(int prec_) {prec = prec_; prec_str = "." + itos(prec_);} + int read(void * read_to, int max_size) {if (!canRead()) return -1; return fread(read_to, max_size, 1, fd);} int write(const void * data, int max_size) {if (!canWrite()) return -1; return fwrite(data, max_size, 1, fd);} PIFile & writeToBinLog(ushort id, const void * data, int size) {if (!isWriteable()) return *this; writeBinary(id).writeBinary((ushort)size); write(data, size); flush(); return *this;} @@ -59,10 +66,12 @@ public: PIFile & writeBinary(const short v) {write(&v, sizeof(v)); return *this;} PIFile & writeBinary(const int v) {write(&v, sizeof(v)); return *this;} PIFile & writeBinary(const long v) {write(&v, sizeof(v)); return *this;} + PIFile & writeBinary(const llong v) {write(&v, sizeof(v)); return *this;} PIFile & writeBinary(const uchar v) {write(&v, sizeof(v)); return *this;} PIFile & writeBinary(const ushort v) {write(&v, sizeof(v)); return *this;} PIFile & writeBinary(const uint v) {write(&v, sizeof(v)); return *this;} PIFile & writeBinary(const ulong v) {write(&v, sizeof(v)); return *this;} + PIFile & writeBinary(const ullong v) {write(&v, sizeof(v)); return *this;} PIFile & writeBinary(const float v) {write(&v, sizeof(v)); return *this;} PIFile & writeBinary(const double v) {write(&v, sizeof(v)); return *this;} @@ -70,26 +79,30 @@ public: //PIFile & operator <<(const string & v) {write(v.c_str(), v.size()); return *this;} PIFile & operator <<(const PIString & v) {if (!isWriteable()) return *this; write(v.data(), v.lengthAscii()); return *this;} PIFile & operator <<(const PIByteArray & v) {if (!isWriteable()) return *this; write(v.data(), v.size()); return *this;} - PIFile & operator <<(short v) {if (!isWriteable()) return *this; fprintf(fd, "%hd", v); return *this;} - PIFile & operator <<(int v) {if (!isWriteable()) return *this; fprintf(fd, "%d", v); return *this;} - PIFile & operator <<(long v) {if (!isWriteable()) return *this; fprintf(fd, "%ld", v); return *this;} - PIFile & operator <<(uchar v) {if (!isWriteable()) return *this; fprintf(fd, "%c", v); return *this;} - PIFile & operator <<(ushort v) {if (!isWriteable()) return *this; fprintf(fd, "%hd", v); return *this;} - PIFile & operator <<(uint v) {if (!isWriteable()) return *this; fprintf(fd, "%d", v); return *this;} - PIFile & operator <<(ulong v) {if (!isWriteable()) return *this; fprintf(fd, "%ld", v); return *this;} - PIFile & operator <<(float v) {if (!isWriteable()) return *this; fprintf(fd, "%f", v); return *this;} - PIFile & operator <<(double v) {if (!isWriteable()) return *this; fprintf(fd, "%lf", v); return *this;} + PIFile & operator <<(short v) {if (!isWriteable()) return *this; ret = fprintf(fd, "%hd", v); return *this;} + PIFile & operator <<(int v) {if (!isWriteable()) return *this; ret = fprintf(fd, "%d", v); return *this;} + PIFile & operator <<(long v) {if (!isWriteable()) return *this; ret = fprintf(fd, "%ld", v); return *this;} + PIFile & operator <<(llong v) {if (!isWriteable()) return *this; ret = fprintf(fd, "%lld", v); return *this;} + PIFile & operator <<(uchar v) {if (!isWriteable()) return *this; ret = fprintf(fd, "%c", v); return *this;} + PIFile & operator <<(ushort v) {if (!isWriteable()) return *this; ret = fprintf(fd, "%hd", v); return *this;} + PIFile & operator <<(uint v) {if (!isWriteable()) return *this; ret = fprintf(fd, "%d", v); return *this;} + PIFile & operator <<(ulong v) {if (!isWriteable()) return *this; ret = fprintf(fd, "%ld", v); return *this;} + PIFile & operator <<(ullong v) {if (!isWriteable()) return *this; ret = fprintf(fd, "%lld", v); return *this;} + PIFile & operator <<(float v) {if (!isWriteable()) return *this; ret = fprintf(fd, ("%" + prec_str + "f").c_str(), v); return *this;} + PIFile & operator <<(double v) {if (!isWriteable()) return *this; ret = fprintf(fd, ("%" + prec_str + "lf").c_str(), v); return *this;} - PIFile & operator >>(char & v) {if (!isWriteable()) return *this; fscanf(fd, "%hhn", &v); return *this;} - PIFile & operator >>(short & v) {if (!isWriteable()) return *this; fscanf(fd, "%hn", &v); return *this;} - PIFile & operator >>(int & v) {if (!isWriteable()) return *this; fscanf(fd, "%n", &v); return *this;} - PIFile & operator >>(long & v) {if (!isWriteable()) return *this; fscanf(fd, "%ln", &v); return *this;} - PIFile & operator >>(uchar & v) {if (!isWriteable()) return *this; fscanf(fd, "%hhn", &v); return *this;} - PIFile & operator >>(ushort & v) {if (!isWriteable()) return *this; fscanf(fd, "%hn", &v); return *this;} - PIFile & operator >>(uint & v) {if (!isWriteable()) return *this; fscanf(fd, "%n", &v); return *this;} - PIFile & operator >>(ulong & v) {if (!isWriteable()) return *this; fscanf(fd, "%ln", &v); return *this;} - PIFile & operator >>(float & v) {if (!isWriteable()) return *this; fscanf(fd, "%f", &v); return *this;} - PIFile & operator >>(double & v) {if (!isWriteable()) return *this; fscanf(fd, "%lf", &v); return *this;} + PIFile & operator >>(char & v) {if (!isWriteable()) return *this; ret = fscanf(fd, "%hhn", &v); return *this;} + PIFile & operator >>(short & v) {if (!isWriteable()) return *this; ret = fscanf(fd, "%hn", &v); return *this;} + PIFile & operator >>(int & v) {if (!isWriteable()) return *this; ret = fscanf(fd, "%n", &v); return *this;} + PIFile & operator >>(long & v) {if (!isWriteable()) return *this; ret = fscanf(fd, "%ln", &v); return *this;} + PIFile & operator >>(llong & v) {if (!isWriteable()) return *this; ret = fscanf(fd, "%lln", &v); return *this;} + PIFile & operator >>(uchar & v) {if (!isWriteable()) return *this; ret = fscanf(fd, "%hhn", &v); return *this;} + PIFile & operator >>(ushort & v) {if (!isWriteable()) return *this; ret = fscanf(fd, "%hn", &v); return *this;} + PIFile & operator >>(uint & v) {if (!isWriteable()) return *this; ret = fscanf(fd, "%n", &v); return *this;} + PIFile & operator >>(ulong & v) {if (!isWriteable()) return *this; ret = fscanf(fd, "%ln", &v); return *this;} + PIFile & operator >>(ullong & v) {if (!isWriteable()) return *this; ret = fscanf(fd, "%lln", &v); return *this;} + PIFile & operator >>(float & v) {if (!isWriteable()) return *this; ret = fscanf(fd, "%f", &v); return *this;} + PIFile & operator >>(double & v) {if (!isWriteable()) return *this; ret = fscanf(fd, "%lf", &v); return *this;} static PIFile openTemporary(PIIODevice::DeviceMode mode = PIIODevice::ReadWrite) {return PIFile(PIString(tmpnam(0)), mode);} static bool isExists(const PIString & path); @@ -103,6 +116,8 @@ private: PIString strType(const PIIODevice::DeviceMode type) {switch (type) {case PIIODevice::ReadOnly: return "rb"; case WriteOnly: return "ab"; case ReadWrite: return "a+b";} return "rb";} FILE * fd; + int ret, prec; + string prec_str; }; diff --git a/pigeometry.h b/pigeometry.h old mode 100755 new mode 100644 index 642b559d..1536f7b0 --- a/pigeometry.h +++ b/pigeometry.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Geometry - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/piincludes.cpp b/piincludes.cpp index 35736881..eba50cb5 100644 --- a/piincludes.cpp +++ b/piincludes.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Global includes - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,3 +21,7 @@ bool isPIInit = false; bool piDebug = true; +string ifconfigPath; + +PIInit piInit; +lconv * currentLocale = std::localeconv(); diff --git a/piincludes.h b/piincludes.h old mode 100755 new mode 100644 index bede5cb1..ecd8ad4d --- a/piincludes.h +++ b/piincludes.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Global includes - Copyright (C) 2011 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,11 +20,11 @@ #ifndef PIINCLUDES_H #define PIINCLUDES_H -#define PIP_VERSION 0x000200 +#define PIP_VERSION 0x000206 #define PIP_VERSION_MAJOR (PIP_VERSION & 0xFF0000) >> 16 #define PIP_VERSION_MINOR (PIP_VERSION & 0xFF00) >> 8 #define PIP_VERSION_REVISION PIP_VERSION & 0xFF -#define PIP_VERSION_SUFFIX "" +#define PIP_VERSION_SUFFIX "_beta" #if WIN32 || WIN64 || _WIN32 || _WIN64 || __WIN32__ || __WIN64__ # define WINDOWS @@ -32,15 +32,33 @@ #if __QNX__ || __QNXNTO__ # define QNX #endif +#if __FreeBSD__ +# define FREE_BSD +#endif +#if __APPLE__ || __MACH__ +# define MAC_OS +#endif #ifndef WINDOWS # ifndef QNX -# define LINUX +# ifndef FREE_BSD +# ifndef MAC_OS +# define LINUX +# endif +# endif # endif #endif #if __GNUC__ # define CC_GCC +# define CC_GCC_VERSION ((__GNUC__ << 8) | __GNUC_MINOR__) +# if CC_GCC_VERSION > 0x025F // > 2.95 +# ifdef LINUX +# define HAS_LOCALE +# endif +# endif #elif _MSC_VER # define CC_VC +#else +# define CC_OTHER #endif #ifdef WINDOWS @@ -70,6 +88,9 @@ #include #include #include +#include +#include +//#include #include #include #include @@ -80,10 +101,46 @@ #include #include #include +#include #ifdef WINDOWS +typedef int socklen_t; # include +# include +# include +# ifdef CC_VC +# define SHUT_RDWR 2 +# pragma comment(lib, "Ws2_32.lib") +# pragma comment(lib, "Iphlpapi.lib") +# else +# define SHUT_RDWR SD_BOTH +# endif # include # include +# include +#else +# include +# include +# include +# include +# include +# include +#endif +#ifndef QNX +# ifndef WINDOWS +# ifndef FREE_BSD +# define environ __environ +# endif +# endif +#endif +#if !defined(WINDOWS) && !defined(MAC_OS) +# define PIP_TIMER_RT +#endif +#ifdef FREE_BSD +extern char ** environ; +#endif +#ifdef MAC_OS +typedef long time_t; +clock_serv_t __pi_mac_clock; #endif #include "pimonitor.h" @@ -110,18 +167,65 @@ using std::queue; using std::deque; using std::stack; using std::set; +using std::map; using std::string; #ifndef QNX using std::wstring; -# ifndef WINDOWS -static locale_t currentLocale_t = 0; -# endif +//# ifndef WINDOWS +//static locale_t currentLocale_t = 0; +//# endif #else typedef std::basic_string wstring; #endif +#ifdef HAS_LOCALE +static locale_t currentLocale_t = 0; +#endif + +template inline void piSwap(T & f, T & s) {T t = f; f = s; s = t;} +template inline int piRound(const T & v) {return int(v >= T(0.) ? v + T(0.5) : v - T(0.5));} +template inline int piFloor(const T & v) {return v < T(0) ? int(v) - 1 : int(v);} +template inline int piCeil(const T & v) {return v < T(0) ? int(v) : int(v) + 1;} +template inline T piAbs(const T & v) {return (v >= T(0) ? v : -v);} +template inline T piMin(const T & f, const T & s) {return (f > s) ? s : f;} +template inline T piMin(const T & f, const T & s, const T & t) {return (f < s && f < t) ? f : ((s < t) ? s : t);} +template inline T piMax(const T & f, const T & s) {return (f < s) ? s : f;} +template inline T piMax(const T & f, const T & s, const T & t) {return (f > s && f > t) ? f : ((s > t) ? s : t);} +template inline T piClamp(const T & v, const T & min, const T & max) {return (v > max ? max : (v < min ? min : v));} + +#define piRoundf piRound +#define piRoundd piRound +#define piFloorf piFloor +#define piFloord piFloor +#define piCeilf piCeil +#define piCeild piCeil +#define piAbss piAbs +#define piAbsi piAbs +#define piAbsl piAbs +#define piAbsll piAbs +#define piAbsf piAbs +#define piAbsd piAbs +#define piMins piMin +#define piMini piMin +#define piMinl piMin +#define piMinll piMin +#define piMinf piMin +#define piMind piMin +#define piMaxs piMax +#define piMaxi piMax +#define piMaxl piMax +#define piMaxll piMax +#define piMaxf piMax +#define piMaxd piMax +#define piClamps piClamp +#define piClampi piClamp +#define piClampl piClamp +#define piClampll piClamp +#define piClampf piClamp +#define piClampd piClamp extern bool isPIInit; extern bool piDebug; +extern string ifconfigPath; #define piCout if (piDebug) cout @@ -130,8 +234,34 @@ public: PIInit() { if (isPIInit) return; isPIInit = true; +#ifndef WINDOWS + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss, SIGALRM); + if (pthread_sigmask(SIG_BLOCK, &ss, 0) == -1) { + //cout << "[PITimer] sigaction error: " << errorString() << endl; + //return 0; + } + ifconfigPath = "/bin/ifconfig"; + if (!fileExists(ifconfigPath)) { + ifconfigPath = "/sbin/ifconfig"; + if (!fileExists(ifconfigPath)) { + ifconfigPath = "/usr/bin/ifconfig"; + if (!fileExists(ifconfigPath)) { + ifconfigPath = "/usr/sbin/ifconfig"; + if (!fileExists(ifconfigPath)) { + ifconfigPath = ""; + } + } + } + } +#else + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); +#endif //piDebug = true; -#ifdef LINUX +#ifdef HAS_LOCALE + //cout << "has locale" << endl; if (currentLocale_t != 0) { freelocale(currentLocale_t); currentLocale_t = 0; @@ -139,21 +269,26 @@ public: currentLocale_t = newlocale(LC_ALL, setlocale(LC_ALL, ""), 0); #else setlocale(LC_ALL, ""); +#endif +#ifdef MAC_OS + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &__pi_mac_clock); #endif } ~PIInit() { +#ifdef WINDOWS + WSACleanup(); +#endif +#ifdef MAC_OS + mach_port_deallocate(mach_task_self(), __pi_mac_clock); +#endif //if (currentLocale_t != 0) freelocale(currentLocale_t); } +private: + bool fileExists(const string & p) {FILE * f = fopen(p.c_str(), "r"); if (f == 0) return false; fclose(f); return true;} }; -static PIInit piInit; -static lconv * currentLocale = std::localeconv(); - -#ifdef CC_VC -inline string errorString() {char buff[1024]; strerror_s(buff, 1024, GetLastError()); return string(buff);} -#else -inline string errorString() {return string(strerror(errno));} -#endif +extern PIInit piInit; +extern lconv * currentLocale; #ifdef WINDOWS inline int random() {return rand();} @@ -162,12 +297,6 @@ inline double round(const double & v) {return floor(v + 0.5);} # endif #endif -template inline void piSwap(Type & f, Type & s) {Type t = f; f = s; s = t;} -template inline Type piMin(const Type & f, const Type & s) {return (f > s) ? s : f;} -template inline Type piMin(const Type & f, const Type & s, const Type & t) {return (f < s && f < t) ? f : ((s < t) ? s : t);} -template inline Type piMax(const Type & f, const Type & s) {return (f < s) ? s : f;} -template inline Type piMax(const Type & f, const Type & s, const Type & t) {return (f > s && f > t) ? f : ((s > t) ? s : t);} -template inline Type piClamp(const Type & v, const Type & min, const Type & max) {return (v > max ? max : (v < min ? min : v));} inline ushort letobe_s(ushort v) {return v = (v << 8) | (v >> 8);} inline bool atob(const string & str) { return str == "1" ? true : false;}; inline string btos(const bool num) { return num ? "0" : "1";}; @@ -220,6 +349,18 @@ inline string dtos(const double num) { #endif return string(ch); }; +#ifdef CC_VC +inline string errorString() { + char * msg; + int err = GetLastError(); + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL); + return "code " + itos(err) + " - " + string(msg); +} +#else +inline void errorClear() {errno = 0;} +inline string errorString() {int e = errno; return "code " + itos(e) + " - " + string(strerror(e));} +#endif + inline string PIPVersion() {return itos(PIP_VERSION_MAJOR) + "." + itos(PIP_VERSION_MINOR) + "." + itos(PIP_VERSION_REVISION) + PIP_VERSION_SUFFIX;} #endif // PIINCLUDES_H diff --git a/piiodevice.cpp b/piiodevice.cpp old mode 100755 new mode 100644 index 27a78afe..41912d9b --- a/piiodevice.cpp +++ b/piiodevice.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Abstract input/output device - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -96,7 +96,6 @@ void PIIODevice::run() { //cout << "not started\n"; return; } - readed_ = read(buffer_tr.data(), buffer_tr.size_s()); if (readed_ <= 0) { msleep(10); diff --git a/piiodevice.h b/piiodevice.h old mode 100755 new mode 100644 index 8fee6051..bc8b7181 --- a/piiodevice.h +++ b/piiodevice.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Abstract input/output device - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,6 +25,18 @@ // function executed from threaded read, pass ThreadedReadData, readedData, sizeOfData typedef bool (*ReadRetFunc)(void * , uchar * , int ); +/// events: +/// void opened() +/// void closed() +/// +/// handlers: +/// bool open() +/// bool open(const PIString & path) +/// bool open(const DeviceMode & type) +/// bool open(const PIString & path, const DeviceMode & type) +/// bool close() +/// bool initialize() +/// void flush() class PIIODevice: public PIThread { public: PIIODevice(); @@ -32,7 +44,7 @@ public: enum DeviceMode {ReadOnly = 0x01, WriteOnly = 0x02, ReadWrite = 0x03}; PIIODevice(const PIString & path, DeviceMode type = ReadWrite, bool initNow = true); - ~PIIODevice() {if (opened_) closeDevice();} + virtual ~PIIODevice() {if (opened_) {closeDevice(); if (!opened_) closed();}} DeviceMode mode() const {return mode_;} @@ -66,15 +78,19 @@ public: void startThreadedRead(ReadRetFunc func) {ret_func_ = func; if (!isRunning()) start();} - EVENT_HANDLER(PIIODevice, bool, open) {if (!init_) init(); opened_ = openDevice(); return opened_;} - EVENT_HANDLER1(PIIODevice, bool, open, const PIString &, _path) {path_ = _path; if (!init_) init(); opened_ = openDevice(); return opened_;} - EVENT_HANDLER1(PIIODevice, bool, open, const DeviceMode &, _type) {mode_ = _type; if (!init_) init(); opened_ = openDevice(); return opened_;} - EVENT_HANDLER2(PIIODevice, bool, open, const PIString &, _path, const DeviceMode &, _type) {path_ = _path; mode_ = _type; if (!init_) init(); opened_ = openDevice(); return opened_;} - EVENT_HANDLER(PIIODevice, bool, close) {opened_ = !closeDevice(); return !opened_;} + EVENT_HANDLER(PIIODevice, bool, open) {if (!init_) init(); opened_ = openDevice(); if (opened_) opened(); return opened_;} + EVENT_HANDLER1(PIIODevice, bool, open, const PIString &, _path) {path_ = _path; if (!init_) init(); opened_ = openDevice(); if (opened_) opened(); return opened_;} + EVENT_HANDLER1(PIIODevice, bool, open, const DeviceMode &, _type) {mode_ = _type; if (!init_) init(); opened_ = openDevice(); if (opened_) opened(); return opened_;} + EVENT_HANDLER2(PIIODevice, bool, open, const PIString &, _path, const DeviceMode &, _type) {path_ = _path; mode_ = _type; if (!init_) init(); opened_ = openDevice(); if (opened_) opened(); return opened_;} + EVENT_HANDLER(PIIODevice, bool, close) {opened_ = !closeDevice(); if (!opened_) closed(); return !opened_;} + EVENT_HANDLER(PIIODevice, bool, initialize) {init_ = init(); return init_;} // Flush device EVENT_VHANDLER(PIIODevice, void, flush) {;} + EVENT(PIIODevice, opened) + EVENT(PIIODevice, closed) + // Read from device to "read_to" maximum "max_size" bytes, return readed bytes count virtual int read(void * read_to, int max_size) {piCout << "[PIIODevice] \"read\" not implemented!" << endl; return -2;} @@ -84,6 +100,7 @@ public: // Read from device maximum "max_size" bytes and return them as PIByteArray PIByteArray read(int max_size) {buffer_in.resize(max_size); int ret = read(buffer_in.data(), max_size); if (ret < 0) return PIByteArray(); return buffer_in.resized(ret);} + int write(const PIByteArray & data) {return write(data.data(), data.size_s());} protected: @@ -97,6 +114,8 @@ protected: // Function executed when thread read some data, default implementation execute external slot "ret_func_" virtual bool threadedRead(uchar * readed, int size) {if (ret_func_ != 0) return ret_func_(ret_data_, readed, size); return true;} + void terminate(); + PIString path_; DeviceMode mode_; ReadRetFunc ret_func_; @@ -106,7 +125,6 @@ protected: private: EVENT_HANDLER2(PIIODevice, void, check_start, void * , data, int, delim); - void terminate(); void begin(); void run(); diff --git a/pikbdlistener.cpp b/pikbdlistener.cpp old mode 100755 new mode 100644 index e505eb45..3fc84153 --- a/pikbdlistener.cpp +++ b/pikbdlistener.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Keyboard grabber for console - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -64,6 +64,7 @@ void PIKbdListener::run() { PIKbdListener::exiting = true; return; } + keyPressed(rc, data); if (ret_func != 0 && ret > 0) ret_func(rc, data); } diff --git a/pikbdlistener.h b/pikbdlistener.h old mode 100755 new mode 100644 index c8ed6b87..9d122477 --- a/pikbdlistener.h +++ b/pikbdlistener.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Keyboard grabber for console - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,29 +29,39 @@ typedef void (*KBFunc)(char, void * ); +/// events: +/// keyPressed(char key, void * data) +/// +/// handlers: +/// void enableExitCapture(char key = 'Q') +/// void setActive(bool yes = true) class PIKbdListener: public PIThread { friend class PIConsole; public: // slot is any function format "void (char, void * )" PIKbdListener(KBFunc slot = 0, void * data = 0); ~PIKbdListener() {terminate(); end();} - + void setData(void * data_) {data = data_;} void setSlot(KBFunc slot_) {ret_func = slot_;} - void enableExitCapture(char key = 'Q') {exit_enabled = true; exit_key = key;} + EVENT_HANDLER(PIKbdListener, void, enableExitCapture) {enableExitCapture('Q');} + EVENT_HANDLER1(PIKbdListener, void, enableExitCapture, char, key) {exit_enabled = true; exit_key = key;} void disableExitCapture() {exit_enabled = false;} bool exitCaptured() const {return exit_enabled;} char exitKey() const {return exit_key;} bool isActive() {return is_active;} - void setActive(bool yes = true); + EVENT_HANDLER(PIKbdListener, void, setActive) {setActive(true);} + EVENT_HANDLER1(PIKbdListener, void, setActive, bool, yes); + EVENT2(PIKbdListener, keyPressed, char, key, void * , data) + static bool exiting; - + private: void begin(); void run(); void end(); - + KBFunc ret_func; char exit_key; bool exit_enabled, is_active; @@ -65,7 +75,7 @@ private: int ret; struct termios sterm, tterm; #endif - + }; #endif // PIKBDLISTENER_H diff --git a/pimath.cpp b/pimath.cpp old mode 100755 new mode 100644 index b0107e67..c7cff361 --- a/pimath.cpp +++ b/pimath.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Math - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,160 +19,6 @@ #include "pimath.h" -/* -* Fast Fourier Transformation -* ==================================================== -* Coded by Miroslav Voinarovsky, 2002 -* This source is freeware. -*/ - -// This array contains values from 0 to 255 with reverse bit order -static uchar reverse256[]= { - 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, - 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, - 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, - 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, - 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, - 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, - 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, - 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, - 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, - 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, - 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, - 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, - 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, - 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, - 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, - 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, - 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, - 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, - 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, - 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, - 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, - 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, - 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, - 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, - 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, - 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, - 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, - 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, - 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, - 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, - 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, - 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF, -}; - -//This is array exp(-2*pi*j/2^n) for n= 1,...,32 -//exp(-2*pi*j/2^n) = complexd( cos(2*pi/2^n), -sin(2*pi/2^n) ) -static complexd W2n[32] = { - complexd(-1.00000000000000000000000000000000, 0.00000000000000000000000000000000), // W2 calculator (copy/paste) : po, ps - complexd( 0.00000000000000000000000000000000, -1.00000000000000000000000000000000), // W4: p/2=o, p/2=s - complexd( 0.70710678118654752440084436210485, -0.70710678118654752440084436210485), // W8: p/4=o, p/4=s - complexd( 0.92387953251128675612818318939679, -0.38268343236508977172845998403040), // p/8=o, p/8=s - complexd( 0.98078528040323044912618223613424, -0.19509032201612826784828486847702), // p/16= - complexd( 0.99518472667219688624483695310948, -9.80171403295606019941955638886e-2), // p/32= - complexd( 0.99879545620517239271477160475910, -4.90676743274180142549549769426e-2), // p/64= - complexd( 0.99969881869620422011576564966617, -2.45412285229122880317345294592e-2), // p/128= - complexd( 0.99992470183914454092164649119638, -1.22715382857199260794082619510e-2), // p/256= - complexd( 0.99998117528260114265699043772857, -6.13588464915447535964023459037e-3), // p/(2y9)= - complexd( 0.99999529380957617151158012570012, -3.06795676296597627014536549091e-3), // p/(2y10)= - complexd( 0.99999882345170190992902571017153, -1.53398018628476561230369715026e-3), // p/(2y11)= - complexd( 0.99999970586288221916022821773877, -7.66990318742704526938568357948e-4), // p/(2y12)= - complexd( 0.99999992646571785114473148070739, -3.83495187571395589072461681181e-4), // p/(2y13)= - complexd( 0.99999998161642929380834691540291, -1.91747597310703307439909561989e-4), // p/(2y14)= - complexd( 0.99999999540410731289097193313961, -9.58737990959773458705172109764e-5), // p/(2y15)= - complexd( 0.99999999885102682756267330779455, -4.79368996030668845490039904946e-5), // p/(2y16)= - complexd( 0.99999999971275670684941397221864, -2.39684498084182187291865771650e-5), // p/(2y17)= - complexd( 0.99999999992818917670977509588385, -1.19842249050697064215215615969e-5), // p/(2y18)= - complexd( 0.99999999998204729417728262414778, -5.99211245264242784287971180889e-6), // p/(2y19)= - complexd( 0.99999999999551182354431058417300, -2.99605622633466075045481280835e-6), // p/(2y20)= - complexd( 0.99999999999887795588607701655175, -1.49802811316901122885427884615e-6), // p/(2y21)= - complexd( 0.99999999999971948897151921479472, -7.49014056584715721130498566730e-7), // p/(2y22)= - complexd( 0.99999999999992987224287980123973, -3.74507028292384123903169179084e-7), // p/(2y23)= - complexd( 0.99999999999998246806071995015625, -1.87253514146195344868824576593e-7), // p/(2y24)= - complexd( 0.99999999999999561701517998752946, -9.36267570730980827990672866808e-8), // p/(2y25)= - complexd( 0.99999999999999890425379499688176, -4.68133785365490926951155181385e-8), // p/(2y26)= - complexd( 0.99999999999999972606344874922040, -2.34066892682745527595054934190e-8), // p/(2y27)= - complexd( 0.99999999999999993151586218730510, -1.17033446341372771812462135032e-8), // p/(2y28)= - complexd( 0.99999999999999998287896554682627, -5.85167231706863869080979010083e-9), // p/(2y29)= - complexd( 0.99999999999999999571974138670657, -2.92583615853431935792823046906e-9), // p/(2y30)= - complexd( 0.99999999999999999892993534667664, -1.46291807926715968052953216186e-9), // p/(2y31)= -}; - -/* - * x: x - array of items - * T: 1 << T = 2 power T - number of items in array - * complement: false - normal (direct) transformation, true - reverse transformation - */ -void fft(complexd * x, int T, bool complement) -{ - uint I, J, Nmax, N, Nd2, k, m, mpNd2, Skew; - uchar *Ic = (uchar*) &I; - uchar *Jc = (uchar*) &J; - complexd S; - complexd * Wstore, * Warray; - complexd WN, W, Temp, *pWN; - - Nmax = 1 << T; - - //first interchanging - for(I = 1; I < Nmax - 1; I++) - { - Jc[0] = reverse256[Ic[3]]; - Jc[1] = reverse256[Ic[2]]; - Jc[2] = reverse256[Ic[1]]; - Jc[3] = reverse256[Ic[0]]; - J >>= (32 - T); - if (I < J) - { - S = x[I]; - x[I] = x[J]; - x[J] = S; - } - } - - //rotation multiplier array allocation - Wstore = new complexd[Nmax / 2]; - Wstore[0] = complexd(1., 0.); - - //main loop - for(N = 2, Nd2 = 1, pWN = W2n, Skew = Nmax >> 1; N <= Nmax; Nd2 = N, N += N, pWN++, Skew >>= 1) - { - //WN = W(1, N) = exp(-2*pi*j/N) - WN= *pWN; - if (complement) - WN = complexd(WN.real(), -WN.imag()); - for(Warray = Wstore, k = 0; k < Nd2; k++, Warray += Skew) - { - if (k & 1) - { - W *= WN; - *Warray = W; - } - else - W = *Warray; - - for(m = k; m < Nmax; m += N) - { - mpNd2 = m + Nd2; - Temp = W; - Temp *= x[mpNd2]; - x[mpNd2] = x[m]; - x[mpNd2] -= Temp; - x[m] += Temp; - } - } - } - - delete[] Wstore; - - if (complement) - { - for( I = 0; I < Nmax; I++ ) - x[I] /= Nmax; - } -} - const char Solver::methods_desc[] = "b{Methods:}\ \n -1 - Global settings\ @@ -401,3 +247,981 @@ void Solver::solvePA(double u, double h, uint deg) { } moveF(); } + + + +PIFFT::PIFFT() { + prepared = false; +} + + +PIVector * PIFFT::calcFFT(const PIVector & val) { +// for (uint i=0; i *PIFFT::calcFFTinverse(const PIVector &val) +{ + result.clear(); + if (val.size_s() < 4) return &result; + fftc1dinv(val, val.size()); + return &result; +} + + +PIVector *PIFFT::calcHilbert(const PIVector &val) +{ + result.clear(); + if (val.size_s() < 4) return &result; + fftc1r(val, val.size()); + for (uint i=0; i* PIFFT::calcFFT(const PIVector & val) { + result.clear(); + if (val.size_s() < 4) return &result; + fftc1r(val, val.size()); + return &result; +} + + +PIVector PIFFT::getAmplitude() { + PIVector a; + double tmp; + for (uint i=0; i &a, uint n) { + createPlan(n); + uint i; + PIVector buf; + buf.resize(2*n); + for(i=0; iptr.p_complex[i].x; + buf[2*i+1] = a.at(i).imag();//a->ptr.p_complex[i].y; + } + ftbaseexecuteplan(&buf, 0, n, &curplan); + result.resize(n); + for(i=0; i & a, uint n) { + uint i; + if( n%2==0 ) { + PIVector buf; + uint n2 = n/2; + //buf.resize(n); + buf = a; + createPlan(n2); + //cout << "fftr " << n2 << endl; + ftbaseexecuteplan(&buf, 0, n2, &curplan); + result.resize(n); + uint idx; + complexd hn, hmnc, v; + for(i=0; i<=n2; i++) { + idx = 2*(i%n2); + hn = complexd(buf[idx+0], buf[idx+1]); + idx = 2*((n2-i)%n2); + hmnc = complexd(buf[idx+0], -buf[idx+1]); + v = complexd(sin(M_PI*i/n2), cos(M_PI*i/n2)); + result[i] = ((hn + hmnc) - (v * (hn - hmnc))); + result[i] *= 0.5; + } + for(i=n2+1; i cbuf; + cbuf.resize(n); + for(i=0; i &a, uint n) +{ + PIVector cbuf; + cbuf.resize(n); + uint i; + for(i=0; i(*planarraysize) ) { + curplan.plan.resize(8*(*planarraysize)); + *planarraysize = 8*(*planarraysize); + } + entryoffset = *plansize; + esize = ftbase_ftbaseplanentrysize; + *plansize = *plansize+esize; + if( n==1 ) { + curplan.plan[entryoffset+0] = esize; + curplan.plan[entryoffset+1] = -1; + curplan.plan[entryoffset+2] = -1; + curplan.plan[entryoffset+3] = ftbase_fftemptyplan; + curplan.plan[entryoffset+4] = -1; + curplan.plan[entryoffset+5] = -1; + curplan.plan[entryoffset+6] = -1; + curplan.plan[entryoffset+7] = -1; + return; + } + ftbasefactorize(n, &n1, &n2); + if( n1!=1 ) { + *tmpmemsize = piMax(*tmpmemsize, 2*n1*n2); + curplan.plan[entryoffset+0] = esize; + curplan.plan[entryoffset+1] = n1; + curplan.plan[entryoffset+2] = n2; + if( tasktype==ftbase_ftbasecffttask ) + curplan.plan[entryoffset+3] = ftbase_fftcooleytukeyplan; + else + curplan.plan[entryoffset+3] = ftbase_fftrealcooleytukeyplan; + curplan.plan[entryoffset+4] = 0; + curplan.plan[entryoffset+5] = *plansize; + debugi++; + ftbase_ftbasegenerateplanrec(n1, ftbase_ftbasecffttask, plan, plansize, precomputedsize, planarraysize, tmpmemsize, stackmemsize, stackptr,debugi); + curplan.plan[entryoffset+6] = *plansize; + ftbase_ftbasegenerateplanrec(n2, ftbase_ftbasecffttask, plan, plansize, precomputedsize, planarraysize, tmpmemsize, stackmemsize, stackptr,debugi); + curplan.plan[entryoffset+7] = -1; + return; + } else { + if (n>=2 && n<=5) { + curplan.plan[entryoffset+0] = esize; + curplan.plan[entryoffset+1] = n1; + curplan.plan[entryoffset+2] = n2; + curplan.plan[entryoffset+3] = ftbase_fftcodeletplan; + curplan.plan[entryoffset+4] = 0; + curplan.plan[entryoffset+5] = -1; + curplan.plan[entryoffset+6] = -1; + curplan.plan[entryoffset+7] = *precomputedsize; + if( n==3 ) + *precomputedsize = *precomputedsize+2; + if( n==5 ) + *precomputedsize = *precomputedsize+5; + return; + } else { + k = 2*n2-1; + m = ftbasefindsmooth(k); + *tmpmemsize = piMax(*tmpmemsize, 2*m); + curplan.plan[entryoffset+0] = esize; + curplan.plan[entryoffset+1] = n2; + curplan.plan[entryoffset+2] = -1; + curplan.plan[entryoffset+3] = ftbase_fftbluesteinplan; + curplan.plan[entryoffset+4] = m; + curplan.plan[entryoffset+5] = *plansize; + stackptr = stackptr+2*2*m; + *stackmemsize = piMax(*stackmemsize, stackptr); + ftbase_ftbasegenerateplanrec(m, ftbase_ftbasecffttask, plan, plansize, precomputedsize, planarraysize, tmpmemsize, stackmemsize, stackptr); + stackptr = stackptr-2*2*m; + curplan.plan[entryoffset+6] = -1; + curplan.plan[entryoffset+7] = *precomputedsize; + *precomputedsize = *precomputedsize+2*m+2*n; + return; + } + } +} + + +/************************************************************************* +Recurrent subroutine for precomputing FFT plans + +-- ALGLIB -- + Copyright 01.05.2009 by Bochkanov Sergey +*************************************************************************/ +void PIFFT::ftbase_ftbaseprecomputeplanrec(ftplan* plan, + int entryoffset, + ae_int_t stackptr) +{ + int n1, n2, n, m, offs; + double v, bx, by; + int ftbase_fftcooleytukeyplan = 0; + int ftbase_fftbluesteinplan = 1; + int ftbase_fftcodeletplan = 2; + int ftbase_fhtcooleytukeyplan = 3; + int ftbase_fhtcodeletplan = 4; + int ftbase_fftrealcooleytukeyplan = 5; + if( (curplan.plan[entryoffset+3]==ftbase_fftcooleytukeyplan||curplan.plan[entryoffset+3]==ftbase_fftrealcooleytukeyplan)||curplan.plan[entryoffset+3]==ftbase_fhtcooleytukeyplan ) { + ftbase_ftbaseprecomputeplanrec(plan, curplan.plan[entryoffset+5], stackptr); + ftbase_ftbaseprecomputeplanrec(plan, curplan.plan[entryoffset+6], stackptr); + return; + } + if( curplan.plan[entryoffset+3]==ftbase_fftcodeletplan||curplan.plan[entryoffset+3]==ftbase_fhtcodeletplan ) { + n1 = curplan.plan[entryoffset+1]; + n2 = curplan.plan[entryoffset+2]; + n = n1*n2; + if( n==3 ) { + offs = curplan.plan[entryoffset+7]; + curplan.precomputed[offs+0] = cos(2*M_PI/3)-1; + curplan.precomputed[offs+1] = sin(2*M_PI/3); + return; + } + if( n==5 ) { + offs = curplan.plan[entryoffset+7]; + v = 2*M_PI/5; + curplan.precomputed[offs+0] = (cos(v)+cos(2*v))/2-1; + curplan.precomputed[offs+1] = (cos(v)-cos(2*v))/2; + curplan.precomputed[offs+2] = -sin(v); + curplan.precomputed[offs+3] = -(sin(v)+sin(2*v)); + curplan.precomputed[offs+4] = sin(v)-sin(2*v); + return; + } + } + if( curplan.plan[entryoffset+3]==ftbase_fftbluesteinplan ) { + ftbase_ftbaseprecomputeplanrec(plan, curplan.plan[entryoffset+5], stackptr); + n = curplan.plan[entryoffset+1]; + m = curplan.plan[entryoffset+4]; + offs = curplan.plan[entryoffset+7]; + for(int i=0; i<=2*m-1; i++) + curplan.precomputed[offs+i] = 0; + for(int i=0; i0 ) { + curplan.precomputed[offs+2*(m-i)+0] = bx; + curplan.precomputed[offs+2*(m-i)+1] = by; + } + } + ftbaseexecuteplanrec(&curplan.precomputed, offs, plan, curplan.plan[entryoffset+5], stackptr); + return; + } +} + + +void PIFFT::ftbasefactorize(int n, int* n1, int* n2) { + *n1 = *n2 = 0; + int ftbase_ftbasecodeletrecommended = 5; + if( (*n1)*(*n2)!=n ) { + for(int j=ftbase_ftbasecodeletrecommended; j>=2; j--) { + if( n%j==0 ) { + *n1 = j; + *n2 = n/j; + break; + } + } + } + if( (*n1)*(*n2)!=n ) { + for(int j=ftbase_ftbasecodeletrecommended+1; j<=n-1; j++) { + if( n%j==0 ) { + *n1 = j; + *n2 = n/j; + break; + } + } + } + if( (*n1)*(*n2)!=n ) { + *n1 = 1; + *n2 = n; + } + if( (*n2)==1 && (*n1)!=1 ) { + *n2 = *n1; + *n1 = 1; + } +} + + +/************************************************************************* +Is number smooth? + +-- ALGLIB -- + Copyright 01.05.2009 by Bochkanov Sergey +*************************************************************************/ +void PIFFT::ftbase_ftbasefindsmoothrec(int n, int seed, int leastfactor, int* best) { + if( seed>=n ) { + *best = piMin(*best, seed); + return; + } + if( leastfactor<=2 ) + ftbase_ftbasefindsmoothrec(n, seed*2, 2, best); + if( leastfactor<=3 ) + ftbase_ftbasefindsmoothrec(n, seed*3, 3, best); + if( leastfactor<=5 ) + ftbase_ftbasefindsmoothrec(n, seed*5, 5, best); +} + + +int PIFFT::ftbasefindsmooth(int n) { + int best, result; + best = 2; + while(best* a, int m, int n, int astart, PIVector* buf) { + ftbase_fftirltrec(a, astart, n, buf, 0, m, m, n); + for (int i=0; i<2*m*n; i++) (*a)[astart+i] = (*buf)[i]; +} + + +void PIFFT::ftbase_fftirltrec(PIVector* a, int astart, int astride, PIVector* b, int bstart, int bstride, int m, int n) { + int idx1, idx2; + int m1, n1; + if( m==0||n==0 ) + return; + if( piMax(m, n)<=8 ) { + for(int i=0; i<=m-1; i++) { + idx1 = bstart+i; + idx2 = astart+i*astride; + for(int j=0; j<=n-1; j++) { + (*b)[idx1] = a->at(idx2); + idx1 = idx1+bstride; + idx2 = idx2+1; + } + } + return; + } + if( n>m ) { + n1 = n/2; + if( n-n1>=8&&n1%8!=0 ) + n1 = n1+(8-n1%8); + ftbase_fftirltrec(a, astart, astride, b, bstart, bstride, m, n1); + ftbase_fftirltrec(a, astart+n1, astride, b, bstart+n1*bstride, bstride, m, n-n1); + } else { + m1 = m/2; + if( m-m1>=8&&m1%8!=0 ) + m1 = m1+(8-m1%8); + ftbase_fftirltrec(a, astart, astride, b, bstart, bstride, m1, n); + ftbase_fftirltrec(a, astart+m1*astride, astride, b, bstart+m1, bstride, m-m1, n); + } +} + + +void PIFFT::ftbase_internalcomplexlintranspose(PIVector* a, int m, int n, int astart, PIVector* buf) { + ftbase_ffticltrec(a, astart, n, buf, 0, m, m, n); + for (int i=0; i<2*m*n; i++) + (*a)[astart+i] = (*buf)[i]; +} + + +void PIFFT::ftbase_ffticltrec(PIVector* a, int astart, int astride, PIVector* b, int bstart, int bstride, int m, int n) { + int idx1, idx2, m2, m1, n1; + if( m==0||n==0 ) + return; + if( piMax(m, n)<=8 ) { + m2 = 2*bstride; + for(int i=0; i<=m-1; i++) { + idx1 = bstart+2*i; + idx2 = astart+2*i*astride; + for(int j=0; j<=n-1; j++) { + (*b)[idx1+0] = a->at(idx2+0); + (*b)[idx1+1] = a->at(idx2+1); + idx1 = idx1+m2; + idx2 = idx2+2; + } + } + return; + } + if( n>m ) { + n1 = n/2; + if( n-n1>=8&&n1%8!=0 ) + n1 = n1+(8-n1%8); + ftbase_ffticltrec(a, astart, astride, b, bstart, bstride, m, n1); + ftbase_ffticltrec(a, astart+2*n1, astride, b, bstart+2*n1*bstride, bstride, m, n-n1); + } else { + m1 = m/2; + if( m-m1>=8&&m1%8!=0 ) + m1 = m1+(8-m1%8); + ftbase_ffticltrec(a, astart, astride, b, bstart, bstride, m1, n); + ftbase_ffticltrec(a, astart+2*m1*astride, astride, b, bstart+2*m1, bstride, m-m1, n); + } +} + + +void PIFFT::ftbaseexecuteplan(PIVector* a, int aoffset, int n, ftplan* plan) { + ae_int_t stackptr; + stackptr = 0; + ftbaseexecuteplanrec(a, aoffset, plan, 0, stackptr); +} + + +/************************************************************************* +Recurrent subroutine for the FTBaseExecutePlan + +Parameters: + A FFT'ed array + AOffset offset of the FFT'ed part (distance is measured in doubles) + +-- ALGLIB -- + Copyright 01.05.2009 by Bochkanov Sergey +*************************************************************************/ +void PIFFT::ftbaseexecuteplanrec(PIVector* a, int aoffset, ftplan* plan, int entryoffset, ae_int_t stackptr) { + int n1, n2, n, m, offs, offs1, offs2, offsa, offsb, offsp; + double hk, hnk, x, y, bx, by, v0, v1, v2, v3; + double a0x, a0y, a1x, a1y, a2x, a2y, a3x, a3y; + double t1x, t1y, t2x, t2y, t3x, t3y, t4x, t4y, t5x, t5y; + double m1x, m1y, m2x, m2y, m3x, m3y, m4x, m4y, m5x, m5y; + double s1x, s1y, s2x, s2y, s3x, s3y, s4x, s4y, s5x, s5y; + double c1, c2, c3, c4, c5; + int ftbase_fftcooleytukeyplan = 0; + int ftbase_fftbluesteinplan = 1; + int ftbase_fftcodeletplan = 2; + int ftbase_fhtcooleytukeyplan = 3; + int ftbase_fhtcodeletplan = 4; + int ftbase_fftrealcooleytukeyplan = 5; + int ftbase_fftemptyplan = 6; + PIVector & tmpb(curplan.tmpbuf); + + if( curplan.plan[entryoffset+3]==ftbase_fftemptyplan ) + return; + if( curplan.plan[entryoffset+3]==ftbase_fftcooleytukeyplan ) { + n1 = curplan.plan[entryoffset+1]; + n2 = curplan.plan[entryoffset+2]; + ftbase_internalcomplexlintranspose(a, n1, n2, aoffset, &(curplan.tmpbuf)); + for(int i=0; i<=n2-1; i++) + ftbaseexecuteplanrec(a, aoffset+i*n1*2, plan, curplan.plan[entryoffset+5], stackptr); + ftbase_ffttwcalc(a, aoffset, n1, n2); + ftbase_internalcomplexlintranspose(a, n2, n1, aoffset, &(curplan.tmpbuf)); + for(int i=0; i<=n1-1; i++) + ftbaseexecuteplanrec(a, aoffset+i*n2*2, plan, curplan.plan[entryoffset+6], stackptr); + ftbase_internalcomplexlintranspose(a, n1, n2, aoffset, &(curplan.tmpbuf)); + return; + } + if( curplan.plan[entryoffset+3]==ftbase_fftrealcooleytukeyplan ) { + n1 = curplan.plan[entryoffset+1]; + n2 = curplan.plan[entryoffset+2]; + ftbase_internalcomplexlintranspose(a, n2, n1, aoffset, &(curplan.tmpbuf)); + for(int i=0; i<=n1/2-1; i++) { + offs = aoffset+2*i*n2*2; + for(int k=0; k<=n2-1; k++) + (*a)[offs+2*k+1] = (*a)[offs+2*n2+2*k+0]; + ftbaseexecuteplanrec(a, offs, plan, curplan.plan[entryoffset+6], stackptr); + tmpb[0] = (*a)[offs+0]; + tmpb[1] = 0; + tmpb[2*n2+0] = (*a)[offs+1]; + tmpb[2*n2+1] = 0; + for(int k=1; k<=n2-1; k++) { + offs1 = 2*k; + offs2 = 2*n2+2*k; + hk = (*a)[offs+2*k+0]; + hnk = (*a)[offs+2*(n2-k)+0]; + tmpb[offs1+0] = 0.5*(hk+hnk); + tmpb[offs2+1] = -0.5*(hk-hnk); + hk = (*a)[offs+2*k+1]; + hnk = (*a)[offs+2*(n2-k)+1]; + tmpb[offs2+0] = 0.5*(hk+hnk); + tmpb[offs1+1] = 0.5*(hk-hnk); + } + for (int i=0; i<2*n2*2; i++) (*a)[offs+i] = tmpb[i]; + } + if( n1%2!=0 ) + ftbaseexecuteplanrec(a, aoffset+(n1-1)*n2*2, plan, curplan.plan[entryoffset+6], stackptr); + ftbase_ffttwcalc(a, aoffset, n2, n1); + ftbase_internalcomplexlintranspose(a, n1, n2, aoffset, &(curplan.tmpbuf)); + for(int i=0; i<=n2-1; i++) + ftbaseexecuteplanrec(a, aoffset+i*n1*2, plan, curplan.plan[entryoffset+5], stackptr); + ftbase_internalcomplexlintranspose(a, n2, n1, aoffset, &(curplan.tmpbuf)); + return; + } + if( curplan.plan[entryoffset+3]==ftbase_fhtcooleytukeyplan ) { + n1 = curplan.plan[entryoffset+1]; + n2 = curplan.plan[entryoffset+2]; + n = n1*n2; + ftbase_internalreallintranspose(a, n1, n2, aoffset, &(curplan.tmpbuf)); + for(int i=0; i<=n2-1; i++) + ftbaseexecuteplanrec(a, aoffset+i*n1, plan, curplan.plan[entryoffset+5], stackptr); + for(int i=0; i<=n2-1; i++) { + for(int j=0; j<=n1-1; j++) { + offsa = aoffset+i*n1; + hk = (*a)[offsa+j]; + hnk = (*a)[offsa+(n1-j)%n1]; + offs = 2*(i*n1+j); + tmpb[offs+0] = -0.5*(hnk-hk); + tmpb[offs+1] = 0.5*(hk+hnk); + } + } + ftbase_ffttwcalc(&(curplan.tmpbuf), 0, n1, n2); + for(int j=0; j<=n1-1; j++) + (*a)[aoffset+j] = tmpb[2*j+0]+tmpb[2*j+1]; + if( n2%2==0 ) { + offs = 2*(n2/2)*n1; + offsa = aoffset+n2/2*n1; + for(int j=0; j<=n1-1; j++) + (*a)[offsa+j] = tmpb[offs+2*j+0]+tmpb[offs+2*j+1]; + } + for(int i=1; i<=(n2+1)/2-1; i++) { + offs = 2*i*n1; + offs2 = 2*(n2-i)*n1; + offsa = aoffset+i*n1; + for(int j=0; j<=n1-1; j++) + (*a)[offsa+j] = tmpb[offs+2*j+1]+tmpb[offs2+2*j+0]; + offsa = aoffset+(n2-i)*n1; + for(int j=0; j<=n1-1; j++) + (*a)[offsa+j] = tmpb[offs+2*j+0]+tmpb[offs2+2*j+1]; + } + ftbase_internalreallintranspose(a, n2, n1, aoffset, &(curplan.tmpbuf)); + for(int i=0; i<=n1-1; i++) + ftbaseexecuteplanrec(a, aoffset+i*n2, plan, curplan.plan[entryoffset+6], stackptr); + ftbase_internalreallintranspose(a, n1, n2, aoffset, &(curplan.tmpbuf)); + return; + } + if( curplan.plan[entryoffset+3]==ftbase_fftcodeletplan ) { + n1 = curplan.plan[entryoffset+1]; + n2 = curplan.plan[entryoffset+2]; + n = n1*n2; + if( n==2 ) { + a0x = (*a)[aoffset+0]; + a0y = (*a)[aoffset+1]; + a1x = (*a)[aoffset+2]; + a1y = (*a)[aoffset+3]; + v0 = a0x+a1x; + v1 = a0y+a1y; + v2 = a0x-a1x; + v3 = a0y-a1y; + (*a)[aoffset+0] = v0; + (*a)[aoffset+1] = v1; + (*a)[aoffset+2] = v2; + (*a)[aoffset+3] = v3; + return; + } + if( n==3 ) { + offs = curplan.plan[entryoffset+7]; + c1 = curplan.precomputed[offs+0]; + c2 = curplan.precomputed[offs+1]; + a0x = (*a)[aoffset+0]; + a0y = (*a)[aoffset+1]; + a1x = (*a)[aoffset+2]; + a1y = (*a)[aoffset+3]; + a2x = (*a)[aoffset+4]; + a2y = (*a)[aoffset+5]; + t1x = a1x+a2x; + t1y = a1y+a2y; + a0x = a0x+t1x; + a0y = a0y+t1y; + m1x = c1*t1x; + m1y = c1*t1y; + m2x = c2*(a1y-a2y); + m2y = c2*(a2x-a1x); + s1x = a0x+m1x; + s1y = a0y+m1y; + a1x = s1x+m2x; + a1y = s1y+m2y; + a2x = s1x-m2x; + a2y = s1y-m2y; + (*a)[aoffset+0] = a0x; + (*a)[aoffset+1] = a0y; + (*a)[aoffset+2] = a1x; + (*a)[aoffset+3] = a1y; + (*a)[aoffset+4] = a2x; + (*a)[aoffset+5] = a2y; + return; + } + if( n==4 ) { + a0x = (*a)[aoffset+0]; + a0y = (*a)[aoffset+1]; + a1x = (*a)[aoffset+2]; + a1y = (*a)[aoffset+3]; + a2x = (*a)[aoffset+4]; + a2y = (*a)[aoffset+5]; + a3x = (*a)[aoffset+6]; + a3y = (*a)[aoffset+7]; + t1x = a0x+a2x; + t1y = a0y+a2y; + t2x = a1x+a3x; + t2y = a1y+a3y; + m2x = a0x-a2x; + m2y = a0y-a2y; + m3x = a1y-a3y; + m3y = a3x-a1x; + (*a)[aoffset+0] = t1x+t2x; + (*a)[aoffset+1] = t1y+t2y; + (*a)[aoffset+4] = t1x-t2x; + (*a)[aoffset+5] = t1y-t2y; + (*a)[aoffset+2] = m2x+m3x; + (*a)[aoffset+3] = m2y+m3y; + (*a)[aoffset+6] = m2x-m3x; + (*a)[aoffset+7] = m2y-m3y; + return; + } + if( n==5 ) { + offs = curplan.plan[entryoffset+7]; + c1 = curplan.precomputed[offs+0]; + c2 = curplan.precomputed[offs+1]; + c3 = curplan.precomputed[offs+2]; + c4 = curplan.precomputed[offs+3]; + c5 = curplan.precomputed[offs+4]; + t1x = (*a)[aoffset+2]+(*a)[aoffset+8]; + t1y = (*a)[aoffset+3]+(*a)[aoffset+9]; + t2x = (*a)[aoffset+4]+(*a)[aoffset+6]; + t2y = (*a)[aoffset+5]+(*a)[aoffset+7]; + t3x = (*a)[aoffset+2]-(*a)[aoffset+8]; + t3y = (*a)[aoffset+3]-(*a)[aoffset+9]; + t4x = (*a)[aoffset+6]-(*a)[aoffset+4]; + t4y = (*a)[aoffset+7]-(*a)[aoffset+5]; + t5x = t1x+t2x; + t5y = t1y+t2y; + (*a)[aoffset+0] = (*a)[aoffset+0]+t5x; + (*a)[aoffset+1] = (*a)[aoffset+1]+t5y; + m1x = c1*t5x; + m1y = c1*t5y; + m2x = c2*(t1x-t2x); + m2y = c2*(t1y-t2y); + m3x = -c3*(t3y+t4y); + m3y = c3*(t3x+t4x); + m4x = -c4*t4y; + m4y = c4*t4x; + m5x = -c5*t3y; + m5y = c5*t3x; + s3x = m3x-m4x; + s3y = m3y-m4y; + s5x = m3x+m5x; + s5y = m3y+m5y; + s1x = (*a)[aoffset+0]+m1x; + s1y = (*a)[aoffset+1]+m1y; + s2x = s1x+m2x; + s2y = s1y+m2y; + s4x = s1x-m2x; + s4y = s1y-m2y; + (*a)[aoffset+2] = s2x+s3x; + (*a)[aoffset+3] = s2y+s3y; + (*a)[aoffset+4] = s4x+s5x; + (*a)[aoffset+5] = s4y+s5y; + (*a)[aoffset+6] = s4x-s5x; + (*a)[aoffset+7] = s4y-s5y; + (*a)[aoffset+8] = s2x-s3x; + (*a)[aoffset+9] = s2y-s3y; + return; + } + } + if( curplan.plan[entryoffset+3]==ftbase_fhtcodeletplan ) { + n1 = curplan.plan[entryoffset+1]; + n2 = curplan.plan[entryoffset+2]; + n = n1*n2; + if( n==2 ) { + a0x = (*a)[aoffset+0]; + a1x = (*a)[aoffset+1]; + (*a)[aoffset+0] = a0x+a1x; + (*a)[aoffset+1] = a0x-a1x; + return; + } + if( n==3 ) { + offs = curplan.plan[entryoffset+7]; + c1 = curplan.precomputed[offs+0]; + c2 = curplan.precomputed[offs+1]; + a0x = (*a)[aoffset+0]; + a1x = (*a)[aoffset+1]; + a2x = (*a)[aoffset+2]; + t1x = a1x+a2x; + a0x = a0x+t1x; + m1x = c1*t1x; + m2y = c2*(a2x-a1x); + s1x = a0x+m1x; + (*a)[aoffset+0] = a0x; + (*a)[aoffset+1] = s1x-m2y; + (*a)[aoffset+2] = s1x+m2y; + return; + } + if( n==4 ) { + a0x = (*a)[aoffset+0]; + a1x = (*a)[aoffset+1]; + a2x = (*a)[aoffset+2]; + a3x = (*a)[aoffset+3]; + t1x = a0x+a2x; + t2x = a1x+a3x; + m2x = a0x-a2x; + m3y = a3x-a1x; + (*a)[aoffset+0] = t1x+t2x; + (*a)[aoffset+1] = m2x-m3y; + (*a)[aoffset+2] = t1x-t2x; + (*a)[aoffset+3] = m2x+m3y; + return; + } + if( n==5 ) { + offs = curplan.plan[entryoffset+7]; + c1 = curplan.precomputed[offs+0]; + c2 = curplan.precomputed[offs+1]; + c3 = curplan.precomputed[offs+2]; + c4 = curplan.precomputed[offs+3]; + c5 = curplan.precomputed[offs+4]; + t1x = (*a)[aoffset+1]+(*a)[aoffset+4]; + t2x = (*a)[aoffset+2]+(*a)[aoffset+3]; + t3x = (*a)[aoffset+1]-(*a)[aoffset+4]; + t4x = (*a)[aoffset+3]-(*a)[aoffset+2]; + t5x = t1x+t2x; + v0 = (*a)[aoffset+0]+t5x; + (*a)[aoffset+0] = v0; + m2x = c2*(t1x-t2x); + m3y = c3*(t3x+t4x); + s3y = m3y-c4*t4x; + s5y = m3y+c5*t3x; + s1x = v0+c1*t5x; + s2x = s1x+m2x; + s4x = s1x-m2x; + (*a)[aoffset+1] = s2x-s3y; + (*a)[aoffset+2] = s4x-s5y; + (*a)[aoffset+3] = s4x+s5y; + (*a)[aoffset+4] = s2x+s3y; + return; + } + } + if( curplan.plan[entryoffset+3]==ftbase_fftbluesteinplan ) { + n = curplan.plan[entryoffset+1]; + m = curplan.plan[entryoffset+4]; + offs = curplan.plan[entryoffset+7]; + for(int i=stackptr+2*n; i<=stackptr+2*m-1; i++) + curplan.stackbuf[i] = 0; + offsp = offs+2*m; + offsa = aoffset; + offsb = stackptr; + for(int i=0; i * a, int aoffset, int n1, int n2) { + int n, idx, offs; + double x, y, twxm1, twy, twbasexm1, twbasey, twrowxm1, twrowy, tmpx, tmpy, v; + int ftbase_ftbaseupdatetw = 4; + n = n1*n2; + v = -2*M_PI/n; + twbasexm1 = -2*sqr(sin(0.5*v)); + twbasey = sin(v); + twrowxm1 = 0; + twrowy = 0; + for(int i=0, j = 0; i<=n2-1; i++) { + twxm1 = 0; + twy = 0; + for(j=0; j<=n1-1; j++) { + idx = i*n1+j; + offs = aoffset+2*idx; + x = (*a)[offs+0]; + y = (*a)[offs+1]; + tmpx = x*twxm1-y*twy; + tmpy = x*twy+y*twxm1; + (*a)[offs+0] = x+tmpx; + (*a)[offs+1] = y+tmpy; + if( j & val) { + double v = 0., v1 = 0., v2 = 0., stddev = 0.; + int i, n = val.size(); + if (n < 2) + return false; + /* + * Mean + */ + for (i = 0; i < n; i++) + mean += val[i]; + mean /= n; + /* + * Variance (using corrected two-pass algorithm) + */ + for (i = 0; i < n; i++) + v1 += sqr(val[i] - mean); + for (i = 0; i < n; i++) + v2 += val[i] - mean; + v2 = sqr(v2) / n; + variance = (v1 - v2) / (n - 1); + if(variance < 0) + variance = 0.; + stddev = sqrt(variance); + /* + * Skewness and kurtosis + */ + if (stddev != 0) { + for (i = 0; i < n; i++) { + v = (val[i] - mean) / stddev; + v2 = sqr(v); + skewness = skewness + v2 * v; + kurtosis = kurtosis + sqr(v2); + } + skewness /= n; + kurtosis = kurtosis / n - 3.; + } + return true; +} diff --git a/pimath.h b/pimath.h old mode 100755 new mode 100644 index 45e11f83..94838ddd --- a/pimath.h +++ b/pimath.h @@ -1,20 +1,20 @@ /* - PIP - Platform Independent Primitives - Math - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + PIP - Platform Independent Primitives + Math + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ #ifndef PIMATH_H @@ -22,18 +22,37 @@ #include "picontainers.h" #ifndef QNX -# include -# include +# include +# include #else -# include -# include -#endif -#ifdef CC_VC -#define M_PI 3.14159265358979323846 +# include +# include #endif -#define M_2PI 6.28318530717958647692 -#define M_PI_3 1.04719755119659774615 +#ifndef M_LN2 +# define M_LN2 0.69314718055994530942 +#endif +#ifndef M_LN10 +# define M_LN10 2.30258509299404568402 +#endif +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif +#ifndef M_2PI +# define M_2PI 6.28318530717958647692 +#endif +#ifndef M_PI_3 +# define M_PI_3 1.04719755119659774615 +#endif +#ifndef M_2PI_3 +# define M_2PI_3 2.0943951023931954923 +#endif +#ifndef M_180_PI +# define M_180_PI 57.2957795130823208768 +#endif +#ifndef M_PI_180 +# define M_PI_180 1.74532925199432957692e-2 +#endif using std::complex; @@ -48,30 +67,47 @@ const complexd complexd_i(0., 1.); const complexd complexd_0(0.); const complexd complexd_1(1.); -const double deg2rad = atan(1.) / 45.; -const double rad2deg = 45. / atan(1.); +const double deg2rad = M_PI_180; +const double rad2deg = M_180_PI; inline int pow2(const int p) {return 1 << p;} inline double sqr(const double & v) {return v * v;} -inline double sinc(const double & v) {double t = M_PI * v; return sin(t) / t;} -inline complexd round(const complexd & c) {return complexd(round(c.real()), round(c.imag()));} +inline double sinc(const double & v) {if (v == 0.) return 1.; double t = M_PI * v; return sin(t) / t;} +inline complexd round(const complexd & c) {return complexd(piRound(c.real()), piRound(c.imag()));} inline complexd floor(const complexd & c) {return complexd(floor(c.real()), floor(c.imag()));} inline complexd ceil(const complexd & c) {return complexd(ceil(c.real()), ceil(c.imag()));} inline complexd atanc(const complexd & c) {return -complexd(-0.5, 1.) * log((complexd_1 + complexd_i * c) / (complexd_1 - complexd_i * c));} inline complexd asinc(const complexd & c) {return -complexd_i * log(complexd_i * c + sqrt(complexd_1 - c * c));} inline complexd acosc(const complexd & c) {return -complexd_i * log(c + complexd_i * sqrt(complexd_1 - c * c));} -#ifdef QNX +#if CC_GCC_VERSION <= 0x025F inline complexd tan(const complexd & c) {return sin(c) / cos(c);} inline complexd tanh(const complexd & c) {return sinh(c) / cosh(c);} inline complexd log2(const complexd & c) {return log(c) / M_LN2;} inline complexd log10(const complexd & c) {return log(c) / M_LN10;} -inline double j0(const double & v) {v;} +inline double j0(const double & v) {return v;} inline double j1(const double & v) {v;} -inline double jn(const int & n, const double & v) {v;} -inline double y0(const double & v) {v;} -inline double y1(const double & v) {v;} -inline double yn(const int & n, const double & v) {v;} +inline double jn(const int & n, const double & v) {return v;} +inline double y0(const double & v) {return v;} +inline double y1(const double & v) {return v;} +inline double yn(const int & n, const double & v) {return v;} #endif +inline double toDb(double val) {return 10. * log10(val);} +inline double fromDb(double val) {return pow(10., val / 10.);} +inline double toRad(double deg) {return deg * M_PI_180;} +inline double toDeg(double rad) {return rad * M_180_PI;} + +inline PIVector abs(const PIVector &v) { + PIVector result; + result.resize(v.size()); + for(uint i=0; i abs(const PIVector &v) { + PIVector result; + result.resize(v.size()); + for(uint i=0; i class PIMathMatrixT; @@ -103,7 +139,7 @@ public: Type angleCos(const _CVector & v) const {Type tv = v.length() * length(); return (tv == Type(0) ? Type(0) : ((*this) ^ v) / tv);} Type angleSin(const _CVector & v) const {Type tv = angleCos(v); return sqrt(Type(1) - tv * tv);} Type angleRad(const _CVector & v) const {return acos(angleCos(v));} - Type angleDeg(const _CVector & v) const {return acos(angleCos(v)) * rad2deg;} + Type angleDeg(const _CVector & v) const {return toGrad(acos(angleCos(v)));} _CVector projection(const _CVector & v) {Type tv = v.length(); return (tv == Type(0) ? _CVector() : v * (((*this) ^ v) / tv));} _CVector & normalize() {Type tv = length(); if (tv == Type(1)) return *this; PIMV_FOR(i, 0) c[i] /= tv; return *this;} _CVector normalized() {_CVector tv(*this); tv.normalize(); return tv;} @@ -404,7 +440,7 @@ public: Type angleCos(const _CVector & v) const {Type tv = v.length() * length(); return (tv == Type(0) ? Type(0) : ((*this) ^ v) / tv);} Type angleSin(const _CVector & v) const {Type tv = angleCos(v); return sqrt(Type(1) - tv * tv);} Type angleRad(const _CVector & v) const {return acos(angleCos(v));} - Type angleDeg(const _CVector & v) const {return acos(angleCos(v)) * rad2deg;} + Type angleDeg(const _CVector & v) const {return toGrad(acos(angleCos(v)));} _CVector projection(const _CVector & v) {Type tv = v.length(); return (tv == Type(0) ? _CVector() : v * (((*this) ^ v) / tv));} _CVector & normalize() {Type tv = length(); if (tv == Type(1)) return *this; PIMV_FOR(i, 0) c[i] /= tv; return *this;} _CVector normalized() {_CVector tv(*this); tv.normalize(); return tv;} @@ -624,7 +660,7 @@ inline std::ostream & operator <<(std::ostream & s, const PIMathMatrix & m /// Multiply matrices {CR x Rows0} on {Cols1 x CR}, result is {Cols1 x Rows0} template inline PIMathMatrix operator *(const PIMathMatrix & fm, - const PIMathMatrix & sm) { + const PIMathMatrix & sm) { uint cr = fm.cols(), rows0 = fm.rows(), cols1 = sm.cols(); PIMathMatrix tm(cols1, rows0); if (fm.cols() != sm.rows()) return tm; @@ -643,7 +679,7 @@ inline PIMathMatrix operator *(const PIMathMatrix & fm, /// Multiply matrix {Cols x Rows} on vector {Cols}, result is vector {Rows} template inline PIMathVector operator *(const PIMathMatrix & fm, - const PIMathVector & sv) { + const PIMathVector & sv) { uint c = fm.cols(), r = fm.rows(); PIMathVector tv(r); if (c != sv.size()) return tv; @@ -669,13 +705,6 @@ typedef PIMathMatrix PIMathMatrixd; #undef PIMM_FOR_R -/* - Fast Fourier Transformation: direct (complement= false) - and complement (complement = true). 'x' is data source. - 'x' contains 2^T items. -*/ -void fft(complexd * x, int T, bool complement); - /// Differential evaluations struct TransferFunction { // Для задания передаточной функции @@ -691,18 +720,18 @@ class Solver { public: enum Method {Global = -1, - Eyler_1 = 01, - Eyler_2 = 02, - EylerKoshi = 03, - RungeKutta_4 = 14, - AdamsBashfortMoulton_2 = 22, - AdamsBashfortMoulton_3 = 23, - AdamsBashfortMoulton_4 = 24, - PolynomialApproximation_2 = 32, - PolynomialApproximation_3 = 33, - PolynomialApproximation_4 = 34, - PolynomialApproximation_5 = 35 - }; + Eyler_1 = 01, + Eyler_2 = 02, + EylerKoshi = 03, + RungeKutta_4 = 14, + AdamsBashfortMoulton_2 = 22, + AdamsBashfortMoulton_3 = 23, + AdamsBashfortMoulton_4 = 24, + PolynomialApproximation_2 = 32, + PolynomialApproximation_3 = 33, + PolynomialApproximation_4 = 34, + PolynomialApproximation_5 = 35 + }; Solver() {times.resize(4); step = 0;} @@ -744,4 +773,68 @@ private: }; + +class PIFFT +{ +public: + PIFFT(); + // const PIVector & getIndexes() {return indexes;} + // const PIVector & getCoefs() {return coefs;} + PIVector * calcFFT(const PIVector &val); + PIVector * calcFFT(const PIVector &val); + PIVector * calcFFTinverse(const PIVector &val); + PIVector * calcHilbert(const PIVector &val); + PIVector getAmplitude(); +private: + // PIVector indexes; + // PIVector coefs; + PIVector result; + // uint iterations; + bool prepared; + // uint out_size; + typedef ptrdiff_t ae_int_t; + void calc_coefs(uint cnt2); + void calc_indexes(uint cnt2, uint deep2); + complexd coef(uint n, uint k); + + struct ftplan { + PIVector plan; + PIVector precomputed; + PIVector tmpbuf; + PIVector stackbuf; + }; + + ftplan curplan; + + void fftc1d(const PIVector &a, uint n); + void fftc1r(const PIVector &a, uint n); + void fftc1dinv(const PIVector &a, uint n); + + void createPlan(uint n); + void ftbasegeneratecomplexfftplan(uint n, ftplan *plan); + void ftbase_ftbasegenerateplanrec(int n, int tasktype, ftplan *plan, int *plansize, int *precomputedsize, int *planarraysize, int *tmpmemsize, int *stackmemsize, ae_int_t stackptr, int debugi=0); + void ftbase_ftbaseprecomputeplanrec(ftplan *plan, int entryoffset, ae_int_t stackptr); + void ftbasefactorize(int n, int *n1, int *n2); + void ftbase_ftbasefindsmoothrec(int n, int seed, int leastfactor, int *best); + int ftbasefindsmooth(int n); + void ftbaseexecuteplan(PIVector *a, int aoffset, int n, ftplan *plan); + void ftbaseexecuteplanrec(PIVector *a, int aoffset, ftplan *plan, int entryoffset, ae_int_t stackptr); + void ftbase_internalcomplexlintranspose(PIVector *a, int m, int n, int astart, PIVector *buf); + void ftbase_ffticltrec(PIVector *a, int astart, int astride, PIVector *b, int bstart, int bstride, int m, int n); + void ftbase_internalreallintranspose(PIVector *a, int m, int n, int astart, PIVector *buf); + void ftbase_fftirltrec(PIVector *a, int astart, int astride, PIVector *b, int bstart, int bstride, int m, int n); + void ftbase_ffttwcalc(PIVector *a, int aoffset, int n1, int n2); +}; + + +class PIStatistic { +public: + PIStatistic(); + bool calculate(const PIVector &val); + double mean; + double variance; + double skewness; + double kurtosis; +}; + #endif // PIMATH_H diff --git a/pimonitor.cpp b/pimonitor.cpp old mode 100755 new mode 100644 index 9ded81ad..f4a666b3 --- a/pimonitor.cpp +++ b/pimonitor.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Counter of some PIP types - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/pimonitor.h b/pimonitor.h old mode 100755 new mode 100644 index 486f86b9..34aab13a --- a/pimonitor.h +++ b/pimonitor.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Counter of some PIP types - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/pimultiprotocol.h b/pimultiprotocol.h old mode 100755 new mode 100644 index 7fe7d6e7..c045de1b --- a/pimultiprotocol.h +++ b/pimultiprotocol.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Multiprotocol - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -81,9 +81,9 @@ public: PIString secondChannelName() {if (count() == 2) return protocol(1)->receiverDeviceName() + " -> " + protocol(0)->senderDeviceName(); return "Config error";} ullong receiveCount() {if (count() == 2) return protocol(0)->receiveCount(); return 0;} - ullong * receiveCount_ptr() {if (count() == 2) return protocol(0)->receiveCount_ptr(); return 0;} + const ullong * receiveCount_ptr() {if (count() == 2) return protocol(0)->receiveCount_ptr(); return 0;} ullong sendCount() {if (count() == 2) return protocol(0)->sendCount(); return 0;} - ullong * sendCount_ptr() {if (count() == 2) return protocol(0)->sendCount_ptr(); return 0;} + const ullong * sendCount_ptr() {if (count() == 2) return protocol(0)->sendCount_ptr(); return 0;} private: void received(PIProtocol * prot, bool , uchar * data, int size) {if (prot == protocol(0)) protocol(1)->send(data, size); else protocol(0)->send(data, size);} diff --git a/pimutex.h b/pimutex.h old mode 100755 new mode 100644 index dba34ec0..43d5930e --- a/pimutex.h +++ b/pimutex.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Mutex - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/piobject.cpp b/piobject.cpp old mode 100755 new mode 100644 index 3674cdc2..fb0d61d5 --- a/piobject.cpp +++ b/piobject.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Object, base class of some PIP classes, provide EVENT -> EVENT_HANDLER mechanism - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -36,4 +36,4 @@ PIStringList PIObject::eventHandlers() { l << (*i).first; return l; } -*/ \ No newline at end of file +*/ diff --git a/piobject.h b/piobject.h old mode 100755 new mode 100644 index 826e9732..8005898f --- a/piobject.h +++ b/piobject.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Object, base class of some PIP classes, provide EVENT -> EVENT_HANDLER mechanism - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,37 +22,62 @@ #include "pistring.h" -//#ifdef CC_VC -//#define HANDLER(c,s) #s,PIObject::handlerPtr((void(c::*)())&c::s) -//#else -//#define HANDLER(c,s) #s,(void*)&c::s -//#endif - -//#define EVENT_HANDLER0(obj, name) static void __stat_##name##__(obj * o) {o->name();} void name() -//#define EVENT_HANDLER1(obj, name, a0, n0) static void __stat_##name##__(obj * o, a0 n0) {o->name(n0);} void name(a0 n0) -//#define EVENT_HANDLER2(obj, name, a0, n0, a1, n1) static void __stat_##name##__(obj * o, a0 n0, a1 n1) {o->name(n0, n1);} void name(a0 n0, a1 n1) - -#define EVENT_HANDLER0(obj, ret, name) static ret __stat_##name##__(void * o) {return ((obj*)o)->name();} ret name() -#define EVENT_HANDLER1(obj, ret, name, a0, n0) static ret __stat_##name##__(void * o, a0 n0) {return ((obj*)o)->name(n0);} ret name(a0 n0) -#define EVENT_HANDLER2(obj, ret, name, a0, n0, a1, n1) static ret __stat_##name##__(void * o, a0 n0, a1 n1) {return ((obj*)o)->name(n0, n1);} ret name(a0 n0, a1 n1) -#define EVENT_HANDLER3(obj, ret, name, a0, n0, a1, n1, a2, n2) static ret __stat_##name##__(void * o, a0 n0, a1 n1, a2 n2) {return ((obj*)o)->name(n0, n1, n2);} ret name(a0 n0, a1 n1, a2 n2) -#define EVENT_HANDLER4(obj, ret, name, a0, n0, a1, n1, a2, n2, a3, n3) static ret __stat_##name##__(void * o, a0 n0, a1 n1, a2 n2, a3 n3) {return ((obj*)o)->name(n0, n1, n2, n3);} ret name(a0 n0, a1 n1, a2 n2, a3 n3) +/// declare event handler \"event\" inside class \"obj\" with name \"name\", ret name() +#define EVENT_HANDLER0(obj, ret, name) static ret __stat_eh_##name##__(void * o) {return ((obj*)o)->name();} ret name() +#define EVENT_HANDLER1(obj, ret, name, a0, n0) static ret __stat_eh_##name##__(void * o, a0 n0) {return ((obj*)o)->name(n0);} ret name(a0 n0) +#define EVENT_HANDLER2(obj, ret, name, a0, n0, a1, n1) static ret __stat_eh_##name##__(void * o, a0 n0, a1 n1) {return ((obj*)o)->name(n0, n1);} ret name(a0 n0, a1 n1) +#define EVENT_HANDLER3(obj, ret, name, a0, n0, a1, n1, a2, n2) static ret __stat_eh_##name##__(void * o, a0 n0, a1 n1, a2 n2) {return ((obj*)o)->name(n0, n1, n2);} ret name(a0 n0, a1 n1, a2 n2) +#define EVENT_HANDLER4(obj, ret, name, a0, n0, a1, n1, a2, n2, a3, n3) static ret __stat_eh_##name##__(void * o, a0 n0, a1 n1, a2 n2, a3 n3) {return ((obj*)o)->name(n0, n1, n2, n3);} ret name(a0 n0, a1 n1, a2 n2, a3 n3) #define EVENT_HANDLER EVENT_HANDLER0 -#define EVENT_VHANDLER0(obj, ret, name) static ret __stat_##name##__(void * o) {return ((obj*)o)->name();} virtual ret name() -#define EVENT_VHANDLER1(obj, ret, name, a0, n0) static ret __stat_##name##__(void * o, a0 n0) {return ((obj*)o)->name(n0);} virtual ret name(a0 n0) -#define EVENT_VHANDLER2(obj, ret, name, a0, n0, a1, n1) static ret __stat_##name##__(void * o, a0 n0, a1 n1) {return ((obj*)o)->name(n0, n1);} virtual ret name(a0 n0, a1 n1) -#define EVENT_VHANDLER3(obj, ret, name, a0, n0, a1, n1, a2, n2) static ret __stat_##name##__(void * o, a0 n0, a1 n1, a2 n2) {return ((obj*)o)->name(n0, n1, n2);} virtual ret name(a0 n0, a1 n1, a2 n2) -#define EVENT_VHANDLER4(obj, ret, name, a0, n0, a1, n1, a2, n2, a3, n3) static ret __stat_##name##__(void * o, a0 n0, a1 n1, a2 n2, a3 n3) {return ((obj*)o)->name(n0, n1, n2, n3);} virtual ret name(a0 n0, a1 n1, a2 n2, a3 n3) +/// declare virtual event handler \"event\" inside class \"obj\" with name \"name\", virtual ret name() +#define EVENT_VHANDLER0(obj, ret, name) static ret __stat_eh_##name##__(void * o) {return ((obj*)o)->name();} virtual ret name() +#define EVENT_VHANDLER1(obj, ret, name, a0, n0) static ret __stat_eh_##name##__(void * o, a0 n0) {return ((obj*)o)->name(n0);} virtual ret name(a0 n0) +#define EVENT_VHANDLER2(obj, ret, name, a0, n0, a1, n1) static ret __stat_eh_##name##__(void * o, a0 n0, a1 n1) {return ((obj*)o)->name(n0, n1);} virtual ret name(a0 n0, a1 n1) +#define EVENT_VHANDLER3(obj, ret, name, a0, n0, a1, n1, a2, n2) static ret __stat_eh_##name##__(void * o, a0 n0, a1 n1, a2 n2) {return ((obj*)o)->name(n0, n1, n2);} virtual ret name(a0 n0, a1 n1, a2 n2) +#define EVENT_VHANDLER4(obj, ret, name, a0, n0, a1, n1, a2, n2, a3, n3) static ret __stat_eh_##name##__(void * o, a0 n0, a1 n1, a2 n2, a3 n3) {return ((obj*)o)->name(n0, n1, n2, n3);} virtual ret name(a0 n0, a1 n1, a2 n2, a3 n3) #define EVENT_VHANDLER EVENT_VHANDLER0 -#define CONNECT0(ret, src, event, dst, handler) PIObject::connect(src, #event, dst, (void*)(ret(*)(void*))(&(dst)->__stat_##handler##__)); -#define CONNECT1(ret, a0, src, event, dst, handler) PIObject::connect(src, #event, dst, (void*)(ret(*)(void*, a0))(&(dst)->__stat_##handler##__)); -#define CONNECT2(ret, a0, a1, src, event, dst, handler) PIObject::connect(src, #event, dst, (void*)(ret(*)(void*, a0, a1))(&(dst)->__stat_##handler##__)); -#define CONNECT3(ret, a0, a1, a2, src, event, dst, handler) PIObject::connect(src, #event, dst, (void*)(ret(*)(void*, a0, a1, a2))(&(dst)->__stat_##handler##__)); -#define CONNECT4(ret, a0, a1, a2, a3, src, event, dst, handler) PIObject::connect(src, #event, dst, (void*)(ret(*)(void*, a0, a1, a2, a3))(&(dst)->__stat_##handler##__)); +/// declare event \"event\" inside class \"obj\" with name \"name\", void name(); +#define EVENT0(obj, name) EVENT_HANDLER0(obj, void, name) {PIObject::raiseEvent(this, #name);} +#define EVENT1(obj, name, a0, n0) EVENT_HANDLER1(obj, void, name, a0, n0) {PIObject::raiseEvent(this, #name, n0);} +#define EVENT2(obj, name, a0, n0, a1, n1) EVENT_HANDLER2(obj, void, name, a0, n0, a1, n1) {PIObject::raiseEvent(this, #name, n0, n1);} +#define EVENT3(obj, name, a0, n0, a1, n1, a2, n2) EVENT_HANDLER3(obj, void, name, a0, n0, a1, n1, a2, n2) {PIObject::raiseEvent(this, #name, n0, n1, n2);} +#define EVENT4(obj, name, a0, n0, a1, n1, a2, n2, a3, n3) EVENT_HANDLER4(obj, void, name, a0, n0, a1, n1, a2, n2, a3, n3) {PIObject::raiseEvent(this, #name, n0, n1, n2, n3);} +#define EVENT EVENT0 + +/// raise event \"event\" from object \"src\" +#define RAISE_EVENT0(src, event) (src)->event(); +#define RAISE_EVENT1(src, event, v0) (src)->event(v0); +#define RAISE_EVENT2(src, event, v0, v1) (src)->event(v0, v1); +#define RAISE_EVENT3(src, event, v0, v1, v2) (src)->event(v0, v1, v2); +#define RAISE_EVENT4(src, event, v0, v1, v2, v3) (src)->event(v0, v1, v2, v3); +#define RAISE_EVENT RAISE_EVENT0 + +/// connect event \"event\" from object \"src\" to event handler \"handler\" from object \"dst\" with check of event and handler exists +#define CONNECT0(ret, src, event, dst, handler) PIObject::piConnect(src, #event, dst, (void*)(ret(*)(void*))(&(dst)->__stat_eh_##handler##__), (void*)(void(*)(void*))(&(src)->__stat_eh_##event##__)); +#define CONNECT1(ret, a0, src, event, dst, handler) PIObject::piConnect(src, #event, dst, (void*)(ret(*)(void*, a0))(&(dst)->__stat_eh_##handler##__), (void*)(void(*)(void*, a0))(&(src)->__stat_eh_##event##__)); +#define CONNECT2(ret, a0, a1, src, event, dst, handler) PIObject::piConnect(src, #event, dst, (void*)(ret(*)(void*, a0, a1))(&(dst)->__stat_eh_##handler##__), (void*)(void(*)(void*, a0, a1))(&(src)->__stat_eh_##event##__)); +#define CONNECT3(ret, a0, a1, a2, src, event, dst, handler) PIObject::piConnect(src, #event, dst, (void*)(ret(*)(void*, a0, a1, a2))(&(dst)->__stat_eh_##handler##__), (void*)(void(*)(void*, a0, a1, a2))(&(src)->__stat_eh_##event##__)); +#define CONNECT4(ret, a0, a1, a2, a3, src, event, dst, handler) PIObject::piConnect(src, #event, dst, (void*)(ret(*)(void*, a0, a1, a2, a3))(&(dst)->__stat_eh_##handler##__), (void*)(void(*)(void*, a0, a1, a2, a3))(&(src)->__stat_eh_##event##__)); #define CONNECT CONNECT0 +/// connect event \"event\" from object \"src\" to event handler \"handler\" from object \"dst\" without check of event exists +#define WEAK_CONNECT0(ret, src, event, dst, handler) PIObject::piConnect(src, #event, dst, (void*)(ret(*)(void*))(&(dst)->__stat_eh_##handler##__)); +#define WEAK_CONNECT1(ret, a0, src, event, dst, handler) PIObject::piConnect(src, #event, dst, (void*)(ret(*)(void*, a0))(&(dst)->__stat_eh_##handler##__)); +#define WEAK_CONNECT2(ret, a0, a1, src, event, dst, handler) PIObject::piConnect(src, #event, dst, (void*)(ret(*)(void*, a0, a1))(&(dst)->__stat_eh_##handler##__)); +#define WEAK_CONNECT3(ret, a0, a1, a2, src, event, dst, handler) PIObject::piConnect(src, #event, dst, (void*)(ret(*)(void*, a0, a1, a2))(&(dst)->__stat_eh_##handler##__)); +#define WEAK_CONNECT4(ret, a0, a1, a2, a3, src, event, dst, handler) PIObject::piConnect(src, #event, dst, (void*)(ret(*)(void*, a0, a1, a2, a3))(&(dst)->__stat_eh_##handler##__)); +#define WEAK_CONNECT WEAK_CONNECT0 + +/// piDisconnect event \"event\" from object \"src\" from event handler \"handler\" from object \"dst\" +#define DISCONNECT0(ret, src, event, dst, handler) PIObject::piDisconnect(src, #event, dst, (void*)(ret(*)(void*))(&(dst)->__stat_eh_##handler##__)); +#define DISCONNECT1(ret, a0, src, event, dst, handler) PIObject::piDisconnect(src, #event, dst, (void*)(ret(*)(void*, a0))(&(dst)->__stat_eh_##handler##__)); +#define DISCONNECT2(ret, a0, a1, src, event, dst, handler) PIObject::piDisconnect(src, #event, dst, (void*)(ret(*)(void*, a0, a1))(&(dst)->__stat_eh_##handler##__)); +#define DISCONNECT3(ret, a0, a1, a2, src, event, dst, handler) PIObject::piDisconnect(src, #event, dst, (void*)(ret(*)(void*, a0, a1, a2))(&(dst)->__stat_eh_##handler##__)); +#define DISCONNECT4(ret, a0, a1, a2, a3, src, event, dst, handler) PIObject::piDisconnect(src, #event, dst, (void*)(ret(*)(void*, a0, a1, a2, a3))(&(dst)->__stat_eh_##handler##__)); +#define DISCONNECT DISCONNECT0 + class PIObject { friend class PIObjectManager; @@ -109,24 +134,34 @@ public: } */ /// Direct connect - static void connect(PIObject * src, const PIString & sig, void * dest, void * ev_h) {src->connections << Connection(ev_h, sig, dest);} - //static void connect(PIObject & src, const PIString & sig, PIObject * dest, void * ev_h) {src.connections << Connection(ev_h, sig, dest);} - //static void connect(PIObject * src, const PIString & sig, PIObject & dest, void * ev_h) {src->connections << Connection(ev_h, sig, &dest);} - //static void connect(PIObject & src, const PIString & sig, PIObject & dest, void * ev_h) {src.connections << Connection(ev_h, sig, &dest);} + static void piConnect(PIObject * src, const PIString & sig, void * dest, void * ev_h) {src->connections << Connection(ev_h, 0, sig, dest);} + static void piConnect(PIObject * src, const PIString & sig, void * dest, void * ev_h, void * e_h) {src->connections << Connection(ev_h, e_h, sig, dest);} + static void piDisconnect(PIObject * src, const PIString & sig, void * dest, void * ev_h) { + for (int i = 0; i < src->connections.size_s(); ++i) { + Connection & cc(src->connections[i]); + if (cc.event == sig && cc.dest == dest && cc.slot == ev_h) { + src->connections.remove(i); + i--; + } + } + } + //static void piConnect(PIObject & src, const PIString & sig, PIObject * dest, void * ev_h) {src.connections << Connection(ev_h, sig, dest);} + //static void piConnect(PIObject * src, const PIString & sig, PIObject & dest, void * ev_h) {src->connections << Connection(ev_h, sig, &dest);} + //static void piConnect(PIObject & src, const PIString & sig, PIObject & dest, void * ev_h) {src.connections << Connection(ev_h, sig, &dest);} /*/// Connect through manager - static bool connect(const PIString & srcObject, const PIString & event, const PIString & destObject, const PIString & handler, bool force = false) { + static bool piConnect(const PIString & srcObject, const PIString & event, const PIString & destObject, const PIString & handler, bool force = false) { PIObject * src = findByName(srcObject); if (src == 0) { - cout << "PIObject::connect: can`t find PIObject with \"" << srcObject << "\" name!" << endl; + cout << "PIObject::piConnect: can`t find PIObject with \"" << srcObject << "\" name!" << endl; return false; } PIObject * dest = findByName(destObject); if (dest == 0) { - cout << "PIObject::connect: can`t find PIObject with \"" << destObject << "\" name!" << endl; + cout << "PIObject::piConnect: can`t find PIObject with \"" << destObject << "\" name!" << endl; return false; } - return PIObject::connect(src, event, dest, handler, force); + return PIObject::piConnect(src, event, dest, handler, force); }*/ /// Raise events @@ -195,7 +230,7 @@ public: static void raiseEvent(const PIString & destObject, const PIString & name) { PIObject * dest = findByName(destObject); if (dest == 0) { - cout << "PIObject::connect: can`t find PIObject with \"" << destObject << "\" name!" << endl; + cout << "PIObject::piConnect: can`t find PIObject with \"" << destObject << "\" name!" << endl; return; } raiseEvent(dest, name); @@ -204,7 +239,7 @@ public: static void raiseEvent(const PIString & destObject, const PIString & name, const T0 & v0 = T0()) { PIObject * dest = findByName(destObject); if (dest == 0) { - cout << "PIObject::connect: can`t find PIObject with \"" << destObject << "\" name!" << endl; + cout << "PIObject::piConnect: can`t find PIObject with \"" << destObject << "\" name!" << endl; return; } raiseEvent(dest, name, v0); @@ -213,7 +248,7 @@ public: static void raiseEvent(const PIString & destObject, const PIString & name, const T0 & v0 = T0(), const T1 & v1 = T1()) { PIObject * dest = findByName(destObject); if (dest == 0) { - cout << "PIObject::connect: can`t find PIObject with \"" << destObject << "\" name!" << endl; + cout << "PIObject::piConnect: can`t find PIObject with \"" << destObject << "\" name!" << endl; return; } raiseEvent(dest, name, v0, v1); @@ -222,7 +257,7 @@ public: static void raiseEvent(const PIString & destObject, const PIString & name, const T0 & v0 = T0(), const T1 & v1 = T1(), const T2 & v2 = T2()) { PIObject * dest = findByName(destObject); if (dest == 0) { - cout << "PIObject::connect: can`t find PIObject with \"" << destObject << "\" name!" << endl; + cout << "PIObject::piConnect: can`t find PIObject with \"" << destObject << "\" name!" << endl; return; } raiseEvent(name, dest, v0, v1, v2); @@ -231,21 +266,24 @@ public: static void raiseEvent(const PIString & destObject, const PIString & name, const T0 & v0 = T0(), const T1 & v1 = T1(), const T2 & v2 = T2(), const T3 & v3 = T3()) { PIObject * dest = findByName(destObject); if (dest == 0) { - cout << "PIObject::connect: can`t find PIObject with \"" << destObject << "\" name!" << endl; + cout << "PIObject::piConnect: can`t find PIObject with \"" << destObject << "\" name!" << endl; return; } raiseEvent(name,dest , v0, v1, v2, v3); } +protected: + PIString name_; + private: struct Connection { - Connection(void * s = 0, const PIString & e = PIString(), void * o = 0) {slot = s; event = e; dest = o;} + Connection(void * sl = 0, void * si = 0, const PIString & e = PIString(), void * o = 0) {slot = sl; signal = si; event = e; dest = o;} void * slot; + void * signal; PIString event; void * dest; }; - PIString name_; PIVector connections; static PIVector objects; diff --git a/pip.h b/pip.h old mode 100755 new mode 100644 index eba95202..bd1fb55b --- a/pip.h +++ b/pip.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives All includes - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,3 +27,5 @@ #include "pisignals.h" #include "piobject.h" #include "pisystemmonitor.h" +#include "pipeer.h" +#include "picrc.h" diff --git a/pipacketextractor.cpp b/pipacketextractor.cpp index a68c2883..a24738da 100644 --- a/pipacketextractor.cpp +++ b/pipacketextractor.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Packets extractor - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,9 +23,10 @@ PIPacketExtractor::PIPacketExtractor(PIIODevice * device_, void * recHeaderPtr, int recHeaderSize, int recDataSize) { ret_func_header = 0; setPacketData(recHeaderPtr, recHeaderSize, recDataSize); - setBufferSize(4096); + setThreadedReadBufferSize(65536); + setBufferSize(65536); setDevice(device_); - allReaded = addSize = curInd = 0; + allReaded = addSize = curInd = missed = 0; } @@ -55,7 +56,8 @@ bool PIPacketExtractor::threadedRead(uchar * readed, int size_) { return true; } } - memcpy(mheader.data(), buffer.data(curInd + headerSize), headerSize); + memcpy(mheader.data(), buffer.data(curInd), headerSize); + if (headerPtr != 0) memcpy(headerPtr, buffer.data(curInd), headerSize); if (!packetValidate(buffer.data(curInd + headerSize), dataSize)) { curInd++; missed++; if (packetSize > 0) missed_packets = missed / packetSize; @@ -66,10 +68,17 @@ bool PIPacketExtractor::threadedRead(uchar * readed, int size_) { allReaded -= packetSize + curInd; curInd = addSize = 0; } else { - packetValidate(buffer.data(), dataSize); - memcpy(sbuffer.data(), buffer.data(), allReaded); - memcpy(buffer.data(), sbuffer.data(packetSize), allReaded); - allReaded -= packetSize; + if (dataSize == 0) { + packetValidate(buffer.data(), size_); + memcpy(sbuffer.data(), buffer.data(), allReaded); + memcpy(buffer.data(), sbuffer.data(size_), allReaded); + allReaded -= size_; + } else { + packetValidate(buffer.data(), dataSize); + memcpy(sbuffer.data(), buffer.data(), allReaded); + memcpy(buffer.data(), sbuffer.data(packetSize), allReaded); + allReaded -= packetSize; + } } return true; } diff --git a/pipacketextractor.h b/pipacketextractor.h index 38bdb2cb..a293caa6 100644 --- a/pipacketextractor.h +++ b/pipacketextractor.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Packets extractor - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,18 +30,19 @@ class PIPacketExtractor: public PIIODevice { public: PIPacketExtractor(PIIODevice * device_ = 0, void * recHeaderPtr = 0, int recHeaderSize = 0, int recDataSize = 0); + virtual ~PIPacketExtractor() {} PIIODevice * device() {return dev;} void setDevice(PIIODevice * device_); int bufferSize() const {return buffer_size;} - void setBufferSize(int new_size) {buffer_size = new_size; buffer.resize(buffer_size); sbuffer.resize(buffer_size);} + void setBufferSize(int new_size) {buffer_size = new_size; buffer.resize(buffer_size); sbuffer.resize(buffer_size); memset(buffer.data(), 0, buffer.size()); memset(sbuffer.data(), 0, sbuffer.size());} void setHeaderCheckSlot(HeaderCheckFunc f) {ret_func_header = f;} void setPacketData(void * recHeaderPtr, int recHeaderSize, int recDataSize) {headerPtr = recHeaderPtr; headerSize = recHeaderSize; dataSize = recDataSize; packetSize = headerSize + dataSize; if (headerSize > 0) mheader.resize(headerSize);} ullong missedBytes() const {return missed;} - ullong missedPackets() const {return missed / packetSize;} + ullong missedPackets() const {if (packetSize == 0) return missed; return missed / packetSize;} const ullong * missedBytes_ptr() const {return &missed;} const ullong * missedPackets_ptr() const {return &missed_packets;} @@ -56,7 +57,7 @@ protected: private: bool threadedRead(uchar * readed, int size); - bool openDevice() {if (dev == 0) return false; return dev->isOpened();} + bool openDevice() {if (dev == 0) return false; return dev->open();} PIIODevice * dev; PIByteArray mheader, buffer, sbuffer; diff --git a/pipeer.cpp b/pipeer.cpp new file mode 100644 index 00000000..d48de099 --- /dev/null +++ b/pipeer.cpp @@ -0,0 +1,186 @@ +/* + PIP - Platform Independent Primitives + Peer - named I/O ethernet node + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "pipeer.h" + + +PIPeer::PIPeer(const PIString & name): PIEthernet() { + setName(name); + setParameter(PIEthernet::Broadcast); + setReadPort(13312); + setSendPort(13312); + srand(uint(PITimer::elapsed_system_m())); + //id_ = name() + "_" + PIString::fromNumber(rand()); + CONNECT2(void, void * , int, &timer, timeout, this, timerEvent); + + PIEthernet * ce; + PIStringList sl = PIEthernet::allAddresses(); + PIString ta; + self_info.name = name_; + self_info.dist = 0; + piForeachC (PIString & i, sl) { + ce = new PIEthernet(this, func_readed); + ce->setReadAddress(i, 13313); + eths << ce; + ce->startThreadedRead(); + self_info.addresses << i; + //cout << i << ": " << ta << endl; + } + eth_send = new PIEthernet(); + eth_send->initialize(); + + startThreadedRead(); + //joinMulticastGroup("239.13.3.12"); + //timer.addDelimiter(5); + timer.start(1000); + sendSelfInfo(); +} + + +PIPeer::~PIPeer() { + terminate(); + sendSelfRemove(); + //leaveMulticastGroup("239.13.3.12"); + delete eth_send; + piForeach (PIEthernet * i, eths) + delete i; + eths.clear(); +} + + +void PIPeer::timerEvent(void * data, int delim) { + switch (delim) { + case 1: // 5 s + syncPeers(); + break; + } + //send("broadcast", 9); +} + + +bool PIPeer::threadedRead(uchar * data, int size) { + int header; + PeerInfo pi; + PIByteArray ba(data, size); + PIVector rpeers; + ba >> header >> pi.name; + if (pi.name == name_) return true; + switch (header) { + case 1: // new peer accepted + if (hasPeer(pi.name)) break; + ba >> pi.dist >> pi.addresses; + pi.sync = 0; + peers << pi; + cout << "[PIPeer \"" + name_ + "\"] new peer \"" << pi.name << "\"" << " dist " << pi.dist << endl; + pi.dist++; + sendSelfInfo(); + sendPeerInfo(pi); + findNearestAddresses(); + break; + case 2: // remove peer accepted + if (removePeer(pi.name)) { + cout << "[PIPeer \"" + name_ + "\"] remove peer \"" << pi.name << "\"" << endl; + sendPeerRemove(pi.name); + findNearestAddresses(); + } + break; + case 3: // sync peers + ba >> pi.addresses >> rpeers; + rpeers << pi; + //cout << "[PIPeer \"" + name_ + "\"] rec sync " << rpeers.size_s() << " peers" << endl; + for (uint i = 0; i < rpeers.size(); ++i) { + PeerInfo & rpeer(rpeers[i]); + //cout << " to sync " << rpeer.name << endl; + if (rpeer.name == name_) continue; + bool exist = false; + for (uint j = 0; j < peers.size(); ++j) { + PeerInfo & peer(peers[j]); + if (peer.name == rpeer.name) { + //cout << "synced " << peer.name << endl; + peer.addresses == rpeer.addresses; + if (peer.name == pi.name) peer.sync = 0; + exist = true; + break; + } + } + if (exist) continue; + peers << rpeer; + peers.back().dist++; + findNearestAddresses(); + } + break; + } + return true; +} + + +bool PIPeer::func_readed(void * peer, uchar * data, int size) { + PIPeer * p = (PIPeer * )peer; + cout << "[PIPeer \"" + p->name_ + "\"] received " << data << endl; + return true; +} + + +void PIPeer::sendPeerInfo(const PeerInfo & info) { + PIByteArray ba; + ba << int(1) << info.name << info.dist << info.addresses; + write(ba); +} + + +void PIPeer::sendPeerRemove(const PIString & peer) { + PIByteArray ba; + ba << int(2) << peer; + write(ba); +} + + +void PIPeer::syncPeers() { + //cout << "[PIPeer \"" + name_ + "\"] sync " << peers.size_s() << " peers" << endl; + PIString pn; + for (uint i = 0; i < peers.size(); ++i) { + PeerInfo & cp(peers[i]); + if (cp.sync > 1 && cp.dist == 0) { + pn = cp.name; + cout << "[PIPeer \"" + name_ + "\"] sync: remove " << pn << endl; + peers.remove(i); + sendPeerRemove(pn); + --i; + findNearestAddresses(); + continue; + } + cp.sync++; + } + PIByteArray ba; + ba << int(3) << self_info.name << self_info.addresses << peers; + write(ba); +} + + +void PIPeer::findNearestAddresses() { + cout << "[PIPeer \"" + name_ + "\"] findNearestAddresses" << endl; + int max_dist = -1; + piForeach (PeerInfo & i, peers) + if (max_dist < i.dist) + max_dist = i.dist; + PIVector cwave; + for (int d = 0; d <= max_dist; ++d) { + //if () + } +} diff --git a/pipeer.h b/pipeer.h new file mode 100644 index 00000000..db39a9b1 --- /dev/null +++ b/pipeer.h @@ -0,0 +1,81 @@ +/* + PIP - Platform Independent Primitives + Peer - named I/O ethernet node + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PIPEER_H +#define PIPEER_H + +#include "piethernet.h" + +class PIPeer: public PIEthernet +{ + struct PeerInfo; + friend PIByteArray & operator <<(PIByteArray & s, const PIPeer::PeerInfo & v); + friend PIByteArray & operator >>(PIByteArray & s, PIPeer::PeerInfo & v); +public: + PIPeer(const PIString & name); + ~PIPeer(); + + //const PIString & id() const {return id_;} + +protected: + bool threadedRead(uchar * readed, int size); + +private: + EVENT_HANDLER2(PIPeer, void, timerEvent, void * , data, int, delim); + static bool func_readed(void * peer, uchar * data, int size); + + struct PeerInfo { + PIString name; + PIString nearest_address; + PIStringList addresses; + int dist; + int sync; + int _wave; + }; + + + bool hasPeer(const PIString & name) {piForeachC (PeerInfo & i, peers) if (i.name == name) return true; return false;} + bool removePeer(const PIString & name) {for (uint i = 0; i < peers.size(); ++i) if (peers[i].name == name) {peers.remove(i); return true;} return false;} + + void sendPeerInfo(const PeerInfo & info); + void sendPeerRemove(const PIString & peer); + void sendSelfInfo() {sendPeerInfo(self_info);} + void sendSelfRemove() {sendPeerRemove(name_);} + void syncPeers(); + void findNearestAddresses(); + + struct PeerPacket { + int header; // 1 - new peer, 2 - remove peer, 3 - sync peers + + }; + + PIList eths; + PIEthernet * eth_send; + PITimer timer; + + PeerInfo self_info; + PIVector peers; + //PIString id_; + +}; + +inline PIByteArray & operator <<(PIByteArray & s, const PIPeer::PeerInfo & v) {s << v.name << v.addresses << v.dist; return s;} +inline PIByteArray & operator >>(PIByteArray & s, PIPeer::PeerInfo & v) {s >> v.name >> v.addresses >> v.dist; return s;} + +#endif // PIPEER_H diff --git a/piprocess.cpp b/piprocess.cpp old mode 100755 new mode 100644 index 1971858b..4d944be6 --- a/piprocess.cpp +++ b/piprocess.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Process - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -55,7 +55,7 @@ void PIProcess::run() { //cout << "run" << endl; string str; /// arguments convertion - int as = 0; + as = 0; #ifdef WINDOWS //args.pop_front(); piForeachC (PIString & i, args) @@ -96,30 +96,31 @@ void PIProcess::run() { f_in = PIFile::openTemporary(PIIODevice::ReadWrite); t_in = true; } - f_in.open(PIIODevice::ReadWrite); f_in.close(); + //f_in.open(PIIODevice::ReadWrite); f_in.close(); if (f_out.path().isEmpty()) { f_out = PIFile::openTemporary(PIIODevice::ReadWrite); t_out = true; } - f_out.open(PIIODevice::WriteOnly); f_out.close(); + //f_out.open(PIIODevice::WriteOnly); f_out.close(); if (f_err.path().isEmpty()) { f_err = PIFile::openTemporary(PIIODevice::ReadWrite); t_err = true; } - f_err.open(PIIODevice::WriteOnly); f_err.close(); + //f_err.open(PIIODevice::WriteOnly); f_err.close(); str = args.front().stdString(); is_exec = true; + execStarted(PIString(str)); #ifndef WINDOWS pid = fork(); if (pid == 0) { #endif - FILE * tf = 0; - //cout << "exec" << endl; + tf_in = tf_out = tf_err = 0; + //cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl; //cout << f_out.path() << endl; - if (g_in) tf = freopen(f_in.path().data(), "r", stdin); - if (g_out) tf = freopen(f_out.path().data(), "w", stdout); - if (g_err) tf = freopen(f_err.path().data(), "w", stderr); + if (g_in) tf_in = freopen(f_in.path().data(), "r", stdin); + if (g_out) tf_out = freopen(f_out.path().data(), "w", stdout); + if (g_err) tf_err = freopen(f_err.path().data(), "w", stderr); #ifndef WINDOWS if (!wd.isEmpty()) as = chdir(wd.data()); #endif @@ -142,16 +143,22 @@ void PIProcess::run() { } else piCout << "[PIProcess] \"CreateProcess\" error, " << errorString() << endl; #else + + //cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl; if (execve(str.c_str(), a, e) < 0) piCout << "[PIProcess] \"execve\" error, " << errorString() << endl; } else { msleep(1); //cout << "wait" << endl; wait(&exit_code); + /*if (tf_in != 0) fclose(tf_in); + if (tf_out != 0) fclose(tf_out); + if (tf_err != 0) fclose(tf_err);*/ pid = 0; //cout << "wait done" << endl; } #endif + execFinished(PIString(str), exit_code); is_exec = false; for (int i = 0; i < env.size_s(); ++i) delete e[i]; @@ -162,7 +169,7 @@ void PIProcess::run() { for (int i = 0; i < args.size_s(); ++i) delete a[i]; #endif - //cout << "end" << endl; + //cout << "end " << tf_in << ", " << tf_out << ", " << tf_err << endl; } diff --git a/piprocess.h b/piprocess.h old mode 100755 new mode 100644 index 0dd951e8..aa1ea544 --- a/piprocess.h +++ b/piprocess.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Process - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,6 +28,18 @@ # include #endif +/// events: +/// execStarted(PIString program) +/// execFinished(PIString program, int exit_code) +/// +/// handlers: +/// bool exec(const PIString & program) +/// bool exec(const PIString & program, const PIString & arg1) +/// bool exec(const PIString & program, const PIString & arg1, const PIString & arg2) +/// bool exec(const PIString & program, const PIString & arg1, const PIString & arg2, const PIString & arg3) +/// bool exec(const PIString & program, const PIStringList & args) +/// void terminate() +/// bool waitForFinish(int timeout_msecs = 60000) class PIProcess: private PIThread { public: @@ -53,17 +65,18 @@ public: PIString workingDirectory() const {return wd;} void setWorkingDirectory(const PIString & path) {wd = path;} void resetWorkingDirectory() {wd.clear();} - void exec(const PIString & program) {args.clear(); args << program; exec_();} - void exec(const PIString & program, const PIString & arg) {args.clear(); args << program << arg; exec_();} - void exec(const PIString & program, const PIString & arg1, const PIString & arg2) {args.clear(); args << program << arg1 << arg2; exec_();} - void exec(const PIString & program, const PIString & arg1, const PIString & arg2, const PIString & arg3) {args.clear(); args << program << arg1 << arg2 << arg3; exec_();} - void exec(const PIString & program, const PIStringList & args_) {args << program << args_; exec_();} + EVENT_HANDLER1(PIProcess, void, exec, const PIString & , program) {args.clear(); args << program; exec_();} + EVENT_HANDLER2(PIProcess, void, exec, const PIString & , program, const PIString & , arg) {args.clear(); args << program << arg; exec_();} + EVENT_HANDLER3(PIProcess, void, exec, const PIString & , program, const PIString & , arg1, const PIString & , arg2) {args.clear(); args << program << arg1 << arg2; exec_();} + EVENT_HANDLER4(PIProcess, void, exec, const PIString & , program, const PIString & , arg1, const PIString & , arg2, const PIString & , arg3) {args.clear(); args << program << arg1 << arg2 << arg3; exec_();} + EVENT_HANDLER2(PIProcess, void, exec, const PIString & , program, const PIStringList & , args_) {args << program << args_; exec_();} #ifdef WINDOWS - void terminate() {if (is_exec) if (!TerminateProcess(pi.hProcess, 0)) return; pi.dwProcessId = 0;} + EVENT_HANDLER(PIProcess, void, terminate) {if (is_exec) if (!TerminateProcess(pi.hProcess, 0)) return; pi.dwProcessId = 0;} #else - void terminate() {if (is_exec) kill(pid, SIGKILL); pid = 0;} + EVENT_HANDLER(PIProcess, void, terminate) {if (is_exec) kill(pid, SIGKILL); pid = 0;} #endif - bool waitForFinish(int timeout_msecs = 60000) {return PIThread::waitForFinish(timeout_msecs);} + EVENT_HANDLER(PIProcess, bool, waitForFinish) {return waitForFinish(60000);} + EVENT_HANDLER1(PIProcess, bool, waitForFinish, int, timeout_msecs) {return PIThread::waitForFinish(timeout_msecs);} PIByteArray readOutput() {f_out.open(PIIODevice::ReadOnly); return f_out.readAll();} PIByteArray readError() {f_err.open(PIIODevice::ReadOnly); return f_err.readAll();} @@ -79,6 +92,9 @@ public: static int currentPID() {return getpid();} #endif + EVENT1(PIProcess, execStarted, PIString, program) + EVENT2(PIProcess, execFinished, PIString, program, int, exit_code) + private: virtual void run(); void exec_(); @@ -94,7 +110,8 @@ private: #else pid_t pid; #endif - int exit_code, sz; + FILE * tf_in, * tf_out, * tf_err; + int exit_code, sz, as; bool is_exec; }; diff --git a/piprotocol.cpp b/piprotocol.cpp old mode 100755 new mode 100644 index 357ad9c1..5889313e --- a/piprotocol.cpp +++ b/piprotocol.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Protocol, input/output channel (COM, UDP) - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com, Bychkov Andrey wapmobil@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com, Bychkov Andrey wapmobil@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,7 +33,7 @@ PIProtocol::PIProtocol(const PIString & config, const PIString & name, void * re int ps, gps; bool ok, gok, flag, gflag, has_dev = false; float freq, gfreq; - PIFlags pp; + PIFlags pp(0); PIConfig::Entry & b(conf.getValue(name)), & rb(b.getValue("receiver")), & sb(b.getValue("sender")); @@ -65,7 +65,7 @@ PIProtocol::PIProtocol(const PIString & config, const PIString & name, void * re } type_rec = PIProtocol::Ethernet; eth = new PIEthernet(); - packet_ext.setDevice(eth); + packet_ext->setDevice(eth); //setSenderAddress(dev, ps); setReceiverAddress(dev, ps); has_dev = true; @@ -128,7 +128,7 @@ PIProtocol::PIProtocol(const PIString & config, const PIString & name, void * re devReceiverState = "Config error"; return; } - pp |= PISerial::ParityControl; + pp.setFlag(PISerial::ParityControl, flag); } flag = rb.getValue("twoStopBits", false, &ok); gflag = b.getValue("twoStopBits", false, &gok); @@ -139,12 +139,12 @@ PIProtocol::PIProtocol(const PIString & config, const PIString & name, void * re devReceiverState = "Config error"; return; } - pp |= PISerial::TwoStopBits; + pp.setFlag(PISerial::TwoStopBits, flag); } type_rec = PIProtocol::Serial; type_send = PIProtocol::Serial; ser = new PISerial(dev); - packet_ext.setDevice(ser); + packet_ext->setDevice(ser); //setSenderDevice(dev, (PISerial::Speed)ps); setReceiverDevice(dev, (PISerial::Speed)ps); ser->setInSpeed((PISerial::Speed)ps); @@ -297,7 +297,7 @@ PIProtocol::PIProtocol(const PIString & config, const PIString & name, void * re devSenderState = "Config error"; return; } - pp |= PISerial::ParityControl; + pp.setFlag(PISerial::ParityControl, flag); } flag = sb.getValue("twoStopBits", false, &ok); gflag = b.getValue("twoStopBits", false, &gok); @@ -308,7 +308,7 @@ PIProtocol::PIProtocol(const PIString & config, const PIString & name, void * re devSenderState = "Config error"; return; } - pp |= PISerial::TwoStopBits; + pp.setFlag(PISerial::TwoStopBits, flag); } } else { piCout << "[PIProtocol \"" << name << "\"] Can`t find \"" << name << ".sender.speed\" or \"" << name << ".speed\" in \"" << config << "\"!" << endl; @@ -365,14 +365,7 @@ PIProtocol::PIProtocol(const PIString & config, const PIString & name, void * re dataSize = recDataSize; sendDataPtr = (uchar * )sendDataPtr_; sendDataSize = sendDataSize_; - packet_ext.setPacketData(recHeaderPtr, recHeaderSize, recDataSize); - if (type_rec == PIProtocol::Ethernet) { - if (recHeaderPtr != 0) { - dataPtr = (uchar * )recHeaderPtr; - dataSize = recHeaderSize + recDataSize; - if (dataSize > 0) packet = new char[dataSize]; - } else if (recDataSize > 0) packet = new char[recDataSize]; - } else if (recHeaderSize + recDataSize > 0) packet = new char[recHeaderSize + recDataSize]; + packet_ext->setPacketData(recHeaderPtr, recHeaderSize, recDataSize); } @@ -394,16 +387,18 @@ PIProtocol::~PIProtocol() { } delete diagTimer; delete sendTimer; - if (packet != 0) delete packet; + delete secTimer; if (eth != 0) delete eth; if (ser != 0) delete ser; + delete packet_ext; } void PIProtocol::init() { - packet_ext.setThreadedReadData(this); - packet_ext.setThreadedReadSlot(receiveEvent); - packet_ext.setHeaderCheckSlot(headerValidateEvent); + packet_ext = new PIPacketExtractor(); + packet_ext->setThreadedReadData(this); + packet_ext->setThreadedReadSlot(receiveEvent); + packet_ext->setHeaderCheckSlot(headerValidateEvent); work = new_mp_prot = history_write_rec = history_write_send = false; eth = 0; ser = 0; @@ -412,18 +407,20 @@ void PIProtocol::init() { net_diag = PIProtocol::Unknown; cur_pckt = 0; diagTimer = 0; - packet = 0; timeout_ = 3.f; sendTimer = new PITimer(sendEvent, this); diagTimer = new PITimer(diagEvent, this); + secTimer = new PITimer(secEvent, this); wrong_count = receive_count = send_count = missed_count = 0; - immediate_freq = integral_freq = 0.f; + packets_in_sec = packets_out_sec = bytes_in_sec = bytes_out_sec = 0; + immediate_freq = integral_freq = ifreq = 0.f; headerPtr = dataPtr = sendDataPtr = 0; headerSize = dataSize = sendDataSize = 0; type_rec = type_send = PIProtocol::None; devSenderState = devReceiverState = "Unknown"; devSenderName = devReceiverName = "no device"; history_rsize_rec = history_rsize_send = "no file"; + secTimer->start(1000.); /*addEvent("receiver started"); addEvent("receiver stopped"); addEvent("sender started"); @@ -444,7 +441,7 @@ void PIProtocol::setReceiverDevice(const PIString & device, PISerial::Speed spee type_send = type_rec = PIProtocol::Serial; if (ser == 0) { ser = new PISerial(); - packet_ext.setDevice(ser); + packet_ext->setDevice(ser); } } if (type_rec == PIProtocol::Serial && ser != 0) { @@ -461,7 +458,7 @@ void PIProtocol::setReceiverAddress(const PIString & ip, int port, bool force) { type_rec = PIProtocol::Ethernet; if (eth == 0) { eth = new PIEthernet(); - packet_ext.setDevice(eth); + packet_ext->setDevice(eth); } } if (type_rec == PIProtocol::Ethernet && eth != 0) { @@ -493,13 +490,38 @@ void PIProtocol::setSenderAddress(const PIString & ip, int port, bool force) { } if (type_send == PIProtocol::Ethernet && eth != 0) { eth->setSendAddress(ip, port); - eth->open(); if (ip.isEmpty()) devSenderName = "no ip"; else devSenderName = ip + ":" + PIString::fromNumber(port); } } +void PIProtocol::setSenderIP(const PIString & ip, bool force) { + if (force) { + type_send = PIProtocol::Ethernet; + if (eth == 0) eth = new PIEthernet(); + } + if (type_send == PIProtocol::Ethernet && eth != 0) { + eth->setSendIP(ip); + if (ip.isEmpty()) devSenderName = "no ip"; + else devSenderName = ip + ":" + PIString::fromNumber(eth->sendPort()); + } +} + + +void PIProtocol::setSenderPort(int port, bool force) { + if (force) { + type_send = PIProtocol::Ethernet; + if (eth == 0) eth = new PIEthernet(); + } + if (type_send == PIProtocol::Ethernet && eth != 0) { + eth->setSendPort(port); + if (eth->sendIP().isEmpty()) devSenderName = "no ip"; + else devSenderName = eth->sendIP() + ":" + PIString::fromNumber(port); + } +} + + void PIProtocol::setExpectedFrequency(float frequency) { exp_freq = frequency; changeDisconnectTimeout(); @@ -517,29 +539,35 @@ void PIProtocol::startReceive(float exp_frequency) { if (exp_frequency > 0.f) exp_freq = exp_frequency; //if (type_rec == PIProtocol::Serial) ser->start(); //if (type_rec == PIProtocol::Ethernet) eth->start(); + packet_ext->startThreadedRead(); + msleep(1); + check_state(); if (exp_freq <= 0.f) return; - packet_ext.startThreadedRead(); setExpectedFrequency(exp_freq); diagTimer->start(1000. / exp_freq); diagTimer->reset(); - raiseEvent(this, "receiver started"); + receiverStarted(); } void PIProtocol::startSend(float frequency) { + //cout << "** start send " << send_freq << ", " << frequency << endl; if (frequency > 0.f) send_freq = frequency; + msleep(1); + check_state(); if (send_freq <= 0.f) return; sendTimer->start(1000. / send_freq); - raiseEvent(this, "sender started"); + diagTimer->reset(); + senderStarted(); } void PIProtocol::stopReceive() { //if (type_rec == PIProtocol::Serial) ser->stop(); //if (type_rec == PIProtocol::Ethernet) eth->stop(); - packet_ext.stop(); + packet_ext->stop(); diagTimer->stop(); - raiseEvent(this, "receiver stopped"); + receiverStopped(); } @@ -553,15 +581,20 @@ bool PIProtocol::receiveEvent(void * t, uchar * data, int size) { p->history_file_rec.writeToBinLog(p->history_id_rec, data, size); p->history_rsize_rec.setReadableSize(p->history_file_rec.pos()); } - raiseEvent(p, "received", true); + p->received(true); //p->unlock(); + p->ifreq = p->diagTimer->elapsed_m(); + if (p->ifreq > 0.) p->ifreq = 1000. / p->ifreq; + p->diagTimer->reset(); p->receive_count++; + p->packets_in_sec++; + p->bytes_in_sec += size; p->cur_pckt = 1; if (p->ret_func != 0) p->ret_func(p); if (p->mp_owner != 0) PIMultiProtocolBase::receiveEvent(p->mp_owner, p, true, data, size); return true; } - raiseEvent(p, "received", false); + p->received(false); //p->unlock(); p->wrong_count++; if (p->mp_owner != 0) PIMultiProtocolBase::receiveEvent(p->mp_owner, p, false, data, size); @@ -569,19 +602,21 @@ bool PIProtocol::receiveEvent(void * t, uchar * data, int size) { } -bool PIProtocol::headerValidateEvent(void * t, uchar * src, uchar * rec, int size) { - PIProtocol * p = (PIProtocol * )t; - //cout << "validate\n"; - return p->headerValidate(src, rec, size); -} - - void PIProtocol::diagEvent(void * t, int) { PIProtocol * p = (PIProtocol * )t; p->calc_freq(); p->calc_diag(); p->check_state(); - if (p->ser != 0) p->missed_count = p->packet_ext.missedPackets(); + if (p->ser != 0) p->missed_count = p->packet_ext->missedPackets(); +} + + +void PIProtocol::secEvent(void * t, int ) { + PIProtocol * p = (PIProtocol * )t; + p->speedIn = PIString::readableSize(p->bytes_in_sec) + "/s"; + p->speedOut = PIString::readableSize(p->bytes_out_sec) + "/s"; + p->bytes_in_sec = p->bytes_out_sec = p->packets_in_sec = p->packets_out_sec = 0; + if (p->ser != 0) p->missed_count = p->packet_ext->missedPackets(); } @@ -608,17 +643,16 @@ void PIProtocol::calc_diag() { else if (good_percents > 20.f && good_percents <= 80.f) diag = PIProtocol::Average; else diag = PIProtocol::Good; if (diag != net_diag) { + qualityChanged(net_diag, diag); net_diag = diag; - raiseEvent(this, "quality changed", diag); } } void PIProtocol::calc_freq() { - float tf = float(1000.f / diagTimer->elapsed_m()); - diagTimer->reset(); - if (cur_pckt != 1) tf = 0.f; - immediate_freq = tf; + float tf;// = float(1000.f / diagTimer->elapsed_m()); + tf = immediate_freq = ifreq; + ifreq = 0.f; if (last_freq.size_s() >= pckt_cnt_max && last_freq.size_s() > 0) last_freq.pop_front(); last_freq.push_back(tf); tf = last_freq[0]; @@ -670,11 +704,17 @@ void PIProtocol::send(const void * data, int size, bool direct) { history_rsize_send.setReadableSize(history_file_send.pos()); } if (type_send == PIProtocol::Serial) - if (ser->send(data, size)) + if (ser->send(data, size)) { send_count++; + packets_out_sec++; + bytes_out_sec += size; + } if (type_send == PIProtocol::Ethernet) - if (eth->send(data, size)) + if (eth->send(data, size)) { send_count++; + packets_out_sec++; + bytes_out_sec += size; + } } @@ -682,6 +722,7 @@ void PIProtocol::send() { //lock(); //memcpy(packet, sendDataPtr, sendDataSize); //unlock(); + //cout << "**send" << endl; if (!aboutSend()) return; if (sendDataPtr == 0 || sendDataSize == 0) return; if (history_write_send) { @@ -689,9 +730,15 @@ void PIProtocol::send() { history_rsize_send.setReadableSize(history_file_send.pos()); } if (type_send == PIProtocol::Serial) - if (ser->send(sendDataPtr, sendDataSize)) + if (ser->send(sendDataPtr, sendDataSize)) { send_count++; + packets_out_sec++; + bytes_out_sec += sendDataSize; + } if (type_send == PIProtocol::Ethernet) - if (eth->send(sendDataPtr, sendDataSize)) + if (eth->send(sendDataPtr, sendDataSize)) { send_count++; + packets_out_sec++; + bytes_out_sec += sendDataSize; + } } diff --git a/piprotocol.h b/piprotocol.h old mode 100755 new mode 100644 index 1f512a62..9bd60ad9 --- a/piprotocol.h +++ b/piprotocol.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Protocol, input/output channel (COM, UDP) - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com, Bychkov Andrey wapmobil@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com, Bychkov Andrey wapmobil@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ class PIMultiProtocolBase friend class PIProtocol; public: PIMultiProtocolBase() {;} - ~PIMultiProtocolBase() {;} + virtual ~PIMultiProtocolBase() {;} protected: virtual void received(PIProtocol * prot, bool corrected, uchar * data, int size) {;} @@ -48,6 +48,23 @@ private: typedef void (*ReceiveFunc)(void * ); +/// events: +/// void receiverStarted() +/// void receiverStopped() +/// void senderStarted() +/// void senderStopped() +/// void received(bool validate_is_ok) +/// void qualityChanged(PIProtocol::Quality old_quality, PIProtocol::Quality new_quality) +/// +/// handlers: +/// void startReceive(float exp_frequency = -1.f) +/// void stopReceive() +/// void startSend(float frequency = -1.f) +/// void stopSend() +/// void start() +/// void stop() +/// void send() +/// void send(const void * data, int size, bool direct = false) class PIProtocol: public PIObject { friend class PIMultiProtocolBase; @@ -58,7 +75,7 @@ public: PIProtocol(): PIObject() {init();} PIProtocol(const PIString & config, const PIString & name, void * recHeaderPtr = 0, int recHeaderSize = 0, void * recDataPtr = 0, int recDataSize = 0, void * sendDataPtr = 0, int sendDataSize = 0); // from config - ~PIProtocol(); + virtual ~PIProtocol(); enum Quality {Unknown = 1, Failure = 2, Bad = 3, Average = 4, Good = 5}; @@ -67,8 +84,8 @@ public: EVENT_HANDLER0(PIProtocol, void, stopReceive); void setExpectedFrequency(float frequency); // for connection quality diagnostic void setReceiverDevice(const PIString & device, PISerial::Speed speed, bool force = false); // for Serial - void setReceiverData(void * dataPtr, int dataSize) {this->dataPtr = (uchar * )dataPtr; this->dataSize = dataSize; packet_ext.setPacketData(headerPtr, headerSize, dataSize);} - void setReceiverDataHeader(void * headerPtr, int headerSize) {this->headerPtr = (uchar * )headerPtr; this->headerSize = headerSize; packet_ext.setPacketData(headerPtr, headerSize, dataSize);} + void setReceiverData(void * dataPtr, int dataSize) {this->dataPtr = (uchar * )dataPtr; this->dataSize = dataSize; packet_ext->setPacketData(headerPtr, headerSize, dataSize);} + void setReceiverDataHeader(void * headerPtr, int headerSize) {this->headerPtr = (uchar * )headerPtr; this->headerSize = headerSize; packet_ext->setPacketData(headerPtr, headerSize, dataSize);} void setReceiverAddress(const PIString & ip, int port, bool force = false); // for Ethernet void setReceiverParameters(PIFlags parameters) {if (type_rec == PIProtocol::Serial || type_send == PIProtocol::Serial) ser->setParameters(parameters);} // for Serial void setReceiveSlot(ReceiveFunc slot) {ret_func = slot;} @@ -76,11 +93,13 @@ public: EVENT_HANDLER0(PIProtocol, void, startSend) {startSend(-1.f);} // if "frequency = -1" used last passed value EVENT_HANDLER1(PIProtocol, void, startSend, float, frequency); // if "frequency = -1" used last passed value - EVENT_HANDLER0(PIProtocol, void, stopSend) {sendTimer->stop(); raiseEvent(this, "sender stopped");} + EVENT_HANDLER0(PIProtocol, void, stopSend) {sendTimer->stop(); senderStopped();} void setSenderFrequency(float frequency) {send_freq = frequency;} void setSenderDevice(const PIString & device, PISerial::Speed speed, bool force = false); // for Serial void setSenderData(void * dataPtr, int dataSize) {sendDataPtr = (uchar * )dataPtr; sendDataSize = dataSize;} void setSenderAddress(const PIString & ip, int port, bool force = false); // for Ethernet + void setSenderIP(const PIString & ip, bool force = false); // for Ethernet + void setSenderPort(int port, bool force = false); // for Ethernet void setSenderParameters(PIFlags parameters) {if (type_send == PIProtocol::Serial) ser->setParameters(parameters);} // for Serial float senderFrequency() const {return send_freq;} @@ -94,40 +113,60 @@ public: PIString name() const {return protName;} void setDisconnectTimeout(float timeout) {timeout_ = timeout; changeDisconnectTimeout();} float disconnectTimeout() const {return timeout_;} - float * disconnectTimeout_ptr() {return &timeout_;} + const float * disconnectTimeout_ptr() const {return &timeout_;} float immediateFrequency() const {return immediate_freq;} float integralFrequency() const {return integral_freq;} - float * immediateFrequency_ptr() {return &immediate_freq;} - float * integralFrequency_ptr() {return &integral_freq;} + const float * immediateFrequency_ptr() const {return &immediate_freq;} + const float * integralFrequency_ptr() const {return &integral_freq;} + ullong receiveCountPerSec() const {return packets_in_sec;} + const ullong * receiveCountPerSec_ptr() const {return &packets_in_sec;} + ullong sendCountPerSec() const {return packets_out_sec;} + const ullong * sendCountPerSec_ptr() const {return &packets_out_sec;} + ullong receiveBytesPerSec() const {return bytes_in_sec;} + const ullong * receiveBytesPerSec_ptr() const {return &bytes_in_sec;} + ullong sendBytesPerSec() const {return bytes_out_sec;} + const ullong * sendBytesPerSec_ptr() const {return &bytes_out_sec;} ullong receiveCount() const {return receive_count;} - ullong * receiveCount_ptr() {return &receive_count;} + const ullong * receiveCount_ptr() const {return &receive_count;} ullong wrongCount() const {return wrong_count;} - ullong * wrongCount_ptr() {return &wrong_count;} + const ullong * wrongCount_ptr() const {return &wrong_count;} ullong sendCount() const {return send_count;} - ullong * sendCount_ptr() {return &send_count;} + const ullong * sendCount_ptr() const {return &send_count;} ullong missedCount() const {return missed_count;} - ullong * missedCount_ptr() {return &missed_count;} + const ullong * missedCount_ptr() const {return &missed_count;} PIProtocol::Quality quality() const {return net_diag;} // receive quality - int * quality_ptr() {return (int * )&net_diag;} // receive quality pointer + const int * quality_ptr() const {return (int * )&net_diag;} // receive quality pointer PIString receiverDeviceName() const {return devReceiverName;} PIString senderDeviceName() const {return devSenderName;} PIString receiverDeviceState() const {return devReceiverState;} - PIString * receiverDeviceState_ptr() {return &devReceiverState;} + const PIString * receiverDeviceState_ptr() const {return &devReceiverState;} PIString senderDeviceState() const {return devSenderState;} - PIString * senderDeviceState_ptr() {return &devSenderState;} + const PIString * senderDeviceState_ptr() const {return &devSenderState;} + PIString receiveSpeed() const {return speedIn;} + const PIString * receiveSpeed_ptr() const {return &speedIn;} + PIString sendSpeed() const {return speedOut;} + const PIString * sendSpeed_ptr() const {return &speedOut;} PIString receiverHistorySize() const {return history_rsize_rec;} - PIString * receiverHistorySize_ptr() {return &history_rsize_rec;} + const PIString * receiverHistorySize_ptr() const {return &history_rsize_rec;} PIString senderHistorySize() const {return history_rsize_send;} - PIString * senderHistorySize_ptr() {return &history_rsize_send;} + const PIString * senderHistorySize_ptr() const {return &history_rsize_send;} bool writeReceiverHistory() const {return history_write_rec;} - bool * writeReceiverHistory_ptr() {return &history_write_rec;} + const bool * writeReceiverHistory_ptr() const {return &history_write_rec;} bool writeSenderHistory() const {return history_write_send;} - bool * writeSenderHistory_ptr() {return &history_write_send;} + const bool * writeSenderHistory_ptr() const {return &history_write_send;} void * receiveData() {return dataPtr;} void * sendData() {return sendDataPtr;} - PIByteArray lastHeader() {return packet_ext.lastHeader();} + PIPacketExtractor * packetExtractor() {return packet_ext;} + PIByteArray lastHeader() {return packet_ext->lastHeader();} + + EVENT0(PIProtocol, receiverStarted) + EVENT0(PIProtocol, receiverStopped) + EVENT0(PIProtocol, senderStarted) + EVENT0(PIProtocol, senderStopped) + EVENT1(PIProtocol, received, bool, validate_is_ok) + EVENT2(PIProtocol, qualityChanged, PIProtocol::Quality, old_quality, PIProtocol::Quality, new_quality) protected: virtual bool receive(uchar * data, int size) {if (dataPtr != 0) memcpy(dataPtr, data, size); return true;} // executed when raw data received, break if 'false' return @@ -137,15 +176,13 @@ protected: uint c = 0; for (int i = 0; i < size; ++i) c += ((uchar*)data)[i]; - c = ~(c + 1); - return c; + return ~(c + 1); } virtual uchar checksum_c(void * data, int size) { // function for checksum (uchar) uchar c = 0; for (int i = 0; i < size; ++i) c += ((uchar*)data)[i]; - c = ~(c + 1); - return c; + return ~(c + 1); } virtual bool aboutSend() {return true;} // executed before send data, if return 'false' then data is not sending @@ -162,30 +199,31 @@ protected: private: static void sendEvent(void * e, int) {((PIProtocol * )e)->send();} static bool receiveEvent(void * t, uchar * data, int size); - static bool headerValidateEvent(void * t, uchar * src, uchar * rec, int size); + static bool headerValidateEvent(void * t, uchar * src, uchar * rec, int size) {return ((PIProtocol * )t)->headerValidate(src, rec, size);} static void diagEvent(void * t, int); + static void secEvent(void * t, int); void setMultiProtocolOwner(PIMultiProtocolBase * mp) {mp_owner = mp;} PIMultiProtocolBase * multiProtocolOwner() const {return mp_owner;} void changeDisconnectTimeout(); ReceiveFunc ret_func; - PIPacketExtractor packet_ext; - PITimer * diagTimer, * sendTimer; + PIPacketExtractor * packet_ext; + PITimer * diagTimer, * sendTimer, * secTimer; PIMultiProtocolBase * mp_owner; PIProtocol::Type type_send, type_rec; PIProtocol::Quality net_diag; PIDeque last_freq; PIDeque last_packets; - PIString protName, devReceiverName, devReceiverState, devSenderName, devSenderState; + PIString protName, devReceiverName, devReceiverState, devSenderName, devSenderState, speedIn, speedOut; PIString history_path_rec, history_path_send, history_rsize_rec, history_rsize_send; PIFile history_file_rec, history_file_send; ushort history_id_rec, history_id_send; bool work, new_mp_prot, history_write_rec, history_write_send; - float exp_freq, send_freq, immediate_freq, integral_freq, timeout_; + float exp_freq, send_freq, ifreq, immediate_freq, integral_freq, timeout_; int packets[2], pckt_cnt, pckt_cnt_max; - char * packet, cur_pckt; - ullong wrong_count, receive_count, send_count, missed_count; + char cur_pckt; + ullong wrong_count, receive_count, send_count, missed_count, packets_in_sec, packets_out_sec, bytes_in_sec, bytes_out_sec; }; diff --git a/piserial.cpp b/piserial.cpp old mode 100755 new mode 100644 index a5f90322..2370c5e1 --- a/piserial.cpp +++ b/piserial.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives COM - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com, Bychkov Andrey wapmobil@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com, Bychkov Andrey wapmobil@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -60,6 +60,81 @@ PISerial::~PISerial() { } +bool PISerial::setPin(int number, bool on) { + switch (number) { + case 1: return setCAR(on); break; + case 2: return setSR(on); break; + case 3: return setST(on); break; + case 4: return setDTR(on); break; + case 5: + piCout << "[PISerial] Pin number 5 is ground" << endl; + return false; + case 6: return setDSR(on); break; + case 7: return setRTS(on); break; + case 8: return setCTS(on); break; + case 9: return setRNG(on); break; + default: + piCout << "[PISerial] Pin number " << number << " doesn`t exists!" << endl; + return false; + } + return false; +} + + +bool PISerial::isPin(int number) const { + switch (number) { + case 1: return isCAR(); break; + case 2: return isSR(); break; + case 3: return isST(); break; + case 4: return isDTR(); break; + case 5: return false; + case 6: return isDSR(); break; + case 7: return isRTS(); break; + case 8: return isCTS(); break; + case 9: return isRNG(); break; + default: + piCout << "[PISerial] Pin number " << number << " doesn`t exists!" << endl; + return false; + } + return false; +} + + +bool PISerial::setBit(int bit, bool on, const PIString & bname) { +#ifndef WINDOWS + if (fd < 0) { + piCout << "[PISerial] set" << bname << " error: \"" << path_ << "\" is not opened!" << endl; + return false; + } + if (ioctl(fd, on ? TIOCMBIS : TIOCMBIC, &bit) < 0) { + piCout << "[PISerial] set" << bname << " error: " << errorString(); + return false; + } + return true; +#else + piCout << "[PISerial] set" << bname << " doesn`t implemented on Windows, sorry :-(" << endl; + return false; +#endif +} + + +bool PISerial::isBit(int bit, const PIString & bname) const { +#ifndef WINDOWS + if (fd < 0) { + piCout << "[PISerial] is" << bname << " error: \"" << path_ << "\" is not opened!" << endl; + return false; + } + int ret = 0; + if (ioctl(fd, TIOCMGET, &ret) < 0) + piCout << "[PISerial] is" << bname << " error: " << errorString(); + return ret & bit; +#else + piCout << "[PISerial] set" << bname << " doesn`t implemented on Windows, sorry :-(" << endl; + return false; +#endif +} + + bool PISerial::closeDevice() { if (!isInitialized()) return true; if (isRunning()) { @@ -86,6 +161,8 @@ bool PISerial::closeDevice() { int PISerial::convertSpeed(PISerial::Speed speed) { switch (speed) { + case S50: return B50; + case S75: return B75; case S110: return B110; case S300: return B300; case S600: return B600; @@ -97,6 +174,13 @@ int PISerial::convertSpeed(PISerial::Speed speed) { case S38400: return B38400; case S57600: return B57600; case S115200: return B115200; + case S1500000: return B1500000; + case S2000000: return B2000000; + case S2500000: return B2500000; + case S3000000: return B3000000; + case S3500000: return B3500000; + case S4000000: return B4000000; + default: break; } return B115200; } @@ -114,6 +198,7 @@ bool PISerial::read(void * data, int size, double timeout_ms) { if (ret > 0) all += ret; else msleep(1); } + received(data, all); return (all == size); } else { setReadIsBlocking(true); @@ -122,15 +207,96 @@ bool PISerial::read(void * data, int size, double timeout_ms) { ret = ::read(fd, &((uchar * )data)[all], size - all); if (ret > 0) all += ret; } + received(data, all); return (all == size); } return false; } +PIByteArray PISerial::readData(int size, double timeout_ms) { + int ret, all = 0; + uchar td[1024]; + PIByteArray str; + if (timeout_ms > 0.) { + setReadIsBlocking(false); + timer.reset(); + if (size <= 0) { + while (timer.elapsed_m() < timeout_ms) { + ret = ::read(fd, td, 1024); + if (ret <= 0) msleep(1); + else str << PIByteArray(td, ret); + } + } else { + while (all < size && timer.elapsed_m() < timeout_ms) { + ret = ::read(fd, td, size - all); + if (ret <= 0) msleep(1); + else { + str << PIByteArray(td, ret); + all += ret; + } + } + } + } else { + setReadIsBlocking(true); + all = ::read(fd, td, 1); + str << PIByteArray(td, all); + while (all < size) { + ret = ::read(fd, td, size - all); + if (ret <= 0) msleep(1); + else { + str << PIByteArray(td, ret); + all += ret; + } + } + } + return str; +} + + +PIString PISerial::read(int size, double timeout_ms) { + int ret, all = 0; + uchar td[1024]; + PIString str; + if (timeout_ms > 0.) { + setReadIsBlocking(false); + timer.reset(); + if (size <= 0) { + while (timer.elapsed_m() < timeout_ms) { + ret = ::read(fd, td, 1024); + if (ret <= 0) msleep(1); + else str << PIString((char*)td, ret); + } + } else { + while (all < size && timer.elapsed_m() < timeout_ms) { + ret = ::read(fd, td, size - all); + if (ret <= 0) msleep(1); + else { + str << PIString((char*)td, ret); + all += ret; + } + } + } + } else { + setReadIsBlocking(true); + all = ::read(fd, td, 1); + str << PIString((char*)td, all); + while (all < size) { + ret = ::read(fd, td, size - all); + if (ret <= 0) msleep(1); + else { + str << PIString((char*)td, ret); + all += ret; + } + } + } + return str; +} + + bool PISerial::openDevice() { #ifdef WINDOWS - DWORD da = 0, sm = 0; + DWORD ds = 0, sm = 0; if (isReadable()) {ds |= GENERIC_READ; sm |= FILE_SHARE_READ;} if (isWriteable()) {ds |= GENERIC_WRITE; sm |= FILE_SHARE_WRITE;} hCom = CreateFileA(path_.data(), ds, sm, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0); @@ -176,19 +342,19 @@ bool PISerial::openDevice() { case PIIODevice::WriteOnly: om = O_WRONLY; break; case PIIODevice::ReadWrite: om = O_RDWR; break; } - //cout << "init ser " << path_ << " mode " << om << endl; + //cout << "init ser " << path_ << " mode " << om << " param " << params << endl; fd = ::open(path_.data(), O_NOCTTY | om); if(fd == -1) { piCout << "[PISerial] Unable to open \"" << path_ << "\"" << endl; return false; } - fcntl(fd, F_SETFL, 0); tcgetattr(fd, &desc); sdesc = desc; - desc.c_iflag = desc.c_oflag = desc.c_lflag = 0; + desc.c_iflag = desc.c_oflag = desc.c_lflag = desc.c_cflag = 0; desc.c_cflag = CLOCAL | CSIZE | CS8; if (isReadable()) desc.c_cflag |= CREAD; + if (params[PISerial::HardwareFlowControl]) desc.c_cflag |= CRTSCTS; if (params[PISerial::TwoStopBits]) desc.c_cflag |= CSTOPB; if (params[PISerial::ParityControl]) { desc.c_iflag |= INPCK; @@ -201,12 +367,14 @@ bool PISerial::openDevice() { cfsetispeed(&desc, convertSpeed(ispeed)); cfsetospeed(&desc, convertSpeed(ospeed)); + tcflush(fd, TCIOFLUSH); + fcntl(fd, F_SETFL, 0); + if(tcsetattr(fd, TCSANOW, &desc) < 0) { piCout << "[PISerial] Can`t set attributes for \"" << path_ << "\"" << endl; ::close(fd); return false; } - tcflush(fd, TCIOFLUSH); //piCout << "[PISerial] Initialized " << path_ << endl; #endif return true; @@ -214,7 +382,7 @@ bool PISerial::openDevice() { int PISerial::write(const void * data, int max_size, bool wait) { - //piCout << "[PISerial] send size: " << sizeof(data) << endl; + //piCout << "[PISerial] send " << max_size << ": " << PIString((char*)data, max_size) << endl; if (fd == -1 || !canWrite()) { //piCout << "[PISerial] Can`t write to uninitialized COM" << endl; return -1; @@ -222,12 +390,18 @@ int PISerial::write(const void * data, int max_size, bool wait) { #ifdef WINDOWS DWORD wrote; WriteFile(hCom, data, max_size, &wrote, 0); + if (wait) { + DWORD event; + SetCommMask(hCom, EV_TXEMPTY); + WaitCommEvent(hCom, &event, NULL); + SetCommMask(hCom, EV_RXCHAR); + } #else int wrote; wrote = ::write(fd, data, max_size); if (wait) tcdrain(fd); #endif return (int)wrote; - //piCout << "[PISerial] Error while sending" << endl; + //piCout << "[PISerial] Error while sending" << endl; //piCout << "[PISerial] Wrote " << wrote << " bytes in " << path_ << endl; } diff --git a/piserial.h b/piserial.h old mode 100755 new mode 100644 index 1b823831..4e00e2d8 --- a/piserial.h +++ b/piserial.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives COM - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com, Bychkov Andrey wapmobil@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com, Bychkov Andrey wapmobil@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,21 +25,63 @@ #ifndef WINDOWS # include # include +# include +# ifndef B50 +# define B50 0000001 +# endif +# ifndef B50 +# define B75 0000002 +# endif +# ifndef B1500000 +# define B1500000 0010012 +# endif +# ifndef B2000000 +# define B2000000 0010013 +# endif +# ifndef B2500000 +# define B2500000 0010014 +# endif +# ifndef B3000000 +# define B3000000 0010015 +# endif +# ifndef B3500000 +# define B3500000 0010016 +# endif +# ifndef B4000000 +# define B4000000 0010017 +# endif #else -# define B110 110 -# define B300 300 -# define B600 600 -# define B1200 1200 -# define B2400 2400 -# define B4800 4800 -# define B9600 9600 -# define B14400 14400 -# define B19200 19200 -# define B38400 38400 -# define B57600 57600 -# define B115200 115200 -# define B128000 128000 -# define B256000 256000 +# define TIOCM_LE 1 +# define TIOCM_DTR 4 +# define TIOCM_RTS 7 +# define TIOCM_CTS 8 +# define TIOCM_ST 3 +# define TIOCM_SR 2 +# define TIOCM_CAR 1 +# define TIOCM_RNG 9 +# define TIOCM_DSR 6 +# define B50 50 +# define B75 75 +# define B110 110 +# define B300 300 +# define B600 600 +# define B1200 1200 +# define B2400 2400 +# define B4800 4800 +# define B9600 9600 +# define B14400 14400 +# define B19200 19200 +# define B38400 38400 +# define B57600 57600 +# define B115200 115200 +# define B128000 128000 +# define B256000 256000 +# define B1500000 1500000 +# define B2000000 2000000 +# define B2500000 2500000 +# define B3000000 3000000 +# define B3500000 3500000 +# define B4000000 4000000 #endif class PISerial: public PIIODevice { @@ -50,8 +92,10 @@ public: PISerial(void * data = 0, ReadRetFunc slot = 0); ~PISerial(); - enum Parameters {ParityControl = 0x01, ParityOdd = 0x02, TwoStopBits = 0x04}; + enum Parameters {ParityControl = 0x1, ParityOdd = 0x2, TwoStopBits = 0x4, HardwareFlowControl = 0x8}; enum Speed { + S50 = 50, + S75 = 75, S110 = 110, S300 = 300, S600 = 600, @@ -62,7 +106,13 @@ public: S19200 = 19200, S38400 = 38400, S57600 = 57600, - S115200 = 115200 + S115200 = 115200, + S1500000 = 1500000, // Linux only + S2000000 = 2000000, // Linux only + S2500000 = 2500000, // Linux only + S3000000 = 3000000, // Linux only + S3500000 = 3500000, // Linux only + S4000000 = 4000000 // Linux only }; void setData(void * d) {data = d;} @@ -72,12 +122,39 @@ public: void setOutSpeed(PISerial::Speed speed) {ospeed = speed;} void setInSpeed(PISerial::Speed speed) {ispeed = speed;} void setDevice(const PIString & dev) {path_ = dev;} - void setParameters(PIFlags parameters) {params = parameters;} + + void setParameters(PIFlags parameters_) {params = parameters_;} void setParameter(PISerial::Parameters parameter, bool on = true) {params.setFlag(parameter, on);} + bool isParameterSet(PISerial::Parameters parameter) const {return params[parameter];} + PIFlags parameters() const {return params;} + + bool setPin(int number, bool on); + bool isPin(int number) const; + + bool setLE(bool on) {return setBit(TIOCM_LE, on, "LE");} // useless function, just formally + bool setDTR(bool on) {return setBit(TIOCM_DTR, on, "DTR");} + bool setRTS(bool on) {return setBit(TIOCM_RTS, on, "RTS");} + bool setCTS(bool on) {return setBit(TIOCM_CTS, on, "CTS");} // useless function, just formally + bool setST(bool on) {return setBit(TIOCM_ST, on, "ST");} // useless function, just formally + bool setSR(bool on) {return setBit(TIOCM_SR, on, "SR");} // useless function, just formally + bool setCAR(bool on) {return setBit(TIOCM_CAR, on, "CAR");} // useless function, just formally + bool setRNG(bool on) {return setBit(TIOCM_RNG, on, "RNG");} // useless function, just formally + bool setDSR(bool on) {return setBit(TIOCM_DSR, on, "DSR");} // useless function, just formally + + bool isLE() const {return isBit(TIOCM_LE, "LE");} + bool isDTR() const {return isBit(TIOCM_DTR, "DTR");} + bool isRTS() const {return isBit(TIOCM_RTS, "RTS");} + bool isCTS() const {return isBit(TIOCM_CTS, "CTS");} + bool isST() const {return isBit(TIOCM_ST, "ST");} + bool isSR() const {return isBit(TIOCM_SR, "SR");} + bool isCAR() const {return isBit(TIOCM_CAR, "CAR");} + bool isRNG() const {return isBit(TIOCM_RNG, "RNG");} + bool isDSR() const {return isBit(TIOCM_DSR, "DSR");} + void setVTime(int t) {vtime = t;} -#ifdef WINDOWS void setReadIsBlocking(bool yes) { +#ifdef WINDOWS COMMTIMEOUTS times; times.ReadIntervalTimeout = yes ? vtime : MAXDWORD; times.ReadTotalTimeoutConstant = yes ? 1 : 0; @@ -85,35 +162,48 @@ public: times.WriteTotalTimeoutConstant = 1; times.WriteTotalTimeoutMultiplier = 0; if (isOpened()) SetCommTimeouts(hCom, ×); - } #else - void setReadIsBlocking(bool yes) {if (isOpened()) fcntl(fd, F_SETFL, yes ? 0 : O_NONBLOCK);} + if (isOpened()) fcntl(fd, F_SETFL, yes ? 0 : O_NONBLOCK); #endif + } const PIString & device() const {return path_;} PISerial::Speed outSpeed() const {return ospeed;} PISerial::Speed inSpeed() const {return ispeed;} int VTime() const {return vtime;} -#ifdef WINDOWS +#ifndef WINDOWS + void flush() {if (fd != -1) tcflush(fd, TCIOFLUSH);} +#endif + int read(void * read_to, int max_size) { +#ifdef WINDOWS if (!canRead()) return -1; WaitCommEvent(hCom, 0, 0); ReadFile(hCom, read_to, max_size, &readed, 0); return readed; - } #else - int read(void * read_to, int max_size) {if (!canRead()) return -1; return ::read(fd, read_to, max_size);} + if (!canRead()) return -1; + return ::read(fd, read_to, max_size); #endif + } bool read(void * data, int size, double timeout_ms); + PIString read(int size = -1, double timeout_ms = 1000.); + PIByteArray readData(int size = -1, double timeout_ms = 1000.); int write(const void * data, int max_size, bool wait); int write(const void * data, int max_size) {return write(data, max_size, false);} bool send(const void * data, int size, bool wait = false) {return (write(data, size, wait) == size);} + bool send(const PIString & data, bool wait = false) {return (write(data.data(), data.lengthAscii(), wait) == data.size_s());} + bool send(const PIByteArray & data, bool wait = false) {return (write(data.data(), data.size_s(), wait) == data.size_s());} protected: + virtual void received(void * data, int size) {;} + int convertSpeed(PISerial::Speed speed); + bool setBit(int bit, bool on, const PIString & bname); + bool isBit(int bit, const PIString & bname) const; static bool headerValidate(void * d, uchar * src, uchar * rec, int size) {for (int i = 0; i < size; ++i) if (src[i] != rec[i]) return false; return true;} bool openDevice(); diff --git a/pisignals.cpp b/pisignals.cpp old mode 100755 new mode 100644 index 12f4705c..1336baab --- a/pisignals.cpp +++ b/pisignals.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Signals - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/pisignals.h b/pisignals.h old mode 100755 new mode 100644 index 43b58119..b1e9ec3d --- a/pisignals.h +++ b/pisignals.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Signals - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/pistring.cpp b/pistring.cpp old mode 100755 new mode 100644 index cb1329a9..bbd1721e --- a/pistring.cpp +++ b/pistring.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives String - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,6 +20,28 @@ #include "pistring.h" +const char PIString::toBaseN[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^'}; +const int PIString::fromBaseN[] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, -1, + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; + + void PIString::appendFromChars(const char * c, int s) { int sz; wchar_t wc; @@ -88,11 +110,13 @@ PIString & PIString::operator +=(const wchar_t * str) { } +#ifdef HAS_LOCALE PIString & PIString::operator +=(const wstring & str) { uint l = str.size(); for (uint i = 0; i < l; ++i) push_back(str[i]); return *this; } +#endif PIString & PIString::operator +=(const PIString & str) { @@ -324,13 +348,20 @@ int PIString::lengthAscii() const { const char * PIString::data() const { PIByteArray & d_(*(const_cast(&data_))); d_.clear(); - for (int i = 0, j = 0; i < size_s(); ++i, ++j) { - if (at(i).isAscii()) + int wc; + uchar tc; + for (int i = 0, j = 0; i < size_s(); ++i) { + wc = at(i).toInt(); + while (tc = wc & 0xFF, tc) { + d_.push_back(uchar(tc)); ++j; + wc >>= 8; + } + /*if (at(i).isAscii()) d_.push_back(uchar(at(i).toAscii())); else { d_.push_back((at(i).toCharPtr()[0])); ++j; d_.push_back((at(i).toCharPtr()[1])); - } + }*/ } d_.push_back(uchar('\0')); return (const char * )d_.data(); @@ -339,14 +370,21 @@ const char * PIString::data() const { string PIString::convertToStd() const { string s; + int wc; + uchar tc; if (size() > 0) { for (int i = 0; i < length(); ++i) { - if (at(i).isAscii()) + wc = at(i).toInt(); + while (tc = wc & 0xFF, tc) { + s.push_back(char(tc)); + wc >>= 8; + } + /*if (at(i).isAscii()) s.push_back(at(i).toAscii()); else { s.push_back(at(i).toCharPtr()[0]); s.push_back(at(i).toCharPtr()[1]); - } + }*/ } } return s; @@ -360,7 +398,7 @@ char PIString::toChar() const { return v; } - +/* short PIString::toShort() const { PIString s(trimmed().toLowerCase().toNativeDecimalPoints()); short v; @@ -399,7 +437,7 @@ llong PIString::toLLong() const { sscanf(s.data(), "%lld", &v); return v; } - +*/ PIString & PIString::setReadableSize(long bytes) { clear(); @@ -424,14 +462,14 @@ PIString & PIString::setReadableSize(long bytes) { } -char chrUpr(char c) { +inline char chrUpr(char c) { if (c >= 'a' && c <= 'z') return c + 'A' - 'a'; //if (c >= 'а' && c <= 'я') return c + 'А' - 'а'; return c; } -char chrLwr(char c) { +inline char chrLwr(char c) { if (c >= 'A' && c <= 'Z') return c + 'a' - 'A'; //if (c >= 'А' && c <= 'Я') return c + 'а' - 'А'; return c; diff --git a/pistring.h b/pistring.h old mode 100755 new mode 100644 index 7f1d784f..35e12a7b --- a/pistring.h +++ b/pistring.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives String - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -38,19 +38,23 @@ public: PIString & operator +=(const string & str) {appendFromChars(str.c_str(), str.length()); return *this;} PIString & operator +=(const PIByteArray & ba) {appendFromChars((const char * )ba.data(), ba.size_s()); return *this;} PIString & operator +=(const PIString & str); +#ifdef HAS_LOCALE PIString & operator +=(const wstring & str); +#endif //PIString(const char c) {*this += c;} - PIString(const PIChar c) {piMonitor.strings++; piMonitor.containers--;*this += c;} - PIString(const char * str) {piMonitor.strings++; piMonitor.containers--;*this += str;} - PIString(const wchar_t * str) {piMonitor.strings++; piMonitor.containers--;*this += str;} - PIString(const string & str) {piMonitor.strings++; piMonitor.containers--;*this += str;} - PIString(const wstring & str) {piMonitor.strings++; piMonitor.containers--;*this += str;} - PIString(const PIByteArray & ba) {piMonitor.strings++; piMonitor.containers--;*this += ba;} - PIString(const char * str, const int len) {piMonitor.strings++; piMonitor.containers--;*this += string(str, len);} - PIString(const int len, const char c) {piMonitor.strings++; piMonitor.containers--;for (int i = 0; i < len; ++i) push_back(c);} - PIString(const int len, const PIChar & c) {piMonitor.strings++; piMonitor.containers--;for (int i = 0; i < len; ++i) push_back(c);} - PIString(const PIString & str) {piMonitor.strings++; piMonitor.containers--;*this += str;} + PIString(const PIChar c) {reserve(256); piMonitor.strings++; piMonitor.containers--; *this += c;} + PIString(const char * str) {reserve(256); piMonitor.strings++; piMonitor.containers--; *this += str;} + PIString(const wchar_t * str) {reserve(256); piMonitor.strings++; piMonitor.containers--; *this += str;} + PIString(const string & str) {reserve(256); piMonitor.strings++; piMonitor.containers--; *this += str;} +#ifdef HAS_LOCALE + PIString(const wstring & str) {reserve(256); piMonitor.strings++; piMonitor.containers--; *this += str;} +#endif + PIString(const PIByteArray & ba) {reserve(256); piMonitor.strings++; piMonitor.containers--; *this += ba;} + PIString(const char * str, const int len) {reserve(256); piMonitor.strings++; piMonitor.containers--; *this += string(str, len);} + PIString(const int len, const char c) {reserve(256); piMonitor.strings++; piMonitor.containers--; for (int i = 0; i < len; ++i) push_back(c);} + PIString(const int len, const PIChar & c) {reserve(256); piMonitor.strings++; piMonitor.containers--; for (int i = 0; i < len; ++i) push_back(c);} + PIString(const PIString & str) {reserve(256); piMonitor.strings++; piMonitor.containers--; *this += str;} ~PIString() {piMonitor.strings--; piMonitor.containers++;} operator const char*() {return data();} @@ -133,16 +137,18 @@ public: int lengthAscii() const; const char * data() const; const string stdString() const {return convertToStd();} +#ifdef HAS_LOCALE wstring stdWString() const {return convertToWString();} - PIByteArray toByteArray() {string s(convertToStd()); return PIByteArray(s.c_str(), s.length());} +#endif + PIByteArray toByteArray() const {const char * d = data(); return PIByteArray(d, lengthAscii());} PIStringList split(const PIString & delim) const; PIString toUpperCase() const; PIString toLowerCase() const; -#ifdef QNX - PIString toNativeDecimalPoints() const {PIString s(*this); return s;} -#else +#ifdef HAS_LOCALE PIString toNativeDecimalPoints() const {PIString s(*this); if (currentLocale == 0) return s; return s.replaceAll(".", currentLocale->decimal_point);} +#else + PIString toNativeDecimalPoints() const {PIString s(*this); return s;} #endif int find(const char str, const int start = 0) const; @@ -157,30 +163,43 @@ public: int length() const {return size();} bool isEmpty() const {return (size() == 0 || *this == "");} - bool toBool() const {PIString s(*this); if (atof(s.toNativeDecimalPoints().data()) > 0. || s.trimmed().toLowerCase() == "true") return true; return false;} + bool toBool() const {PIString s(*this); if (atof(s.toNativeDecimalPoints().data()) > 0. || s.trimmed().toLowerCase() == "true" || s.trimmed().toLowerCase() == "on") return true; return false;} char toChar() const; - short toShort() const; - int toInt() const; - long toLong() const; - llong toLLong() const; + short toShort(int base = -1, bool * ok = 0) const {return short(toNumberBase(*this, base, ok));} + ushort toUShort(int base = -1, bool * ok = 0) const {return ushort(toNumberBase(*this, base, ok));} + int toInt(int base = -1, bool * ok = 0) const {return int(toNumberBase(*this, base, ok));} + uint toUInt(int base = -1, bool * ok = 0) const {return uint(toNumberBase(*this, base, ok));} + long toLong(int base = -1, bool * ok = 0) const {return long(toNumberBase(*this, base, ok));} + ulong toULong(int base = -1, bool * ok = 0) const {return ulong(toNumberBase(*this, base, ok));} + llong toLLong(int base = -1, bool * ok = 0) const {return toNumberBase(*this, base, ok);} + ullong toULLong(int base = -1, bool * ok = 0) const {return ullong(toNumberBase(*this, base, ok));} float toFloat() const {PIString s(*this); return (float)atof(s.toNativeDecimalPoints().data());} double toDouble() const {PIString s(*this); return atof(s.toNativeDecimalPoints().data());} ldouble toLDouble() const {PIString s(*this); return atof(s.toNativeDecimalPoints().data());} //inline PIString & setNumber(const char value) {clear(); *this += itos(value); return *this;} - PIString & setNumber(const int value) {clear(); *this += itos(value); return *this;} - PIString & setNumber(const short value) {clear(); *this += itos(value); return *this;} - PIString & setNumber(const long value) {clear(); *this += ltos(value); return *this;} + PIString & setNumber(const short value, int base = 10, bool * ok = 0) {clear(); *this += PIString::fromNumber(value, base, ok); return *this;} + PIString & setNumber(const ushort value, int base = 10, bool * ok = 0) {clear(); *this += PIString::fromNumber(value, base, ok); return *this;} + PIString & setNumber(const int value, int base = 10, bool * ok = 0) {clear(); *this += PIString::fromNumber(value, base, ok); return *this;} + PIString & setNumber(const uint value, int base = 10, bool * ok = 0) {clear(); *this += PIString::fromNumber(value, base, ok); return *this;} + PIString & setNumber(const long value, int base = 10, bool * ok = 0) {clear(); *this += PIString::fromNumber(value, base, ok); return *this;} + PIString & setNumber(const ulong value, int base = 10, bool * ok = 0) {clear(); *this += PIString::fromNumber(value, base, ok); return *this;} + PIString & setNumber(const llong value, int base = 10, bool * ok = 0) {clear(); *this += PIString::fromNumber(value, base, ok); return *this;} + PIString & setNumber(const ullong value, int base = 10, bool * ok = 0) {clear(); *this += PIString::fromNumber(value, base, ok); return *this;} PIString & setNumber(const float value) {clear(); *this += ftos(value); return *this;} PIString & setNumber(const double value) {clear(); *this += dtos(value); return *this;} PIString & setNumber(const ldouble value) {clear(); *this += dtos(value); return *this;} PIString & setReadableSize(long bytes); //inline static PIString fromNumber(const char value) {return PIString(itos(value));} - static PIString fromNumber(const int value) {return PIString(itos(value));} - static PIString fromNumber(const uint value) {return PIString(itos(value));} - static PIString fromNumber(const short value) {return PIString(itos(value));} - static PIString fromNumber(const long value) {return PIString(ltos(value));} + static PIString fromNumber(const short value, int base = 10, bool * ok = 0) {return fromNumberBase(llong(value), base, ok);} + static PIString fromNumber(const ushort value, int base = 10, bool * ok = 0) {return fromNumberBase(llong(value), base, ok);} + static PIString fromNumber(const int value, int base = 10, bool * ok = 0) {return fromNumberBase(llong(value), base, ok);} + static PIString fromNumber(const uint value, int base = 10, bool * ok = 0) {return fromNumberBase(llong(value), base, ok);} + static PIString fromNumber(const long value, int base = 10, bool * ok = 0) {return fromNumberBase(llong(value), base, ok);} + static PIString fromNumber(const ulong value, int base = 10, bool * ok = 0) {return fromNumberBase(llong(value), base, ok);} + static PIString fromNumber(const llong value, int base = 10, bool * ok = 0) {return fromNumberBase(value, base, ok);} + static PIString fromNumber(const ullong value, int base = 10, bool * ok = 0) {return fromNumberBase(llong(value), base, ok);} static PIString fromNumber(const float value) {return PIString(ftos(value));} static PIString fromNumber(const double value) {return PIString(dtos(value));} static PIString fromNumber(const ldouble value) {return PIString(dtos(value));} @@ -188,9 +207,52 @@ public: static PIString readableSize(long bytes) {PIString s; s.setReadableSize(bytes); return s;} private: + static const char toBaseN[]; + static const int fromBaseN[]; + + static PIString fromNumberBase(const llong value, int base = 10, bool * ok = 0) { + if (base < 2 || base > 40) {if (ok != 0) *ok = false; return PIString();} + if (ok != 0) *ok = true; + if (base == 10) return itos(value); + PIString ret; + llong v = value < 0 ? -value : value, cn, b = base; + while (v >= base) { + cn = v % b; + v /= b; + //cout << int(cn) << ", " << int(v) << endl; + ret.push_front(PIChar(toBaseN[cn])); + } + if (v > 0) ret.push_front(PIChar(toBaseN[v])); + if (value < 0) ret.push_front('-'); + return ret; + } + static llong toNumberBase(const PIString & value, int base = -1, bool * ok = 0) { + PIString v = value.trimmed(); + if (base < 0) { + int ind = v.find("0x"); + if (ind == 0 || ind == 1) {v.remove(ind, 2); base = 16;} + else base = 10; + } else + if (base < 2 || base > 40) {if (ok != 0) *ok = false; return 0;} + v.reverse(); + if (ok != 0) *ok = true; + llong ret = 0, m = 1; + int cs; + piForeachC (PIChar & i, v) { + if (i == PIChar('-')) {ret = -ret; continue;} + //cout << i << ", " << fromBaseN[int(i.toAscii())] << ", " << m << endl; + cs = fromBaseN[int(i.toAscii())]; + if (cs < 0 || cs >= base) break; + ret += cs * m; + m *= base; + } + return ret; + } void appendFromChars(const char * c, int s); string convertToStd() const; +#ifdef HAS_LOCALE wstring convertToWString() const {wstring s; for (int i = 0; i < length(); ++i) s.push_back(at(i).toWChar()); return s;} +#endif PIByteArray data_; //string std_string; @@ -201,6 +263,9 @@ private: inline std::ostream & operator <<(std::ostream & s, const PIString & v) {for (int i = 0; i < v.length(); ++i) s << v[i]; return s;} inline std::istream & operator >>(std::istream & s, PIString & v) {string ss; s >> ss; v << PIString(ss); return s;} +inline PIByteArray & operator <<(PIByteArray & s, const PIString & v) {s << v.size_s(); for (int i = 0; i < v.length(); ++i) s << v[i]; return s;} +inline PIByteArray & operator >>(PIByteArray & s, PIString & v) {int sz; s >> sz; v.resize(sz); for (int i = 0; i < sz; ++i) s >> v[i]; return s;} + inline PIString operator +(const PIString & str, const PIString & f) {PIString s(str); s += f; return s;} //inline PIString operator +(const PIString & f, const char c) {PIString s(f); s.push_back(c); return s;} @@ -211,8 +276,8 @@ inline PIString operator +(const PIString & f, const string & str) {PIString s(f inline PIString operator +(const char * str, const PIString & f) {return PIString(str) + f;} inline PIString operator +(const string & str, const PIString & f) {return PIString(str) + f;} -char chrUpr(char c); -char chrLwr(char c); +inline char chrUpr(char c); +inline char chrLwr(char c); class PIStringList: public PIVector { diff --git a/pisystemmonitor.cpp b/pisystemmonitor.cpp old mode 100755 new mode 100644 index 661ed91d..b880ac0c --- a/pisystemmonitor.cpp +++ b/pisystemmonitor.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Process resource monitor - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -53,6 +53,7 @@ bool PISystemMonitor::startOnProcess(int pID) { void PISystemMonitor::run() { #ifndef WINDOWS + file.seekToBegin(); PIString str(file.readAll(true)); int si = str.find('(') + 1, fi = 0, cc = 1; for (int i = si; i < str.size_s(); ++i) { @@ -93,6 +94,7 @@ void PISystemMonitor::run() { stat.priority = sl[16].toInt(); stat.threads = sl[18].toInt(); + filem.seekToBegin(); str = filem.readAll(true); sl = str.split(" "); if (sl.size_s() < 5) return; diff --git a/pisystemmonitor.h b/pisystemmonitor.h old mode 100755 new mode 100644 index d721646d..2f013092 --- a/pisystemmonitor.h +++ b/pisystemmonitor.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Process resource monitor - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,7 +27,7 @@ class PISystemMonitor: public PIThread { public: PISystemMonitor(); - + struct ProcessStats { PIString exec_name; PIString state; @@ -50,21 +50,21 @@ public: float cpu_load_system; float cpu_load_user; }; - + bool startOnProcess(int pID); bool startOnSelf() {return startOnProcess(PIProcess::currentPID());} - ProcessStats & statistic() {return stat;} - + const ProcessStats & statistic() const {return stat;} + private: void run(); - + PIFile file, filem; ProcessStats stat; int pID_, page_size, cpu_count, cycle; #ifndef WINDOWS llong cpu_u_cur, cpu_u_prev, cpu_s_cur, cpu_s_prev; #endif - + }; #endif // PISYSTEMMONITOR_H diff --git a/pisystemtests.cpp b/pisystemtests.cpp new file mode 100644 index 00000000..8dc565f7 --- /dev/null +++ b/pisystemtests.cpp @@ -0,0 +1,44 @@ +/* + PIP - Platform Independent Primitives + System tests results (see system_test folder) + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "pisystemtests.h" + + +namespace PISystemTests { + long time_resolution_ns = 1; + long time_elapsed_ns = 0; + long usleep_offset_us = 60; + + PISystemTestReader pisystestreader; + +}; + + +PISystemTests::PISystemTestReader::PISystemTestReader() { + PIConfig conf( +#ifndef WINDOWS + "/etc/pip.conf" +#else + "pip.conf" +#endif + , PIIODevice::ReadOnly); + time_resolution_ns = conf.getValue("time_resolution_ns", 1); + time_elapsed_ns = conf.getValue("time_elapsed_ns", 0); + usleep_offset_us = conf.getValue("usleep_offset_us", 60); +} diff --git a/pimultiprotocol.cpp b/pisystemtests.h old mode 100755 new mode 100644 similarity index 61% rename from pimultiprotocol.cpp rename to pisystemtests.h index 422263cf..1c8e4539 --- a/pimultiprotocol.cpp +++ b/pisystemtests.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives - Multiprotocol - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + System tests results (see system_test folder) + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,5 +17,23 @@ along with this program. If not, see . */ -#include "pimultiprotocol.h" +#ifndef PISYSTEMTESTS_H +#define PISYSTEMTESTS_H +#include "piconfig.h" + +namespace PISystemTests { + extern long time_resolution_ns; + extern long time_elapsed_ns; + extern long usleep_offset_us; + + class PISystemTestReader { + public: + PISystemTestReader(); + }; + + extern PISystemTestReader pisystestreader; + +}; + +#endif // PISYSTEMTESTS_H diff --git a/pithread.cpp b/pithread.cpp old mode 100755 new mode 100644 index f9d796df..b9e97860 --- a/pithread.cpp +++ b/pithread.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Thread - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,6 +18,17 @@ */ #include "pithread.h" +#include "pisystemtests.h" + + +void piUSleep(int usecs) { +#ifdef WINDOWS + if (usecs > 0) Sleep(usecs / 1000); +#else + usecs -= PISystemTests::usleep_offset_us; + if (usecs > 0) usleep(usecs); +#endif +} PIThread::PIThread(void * data, ThreadFunc func, bool startNow, int timer_delay): PIObject() { @@ -103,7 +114,7 @@ void PIThread::terminate(bool hard) { if (thread == 0) return; running = false; #ifndef WINDOWS - if (hard) kill(thread, SIGKILL); + if (hard) kill((ullong)thread, SIGKILL); else pthread_cancel(thread); #else CloseHandle(thread); @@ -114,20 +125,20 @@ void PIThread::terminate(bool hard) { void * PIThread::thread_function(void * t) { - PIThread * ct = (PIThread * )t; - ct->running = true; - ct->begin(); - raiseEvent(ct, "started"); - while (!ct->terminating) { - if (ct->lockRun) ct->mutex_.lock(); - ct->run(); - if (ct->ret_func != 0) ct->ret_func(ct->data_); - if (ct->lockRun) ct->mutex_.unlock(); - if (ct->timer > 0) msleep(ct->timer); + PIThread & ct = *((PIThread * )t); + ct.running = true; + ct.begin(); + ct.started(); + while (!ct.terminating) { + if (ct.lockRun) ct.mutex_.lock(); + ct.run(); + if (ct.ret_func != 0) ct.ret_func(ct.data_); + if (ct.lockRun) ct.mutex_.unlock(); + if (ct.timer > 0) msleep(ct.timer); } - raiseEvent(ct, "stopped"); - ct->end(); - ct->running = false; + ct.stopped(); + ct.end(); + ct.running = false; //cout << "thread " << t << " exiting ... " << endl; #ifndef WINDOWS pthread_exit(0); @@ -139,17 +150,17 @@ void * PIThread::thread_function(void * t) { void * PIThread::thread_function_once(void * t) { - PIThread * ct = (PIThread * )t; - ct->running = true; - ct->begin(); - raiseEvent(ct, "started"); - if (ct->lockRun) ct->mutex_.lock(); - ct->run(); - if (ct->ret_func != 0) ct->ret_func(ct->data_); - if (ct->lockRun) ct->mutex_.unlock(); - raiseEvent(ct, "stopped"); - ct->end(); - ct->running = false; + PIThread & ct = *((PIThread * )t); + ct.running = true; + ct.begin(); + ct.started(); + if (ct.lockRun) ct.mutex_.lock(); + ct.run(); + if (ct.ret_func != 0) ct.ret_func(ct.data_); + if (ct.lockRun) ct.mutex_.unlock(); + ct.stopped(); + ct.end(); + ct.running = false; //cout << "thread " << t << " exiting ... " << endl; #ifndef WINDOWS pthread_exit(0); diff --git a/pithread.h b/pithread.h old mode 100755 new mode 100644 index b01d4a02..d9f109e0 --- a/pithread.h +++ b/pithread.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Thread - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,14 +29,31 @@ inline void msleep(int msecs) {Sleep(msecs);} #else inline void msleep(int msecs) {usleep(msecs * 1000);} #endif +void piUSleep(int usecs); // on !Windows consider constant "usleep" offset +inline void piMSleep(int msecs) {piUSleep(msecs * 1000);} // on !Windows consider constant "usleep" offset typedef void (*ThreadFunc)(void * ); +/// events: +/// void started() +/// void stopped() +/// +/// handlers: +/// bool start(int timer_delay = -1) +/// bool start(ThreadFunc func, int timer_delay = -1) +/// bool startOnce() +/// bool startOnce(ThreadFunc func) +/// void stop(bool wait = false) +/// void terminate(bool hard = false) +/// bool waitForStart(int timeout_msecs = -1) +/// bool waitForFinish(int timeout_msecs = -1) +/// void lock() +/// void unlock() class PIThread: public PIObject { public: PIThread(void * data, ThreadFunc func, bool startNow = false, int timer_delay = -1); PIThread(bool startNow = false, int timer_delay = -1); - ~PIThread(); + virtual ~PIThread(); #ifdef QNX enum Priority {piHighest = 12, @@ -76,6 +93,9 @@ public: EVENT_HANDLER0(PIThread, void, lock) {mutex_.lock();} EVENT_HANDLER0(PIThread, void, unlock) {mutex_.unlock();} PIMutex & mutex() {return mutex_;} + + EVENT(PIThread, started) + EVENT(PIThread, stopped) protected: static void * thread_function(void * t); diff --git a/pitimer.cpp b/pitimer.cpp index 802df41d..da90d0c2 100644 --- a/pitimer.cpp +++ b/pitimer.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Timer - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,49 +18,87 @@ */ #include "pitimer.h" +#include "pisystemtests.h" -PITimer::PITimer(TimerEvent slot, void * data_) -#ifdef WINDOWS +#ifdef PIP_TIMER_RT +PITimer::TimerPool * pool = 0; +#endif + + +PITimer::PITimer(TimerEvent slot, void * data_, bool threaded_) +#ifndef PIP_TIMER_RT : PIThread() { #else : PIObject() { #endif ret_func = slot; data = data_; -#ifndef WINDOWS +#ifdef PIP_TIMER_RT piMonitor.timers++; ti = -1; + threaded = threaded_; running = false; + memset(&se, 0, sizeof(se)); se.sigev_notify = SIGEV_THREAD; se.sigev_value.sival_ptr = this; - se.sigev_notify_function = timer_event; + se.sigev_notify_function = PITimer::timer_event; + se.sigev_notify_attributes = 0; + lockRun = false; +#endif + reset(); +} + + +PITimer::PITimer(bool threaded_) +#ifndef PIP_TIMER_RT +: PIThread() { +#else +: PIObject() { +#endif + ret_func = 0; + data = 0; +#ifdef PIP_TIMER_RT + piMonitor.timers++; + ti = -1; + threaded = threaded_; + running = false; + memset(&se, 0, sizeof(se)); + se.sigev_notify = SIGEV_THREAD; + se.sigev_value.sival_ptr = this; + se.sigev_notify_function = PITimer::timer_event; se.sigev_notify_attributes = 0; lockRun = false; #endif reset(); - /*addEvent("timeout"); - addEventHandler(HANDLER(PITimer, start)); - addEventHandler(HANDLER(PITimer, stop)); - addEventHandler(HANDLER(PITimer, reset));*/ } PITimer::~PITimer() { -#ifndef WINDOWS +#ifdef PIP_TIMER_RT piMonitor.timers--; #endif stop(); } -#ifndef WINDOWS +#ifdef PIP_TIMER_RT void PITimer::start(double msecs) { - if (ti == 0 || msecs < 0) return; + if (ti != -1 || msecs < 0 || running) return; + if (!threaded) { + ticks = int(msecs); + if (pool == 0) pool = new TimerPool(); + pool->add(this); + //cout << "not threaded timer start " << msecs << " msecs\n"; + if (!pool->isRunning()) pool->start(); + running = true; + return; + } spec.it_interval.tv_nsec = ((int)(msecs * 1000) % 1000000) * 1000; spec.it_interval.tv_sec = (time_t)(msecs / 1000); spec.it_value = spec.it_interval; ti = timer_create(CLOCK_REALTIME, &se, &timer); + //cout << "***create timer " << msecs << " msecs\n"; if (ti == -1) { piCout << "[PITimer] Can`t create timer for " << msecs << " msecs: " << errorString() << endl; return; @@ -70,18 +108,146 @@ void PITimer::start(double msecs) { } +void PITimer::deferredStart(double interval_msecs, double delay_msecs) { + if (ti != -1 || interval_msecs < 0 || running) return; + spec.it_interval.tv_nsec = ((int)(interval_msecs * 1000) % 1000000) * 1000; + spec.it_interval.tv_sec = (time_t)(interval_msecs / 1000); + spec.it_value.tv_nsec = ((int)(delay_msecs * 1000) % 1000000) * 1000; + spec.it_value.tv_sec = (time_t)(delay_msecs / 1000); + ti = timer_create(CLOCK_REALTIME, &se, &timer); + //cout << "***create timer\n"; + if (ti == -1) { + piCout << "[PITimer] Can`t create timer for " << interval_msecs << " msecs: " << errorString() << endl; + return; + } + timer_settime(timer, 0, &spec, 0); + running = true; +} + + +void PITimer::deferredStart(double interval_msecs, const PIDateTime & start_datetime) { + if (ti != -1 || interval_msecs < 0 || running) return; + spec.it_interval.tv_nsec = ((int)(interval_msecs * 1000) % 1000000) * 1000; + spec.it_interval.tv_sec = (time_t)(interval_msecs / 1000); + struct tm dtm; + memset(&dtm, 0, sizeof(dtm)); + dtm.tm_sec = start_datetime.seconds; + dtm.tm_min = start_datetime.minutes; + dtm.tm_hour = start_datetime.hours; + dtm.tm_mday = start_datetime.day; + dtm.tm_mon = start_datetime.month - 1; + dtm.tm_year = start_datetime.year - 1900; + spec.it_value.tv_nsec = 0; + spec.it_value.tv_sec = mktime(&dtm); + ti = timer_create(CLOCK_REALTIME, &se, &timer); + //cout << "***create timer\n"; + if (ti == -1) { + piCout << "[PITimer] Can`t create timer for " << interval_msecs << " msecs: " << errorString() << endl; + return; + } + timer_settime(timer, TIMER_ABSTIME, &spec, 0); + running = true; +} + + + +void PITimer::TimerPool::remove(PITimer * t) { + mutex.lock(); + for (int i = 0; i < timers.size_s(); ++i) + if (timers[i].first == t) { + timers.remove(i); + mutex.unlock(); + return; + } + mutex.unlock(); +} + + +void PITimer::TimerPool::begin() { + //cout << "pool begin\n"; + /*struct sigaction sa; + sa.sa_flags = 0; + sa.sa_handler = empty_handler; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGALRM, &sa, 0) == -1) { + piCout << "[PITimer] sigaction error: " << errorString() << endl; + stop(); + return; + }*/ + sigemptyset(&ss); + sigaddset(&ss, SIGALRM); + memset(&se, 0, sizeof(se)); + se.sigev_notify = SIGEV_SIGNAL; + se.sigev_signo = SIGALRM; + spec.it_interval.tv_nsec = 1000000; + spec.it_interval.tv_sec = 0; + spec.it_value = spec.it_interval; + //cout << "***create pool timer\n"; + if (timer_create(CLOCK_REALTIME, &se, &timer) == -1) { + piCout << "[PITimer] Can`t create timer for pool: " << errorString() << endl; + stop(); + return; + } + if (timer_settime(timer, 0, &spec, 0) == -1) { + piCout << "[PITimer] Can`t set timer for pool: " << errorString() << endl; + stop(); + return; + } + ti = 1; +} + + +void PITimer::TimerPool::run() { + //cout << "wait ...\n"; + sigwait(&ss, &si); + //cout << "ok\n"; + mutex.lock(); + //cout << "* pool tick , pool = " << this <<", timers = " << timers.size()<<"\n"; + for (int i = 0; i < timers.size_s(); ++i) { + TimerPair & ct(timers[i]); + sv.sival_ptr = ct.first; + ct.second++; + //cout << "** pool tick for " << ct.first << ", cnt " << ct.second << ", " << ct.first->ticks << "\n"; + if (ct.second >= ct.first->ticks) { + //cout << "*** timer "<remove(this); + if (pool->isEmpty()) pool->terminate(); + } + } + if (ti != -1) timer_delete(timer); + ti = -1; +} + + void PITimer::timer_event(sigval e) { PITimer * ct = (PITimer * )e.sival_ptr; if (!ct->running) return; if (ct->lockRun) ct->lock(); if (ct->ret_func != 0) ct->ret_func(ct->data, 1); - raiseEvent(ct, "timeout", ct->data, 1); + ct->timeout(ct->data, 1); + ct->tick(ct->data, 1); piForeach (TimerSlot & i, ct->ret_funcs) { if (i.delim > ++(i.tick)) continue; i.tick = 0; if (i.slot != 0) i.slot(ct->data, i.delim); else if (ct->ret_func != 0) ct->ret_func(ct->data, i.delim); - raiseEvent(ct, "timeout", ct->data, i.delim); + ct->timeout(ct->data, i.delim); + ct->tick(ct->data, i.delim); } if (ct->lockRun) ct->unlock(); } @@ -101,32 +267,48 @@ bool PITimer::waitForFinish(int timeout_msecs) { return cnt < timeout_msecs; } - #else + +void PITimer::start(double msecs) { + if (msecs < 0 || running) return; + inc_time = PISystemTime::fromMilliseconds(msecs); + st_time = currentSystemTime() + inc_time; + PIThread::start(); +} + + void PITimer::run() { if (!running) return; + (st_time - currentSystemTime()).sleep(); + st_time += inc_time; if (lockRun) lock(); if (ret_func != 0) ret_func(data, 1); - raiseEvent(this, "timeout", data, 1); + timeout(data, 1); + tick(data, 1); piForeach (TimerSlot & i, ret_funcs) { if (i.delim > ++(i.tick)) continue; i.tick = 0; if (i.slot != 0) i.slot(data, i.delim); else if (ret_func != 0) ret_func(data, i.delim); - raiseEvent(this, "timeout", data, i.delim); + timeout(data, i.delim); + tick(data, i.delim); } if (lockRun) unlock(); } -#endif +#endif double PITimer::elapsed_n() { #ifdef WINDOWS t_cur = GetCurrentTime(); return (t_cur - t_st) * 1000000.; #else +# ifdef MAC_OS + clock_get_time(__pi_mac_clock, &t_cur); +# else clock_gettime(0, &t_cur); - return (t_cur.tv_sec - t_st.tv_sec) * 1.e+9 + (t_cur.tv_nsec - t_st.tv_nsec); +# endif + return (t_cur.tv_sec - t_st.tv_sec) * 1.e+9 + (t_cur.tv_nsec - t_st.tv_nsec - PISystemTests::time_elapsed_ns); #endif } @@ -136,8 +318,12 @@ double PITimer::elapsed_u() { t_cur = GetCurrentTime(); return (t_cur - t_st) * 1000.; #else +# ifdef MAC_OS + clock_get_time(__pi_mac_clock, &t_cur); +# else clock_gettime(0, &t_cur); - return (t_cur.tv_sec - t_st.tv_sec) * 1.e+6 + (t_cur.tv_nsec - t_st.tv_nsec) / 1.e+3; +# endif + return (t_cur.tv_sec - t_st.tv_sec) * 1.e+6 + (t_cur.tv_nsec - t_st.tv_nsec - PISystemTests::time_elapsed_ns) / 1.e+3; #endif } @@ -147,8 +333,12 @@ double PITimer::elapsed_m() { t_cur = GetCurrentTime(); return (double)(t_cur - t_st); #else +# ifdef MAC_OS + clock_get_time(__pi_mac_clock, &t_cur); +# else clock_gettime(0, &t_cur); - return (t_cur.tv_sec - t_st.tv_sec) * 1.e+3 + (t_cur.tv_nsec - t_st.tv_nsec) / 1.e+6; +# endif + return (t_cur.tv_sec - t_st.tv_sec) * 1.e+3 + (t_cur.tv_nsec - t_st.tv_nsec - PISystemTests::time_elapsed_ns) / 1.e+6; #endif } @@ -158,18 +348,20 @@ double PITimer::elapsed_s() { t_cur = GetCurrentTime(); return (t_cur - t_st) / 1000.; #else +# ifdef MAC_OS + clock_get_time(__pi_mac_clock, &t_cur); +# else clock_gettime(0, &t_cur); - return (t_cur.tv_sec - t_st.tv_sec) + (t_cur.tv_nsec - t_st.tv_nsec) / 1.e+9; +# endif + return (t_cur.tv_sec - t_st.tv_sec) + (t_cur.tv_nsec - t_st.tv_nsec - PISystemTests::time_elapsed_ns) / 1.e+9; #endif } double PITimer::reset_time_n() { #ifdef WINDOWS - t_cur = GetCurrentTime(); - return t_st * 1000000.; + return t_st * 1.e+6; #else - clock_gettime(0, &t_cur); return t_st.tv_sec * 1.e+9 + t_st.tv_nsec; #endif } @@ -177,10 +369,8 @@ double PITimer::reset_time_n() { double PITimer::reset_time_u() { #ifdef WINDOWS - t_cur = GetCurrentTime(); - return (t_cur - t_st) * 1000.; + return t_st * 1.e+3; #else - clock_gettime(0, &t_cur); return t_st.tv_sec * 1.e+6 + t_st.tv_nsec / 1.e+3; #endif } @@ -188,10 +378,8 @@ double PITimer::reset_time_u() { double PITimer::reset_time_m() { #ifdef WINDOWS - t_cur = GetCurrentTime(); - return (double)(t_cur - t_st); + return (double)t_st; #else - clock_gettime(0, &t_cur); return t_st.tv_sec * 1.e+3 + t_st.tv_nsec / 1.e+6; #endif } @@ -199,22 +387,34 @@ double PITimer::reset_time_m() { double PITimer::reset_time_s() { #ifdef WINDOWS - t_cur = GetCurrentTime(); - return (t_cur - t_st) / 1000.; + return t_st / 1000.; #else - clock_gettime(0, &t_cur); return t_st.tv_sec + t_st.tv_nsec / 1.e+9; #endif } +PISystemTime PITimer::reset_time() { +#ifdef WINDOWS + return PISystemTime(t_st / 1000, (t_st % 1000) * 1000000); +#else + return PISystemTime(t_st.tv_sec, t_st.tv_nsec); +#endif +} + + double PITimer::elapsed_system_n() { #ifdef WINDOWS long t_cur = GetCurrentTime(); return (t_cur * 1000000.); #else +# ifdef MAC_OS + mach_timespec_t t_cur; + clock_get_time(__pi_mac_clock, &t_cur); +# else timespec t_cur; clock_gettime(0, &t_cur); +# endif return (t_cur.tv_sec * 1.e+9 + t_cur.tv_nsec); #endif } @@ -225,8 +425,13 @@ double PITimer::elapsed_system_u() { long t_cur = GetCurrentTime(); return (t_cur * 1000.); #else +# ifdef MAC_OS + mach_timespec_t t_cur; + clock_get_time(__pi_mac_clock, &t_cur); +# else timespec t_cur; clock_gettime(0, &t_cur); +# endif return (t_cur.tv_sec * 1.e+6 + (t_cur.tv_nsec / 1.e+3)); #endif } @@ -237,8 +442,13 @@ double PITimer::elapsed_system_m() { long t_cur = GetCurrentTime(); return (double)t_cur; #else +# ifdef MAC_OS + mach_timespec_t t_cur; + clock_get_time(__pi_mac_clock, &t_cur); +# else timespec t_cur; clock_gettime(0, &t_cur); +# endif return (t_cur.tv_sec * 1.e+3 + (t_cur.tv_nsec / 1.e+6)); #endif } @@ -249,8 +459,13 @@ double PITimer::elapsed_system_s() { long t_cur = GetCurrentTime(); return (t_cur / 1000.); #else +# ifdef MAC_OS + mach_timespec_t t_cur; + clock_get_time(__pi_mac_clock, &t_cur); +# else timespec t_cur; clock_gettime(0, &t_cur); +# endif return (t_cur.tv_sec + (t_cur.tv_nsec / 1.e+9)); #endif } @@ -278,6 +493,37 @@ PIDate currentDate() { } +PIDateTime currentDateTime() { + time_t rt = time(0); + tm * pt = localtime(&rt); + PIDateTime dt; + dt.seconds = pt->tm_sec; + dt.minutes = pt->tm_min; + dt.hours = pt->tm_hour; + dt.day = pt->tm_mday; + dt.month = pt->tm_mon + 1; + dt.year = pt->tm_year + 1900; + return dt; +} + + +PISystemTime currentSystemTime() { +#ifdef WINDOWS + long t_cur = GetCurrentTime(); + return PISystemTime(t_cur / 1000, (t_cur % 1000) * 1000000); +#else +# ifdef MAC_OS + mach_timespec_t t_cur; + clock_get_time(__pi_mac_clock, &t_cur); +# else + timespec t_cur; + clock_gettime(0, &t_cur); +# endif + return PISystemTime(t_cur.tv_sec, t_cur.tv_nsec); +#endif +} + + PIString PITime::toString(const PIString & format) { PIString ts = format; ts.replace("hh", PIString::fromNumber(hours).expandLeftTo(2, '0')); @@ -293,10 +539,29 @@ PIString PITime::toString(const PIString & format) { PIString PIDate::toString(const PIString & format) { PIString ts = format; ts.replace("yyyy", PIString::fromNumber(year).expandLeftTo(4, '0')); - ts.replace("yy", PIString::fromNumber(year).expandLeftTo(2, '0')); - ts.replace("y", PIString::fromNumber(year)); - ts.replace("mm", PIString::fromNumber(month).expandLeftTo(2, '0')); - ts.replace("m", PIString::fromNumber(month)); + ts.replace("yy", PIString::fromNumber(year).right(2)); + ts.replace("y", PIString::fromNumber(year).right(1)); + ts.replace("MM", PIString::fromNumber(month).expandLeftTo(2, '0')); + ts.replace("M", PIString::fromNumber(month)); + ts.replace("dd", PIString::fromNumber(day).expandLeftTo(2, '0')); + ts.replace("d", PIString::fromNumber(day)); + return ts; +} + + +PIString PIDateTime::toString(const PIString & format) { + PIString ts = format; + ts.replace("hh", PIString::fromNumber(hours).expandLeftTo(2, '0')); + ts.replace("h", PIString::fromNumber(hours)); + ts.replace("mm", PIString::fromNumber(minutes).expandLeftTo(2, '0')); + ts.replace("m", PIString::fromNumber(minutes)); + ts.replace("ss", PIString::fromNumber(seconds).expandLeftTo(2, '0')); + ts.replace("s", PIString::fromNumber(seconds)); + ts.replace("yyyy", PIString::fromNumber(year).expandLeftTo(4, '0')); + ts.replace("yy", PIString::fromNumber(year).right(2)); + ts.replace("y", PIString::fromNumber(year).right(1)); + ts.replace("MM", PIString::fromNumber(month).expandLeftTo(2, '0')); + ts.replace("M", PIString::fromNumber(month)); ts.replace("dd", PIString::fromNumber(day).expandLeftTo(2, '0')); ts.replace("d", PIString::fromNumber(day)); return ts; @@ -318,10 +583,29 @@ PIString time2string(const PITime & time, const PIString & format) { PIString date2string(const PIDate & date, const PIString & format) { PIString ts = format; ts.replace("yyyy", PIString::fromNumber(date.year).expandLeftTo(4, '0')); - ts.replace("yy", PIString::fromNumber(date.year).expandLeftTo(2, '0')); - ts.replace("y", PIString::fromNumber(date.year)); - ts.replace("mm", PIString::fromNumber(date.month).expandLeftTo(2, '0')); - ts.replace("m", PIString::fromNumber(date.month)); + ts.replace("yy", PIString::fromNumber(date.year).right(2)); + ts.replace("y", PIString::fromNumber(date.year).right(1)); + ts.replace("MM", PIString::fromNumber(date.month).expandLeftTo(2, '0')); + ts.replace("M", PIString::fromNumber(date.month)); + ts.replace("dd", PIString::fromNumber(date.day).expandLeftTo(2, '0')); + ts.replace("d", PIString::fromNumber(date.day)); + return ts; +} + + +PIString datetime2string(const PIDateTime & date, const PIString & format) { + PIString ts = format; + ts.replace("hh", PIString::fromNumber(date.hours).expandLeftTo(2, '0')); + ts.replace("h", PIString::fromNumber(date.hours)); + ts.replace("mm", PIString::fromNumber(date.minutes).expandLeftTo(2, '0')); + ts.replace("m", PIString::fromNumber(date.minutes)); + ts.replace("ss", PIString::fromNumber(date.seconds).expandLeftTo(2, '0')); + ts.replace("s", PIString::fromNumber(date.seconds)); + ts.replace("yyyy", PIString::fromNumber(date.year).expandLeftTo(4, '0')); + ts.replace("yy", PIString::fromNumber(date.year).right(2)); + ts.replace("y", PIString::fromNumber(date.year).right(1)); + ts.replace("MM", PIString::fromNumber(date.month).expandLeftTo(2, '0')); + ts.replace("M", PIString::fromNumber(date.month)); ts.replace("dd", PIString::fromNumber(date.day).expandLeftTo(2, '0')); ts.replace("d", PIString::fromNumber(date.day)); return ts; diff --git a/pitimer.h b/pitimer.h index 8dc21ac2..caa009f3 100644 --- a/pitimer.h +++ b/pitimer.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Timer - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,6 +28,45 @@ typedef void (*TimerEvent)(void * , int ); +class PISystemTime { +public: + PISystemTime() {seconds = nanoseconds = 0;} + PISystemTime(long s, long ns) {seconds = s; nanoseconds = ns; checkOverflows();} + PISystemTime(const PISystemTime & t) {seconds = t.seconds; nanoseconds = t.nanoseconds;} + + double toSeconds() const {return double(seconds) + nanoseconds / 1.e+9;} + double toMilliseconds() const {return seconds * 1.e+3 + nanoseconds / 1.e+6;} + double toMicroseconds() const {return seconds * 1.e+6 + nanoseconds / 1.e+3;} + double toNanoseconds() const {return seconds * 1.e+9 + double(nanoseconds);} + void sleep() {piUSleep(piFloord(toMicroseconds()));} // wait self value, useful to wait some dT = (t1 - t0) + PISystemTime abs() const {return PISystemTime(piAbsl(seconds), piAbsl(nanoseconds));} + PISystemTime operator +(const PISystemTime & t) {PISystemTime tt(*this); tt.seconds += t.seconds; tt.nanoseconds += t.nanoseconds; tt.checkOverflows(); return tt;} + PISystemTime operator -(const PISystemTime & t) {PISystemTime tt(*this); tt.seconds -= t.seconds; tt.nanoseconds -= t.nanoseconds; tt.checkOverflows(); return tt;} + PISystemTime & operator +=(const PISystemTime & t) {seconds += t.seconds; nanoseconds += t.nanoseconds; checkOverflows(); return *this;} + PISystemTime & operator -=(const PISystemTime & t) {seconds -= t.seconds; nanoseconds -= t.nanoseconds; checkOverflows(); return *this;} + bool operator ==(const PISystemTime & t) {return ((seconds == t.seconds) && (nanoseconds == t.nanoseconds));} + bool operator !=(const PISystemTime & t) {return ((seconds != t.seconds) || (nanoseconds != t.nanoseconds));} + bool operator >(const PISystemTime & t) {if (seconds == t.seconds) return nanoseconds > t.nanoseconds; return seconds > t.seconds;} + bool operator <(const PISystemTime & t) {if (seconds == t.seconds) return nanoseconds < t.nanoseconds; return seconds < t.seconds;} + bool operator >=(const PISystemTime & t) {if (seconds == t.seconds) return nanoseconds >= t.nanoseconds; return seconds >= t.seconds;} + bool operator <=(const PISystemTime & t) {if (seconds == t.seconds) return nanoseconds <= t.nanoseconds; return seconds <= t.seconds;} + + static PISystemTime fromSeconds(double v) {long s = piFloord(v); return PISystemTime(s, (v - s) * 1000000000);} + static PISystemTime fromMilliseconds(double v) {long s = piFloord(v / 1000.); return PISystemTime(s, (v / 1000. - s) * 1000000000);} + static PISystemTime fromMicroseconds(double v) {long s = piFloord(v / 1000000.); return PISystemTime(s, (v / 1000000. - s) * 1000000000);} + static PISystemTime fromNanoseconds(double v) {long s = piFloord(v / 1000000000.); return PISystemTime(s, (v / 1000000000. - s) * 1000000000);} + + long seconds; + long nanoseconds; + +private: + void checkOverflows() {while (nanoseconds >= 1000000000) {nanoseconds -= 1000000000; seconds++;} while (nanoseconds < 0) {nanoseconds += 1000000000; seconds--;}} + +}; + +inline PIByteArray & operator <<(PIByteArray & s, const PISystemTime & v) {s << v.seconds << v.nanoseconds; return s;} +inline PIByteArray & operator >>(PIByteArray & s, PISystemTime & v) {s >> v.seconds >> v.nanoseconds; return s;} + struct PITime { int seconds; int minutes; @@ -38,29 +77,67 @@ struct PITime { struct PIDate { int day; int month; - int year; // since 1900 - PIString toString(const PIString & format = "d.mm.yyyy"); + int year; + PIString toString(const PIString & format = "d.MM.yyyy"); }; +struct PIDateTime { + int seconds; + int minutes; + int hours; + int day; + int month; + int year; + PIString toString(const PIString & format = "h:mm:ss d.MM.yyyy"); +}; + +/// events: +/// void timeout(void * data, int delimiter) +/// +/// handlers: +/// void start(double msecs) +/// void deferredStart(double interval_msecs, double delay_msecs) +/// void deferredStart(double interval_msecs, const PIDateTime & start_datetime) +/// void stop() +/// bool waitForFinish(int timeout_msecs = -1) +/// void reset() +/// void clearDelimiters() +/// void lock() +/// void unlock() class PITimer -#ifdef WINDOWS +#ifndef PIP_TIMER_RT : public PIThread #else : public PIObject #endif { public: - PITimer(TimerEvent slot = 0, void * data = 0); - ~PITimer(); + PITimer(TimerEvent slot = 0, void * data = 0, bool threaded = true); + PITimer(bool threaded); + virtual ~PITimer(); void setData(void * data_) {data = data_;} void setSlot(TimerEvent slot) {ret_func = slot;} -#ifdef WINDOWS - void reset() {t_st = GetCurrentTime();} +#ifndef PIP_TIMER_RT +# ifdef WINDOWS + EVENT_HANDLER0(PITimer, void, reset) {t_st = GetCurrentTime();} + EVENT_HANDLER1(PIThread, bool, start, int, timer_delay) {start(double(timer_delay)); return true;} + EVENT_HANDLER1(PITimer, void, start, double, msecs); +# elif defined(MAC_OS) + EVENT_HANDLER0(PITimer, void, reset) {clock_get_time(__pi_mac_clock, &t_st);} + EVENT_HANDLER1(PIThread, bool, start, int, timer_delay) {start(double(timer_delay)); return true;} + EVENT_HANDLER1(PITimer, void, start, double, msecs); +# else + EVENT_HANDLER0(PITimer, void, reset) {clock_gettime(0, &t_st);} + EVENT_HANDLER1(PIThread, bool, start, int, timer_delay) {start(double(timer_delay)); return true;} + EVENT_HANDLER1(PITimer, void, start, double, msecs); +# endif #else EVENT_HANDLER0(PITimer, void, reset) {clock_gettime(0, &t_st);} EVENT_HANDLER1(PITimer, void, start, double, msecs); - EVENT_HANDLER0(PITimer, void, stop) {if (ti == 0) timer_delete(timer); ti = -1; running = false;} + EVENT_HANDLER2(PITimer, void, deferredStart, double, interval_msecs, double, delay_msecs); + EVENT_HANDLER2(PITimer, void, deferredStart, double, interval_msecs, const PIDateTime &, start_datetime); + EVENT_HANDLER0(PITimer, void, stop); EVENT_HANDLER0(PITimer, bool, waitForFinish) {return waitForFinish(-1);} EVENT_HANDLER1(PITimer, bool, waitForFinish, int, timeout_msecs); bool isRunning() const {return running;} @@ -83,29 +160,71 @@ public: double reset_time_u(); // microseconds double reset_time_m(); // miliseconds double reset_time_s(); // seconds + PISystemTime reset_time(); static double elapsed_system_n(); // nanoseconds static double elapsed_system_u(); // microseconds static double elapsed_system_m(); // miliseconds static double elapsed_system_s(); // seconds -private: -#ifdef WINDOWS - void run(); - long t_st, t_cur; -#else +#ifdef PIP_TIMER_RT + class TimerPool: public PIThread { + public: + TimerPool(): PIThread() {/*cout << "+++++new pool\n"; */ti = -1;} + ~TimerPool() {stop();} + void add(PITimer * t) {mutex.lock(); timers << TimerPair(t, 0); mutex.unlock();} + void remove(PITimer * t); + bool isEmpty() const {return timers.isEmpty();} + typedef PIPair TimerPair; + private: + static void empty_handler(int) {} + void begin(); + void run(); + void end() {/*cout << "pool end\n"; */if (ti != -1) timer_delete(timer); ti = -1;} + int ti, si; + sigset_t ss; + sigevent se; + sigval sv; + itimerspec spec; + + timer_t timer; + PIVector timers; + PIMutex mutex; + }; static void timer_event(sigval e); + int ticks; +#endif + + EVENT2(PITimer, timeout, void * , data, int, delimiter) + +protected: + virtual void tick(void * data, int delimiter) {;} - bool running; +private: +#ifndef PIP_TIMER_RT + void run(); + + PISystemTime st_time, inc_time; +#else + bool running, threaded; volatile bool lockRun; PIMutex mutex_; int ti; itimerspec spec; - timespec t_st, t_cur; timer_t timer; sigevent se; #endif + +#ifdef WINDOWS + long +#elif defined(MAC_OS) + mach_timespec_t +#else + timespec +#endif + t_st, t_cur; + struct TimerSlot { TimerSlot(TimerEvent slot_ = 0, int delim_ = 1) {slot = slot_; delim = delim_; tick = 0;} TimerEvent slot; @@ -119,9 +238,16 @@ private: }; +#ifdef PIP_TIMER_RT +extern PITimer::TimerPool * pool; +#endif + PITime currentTime(); PIDate currentDate(); +PIDateTime currentDateTime(); +PISystemTime currentSystemTime(); PIString time2string(const PITime & time, const PIString & format = "h:mm:ss"); // obsolete, use PITime.toString() instead -PIString date2string(const PIDate & date, const PIString & format = "d.mm.yyyy"); // obsolete, use PITime.toString() instead +PIString date2string(const PIDate & date, const PIString & format = "d.MM.yyyy"); // obsolete, use PITime.toString() instead +PIString datetime2string(const PIDateTime & datetime, const PIString & format = "h:mm:ss d.MM.yyyy"); // obsolete, use PIDateTime.toString() instead #endif // PITIMER_H diff --git a/pivariable.cpp b/pivariable.cpp old mode 100755 new mode 100644 index 5890ae1d..85393b79 --- a/pivariable.cpp +++ b/pivariable.cpp @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Variable, Struct (simple serialization) - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/pivariable.h b/pivariable.h old mode 100755 new mode 100644 index ba35da6b..6f20de90 --- a/pivariable.h +++ b/pivariable.h @@ -1,7 +1,7 @@ /* PIP - Platform Independent Primitives Variable, Struct (simple serialization) - Copyright (C) 2012 Ivan Pelipenko peri4ko@gmail.com + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/protocols.conf b/protocols.conf new file mode 100644 index 00000000..41fbffb5 --- /dev/null +++ b/protocols.conf @@ -0,0 +1,192 @@ +# Eth + +#gas.receiver.ip = 192.168.5.36 #i +#gas.receiver.ip = 192.168.0.190 #i +gas.receiver.ip = 192.168.6.36 #i +gas.receiver.port = 9217 #n +gas.receiver.frequency = 100 #f +#gas.sender.ip = 192.168.5.2 #i +#gas.sender.ip = 192.168.0.190 #i +gas.sender.ip = 192.168.6.133 #i +#gas.sender.port = 1 #n +gas.sender.port = 10000 #n +gas.sender.frequency = 10 #f +gas.writeHistory = false #b + +# Eth +mcp1_c.receiver.ip = 192.168.5.36 #i +#mcp1.receiver.ip = 127.0.0.1 #i +mcp1_c.receiver.port = 4012 #n +mcp1_c.receiver.frequency = 20 #f +mcp1_c.sender.ip = 192.168.5.13 #i +#mcp1.sender.ip = 127.0.0.1 #i +mcp1_c.sender.port = 4013 #n +mcp1_c.sender.frequency = 20 #f +mcp1_c.writeHistory = false #b + +# Eth +mcp1_m.sender.ip = 192.168.5.13 #i +mcp1_m.sender.port = 4014 #n +mcp1_m.sender.frequency = 20 #f +mcp1_m.writeHistory = false #b + +# Eth +mcp1_i.receiver.ip = 192.168.5.36 #i +mcp1_i.receiver.port = 4016 #n +mcp1_i.sender.ip = 192.168.5.13 #i +mcp1_i.sender.port = 4015 #n +mcp1_i.writeHistory = false #b + +# Eth +mcp2.receiver.ip = 192.168.6.36 #i +mcp2.receiver.port = 4014 #n +mcp2.receiver.frequency = 20 #f +mcp2.sender.ip = 192.168.6.133 #i +mcp2.sender.port = 4015 #n +mcp2.sender.frequency = 20 #f +mcp2.writeHistory = false #b + +# RS +#slk.receiver.ip = 192.168.0.190 #i +slk.receiver.ip = 192.168.6.36 #i +slk.receiver.port = 3108 #n +slk.receiver.frequency = 10 #f +#slk.sender.ip = 192.168.0.175 #i +slk.sender.ip = 192.168.6.133 #i +slk.sender.port = 3109 #n +slk.sender.frequency = 10 #f +slk.disconnectTimeout = 1.5 #f +slk.writeHistory = false #b + +# RS +#ts.receiver.device = /dev/ttyS0 +#ts.receiver.speed = 57600 +#ts.receiver.parity = false +#ts.receiver.twoStopBits = false +ts.receiver.ip = 192.168.0.190 #i +ts.receiver.port = 4023 #n +ts.receiver.frequency = 23 #f +ts.sender.ip = 192.168.0.175 #i +ts.sender.port = 4023 #n +#ts.sender.frequency = 23 +ts_mcp1.receiver.ip = 192.168.0.190 #i +ts_mcp1.receiver.port = 4022 #n +ts_mcp1.sender.ip = 192.168.0.175 #i +ts_mcp1.sender.port = 4022 #n +ts.writeHistory = false #b + +# Eth +mv2.receiver.ip = 192.168.150.1 #i +mv2.receiver.port = 3003 #n +mv2.receiver.frequency = 20 #f +mv2.sender.ip = 192.168.150.16 #i +mv2.sender.port = 3003 #n +mv2.sender.frequency = 20 #f +mv2.writeHistory = false #b +mv2.historyID = 43079 #n + +#RS422 +mv2_res.device = /dev/ttyMI0 #s +mv2_res.speed = 115200 #n +mv2_res.frequency = 10 #f +mv2_res.twoStopBits = false #b +mv2_res.parity = false #b + + +# Eth +ki.mcp1.receiver.ip = 192.168.5.36 #i +ki.mcp1.receiver.port = 4102 #n +ki.mcp1.sender.ip = 192.168.5.13 #i +ki.mcp1.sender.port = 4101 #n +ki.mv2.receiver.ip = 192.168.150.1 #i +ki.mv2.receiver.port = 4104 #n +ki.mv2.sender.ip = 192.168.150.16 #i +ki.mv2.sender.port = 4103 #n + +# Eth +kpi_mcp1.receiver.ip = 192.168.5.36 #i +kpi_mcp1.receiver.port = 4204 #n +kpi_mcp1.sender.ip = 192.168.5.13 #i +kpi_mcp1.sender.port = 4203 #n + +# Eth +kpi_mcp2.receiver.ip = 192.168.6.36 #i +kpi_mcp2.receiver.port = 4206 #n +kpi_mcp2.sender.ip = 192.168.6.133 #i +kpi_mcp2.sender.port = 4205 #n + +# Eth +#rud.receiver.ip = 192.168.5.36 #i +rud.receiver.ip = 192.168.6.36 #i +rud.receiver.port = 4050 #n +rud.receiver.frequency = 20 #f + +# Eth +vpu.ip = 127.0.0.1 #i +vpu.receiver.port = 6001 #n +vpu.sender.port = 6000 #n +vpu.frequency = 20 #f + +# Eth +#kku.receiver.ip = 192.168.6.36 #i +kku.receiver.ip = 192.168.5.36 #i +kku.receiver.port = 5011 #n +kku.receiver.frequency = 20 #f +#kku.sender.ip = 192.168.6.133 #i +kku.sender.ip = 192.168.5.133 #i +kku.sender.port = 5010 #n +kku.sender.frequency = 20 #f + +# ??? +sep.receiver.ip = 192.168.150.1 #i +sep.receiver.port = 4031 #n +sep.receiver.frequency = 4 #f +sep.sender.ip = 192.168.150.16 #i +sep.sender.port = 4030 #n +#sep.sender.frequency = 4 #f + +# Eth +108ua.receiver.ip = 192.168.5.36 #i +108ua.receiver.port = 2002 #n +108ua.receiver.frequency = 10 #f +108ua.sender.ip = 192.168.5.36 #i +108ua.sender.port = 2011 #n +108ua.sender.frequency = 10 #f + +# Eth +108ua_r.receiver.ip = 192.168.6.36 #i +108ua_r.receiver.port = 2002 #n +108ua_r.receiver.frequency = 10 #f +108ua_r.sender.ip = 192.168.6.2 #i +108ua_r.sender.port = 2011 #n +108ua_r.sender.frequency = 10 #f + +# Eth +vpu_driver.ip = 127.0.0.1 #i +vpu_driver.receiver.port = 6000 #n +vpu_driver.sender.port = 6001 #n +vpu_driver.frequency = 20 #f + +# Eth +tvk.mv2.receiver.ip = 192.168.150.1 #i +tvk.mv2.receiver.port = 4024 #n +tvk.mv2.sender.ip = 192.168.150.16 #i +tvk.mv2.sender.port = 4024 #n +tvk.mcp2.receiver.ip = 192.168.6.36 #i +tvk.mcp2.receiver.port = 4024 #n +tvk.mcp2.sender.ip = 192.168.6.133 #i +tvk.mcp2.sender.port = 4024 #n + +#Eth +bpd.receiver.ip = 127.0.0.1 #i +bpd.receiver.port = 2012 #n +bpd.sender.ip = 127.0.0.1 #i +bpd.sender.port = 2013 #n + +# Eth +astd.receiver.ip = 192.168.5.36 #i +astd.receiver.port = 5298 #n +astd.sender.ip = 192.168.5.2 #i +astd.sender.port = 1101 #n +astd.frequency = 1 #f +astd.writeHistory = false #b diff --git a/system_test/CMakeLists.txt b/system_test/CMakeLists.txt new file mode 100644 index 00000000..8dd7e8a7 --- /dev/null +++ b/system_test/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.6) +include_directories(${CMAKE_CURRENT_SOURCE_DIR} . ../) +file(GLOB CPPS "*.cpp") +add_definitions(-Wall -O2) +add_executable(pip_sys_test "main.cpp") +target_link_libraries(pip_sys_test pip) diff --git a/system_test/main.cpp b/system_test/main.cpp new file mode 100644 index 00000000..91e87ca3 --- /dev/null +++ b/system_test/main.cpp @@ -0,0 +1,106 @@ +/* + PIP - Platform Independent Primitives + System tests program + Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "pip.h" +#include "pisystemtests.h" + + +int main(int argc, char * argv[]) { +#ifdef WINDOWS + cout << "This program is useless for Windows" << endl; + return 0; +#else + if (getuid() != 0) { + cout << "You should run this program as root!" << endl; + return 0; + } + PIConfig conf( +#ifndef WINDOWS + "/etc/pip.conf" +#else + "pip.conf" +#endif + ); + PITimer timer, tm; + timespec ts; + long stc = 0; + double st; + llong sts = 0; + clock_getres(CLOCK_REALTIME, &ts); + stc = long(ts.tv_sec) * 1000000000l + long(ts.tv_nsec); + conf.setValue("time_resolution_ns", stc); + cout << "Timer resolution is " << stc << " ns" << endl; + + cout << "\"PITimer.elapsed_*\" test ... " << flush; + stc = 0; + ts.tv_sec = 0; + ts.tv_nsec = 1000; + PIVector times; + times.resize(8192); + tm.reset(); + PISystemTests::time_elapsed_ns = 0; + while (tm.elapsed_s() < 3.) { + for (int i = 0; i < times.size_s(); ++i) { + timer.reset(); + times[i] = timer.elapsed_m(); + times[i] = timer.elapsed_s(); + times[i] = timer.elapsed_u(); + } + st = 0; + for (int i = 0; i < times.size_s(); ++i) + st += times[i]; + //cout << times[0] << endl; + //cout << st / times.size_s() / 3. * 1000. << endl; + sts += piRoundd(st / times.size_s() / 3. * 1000.); + //cout << sts << endl; + stc++; + } + sts /= stc; + conf.setValue("time_elapsed_ns", long(sts)); + cout << "ok, cost " << sts << " ns, average in " << stc << " series (" << (stc * 3 * times.size_s()) << " executes)" << endl; + + cout << "\"usleep\" offset test ... " << flush; + PISystemTests::time_elapsed_ns = sts; + tm.reset(); + stc = 0; + sts = 0; + times.resize(128); + while (tm.elapsed_s() < 3.) { + for (int i = 0; i < times.size_s(); ++i) { + timer.reset(); + usleep(1000); + times[i] = timer.elapsed_u(); + } + st = 0; + for (int i = 0; i < times.size_s(); ++i) + st += times[i] - 1000; + //cout << times[0] << endl; + //cout << st / times.size_s() / 3. * 1000. << endl; + sts += piRoundd(st / times.size_s()); + //cout << sts << endl; + stc++; + } + sts /= stc; + conf.setValue("usleep_offset_us", long(sts)); + cout << "ok, " << sts << " us, average in " << stc << " series (" << (stc * times.size_s()) << " executes)" << endl; + + //WAIT_FOR_EXIT + return 0; +#endif +}; diff --git a/Описание.odt b/Описание.odt old mode 100755 new mode 100644 index 3ccc99e691dd8441232580fff50198747635d00f..0b887c4a910494fdcc3f664caba2ebf1d7d8cf58 GIT binary patch delta 94944 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[(TY_6 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ə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 ujM)uDp#2XJ{)uh;f7S(i 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 LbA^rY@qq9@&H~?| diff --git a/Описание.pdf b/Описание.pdf old mode 100755 new mode 100644