🗐 電装工芸日記 - 舞台照明機器の製作とか -

能登半島地震で被災された方々にお見舞い申し上げます。

or 管理画面へ

2023年1月 この範囲を時系列順で読む この範囲をファイルに出力する

Icon of admin
 てなわけで、socketの実験に入る下準備が整いました。Pythonで出来たことですからC言語(gcc)で出来ないことではないでしょう。
 Art-Netエンジンを構成するにはFIFOというか循環配列というかリングキャッシュというかQueueも勉強しないとけません。例えば1024個の配列を作ったとして、これがリング状に繋がったイメージで使います。1024で折り返すカウンタを使って配列の基点位置を表すだけですが、カウンタの計算モジュールだけでもライブラリ化しないと面倒かなと。
 これはDelayで必要な機能です。一定の時間間隔でDMXのデータを保存し続ければ配列サイズが許す範囲で過去情報を取り出せます。つまりDelayになります。受信毎の保存でないことが肝ですが、往年のテープエコーと要領は同じです。
 C言語はボチボチ書けるようになってきましたし、先達の情報も読み取れるようになってきました。パッチマシンとしての完成は先としても、この閑散期にArt-Netエンジンだけでも完成させたいです。

 あとは、コマンド入力と処理の方法も考えないといけません。
 ルールマップに基づいた入力制限とか、入力されたコマンドや数値のスタック方法とか、それの表示とかです。入力値はコマンドと数値の文字列で処理関数に渡すつもりですが、それをどの様に解析して実行するかも案外難しい。

#[Art-Net] #C言語
Icon of admin
 キー入力の処理を書き直してみました。
 ファンクションキーやカーソルキーなどの複数バイトでキーコードを出すキーも扱えます。shiftやAltなどの装飾キーは拾えませんケド。
 入力の待ち時間はありません。get_inkey()はすぐ戻ってきます。戻り値はキーコードの長さを表しますが、0なら入力が無かったと判別出来ます。
 得られるのは標準入力からのASCIIコードです。read()はキャッシュを読むだけでタイミング次第では取得値に複数のキーコードが混じることがありますが、これをキー単体のコードとして仕分けています。キャッシュの一番前のキーコードだけを取り出す単純な仕分けで、想定外のキーコードは読み飛ばしていますが十分でした。
 キーコードで何かをするにはswitch文やif文でそのコードを仕分ける必要があります。
 正常に動かすなら、100msec(1/10秒)毎以下でget_inkey()を読みに行く必要があります。
 定数設定とプロトタイプ宣言をヘッダーファイルに書けばincludeしてライブラリとして使えます。mainはコメント化して使った方がいいですけど。

Raspberry Pi 4B / Rasbian11_32bit(blueseye) / OS標準gcc
/* ---------------------------------
  リアルタイムにキー入力をチェックする
  --------------------------------- */
/* getcharはキー入力のキャッシュが空だと入力があるまで待つ。
  キャッシュが無いなら無いでそのまま抜けたいが、タイムアウトを設定しても
  抜けない。環境に寄るのかもしれないが、ioctl.hを用いることで
  タイムアウトが実現できた。
  get_inkeyの呼び出しは100msec(1/10秒)毎以下で繰り返し行うこと。短い方がいい。
*/

/* ライブラリ読み込み */
#include <stdio.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include <time.h>

/* 定数設定 */
#define SET 1        // ioctl.readのモードをRawにするフラグ
#define RESET 0       // ipctl.readのモードをCanonicalに戻すフラグ
#define LENGTH_STACK 32   // キー入力のスタック長 状況によるが、長めにしないと取り出しが半端になる
#define ESC 0x1B      // ASCIIコード ESC
#define BRACKET 0x5B    // ASCIIコード [

/* プロトタイプ宣言 */
int get_inkey( char *get_char ) ;    // キー入力を取得する関数
  // 戻り値              // -1=エラー、0>=取得したキーコードの長さ
  // char *get_char          // 取得したキーコードを返すポインタ
int set_inkey( int mode_flag ) ;    // キー入力のモードを設定する関数
  // int mode_flag          // モードの設定フラグ 1(SET)=Rawモードにする / 0(CLR)=モードを戻す

