用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

译者 | 陈峻

如今,市场上的智能监控摄像头林林总总,它们往往对我们来说是一种看家护院的黑匣子,我们无法知晓其内部的工作机制。如果我们想一探究竟,则需要利用物联网的相关知识,去自行搭建监控系统。下面,我将从客户端、仪表板UI、以及服务器端等方面,从硬件组装和软件部署入手,和您深入讨论如何构建一个物联网安全摄像头。

1.构建的目标

我们希望新创建的家用智能摄像头监控系统,能够实现如下四个方面:1. 通过运动检测模块,系统会对检测到的运动物体进行拍照。2. 可将图像保存到远程服务器上。3. 通过访问服务器的仪表板,我们可以查看所有事件,包括照片和时间戳。4. 以滑动窗口的形式,保存最近20个事件,并清理所有旧的事件。

2.需要哪些组件?

硬件

一个Raspberry Pi树莓派) 4、一个运动检测传感器、一个摄像头模块,以及如下物料清单BOM)中的各种小组件。

软件

  • 一个用于部署、保存和显示摄像头图像的服务器端Render.com帐户。
  • Gitpython 3和一个代码编辑器。

3.配置Raspberry Pi

第1步:为Raspberry Pi插入可靠的电源。最好使用BOM中指定的官方版本。毕竟有消息称,一些旧的Raspberry Pi 4型号存在着一些USB-C电缆和电源的适配问题。

第2步:安装Raspberry Pi OS(树莓派操作系统)。从官网上获取相关的指南和工具,包括如何使用SD卡等。

由于并不需要图形化界面,因此我们可以安装仅供专家使用的Raspberry Pi OS Lite版本。不过,如果您是首次使用Raspberry Pi进行开发的话,也可选用带有桌面的64 位版本的Raspberry Pi OS。

第3步:测试PIR运动传感器,以检测和捕捉房间中的运动物体。注意,传感器上有三根线,其中两根用于电源( 5V和接地),第三根用于从传感器读取数值,即:如果传感器检测到移动,就读取1,否则读取0。请使用pinout命令,查看Raspberry每个引脚的完整说明。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

在本例中,我们使用一根黑线将传感器的地线,连接到电路板的地线(PIN 6)上,一根红线连接到 5V(PIN 2)上,并将信号线连接到其中一个GPIO(PIN 11)上。下面的两张图像展示了组装的效果,当然,如果您不知道哪根线缆应当对应哪里的话,请取下传感器上的盖子,并仔细检查PCB上的标签。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

4.检测运动

为了检测运动,我们需要通过软件来读取PIR的数值,并发送通知。GitHub上的 Python版本提供了针对此类应用的简单版本,请参考如下代码段:

Python

from gpiozero import MotionSensorfrom datetime import datetimefrom signal import pausepir = MotionSensor(17)def capture():timestamp = datetime.now().isoformat()print('%s Detected movement' % timestamp)def not_moving():timestamp = datetime.now().isoformat()print('%s All clear' % timestamp)pir.when_motion = capturepir.when_no_motion = not_movingpause()1.2.3.4.5.6.7.8.9.10.11.12.13.

注意,由于我们的运动检测器已插入GPIO17(尽管在物理板上,它对应的是引脚11),因此我们将17的值传递给MotionSensor(),并通过运行python pir_motion_sensor.py,来启动之,以实现对PIR时间的调整。

为了避免过于频繁地被运动触发,内部计时器会阻止系统持续发送运动信号,因此传感器存在着虽然能够每次检测到运动,但可能不会去通知系统的风险。由于计时器的范围是0-255秒(255是全部顺时针方向,0为所有逆时针),因此根据我的经验,只需将定时器配置在7-10秒之间,电位器便可以在几乎水平的位置,逆时针地转动。类似地,对于灵敏度电位器而言,顺时针方向表示灵敏度更高。其对应的命令输出会显示如下:

