接口与指令
除了内置的控制台页面外,Node-Camera还可以利用接口调用的方式集成到目标系统。以下描述接口时,Node-Camera的IP地址或mDNS主机名统一使用${host}
表示。
若系统设置启用了 Access Control,请参考访问控制。
HTTP照片接口
使用HTTP GET获取Node-Camera实时照片。接口地址:
http://${host}/still
接口返回当前时点摄像头拍摄的JPEG格式照片。使用浏览器即可测试照片接口。
HTTP视频接口
使用HTTP GET访问Node-Camera摄像头视频流。与照片接口不同,视频流使用81
端口。接口地址:
http://${host}:81/stream
接口以MIME类型multipart/x-mixed-replace
返回实时MJPEG视频流。使用浏览器即可测试视频接口。
HTTP视频接口在同一时间只允许一个连接(独占),若已有客户端在访问视频,则后续的视频请求将被忽略。
WebSocket视频接口
系统设置启用 Stream Socket 后,Node-Camera将提供WebSocket视频接口,并停止HTTP视频接口。WebSocket接口地址:
ws://${host}:81/stream
客户端与Node-Camera建立WebSocket连接后,通过发送以下指令(字符串)控制视频流传输:
stream
- 开始连续视频流传输。stop
- 停止视频流传输。still
- 传输一张照片后停止。
服务端发送的每一帧消息数据包(event.data)为一张完整的JPEG图片数据(Blob)。
WebSocket视频接口在同一时间只允许一个连接(独占),若已有客户端连接了WebSocket视频接口,则后续的连接请求将被忽略。当系统设置启用了 Stream Socket 后,Camera与Console页面会自动连接视频接口。因此,通过其它(自定义)方式访问WebSocket视频接口时,应该关闭Camera与Console页面。
客户端样例:
<!DOCTYPE html>
<html>
<head>
<title>Stream-WebSocket</title>
</head>
<body>
<div>
<img id="image" src="">
</div>
<div>
<button id="stream">Stream</button>
<button id="stop">Stop</button>
<button id="still">Still</button>
</div>
</body>
<script>
const image = document.getElementById('image');
const stream = document.getElementById('stream');
const stop = document.getElementById('stop');
const still = document.getElementById('still');
const socketURL = 'ws://192.168.1.100:81/stream'; // 替换为实际地址
let socket = null;
const connect = () => {
socket = new WebSocket(socketURL);
socket.addEventListener('open', e => {
// Send the token here if 'Access Control' is enabled
});
socket.addEventListener('close', e => {
setTimeout(connect, 1000);
});
socket.addEventListener('error', e => {
console.warn('WebSocket Connection Failed');
});
socket.addEventListener('message', e => {
if (e.data instanceof Blob) {
image.src = URL.createObjectURL(e.data);
}
});
}
const send = arg => {
if (socket?.readyState === WebSocket.OPEN) {
socket.send(arg);
console.log(arg);
}
else {
console.warn('WebSocket Not Connected');
}
}
stream.onclick = () => send('stream');
stop.onclick = () => send('stop');
still.onclick = () => send('still');
connect();
</script>
</html>
指令
Node-Camera指令功能包括LED开关、云台控制、摇杆控制、抓拍触发、定时器开关、端口读/写、PWM控制、PCA9685驱动等。
指令接口
指令可以通过HTTP GET或WebSocket提交。若系统设置启用了 UART CMD,指令也可以由串口输入。
HTTP GET指令接口:
http://${host}/cmd
指令作为URL参数提交。例如
http://${host}/cmd?flash=1
(打开LED灯)WebSocket指令接口:
ws://${host}/socket/cmd
指令通过已连接的WebSocket发送。例如
socket.send("flash=1")
(打开LED灯)UART指令接口:
若系统设置开启了 UART CMD,串口接收到的数据将作为指令解析并执行。例如RXD收到字符串
flash=1
,则打开LED灯。
指令定义
指令(command
)由指令名称(command_name
)与可选的指令参数(command_args
)组成。指令格式:
command_name[=command_args]
对于有返回值(return_value
)的指令,返回格式为:
command:return_value
其中command
为接收到的完整指令。
例如,读取GPIO12端口指令get=12
,返回get=12:1
,表示GPIO12端口为高电平。
以下,按照指令名称分别说明:
up - 云台舵机向上转,无参数,无返回值。舵机连续转动,直到上限或收到stop指令。
down - 云台舵机向下转,无参数,无返回值。舵机连续转动,直到下限或收到stop指令。
left - 云台舵机向左转,无参数,无返回值。舵机连续转动,直到下限或收到stop指令。
right - 云台舵机向右转,无参数,无返回值。舵机连续转动,直到上限或收到stop指令。
stop - 云台舵机停止转动,无参数,无返回值。
joy - 摇杆控制,无返回值。
摇杆由Console系统设置定义。参数为逗号分隔的两个整数,根据摇杆模式(joy_mode)分别定义。指令格式:
joy=arg1,arg2
joy_mode =
1
- 两轮模式(Dual-Wheels)arg1
- 左轮PWM负载,取值范围-1000
~1000
,负值表示反转。arg2
- 右轮PWM负载,取值范围-1000
~1000
,负值表示反转。
joy_mode =
2
- 转向驱动模式(Steering & Drive)arg1
- 转向舵机负载,取值范围-1000
~1000
。-1000
为转角下限,1000
为转角上限,0
值表示中间角。arg2
- 驱动轮PWM负载,取值范围-1000
~1000
,负值表示反转。
capture - 抓拍触发,无参数,无返回值。系统设置启用 Command Trigger 后有效。
reset_seq - 重置抓拍照片文件名的循环递增序号为
0
,无参数,无返回值。timing - 抓拍定时器开关控制。参数取值:
0
- 关闭;1
- 打开。无返回值。get - 读取端口电平值,参数为GPIO端口号。
该指令对Peripherals系统设置的 gpio.input 端口有效。如读取GPIO12端口电平:
get=12
该指令返回对应端口的电平值(
0
- 低电平;1
- 高电平)。若GPIO12端口为高电平,则返回:get=12:1
set - 设置端口高电平,参数为GPIO端口号。
该指令对Peripherals系统设置的 gpio.output 端口有效。如设置GPIO12为高电平:
set=12
clr - 设置端口低电平,参数为GPIO端口号。
该指令对Peripherals系统设置的 gpio.output 端口有效。如设置GPIO12为低电平:
clr=12
raw - 读取模拟端口采样值,参数为ADC1 GPIO端口号。
该指令对Peripherals系统设置的 gpio.analog 端口有效。如读取GPIO32 (ADC1_CH4)端口采样值:
raw=32
该指令返回对应端口的采样值,系统默认ADC为12bits位宽,返回值范围
0
~4095
。如GPIO32端口采样返回:raw=32:2033
模拟端口建议电压范围150mV~2450mV。
vol - 读取模拟端口电压值,参数为ADC1 GPIO端口号。
该指令对Peripherals系统设置的 gpio.analog 端口有效。与 raw 指令不同,该指令将采样值转换为实际的电压值(mV)返回。如读取GPIO32 (ADC1_CH4)端口电压值:
vol=32
指令返回对应端口的电压值,单位为mV。如GPIO32端口电压返回(1830mV):
vol=32:1830
模拟端口建议电压范围150mV~2450mV。
bat - 读取电池电量,无参数。
该指令需在Features系统设置中启用并配置电池监测(Battery Monitor)后有效,用于获取当前电池电量。电量以百分比数值表示(50表示50%),返回
-1
表示功能未启用。如剩余电量60%返回:bat:60
pwm - 设置由Peripherals系统设置中 pwm 定义的PWM端口输出脉宽(Duty)。参数为逗号分隔的端口号(gpio_num)与负载值(duty)。无返回值。指令格式:
pwm=gpio_num,duty
gpio_num
- GPIO端口号。duty
- 脉宽负载,取值范围0
~4095
(12bits)。
pca9685 - 设置由Peripherals系统设置中 pca9685 定义的PWM模块端口输出脉宽(Duty)。参数为逗号分隔的设备序号(id)、通道序号(channel),及负载值(duty)。无返回值。指令格式:
pca9685=id,channel,duty
id
- 指定一个Peripherals系统设置的PCA9685设备序号。channel
- 通道序号,取值范围0
~15
。duty
- 脉宽负载,取值范围0
~4095
(12bits)。
串口透传
系统设置启用 UART Socket 后,Node-Camera串口(UART0)将映射到WebSocket地址:
ws://${host}/socket/uart
客户端通过该地址与Node-Camera建立WebSocket连接后,客户端向WebSocket发送的数据将透传到Node-Camera串口输出(TXD),Node-Camera串口接收到的数据(RXD)将由WebSocket发送给客户端。
串口透传接口在同一时间只允许一个连接(独占),若已有客户端连接了串口透传接口,则后续的连接请求将被忽略。当系统配置启用了 UART Socket 后,Settings页面会自动连接串口透传接口。因此,通过其它(自定义)方式访问串口透传接口时,应该关闭Settings页面。
客户端样例:
<!DOCTYPE html>
<html>
<head>
<title>UART-WebSocket</title>
</head>
<body>
<div>RXD</div>
<div>
<textarea id="rxd" rows="8" cols="32"></textarea>
</div>
<div>TXD</div>
<div>
<input id="txd" type="text"><button id="send">Send</button>
</div>
</body>
<script>
const rxd = document.getElementById('rxd');
const txd = document.getElementById('txd');
const send = document.getElementById('send');
const socketURL = 'ws://192.168.1.100/socket/uart'; // 替换为实际地址
let socket;
const connect = () => {
socket = new WebSocket(socketURL);
socket.addEventListener('open', e => {
// Send the token here if 'Access Control' is enabled
});
socket.addEventListener('close', e => {
setTimeout(connect, 1000);
});
socket.addEventListener('error', e => {
rxd.value = 'WebSocket Connection Failed';
});
socket.addEventListener('message', e => {
rxd.value += e.data;
});
}
send.onclick = () => {
if (txd.value === '') return;
if (socket?.readyState === WebSocket.OPEN) {
socket.send(txd.value);
}
else {
rxd.value = 'WebSocket Not Connected';
}
}
connect();
</script>
</html>
SD卡接口
系统设置启用SD卡支持后,可通过HTTP GET访问SD卡接口。
获取SD卡存储的照片列表:
http://${host}/sdcard?list
接口返回JSON格式照片列表:
json[ ["file_name", "yyyy-mm-dd hh:mi:ss"], ... ]
返回样例:
json[ ["IMG_0000.JPG", "1980-01-01 00:00:30"], ["IMG_0001.JPG", "1980-01-01 00:00:40"], ["IMG_0002.JPG", "1980-01-01 00:00:50"] ]
获取指定照片:
http://${host}/sdcard?image=${file_name}
调用样例:
http://${host}/sdcard?image=IMG_0000.JPG
删除指定照片:
http://${host}/sdcard?delete=${file_name}
调用样例:
http://${host}/sdcard?delete=IMG_0000.JPG
访问控制
Node-Camera基于HTTP Cookie机制实现访问控制。
系统配置启用 Access Control(访问控制)并设置密码后,对每个有效的HTTP请求,Node-Camera将检查其请求头Cookie中是否有名为"token"的键-值对(令牌)。若没有或token验证失败,则按以下规则处理:
访问控制台页面,将跳转到密码输入页面,验证通过后,继续返回请求的页面。
访问照片或视频接口,将返回Access Denied图片。
访问控制接口,将返回HTTP状态:401 Unauthorized。
对于WebSocket连接,当连接成功后,客户端需要先发送令牌token=xxxxxx
,否则连接将被服务端关闭。
由密码输入页面提交访问密码,在验证通过的HTTP响应头中包含Cookie令牌:
Set-Cookie: token=xxxxxx
浏览器的后续请求会自带令牌Cookie:token=xxxxxx
,实现正常访问。
Cookie机制被浏览器自动支持。若是用代码访问接口,可以参考以上机制,在HTTP请求头中添加Cookie令牌:
通过HTTP POST提交访问密码(Access Code)到地址:
http://${host}/code
从响应头中获取令牌
token=xxxxxx
。将令牌作为Cookie项添加到后续HTTP请求头中。
若使用WebSocket连接,则在连接建立后,首先发送由POST请求获取的令牌
token=xxxxxx
即可。
客户端样例(Node.js):
/*
* 访问控制 - 获取摄像头实时照片并保存到本地
*/
const http = require('http');
const fs = require('fs');
const hostIP = '192.168.1.100'; // 替换为实际IP
const accessCode = 'nodenode'; // 访问密码
const localFile = 'still.jpg'; // 本地文件名
// step 3. 通过令牌获取摄像头照片并保存
function saveStill(token) {
console.log(token);
let file = fs.createWriteStream(localFile);
http.get(
{
host: hostIP,
path: '/still',
headers: { 'Cookie': token } // 添加Cookie令牌
},
response => {
response.pipe(file); // 保存
console.log('Saved');
}
);
}
// step 2. 回调 - 从响应头提取令牌作为参数调用saveStill(token)
function callback(response) {
console.log(response.statusCode);
console.log(JSON.stringify(response.headers));
let data = '';
response.on('data', chunk => data += chunk);
response.on('end', () => console.log(data));
const cookies = response.headers['set-cookie'];
if (cookies) {
cookies.forEach(ck => {
if (ck.indexOf('token=') == 0) saveStill(ck.split(';')[0]);
});
}
}
// step 1. 提交访问密码 (accessCode)
const req = http.request(
{
host: hostIP,
path: '/code',
method: 'POST',
headers: {
'Content-Type': 'text/plain',
'Content-Length': accessCode.length
}
},
callback);
req.write(accessCode); // POST
req.end();