全都是矩阵~

研究生的课上了一周了,选课系统有多难过,就不吐槽了.

还好机智的我在选课之前就已经做了充足的准备,保证没有冲突的课,并且确定了抢课顺序.

但是,顺利的抢完课,去听了听课,才有点儿后悔…

信号分析导论: 函数信号集合正交

最优化理论: 正定矩阵,范数……

矩阵理论: 那都是矩阵

嗯,好像难一点的课,都和矩阵有关!!

关键是,矩阵理论我每次都没有位置坐,大家都太积极了!

课后自己自学失败

我只想对大家说…

Don't make me have to hurt you!!

公司新产品研发和研究生那么一堆课,交织在一起…

这是逼着我求肖奈大神附体嘛~

这几周写前端写的我,都有点对iOS手生了…虽然前端真的很爽,但是,

我水平还是太烂了……

等这些产品都上架了,好好地重构一下吧! (好像官网还没写…T_T)

BLE的广播帧

之前还以为BLE也就GATT那些东西嘛,没什么好说的了

其实

BLE或者说,蓝牙,的广播帧还是有很多很多奥秘的.还是有些东西可以拿来琢磨琢磨,学习学习

Context

至于为什么要突然学习这个东西,肯定是和新产品有关系的,具体的,可以等上市之后,买来一个试试看啊,哈哈哈哈哈哈

另一个原因,这个东西的官方文档真的很难找,至少在 www.bluetooth.com 上面我就没有看到讲述广播帧结构的文档,最后在TI的一个文档中,终于找到了相关的内容.

平时用BLE只要是在连接成功之后,搜索Service和Characteristc,后进行读写操作.这样的工作模式,只能一对一,不能做到一对多.但是利用了广播信息,可以做到一对多的发送消息…虽然这时候不能反过来写入.

Let’s go

我们先来上一张图片,看看Bluetooth到底广播了什么吧

AD

我是做上位机软件开发的,所以只关心应用层的东西,那么上面图片中,iOS和Android能被处理的数据是标红的那一段

一共31个字节,看起来好像不是很足够,但是由于协议是规定好了的,所以对应的位置表示对应的数据,还是够用的.

说说我们熟悉的一些广播信息吧:(iOS端CoreBluetooth的广播帧字典的Key)

1.LocalName (外设名称)
2.ServiceUUIDs (Service列表)
3.ManufacturerData (制造商数据)
4.IsConnectable (是否可连)

下面再给点儿我第一次知道的广播信息类型:

1.ServiceData (某个Service的广播数据)
2.TxPowerLevel (传输信号强度~.~,用来计算损耗用...好像暂时没啥用处,反正我也不会算)
3.OverflowServiceUUIDs (没懂他和ServiceUUIDs的区别~.~)
4.SolicitedServiceUUIDs (貌似和从设备没啥关系~.~)

说实话,平时用到的只有ServiceUUIDs,因为我们用这个来过滤出来我们需要的蓝牙外设,也用这个来区分不同的硬件.

LocalName的话iOS和Android都已经自动提取并封装成简单的接口了

上面是软件层面的东西,我们来看看硬件层面的呗~

一个广播帧会有很多类型的数据,每个类型有自己的type,上位机也是通过type字段来分辨数据是UUID还是Name或者其他的东西~

每一类数据都由3部分组成:

1.length of data (这个type的总长度)
2.type (定义type)
3.data (根据type的协议来填入数据)

这里简单的列举几个type:

0x02 不完整的16位UUID
0x04 不完整的32位UUID
0x06 不完整的128位UUID
0x08 设备全名
0x16 16位UUID的Service Data
0x20 32位UUID的Service Data
0x21 128位UUID的Service Data
0xFF 制造商自定义的数据

Example of Peripheral and Bluetooth Central

一台名叫VK的外设,公布了一个Service UUID 3344,5566;其中3344的Service Data是”0xAA,0xBB,0xCC”,制造商的自定义数据是”0x12,0x34,0x56,0x78”

那么他的广播数据是~

