个人案例
- IIOT控制器
IIoT控制器小程序,可以操控blockbot和ikakabot系列机器人。
IIoT控制器小程序扫码体验
- canvas画图随记
最近画了一张分享图,在此记录一下遇到的问题及解决方法。 画布尺寸自适应 微信小程序尺寸为rpx,会自适应各种机型,但canvas的方法参数默认为px,所以需要对画布上的每一项参数乘以(画布宽度/设备屏幕宽度),将rpx换算成px,达到尺寸自适应的目的,所以将此系数设置为全局变量。代码如下: [代码]var app = getApp(); const device = wx.getSystemInfoSync(); const width = device.windowWidth;//设备屏幕宽度 const xs = width / 375; [代码] 调用: [代码]createCard: function() { var context = wx.createCanvasContext('myCanvas'); context.fillText('内容', 100 * xs , 100 * xs) } [代码] 长文本换行 由于fillText只能画一行,但很多情况下是需要将长文本自动换行展示的,这个时候则需要对文本进行处理。 方法:遍历该文本,计算出每一字宽度之和,当该宽度大于文本最大宽度时绘制当前截取部分,并将绘制高度加上行高,宽度置0,重新计算并绘制下一行。当只剩最后一字时,绘制剩余部分。 缺陷:当文本内有换行符时,绘制会换行,但当前计算宽度不会增加,导致格式混乱。所以需要在计算宽度之和前判断该字符是否为换行符,若果是,则绘制当前部分,开始下一行的计算。 完善:如果需要知道绘制文本的总高度,设置初始文本高度为0,在绘制一行时加上行高则可。代码如下: [代码] /** * context:当前画布对象 * text:文本内容 * leftWidth:文本左上角x坐标 * initHeight:文本左上角y坐标 * canvasWidth:一行文本最大宽度 */ drawText: function(context, text, leftWidth, initHeight, canvasWidth) { var lineWidth = 0; //文本宽度 var textHeight = 0; //文本总高度 var lastSubStrIndex = 0; //每次开始截取的字符串的索引 for (let i = 0; i < text.length; i++) { if (text[i] == "\n") { //如遇换行 context.fillText(text.substring(lastSubStrIndex, i), leftWidth, initHeight, canvasWidth); //绘制截取部分 initHeight += 17.5 * xs; //17.5为字体高度 lineWidth = 0; lastSubStrIndex = i + 1; //截取字符串时跳过换行符 textHeight += 17.5 * xs; } else { lineWidth += context.measureText(text[i]).width; //计算每个字的宽度之和 if (lineWidth > canvasWidth) { context.fillText(text.substring(lastSubStrIndex, i), leftWidth, initHeight, canvasWidth); initHeight += 17.5 * xs; lineWidth = 0; lastSubStrIndex = i; textHeight += 17.5 * xs; } } if (i == text.length - 1) { //绘制剩余部分 context.fillText(text.substring(lastSubStrIndex, i + 1), leftWidth, initHeight, canvasWidth); textHeight += 17.5 * xs; } } return textHeight; }, [代码] 调用: [代码] var text = '新建项目选择小程序项目,选择代码存放的硬盘路径,填入刚刚申请到的小程序的 AppID,给你的项目起一个好听的名字,最后,勾选 "创建 QuickStart 项目" (注意: 你要选择一个空的目录才会有这个选项),点击确定,你就得到了你的第一个小程序了,点击顶部菜单编译就可以在微信开发者工具中预览你的第一个小程序。'; context.setFontSize(15 * xs) that.drawText(context, text, 30 * xs, 100 * xs, 320 * xs) [代码] 高度自适应 如碰到画布高度需要根据内容高度不同而不同,或者某元素与可变化高度的元素固定距离的情况,则需要计算出可变化元素高度,再根据该高度进行计算其他高度。例如: [图片] 微信图标始终距离文本30px,而该文本高度可变,所以图标的左上角y轴坐标=文本y轴坐标+文本高度+下边距,代码如下: [代码]var textHeight = that.drawText(context, text, 30 * xs 100 * xs, 320 * xs) context.drawImage('/images/wx.png', 68 * xs, (100 + 30) * xs + textHeight, 80 * xs, 80 * xs) [代码] 注意:因为计算文本高度的方法里已经乘过系数,所以这里不需要乘。宽度自适应同理。 绘制圆角矩形框 由于没有绘制圆角矩形的方法,所以需要将圆角矩形分开绘制。 方法:将四个圆角当成四分之一圆绘制,然后分别画四条边,坐标如下图所示。 [图片] 代码: [代码] /** * context:当前画布对象 * x:圆角矩形左上角x坐标 * y:圆角矩形左上角y坐标 * w:宽度 * h:高度 * r:border-radius * color:填充颜色 */ roundRect(ctx, x, y, w, h, r, color) { ctx.beginPath() // 左上角 ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5) // 上边框 ctx.moveTo(x + r, y) ctx.lineTo(x + w - r, y) ctx.lineTo(x + w, y + r) // 右上角 ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2) // 右边框 ctx.lineTo(x + w, y + h - r) ctx.lineTo(x + w - r, y + h) // 右下角 ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5) // 下边框 ctx.lineTo(x + r, y + h) ctx.lineTo(x, y + h - r) // 左下角 ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI) // 左边框 ctx.lineTo(x, y + r) ctx.lineTo(x + r, y) //填充颜色 ctx.setFillStyle(color); ctx.fill() ctx.closePath() } [代码] 调用: [代码]that.roundRect(context, 15 * xs, 60 * xs, 350* xs, 200 * xs, 14 * xs, '#ffffff') [代码] 文本加粗 官方文档里有说到font的使用规则与css语法一致,有几个需要注意的地方,否则可能会导致设置无效。 [图片] 调用: [代码] context.font = "normal bold 27px sans-serif"; context.setFontSize(27 * xs) context.fillText('加粗字体', 100 * xs , 145 * xs) [代码] 效果: [图片] 注意:在真机上若没有写第一个normal参数,则不能成功设置。 字体大小可以在下面重新赋值。 如果没有效果可以注意console有没有如下图所示 设置无效的警告,原因很大可能是因为参数写的不对。 [图片] 圆形头像绘制 方法:在画布上剪切一个圆,然后在圆上画头像,最后恢复即可。有一个需要注意的地方,drawImage方法只能绘制本地图片,如果需要绘制网络图片需下载完成之后再画。代码如下: [代码] context.save() context.beginPath() context.arc(77 / 2 * xs + 150 * xs, 77 / 2 * xs + 73 * xs, 77 / 2 * xs, 0, Math.PI * 2, false) context.clip() var headimg = '/images/headimg.jpg' context.drawImage(headimg, 150 * xs, 73 * xs, 77 * xs, 77 * xs) context.restore() context.draw(); [代码] 遇到的问题:当图片为长方形时,强行将图片压缩为正方形会导致头像变形。 解决办法:image组件里,参数mode有一个值为aspectFill,即保持纵横比缩放图片,只保证图片的短边能完全显示出来,我们参考这种思路来截取图片。 [图片] 这里以宽比高长的图为例。如上图所示,圆为头像显示位置,线为中线,矩形框为一张宽大于高的图片。矩形左上角即为画图时的左上角坐标。截部分如图所示,得到图片宽高后,短边固定为头像尺寸,长边根据短边缩放比计算得到。图片宽=原图宽 /(头像高 / 原图高)。左上角的x轴坐标为:中线x坐标 - 图片宽 / 2。代码如下所示: [代码] context.save() context.beginPath() context.arc(77 / 2 * xs + 150 * xs, 77 / 2 * xs + 73 * xs, 77 / 2 * xs, 0, Math.PI * 2, false) context.clip() var headimg = '/images/headimg.jpg'; //头像路径 var headimgHeight = 0; var headimgWidth = 0; wx.getImageInfo({ src: headimg, success(res) { headimgHeight = res.height; //原图高度 headimgWidth = res.width; //原图宽度 //当宽 > 高时 if (headimgWidth > headimgHeight) { var width = headimgWidth / (headimgHeight / (77 * xs)); //图片宽度 var x = (150 + 77 / 2) * xs - width / 2; //x轴坐标 context.drawImage(headimg, x, 73 * xs, width, 77 * xs) } else { //当高>=宽时 var height = headimgHeight / (headimgWidth / (77 * xs)); //图片高度 var h = (73 + 77 / 2) * xs - height / 2; //y轴坐标 context.drawImage(headimg, 150 * xs, h, 77 * xs, height) context.restore() context.draw(); } } }) [代码] 注意:这里得到的图片宽高已经是px为单位,所以不乘系数。
2019-03-18 - 一招之力,Python打通云开发七经六脉
“众所周知,云开发目前只支持Node js,如何突破这个限制?且看高手们如何用Python打通云开发七经六脉,让云开发的使用更加行云流水。” 一、项目背景 由于BBC六分钟官网直接访问,平时看BBC新闻也是颇费力气。于是我想,为何不自己做个“BBC新闻摘要小程序”呢? 二、小程序实现策略 做一个方便自己的小程序,不需要很复杂的架构。:用闲置的DigitalOcean服务器下载音频和对话脚本,传回国内COS。然后用小程序展示就搞定啦。 我还清晰地记得,5月17号,微信开发者社区推送了一条消息:可以外网上传文件到云存储了! https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/index.html 说实话,之前也考虑过小程序里的云存储,但好像只能通过开发者工具人工上传。所以收到这条推送之后第一时间就去看了文档,理了一遍小程序云存储上传逻辑是: step1: 用小程序Appid 和Appsecret 拿 Access_token step2: 用拿到的Access_token拿文件上传URL和相关参数 step3: 用拿到的URL和相关参数拼接完整的POST请求来上传文件 从写抓取脚本和小程序制作上线花了大概一天的时间。 三、如何用Python实现云开发的文件上传? 理顺了逻辑,接下来就是写代码了。 Python用来http请求的,选用requests。 首先:拿access_token [代码]def get_token(): token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + ID + "&secret=" + SECRET try: token = requests.get(token_url) token = token.json() return token["access_token"] except Exception as e: logging.error(e) [代码] 然后,用access_token获取文件上传相关参数 [代码]def get_upload_url(token, env, path): post_url = "https://api.weixin.qq.com/tcb/uploadfile?access_token=" + token playload = json.dumps({"env":env, "path":path}) try: upload = requests.post(post_url, data=playload) return upload.json() except Exception as e: logging.error(e) [代码] 拿到参数后需要解析重新拼接来完成上传: [代码]def parse_form(res): form = {} form["key"] = res["url"].split("/")[-1] form["Signature"] = res["authorization"] form["x-cos-security-token"] = res["token"] form["x-cos-meta-fileid"] = res["cos_file_id"] return (form, res["url"]) [代码] 最后,就是上传了: [代码]def upload(res, file): form = res[0] upload_url = res[1] with open(file, "rb") as f: form["file"] = f.read() try: success = requests.post(upload_url, files=form) except Exception as e: logging.error(e) [代码] 四、延展思考 其实Python实现小程序·云开发的文件上传,只是一个小功能实战,但是由此给我们的启示是,可以利用云开发的HTTP API去实现各类语言和云开发的对接。 关于云开发HTTP API的使用文档,可参考《云开发新能力,支持HTTP调用API》 最后放上小程序二维码,以及效果预览。 [图片] [图片]
2019-07-17 - (11)低功耗蓝牙能力
在无线通信领域,蓝牙是最基础又常用的能力。蓝牙组网十分简单,两设备间做个配对就可以建立起连接,其通信距离理论值为10米至100米以内,是近距离通信应用场景的首选。今天我们想跟大家分享小程序连接蓝牙的能力。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 目前蓝牙最为普遍的两种规格为蓝牙基础率/增强数据率 (BR/EDR) 和低功耗 (LE) 蓝牙。br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 蓝牙基础率/增强数据率 (BR/EDR) 是经典的蓝牙协议,常用在对数据传输带宽有一定要求的场景上,比如需要传输音频数据的蓝牙音箱等; br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 低功耗 (LE) 蓝牙是从蓝牙4.0起支持的协议,特点就是耗电极低、传输速度更快,常用在对续航要求较高且只需小数据量传输的各种智能电子产品中,包括心率监测仪器、体温计、血糖仪、智能穿戴设备、胎压监测和电子烟等等,应用场景广泛,所以小程序在很早的版本(基础库 1.1.0)就优先支持了低功耗蓝牙能力。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 我们在开发基于低功耗蓝牙接口的小程序时,需要先理解一下蓝牙在连接和通信过程中的一些概念。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 工作模式 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 低功耗蓝牙协议给设备定义了若干角色,其中最主要的角色是:外围设备(Peripheral)与中心设备(Central)。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 1 外围设备是用来提供数据,通过不停地向外广播数据,让中心设备发现自己。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 2 中心设备会扫描外围设备,发现有外围设备存在后,可以与之建立连接,之后就可以使用外围设备提供的服务(Service)。一般而言,手机会担任中心设备的角色,利用外围设备提供的数据进行处理或展示等等。因此,小程序提供低功耗蓝牙接口是默认设定手机为中心设备的。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 通信协议 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 在两个蓝牙设备建立连接之后,双方的数据交互是基于一个叫做 GATT (Generic Attribute Profile) 的规范,根据该规范可以定义出一个个配置文件(Profile),描述该蓝牙设备提供的服务(Service)。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 在整个通信过程中,有三个最主要的概念:配置文件(Profile)、服务(Service)、特性(Characteristic)。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 1 Profile 并不真实存在于蓝牙设备中,它只是被蓝牙标准预先定义的一些 Service 的集合,如果蓝牙设备之间要相互兼容,它们只要支持相同的 Profile 即可。一个蓝牙设备可以支持多个 Profile。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 2 Service 可以理解为蓝牙设备提供的服务,一个设备可以提供多个服务,比如电量信息服务、系统信息服务等。每个 Service 又包含多个 Characteristic 特性值,比如电量信息服务就会有个 Characteristic 表示电量数据,同时还会有一个 16bit 或 128bit 的 UUID 唯一标识该服务,像微信硬件平台的蓝牙智能灯的主服务 UUID 为 0xFEE7。16 bit 的 UUID 实际上是 128 bit 的缩短版,接收方收到后会补上蓝牙的 UUID 基数,目的是为了提高传输效率。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 3 Characteristic 是在 GATT 规范中最小的逻辑数据单元,由一个 value 和多个描述特性的 Desciptor 组成。实际上,在与蓝牙设备打交道,主要就是通过读写 Characteristic 的 value 完成。同样的,Characteristic 也是通过一个 16bit 或 128bit 的 UUID 唯一标识。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 总结一下,如下图所示,我们可以简单地理解为:每个蓝牙设备可能提供多个 Service,每个 Service 可能有多个 Characteristic,我们根据蓝牙设备的协议用对应的 Characteristic 进行读写即可达到与其通信的目的。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> [图片] br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 在理解了上面的模式和概念后,接下来我们看看如何可以使用小程序提供的蓝牙接口。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> Step 1 :扫描并发现蓝牙外围设备 扫描并发现蓝牙外设。如果蓝牙功能未开启,可监听蓝牙状态变化以便自动进入下一步,提升用户体验。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> [图片] br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> Step 2 :连接蓝牙外围设备 若之前连接过某个设备,可跳过扫描步骤,直接传入 deviceId 连接。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> [图片] br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> Step 3 :查找蓝牙外围设备的服务 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> [图片] br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> Step 4 : 读写指定服务的特性值 [图片] br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 基本上,小程序暴露出来的蓝牙接口都是系统级 API ,但在使用流程上对安卓和 iOS 两个平台做了统一,因此在使用这一套接口时也会出现一些因系统限制而导致的问题,在这里我们整理了一些常见的问题供开发者参考: br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 1 在安卓上,部分机型无定位权限或者是定位开关未打开时会搜不到设备。原因是蓝牙功能是可以获取到定位的,系统基于安全考量,使用蓝牙接口时必须要有定位权限,否则搜索不到; br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 2 在安卓上,部分机型获取设备服务时会多出 00001800 和 00001801 UUID 的服务,这是系统行为,注意不要使用这两个服务; br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 3 建立连接和关闭连接必须要成对调用。如果未能及时关闭连接释放资源,容易导致 state 133 GATT ERROR的异常; br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 4 与蓝牙设备通信的 MTU(最大传输单元)系统限定为 20 字节,如果超过则会出错,这里应该根据蓝牙设备协议进行分片传输。 br ="" class="" style="max-width: 100%; box-sizing: border-box; word-wrap: break-word !important;"> 更多有关小程序连接蓝牙功能的信息,可查阅[接口文档]。
2018-08-17