/* テスト用main */
int main(void) {
  int i ;               // for文用カウンタ
  char get_char[ LENGTH_STACK ] ;   // 入力されたキーコードを保存する変数
  int ret_inkey ;          // get_inkeyからの戻り値を保存する変数
  /* コンソールの設定を初期化 */
  set_inkey( SET ) ;
  /* 無限ループで入力したキーを表示する */
  for(;;) {
    usleep( 1e5 ) ;
    ret_inkey = get_inkey( get_char ) ;     // get_inkeyから文字取得
    /* get_inkeyの戻り値を処理 */
    if( ret_inkey == -1 ) break ;        // 戻り値が-1ならエラーなのでループから抜けて終了
    if( get_char[0] == 0x0A ) break ;      // エンターキーが押されたならループを抜けて終了
    /* 入力された文字を出力する */
    if( ret_inkey > 0) {
      for( i = 0; i < ret_inkey; i++ ) {
        if( get_char[i] >= 0x20 && get_char[i] <= 0x7E ) {     // 入力された文字を出力する・・・表示可能な文字
          printf( " %c<%02X>", get_char[i], get_char[i] ) ;
        } else if( get_char[i] >= 0x00 && get_char[i] <= 0xFF ) {  // 入力された文字を出力する・・・表示不可能な文字
          printf( " <%02X>", get_char[i] ) ;
        }
      }
      printf( "\n" ) ;
    }
    fflush( stdout ) ; // 画面表示のスタックを吐き出す   
  }
  /* 正常終了 */
  printf( "\n" ) ;    // 念のための改行
  set_inkey( RESET ) ;  // コンソールの設定を元に戻す
  return 0 ;
}

/* キー入力を取得する */
int get_inkey( char *get_char) {
  // char *get_char                  // キーコードを返すためのchar配列のポインタ
  int i ;                       // for用変数
  char in_char[ LENGTH_STACK ] = { 0x00 };      // 入力されたキーを取得
  int read_length ;                  // read()戻り値 キーコードのバイト数
  int put_length = 0 ;                // 戻り値 キーコードのバイト数

  /* get_charの初期化 */
  for( i = 0; i < LENGTH_STACK; i++ ) {        // すべての値を0x00にする
    get_char[ i ] = 0x00 ;
  }
  /* キー入力を読み取る */
  read_length = read( 0, &in_char, LENGTH_STACK ) ;  // read_lengthに取得バイト数、in_charにキーコードを得る

  /* 取得値の仕分け */
  if( read_length < 0 ) {               // 取得エラーなのでエラーを返して終了
    return -1 ;
  } else if( read_length == 0 ) {           // キーコードを得られなかったのでゼロを返す
    return 0 ;
  } else if( read_length == 1             // 取得が1バイトか先頭バイトがESC(0x1B)以外なら先頭バイトを返す
       || in_char[ 0 ] != ESC ) {
    get_char[ 0 ] = in_char[ 0 ] ;         // 値コピー
    return 1 ;
  } else if( read_length > 2             // 3バイト以上で先頭2バイトが0x1B(ESC)、0x5B([)なら
       && in_char[ 0 ] == ESC          // ファンクションキーまたはカーソルキー
       && in_char[ 1 ] == BRACKET ) {
    get_char[ 0 ] = ESC ;              // 0バイト目
    get_char[ 1 ] = BRACKET ;            // 1バイト目
    put_length = 2;                 // 戻り値設定
    for( i = 2; i < read_length; i++ ) {      // 2バイト目以降をコピー
      if( in_char[ i ] == ESC ) break ;      // 末尾にESCがあれば終了
        get_char[ i ] = in_char[ i ] ;     // 値コピー
        put_length++ ;             // 戻り値インクリメント
    }
    return put_length ;               // 戻り値送って終了
  }
  return 0 ;
}