Plain Textpi@raspberrypi:~/raspberry-pi-security-camera-client $ python pir_motion_sensor.py2022-04-21T15:35:35.275947 Detected movement2022-04-21T15:35:41.607265 All clear1.2.3.4.

5.添加摄像头

在Raspberry Pi处于关闭状态,以及断开了与任何电源的连接时,我们将摄像头安装在右侧。而在完成后,请重启Raspberry Pi,并确保已拥有最新的摄像头栈(camera stack)。

然后,请打开控制台并输入如下内容:

Shell$ sudo raspi-config1.2.

请选择“接口选项”菜单。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

选择“启用/禁用传统摄像头支持”并确保将其已禁用。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

最后,保存并重新启动。

6.Picamera2与Picamera

Picamera2是libcamera的新式Python端口。其对应的旧项目–Picamera虽然基于不同的系统,但是其接受度颇高。

7.测试摄像头

为了测试摄像头,我使用Picamera2创建了一个简短的脚本。鉴于Picamera2项目仍处于预览阶段,其安装并不容易。下面,我们先运行example_picamera2.py脚本,来验证摄像头是否已设置正确:

Shell$ python example_picamera2.py1.2.

而example_picamera2.py的具体内容如下:

Python

from gpiozeroimport MotionSensorfrompicamera2.picamera2 import *fromdatetime import datetimefromsignal import pausepir = MotionSensor(17)camera = Picamera2()camera.start_preview(Preview.NULL)config = camera.still_configuration()camera.configure(config)defcapture():camera.start()timestamp = datetime.now().isoformat()print('%s Detected movement' % timestamp)metadata = camera.capture_File('/home/pi/%s.jpg' % timestamp)print(metadata)camera.stop()defnot_moving():timestamp = datetime.now().isoformat()print('%s All clear' % timestamp)pir.when_motion = capturepir.when_no_motion = not_movingpause()1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.

每当移动检测PIR传感器检测到物体移动时,该文件都会执行快照,并将图像放置在/home/pi目录中,并保持文件名与摄像头捕获图像的时间相一致。下图便是我的摄像头所拍摄到的图像:

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

至此,我们只完成了项目的一半,毕竟这些都是在本地实现的,并未通过物联网进行远程监控,更谈不上防止有人访问我们的Raspberry Pi、移除SD卡、并带走监控记录。

8.编写客户端代码并在本地进行测试

下面,我们准备在客户端上实现以下软件逻辑:

(1)使用Picamera2设置摄像头。

(2)初始化运动传感器。

(3) 当检测到物体运动时,读取事件并调用以下函数:

a. 捕获图像并将其保存到本地文件系统上的一个文件中。

b. 将图像上传到远程服务器上。

c. 如果上传正确,则删除本地文件,以避免填满Raspberry Pi上的所有空间。

(4) 当由于超时(在本例子中为6-7秒)而不再检测到运动时,开始读取事件,并打上带有“All clear”消息的时间戳。5. 等待下一个事件。

下面是对应的高级别(high-level)代码:

Python

def init(settings): camera = setup_camera() pir = MotionSensor(settings.get('PIR_GPIO')) pir.when_motion = picture_when_motion(pir, camera, settings) pir.when_no_motion = not_movingpause()1.2.3.4.5.6.

其中,最复杂的函数是picture_when_motion。当设备从非运动状态变为运动状态时,when_motion便会开始执行。我们可以设置为不接受其他参数,或仅接受单个强制参数。我将通过下面的代码,将其转换为一个函数,并创建一个回调callback)来返回它。

Python

defpicture_when_motion(pir, camera, settings): setup_path(settings.get('IMG_PATH')) def capture_and_upload_picture(): if camera: file_path = capture(camera, settings.get('IMG_PATH')) server_settings = settings.get('SERVER') uploaded = upload_picture(file_path, server_settings) if uploaded: cleanup(file_path) else: print("Camera not defined")return capture_and_upload_picture1.2.3.4.5.6.7.8.9.10.11.12.

