回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

情绪共振纸雕灯——基于人在传感器的情感化交互照明装置 简单

头像 肥嘟嘟左卫门 2026.06.22 16 0

1.作品简介

1.1 项目名称:情绪共振纸雕灯——基于步伐节奏的情感化交互照明装置

1.2 项目概述

情绪共振纸雕灯是一个将传统纸雕艺术与现代传感技术相结合的交互照明装置,旨在为城市独居青年提供一种无需语言的情绪回应与深夜陪伴。装置通过人在传感器实时捕捉使用者的移动速度与存在状态,当监测到人快速经过时,灯带呈现五彩斑斓的光效并伴随轻快的蜂鸣声,表达“快乐被看见”的正向回应;当人缓慢经过时,灯带切换为静谧的紫色光效,给予“低落被接纳”的无声陪伴;人离开后灯光自动熄灭,空间回归平静。装置以纸雕为光影载体,将冰冷的传感器数据转化为有温度、可凝视的光影语言,让独居者在进门的第一刻和深夜的每一个起身瞬间,都能感受到不被评判、无需开口的情感共振。

1.3 核心功能介绍

1.3.1 步伐节奏情绪感知系统

通过人在传感器实时监测使用者经过装置时的移动速度。传感器将速度数据传输至掌控板进行处理分析,根据预设算法判断使用者的情绪状态:快速通过被识别为“愉悦/积极”状态,缓慢通过被识别为“低落/沉思”状态。系统不采集任何图像或声音信息,完全基于人体移动的物理节奏进行判断,充分保护使用者的隐私安全。

1.3.2 情绪化光影反馈系统

当系统判定使用者处于积极状态时,灯带自动切换为五彩斑斓的动态光效,色彩明亮跳跃,如同情绪的视觉化绽放,对快乐进行正向强化。当判定使用者处于低落状态时,灯带切换为柔和静谧的紫色静态光效,紫色作为光谱中偏冷、偏沉思的颜色,传递一种“我懂你”的静默陪伴,不强行提振情绪,而是允许低落的合理存在。人离开感应区域后,灯光缓慢渐暗直至完全熄灭,完成一次完整的情绪对话循环。

1.3.3 纸雕光影艺术载体

区别于普通数码显示面板,装置采用纸雕作为光影输出的艺术载体。灯光透过精雕细琢的纸张图案,在墙面和地面投射出富有层次感的光影画面。纸雕的手工质感与温暖光晕赋予情绪反馈以美感和仪式感,让使用者在凝视光影的过程中获得类似艺术疗愈的心理舒缓体验。纸雕图案可根据场景需求定制设计,如星空、森林、山海等自然意象。

1.3.4 夜间微光陪伴模式

在深夜时段(可预设时间区间),系统自动切换为夜间微光模式。当人在传感器检测到使用者起身时,纸雕灯以极低亮度、暖橙色微光缓缓亮起,仅照亮脚下一米范围内的地面,提供不刺眼的安全导航。光效设计避免驱逐睡意,蜂鸣器保持静默或以极轻柔的单音回应,实现“被陪伴但不被打扰”的深夜守护。使用者回到床上后,灯光在30秒内缓慢渐暗,温柔送回睡眠。

1.3.5 蜂鸣器情绪化音效系统

蜂鸣器根据不同情绪状态输出差异化的声音反馈。积极状态时,发出一串短促上扬的轻快提示音,类似风铃被微风触碰,强化愉悦感。低落状态时,发出一声极轻的悠远单音,类似远处钟鸣或海浪轻拍,不打扰沉思氛围。夜间模式下,蜂鸣器保持静默或以不可察觉的最低音量输出,确保不惊扰半梦半醒状态。所有音效设计遵循“不提问、不评判、不要求回应”的原则。

1.4 研究过程

1.4.1 文献研究

通过在中国知网(CNKI)等相关学术期刊研究数据库,将关键词设置为“独居青年、情绪感知、光环境疗愈、交互装置、纸雕艺术”等。发现针对独居人群心理健康、环境光照对情绪影响、无感交互设计等方面进行了相关研究,找到如下相关文献。

