kejinlu 2024-05-06T21:28:41+08:00 kejinlu@gmail.com 网站简介 2024-05-01T00:00:00+08:00 kejinlu http://kejinlu.com/2024/05/intro 欢迎来到我的主页,这里用来记录我的一些技术琐碎。 主要涉及iOS开发,智能家居,家庭组网,游戏硬件,VR使用相关内容。 3D电影简介及资源整理

]]>
3D电影简介及资源整理 2024-05-01T00:00:00+08:00 kejinlu http://kejinlu.com/2024/05/3dfilm 随着VR设备的用户越来越多,3D电影似乎又可以发光发热了。

电影名 大小
院线大片\霍比特人三部曲\霍比特人1 16.1 GB
院线大片\霍比特人三部曲\霍比特人2 25.2 GB
院线大片\霍比特人三部曲\霍比特人3 25.0 GB
院线大片\蜘蛛侠系列\超凡蜘蛛侠 18.6 GB
院线大片\蜘蛛侠系列\超凡蜘蛛侠2 15.9 GB
院线大片\蜘蛛侠系列\蜘蛛侠英雄归来 18.5 GB
院线大片\蜘蛛侠系列\蜘蛛侠英雄远征 14.6 GB
院线大片\蜘蛛侠系列\蜘蛛侠英雄无归 20.0 GB
院线大片\蜘蛛侠系列\蜘蛛侠平行宇宙动画 17.2 GB
院线大片\星球大战系列\星球大战7 17.4 GB
院线大片\星球大战系列\星球大战8 23.6 GB
院线大片\星球大战系列\星球大战9 20.5 GB
院线大片\星球大战系列\星球大战外传侠盗一号 21.3 GB
院线大片\星球大战系列\星球大战外传游侠索罗 21.0 GB
院线大片\复仇者联盟系列 86.2 GB
院线大片\复仇者联盟系列\复仇者联盟4终局之战 26.6 GB
院线大片\复仇者联盟系列\复仇者联盟3无限战争 23.1 GB
院线大片\复仇者联盟系列\复仇者联盟2奥创纪元 21.2 GB
院线大片\复仇者联盟系列\复仇者联盟 15.3 GB
院线大片\侏罗纪系列 85.1 GB
院线大片\侏罗纪系列\侏罗纪世界3 39.7 GB
院线大片\侏罗纪系列\侏罗纪世界2 17.2 GB
院线大片\侏罗纪系列\侏罗纪公园 14.3 GB
院线大片\侏罗纪系列\侏罗纪世界 14.0 GB
院线大片\变形金刚系列 66.6 GB
院线大片\变形金刚系列\变形金刚4 22.6 GB
院线大片\变形金刚系列\变形金刚5 22.4 GB
院线大片\变形金刚系列\变形金刚3 21.6 GB
院线大片\狄仁杰系列 55.8 GB
院线大片\狄仁杰系列\狄仁杰之神都龙王 30.1 GB
院线大片\狄仁杰系列\狄仁杰之四大天王 25.7 GB
院线大片\美国队长系列 55.7 GB
院线大片\美国队长系列\美国队长3 20.3 GB
院线大片\美国队长系列\美国队长 19.0 GB
院线大片\美国队长系列\美国队长2 16.4 GB
院线大片\雷神系列 53.1 GB
院线大片\雷神系列\雷神3 20.2 GB
院线大片\雷神系列\雷神2 17.3 GB
院线大片\雷神系列\雷神4 15.7 GB
院线大片\生化危机系列 46.5 GB
院线大片\生化危机系列\生化危机4 11.8 GB
院线大片\生化危机系列\生化危机诅咒 11.7 GB
院线大片\生化危机系列\生化危机5 9.4 GB
院线大片\生化危机系列\生化危机6 8.6 GB
院线大片\生化危机系列\生化危机2 5.0 GB
院线大片\银河护卫队系列 45.7 GB
院线大片\银河护卫队系列\银河护卫队2 21.3 GB
院线大片\银河护卫队系列\银河护卫队1 16.5 GB
院线大片\银河护卫队系列\银河护卫队3 7.9 GB
院线大片\哥斯拉系列 45.1 GB
院线大片\哥斯拉系列\哥斯拉2怪兽之王 18.5 GB
院线大片\哥斯拉系列\哥斯拉大战金刚 13.8 GB
院线大片\哥斯拉系列\哥斯拉 12.8 GB
院线大片\黑豹系列 40.7 GB
院线大片\黑豹系列\黑豹2 21.5 GB
院线大片\黑豹系列\黑豹 19.2 GB
院线大片\神奇女侠系列 39.1 GB
院线大片\神奇女侠系列\神奇女侠 20.8 GB
院线大片\神奇女侠系列\神奇女侠1984 18.4 GB
院线大片\猩球崛起系列 38.3 GB
院线大片\猩球崛起系列\猩球崛起3 20.3 GB
院线大片\猩球崛起系列\猩球崛起2 18.0 GB
院线大片\终结者系列 37.9 GB
院线大片\终结者系列\终结者2审判日 20.0 GB
院线大片\终结者系列\终结者创世纪 17.8 GB
院线大片\蚁人系列 34.4 GB
院线大片\蚁人系列\蚁人与黄蜂女 18.3 GB
院线大片\蚁人系列\蚁人 16.1 GB
院线大片\加勒比海盗系列 33.2 GB
院线大片\加勒比海盗系列\加勒比海盗5 20.3 GB
院线大片\加勒比海盗系列\加勒比海盗4 13.0 GB
院线大片\奇异博士系列 32.4 GB
院线大片\奇异博士系列\奇异博士2 16.3 GB
院线大片\奇异博士系列\奇异博士1 16.1 GB
院线大片\X战警系列 31.7 GB
院线大片\X战警系列\X战警逆转未来 13.0 GB
院线大片\X战警系列\金刚狼2 11.5 GB
院线大片\X战警系列\死侍 7.1 GB
院线大片\舞出我人生系列 30.0 GB
院线大片\舞出我人生系列\舞出我人生5 11.9 GB
院线大片\舞出我人生系列\舞出我人生3 11.4 GB
院线大片\舞出我人生系列\舞出我人生4 6.7 GB
院线大片\星际迷航系列 29.3 GB
院线大片\星际迷航系列\星际迷航3 17.1 GB
院线大片\星际迷航系列\星际迷航2 12.3 GB
院线大片\阿凡达系列 28.7 GB
院线大片\阿凡达系列\阿凡达水之道 15.8 GB
院线大片\阿凡达系列\阿凡达 12.9 GB
院线大片\环太平洋系列 27.8 GB
院线大片\环太平洋系列\环太平洋2 14.9 GB
院线大片\环太平洋系列\环太平洋 12.9 GB
院线大片\食人鱼系列 25.7 GB
院线大片\食人鱼系列\食人鱼3D 16.2 GB
院线大片\食人鱼系列\食人鱼3DD 9.5 GB
院线大片\银翼杀手2049 22.9 GB
院线大片\死神来了系列 22.5 GB
院线大片\死神来了系列\死神来了4 15.6 GB
院线大片\死神来了系列\死神来了5 7.0 GB
院线大片\火星救援 22.1 GB
院线大片\创战纪 21.6 GB
院线大片\海王 21.3 GB
院线大片\蝙蝠侠大战超人正义黎明 20.3 GB
院线大片\金刚骷髅岛 19.9 GB
院线大片\星际特工千星之城 19.8 GB
院线大片\毒液系列 19.6 GB
院线大片\毒液系列\毒液 14.5 GB
院线大片\毒液系列\毒液2 5.0 GB
院线大片\少年派的奇幻漂流 19.2 GB
院线大片\了不起的盖茨比 19.1 GB
院线大片\沙丘 19.1 GB
院线大片\永恒族 19.0 GB
院线大片\普罗米修斯 18.8 GB
院线大片\惊奇队长 18.7 GB
院线大片\头号玩家 18.7 GB
院线大片\掠食城市 18.1 GB
院线大片\正义联盟 18.0 GB
院线大片\绝命海拔 18.0 GB
院线大片\古墓丽影源起之战 17.6 GB
院线大片\泰坦尼克号 17.6 GB
院线大片\阿丽塔战斗天使 17.5 GB
院线大片\黑寡妇 17.4 GB
院线大片\尚气与十环传奇 16.9 GB
院线大片\机械公敌 16.9 GB
院线大片\末日崩塌 16.9 GB
院线大片\独立日2卷土重来 16.7 GB
院线大片\钢铁侠系列 16.6 GB
院线大片\钢铁侠系列\钢铁侠3 12.6 GB
院线大片\钢铁侠系列\钢铁侠2 4.0 GB
院线大片\双子杀手 16.6 GB
院线大片\太空旅客 16.0 GB
院线大片\全球风暴 16.0 GB
院线大片\神奇动物在哪里 16.0 GB
院线大片\巨齿鲨 15.9 GB
院线大片\明日边缘 15.7 GB
院线大片\像素大战 15.6 GB
院线大片\地心引力 15.4 GB
院线大片\狂暴巨兽 15.0 GB
院线大片\攻壳机动队 14.5 GB
院线大片\比利·林恩的中场战事 14.4 GB
院线大片\极品飞车 14.3 GB
院线大片\海洋深处 14.3 GB
院线大片\摩天营救 13.8 GB
院线大片\与恐龙同行2013 13.3 GB
院线大片\云中行走 13.1 GB
院线大片\纳尼亚传奇3 13.0 GB
院线大片\画皮2 13.0 GB
院线大片\心灵传输者 12.7 GB
院线大片\智取威虎山 12.6 GB
院线大片\鬼影实录5 12.2 GB
院线大片\西游伏妖篇 11.8 GB
院线大片\黑衣人3 11.6 GB
院线大片\金属乐队穿越永恒 11.6 GB
院线大片\龙门飞甲 11.0 GB
院线大片\特警判官 10.5 GB
院线大片\雨果 10.3 GB
院线大片\加州大地震 9.9 GB
院线大片\电锯惊魂7 9.9 GB
院线大片\逃出生天 9.7 GB
院线大片\自杀小队 9.7 GB
院线大片\逆世界 9.0 GB
院线大片\疯狂的麦克斯4狂暴之路 8.8 GB
院线大片\我的血腥情人节 8.1 GB
院线大片\地心历险记 8.0 GB
院线大片\格列佛游记 7.5 GB
动画 465.8 GB
动画\乐高系列 53.7 GB
动画\乐高系列\乐高蝙蝠侠大电影 15.5 GB
动画\乐高系列\乐高大电影2 14.7 GB
动画\乐高系列\乐高幻影忍者大电影 14.4 GB
动画\乐高系列\乐高大电影 9.0 GB
动画\蓝精灵系列 32.4 GB
动画\蓝精灵系列\蓝精灵3 12.1 GB
动画\蓝精灵系列\蓝精灵2 10.2 GB
动画\蓝精灵系列\蓝精灵1 10.2 GB
动画\驯龙高手系列 28.7 GB
动画\驯龙高手系列\驯龙高手3 11.7 GB
动画\驯龙高手系列\驯龙高手2 9.0 GB
动画\驯龙高手系列\驯龙高手 8.0 GB
动画\宝贝老板系列 28.4 GB
动画\宝贝老板系列\宝贝老板 15.3 GB
动画\宝贝老板系列\宝贝老板2 13.0 GB
动画\里约大冒险 28.2 GB
动画\里约大冒险\里约大冒险2 17.9 GB
动画\里约大冒险\里约大冒险2*.* 12.9 GB
动画\里约大冒险\里约大冒险2\裁剪版 4.9 GB
动画\里约大冒险\里约大冒险1 10.3 GB
动画\神偷奶爸系列 26.8 GB
动画\神偷奶爸系列\神偷奶爸3 13.2 GB
动画\神偷奶爸系列\神偷奶爸2 9.3 GB
动画\神偷奶爸系列\神偷奶爸 4.4 GB
动画\疯狂原始人系列 22.2 GB
动画\疯狂原始人系列\疯狂原始人2 13.5 GB
动画\疯狂原始人系列\疯狂原始人 8.7 GB
动画\小黄人系列 19.8 GB
动画\小黄人系列\小黄人大眼萌 11.4 GB
动画\小黄人系列\神偷奶爸前传 8.4 GB
动画\魔发精灵系列 19.0 GB
动画\魔发精灵系列\魔发精灵2 12.1 GB
动画\魔发精灵系列\魔发精灵 6.9 GB
动画\天降美食系列 17.3 GB
动画\天降美食系列\天降美食2 12.5 GB
动画\天降美食系列\天降美食 4.7 GB
动画\穿靴子的猫系列 16.2 GB
动画\穿靴子的猫系列\穿靴子的猫2 9.0 GB
动画\穿靴子的猫系列\穿靴子的猫 7.2 GB
动画\贝奥武夫 15.0 GB
动画\小王子 14.9 GB
动画\极地特快 14.5 GB
动画\功夫熊猫系列 14.4 GB
动画\功夫熊猫系列\功夫熊猫3 8.4 GB
动画\功夫熊猫系列\功夫熊猫 3.0 GB
动画\功夫熊猫系列\功夫熊猫2 3.0 GB
动画\雪怪大冒险 13.9 GB
动画\海绵宝宝历险记海绵出水 13.8 GB
动画\雪人奇缘 13.3 GB
动画\绿毛怪格林奇 11.7 GB
动画\丁丁历险记 10.0 GB
动画\愤怒的小鸟 8.7 GB
动画\哆啦A梦伴我同行 7.9 GB
动画\鬼妈妈 7.9 GB
动画\大战外星人 7.4 GB
动画\怪兽屋 7.3 GB
动画\猫头鹰王国守卫者传奇 7.2 GB
动画\老雷斯的故事 5.4 GB
迪士尼 461.4 GB
迪士尼\玩具总动员系列 37.3 GB
迪士尼\玩具总动员系列\玩具总动员4 11.3 GB
迪士尼\玩具总动员系列\玩具总动员3 8.2 GB
迪士尼\玩具总动员系列\玩具总动员2 7.7 GB
迪士尼\玩具总动员系列\玩具总动员 5.4 GB
迪士尼\玩具总动员系列\光年正传 4.7 GB
迪士尼\海底总动员系列 32.3 GB
迪士尼\海底总动员系列\海底总动员2 16.2 GB
迪士尼\海底总动员系列\海底总动员 16.1 GB
迪士尼\无敌破坏王系列 27.1 GB
迪士尼\无敌破坏王系列\无敌破坏王2 16.2 GB
迪士尼\无敌破坏王系列\无敌破坏王 10.9 GB
迪士尼\爱丽丝梦游仙境系列 26.5 GB
迪士尼\爱丽丝梦游仙境系列\爱丽丝梦游仙境2 17.8 GB
迪士尼\爱丽丝梦游仙境系列\爱丽丝梦游仙境 8.7 GB
迪士尼\沉睡魔咒系列 24.7 GB
迪士尼\沉睡魔咒系列\沉睡魔咒2 17.3 GB
迪士尼\沉睡魔咒系列\沉睡魔咒1 7.4 GB
迪士尼\冰雪奇缘系列 23.4 GB
迪士尼\冰雪奇缘系列\冰雪奇缘2 17.7 GB
迪士尼\冰雪奇缘系列\冰雪奇缘 5.6 GB
迪士尼\美女与野兽 20.6 GB
迪士尼\阿拉丁 18.7 GB
迪士尼\飞机总动员系列 18.6 GB
迪士尼\飞机总动员系列\飞机总动员2 11.2 GB
迪士尼\飞机总动员系列\飞机总动员 7.5 GB
迪士尼\寻梦环游记 17.4 GB
迪士尼\奇幻森林 16.7 GB
迪士尼\疯狂动物城 16.7 GB
迪士尼\二分之一的魔法 16.6 GB
迪士尼\拜见罗宾逊一家 15.9 GB
迪士尼\超能陆战队 15.8 GB
迪士尼\头脑特工队 15.1 GB
迪士尼\海洋奇缘 14.8 GB
迪士尼\勇敢传说 14.7 GB
迪士尼\奇异世界 13.1 GB
迪士尼\狮子王 12.7 GB
迪士尼\美食总动员 9.5 GB
迪士尼\怪兽大学 8.0 GB
迪士尼\闪电狗 7.8 GB
迪士尼\飞屋环游记 7.7 GB
迪士尼\圣诞夜惊魂 7.5 GB
迪士尼\机器人总动员 7.3 GB
迪士尼\魔发奇缘 5.9 GB
迪士尼\小美人鱼 5.2 GB
迪士尼\四眼天鸡 3.7 GB
纪录片 137.2 GB
纪录片\植物王国 17.1 GB
纪录片\雷克斯海 16.0 GB
纪录片\世界自然遗产系列 13.3 GB
纪录片\世界自然遗产系列\美国黄石公园 4.5 GB
纪录片\世界自然遗产系列\美国大峡谷 4.4 GB
纪录片\世界自然遗产系列\美国夏威夷 4.4 GB
纪录片\狂野非洲 9.6 GB
纪录片\深海探奇 9.3 GB
纪录片\TT3D触摸极限 8.3 GB
纪录片\詹姆斯卡梅隆深海挑战 7.9 GB
纪录片\詹姆斯卡梅隆深渊幽灵 6.4 GB
纪录片\海底世界 6.1 GB
纪录片\狐猴之岛 6.0 GB
纪录片\第二次世界大战 4.6 GB
纪录片\国际空间站 4.6 GB
纪录片\我们的宇宙 4.5 GB
纪录片\小巨人 4.4 GB
纪录片\深海猎奇 4.2 GB
纪录片\狂野之海 4.1 GB
纪录片\哈勃望远镜 4.0 GB
纪录片\飞行传奇 3.4 GB
纪录片\海豚和鲸鱼 3.3 GB
成人 73.6 GB
成人\爱恋 18.8 GB
成人\3D肉蒲团之极乐宝鉴 12.0 GB
成人\一路向西 12.0 GB
成人\3D豪情 9.9 GB
成人\JailhouseHeat3D 9.9 GB
成人\火舞间3D癫马夜总会 7.8 GB
成人\性爱宝典 3.2 GB
老片 59.3 GB
老片\壮志凌云 16.9 GB
老片\黑湖妖潭 10.8 GB
老片\电话谋杀案 8.3 GB
老片\刁蛮公主 7.9 GB
老片\恐怖蜡像馆 7.7 GB
老片\绿野仙踪 7.7 GB
]]>
能率燃气热水器智能化改造 2022-10-30T00:00:00+08:00 kejinlu http://kejinlu.com/2022/10/noritz-controller 一般燃气热水器是很少需要操作的,设置好温度和水量之后基本长年都不需要调整设置,所以按理来说其智能化联网的需求并不是很迫切,不过能率的这些燃气热水器有一个问题就是每次断电之后再次通电,默认是关闭的状态,都需要手动按一下开关,非常麻烦。 所以需要一个断电恢复之后能够自动开机,以及能够实时监控燃气热水器的燃烧状态。这样也可以对燃气热水器的使用情况做一些分析或者异常检测。 我家用的是能率的室外机GQ-2040W,燃气热水器相比于传统电热水器或者空气能热水器这种储水式的还是方便不少,热水不限量,能够随时用随时加热。