04 08 56 4B 05 02 44 33 66 55 06 16 44 33 cc bb aa 07 FF 00 00 78 56 34 12

拆分数据分析分析

04 08 56 4B
长度4 设备完整名称 V K

05 02 44 33 66 55
长度5 不完整的16位列表 3344 5566

06 16 44 33 cc bb aa
长度6 16位UUID的ServiceData UUID=3344 数据是 AA BB CC

07 FF 00 00 78 56 34 12
长度7 厂商自定义数据 厂商ID=0000 厂商自定义数据=12345678

如果有错误不要打我…

iOS端解析广播数据

-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI
{
    // 名字的字符串
    NSLog(@"Name %@",advertisementData[CBAdvertisementDataLocalNameKey]);
    // CBUUID的NSArray
    NSLog(@"Service UUID %@",advertisementData[CBAdvertisementDataServiceUUIDsKey]);
    // Key为CBUUID,Value为NSData的NSDictionary
    NSLog(@"Service Data %@",advertisementData[CBAdvertisementDataServiceDataKey]);
    // 制造商的数据NSData
    NSLog(@"ManufacturerData %@",advertisementData[CBAdvertisementDataManufacturerDataKey]);
}

Android端解析

@Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {
        // scanRecord 就是所有的广播数据呗
   }

iOS端为了隐私保护,做了各种各样奇怪的限定,所以不像Android那么爽,可以直接把广播帧的字节流都提取出来,所以就只能好好阅读协议,才能让iOS的App获取到想要的数据

相同的,Android虽然数据很开放,下位机可以不按照BLE协议去设置广播数据,Android App依然可以读得出来数据,但是解析数据就很头疼的样子了.

嗯,各有各的好处~

BTW

其中一个type是

0x17  Public Target Address
目标Mac地址

官方定义是

The Public Target Address data type defines the address of one or more intended recipients of an advertisement when one or more devices were bonded using a public address. This data type is intended to be used to avoid a situation where a bonded device unnecessarily responds to an advertisement intended for another bonded device.

what are you 弄啥嘞…

我YY他的用处就是,这个BLE设备只能被指定Mac地址的主机连接.

好啦,这次又学到了一点儿蓝牙的知识,写个文章当笔记.

纠结的随想

矛盾存在于一切事物中,并且贯穿于事物发展过程的始终,即矛盾无处不在,矛盾无时不有

这矛盾,大概也称为纠结

我们每天都可能纠结很多的事情,有的是大事儿,有的是小事儿.小事儿也可以称为矫情 ,比如说:

1.今天吃咩啊?
2.看哪部电影啊?
3.今天要不要洗衣服啊?
4.....

这些矫情的小矛盾出现在~选择困难症的人的日常生活中,或者当一件事情的两个选项,会牵扯到另外一些矫情的事情的时候

上面都是废话

这篇日志,主要是说说最近遇到的一些小故事吧

麦当劳不搭配套餐事件

某一天去麦当劳,前面排着一个老奶奶,拿着一张餐巾纸,上面写着:

巨无霸,鸡翅,可乐,大鸡排

老奶奶拿出老花眼镜来慢吞吞的读着纸巾上的字,收银员有点儿不耐烦,于是就直接看着纸巾帮她点餐了.

我看着纸巾上的内容,觉得有点儿眼熟,好像是一个套餐+大鸡排,但是服务员最后给的价格远远超出我的想象,50+

这时候我的大脑开始了严密而紧张的计算,最后得出结论,如果不用单点,而是使用套餐,可以节省9元钱左右

这貌似和本文又无关了

No,我计算完毕并且验算完毕之后,一直很纠结,要不要当着大家的面指出这个事情呢?

我总觉得他们是在欺负老人,我最讨厌欺负老人的人了.

往往在这内心矛盾,纠结选项的时候,事情就过去了.老奶奶拿着食物离开了餐厅.

这件事情我憋了很久.或许正如一个朋友说的那样,收营员不熟悉菜单,不知道这个是套餐;又或许和我的理解一样,收营员是故意的,觉得老奶奶没说起,就不用管她.

