Progate で Ruby 履修してみた

当方、Ruby Association Certified Ruby Programmer "Silver" (Ver 2.1ではない) を持っているのですが、業務で使うこともなく、趣味でダラダラ Ruby コードを書く日々であります。ひょんなことから、Progate を知り、Ruby 再入門でもしてみるかという気持ちで挑みました。

Progate というのは、複数あるプログラム言語をWeb上で演習を交えながら履修できる勉強サイトで、お試しでいくつかのレッスンを無料で受講できます。(3,4 レッスンすると有料になるのかな?) 私は、月額980円の料金を払って受講していますが、言語を一通り勉強するには月額料を支払う必要があります。また、レベルという概念があり、レッスンを進めていくと経験値がたまり、レベルが上がる仕組みになっており、ゲーム感覚でプログラミング言語を勉強できるようになっています。レベルは言語ごとのレベルとトータルのレベルがあり、トータルのレベルでランキングが作成されているので、今自分がどの位置にいるのかというのが分かりやすく、目標も立てやすいものになっている。

現在あるRubyコースのレッスンをすべて履修してみたが、Rubyの基礎的な部分を的確に教えてくれていて、スライドもポップでキッチュな感じに仕上がっているので、入門としては楽しく学べるだろう。しかし、これですべてのRubyコードを知ることができるかというと疑問が残る部分が多く、このレッスンをきっかけとして他の参考書なり勉強サイトなりで再度勉強しなおすのがいいのだろうというのが当方の感想だった。

シリアル通信で受信完了をできるだけ早く検知する方法 [Raspberry pi]

最近、ラズパイでシリアル通信をするプログラムを書いてる。いくつか詰まったところがあったので備忘録的に残しておこうと思う。

作成したプログラムは以下のリンクにある GitHub - bamchoh/rpi-serialport-test

TERMIOS 構造体

Linux で シリアル通信をするとなると tty 経由でやることになる。tty の設定は TERMIOS 構造体/関数群 でできるわけだが、ASCII ベースの通信の場合に良しなに動作させるための設定となるため、バイナリ通信の場合は、それら設定が邪悪に働く場合がある。例えば、TERMIOS 構造体の c_iflag の一つ IGNCR は、設定すると CR を無視するようになる。つまり ホストから 0x0d を送信しても ラズパイでは受信されない。また、c_oflagにも特定のコードを変換するフラグがある。なので、バイナリ通信をする場合 c_iflag と c_oflag は 0 を設定しておくのが無難だと思っている。今のところ 0 を設定していて困ったことはない。

カノニカルモード/非カノニカルモード

詳しい説明は TERMIOS の MANページ にあるので、そちらを読んでもらうとして、ざっくりとした説明としては、「端末は行単位で処理することが多いので、行入力が終わった段階で処理しよう。というのがカノニカルモード(だと思う。)」 ただ、私がやりたいのはバイナリ通信なので、今回は非カノニカルモードを選択する。非カノニカルモードを選択すると、read(2) の動作が c_cc[VMIN] と c_cc[VTIME] の設定によって、以下のように変わってくる。

  1. MIN が 0, TIME も 0 の場合
    データが利用可能であれば、 read(2) はすぐに返る。このときの返り値は、そのとき利用可能なバイト数か read(2) の要求バイト数のうち小さい方となる。 利用可能なデータがない場合 read(2) は 0 を返す。
  2. MIN が 1 以上, TIME が 0 の場合
    read(2) は、利用可能なデータが MIN バイトに達するまで停止する。返り値は最大でも要求バイト数である。
  3. MIN が 0, TIME が 1 以上
    TIME はタイマーの上限を規定し、単位は 1/10 秒である。 タイマーは read(2) が呼ばれた時点で開始される。 read(2) が返るのは、少なくとも 1バイトのデータが利用可能となった時点、 またはタイマーが時間切れとなった時点である。 入力が全くなくタイマーが時間切れとなった場合、 read(2) は 0 を返す。 read(2) の呼び出し時にすでに利用可能なデータがある場合、 その呼び出しは呼び出し直後にそのデータが到着したかのように振る舞う。
  4. MIN が 1 以上, TIME も 1 以上
    TIME はタイマーの上限を規定し、単位は 1/10 秒である。 入力の最初のバイトが利用可能になった後は、 新たに 1バイト受信する度にタイマーがリセットされる。 read(2) は以下の条件のいずれかを満たした場合に返る。

    1) MIN バイトのデータを受信した。
    2) バイト間タイマーが時間切れになった。
    3) read(2) で要求されたバイト数のデータを受信した (POSIX ではこの終了条件は規定されておらず、 他のいくつかの実装では read(2) はこの条件では返らない)。

    タイマーは最初のバイトが利用可能になった時点で開始されるので、 少なくとも 1 バイトは読み出される。 read() の呼び出し時にすでに利用可能なデータがある場合、 その呼び出しは呼び出し直後にそのデータが到着したかのように振る舞う。