室外机通过两根线连接线控,有一些燃气热水器可以支持同时连接多个线控,我的这台经过测试只能连接一个线控,多个线控连接之后,时间长了线控会出现异常,影响燃气热水器的使用。

能率官方在国外推出了Wi-Fi版的控制器,NWC Adapter,不过国内没有销售,也不知道和国内销售的机型能不能适配,而且动辄四五百元人民币的价格也是相当的惊人,另外需要使用官方自己的App进行控制,无法接入Home Assistant系统。 所以我就寻思能不能对原装线控做一些DIY改造,读取相关的状态信息或者写入相关控制信号来达到目的。 虽然对数字电路一窍不通,但是心想多多少少有编程基础,数字电路相关的东西,不管是软件还是硬件,到了最底层都是0101,低电平高电平的序列,很多思想都是相通的。所以这次趁着国庆假期就着手对线控电路板做了一通研究,当然一开始并没有抱太大的希望。

一.前期准备

数字电路逆向必须准备好万用电表,逻辑分析仪,杜邦线等必备的工具,这些工具不需要高端的,所有东西成本50元以内。 然后需要了解一些基本的电路知识,至少得知道电压,电阻(电阻,排阻),发光二极管,三极管这些电子元器件的基本性质。 我们要知道在数字电路中常见的电压有5V,3.3V; 电阻串联分压;发光二极管要注意正负极方向;三极管这个稍微复杂点,我们重点讲一下,三极管有NPN和PNP两种,不管哪种,都有三极:基极b,集电集c,发射极e。三极管有放大电流的作用(工作在放大区),也可以用作控制开关(一般工作在饱和区)。

