zygalaxy 发表于 2024-10-31 20:29

【2024 DigiKey创意大赛】- 基于毫米波雷达的生命体征检测及健康监护系统-摔倒检测

本帖最后由 zygalaxy 于 2024-10-31 14:54 编辑

# 【2024 DigiKey创意大赛】- 基于毫米波雷达的生命体征检测及健康监护系统 - 摔倒检测模块开发

## 一、模块概述

本摔倒检测模块旨在通过先进的毫米波雷达传感器技术,及时检测人体摔倒事件,并在确认摔倒后自动触发报警并通知相关人员,确保及时救助。同时,该模块还能记录摔倒事件的时间、地点等信息,供事后分析和改善。



## 二、功能需求



1. **摔倒检测**
   - 利用 MR60ADF 毫米波雷达传感器检测人体摔倒事件。
   - 当检测到摔倒事件时,迅速确认并启动报警机制。
2. **报警通知**
   - 自动触发报警,通知家人、医疗服务机构或指定的联系人。
   - 报警方式可以包括声音、震动、推送通知等多种形式。
3. **数据记录**
   - 记录摔倒事件的时间、地点等信息。
   - 存储这些信息以便事后进行分析和改善。

## 三、硬件需求

1. **MR60ADF 毫米波 60GHz 摔倒检测模块**:核心检测部件。



2. **ESP32 评估板**:用于数据处理和传输。
3. **报警装置**:扬声器等,用于发出报警信号。
4. **存储设备**:用于记录摔倒事件信息。




## 四、软件设计
1. **数据采集**
   - 通过毫米波雷达传感器持续监测人体活动状态。
   - 采集的数据应准确反映人体的运动特征。
   
2. **数据处理**
   - 对采集到的数据进行分析,判断是否发生摔倒事件。
3. **报警触发**
   - 一旦确认摔倒事件,立即触发报警。
   - 确保报警信号能够及时、准确地传达给相关人员。
4. **数据记录**
   - 将摔倒事件的时间、地点等信息记录下来。
   - 可以选择将数据存储在本地存储设备或通过网络上传至服务器。

## 五、用户界面设计

设计一个简单的用户界面,用于显示摔倒检测模块的状态和历史记录。

1. 显示当前是否处于监测状态。



2. 展示最近的摔倒事件记录,包括时间和地点。





## 六、相关代码

硬件代码:

```c
// #include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>

#define FALL_DETECT_PIN 3 // 新增:定义摔倒检测引脚

// WiFi network credentials
const char *ssid = "ExclusiveForQuZhihang";
const char *password = "quzhihang1314!";

// MQTT broker settings
const char *mqtt_server = "192.168.2.105";
const int mqtt_port = 1883;
const char *clientId = "humidity_1";
const char *mqtt_user = "zygalaxy";

// 摔倒检测主题
const char *fall_topic = "t/fall_detection"; // 新增:定义摔倒检测的主题

WiFiClient wifiClient;
PubSubClient client(wifiClient);

void callback(char *topic, byte *payload, unsigned int length)
{
Serial.print("Message arrived on topic: ");
Serial.println(topic);
Serial.print("Message:");
for (int i = 0; i < length; i++)
{
    Serial.print((char)payload);
}
Serial.println();
}

void reconnect()
{
while (!client.connected())
{
    Serial.print("Attempting MQTT connection...");
    if (client.connect(clientId))
    {
      Serial.println(" connected");
    }
    else
    {
      Serial.print(" failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
}
}

void setup()
{
Serial.begin(115200);
while (!Serial)
    ;
Serial.println("Ready");

pinMode(FALL_DETECT_PIN, INPUT_PULLUP); // 新增:设置摔倒检测引脚为输入模式,并启用内部上拉电阻

WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
    delay(500);
    Serial.print(".");
}
Serial.println("\nConnected to the WiFi network");

client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
}

void loop()
{
if (!client.connected())
{
    reconnect();
}
client.loop();

// 检测摔倒
if (digitalRead(FALL_DETECT_PIN) == LOW) // 新增:如果摔倒检测引脚为低电平
{
    Serial.println("Fall detected!");
    // 发送摔倒信息
    client.publish(fall_topic, "Fall Detected"); // 新增:发布摔倒信息
    // 可以添加延迟或其他逻辑来避免重复发送
    delay(3000); // 等待一段时间,防止连续触发
}

delay(200);
}
```

软件部分代码:

```python
# 摔倒检测
@app.route('/fall_detection', methods=['POST'])
def fall():
    reply = {"result": "ok", "message": "success"}
    """
    {'publish_received_at': 1723569671836, 'pub_props': {'User-Property': {}}, 'peerhost': '39.85.60.209', 'qos': 0, 'topic': 't/fall_detection', 'clientid': 'mqttx_7492d77a', 'payload': '{
    \n"msg": "hello HTTP Server"\n}', 'username': 'undefined', 'event': 'message.publish', 'metadata': {'rule_id': 'fall_detection_WH_D'}, 'timestamp': 1723569671836, 'node': 'emqx@172.17.0.4', 'id': '00061F93D60912043249000A6ABB0002', 'flags': {'retain': True, 'dup': False}}
    """
    # 解析 device_id
    raw_data = request.get_json()
    print(raw_data)
    device_id = raw_data['clientid']
    device = DeviceInfo.query.filter_by(device_id=device_id).first()
    if device:
      if device.device_type == 'fall_detection':
            fall_detection_data = FallDetectionData(
                device_id=device_id,
                timestamp=datetime.utcnow(),
                user_id=device.user_id
            )
            db.session.add(fall_detection_data)
            db.session.commit()
            warning_record = WarningRecord(
                device_id=device_id,
                timestamp=datetime.utcnow(),
                user_id=device.user_id,
                warning_type=1
            )
            db.session.add(warning_record)
            db.session.commit()
            print('设备:', device_id, ' 触发了跌倒检测')
            socketio.emit('message', '设备:' + device_id + ' 触发了跌倒检测')
    return json.dumps(reply), 200

```