上述代码中的捕获函数类似于前面用于测试摄像头的函数,而upload_picture函数是将软件从本地转换为物联网应用的核心。下面让我们来对其进行分析:Python

def upload_picture(file_path, server_settings):if server_settings.get('base_url'):url = urljoin(server_settings.get('base_url'), 'upload')if server_settings.get('user') and server_settings.get('password'):user = server_settings.get('user')password = server_settings.get('password')files = {'file': open(file_path, 'rb')}print('Uploading file %s to URL: %s' %(file_path, url))try:r = requests.post(url, files=files, auth=HTTPBasicAuth(user, password))image_path = r.json().get('path')except e:print(e)if not image_path or not r.ok:print('Error uploading image')return Falseprint('Image available at: {}'.format(image_path))return True1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.

理想情况下,我们让服务器使用用户名和密码的验证方式,接受作为POST请求的负载文件。其对应的命令为:

Shellcurl -F "file=@/home/user/Desktop/test.jpg" http://localhost:5000/upload1.2.3.4.

由于它们是使用开源的MIT许可证发布的,因此您既可以随意复制它们,也可以使用python main.py来执行之。

9.创建一个服务器来存储图像

针对存储图像的服务器,我们希望:

  • 支持Python代码,特别是Flask
  • 无需RDBMS或复杂的数据库,仅靠文件系统来存储图像。
  • 提供如下简单API的REST接口:

/upload 上传图像。

/ 获取所有图像的列表。

/cleanup 删除旧的图像。

/download/<name>下载单个图像。

  • 安全的TLS连接。
  • 在完成身份验证的基础上,允许Raspberry Pi上传文件。
  • 自动部署。
  • 支持安全的环境变量,可用于存储用户凭据。
  • 低成本(不超过几美元/月)。

GitHub上提供了在本地、或在服务器上运行代码的相关说明。

10.Flask应用

Flask是一个简单灵活的Python框架,可用于快速创建以REST API为主的Web应用。同时,我们可以将主要代码放在main.py文件。首先,我们需要初始化Flask应用,并声明身份验证的方法。对此,我会声明一个名为setup的函数,以读取本地机器上的各种可用环境变量。同时,我也会创建一个包含了所有环境变量的.env文件。接着,我声明了一个verify_password函数,来验证提供给服务器的密码是否正确。然后,我通过函数upload_file,来支持上传新的文件,并访问/upload端点,将图像存储在文件系统中,其具体内容如下:

Python

defupload_file(): if request.method == 'POST': # check if the post request has the file part if 'file' not in request.files: flash('No file part') return redirect(request.url) file = request.files['file'] # If the user does not select a file, the browser submits an # empty file without a filename. if file.filename == '': flash('No selected file') return redirect(request.url) if file and allowed_file(file.filename): filename = secure_filename(file.filename) file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return jsonify(success=True, filename=filename, path=urljoin(request.host_url, url_for('download_file', name=filename))) return ''' <!doctype html> <title>Upload new File</title> <h2>Upload new File</h2> <form method=post enctype=multipart/form-data> <input type=file name=file> <input type=submit value=Upload> </form>'''1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.

该函数在GET和POST模式下均可有效。其中,运行在POST中时,我们可以从文本客户端、或其他应用程序处上传文件;而在GET模式下,我们则可以使用浏览器来实现。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

11.本地测试

虽然您可以直接在Raspberry Pi 4中测试服务器,但是如果您有Linux或Mac系统,那么配置和启动它会更加容易。在本例中,我们首先需要创建一个.env文件,并将其放在与应用程序相同的目录中。.env文件将会存储服务器如下所需的信息:

  • 用于管理Flask会话的密钥
  • 保存图像的上传文件夹
  • 可接受图像的最大尺寸
  • 用于身份验证的用户名和密码
  • 服务器本身的端点URL