NPN型三极管

NPN三极管中,当 Vb 比 Ve大0.7V左右的时候,b到e之间产生电流,然后c到e之间的电路也就通了,反之,电路就处于断开状态。电流为两进一出,bc都流向e。

PNP型三极管

PNP三极管中,当 Vb 比 Ve小0.7V左右的时候,e到b之间就产生电流,然后e到c的电路也就通了,反之,电路就处于断开状态。电流为一进两出,e流向b和c。 总之我们可以通过配置基极和发射极的电压差,来控制集电极和发射极之间电路的通断。

二.线控逆向分析

2.1 几种方案

 对于燃气热水器控制器这种靠电力线来载波通讯的DIY改造的话一般有如下这些改造方案:

  1. PLC(Power Line Communication,电力线通信不是可编程逻辑控制器)方案,这种方案也就是你得完全造出一个自己的线控,接上两根主机出来的电源线就可以控制了。这个方案是最难的,你需要对线载的模拟信号做彻底的逆向解析,工作量极大,近乎不可行。
  2. MCU数字信号拦截方案,也就是在PLC芯片(这里是T6B70BFG)和MCU芯片之间进行桥接,截取数字信号获得状态信息,以及模拟发送控制信号给PLC芯片。
  3. LED,按钮末端方案,这个就是分析LED电路的状态,获取当前状态信息,以及直接控制按钮的线路的通断模拟点击,这个方案简单,但是完整解析状态需要比较多的飞线。
  4. 纯物理的方案,使用智能机械按压设备来模拟人手点击按钮,以及通过摄像头或者感光器件来物理读取控制的状态。从效率上来讲,只有实在没有办法的情况下才会使用此种方案。

我比较偏向于第二种或者第三种方案。 不管怎么样,还是先对板子上的电路以及信号做一些必要的分析。

2.2 线控PCB板初识

首先在闲鱼上30块钱买了一个一样的二手线控,拆出板子进行分析研究。 拆看PCB板,我们可以发现其质量还是非常不错的,标识清晰,焊接做工都很好。 我们先看PCB正面,通过看各个元器件上的标识,查阅资料以及万用表进行检测,标记出下面的一些主要元器件 5v稳压器的作用主要是将热水器主机14V的输入电压转成5V的电压供各个芯片使用,T6B70BFG芯片主要通过两根电力线和主机通讯,MCU则接受T6B70BFG的数据更新LED显示以及按按钮之后发送指令给T6B70BFG。

PCB反面可以看到很多圆形的金属点,这种应该是用于PCB自动化测试的点,所以PCB上的主要节点都可以找到相应的测试点,这些测试点也给我们的线路DIY焊接带来了方便。以下图里标识出了几个重要的测试点。

2.3 T6B70BFG芯片

东芝的T6B70BFG,网上有详细的规格说明书,这款芯片主要用于热水器主机和控制器之间的接口电路,通过datasheet可以知道这个芯片大概的工作流程。 datasheet.pdf 我们可以将关注点放在数字信号的 /DOUT端口,以及/SCTL控制信号端口,从“控制器”的角度来说,/DOUT为数字信号的输入,/SCTL为控制信号的输出。

通过电路板可以看到T6B70BFG这两个端口都是通过一个10K的电阻连到了一颗名为SQE 702 K4C0的芯片(应该是一个定制芯片,查不到相关信息),然后PCB板子通过万用表分析这两个端口的连接。 将相关的电路简化了之后就是如下图的样子:

2.4 MCU数据协议分析

我们先了解下线控的基本功能, 首先就是开关功能,燃气热水器每次断电后再通电,默认是关闭状态的,需要按下控制器的开关开启;然后还可以设置水温,水温的值有37,38,39,40,41,42,43,44,45,46,47,48,60,75。按下水量按钮,可以设置水量,水量有4,6,8,10,12,14,16,18,20,22,24,26,30,35,40,99。 这里可以看到温度60,75度是非常高的温度了,最好能限制,不能将其纳入远程调节的范围,避免安全问题。

逻辑分析仪如下接法,然后通过Logic2软件进行抓包数据,然后进行线控的开机关机操作。

通过Logic2可以看到,没次按下按钮操作时候,/STL 端 Channel0会收到一串数据信号序列,然后紧接着/DOUT会输出一段和/STL 端接收到的一样的数字序列,然后大概27ms之后 又会收到一串数字信号序列。 /DOUT端收到一个和发出去的命令序列一样的序列估计是因为此T6B70BFG是单线接口通信,所以模拟信号发出的时候,T6B70BFG自身也能收到这个信号,所以会在/DOUT端进行重放,内部MCU处理的时候应该是忽略了此信号,而只处理后继收到的那个信号序列。

这边对各种状态下的数字信号做了抓取分析,虽然有一些状态值可以看出规律来,但是像温度这种数值 没有分析出规律,真想做的话只能一一对应的进行枚举分析了,如果真要通过MCU协议的方式来实现,那么只能一一对应的数据来实现了,代码会非常繁复。 或许后面有机会再继续研究,最终破解出其数据协议。 所以我们继续看末端交互的电路,主要是LED显示和按钮控制。

2.5 LED电路分析

我们再来看看线控板的LED实现的细节,如果每一个发光二极管都通过一个电平信号来控制,那么一共17个发光二极管就一共需要18个引脚(一根共用引脚,17根独立的控制引脚),非常浪费。 所以一般实现的时候都是将发光二极管分组,每一组中的LED进行编号,同一分组的所有led公用引脚接分组控制端,不同组的相同编号的LED共用控制引脚。分时轮流显示时,也就是一个微小的时间片段中只有某一组LED能够显示,然后控制引脚此时只控制当前分组的各个led的状态,由于时间片段非常短,循环交替显示各个分组的LED,对于人眼来说,各个分组的LED都得到了显示。 其实现代LED显示器本质上也是这么个原理,所以用手机拍视频或者拍照的时候有时候能看出闪烁。 通过万用电表的分析,这边对LED显示电路做了近乎1比1的还原,具体的电路图可见 https://oshwhub.com/kejinlu/noritz-controller,通过嘉立创EDA可以进行仿真模拟,修改各个控制引脚的电平来控制验证LED的显示。

通过LED电路的分析,我们可以知道 这颗SQE 702 K4C0的芯片其作用就是接受燃气热水状态的数字信号,然后通过LED显示出来;以及接受按钮的操作信号,输出控制数字命令。

2.6 开关电路

按钮的实现比LED电路简单多了,基本都是给某个MCU芯片上的引脚通上低电平然后断开就实现了一次触发。 这里以开关机按钮为例,电路示意图如下:

开关按钮按下松开,也就是开关闭合后断开就完成一次开关机指令的触发。

三.末端数字信号方案实现

方案目标:简单,无损 简单不仅仅是电路改造简单,而且固件代码要简单,稳定且很容易理解。 无损就是不切断修改原有燃气热水器线控的电路通路,不改变原有数字信号序列,不影响原有线控的所有功能和稳定性。

3.1 开关机及燃烧状态

未开机

开机状态

燃烧状态

我们的目标是能够判断当前燃气热水的开机状态,燃烧状态,延迟控制在1s内,通过上面的逻辑信号序列我们可以将问题简化,就是直接通过分析开机/燃烧LED信号这一个信号的状态就可以判断目前燃气热水器的状态。分组信号任何时候就是对LED分组进行轮询。

3.2 电路设计及实现

https://oshwhub.com/kejinlu/noritz-controller

这里我使用的是ESP32-DevKitC 如果你有别的基于ESP32的或者ESP8266的也应该都可以。 关于此电路设计有几个注意点:

  • 约定
    • 我们将智能控制器数据读入端叫做 RX(Receive)
    • 信号输出端叫做TX(Transport),这里TX其实并是不普通意义上的信息输出,只是控制和GND的通断
  • ESP32-DevKitC 开发板
    • 必须和线控共地,也就是线控板的GND需要接到ESP32的GND端。
    • 供电,开发版使用USB单独供电,经过测试如果从线控板的5v供电的话会影响线控的正常运行。
    • GPIO34,35引脚是input only的,所以 输出TX使用了下面的32引脚。
  • 用作TX开关控制的三极管
    • 基极输入端注意设置下拉电阻R3,这里的下拉电阻既可以保证当GPIO32断开或者ESP32断电时候能够有确定的低电平,也可以在GPIO 32 输出高电平的时候进行分压,控制输入电压。
    • 这里的电阻大小都是基于能够让三极管工作在饱和区;且电流足够小。这些都需要通过计算而得,电阻大小并不是唯一,只要满足条件即可。
  • 开关、燃烧LED状态读取端RX
    • 看之前的LED的仿真电路我们可以知道,RX端连接点其实也是和开关、燃烧LED直接相连的,所以我们的智能控制器其实是和线控MCU是一个并联的关系。
    • 当RX是高电平的时候,线控MCU的高电平是5V,所以为了安全做了一个电阻降压,可以选择两个R1,R2比值接近2:1的两个电阻。这样5V降压之后就接近3.3V了。
    • 当RX是高电平的时候,相关LED都应该是不亮的,不过这边接入了我们自己的电路之后有一个问题,如果R1,R2过小,并联之后就会有电流从LED中流过,导致 LED异常亮起。所以我们需要R1,R2的阻值比较大,使得电流极小,不足以使LED点亮。

