最近动态

编程

Linkit One 空调遥控器

使用linkit one 控制空调

这一篇会比较长,会写很多踩过的坑,一些知识,弯路,废话.

如果你想直接看结果,可以直接看小标题Release

Context

这夏天真是热,虽然成都比杭州的温度低7-10度,但是相比较舒适的22-25度来说,还是比较燥热的.

所以空调肯定是要开的.

可是,一直开会比较冷,开一会儿关掉又会比较热,即使空调有定时开定时关的功能,那这个操作在睡觉期间,只能做一次.它并不能智能的开关开关开关……

但是,自己做一个遥控器,就可以啊!

于是就开始着手搞这个了!

My Limitation

我是个纯种的程序员,虽然说数电学的还不错,但是模电基本上都忘光了.所以要我画画板子,计算机算电阻电容,那我还不如直接放弃算了~

之前玩过Arduino,所以我对Arduino还算有点儿了解,所以肯定就选择这个平台了.

因为我们公司自己的智能硬件大部分都涉及到低功耗蓝牙,所以我觉得用蓝牙来进行互动对我来说开发起来也比较方便,所以我就选择了之前参加比赛骗来的Linkit One开发版.它自带了蓝牙、Wifi、GPS、GSM模块(一波广告)

Requirements

DHT22温湿度传感器,IR发射器,IR接收器.

面包版,线,线,线,线,线…

Linkit One,Arduino开发环境, Linkit One SDK.

Knowledge

以下内容基本上是摸索过程中学的,因为我一开始并没有觉得这个东西会那么的复杂

遥控器和空调的通信

一查就知道,他们用一个叫做38khz红外来通信.

那么38khz红外是个啥玩意儿呢?

先上图

irsignal

就是发射器在一定的时间内通过亮灭亮灭的快速切换来发送0,1,0,1

比如说 载波发射0.56ms,不发射0.56ms表示数字信号”0”;载波发射0.56ms,不发射1.68ms表示数字信号”1”

上面说的这个操作只是个例子,不代表所有的空调,电视什么的都用这种方式来表示0和1.

Arduino

这个怎么解释呢?

举个例子吧~

单片机工作起来就是在永远循环执行一个代码块儿.

在Arduino中有void loop()这个函数,在这个函数中把要循环做的事情放进去,就可以让它正常工作了.

比如说把某一个引脚置为高电平,只要简单的写一句DigitalWrite(Pin Number,HIGH);就OK了

读取某个引脚的点平值,只要简单的写一句DigitalRead(Pin Number);就OK了

差不多就这么个操作,基本上不用考虑什么中断、分片、多线程之类的东西

发送指令*

虽然我们知道了如何让IR发射器发送红外信号,但是我们依然不知道这个空调的发射指令是什么~

这个学习指令几年前在机顶盒遥控器上面,就见过了.在拥有了IR接收器之后,他的原理其实蛮简单的.

IR接收器在收到高低点平切换的时候,记录一下时间,就可以知道发射端高低信号的持续时间了.

比如说:

我抓到的关机指令

4360,4410,477,2205,237,266,520,1678,514,1734,484,693,367,527,533,1767,479,735,370,482,539,1956,309,491,630,550,477,2208,2234,473,532,1748,482,563,515,1585,1065,1449,281,1590,1090,1402,347,461,539,2139,208,1542,506,2262,185,283,769,343,536,516,551,522,942,1422,420,512,556,513,1150,1339,331,1584,544,1762,552,430,581,496,565,1133,198,316,846,266,539,489,574,526,807,339,531,1694,503,1729,517,1614,521,1753,456,1645,522,5371,4506,4419,463,1699,726,292,531,1601,838,1442,524,476,576,508,1098,1414,279,501,567,514,1063,1484,309,466,555,510,557,1766,693,1394,538,1135,205,1455,511,503,547,1773,450,1674,517,1719,480,1634,555,486,562,1736,535,1564,540,1758,737,288,499,520,553,540,822,339,522,1663,532,504,1096,200,381,1680,509,1762,436,1650,524,505,571,1044,205,389,753,322,560,527,565,1199,190,260,789,334,512,1625,903,1428,434,1599,1139,1335,316,1592,559

开机指令

