diff --git a/README.md b/README.md index ef18f4f..aa609d7 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,83 @@ # Cellulo in Unity 3D Demo https://youtu.be/XnN9yhErv5g ## Installation (common) 1. Need Qt and access to `cellulo-qml-plugin` on c4science through ssh 2. `git submodule init; git submodule update`. Build and install `cellulo-qml-plugin` for your architecture 3. Using qt, build the project `cellulo-unity.pro` for your architecture 4. Add the resulting file `libcellulo-unity.so` as a Unity asset to your project 5. Use the C# class `EscapeTheGhost/Assets/Cellulo.cs` in your project to wrap the C calls to Cellulo ## Usage 1. Need Robot pool daemon running. In C#: 2. After connection to the pool with `Cellulo.initialize()`, test if have enough robots with `Cellulo.robotsRemaining()`. 3. If the quantity is enough for you, create Cellulo objects with `new Cellulo()` 4. If the constructor did not show errors, the robot is ready to use! ## Additional steps for Android First of all, the library `libcelluloplugin.so` must be bundled with the app. On Android, apart from building the library `libcellulo-unity.so` and including it as an asset to Unity, also need to supply the following libraries: ``` libgnustl_shared.so libqandroidbearer.so libQt5AndroidExtras.so libQt5Bluetooth.so libQt5Concurrent.so libQt5Core.so libQt5Gui.so libQt5Multimedia.so libQt5Network.so libQt5Qml.so libQt5Quick.so libqtforandroid.so ``` These are taken from Qt and Android NDK. Check `logcat` on your device and use `/path_to/android_ndk/find_it/arm-linux-androideabi-readelf -d yourfile.so` to get dependencies of libs, if new versions are different. Also the following jar files are required (copied from Qt installation): ``` QtAndroidBearer.jar QtAndroidBluetooth.jar QtAndroidExtras.jar QtAndroidGamepad.jar QtAndroid.jar QtAndroidWebView.jar QtMultimedia.jar QtNfc.jar QtPositioning.jar QtSensors.jar QtTextToSpeech.jar ``` Moreover, Qt libraries need to be loaded from Java using `System.load`, which calls C function `JNI_OnLoad`. Unity does that to `libcellulo-unity.so` because it's referenced in `Cellulo.cs` file. However, it does not do that for Qt libs because they are simply dependencies of `libcellulo-unity` and are loaded using regular `dlopen`. Qt libs crash if that method has not been called because they need the pointer to Java VM. To fix the issue, methods are called manually in `CelluloUnity_Cellulo.cpp`. To obtain list of libs which require manual call, run `/path_to/android_ndk/find_it/arm-linux-androideabi-gcc-nm -D your_so_file|grep OnLoad`. If it is nonempty, it needs to be called. The exception is `libqtforandroid.so` which also enables Bluetooth. Calling it results in a crash and it is not clear at this moment how to enable it. Therefore, the Robot Pool is used which does not require Bluetooth. +## C++ example + +Example of usage in C++ is at `cpp-test` + ## Example MsPacman-like example The project EscapeTheGhost is a simple demo with two Cellulos where one of them is a ghost and is dangerous when is red. The other Cellulo is the player and it must be moved to the goal while avoiding the ghost and other obstacles. When the robot hits the obstacle, it vibrates and returns to start of the level (level failed). 2D GUI interface shows some info. diff --git a/cellulounity.cpp b/cellulounity.cpp index 7c33645..7f273a1 100644 --- a/cellulounity.cpp +++ b/cellulounity.cpp @@ -1,170 +1,194 @@ #include "cellulounity.h" #include "cellulothread.h" #include "cellulorobotwrapper.h" #include "cellulopoolclient.h" #include // thread with an event loop CelluloThread* thread = nullptr; // pool client wrapper CelluloPoolClientWrapper* client = nullptr; // number of used by Unity robots int used_robots; // TODO: use a less ugly way of creating thread (call at library loading) void initialize() { if(thread == nullptr) { qDebug() << "creating thread..."; thread = new CelluloThread; qDebug() << "starting thread..."; thread->start(); } + // initializing the library again... + if(client != nullptr) { + qDebug() << "Client deinit..."; + client->deinit(); + qDebug() << "Deleting client..."; + client->deleteLater(); + qDebug() << "Setting ptr to nullptr"; + client = nullptr; + } + if(client == nullptr) { qDebug() << "creating pool..."; client = new CelluloPoolClientWrapper; + qDebug() << "moving pool to thread..."; client->moveToThread(thread); + qDebug() << "initializing pool..."; client->init(); + qDebug() << "Pool OK"; // number of used by Unity robots used_robots = 0; } } int64_t newRobot() { + if(client == nullptr) { + qDebug() << "Cannot create a new robot because initialize() was not called"; + return 0; // an error message will be shown in C# code + } + if(used_robots >= client->robots_N) { qDebug() << "Cannot create a new robot because the pool is exhausted. Connect to more and restart the app."; return 0; // an error message will be shown in C# code } // obtaining a fee robot from pool - Cellulo::CelluloBluetooth* robot1 = client->getRobots().at(used_robots++); // wrapping it qDebug() << "Wrapping robot" << robot1; CelluloRobotWrapper* robot = new CelluloRobotWrapper(robot1); + qDebug() << "moving robot to thread..."; robot->moveToThread(thread); + qDebug() << "initializing robot..."; robot->init(); qDebug() << "Got wrapped robot" << robot; // TODO: use something less ugly than that return (int64_t) robot; } // TODO: check if *robot is actually a robot object void setGoalVelocity(int64_t robot, float vx, float vy, float w) { ((CelluloRobotWrapper*) robot)->setGoalVelocity(vx, vy, w); } void setGoalPose(int64_t robot, float x, float y, float theta, float v, float w) { ((CelluloRobotWrapper*) robot)->setGoalPose(x, y, theta, v, w); } void setGoalPosition(int64_t robot, float x, float y, float v) { ((CelluloRobotWrapper*) robot)->setGoalPosition(x, y, v); } void clearTracking(int64_t robot) { ((CelluloRobotWrapper*) robot)->clearTracking(); } void clearHapticFeedback(int64_t robot) { ((CelluloRobotWrapper*) robot)->clearHapticFeedback(); } void setVisualEffect(int64_t robot, int64_t effect, int64_t r, int64_t g, int64_t b, int64_t value) { ((CelluloRobotWrapper*) robot)->setVisualEffect(effect, QColor(r, g, b), value); } void setCasualBackdriveAssistEnabled(int64_t robot, int64_t enabled) { ((CelluloRobotWrapper*) robot)->setCasualBackdriveAssistEnabled(enabled); } void setHapticBackdriveAssist(int64_t robot, float xAssist, float yAssist, float thetaAssist) { ((CelluloRobotWrapper*) robot)->setHapticBackdriveAssist(xAssist, yAssist, thetaAssist); } void reset(int64_t robot) { ((CelluloRobotWrapper*) robot)->reset(); } void simpleVibrate(int64_t robot, float iX, float iY, float iTheta, int64_t period, int64_t duration) { ((CelluloRobotWrapper*) robot)->simpleVibrate(iX, iY, iTheta, period, duration); } float getX(int64_t robot) { return ((CelluloRobotWrapper*) robot)->robot->getX(); } float getY(int64_t robot) { return ((CelluloRobotWrapper*) robot)->robot->getY(); } float getTheta(int64_t robot) { return ((CelluloRobotWrapper*) robot)->robot->getTheta(); } int64_t getKidnapped(int64_t robot) { Cellulo::CelluloBluetooth* robot1 = (Cellulo::CelluloBluetooth*) ((CelluloRobotWrapper*) robot)->robot; return robot1->getKidnapped(); } void destroyRobot(int64_t robot) { + qDebug() << "Removing callback..."; ((CelluloRobotWrapper*) robot)->kidnappedCallback = nullptr; - ((CelluloRobotWrapper*) robot)->deinit(); - used_robots--; + qDebug() << "Calling deinit..."; - // shutting down pool if no robots are in use - if(used_robots == 0) { - client->deinit(); - delete client; - client = nullptr; - } + ((CelluloRobotWrapper*) robot)->deinit(); } void setKidnappedCallback(int64_t robot, callback_t callback) { ((CelluloRobotWrapper*) robot)->kidnappedCallback = callback; } int64_t robotsRemaining() { + if(client == nullptr) { + qDebug() << "Cannot get robotsRemaining() because initialize() was not called"; + return 0; + } + if(client == nullptr) return 0; return client->robots_N - used_robots; } int64_t totalRobots() { + if(client == nullptr) { + qDebug() << "Cannot get totalRobots() because initialize() was not called"; + return 0; + } + if(client == nullptr) return 0; return client->robots_N; } diff --git a/cpp-test/cpp-test.pro b/cpp-test/cpp-test.pro index 760690e..8dda199 100644 --- a/cpp-test/cpp-test.pro +++ b/cpp-test/cpp-test.pro @@ -1,14 +1,14 @@ CONFIG += c++11 console CONFIG -= app_bundle SOURCES += \ main.cpp -LIBS += -L$$PWD/../build_x86 -lcellulo-unity +LIBS += -L$$PWD/../build_x86 -lcellulo-unity -lcelluloplugin LIBS += -L$$[QT_INSTALL_QML]/Cellulo # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target diff --git a/cpp-test/main.cpp b/cpp-test/main.cpp index da9546a..a56792d 100644 --- a/cpp-test/main.cpp +++ b/cpp-test/main.cpp @@ -1,48 +1,61 @@ #include #include "../cellulounity.h" #include #include #include void onKidnappedChanged() { qDebug() << "callback: kidnapped changed"; } void* testCellulo(void* arg) { - // waiting until connected - initialize(); - while(true) { - if(robotsRemaining() <= 1) { - qDebug() << "robots remaining: " << robotsRemaining(); - sleep(1); - } - else break; - } - - long robot = newRobot(); - fprintf(stderr, "Got robot %x\n", robot); - setKidnappedCallback(robot, &onKidnappedChanged); + // LED colors + int r, g, b; r = g = b = 0; - int r, g, b; - g = 0; - b = 0; - r = 0; + // creating and destroying robots while(true) { + // starting the library + initialize(); + + // waiting for connection + while(true) { + if(robotsRemaining() <= 1) { + qDebug() << "robots remaining: " << robotsRemaining(); + sleep(1); + } + else break; + } + + // creating robot + long robot = newRobot(); + fprintf(stderr, "Got robot %x\n", robot); + + // will react on kidnapping + setKidnappedCallback(robot, &onKidnappedChanged); + + // printing data qDebug() << getX(robot) << getY(robot) << getTheta(robot) << (getKidnapped(robot) ? "true" : "false"); + + // updating LED color setVisualEffect(robot, 0, r, g, b, 255); r += 50; r %= 255; - usleep(100000); + + // waiting and reinitizlizing library + // will connect to the same robot on next iteration if there are no other users of the library + sleep(1); + destroyRobot(robot); + initialize(); } return NULL; } int main() { pthread_t thread; pthread_create(&thread, NULL, testCellulo, NULL); pthread_join(thread, NULL); return 0; } diff --git a/test/.gitignore b/test/.gitignore deleted file mode 100644 index 3fda488..0000000 --- a/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -a.out -libcellulo-unity.so.1 diff --git a/test/README.txt b/test/README.txt deleted file mode 100644 index 8d15e19..0000000 --- a/test/README.txt +++ /dev/null @@ -1 +0,0 @@ -cp /path/to/libcellulo-unity.so.1.0.0 libcellulo-unity.so.1 ; g++ -lcellulo-unity -L. main.cpp; ./a.out diff --git a/test/cellulounity.h b/test/cellulounity.h deleted file mode 120000 index 17b1a86..0000000 --- a/test/cellulounity.h +++ /dev/null @@ -1 +0,0 @@ -../cellulounity.h \ No newline at end of file diff --git a/test/libcellulo-unity.so b/test/libcellulo-unity.so deleted file mode 120000 index 9553b66..0000000 --- a/test/libcellulo-unity.so +++ /dev/null @@ -1 +0,0 @@ -libcellulo-unity.so.1 \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp deleted file mode 100644 index c998fc1..0000000 --- a/test/main.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include "cellulounity.h" -#include - -void onKidnappedChanged() { - fprintf(stderr, "callback: kidnapped changed\n"); -} - -int main() { - long robot = newRobot(); - fprintf(stderr, "Got robot %d\n", robot); - setLocalAdapterMacAddr(robot, "00:1A:7D:DA:71:03"); - setMacAddr(robot, "00:06:66:74:41:1E"); - connectToServer(robot); - setKidnappedCallback(robot, &onKidnappedChanged); - - sleep(1); - - int r, g, b; - g = 0; - b = 0; - r = 0; - while(true) - { - fprintf(stderr, "x=%f y=%f theta=%f kidnapped=%s\n", getX(robot), getY(robot), getTheta(robot), getKidnapped(robot) ? "true" : "false"); - setVisualEffect(robot, 0, r, g, b, 255); - r += 50; - r %= 255; - usleep(1000000); - } -}