Frossky 发布的文章

python总是和别人不一样...

  1. 在函数中访问全局变量并不能直接访问,而是要加上global修饰,否则你访问的很可能只是一个新创建的局部变量...
a_global_var = 100
def func():
    global a_global_var
    a_global_var = 200

func()
print(a_global_var)
  1. 检查系统命令是否存在

这主要是参考这儿
适用于python2.7是其中一个lambda表达式的答案,在windows上也测试通过了

import os

cmd_exists = lambda x: any(os.access(os.path.join(path, x), os.X_OK) for path in os.environ["PATH"].split(os.pathsep))
cmd_exists('ls')

科技的最大谎言是什么?就是他保证你生活的轻松而富足。难道不是吗?近100多年以来,科技都是一直在这么宣传自己的。科技说,他可以帮你干活儿帮你赚钱。比如说一个工厂的工人他买了一个机器人,然后把它放在产线上。帮他干活,帮他赚钱。那个人他就可以天天在家里享受这种无忧无虑,又有钱赚的日子啦。可是事实是怎样呢?我们都知道了。那个工人他其实买不起机器人。买得起机器人的工厂呢,最后把他解雇了。

grav相对wp,确实在很多方面体验都要好,比如对md的支持更加完美,界面也更好看,不需要数据库等。然而写文章的体验却不好。
目前在线写文章,还是通过grav的官方admin。创建文章的步骤要繁琐很多,除了写文章标题和内容外,还要选择目录结构、目录名、模板、类别、发布时间等。并且遭遇了一次没有保存,第二天点击保存直接跳出到登录页面(登录过期),导致辛辛苦苦写的内容全部不见了。。。
所以想了想,还是用本地编辑器sublime text或者vs code写好了再通过ftp工具filezilla上传吧!

网络低延时技术,或者可以称之为实时技术,对远程人人交互和远程人机交互有很大的作用。以前有很多设想,都受到网络延迟不可控、带宽低、带宽费用昂贵的影响。而最近一些技术的成熟让这类创新成为可能,或者具备爆发的可能。

  • 信令低延时。以前多人在线游戏,尤其是实时性要求比较高的游戏类型,如FPS、ARPG,尤其是FPS,都只能在局域网环境运行。而现在互联网环境中的此类游戏已经发展多年,一个每秒20帧(20fps)的游戏,信令需要在1帧之内(50ms)完成采集、上传、广播、渲染,已经有很多技术应对这项挑战参考:dota 类游戏是如何解决网络延迟同步的?
  • 语音低延时。同样由于在线游戏的发展,队友之间通过语音相互协同通话、共同作战。在线语音聊天也对实时性提出要求,电信级的语音通话延时要求也只是400ms。而网络KTV的连麦要求,也需要400ms之内的延时。参考即构科技:为了互动直播,如何让直播技术实现低延迟?
  • 视频低延时。这次则是由于直播的发展,对视频低延迟构成了需要。比如双方进行谈话、再由第三方观看。按上面即构科技的文章,超过800ms就会有不适。另外还可以参考网络直播对实时通信的技术要求有多高?
  • 移动宽带费用下降。联通今年推出299元不限流量的冰淇淋套餐,电信在今年高交会上也推出99元不限流量的套餐。一旦不限流量套餐逐渐成为主流,势必为很多高流量应用增加新的使用场景。
  • 物联网技术、传感器技术、以Arduino、树莓派为代表的嵌入式的发展。智能手机的发展降低了一部分传感器的价格,嵌入式开发环境的通用化,使得第三方库大量出现,降低了对物的开发的门槛。
  • 人工智能的快速发展。这一点我还没有深入的了解和使用过,不过发展确实是有目共睹的。
  • AR、VR的发展带来输入和显示技术的革新。目前AR/VR已经解决了显示,但输入还没有形成稳定的方式,各种手柄、感应器、动作捕捉技术、Kinect等各显神通,相信很快有一天会形成像鼠标键盘一样的标准输入。

现在的一个爆发的应用就是线上娃娃机。使用了信令、语音、视频的低延时的技术,加上和娃娃机这个物体的信令连接,实现了远程操控。而移动宽带费用的下降,则可以实现实时远程操控移动物体,并且不受地理位置的限制。这可能就会有很多应用,可能会真正带来一些远程控制机器人的民用化。AR/VR的输入方式,也会使得这些操控更加直觉和方便。而人工智能的加入,会逐渐提高这些机器人的自主性,使其可以相应无需精确操作的目的指向的指令,如端水、买菜、到达目的地等。

