前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >3个bytes, 怎么接?

3个bytes, 怎么接?

作者头像
iOS Development
发布2019-02-14 17:45:50
1.5K0
发布2019-02-14 17:45:50
举报
文章被收录于专栏:Antony iOS Development

这个问题,对于熟悉C语言的人来说,答案很简单。

不过对我这种不熟悉C语言的人,在坑中「摸索」良久,先后尝试好几种方法。

其实,生活中很多事情也像编程:解决问题的办法有万千,但某些方法确实是比较优雅的。

在这个「摸索」的过程中,也是一个蛮有趣的过程,遂记之。

问题

在过去的项目中,所接触到的「协议/指令(protocol/command)」,数据大多是以1个byte(字节),2个bytes,4个bytes,8个bytes......为单位进行切割组合的。类似如下指令:

xxxCommand:

  • [1]commandID# 0xFF // 方括号数字:该数据所占字节(byte)数
  • [1]week:
    • bit0:Mon
    • bit1:Tue
    • bit2:Wed
    • bit3:Thu
    • bit4:Fri
    • bit5:Sat
    • bit6:Sun
    • bit7:不使用
  • [4]ip address
  • [2]reserved
  • [1]checksum

这时候,1 byte的数据,用UInt8接,2 bytes的数据,用UInt16接,4 bytes的数据,用UInt32接——一切都很美好。

关于UInt8UInt16UInt32等数据类型,在MacTypes.h中,有相关说明:

代码语言:javascript
复制
/********************************************************************************

    Base integer types for all target OS's and CPU's
    
        UInt8            8-bit unsigned integer 
        SInt8            8-bit signed integer
        UInt16          16-bit unsigned integer 
        SInt16          16-bit signed integer           
        UInt32          32-bit unsigned integer 
        SInt32          32-bit signed integer   
        UInt64          64-bit unsigned integer 
        SInt64          64-bit signed integer   

*********************************************************************************/

也有具体定义:

代码语言:javascript
复制
    typedef unsigned char                   UInt8;
    typedef signed char                     SInt8;
    typedef unsigned short                  UInt16;
    typedef signed short                    SInt16;

而最近,遇到一种新情况:硬件那边发过来的数据,是3个bytes为单位的数据——有3个bytes的mac地址(截取了mac地址的一半,发送/广播给手机端),也有3个bytes的大气压数据。类似如下数据格式:

xxxCommand:

  • [2]UUID
  • [3]mac // 截取了mac地址的一半
  • [3]presure // 据闻大气压数值,2 bytes表示不完,4 bytes又太多了,所以定义了3 bytes~
  • ...

于是,就有了此文的标题:系统没有UInt24,3个bytes的数据,怎么接?(不要怪我问那么白痴的问题)

先贴出我所认为的「最优雅」解决方案,再描述一下我「踩坑」的心路历程。

Solution:

1.先定义一个UInt24

关于如何定一个UInt24,StackOverFlow上有人提问:How to define 24bit data type in C?

尝试过某个回答者的做法:

代码语言:javascript
复制
    struct int24{
        unsigned int data : 24;
    };

经验证,这个写法不work,因为这个类型还是占4个bytes(用sizeof()函数打印验证),这样拿去接数据,会把别人的那个byte也装过来,后面的数据就会乱掉。

那试着仿照MacTypes.h里的定义,定义如下:

typedef unsigned char[3] UInt24;

这样OK吗?事实上,也有问题,系统会报如下错误:

Brackets are not allowed here; to declare an array, place the brackets after the identifier. Replace '[3] UInt24' with ' UInt24'

报错说得很清楚:方括号放错地方。要定义一个array(数组/数列),方括号应该放在新定义类型名称的后面:

typedef unsigned char UInt24[3];

这样就OK了。

2.定义command

有了对应的「容器」装数据,那接下来可以定义command(指令)了:

代码语言:javascript
复制
    typedef struct __attribute__((packed)) {
        UInt16 UUID;
        UInt24 mac; // 用自己定义的UInt24接数据 
        UInt24 pressure;
    } D2MXxxCommand; // D2M: Device to Mobile phone

其实到这里,基本就把问题解决了,后面该干嘛干嘛了。但是在获取到数据,显示出来的过程中,有些写法还是刷新了我的认知(主要还是自己对C语言不熟)。

  • 将mac地址的3个bytes转为十六进制形式的字符串

一开始我用了很复杂的方法,网上查到的方法也大都比较复杂(下面会有叙述)。