研究发现,城市独居青年长期高压工作导致个体对自身情绪状态的觉察能力下降,需要一个外部“镜子”来帮助情绪被看见。光照环境对人的情绪调节具有显著作用,不同色温与亮度的光线可引发不同的心理反应,暖色光有助于放松,冷色光有助于沉思,动态彩色光可提升积极情绪体验。在智能家居交互设计中,无感交互日益受到重视,即系统在不打扰用户、不要求用户主动操作的前提下完成服务响应,尤其适用于情绪脆弱时刻的陪伴场景。纸雕作为一种传统手工艺形式,其光影效果在艺术疗愈领域已有初步应用,能够通过视觉美感带来心理舒缓体验。

1.4.3 同理心地图

① 确定目标用户

城市独居青年(22-35岁),工作压力大,独居租房,夜间独处时间长,习惯压抑情绪。

② 收集信息

本研究通过观察法收集目标用户的信息。选取独居Vlog夜间实录、素人真实记录视频等影像素材,重点观察独居青年下班回家、深夜失眠、凌晨起夜等场景中的行为表现和情绪状态。

③ 整理信息

将观察到的用户行为、语言、情绪进行归类整理,梳理出核心需求与痛点。

④ 绘制地图

 

⑤ 分析和讨论

用户需求:需要不刺眼的安全感(深夜照明不过亮);需要无声的陪伴(不被提问、不被教育);需要情绪被看见但不必说出口;需要帮助自己保护珍贵的睡意。

用户痛点:开大灯刺眼驱散睡意,不开灯摸黑怕磕碰;深夜情绪涌起无人可说;手机屏幕光冰冷,缺乏温暖感;空间沉默,孤独感被黑暗放大。

1.4.4 未来研究趋势

随着独居经济与单身社会的持续发展,面向独居人群的情感化智能产品将成为重要赛道。物联网技术将使家居设备具备更细腻的环境感知能力,通过非侵入式的传感器采集用户的生理与行为数据,实现真正的“无感交互”。情感计算技术将赋予智能设备读懂人类情绪的能力,不仅是简单的“检测—反馈”,更是深层次的“理解—回应”。纸雕、木艺、布艺等传统手工材质将与数字技术深度融合,为冰冷的电子设备注入手工温度与艺术疗愈属性。光影作为一种非语言的表达媒介,将在心理疗愈、情绪调节领域得到更系统的应用研究。未来的独居空间将不再只是物理居所,而是能够感知、回应、陪伴居住者的“情感共鸣体”。

1.5 设计思路

本装置依托于Arduino IDE编程平台和人在传感器、可编程灯带和蜂鸣器,结合纸雕手工艺术塑造外观,开发一个情绪共振交互照明装置。该装置具备步伐节奏情绪感知、情绪化光影反馈、纸雕艺术呈现、夜间微光陪伴等功能,其功能概述如下:

1.5.1步伐节奏情绪感知

通过将Arduino UNO、人在传感器和蜂鸣器相结合,实时采集使用者在感应区域内移动的速度数据,根据预设速度阈值算法判断情绪状态(快速=愉悦,慢速=低落),将分类结果输出至灯光控制模块。

1.5.2情绪化光影反馈

灯带根据情绪状态切换光效模式。积极状态时输出五彩斑斓的动态循环光效,低落状态时输出紫色静态柔和光效。灯光透过纸雕层叠结构投射出富有美感的光影画面,将情绪进行视觉化、艺术化的表达。

1.5.3夜间微光陪伴

系统通过环境光传感器与时间段判断进入夜间模式。深夜检测到人体移动时,灯带以极低亮度暖橙色微光缓慢亮起,仅提供地面导航照明。蜂鸣器静默或以极低音量输出单音。人离开后灯光在预设时间内缓慢渐暗。

1.5.4蜂鸣器声音反馈

蜂鸣器根据情绪状态输出差异化音效,积极状态为上扬短促音,低落状态为悠远单音,夜间模式保持静默。所有音效遵循“轻、短、不追问”的设计原则。

1.5.5纸雕艺术载体

采用手工纸雕工艺制作灯罩,图案可根据场景主题定制(如星空、山海、森林)。纸雕的层叠透光结构将灯光切割为明暗交替的光影层次,赋予情绪反馈以艺术观赏性和疗愈感。

1.6 适用场景与对象

1.6.1 适用对象