准备迁移wp到基于markdown的Php平台,最好没有数据库这类东东,因为我用的原来万网的虚拟主机,只提供php空间,没有ssh后台登录界面。搜来搜去,发现grav和typecho是比较高关注的两个,要求都是php5.5以上。后来又发现typecho需要数据库,就只有选择grav了。
grav有三个下载包概念:skeleton/theme/plugin。后两个都知道,theme是主题,plugin是插件,但skeleton是什么?翻译过来是“骨架”,其实是框架、整体安装包的意思,包含了特定的一些plugin、示例页面等。这道和grav“坟墓”很搭配啊。不过这倒是谁起的这么个名字,估计要犯了很多国人的忌讳...
要有的功能,第一是有个blog的样子,另一个是能通过网页写md,毕竟通过ftp上传md还是很麻烦的。
这两个功能都不是grav core核心含有的,而是通过组织页面、插件等完成的。还好,有两个skeleton,一个是grav core + admin,另一个是在skeleton里搜索blog, 下载其中的blog site
下载完成后,需要对这两个skeleton合二为一。以blog site为基础,把admin中的user/plugins/中的除了error和problems的目录(包括admin/email/login/form/markdown-notices),全部复制到blog site对应的目录中(user/plugins)。
然后打包、上传ftp、解压缩、使用浏览器安装即可。
如安装到your.site/grav中,进入your.site/grav/admin,创建admin用户,密码必须是8位大小写加数字,就可以在pages中写文章了。
文章需要写在home下面,使用模板是item,options里面taxonomies/category选择blog,这样才搜索得到。
可以看一下搜索的设置,plugins/simplesearch里面,有个category filter,写的是blog,所以只有文章标了category是blog的才会被搜到。
解释一下涉及的目录:

  • user,主要的内容,包括页面、配置、插件都在这个目录下。
  • user/plugins是插件目录,可以直接复制新的插件(目录形式)到这个目录下,目录名称需要改成插件要求的名称
  • user/pages页面目录,是按标题名称命名的目录名称,里面会有一个.md文件,按模板item创建的时候,文件名就是item.md,按blog创建的时候文件名就是blog.md。但不要按blog模板创建,因为blog其实是列表页。
    pages目录下面的是网站一级菜单,下一级目录里面可以放具体的文章。
  • user/themes是主题页,不过不能直接ftp上传到这个目录生效,两种方式:登录admin后台,在themes里面搜索,不过我试过搜索不到,似乎是因为php的ssl配置的问题。另外,可以将theme的zip文件通过tools上传,不过注意php上传大小要求,我这儿只有2M。

pages/

pages下面的目录可以用数字编号如01.home,一方面这会让其出现在一级菜单,另一方面标明菜单顺序。小数点前面的内容不会在页面和url上显示。

.md

.md开始时在两个---之间的,是此文件的配置说明,以YAML格式书写。这种在文件最前面以YAML形式书写的配置文件又称为YAML Front Matter,简称YFM,是标准的。
支持的Markdown语法在这儿

  1. exitwp项目地址,克隆下来(python项目,需要python运行,2.7试过没问题)

    git clone --depth=1 https://github.com/thomasf/exitwp.git
  2. wordpress后台->工具->导出,将文章下载为wordpressxxxx.xml格式

  3. 把wordpress的下载文章复制到exitwp/wordpress-xml下面

  4. 你可能需要安装几个exitwp.py的依赖

    pip install pyyaml bs4 html2text
  5. 在exitwp下运行:

    python exitwp.py

    如果还有依赖没有安装就装一下

  6. 在exitwp/build目录下可以找到。exitwp是为了jekyll博客准备的,而这个博客是ruby的,不过至少你已经有了md文件

在把8266的(4M Dio)的固件通过esptool.py烧录到8285(1M)的过程中,发现烧录后不能启动。
8266使用的命令如下:

esptool.py --port com3 write_flash 0x00000 esp.flash.bin 0x10000 esp.irom0text.bin 0x3fc000 esp_init_data_default.bin

8285命令如下:

esptool.py --port com3 write_flash 0x00000 esp.flash.bin 0x10000 esp.irom0text.bin 0xfc000 esp_init_data_default.bin