下面展示的是.env-example-local文件的内容。您可以将其用作模板,复制、重命名、并按需予以修改。

属性文件SECRET_KEY='change-this-to-something-unlikely-to-guess'UPLOAD_FOLDER = './img'MAX_CONTENT_LENGTH = 16000000USERNAME = 'admin'PASSWORD = 'change-this-to-your-unique-password'SERVER='http://127.0.0.0:5001/'1.2.3.4.5.6.7.

通过运行python main.py,服务器将被启动,并进入主动调试模式,以便我们观察到后台发生的情况。

Shell$ python main.py* Serving Flask app 'main' (lazy loading)* Environment: productionWARNING: This is a development server. Do not use it in a production deployment.Use a production WSGI server instead.* Debug mode: on* Running on all addresses (0.0.0.0)WARNING: This is a development server. Do not use it in a production deployment.* Running on http://127.0.0.1:5001* Running on http://192.168.123.228:5001 (Press CTRL C to quit)* Restarting with stat* Debugger is active!* Debugger PIN: 332-932-8291.2.3.4.5.6.7.8.9.10.11.12.13.14.

让我们首先通过CURL的方式来测试上传文件。您也可以使用Postman之类的工具来进行测试。假设您想从路径/Users/luca/Pictures/image.jpeg处上传图像,请使用如下命令:

Shellcurl -F "file=@/Users/luca/Pictures/image.jpeg" -u 'admin:password' 'http://127.0.0.1:5001/upload'{"filename": "image.jpeg","path": "http://127.0.0.1:5001/download/image.jpeg","success": true}1.2.3.4.5.6.7.8.9.10.

下图展示了上传图像的请求已被成功受理。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

12.将服务器部署到Render处

至此,我们可以将服务器推送到一个真实、稳定且安全的环境中了。我们希望:

  • 能够在磁盘上直接存储图像,而无需配置数据库。
  • 为了避免在服务器上保留过多的图像,通过Cronjob运行某个API,以定期只保留最后20张图像。
  • 简单地使用基于.env文件的密钥和变量

13.注册并配置首个服务

在注册到平台之前,我建议您通过单击屏幕右上角的“分叉(fork)”按钮,来分叉现有的GitHub存储库。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

接着,您可以在Github上完成注册。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

然后,请从仪表板中选择“新建Web服务”。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

并搜索最近分叉的存储库(repo)。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

为了配置服务器,您可以先选择一个免费的入门计划,并在后期按需选购永久性磁盘。其中会涉及到如下参数:

  • 名称:可自由选择
  • 环境:Python 3
  • 地区:选择离您最近的一个
  • 分支:main
  • 构建命令 pip install -r requirements.txt
  • 启动命令gunicorn main:app

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

现在让我们转到界面的高级部分,以设置密钥文件。您可以将其命名为.env,并粘贴以下的文本内容(您可以按需进行更改):

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

14.创建永久性磁盘

在Render上创建永久性磁盘并不难,我们完全可以使用界面来完成。您只需单击左侧的磁盘部分,为其选择名称和安装路径即可。例如:

  • 名称:图像
  • 挂载路径:/var/img
  • 大小:1GB

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

我们将会在“事件”选项卡中收到有关其状态的通知。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

如果我们点击一个特定的事件,将能够看到所有的细节。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

完成后,您将在页面的顶部看到服务器的URL。

现在,是时候开始从我们的Raspberry Pi客户端处上传一些真实的图像了。首先,我们需要更改Raspberry客户端中的.env文件。下面展示了其环境变量的信息:

属性文件PIR_GPIO=17USERNAME='admin'PASSWORD='change-me-with-a-real-password-please'API_SERVER='https://your-api-address.onrender.com/'IMG_PATH='img'1.2.3.4.5.6.

接着,请使用Python 3启动main.py的服务。

如果您在PIR传感器的区域内移动,摄像头将会拍摄照片并将其上传到服务器上。我们可以通过获取图像的URL,实现浏览器下载照片。

