#include <FastLED.h>
#include <EMFButton.h>
const uint8_t WIDTH = 8;
const uint8_t HEIGHT = 8;
#define LED_PIN 4
CRGB leds[WIDTH * HEIGHT];
const CRGB color = CRGB(0xff, 0x12, 0x10);
uint16_t XY(uint8_t x, uint8_t y)
{
return ((HEIGHT - 1 - y) * WIDTH) + x;
}
bool showmouth = 1;
// Expression constants
enum Expression
{
HAPPY = 0,
SAD,
SURPRISED,
NEUTRAL,
ANGRY,
SLEEPY,
MAX_EXPRESSIONS
};
Expression exxp = HAPPY;
template <typename T>
struct pos_t
{
pos_t() {}
pos_t(T xn, T yn) : x(xn), y(yn) {}
T x, y;
};
int8_t sign(int8_t in)
{
return (in < 0) ? -1 : (in > 0) ? 1
: 0;
}
class eye
{
public:
eye() {}
eye(uint8_t x, uint8_t y, CRGB color) : _matrix_pos{x, y}, _color(color) {}
void setPosition(int8_t x, int8_t y)
{
_pos.x = x;
_pos.y = y;
}
void setPosition(pos_t<int8_t> coord)
{
_pos = coord;
}
void setOpen(uint8_t open)
{
_open = open;
}
void setAngle(int8_t angle)
{
_angle = angle;
}
void setColor(CRGB color)
{
_color = color;
}
pos_t<int8_t> getPosition()
{
return _pos;
}
uint8_t getOpen()
{
return _open;
}
int8_t getAngle()
{
return _angle;
}
CRGB getColor()
{
return _color;
}
void draw()
{
uint8_t open_in_pix = (_open / 64);
uint8_t open_pix_val = (_open % 64) * 4;
uint8_t pos_x_val = abs(_pos.x) * 2;
uint8_t pos_y_val = abs(_pos.y) * 2;
for (uint8_t ay = 0; ay < 3; ay++)
{
CRGB col = blend(CRGB(0, 0, 0), _color, (ay < open_in_pix) ? 255 : ay == open_in_pix ? open_pix_val
: 0);
CRGB colP1 = blend(col, CRGB(0, 0, 0), pos_x_val);
CRGB colP2 = blend(CRGB(0, 0, 0), col, pos_x_val);
uint8_t X = _matrix_pos.x;
uint8_t Y = _matrix_pos.y + ay;
leds[XY(X, Y)] += blend(colP1, CRGB(0, 0, 0), pos_y_val);
leds[XY(X + sign(_pos.x), Y)] += blend(colP2, CRGB(0, 0, 0), pos_y_val);
leds[XY(X, Y + sign(_pos.y))] += blend(CRGB(0, 0, 0), colP1, pos_y_val);
leds[XY(X + sign(_pos.x), Y + sign(_pos.y))] += blend(CRGB(0, 0, 0), colP2, pos_y_val);
}
for (int8_t ax = -1; ax < 2; ax++)
{
int16_t eyepo = (_open * 4) - (sign(ax) * _angle * 2);
int16_t eyepospos = eyepo + (_pos.y * 2);
uint8_t X = _matrix_pos.x + ax;
uint8_t Y = _matrix_pos.y + (eyepospos / 255);
if (eyepo > -(_pos.y * 2) && eyepo < 765 - (_pos.y * 2))
leds[XY(X, Y)] = blend(leds[XY(X, Y)], _color, (eyepospos % 255));
if (eyepo > 255 && eyepo < 1020 - (_pos.y * 2))
leds[XY(X, Y - 1)] = blend(_color, leds[XY(X, Y - 1)], (eyepospos % 255));
}
}
private:
pos_t<uint8_t> _matrix_pos;
CRGB _color;
pos_t<int8_t> _pos{0, 0};
int8_t _angle = 0;
uint8_t _open = 0;
};
class mouth
{
public:
mouth() {}
mouth(uint8_t x, uint8_t y, CRGB color) : _matrix_pos{x, y}, _color(color) {}
void setOpen(uint8_t open)
{
_open = open;
}
void setPosition(int8_t pos)
{
_pos = pos;
}
void setColor(CRGB color)
{
_color = color;
}
uint8_t getOpen()
{
return _open;
}
int8_t getPosition()
{
return _pos;
}
CRGB getColor()
{
return _color;
}
void draw()
{
uint8_t abs_pos = (abs(_pos) * 2);
for (uint8_t ax = 0; ax < 4; ax++)
{
if (ax > 0 && ax < 3)
{
leds[XY(_matrix_pos.x + ax, _matrix_pos.y)] += blend(CRGB(0, 0, 0), color, max(_open, (uint8_t)~abs_pos));
leds[XY(_matrix_pos.x + ax, _matrix_pos.y + sign(_pos))] += blend(CRGB(0, 0, 0), _color, abs_pos);
}
else
{
leds[XY(_matrix_pos.x + ax, _matrix_pos.y)] += blend(CRGB(0, 0, 0), _color, max(_open, abs_pos));
}
}
}
private:
pos_t<uint8_t> _matrix_pos;
int8_t _pos;
uint8_t _open = 0;
CRGB _color;
};
class face
{
public:
face()
{
leftEye = eye(1, 3, color);
rightEye = eye(6, 3, color);
theMouth = mouth(2, 1, color);
_left_eye_open = 255, _right_eye_open = 255;
right_blink_dir = 1;
left_blink_dir = 1;
leftEye.setOpen(0);
rightEye.setOpen(0);
theMouth.setOpen(0);
theMouth.setPosition(0);
};
bool right_blink_flag, right_blink_dir;
bool left_blink_flag, left_blink_dir;
uint8_t _left_eye_open, _right_eye_open;
int8_t _left_eye_angle, _right_eye_angle;
pos_t<int8_t> _left_eye_pos, _right_eye_pos;
int8_t _mouth_pos;
uint8_t _mouth_open;
void draw()
{
leftEye.draw();
rightEye.draw();
if (showmouth)
theMouth.draw();
}
void blink_event()
{
right_blink_flag = 1;
left_blink_flag = 1;
}
void eventMaking()
{
EVERY_N_MILLISECONDS(random(500, 5000))
{
blink_event();
}
}
void eventHandling()
{
int16_t cur_left_eye_open = leftEye.getOpen();
int16_t cur_right_eye_open = rightEye.getOpen();
int16_t cur_left_eye_angle = leftEye.getAngle();
int16_t cur_right_eye_angle = rightEye.getAngle();
pos_t<int8_t> cur_left_eye_pos = leftEye.getPosition();
pos_t<int8_t> cur_right_eye_pos = rightEye.getPosition();
int16_t cur_mouth_open = theMouth.getOpen();
int16_t cur_mouth_pos = theMouth.getPosition();
if (left_blink_flag == 1)
{
cur_left_eye_open += ((left_blink_dir * 2) - 1) * 4;
if (cur_left_eye_open < 0 && !left_blink_dir)
left_blink_dir = 1; // it can be for changeing eye
if (cur_left_eye_open > _left_eye_open && left_blink_dir)
{
left_blink_flag = 0;
left_blink_dir = 0;
}
}
if (right_blink_flag == 1)
{
cur_right_eye_open += ((right_blink_dir * 2) - 1) * 4;
if (cur_right_eye_open < 0 && !right_blink_dir)
right_blink_dir = 1; // it can be for changeing eye
if (cur_right_eye_open > _right_eye_open && right_blink_dir)
{
right_blink_flag = 0;
right_blink_dir = 0;
}
}
cur_left_eye_open = constrain(cur_left_eye_open, 0, 255);
cur_right_eye_open = constrain(cur_right_eye_open, 0, 255);
leftEye.setOpen((uint8_t)cur_left_eye_open);
rightEye.setOpen((uint8_t)cur_right_eye_open);
if (cur_left_eye_angle != _left_eye_angle)
{
cur_left_eye_angle += ((cur_left_eye_angle < _left_eye_angle) * 2) - 1;
}
if (cur_right_eye_angle != _right_eye_angle)
{
cur_right_eye_angle += ((cur_right_eye_angle < _right_eye_angle) * 2) - 1;
}
leftEye.setAngle(cur_left_eye_angle);
rightEye.setAngle(cur_right_eye_angle);
if (cur_left_eye_pos.x != _left_eye_pos.x)
{
cur_left_eye_pos.x += ((cur_left_eye_pos.x < _left_eye_pos.x) * 2) - 1;
}
if (cur_left_eye_pos.y != _left_eye_pos.y)
{
cur_left_eye_pos.y += ((cur_left_eye_pos.y < _left_eye_pos.y) * 2) - 1;
}
if (cur_right_eye_pos.x != _right_eye_pos.x)
{
cur_right_eye_pos.x += ((cur_right_eye_pos.x < _right_eye_pos.x) * 2) - 1;
}
if (cur_right_eye_pos.y != _right_eye_pos.y)
{
cur_right_eye_pos.y += ((cur_right_eye_pos.y < _right_eye_pos.y) * 2) - 1;
}
leftEye.setPosition(cur_left_eye_pos);
rightEye.setPosition(cur_right_eye_pos);
if (cur_mouth_open != _mouth_open)
{
cur_mouth_open += ((cur_mouth_open < _mouth_open) * 2) - 1;
}
if (cur_mouth_pos != _mouth_pos)
{
cur_mouth_pos += ((cur_mouth_pos < _mouth_pos) * 2) - 1;
}
theMouth.setOpen(cur_mouth_open);
theMouth.setPosition(cur_mouth_pos);
}
void setExpression(Expression exxp)
{
switch (exxp)
{
case HAPPY:
_left_eye_open = 255;
_right_eye_open = 255;
_left_eye_angle = 0;
_right_eye_angle = 0;
_mouth_open = 0;
_mouth_pos = -127;
break;
case SAD:
_left_eye_open = 192;
_right_eye_open = 192;
_left_eye_angle = -64;
_right_eye_angle = 64;
_mouth_open = 64;
_mouth_pos = 127;
break;
case SURPRISED:
_left_eye_open = 255;
_right_eye_open = 255;
_left_eye_angle = 0;
_right_eye_angle = 0;
_mouth_open = 255;
_mouth_pos = 0;
break;
case NEUTRAL:
_left_eye_open = 192;
_right_eye_open = 192;
_left_eye_angle = 0;
_right_eye_angle = 0;
_mouth_open = 255;
_mouth_pos = 0;
break;
case ANGRY:
_left_eye_open = 192;
_right_eye_open = 192;
_left_eye_angle = 64;
_right_eye_angle = -64;
_mouth_open = 0;
_mouth_pos = 64;
break;
case SLEEPY:
_left_eye_open = 128;
_right_eye_open = 128;
_left_eye_angle = 0;
_right_eye_angle = 0;
_mouth_open = 0;
_mouth_pos = 0;
break;
default:
_left_eye_open = 128;
_right_eye_open = 128;
_left_eye_angle = -32;
_right_eye_angle = 32;
_mouth_open = 0;
_mouth_pos = 0;
break;
}
}
private:
eye leftEye;
eye rightEye;
mouth theMouth;
};
face theFace;
EMFButton btn(3);
void setup()
{
FastLED.addLeds<NEOPIXEL, LED_PIN>(leds, WIDTH * HEIGHT);
// Serial.begin(112500);
FastLED.setBrightness(255);
theFace.setExpression(exxp);
theFace.blink_event();
}
void handleSerialInput()
{
if (Serial.available() > 0)
{
String command = Serial.readStringUntil('\n');
command.trim();
if (command == "H")
{
exxp = HAPPY;
Serial.println("Expression set to HAPPY");
}
else if (command == "S")
{
exxp = SAD;
Serial.println("Expression set to SAD");
}
else if (command == "U")
{
exxp = SURPRISED;
Serial.println("Expression set to SURPRISED");
}
else if (command == "N")
{
exxp = NEUTRAL;
Serial.println("Expression set to NEUTRAL");
}
else if (command == "A")
{
exxp = ANGRY;
Serial.println("Expression set to ANGRY");
}
else if (command == "L")
{
exxp = SLEEPY;
Serial.println("Expression set to SLEEPY");
}
else
{
Serial.println("Unknown command");
}
theFace.setExpression(exxp);
theFace.blink_event();
}
}
void loop()
{
theFace.eventMaking();
theFace.eventHandling();
FastLED.clear();
theFace.draw();
FastLED.show();
handleSerialInput();
btn.tick();
if (btn.hasSingle())
{
exxp = (Expression)((int)exxp + 1);
exxp = (Expression)((int)exxp % MAX_EXPRESSIONS);
theFace.setExpression(exxp);
theFace.blink_event();
}
if (btn.hasTriple())
{
showmouth = !showmouth;
}
}