发现esptool.py对8285也是按8266识别的,这倒也没错。识别出来的存储空间也是1M,没错。
连接8285串口,74880波特率查看BOOT ROM打印的错误,如下:

 ets Jan  8 2013,rst cause:2, boot mode:(3,6)<\r><\n>
load 0x40100000, len 32440, room 16 <\r><\n>
tail 8<\r><\n>
chksum 0xef<\r><\n>
load 0x00000000, len 0, room 0 <\r><\n>
tail 0<\r><\n>
chksum 0xef<\r><\n>
load 0x00000000, len 0, room 8 <\r><\n>
tail 0<\r><\n>
chksum 0xef<\r><\n>
csum 0xef<\r><\n>
csum err<\r><\n>
ets_main.c 

后面两个load 0x0000000显然是有问题的,拿一个正常的8285试了一下,信息如下:

load 0x40100000, len 29408, room 16 <\r><\n>
tail 0<\r><\n>
chksum 0x70<\r><\n>
load 0x3ffe8000, len 1968, room 8 <\r><\n>
tail 8<\r><\n>
chksum 0x72<\r><\n>
load 0x3ffe87b0, len 692, room 0 <\r><\n>
tail 4<\r><\n>
chksum 0x4c<\r><\n>
csum 0x4c<\r><\n>
<\r><\n>

果然后面两个load的地址是不对的。
网上查了一下,8285应将flash_mode设置为dout,如下:

esptool.py --port com3 write_flash --flash_mode dout 0x00000 esp.flash.bin 0x10000 esp.irom0text.bin 0xfc000 esp_init_data_default.bin

烧录后果然成功了。
几个flash mode的解释:

3.spi mode :
1)QIO ,for flash that support quad r/w operation(e.g. W25Q)
2)QOUT,for flash that support quad read operation(e.g. W25Q)
3)DIO,for flash that support dual r/w operation(e.g. W25Q &W25X)
4)DOUT, for flash that support dual read operation(e.g. W25Q &W25X)

参考:
ESP8266-blink
初始化的时候需要调用gpio_init()
然后,上述Github代码中欠缺一个非常重要的步骤:需要选择gpio的功能,使用PIN_FUNC_SELECT
然后使用GPIO_OUTPUT_SET设置电平。
GPIO2是模块上的LED,GPIO14是接口板的LED

参考

二级域名设置为grav.idarc.cn,通过隐性Url转向到idarc.cn/grav ,这个时候,发现在手机浏览器上显示的仍然是PC端的布局方式。在chrome中调试,发现grav的页面是响应式布局,缩小chrome边框会逐渐改为手机布局方式。chrome调试打开手机模拟(Nexus 6P),发现仍然是PC布局,查看window.innerWidth,居然=950。直接打开idarc.cn/grav ,查看window.innerWidth,布局正确,是手机样式,并且window.width=412,也是没错的。

同事在使用官方的EspFlashDownloadTool的时候,虽然能一次同时烧录8个,然而烧录完毕后该工具不会自动重启ESP固件,每次还需要为每个设备手动去选择串口,以及为不同产品选择不同的bin文件。
为什么用Python开发:本来对node.js比较熟悉,然而考虑到要给同事安装,装完node又装python 显得很麻烦,而且node的serialport模块需要安装node-gyp,node-gyp需要vs studio本地编译,并且项目依赖还需要npm install。最坑爹的是serialport没有promise化,自动化和串口交互就会掉入回调地狱(虽然本项目不涉及)。而python的所有模块统一装在一起,pyserial也不需要二次编译,而且似乎pyserial也比serialport的功能强大一些,就还是选择python开发,虽然一两年每碰python手生得很了——得抱着本书在旁边当字典。
最终源码只需要单个文件,如起个名字叫autoflash.py,关联python可以直接运行。
安装过程:
1. 装python2.7.14,装的时候添加python路径的选项要勾上;
2. 更换pip源为豆瓣,(国内开发python靠豆瓣,开发node靠淘宝啊),在用户目录(如C:\users\xxx)下新建pip\pip.ini ,输入:

[global]

index-url = https://pypi.douban.com/simple
  1. 安装esptool
pip install esptool
  1. 将autoflash.py复制到固件目录下。
  2. 修改一下源码,里面固件文件名、烧录地址、esptool.py的名称有不对的地方,改成跟自己的环境相同的。
    这个是很奇怪的,我使用pip install esptool安装后的文件叫esptool.exe,而同事电脑(都是 win10 x64)装好却是esptool.py(与官方介绍的相同)。
    另外,我的不需要shell=True也可以调用subprocess,而他的电脑却需要,否则会报错:windowserror error 2。Error 2查了一下,是找不到文件的意思,连dir都找不到。加上shell=True就好了。