/* キー入力のモードを設定する */
int set_inkey( int mode_flag ) {
  static struct termio tty_backup ;        // 変更前の設定を保持
  static struct termio tty_change ;        // 変更後の設定を保持
  /* モードを設定する */
  if( mode_flag == SET ) {
    ioctl( 0, TCGETA, &tty_backup ) ;      // 現在の設定を読み出し
    tty_change = tty_backup ;          // 現在設定のスタック
    tty_change.c_lflag &= ~( ECHO | ICANON ) ; // エコーを止め、RAW モードへ変更
    tty_change.c_cc[ VMIN ]= 0 ;        // 0文字入力された時点で入力を受け取る
    tty_change.c_cc[ VTIME ] = 0.01 ;      // 何も入力がない場合、1msec待つ (1 = 1/10sec)
    ioctl( 0, TCSETAF, &tty_change ) ;     // ここで設定を反映
  /* モードを戻す */
  } else if( mode_flag == 0 ) {
    ioctl( 0, TCSETAF, &tty_backup ) ;     // スタックしていた設定に戻す
  }
  return 0;
}

 ※ このブログシステムでは#や[が機能文字扱いなので、上記ではこれらを全角文字で書いています。

#C言語
Icon of admin
 本業は当面の課題を一通りやっつけました。
 追われる感じが無いって幸せ。

 そんぢゃ今夜はC言語の勉強!!、といきたいところですが、ここ数日、朝から晩まで10時間以上図面書きをやりますと帰宅してからヤル気はおきません。今夜は脳味噌を休ませてアタマを切替えます。
 明日と明後日は急ぎの要件が無いので、キー入力の実験、これまでに実験したネタの整理、socketによるArt-Net処理の実験に手を付けましょう。

 キー入力はioctl.hとtermios.hを用いて設定しread()で読み込む方法を用いていますが、これってデバイスドライバを書く基本なんだそうです。
 宛先を設定し、read()で取り込み、write()で送り出すという極めてシンプルな構成ですが、合理主義者の神達がローレベルの制御に対し案件毎に違ったやり方を定義するとは思えませんので納得です。
 肝心なところは先達のサンプルプログラムをコピペしただけなので何が何だかよくわかりませんが、デバイスドライバが書けたら守備範囲が広くなりそうです。

#C言語
Icon of admin
 キー入力はファンクションキーや矢印キーが複数バイトのデータなので、先のプログラムではこれらを正しく判別出来ません。0x1Bを得てもESCキーなのか矢印キーなのか区別できないからです。
 これらのデータが一度にキャッシュに入って連続した読み出しとなる(タイムアウト無しに連続して読み出せる)ならいいですが、そうでないと面倒です。いずれにしてもこの辺りの挙動をチェックする必要があります。今は1バイトのcharとして処理していますが、8バイトくらいのchar配列、つまり文字列として処理することになりそうです。

#C言語
Icon of admin
 キー入力の処理が出来ましたが、願わくば押し下げと解放も拾いたいところではあります。
 ですが、ハードウェアが汎用ですし、キーボードが押されたことを検知しているのではなく、入力された文字コードを得る処理ですので、これ以上細かいことはもう少し勉強しないとダメかな。

 socketの検証は場所も時間も潤沢に必要なので、もう少し本業を終わらせないと手を付けられませんが、キー入力によるコマンド処理をどうしようか考えています。
 入力されたアルファベットをコマンドショートカットとし、数字は数字として扱うことを基本にしようと思います。キーに対して処理を割り付けるのではなく、得たASCIIテキストに対して処理を割り付ければ標準入力だろうがUARTだろうがsocketだろうが何でも良く、ハードウェアに合わせた入口さえ作ればその先の処理は同じでいけます。こうするのは専用入力端末が壊れた時に汎用のキーボードでも対応したいからです。
 では、得たASCIIテキストをどう処理するかです。
 入力されたASCIIテキストは文字列として普通に扱えばいいでしょう。画面にコマンド列を表示したいならそれを変換すればいいし、コマンドを実行するなら関数に文字列として投げればいい。
 せめて、入力に制限をかけてありえない文字列にならないようにした方がいいでしょう。「コレの後にありえる入力はコレ」といった制限です。一種のルールマップを作り、条件に合致しない入力は応答しないかエラーを出すのです。数値に最大値の制限もかけたいですが、これはコマンド実行関数側の仕事であって入力時にチェックするものでもないかな?

