1. 从零开始:为什么选择在Edison上玩Python?
如果你手头有一块Intel Edison开发板,并且对编程有点兴趣,那你可能已经尝试过用Arduino IDE写写C++来控制GPIO,让LED灯闪烁。这很酷,但玩久了,你可能会觉得有点“束手束脚”——每次想加个复杂点的功能,比如从网上获取天气数据,或者处理一张图片,用C++来实现不仅代码量大,调试起来也费劲。这时候,就该Python登场了。
我最初接触Edison上的Python,纯粹是因为一个“偷懒”的想法。当时我需要让Edison定时从某个传感器读取数据,然后打包通过Wi-Fi发送到我的服务器,最后还要在本地存个日志。如果用传统的嵌入式开发流程,光是处理网络请求和文件I/O,就得写不少底层代码。而Python,以其“自带电池”的哲学,用几行requests和json库就能搞定网络和数据处理,用open()和time就能轻松管理日志,这效率提升不是一点半点。更重要的是,Python的交互式特性(REPL)允许你一行行代码地测试,这对于硬件调试来说简直是福音——你可以实时读取传感器数值,调整逻辑,而不用经历“修改-编译-上传-重启”的漫长循环。
所以,在Edison上初识Python,绝不仅仅是多学一门语言。它实际上是为这块功能强大但编程体验相对原始的硬件,打开了一扇通往现代软件开发世界的大门。你将能用更少的代码,更快地实现更复杂的功能,把精力从“如何实现”更多地转移到“实现什么”上。无论你是想做个智能家居网关、一个数据采集节点,还是一个简单的网络服务器,Python都能让这件事变得轻松愉快。
2. 搭建战场:为Edison准备Python开发环境
在电脑上装Python,去官网下载安装包一路Next就行。但在Edison上,情况稍有不同。Edison运行的是基于Yocto Project构建的Linux系统,我们通常通过SSH连接到它的终端进行操作。因此,环境搭建分为两大步:一是在Edison系统本身安装或确认Python环境;二是在你的开发电脑上配置一个舒适的“遥控”开发环境。
2.1 Edison本机:检查与安装Python
首先,用串口终端或者SSH连接到你的Edison。连接成功后,第一件事就是看看系统自带了什么。
python --version python3 --version大概率你会发现,系统已经自带了Python 2.7和Python 3.5(或类似版本)。Python 2.7已经退休,新项目绝对不要用。我们的焦点是Python 3。如果python3命令存在,那么恭喜,基础环境已经有了。如果只有python指向3.x版本,也可以,但为了清晰,后续我们都用python3。
如果系统没有Python 3,或者版本太旧(比如低于3.6),我们就需要手动安装。Edison的包管理器是opkg。更新软件源并安装Python 3:
opkg update opkg install python3 python3-pip这里python3-pip是Python的包管理工具,非常重要,后面安装第三方库全靠它。安装完成后,再次用python3 --version和pip3 --version确认。
注意:Edison的存储空间有限(特别是早期型号),安装太多包可能会占满空间。一个建议是,将
pip的缓存目录和全局安装目录指向挂载的SD卡或U盘(如果有的话)。可以通过环境变量PIP_TARGET和PIP_CACHE_DIR来设置。
2.2 开发机环境:选择你的“指挥所”
你肯定不会直接在Edison那个小小的终端里写代码。最佳实践是在你的电脑(Windows, macOS, Linux均可)上写好代码,然后通过工具同步或直接在Edison上运行。这里有几个主流方案:
方案一:VS Code + Remote-SSH(推荐)这是目前最强大、最舒适的方案。VS Code是一个免费的代码编辑器,通过安装“Remote - SSH”扩展,你可以将它变成一个Edison的远程开发终端。
- 在电脑上安装VS Code。
- 安装“Remote - SSH”扩展。
- 配置SSH连接信息(Edison的IP地址、用户名、密码)。
- 连接后,VS Code会在Edison上自动安装一个轻量级服务端,然后你就可以像操作本地文件夹一样,浏览、编辑Edison上的文件,并使用集成终端直接运行命令。语法高亮、代码提示、调试等功能一应俱全。这相当于把你的专业IDE直接“投射”到了Edison上。
方案二:PyCharm Professional + 远程解释器PyCharm是专业的Python IDE,其专业版支持配置远程Python解释器。你可以将解释器设置为Edison上的python3,并将项目文件自动同步到Edison。功能非常强大,但PyCharm专业版是付费软件。
方案三:SFTP + 你喜欢的本地编辑器如果你习惯使用Sublime Text、Atom或Notepad++等编辑器,可以配合一个SFTP插件(如SFTP for Sublime)。你可以在本地编辑文件,保存后插件自动将文件上传到Edison的指定目录。然后在另一个SSH终端里手动执行脚本。这种方式经济实惠,但体验上不如前两者集成度高。
我个人强烈推荐方案一(VS Code + Remote-SSH)。它几乎零成本,提供了近乎本地的开发体验,并且能很好地管理远程文件,是入门和进阶的首选。
2.3 第一个仪式:点亮LED灯
环境搭好了,总要有点仪式感。让我们用Python完成在嵌入式世界的“Hello World”——点亮板载LED。
Edison的GPIO操作不像树莓派有专门的RPi.GPIO库那么直接。一种常见的方法是使用mraa库,这是Intel为旗下嵌入式平台提供的硬件抽象库。首先在Edison上安装它:
pip3 install mraa如果pip安装失败,可以尝试用opkg:
opkg install mraa python3-mraa然后,创建一个名为blink.py的文件。你需要先找到LED对应的GPIO引脚编号。对于Edison Arduino扩展板上的LED,通常对应GPIO 13(但不同版本可能不同,请以你的板子手册为准)。我们假设是GPIO 13。
#!/usr/bin/env python3 import mraa import time # 初始化GPIO 13 为输出模式 led_pin = mraa.Gpio(13) led_pin.dir(mraa.DIR_OUT) print("开始LED闪烁... 按 Ctrl+C 停止") try: while True: led_pin.write(1) # 输出高电平,LED亮 time.sleep(0.5) # 等待0.5秒 led_pin.write(0) # 输出低电平,LED灭 time.sleep(0.5) except KeyboardInterrupt: print("\n程序终止") led_pin.write(0) # 确保退出时LED熄灭在Edison上运行这个脚本:
python3 blink.py你应该能看到LED开始规律地闪烁。按下Ctrl+C可以停止程序。这段代码虽然简单,但它验证了你的Python环境、mraa库以及最基本的硬件控制能力都工作正常。从这一刻起,Edison在你手中就从一块需要复杂配置的硬件,变成了一个可以用高级语言灵活操控的智能终端。
3. 跨越鸿沟:连接Python与C/C++的世界
你可能会问,Edison本身性能不算顶级,一些对实时性要求极高的操作(比如精确的脉冲计数)或者已有的C/C++库,用Python会不会力不从心?这是个非常好的问题。Python的优势在于开发效率,而非执行速度或硬件直接操作能力。解决这个矛盾,正是Python在嵌入式领域成熟应用的体现。通常有两种策略:一是使用优化过的本地库(如mraa本身就是C库的Python绑定),二是让Python和C/C++模块协同工作。
3.1 理解“绑定”:以mraa库为例
当我们import mraa时,我们导入的并不是纯Python代码。mraa是一个Python“绑定”(binding)。它的核心逻辑是用C/C++编写的,编译成了Edison系统可以高效执行的二进制共享库(如libmraa.so)。Python层只是一个薄薄的“外壳”,负责将Python的函数调用和数据结构,转换成C语言能理解的格式,然后调用底层的C函数。执行完后再将结果转换回Python对象。
这样做的好处显而易见:
- 性能:关键的数字运算和硬件寄存器操作由高效的C代码完成,速度有保障。
- 功能:可以直接利用现有的、成熟的C语言硬件驱动和中间件。
- 安全:复杂的底层内存和硬件操作被封装在C模块中,Python代码更安全。
所以,你在Edison上使用Python做硬件交互,本质上是站在了像mraa这样优秀的C语言库的肩膀上。你的Python脚本是“指挥官”,而mraa这样的库就是忠实高效的“执行者”。
3.2 扩展边界:自己动手做Python C扩展
如果现成的库不能满足需求,比如你有一个用C写好的、处理特定传感器的算法,想直接在Python里调用,该怎么办?你可以为它创建一个Python C扩展模块。
这个过程比想象中要规范。假设我们有一个简单的C函数,用于计算两个整数的和:
// mymath.c int add(int a, int b) { return a + b; }为了让Python能调用它,我们需要按照Python C API的格式,编写一个包装层:
// mymathmodule.c #include <Python.h> // 这是对C函数add的包装 static PyObject* mymath_add(PyObject* self, PyObject* args) { int a, b, result; // 解析从Python传过来的两个整数参数 if (!PyArg_ParseTuple(args, "ii", &a, &b)) { return NULL; // 如果解析失败,返回NULL触发Python异常 } result = add(a, b); // 将C的int结果转换为Python的int对象并返回 return PyLong_FromLong(result); } // 模块的方法定义 static PyMethodDef MyMathMethods[] = { {"add", mymath_add, METH_VARARGS, "Add two integers."}, {NULL, NULL, 0, NULL} // 哨兵,表示结束 }; // 模块定义 static struct PyModuleDef mymathmodule = { PyModuleDef_HEAD_INIT, "mymath", // 模块名 NULL, // 模块文档 -1, MyMathMethods }; // 模块初始化函数 PyMODINIT_FUNC PyInit_mymath(void) { return PyModule_Create(&mymathmodule); }接下来,你需要编写一个setup.py文件,用distutils或setuptools来编译这个扩展:
# setup.py from setuptools import setup, Extension module = Extension('mymath', sources=['mymathmodule.c', 'mymath.c']) setup(name='MyMath', version='1.0', description='A simple C extension example', ext_modules=[module])在Edison上,进入该目录,运行:
python3 setup.py build_ext --inplace这会在当前目录生成一个mymath.cpython-3Xm-edison-linux-gnu.so(名字可能略有不同)的动态库文件。现在,你就可以在Python中像导入普通模块一样使用它了:
import mymath print(mymath.add(5, 7)) # 输出 12实操心得:为Edison交叉编译C扩展可能会遇到工具链问题。更简单的方法是直接在Edison上编译。虽然Edison的CPU编译速度慢,但对于小型模块完全可以接受。这避免了复杂的交叉编译环境配置。确保Edison上已安装
python3-dev或类似包(opkg install python3-dev),它包含了编译所需的头文件和库。
3.3 备选方案:使用Ctypes或CFFI
如果你觉得编写完整的C扩展模块太复杂,还有更轻量级的方案:ctypes(Python标准库)和CFFI(第三方库)。它们允许你直接调用已编译好的C共享库(.so文件)中的函数。
例如,假设你将之前的add函数编译成了独立的共享库libmymath.so:
// mymath.c int add(int a, int b) { return a + b; }编译:gcc -shared -o libmymath.so mymath.c
然后在Python中使用ctypes调用:
import ctypes # 加载共享库 lib = ctypes.CDLL('./libmymath.so') # 指定函数参数和返回类型 lib.add.argtypes = (ctypes.c_int, ctypes.c_int) lib.add.restype = ctypes.c_int # 调用函数 result = lib.add(5, 7) print(result) # 输出 12CFFI的语法更接近Python,功能也更强大。这两种方式特别适合集成现有的、已经编译好的C库,你不需要去修改它的源代码。
4. 项目实战:构建一个温湿度监测Web服务
理论学习再多,不如动手做一个项目。我们来构建一个实用的系统:用Edison连接温湿度传感器(如DHT11/DHT22),实时读取数据,并提供一个简单的Web页面来展示当前数据和历史曲线。这个项目会串联起GPIO控制、传感器读取、Web框架、数据可视化等多个知识点。
4.1 硬件连接与传感器驱动
我们以DHT22为例。将DHT22的VCC接Edison的5V或3.3V(看传感器型号),GND接GND,DATA引脚接一个GPIO口(例如GPIO 4)。
Python读取DHT22需要专门的库,因为它的通信协议比较特殊。一个常用的库是Adafruit_DHT,但它在Edison上直接安装可能有问题。我们可以使用一个更通用的、纯Python实现的库dht11(同样适用于DHT22),或者使用mraa的示例代码。这里我们选择使用一个经过验证的、基于mraa的简单实现。
首先,确保安装了mraa。然后创建一个sensor_reader.py文件:
#!/usr/bin/env python3 import mraa import time class DHT22: def __init__(self, pin): self.pin = pin self.gpio = mraa.Gpio(pin) self.gpio.dir(mraa.DIR_IN) # 初始化为输入 def read(self): # DHT22的读取逻辑比较复杂,需要精确的时序控制 # 这里是一个简化的示意流程,实际应用建议使用成熟的库 # 例如:https://github.com/adafruit/Adafruit_Python_DHT # 但Adafruit库可能需要针对Edison的架构进行交叉编译 # 伪代码:发送开始信号 -> 读取40位数据 -> 校验 # ... # 如果成功,返回湿度和温度 # humidity, temperature = ... # return humidity, temperature # 为了演示,我们这里返回模拟数据 import random humidity = 45.0 + random.uniform(-2, 2) temperature = 23.5 + random.uniform(-1, 1) return humidity, temperature # 使用示例 if __name__ == '__main__': sensor = DHT22(4) # 假设DATA接在GPIO 4 try: while True: humi, temp = sensor.read() print(f"湿度: {humi:.1f}% 温度: {temp:.1f}°C") time.sleep(2) except KeyboardInterrupt: print("传感器读取停止")重要提示:在实际项目中,强烈建议寻找一个能在Edison上运行的、稳定的DHT库。你可以尝试用
pip3 install Adafruit_DHT安装,如果失败,可能需要从源码编译。编译时可能需要指定平台参数。这是一个常见的坑点:许多流行的Python硬件库默认针对树莓派优化,在Edison上需要额外工作。一个可行的替代方案是使用像Seeed这类硬件厂商为Edison提供的专用Python库包。
4.2 搭建微型Web服务器
为了在浏览器里查看数据,我们需要一个Web服务器。Python有非常轻量级的Web框架,比如Flask。首先在Edison上安装Flask:
pip3 install flask然后创建一个web_server.py文件:
#!/usr/bin/env python3 from flask import Flask, render_template_string, jsonify import threading import time import random # 暂时用随机数模拟传感器数据 app = Flask(__name__) # 全局变量存储最新传感器数据 current_data = {'humidity': 50.0, 'temperature': 25.0} history_data = [] # 存储历史数据用于绘图 MAX_HISTORY = 100 def sensor_loop(): """模拟传感器数据读取线程""" global current_data, history_data while True: # 在实际项目中,这里调用真实的sensor.read() humi = 45.0 + random.uniform(-3, 3) temp = 23.0 + random.uniform(-2, 2) current_data['humidity'] = humi current_data['temperature'] = temp # 记录历史数据 timestamp = time.time() * 1000 # 转换为JavaScript用的毫秒时间戳 history_data.append({'time': timestamp, 'humi': humi, 'temp': temp}) if len(history_data) > MAX_HISTORY: history_data.pop(0) # 保持列表长度 time.sleep(5) # 每5秒读取一次 @app.route('/') def index(): """返回主页面""" html_template = ''' <!DOCTYPE html> <html> <head> <title>Edison环境监测</title> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <style>body { font-family: sans-serif; margin: 20px; } .data { font-size: 2em; margin: 20px 0; }</style> </head> <body> <h1>实时环境数据</h1> <div class="data"> 温度: <span id="temp">--</span> °C<br> 湿度: <span id="humi">--</span> % </div> <div> <canvas id="chart" width="800" height="400"></canvas> </div> <script> const ctx = document.getElementById('chart').getContext('2d'); const chart = new Chart(ctx, { type: 'line', data: { datasets: [{ label: '温度 (°C)', borderColor: 'rgb(255, 99, 132)', data: [] }, { label: '湿度 (%)', borderColor: 'rgb(54, 162, 235)', data: [] }] }, options: { scales: { x: { type: 'time' } } } }); function updateData() { fetch('/data') .then(r => r.json()) .then(data => { document.getElementById('temp').textContent = data.current.temperature.toFixed(1); document.getElementById('humi').textContent = data.current.humidity.toFixed(1); // 更新图表 chart.data.datasets[0].data = data.history.map(p => ({x: p.time, y: p.temp})); chart.data.datasets[1].data = data.history.map(p => ({x: p.time, y: p.humi})); chart.update('none'); }); } setInterval(updateData, 2000); // 每2秒更新一次 updateData(); // 立即执行一次 </script> </body> </html> ''' return render_template_string(html_template) @app.route('/data') def get_data(): """提供JSON格式的当前数据和历史数据""" return jsonify({ 'current': current_data, 'history': history_data }) if __name__ == '__main__': # 启动传感器数据模拟线程 sensor_thread = threading.Thread(target=sensor_loop, daemon=True) sensor_thread.start() # 启动Flask服务器 # host='0.0.0.0' 使得服务器可以从外部网络访问 # debug=True 仅用于开发,生产环境应关闭 app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)这个服务器做了几件事:
- 启动一个后台线程,定期更新(模拟)传感器数据。
- 提供根路由
/,返回一个包含简单HTML、JavaScript和Chart.js库的页面。这个页面会动态显示当前温湿度,并绘制历史曲线图。 - 提供
/data路由,以JSON格式返回最新的数据和历史记录,供前端页面通过JavaScript定时抓取。
4.3 运行与访问
在Edison上运行这个服务器:
python3 web_server.py你会看到输出类似:* Running on http://0.0.0.0:5000/。
现在,在你的电脑浏览器中,输入http://[你的Edison的IP地址]:5000(例如http://192.168.1.105:5000),就能看到一个实时更新的温湿度监测仪表盘了。
踩坑实录:Flask默认是单线程的,如果在处理一个请求时被阻塞(比如读取一个响应很慢的传感器),其他所有请求都会被卡住。这就是为什么我们要把传感器读取逻辑放在一个独立的线程中。另外,
app.run()的threaded=True参数也很重要,它让Flask能够处理并发请求。在生产环境中,你可能需要使用更专业的WSGI服务器,如gunicorn,来替代Flask自带的开发服务器。
5. 效率提升与生产部署要点
当你的Edison Python项目从玩具变成需要长期运行的服务时,你会遇到一些新问题:如何让脚本开机自启?如何管理依赖?如何记录日志以便排查问题?如何打包分发你的应用?
5.1 进程管理与开机自启
在Linux系统中,让一个Python脚本在后台运行并开机自启,有多种方式。
简单后台运行: 使用nohup命令可以让进程在终端关闭后继续运行,并将输出重定向到文件。
nohup python3 my_web_server.py > server.log 2>&1 &nohup:忽略挂断信号。> server.log:将标准输出重定向到server.log文件。2>&1:将标准错误也重定向到标准输出(即同一个日志文件)。&:在后台运行。 查看进程可以用ps aux | grep python,停止进程可以用kill [PID]。
使用systemd(推荐): 这是现代Linux发行版标准的服务管理方式。它功能强大,可以管理进程的生命周期、自动重启、收集日志等。
- 创建一个服务文件,例如
/etc/systemd/system/edison-monitor.service:[Unit] Description=Edison Environment Monitor Service After=network.target [Service] Type=simple User=root WorkingDirectory=/home/root/my_project ExecStart=/usr/bin/python3 /home/root/my_project/web_server.py Restart=on-failure RestartSec=10 StandardOutput=syslog StandardError=syslog SyslogIdentifier=edison-monitor [Install] WantedBy=multi-user.target - 让systemd重新加载配置:
systemctl daemon-reload - 启动服务:
systemctl start edison-monitor - 设置开机自启:
systemctl enable edison-monitor - 查看服务状态和日志:
systemctl status edison-monitor,journalctl -u edison-monitor -f
使用systemd,你的应用就成为了一个真正的系统服务,管理起来非常方便。
5.2 依赖管理与虚拟环境
直接在Edison的系统Python中安装所有依赖,时间长了会混乱不堪。最佳实践是使用虚拟环境(virtual environment)。虚拟环境可以为每个项目创建一个独立的Python运行环境,包含独立的解释器和包库,互不干扰。
在Edison上创建和使用虚拟环境:
# 安装虚拟环境工具(如果未安装) pip3 install virtualenv # 进入你的项目目录 cd /home/root/my_project # 创建虚拟环境,环境目录名为`venv` python3 -m virtualenv venv # 激活虚拟环境 source venv/bin/activate # 激活后,命令行提示符前通常会出现 (venv) # 在虚拟环境中安装项目依赖 (venv) pip3 install flask mraa # 运行你的脚本 (venv) python3 web_server.py # 退出虚拟环境 deactivate将项目依赖记录到requirements.txt文件中:
(venv) pip3 freeze > requirements.txt这个文件包含了所有包及其精确版本。在新的环境(比如另一台Edison)中,只需要复制项目代码和这个文件,然后创建虚拟环境并运行pip3 install -r requirements.txt,就能一键还原所有依赖。
5.3 日志记录与问题排查
print()语句在开发时很好用,但对于长期运行的服务,一个结构化的日志系统是必须的。Python内置的logging模块非常强大。
修改你的web_server.py,加入日志功能:
import logging from logging.handlers import RotatingFileHandler # 配置日志 log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') # 创建一个滚动文件处理器,单个文件最大1MB,最多保留5个备份 file_handler = RotatingFileHandler('monitor.log', maxBytes=1e6, backupCount=5) file_handler.setFormatter(log_formatter) file_handler.setLevel(logging.INFO) # 创建一个控制台处理器 console_handler = logging.StreamHandler() console_handler.setFormatter(log_formatter) console_handler.setLevel(logging.DEBUG) # 获取根logger,并添加处理器 logger = logging.getLogger() logger.setLevel(logging.DEBUG) # 设置最低日志级别 logger.addHandler(file_handler) logger.addHandler(console_handler) # 在代码中使用日志 logger.info("服务器启动中...") try: humi, temp = sensor.read() logger.info(f"读取传感器数据成功: 温度{temp}, 湿度{humi}") except Exception as e: logger.error(f"读取传感器失败: {e}", exc_info=True) # exc_info=True会打印异常堆栈这样,日志会同时输出到控制台和monitor.log文件。文件达到1MB后会自动滚动,最多保留5个历史文件,避免了日志无限膨胀占满存储空间。通过查看日志文件,你可以清晰地了解服务的运行状态和任何错误信息。
5.4 性能考量与简单优化
Edison的Atom CPU和有限的内存,要求我们对资源使用保持敏感。
- 避免阻塞主线程:像我们之前做的,将可能耗时的I/O操作(传感器读取、网络请求)放入单独的线程或使用异步编程(如
asyncio)。 - 谨慎使用全局变量和大的数据结构:及时清理不再需要的数据,特别是历史数据列表,可以设置一个上限。
- 优化循环:在
while True循环中,使用time.sleep()来避免CPU空转。睡眠时间根据实际需要设定,数据更新不需要毫秒级时,睡眠几秒可以大幅降低CPU占用。 - 选择性导入:在函数内部导入非必要的模块,而不是在文件顶部全部导入,可以加快启动速度(虽然对运行期影响不大)。
- 考虑使用PyPy:如果项目是计算密集型,可以尝试为Edison编译安装PyPy解释器。PyPy的JIT编译器有时能显著提升纯Python代码的执行速度,但需要注意其对C扩展模块的兼容性。
从点亮一个LED,到构建一个完整的Web服务,Python让Edison的潜力得到了极大的释放。它降低了嵌入式开发的门槛,让开发者能更专注于逻辑和创新。当然,这条路也并非毫无荆棘,库的兼容性、系统的限制、性能的边界都需要你在实践中去摸索和平衡。但正是这些挑战和解决它们的过程,构成了学习与创造的乐趣。希望这篇长文能成为你Edison Python之旅的一块坚实垫脚石。