#include <Avatar.h>
#include <SD.h>
#include <SPI.h>
#include <M5Unified.h>
#include <Toio.h>
#include <Adafruit_TCS34725.h>
#include <Wire.h>
#include <faces/FaceTemplates.hpp>
#define HAPPY 1
#define ANGRY 2
#define SLURP 3
// Core2 の Port.A は SDA=32, SCL=33
static constexpr int SDA_PIN = 32;
static constexpr int SCL_PIN = 33;
using namespace m5avatar;
int randspeed = 0;
int randposX = 200;
int randtime = 3000;
int randdesire = 0;
int evolution = 0;
int flushbackcount = 0;
int naderu = 0;
int desirecount = 0;
int desire = 0;
int desirecolor[3] = {0,0,0};
int desirecolor1[3] = {72,45,55}; //赤色食べ物
int desirecolor2[3] = {121,220,200}; //緑色食べ物
int desirecolor3[3] = {31,115,190}; //青色食べ物
int desirecolor_other1[3] = {0,0,0}; //違う色食べ物1
int desirecolor_other2[3] = {0,0,0}; //違う色食べ物2
int fooddesire = 0;
int redpoint = 0;
int greenpoint = 0;
int bluepoint = 0;
int slurppoint = 0;
// Toio オブジェクト生成
Toio toio;
Avatar avatar;
// 積分時間・ゲインは環境に合わせて調整可能
Adafruit_TCS34725 tcs(
TCS34725_INTEGRATIONTIME_50MS, // 2.4/24/50/101/154/700ms など
TCS34725_GAIN_4X // 1X/4X/16X/60X
);
// stackchanの設定
Face* faces[7];
const int num_faces = sizeof(faces) / sizeof(Face*);
int face_idx = 0; // face index
const Expression expressions[] = {Expression::Angry, Expression::Sleepy,
Expression::Happy, Expression::Sad,
Expression::Doubt, Expression::Neutral};
const int num_expressions = sizeof(expressions) / sizeof(Expression);
int idx = 0;
ColorPalette* color_palettes[12];
const int num_palettes = sizeof(color_palettes) / sizeof(ColorPalette*);
int palette_idx = 0;
bool isShowingQR = false;
//////////////
// an example of customizing
class MyCustomFace : public Face {
public:
MyCustomFace()
: Face(new RectMouth(50, 90, 4, 60), new BoundingRect(148, 123),
// right eye, second eye arg is center position of eye in (y,x)
new EllipseEye(16, 16, false), new BoundingRect(93, 50),
// left eye
new EllipseEye(16, 16, true), new BoundingRect(96, 190),
// hide eye brows with setting these height zero
new EllipseEyebrow(0, 0, false), new BoundingRect(67, 56),
new EllipseEyebrow(0, 0, true), new BoundingRect(72, 190)) {}
};
class MyCustomFace2 : public Face {
public:
MyCustomFace2()
: Face(new UShapeMouth(64, 64, 0, 16), new BoundingRect(194, 120),
// right eye, second eye arg is center position of eye in (y,x)
new EllipseEye(16, 16, false), new BoundingRect(93, 50),
// left eye
new EllipseEye(16, 16, true), new BoundingRect(96, 190),
// hide eye brows with setting these height zero
new EllipseEyebrow(0, 0, false), new BoundingRect(67, 56),
new EllipseEyebrow(0, 0, true), new BoundingRect(72, 190)) {}
};
/**
* @brief face template for "OωO" face
*
*/
class OmegaFace2 : public Face {
public:
OmegaFace2()
: Face(new OmegaMouth(), new BoundingRect(225, 120),
// right eye, second eye arg is center position of eye in (y,x)
new EllipseEye(false), new BoundingRect(165, 44),
// left eye
new EllipseEye(true), new BoundingRect(165, 44 + 154),
// hide eye brows with setting these height zero
new EllipseEyebrow(0, 0, false), new BoundingRect(67, 56),
new EllipseEyebrow(0, 0, true), new BoundingRect(72, 190)) {}
};
class GirlyFace3 : public Face {
public:
GirlyFace3()
: Face(new UShapeMouth(44, 44, 0, 16), new BoundingRect(222, 120),
// right eye, second eye arg is center position of eye
new GirlyEye(64, 64, false), new BoundingRect(163, 54),
// left eye
new GirlyEye(64, 64, true), new BoundingRect(163, 186),
// right eyebrow
new BowEyebrow(140, 160, false),
new BoundingRect(163, 54), // (y,x)
// left eyebrow
new BowEyebrow(140, 160, true), new BoundingRect(163, 186)) {}
};
class DoggyFace2 : public Face {
public:
DoggyFace2()
: Face(new DoggyMouth(50, 90, 4, 60), new BoundingRect(168, 123),
// right eye, second eye arg is center position of eye
new DoggyEye(false), new BoundingRect(103, 40),
// left eye
new DoggyEye(true), new BoundingRect(106, 200),
// hide eye brows with setting these height zero
new RectEyebrow(15, 2, false), new BoundingRect(67, 56),
new RectEyebrow(15, 2, true), new BoundingRect(72, 190)) {}
};
class PinkDemonFace2 : public Face {
public:
PinkDemonFace2()
: Face(new UShapeMouth(64, 64, 0, 16), new BoundingRect(214, 120),
// right eye, second eye arg is center position of eye
new PinkDemonEye(52, 134, false), new BoundingRect(134, 66),
// left eye
new PinkDemonEye(52, 134, true), new BoundingRect(134, 178),
// hide eye brows with setting these height zero
new EllipseEyebrow(15, 0, false), new BoundingRect(67, 56),
new EllipseEyebrow(15, 0, true), new BoundingRect(72, 190)) {}
};
std::vector<ToioCore*> toiocore_list;
ToioCore* toiocore;
void changeemotion(int emotion){
if(emotion == 1){ //喜ぶ
avatar.setExpression(expressions[2]);
avatar.setColorPalette(*color_palettes[7]);
}else if(emotion == 2){ //怒る
avatar.setExpression(expressions[0]);
avatar.setColorPalette(*color_palettes[8]);
}else if(emotion == 3){ //すねる
avatar.setExpression(expressions[1]);
avatar.setColorPalette(*color_palettes[9]);
}else{
avatar.setExpression(expressions[5]);
avatar.setColorPalette(*color_palettes[2]);
}
}
void changeaction(int action){
if(action == 1){ // ご飯食べる
toiocore->controlMotorWithTarget(0, 5, 0, 30, 0, 150, 270, 1, 0);
delay(2000); // 押下のバウンス防止
toiocore->controlMotorWithTarget(0, 5, 0, 50, 0, 150, 270, 360, 3);
delay(2000); // 押下のバウンス防止
toiocore->controlMotorWithTarget(0, 5, 0, 30, 0, 250, 270, 1, 0);
delay(2000); // 押下のバウンス防止
toiocore->controlMotorWithTarget(0, 5, 0, 50, 0, 250, 270, 360, 3);
delay(2000); // 押下のバウンス防止
toiocore->controlMotorWithTarget(0, 5, 0, 30, 0, 350, 270, 1, 0);
delay(7000); // 押下のバウンス防止
} else if(action == 2){ //遊ぶ
ToioCoreIDData data = toiocore->getIDReaderData();
toiocore->controlMotorWithTarget(0, 5, 0, 50, 0, randposX, 250, 360, 3);
delay(1000); // 押下のバウンス防止
}
}
void changedesire(){
if(desire == 1){ // おなかすいた
avatar.setExpression(expressions[5]);
if(fooddesire == 0){
fooddesire = random(1,4);
}
for(int i = 0;i < 3;i++){
if(fooddesire == 1){
desirecolor[i] = desirecolor1[i];
desirecolor_other1[i] = desirecolor2[i];
desirecolor_other2[i] = desirecolor3[i];
avatar.setColorPalette(*color_palettes[5]);
}else if(fooddesire == 2){
desirecolor[i] = desirecolor2[i];
desirecolor_other1[i] = desirecolor1[i];
desirecolor_other2[i] = desirecolor3[i];
avatar.setColorPalette(*color_palettes[10]);
}else if(fooddesire == 3){
desirecolor[i] = desirecolor3[i];
desirecolor_other1[i] = desirecolor1[i];
desirecolor_other2[i] = desirecolor2[i];
avatar.setColorPalette(*color_palettes[11]);
}
}
desirecount++;
} else if(desire == 2){ //遊びたい
avatar.setExpression(expressions[5]);
avatar.setColorPalette(*color_palettes[6]);
desirecount++;
} else if(desire == 0){
avatar.setExpression(expressions[5]);
avatar.setColorPalette(*color_palettes[2]);
}
if(desirecount == 500){
changeemotion(SLURP);
desire = 3;
delay(1000);
M5.Log.printf("naderu = %d \n",naderu);
if(naderu >= 3){
naderu = 0;
desire = 0;
desirecount = 0;
slurppoint++;
changeemotion(4);
}
}
}
void judgedesire(){
if(desire!=0){
M5.Log.printf("judgedesire");
uint16_t r, g, b, c;
tcs.getRawData(&r, &g, &b, &c);
uint16_t colorTemp = tcs.calculateColorTemperature_dn40(r, g, b, c);
uint16_t lux = tcs.calculateLux(r, g, b);
M5.Log.printf("r=%d,g=%d,b=%d\n correct r=%d,g=%d,b=%d \n",r,g,b,desirecolor[0],desirecolor[1],desirecolor[2]);
if((r < desirecolor[0]+20)&&(r > desirecolor[0]-20)){
if((g < desirecolor[1]+20)&&(g > desirecolor[1]-20)){
if((b < desirecolor[2]+20)&&(b > desirecolor[2]-20)){
changeemotion(1);
changeaction(1);
naderu = 0;
desire = 0;
desirecount = 0;
if(fooddesire == 1){
redpoint++;
}else if(fooddesire == 2){
greenpoint++;
}else if(fooddesire == 3){
bluepoint++;
}
fooddesire = 0;
changeemotion(4);
if(evolution >= 1){
flushbackcount=flushbackcount+1;
}
}
}
}
if((r < desirecolor_other1[0]+30)&&(r > desirecolor_other1[0]-30)){
if((g < desirecolor_other1[1]+30)&&(g > desirecolor_other1[1]-30)){
if((b < desirecolor_other1[2]+30)&&(b > desirecolor_other1[2]-30)){
changeemotion(ANGRY);
delay(3000);
naderu = 0;
desire = 0;
desirecount = 0;
fooddesire = 0;
changeemotion(4);
}
}
}
if((r < desirecolor_other2[0]+30)&&(r > desirecolor_other2[0]-30)){
if((g < desirecolor_other2[1]+30)&&(g > desirecolor_other2[1]-30)){
if((b < desirecolor_other2[2]+30)&&(b > desirecolor_other2[2]-30)){
changeemotion(ANGRY);
delay(3000);
naderu = 0;
desire = 0;
desirecount = 0;
fooddesire = 0;
changeemotion(4);
}
}
}
}
}
void evolutionJudge(){
if(evolution == 0){
if(redpoint + greenpoint + bluepoint >= 3){
//avatar.setFace(faces[6]);
}
if(redpoint >= 5 || greenpoint >= 5 || bluepoint >= 5 || slurppoint >= 3){
avatar.setFace(faces[1]);
delay(1000);
avatar.setFace(faces[2]);
delay(1000);
avatar.setFace(faces[3]);
delay(1000);
avatar.setFace(faces[4]);
delay(1000);
avatar.setFace(faces[1]);
delay(700);
avatar.setFace(faces[2]);
delay(700);
avatar.setFace(faces[3]);
delay(700);
avatar.setFace(faces[4]);
delay(700);
avatar.setFace(faces[1]);
delay(300);
avatar.setFace(faces[2]);
delay(300);
avatar.setFace(faces[3]);
delay(300);
avatar.setFace(faces[4]);
delay(300);
avatar.setFace(faces[1]);
delay(200);
avatar.setFace(faces[2]);
delay(200);
avatar.setFace(faces[3]);
delay(200);
avatar.setFace(faces[4]);
delay(200);
if(slurppoint == 3){
avatar.setFace(faces[5]);
evolution = 0;
}
if((redpoint + greenpoint + bluepoint) >= 11)
{
avatar.setFace(faces[4]);
evolution = 4;
}
if(redpoint >= 5)
{
avatar.setFace(faces[1]);
evolution = 1;
}
if(greenpoint >= 5)
{
avatar.setFace(faces[2]);
evolution = 2;
}
if(bluepoint >= 5)
{
avatar.setFace(faces[3]);
evolution = 3;
}
redpoint = 0;
greenpoint = 0;
bluepoint = 0;
slurppoint = 0;
}
}
}
void flushback(){
if(flushbackcount == 3){
avatar.setExpression(expressions[3]);
avatar.setColorPalette(*color_palettes[12]);
delay(2000);
avatar.stop();
delay(1000);
M5.Lcd.fillScreen(BLACK);
delay(500);
if(evolution == 1){
displayBMP("/robot.bmp", 0, 0);
}else if(evolution == 2){
displayBMP("/drop.bmp", 0, 0);
}else if(evolution == 3){
displayBMP("/light.bmp", 0, 0);
}else if(evolution == 4){
displayBMP("/sea.bmp", 0, 0);
}
delay(7000);
M5.Lcd.fillScreen(BLACK);
delay(500);
avatar.init(8);
avatar.setExpression(expressions[3]);
delay(2000);
avatar.setColorPalette(*color_palettes[2]);
avatar.setExpression(expressions[5]);
delay(2000);
flushbackcount=0;
evolution = 0;
}
}
void setup() {
auto cfg = M5.config();
M5.begin();
// M5.Power.begin();
M5.Lcd.clear();
M5.Lcd.setRotation(0); // 画面の向きを設定
// M5.Lcd.fillScreen(BLACK);
// I2C 初期化(Core2 の Port.A)
Wire.begin(SDA_PIN, SCL_PIN);
// センサ初期化
if (!tcs.begin(TCS34725_ADDRESS, &Wire)) {
M5.Display.setTextColor(TFT_RED, TFT_BLACK);
M5.Display.println("TCS3472 not found (addr 0x29).");
while (1) { delay(100); }
}
// LED(オンボード照明)を少し弱めに
tcs.setInterrupt(false); // 変換動作開始(必要に応じて)
tcs.setIntegrationTime(TCS34725_INTEGRATIONTIME_50MS);
tcs.setGain(TCS34725_GAIN_4X);
M5.Display.setTextColor(TFT_GREEN, TFT_BLACK);
M5.Display.println("Sensor OK.");
if (!SD.begin(GPIO_NUM_4, SPI, 40000000)) {
// M5.Lcd.println("SD Card Mount Failed");
return;
}
faces[0] = avatar.getFace(); // native face
faces[1] = new DoggyFace2();
faces[2] = new OmegaFace2();
faces[3] = new GirlyFace3();
faces[4] = new PinkDemonFace2();
faces[5] = new MyCustomFace();
faces[6] = new MyCustomFace2();
color_palettes[0] = new ColorPalette();
color_palettes[1] = new ColorPalette();
color_palettes[2] = new ColorPalette();
color_palettes[3] = new ColorPalette();
color_palettes[4] = new ColorPalette();
color_palettes[5] = new ColorPalette();
color_palettes[6] = new ColorPalette();
color_palettes[7] = new ColorPalette();
color_palettes[8] = new ColorPalette();
color_palettes[9] = new ColorPalette();
color_palettes[10] = new ColorPalette();
color_palettes[11] = new ColorPalette();
color_palettes[12] = new ColorPalette();
color_palettes[1]->set(COLOR_PRIMARY,
M5.Lcd.color24to16(0x383838)); // eye
color_palettes[1]->set(COLOR_BACKGROUND,
M5.Lcd.color24to16(0xfac2a8)); // skin
color_palettes[1]->set(COLOR_SECONDARY,
TFT_PINK); // cheek
color_palettes[2]->set(COLOR_PRIMARY, TFT_YELLOW);
color_palettes[2]->set(COLOR_BACKGROUND, TFT_DARKCYAN);
color_palettes[3]->set(COLOR_PRIMARY, TFT_DARKGREY);
color_palettes[3]->set(COLOR_BACKGROUND, TFT_WHITE);
color_palettes[4]->set(COLOR_PRIMARY, TFT_RED);
color_palettes[4]->set(COLOR_BACKGROUND, TFT_PINK);
color_palettes[5]->set(COLOR_PRIMARY, TFT_RED); //おなかすいた 赤
color_palettes[5]->set(COLOR_BACKGROUND, TFT_YELLOW);
color_palettes[6]->set(COLOR_PRIMARY, TFT_BLACK); //遊びたい
color_palettes[6]->set(COLOR_BACKGROUND, TFT_GREEN);
color_palettes[7]->set(COLOR_PRIMARY, TFT_BLACK); //喜び
color_palettes[7]->set(COLOR_BACKGROUND, TFT_BLUE);
color_palettes[8]->set(COLOR_PRIMARY, TFT_BLACK); //怒り
color_palettes[8]->set(COLOR_BACKGROUND, TFT_RED);
color_palettes[9]->set(COLOR_PRIMARY, TFT_BLACK); //すねる
color_palettes[9]->set(COLOR_BACKGROUND, TFT_WHITE);
color_palettes[10]->set(COLOR_PRIMARY, TFT_GREEN); //おなかすいた 緑
color_palettes[10]->set(COLOR_BACKGROUND, TFT_YELLOW);
color_palettes[11]->set(COLOR_PRIMARY, TFT_BLUE); //おなかすいた 青
color_palettes[11]->set(COLOR_BACKGROUND, TFT_YELLOW);
color_palettes[12]->set(COLOR_PRIMARY, TFT_LIGHTGREY); //GREY
color_palettes[12]->set(COLOR_BACKGROUND, TFT_WHITE);
avatar.setFace(faces[5]);
avatar.init(8); // start drawing w/ 8bit color mode
avatar.setColorPalette(*color_palettes[2]);
M5.Log.println("- toio コア キューブをスキャンします。");
toiocore_list = toio.scan(3);
size_t n = toiocore_list.size();
if (n == 0) {
M5.Log.println("- toio コア キューブが見つかりませんでした。");
return;
}
M5.Log.printf("- %d 個の toio コア キューブが見つかりました。\n", n);
M5.Log.println("- toio コア キューブに BLE 接続します。");
toiocore = toiocore_list.at(0);
bool connected = toiocore->connect();
if (!connected) {
M5.Log.println("- BLE 接続に失敗しました。");
return;
}
// ボタン押下状態イベントのコールバックをセット
toiocore->onButton([](bool state) {
M5.Log.printf("naderu = %d \n",naderu);
naderu++;
});
M5.Log.println("- 準備完了");
}
void displayBMP(const char *filename, int16_t x, int16_t y) {
File bmpFile = SD.open(filename);
if (!bmpFile) {
M5.Lcd.println("File not found");
return;
}
uint8_t header[54];
bmpFile.read(header, 54);
int bmpWidth = *(int*)&header[18];
int bmpHeight = *(int*)&header[22];
uint16_t bitDepth = *(uint16_t*)&header[28];
if (bitDepth != 24) {
M5.Lcd.println("Only 24-bit BMP supported");
bmpFile.close();
return;
}
int rowSize = (bmpWidth * 3 + 3) & ~3;
uint8_t rowBuffer[rowSize];
for (int row = 0; row < bmpHeight; row++) {
bmpFile.seek(54 + (bmpHeight - 1 - row) * rowSize);
bmpFile.read(rowBuffer, rowSize);
for (int col = 0; col < bmpWidth; col++) {
int index = col * 3;
// BGR -> RGB に変換
uint16_t color = M5.Lcd.color565(rowBuffer[index + 2], rowBuffer[index + 1], rowBuffer[index]);
M5.Lcd.drawPixel(x + col, y + row, color);
}
}
bmpFile.close();
}
void loop() {
M5.update();
// If you want to handle event callbacks, you shoud call loop() method of Toio Object here.
// イベントを扱う場合は、必ずここで Toio オブジェクトの
// loop() メソッドを呼び出すこと
toio.loop();
//M5.Log.printf("press/release A btn\n");
if(desire == 0){ //欲求がないとき
randspeed = random(30,50);
randposX = random(150,300);
randtime = random(1000,5000);
toiocore->controlMotorWithTarget(0, 5, 0, randspeed, 0, randposX, 270, 1, 0);
delay(randtime); // 押下のバウンス防止
toiocore->controlMotorWithTarget(0, 5, 0, 0, 0, randposX, 270, 1, 0);
delay(500); // 押下のバウンス防止
//確率で何かしらの欲求が発生
randdesire = random(0,100);
M5.Log.printf("randdesire = %d\n",randdesire);
M5.Log.printf("desire = %d\n",desire);
if(randdesire < 20){ //おなかすいた
desire = 1;
}else if(randdesire > 100){ //あそびたい
desire = 2;
}
}
//stackchan 状態変更
changedesire();
judgedesire();
evolutionJudge();
flushback();
delay(100);
}
Comments