城市独居青年(22-35岁),工作压力大,夜间独处时间长对居住空间有情感需求、追求生活仪式感的独居者有失眠困扰或深夜易醒的人群希望在不使用语言的情况下获得情绪回应的用户。

1.6.2 适用场景

①独居公寓的玄关走廊(下班回家的第一刻)

②卧室床头至门口的过道(深夜起夜的路径)

③出租屋的单间空间(多功能一体化空间)

④心理咨询室的等候过渡区

⑤艺术展览中的互动体验展项

⑥合租公寓的公共走廊(室友间无需语言的默契)

2.作品实现方案

2.1传感器模块

传感器模块是本系统的感知核心,主要包括人在传感器和环境光传感器两部分。

人在传感器用于实时检测人体在感应区域内的存在状态与移动速度。传感器将采集到的速度数据传输至掌控板,作为情绪状态判断的核心依据。当使用者快速经过感应区域时,传感器输出较高的速度数值。缓慢经过时,输出较低的速度数值。传感器仅采集移动速度与存在状态,不采集图像、声音等隐私数据,充分保护使用者个人空间的安全边界。

2.2 主控与执行器模块

掌控板接收传感器模块传来的数据,根据预设算法对移动速度进行分类处理,输出对应的情绪状态判定结果,并向执行器模块发送控制信号。

执行器模块包括可编程彩色灯带和蜂鸣器两部分。

可编程彩色灯带根据掌控板输出的情绪状态信号切换光效模式。当判定为积极状态(快速通过)时,灯带输出五彩斑斓的动态循环光效,色彩在预设色盘中依次切换,形成跳跃、欢快的视觉感受。当判定为低落状态(缓慢通过)时,灯带切换为紫色静态柔和光效,亮度适中、色彩稳定,营造静谧、沉思的氛围。当使用者离开感应区域后,灯带按照预设的渐变时间缓慢降低亮度直至完全熄灭,完成一次完整的情绪对话过程。在夜间微光模式下,灯带仅输出暖橙色低亮度光照,照亮地面一米范围内的区域,光效平缓无闪烁。

蜂鸣器根据情绪状态输出差异化的声音反馈。积极状态时,发出一串短促上扬的轻快提示音;低落状态时,发出一声悠远的单音;夜间模式下,蜂鸣器保持静默状态,确保不惊扰使用者的睡眠状态。

2.3纸雕光影载体模块

纸雕光影载体模块是本装置区别于普通灯光装置的核心设计要素。模块由多层手工雕刻的纸质灯罩构成,灯带光源置于纸雕结构内部。光线透过纸雕的层叠镂空图案后,在墙面和地面投射出明暗交替、富有层次感的光影画面。

纸雕图案可根据使用场景进行主题定制。面向独居青年的卧室场景,推荐采用星空、山海、森林等自然意象主题,通过光影将自然元素引入封闭的居住空间,增强心理上的开阔感与疗愈感。纸雕的手工质感与暖色光晕相结合,赋予情绪反馈以温度和美感,让使用者在凝视光影的过程中获得类似艺术疗愈的心理舒缓体验。

3. 作品制作与展示(详情见视频)

3.1 硬件搭建

3.1.1 硬件清单

序号硬件名称数量用途说明
1Arduino UNO1主控核心,负责数据处理与逻辑控制
2人在传感器1检测人体存在状态与移动速度
3可编程彩色灯带1输出五彩/紫色/暖橙等多种光效
4蜂鸣器1输出情绪化音效反馈
5木板1手工纸雕艺术载体,形成层叠光影
6道林纸1容纳电子元件,支撑纸雕灯罩
7电源适配器及连接线若干供电与线路连接

3.1.2 硬件连接示意图

 

3.2纸雕灯外观

纸雕灯外观通过手工雕刻与激光切割相结合的方式进行设计制作。首先通过手绘草图确定纸雕灯罩的主题图案,本项目选用“星空与山海”主题,寓意在独居空间中引入自然的辽阔与宁静。将手绘稿导入Laser Maker软件进行数字化处理,提取图案轮廓线条,设置切割与雕刻参数。使用Smart Art软件连接激光切割机,在象牙白卡纸上进行精准切割与镂空雕刻。