而实际上,只需要一行就OK了:

NSString *macHexString = [NSString stringWithFormat:@"%02X%02X%02X", cmd->mac[0], cmd->mac[1], cmd->mac[2]];

正常的占位符应该是%X,而这里中间的02,表示该十六进制数限制固定两位数。

目的是预防这种情况:当第一个byte是小于16的数,只输出1位。例:0x014B5C,如果是用%X,则只输出14B5C;而用%02X,则可输出014B5C

直接用%02X,就无须再额外判断第一个byte长度是否小于1,如果小于1,再在前面补个零……

备注:这个写法,参考了以前公司boss的写法。

  • 将pressure(大气压)的3个byte转为十进制浮点数

比如,我们用UInt24接了一个数:0x0185B2(大气压),要转换为十进制的浮点数:

代码语言:javascript
复制
   // 大气压值 = 十进制值 / 100 
   float pressure = ((cmd->pressure[0]<<16) + (cmd->pressure[1]<<8) + cmd->pressure[2]) * 0.01; 

因为大气压的值,同事定义为:该十六进制数的十进制形式再除以100。所以,思路就是将该3个独立的byte组合成一个完整的数,再转十进制就OK了。

cmd->pressure[0]<<16的意思,就是将pressure中第一个byte左移16bit(位),也就是左移2个byte(字节)的位置——所以操作完后,pressure中第一个byte,从右往左数,就变成是第三个byte了。如下图所示(将0x01往左移16bit(位)):

将0x01往左移16bit(位)

cmd->pressure[1]<<8也做了类似的事情,将pressure中第二个byte左移8bit(位),也就是左移1个byte(字节)的位置,如下图(将0x85往左移8bit(位)):

将0x85往左移8bit(位)

最后把他们加起来,就是我们要的数了:997.62(Hpa)

另外,

代码语言:javascript
复制
float pressure = ((cmd->pressure1 * 65536) + (cmd->pressure2 * 256) + cmd->pressure3) * 0.01

也有同样的效果,但个人认为这样操作,没有用<<操作符直观易懂。

以上,就是关于「3个bytes, 怎么接?」的回答。

接下来描述一下踩过的「坑」。

其他的尝试:

1.定义成3个单独的UInt8

最开始想到,就是单独定义3个UInt8来接数据:

代码语言:javascript
复制
    UInt8 pressure1;
    UInt8 pressure2;
    UInt8 pressure3; 

写完这个还「怨气满满」地想:为什么非得要传3个bytes过来,多一个、少一个不行吗?

这埋怨虽是戏言,但是从「产品、消费者」的角度思考,又可以延伸到另外一件事:我们写的框架、软件、产品,有一个重要的准绳——「把复杂留给自己,把简单留给客户」。大部分人拿到一个东西,肯定希望是「插电即用」的,并不希望东折腾西捣鼓才能使用。

扯远了,继续:拿到这3个bytes后,第一反应就是NSData对象——于是就变着法把这三个bytes捣鼓成NSData对象:

代码语言:javascript
复制
    // 将3个bytes重新组合起来
    Byte pressureBytes[] = {cmd->pressure1, cmd->pressure2, cmd->pressure3};
    // 转为NSData
    NSData *pressureData = [NSData dataWithBytes:pressureBytes length:sizeof(pressureBytes)];

然后又想办法将NSData对象捣鼓成十六进制字符串,或者是十进制的浮点数——硬生生把一行代码搞定的事情,写成了几十行。

2.定义成UInt8 mac[3]

定义成UInt8 mac[3]形式,其实这和最上面定义UInt24是类似的,只是最上面的方法起了一个更易于理解的UInt 24而已。

3.用UInt32接数据,再截前面3个bytes

这种方法也work,不过要注意,UInt32接回来的数据是4bytes,最后一个byte要进行正确处理(正确给到其他需要的地方),否则后面数据的读取全会乱(少一个byte)。

另外,还试过定义成char *mac形式,不work,因为sizeof(cmd->mac)是8,一个指针占用了8 bytes,并不是我们想要的3bytes。

所以,

还要继续熟悉C语言。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017.09.26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题
  • Solution:
    • 1.先定义一个UInt24
      • 2.定义command
      • 其他的尝试:
        • 1.定义成3个单独的UInt8
          • 2.定义成UInt8 mac[3]
            • 3.用UInt32接数据,再截前面3个bytes
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档