解释一下subprocess,Popen默认是开新的子进程的。等待进程结束使用wait或者communicate,不过建议用communicate,因为后者机制上更好一些,不容易出问题。
serial_port方法的源码来自于Seg

源码如下:

#!/usr/bin/env python

from __future__ import division, print_function

import sys
import glob
import subprocess
import multiprocessing as mp
import shlex

import serial


def serial_ports():
    """ Lists serial port names

        :raises EnvironmentError:
            On unsupported or unknown platforms
        :returns:
            A list of the serial ports available on the system
    """
    if sys.platform.startswith('win'):
        ports = ['COM%s' % (i + 1) for i in range(256)]
    elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
        # this excludes your current terminal "/dev/tty"
        ports = glob.glob('/dev/tty[A-Za-z]*')
    elif sys.platform.startswith('darwin'):
        ports = glob.glob('/dev/tty.*')
    else:
        raise EnvironmentError('Unsupported platform')

    result = []
    for port in ports:
        try:
            s = serial.Serial(port)
            s.close()
            result.append(port)
        except (OSError, serial.SerialException):
            pass
    return result


def main():
    sps = serial_ports()
    print(sps)
    processes = {}
    for sp in sps:
        cmd = shlex.split('esptool.py --port ' + sp + 
            ' --baud 1152000 --after hard_reset write_flash 0x00000 \
            eagle.flash.bin 0x10000 eagle.irom0text.bin 0x3fc000 esp_init_data_default.bin')
        print(cmd)
        processes[sp] = subprocess.Popen(cmd)

    for sp in sps:
        processes[sp].communicate()

    print('[Finished]')


if __name__ == '__main__':
    main()

BLE事实上是完全不同的技术,与经典蓝牙完全不兼容。最早由诺基亚与2006提出,名字开始也不叫蓝牙,叫Wibree。并且在蓝牙4.0版本中被纳入。

特性

BLE传输距离100米,空中速率125k/1M/2Mbps,应用速率270kbps,延时仅6ms,发送数据耗时仅3ms,发射功率10mW,功耗10mW~500mW。峰值电流15mA。

广告和发现机制

BLE使用3个独立信道发送广播包来发现彼此。设备每隔一个广播间隔发送一次广播包,为防止冲突,发送间隔会加上一个<10ms的随机值。扫描者在每个扫描间隔后的扫描窗口上监听。

软件模型

客户端Client,一般是手机、电脑,发送GATT( Generic Attribute Profile)命令和请求,接收回应。
服务端Server,例如温度传感器,接收GATT请求并回应。
特性Characteristic,客户端、服务端之间传输的数据,如电池电压。
服务Service,一组相关特性,组合提供特定功能。如体温服务包括温度测量值、测温间隔等特性。
描述符Descriptor,为特性提供额外信息。例如温度特性可能有单位、最大最小可测量值。特性可有0个或多个描述符。
有的特性和描述符是用于系统管理的,如通用访问(Generic Access)服务可读取模块名、序列号等特性。服务也可以包括其他子服务。设备主要功能称为主服务、辅助功能称为次服务。
标识Identifiers,特性、服务、描述符统称为属性attributes,以UUID标识。任何实现都可以为特定用法选择随机或者伪随机的UUID,但蓝牙标准保留了一些UUID(形式是 xxxxxxxx-0000-1000-8000-00805F9B34FB )做标准属性。
128位的UUID常常在标准服务中被省略为16位或32位。例如,设备信息服务简称是0x180A,而不是0000180A-0000-1000-... 完整列表在这儿
GATT操作,GATT有一系列的命令,用于客户端发现服务端。

  • 发现所有主服务的UUID
  • 根据UUID寻找服务
  • 根据主服务寻找次服务
  • 发现指定服务的所有特性
  • 根据UUID寻找特此那个
  • 读取指定特性的所有描述符

命令还能读(C<-S)写(C->S)特性值。
可以通过特性的UUID、或者句柄值读取值。
写入操作一般以句柄标识特性,还可选择是否从服务端返回。
当特性数据超过MTU,可以使用“长读”和“长写”操作。
GATT提供通知和指示。客户端可以从服务端请求特定特性的一个通知。服务端可以在特性值可用的时候再发给客户端(有点像订阅)。例如,温度传感器可以在每次测出温度后通知客户端。这可以防止客户端轮询服务端。
指示和通知类似,不同点是,指示需要客户端回应他收到了消息。

