Brother TD-4750TNWBR RFIDタグへの書込とラベル印刷を両方行う

# 概要
- Brother工業製RFIDプリンタ [TD-4750TNWBR](https://www.brother.co.jp/product/labelprinter/td4750tnwbr/spec/index.aspx) を使ってRFIDタグラベルに「書き込み」と「印刷」を行う



# 経緯
- 公式サンプルには「書き込み」「印刷」それぞれのやり方はあるが、
- １枚のラベルに対して「書き込み」と「印刷」を同時に行うものがなかった
- 公式のサポートに聞いたり、専用コミュニティにも質問しましたがズバリの答えがなかったので試行錯誤の結果を忘備録としてここへ残すことにしました

# 試行錯誤の結果
- FBPL を使って実現します
- 動作環境は最後に記載

## コード
- フォーム上に配したテキストボックスの値をラベルにそのまま印字、
- QRコードとRFIDタグに同じ値を書き込む様にします

---
- Form1
```csharp
using System;
using System.Text;
using System.Windows.Forms;

/// <summary>
/// Form上にテキストボックスを２つ配置
/// (コントロール名：txtHinban, txtEpcCode)
/// Form上にボタンを配置して下記イベントを実装
/// </summary>
private void button1_Click(object sender, EventArgs e)
{
    // プリンタに渡すコマンドを生成します。
    StringBuilder sb = new StringBuilder();
    
    //********************************
    // RFID タグへの書き込み情報
    //********************************
    // イメージバッファを消去します
    sb.AppendLine("CLS");
    
    // RFIDへの書き込み情報セット 
    // 今回はHex文字列を書き込みます
    sb.AppendLine(@"RFID WRITE,0,H,0,@Length,EPC,""@EPCCode""");
    
    // ラベル１枚にRFID書き込み 
    sb.AppendLine("PRINT 1");
    //
    // ここで一旦ラベルが排出されてしまう。このまま印字を続けると次のラベルに印字されるので・・・
    //
    // 今回使用するタグラベルのサイズと同じ89mm戻る。300dpiの時：1mm=12ドットの為、89mm=1068ドット
    sb.AppendLine("BACKFEED 1068");
    
    //********************************
    // ここからラベル印刷情報
    //********************************
    // 印刷方向。1は出力後に人間が視認しやすい方向 
    sb.AppendLine("DIRECTION 1");
    
    sb.AppendLine("CLS");

    // 枠線出力
    BorderBox box = new BorderBox();
    box.TopMM = 4; // mmで指定
    box.LeftMM = 6;
    box.WidthMM = 75;
    box.HeightMM = 75;
    box.Tick = 4; // ドットで指定
    box.radius = 20;
    sb.AppendLine(box.ToCommandString());
    
    // 品番文字列出力
    sb.AppendLine(@"TEXT 100,100, ""5"",0,1,1,""@Hinban""");
    
    // QRコード出力
    //QRCODE x,y,ECC Level,cell width,mode,rotation,[justification,]model,]mask,]area] "content"
    string qr = @"QRCODE 100,200,H,10,A,0,M2, ""@EPCCode""";
    sb.AppendLine(qr);
    
    // テストのためにRFIDに書き込んだ文字列を出力
    sb.AppendLine(@"TEXT 100,480, ""3"",0,1,1,""@EPCCode""");
    
    // ロゴ画像
    string logo = @"PUTBMP 100,700,""LOGO.BMP"",8";
    sb.AppendLine(logo);
    
    // ラベルに印字。1セット1部＝ようするに１枚印刷
    sb.AppendLine("PRINT 1");
    
    // 品番
    string hinban = txtHinban.Text;
    // RFIDタグに書き込むデータ。24桁のHEX文字列
    string epc_data = txtEpcCode.Text;
    // その長さ
    string length = epc_data.Length.ToString();
    
    // 変数セット
    string command = sb.ToString()
        .Replace("@Hinban", hinban)
        .Replace("@EPCCode", epc_data)
        .Replace("@Length", length);
    
    //********************************
    // プリンタにデータを送信
    //********************************
    // スプール開始
    // インストールしたプリンタ名に合わせて下さい
    BROLIB_DLL.openport("Brother TD-4750TNWBR");
    
    // コマンド送信
    BROLIB_DLL.sendcommand(command);
    
    // スプール終了
    BROLIB_DLL.closeport();
    // ここからプリンタがコマンドに沿って動作を始めます
}

/// <summary>
/// 枠線描画用内部クラス
/// 300 dpi(dot per inch=2.54cm)
/// 1mm = 12ドットで計算します。
/// </summary>
private class BorderBox
{
    public BorderBox()
    {
        // 初期値
        Tick = 4;
        radius = 0;
    }
    /// <summary>
    /// 開始上位置（ミリメートル）を指定します。
    /// </summary>
    public int TopMM { get; set; }
    /// <summary>
    /// 開始左位置（ミリメートル）を指定します。
    /// </summary>
    public int LeftMM { get; set; }
    /// <summary>
    /// 幅（ミリメートル）を指定します
    /// </summary>
    public int WidthMM { get; set; }
    /// <summary>
    /// 高さ（ミリメートル）を指定します
    /// </summary>
    public int HeightMM { get; set; }
    /// <summary>
    /// 線の太さ（ドット）で指定します
    /// <para>1mm = 12ドット</para>
    /// </summary>
    public int Tick { get; set; }
    /// <summary>
    /// 角の丸め度。0で直角、数値が大きければ丸め度が大きい
    /// </summary>
    public int radius { get; set; }
    /// <summary>
    /// BOXコマンド文字列を返します
    /// </summary>
    public string ToCommandString()
    {
        int x = this.LeftMM * 12;
        int y = this.TopMM * 12;
        int x_end = (this.LeftMM + this.WidthMM) * 12;
        int y_end = (this.TopMM + this.HeightMM) * 12;
        return ($"BOX {x}, {y},{x_end},{y_end},{this.Tick},{this.radius}");
    }
}

```
- Program.cs に下記クラスを追加
    - 公式のサンプルから必要なものだけを抜粋しました
```csharp
public class BROLIB_DLL
{
    [DllImport("BROLIB.dll", EntryPoint = "openport")]
    public static extern int openport(string printername);
    
    [DllImport("BROLIB.dll", EntryPoint = "sendcommand")]
    public static extern int sendcommand(string printercommand);
    
    [DllImport("BROLIB.dll", EntryPoint = "closeport")]
    public static extern int closeport();
}
```

# 事前準備
- プリンタドライバ([公式](https://support.brother.co.jp/j/b/downloadtop.aspx?c=jp&lang=ja&prod=es_td4750tnwbrjp))をインストールします
- Brother Windows DLL (32bit / 64bit)をダウンロード（[公式](https://online.brother.co.jp/dev/Top.aspx)）
    - 無料会員登録必要です
    - FBPLコマンドリファレンスも含まれています
        - 細かいことを行うにはFBPLリファレンスを学習する必要があります
    - ダウンロードした中のサンプルコードから上述の```BROLIB_DLL```クラスをコピペ
    - ダウンロードした中に含まれる "BROLIB.dll" をプロジェクトの中に含める
        - プロパティを開き、「出力ディレクトリにコピー」【常にコピーする】を選択

# ポイント
- 「書き込み」 or 「印刷」が終了すると用紙は排出されるので、```BACKFEED```コマンドを使って戻してやる必要がある
    - 今回はRFID書き込みから先に行いましたが、先にラベル印刷する場合同様
- 戻す長さはドットで指定するので用紙の長さに合わせて計算しています
    - 今回は89mm の長さを戻すように作りました

# 画像の印刷について
- 画像印刷はいくつか方法があるようですが今回は
    - あらかじめプリンタ本体にBitmap画像を保存
    - 印刷時にBitmapを指定して印刷
- という方法にしました

---
## 画像の準備
### 画像の作成
- 画像の印刷はかなりハマってしまいました
    - FBPLリファレンスをちゃんと読んでなかったのが悪いんですが・・・
- 印刷対応しているのがBMPまたはPCX形式
- モノクロ（1ビット）、または256色（8ビット）のみ
    - 16色やTrueカラー（24ビット）は対応していない
    - BMP画像を作成するときに、画像のビット数を指定する必要がある
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160570/d0a3a2dd-a5a0-9085-61e3-e6de6828e846.png)
- 画像ファイル名は全て大文字にする必要があるとのこと

### 画像をプリンタ本体に転送
- Brother Printer Management Tool （BPM）（[公式](https://support.brother.co.jp/j/b/downloadend.aspx?c=jp&lang=ja&prod=es_td4750tnwbrjp&os=10069&dlid=dlfp100943_000&flang=1095&type3=10428)からダウンロード）を使って転送します
    - ※プリンタドライバが必要です

# 動作環境
### PC
- Windows 11 Pro (x64)
- Visual Studio 2019
- .Net Framework 4.0 
    - C#
- Brother Windows DLL (x64)

### プリンタ
- TD-4750TNWBR
    - USB、有線LANで検証済み
    - リボン TR-S90300
        - リボンは使用するラベルに合わせる必要あり