diff --git a/.github/workflows/ci-platformio.yml b/.github/workflows/ci-platformio.yml index 3b2e55c..d219833 100644 --- a/.github/workflows/ci-platformio.yml +++ b/.github/workflows/ci-platformio.yml @@ -7,21 +7,21 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Cache pip - uses: actions/cache@v2 + uses: actions/cache@v6 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - name: Cache PlatformIO - uses: actions/cache@v2 + uses: actions/cache@v6 with: path: ~/.platformio key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v6 - name: Install PlatformIO run: | python -m pip install --upgrade pip diff --git a/download_scservo.sh b/download_scservo.sh deleted file mode 100755 index 0e3421d..0000000 --- a/download_scservo.sh +++ /dev/null @@ -1,8 +0,0 @@ -TARGET="https://akizukidenshi.com/download/ds/feetech/SCServo.zip" -echo "start downloading SCServo.zip from ${TARGET}" -curl -OL ${TARGET} -unzip SCServo.zip -mv SCServo/*.h include/ -mv SCServo/*cpp src/ -rm SCServo.zip -rm -rf SCServo \ No newline at end of file diff --git a/include/BLEStackchanService.hpp b/include/BLEStackchanService.hpp index 17cbe56..fb17d3f 100644 --- a/include/BLEStackchanService.hpp +++ b/include/BLEStackchanService.hpp @@ -4,12 +4,17 @@ #define BLE_STACKCHAN_SERVICE_HPP #include -#include #include "Animation.hpp" #include "BLEFormat.hpp" #include "BLEUnit.hpp" +using stackchan::display::ColorPalette; +using stackchan::display::Display; +using stackchan::display::Expression; +using stackchan::display::ExpressionWeight; +using stackchan::display::Face; + namespace color { u_int16_t to16bitscolor(u_int32_t color) { // https://docs.arduino.cc/library-examples/tft-library/TFTColorPicker @@ -95,22 +100,19 @@ class StackchanService : public BLEService { // ~StackchanService(); void setInitialValues(); - void servoPoll(botamochi::AnimationController &controller); + void servoPoll(botamochi::AnimationController& controller); - void animationPoll(botamochi::AnimationController &controller); + void animationPoll(botamochi::AnimationController& controller); - void facePoll(m5avatar::Avatar &avatar, m5avatar::Face *faces[], - uint8_t face_size); + void facePoll(Display& display, Face* faces[], uint8_t face_size); - void facialExpressionPoll(m5avatar::Avatar &avatar, - const m5avatar::Expression expressions[], + void facialExpressionPoll(Display& display, const Expression expressions[], uint8_t expression_size); - void facialColorPoll(m5avatar::Avatar &avatar, - m5avatar::ColorPalette *palettes[], + void facialColorPoll(Display& display, ColorPalette* palettes[], uint8_t palette_size); - void mouseOpenPoll(m5avatar::Avatar &avatar); + void mouseOpenPoll(Display& display); }; StackchanService::StackchanService() @@ -210,7 +212,7 @@ void StackchanService::setInitialValues() { // this->background_color_chr.writeValueLE(0x000000); }; -void StackchanService::servoPoll(botamochi::AnimationController &controller) { +void StackchanService::servoPoll(botamochi::AnimationController& controller) { unsigned short pos1 = controller.servo_driver.getCurrentPosition( controller.joint_servo_map.get(botamochi::JointName::kHeadPan)); this->servo_pan_angle_chr.writeValue(pos1); @@ -220,67 +222,65 @@ void StackchanService::servoPoll(botamochi::AnimationController &controller) { } void StackchanService::animationPoll( - botamochi::AnimationController &controller) { + botamochi::AnimationController& controller) { if (this->animation_clip_chr.written()) { auto idx = this->animation_clip_chr.value(); controller.play(idx); } } -void StackchanService::facePoll(m5avatar::Avatar &avatar, - m5avatar::Face *faces[], uint8_t face_size) { +void StackchanService::facePoll(Display& display, Face* faces[], + uint8_t face_size) { if (this->face_chr.written()) { auto idx = this->face_chr.value(); if (face_size <= idx) { return; } - avatar.setFace(faces[idx]); + display.setFace(faces[idx]); } } -void StackchanService::facialExpressionPoll( - m5avatar::Avatar &avatar, const m5avatar::Expression expressions[], - uint8_t expression_size) { - if (this->facial_expression_chr.written()) { - auto idx = this->facial_expression_chr.value(); - if (expression_size <= idx) { - return; - } +void StackchanService::facialExpressionPoll(Display& display, + const Expression expressions[], + uint8_t expression_size) { + if (!this->facial_expression_chr.written()) { + return; + } - avatar.setExpression(expressions[idx]); + auto idx = this->facial_expression_chr.value(); + if (expression_size <= idx) { + return; } + + display.getExpressionWeight().setEmotionalExpression( + static_cast( + idx % (static_cast(stackchan::display::Expression::kRelax) + 1)), + 255); } -void StackchanService::facialColorPoll(m5avatar::Avatar &avatar, - m5avatar::ColorPalette *palettes[], +void StackchanService::facialColorPoll(Display& display, + ColorPalette* palettes[], uint8_t palette_size) { - if (this->facial_color_chr.written()) { - auto idx = this->facial_color_chr.value(); - if (palette_size <= idx) { - return; // out of index - } - avatar.setColorPalette(*palettes[idx]); + if (!this->facial_color_chr.written()) { + return; } - // if (this->primary_color_chr.written() || - // this->background_color_chr.written()) { - // auto idx = this->facial_color_chr.value(); - // // convert 24 bit color to 16bit color - // palettes[idx]->set(COLOR_PRIMARY, - // color::to16bitscolor(this->primary_color_chr.valueLE())); - // palettes[idx]->set( - // COLOR_BACKGROUND, - // color::to16bitscolor(this->background_color_chr.valueLE())); - // avatar.setColorPalette(*palettes[idx]); - // } + // processes on characteristic-written + auto idx = this->facial_color_chr.value(); + if (palette_size <= idx) { + return; // out of index + } + display.setColorPalette(palettes[idx]); } -void StackchanService::mouseOpenPoll(m5avatar::Avatar &avatar) { - if (this->mouse_open_ratio_chr.written()) { - avatar.setMouthOpenRatio( - static_cast(this->mouse_open_ratio_chr.value()) / 255U); +void StackchanService::mouseOpenPoll(Display& display) { + if (!this->mouse_open_ratio_chr.written()) { + return; } + + display.getExpressionWeight().set(Expression::kAa, + this->mouse_open_ratio_chr.value()); } } // namespace ble diff --git a/include/PaletteExamples.hpp b/include/PaletteExamples.hpp index f6b9843..f80f8e5 100644 --- a/include/PaletteExamples.hpp +++ b/include/PaletteExamples.hpp @@ -2,7 +2,7 @@ #define PALETTE_EXAMPLES_HPP #include -namespace m5avatar { +namespace stackchan::display { void assignUiPalette(ColorPalette* palette) { palette->set(DrawingLocation::kIris1, @@ -20,7 +20,7 @@ void assignUiPalette(ColorPalette* palette) { M5.Lcd.color24to16(0xfac2a8)); // skin palette->set(DrawingLocation::kEyeBrow, M5.Lcd.color24to16(0x321F39)); palette->set(DrawingLocation::kMouthBackground, TFT_BLACK); - palette->set(DrawingLocation::kInnerMouse, TFT_RED); + palette->set(DrawingLocation::kInnerMouth, TFT_RED); palette->set(DrawingLocation::kCheek1, TFT_PINK); // cheek } @@ -33,7 +33,7 @@ void assignFbkPalette(ColorPalette* palette) { palette->set(DrawingLocation::kCheek1, M5.Lcd.color24to16(0xea9c60)); // cheek, #ea9c60 palette->set(DrawingLocation::kMouthBackground, TFT_BLACK); - palette->set(DrawingLocation::kInnerMouse, TFT_RED); + palette->set(DrawingLocation::kInnerMouth, TFT_RED); } void assignLeonaPalette(ColorPalette* palette) { @@ -53,7 +53,7 @@ void assignLeonaPalette(ColorPalette* palette) { palette->set(DrawingLocation::kEyeBrow, M5.Lcd.color24to16(0x63434F)); // #63434F palette->set(DrawingLocation::kMouthBackground, M5.Lcd.color24to16(0x63434F)); - palette->set(DrawingLocation::kInnerMouse, + palette->set(DrawingLocation::kInnerMouth, M5.Lcd.color24to16(0x8B0035)); // #8B0035 palette->set(DrawingLocation::kCheek1, M5.Lcd.color24to16(0xF3B5CC)); // cheek #F3B5CC @@ -76,7 +76,7 @@ void assignNaorePalette(ColorPalette* palette) { palette->set(DrawingLocation::kEyeBrow, M5.Lcd.color24to16(0x63434F)); // #63434F palette->set(DrawingLocation::kMouthBackground, M5.Lcd.color24to16(0x63434F)); - palette->set(DrawingLocation::kInnerMouse, + palette->set(DrawingLocation::kInnerMouth, M5.Lcd.color24to16(0x8B0035)); // #8B0035 palette->set(DrawingLocation::kCheek1, M5.Lcd.color24to16(0xd77398)); // cheek #d77398 @@ -95,7 +95,7 @@ void assignPinkDemonPalette(ColorPalette* palette) { palette->set(DrawingLocation::kCheek1, M5.Lcd.color24to16(0xEB6896)); // cheek, #EB6896 palette->set(DrawingLocation::kMouthBackground, TFT_BLACK); - palette->set(DrawingLocation::kInnerMouse, TFT_RED); + palette->set(DrawingLocation::kInnerMouth, TFT_RED); } -} // namespace m5avatar +} // namespace stackchan::display #endif // PALETTE_EXAMPLES_HPP \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 2435285..7d5bca9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,9 +14,9 @@ default_envs = m5stack-core1 monitor_speed = 115200 lib_deps_external = arduino-libraries/ArduinoBLE@^1.3.5 - m5stack/M5Unified@^0.1.11 + m5stack/M5Unified@^0.2.14 hideakitai/TaskManager@^0.5.2 - https://github.com/botamochi6277/m5stack-avatar.git#3764520 + https://github.com/botamochi6277/stackchan-display.git#0.3.0 https://github.com/botamochi6277/STS_servos.git#esp32s3 [env:m5stack-core1] diff --git a/policy.md b/policy.md new file mode 100644 index 0000000..2c6e12b --- /dev/null +++ b/policy.md @@ -0,0 +1,95 @@ +# StackChan policy + +Draft for policy models + +```mermaid +--- +config: + theme: 'redux-dark' +title: 'StackChan Policy' +--- +graph LR + +%% sensor inputs + +subgraph sensors + camera[fas:fa-camera camera] + touch[head touch sensor] + head_rot[head rotation sensor] + mic[microphone] + bat[battery sensor] +end + +subgraph observations + cam_image([image]) + tactile([tactile]) + joint_pos([pan tilt]) + sound([sound]) + bat_lv([battery voltages]) +end +camera-->cam_image +touch-->tactile +head_rot-->joint_pos +mic-->sound +bat-->bat_lv +%% comm inputs + + +subgraph interceptions + esp_now[esp now] + ble[bluetooth] + usb[usb] + wifi[WiFi] +end +%% extra + +llm[large language model]-.->wifi +interceptions-.->observations + +%% outputs + + +subgraph display[display control] + face([face]) + speech([speech bubble]) + decorators([decorators]) +end + +display-->display_lib["display lib
(M5Unified)"] + +%%mot-->servo_lib[servo lib] + +%% head neopixels on M5StackChan Head +%%head_led[head LED control] +%%head_led-->led_lib[LED lib] + + + + + +subgraph actions + led_colors([LED colors]) + head_act_rot([head rotation]) + facial_expression([facial expression]) + voice([voice]) +end + +subgraph executors + leds + servos + speaker + display +end + +led_colors-->leds +head_act_rot-->servos +voice-->speaker +facial_expression-->display + +policy((policy)) +observations-->policy-->actions + +``` + +How to train the policy? +To use imitation learning, reference motions are required. Someone should be stackchan during a few days diff --git a/src/main.cpp b/src/main.cpp index 07c8d54..3d586db 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,16 @@ #include -#include #include #include #include -#include +// stackchan display +#include +#include +#include +#include + +// #include "Animation.hpp" #include "AnimationClipExamples.hpp" @@ -26,13 +31,18 @@ #define RXD 7 #define TXD 6 #elif defined(ARDUINO_M5STACK_CORES3) -#define RXD 1 -#define TXD 2 +// #define RXD 1 +// #define TXD 2 +// TODO: Add Stackchan Body + +#define RXD 7 +#define TXD 6 + #endif using botamochi::AnimationName; -m5avatar::Avatar avatar; +Display display; char balloon_text[20]; ble::StackchanService stackchan_srv; @@ -44,69 +54,115 @@ unsigned short anim_clip_id = 0; float time_sec = 0.0f; unsigned long milli_sec = 0U; +unsigned long m5_count = 0; +unsigned long main_count = 0; -const m5avatar::Expression expressions[] = { - m5avatar::Expression::kNeutral, m5avatar::Expression::kHappy, - m5avatar::Expression::kAngry, m5avatar::Expression::kSad, - m5avatar::Expression::kSleepy, m5avatar::Expression::kDoubt}; +const Expression expressions[] = {Expression::kNeutral, Expression::kSmile, + Expression::kAngry, Expression::kSad, + Expression::kSleepy, Expression::kDoubt}; const uint8_t expressions_size = 6; -m5avatar::Expression current_expression = expressions[0]; +Expression current_expression = expressions[0]; uint8_t expression_idx = 0; -m5avatar::ColorPalette* color_palettes[5]; +ColorPalette* color_palettes[5]; const uint8_t color_palettes_size = 5U; uint8_t color_palettes_idx = 0; -m5avatar::Face* faces[5]; -const int faces_length = sizeof(faces) / sizeof(m5avatar::Face*); -int face_idx = 0; +Face* faces[4]; +const uint8_t faces_length = sizeof(faces) / sizeof(Face*); +uint8_t face_idx = 0; + +void assignMiaPalette(ColorPalette* palette) { + using stackchan::display::DrawingLocation; + palette->set(DrawingLocation::kIris1, + M5.Lcd.color24to16(0x4A99D9)); // iris 1, #4A99D9 + palette->set(DrawingLocation::kEyelash, M5.Lcd.color24to16(0x953346)); + palette->set(DrawingLocation::kEyelid, + M5.Lcd.color24to16(0x63436F)); // #63436F + palette->set(DrawingLocation::kIrisBackground, TFT_BLACK); + palette->set(DrawingLocation::kIris2, + M5.Lcd.color24to16(0xBFEDE8)); // iris2,#BFEDE8 + palette->set(DrawingLocation::kPupil, + M5.Lcd.color24to16(0x2D493A)); // #2D493A + palette->set(DrawingLocation::kEyeHighlight, TFT_WHITE); + palette->set(DrawingLocation::kSkin, + M5.Lcd.color24to16(0xfac2a8)); // skin + palette->set(DrawingLocation::kEyeBrow, + M5.Lcd.color24to16(0x63434F)); // #63434F + palette->set(DrawingLocation::kMouthBackground, M5.Lcd.color24to16(0x63434F)); + palette->set(DrawingLocation::kInnerMouth, + M5.Lcd.color24to16(0x8B0035)); // #8B0035 + palette->set(DrawingLocation::kCheek1, + M5.Lcd.color24to16(0xd77398)); // cheek #d77398 +} void setup() { auto cfg = M5.config(); // default config? -#ifdef ARDUINO_M5STACK_CORES3 - cfg.output_power = false; -#endif + // please uncomment if you supply power from GROVE connector to the CORES3 + // #ifdef ARDUINO_M5STACK_CORES3 + // cfg.output_power = false; + // #endif + M5.begin(cfg); - Serial.begin(115200); - Serial2.begin(1000000, SERIAL_8N1, RXD, TXD); + M5.Log.setLogLevel(m5::log_target_serial, ESP_LOG_VERBOSE); + // Serial.begin(115200); + // Serial2.begin(1000000, SERIAL_8N1, RXD, TXD); // for servo driver delay(1000); // waiting for connection - M5.Lcd.setBrightness(100); + M5.Lcd.setBrightness(150); M5.Lcd.clear(); + M5.Display.drawString("Smart", 10, 10, 4); + delay(200); + M5.Display.drawString("Tech", 10, 50, 4); + delay(200); + M5.Display.drawString("Animating", 10, 90, 4); + delay(200); + M5.Display.drawString("Creative", 10, 130, 4); + delay(200); + M5.Display.drawString("Kit", 10, 170, 4); + delay(200); + M5.Display.drawString("CHANt", 10, 210, 4); + + delay(10000); + // default - faces[0] = avatar.getFace(); - color_palettes[0] = new m5avatar::ColorPalette(); + faces[0] = display.getFace(); + color_palettes[0] = new ColorPalette(); // leona - faces[1] = new m5avatar::ToonFace1(); - color_palettes[1] = new m5avatar::ColorPalette(); - m5avatar::assignLeonaPalette(color_palettes[1]); + faces[1] = new stackchan::display::ToonFace1(); + color_palettes[1] = new ColorPalette(); + assignLeonaPalette(color_palettes[1]); // ui - faces[2] = new m5avatar::ToonFace1(); - color_palettes[2] = new m5avatar::ColorPalette(); - m5avatar::assignNaorePalette(color_palettes[2]); + faces[2] = new stackchan::display::ToonFace1(); + color_palettes[2] = new ColorPalette(); + assignUiPalette(color_palettes[2]); // fbk - faces[3] = new m5avatar::OmegaFace(); - color_palettes[3] = new m5avatar::ColorPalette(); - m5avatar::assignFbkPalette(color_palettes[3]); + faces[3] = new stackchan::display::OmegaFace(); + color_palettes[3] = new ColorPalette(); + assignFbkPalette(color_palettes[3]); // pink demon - faces[4] = new m5avatar::PinkDemonFace(); - color_palettes[4] = new m5avatar::ColorPalette(); - m5avatar::assignPinkDemonPalette(color_palettes[4]); + // faces[4] = new PinkDemonFace(); + // color_palettes[4] = new ColorPalette(); + // assignPinkDemonPalette(color_palettes[4]); - avatar.init(8); // start drawing w/ 8bit color mode - avatar.setColorPalette(*color_palettes[0]); + display.getCanvas().setColorDepth(8); // start drawing w/ 8bit color mode + display.setColorPalette(color_palettes[0]); + display.setFace(faces[0]); + // display.getFace()->autoScale(); if (!BLE.begin()) { // "starting BLE failed!" - avatar.setSpeechText("BLE is unavailable"); + display.getSpeechBalloon().setText("BLE is unavailable"); + display.update(); + M5_LOGW("BLE is unavailable"); } else { M5_LOGD("BLE is available"); } if (!Serial2) { - avatar.setSpeechText("Serial2 is not connected"); + display.getSpeechBalloon().setText("Serial2 is not connected"); } else { M5_LOGD("Serial2 is connected"); } @@ -124,19 +180,20 @@ void setup() { // start advertising BLE.advertise(); - // ## Servo setting + // // ## Servo setting auto is_connected = anim_controller.servo_driver.init(&Serial2); if (!is_connected) { - avatar.setSpeechText("servo is not connected"); + display.getSpeechBalloon().setText("servo is not connected"); } else { - avatar.setSpeechText("servo is connected"); + display.getSpeechBalloon().setText("servo is connected"); } + display.update(); anim_controller.joint_servo_map.set(botamochi::JointName::kHeadPan, servo_pan_id); anim_controller.joint_servo_map.set(botamochi::JointName::kHeadTilt, servo_tilt_id); - avatar.setSpeechText("Scaning servos..."); + display.getSpeechBalloon().setText("Scaning servos..."); for (size_t i = 1; i < 3; i++) { bool b = anim_controller.servo_driver.ping(i); anim_controller.servo_driver.getCurrentPosition( @@ -144,17 +201,20 @@ void setup() { } // initial move - avatar.setSpeechText("moving to 400"); + display.getSpeechBalloon().setText("moving to 400"); anim_controller.servo_driver.setTargetPosition(servo_pan_id, 400); anim_controller.servo_driver.setTargetPosition(servo_tilt_id, 400); + display.update(); delay(3000); // wait for servo to move - avatar.setSpeechText("moving to 601"); + display.getSpeechBalloon().setText("moving to 601"); anim_controller.servo_driver.setTargetPosition(servo_pan_id, 601); anim_controller.servo_driver.setTargetPosition(servo_tilt_id, 601); + display.update(); delay(3000); // wait for servo to move - avatar.setSpeechText("moving to 511"); + display.getSpeechBalloon().setText("moving to 511"); anim_controller.servo_driver.setTargetPosition(servo_pan_id, IDLE_POSITION); anim_controller.servo_driver.setTargetPosition(servo_tilt_id, IDLE_POSITION); + display.update(); delay(3000); // wait for servo to move // register animation clips @@ -175,39 +235,48 @@ void setup() { // sprintf(balloon_text, "servo 01 pos: %d", // anim_controller.servo_driver.getCurrentPosition(1)); // 14338??? - // avatar.setSpeechText(balloon_text); + // display.getSpeechBalloon().setText(balloon_text); M5_LOGI("setting tasks..."); // ## register tasks Tasks.setAutoErase(true); - Tasks - .add("M5_update", - [] { - M5.update(); - if (M5.BtnA.wasPressed()) { - avatar.setFace(faces[face_idx]); - face_idx = (face_idx + 1) % faces_length; - avatar.setColorPalette(*color_palettes[color_palettes_idx]); - color_palettes_idx = - (color_palettes_idx + 1) % color_palettes_size; - } - if (M5.BtnB.wasPressed()) { - avatar.setExpression(expressions[expression_idx]); - expression_idx = (expression_idx + 1) % expressions_size; - } - if (M5.BtnC.wasPressed()) { - anim_controller.play(anim_clip_id); - anim_clip_id = (anim_clip_id + 1) % 7; - } - }) - ->startFps(100); - Tasks - .add("Clock", - [] { - milli_sec = millis(); - stackchan_srv.timer_chr.writeValue(milli_sec); - }) - ->startFps(60); + // Tasks + // .add("M5_update", + // [] { + // // M5.update(); + // // if (M5.BtnA.wasPressed()) { + // // display.setFace(faces[face_idx]); + // // face_idx = (face_idx + 1) % faces_length; + // // display.setColorPalette(color_palettes[color_palettes_idx]); + // // color_palettes_idx = + // // (color_palettes_idx + 1) % color_palettes_size; + // // } + // // if (M5.BtnB.wasPressed()) { + // // display.getExpressionWeight().set(expressions[expression_idx], + // // 255); + // // expression_idx = (expression_idx + 1) % expressions_size; + // // } + // // if (M5.BtnC.wasPressed()) { + // // anim_controller.play(anim_clip_id); + // // anim_clip_id = (anim_clip_id + 1) % 7; + // // } + + // // if (m5_count % (100 * 60) == 0) { + // // auto i = random(7); + // // anim_controller.play(i); + // // } + + // // random motion + // m5_count += 1; + // }) + // ->startFps(100); + // Tasks + // .add("Clock", + // [] { + // milli_sec = millis(); + // stackchan_srv.timer_chr.writeValue(milli_sec); + // }) + // ->startFps(60); Tasks .add("BLE_polling", [] { @@ -218,12 +287,15 @@ void setup() { Tasks .add("Facial_Update", [] { - stackchan_srv.facePoll(avatar, faces, faces_length); - stackchan_srv.facialExpressionPoll(avatar, expressions, - expressions_size); - stackchan_srv.facialColorPoll(avatar, color_palettes, - color_palettes_size); - stackchan_srv.mouseOpenPoll(avatar); + M5.update(); + // stackchan_srv.facePoll(display, faces, faces_length); + // stackchan_srv.facialExpressionPoll(display, expressions, + // expressions_size); + // stackchan_srv.facialColorPoll(display, color_palettes, + // color_palettes_size); + // stackchan_srv.mouseOpenPoll(display); + display.update(); + // M5_LOGI("Face updated"); }) ->startFps(30); @@ -235,7 +307,7 @@ void setup() { }) ->startFps(ANIMATION_FPS); - avatar.setSpeechText(""); // hide speech balloon + display.getSpeechBalloon().setText(""); // hide speech balloon anim_controller.play((unsigned short)AnimationName::kNod); }