#[Art-Net] #C言語
Icon of admin
 ANSI-CというかLinuxのC言語ではキー入力は「待ち」が基本です。何かキーが入力されるとか、Enterを押されるまで処理が一時停止します。それでも構わないこともありますが、ダメな時もあります。
 WindowsならDxLib.hを使って様々な入力を直に得ることが出来ますが、ANSI-Cの標準ライブラリには同様の物がありません。
 そこで、キー入力があれば受け付け、無いなら飛ばして次に行く処理を作ってみました。キー入力はOSがバッファしているので、これを覗く形です。

Raspberry Pi 4B / Rasbian11_32bit(blueseye) / OS標準gcc
/* ---------------------------------
  リアルタイムにキー入力をチェックする
  --------------------------------- */
/* getcharはキー入力のキャッシュが空だと入力があるまで待つ。
  キャッシュが無いなら無いでそのまま抜けたいが、タイムアウトを設定しても
  抜けない。環境に寄るのかもしれないが、ioctl.hを用いることで
  タイムアウトが実現できた。
*/

/* ライブラリ読み込み */
#include <stdio.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>

/* 定数設定 */
#define SET 1        // ioctl.readのモードをRawにするフラグ
#define RESET 0       // ipctl.readのモードをCanonicalに戻すフラグ

/* プロトタイプ宣言 */
int get_inkey( char *get_char ) ;  // キー入力を取得する関数
  // char *get_char  取得したキーコードを返すポインタ
int set_inkey( int mode_flag ) ;  // キー入力のモードを設定する関数
  // int mode_flag  モードの設定フラグ 1(SET)=Rawモードにする / 0(CLR)=モードを戻す

/* テスト用main */
int main(void) {
  char in_char = 0 ;   // 入力されたキーコードを保存する変数
  int ret_inkey = 0 ;   // get_inkeyからの戻り値を保存する変数
  /* コンソールの設定を初期化 */
  set_inkey( SET ) ;
  /* 無限ループで入力したキーを表示する */
  for(;;) {
    // get_inkeyから1文字取得
    in_char = 0x00 ;          // 事前初期化
    ret_inkey = get_inkey( &in_char ) ; // キー取得
    /* get_inkeyの戻り値を処理 */
    // 戻り値が-1ならエラーなのでループから抜けて終了
    if( ret_inkey == -1 ) {
      break ;
    // エンターキーが押されたならループを抜けて終了
    } else if( in_char == 0x0A ) {
      break ;
    // 入力された文字を出力する・・・表示可能な文字
    } else if( in_char >= 0x20 && in_char <= 0x7F ) {
      printf( " %c<%02X>", in_char, in_char ) ;
    // 入力された文字を出力する・・・表示不可能な文字
    } else if( in_char > 0x00 && in_char <= 0xFF ) {
      printf( " <%02X>", in_char ) ;
    }
    fflush( stdout ) ; // 画面表示のスタックを吐き出す   
  }
  /* 正常終了 */
  printf( "\n" ) ;    // 念のための改行
  set_inkey( RESET ) ;  // コンソールの設定を元に戻す
  return 0 ;
}

/* キー入力を取得する */
int get_inkey( char *get_char) {
  char in_char = 0 ;     // 入力されたキーを保持
  char read_length = 0 ;   // 読み込んだバイト数

  // readを使って標準入力から1文字取得
  read_length = read( 0, &in_char, 1 ) ;
  // read_lengthが-1ならエラー、それ以外なら正常
  if( read_length != -1 ) {  // 正常なので取得値を返す
    *get_char = in_char ;
  } else {          // エラーなので以上通知
    *get_char = 0x00 ;
    set_inkey( RESET ) ;
    return -1 ;
  }
  return 0;
}