纸雕灯罩采用多层结构设计,共计3-5层卡纸叠合,层与层之间留有均匀间隙。光源置于纸雕结构最内层,光线透过逐层镂空的图案后,在墙面和地面投射出明暗交替、富有纵深感的层叠光影画面。灯箱底座采用椴木板激光切割成型,内部预留掌控板、传感器、灯带和蜂鸣器的安装槽位,确保电子元件整齐排布且便于后期维护。

 

 

 

3.3传感器与灯带编程

传感器与灯带编程依托于Mind+图形化编程平台,通过掌控板实现整体逻辑控制。编程工作分为三个核心模块:

3.3.1移动速度采集与情绪判定模块

程序通过人在传感器实时读取人体在感应区域内的移动速度数据。设定速度阈值参数:当移动速度高于阈值时,判定为“快速通过”,输出“积极状态”信号;当移动速度低于阈值时,判定为“缓慢通过”,输出“低落状态”信号。传感器仅采集速度数值,不涉及图像或声音信息。

3.3.2灯带光效控制模块

根据情绪状态信号驱动灯带切换光效模式。积极状态下,灯带循环输出五彩动态光效,色彩在红、橙、黄、绿、蓝、紫之间平滑过渡切换,亮度和变化速度预设为欢快节奏。低落状态下,灯带输出紫色静态光效,亮度适中、色彩稳定,营造静谧氛围。当人在传感器检测到人离开感应区域后,灯带进入渐暗程序,在预设时间内(约5秒)将亮度从当前值平滑降至零,完成情绪对话的柔和收尾。

夜间模式通过环境光传感器与系统时间联合触发。当环境光低于预设暗度阈值且处于深夜时段(如23:00-06:00)时,系统自动切换模式。该模式下检测到人体移动时,灯带仅输出暖橙色低亮度微光,亮度值控制在最高亮度的15%-20%,仅照亮脚下近地面区域。人离开后灯光缓慢渐暗(约30秒),保护使用者的睡意。

3.3.3蜂鸣器音效控制模块

蜂鸣器根据情绪状态输出差异化声音。积极状态下,输出一段短促上扬的提示音序列(如三声快速升调“嘀嘀嘀”),模拟风铃的轻快感。低落状态下,输出一声悠远绵长的单音,模拟远处钟鸣或海螺回响。夜间模式下,蜂鸣器保持完全静默,确保不惊扰使用者半梦半醒状态。

 

图片1.png图片2.png图片3.png图片4.png

代码
/*
 * 归途 HomeMood FINAL v2
 *
 * 帧协议: 0x43 0x42 距离
 * 快速靠近 → 彩虹灯 + 蜂鸣器(持续)
 * 缓慢靠近 → 暖白常亮 + 静音(持续)
 * 5秒无移动 → 3秒渐灭
 *
 * v2: TH_FAST=8, MOOD_MS=3s
 */

#include <SoftwareSerial.h>
#include <avr/pgmspace.h>
#include <util/delay.h>

#define PIN_RX    2
#define PIN_TX    3
#define PIN_LED   5
#define PIN_BUZZ  9

#define NUM_LEDS    45
#define COMPUTE_MS  500
#define AVG_COUNT   3
#define TH_MOVE     5
#define TH_FAST     8
#define GONE_MS     5000
#define MOOD_MS     3000
#define FADE_MS     3000

#define F_H1  0x43
#define F_H2  0x42

enum Mode { MODE_OFF, MODE_FADING, MODE_SAD, MODE_HAPPY };

static const uint8_t sineTab[64] PROGMEM = {
  128,140,153,165,177,188,199,209,
  218,226,234,240,245,250,253,254,
  255,254,253,250,245,240,234,226,
  218,209,199,188,177,165,153,140,
  128,116,103,91,79,68,57,47,
  38,30,22,16,11,6,3,2,
  1,2,3,6,11,16,22,30,
  38,47,57,68,79,91,103,116
};

SoftwareSerial radar(PIN_RX, PIN_TX);

uint8_t ledBuf[NUM_LEDS * 3];
uint8_t gBrCur = 0, gBrTgt = 0;
Mode gMode = MODE_OFF;
unsigned long gModeTime = 0;
uint8_t gFadeStartBr = 0;

uint16_t gDist = 0;
uint32_t accSum = 0;
uint16_t accCnt = 0;
int16_t prevAvgDist = -1;
int16_t spdBuf[AVG_COUNT];
uint8_t spdIdx = 0, spdCnt = 0;
int16_t rawSpeed = 0, smoothSpeed = 0;

