学
给笨笨完整的表情
by xufan6 on 3月.20, 2023, under 学
流浪地球2看完后,花了4个人*半下午+晚上,拼完了众筹售价800多的笨笨拼装版(1800+零件的乐高MOC)。拼完之后发现,它的电动/远程控制,只能是4个差速轮,头不能转,表情是两张贴纸+小灯珠,手臂是手动控制,一眼看起来像是“先上线再说”。于是就想,能不能对它做些增强。
经过了一些调研,表情可以用小oled屏来显示,正好也能匹配电影中类似点阵屏效果,好了,那就给笨笨完整的<del>一生</del>表情。

技术实现上用了ESP32开发版+MicroPython固件,使用了SPI协议连接屏,一块ESP32连两块OLED屏。
由于拼装版笨笨表情部位大概9CM宽4CM高,于是选择了2块1.54寸支持SPI的单色OLED屏,单块分辨率128*64,价格¥35一块
ESP32的选择就非常宽容,可能ESP8266、ESP32-S2都行,最便宜闲鱼10块多,我选择了¥26焊接好排针的DevKit V1。


ESP32与OLED屏都有排针,所以它们中间使用杜邦线进行连接。
电源上,前期直接使用了MicroUSB插线,后期换成了18650+锂电池充放电控制板+单母头杜邦线焊接。
笨笨表情,这里给出了GIF原文件,https://www.gcores.com/articles/161992
材料购买到货后,当晚就能helloworld了。因为买了两块不一样的ESP32,先到的一块直接刷好了MicroPython,只需要搭建环境,电脑连接板子就可以开始写功能逻辑。不同的ESP32,USB连接的驱动可能不一样,CH343p、CP2102等等,需要安装好。
ESP32本身有4路SPI,2路可供用户使用(HSPI、VSPI),理论上每一路SPI都可以通过一条CS(Chip Select)控制三个SPI设备。因为只控制2个屏,那就简化一下,HSPI连接一块,VSPI连接一块。OLED上7根线,其他5根没什么好说;DC(Data/Command)接了ESP32上的MISO,反正屏不需要SPI上行;RESET随便找了2个GPIO(32、33)。
OLED屏的驱动是SSD1309,据说和SSD1306基本类似,用了这个MicroPython的库 https://github.com/rdagger/micropython-ssd1309 ,里面提供了draw_bitmap方法,就能把单色图画到屏幕上了。

