2018-10-01 心情

Chapter 1

父母总是希望孩子好的。

父母也是爱孩子的。

人们好面子的,要面子的。

到底是谁定义了美。

爱,不应该自己才是最懂的嘛?

如果连现实问题都无法克服,那还谈什么爱情。

如果要为了减轻生活的负担,为了减少不必要的麻烦去结婚,那还谈什么爱情。

或许,我看的电视剧,和别人的不同吧~

Chapter 2

“门当户对”不过是大户人家爱用的词语。大户人家觉得自己有钱,有权有势,很得瑟。自己的孩子如果找不到差不多条件的另一半,就是给自己丢脸,给家族丢脸。

这听起来,就像古代的公主和公子,他们的婚姻都是为了皇家。他们没有权利去争取自己的感情。

可是,这么多年过去了。为什么大家还在提”门当户对”。20、30年前大家普遍都贫穷的时候,难道就不考虑”门当户对”了嘛?

那时候贫穷的年轻人,靠着自己的奋斗,成就了今天的自己。年轻时候的他们如果被另一半的家庭嫌弃,这段爱情婚姻如果被毁灭,不知道他们是否还能成为今天这样,变得出人头地。

我一直觉得,年轻人不应该把过多的时间浪费在如何处理自己家里人的问题上。如果一个人在工作的时候,整天需要担心的父母是否会和自己的另一半发生矛盾,而且这种矛盾又是完全不必要的,那即便是一个优秀的人,也会被累死。

Chapter 3

情人眼里出西施。这句话说明了:美,是每个人自己定义的!

或许某种外貌让你觉得难受,但是在其他人的眼中,或许是一个亮点。

父亲母亲的白发,是美。

残疾人努力锻炼的身影,是美。

农民伯伯压弯了的腰,是美。

年轻人为了梦想留下的汗水,也是美。

美是一种气质,一种态度,并不是上过手术台就能得到的属性。

End

我,还会继续战斗。

我,希望改变人们的观念。

我,要用理性来粉碎这些封建思想。

透明度、适应

Context

《发布!软件的设计与部署》这本书终于读完了,不过我感觉我现在的水平还有很多内容没有理解。所以这本书,我总还要找机会搞来的!

Chapter

这剩下的两节都是第四部分:运营 的内容,帮助

透明度

透明性是指这样一种特性,它能让操作员、开发者和商业赞助商理解系统的历史趋势、当前状态、瞬间状态和未来设想

如果系统的透明性很差,那么即便我们发现系统出现了异常或者故障,也很难快速的找到问题的根源。按照我的猜想,透明性大概就是在系统的各种关键点(继承点)增加控制接口、log记录等。因为在遇到问题的时候,知道的信息越多,定位问题越方便;组件提供的接口越多,操作起来越方便。

想一下,如果好不容易发现了问题,结果这个问题的解决方法只能通过修改系统代码(更新软件)来解决,那实在是太痛苦了;如果一个系统中很多的操作都是根据配置文件来确定的,那只要修改了配置文件,并重启服务(软件),那么问题就解决了,岂不是爽歪歪。

没有透明性的系统不能在生产中长期存在。如果管理员不知道它正在做什么,就不能调整和优化。如果开发者不知道在生产环境中什么能行什么不行,就不能对它进行增强和更新

这一章从4个层次解释了透明性:历史趋势、未来预测、当前状态、瞬间行为。

历史趋势很有意思,作为一个初等程序员,对于历史问题,我可能只会关心什么时候产生错误警告,错误警告产生的原因,触发条件这些东西。而资深架构师运营经理总是可以问一些小白、普通程序员不会想到的问题。比如:我们昨天有多少订单;第一季度我们使用了多少磁盘空间;过去三年顾客流量的增加和CPU使用率增加相比的结果~

当我们觉得没有看到警告,没有看到错误就心满意足的时候,他们总是可以想的更加长远(现在我也可以想的更长远啦~)

当前状态简单说,就是要给出足够的数据来反映系统运行的状态,产生了多少报警,多少异常,内存用了多少,还剩多少,线程数量,可用线程、繁忙线程、等等等等。不能让系统运行的像个薛定谔的猫一样。说到这里,作者也提到了日志的规范性,不能说有了日志就大功告成了,日志要分级,要有良好的格式,让人阅读的方便。不然几万几万的日志,即使知道关键内容在里面,也会让人失去探索的兴趣。

适应

适应性差不多就是兼容性的意思。系统在不断的被完善,老用户如何适应新系统,新系统如何适应老用户。开发人员如何用更低的成本去维护系统,去更新系统。这就是这一章的主要内容。

依赖注入,对象设计,极限编程实践,敏捷数据库,这是适应性软件设计的四个方法。简单的说他们都在鼓励松耦合,高内聚,提高向下兼容的能力。