归根结底,假如收银机智能一些,自动的规划成套餐;假如我早一点儿看透这一切,帮老奶奶来点餐.说不定也不至于这么耿耿于怀了吧~

开学之各种纠结的事情

开学是个新的开始,一旦出现新的开始,大家就会忙着制定计划,这时候也会纠结迷茫.

有的人纠结事情多,不知道怎么分配精力
有的人纠结未来能做些什么
有的人同时纠结上述两项

来一句虽然有点儿难听,但是却又很真实的话

正正常常的读完大学依然不知道,自己可以做什么,自己会什么

这样的事情在我们IEC也会出现~

当然IEC的孩子不会不知道自己可以做什么,毕竟在IEC坚持下来的孩子,都能写得一手好代码,出去做个程序员,不是什么大问题

可即便如此,他们还是会在读研,出国,工作三者中纠结.

各有各的好,各有各的不好.出国比国内读研牛逼,但是费钱;读研比工作轻松,但是没有钱挣,过得很拮据

刚加入不久的孩子会在学业,课余生活和编程的权重比例上面纠结很久

听到他们在纠结的时候,我总是会觉得:这种事儿有什么好纠结的呢?

然而自己在两年前,也因为纠结这件事情,走了很多很多的弯路.

现在我来充当一下老司机吧~

没什么好纠结的!选择一个目标,不要改变,往下走,往里钻,一定会搞出些名堂的!

最后是自己纠结的事情

可能是第一次,我不纠结学习和工作的事情……

因为,

这次是玩耍和工作起了矛盾~

回想一下大学的4年,我从来没有离开过成都.没有看过大熊猫,没有去过锦里,没有去过各种名胜古迹.反而是其他城市读书的同学走过的地方更多,每次被杭州的朋友或者亲戚问起,成都好玩不?四川好玩不?

我总是无言以对,笑笑说,平时太忙了,没时间去玩.

其实,这并不是假话~

作业,工作,确实占用了很多的时间,而且把时间剁碎了,变得碎片化,找不到完整的可以用来出游的完整时间.

大概是应该我的性格,所以我没有崩溃,没有抑郁,反而津津乐道,觉得很有成就感.

可是今天听到了 起风了 里面的主题曲,突然脑子里面就是电影的画面,男主角是个牛逼的工程师,每天有画不完的图纸.但是他又经常可以在草原上看着飞机飞,可以感受风,可以用风来洗净自己,让自己可以在黑暗丑陋的时代里依旧很干净

当然,我没有那么高尚.只是我突然发现,自己并不是可以不用玩耍的人,我虽然喜欢工作,但也喜欢干净安静的风景,总给我一种很舒服的感觉,让我可以放松一下紧绷的心

今天话很多啊

最近写的代码,没啥技术含量,不说也罢,所以就来”文艺”一下自己

等这段时间的感性期过了,就可以继续post一些技术文章咯~

开始研究生活

假期还是结束了,又回到了”熟悉”的校园,搬了两天的家,出了几顿的汗水.

躺下来的时候,发现,枕头没有,先垫几块布凑活凑活吧~

刚打开研究生寝室的门,我就惊呆了,这也忒小了吧…比本科生寝室的一半还要小…

不过通宵供电和超级爽的澡堂还不错,再也不用担心回到寝室,睁眼黑.也不用担心在浴室打开龙头出来的永远是冷水~

研究生的课表真的很恼火,培养方案特别难理解,只能走一步看一步了.

虽然我已经本科毕业了,但是还是很喜欢IEC的氛围,放心,我不会走的,哈哈哈,我要来充当2016级新生.

开学总是斗志满满的.一开学脑子里面就浮现出来了超级大学霸:赫敏.觉得自己也能和她一样不仅可以搞定很多很多课,而且还能兼顾公司的产品.不过,我确定,自己是没有这个水平的…

说到公司,我确实得在后面几天考虑考虑时间的分配,不然,会辜负SUP这个职称的!

这,已经开学5天了,我数了数,还是有一大堆事情没做,看来神经还没有绷紧.