今回は、データを受信しきったら速やかに次の処理に移行する必要があったため、read でブロックされることは避けたかった。とはいえ、ブロックしない 1 の場合、自前でポーリングループを作成する必要があり、CPUのパワーも使ってしまうので、それも避けたかった。折衷案として、3が候補になるが、タイムアウトが最短でも100msというのは遅すぎた。

受信完了

さて、先ほど「データを受信しきったら」と言ったが、何をもって受信しきったといえるだろうか?ファイルであれば、EOF(-1)ならデータを読み切ったといえるだろうし、ASCII通信であればCR(13)/LF(10)が来れば終わりといえるかもしれない。つまり、扱うデータによってその定義は変化する。今回はバイナリ通信なので、CR/LFやEOFをデータの終わりとすることはできない。

なのでバイト間タイムアウトというものを使う。これは何かというと、最後のバイトを受信してから一定期間データを受信しなかったら、タイムアウトを発生させるというもの。連続して受信している時点ではタイムアウトは発生しないので、タイムアウトが発生したらデータを受信しきった。と判断できるといえる。

ただ、先ほども言っていた通り、TERMIOSで設定するVTIMEは最小で100msとシリアル通信としては比較的遅いタイムアウトになってしまうため、別のタイムアウトの機構が必要になってくる。

ハードウェア受信割り込み

そこで登場するのがハードウェア割り込みだ。シリアル通信チップから受信時の割り込みを発生させ、CPUがそれを捕捉する。今使っているラズパイはRaspberry pi 2 Model B なので シリアル通信は CPU上に組み込まれている PL011 UART が行っている。PL011 にはいくつかの割り込みがあるが、今回注目すべきなのはその中の2つ、受信割り込みと受信タイムアウト割り込みだ。

受信割り込み

受信割り込みは受信データの量があらかじめ決められた量(ウォーターレベル)を超えた時に発生する。ウォーターレベル、UARTが持っているFIFOのサイズ に対して割合で指定する。Raspberry Pi では PL011のFIFO サイズを16バイトに設定してあり、その半分のデータが受信されれば、受信割り込みが発生する。つまりは8バイト受信するごとに割り込みが発生する。

受信タイムアウト割り込み

受信タイムアウト割り込みは32bitの間、何もデータが受信されなかった場合に発生する。ただし、元からFIFOにデータがない場合は発生しない。

データ完了待ち戦略

ということは、受信データが8バイトであれば次のデータがあるかもしれないので、受信待ちを継続し、8以外であれば受信終了とすればいい。

...

本当にそうだろうか?例えば、データが8バイト丁度の時は結局、TERMIOSのバイト間タイムアウトに頼るしかなくなってしまう。この案は失敗だった。

バイト間タイマーの単位変更

次に思いついたのが、バイト間タイマー(VTIME)の単位を100msからもっと小さい値に変更することだった。ただ、全ての端末に関係することと、カーネルの修正が必要だったことからこの方法は実験していない。

自前でタイマーを持つ

バイト間タイマーが遅いので、自前でタイマーを持つ戦略をとる。select や poll をタイマ付きで使うことで read にタイムアウトをつける。ただ、何も受信していないときにタイムアウト付きで待つと必ずタイムうアウトになり、かつ、タイムアウトの周期を1msとかにしてしまうと、1msごとにread → タイムアウトのループが発生して結局CPUパワーを使ってしまうので、ハードウェア受信割り込みと併用して、8バイトの受信があったときのみ 自前のタイマーで待ち受けるという処理にした。この戦略は意外とうまくいったのだが、今回はこの方法を見送った。 というのも、通信ボーレート(bps)に依ってタイムアウトを変更したいと思っていたのだが、2400 bps だと最大で約40ms の待ちを設けなければならないことが分かったためだ。なぜ、そうなるのかを説明すると、8バイト受信後、まだデータが存在していないことがわかるのは、次の受信割り込みのタイミングまでの時間で割り込みがかからなかった場合に判明する。受信割り込みのほうが割り込みがかかる時間が長いので、そちらがかからない場合にようやく次の受信がなかったということになる。8バイト受信に必要な時間は、8バイト × (1バイトに含まれるビット数) / (ボーレート) となるので、1バイトに含まれるビット数が11bit だとすると、0.036 秒 つまり 36 ms となる。ある程度の遅延があるとして約40ms のタイムアウトになる。

