2016年4月13日水曜日

自転車発見器の無音モード

 閑静な住宅地など、使用する場所によっては、自転車発見器のメロディーが邪魔になる場合が考えられます。そのような場合を想定し、受信機のオプションビットで音を消せるようにしてあります。しかし、この方法では、インタラクティブモードに入らないと切替ができません。
 ところが、もっと単純で利便性の高い方法がありました。その方法とは、休符のみのプリセットメロディーを用意しておくだけです。iPhoneでアラームを無音にする方法と同じです。
 早速、ソースコードを修正して、プリセットメロディーを追加しました。

 因みに、自転車発見器のメロディー停止は、ごく短い休符1個だけのメロディーを再生するという方法で実現しています。これは、ベースにしたApp_Melodyの機能をそのまま引き継いだものです。

2016年2月15日月曜日

TWE-Liteの自転車ビーコンにオートペアリングを実装

 自転車ビーコンにオートペアリングを実装してみた。

 自転車ビーコンは、ペアリングした送信機と受信機の間でのみ通信が成立する。今までのペアリングは、設定モードに入って手でIDを入力して行っていた。入力を注意深く行うのは面倒だし、動作確認するとはいえ、この方法ではいずれ間違いが発生するのは避けられない。

 オートペアリングでは、コールドスタート時に特定のボタンが押されていたらペアリングモードに入り、ペアリング用のパケットで双方が希望するAppIdとランダムに選んだチャンネルを送り合う。暗黙のルールの下にAppIdを決定し、従属的にチャンネルも決定する。ペアリングのパラメータが決まったら、新しいチャンネルで通信を確認し、一定数のパケットが交換できたらEEPROMに設定を保存する。
 オートペアリングで使うAppIdとチャンネルは固定だが、送信出力を最小にする事と、ペアリング用パケットに双方のシリアル番号を含めることによって、確実に1対1でペアリングする。同時に3台以上がペアリングモードに入ったとしても、ペアリングが成功するのは必ず1対1の組み合わせに限られる(または、ペアリングが失敗する)。

 今のところ、ペアリングのパラメータはAppIdとチャンネルだけ。他のパラメータを含めることも可能だが、自転車ビーコンでは、これ以上は必要無い。