王铭东老师的教程和B站视频都非常基础,适合入门,https://doc.itprojects.cn/0006.zhishi.esp32/02.doc/index.html#/01.dajianhuanjing
ESP32的SPI部分参考文档 https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/spi_master.html
SPI协议的解释视频5分钟看懂 https://www.bilibili.com/video/BV1F54y1M7e7/
SSD1309的库里还提供了一个方法(img2monoHMSB.py),使用了Python Pillow,将图片转化为单色图,可以理解为原来是彩色的图片,需要转换至黑白图片,而且没有灰阶,只有黑色和白色。直接用这个库里的方法,容易把一些看起来是黑色背景的图转出白色的点。所以手动掏出<del>画图板</del>Mac下的预览,手动画了黑色方块盖住应该黑色的地方,每个图都要手动处理效率有点低下,但是表情至少动起来了。
为了批量处理gif,掏出了上个世纪的命令行图片处理软件ImageMagick:
-coalesce:补一下gif中省略掉的帧
-crop 686x225+32+17:切一下图里不要的部分
-level 10%:拉一下曲线,将不够黑的黑直接变成黑
-resize x64:将图片的高度缩放到64,适合OLED屏
+repage:前面切的边框这步实际切掉,切完之后尺寸是195*64
-monochrome:转为黑白单色,这步最后做效果比较好
再把gif按帧转为png,最后5个gif变成了143张单色png。
再花了几分钟尝试用magick转成ssd1309库里能用的mono格式,由于不想试了,直接用img2monoHMSB.py一把梭。
ESP32存储空间4MB,每张图片4KB不到,全上传也就0.5MB多,写了一个while True,每个图片中间sleep 100ms。把逻辑放到main.py里,这样启动就能自动循环放了。
剩下来的问题是这套方案怎么塞到眼睛里?截止目前还没塞进去……
因为本来眼睛内的厚度有限,屏后、ESP32的杜邦线在里面不太挤的下,于是考虑过整个拼接板笨笨换头。换头涉及拆头,目前组装完的笨笨上单独把头拆下来的动手成本有点高,近期笨笨又在抛头露面(https://weibo.com/7773082412/MxS0zxRNL 最后一图中跑到模糊那只) ,所以还没实操。
换头考虑过用乐高MOC做个更大的头部空间,但是设计与零件成本,加一起估计起飞。考虑直接3D打印,闲鱼大学生,头部建模报价80,打印的话得有模型才能估价,所以也没进展。
现在先就这样吧,先上线再说
可能可以排期的需求:
- 增加麦克风,接入离线唤醒词、在线语音识别。
- 屏幕支持临时自定义内容输入,比如临时投一些文本内容
- 蓝牙接管电机控制
- 前进方向的前避障
- 头部旋转使用步进电机控制
单线多播一次无谓的尝试
by xufan6 on 1月.28, 2017, under 学
家里宽带(杭州电信)套餐是100M,之前speedtest测试可以到105+,而且在测试时候发现光猫下两根线可以同时pppoe拨号拿到ip,考虑可以多播尝试下。于是耗费巨资¥140买了个二手路由器,Netgear WNDR3800。历经千辛万苦(刷完机wifi没启,家里设备没有线口)装上openwrt 15.05.1,并成功使用配置好了单线多播。结论是无用。
openwrt 15.05.1 完成这事已经比较简单了,只要在界面上System/Sofeware里搜软件装就好:luci-app-mwan3、mwan3、kmod-macvlan。前两个是mwan3 管理多条wan使用,后一个是虚拟网卡用。
步骤简单分成几块:1. 虚拟网卡。
ip link add link eth1 vth1 type macvlan
ifconfig vth1 upip link add link eth1 vth2 type macvlan
ifconfig vth2 up
eth1是wan的interface所在,别的设备可能不叫eth1。vth1只是个名字。完成之后在界面上Add new interface,类型啥参照原来wan的,我这儿就是pppoe了,注意metric要每个不一致(没太懂一样会怎样)。这步看别的文章好了,内容大同小异。上面几行命令加在System/Startup里,其实就是rc.local,这样开机能起来。正常的话应该各个网卡都能拿到pppoe后的ip了,如前文图。
步骤2. mwan3配置
在Network/Load Balancing下点点就能完成,都是属于mwan3的配置,Interfaces、Members、Policies、Rules 4个地方要改改。Interfaces里的tracking IP用国内的,比如alidns的223.5.5.5 223.6.6.6 再加个百度dns 180.76.76.76。Members里metric weight太复杂,都1先。Policies里也就留一条好了,其他不删留着也可以,Last resort的3个选项没太理解各自含义与用法。Rules也就配置了默认路由到Policy。Rules里用到了ipset,反正我lsmod看了都在,dnsmasq看了下也支持ipset,所以没装dnsmasq-full。至此就起来啦,如果不正常在”/admin/network/mwan/advanced/diagnostics” 这个下restart mwan。
speedtest测试三个wan都走字,然而上传和下载都没有叠加……gg
stop单个wan、两个wan,流量都能正常切换,暂时也没发现多播之后客户端会不会出什么问题。不过……在原有1个路由就一个pppoe,加新增一个路由器3个pppoe同时拨上之后(总共4个存活),只要断了一个,断的那个是无法再拨上的,提示超时(Timeout waiting for PADS packets),也就是一只光猫下只有3个pppoe的链接是稳定的。在晚上测试的时候发现只能2个pppoe了,可能是在光猫再上一层做的限制。
结论:杭州电信100M光宽带能够单线多播,然而网速不能叠加,且多播数>2时不稳定。
ref不分先后:
https://wiki.openwrt.org/doc/howto/mwan3
https://www.dianlujitao.com/archives/46
http://leon.leanote.com/post/OpenWrt%E5%A4%9A%E6%8B%A8%E8%AE%B0%E5%BD%95-macvlan-mwan3
http://www.right.com.cn/forum/thread-132875-1-1.html
kernel_task狂飙cpu之迷
by xufan6 on 2月.21, 2015, under 学
MacBookAir5,1,之前情况是突发的cpu狂飙伴随着磁盘快速增长导致磁盘满(增量大约4G),约几分钟后自行恢复。由于磁盘满时不能进行任何操作,当然也没法找到底是谁快速占磁盘。
网上看到的解kernel_task cpu快速涨的方法,简单来说是在/System/Library/Extensions/IOPlatformPluginFamily.kext/Contents/PlugIns/ACPI_SMC_PlatformPlugin.kext/Contents/Resources
这个目录下删除机型对应的plist,并重启。实际操作后发现重启完后磁盘空间还占着但cpu不高(可能偶然),有稍许空余空间得以检查磁盘满之事。
发现大文件目录为/System/Library/Caches/com.apple.coresymbolicationd/
下的data文件以及另一个grow.mkxAdPh文件,各约4G左右吧,lsof检查后发现有系统进程还开着data文件,mv掉之后重启,暂无异常。
真是专业清磁盘20年……
从str2time开始的优化
by xufan6 on 4月.07, 2013, under 学, 教
线上有个perl的脚本,准实时(1分钟cron着)的跑着,一般一跑要1分多钟。之前初看还以为是sql设计差、脚本变量都是全局等等问题,后来想想不行,得用数据说话,于是找到了传说中的nytprof。安装据说要先装下JSON::Any,再装Devel::NYTProf;用的时候perl -d:NYTProf ./a.pl,再nytprofhtml nytprof.out下,好了现在只要w3m nytprof/index.html就能看到详细了。
不看不知道,一看吓一跳,最前面耗时最长的居然都是Date::Parse、Time::Local,而main、DBI这些原以为会慢的差前面两个Date、Time一个数量级啊。那今天就把目光放在Date::Parse里,仔细看了脚本,其实就是用到了str2time,把形如”2013-04-07 22:22:22″的转化为那个秒数。我们这个脚本大概执行一次会用到50多万次的str2time。
网上翻了翻说是Date::Parse慢,于是换了个Time::ParseDate,测试用例如下:
#1.pl
use Date::Parse;
my $start = "2013-04-08 02:00:00";
my $end = "2013-04-08 03:00:00";
for (my $i = 0; $i < 100000; $i++)
{ print str2time($start),"\n",str2time($end),"\n";}
#2.pl
use Time::ParseDate;
my $start = "2013-04-08 02:00:00";
my $end = "2013-04-08 03:00:00";
for (my $i = 0; $i < 100000; $i++)
{ print parsedate($start),"\n",parsedate($end),"\n";}
time比较出来的时间是从原来的13秒变成9秒,稍有提高。
期间多次strace发现经常会访问/etc/localtime,怀疑是模块里不论何时都要去算下现在时间引起的,遂决定直接用timelocal这个最基本的命令传入年月日时分秒来计算。
#3.pl
use Time::Local;
sub new() {
$_ = @_[0];
my( $Y,$M,$D,$h,$m,$s ) = /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/;
return timelocal($s,$m,$h,$D,$M-1,$Y);
}
my $start = "2013-04-08 02:00:00";
my $end = "2013-04-08 03:00:00";
for (my $i = 0; $i < 100000; $i++)
{ print &new($start),"\n",&new($end),"\n";}
于2.pl的耗时比较,从9秒降到6秒多。
strace里还是能看到访问/etc/localtime,在Time::Local里看到还有timegm,试试
> return timelocal($s,$m,$h,$D,$M-1,$Y);
< return timegm($s,$m,$h,$D,$M-1,$Y)-28800;
由于并不牵涉时区,遂直接写死在里面。瞬间,从6秒多降到2秒内,和最开始比已经下降了一个数量级了。所以那,调用次数高的函数啊一定要好好调教下~
把这个提给原脚本owner后发现,原脚本只是为了比较两个时间string转后的大小。他直接改成
> return timegm($s,$m,$h,$D,$M-1,$Y)-28800;
< return ($Y*366*24*3600 + $M*31*24*3600 + $D*24*3600 + $h*3600 + $m*60 + $s);
来进行比较。时间差不多从2秒内变为1秒内。
然后洗澡时候忽然想起别人说过笑话,数据库存时间直接用string存20130407222222,于是测试这样比大小。
sub new() {
$a = @_[0];
$a =~ tr/ \-://d ;
return $a;
}
my $start = "2013-04-08 02:00:00";
my $end = "2013-04-08 03:00:00";
for (my $i = 0; $i < 1000000; $i++)
{ if (&new($start) < &new($end)){print 1};}
测试用例上提高一个数量级跑,上一种7秒多,这一种2秒多。
#p.s.用=~ s/\-|\s*|\://g;代替=~ tr/ \-://d;会比上一段所说的还要慢
也就是说,最开始13秒的,现在已经变成0.2秒了,所以那,优化逻辑也很重要~
$PS1增加py的virtualenv支持
by xufan6 on 4月.09, 2012, under 学, 玩
刚刚开始学用python的virtualenv,顺带用上了virtualenv wrapper,发现默认会在PS1之前加上($VIRTUAL_ENV),不好看啊,和我原来的不匹配。然后就花了几小时去改之前就写的很头大的PS1。现在显示到右边啦~
PS1='${debian_chroot:+($debian_chroot)}`a=$?;if [ $a -ne 0 ]; then a=" "$a; echo -ne "\[\e[s\e[1A\e[$((COLUMNS-2))G\e[32m\e[1;41m${a:(-3)}\e[u\]\[\e[0m\e[2m\]"; fi`\[\033[01;32m\]\u@\[\033[01;35m\]\h\[\033[00m\]:\[\033[01;34m\]`pwd`\[\033[00m\]`B=$(git branch 2>/dev/null | sed -e "/^ /d" -e "s/* \(.*\)/\1/"); if [ "$B" != "" ]; then S="git"; elif [ -e .bzr ]; then S=bzr; elif [ -e .hg ]; then S="hg";B="$(hg branch)"; elif [ -e .svn ]; then S="svn"; else S=""; fi; W="\`showvirtualenv 2>/dev/null \`"; if [ "$W" != "" ]; then if [ "${W:0:14}" = "showvirtualenv" ]; then W=""; else W="workon:$W"; fi; fi; if [ "$S" != "" ]; then if [ "$B" != "" ]; then M=$S:$B; else M=$S; fi; fi; if [ "$W" != "" ]; then if [ "$M" = "" ]; then M=$W; else M="$W $M"; fi; fi; [[ "$M" != "" ]] && echo -en "\e[s\e[$((COLUMNS-${#M}-1))G\e[33m\e[1;40m($M)\e[0m\e[u"`\n\[\033[01;34m\]\$\[\033[00m\] '
不分割了,太损血了。