15.定期清理图像

为了避免在服务器上存储太多的图像,我人为地设定为最多保留20张。为此,我们需要创建一个额外的Cronjob服务,来定期调用API。

首先,我创建了一个名为/cleanup的服务器路由,它会调用keep_last_images()函数。该函数的定义如下:

Shell$ curl-v -d '{"keep": "20"}' -H "Content-Type: application/json" -u 'username:password' -X POST http://127.0.0.1:5001/cleanup/1.2.

此函数会按照创建图像的时间对图像进行排序,并保留POST请求有效负载中所指示的X数量的图像。请使用如下命令测试CURL的执行效果:

Shell$ curl-v -d '{"keep": "20"}' -H "Content-Type: application/json" -u 'username:password' -X POST http://127.0.0.1:5001/cleanup/1.2.

通过定期(如每周)调用上述函数,我们将能够清理所有比最近20张更旧的图像。

接着,我在Render的仪表板中创建了一个新的Cronjob服务。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

下面是针对Cronjob的设置:

名称:清理旧文件

  • 地区:法兰克福
  • 时间表:4 5 * * 2(我使用https://crontab.guru/来创建正确的字符串。)
  • 命令:Python 3 auto_cleanup.py 20(最后一个参数是设定保留图像的数量)
  • 构建命令:pip install -r requirements.txt(这是安装所有依赖项所必需的)
  • 分支:main
  • 自动部署:是
  • Cronjob失败通知:用户帐户的相关通知设置

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

为了测试其效果,我们可以在Cronjob上手动触发其运行,而无需等待真实的时间表,即:单击页面顶部的“触发运行”按钮即可。Render界面的仪表板会显示如下信息:

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

16.创建事件的仪表板

为了实现对摄像头的安全管理,我们可以使用list_files()函数查询文件系统,并按照创建日期列出所有的图像文件。请参考如下代码段:

Python

# List endpoint, get an HTML page listing all the uploaded files link@app.route('/')@auth.login_requireddef list_files():files = get_list_of_img_path(path=app.config['UPLOAD_FOLDER'], reverse=True)images_url = []for file in files:images_url.append(urljoin(request.host_url, url_for('download_file', name=os.path.basename(file))))return render_template('imglist.html', images_url=images_url)1.2.3.4.5.6.7.8.9.

上述函数会调用与操作系统相关的API,并返回按创建时间排序的文件列表。接着,它会使用jinja模板,将数据返回到imglist.html文件中。该文件的基本部分为:

HTML<ul>{% for image in images_url %}<li><a href="{{image}}">{{image}}</a></li>{% else %}<li>No images uploaded yet</li>{% endfor %}</ul>1.2.3.4.5.6.7.8.

它会产生如下列表:

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

17.在外出时查看自己的仪表板

物联网的好处不仅在于您可以安全地远程存储图像,而且能够避免因有人窃取或损坏您的Raspberry Pi,而丢失数据。也就是说,您可以身处世界任何地方,通过使用服务器.env文件中记录的用户名和密码,访问并登录Render的完整URL,以查看照片数据,并及时捕获设备前的运动事物。下面的一组照片来自我家的摄像头。其中的最后一张记录了我爱人对摄像头进行测试的场景。

用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)用树莓派和Render构建一个物联网安全摄像头(树莓派搭建网络摄像头)

18.小结

在上文中,我向您介绍了如何以端到端的方式,从硬件设置到服务器部署,来创建一个廉价且实用的物联网摄像头应用。您可以使用Raspberry Pi 4、Python、Flask、Render等技术组件与服务,在短短几个小时内构建出具有远程图像上传功能的安全摄像头。

原文链接:https://dzone.com/articles/iot-security-camera-with-rasbperry-and-render

译者介绍