然后我们就按照设计好的电路图,在线控PCB相应的测试点上引出信号线。

3.3 软件编码

https://github.com/kejinlu/noritz-controller 详细代码见github

3.4 Homeassistant接入

这一步算是最简单的了,在configuration.yaml中进行配置即可,然后再将其加入卡片

mqtt:
  switch:
    - name: "Noritz Power"
      unique_id: "switch.noritz_power"
      state_topic: "noritz/state"
      command_topic: "noritz/cmd"
      payload_on: '{"power":"ON"}'
      payload_off: '{"power":"OFF"}'
      state_on: "ON"
      state_off: "OFF"
      retain: false
      value_template: >
        {% if value_json.power is defined %}
          {{value_json.power}}
        {% endif %}

  binary_sensor:
    - name: "Noritz Heating"
      unique_id: "binary_sensor.noritz_heating"
      state_topic: "noritz/state"
      payload_on: "ON"
      payload_off: "OFF"
      qos: 1
      value_template: >
        {% if value_json.heating is defined %}
          {{value_json.heating}}
        {% endif %}

最后使用的实际情况请看下面的视频: 点击查看【bilibili】

]]>
家庭网络分享 2022-10-29T00:00:00+08:00 kejinlu http://kejinlu.com/2022/10/home-network 选型

AP还是Mesh?

对于小户型选一个穿墙能力强的路由器放在房子中央位置基本就能全屋覆盖。对于大户型一般就AC+AP 和Mesh两种方案,AC+AP需要提前布好网线连接AP,Mesh的话则相对自由一些,可以通过无线回程进行Mesh组网,但是稳定性上肯定不如AC+AP的方案(有线回程的Mesh估计好点),而且一般情况下AP的带机量也是远远大于Mesh的。

千兆还是万兆?

目前来说大部分内网使用场景千兆是完全可以满足需求的比如4K视频播放,VR(比如Quest2无线串流),目前家庭万兆的使用场景一般是万兆NAS在线视频剪辑,需要频繁内网设备之间高速拷贝数字内容,多人同时需要超高速内网访问。 外网宽带目前一般最高也就是千兆,除非你拉专线价格非常高或者多个账号叠加拨号,一般都很少用到。 另外现在好一点的万兆网络设备都价格不菲。 虽然现在不上万兆,但是我们也得为日后的万兆升级做好准备。

布线

如果房子是自己装修,那么就可以完全按照自己的想法来,如果是精装修好的房子那么就局限比较多了,顶多就是换换线。 在装修之前我们先要想好弱电箱的位置,然后所有的网线都汇聚在此,如果空间足够,专门整个弱电间也是ok的。机柜或者设备放在柜子里面的,需要提前考虑好散热风道之类的,可以打散热孔加散热风扇。

我这边是设计了储藏间当弱电间,弱电间放机柜,然后由于从地下室机柜端拉网线到二层三层网线会非常长,所以二层设置一个弱电箱 作为一个交换机中继节点,二楼三楼的网络端口都汇聚到这个弱电箱。

网线六类足够了,50米的六类线万兆的速度是绝对没有问题的,另外尽量不要用屏蔽线,除非你能确保屏蔽线都能正确的接地。主干节点之间如有必要布网线的同时也可以布上光纤,万兆网络时,光口光纤无论能耗发热都优于电口网线。 其他要注意的点就是 汇聚到机柜端的网线的收口的美观,你可以使用空白面板, 如果网线太多,86面板盒塞不下也可以不用线盒,直接墙壁上安装防水盒,防水盒开孔来引出网线。

剩下就是所有网线提前做好规划,进行布线

  • 规划好网口面板的位置,比如电视机,电脑,别的有线网络设备等,每个房间都需要预留网口
  • 提前规划好AP的位置,吸顶,壁挂或者面板AP,所有的AP都通过POE进行供电
  • 提前规划好户外POE摄像头位置
  • 特定设备布上网线到弱电箱或者机柜(因为网线八根铜线有时候可以用作信号线,比如用于传输智能电rs485信号线 或者中央空调到空调控制器的信号线)

网络设备

最终选择的是AP模式,AP企业产品比较多,近些年也有不少家用的AP产品,常见的AP品牌也有不少,TPLink,华三,锐捷,aruba,unifi(UBNT),综合工业设计,系统软件等各方面因素,选择了Unifi全家桶。U家的产品特点主要是苹果风格的工业设计,复杂功能抽象封装,一些高级的功能只需要简单的配置即可。 Unifi的AP应该都是胖瘦一体的AP,支持软AC,也支持脱离AC独立运行。目前软AC支持的部署方式也很多,有pc上的软件客户端,或者docker进行部署,ac软件设置好之后,即使脱离了AC,AP也能正常工作,这两年官方都在推自带AC软件的UnifiOS设备,比如UDM,UDM Pro,UDM SE等设备,这些设备集成了路由器,交换机,UnifiOS(包含了AC管理)。

目前的拓扑结构如下:

地下室机柜

路由器和主交换机

其他设备

机柜主要核心设备就是路由器和交换机 路由器 之前一直用的USG3P加上docker部署的unifi controller(unifi network)来对unifi网络进行控制,为了事情变得简单以及更好的和机柜搭配,所以后来升级了UDM SE,自带UnifiOS(包含了软AC,Unifi Network套件),这样就可以摆脱dcoker部署的Unifi Network。 主交换机 一般情况下我们只需要2层交换机就够了,如果存在需要POE驱动的设备比如POE摄像头,AP,那么就需要带POE的交换机了,POE协议发展到今天有大概分三类,802.3af、802.3at、802.3bt,一般交换机用到的进阶特性无非就是链路聚合和VLAN, 机柜中还有的设备包含散热风扇UPS+NAS软路由监控录像机以及其他的一些智能家具的网关设备。 因为NAS中的机械硬盘对突然掉电比较敏感,容器损坏硬盘影响硬盘寿命,所以一般情况下NAS最好都配一个UPS,这边用的是APC的BK650M2-CH,可以和NAS搭配工作,当断电后,NAS会接收到UPS的通知,然后进行关机保护,NAS和主交换机通过两根网线进行链路聚合,提高带宽。另外NAS下面可以放减震垫,毕竟4个硬盘同时工作多多少少有些震动的😂。 软路由则安装了PVE,然后PVE中安装各种需要的虚拟机,比如用于部署各种Docker的RancherOS系统,作为旁路由角色的OpenWRT,用于管理智能家居的Home Assistant OS。

二楼中继弱电箱

二楼弱电箱主要设备就一个POE交换机和散热风扇,主要负责链接主交换机以及二楼和三楼的所有网络设备,由于目前没有使用万兆设备,这边和主交换机使用两根网线进行链路聚合,所以带宽可以达到2GbE,也就是如果两个AP同时访问NAS的话,整体带宽会突破1GbE。

AP

AP算是日常使用用比较核心的设备了,毕竟现在很多设备都是WiFi连接的。Unifi Network可以很方便的对所有AP进行集中管理,一般情况下我们可以根据使用场景来创建多个WiFi Network,不同WiFi网络可以绑定不同的Network(不同的Network可以对应不同的VLAN),比如我们可以单独设置一个WiFi 出来进行科学上网,连上这个无线网就可以科学上网了,而且这个网络所有的设置更改都不影响默认的正常的WiFi网络。 以下是各个位置的AP布置: 地下室 0F(UAP-AC-IW) 面板型AP

餐厅 1F-N(UAP-AC-LR) 吸顶AP 放置在餐厅位置,兼顾厨房餐厅厕所以及厕所外的设备平台等空间,LR型号的穿墙能力稍强

客厅 1F-S(UAP-AC-Pro) 吸顶AP 放置在客厅靠窗户的位置,兼顾客厅和院子, Pro型号的带机量以及带宽会比较高

1F(USW-Flex-Mini) 客厅这边同时布置了一个小的交换机,毕竟无线再快也没有有线稳定,所以Apple TV,PS5啥的还是连的有线,还有IPTV的机顶盒需要使用有线和VLAN的特性,交换机也是必要的。

二楼北 2F-N(UAP-FlexHD) 主要供北面的房间和书房使用,FlexHD的信号强度以及无线速度都比较强

二楼南 2F-S(UAP-AC-M) 南面的房间使用

三楼 3F(UAP-AC-Pro) 顶楼房间使用

入户 Frontyard(UAP-AC-Lite) 这边是通过USW Flex交换机(放在摄像头上方的防水盒内),来同时对摄像头和AP供电,而USW Flex自身也是通过POE(af,at或者bt)进行供电的,这种模式用在户外一分多的情况下很方便,避免了电源线的布置,只需要一根网线即可搞定多个设备的网络和供电。

使用场景举例

影视多媒体

还记得以前看下载的电影或者电视剧都是直接放在NAS上用本地的普通播放器看。后来知道了Jellyfin或者Plex这类媒体服务器的概念,媒体服务器可以对电影电视剧进行统一的管理,自动刮削电影或者电视剧的元数据。在多个客户端上观看记录和状态都可以自动进行同步,你在电视上看的记录,到手机或者电脑上观看进度都在,随时继续观看。 我这边使用的是开源免费的Jellyfin,客户端一般用的Infuse和官方的客户端。 Jellyfin服务直接部署在QNAP NAS的Docker的容器中,这样可以直接挂载NAS的硬盘。

Infuse Pro客户端(Apple TV)

Jellyfin网页端

每一集的简介信息

另外不要使用服务器端解码,端上解码是最好的解决方案。

远程访问

DDNS DDNS 是使用的阿里云的服务,通过OpenWRT的服务来进行更新,当wan口ip地址发生变化的时候就会自动更新域名ip地址。当然这个需要你的互联网提供商能够给你分配外网IP地址。

VPN 有ddns的帮助,我们就可以通过域名访问家里的ip地址了,如果需要访问家中的一些服务,我们可以通过端口转发的方式进行,端口转发这功能一般的路由器都提供了,但是端口转发存在一定的安全性,直接将相关服务暴露在互联网上,所以为了安全起见,还是走VPN比较安全。 一般的路由器也是提供了VPN服务的,Unifi VPN的设置

