基于树莓派与Arduino的小鼠行为追踪物联网系统构建指南

基于树莓派与Arduino的小鼠行为追踪物联网系统构建指南 1. 项目概述一个高中生如何用开源硬件追踪小鼠行为作为一名热衷于用技术解决实际问题的创客我最近完成了一个让我自己都感到有点“疯狂”的生物行为学实验项目。起因很简单我对睡眠和昼夜节律Circadian Rhythm如何受光照影响产生了兴趣。教科书上说夜行性动物在持续光照下会产生压力进而影响其行为。我想为什么不自己动手验证一下呢于是一个最初只是“两个笼子两种灯光”的简单想法迅速演变成了一个集成了传感器网络、自动控制和数据记录的完整物联网IoT系统。这个项目的核心目标是构建一套能够自动、持续、非侵入式地追踪记录小鼠作为夜行性动物的模型运动与进食活动的系统并研究不同光照制度对其行为模式的影响。听起来像是实验室的活儿但我用的全是你能在创客社区找到的开源硬件Raspberry Pi树莓派作为大脑进行调度与数据记录Arduino Uno作为四肢负责实时采集传感器数据再配合一些常见的传感器模块。最终所有数据会被自动整理成CSV文件方便导入任何数据分析软件进行绘图和研究。如果你也对生物实验自动化、物联网传感器应用或者单纯想用技术手段观察小动物的生活感兴趣那么这个项目会为你提供一个非常详实的参考。它不仅是一套硬件搭建指南更包含了我从构思、踩坑到最终获得数据全过程的心得与教训。你会发现很多挑战并非来自高深的理论而是源于如何让不同的硬件、代码和现实中的小动物“和谐共处”。2. 系统整体设计与核心思路拆解在动手焊接第一根线之前理清整个系统的逻辑至关重要。我的设计思路可以概括为“感知-决策-记录”三层架构这几乎是所有物联网项目的通用范式只是在本项目中有了具体的生物学内涵。2.1 核心需求与方案选型我的实验需要对比两组小鼠对照组CTRL接受模拟自然的光照如每日4-6小时光照实验组EXP接受持续24小时光照。需要对它们进行以下几项监测总体活动量反映小鼠的清醒与活跃程度。进食行为记录进食次数并粗略估算进食量通过监测在食盆前停留的时长或次数。环境控制精确、自动地控制两组笼舍的灯光开关并允许灵活调整光照时间表。基于这些需求我做出了以下关键选择主控单元Raspberry Pi Arduino组合。为什么不只用ArduinoArduino擅长实时、不间断地读取传感器信号但它的文件系统操作、复杂定时任务如整点记录和运行图形界面GUI的能力较弱。而树莓派作为一台微型电脑完美胜任这些工作。因此分工如下Arduino充当“数据采集卡”以极高的频率毫秒级轮询所有传感器并将原始数据流通过串口发送树莓派充当“数据记录与指挥中心”接收串口数据解析、汇总、按小时记录到CSV文件并通过GUI设置灯光时间表再通过串口向Arduino发送控制指令。运动感知HC-SR501 PIR传感器。这是一种被动式红外热释电传感器价格低廉非常可靠。它能检测到生物体如小鼠移动时发出的红外热辐射变化。将其安装在笼子角落即可感知笼内大范围的活动。进食感知红外避障/距离传感器。我选用的是常见的3线反射式红外传感器。将其垂直安装在食盆上方通过调节其板载电位器设定一个约3厘米的触发距离。当小鼠抬头进食头部进入该区域时传感器输出电平变化从而记录一次“进食事件”。这比称重传感器更简单且能区分“靠近”和“真正在吃”的行为。灯光控制继电器模块 LED灯条。树莓派和Arduino的GPIO引脚驱动能力有限无法直接驱动大功率LED。因此使用继电器模块作为电子开关由微控制器的5V信号控制220V或12/24V电路的通断从而安全地控制LED灯条的亮灭。为什么选择串口通信这是连接Arduino与树莓派最经典、最稳定的方式。USB线既提供电力也建立串行通信通道无需复杂的网络配置延迟极低可靠性高非常适合这种传感器数据流传输。2.2 硬件系统架构图逻辑描述整个系统的信号与电力流向如下电源层一个24V开关电源因为我手头有这个为整个系统供电。通过多个降压模块Buck Converter转换为系统所需的各种电压24V转12V给LED灯条24V转5V给Arduino、树莓派、传感器和继电器线圈。感知层两个笼子各有一套相同的传感器1个PIR 1个红外距离传感器共4个传感器其信号线接入Arduino的数字输入引脚。控制层Arduino读取所有传感器状态。树莓派通过USB串口每秒多次向Arduino请求数据同时也会发送灯光控制指令。Arduino根据指令控制连接在相应数字引脚上的继电器模块。执行层继电器控制着通往两个笼子、两种亮度明亮/昏暗LED灯条的电路。数据层树莓派上的Python程序解析来自Arduino的数据包在GUI上实时显示计数器并在每个整点将累计数据如“过去一小时运动触发次数”写入mouse_activity_log.csv文件。这个架构的优势在于模块化和灵活性。传感器坏了可以单独更换灯光控制逻辑可以在Python代码中轻松修改数据记录格式也可以根据分析需求调整。3. 硬件搭建从笼舍到电路板的实操要点硬件部分是项目的基石也是最容易出“幺蛾子”的地方。我的原则是先独立测试每个模块再逐步集成。3.1 笼舍设计与建造的考量笼舍不是简单的容器它是实验的“舞台”必须满足科研的可控性与动物的福利要求。尺寸与材料我用了16″x24″x16″约40x60x40厘米的½英寸胶合板箱体。这个尺寸足以让3-5只小鼠舒适活动避免过度拥挤导致的行为异常或压力。胶合板易于加工且足够坚固。避光处理为了精确控制光照笼子必须严格避光。我在所有接缝处使用了木工胶和填缝剂内部涂上无光油漆对照组用白色以反射部分光线实验组用黑色以吸收光线减少反射干扰。顶盖也做了密封处理仅在喂食口和通风口处有可控开口。喂食器创新设计我不想每次添食都打扰小鼠。于是设计了一个带活动挡板的3D打印食盆。食盆可以从外部滑入通过磁铁吸附关闭。当抽出食盆添加食物时挡板会在磁力作用下自动关闭喂食口防止小鼠逃逸。红外距离传感器就集成在这个食盆支架的上方。传感器安装位置PIR传感器安装在笼子一个上方的角落视角覆盖大部分活动区域。注意PIR传感器对横向移动最敏感所以安装时要让探测方向平行于小鼠可能跑动的长边。红外距离传感器垂直固定在食盆正上方约8-10厘米处通过调节其背部的电位器使检测距离精确到食盆边缘约3厘米。这样只有当小鼠身体探入食盆上方时才会触发。实操心得传感器调试。在放入小鼠前务必用你的手或一个温热的物体模拟小鼠进行测试。调整PIR的灵敏度旋钮和延时旋钮我建议将跳线帽设置为“重复触发”模式这样在持续运动时会连续输出高电平确保响应灵敏且没有误触发如因环境温度缓慢变化引起的触发。红外距离传感器则用一个固定高度的物体反复测试直到触发指示灯在你需要的位置稳定亮起。3.2 电路集成与布线规范电路部分看起来杂乱但遵循清晰的规划就能避免混乱。供电是头等大事我使用了一块洞洞板作为底座。首先将24V主电源输入端固定好。然后安装4个降压模块两个输出12V分别给两个笼子的LED灯条供电两个输出5V一个给Arduino、树莓派和继电器控制端另一个专门给4个传感器供电避免电机或继电器动作时对敏感的传感器造成电压波动干扰。继电器连接每个继电器有3个接口常开NO、常闭NC、公共端COM。将LED灯条的正极线接到NO口COM口接12V电源正极灯条负极直接接12V电源负极。继电器的控制端信号线、VCC、GND则接入面包板由Arduino控制。传感器连接所有传感器都遵循“电源VCC/5V、地GND、信号OUT”三线制。将它们并联到专为传感器供电的5V电源轨上信号线则分别连接到Arduino的指定数字引脚如D2, D3, D4, D5。Arduino与树莓派连接用一根USB A to B线打印机线直接连接即可。树莓派通过USB为Arduino供电同时也建立了串口通信通道。注意事项抗干扰与稳定性。电源隔离为传感器单独供电是提升数据稳定性的关键一步。继电器吸合瞬间的电流冲击可能会在电源线上产生毛刺导致传感器误触发或Arduino复位。线缆整理使用扎带或线槽将电源线尤其是12V/24V与信号线分开捆扎减少电磁干扰。热熔胶固定在所有接线端子、降压模块和轻量元件上点一些热熔胶防止因振动或小鼠啃咬虽然线在笼外但以防万一导致脱落。但注意不要覆盖散热孔或可调电位器。4. 软件与代码实现让硬件“活”起来软件是项目的灵魂也是最考验耐心的部分。我的策略是“分而治之”先确保Arduino能正确读取传感器再确保树莓派能稳定接收数据并发送控制命令最后完善数据记录和用户界面。4.1 Arduino端高效的数据采集与响应Arduino的代码核心是一个快速的loop()函数它不断检查传感器状态并随时准备响应来自树莓派的指令。// 引脚定义必须与实际接线一致 const int PIR_Control_Pin 2; const int PIR_Experimental_Pin 3; const int IR_Control_Pin 4; const int IR_Experimental_Pin 5; // 继电器控制引脚 const int Light_Control_Pin 6; const int Light_Experimental_Pin 7; // 变量声明 int pirCtrlState 0, pirExpState 0; int irCtrlState 0, irExpState 0; String inputString ; // 用于接收串口指令 void setup() { Serial.begin(9600); // 初始化串口通信与Pi端波特率匹配 pinMode(PIR_Control_Pin, INPUT); pinMode(PIR_Experimental_Pin, INPUT); pinMode(IR_Control_Pin, INPUT); pinMode(IR_Experimental_Pin, INPUT); pinMode(Light_Control_Pin, OUTPUT); pinMode(Light_Experimental_Pin, OUTPUT); digitalWrite(Light_Control_Pin, LOW); // 初始状态关闭 digitalWrite(Light_Experimental_Pin, LOW); } void loop() { // 1. 读取所有传感器当前状态 pirCtrlState digitalRead(PIR_Control_Pin); pirExpState digitalRead(PIR_Experimental_Pin); irCtrlState digitalRead(IR_Control_Pin); irExpState digitalRead(IR_Experimental_Pin); // 2. 将数据打包成一个字符串通过串口发送 // 格式C_PIR:1,E_PIR:0,C_IR:1,E_IR:0 Serial.print(C_PIR:); Serial.print(pirCtrlState); Serial.print(,E_PIR:); Serial.print(pirExpState); Serial.print(,C_IR:); Serial.print(irCtrlState); Serial.print(,E_IR:); Serial.println(irExpState); // println 末尾添加换行符便于Pi端解析 // 3. 检查并处理来自树莓派的指令 while (Serial.available()) { char inChar (char)Serial.read(); if (inChar \n) { // 指令以换行符结束 processCommand(inputString); inputString ; } else { inputString inChar; } } delay(100); // 短暂延迟避免串口缓冲区溢出。100ms的间隔对于小鼠行为采样足够了。 } void processCommand(String cmd) { // 解析来自Pi的指令例如 LIGHT_CTRL_ON, LIGHT_CTRL_OFF if (cmd LIGHT_CTRL_ON) { digitalWrite(Light_Control_Pin, HIGH); Serial.println(ACK: CTRL Light ON); } else if (cmd LIGHT_CTRL_OFF) { digitalWrite(Light_Control_Pin, LOW); Serial.println(ACK: CTRL Light OFF); } // ... 处理其他灯光的指令 }这段代码的关键在于数据格式的约定。我定义了一个简单的键值对字符串格式如C_PIR:1这样在树莓派端可以用Python的字符串方法轻松分割和解析。同时Arduino也随时监听串口执行来自树莓派的灯光开关指令并返回确认信息ACK。4.2 树莓派端Python实现的数据管理与调度树莓派上的Python程序承担了核心业务逻辑我使用tkinter构建了一个简单的GUI并用pyserial库与Arduino通信。import serial import time import csv from datetime import datetime import threading import tkinter as tk from tkinter import ttk class MouseTrackerApp: def __init__(self, root): self.root root self.root.title(啮齿动物活动追踪系统) # 初始化串口连接 try: # 注意端口号可能是 /dev/ttyACM0 或 /dev/ttyUSB0 self.ser serial.Serial(/dev/ttyACM0, 9600, timeout1) time.sleep(2) # 等待Arduino重启 self.connection_status 已连接 except Exception as e: print(f无法打开串口: {e}) self.connection_status 未连接 self.ser None # 初始化数据计数器每小时清零 self.counters { ctrl_motion: 0, ctrl_eating: 0, exp_motion: 0, exp_eating: 0 } self.last_log_time datetime.now().replace(minute0, second0, microsecond0) # 创建GUI组件 self.setup_ui() # 启动后台线程持续读取串口数据 self.running True self.read_thread threading.Thread(targetself.read_serial_data, daemonTrue) self.read_thread.start() # 启动定时检查用于整点记录和灯光控制 self.root.after(1000, self.check_schedule) def setup_ui(self): # 状态显示区域 status_frame ttk.LabelFrame(self.root, text系统状态) status_frame.grid(row0, column0, padx10, pady10, stickyew) ttk.Label(status_frame, textfArduino: {self.connection_status}).pack() # 实时计数器显示 data_frame ttk.LabelFrame(self.root, text实时活动计数 (本小时)) data_frame.grid(row1, column0, padx10, pady10, stickyew) self.ctrl_motion_label ttk.Label(data_frame, text对照组运动: 0) self.ctrl_motion_label.pack() # ... 创建其他计数标签 # 灯光时间表设置 schedule_frame ttk.LabelFrame(self.root, text灯光时间表 (24小时制)) schedule_frame.grid(row0, column1, rowspan2, padx10, pady10, stickynsew) ttk.Label(schedule_frame, text对照组开灯时间:).grid(row0, column0) self.ctrl_on_entry ttk.Entry(schedule_frame) self.ctrl_on_entry.insert(0, 06:00) self.ctrl_on_entry.grid(row0, column1) # ... 创建其他时间输入框和按钮 # 手动控制按钮 control_frame ttk.LabelFrame(self.root, text手动灯光控制) control_frame.grid(row2, column0, columnspan2, padx10, pady10, stickyew) ttk.Button(control_frame, text对照组开灯, commandlambda: self.send_command(LIGHT_CTRL_ON)).pack(sidetk.LEFT) # ... 创建其他控制按钮 # 立即记录按钮 ttk.Button(self.root, text立即记录CSV, commandself.manual_log).grid(row3, column0, pady10) def read_serial_data(self): 在后台线程中持续读取并解析串口数据 while self.running and self.ser: try: if self.ser.in_waiting: line self.ser.readline().decode(utf-8).strip() if line.startswith(C_PIR): # 解析数据行例如: C_PIR:1,E_PIR:0,C_IR:1,E_IR:0 parts line.split(,) for part in parts: key, value part.split(:) if key C_PIR and value 1: self.counters[ctrl_motion] 1 elif key C_IR and value 1: self.counters[ctrl_eating] 1 # ... 更新其他计数器 # 更新GUI显示需要在主线程中操作 self.root.after(0, self.update_counters_display) except Exception as e: print(f读取串口数据错误: {e}) def check_schedule(self): 定时任务检查是否到达整点以及控制灯光 now datetime.now() # 整点记录逻辑 if now.minute 0 and now.second 0 and (now - self.last_log_time).total_seconds() 3600: self.log_to_csv() self.last_log_time now # 每小时清零计数器 for key in self.counters: self.counters[key] 0 # 灯光控制逻辑根据GUI设置的时间 current_time_str now.strftime(%H:%M) ctrl_on_time self.ctrl_on_entry.get() ctrl_off_time self.ctrl_off_entry.get() # ... 获取实验组时间 # 判断当前时间是否在设定的开灯时间段内并发送相应指令给Arduino # 这里需要实现一个时间区间判断的逻辑 # 每秒检查一次 self.root.after(1000, self.check_schedule) def log_to_csv(self): 将当前计数和灯光状态记录到CSV文件 filename mouse_activity_log.csv file_exists os.path.isfile(filename) with open(filename, a, newline) as csvfile: fieldnames [DateTime, Ctrl_Motion, Ctrl_Eating, Exp_Motion, Exp_Eating, Ctrl_Light_Schedule, Exp_Light_Schedule] writer csv.DictWriter(csvfile, fieldnamesfieldnames) if not file_exists: writer.writeheader() writer.writerow({ DateTime: datetime.now().strftime(%Y-%m-%d %H:%M:%S), Ctrl_Motion: self.counters[ctrl_motion], Ctrl_Eating: self.counters[ctrl_eating], Exp_Motion: self.counters[exp_motion], Exp_Eating: self.counters[exp_eating], Ctrl_Light_Schedule: f{self.ctrl_on_entry.get()}-{self.ctrl_off_entry.get()}, Exp_Light_Schedule: f{self.exp_on_entry.get()}-{self.exp_off_entry.get()} }) print(f数据已记录: {datetime.now()}) # ... 其他方法send_command, update_counters_display, manual_log等 if __name__ __main__: root tk.Tk() app MouseTrackerApp(root) root.mainloop()编程心得线程与GUI。串口读取是一个阻塞操作readline()会等待直到收到一行数据如果放在主线程即GUI事件循环中会导致界面卡死。因此必须使用threading模块创建一个后台线程专门负责读取串口。当需要更新GUI上的计数器时通过root.after()方法将更新操作投递到主线程队列中执行这是tkinter中线程安全的做法。4.3 系统部署与自动化运行我们希望树莓派开机后就能自动运行这个追踪程序无需手动操作。创建虚拟环境在树莓派上为项目创建一个独立的Python环境是个好习惯可以避免包版本冲突。cd ~ mkdir mice_tracker cd mice_tracker python3 -m venv venv source venv/bin/activate pip install pyserial将Python脚本和Arduino代码放入项目文件夹。设置开机自启动编辑树莓派的rc.local文件。sudo nano /etc/rc.local在exit 0这一行之前添加# 等待网络和系统服务就绪 sleep 10 # 切换到项目目录激活虚拟环境并运行程序 cd /home/pi/mice_tracker sudo -u pi /home/pi/mice_tracker/venv/bin/python /home/pi/mice_tracker/raspberry_pi_code.py 注意这里使用sudo -u pi以普通用户pi的身份运行避免权限问题。符号让程序在后台运行。5. 实验过程、数据解读与避坑实录硬件和软件就绪后真正的挑战才刚刚开始——如何从活体动物身上获得可靠、有说服力的数据。5.1 实验设计与意外频发的实操阶段我的初始设计是对照组每日4小时光照和实验组24小时持续光照。然而现实给我上了一课。动物来源与分组我购买了9只雌性小鼠意图是每组3只。但宠物店误混入1只雄性。这只雄性小鼠导致实验组的两只雌鼠怀孕彻底打乱了实验计划。怀孕和哺乳期的母鼠行为模式与普通成年鼠截然不同其数据无法用于简单的光照影响对比。“布朗鼠”事件我购买的9只鼠中有3只体型明显偏小的棕色鼠。它们异常安静几乎不活动在实验开始前就全部死亡。这很可能是因为它们是体质较弱的“淘汰”个体。教训实验动物应尽量选择健康、年龄和体重相近的个体最好来自可靠的实验室动物供应商而非宠物市场。母鼠杀婴在持续光照的压力下怀孕的母鼠产后表现出极度的焦虑和不适导致其杀死了大部分幼崽。这是啮齿类动物在极端压力下的常见行为但也导致了实验组个体数量的锐减和数据的中断。调整与应对面对这些混乱我不得不调整方案。我将幸存且状态稳定的2只小鼠重新分配确保每组仍有2只。同时我放弃了复杂的“明亮/昏暗”双灯光设计简化为简单的“开/关”控制。实验重点也转为更长时间地观察稳定状态下正常光照周期与持续光照对小鼠行为的差异。5.2 数据采集、分析与初步观察系统稳定运行数日后CSV文件中积累了大量的数据行。我使用Python的pandas和matplotlib库进行初步分析。import pandas as pd import matplotlib.pyplot as plt from datetime import datetime # 读取数据 df pd.read_csv(mouse_activity_log.csv) df[DateTime] pd.to_datetime(df[DateTime]) df.set_index(DateTime, inplaceTrue) # 按小时绘制对照组运动活动趋势 ctrl_motion_hourly df[Ctrl_Motion].resample(H).sum() # 按小时汇总 plt.figure(figsize(12, 6)) plt.plot(ctrl_motion_hourly.index, ctrl_motion_hourly.values, markero, label对照组运动) plt.xlabel(日期时间) plt.ylabel(运动触发次数/小时) plt.title(对照组小鼠每小时运动活动量) plt.legend() plt.grid(True, linestyle--, alpha0.7) plt.xticks(rotation45) plt.tight_layout() plt.show() # 对比分析提取光照周期内的数据 # 假设对照组光照时间为 06:00-12:00 df[Hour] df.index.hour df[In_Light_Period] (df[Hour] 6) (df[Hour] 12) light_period_avg df[df[In_Light_Period]][Ctrl_Motion].mean() dark_period_avg df[~df[In_Light_Period]][Ctrl_Motion].mean() print(f光照期间平均运动次数/小时: {light_period_avg:.2f}) print(f黑暗期间平均运动次数/小时: {dark_period_avg:.2f})初步观察结果基于相对稳定的对照组数据光照期反应在每天早晨6点开灯后小鼠活动量会出现一个明显的瞬时峰值这可能是对光线变化的警觉反应。但峰值过后活动量迅速下降在接下来的光照小时内维持在较低水平。黑暗期活跃关灯后12点之后小鼠活动量开始稳步上升在夜间如下午6点到次日凌晨达到活跃高峰这与它们夜行性的天性相符。进食与运动的相关性在正常光照周期下进食事件的高发期与运动活跃期高度重合主要集中在黑暗阶段。持续光照的影响将对照组小鼠临时置于24小时光照下观察其活动节律变得紊乱原有的活跃高峰消失活动分布变得平缓且不可预测。进食时间也变得分散与运动量的相关性减弱。这表明持续光照确实干扰了其内在的生物钟。数据分析心得关注“节律”而非绝对值。单个数据点如一小时的触发次数受偶然因素影响很大比如小鼠恰好长时间在传感器前理毛。更有价值的是观察24小时周期内的活动模式。将多天的数据按小时对齐后求平均可以得到一个更清晰的“日活动模式图”这比看某一天的波动更有说服力。5.3 常见问题排查与优化技巧在项目过程中我遇到了无数小问题以下是其中一些典型问题的解决方案问题现象可能原因排查与解决步骤树莓派无法识别Arduino1. USB线或端口故障2. 串口权限问题3. Arduino未正确供电或损坏1. 换USB线或端口在终端输入ls /dev/tty*查看是否有ttyACM0或ttyUSB0出现。2. 将当前用户加入dialout组sudo usermod -a -G dialout $USER然后重启。3. 检查Arduino上的电源指示灯是否亮起。传感器数据全为0或恒定不变1. 传感器供电错误电压不对或正负极接反2. 信号线接触不良或接错引脚3. 传感器本身损坏或模式设置错误如PIR在“单次触发”模式1. 用万用表测量传感器VCC和GND间电压是否为5V。2. 重新插拔杜邦线确认代码中引脚定义与实际接线一致。3. 单独测试传感器将其OUT脚接到Arduino并打开串口监视器观察数值变化。检查PIR跳线帽是否在“H”重复触发模式。继电器有吸合声但灯不亮1. 继电器输出端接线错误常开/常闭接反2. 负载LED灯条功率超过继电器额定值3. 外部电源如12V未接通或损坏1. 用万用表通断档测量继电器吸合时COM和NO口是否导通。2. 计算LED灯条电流功率/电压确保小于继电器触点容量通常10A。3. 检查12V电源适配器是否正常工作。CSV文件记录时间错乱或重复1. 系统时区未设置2.check_schedule函数逻辑有误导致一小时多次触发记录3. 程序被重复启动1. 在树莓派上运行sudo raspi-config设置正确时区。2. 在日志函数开头加入打印语句检查触发条件。确保使用(now - self.last_log_time).total_seconds() 3600防止整点前后一秒内多次触发。3. 检查开机自启动脚本确保只启动一次。可以用 ps auxGUI界面卡死或无响应1. 串口读取阻塞了主线程2. 在非主线程中直接操作了Tkinter组件1.必须将串口读取放在单独的线程中。2. 所有更新GUI的操作如label.config(text...)都必须通过root.after()或设置线程安全变量再通知主线程的方式执行。最后的建议这个项目最大的收获不是得出了某个确切的科学结论而是完整地体验了从问题定义、方案设计、硬件实现、软件开发到数据处理的全过程。每一个环节都可能会出错耐心调试和记录无论是代码注释还是实验日志至关重要。对于想复现或改进此项目的朋友我建议可以先从一个笼子、一个传感器开始把数据流跑通再逐步增加复杂度。生物学实验变量众多技术只是辅助我们更精确地观察世界的手段而对生命本身的尊重与严谨的实验设计才是获得有意义结果的前提。