1バイトだけ残す

FIFOにデータがない場合は受信割り込みがかからないということは、逆に言えば、1バイトでもデータがあれば、受信タイムアウト割り込みによって割り込みが発生するということになる。なので、8バイト受信したときにFIFOから7バイト受信するだけにしておき、1バイト残しておけば、次のサイクルで受信データがなければ受信タイムアウト割り込みが発生するはずだ。ということは、受信タイムアウトが発生した場合は必ずデータがそこで終了したということを表すことになる。そのためにはシリアルドライバの修正が必要になる。

github.com

Raspbian のソースは上記リポジトリにある。その中のPL011ドライバのソースは drivers/tty/serial/amba-pl011.c がそれに当たる。修正箇所は以下の通り

  1. uart_amba_port に 受信タイムアウト割り込み回数を記録するための変数を用意
  2. pl011_ioctl 関数を実装
  3. pl011_fifo_to_tty 関数 で受信タイムアウト割り込みの個数をカウント
  4. pl011_fifo_to_tty 関数 で受信割り込みの場合はFIFOからのデータ転送を7バイトに抑える処理を追加
  5. pl011_fifo_to_tty 関数 で割り込みステータスを判定するために 呼び出し元関数(pl011_rx_chars, pl011_int)を修正

uart_amba_port 構造体

受信タイムアウト割り込みが発生したことをユーザープログラムから認識するために、受信タイムアウト割り込みが発生した個数を記録しておき、ioctrl で取得するようにする。そのために、uart_amba_port にカウンタ変数を用意する。uart_amba_port は PL011 UART ポートに必要な情報を格納するための構造体。汎用UARTポート構造体の uart_port をラップする形で定義されている。

struct uart_amba_port {
    struct uart_port   port;
    const u16      *reg_offset;
    struct clk     *clk;
    const struct vendor_data *vendor;
    unsigned int      dmacr;      /* dma control reg */
    unsigned int      im;     /* interrupt mask */
    unsigned int      old_status;
    unsigned int      fifosize;   /* vendor-specific */
    unsigned int      old_cr;     /* state during shutdown */
    bool           autorts;
    unsigned int      fixed_baud; /* vendor-set fixed baud rate */
    char           type[12];
    unsgined long rxto; /* ← この行を追加 */
#ifdef CONFIG_DMA_ENGINE
    /* DMA stuff */
    bool           using_tx_dma;
    bool           using_rx_dma;
    struct pl011_dmarx_data dmarx;
    struct pl011_dmatx_data    dmatx;
    bool           dma_probed;
#endif
};

pl011_ioctl 関数

amba-pl011.c にはioctlが実装されていないので、実装する必要がある。

static int pl011_ioctl(struct uart_port *port, unsigned int cmd, unsigned long arg)
{
    struct uart_amba_port *uap =
        container_of(port, struct uart_amba_port, port);

    int ret = -ENOIOCTLCMD;

    switch(cmd) {
        case 0x80000001:
        {
            unsigned long *counter = (unsigned long *)arg;
            if(arg != 0)
                *counter = uap->rxto;
            ret = 0;
            break;
        }
    }
    return ret;
}

この関数を上位から呼び出すために amba_pl011_pops 構造体に追加する