这样电脑或者手机上就可以通过域名和L2TP相关的信息设置 VPN连入家里的网络了。

科学上网

为了保证默认WiFi网络的稳定,默认网络不提供科学上网,所以单独划出一个WiFi来给科学上网使用,这样这个单独的WiFi再怎么折腾也不会影响正常的网络。 软路由的OpenWRT上安装OpenClash服务(别的类似服务也ok),然后OpenWRT以旁路由的角色接入网络(LAN口接入交换机),如果多个VLAN网络都需要使用相关服务,那么可以添加多个网络接口,保证OpenWRT在相应的VLAN网络中能够访问。

然后我们设置单独划出来的WiFi网络 我们先创建一个Network,对其进行相关配置,设置相应的网段和VLAN ID

设置DHCP的DNS Server和Geteway为旁路由的地址,这样客户端脸上这个网络之后 就会将网关和DNS Server设置为相应的IP地址。

网络设置好了之后,我们再去设置WiFi,选择刚才创建好的Network,这样创建出来的WiFi就可以进行科学上网了。

iTV

目前iTV也就用来看看比赛,平时也比较少看。 通过VLAN,即使客厅电视和机柜只有一根网线我们可以方便的通过交换机机顶盒来连光猫的IPTV口拨号看电视。不少地方的iTV 你可以进行抓包,然后通过OpenWRT模拟拨号,然后再通过udpxy服务将多播转为内网单播,随处通过rtsp流进行观看,网上资料很多,这里就不多做陈述了。

IoT

IoT网络这块其实没啥好说的,就是如果想要提高安全性,可以为IoT设备单独划一个VLAN网络,和一般上网用的网络进行隔离。我目前为了简单还没有这么做,我一般的诸如智能开关,插座,zigbee网关啥的基本都是买的sonoff家的产品然后刷的tasmota的开源固件,这样就可以得到设备的完全控制权,然后再集成中Home Assistant系统中,其他很多小米的设备也可以集成到HA中,如果有同学想详细了解相关内容,我可以单独再开一篇来写。

]]>
德业除湿机接入Homeassistant 2022-10-28T00:00:00+08:00 kejinlu http://kejinlu.com/2022/10/deye-homeassistant 之前已经有人对德业除湿机做过反编译,找到了其背后的实现原理,并通过修改homeassistant相关组件远吗接入homeassistant,https://xiking.win/2020/11/12/3-deye-dehumidifer-add-to-homeassistant/ ,也有将其做成Homekit插件的https://github.com/IcesandSora/homebridge-deye 。我是想将其接入Homeassistant,但是并不想修改Homeassistant源码,所以方案就是通过Node-RED进行德业MQTT消息的桥接转发,然后再接入Homeassistant。

协议

HTTP API

我们可以通过手机上的“德业智能”App抓包来研究德业服务相关的协议,通过登录过程的抓包我们可以发现登录成功后,会得到一个token,这个token是德业其余需要权限验证的API请求时必要的条件,也就是请求头中需要一个名为“Authorization”的参数,value为”JWT “+token。后继我们就可以通过相关API就可以获取德业MQTT服务的配置信息,以及设备列表。 需要了解整个请求的过程可以参见我写的这个python脚本 https://github.com/kejinlu/deyeinfo/blob/main/deyeinfo.py

MQTT 设备状态和命令

MQTT的抓包这边建议使用iOS的App来抓,iOS App的MQTT目前是没有走TLS协议的,可以iOS设备连PC分享出来的Wi-Fi,然后PC上使用Wireshark来抓包分析,找到MQTT协议的数据包。

刷新码

首先我们发现进入App之后即使我们不操作,客户端也是不断地发送MQTT消息,每次发送完就可以接收到,服务器端发过来的MQTT消息。其实这个定时不断发送的就是刷新命令,大概是3秒发送一次,德业这边是通过不停的轮询发送MQTT消息的方式来及时刷新设备的状态信息的。

所以我们就知道刷新命令了,后面我们也会用到。

从刷新命令的详细内容可以看到,MQTT消息内容为0001,而且消息内容的格式并不是字符串而是内存数据,这边还有个注意点就是QoS,这边需要设置为0,德业的服务器端貌似对QoS做了校验,反正我测试的时候如果不是0,则发过去没有作用。

设备状态码

接下来研究下返回的设备状态值,

下发设备状态的MQTT消息体解析之后是一个json格式的数据,里面data的内容是一个字符串,这个字符串就是设备的状态码。 14118108101900000000000000000041450000000000 通过设备不同的状态值的比对,我们可以弄清楚哪些数据对应什么内容,这个是需要慢慢设置,然后抓包看数据,进行比照,比较费时间。 需要额外注意的是,不同的设备某些状态值可能并不相同,比如开关机的状态,这些最好自己MQTT抓包确实下。这里的数值都是以DYD-D50B3型号为准。

数据片段 含义
1411  
8 风扇水箱状态(0风扇停转,8风扇打开,4水箱满)
1 开关机状态(0关机,1开机,B开机并设置了定时关机,6关机并开启了童锁,7开机并开启了童锁)
0  
8 压缩机状态(0待机,8运行)
1 风速(0停转,1低风,2中风,3高风)
0 设备模式(0手动,1干衣)
19 设定湿度(16进制格式)
000000000000000000  
41 环境温度(16进制格式,可能是为了表示零下温度,所以转为10进制再减去40才是真实的温度,比如41hex = 65oct 65-40=25,所以25是当前环境温度)
45 环境湿度(16进制格式)
0000000000  

设备设置码

通过在App上对除湿机进行各种设置,我们可以抓到对应设置的编码。

这里可以看到,和刷新码一样,都是二进制格式(图里面是转成了16进制),并不是字符串。 通过各种App的操作设置我们也可以知道他们的各位的含义。

数据片段 含义
08 02 0  
1 设备开关
1 风速(1低风,2中风,3高风)
0 模式(0手动,1干衣)
4b 设置湿度(16进制)
00 00 00  

Node-RED Flows

然后我们将flows.json导入Node-RED

  • “德业除湿机状态接受” 设置德业官方MQTT服务的配置以及状态的topic
  • “状态转发”节点中设置好本地MQTT服务的配置。
  • “自动刷新”节点可以设置主动刷新状态的时间间隔,德业这方便设计的不是特别好,状态的及时更新依赖于本地的轮询刷新命令
  • 三个“设置”节点们需要配置好本地mqtt服务
  • “德业除湿机命令发送”节点设置好德业官方MQTT命令topic
  • “解析状态”和“设置命令”节点中,需要根据自己设备的MQTT协议抓包结果来做相关的修改适配,因为不同设备一些状态码,或者命令码有细微的差别。

Homeassistant配置及使用

根据configuration.yaml中的示范配置,将除湿机和相关的传感器配置到自己的Homeassistant的配置中。

]]>
深度学习起步 2016-07-27T00:00:00+08:00 kejinlu http://kejinlu.com/2016/07/deep-learning-intro 深度学习作为机器学习的一个分支也发展了也很多年了,只是一直感觉特别遥远,高深莫测,所以一般都不会接触到。不过近些年深度学习被越来越多的提起,特别是年初AlphaGo和李世石的比赛,打破了电脑在围棋上无法击败人类的预言,深度学习再次被推上风口浪尖。这两年逐渐火起来的自动驾驶,背后也是深度学习,像Google的自动驾驶汽车都需要不断地行驶在真实的路面上进行“学习”。最近流行的Prisma照片艺术化处理软件,其背后的技术也是深度学习:A Neural Algorithm of Artistic Style

我们先从机器学习开始吧,通俗点讲机器学习就是指计算机程序随着经验积累而自动提高性能。在机器学习界有一个类似于编程语言中的Hello World问题,即手写数字识别问题,一般都是通过MNIST(Mixed National Institute of Standards and Technology)手写数字图片数据库来训练程序,最后再通过对应的测试数据来测试,得出识别率。 也就是说如果一个程序对手写数字图片识别的准确率随着不断的训练识别率不断得到提升,那么可以称计算机程序在从训练中进行学习。这里有几个概念,首先任务,这里就是对输入的某个手写图片进行识别;然后是训练,训练则是训练数据,上面的书写数字识别的训练数据便是大量的手写数字图片以及对应的答案;还有就是识别率,识别率会随着不断的学习而变得越来越高,也就是通过学习,程序自己改了自己内部的一些状态,使得对新的手写图片进行识别的时候更加准确。

再来抽象点进行描述,机器学习就是类比人类认识世界的过程,人类通过历史经验进行归纳总结,碰到新的问题后能够根据归纳总价的规律来解决问题。当然机器没有人类大脑这么牛逼的硬件,而有的只是一堆集成电路和软件程序,对于机器学习来讲,机器学习软件通过训练数据来调整优化软件内部计算模型的参数,然后通过优化后的模型参数来处理新的数据,得到结果。

机器学习可以分为两大类:监督学习和无监督学习。监督学习就是训练数据给出数据以及数据对应的标准答案,学习后,对训练样本外的数据做出分类预测;非监督学习就是对没有答案或者标记的数据进行学习,发现数据的结构特性,将相似的数据聚集在一起,这就是所谓的聚类了,聚类并不关心某一类是什么。

我们再来谈谈机器学习的训练数据库,目前在机器视觉这方面比较好些,除了上面Hello World的MNIST数据库,还有全球最大的图像识别数据库ImageNet,它的缔造者就是斯坦福大学的李飞飞教授,他们利用互联网图片以及众包技术平台来帮助标记这些图片,所以这些图片数据库的构建可以说是“人肉计算”。斯坦福大学每年都会举行一个比赛,邀请谷歌、微软,百度等 IT 企业使用 ImageNet,测试他们的系统运行情况。每年一度的比赛也牵动着各大巨头公司的心弦,过去几年中,系统的图像识别功能大大提高,出错率仅为约 5% ,比人眼还低。还有一个数据库就是MSCOCO ,由微软资助,每年也会举行比赛。

