使用Air724UG模块拍摄照片并上传至云服务器_air724ug 4g摄像头-程序员宅基地

技术标签: 4G通信  stm32  air724ug  物联网  摄像头模块  

前言

最近在做物联网的项目,有一个需求是要每隔一段时间要拍摄一张现场的图片并上传至云服务器保存。在查阅了很多资料后,发现这方面的资料是真的匮乏。同时,tb 上的摄像头产品也太高度集成了,很难进行二次开发。一次机缘巧合下,在逛 tb 的时候偶然发现一款产品,就是如下图所示 Air724UG 模块,自带 4G 通信模块和摄像头接口,而且成本也比较便宜,带通信卡和摄像头总价格不超过 80,简直就是完美符合我需求的天选产品。同时该系列产品的官方网站上也给出了很多代码 demo,非常有利于初学者的学习。最终,在经历了几天的学习后,终于成功把这个需求实现了,开心ヾ(•ω•`)o,于是在此记录下来,希望能帮助到其他有需要的小伙伴们~

Air724UG

Air724UG

基础知识

如果买了这个模块,首先把卖家写的五篇开发文档看一下,如下

最重要的是最后一个文档,因为我们后面要修改这里面的代码。

看完文档后,应该就会对 Air724UG 模块有了大致的了解,同时我们也会发觉二次开发是基于 Lua 语言的。有小伙伴可能就会问,没学过 Lua 语言肿么办

莫慌,因为合宙官方给出了很多案例的示例代码,包括上面第五个文档里面用的摄像头案例的代码。我们可以打开代码看一下,官方的代码规范写的很好,每一句代码几乎都有注释,这就非常有利于我们初学者的学习~

博主也是在大致浏览了下代码后,尝试性的将 socket 的示例代码和 camera 的示例代码融合起来,成功的解决了这个需求,下面就具体看看修改后的代码吧~

编写 TCP 客户端代码

目前拍摄的需求已经通过默认的 camera 示例代码实现了,下面要实现的就是如何把照片上传到云端。搞过开发的同学应该都大致了解 socket 这个东西,我们各种网络通信的基础就是 socket,我们用 socket 也可以很快搭建一个 tcp 服务器,这样就相当于我们的摄像头模块是 tcp 客户端,部署了代码的云服务器是 tcp 服务器端,两端连通以后,就可以实现将照片传输到云端啦~

下面先看一下 TCP 客户端具体代码

主要用到的就是官方 demo\socket\async\asyncSocket 的案例和 demo\camera 这两个案例,我们按照 socket 案例的 main 文件修改 camera 案例的 main 文件,修改后如下

--必须在这个位置定义PROJECT和VERSION变量
--PROJECT:ascii string类型,可以随便定义,只要不使用,就行
--VERSION:ascii string类型,如果使用Luat物联云平台固件升级的功能,必须按照"X.X.X"定义,X表示1位数字;否则可随便定义
PROJECT = "CAMERA"
VERSION = "2.0.0"

--加载日志功能模块,并且设置日志输出等级
--如果关闭调用log模块接口输出的日志,等级设置为log.LOG_SILENT即可
require "log"
LOG_LEVEL = log.LOGLEVEL_TRACE
--[[
如果使用UART输出日志,打开这行注释的代码"--log.openTrace(true,1,115200)"即可,根据自己的需求修改此接口的参数
如果要彻底关闭脚本中的输出日志(包括调用log模块接口和Lua标准print接口输出的日志),执行log.openTrace(false,第二个参数跟调用openTrace接口打开日志的第二个参数相同),例如:
1、没有调用过sys.opntrace配置日志输出端口或者最后一次是调用log.openTrace(true,nil,921600)配置日志输出端口,此时要关闭输出日志,直接调用log.openTrace(false)即可
2、最后一次是调用log.openTrace(true,1,115200)配置日志输出端口,此时要关闭输出日志,直接调用log.openTrace(false,1)即可
]]
--log.openTrace(true,1,115200)

require "sys"
require "utils"
require "patch"
require "pins"

require "net"
--每1分钟查询一次GSM信号强度
--每1分钟查询一次基站信息
-- net.startQueryAll(60000, 60000)
--8秒后查询第一次csq
net.startQueryAll(8 * 1000, 60 * 1000)

--此处关闭RNDIS网卡功能
--否则,模块通过USB连接电脑后,会在电脑的网络适配器中枚举一个RNDIS网卡,电脑默认使用此网卡上网,导致模块使用的sim卡流量流失
--如果项目中需要打开此功能,把ril.request("AT+RNDISCALL=0,1")修改为ril.request("AT+RNDISCALL=1,1")即可
--注意:core固件:V0030以及之后的版本、V3028以及之后的版本,才以稳定地支持此功能
ril.request("AT+RNDISCALL=0,1")

--加载控制台调试功能模块(此处代码配置的是uart1,波特率115200)
--此功能模块不是必须的,根据项目需求决定是否加载
--使用时注意:控制台使用的uart不要和其他功能使用的uart冲突
--使用说明参考demo/console下的《console功能使用说明.docx》
--require "console"
--console.setup(1, 115200)

--加载硬件看门狗功能模块
--根据自己的硬件配置决定:1、是否加载此功能模块;2、配置Luat模块复位单片机引脚和互相喂狗引脚
--合宙官方出售的Air201开发板上有硬件看门狗,所以使用官方Air201开发板时,必须加载此功能模块
--[[
require "wdt"
wdt.setup(pio.P0_30, pio.P0_31)
]]

--加载网络指示灯和LTE指示灯功能模块
--根据自己的项目需求和硬件配置决定:1、是否加载此功能模块;2、配置指示灯引脚
--合宙官方出售的Air720U开发板上的网络指示灯引脚为pio.P0_1,LTE指示灯引脚为pio.P0_4
require "netLed"
pmd.ldoset(2,pmd.LDO_VLCD)
netLed.setup(true,pio.P0_1,pio.P0_4)
--网络指示灯功能模块中,默认配置了各种工作状态下指示灯的闪烁规律,参考netLed.lua中ledBlinkTime配置的默认值
--如果默认值满足不了需求,此处调用netLed.updateBlinkTime去配置闪烁时长

--加载错误日志管理功能模块【强烈建议打开此功能】
--如下2行代码,只是简单的演示如何使用errDump功能,详情参考errDump的api
require "errDump"
errDump.request("udp://ota.airm2m.com:9072")

--加载远程升级功能模块【强烈建议打开此功能】
--如下3行代码,只是简单的演示如何使用update功能,详情参考update的api以及demo/update
--PRODUCT_KEY = "v32xEAKsGTIEQxtqgwCldp5aPlcnPs3K"
--require "update"
--update.request()

require"color_lcd_spi_st7735"
--require"color_lcd_spi_gc9106l"
require"testCamera"

-- 系统工具
require "misc"
require "testSocket"
require "ntp"

ntp.timeSync(1,function()log.info("----------------> AutoTimeSync is Done ! <----------------")end)

--启动系统框架
sys.init(0, 0)
sys.run()

然后在 camera 案例下添加一个 testSocket.lua 文件,编写代码如下

--- testSocket
-- @module asyncSocket
-- @author AIRM2M
-- @license MIT
-- @copyright openLuat.com
-- @release 2018.10.27
require "socket"
module(..., package.seeall)

-- 此处的IP和端口请填上你自己的socket服务器和端口
-- local ip, port, c = "180.97.80.55", "12415"
local ip, port, c = "xxx.xxx.xxx.xxx", "9999"

-- 异步接口演示代码
local asyncClient
sys.taskInit(function()
    while true do
        while not socket.isReady() do sys.wait(1000) end
        asyncClient = socket.tcp()
        while not asyncClient:connect(ip, port) do sys.wait(2000) end
        while asyncClient:asyncSelect() do end
        asyncClient:close()
    end
end)

-- 测试代码,用于发送消息给socket
function sendFile(size)
sys.taskInit(
    function()
        while not socket.isReady() do sys.wait(2000) end
        local fileHandle = io.open("/testCamera.jpg","rb")
        if not fileHandle then
            log.error("testALiYun.otaCb1 open file error")
            return
        end
        log.info("-----------------------------------------------start send photo")
        asyncClient:asyncSend(size)
        while true do
            local data = fileHandle:read(size)
            if not data then break end
            asyncClient:asyncSend(data)
        end
        log.info("-----------------------------------------------end send photo")
        fileHandle:close()
    end
)
end

注意把上面的 ip 地址换成你后面部署 tcp 服务器端的云服务器的 ip 地址

这个文件编写完成以后,就可以在 testCamera.lua 文件里面调用这个函数了,testCamera.lua 文件具体编辑如下,其中中间省略了一些没有修改的代码

--- 模块功能:camera功能测试.
-- @author openLuat
-- @module fs.testFs
-- @license MIT
-- @copyright openLuat
-- @release 2018.03.27

module(...,package.seeall)

require"pm"
require"scanCode"
require"utils"
require"common"
require"testUartSentFile"
require"testSocket"

local WIDTH,HEIGHT = disp.getlcdinfo()
local DEFAULT_WIDTH,DEFAULT_HEIGHT = 320,240

-- 扫码结果回调函数
-- @bool result,true或者false,true表示扫码成功,false表示超时失败
-- @string[opt=nil] codeType,result为true时,表示扫码类型;result为false时,为nil;支持QR-Code和CODE-128两种类型
-- @string[opt=nil] codeStr,result为true时,表示扫码结果的字符串;result为false时,为nil
local function scanCodeCb(result,codeType,codeStr)
    ...
end

local bf302A_sdr =
{
    
	...
}

local gc6153 =
{
    
	...
}

local gc0310_ddr =
{
    
	...
}

local gc0310_ddr_big =
{
    
	....
}

local gc0310_sdr =
{
    
	...
}

function scan()
    ...
end

-- 拍照并显示
function takePhotoAndDisplay()
    ...
end

-- 拍照并通过uart1发送出去
function takePhotoAndSendToUart()
    ...
end

-- 拍照并通过socket发送出去
function takePhotoAndSendToSocket()
    --唤醒系统
    pm.wake("testTakePhoto")
    --打开摄像头
    disp.cameraopen(1,0,0,1)
    --disp.cameraopen(1,0,0,0)  --因目前core中还有问题没解决,所以不能关闭隔行隔列
    --打开摄像头预览
    --如果有LCD,使用LCD的宽和高
    --如果无LCD,宽度设置为240像素,高度设置为320像素,240*320是Air268F支持的最大分辨率
    disp.camerapreview(0,0,0,0,DEFAULT_WIDTH,DEFAULT_HEIGHT)
    --设置照片的宽和高像素并且开始拍照
    --此处设置的宽和高和预览时的保持一致
    disp.cameracapture(DEFAULT_WIDTH,DEFAULT_HEIGHT)
    --设置照片保存路径
    disp.camerasavephoto("/testCamera.jpg")
    log.info("-----------------------------------------------testCamera.takePhotoAndSendToSocket fileSize",io.fileSize("/testCamera.jpg"))
    --关闭摄像头预览
    disp.camerapreviewclose()
    --关闭摄像头
    disp.cameraclose()
    --允许系统休眠
    pm.sleep("testTakePhoto")

    testSocket.sendFile(io.fileSize("/testCamera.jpg"))

    sys.timerStart(takePhotoAndSendToSocket,1*60*1000)
end


-- sys.timerStart(takePhotoAndDisplay,1000)
-- sys.timerStart(takePhotoAndSendToUart,1000)
sys.timerStart(takePhotoAndSendToSocket,10000)
-- sys.timerStart(scan,1000)

修改完这三个文件之后,我们就可以把这个案例烧录到 Air724UG 模块上了,这样我们 TCP 客户端就弄好了,下面来看下 TCP 服务器端的代码吧~

编写 TCP 服务端代码

这里 socket 的编程可以用 python 写,也可以用 go 或其他语言写,因为我最近在学 go,所以我就用 go 来编写 tcp 服务器端的代码啦,具体 main.go 文件编写如下

package main

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"log"
	"net"
	"os"
	"strconv"
	"time"
)

func GetDetailTime() string {
    
	now := time.Now()
	return fmt.Sprintf("%02d%02d%02d%02d%02d%02d", now.Year(), int(now.Month()),
		now.Day(), now.Hour(), now.Minute(), now.Second())
}

func handleClient(conn *net.TCPConn) {
    
	var buf [256]byte
	var imageData [10 * 1024]byte
	for {
    
		n, _ := conn.Read(buf[0:])
		fmt.Println("------------------------receive bytes--------------------", n, string(buf[0:n]))
		size, _ := strconv.Atoi(string(buf[0:n]))
		fmt.Println("------------------------file size--------------------", size)

		i := 0
		for i < size {
    
			n, _ := conn.Read(imageData[i:])
			i += n
		}
		encodedStr := hex.EncodeToString(imageData[0:i])
		fmt.Println("------------------------received bytes--------------------", i)
		fmt.Println(encodedStr)

		imgName := fmt.Sprintf("%s.jpg", GetDetailTime())
		byte2image(imageData, i, imgName)
	}
}

func byte2image(b [10 * 1024]byte, n int, path string) {
    
	fp, err := os.Create(path)
	if err != nil {
    
		fmt.Println(err)
		return
	}
	defer fp.Close()

	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, b[0:n])
	fp.Write(buf.Bytes())
}

func main() {
    
	address := net.TCPAddr{
    
		IP:   net.ParseIP("0.0.0.0"), // 把字符串IP地址转换为net.IP类型
		Port: 9999,
	}
	fmt.Println("v2.0 server listen at ", address.IP, address.Port)
	listener, err := net.ListenTCP("tcp4", &address) // 创建TCP4服务器端监听器
	if err != nil {
    
		log.Fatal(err) // Println + os.Exit(1)
	}
	for {
    
		conn, err := listener.AcceptTCP()
		if err != nil {
    
			log.Fatal(err) // 错误直接退出
		}
		fmt.Println("remote address:", conn.RemoteAddr())
		// go echo(conn)
		go handleClient(conn)
	}
}

编写完成之后,可以通过在当前目录下运行如下命令将代码打包成 Linux 可执行文件

SET CGO_ENABLED=0
SET GOOS=linux
SET GOARCH=amd64
go build main.go

这样就可以在当前目录下生成 main 可执行文件了,这也是我喜欢 go 的原因,同样的代码,可以方便的打包成 windows 可执行文件和 linux 可执行文件,十分的方便

将打包生成的 main 文件传输到云服务器上,然后执行,TCP 服务器端代码就运行起来了,记得开放云服务器防火墙的对应端口

接下来只要保证 TCP 客户端代码里的 ip 地址和端口正确,照片就可以顺利的上传到云服务器上,然后保存到可执行文件的目录下了~

如果这篇文件对你有帮助,记得给博主点个赞支持一下呀 (✿◡‿◡)

也欢迎关注我的 github 项目 ο(=•ω<=)ρ⌒☆

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Apple_Coco/article/details/121525083

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签