/* キー入力のモードを設定する */
int set_inkey( int mode_flag ) {
  static struct termio tty_backup;    // 変更前の設定を保持
  static struct termio tty_change;    // 変更後の設定を保持
  // モードを設定する
  if( mode_flag == SET ) {
    // 現在の設定をスタック
    ioctl(0, TCGETA, &tty_backup);
    tty_change = tty_backup;
    // 設定を変更
    tty_change.c_lflag &= ~( ECHO | ICANON ) ;   // エコーを止め、RAW モードへ変更
    tty_change.c_cc[VMIN]= 0 ;          // 0文字入力された時点で入力を受け取る
    tty_change.c_cc[VTIME] = 0.01 ;        // 何も入力がなくても1msec待つ (1 = 1/10sec)
    ioctl( 0, TCSETAF, &tty_change ) ;       // ここで設定を反映
  // モードを戻す
  } else if( mode_flag == 0 ) {
    // スタックしていた設定に戻す
    ioctl( 0, TCSETAF, &tty_backup ) ;
  }
  return 0;
}


 同じ感じでgetcharを使ってもいいような情報が多かったのですが、RaspberryPiのblueseyeではこの方法でないとキー入力があるまで一時停止してしまいます。
 Pythonでも同様の処理を書いたことがありますが、C言語の方が圧倒的にレスポンスがいいです。
 矢印キーがANSIエスケープシーケンスのコードで取得できたことには驚きましたが、ファンクションキーも含め、getcharでは取得できないこともあるキーも取得出来て満足。
 ただし、シフトキーなどの装飾キーを単体で取得することは出来ません。

#C言語
Icon of admin
 棚上げしていた本業に取り組んでおりますが、ここ10日ほどは残業することも精神的に追われることもない日常を送っているので心身共に回復しております。

 VSCodeのオカゲですが、Pythonと同じ感覚でC言語を使えています。覚えなきゃならないことはまだまだ沢山ありますが、実行ファイルの完成まで1クリックですし、コンパイルエラーの説明がとても丁寧でわかりやすいし、コンパイルの時間もPythonのインタプリタ翻訳と大差ありませんので、方言違いのPythonを書いている様な使い勝手です。相当昔の記憶によりますが、リンカーの処理を勝手にやってくれるだけでも楽ってもんです。
 C言語の取り扱いにはローレベルのコンピュータの知識と摩訶不思議な記述の習得が不可欠ですが、慣れてくるとPythonよりも書いてて楽しいですね。コンピュータを動かしているって実感がPICのアセンブラ並に強いからでしょうか。何をしているかも所要時間もわからない便利なだけのコマンドでハードウェアの制御をするとすべてが遠くに思えて面白くありません。この感覚が変人たる所以かもしれませんが、私の基本はハードウェアを直に動かすPICのアセンブラなんだと改めて思います。

 この先数日は棚上げしていた本業(某劇場の舞台資料)の仕上げをせねばなりませんが、これが終われば半月くらいは自由研究に没頭できそうです。新規のご相談も頂いているので進捗次第ではわかりませんが。。。
 もちろん、息抜きにC言語の学習と試作はしますよ。

#C言語
Icon of admin
 ダンプ表示のライブラリは出来ました。何の変哲もない16進数のカラム表示です。
 データ配列のポインタ、表示データ数、列数、行数、最上行値のポインタなどを引数にして関数を呼ぶと表示します。
 地味ですが、こういったライブラリがあると開発の効率が良くなるのでは?という想いで作ってみました。後でDMXデータを表示する画面を作る際にも同様のことをするので習作でもあります。

 以下オレメモです。
 ANSIエスケープシーケンスにおいてパラメータに変数を用いたい場合。

 // カーソルをx=10,y=5にして文字列を表示する
#define ESC 0x1B
int main(void) {
 int x = 10 ;
 int y = 5 ;
 printf("%c[%d:%dHカーソルを指定の位置に", ESC, x, y ) ;
 return 0 ;
}

 ※ このブログシステムでは#や[が機能文字扱いなので、上記ではこれらを全角文字で書いています。

 などとします。大昔のN-BASICの「LOCATE + PRINT」みたいなことをしています。
 printfの文字列中のエスケープを\eと直で書くと後に続く%dが機能しませんが、#defineで定義したESC(0x1B)を%cで読み込めば機能します。
 もちろん、printfのESCの位置に0x1Bと直書きしてもいいのですが、#defineした方が読みやすいと思います。

 C言語ではこういった不思議な記述が多いです。正規表現的でもありますが、バイナリ的な指向が各所に出てきます。
 アセンブラに慣れた身に違和感はありませんが、理解に苦しむ要素かもしれません。