再来说说深度学习,所谓深度是相对于浅层结构的机器学习,这些典型结构包含至多一层或两层非线性特征变换。而深度学习则是增加了计算的层次,原始的数据输入后,会经过多层处理之后才会最终输出得到结果。网络很多都提到深度学习是对人类视觉皮层结构的模拟,但是实际上深度学习的结构更多来源于理论、直觉和经验探索,和神经科学的相关性并没有那么大。拿深度学习中的卷积神经网络来说,确实部分灵感来自于神经科学,但是其和人类大脑的差距还是很大,所以在宣传的时候如果着重强调“神经”这个关键词会给人以错觉。

上面谈到了深度学习的 多层结构,其实里面的每一层都是一些数学计算,有些层里面的节点都包含一些典型的算法,通常我们都是从“回归”相关的算法开始说起,后面会对回归算法进行详细讲解。

]]>
Ubuntu使用环境配置 2016-07-23T00:00:00+08:00 kejinlu http://kejinlu.com/2016/07/ubuntu-config

还记得大学那会儿,特别爱折腾系统,装的最多估计就是Ubuntu系统了,也是从那时候起开始接触Linux,工作之后也就懒得折腾了,选择了同属UNIX系得macOS 。所以很长的时间里就一直使用macOS了。

去年家里买了一个NAS,当时考虑的因素有做工,盘位,系统。最后选择QNAP TS-453 Pro,当时为了便宜选择了从computeruniverse海淘,即使加上关税到手也比国内行货便宜一两千。虽然QNAP的系统比不上群晖,但是这两年已经有很大的改观,用户体验有追赶群晖的趋势。

QNAP TS-453 Pro 四核,四盘位,四千兆网口,内存升级到8G。内置的HD Station支持HDMI显示,HD Station支持一些简单的App,比如Chrome,Kodi,还是比较方便的。TS-453 Pro还支持虚拟化技术,通过Virtualization Station可以安装各种虚拟机,应该是基于KVM技术的;另外Container Station还支持Docker容器,所以使用起来想象空间很大。 没想到今年新的系统开始支持Linux Station,可以有完整的Linux的桌面体验了,当然Linux Station开启后,HD Station 会被关闭,因为都是通过HDMI接口来进行显示的输出。Linux Station是基于LXC来实现的,所以性能优越和原生基本无异。

目前Linux Station还只支持Ubuntu 14.04和16.04这两个LTS版本。想想NAS基本24小时开机,配置个简单的Ubuntu的工作环境,方便平时写写代码,写写文档。在QTS的Linux Station中安装好Ubuntu 16.04,以及确认开启Linux Station后,便可以在显示器上进行操作了。

简单的想了下,想搭建下python和Java的开发环境,以及装一些必要的软件。

1. python工作环境配置

Ubuntu 16.04安装好了之后默认就带了2.x和3.x的python版本,但是为了更方便的进行版本管理,决定通过pyenv来安装和管理python版本。 安装pyenv之前先装下git

sudo apt-get install git

然后进行pyenv的安装

git clone https://github.com/yyuu/pyenv.git ~/.pyenv

然后在~/.bashrc 末尾加上下面三行代码

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

然后重启Terminal,输入pyenv看命令是否起作用。

下面便可以通过pyenv来安装你所需要的python的版本了,比如

pyenv install 3.5.1

这个时候你会发现 悲剧发生了,由于国内的特定网络原因,python安装的下载速度及其缓慢,基本是10k以内的速度无法忍受(突然想到以前python官网一度无法访问),好在还是由办法解决这个问题的,你可以先想办法到官网把安装包单独下载好,比如我讲下载好的包Python-3.5.1.tar.xz放到~/Downloads目录下,然后指定build缓存目录后,再运行install命令

export PYTHON_BUILD_CACHE_PATH=~/Downloads
pyenv install 3.5.1
pyenv rehash

安装多个版本的python之后可以通过下面的命令来切换全局的python的版本

pyenv global 3.5.1

安装后对应版本的pip也是安装好了的。

最近对深度学习有点感兴趣,所以正好打算装个TensorFlow玩一玩,找到官方的安装指南,选择正确的版本进行安装, https://www.tensorflow.org/versions/r0.9/get_started/os_setup.html#pip-installation, NAS的配置注定只能使用CPU的,然后选择对应的python版本和Ubuntu的架构版本

export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.9.0-cp35-cp35m-linux_x86_64.whl
pip install --upgrade $TF_BINARY_URL

这个时候你会发现老问题又来了,pip的库在国内访问慢,所以这个时候你最好指定一个国内的pip源进行安装,这里选择豆瓣的源

pip install --upgrade $TF_BINARY_URL  -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com

另外如果你想选择一款python的ide可以选择Pycharm的社区版,一般情况下社区版够用了。

2.配置Java环境

Java目前一般有open jdk,还有oracle jdk,这里安装oracle jdk,你可以自己到oracle官网下载,手动进行安装,也可以通过添加源的方式使用apt-get工具进行安装

sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer

安装好了之后可以进行配置,比如如果你系统中安装有多个版本的jdk,那么就可以通过下面的方式来指定默认使用的jdk的版本

sudo update-alternatives --config java

3.其他常用软件安装

  • 对应一些独立的第三方下载的deb包,推荐使用GDebi Package Installer (sudo apt-get install gdebi
  • 中文输入法推荐搜狗输入法,安装搜狗输入法前先安装依赖的包 (sudo apt-get install fcitx libssh2-1),系统设置中将输入法系统设置为fcitx,然后下载搜狗输入法的包,可以从这里下载 http://download.pchome.net/utility/lan/ime/download-3955.html ,官网的包安装有问题
  • 编辑器可以使用github的Atom或者Sublime Text 3
  • markdown编辑器推荐使用haroopad
  • 系统优化配置可以使用Unity Tweak Tool (sudo apt-get install unity-tweak-tool), 此工具可以设定诸如Launcher的位置等
  • 实时顶端状态栏显示系统cpu内存等信息可以通过 indicator multiload 来shi实现 (sudo apt-get install indicator-multiload
  • 中文字体可以选择微软雅黑,首先到Windows系统下拷贝过来字体文件,进行下列操作之后,便可以选择微软雅黑字体了

    sudo cp msyh.ttf /usr/share/fonts/
    sudo mkfontscale
    sudo mkfontdir
    sudo fc-cache -fv
    
]]>
关于Xcode7中的tbd文件 2016-03-31T00:00:00+08:00 kejinlu http://kejinlu.com/2016/03/tbd-file

tbd 是 text-based stub libraries的意思, 是苹果在Xcode7中使用的一个技术,便于减少Xcode7中SDK的体积。
下面讲解下Xcode7如何通过tbd这个技术减少SDK的大小的。 Xcode7中和各个平台相关的sdk都在/Applications/Xcode.app/Contents/Developer/Platforms 这个目录下,你可以看到如下的一些平台:

这里列出了平台的名字 以及对应的动态链接库所需要的架构

MacOSX (i386,x86_64)
iPhoneOS (armv7, armv7s, arm64)
iPhoneSimulator (i386,x86_64)
AppleTVOS (arm64)
AppleTVSimulator (x86_64)
WatchOS (armv7k)
WatchSimulator (i386)

每个平台的SDK都在对应的Developer/SDKs/的子目录下,比如iPhoneOS的sdk在/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk下, 每一个SDK目录下都会包含相应的动态Framework以及dylib库,分别在SDK目录下的System/Library/Frameworks/usr/lib目录下。
在使用tbd之前不管是哪个平台,Framework以及各个单独的dylib库的二进制都得放进来,数量多,体积大。但是真正有必要的其实只是各个模拟器要用的动态库,因为MacOSX的库系统自带,那些诸如iPhoneOS,AppleTVOS,WatchOS这些设备要用的动态库,也只是在设备上真正运行的时候才需要,编译的时候只需要一些简单的信息,符号表啥的,编译通过就好了,真正到设备上去跑的时候才真正需要整个动态库的二进制文件。
所以为了节省Xcode的体积,苹果创造了一种tbd文件,用作替代那些设备SDK下的动态库,这里我们以/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/CFNetwork.framework这个动态Framework库为例,进入到CFNetwork.framework目录下你会看到一个CFNetwork.tbd文件,cat下

---
archs:           [ armv7, armv7s, arm64 ]
platform:        ios
install-name:    /System/Library/Frameworks/CFNetwork.framework/CFNetwork
current-version: 758.3.15
exports:         
  - archs:           [ armv7, armv7s, arm64 ]
    symbols:         [ '$ld$hide$os4.3$_NSHTTPCookieComment', '$ld$hide$os4.3$_NSHTTPCookieCommentURL', 
                       '$ld$hide$os4.3$_NSHTTPCookieDiscard', '$ld$hide$os4.3$_NSHTTPCookieDomain', 
                       '$ld$hide$os4.3$_NSHTTPCookieExpires', '$ld$hide$os4.3$_NSHTTPCookieLocationHeader', 
                       '$ld$hide$os4.3$_NSHTTPCookieManagerAcceptPolicyChangedNotification', 
                       '$ld$hide$os4.3$_NSHTTPCookieManagerCookiesChangedNotification', 
                       '$ld$hide$os4.3$_NSHTTPCookieMaximumAge', '$ld$hide$os4.3$_NSHTTPCookieName', 
                       '$ld$hide$os4.3$_NSHTTPCookieOriginURL', '$ld$hide$os4.3$_NSHTTPCookiePath', 
                       '$ld$hide$os4.3$_NSHTTPCookiePort', '$ld$hide$os4.3$_NSHTTPCookieSecure', 
                       '$ld$hide$os4.3$_NSHTTPCookieValue', '$ld$hide$os4.3$_NSHTTPCookieVersion', 
                       '$ld$hide$os4.3$_NSNetServicesErrorCode', '$ld$hide$os4.3$_NSNetServicesErrorDomain',

.......

你会发现其实tbd文件就是一个文本文件,其中包含架构信息,以及在真实运行时候二进制所在的位置,以及包含了动态库的符号表还有类的一些信息,这些信息在编译阶段足够了。通过通过这种技术,可以大大减少所有的设备SDK的二进制动态库的体积,其中包含MacOSX,iPhoneOS,AppleTVOS,WatchOS
模拟器SDK的动态库依然是原始的动态库二进制文件,这点你可以进到/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/CFNetwork.framework目录下进行验证。

]]>
iOS VoiceOver Programming Guide 2016-03-07T00:00:00+08:00 kejinlu http://kejinlu.com/2016/03/ios-voiceover-programming-guide 前言

