楼宇自动控制网络数据通信协议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;
}
}