嵌入式开发知识
大小端数
小端数:低位在前,高位在后
例如:变量x在内存中的地址为0x0010,x的值为0x1122,那么0x11为高位字节序,而0x22为低位字节序。
对于大端存储,将0x11放在低地址中,既0x0010,0x22则放在高地址中,既0x0011。
对于小端模式,内存地址不用管,值0x1234,小端模式为0x3412
binary.LittleEndian.PutUint16(data[:2], tmpX)
高低八位
//Conv2Uint8 将实际参数转换为命令序参数,x10000
func Conv2Uint8(param float32) []uint8 {
var data [4]uint8
tmp := uint32(param * 10000)
data[0] = uint8(tmp)
data[1] = uint8(tmp >> 8)
data[2] = uint8(tmp >> 16)
data[3] = uint8(tmp >> 24)
return data[:]
}
uint32转uint82
a:=uint32(32000) 二进制 0111 01101 0000 0000 ,取低八位0000 0000 ,所以uint8(a)=0
传入3.2返回 []uint8 =[0 125 0 0]
aa:=uint32(98000) 二进制 0001 0111 1110 1101 0000 ,取低八位1101 0000 ,所以uint8(aa)208,以此类推。
传入9.8返回 []uint8 =[208 126 1 0]
字符串
str 存的是data和len,只准读内存,不允许修改。如果要修改直接给变量整体赋新值,它存储的地址就会指向新的内容。
字符串修改
- str转换成切片
- unsafe包
unsafe
unsafe可以绕过go的内存安全机制,直接对内存进行读写,操作任意的内存地址,操作私有变量(转换成员属性相同,但名字不同的结构体)
内存对齐
为什么要内存对齐?
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
- 数据在内存的存储并不是连续存放的,而是按照一定的对齐规则。使用对齐规则则有一个最大的好处是避免cpu的二次读取,也就是说对齐后的数据cpu只需要读取一次。
- CPU读取数据,不是一个字节一个字节的读取,而是一块一块的读取;根据CPU位数不同而不同。
检测工具
-
需要先安装Ubuntu 20.04安装 linuxbrew
-
那么, 有没有什么办法能够帮我们检测是否存在内存对齐的优化呢? 毕竟平常写的时候, 谁会关心这玩意呢. 别说, 还真有.
golangci-lint
官网: https://golangci-lint.run/
安装:
brew install golangci-lint
检测所有文件命令:
golangci-lint run ./..
检测一下最开始的结构体文件(添加参数指定检测内存对齐):
golangci-lint run --disable-all -E maligned main.go
package main
import (
"fmt"
"unsafe"
)
// 内存对齐 memory alignment
// cpu读取内存是一块一块读取的,内存对齐是空间换时间
type Test struct { //结构体的对齐保证是8 Byte的整数倍
b bool
i3 int32
i8 int8
i64 int64
by byte
}
type RevisedTest struct {
b bool
by byte
i8 int8
i3 int32
i64 int64
}
func main() {
t := Test{} //Test结构体占用应该是: 1+4+1+8+1=15B. 15个字节对吧
fmt.Printf("%d", unsafe.Sizeof(t)) //32
fmt.Println("")
fmt.Printf("bool:%d\n", unsafe.Alignof(t.b))
fmt.Printf("int32:%d\n", unsafe.Alignof(t.i3))
fmt.Printf("int8:%d\n", unsafe.Alignof(t.i8))
fmt.Printf("int64:%d\n", unsafe.Alignof(t.i64))
fmt.Printf("byte:%d\n", unsafe.Alignof(t.by))
//修改后的结构体
rt := RevisedTest{}
fmt.Printf("%d",unsafe.Sizeof(rt))//16
fmt.Println("")
}
slice类型
https://www.bilibili.com/video/BV1CV411d7W8?spm_id_from=333.999.0.0
slice类型啥结构?
切片本身并不是动态数组或者数组指针。它内部实现的数据结构通过指针引用
底层数组,设定相关属性将数据读写操作限定在指定的区域内。切片本身是一
个只读对象,其工作机制类似数组指针的一种封装.
三部分:data(元素存哪里),len(存了多少个元素),cap(可以存多少个元素)
make一个slice
slice要存在一段连续的内存中
var ints []int
data(数组起始地址,没有分配底层数组,所以为nil),len长度和cap容量都为0;
var ints []int = make([]int,2,5)
已经存储的元素是可以安全读写的,但超出这个范围,会出现越界访问发生panic。
再new一个呗!
ps:=new([]string)
*ps = append(*ps,"ling")
new不负责底层数组的分配,new的返回值是ps的起始地址,此时这个slice变量还没有底层数组,ps = append(ps,"ling")会给slice开辟底层数组;字符串有两部分组成,一个是内存的起始地址,指向字符串内容,另一个是字节长度。
这个,底层数组呀
arr:=[3]int{1,2,3}
var s1 []int = arr[1,2]
数组容量声明了就不能改变,var s1 []int = arr[1,2]共用底层数组
new和make的区别
- 都是用于分配内存;
- 都是在堆上分配内存;
- new对指针类型分配内存,返回值是分配类型的指针,new不能直接对slice、map、channel分配内存;
- make仅用于slice、map和channel的初始化,返回值为类型本身,而不是指针;
你咋知道扩容到多大?
-
预估扩容后的容量newCap
如果期望的最小容量大于原始的两倍容量时,那么新的容量就是等于期望的最小容量;
不满足第一种情况,那么判断原slice的底层数组长度是不是小于1024,小于,新容量是原来的两倍;大于等于1024,则最终容量从旧容量开始循环增加原来的 1/4, 直到最终容量大于等于新申请的容量 ,新容量是原来的1.25倍。
-
newCap个元素需要多大内存
预估容量×元素类型大小=所需内存,不是直接分配所需内存;在许多编程语言中申请分配内存(malloc)并不是直接与操作系统交涉,而是和语言自身实现的内存管理模块,它会提前向操作系统申请一批内存,分成常用的规格管理起来(8,16,32,48,64...)。
-
匹配到合适的内存规格
我们申请内存时,他会帮我们匹配到足够大且最接近的规则。
扩容源码 1.14.2
func growslice(et *_type, old slice, cap int) slice {
...........
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
...........
}
扩容前后的slice是否相同
-
情况1
原数组还有容量可以扩容(实际容量没有填充完),这种情况下,扩容以后的数组还是指向原来的数组,对一个切片的操作可能影响多个指针指向相同地址的slice。
-
情况2
原来数组的容量已经达到了最大值,再想扩容,go默认会先开一片内存区域,把原来的值拷贝过来,然后再执行append()操作。这个情况丝毫不影响原数组。需要复制一个切片,最好使用Copy函数
slice实现订阅定时判断,定义了一个全局的切片,新增订阅的时候才会分配内存,没有数据绑定,判断的时候取到的是扩容后拷贝的切片,导致不能删除。
数组和切片传递时候的区别
数组是值传递,切片是引用传递。
golang的参数传递(值传递)、引用传递
go语言中所有的传参都是值传递,都是一个副本(拷贝),因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中无法修改原内容数据;
引用类型(指针、map、slice、chan等),这些可以修改原内容数据。他们有复杂的内部结构,除了申请内存外,还需要初始化相关属性。内置函数new计算类型大小,为其分配零值内存,返回指针。而make会被编译器翻译成具体的创建函数,由其分配内存和初始化成员结构,返回对象而非指针。
Map
https://www.bilibili.com/video/BV1Nr4y1w7aa
https://github.com/WuPeiqi/go_course/blob/master/day06 数据类型:指针、切片、字典/笔记/day06 数据类型.md
底层存储是基于哈希表(数组+链表)存储的,这种类型最大的特点就是查找速度非常快。
以取模+拉链法来快速了解一下哈希表存储原理:
这种结构之所以快,是因为根据key可以直接找到数据存放的位置;而其他的数据类型是需要从前到后去逐一比对,相对来说比较耗时。
Map的特点
- 键不能重复
- 键必须可哈希(目前我们已学的数据类型中,可哈希的有:int/bool/float/string/array)
- 无序
Map底层原理剖析
核心是有hmap和bmap两个结构体实现
1.初始化
2.写入数据
3.读取数据
4.扩容
5.迁移
怎么让goroutine跑一半就退出?runtime.Goexit()
package main
import (
"fmt"
"runtime"
"time"
)
func Foo() {
fmt.Println("打印1")
defer fmt.Println("打印2")
runtime.Goexit() // 加入这行
fmt.Println("打印3")
}
func main() {
go Foo()
fmt.Println("打印4")
time.Sleep(1000*time.Second)
}
// 输出结果
打印4
打印1
打印2
runtime.Goexit()
package main
import (
"fmt"
"runtime"
"time"
)
func Foo() {
fmt.Println("打印1")
defer fmt.Println("打印2")
runtime.Goexit() // 加入这行
fmt.Println("打印3")
}
func main() {
go Foo()
fmt.Println("打印4")
time.Sleep(1000*time.Second)
}
// 输出结果
打印4
打印1
打印2
main函数也是个协程,通过runtime·newproc
创建runtime.main
协程,然后在runtime.main
里会启动main.main
函数,这个就是我们平时写的那个main函数了。
结论是,其实main函数也是由newproc创建的,只要通过newproc创建的goroutine,栈底就会有一个goexit。
GMP
Go程序执行过程
Git上传流程
从远程仓库(git remote)拉取到本地仓库(git pull)--->暂存(git add .)--->本地仓库(git commit)--->远程仓库(git push)
四个区域
Git本地有三个工作区域:工作目录(Working Directory)、暂存区(Stage/Index)、资源库(Repository或Git Directory)。如果在加上远程的git仓库(Remote Directory)就可以分为四个工作区域。文件在这四个区域之间的转换关系如下:
- Workspace:工作区,就是你平时存放项目代码的地方
- Index / Stage:暂存区,用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息
- Repository:仓库区(或本地仓库),就是安全存放数据的位置,这里面有你提交到所有版本的数据。其中HEAD指向最新放入仓库的版本
- Remote:远程仓库,托管代码的服务器,可以简单的认为是你项目组中的一台电脑用于远程数据交换
工作流程
git的工作流程一般是这样的:
1、在工作目录中添加、修改文件;
2、将需要进行版本管理的文件放入暂存区域;
3、将暂存区域的文件提交到git仓库。
因此,git管理的文件有三种状态:已修改(modified),已暂存(staged),已提交(committed)
桶排序
桶排序的基本思想是:把数组 arr 划分为n个大小相同子区间(桶),每个子区间各自排序,最后合并
。
计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。
基数排序
根据个十百千万进行排序
计数排序
用待排序的数作为计数数组的下标,统计每个数字的个数。然后依次输出即可得到有序序列。
算法步骤:
(1)找出待排序的数组中最大的元素;
(2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
(3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
(4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
时间复杂度:Ο(n+k)。
空间复杂度:Ο(k)。
要求:待排序数中最大数值不能太大。最大值确定并且不大,必须是正整数。
图片上传
package mainimport ( "github.com/gogf/gf/frame/g" "github.com/gogf/gf/net/ghttp")// Upload uploads files to /tmp .func Upload(r *ghttp.Request) { files := r.GetUploadFiles("upload-file") names, err := files.Save("/tmp/") if err != nil { r.Response.WriteExit(err) } r.Response.WriteExit("upload successfully: ", names)}// UploadShow shows uploading simgle file page.func UploadShow(r *ghttp.Request) { r.Response.Write(` <html> <head> <title>GF Upload File Demo</title> </head> <body> <form enctype="multipart/form-data" action="/upload" method="post"> <input type="file" name="upload-file" /> <input type="submit" value="upload" /> </form> </body> </html> `)}// UploadShowBatch shows uploading multiple files page.func UploadShowBatch(r *ghttp.Request) { r.Response.Write(` <html> <head> <title>GF Upload Files Demo</title> </head> <body> <form enctype="multipart/form-data" action="/upload" method="post"> <input type="file" name="upload-file" /> <input type="file" name="upload-file" /> <input type="submit" value="upload" /> </form> </body> </html> `)}func main() { s := g.Server() s.Group("/upload", func(group *ghttp.RouterGroup) { group.POST("/", Upload) group.ALL("/show", UploadShow) group.ALL("/batch", UploadShowBatch) }) s.SetPort(8199) s.Run()}
子图片单独表crud
vscode调试go launch.json配置
{ // 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Launch Package", "type": "go", "request": "launch", "mode": "debug", "program": "${workspaceFolder}" }, { "name": "Launch", "type": "go", "request": "launch", "mode": "auto", "program": "${fileDirname}", "env": {}, "args": [] } ]}
进制转化
16进制转10进制
3f 0011 1111 1 2 4 8 16 32 =15+16+32=31+32=63
分库分表
https://zhuanlan.zhihu.com/p/137368446
阅读528代码
1.智能分析(四个主要功能,线和区域入侵、动态检测、火点报警)
通用行为分析
绊线入侵
最多支持八条线
arch_amd64.go
空实现,为了在x86上可以编译,生成swagger文档等。
arch_arm.go
分析结果算法,需要在arm板子上跑,调用良师傅给的函数接口。
iva_dev.go
-
DevBasic 抽象出基础智能分析器设备
-
通道号、开启状态、循环队列(分析结果缓冲队列)、缓冲队列当前读取位置、重复计数
-
quit chan int32 取消信号量(共享数据),GetResult() 获取智能分析结果
-
select是Go中的一个控制结构,类似于用于通信的 switch 语句。每个case必须是一个通信操作,要么是发送要么是接收。
select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。
-
-
cfgLine []conf.DetectLine
- LineInfo 绊线事件类型,1:单向,2:双向"`、检测线的起始结束点、检测方向 双向 a->b b->a、人车检测
- types.Linkage 联动动作 录像抓图、蜂鸣语言报警
- types.WeekSchedule 布防计划
-
-
启动分析 arch->C.IVA_Start (mediaIva.h)->go dev.GetResult() 获取智能分析结果
-
停止分析 if dev.start →C.IVA_Stop (mediaIva.h)→dev.quit <- 1→清空队列→dev.start = false
SetLine 设置绊线区域
- 加互斥锁 dev.mtx.Lock() defer dev.mtx.Unlock()
- var lines = make([]conf.LineInfo, len(cfg)) 在堆上分配内存
- dev.avhLine = make([]define.LineRule, 0) 生效的规则线
- SetLine(cfg []conf.DetectLine) 遍历cfg ,先判断使能,然后赋值,最后arch SetLine
- 判断超出最大长度为最大长度,lines_c赋值,发送给底层C.IVA_SetLines
GetData GetResult() 获取分析结果
- 加互斥锁
- GetData 开始获取分析结果 ,判断缓冲队列为空和到队尾,最多重复取10次,第一次找到最近一个
- queue.Look() 使用序列号访问数据,sn序列号,必须介于begin和end之间
- GetResult() dev.quit 直接返回,从底层获取数据,为空结束本次循环,睡眠10毫秒(防止分析结果慢,取不到数据),执行procResult 处理智能分析结果
读写的为了线程安全都需要加互斥锁
开始处理智能分析结果 procResult
人车过滤 回调函数获取人车配置,判断使能,识别出来的结果先入队列,队列满后出队列
- 没有使能car和man都是false就var obj []define.ObjectInfo,所有的人车结果都不推送出去
- 使能都为true,人车结果都推送出去
- 直选人或车,跳出本次循环,把人或车的结果追加到数组,然后推送出去
var cfgline conf.DetectLineConfigs config.GetConfig(&cfgline)
【当时用这个一直循环获取配置,导致板子内存不足】
分发智能事件
- 遍历rlt.Events(遍历数组,禁用arr[0]易越界),规则类型和事件类型是否相同
- 获取配置,事件码,检测线信息,判断触发事件,事件通知
CheckLineSchedule 判断当前时间点布防计划与前一次是否有变化
- 先判断回调函数的线与配置的线长度是否相等
- 遍历lines,判断使能和布防检查,返回布尔值
- checkSchedule() SetLine 设置绊线检测参数
blp.go
init()
//初始化默认的目标过滤器,8191*8191 4346*3846 设置默认配置SetConfig,加读写锁修复配置频繁设置导致死机问题conf.InitDefaultTargetFilter()//获取当前配置config.GetConfig(&cfgTargetFilter)//注册配置回调函数config.Attach(cfgTargetFilter, OnApplyConfigTargetFilter)遍历cfgTargetFilter (TargetFilters 多通道目标过滤配置)arch.SetTargetFilter(ch, val) 设置过滤目标
OnApplyConfigTargetFilter(val interface{}) (bool,int32)
cfg := val.(conf.TargetFilters)遍历cfg,arch.SetTargetFilter(ch, val) 设置过滤目标return true, config.ApplySuccessconfig.ApplySuccessApplySuccess = int32(0x00000000) //成功 0 1 2 4 8
iva_mgr.go
var ( instance *IvaManager //定义指针实例 once sync.Once)// New init ,分配内存,第一个参数传递类型,返回类型指针*IvaManagerfunc New() (ptr *IvaManager) { ptr = new(IvaManager) return ptr}// GetIns get instance object,once只执行一次func GetIns() *IvaManager { once.Do(func() { instance = New() }) return instance}//IvaManager IVA管理器type IvaManager struct { dev []DevBasic //基础智能分析设备 cfgLine conf.DetectLineConfigs //当前检测线配置 cfgRegion conf.DetectRegionConfigs //当前检测区域配置}
func (mgr *IvaManager) Init() bool {}
mgr *IvaManager 类型绑定,
The make built-in function allocates and initializes an object of type slice, map, or chan (only). Like new, the first argument is a type, not a value. Unlike new, make's return type is the same as the type of its argument, not a pointer to it. The specification of the result depends on the type:
make 内置函数分配和初始化 slice、map 或 chan(仅限)类型的对象。 和 new 一样,第一个参数是一个类型,而不是一个值。 与 new 不同,make 的返回类型与其参数的类型相同,而不是指向它的指针。 结果的规范取决于类型:
mgr.dev = make([]DevBasic, caps.MaxVideoChannel)//循环创建2个智能分析设备 for chn := 0; chn < caps.MaxVideoChannel; chn++ { //New 赋值 mgr.dev[chn].New(int32(chn), 10) }mgr.cfgLine.Default() //Default() 循环赋值config.SetDefault(mgr.cfgLine) //设置默认配置config.Attach(mgr.cfgLine, mgr.OnApplyConfigDetectLine) //注册回调函数
start
- 获取点和区域的当前配置
- 2次循环,首次启动,判断是否需要开启分析(根据点和线的使能),设置绊线检测参数
- 检查布防 5秒检查一次,未使能就跳过本次循环,CheckLineSchedule 判断当前时间点布防计划与前一次是否有变化,有变化就重新设置参数
stop
调用iva_dev.go里面的停止分析
func (dev *DevBasic) Stop() bool {//是否处于开始状态 if dev.start { arch.Stop(dev.chn)//调用良师傅底层的停止 dev.quit <- 1 dev.queue.Clear() dev.start = false } return true}
OnApplyConfigDetectLine 配置改变回调
global.go
Name() 获取配置名称 ,有很多的配置,加上配置名方便区分
ParamVerify() 参数校验
- 类型绑定TargetFilters 多通道目标过滤配置,循环先判断 最大目标尺寸是否在8191*8191外,再判断是否是矩形,直接返回false
- 最小尺寸,是否是矩形;最小目标尺寸长度和宽度是否小于32
- 校正矩形框最大和最小尺寸,Correct() 判断矩形框上下左右是否左大于右或者上大于下,用中间变量重新赋值
InitDefaultTargetFilter() 设置默认区域配置8191 * 8191 4346 * 3846
line.go
func (cfg *DetectLine) Default() 检测线默认值 为了让ParamVerify调用
func (cfg DetectLineConfigs) Default() 多通道智能分析配置-检测线,设置值,返回 cfg = cfgdft
func (cfg *DetectLineConfig) ParamVerify() 检测线数量大于最多检测线数量,判断是否越界和是否是线,否则设置默认值
并发安全 mutex锁
http.go
- beego解析请求参数和json数据
- 参数校验
- 获取当前配置,解析的配置赋值给当前配置,设置当前配置SetConfig(config IConfig, opts ...ConfigOption)
service.go
GetExtendData 扩展数据,江淮哪里取到的是拓展数据,私有码流
回调函数(类似钩子函数)
- init的时候根据Name() 先注册OnApplyConfigDetectLine 配置改变回调
- 当调用 /line 接口,会调用config.SetConfig (),执行配置修改回调函数
私有码流封装
Z:\03-公共资料\07-码流文档
火点报警
http.go
GetCaps() 获取火点能力集
- amd64 直接返回 Cap = dsd.FireCaps{Row: 16, Col: 12},channel2 分成16*12的小格
- arm cgo获取底层的cap.Row和cap.Col
GetFireAlarm() 未使能获取火点报警默认配置,使能获取当前配置,json返回。
SetFireAlarm()
- 循环cfg.RegionInfo,判断区域是否使能,未使能跳出本次循环
- 循环cfg.Region ,判断cfg.RegionInfo的i是否等于cfg.Region的index
- 相等则报警检测区域使能为true,跳出循环
- 设置当前配置 config.SetConfig()
iva_dev.go
SetParam(cfg conf.FireAlarm) 设置火点区域检测参数
- ok:=arch.SetParam(cfg.Mode,cfg.Region,cfg.RegionInfo)
- 判断ok设置成功与否,循环检测火点检测报警区域,报警状态为报警中,通知报警事件,否则报警状态为停止
- 设置联动项和简单检测区域
- 循环cfg.RegionInfo,使能后简单检测区域赋值
GetResult() 获取智能分析结果 是否有取消信号量,有的话直接返回,默认从底层获取结果,存在分析结果,则把事件通知出去
procResult(rlt []define.SimEvent) rlt底层传过来的
- dev.avhEvent = rlt ,结构体SimRegion比SimEvent多了一个Name字段,Region和Rect都是Types.Rect
- Rects追加循环得到rlt的值,判断火警状态是否为空,把火警状态改为开始
- 循环类型绑定报警,报警状态为报警中,更改报警状态为停止;循环rlt的ID,等于报警id就改报警状态为报警中,事件通知
GetData() []define.SimEvent
- 判断是否处于开始状态和事件为空,返回nil
- make分配内存,类型[]define.SimEvent,追加事件dev.avhEvent
- dev.avhEvent = dev.avhEvent[0:0] //清空防止事件重复上报,结果返回
NotifyFireAlarm(id uint32) 火点报警通知
- 报警状态开始,事件开始,该报警状态为报警中,报警事件追加到订阅事件推送信息数组里
- 报警状态停止,事件停止,状态为空,清空防止事件重复上报;default直接返回
- 新建事件,设置私有数据、详细信息、触发对象,事件通知
iva_mgr.go
OnApplyConfig 配置改变回调 调用mgr.dev.SetParam(cfg) 设置火点区域检测参数
highlight
设置和获取强光保护参数
太阳光久照热成像摄像头,会把热成像摄像头灼伤,强光照射的时候,根据能量值和灵敏度报警后触发联动动作。
可见光是良师傅负责,热成像是艾睿算法那边的。
2.测温
thermaltemp
http.go
SetTempLine() 设置测温线配置
- 初始化tmpcfg := conf.TempLineConfigs{},解析json,解析错误判断
- 当前系统温度单位转换为开尔文
- Verify 验证使能项有效性,非使能项恢复默认;实现:判断是否超出最大12条规则数,超过直接返回,判断是否使能、超出最大尺寸、两点是否构成线,符合直接返回,否则为默认配置
- config.SetConfig 配置设置下去
GetTempLine() 获取测温线配置
- 获取前端传来的 default,true获取默认配置,false获取当前配置,判断获取配置是否出错
- 开尔文温度转化为系统温度,返回结果
GetTempLineTemp() 获取测温线温度值
-
data := blp.GetIns().GetLineTempData() //从底层获取温度id, _ := c.GetUint32("id", 0) //线测温序号id,热成像通道
GetIns() 实例化一次(sync.Once) once.Do() ptr = new(TempManager)
GetLineTempData(0).Line ,获取到的数据追加到 []TempData
GetLineTempData() 调用 GetTempData 获取温度数据
-
加读写锁(适用于读多写少
-
通道类型是否等于热成像通道(可见光1 热成像2)
-
result.Line, _ = arch.GetTempData(dsd.TempModeLine) 获取温度数据集合
-
arch_arm.go GetTempData()
//确定好类型,分配内存空间var data_c [dsd.MaxTempRules]C.struct_tpc_temp_data// ret只是返回值,&data_c[0]是从底层获取到的地址,把值赋给data_cret := C.VI_GetTpcTemperatureArray(C.uint32_t(dsd.MaxTempRules), C.uint32_t(mode), &data_c[0])
判断ret返回值不等于0直接返回
数据转换
-
-
温度转化返回结果
温差分析 temp/difference
比如两个区域R1和R2判断高低平均温,符合条件触发报警输出 01,可接音响等。
显示温度条 thermal/env/state?enable=true
点、线、区域 设置获取配置,获取温度都差不多;整帧温度有什么用,整帧里面获取到的温度供谁使用?
256*192热成像特殊分辨率,整帧温度就是取整帧的高低平均温;供查询整帧温度。
GetRandomPointTemp() 获取任意点温度值
- 接收传入x,y轴坐标(8192*8192坐标系),视频通道号
- point 是数组点坐标,传入arch.GetTempPoint(point)
- ConvTemp2Std 开尔文转系统温度,数据返回
GetTempDifference() 从配置中获取 SetTempDifference()
GetVisibleOsd 获取可见光测温信息叠加配置
前叠加:良师傅底层描好传上来,线比较粗(转码会有数据丢失)。
temp_dev.go
new() 创建测温分析设备
初始化通道、退出信号量、事件、报警,为点线区域整帧,屏蔽区域和温差分析初始化默认配置
Start() 启动分析 判断没有处于开启状态,启动协程获取测温分析结果,状态改为true
Stop()停止分析 判断开始状态,更改取消信号量,状态改为关闭,加互斥锁,循环关闭报警事件,更改报警状态,测温报警事件通知(根据状态判断)
func (dev *DevTemp) SetPoint(cfg []conf.TempPointConfig) bool 设置测温点参数
- 加锁,循环配置,判断当前线配置,报警事件通知
- arch.SetTempLine 设置测温线
- 测温分析管理器dev DevTemp ,dev.avhLine循环当前测温线配置,判断是否使能,赋值结果追加到dev.avhLine
GetResult()
- conv匿名函数测温数据转化
- 循环 判断取消信号量,取消则直接返回
- 获取测温数据,加锁,判断是否获取到点、线、区域、整帧温度,获取到把温度数据copy(dev.tempPoint[:], rlt.Point)
- 循环dev.avhPoint 当前有效测温点配置,封装扩展帧使用,测温数据转化的结果赋值温度信息描述
- 区域温差分析
- 睡眠500毫秒,存在分析结果,则把时间通知出去
procResult 开始处理测温分析结果
- 循环 dev.avhPoint 当前有效测温点配置,封装扩展帧使用
- 获取当前点配置和点测温数据
- 判断报警规则是否等于null,等于就不检查布防时间和不使能,跳出本次循环
- getEventStatus()获取事件状态,根据测温规则,报警事件通知
debounce(es *dsd.EventStatus,debounce uint32) bool
- 取当前时间戳
- 测温分析管理器开始时间大于0,返回当前时间戳大于starttime(记录报警时间是否触发)+去抖动时间默认5s
- 报警触发时间等于当前时间戳,拼接字符串
- 把报警状态赋值到报警事件,返回false
getEventStatus(mode,id uint32,linkage types.Lineage) *dsd.EventStatus
- mode 测温类型,id测温id ,字符串拼接,记录温度报警事件,返回
- 否则新建事件状态
ExecRuleGe(es *dsd.EventStatus,temp float32,rule conf.TempRule)
- temp温度大于温度阀值,报警阀值为true
- 判断是否达到测温报警阀值,temp是否大于温度阀值-容差温度,去抖动,改事件状态
- 否则事件停止
GetEvent() []define.TempEvent
- 事件数组长度为空,返回nil
- 为[]define.TempEvent类型分配内存,事件追加到数组,清空类型绑定事件(防止事件重复上报)
NotifyAlarmEvent(es *dsd.EventStatus,param interface{})
- 事件状态判断分支,事件开始状态,事件开始,更改事件状态为进行中,记录温度报警事件,单个事件信息(类型和id)追加到事件分析管理器数组,创建时间对象,进行通知。
- 事件状态正在通知,则单个事件信息(类型和id)追加到事件分析管理器数组
- 事件状态停止,事件开始改为结束状态,builtin/builtin.go 用delete()删除map里的key
- 默认判断是否触发报警事件,删除报警,返回
DiffAbs() 求温差绝对值
func DiffAbs(n,m float32) float32 { if n>m { return n - m } return m -n}
temp_visible.go
New(chn int32) 初始赋值通道号和取消信号量
Start() 可见光没有使能,启用协程获取数据,使能状态为true
Stop() 判断可见光是否已经使能,取消信号量改为1,使能状态为false
SetPoint(cfg []conf.tempPointConfig)
- 加锁
- make分配类型空间,循环配置cfg,判断使能
- tmp := define.TempPoint{} 定义类型变量,循环取得的值赋值给tmp
- tmp.Point, _ = arch.CoordinateConvertVisible(val.Point) 坐标转化(数据坐标映射,可先乘)
- 当前有效测温点配置,封装可见光扩展帧使用
☆☆☆config.SetDefault(mgr.cfgVisibleOsd) 设置默认配置
func SetDefault(config IConfig, opts ...ConfigOption) (err error) { cfgname := config.Name() //获取配置名 tmpcfg, errr := deepCopy(config) //deepCopy() json解码和编码一下 if errr != nil { return errors.ErrSetCfgFailed } err = cfgDefault.Set(cfgname, tmpcfg)//Set() Set 通过 `key` 和 `value` 修改 `JsonConfig` 映射 用于轻松更改 `JsonConfig` 对象中的单个键/值。 if err != nil { return } // 判断当前配置是否为空,若为空则合并默认值 if ok := cfgCurrent.Have(cfgname); !ok { err = SetConfig(config, opts...) if err == nil { log.Infof("merage config[%s] from default config.", cfgname) } } // TODO:配置结构有变化,做合并处理 // 执行延时保存 err = save(cfgDefault, fileDefault) // 保存配置文件 return err}
☆☆☆func SetConfig(config IConfig, opts ...ConfigOption) (err error) 设置配置
func SetConfig(config IConfig, opts ...ConfigOption) (err error) { cfgname := config.Name() //获取配置名 cfgopts := newConfigOptions(opts...) //system配置操作者,不记录日志 // 执行配置修改回调函数 appRlt := int32(ApplySuccess) configCbMutex.RLock() cbMap, ok := configCbMap[cfgname] configCbMutex.RUnlock() if ok { for cbname, callback := range cbMap { log.Infof("exec config[%s] callback function[%s].", cfgname, cbname) ok, rlt := callback(config) appRlt = appRlt | rlt //或位运算 if !ok { log.Errorf("exec config[%s] callback failed.", cfgname) return errors.ErrSetCfgFailed } } } tmpcfg, errr := deepCopy(config) //json解码编码 if errr != nil { return errors.ErrSetCfgFailed } //TODO:存在多线程(多个http请求)或多个goroutine并发map写入风险,后续优化 cfgCurrentMutex.Lock() //加读写锁 defer cfgCurrentMutex.Unlock() err = cfgCurrent.Set(cfgname, tmpcfg) if err != nil { log.Errorf("set config[%s] failed. error:", cfgname, err) return errors.ErrSetCfgFailed } // 执行延时保存 err = save(cfgCurrent, fileCurrent, fileCurrentBak) if err != nil { log.Errorf("save config[%s] failed. error:", cfgname, err) return errors.ErrSetCfgFailed } // 记录配置修改日志 if !cfgopts.NoLog { ev := event.NewEvent(event.EventConfigModify, 0, event.ActionPulse) ev.SetData(config.Name()) ev.SetUser(cfgopts.User) event.Notify(ev, false) } if (appRlt & ApplyNeedReboot) != 0 { return errors.SuccessNeedReboot } if (appRlt & ApplyAutoReboot) != 0 { return errors.SuccessAutoReboot } return nil}
-
获取配置名,newConfigOptions() system用户不记录日志,appRlt返回结果0成功,加读写锁执行配置修改回调函数
-
cbMap, ok := configCbMap[cfgname] configCbMap配置函数回调表(map[string]map[string]CbFunc)
-
循环cbMap 回调函数表,cbname 类型:string , callback 类型:CbFunc
CbFunc 配置回调函数类型 type CbFunc func(val interface{}) (bool, int32)
ok, rlt := callback(config)
-
appRlt = appRlt | rlt 位运算响应信息
-
deepCopy() 解析编码json
-
改变当前配置,执行延时保存(写入到配置文件),记录配置修改日志,返回成功与否
☆☆☆config.Attach(mgr.cfgVisibleOsd, mgr.OnApplyConfigVisibleTemp) 注册配置回调函数
// Attach 注册配置回调函数func Attach(config IConfig, cb CbFunc) bool { cfgname := config.Name() cbname := runtime.FuncForPC(reflect.ValueOf(cb).Pointer()).Name() if ok := cfgCurrent.Have(cfgname); !ok { // Have check config have key or not return false } configCbMutex.Lock() defer configCbMutex.Unlock() cbMap, ok := configCbMap[cfgname] if !ok { cbMap = make(map[string]CbFunc) configCbMap[cfgname] = cbMap } cbMap[cbname] = cb return true}
回调函数的注册和取消,通过KV对map回调表进行操作,用builtin操作map。
OnApplyConfigVisibleTemp 配置改变回调
func (mgr *TempManager) OnApplyConfigVisibleTemp(val interface{}) (bool, int32) { rlt := config.ApplySuccess //返回结果1 cfg := val.(conf.VisibleTempConfig) if mgr.cfgTempAttr.Enable { if cfg.Enable { if !mgr.cfgVisibleOsd.Enable { mgr.devVisible.Start()//未使能开始 } } else { if mgr.cfgVisibleOsd.Enable { mgr.devVisible.Stop() } } } mgr.cfgVisibleOsd = cfg //可见光测温信息叠加配置赋值,这里很重要,上面是调用start获取结果,这个是更改配置 return true, rlt}
thermalparam
func CalibrateBlackbodyTemp(id uint8, param float32) bool { tmpparam := blp.Conv2Uint8Multiply10(param) cmd := blp.CmdPack{} cmd.Create(0x07, 0x6F, id, tmpparam) return cmd.Write()}
-
接收参数命令,将实际参数转换为命令序参数
-
发送指令封装
-
创建命令字
-
写入
命令字序列号,发送命令
arch.RecvCmd 机芯返回的指令读取,休眠100毫秒防止读取的快,结果上来的慢
返回指令解析,返回结果
thermalenv
-
Create() 创建命令字 func (cmd *CmdPack) Create(set, code, oper uint8, param []uint8)
头和尾部信息都是固定的
-
serialize() 命令字序列号
func (cmd *CmdPack) serialize() []uint8
把数据组织好,返回数组
-
parse() 命令字解析 校验
-
Write()
-
Read()
相机每周二重启会导致拉不到流
全天布防不检查 CheckSchedule()
func (mgr *IvaManager) CheckSchedule() { // TODO: 此处可以优化,默认全天布防时不检查 var flag bool for { log.Error("// checkSchedule 进入循环检查") time.Sleep(time.Second * 5) if !mgr.cfgFire.Enable { continue } for _, v := range mgr.cfgFire.Schedule.WeekDay { log.Error("mgr.cfgFire.Schedule.WeekDay", mgr.cfgFire.Schedule.WeekDay) log.Error("---------------") log.Error("len(v.Section)", len(v.Section)) if len(v.Section) == 1 { log.Error("退出了") flag = true } if flag { if !mgr.dev.start { mgr.dev.Start() } continue } else { if mgr.cfgFire.Schedule.Check() { if !mgr.dev.start { mgr.dev.Start() } } else { if mgr.dev.start { mgr.dev.Stop() } } } } }}
RTSP Live Server 设计与实现
3.抓图
图片存储
neuron/storage/picture
losf_store 图片集中存储对象
picture_file.go
Start()
-
mmtpoint,ok:=sdcard.GetDir() 获取SD卡当前读写目录
f.curMntPoint = mntpoint 当前挂载点
-
f.StoreSnap,err = losf_store.NewStore() 新建图片存储(losf_store 图片集中存储模块)
-
err = f.StoreSnap.Init() 初始化赋值
-
//打开当前挂载点的图片存储块id,dir := f.parserMntPoint(f.curMntPoint)f.curVolume,err = f.StoreSnap.OpenVolume(int32(id))
-
f.curVolume,err = f.StoreSnap.AddVomume(int32(id),dir) 设备启动时是新的挂载点,添加一个新卷
-
Attach 注册事件处理监听/处理函数
EventCode 事件码
Listener 监听事件
EventPriority 事件回调执行优先级
Stop()
- 图片集中存储为nil,关闭图片集中存储对象
- 事件注销 Detach 注销事件处理监听/处理函数, name为空表示全部注销
Close()
- 图片集中存储为nil,关闭图片集中存储对象
func (f *PictureFile) EventHandle(e *event.Event) error 时间处理
-
获取事件名用switch处理
-
SD卡分区切换
mntpoint, _ := sdcard.GetDir() //获取SD卡当前读写目录 f.switchVolume(mntpoint) //切换到新分区,实现:获取新分区id和dir路径 f.curMntPoint = mntpoint
-
SD卡分区格式化
GetParam 获取事件私有数据删除相关分区的图片数据
删除sqlite 表数据 -
SD卡格式化 直接删除表
func (f *PictureFile) Write(chn int32, time int64, data []byte, snapType uint8) (err error) 写图片
- 当前挂载点和图片集中存储为nil,直接返回
- 获取图片字节流数据([] byte) ,写入卷
- 图片数据库文件插入记录
func (f *PictureFile) Read(path string) []byte
- 图片集中存储对象 为nil直接返回
- http://192.168.1.140/v1/picture/download?path=/tmp/mmcblk1p7/0/1639010484096.jpg
获取挂载点目录,字符串分割strings.Split(path,"/")
判断路径是否为4段,否则直接返回 - 获取文件idx和卷id,目录不存在需要重新挂载,dirs[3] 获取通道号
- 获取图片前缀(时间戳),打开卷,读取图片文件,返回字节流
func (f *PictureFile) parserMntPoint(mntPoint string) (id int, dir string)
// 获取字符串中的数字//LastIndexFunc返回满足f(c)的最后一个Unicode码位的索引s,如果没有则返回-1。index:=strings.LastIndexFunc(mntPoint,func(c rune) bool{ return uincode.IsLetter(c)//uincode是否是字母})//Atoi等价于ParseInt(s, 10,0),被转换为int类型。id, _ = strconv.Atoi(mntPoint[index+1:])
图片编码
抓图
neuron/function/snap
4.录像
5.流媒体
流媒体RTSP
https://www.cnblogs.com/haibindev/p/7918733.html
流媒体数据传输不是rtsp协议的内容,有RTP包来做。
AAC编码(音频编码)
h264 h265区别
https://zhuanlan.zhihu.com/p/71270595
H265是H264 的升级版。
- 改善码流、编码质量、延迟和算法复杂度;
- 降码率 H.264 中每个宏块(macroblock/MB)大小都是固定的16×16像素,而H.265的编码单元可以选择从最小的8×8到最大的64×64;
- 块的四叉树划分结构,采用一系列自适应的预测和变换等编码技术;
- 标清数字图像传送H265更快
- 占用储存空间缩小