```javascript
<template>
        <view class="fall-analysis-container">
                <!-- 图表部分 -->
                <view class="charts-box">
                        <qiun-data-charts type="column" :opts="opts" :chartData="chartData" :canvas2d="true"
                                canvasId="fallDetectionChart" />
                </view>

                <!-- 表格标题 -->
                <view class="table-header">
                        <text class="table-title">跌倒详细记录</text>
                        <view class="filter-group">
                                <picker mode="date" :value="currentMonth" start="2024-01" end="2024-12" fields="month"
                                        @change="onMonthChange">
                                        <view class="picker">
                                                {{ currentMonth }} <text class="picker-arrow">▼</text>
                                        </view>
                                </picker>
                                <button class="export-btn" @tap="exportData">导出数据</button>
                        </view>
                </view>

                <!-- 表格部分 -->
                <view class="table-container">
                        <view class="table-head">
                                <view class="th">日期</view>
                                <view class="th">时间</view>
                                <view class="th">地点</view>
                                <view class="th">持续时间</view>
                                <view class="th">处理状态</view>
                        </view>

                        <scroll-view scroll-y="true" class="table-body">
                                <view v-for="(item, index) in fallRecords" :key="index" class="table-row"
                                        :class="{ 'row-even': index % 2 === 0 }">
                                        <view class="td">{{ item.date }}</view>
                                        <view class="td">{{ item.time }}</view>
                                        <view class="td">{{ item.location }}</view>
                                        <view class="td">{{ item.duration }}</view>
                                        <view class="td">
                                                <text :class="['status-tag', item.status === '已处理' ? 'status-resolved' : 'status-pending']">
                                                        {{ item.status }}
                                                </text>
                                        </view>
                                </view>
                        </scroll-view>

                        <!-- 分页器 -->
                        <view class="pagination">
                                <text class="page-btn" @tap="prevPage" :class="{ disabled: currentPage === 1 }">上一页</text>
                                <text class="page-current">{{ currentPage }}/{{ totalPages }}</text>
                                <text class="page-btn" @tap="nextPage" :class="{ disabled: currentPage === totalPages }">下一页</text>
                        </view>
                </view>

                <!-- 统计摘要 -->
                <view class="summary-box">
                        <view class="summary-item">
                                <text class="summary-label">本月跌倒总次数</text>
                                <text class="summary-value">{{ monthlyStats.totalFalls }}</text>
                        </view>
                        <view class="summary-item">
                                <text class="summary-label">平均处理时间</text>
                                <text class="summary-value">{{ monthlyStats.avgHandleTime }}</text>
                        </view>
                        <view class="summary-item">
                                <text class="summary-label">最常发生地点</text>
                                <text class="summary-value">{{ monthlyStats.mostLocation }}</text>
                        </view>
                </view>
        </view>
</template>

<script>
        export default {
                data() {
                        return {
                                chartData: {},
                                opts: {
                                        color: ["#FC8452"],
                                        padding: ,
                                        enableScroll: false,
                                        legend: {},
                                        xAxis: {
                                                disableGrid: true
                                        },
                                        yAxis: {
                                                data: [{
                                                        min: 0
                                                }]
                                        },
                                        extra: {
                                                column: {
                                                        type: "group",
                                                        width: 30,
                                                        activeBgColor: "#000000",
                                                        activeBgOpacity: 0.08
                                                }
                                        }
                                },
                                currentMonth: '2024-07',
                                currentPage: 1,
                                pageSize: 10,
                                fallRecords: [],
                                monthlyStats: {
                                        totalFalls: 0,
                                        avgHandleTime: '',
                                        mostLocation: ''
                                }
                        };
                },
                computed: {
                        totalPages() {
                                return Math.ceil(this.fallRecords.length / this.pageSize);
                        }
                },
                onReady() {
                        this.getInitialData();
                        this.getFallRecords();
                        this.calculateMonthlyStats();
                },
                methods: {
                        getInitialData() {
                                let res = {
                                        categories: ["2024-7-11", "2024-7-15", "2024-7-17"],
                                        series: [{
                                                name: "摔倒次数",
                                                data:
                                        }]
                                };
                                this.chartData = JSON.parse(JSON.stringify(res));
                        },

                        getFallRecords() {
                               
                        },

                        calculateMonthlyStats() {
                                // 计算月度统计数据
                                this.monthlyStats = {
                                        totalFalls: this.fallRecords.length,
                                        avgHandleTime: '4分钟',
                                        mostLocation: '卧室'
                                };
                        },

                        onMonthChange(e) {
                                this.currentMonth = e.detail.value;
                                this.currentPage = 1;
                                this.getFallRecords(); // 重新获取数据
                                this.calculateMonthlyStats();
                        },

                        prevPage() {
                                if (this.currentPage > 1) {
                                        this.currentPage--;
                                }
                        },

                        nextPage() {
                                if (this.currentPage < this.totalPages) {
                                        this.currentPage++;
                                }
                        },

                        exportData() {
                                uni.showToast({
                                        title: '数据导出中...',
                                        icon: 'loading'
                                });
                                // 实现导出逻辑
                        }
                }
        };
</script>

<style scoped>
        .fall-analysis-container {
                padding: 20rpx;
                background-color: #f5f7fa;
        }

        .charts-box {
                width: 100%;
                height: 300px;
                background-color: #fff;
                border-radius: 12rpx;
                margin-bottom: 20rpx;
        }

        .table-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 20rpx 0;
        }

        .table-title {
                font-size: 32rpx;
                font-weight: bold;
                color: #333;
        }

        .filter-group {
                display: flex;
                align-items: center;
                gap: 20rpx;
        }

        .picker {
                background-color: #fff;
                padding: 10rpx 20rpx;
                border-radius: 8rpx;
                font-size: 28rpx;
        }

        .picker-arrow {
                font-size: 24rpx;
                color: #666;
                margin-left: 10rpx;
        }

        .export-btn {
                font-size: 24rpx;
                padding: 10rpx 20rpx;
                background-color: #4a90e2;
                color: #fff;
                border-radius: 8rpx;
        }

        .table-container {
                background-color: #fff;
                border-radius: 12rpx;
                overflow: hidden;
        }

        .table-head {
                display: flex;
                background-color: #f8f9fa;
                border-bottom: 2rpx solid #eee;
        }

        .th {
                flex: 1;
                padding: 20rpx;
                font-size: 28rpx;
                font-weight: bold;
                color: #333;
                text-align: center;
        }

        .table-body {
                max-height: 600rpx;
        }

        .table-row {
                display: flex;
                border-bottom: 2rpx solid #eee;
        }

        .row-even {
                background-color: #f9fbfd;
        }

        .td {
                flex: 1;
                padding: 20rpx;
                font-size: 26rpx;
                color: #666;
                text-align: center;
        }

        .status-tag {
                padding: 4rpx 12rpx;
                border-radius: 4rpx;
                font-size: 24rpx;
        }

        .status-resolved {
                background-color: #e8f5e9;
                color: #4caf50;
        }

        .status-pending {
                background-color: #fff3e0;
                color: #ff9800;
        }

        .pagination {
                display: flex;
                justify-content: center;
                align-items: center;
                padding: 20rpx;
                gap: 20rpx;
        }

        .page-btn {
                padding: 10rpx 20rpx;
                font-size: 26rpx;
                color: #4a90e2;
        }

        .page-btn.disabled {
                color: #ccc;
        }

        .page-current {
                font-size: 26rpx;
                color: #666;
        }

        .summary-box {
                margin-top: 20rpx;
                display: flex;
                justify-content: space-between;
                background-color: #fff;
                padding: 20rpx;
                border-radius: 12rpx;
        }

        .summary-item {
                text-align: center;
        }

        .summary-label {
                font-size: 24rpx;
                color: #666;
                display: block;
                margin-bottom: 8rpx;
        }

        .summary-value {
                font-size: 32rpx;
                color: #333;
                font-weight: bold;
        }
</style>
```

## 六、总结

通过的MR60ADF毫米波雷达传感器,结合ESP32的联网能力,成功实现了一个可以检测人体摔倒的模块。如果有什么实现上的问题可以留言。








页: [1]
查看完整版本: 【2024 DigiKey创意大赛】- 基于毫米波雷达的生命体征检测及健康监护系统-摔倒检测