VoiceOver是苹果“读屏”技术的名称,属于辅助功能的一部分。VoiceOver可以读出屏幕上的信息,以帮助盲人进行人机交互。 这项技术在苹果的各个系统中都可以看到,OS X,iOS,watchOS,甚至tvOS。 苹果公司的VoiceOver在2015年6月18日获得了美国盲人基金会(American Foundation for the Blind, AFB)颁发的海伦凯勒成就奖,成为全球首家获得此殊荣的科技公司。 单从iOS来说,iOS的VoiceOver功能可以毫不夸张的说是三大移动平台中做的最好的。

虽然说苹果默认的UI组件都已经默认支持VoiceOver功能了,但是通常情况下App还是需要对VoiceOver进行适配和优化的,比如说一些自定义复杂UI组件。

基本使用

iPhone上开启VoiceOver功能后,就可以通过 单指左右轻扫 来遍历当前界面中的所有的AccessibilityElement(可以被VoiceOver访问的UI元素),当一个AccessibilityElement被选中后,VoiceOver会将AccessibilityElement的信息读出来。 单指轻点两次 能够激活当前元素对应的操作,比如当前AccessibilityElement是一个按钮,那么对应的就是按钮的Action事件。

简单点来说在App开发过程中关于VoiceOver我们需要关注如下几点:

  • 界面上的AccessibilityElement有哪些
  • AccessibilityElement的位置和形状
  • AccessibilityElement的信息是什么(就是Element被选中后,被读出来内容)
  • AccessibilityElement所能响应的的事件有哪些

UIKit中的控件基本都是 VoiceOver Ready的,即使是UIView,你也可以通过简单的设置其实变成AccessibilityElement。所以这一小节中所讲的AccessibilityElement其实都是UIView或其子类的实例。
相关属性和方法基本都在UIAccessibility.h这个头文件中进行了声明。

所以简单的情况下通过UIView的isAccessibilityElement属性就可以控制某个View是否是AccessibilityElement,在UIKit的控件中,像UILabel,UIButton 这些控件的isAccessibilityElement属性默认就是true的,UIView这个属性默认是false。

一般情况下AccessibilityElement的位置和形状是通过accessibilityFrame进行设置的,默认值是View在屏幕中的位置,形状就是View的矩形形状。如果你想自己设置accessibilityFrame的值,那么得注意下,这边的frame值是相对于设备Screen的坐标系的,当然可以通过UIAccessibilityConvertFrameToScreenCoordinates函数来帮助转换。此函数有两个参数,一个rect,一个是view, 其含义就是将相对于view这个坐标系的rect转换成相对于screen坐标系的值并返回。所以一般情况下 rect可以是目标Element在父View中的frame,view就为其父view。

public func UIAccessibilityConvertFrameToScreenCoordinates(rect: CGRect, _ view: UIView) -> CGRect

如果你想设置非矩形的形状,你也可以通过给 accessibilityPath 属性指定一个UIBezierPath类型的值来自定义AccessibilityElement的形状。

至于AccessibilityElement的信息可以通过下面几个UIAccessibility的属性来决定

  • accessibilityLabel 这是什么
  • accessibilityHint 这个有什么用,会产生什么样的结果
  • accessibilityValue 这个的 是什么
  • accessibilityTraits 这个的类型以及状态,就是通过traits来表征这个Element的特质,数据类型是一个枚举类型,可以通过按位或的方式合并多个特性。

这里有个需要注意的就是,当某个View的是AccessibilityElement的时候 ,其subviews都会被屏蔽掉,这个特性有时候还是有用的,比如一个View中包含多个Label,那么你希望每一个下面的Label不要单独可以访问到,那么你可以将这个View设置成可以访问的,然后将其accessibilityLabel设置为所有子Label的accessibilityLabel的合并值。

至于AccessibilityElement的事件,最简单的莫过于上面提到 单指轻点两次 能够激活当前元素对应的操作了,如果当前AccessibilityElement实现的public func accessibilityActivate() -> Bool这个方法返回true,那边此逻辑将被调用,否则相当于在AccessibilityElement的accessibilityActivationPoint这个位置点上进行了一次Tap操作。

高级特性

Accessibility Container

设想下这样的一个场景,一个UIView,内部包含一组用户可以进行交互的内容,每一个内容之间是独立的,但是这些内容不是以子View的形式存在,而是通过Quarz 2D或者Core Text渲染而成,所以这部分内容无法通过上面的方式变成AccessibilityElement。这种情况UIView需要按照UIAccessibilityContainer的方式,来将内部的每一个独立的内容都描述成UIAccessibilityElement的实例,这个时候这个UIView我们称之为Accessibility Container。

接下来讲解具体的实现步骤了。 先说说iOS 8之后如何实现,首先Accessibility Container的isAccessibilityElement值必须设置为false,另外我们需要创建出所有的UIAccessibilityElement的实例,然后赋给accessibilityElements属性(iOS 8.0+) 假设在一个UIView的子类中,通过叫做updateAccessibleElements的方法来更新维护所有的UIAccessibilityElement实例,当界面上的内容发生变化,或者VoiceOver开启关闭状态发生变化时,调用此方法以更新accessibilityElements,相关伪代码如下:

    internal func updateAccessibleElements() {
        guard UIAccessibilityIsVoiceOverRunning() else {
            self.accessibilityElements = nil
            return
        }
        
        self.isAccessibilityElement = false
        var elements = [AnyObject]()

        let element1 = UIAccessibilityElement(accessibilityContainer: self)
        element1.accessibilityLabel = "element1"
        element1.accessibilityTraits = UIAccessibilityTraitStaticText
        element1.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(element1FrameInSelf, self)
        elements.append(element1)
        ...
        self.accessibilityElements = elements
    }

如果是iOS 8以下,那么就需要实现下面的几个方法来实现了,比较繁琐:

// accessibilityElement的个数
public func accessibilityElementCount() -> Int

// 返回指定Index的accessibilityElement
public func accessibilityElementAtIndex(index: Int) -> AnyObject?

// 返回指定accessibilityElement的Index
public func indexOfAccessibilityElement(element: AnyObject) -> Int

使用上面第二种方式的时候,往往需要自己维护一个包含所有accessibilityElements的数组,然后通过数组来完成上面的这三个方法的返回。这种方法比较累赘,而且当界面更新需要刷新内容的时候,还需要发送通知系统,告诉系统界面上的accessibilityElements有变动需要更新。所以最小版本定位于iOS 8的情况下还是直接设置accessibilityElements属性的方式比较科学。

关于UIAccessibilityElement这个类的设计还是存在一些疑惑的,既然NSObject的UIAccessibility扩展已经包含了诸如accessibilityLabel,accessibilityHint这些属性,为何UIAccessibilityElement类中还需要重复声明这些属性,有点重复的感觉。

Actions

之前只讲到了最简单的事件,就是单指轻点两下,其实常见的Actions有下面这些,每一个Action都会对应一个方法,可以通过覆盖方法的方式来自定义Action对应的逻辑:

  • Activate 单指轻点两次
    public func accessibilityActivate() -> Bool
  • Escape. 单指 Z-shaped 手势一般用于退出模态界面或者返回导航的上一页界面
    public func accessibilityPerformEscape() -> Bool
  • Magic Tap. 双指轻点两次触发 most-intended action.
    public func accessibilityPerformMagicTap() -> Bool
  • Three-Finger Scroll. 三指滑动触发界面水平或者垂直的滚动
    public func accessibilityScroll(direction: UIAccessibilityScrollDirection) -> Bool
  • Increment. 单指向上滑动,需要设置accessibilityTraits为UIAccessibilityTraitAdjustable,否则对应的方法不会被调用
    public func accessibilityIncrement()
  • Decrement. 单指向下滑动,需要设置accessibilityTraits为UIAccessibilityTraitAdjustable,否则对应的方法不会被调用
    public func accessibilityDecrement()

这些方法中,其中Escape,Magic Tap,Three-Finger Scroll这几种手势支持在响应链中向上寻找对应的Action方法,首先用户在屏幕上进行相应的手势,系统检查当前VoiceOver的Focus的Element有无实现对应的方法,没有实现的话,则向响应链的上一级寻找。比如有些全局性的操作,对应的方法写在上层View或者ViewController中比较合适。 其实很多系统提供的组件都默认实现了一些VoiceOver的手势Action,比如UINavigationController, UIAlertController 都提供了对Escape手势的支持。

Accessibility Notification

Accessibility提供了一系列的通知,可以完成一些特定的需求。比如你可以监听UIAccessibilityVoiceOverStatusChanged通知,来监控Voice Over功能开启关闭的实时通知

或者是你在App中主动发送一些通知,来让系统做出一些变化,比如当你界面上的AccessibilityElement有变动的时候你可以发送UIAccessibilityLayoutChangedNotification通知,通知发送时使用UIAccessibility的专用的函数,UIAccessibilityPostNotification函数有两个参数,第一个是通知名,第二个是你想让VoiceOver读出来的字符串或者是新的VoiceOver的焦点对应的元素。

UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.myFirstElement)

建议

  • AccessibilityElement的信息尽量简洁,accessibilityLabel不要包含提示性质的文案,避免信息干扰
  • TableView的每一个Cell的信息尽量合并,使得Cell变成一个整体的AccessibilityElement,避免无意义的冗余元素之间切换的操作。Cell中有多个按钮的时候,可以考虑使用Magic Tap的方式,Magic Tap的Action中弹出sheet样式的UIAlertController来供用户操作。
  • 自定义的模态页面注意设置accessibilityViewIsModal为true,最好支持Escape手势 的方式退出模态页面。
  • 将页面中装饰用的没有实际意义的元素的accessibilityElementsHidden设置成true,减少操作过程中的干扰