static const struct uart_ops amba_pl011_pops = {
    .tx_empty   = pl011_tx_empty,
    .set_mctrl  = pl011_set_mctrl,
    .get_mctrl  = pl011_get_mctrl,
    .stop_tx    = pl011_stop_tx,
    .start_tx   = pl011_start_tx,
    .stop_rx    = pl011_stop_rx,
    .enable_ms  = pl011_enable_ms,
    .break_ctl  = pl011_break_ctl,
    .startup    = pl011_startup,
    .shutdown   = pl011_shutdown,
    .flush_buffer   = pl011_dma_flush_buffer,
    .set_termios    = pl011_set_termios,
    .type       = pl011_type,
    .release_port   = pl011_release_port,
    .request_port   = pl011_request_port,
    .config_port    = pl011_config_port,
    .verify_port    = pl011_verify_port,
    .ioctl = pl011_ioctl, /* ← この行を追加する */
#ifdef CONFIG_CONSOLE_POLL
    .poll_init     = pl011_hwinit,
    .poll_get_char = pl011_get_poll_char,
    .poll_put_char = pl011_put_poll_char,
#endif
};

この関数はserial_core.c 内の ioctl関数から、与えられたコマンドに合致しない場合のみ呼ばれる。

pl011_fifo_to_tty 関数

pl011_fifo_to_tty 関数にて FIFOからttyのバッファにデータを転送しているので、その部分で受信タイムアウト割り込みが発生した回数を記録すると同時に、受信割り込みだった場合は転送を7バイトに抑える処理を入れる。

// まず、引数に割り込みステータス(stat)を追加する
static int pl011_fifo_to_tty(struct uart_amba_port *uap, unsigned int stat)
{
    u16 status;
    unsigned int ch, flag, max_count = 256;
    int fifotaken = 0;

// 受信タイムアウト割り込みのカウンタを増やす
    if (stat & UART011_RTIS)
        uap->rxto++;

    while (max_count--) {
        status = pl011_read(uap, REG_FR);
        if (status & UART01x_FR_RXFE)
            break;

// 受信割り込みの場合はデータ転送量を7バイトに制限している
        if (stat & UART011_RXIS && fifotaken >= 7)
            break;

        /* Take chars from the FIFO and update status */
        ch = pl011_read(uap, REG_DR) | UART_DUMMY_DR_RX;
        flag = TTY_NORMAL;
        uap->port.icount.rx++;
        fifotaken++;

        if (unlikely(ch & UART_DR_ERROR)) {
            if (ch & UART011_DR_BE) {
                ch &= ~(UART011_DR_FE | UART011_DR_PE);
                uap->port.icount.brk++;
                if (uart_handle_break(&uap->port))
                    continue;
            } else if (ch & UART011_DR_PE)
                uap->port.icount.parity++;
            else if (ch & UART011_DR_FE)
                uap->port.icount.frame++;
            if (ch & UART011_DR_OE)
                uap->port.icount.overrun++;

            ch &= uap->port.read_status_mask;

            if (ch & UART011_DR_BE)
                flag = TTY_BREAK;
            else if (ch & UART011_DR_PE)
                flag = TTY_PARITY;
            else if (ch & UART011_DR_FE)
                flag = TTY_FRAME;
        }

        if (uart_handle_sysrq_char(&uap->port, ch & 255))
            continue;

        uart_insert_char(&uap->port, ch, UART011_DR_OE, ch, flag);
    }

    return fifotaken;
}

pl011_rx_chars, pl011_int 関数

pl011_fifo_to_tty の中で割り込みステータスを判定するために、呼び出し側からステータス情報を渡してやる必要がある。pl011_fifo_to_ttypl011_rx_chars から呼ばれており、その関数はpl011_intから呼ばれている。割り込みステータスはpl011_intで取得されるのでそのデータをpl011_rx_chars経由で渡してやる必要がある。