bool personHere = false, wasHere = false;
bool moodDecided = false;
Mode moodLocked = MODE_SAD;
int16_t peakSpd = 0;
unsigned long arrivalTime = 0;
unsigned long lastMoveTime = 0;

unsigned long gLastCompute = 0, gLastLed = 0, gLastPrint = 0;
unsigned long bzNext = 0;
uint8_t bzIdx = 0;
uint8_t pState = 0;
uint16_t frmCnt = 0;

const uint16_t mFreq[] = {523, 587, 659, 698, 784, 880, 988, 1047};
const uint8_t mLen = 8;

// ==================== WS2812 ====================
void wsInit() {
  DDRD |= _BV(PD5);
  PORTD &= ~_BV(PD5);
  memset(ledBuf, 0, sizeof(ledBuf));
}

void setLed(uint8_t i, uint8_t r, uint8_t g, uint8_t b) {
  uint16_t idx = (uint16_t)i * 3;
  ledBuf[idx] = g;
  ledBuf[idx + 1] = r;
  ledBuf[idx + 2] = b;
}

void fillAll(uint8_t r, uint8_t g, uint8_t b) {
  for (uint8_t i = 0; i < NUM_LEDS; i++) setLed(i, r, g, b);
}

__attribute__((optimize("O2")))
void wsSendByte(uint8_t b) {
  uint8_t hi = PORTD | _BV(PD5);
  uint8_t lo = PORTD & ~_BV(PD5);
  #define WSBIT(m) do { \
    PORTD = hi; \
    __asm__ volatile("nop\n"::); \
    if (b & (m)) { \
      __asm__ volatile("nop\nnop\nnop\nnop\nnop\nnop\nnop\n"::); \
    } \
    PORTD = lo; \
    __asm__ volatile("nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"::); \
  } while(0)
  WSBIT(0x80); WSBIT(0x40); WSBIT(0x20); WSBIT(0x10);
  WSBIT(0x08); WSBIT(0x04); WSBIT(0x02); WSBIT(0x01);
  #undef WSBIT
}

void wsShow() {
  if (gBrCur < gBrTgt) {
    uint8_t d = gBrTgt - gBrCur;
    gBrCur += (d > 4) ? 4 : d;
  } else if (gBrCur > gBrTgt) {
    uint8_t d = gBrCur - gBrTgt;
    gBrCur -= (d > 4) ? 4 : d;
  }
  uint8_t oldSREG = SREG;
  cli();
  uint8_t *p = ledBuf;
  uint16_t n = NUM_LEDS * 3;
  uint8_t br = gBrCur;
  if (br >= 255) {
    while (n--) wsSendByte(*p++);
  } else if (br == 0) {
    while (n--) { wsSendByte(0); p++; }
  } else {
    while (n--) {
      wsSendByte((uint8_t)(((uint16_t)*p * br) >> 8));
      p++;
    }
  }
  SREG = oldSREG;
  _delay_us(60);
}

// ==================== 工具 ====================
uint8_t sin8(uint8_t x) {
  return pgm_read_byte(&sineTab[x >> 2]);
}

void hsvToRgb(uint8_t h, uint8_t *r, uint8_t *g, uint8_t *b) {
  uint8_t region = h / 43;
  uint8_t rem = (h - (region * 43)) * 6;
  uint8_t q = 255 - (uint8_t)(((uint16_t)255 * rem) >> 8);
  uint8_t t = (uint8_t)(((uint16_t)255 * rem) >> 8);
  switch (region) {
    case 0:  *r = 255; *g = t;   *b = 0;   break;
    case 1:  *r = q;   *g = 255; *b = 0;   break;
    case 2:  *r = 0;   *g = 255; *b = t;   break;
    case 3:  *r = 0;   *g = q;   *b = 255; break;
    case 4:  *r = t;   *g = 0;   *b = 255; break;
    default: *r = 255; *g = 0;   *b = q;   break;
  }
}

