• Vue使用Serial连接串口


    1、Serial 接口是 Web Serial API的接口,提供了从网页查找和连接串口的属性和方法。

    注意:

      只能在部分支持Serial并且网站为安全上下文(HTTPS)中可用,或者是本机访问

    一:常用API介绍

    1. requestPort----获取授权串口

    2. open-----打开串口

    3. close---关闭串口(串口关闭前,需要释放锁住的流)

    4. cancel---立即退出读取的循环,然后去调用releaseLock,最后调用close方法

    5. releaseLock---Reader和.Writer的释放方法

    6. read---port.readable.getReader()的读取字节数组方法

    7. write---port.writable.getWriter()的写入方法

    二:代码示例

    MySerialPort.js 是封装的一个SerialPort的工具类

    1. export default class MySerialPort {
    2. constructor() {
    3. this.state = {
    4. portIndex: undefined,
    5. ports: [],
    6. isOpen: false,
    7. writeType: 1,
    8. readType: 1,
    9. isScroll: true,
    10. readValue: [],
    11. status:false,
    12. //port参数
    13. baudRate: "9600",
    14. dataBits: "8",
    15. stopBits: "1",
    16. parity: "none",
    17. flowControl: "none",
    18. };
    19. this.keepReading=false;
    20. this.getPorts = this.getPorts.bind(this);
    21. this.handleRequestPort = this.handleRequestPort.bind(this);
    22. this.handleChildrenChange = this.handleChildrenChange.bind(this);
    23. this.readText = this.readText.bind(this);
    24. this.writeText = this.writeText.bind(this);
    25. this.handleClear = this.handleClear.bind(this);
    26. this.a2hex = this.a2hex.bind(this);
    27. this.hex2a = this.hex2a.bind(this);
    28. this.reader={};
    29. this.closed;
    30. }
    31. async getPorts() {
    32. // 获取已授权的全部串口
    33. let ports = await navigator.serial.getPorts();
    34. this.setState({
    35. ports,
    36. });
    37. }
    38. async handleRequestPort() {
    39. // 请求授权
    40. try {
    41. await navigator.serial.requestPort();
    42. await this.getPorts();
    43. } catch (e) {
    44. this.$message.error(e.toString());
    45. }
    46. }
    47. async openPort(portIndex, isOpen,callBack=null) {
    48. // 打开串口
    49. let port = this.state.ports[portIndex];
    50. if (!isOpen) {
    51. // 关闭串口
    52. this.keepReading = false;
    53. this.reader.cancel();
    54. await this.closed;
    55. this.handlePortOpen({
    56. portIndex,
    57. isOpen,
    58. });
    59. } else {
    60. await port.open({
    61. baudRate: this.state.baudRate,
    62. dataBits: this.state.dataBits,
    63. stopBits: this.state.stopBits,
    64. parity: this.state.parity,
    65. flowControl: this.state.flowControl,
    66. });
    67. this.handlePortOpen({
    68. portIndex,
    69. isOpen,
    70. });
    71. this.keepReading = true;
    72. this.closed=this.readUntilClosed(portIndex,callBack);
    73. }
    74. }
    75. async readUntilClosed(portIndex,callBack=null) {
    76. let port = this.state.ports[portIndex];
    77. while (port.readable && this.keepReading) {
    78. this.reader = port.readable.getReader();
    79. try {
    80. while (true) {
    81. const { value, done } = await this.reader.read();
    82. if (done) {
    83. break;
    84. }
    85. this.readText(value);
    86. callBack(value)
    87. }
    88. } catch (error) {
    89. this.$message.error(error.toString());
    90. } finally {
    91. this.reader.releaseLock();
    92. }
    93. await port.close();
    94. }
    95. }
    96. handlePortOpen({ portIndex, isOpen }) {
    97. // 处理打开串口
    98. this.setState({
    99. portIndex,
    100. isOpen,
    101. });
    102. }
    103. handleChildrenChange(type, value) {
    104. this.setState({
    105. [type]: value,
    106. });
    107. }
    108. portWrite(value) {
    109. return new Promise(async (resolve, reject) => {
    110. if (!this.state.isOpen) {
    111. this.$message.error("串口未打开");
    112. reject();
    113. return;
    114. } else {
    115. let port = this.state.ports[this.state.portIndex];
    116. const writer = port.writable.getWriter();
    117. await writer.write(new Uint8Array(value));
    118. writer.releaseLock();
    119. resolve(value);
    120. }
    121. });
    122. }
    123. readText(value) {
    124. console.log(value, "读取");
    125. let newValue = this.state.readValue.concat({
    126. value,
    127. type: 1,
    128. });
    129. this.setState({
    130. readValue: newValue,
    131. });
    132. }
    133. writeText(value) {
    134. console.log(value, "写入");
    135. this.portWrite(value).then((res) => {
    136. let newValue = this.state.readValue.concat({
    137. value: res,
    138. type: 2,
    139. });
    140. this.setState({
    141. readValue: newValue,
    142. });
    143. });
    144. }
    145. handleClear() {
    146. this.setState({
    147. readValue: [],
    148. });
    149. }
    150. componentDidMount() {
    151. this.getPorts();
    152. }
    153. handleState(status) {
    154. this.setState({
    155. status,
    156. });
    157. }
    158. setState(obj){
    159. Object.keys(this.state).forEach(key => {
    160. if(obj[key]!=undefined){
    161. this.state[key]=obj[key]
    162. }
    163. });
    164. }
    165. //字节转字符串
    166. hex2a(arr) {
    167. return String.fromCharCode.apply(String,arr);
    168. }
    169. //字符转16进制
    170. a2hex(str) {
    171. return str.charCodeAt(0);
    172. }
    173. }

     vue代码:

    1. <template>
    2. <div>
    3. <el-row
    4. type="flex"
    5. class="row-bg"
    6. justify="center"
    7. v-show="portsList.length == 0"
    8. >
    9. <el-col :span="7"
    10. ><div style="margin-top: 400px">
    11. <span style="display: block">
    12. 仅支持Chrome 89+或者Edge 89+浏览器(安全上下文(HTTPS)中可用)
    13. span>
    14. <el-button type="primary" @click="obtainAuthorization"
    15. >授权
    16. >
    17. div>
    18. >
    19. el-row>
    20. <el-form
    21. v-show="portsList.length > 0"
    22. ref="form"
    23. :model="form"
    24. label-width="100px"
    25. >
    26. <el-row>
    27. <el-col :span="6"
    28. ><div>
    29. <el-form-item label="串口">
    30. <el-select
    31. v-model="form.port"
    32. filterable
    33. placeholder="请选择串口"
    34. :disabled="isDisable"
    35. >
    36. <el-option
    37. v-for="item in portsList"
    38. :key="item.value"
    39. :label="item.label"
    40. :value="item.value"
    41. >
    42. el-option>
    43. el-select>
    44. el-form-item>
    45. <el-form-item label="波特率">
    46. <el-autocomplete
    47. popper-class="my-autocomplete"
    48. v-model="form.baudRate"
    49. :fetch-suggestions="querySearch"
    50. placeholder="请输入波特率"
    51. :disabled="isDisable"
    52. >
    53. <i class="el-icon-edit el-input__icon" slot="suffix"> i>
    54. <template slot-scope="{ item }">
    55. <div class="name">{{ item.value }}div>
    56. <span class="addr">{{ item.address }}span>
    57. template>
    58. el-autocomplete>
    59. el-form-item>
    60. <el-form-item label="数据位">
    61. <el-select
    62. v-model="form.dataBits"
    63. placeholder="请选择数据位"
    64. :disabled="isDisable"
    65. >
    66. <el-option label="7" value="7">el-option>
    67. <el-option label="8" value="8">el-option>
    68. el-select>
    69. el-form-item>
    70. <el-form-item label="停止位">
    71. <el-select
    72. v-model="form.stopBits"
    73. placeholder="请选择停止位"
    74. :disabled="isDisable"
    75. >
    76. <el-option label="1" value="1">el-option>
    77. <el-option label="2" value="2">el-option>
    78. el-select>
    79. el-form-item>
    80. <el-form-item label="校验位">
    81. <el-select
    82. v-model="form.parity"
    83. placeholder="请选择校验位"
    84. :disabled="isDisable"
    85. >
    86. <el-option label="None" value="None">el-option>
    87. <el-option label="Even" value="Even">el-option>
    88. <el-option label="Odd" value="Odd">el-option>
    89. el-select>
    90. el-form-item>
    91. <el-form-item label="流控制">
    92. <el-select
    93. v-model="form.flowControl"
    94. placeholder="请选择流控制"
    95. :disabled="isDisable"
    96. >
    97. <el-option label="None" value="None">el-option>
    98. <el-option label="HardWare" value="HardWare">el-option>
    99. el-select>
    100. el-form-item>
    101. <el-form-item label="发送区设置">
    102. <el-radio-group v-model="form.type">
    103. <el-radio label="1">ASCIIel-radio>
    104. <el-radio label="2">HEXel-radio>
    105. el-radio-group>
    106. el-form-item>
    107. <el-form-item label="发送信息">
    108. <el-input type="textarea" v-model="form.sendMsg">el-input>
    109. el-form-item>
    110. <el-form-item>
    111. <el-button :type="btnType" @click="connectBtn">{{
    112. btnText
    113. }}el-button>
    114. <el-button type="info" @click="obtainAuthorization"
    115. >新增授权
    116. >
    117. <el-button type="primary" @click="sendCommon">发送el-button>
    118. el-form-item>
    119. div>
    120. el-col>
    121. <el-col :span="15"
    122. ><div>
    123. <el-form-item label="电子秤信息">
    124. <el-input
    125. type="textarea"
    126. v-model="form.desc"
    127. disabled
    128. :autosize="{ minRows: 25, maxRows: 50 }"
    129. >el-input>
    130. el-form-item>
    131. >el-col>
    132. el-row>
    133. el-form>
    134. div>
    135. template>
    136. <script>
    137. import MySerialPort from "./MySerialPort";
    138. export default {
    139. data() {
    140. return {
    141. input: "",
    142. keepReading: true,
    143. form: {
    144. baudRate: "9600",
    145. dataBits: "8",
    146. stopBits: "1",
    147. parity: "None",
    148. flowControl: "None",
    149. desc: "",
    150. type: "1",
    151. },
    152. btnType: "primary",
    153. btnText: "连接串口",
    154. restaurants: [],
    155. portsList: [],
    156. };
    157. },
    158. mounted() {
    159. navigator.serial.addEventListener("connect", (e) => {
    160. this.$message.success("设备已连接");
    161. this.getPorts();
    162. });
    163. navigator.serial.addEventListener("disconnect", (e) => {
    164. this.$message.error("设备已断开");
    165. });
    166. this.restaurants = this.loadAll();
    167. if ("serial" in navigator) {
    168. this.myserialport = new MySerialPort();
    169. this.getPorts();
    170. }
    171. },
    172. computed: {
    173. isDisable() {
    174. return this.btnType == "danger";
    175. },
    176. },
    177. methods: {
    178. //接受数据的回调
    179. callBack(value) {
    180. this.form.desc = this.myserialport.hex2a(value);
    181. },
    182. //连接
    183. async connectBtn() {
    184. if (this.btnType == "primary") {
    185. try {
    186. await this.myserialport.openPort(this.form.port, true, this.callBack);
    187. } catch (error) {
    188. this.$message.error("串口连接失败!请检查串口是否已被占用");
    189. }
    190. if (this.myserialport.state.isOpen) {
    191. this.$message.success("串口连接成功");
    192. this.btnType = "danger";
    193. this.btnText = "关闭串口";
    194. }
    195. } else {
    196. this.myserialport.openPort(this.form.port, false, this.callBack);
    197. this.$message.success("串口关闭成功");
    198. this.btnType = "primary";
    199. this.btnText = "连接串口";
    200. }
    201. },
    202. //授权
    203. async obtainAuthorization() {
    204. if ("serial" in navigator) {
    205. console.log("The Web Serial API is supported.");
    206. if (!this.myserialport) this.myserialport = new MySerialPort();
    207. try {
    208. await this.myserialport.handleRequestPort();
    209. this.$message.success("串口授权成功");
    210. this.getPortInfo(this.myserialport.state.ports);
    211. } catch (error) {
    212. this.$message.warning("未选择新串口授权!");
    213. }
    214. } else {
    215. this.$message.error(
    216. "当前为HTTP模式或者浏览器版本过低,不支持网页连接串口"
    217. );
    218. }
    219. },
    220. //串口列表初始化
    221. getPortInfo(portList) {
    222. this.portsList = [];
    223. portList.map((port, index) => {
    224. const { usbProductId, usbVendorId } = port.getInfo();
    225. if (usbProductId === undefined || usbVendorId === undefined) {
    226. this.portsList.push({ label: "未知设备" + index, value: index });
    227. }
    228. });
    229. },
    230. // 发送
    231. async sendCommon() {
    232. if (this.myserialport.state.isOpen) {
    233. if (this.form.sendMsg.length !== 0) {
    234. const writeType = this.form.type;
    235. let value = this.form.sendMsg;
    236. let arr = [];
    237. if (writeType == 1) {
    238. // ASCII
    239. for (let i = 0; i < value.length; i++) {
    240. arr.push(this.myserialport.a2hex(value[i]));
    241. }
    242. } else if (writeType == 2) {
    243. // HEX
    244. if (/^[0-9A-Fa-f]+$/.test(value) && value.length % 2 === 0) {
    245. for (let i = 0; i < value.length; i = i + 2) {
    246. arr.push(parseInt(value.substring(i, i + 2), 16));
    247. }
    248. } else {
    249. this.$message.error("格式错误");
    250. return;
    251. }
    252. }
    253. this.myserialport.writeText(arr);
    254. } else {
    255. this.$message.warning("请输入发送的信息");
    256. }
    257. } else {
    258. this.$message.warning("串口处于关闭状态,请连接串口");
    259. }
    260. },
    261. async getPorts() {
    262. await this.myserialport.getPorts();
    263. this.getPortInfo(this.myserialport.state.ports);
    264. },
    265. querySearch(queryString, cb) {
    266. var restaurants = this.restaurants;
    267. var results = queryString
    268. ? restaurants.filter(this.createFilter(queryString))
    269. : restaurants;
    270. // 调用 callback 返回建议列表的数据
    271. cb(results);
    272. },
    273. createFilter(queryString) {
    274. return (restaurant) => {
    275. return (
    276. restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) ===
    277. 0
    278. );
    279. };
    280. },
    281. loadAll() {
    282. return [
    283. { value: "110" },
    284. { value: "300" },
    285. { value: "600" },
    286. { value: "1200" },
    287. { value: "2400" },
    288. { value: "4800" },
    289. { value: "7200" },
    290. { value: "9600" },
    291. { value: "14400" },
    292. { value: "19200" },
    293. { value: "28800" },
    294. { value: "38400" },
    295. { value: "56000" },
    296. { value: "57600" },
    297. { value: "76800" },
    298. { value: "115200" },
    299. { value: "230400" },
    300. { value: "460800" },
    301. ];
    302. },
    303. },
    304. };
    305. script>
    306. <style scoped>
    307. /* /deep/ .el-textarea__inner {
    308. height: 320px !important;
    309. width: 80% !important;
    310. } */
    311. style>

    三:示例截图

    授权界面:

     

    授权成功后:

    使用串口工具调试发送和接收: 

    1、使用vspd创建一个对虚拟串口,com1和com2

    2、网页的连接com1,sscom连接com2就可以进行通信了

     

     

  • 相关阅读:
    基于粒子群优化的BP神经网络(分类应用) - 附代码
    【微信小程序】文章设置
    从一次性销售到持续收益:低代码服务商的转型之路
    RFID服装管理系统改善零售供应链
    【C语言笔记】dll库“__declspec”属性关键字总结,dllexport,dllimport
    JD(按关键字搜索商品)API接口
    MIME type备忘
    js基础算法
    vue报错信息汇总
    Android学习之路(18) 数据存储与访问
  • 原文地址:https://blog.csdn.net/ZhangY1217/article/details/125889000