static irqreturn_t pl011_int(int irq, void *dev_id)
{
    struct uart_amba_port *uap = dev_id;
    unsigned long flags;
    unsigned int status, pass_counter = AMBA_ISR_PASS_LIMIT;
    u16 imsc;
    int handled = 0;

    spin_lock_irqsave(&uap->port.lock, flags);
    imsc = pl011_read(uap, REG_IMSC);
    status = pl011_read(uap, REG_RIS) & imsc;
    if (status) {
        do {
            check_apply_cts_event_workaround(uap);

            pl011_write(status & ~(UART011_TXIS|UART011_RTIS|
                           UART011_RXIS),
                    uap, REG_ICR);

            if (status & (UART011_RTIS|UART011_RXIS)) {
                if (pl011_dma_rx_running(uap))
                    pl011_dma_rx_irq(uap);
                else
                    pl011_rx_chars(uap, status); // ステータスを渡してやる必要がある
            }
            if (status & (UART011_DSRMIS|UART011_DCDMIS|
                      UART011_CTSMIS|UART011_RIMIS))
                pl011_modem_status(uap);
            if (status & UART011_TXIS)
                pl011_tx_chars(uap, true);

            if (pass_counter-- == 0)
                break;

            status = pl011_read(uap, REG_RIS) & imsc;
        } while (status != 0);
        handled = 1;
    }

    spin_unlock_irqrestore(&uap->port.lock, flags);

    return IRQ_RETVAL(handled);
}
// ステータス用の引数を足す
static void pl011_rx_chars(struct uart_amba_port *uap, unsgiend int status)
__releases(&uap->port.lock)
__acquires(&uap->port.lock)
{
    pl011_fifo_to_tty(uap, status); // ステータスを渡す

    spin_unlock(&uap->port.lock);
    tty_flip_buffer_push(&uap->port.state->port);
    /*
    * If we were temporarily out of DMA mode for a while,
    * attempt to switch back to DMA mode again.
    */
    if (pl011_dma_rx_available(uap)) {
        if (pl011_dma_rx_trigger_dma(uap)) {
            dev_dbg(uap->port.dev, "could not trigger RX DMA job "
                "fall back to interrupt mode again\n");
            uap->im |= UART011_RXIM;
            pl011_write(uap->im, uap, REG_IMSC);
        } else {
#ifdef CONFIG_DMA_ENGINE
            /* Start Rx DMA poll */
            if (uap->dmarx.poll_rate) {
                uap->dmarx.last_jiffies = jiffies;
                uap->dmarx.last_residue  = PL011_DMA_BUFFER_SIZE;
                mod_timer(&uap->dmarx.timer,
                    jiffies +
                    msecs_to_jiffies(uap->dmarx.poll_rate));
            }
#endif
        }
    }
    spin_lock(&uap->port.lock);
}

カーネルコンパイル

修正が終わったら、カーネルコンパイルする必要がある。方法は公式ドキュメントが詳しく正しいのでそちらを参考にしてもらいたい。

Kernel building - Raspberry Pi Documentation

修正版カーネルを使っての読み出し

修正版を使っての読み出しサンプルをリポジトリに公開した。そちらを参考にしてほしい。

github.com

サンプルでは受信データ数と受信割り込み数が前回値と異なっていた場合に受信完了としている。これは、受信割り込み数のみをチェックしていた場合、タイミングによっては、ttyバッファからの読み取りが途中であっても、受信割り込み数が異なる場合があったので、確実性を重視して、受信データ数もカウントに入れている。

今後の課題

最終的に理想に近い形で受信完了を判定できたが、115200 bps で送受信を繰り返していると、数十回に一回、数msのウェイトが入ってしまう場合があるようだ。nice 値を -20 に設定したとしても発生するが、何が原因が突き止められていない。できれば安定した受信ができればよいのだが今のところ目処が立たないでいる。いつかはそのあたりも解決していきたい。 (追記 : 2018/08/26 23:35) chrt コマンドを使用して、最大プライオリティを設定することで安定動作になることが判明した。

$ sudo chrt -f 99 ./a.out -b 115200 -f result.txt

ただ、ほかのタスクが完全に止まってしまうのでリスクはある。それを考えると、やはりシビアなタイミングを実行するようなプログラムはマイコンリアルタイムOSを使用するのが無難なのかもしれない。

ラズパイ2のシリアル回りを調べる

ラズパイ3でベアメタル - QEMUでUART(PL011) - へにゃぺんて@日々勉強のまとめ

PL011 の PDF

http://infocenter.arm.com/help/topic/com.arm.doc.ddi0183g/DDI0183G_uart_pl011_r1p5_trm.pdf

カノニカルモードとは

端末のカノニカルモード - 技術メモ帳

FILE の read がどのように実装されているか?

デバイスドライバにreadを実装する - Linuxデバイスドライバ開発

VTIME/VMIN の関係

termios.c_cc の VMIN VTIME について(メモ) - 壊れたメガネ

シリアルドライバに関係する関数的なやつ?

https://www.kernel.org/doc/Documentation/serial/driver

IRQ について

割り込み

ここで受信IO完了を待っているっぽい、VMIN > 0 の時は timeout が MAX_SCHEDULE_TIMEOUT に設定されるので、完全ブロックになる。VMIN = 0 の時は timeout が VTIMEの値になるので、このwaitに来る前にbreakが走って抜ける。

