首页
学习
关于
友链
Search
1
小程序 蓝牙连接(出现的问题和一些解决方法)
338 阅读
2
颜色空间:RGB、HSV和HSL
167 阅读
3
小程序_连接蓝牙设备根据平台去打开定位权限
147 阅读
4
小程序 加快安卓手机向蓝牙设备发送大数据
98 阅读
5
侧边菜单切换的显示与隐藏,图标的 + 与 -
94 阅读
全部
感想
旅行
生活
学习
登录
Search
标签搜索
css
javascript
jquery
html
小程序
github
图床
假期
发布订阅
typecho
第一次
未来
快乐与忧伤
努力
奋斗
PicGo
倒计时
元旦
svg
vue
逸曦穆泽
人生,就是一步一步渐行成路
累计撰写
35
篇文章
累计收到
40
条评论
首页
栏目
全部
感想
旅行
生活
学习
页面
学习
关于
友链
搜索到
3
篇与
小程序
的结果
2022-12-17
小程序_图片转canvas,上传和rgb888转rgb565发送到设备
序: 小程序需要将图片上传到蓝牙设备,那么图片要转为二进制数据,转为二进制的图片数据不能过大,就要裁剪图片,因为要尽量减少图片的二进制数据,要将图片的rgb888转为rgb565,也就是24位缩小为16位数据;例如:100像素*100像素的图片,就有1万个点,一个点包含rgba四个数,只需要rgb这三个数,rgb三个数各包含8位(也就是二进制,为rgb888),rgb888为三个字节,rgb565为两个字节,数据量传输就少了三分一,但也有几十K的数据了,分240个字节每包发送完也要十秒左右;如果要上传,上传到后端的路径要是临时路径才可以,不能是本地路径,所以要使用 canvas 转一下得到 tempFilePath 的临时路径,一、图片渲染为 canvas 得到临时路径,并上传// 选择本地图片渲染成 canvas locationImg(e) { let imgUrl = e.currentTarget.dataset.img; let that = this; gbImgComStr = ""; wx.createSelectorQuery().select('#canvasImg').fields({ node: true, size: true }).exec((res) => { const canvas = res[0].node; const ctx = canvas.getContext('2d'); var dpr = wx.getSystemInfoSync().pixelRatio // 设备像素比 canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr ctx.scale(dpr, dpr) const img = canvas.createImage(); img.onload = () => { ctx.drawImage(img, 0, 0, 200, 200); wx.canvasToTempFilePath({ x: 0, y: 0, width: 200, height: 200, destWidth: 200, destHeight: 200, canvas: canvas, fileType: "jpg", quality: .8, success(res) { that.initImgData(res.tempFilePath); // 使用服务器 } }) } img.src = imgUrl; }) // console.log(imgUrl) }, // 图片上传到服务器 initImgData(imgUrl) { let that = this; let gbImgPath = ""; wx.uploadFile({ url: apiUrl+'/imgUpload', //你的后端上传路径 filePath: imgUrl, name: "imgPath", // 键值key header: { "content-type": "multipart/form-data"}, success: function (res) { if (res.statusCode == 200) { gbImgPath = JSON.parse(res.data).imgPath; // 存放到服务器上的图片路径 } }, fail: function (err) { wx.showToast({ title: "上传失败", icon: "none", duration: 2000 }) }, }) // 图片回显 或 gbImgPath that.setData({ imgPath: imgUrl }) },后端接口(PHP)://对应的文件夹 public function dirFile($name){ $dirName = "store/".$name."/".date("Ym"); if(!file_exists($dirName)){ mkdir(app()->getRootPath().'public/'.$dirName,0777,true); } return $dirName; } // 图片处理 function imgUpload(){ if(request()->isPost()){ $dirName = $this -> dirFile('image'); $file = $_FILES['imgPath']; // 获取小程序传来的图片 if(is_uploaded_file($file['tmp_name'])) { $type = $file["type"];//文件类型 如:image/png $ext = strrchr($type, '/');//切割字符串 //类型判断 if (in_array($ext, ['/jpeg', '/jpg', '/png', '/svg', '/gif','ico'])) { $filePrefix1 = strstr($file["name"], '.', true);// 文件名前缀 无.png $filestr = date("Ymdhis").$filePrefix1; // 避免同一时间调用该方法时产生同一个文件名 $fileExt1 = strstr($file["name"], '.'); // 获取 .png $fileName = $dirName . "/" . md5($filestr) . $fileExt1; //拼接文件路径 //判断文件是否存在 // if (file_exists($fileName)) { // return json(["statusCode" => 200,"msg"=>"exist","imgPath" => $fileName]); // } else { //保存文件 ($_FILES["文件名"]["tmp_name"],newPath),(旧路径,新路径) move_uploaded_file($file["tmp_name"], $fileName); return json(["statusCode" => 200,"msg"=>"success","imgPath" => $fileName]); // } } return json(["statusCode" => 402,"msg"=>"format error"]); } return json(["statusCode"=>401,"msg"=>"file error"]); } return json(["statusCode"=>400,"msg"=>"request error"]); }二、在小程序上将图片数据rgb888转为rgb565var gbImgComStr = ""; // 图片:16进制补零 function imgHexFill(hexStr) { let len = hexStr.length; var map = { 1: "000", 2: "00", 3: "0" } if (map[len]) { hexStr = map[len] + hexStr; } return hexStr; } // 上为 Page 外,下为 Page 内 // 图片画成 canvas 获取数据 initImgData565: async function(imgUrl) { return new Promise((resolve, reject) => { let that = this; imgLimitNum = 0; wx.createSelectorQuery().select('#canvasImg').fields({ node: true, size: true }).exec((res) => { const canvas = res[0].node; const ctx = canvas.getContext('2d'); // var dpr = wx.getSystemInfoSync().pixelRatio // 设备像素比 var dpr = 1; canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr // ctx.scale(dpr, dpr) let sideLen = 100; ctx.fillRect(0, 0, sideLen, sideLen); const img = canvas.createImage(); img.onload = () => { ctx.drawImage(img, 0, 0, sideLen, sideLen); var imageData = ctx.getImageData(0, 0, sideLen, sideLen).data; // console.log(imageData.length) var rgbStr = ""; var rgb = 0; var rgb565 = 0; var w = 0; var h = 0; // 逐列式 (逐行式则是Z扫描) for (var w = 0; w < sideLen; w++) { for (var h = 0; h < sideLen; h++) { var offset = (h*sideLen + w)*4; var red = imageData[offset]; var green = imageData[offset + 1]; var blue = imageData[offset + 2]; rgb = 0xff000000 | (red << 16) | (green << 8) | blue; rgb565 = ((rgb & 0xf80000) >> 8) | ((rgb & 0xfc00) >> 5) | ((rgb & 0xf8) >> 3) rgbStr += imgHexFill(rgb565.toString(16)); } } gbImgComStr = rgbStr.toUpperCase(); imgUrl != "" ? resolve() : reject(); } img.src = imgUrl; }) }) },由于数据过大,这些数据还要分包发送到蓝牙设备,首先,要发第一包数据告诉设备要发图片的指令了,可以加入长度,这个要和设备那边沟通,分包发送还不能全部直接发,不然蓝牙设备很容易造成丢包的,小程序发完了,设备那边可能才获取十分之一呢,要分20、30包发完后再发一个确定指令,如果返回正确的指令则继续分20、30包发下去再发一个确定指令,然后重复发完为止;部分代码:// 图片_下个数据包指令 imgNextCmd: async function(){ let that = this; let sData = that.data; let comStr = "", roleData = "", command = ""; let rlt = ""; let packageNum = 30; // 连续发送包的个数 var rowSum = ((imgPackageSum - imgIndex) >= packageNum) ? (imgIndex + packageNum) : imgPackageSum; for (let row = imgIndex; row < rowSum; row++) { comStr = gbImgComStr.substr((row)*(imgCharLen-10),(imgCharLen-10)); roleData = "AC" + decToHexLenFill(row+1) + decToHex(comStr.length/2) + comStr; let res = new Uint8Array(roleData.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16) })); command = roleData + arrSumCheck(res); imgIndex += 1; // await sleep(50); rlt = await that.sendCom(command, sData.deviceId, sData.serviceId, sData.characteristicId); if (rlt == "success") { // 包确认 if(row == (rowSum-1)){ // console.log(">>> sure",rowSum)// 成功 let sureCom = "BCxxxx"+decToHexLenFill(row+1); let cs = new Uint8Array(sureCom.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16) })); let sureCommand = sureCom + arrSumCheck(cs); that.writeBLECharacteristicValue(sureCommand); } } } if(rowSum == imgPackageSum && imgLimitNum == 0){ imgLimitNum = 1; let endCom = "xxx"; // 传输完毕 that.writeBLECharacteristicValue(endCom); } },彩蛋: 一般一个包只能发20个字节,你觉得一张150*150的图片数据发完需要多久?要两分钟左右吧,这个还没考虑中途断连等情况呢!你觉得你是用户,你会接受吗?所以,可以先获取一下mtu是很有必要的,一般Android的是23(包含头部3个字节信息),iOS的是512,有部分Android手机是没有mtu值的,只能落空默认值了,Android可以协商mtu值 wx.setBLEMTU ,你可以一个包发256、300个字节,看你需求,过大会产生一些问题,并且每包发送也可以设置一下延迟多少毫秒,也可以避免传输过程中断连问题。
2022年12月17日
59 阅读
0 评论
0 点赞
2022-04-23
小程序 canvas 画布半圆环
效果图:2、wxml:<view class="fragrance_item"> <view class="fragPos"> <view class="fragNum">{{perfumePercent}}%</view> <view class="fragText">香水余量</view> </view> <view class="fragPsoReset"> <view bindtap="resetTab" class="fragReset" hover-class="fragModel_hover"> <image class="fragReset_icon" src="/static/image/reset.png" mode="widthFix"> </image> <view>复位</view> </view> </view> <view class="fragRing"> <canvas id="perfumeBalance" type="2d" style="width: 220px; height: 120px;" ></canvas> </view> </view>3、wxss:.fragModel_hover{box-shadow: 0.2rem 0 0.3rem #8fd0e0, -0.2rem 0 0.3rem #58bfd1;} .fragrance_item{position: relative;margin-left: 15px;margin-right: 15px;} .fragPos{position: absolute;left: 50%; top: 50%;transform: translateX(-50%);text-align: center;} .fragPos .fragNum{color:#32CD32;} .fragPsoReset{position: absolute;right: 3px;top: 36px;text-align: center;} .fragPsoReset .fragReset{display:inline-block;padding:8px 13px;background-color: rgba(50,80,80.68);border-radius: 15px;transition: all .3s ease-in;font-size: 12px;} .fragPsoReset .fragReset_icon{width:22px;height:22px;} .fragRing{width: 220px;height:120px;margin: auto;}4、js:let perfumeNum = 1.5; Page({ data: { perfumePercent: 0, // 香水余量 }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { let that = this; // 香水余量圆环 const query = wx.createSelectorQuery() query.select('#perfumeBalance') .fields({ node: true, size: true }).exec((res) => { that.initRing(res); }) }, // b、香水余量 initRing(res) { let that = this; const canvas = res[0].node const ctx = canvas.getContext('2d'); const dpr = wx.getSystemInfoSync().pixelRatio // 设备像素比 canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr ctx.scale(dpr, dpr); var circleR = 220 / 2; // 画布的一半,用来找中心点和半径 ctx.translate(0, 0); // 定义起始点 function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 取色圆环 ctx.beginPath(); ctx.strokeStyle = '#fff';//transparent ctx.lineWidth = 12; ctx.lineCap='round' // 设置圆环端点的形状 ctx.arc(circleR, circleR-10, circleR - 35, Math.PI, 2 * Math.PI, false); ctx.stroke(); ctx.closePath(); console.log("香水余量") // 进度_香水余量 ctx.beginPath(); // 起始一条路径,或重置当前路径 ctx.lineWidth = 6; // 线条宽度 ctx.lineCap='round' // 线条的结束端点样式 ctx.arc(circleR, circleR-10, circleR - 35, Math.PI,perfumeNum * Math.PI, false); ctx.strokeStyle = "#00CED1";//transparent ctx.stroke(); canvas.requestAnimationFrame(draw); } draw(); }, // 复位 resetTab(){ let that = this; perfumeNum = 2; // 香水余量为100% that.setData({ perfumePercent: 100, }) }, })
2022年04月23日
12 阅读
0 评论
0 点赞
2022-03-11
小程序 蓝牙连接(出现的问题和一些解决方法)
序:最近做小程序,要用到蓝牙模块的,不知道如何下手,没关系,看文档,看案例!看文档: 微信文档 看案例: 谷歌/百度/知乎等等,只要你觉得合适,都可以去尝试!一、小程序 BLE 开发 API 简介操作蓝牙适配器的共有 4 个:wx.openBluetoothAdapter 初始化蓝牙适配器 1 wx.closeBluetoothAdapter 关闭蓝牙模块 2 wx.getBluetoothAdapterState 获取本机蓝牙适配器状态 3 wx.onBluetoothAdapterStateChange 监听蓝牙适配器状态变化事件 4扫描和获取周围 BLE 设备的有4个(操作方式与普通蓝牙一样):wx.startBluetoothDevicesDiscovery 开始搜寻附近的蓝牙外围设备 5 wx.stopBluetoothDevicesDiscovery 停止搜寻附近的蓝牙外围设备 6 wx.getBluetoothDevices 获取所有已发现的蓝牙设备 7 wx.onBluetoothDeviceFound 监听寻找到新设备的事件 8连接BLE设备的2个:wx.createBLEConnection 连接低功耗蓝牙设备 9 wx.closeBLEConnection 断开与低功耗蓝牙设备的连接 10连接成功后,读写BLE对应特征对象的数据:wx.getConnectedBluetoothDevices 根据 uuid 获取处于已连接状态的设备 11 wx.getBLEDeviceServices 获取蓝牙设备所有 service(服务) 12 wx.getBLEDeviceCharacteristics 获取蓝牙设备所有 characteristic(特征值)13 wx.readBLECharacteristicValue 读取低功耗蓝牙设备的特征值的二进制数据值 14 wx.writeBLECharacteristicValue 向低功耗蓝牙设备特征值中写入二进制数据 15 wx.notifyBLECharacteristicValueChange 启用低功耗蓝牙设备特征值变化时的 notify 功能 16 wx.onBLECharacteristicValueChange 监听低功耗蓝牙设备的特征值变化 17 wx.onBLEConnectionStateChange 监听低功耗蓝牙连接的错误事件 18二、小程序蓝牙连接流程我的连接流程是这样的(代码我就不多贴了,只要你有思路、有想法,你就是黑马):初始化蓝牙设备之前,我做了一些的骚操作(不懂的查一下):1、打开定位/** 获取定位信息 */ getLocation: function () { let that = this; // 获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限 wx.getSetting({ success: function (res) { if(res.authSetting['scope.userLocation'] != undefined && res.authSetting['scope.userLocation'] != true){ wx.showModal({ title: '是否授权当前位置', content: '需要获取你的地理位置,请确认授权,否则无法获蓝牙', success: function(mres){ if(mres.confirm){ wx.openSetting({ success (authData) { if(authData.authSetting['scope.userLocation'] == true){ wx.showToast({ title: '授权成功', icon: "success", duration: 1000 }) }else{ wx.showToast({ title: '授权失败', icon: "error", duration: 1000 }) } } }) }else if(mres.cancel){ wx.showToast({ title: '授权失败', icon: "error", duration: 1000 }) } } }) }else{ // 当前位置 wgs84 返回 gps 坐标,gcj02 返回可用于 wx.openLocation 的坐标 wx.getLocation({ type: 'wgs84', success: function (res) { console.log('打开地理位置') }, }) } }, }) },2、这个放到一个初始化方法里面,,校验一下微信的蓝牙权限是否打开:init(){ let that = this; // 可以通过 wx.getSetting 先查询一下用户是否授权了 "scope.bluetooth" 这个 scope wx.getSetting({ success(res) { // (1) 微信授权已关闭,弹窗打开 if (res.authSetting['scope.bluetooth'] != undefined && res.authSetting['scope.bluetooth'] == false) { wx.openSetting({ success (authData) { if(authData.authSetting['scope.bluetooth'] == true){ wx.showToast({ title: '授权成功', icon: "success", duration: 1000 }) that.openBluetoothAdapter(); // 打开蓝牙适配器 }else{ wx.showToast({ title: '授权失败', icon: "success", duration: 1000 }) } } }) } // (2) 'scope.bluetooth'属性不存在,需要授权 if(res.authSetting['scope.bluetooth'] == undefined){ wx.authorize({ scope: 'scope.bluetooth', success(res){ that.openBluetoothAdapter(); // 打开蓝牙适配器 } }) } // (3) 已经授权 if(res.authSetting['scope.bluetooth'] == true){ that.openBluetoothAdapter(); // 打开蓝牙适配器 } } }) }初始化蓝牙设备 1=》 (1_success)成功:开始搜索附近的蓝牙外围设备 5 , (1_fail)失败:获取蓝牙适配器状态 4;=》1_success:发现蓝牙设备 8 (对结果进行过滤,符合条件的设备进行连接) =》 连接到目标设备 9 ,连接成功,监听蓝牙低功耗连接状态的改变事件 18,关闭蓝牙搜索 6 =》 获取蓝牙低功耗设备所有服务 12 =》 获取某个服务下所有的特征值 13 (打印一下特征值有:notify,read,write特征的),监听蓝牙低功耗设备的特征值变化 17 =》 启用低功耗蓝牙设备特征值变化时的 notify 功能 16 ;=》1_fail:available 为true时,开始搜索附近的蓝牙外围设备 5,否则提示检查蓝牙是否打开你会发现,有好几个是一些流程中没用到的,但是,这是不可能发生的,存在必有其道理;(1)比如:你刷新时,你不可能再重新走一遍初始化蓝牙吧,那就要用到 获取蓝牙适配器状态 3 ,判断是 获取所有已发现的蓝牙设备 8 还是 开始搜索附近的蓝牙外围设备 5;(2)比如:要你断开主动断开蓝牙连接,断开与低功耗蓝牙设备的连接 10;(3)比如:短时间内,频繁退出或多次扫描 重新进入已连接蓝牙的小程序,在 onLoad 要用的 根据 uuid 获取处于已连接状态的设备 11(连接后,缓存 services 连接的 uuid 值),不然你已连接了设备,还一直搜不到;(4)比如:关闭蓝牙模块 2,获取所有已发现的蓝牙设备 7,读取低功耗蓝牙设备的特征值的二进制数据值 14,向低功耗蓝牙设备特征值中写入二进制数据 15 ,这个几个不用我多说了吧。三、总结:iOS 的坑是比较少一些的,Android 机的确实有很多不完善的地方,毕竟是百花齐放的嘛
2022年03月11日
338 阅读
6 评论
0 点赞