Accessが複数開かれている時にGetObject()する方法

はじめに

  • ExcelAccessをはじめとしたOffice系のソフトは,オートメーションなる機構が準備されているので,各々連携することが可能である.
  • 具体的には,CreateObject()やGetObject()といった関数で,各ソフトのApplicationオブジェクトに相当する参照を取得でき,例えば,ExcelからAccessにクエリを投げて,得られた結果をワークシートに転送する,といったマクロを作成可能である.
  • いや,MSDN*2は最初の引数に使いたいファイルパスを指定すれば選択できそうな雰囲気ではあるが,試してみたところ,新しいインスタンスが開かれ,取得された参照が破棄されると同時にインスタンスも閉じられた.うまくやる方法があるのかもしれないが,ここでは操作するインスタンスを指定できないとしよう.
  • そこで,ウィンドウタイトルから開いているファイルを推測し,そのウィンドウのハンドルを基にしてAccessibleObjectFromWindow()関数を呼び出し,目的のインスタンスの参照をつかむ方法が,首尾良く行きそうなので報告する.

AccessibleObjectFromWindow()関数

MSDN*3を参照.

こいつでウィンドウハンドルから,インスタンスの参照を得られる.VBAでは引数の扱いに注意が必要な点がある.

  • 第2引数は取得したいオブジェクトのIDを渡す.Office系の参照が欲しい場合はOBJID_NATIVEOMを指定する.
  • 第3引数の REFIID riid はGUID渡すべきであるが,VBAのGUIDを指定すると怒られる.Declareな関数では使えないらしい?ので,同等の構造体をユーザー定義する.Office系の参照を得るためには,IDispatchに相当するGUIDを渡す.

EnumWindows()関数

MSDN*4を参照.

ウィンドウを列挙してくれる.第1引数にコールバック関数を渡す必要があるが,なんと,VBAはAddressOf演算子を使えば渡せるらしい.

GetWindowText()関数

MSDN*5を参照.

ウィンドウタイトルを得る.VBAでは固定長文字列を宣言する方法があって,宣言の後に「* 256」を付け足せば良いらしい.

Access複数開かれている場合に参照を取得する例

  • Access複数開かれている場合を例にした実装を張る.
  • Main()からEnumWindows()を呼び出し,目的のインスタンス("tesuto.accdb"なるファイルを開いたAccessインスタンス)が存在したら,フォーム"hoge"を開く.
  • EnumWindows()のコールバック関数では,ウィンドウタイトルを調べ,目的のファイルを開いていそうだったら,Accessインスタンスの参照の取得を試みる.
Option Explicit

Public AccessWndHandle As Long
Public AccessApplication As Object

Private Const OBJID_NATIVEOM = &HFFFFFFF0