不知道刚开学的你们,感觉如何,要不要也来好好地规划一下未来这几个月的生活呢?

VKWKMDZZ

监狱生活

~.~

这次JCY的项目总让我觉得和当年乐山的项目很像,

甲方都很变态!!

工期大幅度缩短,10月->9月->8月底->周一(那一周的周一)

功能也乱改,于是最终版出来的时候,我发现,我好像写了好多接口,最后都不用了~

于是我生气的不给你装PostGRE SQL,不给你装FastCGI!

JCY的办公室里面都开着空调,然而我的汗还是没有停过,感觉把一年的汗都流完了.

OK,讲讲故事吧

第二次使用windows server,也是第一次在windows server上面安装python,双击python的window安装包,一阵狂按回车,表现出很熟练的样子,然后…

boom 安装卡在了奇怪的地方

There is a problem with widows installer package,a program run as part of setup did not finish as excepted. contact your support personnel or package vendor. 

于是,进度条就退了回去.

然后,我就试了试32位的安装包,依然卡在了奇怪的地方,又退了回去.

注意到一个细节:

以前安装的时候好像会弹出一个控制台,在安装setuptool和pip,但是这次并没有弹出,所以我估摸着,是pip的问题,所以新的安装的时候,我把pip去掉了,于是神奇的成功了~

当然,没有pip和setuptool的话,就不能安装其他的模块了呢,所以只能再打开一次安装程序,安装pip.这次竟然没有错误!哈哈哈哈哈哈

于是就这么成功的搭建完毕.

JCY的食堂我竟然觉得还蛮好吃的~~

At Last

求这个活儿赶紧结束,放我回家

django服务器从linux迁移到windows

教研室接了一个检察院的项目

老实说,这个项目坑爆了

一开始说可以提供一个全新的服务器,系统也是我们来定,于是,嫌弃windows的我在Linux上面很熟练的搭建起服务器来

直到…

有一天,他们的技术人员告诉我 “由于经费紧张,咱们就不能买新的服务器了,以前的服务器你想要什么系统都可以”

于是报了三个Windows Server的版本给我…

好吧,反正我也没的选,那就先看看改到windows上面有哪些改变吧?

Context 那边的服务器由于安全问题,是不允许接入外网的

所以PIP都不能用了,所以先把需要的包给下了吧,什么Django,Arrow,Python-DateUtil……

windows上面没有uwsgi,那就用FastCGI吧

想得美,我才不为了你单独去搞FastCGI呢,Django跑跑就够了!!

Nginx还是要的,不然我还得在django里面写下载接口,好在Nginx是有Windows版的 good job

等下

Windows 没有自带 Python ~~

为了保险起见,把32位的和64位的都下载下来

Context 他们是不允许使用优盘的,所以一切的一切,都只能使用刻录的光盘…

OK,前面基本都是废话,简单说,就是在Windows上面装环境啦…

代码改动?

嗯,上传文件接口,Django的Media_Root需要改个位置,当然Nginx的下载地址要改个位置,还有之前使用uwsgi的,现在得换成Django

windows的nginx貌似是不会自动选择conf/nginx.cfg,所以启动的时候还得手动写

nginx.exe -c conf/nginx.cfg

本来是用uwsgi的,那现在就直接Django启动到指定端口

python manage.pyc runserver 9999

哦哦哦,pyc文件的生成方法也很重要,毕竟这个项目xxxx,所以我还是选择不提供源代码给他们.

那就得编译一下再给他们

python -m py_compile manage.py

以此类推,把他们都编程编译过的文件

注意

linux下一个路径是
xxx/xxx/xxx

windows下一个路径是

X:\xxx\xxx

你以为修改django代码的时候就应该把

xxx/xxx/xxx 改成 X:\xxx\xxx

其实…还是

X:/xxx/xxx

本来打算写一个shell来自动安装的,现在看来,只能写成bat文件了~

由于msi的安装不会用bat来做,所以还是手动安装一下比较好