ペアリングモードへの入り口は、cbAppColdStart()から呼ばれるvInitHardware()の中。cold startの時にGPIOの状態からモードを設定する箇所に、DI1によるペアリングモード判定を入れている。
if (bPortRead(PORT_INPUT1)) {
sAppData.bPairingMode = TRUE;
}
DI1は押しボタンスイッチなので、M1,M2,M3のようなプルアップ解除は不要。cbAppColdStart()では、オートペアリング用に状態マシンとAppContextを設定。
// 状態遷移マシンの登録
if (sAppData.bPairingMode) {
ToCoNet_Event_Register_State_Machine(vProcessEvCorePairing);
sAppData.prPrsEv = (void*) vProcessEvCorePairing;
sToCoNet_AppContext.bRxOnIdle = TRUE;
sToCoNet_AppContext.u8TxPower = 0; // 最小出力
sToCoNet_AppContext.u32AppId = APP_ID;
sAppData.u8AppIdentifier = u8CCITT8(
(uint8*) &sToCoNet_AppContext.u32AppId, 4);
sToCoNet_AppContext.u8Channel = CHANNEL; // pairing用に固定
sToCoNet_AppContext.u32ChMask = CHMASK;
}
状態マシンのスタートアップでペアリング用のパラメータを初期化。
static void vProcessEvCorePairing(tsEvent *pEv, teEvent eEvent, uint32 u32evarg) {
switch (pEv->eState) {
case E_STATE_IDLE:
if (eEvent == E_EVENT_START_UP) {
sAppData.u16CtRndCt = 0;
sAppData.u32ReqAppId = ToCoNet_u32GetSerial();  // 要求APP ID
sAppData.u8ReqCh = ((ToCoNet_u16GetRand() & 0xF) + 11); // 要求channel
sAppData.u32CandidateAppId = 0;
sAppData.u32AnotherAppId = 0;
sAppData.u8CandidateCh = 0;
sAppData.u16MatchCount = 0;
sAppData.u16PeerMatched = 0;
}
相手をスキャンして、見つかったら合意形成フェーズへ移行。
case E_STATE_APP_PAIR_SCAN:
// ペアリング相手が現れたら提案確認フェーズ
if (0 < sAppData.u16MatchCount) {
vfPrintf(&sSerStream, "!INF PEER EXIST.@%dms"LB, u32TickCount_ms);
ToCoNet_Event_SetState(pEv, E_STATE_APP_PAIR_PROPOSE);
}
合意形成フェーズで1秒待ち、この間に相手からの合意パケットが十分な量受信できていたら、確認フェーズへ移行。
case E_STATE_APP_PAIR_PROPOSE:
if (1000 <= PRSEV_u32TickFrNewState(pEv)) {
// 1秒待ってから判断
sToCoNet_AppContext.bRxOnIdle = FALSE;
ToCoNet_vRfConfig(); // 受信を一旦停止
if (AUTO_PAIR_COUNT_MIN <= sAppData.u16MatchCount
&& AUTO_PAIR_COUNT_MIN <= sAppData.u16PeerMatched) {
ToCoNet_Event_SetState(pEv, E_STATE_APP_PAIR_CONFIRM);
} else {
ToCoNet_Event_SetState(pEv, E_STATE_APP_PAIR_FAILED);
}
}
break;
確認フェーズでは、最初にRF設定を変更。
case E_STATE_APP_PAIR_CONFIRM:
if (eEvent == E_EVENT_NEW_STATE) {
// カウンタを一旦クリア
sAppData.u16MatchCount = 0;
sAppData.u16PeerMatched = 0;
// u32AppIdはcbAppColdStart以外で変更不可なのでu8AppIdentifierだけを変更
sAppData.u8AppIdentifier = u8CCITT8(
(uint8*) &sAppData.u32CandidateAppId, 4);
sToCoNet_AppContext.u8Channel = sAppData.u8CandidateCh;
sToCoNet_AppContext.u32ChMask = (1UL << sAppData.u8CandidateCh);
// 次のパケットのタイミングを仕込む
sAppData.u16CtRndCt = (ToCoNet_u16GetRand() & 0x3);
sToCoNet_AppContext.bRxOnIdle = TRUE;
ToCoNet_vRfConfig(); // 新たなRF設定に切り替える
}
確認フェーズでも、十分な合意パケットが受信できたら、完了フェーズへ移行。
if (1000 <= PRSEV_u32TickFrNewState(pEv)) {
// 1秒待ってから判断
if (AUTO_PAIR_COUNT_MIN <= sAppData.u16MatchCount
&& AUTO_PAIR_COUNT_MIN <= sAppData.u16PeerMatched) {
ToCoNet_Event_SetState(pEv, E_STATE_APP_PAIR_COMPLETE);
} else {
ToCoNet_Event_SetState(pEv, E_STATE_APP_PAIR_FAILED);
}
}
break;
完了フェーズでは、新しいAppIdとチャンネルに更新してEEPROMへ保存。
case E_STATE_APP_PAIR_COMPLETE:
// AppId,Chを書き換えて保存
{
tsFlash sFlash = sAppData.sFlash;
sFlash.sData.u32appid = sAppData.u32CandidateAppId;
sFlash.sData.u8ch = sAppData.u8CandidateCh;
sFlash.sData.u32chmask = (1UL << sAppData.u8CandidateCh);
sFlash.sData.u32appkey = APP_ID;
sFlash.sData.u32ver = VERSION_U32;
bool_t bRet = bFlash_Write(&sFlash, FLASH_SECTOR_NUMBER - 1, 0);
V_PRINT("!INF FlashWrite %s"LB, bRet ? "Success" : "Failed");
vWait(100000);
}
ペアリングモードでやり取りするパケットは、受信コールバックcbToCoNet_vRxEvent()の中でデコード関数に振り分け。
case TOCONET_PACKET_CMD_APP_USER_PAIRING: // auto pairing
if (PRSEV_eGetStateH(sAppData.u8Hnd_vProcessEvCore) == E_STATE_RUNNING) {
vReceivePairingData(psRx);
}
break;
デコード関数の中では、AppIdの大小で優先度を決定。確定したAppIdが送られてきたら、受信数をカウントする。受信数は状態遷移の判断材料として使用する。
// 要求AppId
uint32 u32ReqAppId = G_BE_DWORD();
// 要求ch
uint8 u8ReqCh = G_OCTET();
(void)u8ReqCh;
// 候補AppId
uint32 u32AcceptAppId = G_BE_DWORD();
// 対向AppId
uint32 u32AnotherAppId = G_BE_DWORD();
// 候補ch
uint8 u8AcceptCh = G_OCTET();
(void)u8AcceptCh;
sAppData.u16PeerMatched = G_BE_WORD(); // 相手方pairingマッチカウンタ
if (sAppData.u32CandidateAppId == 0) {
if (u32ReqAppId < sAppData.u32ReqAppId) {
sAppData.u32CandidateAppId = u32ReqAppId;
sAppData.u32AnotherAppId = sAppData.u32ReqAppId;
sAppData.u8CandidateCh = u8ReqCh;
} else if (sAppData.u32ReqAppId < u32ReqAppId) {
sAppData.u32CandidateAppId = sAppData.u32ReqAppId;
sAppData.u32AnotherAppId = u32ReqAppId;
sAppData.u8CandidateCh = sAppData.u8ReqCh;
} else {
// bad case
}
} else if (sAppData.u32CandidateAppId == u32AcceptAppId
&& sAppData.u32AnotherAppId == u32AnotherAppId) {
sAppData.u16MatchCount++;
} else {
// ignore u32AcceptAppId == 0 or bad case(maybe multiple peer).
}
その他、詳細はgithubのソースコードを参照。

2016年2月7日日曜日

自転車発見器(自転車ビーコン)を出品

ヤフオクにて、「自転車発見器(自転車ビーコン)」として出品しました。
開始価格ではやや赤字なのですが、1世代前の試作機なのでサービス価格。

改めて実測してみると、100m届くケースは滅多に無いとわかったので、スペックはMFT2015でチラシに書いたものより控えめに書いています。

さて、売れるかなぁ?

2015年8月16日日曜日

CR2032放電実験 8日目

 自作のファームウェアにバグがあって、一定電圧以下になると送信を即時中断してしまう動作になっていました。電圧が2400mV未満に落ちなかったのは、これが原因だったようです。この足切り動作をやめたら、電圧が落ちても送信を続けるようになりました。

 大雑把な秒単位ではなく、4msのtick単位で送信中の電圧変化をプロットしたら、意外な形になっていました。
低い電圧でスタートし、いったん高い電圧になってから元の電圧まで戻り、上下を繰り返して開始電圧+0.2V位に収束しています。
 100uFのチップコンデンサを接続した場合は、下のグラフのようになりました。
開始時の電圧の乱れが収束しないばかりか、変動の幅が広くなっています。中盤以降は安定しますが、もともと大きな乱れではありませんでした。このようなバースト送信では、バッファコンデンサを置く意味は無いかもしれません。

 終盤の収束した電圧が電池そのものの電圧とみなしてプロットしたら、このようになりました。送信時間を毎分10秒にしたり5秒にしたりでgdgdになっていますが、このくらいインターバルを置けばバースト送信による寿命短縮は気にしなくてもよいということはわかりました。

2015年8月14日金曜日

CR2032放電実験 6日目

 電源電圧が2.4Vからほとんど下がらなくなりました。生ログを見ると、2.4Vまで下がったあたりでパケットの連打が止まっています。ファームウェアでは送信開始時の電圧が2.1V以上で連打するようにしているので、別な原因があるようです。

1分毎に5秒連続送信を2回繰り返していましたが、2回目の連打ができなくなったので、5秒連続送信を1回だけに変えました。しかし今は、1回に減らした5秒の連打さえも維持できなくなっているようです。

2015年8月12日水曜日

CR2032放電実験 5日目

まだ続いていますが、1分毎に繰り返している、

  1. 5秒送信
  2. 1秒休止
  3. 5秒送信
のうち、3.の5秒送信がスタート直後に止まる場合が多くなりました。終わりが近いようです。
ここからは、休止時間を3秒に延ばして延命を図ってみます。実験途中で条件を変えるのはアレなのですが、そもそもスタートが新品状態ではなかったので、そこは気にしないことにします。
Excelの制限で32000点を超えるデータを扱えないため、1.の送信開始時と3.の終了時だけをプロットするようにしました。

2015年8月11日火曜日

CR2032放電実験 4日目

日中に電圧が上昇し、夜間に下がる傾向が読み取れます。
全部の点をプロットすると処理が重くなるので、2秒毎の代表値を使用しています。