【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]