项目 浙江大学远程实验平台 FANSEA 2024-08-17 2024-09-25 浙江大学远程实验平台 基础知识 引脚和串口 引脚和串口在计算机和电子领域中是两个不同的概念,它们各自具有不同的作用和功能。以下是对引脚和串口的详细解释:
一、引脚(Pin)
内部电路信号通路
定义:引脚,也叫管脚,英文叫Pin。它是从集成电路(芯片)内部电路引出与外围电路的接线,所有的引脚就构成了这块芯片的接口。
功能:引脚用于连接和传输电信号,实现数据的传输和控制。引脚的功能和用途取决于所连接的设备或电路的设计。
分类:按照功能,引脚可以分为主电源、外接晶振或振荡器、多功能I/O口,以及控制、选通和复位等类型。例如,在AT89S52芯片中,引脚被划分为多种功能,包括图像识别、自动相位控制、视频信号输出等。
结构:引脚可划分为脚跟(bottom)、脚趾(toe)、脚侧(side)等部分。通过软钎焊使引脚末端与印制板上的焊盘共同形成焊点。
二、串口(Serial Port)
侧重与外部通信
定义:串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口。它用于在计算机和外部设备之间进行数据传输。
特点:串口通信线路简单,只需一对传输线即可实现双向通信,从而降低了成本。它特别适用于远距离通信,但传送速度较慢。
组成:串口通常具有多个引脚,包括数据线(如TX、RX)、控制线(如RTS、CTS)和地线(GND)。这些引脚共同构成了一个完整的串口接口。
应用:串口可以用于与各种外部设备进行通信,如调制解调器、打印机、传感器等。通过串口,计算机可以与其他设备进行数据传输和控制。
总结来说,引脚是电子设备上的接点,用于连接和传输电信号;而串口是一种用于计算机和外部设备之间进行数据传输的接口。两者在计算机和电子领域中各自发挥着重要作用。
H265视频编码
用于保存视频数据,视频数据大小牵扯到压缩,H265作为一种更高级的视频压缩标准存储视频数据用于网络传输,比H264节省一半的空间,相同宽带下可以传输更加高分辨率的视频,传输后将在流服务器解析成我们所看到的视频,由于其算法相对H264复杂,对硬件要求更高,尽管如此,随着硬件技术的不断进步,HEVC正在变得越来越普及和可行!
H.265,也称为高效视频编码(High Efficiency Video Coding, HEVC),是一种视频压缩标准 ,其目标是提供比其前身H.264/AVC更高的数据压缩率,同时保持相同或更好的视频质量。HEVC被视为H.264/AVC的继任者,并被广泛用于各种视频传输和存储应用中,如高清电视广播、视频会议、视频流服务(如YouTube和Netflix)以及视频监控系统等。
以下是H.265(HEVC)的一些主要特点:
更高的压缩效率 :与H.264/AVC相比,HEVC在相同的视频质量下可以提供大约50%的更高压缩率,这意味着在相同的带宽下可以传输更高质量的视频,或者在相同的存储空间下可以存储更多的视频内容。
更好的视频质量 :HEVC提供了更先进的编码工具和技术,可以在低比特率下保持更好的视频质量,这对于带宽受限的应用特别有用。
灵活性和可扩展性 :HEVC支持广泛的视频分辨率和帧率,从低分辨率的监控视频到高分辨率的4K和8K电视广播。此外,HEVC还设计用于支持未来的视频格式和技术。
并行处理能力 :HEVC的编码和解码算法经过优化,以利用现代多核处理器和图形处理器的并行处理能力,从而提高性能。
向后兼容性 :虽然HEVC是一个全新的标准,但它也考虑到了与现有系统和设备的兼容性。例如,HEVC编码器可以生成与H.264/AVC兼容的视频流,以便在旧版本的解码器上播放。
然而,需要注意的是,由于HEVC的编码和解码算法比H.264/AVC更复杂,因此它需要更高的计算资源来执行。这意味着在一些资源受限的设备上,使用HEVC可能会导致更高的能耗或更长的处理时间。尽管如此,随着硬件技术的不断进步,HEVC正在变得越来越普及和可行。
流媒体服务器
流媒体服务器是一种将连续的音频和视频信息压缩后放到网络服务器上的系统,使用户能够边下载边观看,而无需等待整个文件下载完毕 。
流媒体服务器是流媒体应用的核心系统,其关键作用在于提供音视频内容的传输和存储服务。以下是关于流媒体服务器的详细解释:
定义与原理 :
流媒体服务器是一种将连续的音频和视频信息压缩后放到网络服务器上的系统,使用户能够边下载边观看,而无需等待整个文件下载完毕。
主要功能 :
采集与缓存 :流媒体服务器可以从各种视频设备(如摄像机、DVD等)采集视频信号,并将其缓存以供后续处理。
调度与传输 :服务器根据用户需求,将音视频内容进行调度和传输,确保用户能够流畅地观看。
播放 :流媒体服务器支持多种播放协议(如RTP/RTSP、MMS、RTMP等),以适应不同客户端的播放需求。
应用场景 :
视频点播 :用户可以根据自己的需求选择并播放音视频文件。
视频会议 :支持多人实时音视频通信,广泛应用于企业会议、在线教育等场景。
远程教育 :提供音视频内容的在线传输,支持远程教学和学习。
远程医疗 :支持医生与患者之间的远程视频诊断和交流。
在线直播 :支持各类直播活动,如体育赛事、音乐会、游戏直播等。
关键技术 :
音视频压缩技术 :通过高效的音视频压缩算法,减少音视频数据的传输带宽和存储空间需求。
流媒体传输协议 :支持多种流媒体传输协议,确保音视频内容的实时传输和播放。
加密与安全保护 :对音视频数据进行加密处理,保护内容的私密性和版权,防止非法传播和盗用。
典型产品 :
Windows Media Service(WMS) :微软的流媒体服务器产品,采用MMS协议接收、传输视频,并使用Windows Media Player作为前端播放器。
Helix Server :RealNetworks公司的流媒体服务器产品,采用RTP/RTSP协议接收、传输视频,并使用Real Player作为播放前端。
Adobe Flash Media Server :采用RTMP(RTMPT/RTMPE/RTMPS)协议接收、传输视频,并使用Flash Player作为播放前端。
未来发展 :
随着流媒体技术的不断发展,流媒体服务器的功能和性能也在不断提升,为用户提供更多元化的媒体服务。同时,随着5G、AI等技术的融合应用,流媒体服务器将在超高清视频、虚拟现实、增强现实等领域发挥更大作用。
RTMP
RTMP协议从属于应用层,它是流媒体协议 ,被设计用来在适合的传输协议(如TCP)上复用和打包多媒体传输流(如音频、视频和互动内容)。RTMP提供了一套全双工的可靠的多路复用消息服务 ,类似于TCP协议[RFC0793],用来在一对结点之间并行传输带时间戳的音频流,视频流,数据流。通常情况下,不同类型的消息会被分配不同的优先级,当网络传输能力受限时,优先级用来控制消息在网络底层的排队顺序。
全双工 (Full-Duplex
):意味着通信双方可以同时进行发送和接收操作。就像打电话一样,你可以一边听对方说话,一边向对方讲话。这种特性使得RTMP非常适合实时应用,如视频会议或在线游戏。
可靠的 (Reliable
):这通常指的是该协议能确保数据从一端到另一端的准确传输。如果数据在传输过程中丢失,协议会重传这些数据以保证其完整性。可靠性是很多实时应用所必需的特性之一。
多路复用 (Multiplexing
):这意味着通过同一个连接可以同时传输多种类型的数据流。例如,在一个RTMP连接中,可以同时传输视频流、音频流以及控制命令等不同类型的信息。这样做的好处是可以减少建立多个单独连接的需求,从而简化网络通信,并提高效率。
TIP: 多路复用原来意思就是同一个连接可以实现多种数据的传输,是所谓多种数据共用一个连接
RTSP
RTSP全称实时流协议(Real Time Streaming Protocol),它是流媒体协议 ,设计用于娱乐、会议系统中控制流媒体服务器。RTSP用于在希望通讯的两端建立并控制媒体会话(session),客户端通过发出VCR-style命令如play、record和pause等来实时控制媒体流。
RTSP协议以客户服务器方式工作,如:暂停/继续、后退、前进等。它是一个多媒体播放控制协议,用来使用户在播放从因特网下载的实时数据时能够进行控制, 因此 RTSP 又称为“因特网录像机遥控协议”。
ffmpeg
Fast Forward Moving Picture Experts Group
FFmpeg
全称为Fast Forward Moving Picture Experts Group
,于2000年诞生,是一款免费,开源的音视频编解码工具及开发套件 。它的功能强大,用途广泛,大量用于视频网站和商业软件(比如 Youtube 和 iTunes)
核心技术
心跳包监测 视频展示其实是前端向流服务器发起请求拉取视频流,所以前端需要一直发心跳包询问用户是否在线,当心跳检测用户已下线,或者用户退出页面,前端将停止拉取视频流的操作,也是相当于释放了资源!
前端检测 用于用户在当前页面时和点击退出设备时的监测
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 26 27 28 29 30 setInterval ( ) { if (this .currentDeviceInfo .deviceGroup == "" ) { clearInterval (this .timer ); } else { this .timer = setInterval (() => { this .sendDeviceHeartbeat (); }, 5000 ); } }, sendDeviceHeartbeat ( ) { if (this .currentDeviceInfo .deviceGroup == "" ) { clearInterval (this .timer ); } deviceHeartBeat (this .currentDeviceInfo .deviceGroup ).then ((response ) => { if (response === 0 ) { } else { this .$message({ message : "设备组已经退出使用" , type : "warning" , }); clearInterval (this .timer ); } }); },
后端实现
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 @PostMapping(value = "/deviceHeartBeat/{id}") public String deviceHeartBeat (@PathVariable("id") Integer id) { if (!redisCache.hasKey(CacheConstants.DEVICE_GROUP_KEY + id)) { return "1" ; } else { redisCache.expire(CacheConstants.DEVICE_GROUP_KEY + id, 10 , TimeUnit.SECONDS); return "0" ; } } @GetMapping("/deleteDeviceGroupKey/{id}") public String deleteDeviceGroupKey (@PathVariable("id") Integer id) { redisCache.deleteObject(CacheConstants.DEVICE_GROUP_KEY + id); return redisCache.hasKey(CacheConstants.DEVICE_GROUP_KEY + id); }
视频的获取流程
流服务器(轮询) ZLMediaKit/ZLMediaKit: WebRTC/RTSP/RTMP/HTTP/HLS/HTTP-FLV/WebSocket-FLV/HTTP-TS/HTTP-fMP4/WebSocket-TS/WebSocket-fMP4/GB28181/SRT server and client framework based on C++11 (github.com)
快速开始 | ZLMediaKit
视频加载流程
前端页面
1 2 3 4 5 6 7 8 9 <body > <div class ="mainContainer" > <div class ="center" > <h1 > 视频加载中...</h1 > </div > <video name ="videoElement" style ='object-fit:fill' id ="videoElement" class ="centeredVideo" controls muted autoplay > </video > </div > </body >
注册摄像头
RESTful 接口 | ZLMediaKit
1 /index/api/addStreamProxy
1 2 3 4 5 secret: Jj9JiJgR82NjRoSRYJQSsGtW2TPBEJrt vhost: 192.168 .1 .11 app: live stream: 113 url: rtsp:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 function getStreamUrl ( ) { $.ajax ({ url : '/dev-api/system/config/configKey/camera.request.ip' , beforeSend : function (request ) { request.setRequestHeader ("Authorization" , "Bearer " + getCookie ("Admin-Token" )); }, success : (res ) => { const streamId = `${getParams("orderNum" )} ${getParams("cameraNum" )} ` ; $.ajax ({ url : `http://${res.msg} :800/index/api/addStreamProxy` , data : { "secret" : `Jj9JiJgR82NjRoSRYJQSsGtW2TPBEJrt` , "vhost" : `${res.msg} ` , "app" : "live" , "stream" : streamId, "url" : "rtsp://admin:" + `${getParams("password" )} @${getParams("ip" )} ` }, success : (response ) => { if (response.code != 0 ) { console .log (response); document .querySelector ('.center' ).innerHTML = `<h1>注册流代理失败,请查看流服务器中错误信息,检查摄像头IP及帐号密码是否正确</h1>` ; return ; } else { document .querySelector ('.center' ).style .display = 'none' ; console .log (response); playStream (res.msg , streamId) } } }); } }); }
输出视频流
访问流服务器获取flv视频流
将flv视频流绑定video标签
定时器重试播放
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 26 27 28 29 30 31 32 x var timerId; function playStream (configIP, streamId ) { if (flvjs.isSupported ()) { var videoElement = document .getElementById ('videoElement' ); var flvPlayer = flvjs.createPlayer ({ type : 'flv' , url : `http://${configIP} :800/live/${streamId} .flv` }); flvPlayer.attachMediaElement (flvPlayer); flvPlayer.load (); flvPlayer.play (); if (timerId) { clearInterval (timerId); } timerId = setInterval (() => { flvPlayer.pause (); flvPlayer.unload (); flvPlayer.detachMediaElement (); playStream (configIP, streamId) }, 120000 ); } }
负载均衡
项目启动加载正常端口
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class RuoYiApplication { @Autowired private IRemoteService remoteService; public static void main (String[] args) { SpringApplication.run(RuoYiApplication.class, args); System.out.println("远程共享实验系统启动成功" ); } @PostConstruct public void init () { remoteService.setStreamPortKeys(); } } @Override public void setStreamPortKeys () { String[] streamPorts = streamPort.split("," ); for (String streamPort : streamPorts) { if (PortChecker(Integer.parseInt(streamPort))) { redisCache.setCacheObject(CacheConstants.STREAM_SERVER_PORT_KEY + streamPort, "0" ); } else { redisCache.deleteObject(CacheConstants.STREAM_SERVER_PORT_KEY + streamPort); } } } public boolean PortChecker (Integer port) { String host = "localhost" ; try { Socket socket = new Socket (host, port); log.info("流服务器端口 " + port + " 已启用" ); socket.close(); return true ; } catch (IOException e) { log.info("流服务器端口 " + port + " 未启用" ); return false ; } }
查询所有端口连接数并找出最小的
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 @Override public String getStreamPortByPolling () { try { String minStreamPortKey = getMinStreamPortKey(); String minStreamPort = redisCache.getCacheObject(CacheConstants.STREAM_SERVER_PORT_KEY + minStreamPortKey); redisCache.setCacheObject(CacheConstants.STREAM_SERVER_PORT_KEY + minStreamPortKey, String.valueOf(Integer.parseInt(minStreamPort) + 1 )); log.info("获取占用最少的minStreamPortKey成功: " + minStreamPortKey); return minStreamPortKey; } catch (Exception e) { log.error("获取占用最少的minStreamPortKey失败" , e); return null ; } } public List<String> getStreamPortKeys () { String[] streamPortKeys = redisCache.keys(CacheConstants.STREAM_SERVER_PORT_KEY + "*" ).toArray(new String [0 ]); List<String> streamPortKeysList = new ArrayList <>(); for (String streamPortKey : streamPortKeys) { streamPortKeysList.add(streamPortKey.substring(CacheConstants.STREAM_SERVER_PORT_KEY.length())); } streamPortKeysList.sort(Comparator.comparingInt(Integer::parseInt)); log.info("获取缓存中STREAM_SERVER_PORT_KEY所有的key: " + streamPortKeysList); return streamPortKeysList; } public String getMinStreamPortKey () { List<String> streamPortKeys = getStreamPortKeys(); if (streamPortKeys.isEmpty()) { log.error("缓存中STREAM_SERVER_PORT_KEY的key为空" ); return null ; } String minStreamPortKey = streamPortKeys.get(0 ); for (String streamPortKey : streamPortKeys) { if (Integer.parseInt(redisCache.getCacheObject(CacheConstants.STREAM_SERVER_PORT_KEY + streamPortKey)) < Integer.parseInt(redisCache.getCacheObject(CacheConstants.STREAM_SERVER_PORT_KEY + minStreamPortKey))) { minStreamPortKey = streamPortKey; } } log.info("缓存中占用最少的STREAM_SERVER_PORT_KEY的key为: " + minStreamPortKey); return minStreamPortKey; }
注册流代理
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 @Override public AjaxResult registerStreamProxy (String cameraIp, String vhost, String app, String stream, String url) { log.info("为" + cameraIp + "注册流代理" ); String secret = "Jj9JiJgR82NjRoSRYJQSsGtW2TPBEJrt" ; String param = "secret=" + secret + "&vhost=" + vhost + "&app=" + app + "&stream=" + stream + "&url=" + url; if (redisCache.hasKey((CacheConstants.CAMERA_REGISTER_KEY + cameraIp))) { String result = redisCache.getCacheObject(CacheConstants.CAMERA_REGISTER_KEY + cameraIp); log.info("缓存中有该cameraIp的keyresult: " + result); return AjaxResult.success(result); } else { log.info("缓存中没有该cameraIp的key,开始注册流代理" ); String streamPort = getStreamPortByPolling(); if (streamPort == null ) { return AjaxResult.error("获取视频流服务器端口失败,请检测配置文件中流服务器端口是否启用" ); } String postUrl = "http://" + vhost + ":" + streamPort + "/index/api/addStreamProxy" ; try { String response; try { response = HttpUtils.sendPost(postUrl, param); } catch (Exception e) { log.error("注册流代理失败,请检查参数配置中的IP是否正确" ); return AjaxResult.error("注册流代理失败,请检查参数配置中的IP是否正确" ); } JSONObject responseJson = JSONObject.parseObject(response); Integer code = responseJson.getInteger("code" ); if (code == -1 ) { log.error("注册流代理失败,请查看流服务器中错误信息,检查摄像头IP及帐号密码是否正确" ); return AjaxResult.error("注册流代理失败,请查看流服务器中错误信息,检查摄像头IP及帐号密码是否正确" ); } String streamData = responseJson.getJSONObject("data" ).getString("key" ); String[] split = streamData.split("/" ); String streamUrl = split[0 ] + ":" + streamPort + "/" + split[1 ] + "/" + split[2 ]; redisCache.setCacheObject(CacheConstants.CAMERA_REGISTER_KEY + cameraIp, streamUrl, 30 , TimeUnit.MINUTES); log.info("注册流代理成功streamUrl: " + streamUrl); return AjaxResult.success(streamUrl); } catch (Exception e) { log.error("注册流代理失败,请检查参数配置中的IP是否正确" ); return AjaxResult.error("注册流代理失败,请检查参数配置中的IP是否正确" ); } } }
flv.js
不依托flash的视频播放js组件,支持H256x
bilibili/flv.js: HTML5 FLV Player (github.com)
远程烧录
业务实现: 1 2 String[] my_args = new String []{"python" , scriptPath, port, message}; Process proc = Runtime.getRuntime().exec(my_args);
Java调用Python代码
Python
1 2 3 4 5 6 7 8 9 10 if __name__ == '__main__' : name = '' appellation = '' if len (sys.argv) >1 : name = sys.argv[1 ] appellation = sys.argv[2 ] print_hi('PyCharm' ) sleep(5 ) print ('你好 ' +name + appellation)
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 def main (): if sys.platform == "win32" : port = "COM10" elif sys.platform == "darwin" : port = "/dev/tty.usbserial" else : port = "/dev/ttyUSB0" parser = argparse.ArgumentParser( description=("Stcflash, a command line programmer for " + "STC 8051 microcontroller.\n" + "https://github.com/laborer/stcflash" )) parser.add_argument("-b" ,"--bin" , help ="code image (bin/hex)" , type =argparse.FileType("rb" ), nargs='?' ) parser.add_argument("-p" , "--port" , help ="serial port device (default: %s)" % port, default=port) parser.add_argument("-l" , "--lowbaud" , help ="initial baud rate (default: 2400)" , type =int , default=2400 ) parser.add_argument("-hb" , "--highbaud" , help ="initial baud rate (default: 115200)" , type =int , default=115200 ) parser.add_argument("-r" , "--protocol" , help ="protocol to use for programming" , choices=["89" , "12c5a" , "12c52" , "12cx052" , "8" , "15" , "auto" ], default="auto" ) parser.add_argument("-a" , "--aispbaud" , help ="baud rate for AutoISP (default: 4800)" , type =int , default=4800 ) parser.add_argument("-m" , "--aispmagic" , help ="magic word for AutoISP" ) parser.add_argument("-v" , "--verbose" , help ="be verbose" , default=0 , action="count" ) parser.add_argument("-e" , "--erase_eeprom" , help =("erase data eeprom during next download" +"(experimental)" ), action="store_true" ) parser.add_argument("-ne" , "--not_erase_eeprom" , help =("do not erase data eeprom next download" +"(experimental)" ), action="store_true" )
Java
调用Python安全起见会将py代码拷贝一份再进行调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private File createTempFile (String fileName, String resourcePath) { File file = new File (System.getProperty("java.io.tmpdir" ), fileName); try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resourcePath)) { Files.copy(inputStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { log.error("创建临时文件{}失败" , fileName, e); return null ; } return file; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void main (String[] args) throws IOException, InterruptedException { String[] my_args = new String []{"python" , "E:/pythonProject/main.py" ,"张三" ,"老头" }; Process proc = Runtime.getRuntime().exec(my_args); String line; StringBuilder lines = new StringBuilder (); BufferedReader reader = new BufferedReader (new InputStreamReader (proc.getInputStream(), "GBK" )); while ((line = reader.readLine()) != null ) { lines.append(line).append("\n" ); } reader.close(); proc.waitFor(); System.out.println(lines.toString()); }
输出
1 2 Process pro = RuntimeUtil.exec("python " , scriptPath1, "--protocol=" + protocol, "--port=" + port51, "--bin=" + bin);
Java调用bin文件 1 2 3 4 5 6 7 8 ProcessBuilder processBuilder = new ProcessBuilder (hex2binPath, hexFilePath);Process process = processBuilder.start();int exitCode = process.waitFor(); if (exitCode != 0 ) { log.info("hexToBin转化失败,无进程等待,请联系管理员查看异常日志" ); return null ; }
Java监听串口
打开串口
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 serialPort = SerialPortTool.openComPort(portName, baudRate, 8 , 1 , 0 ); if (serialPort != null ) { SerialPort finalSerialPort = serialPort; Thread thread = new Thread (() -> { try { MySerialPortEventListener listener = new MySerialPortEventListener (finalSerialPort, 1 ); listenerMap.put(portName, listener); log.info("添加串口监听器成功" ); } catch (Exception e) { listenerMap.remove(portName); log.error("添加串口监听器失败" , e); } }); thread.start(); log.info("开始打开串口: portName = " + portName + ",baudrate = " + b + ",datebits = " + d + ",stopbits = " + s + ",parity = " + p); CommPortIdentifier portIdentifier; try { portIdentifier = CommPortIdentifier.getPortIdentifier(portName); } catch (NoSuchPortException e) { log.error("没有找到串口:" + portName); return null ; } commPort = portIdentifier.open(portName, 5000 ); if (commPort instanceof SerialPort) { SerialPort serialPort = (SerialPort) commPort; serialPort.setSerialPortParams(b, d, s, p); log.info("打开串口 " + portName + " 成功" ); return serialPort; }
注册串口监听 实现SerialPortEventListener
类,重写监听方法
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class MySerialPortEventListener implements SerialPortEventListener { private String latestData = "" ; private SerialPort serialPort; private int eventType; public MySerialPortEventListener (SerialPort serialPort, int eventType) throws TooManyListenersException { this .serialPort = serialPort; this .eventType = eventType; serialPort.addEventListener(this ); serialPort.notifyOnDataAvailable(true ); serialPort.notifyOnBreakInterrupt(true ); } @Override public void serialEvent (SerialPortEvent arg0) { if (arg0.getEventType() == SerialPortEvent.DATA_AVAILABLE) { byte [] bytes = SerialPortTool.getDataFromPort(serialPort); String str = new String (bytes); latestData = str; eventType = arg0.getEventType(); log.info("收到的数据长度:" + bytes.length); log.info("收到的数据:" + str); } } } inputStream = serialPort.getInputStream(); Thread.sleep(500 ); int availableBytes = inputStream.available(); if (availableBytes > 0 ) { data = new byte [availableBytes]; int readBytes = inputStream.read(data); log.info("从串口 " + serialPort.getName() + " 接收到数据:" + Arrays.toString(data) + " 完成" ); } else { log.info("从串口 " + serialPort.getName() + " 接收到空数据" ); }
SerialPortListener事件类型详解
在SerialPortEvent中,这些参数代表了串行端口的不同事件。它们的意义如下:
DATA_AVAILABLE:数据可用。当串行端口接收到数据时,这个事件将被触发。
OUTPUT_BUFFER_EMPTY:输出缓冲区空。当串行端口的输出缓冲区为空时,这个事件将被触发。
CTS:清除传输请求。当串行端口的清除传输请求(CTS)信号变为低电平(0)时,这个事件将被触发。
DSR:数据服务请求。当串行端口的数据服务请求(DSR)信号变为低电平(0)时,这个事件将被触发。
RI:远程中断请求。当串行端口的远程中断请求(RI)信号变为低电平(0)时,这个事件将被触发。
CD:数据位中断。当串行端口的数据位中断(CD)信号变为低电平(0)时,这个事件将被触发。
OE:溢出错误。当串行端口的溢出错误(OE)信号变为低电平(0)时,这个事件将被触发。
PE:Parity错误。当串行端口的Parity错误(PE)信号变为低电平(0)时,这个事件将被触发。
FE:帧错误。当串行端口的帧错误(FE)信号变为低电平(0)时,这个事件将被触发。
BI:Break中断。当串行端口的Break中断(BI)信号变为低电平(0)时,这个事件将被触发。
前端知识 html内嵌页面 iframe (内嵌框架)是 HTML 中一种用于将一个网页嵌入到另一个网页中的标签 ,它可以在一个页面中显示来自其他页面的内容。在网页中,使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <div class ="center" > <el-card class ="left" > <div class ="video-container" > <div class ="video-play" style ="border-radius: 10px" height ="100%" width ="100%" > <el-skeleton-item v-if ="currentDeviceInfo.cameraIp == ''" variant ="image" style ="width: 100%; height: 100%;" /> <iframe class ="camera-control" v-else :src ="'./SingleStream/index.html?ip=' + currentDeviceInfo.cameraIp + '&password=' + currentDeviceInfo.cameraPassword + '&orderNum=' + currentDeviceInfo.deviceGroup + '&cameraNum=' + currentDeviceInfo.cameraId" > </iframe > </div > </div > </el-card > <div class ="right" > </div > </div >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <!DOCTYPE html > <html > <head > <meta content ="text/html; charset=utf-8" http-equiv ="Content-Type" > <title > flv.js</title > </head > <body > <div class ="mainContainer" > <div class ="center" > <h1 > 视频加载中...</h1 > </div > <video name ="videoElement" style ='object-fit:fill' id ="videoElement" class ="centeredVideo" controls muted autoplay > </video > </div > </body > </html > <script src ="./flv.js" > </script > <script src ="./jquery.js" > </script >
创建定时器 1 2 3 4 5 6 7 8 9 10 var timerId; timerId = setInterval (() => { flvPlayer.pause (); flvPlayer.unload (); flvPlayer.detachMediaElement (); playStream (configIP, streamId) }, 120000 );
ajax请求 1 2 3 4 5 6 7 8 $.ajax ({ url : '/dev-api/system/config/configKey/camera.request.ip' , beforeSend : function (request ) { request.setRequestHeader ("Authorization" , "Bearer " + getCookie ("Admin-Token" )); }, success : (res ) => { }; });
页面加载完成之后执行 1 2 3 4 5 6 $(function ( ) { getStreamUrl (); })
消息弹窗 1 2 3 4 this .$message({ message : "设备组已经退出使用" , type : "warning" , });
登录校验 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import router from './router' import store from './store' import { Message } from 'element-ui' import NProgress from 'nprogress' import 'nprogress/nprogress.css' import { getToken } from '@/utils/auth' import { isRelogin } from '@/utils/request' NProgress .configure ({ showSpinner : false })const whiteList = ['/login' , '/register' ]router.beforeEach ((to, from , next ) => { NProgress .start () if (getToken ()) { to.meta .title && store.dispatch ('settings/setTitle' , to.meta .title ) if (to.path === '/login' ) { next ({ path : '/' }) NProgress .done () } else { if (store.getters .roles .length === 0 ) { isRelogin.show = true store.dispatch ('GetInfo' ).then (() => { isRelogin.show = false store.dispatch ('GenerateRoutes' ).then (accessRoutes => { router.addRoutes (accessRoutes) next ({ ...to, replace : true }) }) }).catch (err => { store.dispatch ('LogOut' ).then (() => { Message .error (err) next ({ path : '/' }) }) }) } else { next () } } } else { if (whiteList.indexOf (to.path ) !== -1 ) { next () } else { next (`/login?redirect=${to.fullPath} ` ) NProgress .done () } } }) router.afterEach (() => { NProgress .done () })