电池

中心设备和外围设备的耗电不同。距离信标(proximity beacons)设备可以使用1000mA的纽扣电池工作1~2年。另一方面,持续扫描这些信标的设备可以在几小时内耗光1000mA电量。

GATT参考

蓝牙5的变化

2016年6月sig发布蓝牙5,新增如下特性

  • 增加发射功率或使用编码物理层,来是范围增至4倍
  • 使用可选的符号时间减半特性,使速度翻倍
  • 通过增加ble广告数据长度,使得广播容量增至8倍
    2017年7月,sig又发布了mesh网络官方profile和规格书

参考文章
截取解释部分:
帧控制(2 bytes):
用于指示数据帧的类型,是否分片等等信息,说白了,这个字段就是记录了mac 802.11的属性。
*Protocol version:表明版本类型,现在所有帧里面这个字段都是0x00
*Type:指明数据帧类型,是管理帧,数据帧还是控制帧
*Subtype:指明数据帧的子类型,因为就算是控制帧,控制帧还分RTS帧,CTS帧,ACK帧等等,通过这个域判断出该数据帧的具体类型
*To DS/From DS:这两个数据帧表明数据包的发送方向,分四种可能情况讨论
**若数据包To DS为0,From DS为0,表明该数据包在网络主机间传输

    **若数据包To DS为0,From DS为1,表明该数据帧来自AP
    **若数据包To DS为1,From DS为0,表明该数据帧发送往AP
    **若数据包To DS为1,From DS为1,表明该数据帧是从AP发送自AP的,也就是说这个是个WDS(Wireless Distribution System)数据帧,至于什么是WDS,可以参考下这里的介绍 #传送门
*Moreflag:分片标志,若数据帧被分片了,那么这个标志为1,否则为0
*Retry:表明是否是重发的帧,若是为1,不是为0
*PowerManage:当网络主机处于省电模式时,该标志为1,否则为0.
*Moredata:当AP缓存了处于省电模式下的网络主机的数据包时,AP给该省电模式下的网络主机的数据帧中该位为1,否则为0
*Wep:加密标志,若为1表示数据内容加密,否则为0
*Order 这个表示用于PCF模式下,这里不予讨论

生存周期/Associate ID (2 bytes):
先前不是讲过虚拟载波监听的一个机制么,他的Network Allocation Vector(NAV)就存在这里,这里叫duration,即生存周期。当然不是所有时候这个字段存放的NAV值。在特定类型数据帧中,它也可能表示Associate ID。一旦有主机关联到AP了,AP都会为主机分配一个Associate ID。比如在网络主机通知AP自己要进入省电模式(power saving)的时候,网络主机发给AP的通知数据帧里面,这个域就表示的是Associate ID而不是NAV了。当然还可以通过最高位来判断这个域的含义:
*在15bit为0的时候,该域表示duration
*在15bit为1,14bit为1的时候,表示Associate ID。

序列控制(2 bytes:4 bits/12 bits):这个域分2部分,一个是分片序列号和标识帧列号。分片序列号就是记录分片序号的。比如一个帧A被分片成a1,a2,a3,那么a1,a2,a3这三个分片帧的分片序列分别是0,1,2。这个和IP分段原理一样的,该域占4个比特位。剩下的12个比特位就用于标识帧的序号,这个跟IP头里面的序列号一样。

MAC地址 1-4
这四个地址在不同帧中有不同含义。这些以后会讨论。
以后我们可能会碰到以下类型的mac地址
RA(receiver address):无线网络中,该数据帧的接收者
TA(transmitter address):无线网络中,该数据帧的发送者
BSSID(Basic Service Set ID):在infrastructure BBS中,BSSID就是AP的mac地址。但是在IBBS中,它是一个随机即生成的46位二进制序列,还有最高两位分别是Universal/Local标志位和Individual/Group标志位。IBBS的BSSID中,Universal/Local标志位为1,表示本地MAC,Individual/Group标志位为0,表示是个人MAC。也就是说在IBBS中,BSSID地址应该类如 10xxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx(x表示随机数要么0要么1, 2进制表示)
DA(destine address):该帧的目的mac地址
SA(source address):该帧的源mac地址
这里的DA和SA含义和普通以太网中的含义一样,在无线网络中可能我们需要通过AP把数据发送到其它网络内的某台主机中。但是有的人会奇怪,直接在RA中填这台主机的mac地址不就久好了么。但是请注意RA的含义,说的是无线网络中的接收者,不是网络中的接收者,也就是说这台目的主机不再无线网络范围内。在这种情况下我们的RA只是一个中转,所以需要多出一个DA字段来指明该帧的最终目的地,当然,如果有了DA那必须有SA,因为若目的主机要回应的话,SA字段是必不可少的。(假设没有SA字段,那么目的主机回应的数据包就只能发送到源主机所属的AP上了~)