陈峻 (Julian Chen),51CTO社区编辑,具有十多年的IT项目实施经验,善于对内外部资源与风险实施管控,专注传播网络与信息安全知识与经验;持续以博文、专题和译文等形式,分享前沿技术与新知;经常以线上、线下等方式,开展信息安全类培训与授课。

来源: 51CTO技术栈

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

(0)
上一篇 2023年3月27日 上午8:48
下一篇 2023年3月27日 上午8:58

相关推荐

  • 经典推荐!10款最好的Python编程IDE,总有一款适合你

    对于程序员来说,除了日常争论世界上最好的语言是哪一门以外,哪款 IDE 是最好的也是争议颇多,今天我们就来介绍 10 款最好的 python 编程 IDE,总有一款适合你! 经典推…

    科研百科 2023年4月26日
    252
  • 课题研究成果社会经济效益

    课题研究成果社会经济效益 近年来,随着社会经济的发展和改革开放的深入推进,社会经济活动日益复杂,市场竞争激烈,企业间的博弈不断升级。在这样的背景下,企业如何提高自身的竞争力,实现经…

    科研百科 2024年10月19日
    0
  • 党建引领,绘就美丽乡村新画卷(党建引领绘就美丽乡村新画卷)

    今年以来,安图县亮兵镇坚持党建引领,聚焦组织建设、特色产业、环境整治等工作,奋力在乡村振兴中开创新局面,谱写新篇章。 各党支部采取“三会一课”、主题党日、座谈交流、实地参观等形式,…

    科研百科 2023年9月24日
    203
  • 为三江源生态保护减压增效 青海畜牧业打出&quot;有机牌&quot;

      7月的青海,遍地牛羊、水草丰美,正值一年当中最好的时节。此刻,位于青海省果洛藏族自治州的青海五三六九生态牧业科技有限公司加工车间正是一番忙碌景象,绿色有机的高原牦牛肉在这里经过…

    科研百科 2022年5月12日
    354
  • 中国已发现地方畜禽品种545个

      11月20日,第三届国家畜禽遗传资源委员会成立大会在京召开。农业部副部长、国家畜禽遗传资源委员会主任于康震在会上强调,要进一步发挥国家畜禽遗传资源委员会的作用,大力提升畜禽遗传…

    科研百科 2022年5月17日
    282
  • 中山大学科研项目管理办法

    中山大学科研项目管理办法 为加强中山大学科研项目管理,提高科研水平,保障科研经费使用,遵循科学规律,推动科研创新,中山大学制定了科研项目管理办法。 该办法对科研项目的类型、来源、申…

    科研百科 2024年10月12日
    9
  • 幕墙工程项目管理

    幕墙工程项目管理 幕墙工程项目管理是指在建筑行业中,对幕墙项目进行有效管理和控制的过程。幕墙项目是建筑行业中非常重要的一个领域,因为幕墙能够为建筑增添独特的外观和功能,并且能够提高…

    科研百科 2024年5月27日
    73
  • 荣盛办公系统

    荣盛办公系统: 现代企业数字化转型的利器 随着数字化时代的到来,企业数字化转型已经成为不可避免的趋势。作为数字化转型的利器,荣盛办公系统(Rors)为帮助企业提高效率,降低成本,提…

    科研百科 2024年8月31日
    39
  • 壁纸软件《Wallpaper Engine》安卓版正式上线(壁纸wallpapers软件)

    IT之家 11 月 27 日消息,9 月 15 日,《Wallpaper Engine》推出了 1.7 版本,并宣布将在不久之后发行免费的安卓版本。 现在,《Wallpaper E…

    科研百科 2023年4月16日
    268
  • 四川阿坝州促进传统畜牧业向现代草原畜牧业转型初见成效

      畜牧业是阿坝州州牧区经济发展的支柱产业,也是牧民群众赖以生存和发展的基础产业,自2012年以来,阿坝州着力推进现代草原畜牧业试点示范,推动牧区牧民牧业统筹发展、一产二产三产联动…

    科研百科 2022年6月8日
    416