4385,4385,471,1690,447,579,527,1600,996,1236,548,498,584,490,8521,561,106,171,65,62,61,64,67,58,62,450,226,562,504,1590,571,528,722,1503,546,1593,839,312,555,481,571,1630,583,1695,528,1577,572,1693,516,1589,560,518,658,1564,553,1582,753,405,535,524,591,491,571,511,771,410,497,1621,563,1747,514,466,579,1601,567,726,457,459,537,509,627,491,557,531,653,473,527,1620,609,489,852,1388,518,1776,437,1712,485,1740,441,5368,4596,4410,400,1570,553,523,556,1700,567,1552,569,526,809,351,491,1634,554,528,560,621,506,1597,594,493,592,786,360,1571,572,1590,657,463,554,1599,566,1721,502,535,549,513,563,1701,506,1607,562,1771,463,1596,552,1806,458,474,580,1582,556,1709,500,510,567,522,558,536,820,320,534,525,564,1611,584,1677,526,488,561,1874,338,519,565,517,569,512,575,520,660,479,547,523,560,1618,735,377,564,1606,576,1685,520,1619,557,1721,505

有了这一坨数据,后面只要控制IR发射器Data引脚*,就可以发送红外指令了!

PWM(脉冲宽度调制)

嗯,这个东西我琢磨了会儿才明白.

一开始我一直不明白,我要这玩意儿干嘛.

引用百度百科里面的解释: 脉冲宽度调制是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中.

不过后面在pj大佬的教导下,我明白了它的意思,简单说就是我要发高电平,就发PWM波;我要发低点平,就不发PWM波.这看起来和AM调制一样,不同的是AM调制是对一个模拟信号进行调制;而我这里用到的是对数字信号进行调制.

我就假设我说明白了~

Dev

对于DHT和蓝牙的开发我就不多写了,因为真的蛮简单的,也没什么大坑.最多吐槽一下Linkit One这个板子对BLE蓝牙的限制吧~这家伙不能修改广播数据(改不了名字、服务UUIDs、制造商数据……),而且外设模式下Notify貌似是没有用的~

好嘞,蓝牙和温湿度都讲完了!下面可以一心一意的讲IR发射的坑了.

Arduino-IRRemote

在我真正开始这个项目的之前,我做了一些估计.查了我需要的各种模块的开发难度,找找有没有什么可以用的库可以直接调用.

DHT温湿度传感器

IR发射接收

美的空调Arduino发射接收

我找到了这三个库,觉得这个项目在元件齐全了之后大概只要一个下午就可以搞定了.

于是,收到IR发射器之后,我就迫不及待的开工了!

~~~/Arduino/libraries/IRremote/irISR.cpp:18:5: error: expected constructor, destructor, or type conversion before '(' token
ISR (TIMER_INTR_NAME)

编译错误!我溯源找到了这个类,发现了ISR()这个函数,并没有写他的来源,说明这个函数可能是板子自带的.于是我就用Arduino UNO来编译相同的代码,编译通过!

天啦噜,Arduino-IRRemote这个库是不支持Linkit One的!

吓得我赶紧去看看那个直接发美的空调指令的库,这个库,也是调用Arduino-IRRemote的

异想天开的做法

然后我就把发射代码搞出来,放到了Arduino UNO上面来运行.竟然可以成功的关闭空调了!

于是我陷入了深深的沉思~

难道说,我得用两颗单片机来做这个简单的小玩意儿??

说实话,我真的这么折腾了一段时间,效果和想象中的并不一样,这可能和模拟电路的知识有关~

手动生成PWM!

回到原始的方法,我开始理解IRRemote发送数据的工作原理

发送原始数据

void  IRsend::sendRaw (unsigned int buf[],  int len,  int hz)
{
    // Set IR carrier frequency
    enableIROut(hz);

    for (int i = 0;  i < len;  i++) {
        if (i & 1)  space(buf[i]) ;
        else        mark (buf[i]) ;
    }

    space(0);  // Always end with the LED off
}

这好像是发高电平

void  IRsend::mark (int time)
{
    TIMER_ENABLE_PWM; // Enable pin 3 PWM output
    if (time > 0) delayMicroseconds(time);
}

这好像是发低电平

void  IRsend::space (int time)
{
    TIMER_DISABLE_PWM; // Disable pin 3 PWM output
    if (time > 0) delayMicroseconds(time);
}