// ==================== 状态切换 ====================
void setMode(Mode m, unsigned long now) {
  if (m == gMode) return;
  if (m == MODE_OFF && gMode == MODE_FADING) return;
  if (m == MODE_FADING && gMode == MODE_OFF) return;
  if (m == MODE_FADING) gFadeStartBr = gBrCur;
  gMode = m;
  gModeTime = now;
  Serial.print(F(">> MODE -> "));
  switch (m) {
    case MODE_OFF:    Serial.println(F("OFF"));     break;
    case MODE_FADING: Serial.println(F("FADING"));  break;
    case MODE_SAD:    Serial.println(F("SAD/warm")); break;
    case MODE_HAPPY:  Serial.println(F("HAPPY/rainbow")); break;
  }
}

// ==================== 传感器 ====================
void pollSensor() {
  while (radar.available()) {
    uint8_t b = radar.read();
    switch (pState) {
      case 0:
        if (b == F_H1) pState = 1;
        break;
      case 1:
        if (b == F_H2) pState = 2;
        else if (b == F_H1) pState = 1;
        else pState = 0;
        break;
      case 2:
        gDist = b;
        accSum += b;
        accCnt++;
        frmCnt++;
        pState = 0;
        break;
    }
  }
}

// ==================== 速度 ====================
void computeSpeed() {
  if (accCnt < 1) return;
  uint16_t avgDist = (uint16_t)(accSum / accCnt);
  accSum = 0;
  accCnt = 0;

  rawSpeed = 0;
  if (prevAvgDist > 0) {
    rawSpeed = ((int16_t)prevAvgDist - (int16_t)avgDist)
               * (int16_t)(1000L / COMPUTE_MS);
  }
  prevAvgDist = (int16_t)avgDist;
  if (rawSpeed > 250) rawSpeed = 250;
  if (rawSpeed < -250) rawSpeed = -250;

  spdBuf[spdIdx] = rawSpeed;
  spdIdx = (spdIdx + 1) % AVG_COUNT;
  if (spdCnt < AVG_COUNT) spdCnt++;

  int32_t s = 0;
  for (uint8_t i = 0; i < spdCnt; i++) s += spdBuf[i];
  smoothSpeed = (int16_t)(s / spdCnt);
}

// ==================== LED ====================
void tickLED(unsigned long now) {
  unsigned long elapsed = now - gModeTime;
  switch (gMode) {
    case MODE_OFF:
      gBrTgt = 0;
      memset(ledBuf, 0, sizeof(ledBuf));
      break;
    case MODE_FADING:
      if (elapsed >= (unsigned long)FADE_MS) {
        gMode = MODE_OFF;
        gBrTgt = 0;
        memset(ledBuf, 0, sizeof(ledBuf));
      } else {
        gBrTgt = (uint8_t)((uint32_t)gFadeStartBr
                           * ((unsigned long)FADE_MS - elapsed)
                           / (unsigned long)FADE_MS);
      }
      break;
    case MODE_SAD:
      fillAll(255, 200, 140);
      gBrTgt = 153;
      break;
    case MODE_HAPPY: {
      uint8_t timeBase = (uint8_t)(now * 80UL / 1000UL);
      for (uint8_t i = 0; i < NUM_LEDS; i++) {
        uint8_t hue = (uint8_t)((uint16_t)i * 256UL / NUM_LEDS) + timeBase;
        uint8_t r, g, b;
        hsvToRgb(hue, &r, &g, &b);
        setLed(i, r, g, b);
      }
      uint8_t sv = sin8((uint8_t)(now * 256UL / 2000UL));
      gBrTgt = 180 + (uint8_t)((uint16_t)sv * 75UL / 255UL);
      break;
    }
  }
  wsShow();
}

// ==================== 蜂鸣器 ====================
void tickBuzz(unsigned long now) {
  if (gMode != MODE_HAPPY) {
    if (bzIdx > 0 || bzNext > 0) {
      noTone(PIN_BUZZ);
      bzIdx = 0; bzNext = 0;
    }
    return;
  }
  if (bzNext == 0) { bzNext = now; bzIdx = 0; }
  if (now >= bzNext) {
    tone(PIN_BUZZ, mFreq[bzIdx], 120);
    bzNext = now + 180;
    bzIdx = (bzIdx + 1) % mLen;
  }
}