@echo "Start Unzip python packages"
Unzip Django-1.9.9.zip -d C:\
Unzip python-dateutil-patches-for-1.5 -d C:\
Unzip arrow-master -d C:\
@echo "Install Nginx"
Unzip nginx-1.11.3 -d C:\
@echo "install python packages"
python C:\Django-1.9.9\setup.py install
python C:\python-dateutil-patches-for-1.5\setup.py install
python C:\arrow-master\setup.py install
@echo "Copy Nginx configure file"
copy nginx.cfg C:\nginx-1.11.3\conf\nginx.cfg
@echo "build download folder"
md "C:\file"

在启动之前添加一些用户,然后就能启动了.

windows server还是适合使用windows自己的服务器~ASP.net

蓝牙测速

之前的文章中介绍过蓝牙的速度和踏频协议,在那篇文章中也顺便介绍了BLE的一些小名词.这一篇文章就来说说炫轮App 3.0中的测速模块是怎么构成的吧.

Context

炫轮App可以获取到哪些传感器的数据呢?

  1. 炫轮车灯自身的码表数据
  2. 炫轮踏频器
  3. 满足公有踏频速度协议的传感器
  4. GPS

他们的数据是些什么?

1.炫轮车灯的数据

炫轮车灯会通过蓝牙得到每转一圈需要的时间

2.炫轮踏频器

这个产品不测速,所以先不多说~

3.满足公有协议的踏频速度传感器

通过蓝牙得到在第x秒转了y圈的信息

4.GPS

GPS数据本身就携带了速度信息,只不过,如果GPS信号不强,那这个数据就会不精准

再来个附加功能

大多数的骑行App的一次运动运动时间是手动点击开始,暂停,保存来记录的.这样不是很难过嘛?我想要个自动开始,自动暂停的码表!

不用往下翻,我没写~~这只是打个广告

Structure

所以和速度有关的传感器目前为止大概是那么3类: 炫轮车灯,满足公有协议的速度计,GPS

既然,GPS那么不稳定,干脆砍掉算了.

所以问题就变成了,如何根据炫轮车灯和速度计来计算出较为准确地速度.


Structure-Context

App 可以连接两个炫轮车灯,一个速度计

码表模块是和界面分离的,把他当做服务来处理(这里我写了一个单例),这个类可以收数据,也可以被获取数据.收到的自然是解析后的炫轮数据和速度计的数据,提供获取接口的是多种单位下的:当前速度,平均速度,总路程,总运动时间,(当然还有踏频和心率,只是这里不用,所以就不说了)


全局变量

计时器1枚,记录炫轮数据的数组2枚,记录速度计的数据的数组1枚.

其他的变量这篇文章用不到,就当他不存在了

-(void)updateXuanwheelSpeed:(NSTimeInterval)timeinterval ForPosition:(int)position
{
    switch (position) {
        case 0:
        {
            //########
            [xuanWheelTemporaryList addObject:@(timeinterval)];
            xuanwheelRevolution1++;
            //########
            break;
        }
        case 1:
        {
            //########
            [xuanWheelTemporaryList addObject:@(timeinterval)];
            xuanwheelRevolution2++;
            //########
            break;
        }
        default:
            break;
    }    
}

这是处理炫轮数据的方法,把一圈的时间间隔记录下来,分开写是因为要分别记录两个轮子转的圈数,帮助计算距离.

-(void)updateSpeeder:(Float32)frequency
{
    //########
    [publicTemporaryList addObject:@(frequency)];
}

公有协议的数据,我在这之前就一经计算好了转一圈的频率.已知在第X1秒转了Y1圈,在X2秒转了Y2圈.那么频率就是(X1-X2)/(Y1-Y2)

记录完毕数据,就该计算相关信息了

Calculate