这就当作是配置PWM的频率吧

void  IRsend::enableIROut (int khz)
{
    // Disable the Timer2 Interrupt (which is used for receiving IR)
    TIMER_DISABLE_INTR; //Timer2 Overflow Interrupt

    pinMode(TIMER_PWM_PIN, OUTPUT);
    digitalWrite(TIMER_PWM_PIN, LOW); // When not sending PWM, we want it low
    TIMER_CONFIG_KHZ(khz);
}

TIMER_DISABLE_PWMTIMER_ENABLE_PWM里面调用了很多和ISR,PWM有关的东西,所以这里开始就要重新了

先写个高电平

void mark(int time){
    int t=0;
    boolean isHigh=false;
    while(t<=time){
        isHigh=!isHigh;
        t+=enable_pwm(isHigh);
    }
}

int enable_pwm(boolean high){
    if(high){
        digitalWrite(3,HIGH);
    }else{
        digitalWrite(3,LOW);
    }
    delayMicroseconds(13);
    return 13;
}

通过计算38khz的一个周期是26微秒,所以我们13微秒就要转换一次电平~

再写个低电平

void space(int time){
    digitalWrite(3,LOW);
    delayMicroseconds(time);
}

这个不需要发转电平,所以就轻松一些了~

由于我已经固定了13微秒转换一次,所以也不用实现enableIROut(int khz)这个函数了.

运行!编译通过!发送!IR接收机采集到了IR信号!可是~空调好像没反应~

还有这种操作

气氛一度很尴尬,不过我毕竟只是个普通人,看不到发送的数据长什么样子,所以在添哥大佬的帮助下我使用了高档示波器

这是空调遥控器发出的信号在接受端的样子

right signal

这是我发出的信号在接受端的样子

error signal

尖峰哪儿来的?

检查代码真的没有问题,这个异常我只能归结为硬件问题了,要知道linkit one是一个很高级,很牛逼的硬件,它甚至有多线程的操作,所以,它会不会强行的我把我操作所在的线程给中断了呢?又或者linkit one并没有能力做那么高频的电平转换.

analogWriteAdvance

气氛再一度尴尬,不过添哥大佬在官方文档的Analog I/O中找到了analogWriteAdvance()这个函数!

void analogWriteAdvance(
uint32_t pin, 
uint32_t sourceClock, 
uint32_t clockDivider, 
uint32_t cycle, 
uint32_t dutyCycle
);

而他的例子里面就是生成了一个PWM波!

在经过简单的计算之后,我改写了PWM波的生成和暂停

//Enable 38khz PWM
analogWriteAdvance(PWM_PIN,PWM_SOURCE_CLOCK_13MHZ,PWM_CLOCK_DIV1,342,171);
//Disable 38khz PWM
analogWriteAdvance(PWM_PIN,PWM_SOURCE_CLOCK_13MHZ,PWM_CLOCK_DIV1,342,0);

编译上传运行,,空调开了~(由于这是在公司做的,所以信号序列也是公司空调的序列,所以回到寝室,还得再改一下)

时好时坏的遥控器

回到了寝室,我赶紧把新的数据放进去,对准空调,发了开机指令……再发一次……再发一次

还是没用??不过关机指令倒是可以了

这说明发送是可以的,只是内容还有问题.

在网上搜寻了很久的开关机数据,都不太行,这是我想起了那个美的空调遥控指令的库!

之前,我不能使用它,是因为IRRemote中无法使用Linkit One的PWM波发生器,所以如果我改了那个发射函数,不就OK了嘛!

它需要什么方法我就给什么方法,连名字和参数都一样!

VKIRSender.h

class VKIRSender{
public:
    void mark(int time);
    void space(int time);
    void enableIROut(int hz);
};

VKIRSender.cpp

#include "VKIRSender.h"
#include <Arduino.h>

#define PWM_PIN 3

void VKIRSender::mark(int time){
    analogWriteAdvance(PWM_PIN,PWM_SOURCE_CLOCK_13MHZ,PWM_CLOCK_DIV1,342,171);
    delayMicroseconds(time);
}

void VKIRSender::space(int time){
    analogWriteAdvance(PWM_PIN,PWM_SOURCE_CLOCK_13MHZ,PWM_CLOCK_DIV1,342,0);
    delayMicroseconds(time);
}