发布应无害 讲到了’部署成本太高零停机时间部署`和其他的几点。这两点单独拿出来说,是因为,颠覆了我的认知~

本以为部署一个系统多容易啊,改了新的代码,重启服务即可,有什么成本呢?实际上专业的团队在部署的时候,需要创建发布分支,发布说明文档,与营销沟通,计划执行验证部署,后续的技术支持。这些都是人力成本~

平时看到停机维护的情况比较多,所以对零停机时间部署这个概念还是比较好奇的。说的是,部署行版本,可以一步一步慢慢来,按阶段分解部署,不是立刻添加改变删除一些东西。增加各种兼容性。

Addition

上一篇中没有说到 快速失效 ,在第18章《适应》中讲到了一个例子,让我猛的想起了这个有用的设计~

比如,一个请求,我们其实早就知道,这个操作可能要出现错误了,却还是让请求做到了出现异常的时候,才反馈。就相当于客户白等了很久得知,自己想做的事情不能被达成。所以快速失效可以相当于做一些中间件吧~提前做一些处理,早就知道要失败了,就直接返回,不再继续执行操作。

End

这本书终于读完了,我肯定还会再搞来的!现在觉得一些书看不懂,没意思,说不定并不是书的问题,而是自己段位太低了~哈哈

(每次写一篇博客,都会晚睡一次~)

第一篇读书笔记

Context

这是第一篇读书笔记,最近看了几本有点厉害的书。我担心我看了之后又忘记了,所以还是有必要记录一下的。

Note

这本书是一个很多年工作经验的架构师大神根据自己的各种亲身经历总结出来的一些东西。总共分为4个部分:稳定性、容量、一般设计问题、运营。

目前呢,我才读到了178页,差不多是第四部分运营的一半位置,所以这本书终于将要被我读完了!

这本书读的时间很长,因为他不是一本工具书,它是一本和小说一样的书,通过各种故事来引出问题和解决方案。有时候一些情况是我现在无法理解的,因为我还没有接触过作者大大所做的那种超高并发,大用户,等复杂场景。也有可能是因为作者大大做的系统都是从最最最底层开始一直垒到最顶层的,而我现在都是使用各种框架,把最难的部分或者最可能出问题的部分交给了其他大佬们去解决了。
其实这个并不是最好的习惯,因为在书的P35就提到了一个点厂商API库,这里说有时候我们使用的第三方API被封装的特别漂亮,但是其实他的实现非常丑陋,甚至存在很多的缺陷~但是其实我们这些小角色现在也只能抱怨抱怨,因为并没有能力和时间去修改它~~

Chapters

讲一下这本书的四个部分都是什么具体内容吧~

目录其实可以在网上查到,所以我也不说了。但是这四个部分讲的内容大概是什么我倒可以稍微概括一下,因为说不定未来遇到了问题,可以很针对性的去找对应的章节。

注意,下面被这样标记的部分就是书中原话的意思~


第一章:稳定性

这一章介绍了稳定性反模式和稳定性模式,其中稳定性反模式指的是一些设计会导致导致没考虑到的小问题被方法,导致系统快速的崩溃。而使用稳定性模式,就可以降低这种崩溃的风险。

在介绍稳定性的时候讲到了故障链,这其实很好理解,一个很小的故障,导致了另一个未知故障的发生,进而导致另另一个未知故障的发生,进而满满的发现所有的模块都爆了。就像什么火箭飞船飞机什么的,一开始发现一个很小的异常,然后慢慢地慢慢地,全屏都是error然后爆炸了~

这一章讲到了一个凌晨5点崩溃的故事,说的是一个服务器每天凌晨5点都会死掉。我相信这套系统在测试阶段应该没有出现过这种问题,但是在生产服务器中,却出现了这个崩溃问题。作者通过获取故障服务器的线程转储(我并不知道这是什么),tcpdump抓包,分析得到这个崩溃是由TCP链接的不对称导致的。
TCP如果双方都不通知对方,这个链接中断了,那么即使物理线路真的断了,那他们也以为没有断。而在生产服务器中,又一个防火墙角色的存在,导致情况变得更加复杂。

防火墙检查到外部节点超过若干时间没有和本机通信了,便会移除防火墙里面的记录,导致后续外部节点再次通信产生失败。这这个例子中,防火墙保护了数据库服务器,而链接数据库服务器的应用服务器,由于过了一个晚上的休息时间,使得防火墙中对应的记录都被删除了,所以应用服务器再也无法跟数据库服务器进行通信了,于是这些线程全部被阻塞了,最终系统崩溃了。

虽然这个故事讲的并不是生产环境和测试环境的事情,但是我觉得这个故事我对他印象还是很深刻的。

之后说了连锁反应, 假设分布式服务器集群有若干台,他们的故障概率为x,那么当某一台服务器挂了之后,剩下的负载服务器的故障概率就会升高,因为他们分担了之前服务器的负载。以此类推,当更多的服务器挂掉之后,剩下的服务器的故障概率会变得高的可怕,说不定就会导致系统崩溃。

用户是可怕的每个用户都好用了更多的内存,这应该不需要再解释了。

无共享,在软件设计的时候我还是挺喜欢使用单例的,因为这是一个全局的东西,可以被各个模块共享使用。但是在系统架构时,为了可扩展性,会希望无共享架构。但是无共享会导致失效备援能力变弱。所以大佬的建议是:减少共享资源。我理解就是除了那些必不可少的资源需要共享,其他的资源可以通过让两个应用服务器彼此做备援服务器的方法来实现援救。

去耦合中间件这个不好解释,总是中间件很有用就对了,’通过完全姐耦合来避免大部分失效模式’

这一章写了那么多,因为他竟然有94页!所以看得我心力憔悴。


第二章:容量

这一章看名字就大概明白意思了。各种暴力环境呗。大用户,高并发,大容量储存,之类的…

写几个印象深刻的点:

容量的神话: 1.”CPU很便宜”。但是机箱很贵啊!4个CPU用一个机箱,那5个CPU可能就需要2个机箱,此时每个CPU的成本就被抬高了啊。2.”存储很便宜”。但是服务器不止一台,而且需要考虑RAID的影响,操作系统大小,应用程序大小等等。所以购买的容量远远小于可以储存数据的容量。根据乘法效应来算,这储存还是挺贵的。3.”带宽很便宜”。现在一般两种带宽服务,按时间计费,固定带宽不限流量,每月支付固定的金额;按流量支付,每月流量小于一个阈值,就可以支付稍低的固定费用,但是如果流量超出则会付出惨痛的代价。于是讨厌的爬虫脚本,或者服务器中传输垃圾信息(每次1kb,累计起来也是钱啊)都会导致带宽变得很昂贵。

JSON的黑暗面这个其实不难理解,因为有的程序员会使用json来执行函数或者命令,这个操作是很有风险的,万一个黑客猜到了,就完蛋了。

昂贵的空白图片,多余的HTML表格,刷新按钮,空白->这些都是HTML中浪费的空间,他们虽然不会影响什么,但是,会浪费很多的流量(都是钱啊!)

‘海外来的RMI’, 这个故事我还没看懂~。~


第三章:一般设计问题

虚拟IP地址,使用虚拟IP地址,可以用作失效备援。一堆失效备援服务器一个作为主动运行服务器,另一台作为备援服务器,他们有自己真实的IP,也会有一个虚拟IP。但是虚拟IP只会指向正在运作的主要服务器,当主要服务器故障之后,便通过将备用服务器迁移到这个虚拟IP上来保证服务可用。


第四章:运营

这一章还没看完,不着急。


注意,上面被这样标记的部分就是书中原话的意思~

End

嗯,这是看了那么多一口气写的笔记,所以花了我2小时的时间,下次读书笔记还是一边读一遍写吧!

BLEoE(1)

Context

蓝牙的传输距离实在是不够长,对于我这种调试人员来说,如果可以有一种方法,远程的调试蓝牙外设,那岂不是超级爽歪歪嘛?

所以某一天洗澡的时候,我就打算使用网络来传输蓝牙数据~

借鉴OSI分层或者网络分层的结构,我觉得只要对BLE传输的数据做一层封装,再通过tcp来传输就可以解决这个问题。

Let’s do it

所以这个系统需要至少三个东西,一台作为central的硬件,一台作为peripheral的硬件,以及一个将他们连在一起的服务器。

由于我手上恰恰恰恰好有两台Linkit One物联网开发平台,而且它本身就拥有蓝牙,Wi-Fi的功能,所以central和peripheral肯定就用这两个硬件来试验了~

服务器的话,测试阶段也可以很简单,只要简简单单的将两方的数据做一个交换就好了。再这个阶段并不需要安全认证,多用户绑定,数据正确性检查,数据缓存等等功能,
只要交换就好!

因为python写的贼熟悉了,所以当然还是搞一个简单的python socket服务器比较好。

这个项目开个源吧

Coding.Net : https://git.coding.net/vikingwarlock/BLEoE.git

Github: https://github.com/VikingWarlock/BLEoE.git

两个进度会不一样,因为github上面还是不要留太多的草稿吧

TCP or UDP

嗯,这个问题可能会比较有争议。一开始我是打算使用udp的,因为会比较快,我担心未来网络延迟或者服务器性能不佳会导致数据包在网络上耽误较长的时间,导致蓝牙操作超时。
可是udp很不可靠,万一出事情了,反而得不偿失。所以先使用tcp来测试测试。

先贴一下我简简单单搞得2用户数据交换的tcp服务器代码吧

import asyncore
import socket

handler_list = {}


class EchoHandler(asyncore.dispatcher_with_send):

    def configure_local_address(self, address):
        self.address = address

    def configure_target_address(self, address):
        self.target_address = address

    def handle_read(self):
        data = self.recv(8192)
        if data and self.target_address:
            hd = handler_list.get(self.target_address)
            if hd:
                hd.send(data)
            else:
                self.target_address = None
                self.send("fail to send, target is down")
        else:
            self.send("no target")

    def handle_close(self):
        handler_list[self.address] = None
        self.close()
        print 'connection from %s lost' % repr(self.addr)


class EchoServer(asyncore.dispatcher):

    def __init__(self, host, port):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind((host, port))
        self.listen(5)

    def handle_accept(self):
        pair = self.accept()
        if pair is not None:
            sock, addr = pair
            print 'Incoming connection from %s' % repr(addr)
            handler = EchoHandler(sock)
            handler.configure_local_address(addr)
            handler_list[addr] = handler
            if len(handler_list) > 1:
                it1 = handler_list.values()[0]
                it2 = handler_list.values()[1]
                it1.configure_target_address(it2.address)
                it2.configure_target_address(it1.address)

    def handle_close(self):
        self.close()


server = EchoServer('0.0.0.0', 3344)
asyncore.loop()

曲线拟合

Context

有个叫做应变片的东西,每个应变片的物理特性都不同,所以收到相同的力时,不同的应变片得到的信号可能不太一样。

所以,每个应变片都需要做一些配置。

所以,我们需要一个程序来收集数据。

比如我们用一组标准力来测量信号,就可以获得到一组数据对:(N1,Sig1),(N2,Sig2)…

已知: 这是N和Sig是线性关系,所以这个要拟合的不是曲线,是条直线~

Math

是时候,上主题了。

凭我模模糊糊的记忆,我感觉好像是,最优化的内容。好像是,牛顿法?

所以我就去查了牛顿逼近法,查了半个下午,才发现这个是求方程根的~

后来模模糊糊的想起来,好像是线性规划,只要求Yi和F(Xi)的差的和的最小值,应该就是最接近的解了~

正在这个时候,我突然查查看Apple有没有做什么牛逼的函数给我~

Accelerate

果然Apple是有这么一个库的,叫做Accelerate,专门用来算各种各样的高等数学。

一开始看到了一个Sparse Solvers是个解方程的东西,于是就列了KX=B的一个矩阵式,使用accelerate提供的解方程函数来计算结果,结果是错的~

因为,直接解方程如果可以解出来,那不就是一个固定的k和b了嘛?然而我是要估计一个k和b,所以肯定不是直接解方程的。

Least squares

最小二乘法,终于让我想起来了这个东西,毕竟是大一时候微积分学的,思考太久很正常。

设Zi=(Yi-F(Xi))^2,将所有的Zi加在一起,这个值如果是最小的,那就意味着估计的曲线和误差是最小的。要使得这个值是最小的,那就可以求所有未知参数的偏导,并使得每个偏导的方程都等于0。

再经过数学推导得到了y=ax+b通过最小二乘法得到a和b的估计值的表达式。

由于我懒得用数学公式编辑器,也懒得截图,所以表达式用纯文字表达。

n:样本个数
X:Xi的和
Y:Yi的和
XX:Xi平方的和
XY:XiYi的和

a=(n*XY-X*Y)/(n*XX-X*X)
b=(Y-X*a)/n

这里还是用了accelerate的函数,做了矩阵的乘法来计算XiYi的和

void vDSP_mmul(const float *__A, vDSP_Stride __IA, const float *__B, vDSP_Stride __IB, float *__C, vDSP_Stride __IC, vDSP_Length __M, vDSP_Length __N, vDSP_Length __P);

虽然这里可以用另一个函数,就是向量的乘法,但是我一开始写的时候,没找到这个函数(文档都是英文的,还是英文版的高数,看着费力啊~)就是下面这个函数:

void vDSP_vsmul(const float *__A, vDSP_Stride __IA, const float *__B, float *__C, vDSP_Stride __IC, vDSP_Length __N);

Function

啰嗦了半天贴个代码吧。

void calculateEquation(double*x,double*y,double* key){
    double xy[1];
    double sum_x=0,sum_y=0,sum_xx=0; //这里就对应了上面说的 X,Y,XX,XY
    int n=7;
    vDSP_mmulD(x, 1, y, 1, xy, 1, 1, 1, n);
    for (int i=0; i<n; i++) {
        sum_x+=x[i];
        sum_xx+=x[i]*x[i];
        sum_y+=y[i];
    }
    key[0]=(n*xy[0]-sum_x*sum_y)/(n*sum_xx-sum_x*sum_x);  //k
    key[1]=(sum_y-key[0]*sum_x)/n;  //b
    NSLog(@"y=%lfx+%lf",key[0],key[1]);
}

写的不一定好,但是应该是对的~

Test

作为一个严谨的人,我还是要做点测试的。在网上找了两组数据做了单元测试,因为浮点型,所以可能最后几位数字不一样,就会导致单元测试判断错误。所以得,做点儿近似值~

- (void)testExample {
    double xS[]={0,1,2,3,4,5,6};
    double yS[]={1,2,3,4,5,6,7};
    double k[2];
    calculateEquation(xS,yS,k);
    XCTAssertEqual(k[0], 1.0);
    XCTAssertEqual(k[1], 1.0);
}

-(void)testExample2{
    double xS[]={34.1, 34.137, 34.174, 34.211, 34.248, 34.285, 34.322};
    double yS[]={74.4, 74.40321739130435, 74.4064347826087, 74.40965217391305, 74.41286956521739, 74.41608695652174, 74.41930434782608};
    double k[2];
    calculateEquation(xS,yS,k);
    XCTAssertEqual(round(k[0]*100000), round(0.08695652173913038*100000));
    XCTAssertEqual(round(k[1]*100000), round(71.43478260869566*100000));
}

-(void)testExample3{
    double xS[]={-1.5, -1.38, -1.26, -1.14, -1.02, -0.9, -0.78};
    double yS[]={57.30000000000001, 57.34640000000001, 57.39280000000001, 57.43920000000001, 57.48560000000001, 57.53200000000001, 57.57840000000001};
    double k[2];
    calculateEquation(xS,yS,k);
    XCTAssertEqual(round(k[0]*100000), round(0.38666666666666627*100000));
    XCTAssertEqual(round(k[1]*100000), round(57.88000000000001*100000));
}

End

数学,还是很有用的!

微信小程序蓝牙坑(1)

一时兴起,就想做一个微信小程序了。

花了点时间,决定还是做一个和蓝牙相关的。

看了微信小程序的蓝牙文档,发现还是好理解的。

流程

1 开启蓝牙适配器

wx.openBluetoothAdapter(OBJECT)

这个说法和Android是一样的,在Android中也是叫蓝牙适配器的,iOS中是CBCentralManager。

2 搜索蓝牙外设

wx.startBluetoothDevicesDiscovery(OBJECT)

从OBJECT的参数可以知道,程序员们还是挺辛苦的~

为了在Android和iOS上面有统一的接口,自己特地封装了一层。

比如allowDuplicatesKey就是iOS特有的

interval是Android后期有的

3 连接蓝牙外设

wx.createBLEConnection(OBJECT)

不容易啊,这应该是以iOS为原型设计的接口~

iOS中是让CentralManager来连接外设的,Android中是BluetoothDevice自己提供了连接的方法~

4 发现服务

wx.getBLEDeviceServices(OBJECT)

5 获取特征

wx.getBLEDeviceCharacteristics(OBJECT)

6 设置notify

wx.notifyBLECharacteristicValueChange(OBJECT)

这里文档中只说了两个tip,也不知道快速订阅多个characteristic有没有做特别的优化,至少写原生Android的时候,订阅多个characteristic并不是那么方便~因为之前的文章里面说了,系统会阻塞~

7 读读写写以及notify的回调

wx.readBLECharacteristicValue(OBJECT)

wx.writeBLECharacteristicValue(OBJECT)

wx.onBLECharacteristicValueChange(CALLBACK)

看函数名就能理解,这里最难过的就是字节流操作了~

坑儿

不知道是不是我手机的问题,蓝牙连接需要特别长的时间~

auto connect,是没有的~如果在连接外设那里不加上timeout的话,他会一直在那儿连接,而且如果连不上,就会一直连不上~

不要想当然的以为只要填写对了characteristicID和serviceID就可以直接读写收,还是要搜索到才行的!

青海

先上图,回去在写文~

为了节省流量,这里放的是很小的缩略图

但是点击小图,可以看原图










HID还是USB

Context

大三时候做了精益防伪的第一代防伪芯片的Android版测试读写器。如今这个公司发展的有点儿好,所以,
他们出了第二代芯片,增加了更多安全措施。

为了对第二代芯片进行生产测试,我们需要做一个全新的测试软件。那么为了与新一代的RFID芯片进行通信,我们需要写一个底层驱动,来使得外接的USB RFID读写器工作。

这个测试上位机是工作在windows上的,对于一个有强迫症的开发者来说,当时不会选择使用有跨平台特性的java来开发。

我们要了上一代芯片的测试程序,是用C++做的通信和界面(MFC)。然而用C++写界面,肯定是很费劲的。所以我们换个思路。充分利用windows上面可以调用各种dll的特点来解决c++写界面要累死这种尴尬的情况。

这就会引出第一个坑(逐渐远离标题)

C#与C++混合编程

C++有多难写,应该不用再多说了,我就是那种不到万不得己,是绝对不会去碰C++的垃圾~所以得到了上一版测试程序后,我们看到了之前的RFID读写器驱动代码,将它进行生成,得到了一个dll。

在这个基础上,再封装一个wpf可以使用的库,那简直完美。

于是就直接新建一个C#的库项目,导入之前的底层项目,引入lib文件,(再花了很多精力解决了一些环境路径之类的问题),终于可以成功生成,在新建的wpf项目中添加引用:

错误:不是一个有效的COM组件!?

于是查了这个错误报告,有的说得用CLR库,就是公共语言运行库,想想好像也有道理。于是又如法炮制了一遍,终于可以成功生成了,再次添加引用:

错误:不兼容!?

于是又去查了一些资料,看到C#和C++混合编程,可以使用DllImport来导入C++ Dll中的函数,不过问题来了,这里在使用DllImport的时候,是需要做一些类型转换的。

毕竟.NET里面的一些数据类型,在C++中是没有的,所以从原理上来想,如果不做一些类型转换或者类型的重新定义的话,操作系统会很迷惑的~

所以,我们就放弃了使用原始C++库这个思路,准备撩起袖子自己写了。(逐渐想起标题)

USB

既然是重新开始,那就要开始疯狂的查询资料了。之前写过windows的串口通信。所以在我的想象中,他们应该是差不多的吧,把USB口子打开,就得到了input和output的流,之后就可以开开兴兴的进行通信了。

不过,并不是这样的~

作为一个懒惰的程序员,我们下一步想到的,就是找开源库。因为这个项目的时间有点紧张,所以怎么搞得快就得怎么做!

LibUsbDotNet

首先我们找到了LibUsbDotNet,仔细研究了他的一些接口,写了一套链接,发送的方法。

USB里面有一些末端(EndPoint)描述,这些末端的ID的第一个bit代表着这个末端的通信方向。如果第一个bit为1,则代表input,如果这个bit为0,则代表output。

public bool connectDevice() {

    UsbRegDeviceList registryList= UsbDevice.AllDevices;

    for (int index = 0; index < registryList.Count; index++) {
        UsbRegistry registy = registryList[index];
        if (registy.SymbolicName.Contains("5750") && registy.SymbolicName.Contains("0483"))
        {
            bool connect_status= registy.Open(out this.device);
            if (connect_status) {

                foreach (UsbConfigInfo info in this.device.Configs){
                    foreach (UsbInterfaceInfo interfaceInfo in info.InterfaceInfoList) {
                        foreach (UsbEndpointInfo endPoint in interfaceInfo.EndpointInfoList) {
                            System.Console.WriteLine(" id:" + endPoint.Descriptor.EndpointID);
                            if ((endPoint.Descriptor.EndpointID & 0x80) == 0x80)
                            {
                                //in
                                this.reader = this.device.OpenEndpointReader((ReadEndpointID)endPoint.Descriptor.EndpointID);
                                continue;
                            }
                            else {
                                //out
                                this.writer = this.device.OpenEndpointWriter((WriteEndpointID)endPoint.Descriptor.EndpointID);
                                continue;
                            }
                        }

            }

        }

        if (this.writer != null && this.reader != null)
        {
            return true;
        }
        else {
             return false;
        }
        }
        }
    }
    return false;
}

缩进问题请谅解,垃圾visual studio

在学习使用lib-usb的时候,我们了解到了pid和vid。就和串口一样有com1,com2等。上位机(操作系统)通过pid和vid来过滤插入的硬件。

但是,我们发现使用lib usb的工作原理貌似不是这样的。感觉他会建立虚拟硬件,虚拟硬件建立完毕后,他的pid和vid都和原来的不一样了,但是好在他的guid里面还是包含了原始的pid和vid,所以采用了上面代码中的方法来过滤来设备。

有了writer和reader之后,对他们进行字节流操作,就ok了。

但是,不行。

还需要设置读入写出的handler,也就是说每一个I/O操作都强制需要有回调函数,否则不会成功调用。

而且当青青成功的使用lib-usb之后,发现做一次写入操作之后,整个软件就卡住了。连问题都没有找到~

Spidey大佬跟我说,usb是仅仅个介质,并不是一种通信。就好比说Wi-Fi是一种介质,真正通信的对象是服务器或者主机

Hid

HID也叫做人体输入学设备,打开windows的设备管理器,插上鼠标键盘就会显示一个人体输入学设备。

一开始用了一个叫HidLibrary的库,没成功,所以也没有做什么记录。

之后我们恍然大悟!一开始就不应该去学习USB开发,而应该去学习HID的开发!

HID的资料就比较多了~

接下来的内容转自于一位前辈大佬,很多的博文也是转载于他的文章。

Windows中对HID的操作比较好理解,和操作系统处理多线程服务器差不多,每个连接都用一个文件来维持。

我对他的代码做了修改,原来版本是异步的发送和接收,这样的做法很高效而且也很健康,只是我们这次的项目似乎用不到这么高级的方法。所以我将它改为了同步发送和同步接收。

这个项目是由HID类和HIDInterface类构成

现在还是直接贴代码吧

HID类:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.IO;
using Microsoft.Win32.SafeHandles;
using System.Windows;


namespace ReaderCSharp
{
        public class Hid : object
        {
            private IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
            private const int MAX_USB_DEVICES = 64;
            private bool deviceOpened = false;
            private FileStream hidDevice = null;
            private IntPtr hHubDevice;


        /// <summary>
        /// 打开指定信息的设备
        /// </summary>
        /// <param name="vID">设备的vID</param>
        /// <param name="pID">设备的pID</param>
        /// <param name="serial">设备的serial</param>
        /// <returns></returns>
        public HID_RETURN OpenDevice(UInt16 vID, UInt16 pID, string serial)
            {
                if (deviceOpened == false)
                {
                //获取连接的HID列表
                    List<string> deviceList = new List<string>();
                    GetHidDeviceList(ref deviceList);
                    if (deviceList.Count == 0)
                        return HID_RETURN.NO_DEVICE_CONECTED;
                    for (int i = 0; i < deviceList.Count; i++)
                    {
                    if (!(deviceList[i].Contains(String.Format("{0:X0000}", vID)) && deviceList[i].Contains(String.Format("{0:X0000}", pID)))) {
                        continue;
                    }

                        IntPtr device = CreateFile(deviceList[i],
                                                    0xc0000000,//DESIREDACCESS.GENERIC_READ | DESIREDACCESS.GENERIC_WRITE,
                                                    0x00000003,
                                                    0,
                                                    3,//CREATIONDISPOSITION.OPEN_EXISTING,
                                                    0x40000000,//FLAGSANDATTRIBUTES.FILE_FLAG_OVERLAPPED,
                                                    0);
                    if (device != INVALID_HANDLE_VALUE)
                    {
                        HIDD_ATTRIBUTES attributes;
                        IntPtr serialBuff = Marshal.AllocHGlobal(512);
                        HidD_GetAttributes(device, out attributes);
                        HidD_GetSerialNumberString(device, serialBuff, 512);
                        string deviceStr = Marshal.PtrToStringAuto(serialBuff);
                        Marshal.FreeHGlobal(serialBuff);
                        if (attributes.VendorID == vID && attributes.ProductID == pID && deviceStr.Contains(serial))
                        {
                            IntPtr preparseData;
                            HIDP_CAPS caps;
                            HidD_GetPreparsedData(device, out preparseData);
                            HidP_GetCaps(preparseData, out caps);
                            HidD_FreePreparsedData(preparseData);
                            //input length ==64
                            hidDevice = new FileStream(new SafeFileHandle(device, false), FileAccess.ReadWrite, 65, true);
                            deviceOpened = true;
                            //BeginAsyncRead();

                            hHubDevice = device;
                            return HID_RETURN.SUCCESS;
                        }
                    }
                    }
                    return HID_RETURN.DEVICE_NOT_FIND;
                }
                else
                    return HID_RETURN.DEVICE_OPENED;
            }

            /// <summary>
            /// 关闭打开的设备
            /// </summary>
            public void CloseDevice()
            {
                if (deviceOpened == true)
                {
                    deviceOpened = false;
                try
                {
                    hidDevice.Close();
                }
                catch (Exception e) {

                    System.Console.WriteLine(e.Message);
                    System.Console.WriteLine(e.StackTrace);

                }
            }
            }

            /// <summary>
            /// 开始一次异步读
            /// </summary>
            public void ReadCMD()
            {
            byte[] inputBuff = new byte[65];
            int offset = 0;
            int total_size = 0;
            int read_size = 0;
            try
            {
                while (total_size < 65&&(read_size=hidDevice.Read(inputBuff, offset, 65 - offset)) > 0)
                {
                    total_size += read_size;
                }
                if (total_size >= 65) {
                    OnDataReceived(inputBuff);
                }
            }
            catch {
                EventArgs ex = new EventArgs();
                OnDeviceRemoved(ex);//发出设备移除消息
                CloseDevice();
            }
        }


            public delegate void DelegateDataReceived(object sender, byte[] e);
            //public event EventHandler<ConnectEventArg> StatusConnected;

            public DelegateDataReceived DataReceived;

            /// <summary>
            /// 事件:数据到达,处理此事件以接收输入数据
            /// </summary>
            protected virtual void OnDataReceived(byte[] e)
            {
                if (DataReceived != null) DataReceived(this, e);
            }

            /// <summary>
            /// 事件:设备断开
            /// </summary>

            public delegate void DelegateStatusConnected(object sender, EventArgs e);
            public DelegateStatusConnected DeviceRemoved;
            protected virtual void OnDeviceRemoved(EventArgs e)
            {
                if (DeviceRemoved != null) DeviceRemoved(this, e);
            }

            /// <summary>
            ///
            /// </summary>
            /// <param name="buffer"></param>
            /// <returns></returns>
            public HID_RETURN Write(byte[] r)
            {
                if (deviceOpened)
                {
                if (!hidDevice.CanWrite) {
                    return HID_RETURN.WRITE_FAILD;
                }
                try
                {
                    byte[] buffer = new byte[65];
                    buffer[0] = 0;
                    for (int i = 0; i < 64; i++)
                        buffer[i+1] = r[i];

                    hidDevice.Write(buffer, 0, 65);

                    return HID_RETURN.SUCCESS;
                }
                catch (IOException exception)
                {
                    System.Console.WriteLine(exception.Message);
                    System.Console.WriteLine(exception.StackTrace.ToString());

                    EventArgs ex = new EventArgs();
                    OnDeviceRemoved(ex);//发出设备移除消息
                    CloseDevice();
                    return HID_RETURN.NO_DEVICE_CONECTED;
                }

                }
                return HID_RETURN.WRITE_FAILD;
            }


            /// <summary>
            /// 获取所有连接的hid的设备路径
            /// </summary>
            /// <returns>包含每个设备路径的字符串数组</returns>
            public static void GetHidDeviceList(ref List<string> deviceList)
            {
                Guid hUSB = Guid.Empty;
                uint index = 0;

                deviceList.Clear();
                // 取得hid设备全局id
                HidD_GetHidGuid(ref hUSB);
                //取得一个包含所有HID接口信息集合的句柄
                IntPtr hidInfoSet = SetupDiGetClassDevs(ref hUSB, 0, IntPtr.Zero, DIGCF.DIGCF_PRESENT | DIGCF.DIGCF_DEVICEINTERFACE);
                if (hidInfoSet != IntPtr.Zero)
                {
                    SP_DEVICE_INTERFACE_DATA interfaceInfo = new SP_DEVICE_INTERFACE_DATA();
                    interfaceInfo.cbSize = Marshal.SizeOf(interfaceInfo);
                    //查询集合中每一个接口
                    for (index = 0; index < MAX_USB_DEVICES; index++)
                    {
                        //得到第index个接口信息
                        if (SetupDiEnumDeviceInterfaces(hidInfoSet, IntPtr.Zero, ref hUSB, index, ref interfaceInfo))
                        {
                            int buffsize = 0;
                            // 取得接口详细信息:第一次读取错误,但可以取得信息缓冲区的大小
                            SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null);
                            //构建接收缓冲
                            IntPtr pDetail = Marshal.AllocHGlobal(buffsize);
                            SP_DEVICE_INTERFACE_DETAIL_DATA detail = new SP_DEVICE_INTERFACE_DETAIL_DATA();
                            detail.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA));
                            Marshal.StructureToPtr(detail, pDetail, false);
                            if (SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, pDetail, buffsize, ref buffsize, null))
                            {
                                deviceList.Add(Marshal.PtrToStringAuto((IntPtr)((int)pDetail + 4)));
                            }
                            Marshal.FreeHGlobal(pDetail);
                        }
                    }
                }
                SetupDiDestroyDeviceInfoList(hidInfoSet);
                //return deviceList.ToArray();
            }

            #region<连接USB返回的结构体信息>
            /// <summary>
            /// 连接USB返回的结构体信息
            /// </summary>
            public enum HID_RETURN
            {
                SUCCESS = 0,
                NO_DEVICE_CONECTED,
                DEVICE_NOT_FIND,
                DEVICE_OPENED,
                WRITE_FAILD,
                READ_FAILD

            }
            #endregion


            // 以下是调用windows的API的函数
            /// <summary>
            /// The HidD_GetHidGuid routine returns the device interface GUID for HIDClass devices.
            /// </summary>
            /// <param name="HidGuid">a caller-allocated GUID buffer that the routine uses to return the device interface GUID for HIDClass devices.</param>
            [DllImport("hid.dll")]
            private static extern void HidD_GetHidGuid(ref Guid HidGuid);

            /// <summary>
            /// The SetupDiGetClassDevs function returns a handle to a device information set that contains requested device information elements for a local machine.
            /// </summary>
            /// <param name="ClassGuid">GUID for a device setup class or a device interface class. </param>
            /// <param name="Enumerator">A pointer to a NULL-terminated string that supplies the name of a PnP enumerator or a PnP device instance identifier. </param>
            /// <param name="HwndParent">A handle of the top-level window to be used for a user interface</param>
            /// <param name="Flags">A variable  that specifies control options that filter the device information elements that are added to the device information set. </param>
            /// <returns>a handle to a device information set </returns>
            [DllImport("setupapi.dll", SetLastError = true)]
            private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, uint Enumerator, IntPtr HwndParent, DIGCF Flags);

            /// <summary>
            /// The SetupDiDestroyDeviceInfoList function deletes a device information set and frees all associated memory.
            /// </summary>
            /// <param name="DeviceInfoSet">A handle to the device information set to delete.</param>
            /// <returns>returns TRUE if it is successful. Otherwise, it returns FALSE </returns>
            [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern Boolean SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet);

            /// <summary>
            /// The SetupDiEnumDeviceInterfaces function enumerates the device interfaces that are contained in a device information set.
            /// </summary>
            /// <param name="deviceInfoSet">A pointer to a device information set that contains the device interfaces for which to return information</param>
            /// <param name="deviceInfoData">A pointer to an SP_DEVINFO_DATA structure that specifies a device information element in DeviceInfoSet</param>
            /// <param name="interfaceClassGuid">a GUID that specifies the device interface class for the requested interface</param>
            /// <param name="memberIndex">A zero-based index into the list of interfaces in the device information set</param>
            /// <param name="deviceInterfaceData">a caller-allocated buffer that contains a completed SP_DEVICE_INTERFACE_DATA structure that identifies an interface that meets the search parameters</param>
            /// <returns></returns>
            [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr deviceInfoSet, IntPtr deviceInfoData, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);

            /// <summary>
            /// The SetupDiGetDeviceInterfaceDetail function returns details about a device interface.
            /// </summary>
            /// <param name="deviceInfoSet">A pointer to the device information set that contains the interface for which to retrieve details</param>
            /// <param name="deviceInterfaceData">A pointer to an SP_DEVICE_INTERFACE_DATA structure that specifies the interface in DeviceInfoSet for which to retrieve details</param>
            /// <param name="deviceInterfaceDetailData">A pointer to an SP_DEVICE_INTERFACE_DETAIL_DATA structure to receive information about the specified interface</param>
            /// <param name="deviceInterfaceDetailDataSize">The size of the DeviceInterfaceDetailData buffer</param>
            /// <param name="requiredSize">A pointer to a variable that receives the required size of the DeviceInterfaceDetailData buffer</param>
            /// <param name="deviceInfoData">A pointer buffer to receive information about the device that supports the requested interface</param>
            /// <returns></returns>
            [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
            private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr deviceInfoSet, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, IntPtr deviceInterfaceDetailData, int deviceInterfaceDetailDataSize, ref int requiredSize, SP_DEVINFO_DATA deviceInfoData);

            /// <summary>
            /// The HidD_GetAttributes routine returns the attributes of a specified top-level collection.
            /// </summary>
            /// <param name="HidDeviceObject">Specifies an open handle to a top-level collection</param>
            /// <param name="Attributes">a caller-allocated HIDD_ATTRIBUTES structure that returns the attributes of the collection specified by HidDeviceObject</param>
            /// <returns></returns>
            [DllImport("hid.dll")]
            private static extern Boolean HidD_GetAttributes(IntPtr hidDeviceObject, out HIDD_ATTRIBUTES attributes);
            /// <summary>
            /// The HidD_GetSerialNumberString routine returns the embedded string of a top-level collection that identifies the serial number of the collection's physical device.
            /// </summary>
            /// <param name="HidDeviceObject">Specifies an open handle to a top-level collection</param>
            /// <param name="Buffer">a caller-allocated buffer that the routine uses to return the requested serial number string</param>
            /// <param name="BufferLength">Specifies the length, in bytes, of a caller-allocated buffer provided at Buffer</param>
            /// <returns></returns>
            [DllImport("hid.dll")]
            private static extern Boolean HidD_GetSerialNumberString(IntPtr hidDeviceObject, IntPtr buffer, int bufferLength);

            /// <summary>
            /// The HidD_GetPreparsedData routine returns a top-level collection's preparsed data.
            /// </summary>
            /// <param name="hidDeviceObject">Specifies an open handle to a top-level collection. </param>
            /// <param name="PreparsedData">Pointer to the address of a routine-allocated buffer that contains a collection's preparsed data in a _HIDP_PREPARSED_DATA structure.</param>
            /// <returns>HidD_GetPreparsedData returns TRUE if it succeeds; otherwise, it returns FALSE.</returns>
            [DllImport("hid.dll")]
            private static extern Boolean HidD_GetPreparsedData(IntPtr hidDeviceObject, out IntPtr PreparsedData);

            [DllImport("hid.dll")]
            private static extern Boolean HidD_FreePreparsedData(IntPtr PreparsedData);

            [DllImport("hid.dll")]
            private static extern uint HidP_GetCaps(IntPtr PreparsedData, out HIDP_CAPS Capabilities);


            /// <summary>
            /// This function creates, opens, or truncates a file, COM port, device, service, or console.
            /// </summary>
            /// <param name="fileName">a null-terminated string that specifies the name of the object</param>
            /// <param name="desiredAccess">Type of access to the object</param>
            /// <param name="shareMode">Share mode for object</param>
            /// <param name="securityAttributes">Ignored; set to NULL</param>
            /// <param name="creationDisposition">Action to take on files that exist, and which action to take when files do not exist</param>
            /// <param name="flagsAndAttributes">File attributes and flags for the file</param>
            /// <param name="templateFile">Ignored</param>
            /// <returns>An open handle to the specified file indicates success</returns>
            [DllImport("kernel32.dll", SetLastError = true)]
            private static extern IntPtr CreateFile(string fileName, uint desiredAccess, uint shareMode, uint securityAttributes, uint creationDisposition, uint flagsAndAttributes, uint templateFile);

            /// <summary>
            /// This function closes an open object handle.
            /// </summary>
            /// <param name="hObject">Handle to an open object</param>
            /// <returns></returns>
            [DllImport("kernel32.dll")]
            private static extern int CloseHandle(IntPtr hObject);

            /// <summary>
            /// This function reads data from a file, starting at the position indicated by the file pointer.
            /// </summary>
            /// <param name="file">Handle to the file to be read</param>
            /// <param name="buffer">Pointer to the buffer that receives the data read from the file </param>
            /// <param name="numberOfBytesToRead">Number of bytes to be read from the file</param>
            /// <param name="numberOfBytesRead">Pointer to the number of bytes read</param>
            /// <param name="lpOverlapped">Unsupported; set to NULL</param>
            /// <returns></returns>
            [DllImport("Kernel32.dll", SetLastError = true)]
            private static extern bool ReadFile(IntPtr file, byte[] buffer, uint numberOfBytesToRead, out uint numberOfBytesRead, IntPtr lpOverlapped);

            /// <summary>
            ///  This function writes data to a file
            /// </summary>
            /// <param name="file">Handle to the file to be written to</param>
            /// <param name="buffer">Pointer to the buffer containing the data to write to the file</param>
            /// <param name="numberOfBytesToWrite">Number of bytes to write to the file</param>
            /// <param name="numberOfBytesWritten">Pointer to the number of bytes written by this function call</param>
            /// <param name="lpOverlapped">Unsupported; set to NULL</param>
            /// <returns></returns>
            [DllImport("Kernel32.dll", SetLastError = true)]
            private static extern bool WriteFile(IntPtr file, byte[] buffer, uint numberOfBytesToWrite, out uint numberOfBytesWritten, IntPtr lpOverlapped);

            /// <summary>
            /// Registers the device or type of device for which a window will receive notifications
            /// </summary>
            /// <param name="recipient">A handle to the window or service that will receive device events for the devices specified in the NotificationFilter parameter</param>
            /// <param name="notificationFilter">A pointer to a block of data that specifies the type of device for which notifications should be sent</param>
            /// <param name="flags">A Flags that specify the handle type</param>
            /// <returns>If the function succeeds, the return value is a device notification handle</returns>
            [DllImport("User32.dll", SetLastError = true)]
            private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags);

            /// <summary>
            /// Closes the specified device notification handle.
            /// </summary>
            /// <param name="handle">Device notification handle returned by the RegisterDeviceNotification function</param>
            /// <returns></returns>
            [DllImport("user32.dll", SetLastError = true)]
            private static extern bool UnregisterDeviceNotification(IntPtr handle);
        }
        #region
        /// <summary>
        /// SP_DEVICE_INTERFACE_DATA structure defines a device interface in a device information set.
        /// </summary>
        public struct SP_DEVICE_INTERFACE_DATA
        {
            public int cbSize;
            public Guid interfaceClassGuid;
            public int flags;
            public int reserved;
        }

        /// <summary>
        /// SP_DEVICE_INTERFACE_DETAIL_DATA structure contains the path for a device interface.
        /// </summary>
        [StructLayout(LayoutKind.Sequential, Pack = 2)]
        internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
        {
            internal int cbSize;
            internal short devicePath;
        }

        /// <summary>
        /// SP_DEVINFO_DATA structure defines a device instance that is a member of a device information set.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public class SP_DEVINFO_DATA
        {
            public int cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
            public Guid classGuid = Guid.Empty; // temp
            public int devInst = 0; // dumy
            public int reserved = 0;
        }
        /// <summary>
        /// Flags controlling what is included in the device information set built by SetupDiGetClassDevs
        /// </summary>
        public enum DIGCF
        {
            DIGCF_DEFAULT = 0x00000001, // only valid with DIGCF_DEVICEINTERFACE
            DIGCF_PRESENT = 0x00000002,
            DIGCF_ALLCLASSES = 0x00000004,
            DIGCF_PROFILE = 0x00000008,
            DIGCF_DEVICEINTERFACE = 0x00000010
        }
        /// <summary>
        /// The HIDD_ATTRIBUTES structure contains vendor information about a HIDClass device
        /// </summary>
        public struct HIDD_ATTRIBUTES
        {
            public int Size;
            public ushort VendorID;
            public ushort ProductID;
            public ushort VersionNumber;
        }

        public struct HIDP_CAPS
        {
            public ushort Usage;
            public ushort UsagePage;
            public ushort InputReportByteLength;
            public ushort OutputReportByteLength;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
            public ushort[] Reserved;
            public ushort NumberLinkCollectionNodes;
            public ushort NumberInputButtonCaps;
            public ushort NumberInputValueCaps;
            public ushort NumberInputDataIndices;
            public ushort NumberOutputButtonCaps;
            public ushort NumberOutputValueCaps;
            public ushort NumberOutputDataIndices;
            public ushort NumberFeatureButtonCaps;
            public ushort NumberFeatureValueCaps;
            public ushort NumberFeatureDataIndices;
        }
        /// <summary>
        /// Type of access to the object.
        ///</summary>
        static class DESIREDACCESS
        {
            public const uint GENERIC_READ = 0x80000000;
            public const uint GENERIC_WRITE = 0x40000000;
            public const uint GENERIC_EXECUTE = 0x20000000;
            public const uint GENERIC_ALL = 0x10000000;
        }
        /// <summary>
        /// Action to take on files that exist, and which action to take when files do not exist.
        /// </summary>
        static class CREATIONDISPOSITION
        {
            public const uint CREATE_NEW = 1;
            public const uint CREATE_ALWAYS = 2;
            public const uint OPEN_EXISTING = 3;
            public const uint OPEN_ALWAYS = 4;
            public const uint TRUNCATE_EXISTING = 5;
        }
        /// <summary>
        /// File attributes and flags for the file.
        /// </summary>
        static class FLAGSANDATTRIBUTES
        {
            public const uint FILE_FLAG_WRITE_THROUGH = 0x80000000;
            public const uint FILE_FLAG_OVERLAPPED = 0x40000000;
            public const uint FILE_FLAG_NO_BUFFERING = 0x20000000;
            public const uint FILE_FLAG_RANDOM_ACCESS = 0x10000000;
            public const uint FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000;
            public const uint FILE_FLAG_DELETE_ON_CLOSE = 0x04000000;
            public const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
            public const uint FILE_FLAG_POSIX_SEMANTICS = 0x01000000;
            public const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
            public const uint FILE_FLAG_OPEN_NO_RECALL = 0x00100000;
            public const uint FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000;
        }
        /// <summary>
        /// Serves as a standard header for information related to a device event reported through the WM_DEVICECHANGE message.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct DEV_BROADCAST_HDR
        {
            public int dbcc_size;
            public int dbcc_devicetype;
            public int dbcc_reserved;
        }
        /// <summary>
        /// Contains information about a class of devices
        /// </summary>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct DEV_BROADCAST_DEVICEINTERFACE
        {
            public int dbcc_size;
            public int dbcc_devicetype;
            public int dbcc_reserved;
            public Guid dbcc_classguid;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
            public string dbcc_name;
        }
        #endregion

}

HID类是最底层的,他直接使用了window API,这是一个C++的库,所以不能直接引用,需要使用前文提到的DllImport来导入并翻译。

HIDInterface类:

using System;
using System.Text;
using System.Threading;
using System.IO;
using System.ComponentModel;

namespace ReaderCSharp
{
    public class HIDInterface : IDisposable
    {

        public enum MessagesType
        {
            Message,
            Error
        }

        public struct ReusltString
        {
            public bool Result;
            public string message;
        }

        public struct HidDevice
        {
            public UInt16 vID;
            public UInt16 pID;
            public string serial;
        }
        HidDevice lowHidDevice = new HidDevice();

        public delegate void DelegateDataReceived(object sender, byte[] data);
        public DelegateDataReceived DataReceived;

        public delegate void DelegateStatusConnected(object sender, bool isConnect);
        public DelegateStatusConnected StatusConnected;

        public bool bConnected = false;


        public Hid oSp = new Hid();
        private static HIDInterface m_oInstance;

        public struct TagInfo
        {
            public string AntennaPort;
            public string EPC;
        }

        public HIDInterface()
        {
            m_oInstance = this;
            oSp.DataReceived = HidDataReceived;
            oSp.DeviceRemoved = HidDeviceRemoved;
        }

        protected virtual void RaiseEventConnectedState(bool isConnect)
        {
            if (null != StatusConnected) StatusConnected(this, isConnect);
        }

        protected virtual void RaiseEventDataReceived(byte[] buf)
        {
            if (null != DataReceived) DataReceived(this, buf);
        }

        public void AutoConnect(HidDevice hidDevice)
        {
            lowHidDevice = hidDevice;
            ContinueConnectFlag = true;

            ReadWriteThread.DoWork += ReadWriteThread_DoWork;
            ReadWriteThread.WorkerSupportsCancellation = true;
            ReadWriteThread.RunWorkerAsync();   //Recommend performing USB read/write operations in a separate thread.  Otherwise,

        }

        public void StopAutoConnect()
        {
            try
            {
                ContinueConnectFlag = false;
                Dispose();
            }
            catch
            {

            }
        }

        ~HIDInterface()
        {
            Dispose();
        }

        public bool Connect(HidDevice hidDevice)
        {
            ReusltString result = new ReusltString();

            Hid.HID_RETURN hdrtn = oSp.OpenDevice(hidDevice.vID, hidDevice.pID, hidDevice.serial);

            if (hdrtn == Hid.HID_RETURN.SUCCESS)
            {

                bConnected = true;

                #region 消息通知
                result.Result = true;
                result.message = "Connect Success!";
                RaiseEventConnectedState(result.Result);
                #endregion


                return true;
            }

            bConnected = false;

            #region 消息通知
            result.Result = false;
            result.message = "Device Connect Error";
            RaiseEventConnectedState(result.Result);

            #endregion
            return false;
        }


        public bool Send(byte[] byData)
        {
            byte[] sendtemp = new byte[64];
            Array.Copy(byData, 0, sendtemp, 0, byData.Length>64?64:byData.Length);

            Hid.HID_RETURN hdrtn = oSp.Write(sendtemp);

            if (hdrtn != Hid.HID_RETURN.SUCCESS)
            {
                return false;
            }

            return true;
        }

        public bool Send(string strData)
        {
            //获得报文的编码字节
            byte[] data = Encoding.Unicode.GetBytes(strData);
            return Send(data);
        }

        public bool Read()
        {
            if (bConnected == false) {
                return false;
            }
            oSp.ReadCMD();
            return true;
        }


        public void DisConnect()
        {
            bConnected = false;

            Thread.Sleep(200);
            if (oSp != null)
            {
                oSp.CloseDevice();
            }
        }


        void HidDeviceRemoved(object sender, EventArgs e)
        {
            bConnected = false;
            #region 消息通知
            ReusltString result = new ReusltString();
            result.Result = false;
            result.message = "Device Remove";
            RaiseEventConnectedState(result.Result);
            #endregion
            if (oSp != null)
            {
                oSp.CloseDevice();
            }

        }

        public void HidDataReceived(object sender, byte[] e)
        {

            try
            {
                //第一个字节为数据长度,因为Device 的HID数据固定长度为64字节,取有效数据
                int length = 64;
                byte[] buf = new byte[length];
                Array.Copy(e, 1, buf, 0, length);

                //推送数据
                RaiseEventDataReceived(buf);
            }
            catch
            {
                #region 消息通知
                ReusltString result = new ReusltString();
                result.Result = false;
                result.message = "Receive Error";
                RaiseEventConnectedState(result.Result);
                #endregion
            }

        }

        public void Dispose()
        {
            try
            {
                this.DisConnect();
                oSp.DataReceived -= HidDataReceived;
                oSp.DeviceRemoved -= HidDeviceRemoved;
                ReadWriteThread.DoWork -= ReadWriteThread_DoWork;
                ReadWriteThread.CancelAsync();
                ReadWriteThread.Dispose();
            }
            catch
            { }
        }

        Boolean ContinueConnectFlag = true;

    }
}

使用起来非常方便,只要使用HIDInterface类就可以了,而且自动重连的功能还保留着。

使用方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace ReaderCSharp
{
    public class TagReaderManager
    {

        private static TagReaderManager shared;


        struct connectStatusStruct
        {
            public bool preStatus;
            public bool curStatus;
        }

        connectStatusStruct connectStatus = new connectStatusStruct();

        //推送连接状态信息
        public delegate void isConnectedDelegate(bool isConnected);
        public isConnectedDelegate isConnectedFunc;


        //推送接收数据信息
        public delegate void PushReceiveDataDele(byte[] datas);
        public PushReceiveDataDele pushReceiveData;


        private byte[] receivedBuffer = null;

        private HIDInterface hid;

        public static TagReaderManager sharedManager()
        {
            if (shared == null) {
                shared = new TagReaderManager();
            }
            return shared;
        }

        private TagReaderManager()
        {
            hid = new HIDInterface();
        }

        private bool sendingData(byte[] data) {
            return hid.Send(data);
        }

        bool receiveData()
        {
            return hid.Read();
        }

        public bool connectDevice()
        {

            hid.StatusConnected = StatusConnected;
            hid.DataReceived = DataReceived;

            HIDInterface.HidDevice hidDevice = new HIDInterface.HidDevice();
            hidDevice.vID = 0x0483;
            hidDevice.pID = 0x5750;
            hidDevice.serial = "";
            hid.AutoConnect(hidDevice);
            connectionStatus = false;

            return true;
        }

        //接受到数据
        public void DataReceived(object sender, byte[] e)
        {
            if (e != null) {
                Console.WriteLine(BitConverter.ToString(e));
                receivedBuffer = e;
            }
        }

        //状态改变接收
        public void StatusConnected(object sender, bool isConnect)
        {
            connectStatus.curStatus = isConnect;
            if (connectStatus.curStatus == connectStatus.preStatus)  //connect
                return;
            connectStatus.preStatus = connectStatus.curStatus;

            if (connectStatus.curStatus)
            {
                connectionStatus = true;
                System.Console.WriteLine("hid connected!");
                //ReportMessage(MessagesType.Message, "连接成功");
            }
            else //disconnect
            {
                connectionStatus = false;
                System.Console.WriteLine("hid disconnected!");
                //ReportMessage(MessagesType.Error, "无法连接");
            }
        }
        public bool connectionStatus{ set; get; }

    }
}

发送就是发送,接收的话,通过一个flag和receivedBuffer来确定,很好理解~

HID的代码和原作者的代码相比较有一些修改,OpenDevice这个函数中CreateFile()函数的参数有些不同,原作者是禁止其他进程共享HID描述文件的。然而这样一来我们的程序就无法成功的建立文件,并进行通信。
所以我怀疑可能是操作系统版本不同,所以系统对于共享权限有了不同的限制吧~

End

这次通过这个项目,感觉自己对软硬件通信又长进了不少。将这几个HID相关的东西做了封装之后,其他同学调用起来操控读取器就方便多了,而且最重要的是,这个库完完全全是.NET的库,所以他们可以用wpf或者winform来写界面了,可以说是非常爽了。

G3PLC

Context

最近实在是忙爆了,终于有时间来更新两篇文章了!

去年教研室的G3PLC项目,终于在我们加班加点后,赶出来了。

我们负责在原有功能上增加路由功能,也就是LOADng。

Route Table

LOADng是一个轻量级的协议,传输的数据都是必须要的最小量。

比如,路由表里存的每一条都只有去目标节点的下一跳。发送也是,将数据发送给下一跳后,便假装自己已经发送完毕了。

每次单播数据都需要ack确认,如果多次重发没有获得ack,那便意味着发送不成功,该条路线出现了故障,于是便开启路由修复,修复成功后,再次发送。

感觉每一个操作都很简单。

Blacklist

黑名单是LOADng用来进一步降低消耗的一个措施。

在电力线路中通信,经过实验,有时候通信并不是双向的。有时候真的只能发出去,但是收不到。

所以有了blacklist之后,明明收到了一条消息,但是发现他在黑名单中,就不会直接原路发送应答,而是会选另一条路去应答。

Route Error

路由错误,这里有个坑儿。一开始我觉得每一个错误路由都可以删除其相关的路由。

比如,我收到了一条A结点不可达的rerr信息,那么按理说我应该把所有需要路过A的路由都删掉。可是在日本测试中,他们却认为只要删除收到的rerr即可。

End

时隔差不多一个月才想起写这一篇。剩下的内容还得再温习一下,再写~

字节对齐

Context

去年我们接手了一个G3PLC的项目,这也算是首次正正规规的做一次嵌入式了,也是我第一次在自己的电脑上安装并使用keil,有点小兴奋。

一开始我们心里还是蛮害怕的,因为协议很多,我们嵌入式基础很弱,一直担心最后这个项目无法完成。不过,到了今年6月,我们竟然真的写完了!

俗话说得好,写程序花20%的时间,调试花80%的时间~ 当然这是他们嵌入式行业的俗话~

于是我们开始了紧张的调试,一开始还蛮顺利,遇到了问题基本上可以非常快的定位到问题点,然后也可以非常快的解决。

直到…

StackTrace

先来看两个结构体

adp.h:

typedef struct _ROUTINGTABLE_
{
    uSHORT    DestinationAddress; //16
    uSHORT    NextHopAddress; //16
    uSHORT    RouteCost; //16
    unsigned    HopCount:4;
    unsigned    WeakLinkCount:4;
    uSHORT    ValidTime;//16
}_ROUTINGTABLE,*_pROUTINGTABLE;

mmu.h:

typedef struct _ROUTINGTABLEITEM_
{
    uSHORT    destination; //2
    uSHORT nextHop; //2
    uSHORT cost; //2
    unsigned hopCount:4; //0.5
    unsigned weakLinkCount:4; //0.5
    uSHORT    ValidTime; //2
}_ROUTINGTABLEITEM, *_pROUTINGTABLEITEM;

这两个结构体除了名字不一样,别的都是一样的,每个变量的位置,变量的类型,所以我们觉得他们就是一样的

然后就出现了奇怪的现象~

我们设置了ValidTime之后,数据变了,变得我们不认识了!

我们上一行把ValidTime设置成300(0x012c)之后下一行用16进制打印强制转换另一个类型的ValidTime,却变成了0x2c00

我们上一行把ValidTime设置成30(0x001e)之后下一行用16进制打印强制转换另一个类型的ValidTime,却变成了0x1e00

是编译器欺负人?还是单片机欺负人?

按理说两个类型是完全一样的,那么我传入的指针,被强制转换之后,每个变量所对应的地址也是一样的,那内容应该也是一样的呀

Solve

折腾了很长时间,最后我就强行把类型统一了,使用同一个struct。竟然就好了!

于是我们发现在adp.h定义这一串struct之前有 #pragma pack(1)

他的功能是字节对齐,1就代表着1字节对齐。

在Keil官网上,对pack这个预编译函数有介绍
编译器默认是8字节对齐,所以只要不屑pack(1),那就是8字节对齐的。官网这里的demo我觉得有点问题,所以我就用另一个例子来说明。

以下内容夹杂猜想

adp.h:

#pragma pack(1)
......

typedef struct _ROUTINGTABLE_
{
    uSHORT    DestinationAddress; //16
    uSHORT    NextHopAddress; //16
    uSHORT    RouteCost; //16
    unsigned    HopCount:4;
    unsigned    WeakLinkCount:4;
    uSHORT    ValidTime;//16
}_ROUTINGTABLE,*_pROUTINGTABLE;

这里按照1个字节对齐,在内存里就是这样的

|-----------0-----------|
 -----------------------
|     Destination_H     |
|     Destination_L     |
|    NextHopAddress_H   |
|    NextHopAddress_L   |
|      RouteCost_H      |
|      RouteCost_L      |
|HopCount,WeakLinkCount |
|      ValidTime_H      |
|      ValidTime_L      |

sizeof(_ROUTINGTABLE)==9,这个应该是理想情况,但是如果没有pack(1)

mmu.h:

typedef struct _ROUTINGTABLEITEM_
{
    uSHORT    destination; //2
    uSHORT nextHop; //2
    uSHORT cost; //2
    unsigned hopCount:4; //0.5
    unsigned weakLinkCount:4; //0.5
    uSHORT    ValidTime; //2
}_ROUTINGTABLEITEM, *_pROUTINGTABLEITEM;

这里我觉得应该是按照2个字节对齐(虽然官网说的是8),在内存里就是这样的

|     0    |    1    |
 --------------------
|   dstH   |  dstL   |
|   NextH  |  NextL  |
|   cstH   |  cstL   |
|hopC\weakC|    *    |
|  ValTimH | ValTimL |

sizeof(_ROUTINGTABLEITEM)==10,于是字节错位了!

我们使用_ROUTINGTABLEITEM这个结构体对validTime赋值,会操作结构体内存的8、9位字节

使用错误案例validTime=300(0x012c),short在内存中存放为0x2c,0x01(倒着的~)

于是结构体为dstH,dstL,NxtH,NxtL,cstH,cstL,hop,weak,0x00,0x2c,0x01

但是使用_ROUTINGTABLE这个结构体去读取validTime的时候,会读取结构体内存的7、8位字节

也就是dst[7]=00,dst[8]=0x2c,于是validTime变成了0x2c00,超大的数字!

One more thing

这里bit field对齐夹杂了我的部分猜测,因为我查了很多很多很多资料,也没有找到比特操作的明确说明~

所以我就参考了这个牛逼的网页

假设bit是自动按序排列的,只要几个bit field大小总和小于等于一个字节,就使用一个字节空间,
而如果超过一个字节,那就需要把超的哪个bit field赶到下一个字节中去。

End

所以嵌入式是真的难啊!

我们这几天的调试还遇到了一些坑,过几天总结一下再来说说~