linux/n_tty.c at rpi-4.14.y · raspberrypi/linux · GitHub

FIFOの段数はここで決まっている。UART011_IFLS_RX4_8 は 1/2 ウォーターマークの意味。include/linux/amba/serial/h にその定義がある

linux/amba-pl011.c at rpi-4.14.y · raspberrypi/linux · GitHub

受信割り込みのマスクの設定はここで行っている

linux/amba-pl011.c at rpi-4.14.y · raspberrypi/linux · GitHub

Vimを毎日自分用にビルドするようにした

github.com

モチベ

さきっちょ追いかけてるおれかっこいいがしたかった。

何ができるか

github に上がってる vim をビルドして、kaoriya-vimのexeと差し替えます

何が必要か

どうしたらできるか

git clone https://bamchoh/build-my-vim してもらって build.bat を実行すればできます

ただ、ruby だとか mysys2 だとかのパスが固定なので、そこは自分の環境に合わせてもらう必要があるので

自分用にするならフォークしてもらったほうがいいかも。あくまで私のリポは参考程度で。

Windowsのシリアル通信でBREAK信号を送受信する

https://msdn.microsoft.com/ja-jp/library/cc429842.aspx

https://github.com/bamchoh/go-serial/commit/f74298f65a6834a06357510f1d208ff8df764c11

SetCommMask で EV_BREAK を設定しておくこと。そうしないと、エラーになる。

あと、立ち上がりは検出できるけど、立下りは検出できなさそう。

https://ja.stackoverflow.com/questions/41123/rs232c%E3%81%AEbreak%E4%BF%A1%E5%8F%B7%E3%81%AEon-off%E3%81%AE%E6%A4%9C%E5%87%BA%E3%82%92windows%E3%81%A7%E8%A1%8C%E3%81%86%E3%81%AB%E3%81%AF

一応、BREAK信号を送信するのと、受信(?)するののサンプルは以下のような感じで

package main

import (
    "bufio"
    "flag"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/bamchoh/go-serial"
)

type SIO struct {
    port serial.Port
    txq  chan []byte
    rxq  chan []byte
}

func (sio *SIO) Start() {
    go sio.Write()
    go sio.Read()
}

func (sio *SIO) Write() {
    for {
        q := <-sio.txq
        log.Printf("send: %q", q)
        sio.port.Write(q)
    }
}

func (sio *SIO) Read() {
    err := sio.port.SetCommMask(0x0041)
    if err != nil {
        fmt.Println(err)
    }
    for {
        buf := make([]byte, 128)
        n, err := sio.port.Read(buf)
        if err != nil {
            fmt.Println(err)
            time.Sleep(1 * time.Second)
            continue
        }
        sio.rxq <- buf[:n]
    }
}

func (s *SIO) SetBreak() {
    s.port.SetBreak()
}

func (s *SIO) ClearBreak() {
    s.port.ClearBreak()
}

var port = flag.String("p", "COM3", "port name")
var baud = flag.Int("b", 9600, "baud rate")

func main() {
    flag.Parse()
    c := &serial.Mode{BaudRate: *baud}
    s, err := serial.Open(*port, c)
    if err != nil {
        log.Fatal(err)
    }

    txq := make(chan []byte, 5)
    rxq := make(chan []byte, 5)

    sio := SIO{
        s,
        txq,
        rxq,
    }

    go sio.Start()

    go func() {
        scanner := bufio.NewScanner(os.Stdin)
        for scanner.Scan() {
            text := scanner.Text()
            s.SetBreak()
            time.Sleep(10 * time.Millisecond)
            s.ClearBreak()
            txq <- []byte(text)
        }
    }()

    for {
        q := <-rxq
        if err != nil {
            log.Fatal(err)
        }
        log.Printf("recv: %v", q)
    }
}

余談だけど、LinuxだとBREAK信号の受信が\FF\00\00という3バイトに変換されてしまうみたい。 バイナリデータの送受信だと、このバイト列が来るようなプロトコルを使うようなものだと BREAK信号を送信できないという制限がかかってしまう。回避策ってないのかな?

https://stackoverflow.com/questions/14803434/receive-read-break-condition-on-linux-serial-port

追記(2018/7/17) Linux の BREAK信号の受信方法がなんとなくわかった。

