1. 需要在微信开发者工具中,点击右上角的详情->本地设置,勾选将JS编译成ES5. 这个相当于启用了babel
  2. 需要在miniprogram\目录中安装npm i -S regenerator,如果之前没有初始化过npm,那还需要提前初始化npm init -y
  3. 可以找一个页面的js文件检验一下:
function f1(){
  return new Promise(resolve=>resolve("OK"));
}
async function f2(){
  return "OK";
}// 上面的f1和f2其实是等价的. 也就是说async是返回promise对象的函数的语法糖, 是不是写起来短了很多
function f3(){
  let r1, r2
  f1().then(res=>{
    r1 = res
    return f2()
  }).then(res=>{
    r2 = res
    console.log(r1,r2)
  })
}
async function f4(){
  let r1 = await f1()
  let r2 = await f2()
  console.log(r1,r2)
}// 上面的f3,f4是等价的. 也即是await是then(res=>{return promiseObj})的语法糖, 不但短了, 也不需要每次then的最后一句return 下一个异步promise函数了, 也不需要套好多括号了. 比在不停的return中推进流程看起来要清晰得多
f3() // 打印 OK OK
f4() // 打印 OK OK

云数据库

官方文档
这是一种NoSQL数据库, 具体是哪种则没有讲.
我感觉NoSQL和SQL在使用上的最大区别是字段的数据类型. NoSQL可以添加Object和Array类型的字段. 然而相同点在于, 无论什么字段类型,每次查询到的都是整条记录(NoSQL叫做Doc文档),而不可能查询Array或Object字段内的子元素.
由于有Array/Object字段, 可以选择其中的子元素作为查询条件, 或者只更新子元素的值.
通过微信开发者工具云开发->数据库建立一个集合,相当于是一张表.

数据类型

除了常规js类型string/number/boolean/array/object以外, 还有1个略有不同的类型date和2个全新类型geo和null
date: 与js的date类型兼容,但是可以使用服务器端生成大法, 也就是说在服务器端再生成date. 如: {createTime: db.serverDate()}
geo是地理位置, 看源文档吧
null是占位符, 表示字段存在但是没有值

查询和删除

默认从小程序增加一条记录都会有一个_id和_openid, 并且collection的默认读写权限限制在创建者也就是小程序用户本身的, 所以查询的时候即使不带_openid也只能查到自己的. 如果修改collection权限, 最多也只能扩展到读取所有记录, 而不能删改其他用户创建的记录.

增删改查代码对比

单条的增删改查(单条的查其实只能算是引用):

