BACnet(二)MS/TP AutoMAC解析

本文详细介绍了楼宇自动控制网络(BACnet)中的MSTP协议自动生成MAC地址的状态机实现原理及过程。通过分析代码,解释了如何在不同状态下随机选取合法的MAC地址并确认其唯一性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

楼宇自动控制网络数据通信协议BACnet(A Data Communication Protocol for Building Automation and Control Network)由美国供热、制冷与空调工程师协会组织的标准项目委员会135P于1995年6月正式通过制定。标准编号为ANSI/ASHRAE Standard l35-1995,同年12月正式成为美国国家标准,并得到欧盟标准委员会的承认,成为欧盟标准草案。2000年1月ISO组织TC205委员会的15个国家(中国、法国、日本、英国、美国等)的代表一致通过决议,将BACnet作为“委员会草案”进行广泛评议,适当修改后列为“国际标准化草案”,最后成为国际标准。
关于更多的介绍,自行百度。
今天主要说一下BACnet MSTP协议中的自动生成MAC地址状态机。
状态图转换
状态图
从代码说起直接上代码说话。

void dlmstp_automac_hander(
    void)
{
    static AUTOMAC_STATE state = AUTOMAC_STATE_IDLE;
    uint8_t mac = 0;
    uint32_t serial_number = 0;
    uint16_t vendor_id = 0;
    bool take_address = false;
    bool start_over = false;

    switch (state) {//state 是静态局部变量,初始化为空闲态
        case AUTOMAC_STATE_IDLE:
            if ((MSTP_Flag.ReceivedValidFrame) ||
                (MSTP_Flag.ReceivedValidFrameNotForUs)) {
                MSTP_Flag.ReceivedValidFrame = false;
                MSTP_Flag.ReceivedValidFrameNotForUs = false;
                //接收到了一个正常的bacnet帧,记录此帧的源地址,标记为已使用
                automac_emitter_set(SourceAddress);
                //判断帧的类型
                switch (FrameType) {
                    case FRAME_TYPE_TOKEN:
                        //如果是令牌帧,标记此地址有令牌使用权
                        automac_token_set(SourceAddress);
                        break;
                    case FRAME_TYPE_POLL_FOR_MASTER:
                        //如果是轮询主站帧,标记目的MAC被轮询到
                        automac_pfm_set(DestinationAddress);
                        break;
                    default:
                        //不处理其他类型帧,因为其他类型帧内容与本站无关
                        break;
                }
            } else if (MSTP_Flag.ReceivedInvalidFrame) {
                //接收到一个错误帧,清空标志位。
                // TODO:个人认为这个可以在接收时过滤,没必要放到这一步。
                MSTP_Flag.ReceivedInvalidFrame = false;
            } else if (automac_pfm_cycle_complete()) {
                //如果本站已经听完一圈轮询,那么就从未使用地址中随机选一个作为自己MAC地址
                mac = automac_free_address_random();
                if (automac_free_address_valid(mac)) {
                    //选中的MAC合法,就设置为本站MAC,跳转到轮询状态
                    automac_address_set(mac);
                    state = AUTOMAC_STATE_PFM;
                } else {
                    //选中地址不合法,重新初始化,再次查找
                    automac_init();
                    state = AUTOMAC_STATE_IDLE;
                }
            } else if (rs485_silence_elapsed(automac_time_slot())) {
                //如果超时没有收到任何帧,此时可能总线上无主站,随机选一个地址作为本站地址
                //跳转到测试状态
                SourceAddress = automac_address();
                state = AUTOMAC_STATE_TESTING;
            }
            break;
        case AUTOMAC_STATE_PFM:
            if ((MSTP_Flag.ReceivedValidFrame) ||
                (MSTP_Flag.ReceivedValidFrameNotForUs)) {
                MSTP_Flag.ReceivedValidFrame = false;
                MSTP_Flag.ReceivedValidFrameNotForUs = false;
                /* store stats until we get a MAC */
                switch (FrameType) {
                    case FRAME_TYPE_POLL_FOR_MASTER:
                        mac = automac_address();
                        if (mac == SourceAddress) {
                            //收到源地址等于本站MAC,说明本站MAC已被使用
                            //重新初始化,重选择一个空闲MAC
                            automac_init();
                            state = AUTOMAC_STATE_IDLE;
                        } else if (mac == DestinationAddress) {
                            //如果轮询到本站,响应轮询
                            MSTP_Send_Frame
                                (FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER,
                                SourceAddress, mac, NULL, 0);
                            state = AUTOMAC_STATE_TOKEN;
                        }
                        break;
                    case FRAME_TYPE_TEST_REQUEST:
                        mac = automac_address();
                        if ((mac == DestinationAddress) ||
                            (mac == SourceAddress)) {
                            //测试帧用来找重复的,此帧目的地址源地址相同
                            //故只有地址重复的站可以收到。收到即为重复
                            //所以,重新初始化
                            automac_init();
                            state = AUTOMAC_STATE_IDLE;
                        }
                        break;
                    case FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY:
                    case FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY:
                    case FRAME_TYPE_TEST_RESPONSE:
                    case FRAME_TYPE_TOKEN:
                        mac = automac_address();
                        if (mac == SourceAddress) {
                             //收到源地址等于本站MAC,说明本站MAC已被使用
                            //重新初始化,重选择一个空闲MAC
                            automac_init();
                            state = AUTOMAC_STATE_IDLE;
                        }
                        break;
                    default:
                        break;
                }
            } else if (MSTP_Flag.ReceivedInvalidFrame) {
                MSTP_Flag.ReceivedInvalidFrame = false;
            } else if (rs485_silence_elapsed(automac_time_slot())) {
                /* start over again */
                automac_init();
                state = AUTOMAC_STATE_IDLE;
            }
            break;
        case AUTOMAC_STATE_TOKEN:
            if ((MSTP_Flag.ReceivedValidFrame) ||
                (MSTP_Flag.ReceivedValidFrameNotForUs)) {
                MSTP_Flag.ReceivedValidFrame = false;
                MSTP_Flag.ReceivedValidFrameNotForUs = false;
                switch (FrameType) {
                    case FRAME_TYPE_TOKEN:
                        mac = automac_address();
                        if (mac == SourceAddress) {
                            /* start over again */
                            automac_init();
                            state = AUTOMAC_STATE_IDLE;
                        } else if (mac == DestinationAddress) {
                            //收到令牌,转到测试状态
                            state = AUTOMAC_STATE_TESTING;
                        }
                        break;
                    default:
                        /* start over again */
                        automac_init();
                        state = AUTOMAC_STATE_IDLE;
                        break;
                }
            } else if (MSTP_Flag.ReceivedInvalidFrame) {
                MSTP_Flag.ReceivedInvalidFrame = false;
            } else if (rs485_silence_elapsed(automac_time_slot())) {
                /* start over again */
                automac_init();
                state = AUTOMAC_STATE_IDLE;
            }
            break;
        case AUTOMAC_STATE_TESTING:
            /* I have the token - confirm my MAC with a quick test */
            mac = automac_address();
            vendor_id = Device_Vendor_Identifier();
            encode_unsigned16(&AutoMAC_Test_Buffer[0], vendor_id);
            serial_number = Device_Object_Instance_Number();
            encode_unsigned32(&AutoMAC_Test_Buffer[2], serial_number);
            MSTP_Send_Frame(FRAME_TYPE_TEST_REQUEST, SourceAddress, mac,
                &AutoMAC_Test_Buffer[0], 6);
            state = AUTOMAC_STATE_CONFIRM;
            break;
        case AUTOMAC_STATE_CONFIRM:
            /* we may timeout if our chosen MAC is unique */
            if (MSTP_Flag.ReceivedInvalidFrame) {
                MSTP_Flag.ReceivedInvalidFrame = false;
                start_over = true;
            } else if ((MSTP_Flag.ReceivedValidFrame) ||
                (MSTP_Flag.ReceivedValidFrameNotForUs)) {
                MSTP_Flag.ReceivedValidFrame = false;
                MSTP_Flag.ReceivedValidFrameNotForUs = false;
                mac = automac_address();
                if ((mac == DestinationAddress) && (DataLength >= 6)) {
                    decode_unsigned16(&InputBuffer[0], &vendor_id);
                    decode_unsigned32(&InputBuffer[2], &serial_number);
                    if ((vendor_id == Device_Vendor_Identifier()) &&
                        (serial_number == Device_Object_Instance_Number())) {
                            //是本站,忽略
                            take_address = true;
                    } else {
                        //收到测试回复帧,表明此地址已经被另一个站使用,重新初始化
                        start_over = true;
                    }
                } else {
                    //收到其他帧,出现多令牌,重新初始化
                    start_over = true;
                }
            } else if (rs485_silence_elapsed(300)) {
                /* use maximum possible value for Treply_timeout */
                /* in case validating device doesn't support Test Request */
                /* no response and no collission */
                // 没有收到测试响应帧,表明此地址可用
                take_address = true;
            }
            if (take_address) {
                /* take the address */
                //加入令牌环成功
                //因为之前已经至少间听过一整圈,所以已经知道下一站地址
                //令牌传递给下一站
                This_Station = automac_address();
                DestinationAddress = automac_next_station(This_Station);
                if (DestinationAddress < 128) {
                    MSTP_Send_Frame(FRAME_TYPE_TOKEN, DestinationAddress,
                        This_Station, NULL, 0);
                }
                //进入空闲态
                // TODO: 此处感觉不进入也可以,反正加入令牌环后不会再次进入这个函数了
                // TODO: 更别提进入哪个状态。
                state = AUTOMAC_STATE_IDLE;
            } else if (start_over) {
                /* start over again */
                automac_init();
                state = AUTOMAC_STATE_IDLE;
            }
            break;
        default:
            break;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值