-(void)optimizeSpeed
{
    if (######) {
        //....
    }else
    {
        //#####
        //Reason1:只有一组数据,还怎么计算平均速度;Reason2:#####
        if (publicTemporaryList.count>1||xuanWheelTemporaryList.count>1) {
            int count=0;
            //time用来存放这1秒内平均的转过1圈需要的时间
            NSTimeInterval time=0;
            for(NSNumber *item in publicTemporaryList)
            {
                //这里来计算公有协议的速度信息
                if (item.floatValue>5) {
                    //这么慢还是忽略吧
                    continue;
                }
                if (item.floatValue<=0) {
                    //谁解析的数据!!
                    continue;
                }
                time+=1.f/item.floatValue;
                count++;
            }
            if (publicTemporaryList.count>0) {
                if (publicTemporaryList.count>1) {
                    [publicTemporaryList removeObjectsInRange:NSMakeRange(0, publicTemporaryList.count-1)];
                }else
                {
                    [publicTemporaryList removeAllObjects];
                }
                //算完删光,留下一组数据,为下一秒做帮助,防止突变
            }
            for(NSNumber *item in xuanWheelTemporaryList)
            {
                //这里来计算炫轮的速度信息
                if (item.floatValue>5) {
                    continue;
                }
                if (item.floatValue<=0) {
                    continue;
                }
                time+=item.floatValue;
                count++;
            }
            if (xuanWheelTemporaryList.count>0) {
                if (xuanWheelTemporaryList.count>1) {
                    [xuanWheelTemporaryList removeObjectsInRange:NSMakeRange(0, xuanWheelTemporaryList.count-1)];
                }else
                {
                    [xuanWheelTemporaryList removeAllObjects];
                }
                //Again,留一组数据来备用
            }
            if (count==0||time==0) {
                currentS=0;
            }else
                currentS=[PublicResource sharedObject].roundLength*count/time*3600;
                //这个就是这1秒的速度了咯
        }else
        {
            //#####
        }
    }
//最新的最高速度
    if (currentS>max_inner_speed) {
        max_inner_speed=currentS;
    }
//防止速度突变
    if (currentS>lastSpeed+30) {
        lastSpeed=currentS;
        currentS=0;
    }
//强制限速
    if (currentS>80) {
        currentS=80;
    }
}

差不多,速度就是这么计算的,在实际测试中,感觉还是满准确地~

Python 处理字节流

python处理字节的点点滴滴

python有多爽,我就不说了,反正想得到的事情,基本都有办法用python来解决,当然还有一个很重要的优点: 写python不用编译~

Context

  1. 之前毕业设计做的是SMS4的实现与安全性分析.安全性分析是什么鬼?查了查书,好像是用有特征的原文来进行测试……,具体的我也忘记了.当时就琢磨,怎么样可以做一个酷炫的安全性分析呢?
  2. 公司的新产品是一款智能硬件,它会通过TCP给服务器发送指令(当然不是熟悉的HTTP请求,是传说中的字节流)

Requirement

  1. commands+re : 这个组合可以轻轻松松的提取出命令行程序的有用信息
  2. binascii : 这个东西可以轻松的产生各种各样和二进制有关或者和ascii有关的字符串
  3. struct : 超级牛逼的解包模块,瞬间把一个字节流解成元组

OK, Let’s go

commands+re

commands.output(cmd),cmd是shell命令我也不知道windows的命令可以不可以~~
这一条可以把运行结果返回出来(字符串形式)

然后用re来做正则匹配,再搭配一些split(),replace()之类的操作字符串的函数,就可以提取出一个命令行程序中有用的信息了.

binascii

binascii.b2a_hex(data)
binascii.hexlify(data)

data就是原始字符串,假设就是”abcde”
返回的也是字符串,不过是16进制的字符串”6162636465”

binascii.a2b_hex(hexstr)
binascii.unhexlify(hexstr)

这个函数就是反过来的
‘hexstr’是16进制的字符串,假定说是”6162636465”
返回的字符串就是他们所对应的ascii的字符,就是”abcde”

这有啥用嘞?

做测试!

让下位机发送一条指令可能还蛮复杂的,或者蛮浪费资源什么的,这样调试服务器代价会比较大.使用binascii来制作测试数据再好不过了.

假设下位机发送的数据是”0xaa,0xbb,0xcc,0x12,0x34,0xdd,0xee,0xff”

我们就可以模拟一条