void VKIRSender::enableIROut(int hz){

}

在这个库的帮助下,我甚至可以不使用抓来的数据,我甚至可以设定温度,冷热模式,风速……

试验了一下,!成功了

TODO

开发历程差不多就是这样,真的好艰辛~

当然,现在还存在一个比较大的问题,就是发射距离!

我只能在离空调1M的位置,让空调听话,稍微远一点,他就不听话了~

在解决了这个问题之后,就可以配合温湿度传感器,让它智能起来了!

RELEASE

前面废话那么多,这下面才是最有用的~

代码开源了,其实并没有什么很难得地方,只是由于踩了很多坑,所以我觉得公开给大家伙用用,可能还是蛮好的.

Github看,给个Star当然也是极好的~

接线差不多是这个样子的

board

阅读剩下更多

编程

用handler/thread来做定时器

刚开始接触android编程的时候,是大三.那时候在做精益防伪的演示版App,网络协议用的是socket.

使用socket的时候,编译器告诉我,需要在新线程中来使用socket操作.

于是乎,就学习了Thread的使用.

当时觉得Thread好像还是蛮简单的.在thread里面搞一个runnable,在runnable里面跑代码块,用start(),stop()来控制线程的开始和结束.

之后在thread中操控了ui,就闪退了.因为UI只能在主线程中去操作.这iOS就牛逼了,即使不在主线程中去操作,他也不会闪退.他会自动的在主线程中去渲染.

分界线

言归正传,后面上课,老师让我们用thread来做一个定时器,在主线程中显示一个数字,在另一个线程中定时增加数字.

这玩意儿是来学习Thread和Handler的.

之后需要用到定时器的地方我都用Thread来实现了~

然而

Thread不是很安全.而且,对于不同的手机厂商,不同的ROM,对thread的操作也是不一样的.

