Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 2 | |||
| × | 1 | ||||
Software apps and online services | ||||||
![]() |
|
Remote work can often feel isolating.
When communication is limited to text, it’s hard to know how your colleagues are really feeling, which can lead to misunderstandings.
"Remote Kun" offers a new way to connect with teammates working from different locations.
Note: "Kun" is a Japanese honorific that addresses someone with familiarity and warmth.
How does this work:- The robot’s face and movements change according to your facial expressions, such as happy or angry, which are detected by your webcam.
- If you appear distressed for a certain period, an alert light will turn on.
- Gently tapping the robot’s back sends a notification to the remote worker via Slack.
Arm movement control
- In response to the MQTT message, M5Stack Core outputs a pulse corresponding to the specified angle to the servo motor.
- Message='happy': raises (90 degrees) arms three times to express 'happiness'
- Message='angry': raises (90 degrees) and lower (-90 degrees) arms three times to express 'anger'
How to make a rotating red light
- Materials: four red LEDs, four resistors, a universal board with a diameter of about 20 mm, and a transparent resin cap with a diameter of about 20 mm.
- The LEDs and resistors are mounted on the circuit board. The LED legs are bent 90 degrees so they face horizontally.
- The resin cap is then placed over the circuit board.
- M5Stack Core turns on the LEDs in sequence every about 100 milliseconds. It looks like they are rotating.
Touch sensor modulemodification
- TTP223 The touch sensor module has a small detection area, so it is connected to an aluminum plate about 10 mm wide and 50 mm long, and a capacitor for adjusting sensitivity is added, the detection area is expanded, making it easier to touch.
Overall Summary
This system is configured to control a robot (RemoteKun) based on emotion data sent from a PC, while also detecting reactions from a touch sensor on the robot and sending notifications to Slack.
Role of Each Component and Data Flow
- PC (Client-side):- An application installed on the PC uses the facial analysis library DeepFace to estimate emotions and generate the results as an Emotion Message.- This data is transmitted to the AWS cloud using the HTTP protocol.
- API Gateway:- It functions as the entry point for HTTP requests (emotion data) sent from the PC.- The API Gateway passes the received requests to the first AWS Lambda Function.
- Lambda Function (1st):- This is a serverless function that processes the emotion data passed from the API Gateway.- Based on the received emotion data, this function sends control commands to the robot (RemoteKun) to AWS IoT.
- AWS IoT:- This service manages communication with IoT devices (in this case, RemoteKun).- It sends messages received from the Lambda Function to RemoteKun via the MQTT protocol.- When the touch sensor on RemoteKun reacts, it transmits that information to AWS IoT.
- Lambda Function (2nd):- The touch sensor on RemoteKun reacts, and that information is passed to this Lambda Function via AWS IoT.- This function is responsible for sending a notification message to Slack based on the received sensor reaction.
- RemoteKun (Robot):- It receives the emotion data sent via MQTT from AWS IoT and executes actions based on that data.- When the touch sensor reacts, it transmits that information to AWS IoT.
- Slack:- It receives messages sent from the second Lambda Function and displays and records the robot's touch events in a chat.
The almost parts of "Remote Kun" were printed with 3D printer.
All parts made with 3D printerThe list of parts made with 3D printer.・Head (with back cover)・Body (with front/back cover)・Arm (right and left)・Leg (×2)To conceal the wiring, both the head and the body have wire holes that allow the cables to pass through.The back cover of the body has a cutout for a USB connector that supplies power, as well as for mounting the touch sensor on the back.
#include <M5Unified.h>
#include <WiFi.h>
#include "remotekun-arm.h"
#include "remotekun-communication.h"
#include "remotekun-face.h"
#include "remotekun-patlump.h"
#include "remotekun-touch.h"
// constant ---------------------------------------
// defined by .env
#ifndef WIFI_SSID
#define WIFI_SSID "WIFI_SSID_UNDEFINED"
#endif
#ifndef WIFI_PASSWORD
#define WIFI_PASSWORD "WIFI_PASSWORD_UNDEFINED"
#endif
#ifndef AWS_IOT_ENDPOINT
#define AWS_IOT_ENDPOINT "AWS_IOT_ENDPOINT_UNDEFINED"
#endif
#ifndef DEVICE_NAME
#define DEVICE_NAME "DEVICE_NAME_UNDEFINED"
#endif
const uint8_t PIN_ARM_L = 5; // left arm
const uint8_t PIN_ARM_R = 19; // right arm
const uint8_t PIN_TOUCH = 15; // touch sensor
const uint8_t PIN_LED0 = 16; // patrol lamp
const uint8_t PIN_LED1 = 17;
const uint8_t PIN_LED2 = 21;
const uint8_t PIN_LED3 = 22;
const unsigned long MAIN_LOOP_WAIT_TIME_MS = 10; // main loop wait [msec]
const unsigned long AUTO_TURNOFF_INTERVAL = 10 * 1000; // patrol lamp auto off time
const unsigned long MESSAGE_DISABLE_INTERVAL = 6 * 1000; // emotion continuation time
const unsigned long EMOTION_EXPLOSION_TIME = 5 * 60 * 1000; // 5 * 60 * 1000; // dissatisfaction explosion time = standard 5 minutes [msec]
// global objects --------------------------------------
RemoteKunArm arm(PIN_ARM_L, PIN_ARM_R);
RemoteKunCommunication communication;
RemoteKunFace face;
RemoteKunPatlump patlump(PIN_LED0, PIN_LED1, PIN_LED2, PIN_LED3);
RemoteKunTouch remoteKunTouch(PIN_TOUCH);
// callbacks --------------------------------------
// MQTT topic reception processing
void onEmotionMessage(const String &payload, const size_t size)
{
// message analysis
char buf[256];
payload.toCharArray(buf, 256);
JsonDocument recievedJson;
DeserializationError error = deserializeJson(recievedJson, buf);
if (error)
{
Serial.println("onEmotionMessage Deserialization failed!");
return;
}
// ignore message that is received multiple times within a certain period
if (!communication.isMessageEnabled()) return;
// face, arms
if (strcmp(recievedJson["emotion"], "angry") == 0)
{
if (face.emotion == Expression::Angry) return;
face.angry();
arm.angry();
communication.updateLastMassageRecievedTime();
}
else if (strcmp(recievedJson["emotion"], "disgusted") == 0)
{
if (face.emotion == Expression::Angry) return;
face.angry();
arm.angry();
communication.updateLastMassageRecievedTime();
}
else if (strcmp(recievedJson["emotion"], "happy") == 0)
{
if (face.emotion == Expression::Happy) return;
face.happy();
arm.happy();
communication.updateLastMassageRecievedTime();
}
else
{
if (face.emotion == Expression::Neutral) return;
face.neutral();
arm.neutral();
communication.updateLastMassageRecievedTime();
}
}
// touch detection
void IRAM_ATTR onTouchDetected()
{
if (!remoteKunTouch.isTouchEnabled()) return;
communication.requestPublish();
remoteKunTouch.updateLastTouchedTime();
face.happy();
face.speech("Thank you!");
patlump.off();
}
// emotion explosion
void onEmotionExplosion() {
patlump.on();
Serial.println("emotion explosion!");
}
void setup()
{
Serial.begin(115200);
M5.begin();
// Wi-Fi
Serial.println(WIFI_SSID);
Serial.println(WIFI_PASSWORD);
Serial.println("Connecting to WiFi");
M5.Lcd.printf("Connecting to WiFi(%s)", WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
M5.Lcd.print('.');
}
Serial.println("Succeessfully connected to WiFi");
M5.Lcd.printf("Succeessfully connected to WiFi");
// arms
arm.begin();
arm.neutral();
// MQTT
communication.begin(AWS_IOT_ENDPOINT, 8883, "RemoteKun-Device1");
Serial.println("Connecting to AWS IoT...");
M5.Lcd.printf("Connecting to AWS IoT...");
bool connectionResult = communication.connect();
M5.Lcd.printf("MQTT Connection Result: %d\n", connectionResult);
Serial.printf("MQTT Connection Result: %d\n", connectionResult);
communication.onEmotionMessage = onEmotionMessage;
communication.messageDisableInterval = MESSAGE_DISABLE_INTERVAL;
communication.subscribe();
// face
face.onEmotionExplosion = onEmotionExplosion;
face.emotionPeriod = MESSAGE_DISABLE_INTERVAL;
face.emotionExplosionTime = EMOTION_EXPLOSION_TIME;
face.autoRecovery = false;
face.begin();
// touch
remoteKunTouch.begin(onTouchDetected);
// patlump
patlump.activeLevel = LOW;
patlump.inactiveLevel = HIGH;
patlump.autoTurnOffInterval = AUTO_TURNOFF_INTERVAL;
patlump.begin();
}
void loop()
{
// MQTT
communication.action();
// arms
arm.action();
// face
face.action();
// patlump
patlump.action();
// adjustment
delay(MAIN_LOOP_WAIT_TIME_MS);
}
/*
arm control
- SG90
- 50Hz -> 20msec
- -90deg: 0.5msec = 6.4 / 256
- 0deg: 1.45msec = 18.56 / 256
- 90deg: 2.4msec = 30.72 / 256
action(): Queues are executed sequentially according to their scheduled execution times
When methods executed, the action is added to the queue.
angry()
disgusted()
happy()
*/
#include <M5Unified.h>
class RemoteKunArm
{
public:
static const uint8_t MAX_ACTIONS = 16;
double PWM_FREQUENCY = 50;
double PWM_RESOLUTION = 8;
uint8_t RIGHT_ARM_CHANNEL = 0;
uint8_t LEFT_ARM_CHANNEL = 1;
RemoteKunArm(uint8_t pin_right_arm, uint8_t pin_left_arm)
{
_pin_right_arm = pin_right_arm;
_pin_left_arm = pin_left_arm;
_pwm_resolution_pulse = pow(2, PWM_RESOLUTION);
}
void begin()
{
ledcSetup(0, PWM_FREQUENCY, PWM_RESOLUTION);
ledcSetup(1, PWM_FREQUENCY, PWM_RESOLUTION);
ledcAttachPin(_pin_right_arm, RIGHT_ARM_CHANNEL);
ledcAttachPin(_pin_left_arm, LEFT_ARM_CHANNEL);
}
void angry()
{
unsigned long now = millis();
uint8_t action_num = 5;
int angles[action_num] = {90, -90, 90, -90, 0};
for (uint8_t i = 0; i < action_num; i++)
{
_right_arm_action_queue[i] = angles[i];
_left_arm_action_queue[i] = -angles[i];
_action_timeout_queue[i] = now + i * 500;
}
_action_index = 0;
_action_count = action_num;
}
void happy()
{
unsigned long t = millis() - 800;
uint8_t action_num = 6;
int angles[action_num] = {90, 0, 90, 0, 90, 0};
for (uint8_t i = 0; i < action_num; i++)
{
_right_arm_action_queue[i] = angles[i];
_left_arm_action_queue[i] = -angles[i];
if (i % 2 == 0) {
t += 800;
} else {
t += 400;
}
_action_timeout_queue[i] = t;
}
_action_index = 0;
_action_count = action_num;
}
void disgusted()
{
angry();
}
void arbitrary(int right_arm_angless[], int left_arm_angles[], int timeouts[], int action_num)
{
unsigned long now = millis();
for (uint8_t i = 0; i < action_num; i++)
{
_right_arm_action_queue[i] = right_arm_angless[i];
_left_arm_action_queue[i] = left_arm_angles[i];
_action_timeout_queue[i] = now + i * timeouts[i];
}
_action_index = 0;
_action_count = action_num;
}
void neutral()
{
unsigned long now = millis();
_right_arm_action_queue[0] = 0;
_left_arm_action_queue[0] = 0;
_action_timeout_queue[0] = now;
_action_index = 0;
_action_count = 1;
}
void action()
{
if (_action_index >= _action_count)
{
return;
}
if (millis() >= _action_timeout_queue[_action_index])
{
ledcWrite(RIGHT_ARM_CHANNEL, _angle2pulse(_right_arm_action_queue[_action_index]));
ledcWrite(LEFT_ARM_CHANNEL, _angle2pulse(_left_arm_action_queue[_action_index]));
_action_index++;
}
}
private:
uint8_t _pin_right_arm = 19;
uint8_t _pin_left_arm = 26;
uint32_t _pwm_resolution_pulse = 256;
uint32_t _right_arm_action_queue[MAX_ACTIONS] = {0}; // right arm angle queue
uint32_t _left_arm_action_queue[MAX_ACTIONS] = {0}; // left arm angle queue
uint32_t _action_timeout_queue[MAX_ACTIONS] = {0}; // scheduled execution time queue
uint8_t _action_index = 0; // queue index
uint8_t _action_count = 0; // queue length
// convert angle [deg] to pulse width
// (1.9 / 180.0 * (angle + 90) + 0.5) * _pwm_resolution_pulse
uint32_t _angle2pulse(int angle)
{
return (uint32_t)((0.0105555555 * (angle + 90) + 0.5) / 20.0 * _pwm_resolution_pulse);
}
};
/*
MQTT communication with AWS IoT
Define the content of the environment variables by concatenating all lines except for the BEGIN CERTIFICATE and END CERTIFICATE lines into a single line
AWS_CERT_CA_CONTENT
AWS_CERT_PRIVATE_CONTENT
AWS_CERT_CRT_CONTENT
Topics
/RemoteKun/worker/{device_name}/emotion
/RemoteKun/device/{device_name}/slack
*/
#include <WiFiClientSecure.h>
#include <MQTTPubSubClient.h>
#include <ArduinoJson.h>
// ---------------------------------------
#ifndef AWS_CERT_CA_CONTENT
#define AWS_CERT_CA_CONTENT "AWS_CERT_CA_UNDEFINED"
#endif
#ifndef AWS_CERT_PRIVATE_CONTENT
#define AWS_CERT_PRIVATE_CONTENT "AWS_CERT_PRIVATE_UNDEFINED"
#endif
#ifndef AWS_CERT_CRT_CONTENT
#define AWS_CERT_CRT_CONTENT "AWS_CERT_CRT_UNDEFINED"
#endif
const char AWS_CERT_CA[] =
"-----BEGIN CERTIFICATE-----\n"
AWS_CERT_CA_CONTENT
"-----END CERTIFICATE-----\n";
const char AWS_CERT_PRIVATE[] =
"-----BEGIN RSA PRIVATE KEY-----\n"
AWS_CERT_PRIVATE_CONTENT
"-----END RSA PRIVATE KEY-----\n";
const char AWS_CERT_CRT[] =
"-----BEGIN CERTIFICATE-----\n"
AWS_CERT_CRT_CONTENT
"-----END CERTIFICATE-----\n";
class RemoteKunCommunication
{
public:
unsigned int timeout = 3;
unsigned long messageDisableInterval = 10000; // Message reception disable period [msec]
WiFiClientSecure client; // SSL client
MQTTPubSubClient mqtt; // MQTT client
std::function<void(const String& payload, const size_t size)> onEmotionMessage;
void begin(const char *host, uint16_t port, const char *device_name)
{
_host = host;
_port = port;
_device_name = device_name;
mqtt.begin(client);
}
bool connect()
{
// Connect to AWS IoT endpoint
Serial.print("connecting to host...");
client.stop();
// connect to aws endpoint with certificates and keys
client.setCACert(AWS_CERT_CA);
client.setCertificate(AWS_CERT_CRT);
client.setPrivateKey(AWS_CERT_PRIVATE);
while (!client.connect(_host, _port))
{
Serial.print(".");
delay(1000);
}
Serial.println(" OK!");
// Connect to AWS MQTT broker
Serial.print("connecting to aws mqtt broker...");
mqtt.disconnect();
unsigned int t = 0;
while (!mqtt.connect("RemoteKun-Device1"))
{
Serial.print(".");
delay(1000);
t++;
if (t > timeout)
{
Serial.println("timeout");
return false;
}
mqtt.update();
}
Serial.println(" OK!");
return true;
}
void subscribe()
{
char TOPIC_ACTION[64] = "";
sprintf(TOPIC_ACTION, "/RemoteKun/worker/%s/emotion", _device_name);
mqtt.subscribe(TOPIC_ACTION, onEmotionMessage);
}
// MQTT publish request
void requestPublish() {
_publishRequest = true;
}
void action() {
mqtt.update();
if (!_publishRequest) return;
publish();
}
// Publish
// Do not call directly from interrupt handlers (doing so may cause WDT timeout and restart)
void publish()
{
_publishRequest = false;
JsonDocument doc;
doc["message"] = "hello from RemoteKun!";
char buffer[128];
serializeJson(doc, buffer);
char TOPIC_SLACK[128] = "";
sprintf(TOPIC_SLACK, "/RemoteKun/device/%s/slack", _device_name);
mqtt.publish(TOPIC_SLACK, buffer);
}
void updateLastMassageRecievedTime() {
_lastRecievedTime = millis();
}
bool isMessageEnabled() {
unsigned long now = millis();
if (now - _lastRecievedTime > messageDisableInterval) {
return true;
} else {
Serial.printf("Message DISable\n"); //
return false;
}
}
private:
const char *_host;
uint16_t _port = 8883;
const char *_device_name;
bool _publishRequest = false;
unsigned long _lastRecievedTime = 0;
};
/*
face control
*/
#include <M5Unified.h>
#include <Avatar.h>
using namespace m5avatar;
class RemoteKunFace {
public:
const uint8_t EMOTION_NEUTRAL = 0;
m5avatar::Expression emotion = Expression::Neutral;
m5avatar::Avatar avatar;
unsigned int emotionPeriod = 10000; // Emotion duration [msec]
unsigned int speechPeriod = 10000; // Speech bubble display duration [msec]
unsigned long emotionExplosionTime = 1000 * 10; // Discontent explosion threshold [msec]
std::function<void(void)> onEmotionExplosion; // Discontent explosion callback
bool autoRecovery = false; // Emotion auto-recovery enable flag
void begin() {
avatar.init();
avatar.setMouthOpenRatio(0);
avatar.setSpeechFont(&fonts::lgfxJapanMinchoP_12);
}
void happy() {
_newEmotion = Expression::Happy;
}
void angry() {
_newEmotion = Expression::Angry;
}
void neutral() {
_newEmotion = Expression::Neutral;
}
void sleepy() {
_newEmotion = Expression::Sleepy;
}
void speech(String text) {
_newText = text;
_speechRequestStatus = 1;
}
void action() {
// Emotion change ----------------------------------------------
if (_newEmotion != emotion) {
emotion = _newEmotion;
_emotionStartTime = millis();
avatar.setExpression(emotion);
_emotionExplosionStatus = 0;
Serial.printf("set emotion: %d\n", emotion);
}
// Automatically revert expression
if (autoRecovery &&
emotion != Expression::Neutral &&
_emotionStartTime + emotionPeriod < millis()) {
neutral();
Serial.printf("auto emotion recovered to neutral\n");
}
// Emotion explosion ----------------------------------------------
if (_emotionExplosionStatus == 0 &&
emotion == Expression::Angry &&
_emotionStartTime + emotionExplosionTime < millis()) {
onEmotionExplosion();
_emotionExplosionStatus = 1;
}
// Speech bubble ----------------------------------------------
if (_speechRequestStatus == 1) {
avatar.setSpeechText(_newText.c_str());
avatar.setMouthOpenRatio(0.7);
Serial.printf("remotekun-face: setSpeechText\n");
_speechRequestStatus = 2;
_speechStartTime = millis();
}
// Speech bubble revert
if (_speechRequestStatus == 2 && _speechStartTime + speechPeriod < millis()) {
avatar.setSpeechText("");
avatar.setMouthOpenRatio(0);
neutral();
_speechRequestStatus = 0;
}
}
private:
m5avatar::Expression _newEmotion = Expression::Neutral;
String _newText = "";
uint8_t _speechRequestStatus = 0;
uint8_t _emotionExplosionStatus = 0;
unsigned long _emotionStartTime = 0;
unsigned long _speechStartTime = 0;
};
/*
Rotating red lights control
*/
class RemoteKunPatlump
{
public:
uint8_t activeLevel = HIGH;
uint8_t inactiveLevel = LOW;
unsigned long interval = 75; // LED switching period [msec]
unsigned long autoTurnOffInterval = 10000; // Auto-off time [msec]
RemoteKunPatlump(
uint8_t pin_led0,
uint8_t pin_led1,
uint8_t pin_led2,
uint8_t pin_led3)
{
_pin_leds[0] = pin_led0;
_pin_leds[1] = pin_led1;
_pin_leds[2] = pin_led2;
_pin_leds[3] = pin_led3;
}
void begin()
{
pinMode(_pin_leds[0], OUTPUT);
pinMode(_pin_leds[1], OUTPUT);
pinMode(_pin_leds[2], OUTPUT);
pinMode(_pin_leds[3], OUTPUT);
action();
}
void off()
{
_on = false;
}
void on()
{
_on = true;
_lastTurnOnTime = millis();
}
void action()
{
unsigned long now = millis();
// Wait for switching period
if (now < _lastSwitchedTime + interval) return;
// Auto-off after a certain period
if (now > _lastTurnOnTime + autoTurnOffInterval) {
off();
}
for (int k = 0; k < 4; k++)
{
if (k == _flashIndex)
{
if (_on)
{
digitalWrite(_pin_leds[k], activeLevel);
}
else
{
digitalWrite(_pin_leds[k], inactiveLevel);
}
}
else
{
digitalWrite(_pin_leds[k], inactiveLevel);
}
}
_lastSwitchedTime = now;
_flashIndex = (_flashIndex + 1) % 4;
}
private:
uint8_t _pin_leds[4] = {0, 1, 2, 3};
unsigned int _flashIndex = 0;
unsigned long _lastSwitchedTime = 0;
unsigned long _lastTurnOnTime = 0;
bool _on = false;
};
<mxfile host="65bd71144e">
<diagram id="FylvCg6JfPMyBLLnnH1h" name="Device">
<mxGraphModel dx="1101" dy="866" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="M5Stack<div>Basic V2.7</div>" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="294" y="100" width="116" height="300" as="geometry"/>
</mxCell>
<mxCell id="3" value="<div>Servo Motor</div>SG-90" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="520" y="180" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="4" value="<div>Servo Motor</div>SG-90" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="520" y="260" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="5" value="rotating red light" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="520" y="100" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="6" value="touch sensor" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="520" y="340" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="7" value="" style="endArrow=classic;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" target="3" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="210" as="sourcePoint"/>
<mxPoint x="490" y="250" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="8" value="" style="endArrow=classic;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="289.8" as="sourcePoint"/>
<mxPoint x="520" y="289.8" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="9" value="" style="endArrow=classic;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="129.57999999999998" as="sourcePoint"/>
<mxPoint x="520" y="129.57999999999998" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="10" value="" style="endArrow=classic;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="6" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="440" y="240" as="sourcePoint"/>
<mxPoint x="410" y="370" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="12" value="<p><br></p>" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;fillColor=#434445;aspect=fixed;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.vvd.wi_fi;rotation=-135;" parent="1" vertex="1">
<mxGeometry x="200" y="230" width="50" height="50" as="geometry"/>
</mxCell>
<mxCell id="13" value="Wi-Fi" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="200" y="290" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="15" value="AWS" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="40" y="215" width="120" height="80" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="b_0gra4JIXNoi4Pa5X-z" name="AWS">
<mxGraphModel dx="1745" dy="722" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="XMrbSRp4rwc0Vd3_QZxJ-1" value="" style="group" parent="1" vertex="1" connectable="0">
<mxGeometry x="-40" y="230" width="820" height="510" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-1" value="API Gateway" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#E7157B;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.api_gateway;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="200" y="50" width="60" height="60" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-4" value="AWS IoT" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.aws_iot;fillColor=#5294CF;gradientColor=none;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="460" y="190" width="50" height="60" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-5" value="Lambda Function" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#ED7100;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.lambda;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="362" y="50" width="60" height="60" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-6" value="Lambda Function" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#ED7100;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.lambda;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="540" y="50" width="60" height="60" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-7" value="Slack" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="700" y="40" width="120" height="80" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-9" value="" style="image;html=1;image=img/lib/clip_art/computers/Laptop_128x128.png;flipH=1;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry y="40" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-11" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="465" y="370" width="40" height="40" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-12" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="460" y="410" width="50" height="50" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-13" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="465" y="460" width="20" height="20" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-14" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="485" y="460" width="20" height="20" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-15" value="" style="rounded=1;whiteSpace=wrap;html=1;arcSize=0;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="510" y="420" width="10" height="30" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-16" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="450" y="420" width="10" height="30" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-17" value="" style="whiteSpace=wrap;html=1;aspect=fixed;labelBackgroundColor=#4F4F4F;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="470" y="375" width="30" height="30" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-18" value="RemoteKun" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="455" y="480" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-22" value="" style="endArrow=classic;html=1;labelBorderColor=#3333FF;fillColor=#0050ef;strokeColor=#001DBC;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="459.52" y="290" as="sourcePoint"/>
<mxPoint x="459.52" y="350" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-23" value="" style="endArrow=classic;html=1;fillColor=#d80073;strokeColor=#A50040;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="509.52" y="350" as="sourcePoint"/>
<mxPoint x="509.52" y="290" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-25" value="" style="endArrow=classic;html=1;labelBorderColor=#3333FF;fillColor=#0050ef;strokeColor=#001DBC;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="400" y="150" as="sourcePoint"/>
<mxPoint x="440" y="210" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-26" value="" style="endArrow=classic;html=1;labelBorderColor=#3333FF;fillColor=#0050ef;strokeColor=#001DBC;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="280" y="80" as="sourcePoint"/>
<mxPoint x="340" y="80" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-27" value="" style="endArrow=classic;html=1;fillColor=#d80073;strokeColor=#A50040;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="610" y="79.57999999999998" as="sourcePoint"/>
<mxPoint x="690" y="79.57999999999998" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-28" value="" style="endArrow=classic;html=1;labelBorderColor=#3333FF;fillColor=#0050ef;strokeColor=#001DBC;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="110" y="79.5" as="sourcePoint"/>
<mxPoint x="170" y="79.5" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-29" value="Emotion Message" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="90" y="90" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-32" value="AWS Cloud" style="points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_aws_cloud_alt;strokeColor=#232F3E;fillColor=none;verticalAlign=top;align=left;spacingLeft=30;fontColor=#232F3E;dashed=0;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="150" width="510" height="310" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-24" value="" style="endArrow=classic;html=1;fillColor=#d80073;strokeColor=#A50040;" parent="ILtzoP_ymIQ9zSnd1lK3-32" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="370" y="210" as="sourcePoint"/>
<mxPoint x="410" y="150" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="m7r5OI7vA8Q7iABK5DND-1" value="HTTP" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="ILtzoP_ymIQ9zSnd1lK3-32">
<mxGeometry x="-30" y="50" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="ILtzoP_ymIQ9zSnd1lK3-31" value="Slack Message" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="XMrbSRp4rwc0Vd3_QZxJ-1" vertex="1">
<mxGeometry x="520" y="320" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="m7r5OI7vA8Q7iABK5DND-4" value="MQTT" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="XMrbSRp4rwc0Vd3_QZxJ-1">
<mxGeometry x="455" y="310" width="60" height="30" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
/*
touch sensor control
*/
#include <M5Unified.h>
class RemoteKunTouch {
public:
unsigned long touchDisableInterval = 5000; // Touch disable period [msec]
RemoteKunTouch(uint8_t pin_touch) {
_pin_touch = pin_touch;
}
void begin(void (*onTouchDetected) ()) {
pinMode(_pin_touch, INPUT);
attachInterrupt(_pin_touch, onTouchDetected, FALLING);
}
void updateLastTouchedTime() {
_lastTouchedTime = millis();
}
bool isTouchEnabled() {
unsigned long now = millis();
if (now - _lastTouchedTime > touchDisableInterval) {
return true;
} else {
return false;
}
}
uint8_t isTouching() {
return digitalRead(_pin_touch);
}
private:
uint8_t _pin_touch;
unsigned long _lastTouchedTime = 0;
};
Comments