コンテナに入るのに docker ps してから docker start して docker exec するのめんどくさいから一度にできるコマンド作った

結論

作った。Goで。 github.com

経緯

パソコン起動してから、dockerのコンテナに入るのにコマンドをいくつも叩かないといけないのがめんどくさかったから。 叩くコマンドは以下の3つ * docker ps -a * docker start <コンテナ名> * docker exec -it -e <デフォルト環境変数> <コンテナ名> bash

コマンドの内容としては、単純で docker ps -a --format "{{.Name}}" で出力されたコンテナ名を cho で選択して、それを docker start に渡して実行した後、docker exec をコンテナ名とともに起動しているだけ。

cho に関しては、以下のリンクを参照。パイプで渡された文字列に対して行単位で選択ができるコマンド。今回作ったコマンドを実行するには必要なので、別途 go get https://github.com/mattn/cho でインストールしておく必要がある。 mattn.kaoriya.net

知見

docker ps の書式指定

docker ps--format というのがあり、表示するフォーマットをGoのtemplate書式で指定できる。 docker ps --format "{{.Name}}" とすれば、コンテナ名のみ表示され docker ps --format "table {{.ID}}\t{{.Labels}}"とすれば、ヘッダ行を追加した状態でIDLabel`が表示されるので便利。

go-pipeline の Output で返される文字列

docker pscho を繋ぐために、今回は go-pipeline (GitHub - mattn/go-pipeline) を使用したが、返される文字列をそのまま exec.Command() の引数として使うとエラーとなる。理由としては文字列の最後に改行文字が入っており、コンテナ名が存在しない。となるからだ。解決方法としては、最後の1バイトを取り除いた部分文字列を渡すことで解決した。

docker exec が動かない

上記問題を解決したうえで docker exec が実行時にエラーとなっていた。原因は exec.Command で作成した Cmd 構造体の Stdin と Stdout にちゃんと標準入力と標準出力をセットしていなかったことが原因だった。nil のままだと NULLデバイス(os.DevNull)にアクセスするようだ。(https://golang.org/pkg/os/exec/#Cmd)

今後の課題

  • 今は 環境変数を固定で指定しているが、コマンドを実行するタイミングで指定できるようにもしたい
  • また、デフォルトの環境変数を rcファイルに設定しておいて、それをデフォルトで読み込めるようにもしたい
  • 現状の名前が goto_docker_container と長いので、何か良い名前を付けてやりたい

JavascriptでDateのインスタンスを作成するときにタイムゾーンを指定しなかった場合の時刻のパースのされ方がiPhoneとWindowsと異なる件

ちょっと躓いたのでメモ

以下のコードがあったとして

alert(new Date("2018-12-12T23:59"));

Windows上の Chrome で実行すると Thu Dec 13 2018 23:59:00 GMT+900 (日本標準時) と表示される iPhone上の Safari だと Fri Dec 14 2018 08:59:00 GMT+0900 (JST) と表示される ちなみに iPhone上の ChromeでもSafari と同様の時刻になるため、OSレベルで違いがある模様。 Pixel3上の Chrome では Windows上の Chrome と同様の表示となった。

この違いを吸収するにはちゃんとタイムゾーンまで指定してやる必要がある

alert(new Date("2018-12-12T23:59+09:00"));

:terminal で開いたコマンドプロンプトからvimを起動してカレントディレクトリを表示させる

ユースケースとしては、vimを起動して、:terminal でコマンドプロンプトを起動したとき、カレントディレクトリが操作したいディレクトリじゃなくて、操作したいディレクトリまで移動して、移動先でvimで編集したいファイルが出てきたときに、vimに戻ると、カレントディレクトリが違うから、またおんなじようにディレクトリ移動しないといけなくなるのがめんどくさいっていうケース。ありますよね?(脅迫)

そういう時は --remote-send を使って vim を実行しましょう。

> vim --servername %VIM_SERVERNAME% --remote-send "何か実行したいコマンド"

%VIM_SERVERNAME% は :terminal で起動したコマンドプロンプトのみで設定される環境変数らしく、これを使うと、親vimに対して--remote-send できるようになります。"何か実行したいコマンド" にいつも通りコマンドを打つとvim上でコマンドが実行されてハッピーです。

ただし、コマンドの前にウィンドウのフォーカスをコマンドプロンプトからVimに移動しておかないと、コマンドプロンプト上にコマンドが入力されてしまうので注意です。.vimrc で何も設定していないのであれば、フォーカス移動のキーバインド<C-W><C-W> のはずです。なので、何か実行したいコマンドの前にプレフィックス的に <C-W><C-W> を入力すればOK

例えば、コマンドプロンプトで開いているカレントディレクトリにVimで移動したい場合は以下のようにコマンドを打てばいいことになります。

> vim --servername %VIM_SERVERNAME% --remote-send "<C-W><C-W>:cd %CD%<CR>"

... 長いですね。このコマンドを一発で打てるようなバッチファイルかコマンドエイリアスを作っておくといいのかもしれません。

ラズパイ2 の有線LANを固定IPにする

kinakomochi-tank.hatenablog.com

この通りにすればイける。 が、結果確認の項目はすっ飛ばして、リブートした。なぜなら、ifconfig down でeth0 がないと言われるため。

CoDeSys V3 には Pythonスクリプトを実行する機能がついている

CoDeSys っていう会社がありまして、FA業界ではまぁ有名なほうなんですが

https://www.codesys.com/

例えば、Raspberry PI を使って 簡単に UI やらをサクッと作ってスマートホームをチャラっと実現することも 夢じゃないとかなんとか。(※ だいぶ大げさに言ってる)

www.youtube.com

まぁ、そんなCoDeSys っつーツールなんですが、起動時にコマンドラインからPythonスクリプトを渡すことで、動作を自動的に実行することができそうなんです。

https://forum.codesys.com/viewtopic.php?f=18&t=1890

ここら辺に中の人がいくつかスクリプト上げてるんで、それを参考にやると、結構自動で動いてくれたりします。

今のところ、POU の操作は PLCopen XML 経由でしかできないみたいですが、そもそも POUを作っている場合で

単純に、動作を自動化したいときなんかは重宝しそうですね。

サンプルは読み出ししかなかったので、下に書き込みもできるバージョンを張っておきます。

PLC_PRG っていうPOUの下に WORD1 っていう変数があること前提です。

# We use the python 3 syntax for print
from __future__ import print_function

# Our project file is in the data subdirectory of the directory containing
# the script, so we calculate the appropriate path.
import os
scriptdir = os.path.dirname(__file__) # Directory of our script.
datadir = os.path.join(scriptdir, "Data") # Enter the subdirectory.
projectpath = os.path.join(datadir, "untitled.project") # And add the project name.

# Now, we can open the project.
proj = projects.open(projectpath)

# We fetch the active application.
app = proj.active_application

# And create the online application for it.
onlineapp = online.create_online_application(app)

# We login into the device.
onlineapp.login(OnlineChangeOption.Try, True)

# We start the application, if necessary.
if not onlineapp.application_state == ApplicationState.run:
    onlineapp.start()

# We let the app do its work for some time...
system.delay(1000)

# 値を設定
onlineapp.set_prepared_value("PLC_PRG.WORD1", "100")

# 値を書く
onlineapp.write_prepared_values()

# We read the value of the variable we're interested in.
value = onlineapp.read_value("PLC_PRG.WORD1")

# We output the value to standard output, so our caller can process it.
print(value)

# Last but not least, we clean up.
onlineapp.logout()
proj.close()

コマンドプロンプトからは以下のようにして起動するみたいです

C:\Program Files\3S CoDeSys V3.4 SP3\CoDeSys\Common>start /wait CoDeSys.exe --profile="CoDeSys V3.4 SP3" --noUI --runscript="D:\TestScripts\ReadVariable.py"

--profile に指定する文字列は自身が使っているバージョンとマッチさせる必要があります。一旦 start /wait CoDeSys.exe で起動して出てくるプロファイル一覧からお好みのものを選ぶといいと思います。私の場合は CODESYS V3.5 SP13 Patch 1 でした。

Arduino でパリティエラーチェックを実装する

私が使ったのは Arduino MEGA 2560 R3 の互換機。

Aruduino IDE には Aruduino 本体のソースコードも含まれており、本体のソースコードを編集すると、スケッチをコンパイルする時に本体が修正されていることを検知し、同時にコンパイルしてくれる。私の環境では Aruduino をインストールしたフォルダの下にhardware というフォルダがあり、その中に本体のコードがあった。Github上のリポジトリでは arduino/ArduinoCore-avr に ハードウェアシリアルのソースがある。

そのソースのシリアルデータ受信部分は以下のリンクの通り

ArduinoCore-avr/HardwareSerial_private.h at master · arduino/ArduinoCore-avr · GitHub

ここでは、パリティエラー/フレーミングエラー/オーバーランエラーが発生していても、受信データを破棄するだけでエラー処理は行っていない。

それを以下のように修正する。

void HardwareSerial::_rx_complete_irq(void)
{
    // (1) 条件を変更
    if (bit_is_clear(*_ucsra, UPE0) && bit_is_clear(*_ucsra, FE0) && bit_is_clear(*_ucsra, DOR0)) {
        icount.rx++; // (2) 受信回数を記録
        unsigned char c = *_udr;
        rx_buffer_index_t i = (unsigned int)(_rx_buffer_head + 1) % SERIAL_RX_BUFFER_SIZE;

        if (i != _rx_buffer_tail) {
            _rx_buffer[_rx_buffer_head] = c;
            _rx_buffer_head = i;
        }
    } else {
        // --- (3) ここから
        if (bit_is_set(*_ucsra, UPE0)) {
            icount.parity++;
        }

        if (bit_is_set(*_ucsra, FE0)) {
            icount.frame++;
        }

        if (bit_is_set(*_ucsra, DOR0)) {
            icount.overrun++;
        }
        // --- (3) ここまで

        *_udr;
    };
}

(1) 通信状態を表すレジスタ(UCSRnA) からパリティエラー・フレーミングエラー・オーバーランエラーのビットをチェックし、全て 0なら正常受信、いずれか一つでも1ならエラー発生としている。

(2) ついでに、正常受信なら受信データ数をカウント

(3) 各エラーをカウント

通信まわりのレジスタの情報は以下のサイトが参考になる。

シリアル通信関連のレジスタ

icountHardwareSerial.h に定義を追加し HardwareSerial に メンバーを足す。 また、スケッチから利用できるように icount_t を返すメソッドを追加する。

struct icount_t {
  unsigned long parity;
  unsigned long frame;
  unsigned long overrun;
  unsigned long rx;
};

class HardwareSerial : public Stream
{
  protected:
    // ...

    struct icount_t icount;

    // ...

  public:
    // ...

    virtual struct icount_t counter(void);

    // ...

メソッドの実態は HardwareSerial.cpp に追加する

struct icount_t HardwareSerial::counter(void)
{
    return icount;
}

スケッチでは、以下のように利用する

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.print(Serial.counter().rx);
  Serial.print(Serial.counter().parity);
  Serial.print(Serial.counter().frame);
  Serial.print(Serial.counter().overrun);
}

スケッチ内でパリティエラーの前回値を取得しておき、現在値と比較することでエラーが新たに発生しているかを検知できる。ちょっとまどろっこしいが、ないよりはマシかなと思う。