参考文档: UIAccessibility Protocol Supporting Accessibility Accessibility Programming Guide for iOS

]]>
Handoff Between iOS App & Website 2015-04-01T00:00:00+08:00 kejinlu http://kejinlu.com/2015/04/handoff-between-native-app-and-web-browser 一.Handoff的基本常识

iOS 8以及Mac OS X Yosemite之后引入了一个新的功能特性:Handoff。Handoff也就是Continuity特性,连续互通,比如你用iPhone写邮件写到一半想在Mac上继续写,或者Mac上看到一个网页想在手机上浏览,这些便是Handoff的使用场景了。

Handoff的支持有一些硬性的要求:

  • 互通的所有设备必须支持 Buletooth LE 4.0,Handoff使用BLE信号来传递用户活动数据。
  • 设备处于联网状态,有时候有些数据还是会通过互联网来传递的,比如Mail App的邮件内容的同步。
  • 所有设备必须连到同一个iCloud账户。
  • 当然你还得保证当前设备的Handoff功能打开了(iOS:设置->通用->Handoff 与建议的应用程序。 Mac:系统偏好设置->通用,倒数第二栏有个选项,”允许这台Mac和iCloud设备之间使用Handoff”)

BLE并不像传统的蓝牙,并不需要人工手动进行配对,只要打开就行了,所有的配对数据传输都是自动完成的;设备并不一定需要连在同一个WIFI网络中,Handoff的活动数据通过BLE进行传递,保证及时性以及数据的安全性,你可以在使用过程中尝试将WIFI或者网络关闭,设备还是可以接受到Handoff的通知的。

苹果已经对很多内置的App做了Handoff支持,如Safari浏览器,邮件,电话,消息,提醒事项等都是支持的,在你开始Handoff编程之前可以先使用这些App进行Handoff功能的体验。

二.iOS App 到 Web Browser

Handoff编程的核心类便是NSUserActivity了,代表着一个用户的活动,每一个Activity都有一个activityType,用来标识Activity的类型。当App 到 App之间进行Handoff的话,那么接受方需要满足几个条件

  • App必须是通过发布证书或者开发者证书进行打包的
  • 和发布Activity的App拥有相同的TeamID
  • info.plist中声明了接受的Activity的activityType(key 为 NSUserActivityTypes)

不过很多应用其实也只是在移动设备上有App,在Mac上绝大多数还是走的浏览器,所以iOS App和浏览器的Handoff的需求就变的很常见了。这个时候Activity的另一个叫做webpageURL的属性便有用武之地了,当没有合适的App能够处理当前的Activity的话,系统会转给默认的浏览器进行处理(当然你的这个默认的浏览器的info.plist的NSUserActivityTypes数组中必须声明了 NSUserActivityTypeBrowsingWeb这个type,目前Mac版本的Chrome已经支持了)。

self.myActivity = [[NSUserActivity alloc] initWithActivityType: @"com.taobao.handoff.act.home"];
self.myActivity.webpageURL = [NSURL URLWithString:@"http://www.taobao.com"];
[self.myActivity becomeCurrent];

当上面的代码执行之后,Activity便会进行分发,接受者接受后,若没有App能够处理当前类型的Activity的话便转交给默认的浏览器去处理了,这里需要特别注意的就是activity的生命周期,当activity被invalidate或者被释放了,那么这个Handoff消息也就消失了,相关设备的Handoff消息就会消失。

关于Handoff的调试,由于到目前为止模拟器还是没有支持Handoff的,所以你必须使用开发者证书进行真机调试。

三.Web Browser 到 iOS App

相比于App到Web Browser,Web Browser到iOS App的Handoff实现起来就复杂一些了。 首先先描述下大体的流程:

  1. 首先在Mac上使用Safari浏览器浏览目标网站,Handoff消息会通过BLE进行分发
  2. iOS设备接收到Handoff消息后,检查对应的webpageURL,看是否有某个App的associated-domains (entitlement中的一项)中包含了这个webpageURL, associated-domains对应的Handoff的配置URL样式为 activitycontinuation:example.com
  3. 如果某个App的associated-domains存在相应的webpageURL,那么iOS会去这个网站的固定的一个URL(地址为https://example.com/apple-app-site-association)获取一个签名过的文件(源文件为一个JSON文件),如果解密后文件中的App IDs中包含了 之前匹配的App的App ID,那么这个Activity便交给这个匹配的App进行处理。

下面讲解详细的操作步骤

1.客户端

首先当然还是折腾客户端工程,当你创建好工程,创建好App ID,XCode中设置好自己的Developer账户之后,你便可以设置编译的Code Sign的相关东西了,配置都得选自动的,这样就可以通过XCode来管理配置 App ID 以及相应的 Provisioning Profiles了,当你通过developer后台网站就可以看到Provisioning Profiles中有一堆所谓的Managed by Xcode的条目了。

你需要在XCode工程对应的Target的Capabilities这个Tab中开启Associated Domains,这个时候时候你可能会遇到错误提示“You must be a team admin or agent in order to enable this capability.”,其实即使账户是admin还是会报错,这个可能是XCode的bug吧,你需要切换到General这个tab中将Team先选None,然后再切换到你对应的Team,这个时候Team下方显示错误了,其实就是你更改了Entitlements,而这个和Provisioning Profiles有关联,所以你的Provisioning Profiles也需要重新更新,点击Team下方的Fix Issue按钮,等待重新下载新的Provisioning Profiles,然后回到Capabilities这个tab你会发现刚才的错误已经不见了。

其实Capabilities中的操作除了会在本地生成entitlements文件,还会同步到developer后台去,会修改app对应的App ID的配置,以及在developer后台生成新的Provisioning Profiles。这些东西都和打包签名息息相关。

接着在Associated Domains下加上所需要支持handoff的domains

activitycontinuation:taobao.com

activitycontinuation是服务名,taobao.com是支持的域名 当Mac上的浏览器访问一个网站的时候,此网站的域名如果被某个App的Associated Domains包含了,那么Handoff底层会去这个域名一个指定的路径下访问一个文件,这个指定的路径便是 : https://taobao.com/apple-app-site-association ,这个路径需要返回一个签名过的文件数据,里面指定了当前网站所支持Handoff的App ID们,这个下面会提及到

2.服务器端

需要进行Handoff的网站,需要在https的特定的路径下放一个签名过的文件,这个文件里面指明了Handoff支持哪些App(Domain-approved apps IDs),这个文件的明文为JSON格式,在对JSON文件签名前最好去掉所有无用的空格以及检测下JSON格式的正确性,避免后面带来问题

{"activitycontinuation":{"apps":["XN6U3EV979.com.taobao.handoff"]}}

签名则是使用网站的ssl的私钥以及证书进行签名(如果不存在中级证书,那么中级证书可以去掉)

cat json.txt | openssl smime -sign -inkey taobao.com.key
                             -signer taobao.com.pem
                             -certfile intermediate.pem
                             -noattr -nodetach
                             -outform DER > apple-app-site-association

生成的文件放到网站根目录下以及确保可以通过指定的路径进行访问。

3.如何进行本机调试

要想在开发机器上进行网站的Handoff的调试则首先的问题就是SSL证书,你需要自己搞一个CA证书,在Mac上可以通过Keychain Access(钥匙串访问)这个App中的证书助理来生成 。

首先是CA证书,这里生成的是自签名的根证书,CA证书的作用就是给网站的SSL的证书进行签名用的,然后创建网站的SSL证书,一步一步走下去,然后通过刚才的CA证书进行签发,这样生成的证书就可以直接用于网站的SSL证书了。

然后选择一个Web Server,我这里选用的Jetty,直接下载下来然后就可以直接使用自带的demo了,主要是需要自己配置下SSL。

将默认的ssl配置拷贝到demo工程相应的目录下

Luke@LukesMac:~/Workspace/jetty » cp etc/jetty-ssl.xml demo-base/etc/ 

从Keychian Access中导出之前生成的证书文件,导出格式为p12,这样就会包含私钥了。假设导出文件为 lukesmac.p12,导出时候 需要你设置一个密码,你就将其设置为 keypwd 然后需要将这个p12文件导入demo工程的keystore文件中,默认在demo工程的etc目录下已经存在一个keystore文件,直接导入这个keystore

keytool -importkeystore -srckeystore lukesmac.p12 -srcstoretype PKCS12 -destkeystore keystore

默认keystore的密钥库口令为storepwd,导入的过程中你还需要输入你上面设置的私钥密码(因为jetty-ssl.xml中配置的私钥密码以及默认keystore中的私钥密码默认为keypwd ,所以为了方便上面导出私钥所设置的私钥密码保持一致为keypwd)。最后你还需要在demo工程的根目录下的start.ini中加入一行

etc/jetty-ssl.xml

然后你就可以开开心心的启动了,

Luke@LukesMac:~/Workspace/jetty/demo-base » java -jar ../start.jar

然后我便可以通过 https://lukesmac.local:8443/ 进行访问了

下面需要将json.txt进行签名,

首先你需要从上面导出的p12文件中搞出私钥文件,再从Keychain Access中导出一份证书的cer文件

openssl pkcs12 -in lukesmac.p12 -nocerts -out privateKey.pem
cat json.txt | openssl smime -sign -inkey privateKey.pem -signer lukesmac.cer -noattr -nodetach -outform DER > apple-app-site-association

将生成的apple-app-site-association文件放到 demo工程的ROOT目录下,然后重启以及在浏览器中对这个文件进行访问测试。

这个时候你以为一切就绪了,发现手机上handoff的图标依然是safari,打开后发现,网页根本无法打开,其原因就是自己生成的自签名的CA证书不被信任,这个时候你可以讲CA证书按照cer的格式导出,然后通过邮件发送,在iPhone上的邮箱App中点击这个cer的附件,系统会跳转到设置的描述文件的界面去,你需要进行安装证书,之后这个CA证书签发的SSL证书对于这台设备都是可信任的了。

最后就是客户端添加处理逻辑了,可以在Appdelegate中添加如下方法,就可以对传递过来的userActivity进行处理

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray *restorableObjects))restorationHandler NS_AVAILABLE_IOS(8_0);
]]>