使用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

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法