Armadillo460シリアルとTFTP | アットマークテクノ ユーザーズサイト

ここによると、ioctlのTIOCGICOUNTを使うとできるらしい。 ラズパイで、確かにBREAK信号を受信するとカウントアップしていることが確認できた。 カウントが上がってることと、\xFF\x00\x00 のバイト列が来てることをダブルチェックして BREAK信号を認識できるかもしれない。O_NONBLOCKなので、ループがすごい回ってしまうのが どうにかしたいところ。

(追記2018/7/18) 非カノニカルモードにすることでブロッキングしていても、指定のバイト数を受信すれば 処理が戻るようにできた。後、残ってる課題は カウンタのクリアの仕方かなぁ

The Linux Serial Programming HOWTO: $B%W%m%0%i%`Nc(B

以下にサンプルを例示しておく。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <linux/serial.h>

#define SERIAL_PORT "/dev/ttyAMA0"

int main(int argc, char *argv[])
{
    unsigned char msg[] = "serial port open...\n";
    unsigned char buf[255];
    int fd;
    struct termios tio;
    int baudRate = B9600;
    int i;
    int len;

    fd = open(SERIAL_PORT, O_RDWR|O_NOCTTY);
    if (fd < 0) {
        printf("open error\n");
        return -1;
    }

    tio.c_cflag += CREAD;
    tio.c_cflag += CLOCAL;
    tio.c_cflag += CS8;
    tio.c_cflag += 0;
    tio.c_cflag += 0;

    cfsetispeed(&tio, baudRate);
    cfsetospeed(&tio, baudRate);

    // non canonical mode setting
    tio.c_lflag = 0;
    tio.c_cc[VTIME] = 0;
    tio.c_cc[VMIN] = 1;

    ioctl(fd, TCSETS, &tio);

    // send break signal for 100 ms(?)
    tcsendbreak(fd, 100);
    tcdrain(fd);

    // write 2 bytes
    buf[0] = 'A';
    buf[1] = 'B';
    len = 2;
    write(fd, buf, len);
    tcdrain(fd);

    // waiting for receive any data
    while (1) {
        len = read(fd, buf, sizeof(buf));
        if (0 < len) {
            // check break detection count
            struct serial_icounter_struct icount;
            ioctl(fd, TIOCGICOUNT, &icount);
            printf("brk: %d", icount.brk);

            // print received data
            for(i = 0; i < len; i++) {
                if(i != 0) {
                    if((i % 8) == 0) {
                        printf("\n");
                    } else {
                        printf(":");
                    }
                }
                printf("%02X", buf[i]);
            }
            printf("\n");
        }
    }
    close(fd);
    return 0;
}

【ブロク読書感想文】→ テスト自動化の理論と技術と戦略:LINE Developer Meetup Tokyo #39 - Testing & Engineering

読書したブログ

engineering.linecorp.com

これを読んでてよくわからない単語があったので、調べてメモ。 後で考えるときの参考にできればいいなという感じ。

違和感のアンドン化

アンドン - Wikipedia

違和感を見える場所に書き出して、リスク的なものをみんなと共有できてイイネ。ということだろうか? リスク共有しても「そうだねー、そういうリスクあるよねー問題だよねー」みたいなので終わりがちなので注意していきたい。

フロントローディング

フロントローディングとは? |【エン転職】

「初期段階で品質の作りこみを綿密にする」という意味らしい。 でも、品質を作りこむのって時間がかかるし、試作からのブラッシュアップのほうが いいような気もするんだけど、早く品質を作りこむ手法みたいなのあるのだろうか? 気になる。

インダストリー 4.0

インダストリー4.0 - Wikipedia

「IoTやAIを用いることによる製造業の革新」という意味合いが強そう。 AIというか機械学習による不具合発見の効率化には興味がある。

デジタルツイン

Digital Twin(デジタルツイン) | IoT

「工場や製品などに関わる物理世界の出来事を、そっくりそのままデジタル上にリアルタイムに再現する」ということらしい。 仮想現実のようなものだろうか?物理的な制約がなくなるので、テストもしやすいよねってことなのかなー?

QAアーキテクチャ

QAアーキテクチャの設計による 説明責任の高いテスト・品質保証 Qaアーキテクチャの話 QAアーキテクチャはプロセスアーキテクチャか? by @YasuharuNishi - Togetter

まだちゃんと読んでない。後で読む(ほんとか?)

SaPID

ソフトウェアプロセス改善手法“SaPID”による自律改善実践セミナー| JUSE-SQiPセミナー

アドホックテスト

アドホックテスト(あどほっくてすと) - ITmedia エンタープライズ

俺が探索的テストだと思っていたものはアドホックテストだったのか...

VSTeP

VSTeP – Qualab

SPLIT

Cookpadエンジニアが解説 “テスト自動化”を実現する「SPLIT」とはなにか? - ログミーTech(テック)

Cookpad TechConf 2018

まとめ

こういう発表資料を見ると、なるほどなー。たしかになー。という気分になるんだけど。 じゃぁ、それをどう自分の関わっているプロジェクトに落とし込んでいこうか。ということを考えると まず上司を説得して、プロセスを改善して。。。みたいなことになるので、まず上司を説得するためには どうするればいいのか。みたいなのを学びたい気もする。

友達同士でもくもく会した

要約

友達同士でもくもく会して、意外と捗って良かった

もくもく会とは?

自宅には誘惑がいっぱいあるので、プログラミングとかやろうと思ってもなかなか捗りませんよね。でも周りに人が居るとよく見られようという意識が高まって誘惑に負けずに作業に集中できる。という効果があります。カフェで勉強すると意外と捗ったりするのはそういう理由ですね。でも、カフェだとガヤガヤしてて集中できない。という人も居ると思います。そこで「もくもく会」です。もくもく会では、静かな場所で集中して作業したい人が集まることで、集中力を高めながら、作業を捗らせる。ということを目的としています。また、同じ同志が集まることで、問題解決をみんなで出来るという強みもあり、一人で作業するよりも何倍も効率が上がる!というもくろみがあります。

友達同士でもくもく会

とはいえ、そういう会に参加するのは、結構ハードルが高いものです。私もその一人でした。ですが偶然、友達が「もくもく会」に興味があり、それに賛同したことから、あれよあれよと「友達同士でのもくもく会」が開催されることが決定しました。もくもく会といっても、上で述べたような効率よく作業しよう!という感じではなく、ゆるく集まって作業できたらいいよね。ぐらいの気持ちでした。現に、「もくもく会」ではなく、「もぐもぐ会」という名前になってて、一部のメンバーはお菓子を食べながら雑談をしたりしてましたし(笑)

なんしか、「もくもく会」というものがどういうもので、どれだけ効果があるのか?友達とやっても効果があるのか?という実験的な会だったと思います。

「もぐもぐ会」感想

日曜の13時から始まって、18時に解散でした。はじめは「5時間もするのかー」という気持ちでしたが、意外と集中して作業していたということもあってかすぐに時間が過ぎたなーという感想です。

開始する前に、みんなで今日やることを言い合ってからもくもく会開始、終了の30分前に何をやったかを発表して終わりました。発表といっても大したことではなく、「○○を実装しようと思ったけど、ここまで作って時間切れでした~」ぐらいの軽い感じです。これぐらい軽いほうが参加しやすいかな。とも思いました。

合計8人の参加者がいて、そのうち3人は「もぐもぐ会」を実施、5人は「もくもく会」を実施しました。「もぐもぐ会」メンバーは結構気を使って会話のトーンを落としてしゃべってくれたってのもあって、良いBGM感という感じでした。集中が切れたら、私ももぐもぐ会に10分ほど参加したりして、それなりのリフレッシュも入れられましたし。差し入れもうまかった。

作業自体は集中できたと思います。完全にYoutube/Twitterをシャットアウトできましたし、最初にやることを明確にしていたということもあり、ここまではやりきるぞ!という気持ちで作業ができたので、いつも以上に集中できたと思います。

今後

今回の会場はマンションの一室を借りて実施しました。(借りたのは友達ですが) 作業スペースはきれいでよかったのですが、トイレが普通に1ルームのトイレだったので、音漏れが気になったので、もし次やるならそういうところも気にしないといけないかなーと思いました。

友達同士でやるもくもく会はストレスフリーにもくもく出来ることがわかったので、これからも定期的に実施していけたらなと思います。

それとは別に、外部のもくもく会にも参加していくことで自分のスキル向上もあるのかなと思っています。 とりあえず、まずは、ネット上でもくもく会が開催されているようなので、それに参加しようかなぁ。

mockmock.connpass.com