URI编码有%20等这样的字符,以%来转义不适合在URI中传输的字符.
在JS中有两对编解码函数, encodeURI/decodeURI和encodeURIComponent/decodeURIComponent, 有什么区别? 参考这篇文章
举例:

//保留字符
encodeURI('?')
"?"
encodeURIComponent('?')
"%3F"
decodeURI('%3F')
"%3F"
decodeURIComponent('%3F')
"?"
//非打印字符
encodeURI(' ')
"%20"
encodeURIComponent(' ')
"%20"
decodeURI('%20')
" "
decodeURIComponent('%20')
" "
//mark字符
encodeURI('_')
"_"
encodeURIComponent('_')
"_"

"?"属于URI中特殊的字符,而空格不是,这两对编解码函数的区别就是这一类特殊字符(保留字符)要不要被编解码.
Mark字符与基本字符一样,不需要被编解码.

保留字符(reserved characters):这类字符是URI中的保留关键字符,它们用于分割URI中的各个部分。这些字符是:";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
Mark字符(mark characters):这类字符在RFC-2396中特别定义,但是没有特别说明用途,可能是和别的RFC标准相关。 这些字符是:"-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
基本字符(alphanum characters):这类字符是URI中的主体部分,它包括所有的大写字母、小写字母和数字
其他非打印字符都需要被编解码

蓝牙分为两个互不兼容的版本:经典蓝牙和低功耗蓝牙。前者又称为BR/EDR,后者又称为BLE,BLE又分为Smart和Smart Ready,前者是单向,后者是双向。
蓝牙使用2.4G~2.483.5G之间的频段,其中2.400~2.402G(2M)、2.480~2.483.5G(3.5M)为保护频段,2.402~2.480为实际使用的频段。
在经典蓝牙中,2.402~2.480这79M频段依次划分为79个信道,编号0~78(这与WIFI 2.4G的1~13编号方式不同),BLE中,则是每2M划分一个频段,共40个信道(实际上用了2.402~2.481G),编号0~39.

经典蓝牙

经典蓝牙中,BR是基本速率,为1Mbps,使用GFSK调制,EDR为2或3Mbps,分别采用π/4-DPSK和8DPSK调制。

通信上采用主从制,基本网络称为piconet,最多1主7从,扩展网络称为scatternet,实际为一个设备的双重身份,在一个网络里为从在另一个里面为主。一个piconet中,主设备定义时钟,1个tick(滴答)为312.5us,2个tick为一个slot(槽),2个slot为一个slot pair(槽对),一个包长为1,3,5个槽,主设备双号槽发送、单号槽接收(双发单收),从设备相反。
除了极少用到的广播外,主从同一时间为一对一通讯,主设备对多通讯为按RR(Round-robin)规则时分复用快速切换。从设备需要时时监听所有接收槽,所以从设备负担要比主设备重一些。一个从设备也可以同时监听多个主设备。
2.4G信号虽然没有可见的线缆,然而传输需要电磁波衍射路径的通畅。辐射功率1~4级,从100mW到0.5mW,增益从20DBm到-3DBm,典型距离从100米到0.5米。
由于class 1级一般用于工业,而一般消费蓝牙设备都是电池供电,采用的是class 2标准。
然而在蓝牙3、4、5这几个版本中,最大速度增加到25、50Mbps,最大距离增加到10~240米。
最终覆盖受到传播条件、覆盖物、产品样品多样性、天线配置、电池电量等的影响。实际空旷环境中,两个class 1设备可以通讯超过1km。

参考文章
常用的创建硬链接和软连接方式。
软连接相当于windows的快捷方式。
而硬链接则相当于指针,而源文件名也是指针,只有在指针都删除的时候,源文件才会被删除。

ln /path/to/file /path/to/hardlink
ln -s /path/to/file /path/to/link