Thread workingThread = new Thread(new Runnable() {
        @Override
        public void run() {
            while (isWorking) {
                byte[] wantSend = sendingData();
                if (wantSend == null || mainCharacteristic == null || mainGatt == null) {
                    dataPointer = 0;
                    isWorking=false;
                    workingThread.interrupt();
                } else {
                    mainCharacteristic.setValue(wantSend);
                    mainGatt.writeCharacteristic(mainCharacteristic);
                    if (dataPointer == 0 && dataStack.size() > 0) {
                        try {
                            Thread.sleep(gifInterval);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        try {
                            Thread.sleep(baseInterval);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    });

某些手机上,这个thread是停不下来的.在某些手机上,这个thread是无法重启的.在某些手机上,这个thread是无法中断的.

这就意味着,某些手机上,炫轮app只能发送一次图片;在某些手机上,炫轮app无法显示附近的蓝牙.

查了一些资料,我才发现thread,的很多接口都被废弃了,而且非常不安全,很容易出现锁死卡死的情况.

于是我去检查了很早很早很早的炫轮app.发现是用延迟来做定时器功能的.

于是我把Runnable提取出来了:

Runnable sendingRunnable=new Runnable() {
    @Override
    public void run() {
        if (isWorking) {
            byte[] wantSend = sendingData();
            if (wantSend == null || mainCharacteristic == null || mainGatt == null) {
                dataPointer = 0;
                isWorking=false;
            } else {
                mainCharacteristic.setValue(wantSend);
                mainGatt.writeCharacteristic(mainCharacteristic);
                if (dataPointer == 0 && dataStack.size() > 0) {
                        mHandler.postDelayed(sendingRunnable,gifInterval);
                } else {
                        mHandler.postDelayed(sendingRunnable,baseInterval);
                }
            }
        }
    }
};

用Handler的postDelayed来做延迟执行,这竟然就解决了前面所有的问题!

原因,我还要好好查些资料,再来补充.下次更新!

阅读剩下更多

学习

缓冲区为啥不溢出

软件安全性分析课,里面花了很多时间讲缓冲区溢出的故事.

大一为了加入凝聚工作室,我也是查过相关的资料,也做过类似的实验,但都是瞎搞.

在老师给出例子之前,我一直把失败归结于自己的代码有问题,或者攻击方式有问题.

在看了老师的例子之后,我觉得我好像没写错什么…

Run directly

现在内核牛逼了呢!

检查到了pc指向非法地址,就直接被内核终止了,所以,我也没办法

Debug with GDB

GDB真是个牛逼的东西,可以反编译,可以打断点,可以查看堆栈地址数据……

只要我在执行非法内存之前加个断点,看看栈数据是不是变得乱七八糟了,我的实验目的就达到了.

根据老师说的

char c[20];
char a;

这玩意儿运行的时候,在栈上面的顺序是ca

所以只要用不安全的函数,冲破c的长度,就可以覆盖a的数据.

然而gdb断点的时候,我惊奇的发现顺序是ac…

于是乎我就换了个代码

char a;
char c[20];
char b;

发现,栈里面是abc…

这,这你让我怎么溢出??

还要再研究研究…不论我是否使用优化编译,结果都是这样的…

等我找到答案,再来更新这一篇!

阅读剩下更多

学习

啥都不懂…

这学期好像做了个死,选了一堆学不会的课~

什么机器学习啊(全是数学,全是概率,已弃疗)

什么Linux内核啊(除了学会了装内核,使用gdb,和装逼,别的都没听懂)

什么软件安全性分析啊(我自己实验永远做不出老师的效果)

安全通信啊(这个我还听得懂……)

报名了华为软件挑战赛(也弃疗了)

为了要听懂Linux内核的课和一部分软件安全性分析的课,我借了买了一些计算机组成原理,编译原理,Binary Hack的书.

好像也没咋看懂~

然后要做推荐算法,就看数据挖掘,好像还是没咋看懂~

所以我承认我笨了…

阅读剩下更多

编程

UIWindow 之坑

Context

做一个Pop View有两种方法.

1.用一个UIView盖在当前UIView或者UIWindow的最上面

2.做一个UIWindow,让他变成keyWindow

第一种的好处是…没想到什么特别的好处,大概写起来简单吧.

第二种的好处是,可以扔个ViewController在里面,哈哈哈.

而且第二种,我觉得做起来结构比较清晰.这个Pop View里面做的事情,可以全部封装好,包括显示和消失.
而使用UIView的话,做显示就一定需要把superview作为参数传入,才可以显示,这个还是蛮麻烦的,特别是当我
想做一个全局接收通知,并在UI上显示的东西的时候.由于很难确定当前最顶上的UIView是什么,所以还是UIWindow比较好.

Bug

然而,在水壶架iOS端中,遇到了一个很奇怪的问题.这个问题貌似只在iOS9.3.5之前的系统上出现.对于之后的系统,没有出现这个问题.

从Leancloud的错误报告中看到,这是一个消息传到了空指针的fetal错误.

那就好好找找,为啥变成了空指针吧.

Come on, Where are you

由于一开始这个问题只在9.3.5的手机上出现了,而我和一大票iOS10都没有这个问题.所以我甚至怀疑,这个是手机的问题.

直到我发现所有的老系统,都爆了!我才慌了.

当弹出这个搜索框,再让他消失的时候,做任何的触摸操作,整个app就挂掉了.

所以,问题肯定就在dismiss方法里面!

-(void)disappear
{
    [UIView animateWithDuration:0.3f animations:^{
        self.view.alpha=0;
    } completion:^(BOOL finished) {
        _displayWindows.rootViewController=nil;
            _displayWindows.hidden=YES;
            _displayWindows=nil;
        [[BluetoothManager sharedManager]stopScan];
        [BluetoothManager sharedManager].delegate=nil;
        if (self.delegate) {
            self.delegate=nil;
        }
    }];
}

每次disappear之后,我就中断了整个app,查看他的UI结构,发现有一个不听话的UIWindow还停留在哪儿~

而我明明都把他设置为hidden了,按理说它已经不是keyWindow了~可是他貌似还在.

而且为什么这个东西在iOS 10就没问题了呢~

于是我就去查了查iOS 10的开发这手册,看看他们都做了些什么改变,导致iOS 10不会闪退.

扯远了.

所以说,当这个alert级的UIWindow设置为hidden的时候,没有key window了……

于是乎Touch Event进来的时候,不知道应该给谁了,就爆掉了……

所以所以,得把之前的UIWindow重新恢复key window的地位.

之后改成这样了:

-(void)disappear
{
    [UIView animateWithDuration:0.3f animations:^{
        self.view.alpha=0;
    } completion:^(BOOL finished) {
        _displayWindows.windowLevel=UIWindowLevelNormal;
        _displayWindows.rootViewController=nil;
        [self.view removeFromSuperview];
            [_displayWindows setHidden:YES];
            _displayWindows=nil;
        [[BluetoothManager sharedManager]stopScan];
        [BluetoothManager sharedManager].delegate=nil;
        if (self.delegate) {
            self.delegate=nil;
        }
    }];
    if (_preWindows) {
        [_preWindows makeKeyAndVisible];
        _preWindows=nil;
    }
}

当然这样一改,在show的时候也要做一些修改了~得把之前的UIWindow给记录下来,不然就懵逼咯.

阅读剩下更多

编程

Block--方便而危险

Context

Block真的很好用对不对?记得很早以前用ASIHttp来做网络请求的时候,得用delegate,然后每次请求的
delegate回调中都要做一下判断,才能知道这个回调是回应了具体哪一次请求,这还是蛮麻烦的.

不过Block出现了,对网络请求的response操作,就可以直接写在block中,不用去判断这个response谁否对应与当前这个request.

除了网络请求,Alert的回调啊,asyncsocket啊,之类的异步操作,用了block都特别的方便.

Situation

然而,有一天我们在炫轮iOS端的DIY界面中发现了一个bug,而这个bug预示着,
我有实例没有成功释放.

Reason

于是我就一行一行的找,感觉没有什么地方出现.但是我发现了一个现象:

我在使用SCLAlertView了之后,就出现了这个bug,所以我就重点查看SCLAlertView的相关代码,
经过排查,我觉得问题只能出现在dismissBlock上.查看了dismissBlock的声明代码,他是个strong~

我想,这里应该是出现了一个循环引用…block中的内容,在声明的时候,会copy一份当时的数据.当AlertView 消失的时候,
调用了dismissBlock,然而,dismissBlock中的内容有引用当前的ViewController,所以.

他们互相引用了!

所以只能在dismissBlock的最后加一行,把这个dismissBlock设置为nil.解决了~

End

这次要不是遇到了这个坑,说不定永远也不会去仔细看Block实现的原理~

关键就是这个copy,所以在block中如果引用了(强引用了该block的)类,就一定要注意,检查会不会出现循环引用!

阅读剩下更多

编程

蛋疼的GATT权限

Context

定位水壶架App的iOS端已经完成了80%,在考完矩阵之前,只要把android端的网络和蓝牙交互做完,就可以没什么太大的压力咯.

然而,在搞android端的时候,还是遇到了很多很多问题的.

第一坑

几天前,当我把iOS端蓝牙配对的代码,翻译为Java版本之后,本来想,肯定会轻轻松松,正正常常的运行了.

但是,在连接上定位器之后,总是会卡在搜索服务或者写入xxx这个阶段,然后就断线了.

当时,我觉得,可能是一些小问题,可能是我写漏了什么东西,所以就卡住了.

也怀疑过是Google的亲儿子Nexus蓝牙硬件不给力~

然后就痛苦的去复习了几天矩阵理论…………

但是,就在两个小时之前,我一步一步调试的时候,发现了一个奇怪的现象.

public void onServicesDiscovered(BluetoothGatt gatt, int status);

BluetoothGattCallback中,搜索到service的时候,竟然没有我们自己的service!

所以for循环就那么做完了……什么service都没有找到,最后就被关闭连接了.

这时候,我开始怀疑人生了

难道说,这蓝牙是为iOS设计的!?

赶紧整了一个锤子过来测测.发现锤子是可以找到所有的service的……

难道说,这蓝牙不是为Google设计的!?

这时候,我突然想到了一个问题.和之前几天的实验,可能有点儿关系.

很久很久的几天前

添哥找我说,蓝牙可以设置一个加密密钥,这样子传输数据,就是加密的了.(虽然说,蓝牙和WiFi一样,是要连接之后才能通信的,但是,无线嘛,肯定是要被窃听的)

于是我们就试验了一下,但是蓝牙加密之后,需要手动在手机上点击配对按钮,才可以进行加密通信.

由于android端,这个配对不是特别的明显,不像iOS那么粗暴地弹出来.所以我们最后没有使用这个方案.

这里还可以顺便说说加密

小时候,以为加密的意思就是,给数据加了一把锁.只要用密码把锁打开了,就可以访问数据了.(真单纯)

原来,加密是把数据都给翻译了一遍.即使知道加密解密的算法,使用错误的密钥,可以得到数据,但是这数据,肯定是错的.

当蓝牙加密通信的时候,会分配一个密钥.咱们配对蓝牙鼠标,蓝牙键盘的时候,也常常看到,需要输入个什么pin码,才能配对.一旦这配对了,之后通信就是加密的了.

(假设蓝牙键盘不用配对,不加密通信……)那就是无线的按键记录器啊!

总而言之

这搜索不出我们需要的service就是这个配对的,当我进入系统蓝牙设置,把这个设备的配对给取消了之后,就正常了~.~

第二坑的Context

BLE的GATT的Characteristic有权限控制.

大概就是Read,Write,WriteWithoutResponse,Notify这四种常见权限.

他们用一个8位的flag来控制.因为不是重点,所以懒得讲了.

第二坑

在我解决了Service Discover的问题了之后,我觉得,这下一定可以顺利地跑下去了!

结果卡在了一个写入之后的读取上…

本来,在写入一个数据之后,需要去读取一个反馈的.但是不知道为什么,没有去读取这个数据.

仔细阅读Monitor里面的log.可以发现,之前的读取,都会调用系统的readCharacteristic()函数,而这一次的读取却没有读取,这是为啥子嘞?

权限!

我们有一个Characteristic在写入之后,需要读取反馈.所以需要Read的权限.

然而,在这个Characteristic没有Read的权限的情况下,iOS端竟然还是可以正确的读取到数据!

而Android直接忽略的readCharacteristic()这个函数调用……

虽然说,这硬件是我们自己设计的,讲道理的话,在连接设备之后,并不需要去检查各种权限的.(除非这个硬件是被山寨的)

但是,加上一个权限验证的话,或许可以提高一些软件的稳健性呢?说不定可以让这个系统可以在遇到山寨货的情况下,可以继续正常的工作下去呢~

End

2016马上要结束了,每年都能更加深入的了解一些蓝牙,真的是好刺激!

阅读剩下更多

编程

初用Volley

Context

水壶架iOS端终于撸完了

由于我Android的界面写的烂的一逼,所以只能拜托xiaopo来帮我撸界面了.

首要目标是干掉蓝牙类,不过蓝牙绑定设备的时候会需要网络请求.

那就顺便把网络也给撸掉吧~

基本用法

1.需要一个请求队列

RequestQueue mQueue = Volley.newRequestQueue(context);

2.好了直接撸请求吧

Form-Data这么搞

StringRequest stringRequest = new StringRequest("一个URL",new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
               Log.d("Network OK", response);
        }
        }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
               Log.e("Network Fail", error.getMessage(), error);
        }
});

JSON这么搞

JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html", null,
    new Response.Listener<JSONObject>() {
        @Override
        public void onResponse(JSONObject response) {
            Log.d("Network OK", response.toString());
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            Log.e("Network Fail", error.getMessage(), error);
        }
    });

Q1 请求是form-data 响应是json

不过这里遇到了一个问题.

我们服务器大佬坑我,请求只能解析Form-Data的,返回的响应是Json

于是只能手动转换了

private static StringRequest FormDataRequest(String url, int method, Map parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock) {
    StringRequest request=new StringRequest(method, url, new Response.Listener<String>() {

        @Override
        public void onResponse(String response) {
            try {
                JSONObject jsonResponse = new JSONObject(response);
                if (jsonResponse.getInt("status") == 10086) {
                    //认证失败
                    failBlock.requestFail(new Exception("SessionExpired"));
                } else {
                    succeedBlock.getResponse(jsonResponse);
                }
            } catch (JSONException e) {
                e.printStackTrace();
                failBlock.requestFail(e);
            }
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            if (error.networkResponse.statusCode == 10000) {
                //认证失败
                failBlock.requestFail(new Exception("SessionExpired"));
            } else
                failBlock.requestFail(error);
        }}){
        @Override
        protected Map<String, String> getParams() throws AuthFailureError {
            return parameter;
        }
    };
    queue().add(request);
    return request;
}

用JSONObject自带的牛逼的初始化方法来解析响应

好不容易,把网络请求跑通了.

才发现,一个劲的403.

原来,Volley本身不处理Cookie的!!

啊席巴

于是就要手动处理

private static StringRequest FormDataRequest(String url, int method, Map parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock) {
    StringRequest request=new StringRequest(method, url, new Response.Listener<String>() {

        @Override
        public void onResponse(String response) {
           //这些你都看过了
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            //这些你都看过了
            }){
        @Override
        protected Map<String, String> getParams() throws AuthFailureError {
            return parameter;
        }

        @Override
        public Map<String, String> getHeaders() throws AuthFailureError {
            Map<String,String> header=super.getHeaders();
            String cookie=getCookie();
            if (cookie!=null){
                header.put("Set-Cookie",cookie);
                //手动从把Cookie放到Header中
            }
            return header;
        }

        @Override
        protected Response<String> parseNetworkResponse(NetworkResponse response) {
            refreshCookie(response.headers);
                //手动从Response的Header中获取Cookie
            return super.parseNetworkResponse(response);
        }
    };
    queue().add(request);
    return request;
}

这其中的Cookie持久化什么的,就不说了.它不是重点.

而重点是……

Final 我放弃了

当Response的Header中有多个Set-Cookie字段时,只会被解析出来一个!

如果需要把所有的Cookie都拿出来的话,需要修改Volley源码!

然而,我使用Gradle来添加Volley工程的,所以,你逗我吗!

于是乎,我就放弃了.

姜还是老的辣

大三的时候做精益防伪,使用了Android-Async-Http

这个很牛逼,功能很强大,还自动缓存了Cookie,真是替我省心.

还好前面撸接口的时候,封装的比较好,所以没有改动太多的东西.

Tip

封装了两个基本网络操作函数

public static void getMethodFormData(String url, Map<String,String> parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock){
    getViaLoopj(url,parameter,succeedBlock,failBlock);
}

public static void postMethodFormData(String url, Map<String,String> parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock){
    postViaLoopj(url,parameter,succeedBlock,failBlock);
}

再来一发Android-Async-Http的实例(封装过了)

public static void postViaLoopj(String url, Map<String,String> params,VKNetworkSucceedBlock succeedBlock,VKNetworkFailBlock failBlock) {
    client.post(url, new RequestParams(params), new JsonHttpResponseHandler(){
        @Override
        public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
            if (statusCode==403){
                failBlock.requestFail(new Exception("SessionExpired"));
            }else
            {
                succeedBlock.getResponse(response);
            }
        }
        @Override
        public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {
            super.onFailure(statusCode, headers, throwable, errorResponse);
            if (statusCode==403){
                failBlock.requestFail(new Exception("SessionExpired"));
            }else{
                failBlock.requestFail(new Exception(errorResponse.toString()));
            }
        }
        @Override
        public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
            super.onFailure(statusCode, headers, responseString, throwable);
            if (statusCode==403){
                failBlock.requestFail(new Exception("SessionExpired"));
            }else
                failBlock.requestFail(new Exception(responseString));
        }
    });
}

定义了两个interface

public interface VKNetworkSucceedBlock {
    void getResponse(JSONObject responseObject);
}

public interface VKNetworkFailBlock {
    void requestFail(Exception error);
}

于是乎做网络请求,就方便的很了

public static void mLogin(String email, String verify, boolean isChina, NetworkUtils.VKNetworkSucceedBlock succeed, NetworkUtils.VKNetworkFailBlock fail){
    HashMap<String,String> map=new HashMap<String, String>(){{
            put("hhh","123@163.com");
            put("jjj","3434");
            put("kkk",String.valueOf(1));
        }};
      NetworkUtils.postMethodFormData(UrlManager.URL_Login,map,succeed,fail);
}

最后网络的效果,就留到具体调用他的类去实现吧.

所以,封装了一遍就是好瞧把你能耐的,更换网络框架,不需要跑到每个Activity啊,Class里面去改.

response也是一套,

顶层完全不用去关注,底层的网络是如何实现的.(好像段老师还是郝老师这么说过)

当然我相信,来看这篇文章的大佬们,肯定早就知道这个了

阅读剩下更多

返回顶部