a="aabbcc1234ddeeff"
b=asciibin.a2b_hex(a)

b就是模拟了服务器收到socket字节流.真方便

struct

收到字节流,根据协议,得知其中的8个字节是double,4个字节是float.

嗯,字节转整形还是有办法的,那么转成浮点型应该怎么转呢??

import struct吧

将python数据封装成字节流

struct.pack(format,argvs...)

将字节流解析成python数据类型

struct.unpack(format,string)

format很有意思,简单说就是解析规则.

比如解析成float,就写’f’

解析成double,就写成’d’

那么一个数据流里面既有float又有double怎么办呢?

嗯,截取字符串嘛!

struct.unpack('f',s1[0:4])
struct.unpack('d',s1[4:12])

哈哈,我一开始也是这样写的,后面章鱼兄跟我说,format里面可以写很多东西

struct.unpack('fd',s1)

这个会直接返回(r1,r2),其中r1就是那个float,r2就是那个double

真的好方便

附表一张

format 类型
f float
d double
c char
i int
l long
3s 长度为3的字符串
4s 长度为4的字符串
5s 长度为5的字符串

至于struct.pack()嘛,既然format都有了,那么应该不难理解了,在python里面试一试就明白了

微信服务号的消息处理

新产品会和微信公众号有交互,所以就来尝尝鲜,做一做微信公众号的各种东西.

首先:注册,认证,bla bla bla,设置一堆东西

在获得了开发权限之后,就可以仔细阅读开发文档了.

和微信的交互有两种:

  1. 服务器给微信发送消息
  2. 微信给服务器发送消息

说说他们的区别.

服务器给微信发消息,很方便,发表单格式的就行了,微信回复的是json格式的

微信服务器主动给服务器发消息,就很恶心了.

  1. 如果在配置页面设置了消息模式是加密的,那么收到了微信的消息之后需要先对包进行解密,才能得知微信发送的消息的确切内容.
  2. 微信的每次请求,都需要按照和它一样的变态格式回复.
  3. 还得验证验证消息到底是不是微信发的~

先来看看微信给服务器发的是什么样子的东西

<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
 <FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
 <MsgType><![CDATA[text]]></MsgType>
 <Content><![CDATA[this is a test]]></Content>
 <MsgId>1234567890123456</MsgId>
 </xml>

哦,这个是没加密的,加密的更难看…

应该用正则来提取内容吧,我把他们都写了出来,方便方便

REGEX_TOUSERNAME = r'\<ToUserName\>\<\!\[CDATA\[(.*)\]\]\>\<\/ToUserName\>'
REGEX_FROMUSERNAME = r'\<FromUserName\>\<\!\[CDATA\[(.*)\]\]\>\<\/FromUserName\>'
REGEX_MSGTYPE = r'\<MsgType\>\<\!\[CDATA\[(.*)\]\]\>\<\/MsgType\>'
REGEX_EVENT = r'\<Event\>\<\!\[CDATA\[(.*)\]\]\>\<\/Event\>'
REGEX_EVENT_KEY = r'\<EventKey\>\<\!\[CDATA\[(.*)\]\]\>\<\/EventKey\>'
REGEX_SCANTYPE = r'\<ScanType\>\<\!\[CDATA\[(.*)\]\]\>\<\/ScanType\>'
REGEX_SCANRESULT = r'\<ScanResult\>\<\!\[CDATA\[(.*)\]\]\>\<\/ScanResult\>'
REGEX_CREATETIME = r'\<CreateTime\>(.*)\<\/CreateTime\>'
REGEX_CONTENT = r'\<Content\>\<\!\[CDATA\[(.*)\]\]\>\<\/Content\>'

还傻乎乎的搞了个类来解析这坨东西