#C言語
Icon of admin
 本業では社内のサーバーやパソコンのメンテをしていますが、処理の待ち時間でC言語のお勉強。
 時間についてです。
 DMXを扱うには無受信1秒でタイムアウトしなければなりませんし、Delayを構成するには1/30~1/50秒程度のインターバルタイムで受信値をスタックしなければなりません。
 ですので、msec(1/1000)クラスの時間評価が必要です。

 システムが持つ時間情報を扱うには<time.h>を用い、エポック秒やPOSIX時間などと呼ばれる基準日時(1970年1月1日0時0分0秒(UTC))からの経過時間(secとnsec)を使うのがよいと思います。有効な精度は10usec程度の様ですが、今現在書いている処理では十分です。これを処理の節目でスタックして経過時間を評価するのですが、年月日時でパラメータが分かれているよりもトータル秒数の方が扱いが簡単です。23時59分59秒の1秒後に0時0分0秒にならないタイマで継続した時間評価をしたいのでこの方がいいと思います。
 ただ、スタックする変数のバイト長には気を付けないといけません。secもnsecもそれぞれ4バイト数なので評価をするには8バイト数で考えないといけないからです。64bitOSならint型でも8バイト数なことが多いので問題ありませんが、私はRaspberryPiで32bitOSを使っているのでint型は4バイト数になります。対策には8バイト数である「(unsigned) long long int」型を使います。ちょっとクセがある型なので計算やprintfで注意しないといけませんケドね。

#C言語
Icon of admin
 オレメモであります。
 参考になりそうな先達のページ

● UDP/IP通信全体の参考
UDP / IP でパケットの送受信を行う
● broadcast通信の参考
UDPブロードキャスト送信サンプル
● ネットワークデバイスを指定した通信の参考
Ethernetインターフェース(eth0, eth1)を指定してソケットを作成する (Linux, C, Raspberry Pi)
● 受信タイムアウトの参考
selectを使用してタイムアウト付き受信を実現する <= recvもrecvfromも受信があるまでひたすら待ち続けるので、タイムアウトさせることは必須。
UDP通信におけるselectの使い方。 <= recvの例が多い中、recvfromの書式を確認出来る。recvfromを使う理由は送信元アドレスを取得したいから。
● 時間の扱い方
時間情報の取得方法と扱い方 <= これの5ページ目の「POSIX環境」が結構大事。DelayをするにもDMXのタイムアウトをするにもこれが必要。
● ターミナルの行長と行数を取得する
ターミナルのサイズを取得する
ターミナルの幅と高さを得る
● ANSIエスケープシーケンス
ANSIエスケープシーケンス チートシート

 この辺りを参考にすれば「ネットワークデバイスを指定してbroadcastで通信する」が出来そうな気がします。
 ただし、1行々々読み込んで使われている構造体をよく把握することが大切です。構造体の中に必要な情報が隠れていることが多いからです。構造体の中に構造体があったりもするので、十分に読み込まないといけません。

 実験は受信が当面の課題となり、
1)ネットワークデバイスを指定せずArt-Netを受信する。<= とにかく受信する
2)ネットワークデバイスを指定してArt-Netを受信する。<= ネットワークデバイスを制限して受信する。
3)送信元IPアドレスを取得し、IPアドレスをもとに受信値を振り分ける。<= 複数の送信元(卓)がある場合の不確実性排除とミックス処理のためには不可欠。
 といった手順で進めることになろうかと思います。
 受信値を確認するため、習作を兼ね、バイナリをダンプ表示をするライブラリも作ってみましょう。

#C言語 #[Art-Net]

■当面の課題

桜のライトアップの季節です。花粉症の季節でもあります。
自分は平気ですが、花粉症の部下は死にそうな顔をしています。

編集

■複合検索:

  • 投稿者名:
  • 投稿年月:
  • #タグ:
  • カテゴリ:
  • 出力順序:

■日付検索:

■カレンダー:

2023年1月
1234567
891011121314
15161718192021
22232425262728
293031

■カテゴリ:

■最近の投稿:

最終更新日時:
2024年3月28日(木) 15時49分12秒