Tbox电源模式
当 Tbox 进行电源模式切换时应该通知 CSP。在车辆熄火(ignition off)后 Tbox 有不同的电源模式。在执行远程车辆信息服务时电源模式将对用户体验产生影响。
Tbox 应该支持下面的电源模式:
- Normal/working
全功能,Tbox 与 CSP能够主要通过 mqtt 进行通信,如果 mqtt 通道无效,部分高优先级业务应该转到 SMS 通道。 - Standby
Tbox 处于低耗能状态,此状态支持 SMS、XCALL、MCU唤醒等。stanby 默认时长为熄火后 10 天。 - sleep_poll
在 Sleep 模式下,没有功能。定义一个轮询计划用来周期性唤醒tbox,并检查云端是否有待执行服务请求,与此同时,Tbox 将上报下一次唤醒时间以及一些车辆基本状态信息到 CSP。
对于处于working模式的Tbox,CSP将仅仅发送MQTT消息。sleep_poll有两个阶段,阶段1持续 5 天,轮询频率周期为 2 hours;阶段2持续 10 天,轮询周期为 4 小时。 - off
无功能,无轮询,最小功耗。
Tbox应该通知 CSP 如下信息:
- 从 stanby / sleep_poll 到 working状态,当进入working状态后,应该通知到CSP。
- 从 working 到 standby,当离开working 到 standby时,tbox应该通知 CSP 说明其将要进入 standby。
- 从 stanby 到 Sleep_poll,当离开 standby进入 sleep_poll时,CSP应该被通知下一次被唤醒的时间。
- 在 sleep_poll时(sleep--polling--sleep...),在polling阶段结束时(在确认 CSP没有待执行服务请求时),CSP应该被通知下一次唤醒的时间。
- 当进入 off 状态时,CSP应该被通知不再有轮询计划。此状态将在轮询状态结束后进入,或 CarMode 模式改变。对于部分车型来说,当tbox上传 tbox状态(例如电量状态)到csp时,csp收到电量状态后,将检查是否电量过低,如果是,CSP将以告警的形式通知移动 APP。
电源模式状态转换图
事件清单
- working->standby
No Telematics Business in past 2 minutes
&& 接收到MCU请求modem进入standby消息
MCU 根据 CAN_bus Sleep && KL 15 Off && USB off 等满足休眠条件后通知 modem 进入 standby。
- standby->work
Incoming SMS :modem唤醒后通知二次开发唤醒原因;
|| Incoming Call :modem唤醒后通知二次开发唤醒原因;
|| RTC Timer expired :提供设置/取消RTC接口,RTC唤醒通知唤醒原因;(modem RTC)
|| 或wakeup_in 有上延, mcu消息通知modem进入 working状态
wakeup_in 包含了以下具体事件:
|| BTN pressed :XCALL案件,MCU唤醒;
|| Movement :MCU唤醒;
|| WAN Antenna removal :暂时不做;
|| KL30 removal :MCU处理
|| CAN_bus Normal :MCU硬件唤醒modem
|| KL15 On :MCU硬件唤醒modem
|| USB On (一体机是通过mcu控制hu电源模式,故hu开机时,mcu一定处于working状态,故此时也会唤醒模块,从而使 usb on)
stanby->sleep polling
RTC Timer expired:standby的RTC到期sleep-> working
MCU拉高模块Power_key引脚,mcu通知模块进入working状态sleep_poll ->sleep
轮询结束。-
sleep_poll->working
此状态转换分为两种情况:- subsleep -> working
mcu拉高power_key引脚,mcu消息通知切换到working状态 - subworking -> working
mcu 消息通知切换到working状态(此场景是modem处于sleep_polling下的subworking时(通常此时mcu应该处于休眠态),但如果mcu被车辆事件唤醒,虽然modem已经处于working状态,但仍然需要通知modem进入working状态的消息)
|| incoming SMS
|| incoming xcall
|| csp有待执行请求
- subsleep -> working
mcu启动modem流程
代码实现
#include <iostream>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/functor_row.hpp>
namespace
{
namespace msm = boost::msm;
namespace msmf = boost::msm::front;
namespace mpl = boost::mpl;
// ----- Events
struct EventMCU {};
struct EventSMS {};
struct EventCall{};
struct EventWrkToStby{};
struct EventStbyToSubSleep {};
struct EventSubWrkToSubSleep{};
struct EventSubWrkToSleep{};
// ----- State machine
struct TemSm_:msmf::state_machine_def<TemSm_>
{
// InitStates
struct Init:msmf::state<> {};
//Choices
struct Choice_:msmf::state<>{};
//Working
struct Working:msmf::state<>
{
// Entry action
template <class Event,class Fsm>
void on_entry(Event const&, Fsm&)
{
std::cout << "Working::on_entry()" << std::endl;
}
// Exit actions
template <class Event,class Fsm>
void on_exit(Event const&, Fsm&)
{
std::cout << "Working::on_exit()" << std::endl;
}
};
//standby
struct Standby:msmf::state<>
{
// Entry action
template <class Event,class Fsm>
void on_entry(Event const&, Fsm&)
{
std::cout << "Standby::on_entry()" << std::endl;
}
// Exit actions
template <class Event,class Fsm>
void on_exit(Event const&, Fsm&)
{
std::cout << "Standby::on_exit()" << std::endl;
}
};
//Sleeping
struct Sleeping:msmf::state<>
{
// Entry action
template <class Event,class Fsm>
void on_entry(Event const&, Fsm&)
{
std::cout << "Sleeping::on_entry()" << std::endl;
}
// Exit actions
template <class Event,class Fsm>
void on_exit(Event const&, Fsm&)
{
std::cout << "Sleeping::on_exit()" << std::endl;
}
};
struct SleepPoll_:msmf::state_machine_def<SleepPoll_>
{
// Entry action
template <class Event,class Fsm>
void on_entry(Event const&, Fsm&)
{
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, TemSm_>::value));
std::cout << "SleepPoll::on_entry()" << std::endl;
}
// Exit actions
template <class Event,class Fsm>
void on_exit(Event const&, Fsm&)
{
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, TemSm_>::value));
std::cout << "SleepPoll::on_exit()" << std::endl;
}
//SubSleep
struct SubSleep:msmf::state<>
{
// Entry action
template <class Event,class Fsm>
void on_entry(Event const&, Fsm&)
{
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, SleepPoll_>::value));
std::cout << "SubSleep::on_entry()" << std::endl;
}
// Exit actions
template <class Event,class Fsm>
void on_exit(Event const&, Fsm&)
{
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, SleepPoll_>::value));
std::cout << "SubSleep::on_exit()" << std::endl;
}
};
//Sleeping
struct SubWork:msmf::state<>
{
// Entry action
template <class Event,class Fsm>
void on_entry(Event const&, Fsm&)
{
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, SleepPoll_>::value));
std::cout << "SubWork::on_entry()" << std::endl;
}
// Exit actions
template <class Event,class Fsm>
void on_exit(Event const&, Fsm&)
{
BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, SleepPoll_>::value));
std::cout << "SubWork::on_exit()" << std::endl;
}
};
struct Entry:msmf::entry_pseudo_state<> {};
struct ExitRTC:msmf::exit_pseudo_state< EventSubWrkToSleep > {};
struct ExitSMS:msmf::exit_pseudo_state< EventSMS > {};
struct ExitCall:msmf::exit_pseudo_state< EventCall > {};
typedef mpl::vector<SubWork> initial_state;
struct transition_table:mpl::vector<
msmf::Row< Entry, boost::any, SubSleep, msmf::none, msmf::none >,
msmf::Row< SubWork, EventSubWrkToSubSleep, SubSleep, msmf::none, msmf::none >,
msmf::Row< SubWork, EventSubWrkToSleep, ExitRTC, msmf::none, msmf::none >,
msmf::Row< SubWork, EventCall, ExitCall, msmf::none, msmf::none >,
msmf::Row< SubWork, EventSMS, ExitSMS, msmf::none, msmf::none >
>{};
};
// Set initial SourceState
typedef Init initial_state;
typedef msm::back::state_machine<SleepPoll_> SleepPoll;
struct GuardCondition
{
template <class Event, class Fsm, class SourceState, class TargetState>
bool operator()(Event const&, Fsm& f, SourceState&, TargetState&) const
{
if(1 == f.condition) return true;
return false;
}
};
// Actions
struct ActionAssign
{
template <class Event, class Fsm, class SourceState, class TargetState>
void operator()(Event const&, Fsm& f, SourceState&, TargetState&) const
{
f.condition = 0;
std::cout << "ActionAssign()" << std::endl;
}
};
struct ActionInitToWorking
{
template <class Event, class Fsm, class SourceState, class TargetState>
void operator()(Event const&, Fsm& f, SourceState&, TargetState&) const
{
std::cout << "ActionInitToWorking() condition = " << f.condition << std::endl;
}
};
struct ActionInitToSleepPoll
{
template <class Event, class Fsm, class SourceState, class TargetState>
void operator()(Event const&, Fsm& f, SourceState&, TargetState&) const
{
std::cout << "ActionInitToSleepPoll() condition = " << f.condition << std::endl;
}
};
// Transition transition_table
struct transition_table:mpl::vector<
//Start Event Next Action Guard
msmf::Row < Init, msmf::none, Working, ActionInitToWorking, msmf::none >,
msmf::Row< Working, EventWrkToStby, Standby, msmf::none, msmf::none >,
msmf::Row< Standby, EventStbyToSubSleep, SleepPoll::entry_pt<SleepPoll_::Entry>, msmf::none, msmf::none>,
msmf::Row< SleepPoll::exit_pt<SleepPoll_::ExitRTC>, EventSubWrkToSleep, Sleeping, msmf::none, msmf::none>,
msmf::Row< SleepPoll::exit_pt<SleepPoll_::ExitSMS>, EventSMS, Working, msmf::none, msmf::none>,
msmf::Row< SleepPoll::exit_pt<SleepPoll_::ExitCall>, EventCall, Working, msmf::none, msmf::none>,
msmf::Row< SleepPoll, EventMCU, Working, msmf::none, msmf::none >
> {};
//template <class Event,class Fsm>
~TemSm_()
{
std::cout << "hello" << std::endl;
}
private:
int condition;
};
// Pick a back-end
typedef msm::back::state_machine<TemSm_> TemSm;
void test()
{
TemSm sm1;
sm1.start();
std::cout << "send EventWrkToStby" << std::endl;
sm1.process_event(EventWrkToStby());
std::cout << "send EventStbyToSubSleep" << std::endl;
sm1.process_event(EventStbyToSubSleep());
//std::cout << "send EventSubWrkToSleep" << std::endl;
// sm1.process_event(EventSubWrkToSleep());
//std::cout << "send EventCall" << std::endl;
// sm1.process_event(EventCall());
std::cout << "send EventMCU" << std::endl;
sm1.process_event(EventMCU());
}
}
int main()
{
test();
return 0;
}
几个注意事项
由于tbox状态机就运行在tbox上,故相关tbox异常场景需要考虑。
- 在tbox的电源模式中 sleep 模式中,各个进程都停止运行,从sleep模式中恢复时需要重启进程,要使状态机在sleep后能够延续,需要将状态机持久化。
//保存状态机到本地
#define POWERMODE_PATH "/oemimage/oemdata/PowerMode.fsm"
std::ofstream ofs(POWERMODE_PATH);
boost::archive::text_oarchive oa(ofs);
oa << sm;
//从本地文件中恢复状态机
std::ifstream ifs(POWERMODE_PATH);
boost::archive::text_iarchive ia(ifs);
ia >> sm;
2.为了区分进程异常退出还是正常退出:
- 在类的析构函数来记录持久化标志,此方法不可行,在系统正常重启和异常重启都不会执行析构函数。
- 进程退出回掉函数 atexit中来记录持久化标识,结果此方法也不可行,在系统正常重启和异常关机都不会执行此回掉函数。
- 场景考虑:
场景1:若不进行持久化的话,由于sleep和subsleep状态实际上就是定时关机与关机的区别,故无法恢复到对应的subsleep状态。
方案: 状态机持久化场景2:若在整个状态机变化的过程中都持久化状态机,当状态机处于standby时(此时状态机已经完成持久化),若在stanby时正常断电,异常断电关机,则此时开启系统将到standby状态,而实际处于work状态,故造成了状态不一致。
方案:此异常场景的解决方案是:不区分异常与正常断电,重启后都从初始working状态开始。则要求不对状态机进行持久化。场景3:若在进入sleep与subsleep都进行持久化,若正常关机,则启动后恢复状态机,需要区分当前状态处于sleep还是subsleep,若处于subsleep则切换到subwork,若处于sleep,则切换到working。若是在subsleep期间异常关机,则异常重启后首先还是会进入subworking,此时则不对。
方案:此时虽然错误处于subworking状态,但在此时只要mcu通知modem切换到working状态,都会使状态机切换到working,从而恢复正常。
综上所述:此处处理的最佳方式是,状态机进入subsleep状态时,持久化状态机,状态机恢复完成后删除此持久化文件;则正常关机后,重新启动,状态为subsleep状态主动切换到subworking。若在sleep状态,无论正常关机还是异常关机,重新启动后都处于working状态。