Private Declare Function GetWindowText Lib "User32" _
Alias "GetWindowTextA" _
(ByVal Hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long

Private Declare Function AccessibleObjectFromWindow Lib "oleacc" _
(ByVal Hwnd As Long, ByVal dwId As Long, _
ByRef riid As UUID, ByRef ppvObject As Object) As Long
     
Private Declare Function EnumWindows Lib "User32" (ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long

Private Type UUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(7) As Byte
End Type

Public Function EnumWindowsCallBackFunc(ByVal Hwnd As Long, ByVal lParam As Long) As Boolean
    Dim title As String * 256

    If False = GetWindowText(Hwnd, title, Len(title)) Then
        GoTo Continue
    End If
    
    If InStr(title, "tesuto") > 0 And InStr(title, "Access") > 0 Then
        Dim IID_IDispatch As UUID
        With IID_IDispatch
            .Data1 = &H20400
            .Data4(0) = &HC0
            .Data4(7) = &H46
        End With
        
        If 0 <> AccessibleObjectFromWindow(Hwnd, OBJID_NATIVEOM, IID_IDispatch, AccessApplication) Then
            GoTo Continue
        End If
        
        AccessWndHandle = Hwnd
        EnumWindowsCallBackFunc = False
        Exit Function
    End If
    
Continue:
    EnumWindowsCallBackFunc = True
    Exit Function
End Function

Public Sub Main()
    AccessWndHandle = 0
    Call EnumWindows(AddressOf EnumWindowsCallBackFunc, 0)
    
    If AccessWndHandle = 0 Then
        Exit Sub
    End If
    
    AccessApplication.DoCmd.OpenForm "hoge"
End Sub

*1:インスタンスとは,現在コンピュータで動いているプログラム,と取って欲しい.例えば,タスクマネージャで「EXCEL.EXE」が複数存在するとき,複数インスタンスが存在すると表現する.

*2:GetObject 関数 (VBScript)

*3:AccessibleObjectFromWindow

*4:EnumWindows 関数

*5:GetWindowText 関数

Twitterのロリコン人工無能Bot @jsfavo について

@jsfavoとは

JSこれくたー,Twitterを監視する淫らなbot君です.

・@sosoruと@jsfavoのHomeTLを見て,指定されたキーワードが入ってるとふぁぼる
・30分おき(朝7時~9時は10分おき)に何かをツイート.深夜帯はお休み
・リプライを送ると何か返してくる

ふぁぼ機能

  • @sosoruと@jsfavoのHomeタイムラインを常に監視しています.指定されたキーワード,もしくは,キーワードの類似語が含まれていたらふぁぼります.
  • UserStreamを使って監視していて,見つけたらすぐにふぁぼる仕様でしたが,何処からか圧力が掛かったので,いくらかの遅延を持たせてふぁぼらせています.

f:id:sosoru_m:20160201024001p:plain
?こんな感じ.(TriggeredFilterが反応した単語)

f:id:sosoru_m:20160201024442p:plain
?類似語の場合はこんな感じ

ツイート機能

  • 予め登録されたキーワードを元に何か文章を作成します.文章の素は@sosoruの過去のHomeTLを使っています.


  • 4語のマルコフ連鎖で幾つか文章を作成していますが,そのままだと「パクツイ」になりやすいので,名詞などを類似語にランダムで置き換えています.
  • リプライを送ると,そのツイートを基に何か文章を作成して返してきます.


?テンプレに反応して返してくれる

  • 深夜帯の1時30分~6時前まではツイートしません.裏でデータベースのメンテナンスをしています.リプライ機能は生きているので,何かしら返してくれるかもしれませんが,メンテナンスの都合で返せない場合があります.


?返してくれないときもある,「わかんないよぉ」って言うときもある

リプライについて

  • 空リプを送ると何のツイートに反応したか返してくれます.


  • 何か文章を送ると,反応する単語が含まれているか教えてくれます.同時に何か喋りかけてきます.


  • 文章に「詳細」が含まれていると,さらに詳しい情報を教えてくれます.triggered filterは内部で使っている正規表現です.


  • この他にも,リプライに対して定型文を返すような設定が幾つか入っています.探してみてください.

しくみ

  • bot自体はC#で書かれていて,辞書学習等々のファイル操作でRubyをちょっと使っています.
  • C#のCoreTweetでTwiterAPIを弄りつつ,SQLServerに得たツイートを垂れ流しています.文章の形態素解析にはMeCabを使っていて,辞書には「はてなキーワード」を主に使っています.(C#側のライブラリはLibNMeCab)
  • 類似語の列挙にはword2vecを使っています.@sosoruのHomeTLをMeCabでわかち書きしたものを学習させています.(C#側のライブラリはWord2Vec.Net)

類似語について

  • word2vecを使えば単語の意味ベクトルが得られるので,2つの単語ベクトルの内積を取れば,どれだけ類似しているかが数値化できます.(これをコサイン類似度と呼ぶ?)
  • 類似語とは文脈で使われやすい単語を指しているようで,同義語と反義語の区別はないようです.

f:id:sosoru_m:20160201032919p:plain
?類似度の高い単語を列挙した場合,それっぽい単語がサジェストされる

f:id:sosoru_m:20160201033003p:plain
?類似語に同義語と反義語の区別はない?

f:id:sosoru_m:20160201033807p:plain
?ペアだと高い類似度を持つ?

  • 類似語の計算はとても重いです.200次元のベクトル同士を10~20万回内積計算させる処理です.キャッシュを入れましょう.
  • 最適化込みのビルドをCore i5-4670K @ 3.40GHzのマシンで並列処理させて,約100msec~200msec程度かかるようです.
  • float精度の計算なのでGPUに肩代わりさせた方が利口かもしれません.

文章生成について

  • @sosoruのHomeTLを基に4語のマルコフ連鎖で文章を作っています.2016年1月の時点で,過去1年程度のTL,300~400万ツイートがデータベースに溜まっています.
  • リプライ等々で文章生成のリクエストを受け取ると30文程度の文章を生成します.生成された文章の中から,比較的短文で,基となった単語や文章と類似したものをランダムで選び出します.基の単語と文章の類似度を測るときにも,word2vecで出した単語の意味のベクトルを利用しています.
  • 単語の意味ベクトルはword2vecが吐きだした数値を用いれば良いですが,文章の場合には,使われている名詞・形容詞などを抜き出し,その単語の意味ベクトルを足し合わせて正規化したものを,文章の意味ベクトルとして扱っています.
  • 意味ベクトルとして考慮する単語が多くなるほど,基の単語・文章との類似度が低下するので,単語数を考慮してランダムで選び出します.
  • 生成された文章と基になった文章の類似度が高い場合,幾つかの単語を類似語で置き換えています.botにたまによくある奇想天外な文章の類似度は,感覚的にあまり高くないようなので,わざと落とすことにしています.

f:id:sosoru_m:20160201040422p:plain
?考慮する品詞,代名詞や接尾語は見ない

f:id:sosoru_m:20160201035316p:plain
?「ゆのっち」を与えたときに生成された文.表示されているのは名詞や形容詞の数が2~10程度の文章.名詞・形容詞などの単語数,基の単語との類似度,生成された文章の順に表示.

辞書の更新について

  • 深夜帯にSQLデータベースのインデックス再構成と形態素解析辞書の更新,word2vecに再学習をさせています.マルコフ連鎖で使う辞書はツイートを受信した段階で,データベースに突っ込んでいます.
  • インデックスの更新が割と重要で,定期的に再構成してやらないと,参照時にタイムアウトすることがありました.

Nゲージ鉄道模型のATS・ATC案③-LEDモジュール

この記事は過去に作ったNゲージ鉄道模型ATCシステムについて記述しています.
システム全体については下の過去記事を参照してください.

sosoru.hatenablog.jp


f:id:sosoru_m:20151004222219g:plain

LEDモジュール

LEDの制御もユーザーの端末で出来るようにしていました.ATCの動作に直接の関係はありません.当時は駅ホームの照明に使っていました.(本当は信号に使うべきかもしれない)

ハードウェアについて

f:id:sosoru_m:20151018122725p:plain

Dフリップフロップ(D-FF)とバッファを組み合わせた(恐らく)ポピュラーな回路構成です.D-FFはクロック入力CLKが立ち上がった時,入力Dの状態が出力Qに保存され,次にCLKに変化あるまで,出力Qは状態を保持し続けます.幾つかのD-FFを組み合わせ,それぞれの入力を共有しておき,各D-FFことにCLKを制御することで,マイコンが扱えるIOピンが増えます.このモジュールでは8入出力D-FFを4つ使っています.

DフリップフロップICの出力ピンに流せる電流が心許ないので,バッファを入れて大電流に備えています.

ちらつかない程度な低周波の128段階PWM波形を生成し,一つのモジュール当たり,32個のLEDを駆動することが出来ます.実際には出力を30個に切っています.これは,出力コネクタにパラレルATAケーブルを流用している都合です.

マイコン ATmega1284P
D-FF TC74VHC574F
バッファ TD62783APG

f:id:sosoru_m:20151018143134p:plain

ソフトウェアについて

マイコンは20MHzで駆動しており,通信基板とSPIで会話しつつ,ソフトPWMをこなします.下にコードを貼ります.(いま見ると命名がひどいなぁ)

#include "avr_base.hpp"
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/interrupt.h>
#include <Timer.h>
#include <string.h>
#include <tus.h>
#include <led_packet.h>
using namespace AVRCpp::Timer;

#define PORT_DATA			PORTA
#define PORT_CLK			PORTD
#define PORT_ENABLE			PORTC
#define PORT_ENABLE_NUM		0

#define LED_COUNT			30

LedState g_States[LED_COUNT];
DeviceID g_myDevID;

uint8_t led_count_remains[LED_COUNT];
uint8_t led_count_init[LED_COUNT];

#define APPLY_TO_LED(clk,ind) \
if(led_count_remains[(clk+1)*8-ind-1] >0){ \
	sbi(led_signal_buffer, ind); \
	led_count_remains[(clk+1)*8-ind-1]--; \
}else{ \
	cbi(led_signal_buffer, ind);} 
				
ISR(TIMER1_COMPA_vect)
{
	tus_spi_process_packets();
}

void device_init()
{
	DDRA = 0xFF;
	DDRD = 0xFF;
	DDRC = 0xFF;
	
	cbi(PORT_ENABLE, PORT_ENABLE_NUM); // enable D-flipflops
		
	memset(led_count_init, 0, sizeof(led_count_init));
	memset(led_count_remains, 0x00, sizeof(led_count_remains));
}

void init_led_status()
{
	uint8_t cache = SREG;
	cli();
	memcpy(led_count_remains, led_count_init, sizeof(led_count_init));
	SREG = cache;
}

void apply_led_status()
{
	uint8_t led_signal_buffer;
	
	APPLY_TO_LED(0,0);
	APPLY_TO_LED(0,1);
	APPLY_TO_LED(0,2);
	APPLY_TO_LED(0,3);
	APPLY_TO_LED(0,4);
	APPLY_TO_LED(0,5);
	APPLY_TO_LED(0,6);
	APPLY_TO_LED(0,7);
	PORT_DATA = led_signal_buffer;
	PORT_CLK = 1<<0;
	
	APPLY_TO_LED(1,0);
	APPLY_TO_LED(1,1);
	APPLY_TO_LED(1,2);
	APPLY_TO_LED(1,3);
	APPLY_TO_LED(1,4);
	APPLY_TO_LED(1,5);
	APPLY_TO_LED(1,6);
	APPLY_TO_LED(1,7);
	PORT_DATA = led_signal_buffer;
	PORT_CLK = 1<<1;
	
	APPLY_TO_LED(2,0);
	APPLY_TO_LED(2,1);
	APPLY_TO_LED(2,2);
	APPLY_TO_LED(2,3);
	APPLY_TO_LED(2,4);
	APPLY_TO_LED(2,5);
	APPLY_TO_LED(2,6);
	APPLY_TO_LED(2,7);
	PORT_DATA = led_signal_buffer;
	PORT_CLK = 1<<2;
	
	//APPLY_TO_LED(3,0);
	//APPLY_TO_LED(3,1);
	APPLY_TO_LED(3,2);
	APPLY_TO_LED(3,3);
	APPLY_TO_LED(3,4);
	APPLY_TO_LED(3,5);
	APPLY_TO_LED(3,6);
	APPLY_TO_LED(3,7);
	PORT_DATA = led_signal_buffer;
	PORT_CLK = 1<<3;
	PORT_CLK = 0;
}

bool ProcessLedPacket(LedState *pstate)
{
	if(pstate->Base.ModuleType != MODULETYPE_LED
		&& pstate->Base.InternalAddr > LED_COUNT
		&& pstate->Base.InternalAddr == 0)
		return false;
	
	led_count_init[pstate->Base.InternalAddr-1] = pstate->DutyValue;
	
	return true;
}

void spi_received(args_received *e)
{
	g_myDevID.raw  = e->pdstId->raw;

	ProcessLedPacket((LedState*)e->ppack);
}

int main(void)
{	
	uint8_t i;
	MCUCR = 0b01100000; //todo: turn off bods
	MCUSR = 0; // Do not omit to clear this resistor, otherwise suffer a terrible reseting cause.
	
	tus_spi_init();
	tus_spi_set_handler(spi_received);
	
	device_init();
	TimerCounter1::SetUp(NoPrescaleB, Normal16, NormalPortOperationA, NormalPortOperationB, Off, Fall);
	TimerCounter1::CompareMatchAInterrupt::Enable();

    while(1)
    {	
		init_led_status();
		for(i=0; i<128; ++i)
		{
			apply_led_status();
			_delay_us(100);
		}
	}
}

通信基板との会話

別ファイルにSPI割り込みの関数が定義されていて,データの送受信を行っています.
タイマ割り込みでtus_spi_process_packets()が呼び出されており,定期的に受信したデータの処理を行っています.この関数は,事前にtus_spi_handler()で指定された関数を間接的に呼び出し,具体的な処理を委ねています.このモジュールではspi_received()が設定されています.spi_received()は受信したデータを実際に反映していて,LEDのDuty比設定を更新しています.

ソフトPWM

led_count_initに設定するDuty比を書いておいて,周期の最初,led_count_remainsにled_count_initをコピーします.
ループ毎にled_count_remainsをデクリメントし,0になるまでオンにしておきます.この処理に相当するのがapply_led_status()です.
これが最も時間的な制約を受けそうですが,コンパイル時に-O3で最適化を書けた場合で,最悪でも大体200クロックで抜けるようです.これは,クロック周波数が20MHzだとすると,10usec程度で処理か終わる見込みです.
直後の100usecのdelayを考えると,1周期は約14msecぐらいで回り,PWM周波数は71Hz程度と予測できます.SPIの割り込み等を考えると,もう少し遅くなっているでしょう.

おわり

あとで写真を撮って貼っておきたい
本当はポイントモジュールが先だったけど,設計がちょっと疑問なので実測してから載せる予定

Nゲージ鉄道模型のATS・ATC案②-閉塞・モータモジュール

sosoru.hatenablog.jp

f:id:sosoru_m:20151004222219g:plain

モータモジュールについて説明します.

モータモジュール

f:id:sosoru_m:20151004222919p:plain
↑ブロック図

f:id:sosoru_m:20151004222347p:plain
↑基板

主要な部品:

マイコン ATmega1284P
モータードライバ TB6559FG
リレー 941H-2C-12D


モータモジュールで担当している閉塞のレール電圧・電流を管理します.
マイコンのADCはモータードライバを貫く電流を見ています.シャント抵抗の電圧を25倍のアンプで増幅した電圧から判断しています.
リレーは,担当する閉塞のレールと,モータドライバの出力を繋ぐか,隣接する閉塞のレールを繋ぐかを制御しています.開放時には隣接する閉塞,短絡時にはモータドライバを接続しています.

モータモジュールの動作モード

このモータモジュールには,大きく分けて4つのモードが存在します.
モータドライバのHブリッジの様子です.

  • スタンバイモード

リレーを開放し,隣接する閉塞と担当する閉塞が接続されている状態です.この時,このモジュールのモータードライバは仕事をしません.

  • 正方向ONモード

リレーが短絡され,モータドライバと担当する閉塞が接続されています.モータードライバはレールと接続され,列車に電流が供給されます.
f:id:sosoru_m:20151004224310p:plain

  • 逆方向ONモード

正方向ONモードと逆方向に電流が供給されます.
f:id:sosoru_m:20151004224321p:plain

  • ブレーキモード

正方向・逆方向ONモードと同様にリレーが短絡されていますが,モータードライバは電流を供給せず,どちらのレールもグランドと接続されます.この時,レールはどちらともグランドに短絡した状態です.
f:id:sosoru_m:20151004224331p:plain

この4つの動作モードを使い分けて閉塞動作を実現します.

閉塞遷移について

列車の進む向きで若干挙動が変わるので,正方向時と逆方向時を分けて説明します.

正方向時

閉塞遷移前

f:id:sosoru_m:20151004224947p:plain
左モータモジュールが担当する閉塞上の列車を駆動しています.次の閉塞を担当している右モータモジュールはブレーキモードで待機しています.矢印は電流を表しています.

閉塞遷移中

f:id:sosoru_m:20151004225434p:plain
列車の「車輪」が次の閉塞をまたごうとする瞬間,次の閉塞のモータモジュールには前の閉塞と短絡します.この時に流れる電流をモータモジュールは検知します.

閉塞遷移後

f:id:sosoru_m:20151004225746p:plain
電流を検知したモータモジュールはスタンバイ状態に移行します.列車は前の閉塞のモータモジュールから電流が供給され,移動し続けます.

逆方向時

閉塞遷移前

f:id:sosoru_m:20151004230009p:plain
正方向時とは逆に,右モータモジュールが担当する閉塞上の列車を駆動しています.同様に,左モータモジュールはブレーキモードで待機しています.

閉塞遷移中

f:id:sosoru_m:20151004230147p:plain
同様に,車輪で前の閉塞と短絡したときに流れる電流を検知します.左モータモジュールはブレーキモードから逆方向ONモードに移行します.この時,左右どちらのモジュールも逆方向ONになります.
同一のマイコンであれば問題ありませんが,異なるマイコンだと,クロック源が異なるのでPWM波形の位相がズレます.この波形のズレは「うねり」となって現れ,走行に問題を引き起こします.この問題は通電カプラーを用いている編成には顕著に表れ,閉塞をまたぐときだけ速度が上昇し,室内灯の明るさに「うねり」が現れます.
この問題を防止するため,左のモータモジュールは逆方向ONモードになった後,速やかに右の閉塞に「スタンバイモード」へ移行するよう命令します.

閉塞遷移後

f:id:sosoru_m:20151004231036p:plain
右のモータモジュールがスタンバイに移行した後,列車は運行を継続します.

閉塞遷移後の処理について

正方向・逆方向ともに,閉塞遷移した後,今までいた閉塞の開放処理をしません.閉塞の開放処理は「制御・Webサーバー」が担います.制御・Webサーバーは列車が抑えている閉塞数を監視し,一定数以上,閉塞を抑えたとき,末尾の閉塞を開放します.

この方法であれば,フィーダだけで閉塞を形成できますが,各列車は最低2閉塞占有することが問題です.これは閉塞数を増やすことでカバーしています.

閉塞の継ぎ目を列車がまたぎ終えたことを光センサ等で監視すれば,この問題を解決できますが,レイアウト形状に任意性を持たせるために導入しませんでした.

Nゲージ鉄道模型のATS・ATC案①-概要

数年前に作ったNゲージ鉄道模型の閉塞システムについて,あまり資料をまとめていなかったので,ここにまとめます.

動機

2010年頃,私はある大学のサークルの鉄道模型展示に関わることになりました.そこでは当時,8mX6mぐらいの規模で4線エンドレスのレイアウトを作成していました.
このレベルの規模だと,どれだけ長い編成でも中々列車が巡ってきてくれず,見ていて少し寂しい印象を受ける欠点がありました.
これを解決するために,私は1線で2編成以上の運行を行うため,閉塞システムを作ることになりました.

2編成以上動かすのであれば,DCCを導入すれば負担が軽減出来そうですが,走らせる車両は部の所有の車両ではなく,部員が持ち寄った車両ということで,車両への加工が必要なDCCは導入できませんでした.
そこで,リレー式の閉塞システムを作ることにしました.

全体図

f:id:sosoru_m:20151004214922g:plain

レールやポイントを操作するハードウェアは「モジュール」にまとめられています.これらのモジュールは「通信基板」とルーターを通してそれぞれが通信できるようになっています.
「制御+Webサーバー」は閉塞制御行うようにモジュールに指示を飛ばし,ユーザーの入力を処理します.
「ユーザー」はタブレットやラップトップといった,Webブラウザが動く端末であればどれでも,列車の制御を行えます.

目次

  • モータモジュール

sosoru.hatenablog.jp

  • ポイントモジュール
  • LEDモジュール

sosoru.hatenablog.jp

  • Web・制御サーバー
  • WebUI
  • 在線表示ページ
  • 電車でGOコントローラの利用

ElecrowからPanelizing(面付け)した基板が届いた

作成した基板について


f:id:sosoru_m:20150911184429p:plain

この基板パターンを100x94mmに5つ面付けする.

f:id:sosoru_m:20150911185235p:plain

Panelizingについて

Elecrowの基板作成サービスにはPanelizingなる項目がある
f:id:sosoru_m:20150911183303p:plain

一つの基板に幾つかの基板パターンを入れて,Vカットの細い溝で区切ってくれるらしい.小さい基板を大量に欲しいときには割りと使えそう.

面付けに関する詳しいことは下記ページにある.基板サイズの制約や注意事項等々がある.
BLOG | PCB Panelize is Available now

その入れ方については公式wikiが説明してくれている.
How to panelize PCBs with CAM350 - Elecrow

  • 面付けを行うためには8cm*8cm以上でなければならないとある.今回は5つ縦に置いても70x90mmなので条件を満たさない.8cm*8cmを下回る場合にはTechnology Edgeなる余白を3mm以上入れなければならない.
  • Vカットを入れる切り取り線はシルクスクリーンに書き込む.基板パターンの間隔は1.6mmの基板厚さだと,0.8mmぐらい空ける必要がある.今回は1mmの間隔を置いている.
  • 注文フォームのPanelizingには幾つか種類があって,面付けする基板パターンの種類や個数によって追加料金が異なるらしい.とりあえず「Single PCB with milling」で注文したら,あとで$8払ってくれメールが届いた.$8は「2-5 copies」に相当する.

基板到着までの流れ

Elecrow

2015/08/30に注文.とりあえず,追加情報に「ボトムシルクに切り取り線入れたからV-cutしてね」と書き込む

2015/08/31に追加料金の旨に関するメールのやりとり.その日のうちに「In Production」に進む

2015/09/07に「Traceable」に変更.製造に5~6営業日かかったのかな?(面付けでちょっと多く掛かるとは書いてある)

DHL

DHLの配達記録.日本語だと空欄になってるところがあったので,英語版を載せる.1~20番までは1日ちょっとで来てるからとても早い.
「Forwarded for delivery」は悪名高き「配達業者への荷物引渡し準備完了」,佐川急便に投げるってやつ.運が良いとDHLが直接届けてくれて,Traceableから1日で来るんだけど残念.

Friday, September 11, 2015 Location Time
21 Delivery attempted; recipient not home TOKYO - JAPAN 17:38
Tuesday, September 08, 2015 Location Time
20 Forwarded for delivery TOKYO - JAPAN 14:20
19 Arrived at Delivery Facility in TOKYO - JAPAN TOKYO - JAPAN 12:37
18 Departed Facility in TOKYO - JAPAN TOKYO - JAPAN 11:36
17 Processed at TOKYO - JAPAN TOKYO - JAPAN 11:28
16 Clearance processing complete at TOKYO - JAPAN TOKYO - JAPAN 10:48
15 Arrived at Sort Facility TOKYO - JAPAN TOKYO - JAPAN 9:55
14 Customs status updated TOKYO - JAPAN 9:07
13 Transferred through TOKYO - JAPAN TOKYO - JAPAN 8:50
12 Departed Facility in HONG KONG - HONG KONG HONG KONG - HONG KONG 3:35
11 Processed at HONG KONG - HONG KONG HONG KONG - HONG KONG 3:33
10 Clearance processing complete at HONG KONG - HONG KONG HONG KONG - HONG KONG 0:44
9 Arrived at Sort Facility HONG KONG - HONG KONG HONG KONG - HONG KONG 0:36
Monday, September 07, 2015 Location Time
8 Shipment on hold HONG KONG - HONG KONG 23:35
7 Clearance processing complete at SHENZHEN - CHINA, PEOPLES REPUBLIC SHENZHEN - CHINA, PEOPLES REPUBLIC 22:00
6 Customs status updated HONG KONG - HONG KONG 18:23
5 Clearance event SHENZHEN - CHINA, PEOPLES REPUBLIC 18:00
4 Departed Facility in SHENZHEN - CHINA, PEOPLES REPUBLIC SHENZHEN - CHINA, PEOPLES REPUBLIC 17:22
3 Processed at SHENZHEN - CHINA, PEOPLES REPUBLIC SHENZHEN - CHINA, PEOPLES REPUBLIC 17:16
2 Arrived at Sort Facility SHENZHEN - CHINA, PEOPLES REPUBLIC SHENZHEN - CHINA, PEOPLES REPUBLIC 15:58
1 Shipment picked up SHENZHEN - CHINA, PEOPLES REPUBLIC 12:08

佐川急便

配達記録.DHLの荷物番号とは紐付けされていないので,荷物が届かないと記録は見られない.

⇒ 2015年09月11日 17:47 配達は終了致しました。
↑ 2015年09月11日 17:38 ご不在でしたので、お預かりしております。
↑ 2015年09月11日 八王子営業所から配達に出発致しました。
↑ 八王子営業所でお預かりしております。
↑ 2015年09月08日 19:31 府中営業所を出発致しました。
↑ お荷物をお預かり致しました。

日本とシンセンよりも府中と八王子の方が遠いらしい.台風だったからね,仕方ないね.

届いた基板

f:id:sosoru_m:20150911194515j:plain

切れてる基板が10枚届いた.隣のは同時に頼んだ普通の基板.Technology Edgeなるエリアには3つの穴とハンダで埋まったスルーホールがある.

f:id:sosoru_m:20150911194736j:plain

Vカットされると思ったけど,ドリルで切ってくれたっぽい.シルク線を別々に書いたからかな?これなら基板外形線で書いても良かったかも.

f:id:sosoru_m:20150911195611j:plain

Vカットのところを横から見るとこんな感じ.場合によっては,端面をヤスリでちょっと削らないといけないかもしれない.

  • 5つ面付けした基板10枚セット(10x10cm)で$23.9だったので,一枚当たり$0.478.日本円で50円ちょっとかな.面付け無し10x5cmで頼むよりは安いですね.
  • Elecrowの基板色はBlackの他にMatte Blackなるオプションがある($20).確かに今回の基板はちょっとくすんでる(縦のスジが目立つ感じ).Matte Black + ENIGなら相当映えるパターンの基板が出来そうだなぁ.

納涼?「すぐにけせ」を再現するUEFIバイナリを作ってみた

「すぐにけせ」?

f:id:sosoru_m:20150819024045j:plain
オカルト都市伝説の一つ,SFCで発売された「真・女神転生(II?)」を起動すると1/65536の確率で表示されるメッセージのことです.
これは広く「ガセネタ」として認識されています.私自身も,SFCは疑似的に乱数を発生させて(る気がし)ますので,起動時にランダムで表示するのは厳しいと思ってます.(セーブデータが消えたら,とか,別のトリガーで出てきたら面白いですけどね)

UEFIとは?

マザーボードに乗っかってるファームウェアのインタフェースです.最近のマザーボードUEFIを実装していて,UEFISDKも公開されているので,OS起動前の処理を比較的簡単に書けたりします.
今回はこれを使って,起動前にメッセージを表示させています.

ポイント

とりあえず,周知の事実から「すぐにけせ」は「起動時」に「1/65536で再現」することが出来れば良さそうです.

「起動時」

起動時に再現するためには,UEFIに関わらず,MBRブートした小さなLinuxに表示させれば良いんじゃないかなぁとも思いました.表示する判定の場合は表示して,表示しない判定の場合には,次回ブートするデバイスをBIOSに伝えればいいわけです.ただ,メッセージを表示しない確率が非常に高いはずなので,毎回起動時に2回ブートさせるのはちょっと微妙だなぁと思いました.
僕のマザーのブートマネージャだけかもしれませんが,UEFIブートであれば,起動に失敗した場合,すぐに次の起動プログラムを試してくれるようです.これを悪用しない手はありません.最初に「すぐにけせ」バイナリを起動させて,2段目に使用するOSを起動するように設定すれば,違和感なく起動時に「すぐにけせ」判定をさせることが出来ます.

「1/65536」

起動時に確率で発生させることはSFCでは出来ないはずです.しかし,我々の使っているモダンなコンピュータはRTCが内蔵されているので,起動時でも問題なく乱数を発生できそうです.
UEFIで日付を手に入れようとするには,RuntimeServicesのGetTime()を使えば良いのですが,渡されるのはEFI_TIME構造体で,○○年元旦からの積算秒で表しているわけではなく,年月日等々が直感的に分かる仕様*1でした.
時分秒とミリ秒が分かるようなので,ミリ秒ベースに変換して65536で割っています.お手軽実装です.ミリ秒だけは電源投入時から計測しているような気がして不安ですが.

画像について

複雑なファイルシステムを扱えるのがUEFIの売りな気がしますが,表示させる画像はリテラルconst unsigned char [] に直してソースコードに埋め込んでいます.
GraphicsOutputで表示させるために,BMP画像を変換してくれる関数がEDK2内のBdsConsole.cで定義されているので利用します.
変換した画像で画面を埋め尽くします.横端・縦端を描画する場合にはスクリーンをはみ出すので,書き込む範囲を予め計算しておきますが,横端を描画するために,描画するwidthを元画像より小さくしようとすると,何というか,水平同期の周期がズレる感じになって,うまく描画されません.1ピクセルずつ書けば良さそうですが,今回は諦めています.

ビルド

VS2012でビルドしました.下の記事を見てやってましたが,IntelliSense効かないっぽいのが悲しいです.
http://uefi.blogspot.jp/2012/06/how-to-set-up-edk2s-windows-hosted-uefi.html
実機で動かすバイナリをビルドするには,Conf/target.txtのACTIVE_PLATFORMをAppPkg/AppPkg.dscのように適切に変更しておきます.Nt32Pkg.dscでは動かなかったです.
また,実機がX64アーキテクチャしかサポートしていない場合には,VS2012の「VS2012 x64 Cross Tools コマンド プロンプト」からedk2setup.bat→buildでクロスコンパイルします.

動作

f:id:sosoru_m:20150819023004j:plain
実機動作.(ASRock Z87 Extreme 3)
1/2で出てくる高確率版を作って動作を確認しました.失敗した場合には,Windowsのブートに移ってくれるようです.
今日から1/65536版を入れてブートしますので,運悪く出てきたら追記します.

コード

EDK2のAppPkg/Mainを改変して作りました.
https://github.com/sosoru/sugunikese-on-uefi

その他

元ネタの記事には音もしたとあるので,ビープ音あたりで作れたら面白いのかなぁ.
むかし,SFCの「真・女神転生」やったときは,イケブクロあたりでやめてしまった気がします.RPGツクールの非公式リメイクでようやくエンディングを見ました.3Dダンジョン系はあんまり得意じゃないんだよなぁ.
この界隈のSFCのROM解析は進んでいて,バグを直すパッチが有志でリリースされてるの見たときはとても驚きました.