// ==================== 串口 ====================
void printDebug() {
  Serial.print(F("frm="));
  Serial.print(frmCnt);
  Serial.print(F(" D="));
  if (gDist < 100) Serial.print(' ');
  if (gDist < 10)  Serial.print(' ');
  Serial.print(gDist);
  Serial.print(F(" avg="));
  if (smoothSpeed >= 0) Serial.print('+');
  if (abs(smoothSpeed) < 100) Serial.print(' ');
  if (abs(smoothSpeed) < 10)  Serial.print(' ');
  Serial.print(smoothSpeed);
  Serial.print(F(" pk="));
  Serial.print(peakSpd);
  unsigned long noMove = (millis() - lastMoveTime) / 1000UL;
  Serial.print(F(" noMv="));
  Serial.print(noMove);
  Serial.print(F("s"));
  Serial.print(F(" P="));
  Serial.print(personHere ? F("Y") : F("N"));
  Serial.print(F(" M="));
  if (moodDecided) {
    Serial.print(moodLocked == MODE_HAPPY ? F("HAP") : F("SAD"));
  } else {
    Serial.print(F("---"));
  }
  Serial.print(F(" ["));
  switch (gMode) {
    case MODE_OFF:    Serial.print(F("OFF"));   break;
    case MODE_FADING: Serial.print(F("FADE"));  break;
    case MODE_SAD:    Serial.print(F("WARM"));  break;
    case MODE_HAPPY:  Serial.print(F("RAIN"));  break;
  }
  Serial.print(F("] Br="));
  Serial.println(gBrCur);
}

// ==================== SETUP ====================
void setup() {
  Serial.begin(115200);
  radar.begin(9600);
  wsInit();
  pinMode(PIN_BUZZ, OUTPUT);
  noTone(PIN_BUZZ);
  gModeTime = millis();
  lastMoveTime = millis();
  Serial.println(F(""));
  Serial.println(F("============================="));
  Serial.println(F("  HomeMood FINAL v2"));
  Serial.println(F("  0x43 0x42 DIST"));
  Serial.println(F("  MOVE=5 FAST=8 GONE=5s"));
  Serial.println(F("  MOOD=3s"));
  Serial.println(F("============================="));
  Serial.println(F(""));
}

// ==================== LOOP ====================
void loop() {
  unsigned long now = millis();

  pollSensor();

  if (now - gLastCompute >= COMPUTE_MS) {
    gLastCompute = now;
    computeSpeed();

    wasHere = personHere;

    if (personHere) {
      if (abs(smoothSpeed) >= TH_MOVE) {
        lastMoveTime = now;
      }
      if ((now - lastMoveTime) > (unsigned long)GONE_MS) {
        personHere = false;
      }
    }

    if (!personHere) {
      if (abs(smoothSpeed) >= TH_MOVE) {
        personHere = true;
        lastMoveTime = now;
      }
    }

    if (personHere && !wasHere) {
      Serial.println(F(""));
      Serial.println(F(">> Person ARRIVED!"));
      moodDecided = false;
      peakSpd = 0;
      arrivalTime = now;
    }

    if (!personHere && wasHere) {
      Serial.println(F(""));
      Serial.println(F(">> Person LEFT!"));
      moodDecided = false;
      if (gMode == MODE_HAPPY || gMode == MODE_SAD) {
        setMode(MODE_FADING, now);
      }
    }

    if (personHere && !moodDecided) {
      int16_t absSpd = abs(smoothSpeed);
      if (absSpd > peakSpd) peakSpd = absSpd;

      if ((now - arrivalTime) > (unsigned long)MOOD_MS) {
        if (peakSpd > TH_FAST) {
          moodLocked = MODE_HAPPY;
          moodDecided = true;
          setMode(MODE_HAPPY, now);
          Serial.print(F(">> HAPPY (pk="));
          Serial.print(peakSpd);
          Serial.println(F(")"));
        } else {
          moodLocked = MODE_SAD;
          moodDecided = true;
          setMode(MODE_SAD, now);
          Serial.print(F(">> SAD (pk="));
          Serial.print(peakSpd);
          Serial.println(F(")"));
        }
      }
    }
  }

  if (now - gLastLed >= 20) {
    gLastLed = now;
    tickLED(now);
  }

  tickBuzz(now);

  if (now - gLastPrint >= 500) {
    gLastPrint = now;
    printDebug();
  }
}

评论

user-avatar