db.collection('todos').add({data: {description: "learn cloud database"})
db.collection('todos').doc(doc_id).remove()
db.collection('todos').doc(doc_id).update({data: {description: "learn cloud database"})
db.collection('todos').doc(doc_id).get()

多条的增删改查

增加多条只有服务端支持, 可使用云函数实现, 参考: https://blog.csdn.net/weixin_44702572/article/details/127438504
多条的改删查都是通过where来实现的, 并且用到_工具const _ = db.command

db.collection('todos').where({price: _.lt(100)}).get()
db.collection('todos').where({price: _.lt(100)}).remove()
db.collection('todos').where({price: _.lt(100)}).update({data: {description: "learn cloud database"})

对数组元素数量的修改:
前面省略.update({data: {tags: _.push('mini-program')}), 除了push还可以用popshiftunshift
对同一数据中数组元素单个值的修改: 假如有元素scores:[10, 20, 30]对单个数组元素值20改为25:
前面省略.update({data: {'scores.1': 25}})
对多组数据中数组元素单个值的修改. 第一个20改成25,前面有where,并且用$替代每一个找到的数据中第一个值的index
前面省略.where({scores: 20}).update({data: {'scores.$': 25}})
对同一数据的数组元素的所有值修改:
.update({data: {'scores.math.$[]': _.inc(10)}})

对象元素的修改, 只修改一个key的值:
前面省略.update({data: {style: {color: 'blue'}})
修改整个对象的值:
前面省略.update({data: {style: _.set({color: 'blue'})})
对对象的键值修改, 可以参考数组的点路径表示法, 如'root.objects.1.numbers.2': 80

分页

.skip(num)

统计

腾讯的文档写得不好, 不过这个数据库显然是nosql, 我估计可以参考mongodb的文档, 搜索后果不其然, 很多文章也说这个本质上就是mongodb.
统计主要是在collection上使用.aggregate(). 这个单词官方文档叫做"聚合", 实际上就有"统计"的含义.
这个和在excel里面做统计类似, 而且理论上应该会更强大.
.aggregate()以后使用一系列的操作处理数据, 最后得到想要的统计结果, 这一系列操作称之为"流水线"(mongodb中叫做管线pipeline), 而"操作"官方叫"聚合阶段".每一种操作都有其特定的一些工具(操作符), 为提高效率, 操作之间有优先顺序, 一些操作需要在另一些之前, 如: 可以缩减数据量的操作优先, 这样后面处理的操作数据量就会比较小.
对数据表中字段引用时, '$字段名'表示该字段标题, 用在_id里; $.操作符('$字段名')表示对字段下的数据按操作符要求操作. 还可用$.操作符(1)表示_id里引用的第1个字段.

聚合阶段 解释 操作符
group 像excel的数据透视表, _id是要透视的字段,可以多个,数据区是要统计的字段, 可以通过操作符设置统计方式. 这儿的操作符是所谓的"累计器", 即一个统计字段下的一组输入数据, 操作后变一个数据 addToSet(合为数组) mergeObjects(合并对象) avg first last max min push(合为数组且能定义数组元素) stdDevPop(标准差) stdDevSamp(样本标准差) sum
sort 排序 用法 .sort({字段名A: -1, 字段名B: 1}), -1为降序, 1为升序
sortByCount 是group+sort的操作 用法 .sortByCount('$字段名'), 只能以降序排列
match 用法同where 操作符同where, 不能使用聚合操作符
limit 结果数量,默认20, 可以修改 用法: .limit(数字)
skip 用于分页 用法: .skip(数字)
lookup 连表查询, 相当于excel的vlookup. 不同点是:1. excel只能匹配一个, 这儿会匹配出一组包括所有字段的数据并作为数组放在as的字段里; 2.本方字段为字符串,对方可以是字符串也可以是字符串数组 .lookup({from: '对方集合名',localField: '本方字段',foreignField: '对方字段',as: '结果数组字段'})
count 计算表中条目数,并映射到一个字段名输出 .count('字段名')
end 每个流水线最后都要带的, 指示执行 .end()
以下是不常用的
geoNear 按距离排序, 内容比较多, 因暂不涉及地图应用就不展开了
addFields 添加新字段, 比如对数组合计, 对几个字段运算得出新字段; 还可以用路径字符串给object添加子记录 可以用操作符
bucket 分组并进行运算.与group不同的是, bucket是通过边界来分组的,分组后_id为边界值 .bucket({groupBy: '$字段名',boundaries: [0, 50, 100], default: 'other', output: { count: $.sum(1),ids: $.push('$_id')}})
bucketAuto 与bucket区别是,bucketAuto通过指定分组数来尽量平均分组,并且自动输出每组最大值/最小值/计数而无须指定,分组依据也可以通过指定granularity来定规则 .bucketAuto({groupBy: '$字段名',buckets: 3})
project 映射字段, 其实就是给字段改个名字,也可以保留字段或删除字段,还可以用路径字符串嵌套 .project({保留字段名: 1, 删除字段名: 0, 全新字段名: 可包括操作符的对象})
replaceRoot 可以把对象字段提升一级, 把对象字段变成collection, 对象的属性变成字段, 也可以构建新的字段 .replaceRoot({newRoot: '$对象字段名'})
sample 随机抽样指定数量的记录 .simple(抽样数)
unwind 有点跟group相反, 将doc按一个数组字段拆分成多个doc .unwind('$数组字段名') 或 .unwind({path: '$数组字段名', includeArrayIndex: 'index', preserveNullAndEmptyArrays: true})

两种等价的操作形式

通过chatGPT和官方文档, 发现有两种等价的操作, 以group中提取日期中的年份为例:

// chatGPT的代码
db.collection('heightDataDebug').aggregate().group({
  _id: { $year: "$date" }
})
// 官方文档建议的代码
const $ = db.command.aggregate
db.collection('heightDataDebug').aggregate().group({
  _id: $.year('$date')
})

形式上来看, 官文突出了操作与数据的不同, 而chatGPT代码将操作作为key,数据作为value, 初看起来就有些迷惑. 但chatGPT这种形式可能说明了一个问题,就是这本质只是一个object而已,表述了运算的方式, 但没有进行运算. 实际运算是数据库端进行的.

云函数

服务端:

// index.js 是入口文件,云函数被调用时会执行该文件导出的 main 方法
// event 包含了调用端(小程序端)调用该函数时传过来的参数,同时还包含了可以通过 getWXContext 方法获取的用户登录态 `openId` 和小程序 `appId` 信息
const cloud = require('wx-server-sdk')
exports.main = async (event, context) => {
  let { userInfo, a, b} = event
  let { OPENID, APPID } = cloud.getWXContext() // 这里获取到的 openId 和 appId 是可信的
  let sum = a + b

  return {
    OPENID,
    APPID,
    sum
  }
}

客户端:

wx.cloud.callFunction({
  // 需调用的云函数名
  name: 'add',
  // 传给云函数的参数
  data: {
    a: 12,
    b: 19,
  },
  // 成功回调
  complete: console.log
})
// 当然 promise 方式也是支持的
wx.cloud.callFunction({
  name: 'add',
  data: {
    a: 12,
    b: 19
  }
}).then(console.log)

可以通过在data中传入type的方式, 使一个云函数支持多个子函数. 在服务端使用switch对type判断并调用子函数.

FAQ

  • console中提示: appservice.js:4786 当前基础库版本过低,建议调整最低支持基础库版本。 怎么调整?
    答: 百度搜到腾讯的官方文档是要求网页登录小程序后台,在设置-基本设置-版本设置-基础库最低可用版本中设置. 但是, 是不对的, 或者至少是不完整的.
    首先, 这行报错也没告诉你过低是低于多少, 现在是多少. 其次, 也没说在哪儿调整.
    其实这句报错查的是本地的基础库, 在小程序开发工具中点击右上角的详情-本地设置-调试基础库中修改. 版本一般建议改为最高的.
    但是究竟是比较了那个版本才说基础库过低呢?
    幸好appservice.js:4786是能跟踪到源码的, 源码压缩过,但格式化后还可以读懂. 可以看到下面这句:
    v = wx.getSystemInfoSync().SDKVersion,
    b = [];
    a.compareVersion(v, "2.10.3") >= 0 && b.push("wx://form-field-button"), a.compareVersion(v, "2.16.1") < 0 && console.warn("当前基础库版本过低,建议调整最低支持基础库版本。")

    所以, 通过wx.getSystemInfoSync().SDKVersion可以取到本地基础库, 最低要求是2.16.1.

隐私授权

小程序用户信息相关接口调整: https://developers.weixin.qq.com/community/develop/doc/000e881c7046a8fa1f4d464105b001?idescene=6

前言

都说微信开发文档是个乐色,过分了啊!我看是非常乐色……

使用扩展组件miniprogram-component-plus

通过开发文档跳转到github链接, 依然没有任何怎么使用它的提示.
其实使用的方法是和使用npm第三方库一样的. Unbelievable!
npm支持的文档在这儿
现在的微信开发工具默认小程序下面会有子目录miniprogram,所以应该进到这个子目录里再下载, 以tabs组件为例:

cd miniprogram
npm init -y
npm i @miniprogram-component-plus/tabs --save

下载完了以后, 点击工具->npm构建后才能使用. 你会看到多了一个miniprogram_npm目录
在要使用的页面目录的json中定义:

  "usingComponents": {
    "mp-tabs": "@miniprogram-component-plus/tabs/index"
  }

还是说这个tabs组件, 长内容不支持上划! 所以超长的内容还是不要用了.

在wx:for列表循环中, 怎么知道点击的是第几项列表?

反正官方文档中我没找到, 是在官方源码中自己参详出来的. 和vue不同, 不能直接在bindtap中传递参数, 需要通过data-index

<view wx:for="array">
  <view bindtap="tapme" data-index="index">{{item}}</view>
</view>
data :{
  array:['a','b','c']
},
tapme: function tapme(e){
   console.log("序号:", e.currentTarget.dataset.index)
}

data-* (dataset)从wxml向js函数传参

参考 事件捕获
按上面的, data-任意都可以. 在bind的函数中, 通过 e.currentTarget.dataset. 来获取

跨页面传参

是通过url传参的, 也就是标准的&a=x&b=y这种方式. 在页面的onLoad(option)函数中, 提取option.xoption.y即可

重要的公告

微信这个公告和蓝牙,获取用户信息等都有很大关系, 需要参照.
setData使用注意, 也比较重要, 如果遇到速度慢, 一定要看看.

超长列表的渲染

超长列表不适合用data中的数据进行常规的渲染, 因为data中数据是全渲染的, 不管array有多大. 此时应使用扩展组件recycle-view, 顾名思义是使用了某种回收技术, 没有全部渲染, 而是把视图外的渲染进行了"回收".
注意这个组件要用npm安装并构建.
recycle-view用起来很不一样,首先数据不在data里维护, 而是单独用createRecycleContext()创建和维护一个ctx对象. 其次,ctx对象有一系列很像Array的函数, 如splice等.

坑来了!

但注意了, 而ctx的append方法表现非常奇诡. 如果在数组arr上用一次append方法, 你会发现后面的ctx.splice方法会直接操作原数组arr! 但如果你用两次append方法, 你就又会发现ctx.splice操作的又不是原数组了!
所以为了表现的一致性, 我建议先append一个空数组[],再append原数组arr, 保证后面的ctx.splice只操作复制数组, 并且同步在原数组arr.splice一次.

ucharts的安装

ucharts使用npm i @qiun/wx-ucharts --save安装,并且npm构建以后, 会发现小程序界面白屏, 提示错误. 此时通过npm uninstall @qiun/wx-ucharts卸载掉以后(不影响构建后的wx-ucharts组件), 就正常可以用了.

微信头像和昵称的获取

据说这个获取接口变了数次, 看这个论坛公告, 很有意思, 截止今天这个公告有88页程序员的留言, 把腾讯都骂出翔了.
总之意思是从基础库2.27.1版本以后wx.getUserInfo和wx.getUserProfile接口都不能用了, 只保留分别填写微信头像和昵称的能力. 这样的话,

微信logManager和RealtimeLogManager功能

微信日志管理确实非常方便, 本地日志LogManager和客服沟通反馈界面直接打通,实时日志还可以进行实时分析. 然而日志是对JSON对象用stringify后存储的, 部分比较复杂的JSON对象很可能存在循环调用情况导致序列化报错, 进而使整个小程序异常. 所以并不是所有能console.log的内容都可以用logmanager的log输出, 不要log输出特别大型的不知道底细的对象. 现在已知listCtx就有这种情况.
LogManager保存5M日志, 存新删旧. RealtimeLogManager 现在属于We分析功能之一, 有基础版和专业版之分.

参考https://zhuanlan.zhihu.com/p/612916407
上文中说的很好了, 还有GIF动画演示. 针对其中缺少的部分:

  • 一个是地图权限设置, 在文件>选项和设置>选项>全局>安全性>地图和着色地图视觉对象>使用地图和着色地图视觉对象上打钩. 如果没有显示则需要重启Power BI
  • 中国地图JSON转换后的, 可以直接这儿下载使用 不需要在转换了: China_topo.zip

人类的颜色视觉

人类亮视觉主要依靠集中在黄斑区域的视锥细胞, 按其感受波长的长度分为L/M/S型, 其敏感波长和数量占比是: 参考

视锥细胞类型 敏感波长 敏感色 数量占比
S型 437nm 蓝色 2%
M型 533nm 绿色32%
L型 564nm 黄绿色 64%

哈哈, 想不到吧, L型看到的564nm其实是黄绿色!可是大多数文章都将L型视锥细胞敏感波长说成是红色! 如下图, 正好处在黄色和绿色的边界.

从波长上看, M和L只差了31nm, 其实是距离很近的. 而S型和和M型相差96nm. 从生物演化角度, M型也是从L型演化出来的.
色盲最多的也是红绿色盲, 也就是红绿无法分辨. 我猜测是L和M细胞缺少分化这一步导致的.

还有就是, S型细胞出奇的少, 不知这和蓝光容易损害黄斑区是否有一定关系,也就是不清楚对蓝光敏感的细胞容易受损还是不敏感的容易受损.

显示器颜色

显示器颜色的红绿蓝, 我测试了一下我的手机屏幕, 峰值分别为458/522/620nm. 其中蓝色和绿色间隔64nm, 绿色和红色间隔98nm.
这个奇怪的是和视锥细胞敏感色并不一致.

LED 波长 与视杆细胞敏感波长距离
458nm +21nm
绿 522nm -11nm
620nm +56nm

三原色

参考: https://www.kxting.com/article/20221114/949298.html
由国际照明委员会(EIC)定义,选择红色(波长λ=700.00nm),绿色(波长λ=546.1nm),蓝色(波长λ=438.8nm)三种单色光作为表色系统的三基色。其中R(red)、G(Green)、B(Blue)分别是红绿蓝的英文首字母。

三原色|波长|与视杆细胞敏感波长距离
--|--|
B | 439nm |+2nm
G | 546nm |+13nm
R | 700nm |+136nm

可见也是有很大差异.
这个标准三原色, 经过人的视锥细胞, 是否能看到所有的色彩(380~780nm)呢? 这就和色度图联系起来了, 参考https://www.jianshu.com/p/213e4500dbea
色度图只有x,y两个坐标, 怎么对应RGB三个值? 那当然就是相当于把亮度取消了. 怎么取消? 把RGB值归一化处理, 让R+G+B=1就可以了,也即是只表示一个颜色中RGB分别所占的比例, 而不考虑其强度.
要让RGB对应可见光380~780nm的所有颜色, 数学上可能,实际上不可能, 为什么? 因为需要R是负值, 用r/g的平面图表示如下:

为了在图上再次归一化, 也就是x,y坐标取值在0~1之间, CIE发明了x/y/z和RGB的换算关系, 这样就把上面的马蹄挤到了1x1的方框里,就得到了经典的马蹄图CIE1931

马蹄的上沿一圈就是光谱的波长, 当然不是均衡分布的. RGB三个波长组成的三角, 就是显示器能显示的范围了. 嗯, 这个图要在显示器上显示, 我想一定是失真的了.
至于为什么用视锥细胞的敏感波长反而不能组合出完整的色彩感受, 还需要进一步的研究.

变量赋值与常量比较

先看个例子:

#include <stdio.h>
int main()
{
   char c = 0xff;
    if(c == 0xff){
        printf("true");
    }else{
        printf("false");
    }

   return 0;
}

你觉得是打印true还是false? 结果出乎意料是false!
因为c赋值为0xff之后, c的值就会变为-1. -1当然就不等于255了.
另外, c要和255比较, 估计还需要升位到int才行, 因为char类型装不下255, unsignd char装不下-1, 就得升到int才能比较.

length选择哪种数据类型

length取值最好为int. 原因是很多第三方返回length的函数都是使用int, 如uart读取. 虽然有时候你觉得char就够用了, 但为了避免反复的类型转换, 还是统一int比较好.
length常常用来确定数组的长度, 此时表示length的变量会被用作数组下标, 而数组下标不能为char.
除非内存真的要非常节约...

字符串和二进制缓存用那种数据类型

字符串多选择char类型, 指针是char , 数组是char string[]. 因为标准库中字符串的处理函数都是char的.
而二进制(16进制)缓存,也就是buffer, 最好使用unsigned char类型. 原因是buffer的值常常表示十六进制也就是0x, 在进行比较的时候会出现被赋值的变量不等于赋值的常量的情况, 如char c = 0xff,而比较时c != 0xff, 因为已经溢出为-1了.

服务器选择

国内默认服务器一般是"cn.pool.ntp.org", 然而稳定性不好, 也许用的人太多? ntp.aliyun.com相对更稳定.

ntp端口号

ntp端口号是123,看起来跟假的一样...

ntp服务器测试

linux下使用ntpdate -q cn.ntp.org.cn测试

使用空白模板登入, 发现就是一个web版本的vscode!
居然也带了terminal, 那就意味着操作系统!
查看一下这个系统的配置:

  • 安装了python3.10.8, node 19.9.0
  • 用户名codespace
  • CPU Intel(R) Xeon(R) Platinum 8272CL CPU @ 2.60GHz 双核
  • 内存4GB
    MemTotal:        4020176 kB
    MemFree:          138564 kB
    MemAvailable:    2357852 kB
    Buffers:          353748 kB
    Cached:          1967260 kB
  • 硬盘信息
    Filesystem      Size  Used Avail Use% Mounted on
    overlay          32G   13G   18G  41% /
    tmpfs            64M     0   64M   0% /dev
    shm              64M  8.0K   64M   1% /dev/shm
    /dev/root        29G   21G  8.1G  73% /usr/sbin/docker-init
    /dev/sda1        16G  288K   15G   1% /tmp
    /dev/loop3       32G   13G   18G  41% /workspaces
  • 操作系统Linux version 5.15.0-1039-azure (buildd@bos03-amd64-016) (gcc (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #46-Ubuntu SMP Mon May 22 15:18:07 UTC 2023

修改默认的软件

从上面可以看出来是docker容器, 这儿有一篇文章讲如何修改默认的软件:https://blog.csdn.net/boling_cavalry/article/details/125110236

文件结构

根目录下有bl2028n_sdk_releaseqcloud-iot-bk-wifi两个目录, 编译是在qcloud-iot-bk-wifi中进行
编译命令

虚拟机方式

  1. 使用虚拟机virtualbox安装Ubuntu18.04(只有64位版本),为编译速度更快,可以启用尽量多的核心;设置好和windows的共享文件夹;给默认用户加上sudoer权限,运行visudo,打开的文件中,在root ALL=(ALL:ALL) ALL下仿照写一行,如我的用户名是rnd:
    root    ALL=(ALL:ALL) ALL
    rnd ALL=(ALL:ALL) ALL
  2. 下载arm-none-eabi交叉编译器5.4-2016q3版本(据说最新可以用到2019年的版本,反正试过2023年的12.2是不行的),解压缩到一个目录。
    解压缩可以用tar命令(tar -xvf filename.tar)或(tar -xzvf filename.tar.gz),也可以直接用ubuntu的文件管理器,右键选择解压缩即可。
    注意解压缩会有一个问题,导致在链接elf文件时候报错:
    root@ubuntu-bl2028n:/media/sf_share/qcloud-iot-bk-wifi# make -j4
    make: Warning: File '/media/sf_share/bl2028n_sdk_release/.config' has modification time 2.1 s in the future
    LD   ../out/beken2028n.elf
    arm-none-eabi-gcc: fatal error: -fuse-linker-plugin, but liblto_plugin.so not found
    compilation terminated.
    ../bl2028n_sdk_release/application.mk:336: recipe for target 'application' failed
    make: *** [application] Error 1

    原因是gcc-arm-none-eabi-5_4-2016q3\lib\gcc\arm-none-eabi\5.4.1\liblto_plugin.so文件和so.0文件时链接到liblto_plugin.so.0.0.0文件上的,所以给liblto_plugin.so.0.0.0复制两份改成上面的liblto_plugin.soliblto_plugin.so.0文件名。
    然后在系统启动文件/etc/profile中加入:

    export ARM_GCC_TOOLCHAIN="/media/sf_share/gcc-arm-none-eabi-5_4-2016q3/bin/"
    export PATH="$PATH:$ARM_GCC_TOOLCHAIN"
  3. arm交叉编译器5.4只有32位,在64位的linux上运行会奇怪的错误no such file or ...,需要安装支持软件,安装前先update:
    ubuntu:
    apt-get update
    apt-get install gcc-multilib g++-multilib

    CentOS:

    dnf install glibc-devel.i686

    dnf比yum先进, 建议改用

  4. 如果是腾讯物联网项目,进入qcloud-iot-bk-wifi目录,运行make clean,在运行make -j8(8是虚拟机的CPU核心数)进行编译;
  5. 编译出来的文件在out目录中,烧录all***.bin文件。烧录方式是打开bk_writer_V1.63.exe文件,选择bl2028n,选择固件,点击烧录即可。
    使用开发板烧录,需要用USB-C供电,另外接一个USB-TTL转接器,接到GND-TXD1-RXD1上来烧录。
  6. 开发板简介:
    CEN_KEY是重置按钮
    UART1(R/TXD1)用作Log输出,和MCU通讯的话请使用UART2

docker方式(推荐)

docker安装起来比虚拟机快很多.
可以先安装docker desktop, 然后取一个镜像, 并运行容器, 我还是用之前腾讯给的esp编译的centos 8镜像, 在windows命令行下:

# 获取镜像
docker pull hubertxxu/esp8266_build:0.1
# 下一句设置共享目录
docker run --name esp -v path/to/host/folder:/path/to/container/folder -dt hubertxxu/esp8266_build:0.1
# 进入docker bash环境
docker exec -it esp bash
# 安装32位兼容包(等同于ubuntu的gcc-multilib)
dnf install glibc-devel.i686
# 跟ubuntu一样, 修改环境变量 `~`目录下的`.bashrc`增加ARM_GCC_TOOLCHAIN和PATH, 运行即可. 注意,和上面ubuntu不同的是修改`/etc/profile`没有作用

编译提速

使用共享文件夹的方式编译会发现速度极慢, 大约会慢100倍. 所以需要提速.
经过测试, 将arm toolchain和源码复制到虚拟机自己的文件夹,而不是用共享文件夹编译的话速度就非常快.
如果为了自动化, 你需要编辑.bashrc或者其他脚本文件来实现.

docker编译(更新+推荐)

我在hub.docker.com上创建了docker编译镜像, 获取: docker pull darceye/bl2028n_buildenv:v0.0.1, 安装好了环境, 并做了加速,将sdk已经复制到docker中.
为便于编译, 在host主机(以windows为例,嗯如果本身是linux也不用这么麻烦了)建立一个blq.bat文件:

@echo off
set bl2028n_build_docker=your_container_name

if "%1" == "build" (
  docker cp ./bl2028n_qcloud_sdk/qcloud-iot-bk-wifi %bl2028n_build_docker%:/home/bl2028n/bl_sdk/
  docker exec -it esp bash -c " cd /home/bl2028n/ && ./bl2028nmake"
  docker cp %bl2028n_build_docker%:/home/bl2028n/bl_sdk/out/all_2M.1220.bin ./build/bl2028n_FW.bin
)
if "%1" == "init" (
  docker start %bl2028n_build_docker%
)
if "%1" == "docker" (
  docker exec -it %bl2028n_build_docker% bash
)

if "%1" == "" (
  echo BLQ: Frossky BL2028N Qcloud C building Tool. Ver1.0-20230619
  echo Usage^: 
  echo blq: show this help.
  echo blq init: init docker container
  echo blq docker: into docker env
  echo blq build: build firmware into `build/` folder
)
@echo on

将相关sdk放在bl2028n_qcloud_sdk/目录中, 就可以按上面的提示进行自动编译, 并将编译后bin文件复制到build/中.

有2个教程,官方的:http://wx.shenzhen.cmbchina.com/EasyRegister/,和知乎的https://zhuanlan.zhihu.com/p/564548847
这儿需要重点说的是:

  1. 对新系统的支持是个问题。我在Win11上反复测试都失败,现象是提示签名失败,错误未知,实际上是签名时不会弹出输入密码的窗口。而在Win7上却一次成功。注意,找一个旧Win7电脑来签名吧。
  2. 必须下载Adobe Reader XI版本11.00.00和其11.00.07补丁,并安装。签名依赖的Adobe Reader XI版本11.00.07在2014年发布,现在早就停止维护,在adobe官网上都不好找。要下载还是要用文章里的链接;
  3. U盾太久没用的,需要下载招行的最新个人银行PC版来更新U盾里面的数字证书后再用;
  4. 特别重要:所有签名人员应统一使用最新的Adobe Acrobat Reader DC x64版本。目前已知使用WPS打开PDF进行签名会导致服务器返回证书链错误,导致提交失败。

根据B站Up主阿甘学长冲冲冲的视频整理如下:

问:你听说过麦肯锡了解行业的方法吗?作为小白,你能帮我介绍一下这个方法吗?
问:我想了解最近比较火的xx行业,根据各项调查、行业报告、新闻研究论文,帮我整理出关于该行业的100个关键词,同时根据关键词的关联性的强弱进行分类,分为5-8个类别进行展示。
问:以上100个关键词,都用一句话帮我解释清楚”xxxx”关键词,我理解不了,帮我举几个例子来
问:目前,在xx行业的公司中,头部玩家有哪些?他们的优劣势分别是什么?

家里一台买了快9年的小米电视2,卡在LOGO画面十几分钟进不去. 我又出差在外, 最近几天回不去, 家里老人没电视看还挺急. 于是在小米官网申请售后服务.
先走标准的网上申请,步骤如下:

  • 小米官网进入服务中心(mi.com/service),点击申请售后
  • 出来快速申请填写申请单两个选项, 进入快速申请,发现都是在小米商城买的产品,通过天猫旗舰店买的不在这儿. 然后退出重新选择填写申请单
  • 选择商品大类->子类->小类, 为电视.
  • 出现填写SN号和发票信息的地方. 发票信息可以选择有发票无发票, 有发票的话要填写年月日和上传发票照片.
  • 点击确认选择, 会根据SN码显示产品图片和完整名称配置, 此时选择服务类型维修或者安装
  • 选择维修后, 要求文字描述产品问题, 上传机器图片(单张不超过3MB。请注意拍摄画质尽量清晰,突出机器故障问题。非必填),选择服务方式, 但只有一项为到家服务(审核通过后,工程师上门为您服务)
  • 点击下一步, 选择选择到家方式,只有预约到家(预约工程师,上门为您服务)可选. 上门地址列出过往收货地址供选择,预期上门时间选择日期和时间段. 日期只能从第二天开始选,可选15天. 时间段分为9:00-12:00,12:00-15:00, 15:00-18:0018:00以后四个时间段.
  • 这时需要同意 《小米上门服务条款》
  • 提交后弹窗提示商品已过三包期限,请联系客服处理. 且只能点确定, 也没有其他链接.

网上维修到此为止, 于是拨打小米400电话400-100-5678, 下面是电话受理过程

  • 一通自动语音后, 提示按9进入自助服务
  • 然后语音不断暗示要排队, 然后提示微信和网上有24小时在线客服提供完全一样的服务

挂断电话, 在网站服务中心->申请售后按钮下面一行有小米客服的按钮

  • 点击进入, 又分了小米客服24小时在线咨询, 小米服务微博, 小米服务微信, 商城客服微信, 下面又根据产品不同列出了小米售后, 金融售后, 移动售后,游戏售后4个400热线电话, 全部注明服务时间, 还不太一致, 有8:00-18:00, 也有别的, 大致上都是工作时间.
  • 点击在线咨询按钮, 会弹出新窗口, 像微信聊天窗口一样为竖屏形式.可以粘贴图片.
  • 一开始都是机器人应付, 发送人工服务, 回复:

若需要小米商城人工客服:
【在线客服】请直接跳转(24小时);
【热线客服】400-100-5678(工作时间每天8:00-18:00、电视线路每天8:00-21:00)
查看相关知识点:

  • 上面回复的在线客服四个字可以点击, 点击后, 对话如下:

客服: 您好,欢迎来到小米商城,我是米小粒,很高兴为您服务,为了避免您重复陈述问题,我先看下之前的记录,请您稍等~
客服: 您好
我: 小米电视2过保了怎么维修?
客服: 您好,非常抱歉给您添麻烦了。由于您购买的产品已经停止生产超过了五年,生产商也已停止了相关物料生产,可能无法再提供相应的维修备件支持;所以售后维修换料服务,需要视目前的物料库存情况而定;
客服: 考虑您的实际需求,我们可以安排一个收费的上门检测调试服务,如果能通过调试解决最好,但如果需要备件,我们可以尽量协调,但不能保证一定能寻找到相关备件,您看您需要安排吗?
我: 好的,需要怎么收费?
客服: 您好,维修请点击【https://m.mi.com/t/q4xXyV】,通过【我要维修】--【商品申请】--通过搜索/分类选择商品--填写信息--提交服务单,您看可以么
客服: 您不方便申请的话,这边也是可以帮您申请的呢
客服: 如果咱们是主要部件(显示屏、背光组件、逻辑组件、高频调谐器)出现质量问题在3年质保期内是免费维修,如不是主要部件需要收取对应费用(包含上门检测费50元+远程费(往返距离≤30km免费。超出30公里的路程×1元/公里;超400公里的路程按400公里的费用收取)+如涉及维修需要再收取物料费+维修人工费;具体费用师傅会给您检测后告知您的~
我: 服务单提交不了呢(发送提交不了的截图)
客服: 客服这边帮您申请下呢
我: 好吧,申请一下吧
客服: 您提供下姓名电话地址
我: **

客服: 是什么问题需要售后呢
客服: 为了给您更好的服务体验,需要与您核实记录下购买渠道(比如:商城、有品、京东、淘宝、小米小店、小米之家、线下实体店等),谢谢您的配合
我: 电视开机卡在LOGO屏幕10分钟不能进入主界面
我: 小米天猫旗舰店购买
客服: 好的 您稍等 这边帮您申请呢
我: 好的
客服: AS230515166*****
客服: 帮您申请好了,这个是您的售后服务单号
客服: 咱们师傅一般在接了您单子后两个小时左右联系您协商上门时间的,具体的上门时间到时咱们和师傅说一下就可以了,一般网点是上午9点-下午6点上班的您保持电话通畅,会尽快联系您沟通下的
客服: 请问还有什么其他的问题可以为您效劳的吗
我: 没有了,谢谢
客服: 您太客气了,很荣幸为您服务呢~
客服: 麻烦您右上角点击退出对话进行评价,4-5只米兔是满意,1-3只米兔是不满意,期待您的满意,祝您生活甜如蜜,开心永常伴。