class wechat_xml_object(object):
    def __init__(self, xml):
        self.origin = str(xml)

    @property
    def toUserName(self):
        return self.returnValueForKey(REGEX_TOUSERNAME)

    @property
    def fromUserName(self):
        return self.returnValueForKey(REGEX_FROMUSERNAME)

    @property
    def msgType(self):
        return self.returnValueForKey(REGEX_MSGTYPE)

    @property
    def event(self):
        return self.returnValueForKey(REGEX_EVENT)

    @property
    def eventKey(self):
        return self.returnValueForKey(REGEX_EVENT_KEY)

    @property
    def scanType(self):
        return self.returnValueForKey(REGEX_SCANTYPE)

    @property
    def scanResult(self):
        return self.returnValueForKey(REGEX_SCANRESULT)

    @property
    def createTime(self):
        return self.returnValueForKey(REGEX_CREATETIME)

    @property
    def content(self):
        return self.returnValueForKey(REGEX_CONTENT)

    def returnValueForKey(self, regex):
        match = re.search(regex,self.origin)
        if match:
            return match.group(1)
        else:
            return None

虽然我知道有kissXML这种神器,但是我得装……

上面是把明文的XML解包出来,现在我们把想回复的数据给封装起来

class wechat_xml_builder(object):
    def __init__(self, data_type):
        self.type = data_type
        self.content = ''
        self.contains = []

    def addObject(self, key, value, data_mark):
        if key in self.contains:
            return False
        if data_mark:
            self.content += '<{0}><![CDATA[{1}]]></{2}>'.format(key, value, key)
        else:
            self.content += '<{0}>{1}</{2}>'.format(key, value, key)
        self.contains.append(key)
        return True

    def add_default_from(self, dictionary):
        fromUser = dictionary['toUser']
        openid = dictionary['openid']
        createTime = dictionary['createTime']
        self.addObject('ToUserName', openid, True)
        self.addObject('FromUserName', fromUser, True)
        self.addObject('CreateTime', createTime, False)

    def exportString(self):
        return '<xml>' + self.content + '</xml>'

我感觉这个类用起来还是蛮简单的.

至于说加密和解密,微信还是很良心的提供了一个库,可以直接操作.

下次再整理一下微信开发中其他的坑儿吧

ios 命令行单元测试

在开发iOS大项目的时候,由于模块太多,每个功能的模块也太多,在后续测试调试的时候,遇见了bug,就会花比较长的时间去定位bug所在的类或者所在的函数.如果使用了单元测试便可以缩小查找bug的范围.

XCode自带单元测试的命令行工具,可以编写UnitTest文件来自动测试工程,下面来介绍一下它的一些些用法.

首先得确定有xcodebuild命令(跳过,都是iOS开发者,肯定装了XCode咯)

$xcodebuild

肯定没这么简单,假设当前目录是有XCode的工程的,那么这个命令就会执行与在xcode里面点击build一样的效果.

使用xcodebuild运行测试

xcodebuild 命令就是执行了 XCode 里面的build,不过这个命令有一些其他的参数,用法太多这儿就不解释了.总之给他加点儿不同的参数就可以做不同的事情.使用 test 来测试.用-scheme指定操作的scheme.用 -destination 指定target.要在本地 iPhone 5模拟机 “iPhone 5” 测试某个工程的某个测试,使用如下命令来指定目标和架构:

xcodebuild test -project rct6updater.xcodeproj -scheme rct6updaterTest -destination 'platform=iOS ,name=iPhone 5'

如果想对真机操作,可以按照名称或id.比如,我的测试机叫做”VikingWarlock 5”,可以这样来测试代码:

xcodebuild test -workspace xuanwheel/xuanwheel.xcworkspace -scheme xuanwheelTests -destination 'platform=iOS,name=VikingWarlock 5'

测试也可以在 iOS模拟器上运行.使用模拟器可以应对不同的外形因素和操作系统版本.例如:

xcodebuild test -workspace xuanwheel/xuanwheel.xcworkspace -scheme xuanwheelTests -destination 'platform=iOS Simulator,name=iPhone,0S=7.0' -destination 

如果测试失败,xcodebuild 会有红色的 FAIL 提示,修改修改测试的代码,多加一些提醒,这样可以更方便的定位错误点.

等下

之前是-project的,怎么突然使用了-workspace嘞?因为我的工程使用cocoaPods了,所以测试的时候要使用workspace才行.