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

今年は開発案件を進めたい

or 管理画面へ

タグ「C言語」を含む投稿[70件]

Icon of admin
 現地照明でしたが、シュートが終わったらバラシまで待機という名の休憩。直しとトラブルシュートに対応出来ればいいのでまとまった空き時間です。こんな時は設計という名の妄想が一番です。

 課題は毎度おなじみ「ArtNetPatch」です。主にソフトウェアの構成が課題です。
 受けたデータを一時保存、加工、出力しますので、機能は映像ストリーミングに近いかもしれませんが、自分のイメージはデータベースです。
 その昔ファイルメーカーを母体に機材の稼働管理システムを作って今も使っていますが、データを動的に仕分けて加工する感覚が今回活用出来ています。
 アルゴリズムと言えばそうなんですが、データを保管する構造体の設計が主な作業です。可能か不可能かを確認しながらになりますが、最終的にまとめる構造体が決まれば仕分けて加工するアルゴリズムはおのずと決まってくるので私にはこの感覚で進めるのが性に合っているようです。処理の全体像が見えてきました。

 アセンブラではないので構想の段階で処理時間の見込みを付けることは難しいのですが、そもそもRaspberryPiのCPUにおけるマシンコードの動作速度はどうだと計算したら恐ろしい数字。ARMの2.4GHzですが、PICの感覚で単純計算したら1クロック当たりの時間は0.42nsec。PICに比べたら桁違いというか単位違い。OSを介するので単純には比べられないもののマシンコードのイメージで書けばかなり速くなりそう。例えば、積や商を求めるために四則演算をするかビットシフトをするかってことです。2倍や1/2などの2の乗数に関わる積や商ならビットシフトの方が速いハズです。この辺りが「C言語はアセンブラを汎用化したもの」ってイメージであり、Pythonとは違い、C言語はアセンブラの感覚で使うベシってのが私個人の感覚になりつつあります。出来るだけ単純な計算方法を目指してデータ構造を考えるのです。C言語の難解さがアセンブラ傾向のアプローチで軽くなった気分です。普通は逆なんでしょうけど。
 C言語を作った神達はアセンブラをマクロ化して手間を減らすところから始まってますので、世代を経ても底辺はアセンブラなんでしょう。同時代のCOBOLやFORTRANは意味付けが違うようですけど。

 勝手な妄想はともかく、どんなデータをどう変換・加工するかを明らかにすればおのずと見えてくるようです。

#[Art-Net] #器具の製作 #C言語
Icon of admin
 C言語のポインタには面白い機能(使い方?)がありました。
 関数の呼び出しをポインタ化するのです。なんのこっちゃ?って話ですねぇ~。
 ソースコードの大半はそのままに、条件分岐で関数を差し替えるイメージです。オブジェクト指向の一歩手前?
 もちろん、構造体を含む変数もポインタを介して差し替えられます。
 DMXの場合、受信中のスタックから読み出すことは避けなければなりません。パケットの中身に不整合が起こるからです。例えばアドレス100までは最新の受信内容、101以降は1フレーム前の受信内容や全てゼロになるのです。2スロット構成のアトリビュートがアドレス100と101にまたがったらよろしくないことが起こります。リトルエンディアンだったら目も当てられません。
 この対策は2つのスタックを交代しながら使います。片方は受信に、1フレーム前のデータを持ったもう片方を出力処理に使い、条件が整えば役割を交代するのです。この場合どちらをどちらに使うかをポインタで指定出来れば全体の処理はスマートになります。ピンと来る方が少ないのはわかっていますが、ソースコードを書く上ではとても合理的な方法です。今の常識からしたら万分の一の処理能力のハードウェアしかなかった時代にC言語をデザインした人は本当の天才だと思います。勉強すればするほど「天才」って言葉が身に沁みます。自分はその成果に甘えるだけで感謝々々です。

 C言語を学ぶ上でポインタは難解な要素の筆頭ですが、自分の様にアタマの半分がPICマイコンのアセンブラで占められている者には案外すんなりと理解出来ました。メモリのアドレスを直接扱う方法だからです。当初は高級言語でメモリのアドレスを直接扱うなんてイメージはありませんでしたので躓きましたが、C言語が値渡ししか出来ない(参照渡しが出来ない)ことも含めるとポインタは合理的であり、C言語は高級言語ではなく汎用化を目指したアセンブラ言語と思えばイイらしいと思った次第。正しくはなくても私にはこの理解が自然でした。

 自分は PC8001(PC6001)-BASIC → Z80アセンブラ(当時小学生、挫折しました) → (20年空いて) → PICアセンブラ → Python → C言語(今) と進んできました。小学生の時分に論理演算、16進数、2進数を本能に焼き付け、成果は出せずともZ80アセンブラに挑んだことが今に活きているようです。小学生当時、戦前産まれの父母は数百円の漫画本は買ってくれませんでしたが高価な技術書やその筋の雑誌(月刊マイコンやBASICマガジンなど)は買ってくれました。100歳まであと数年のカウントダウンなのに林業を生業とし登山やスキーを楽しむ父と米寿間近で父の3倍は元気な母に絶大な感謝をする今日この頃。
 自分の今の最大の不安は三途の川の向こう岸で父母をお迎えすることです。あと100年くらいは部下や後輩に迷惑をかけずに現役をやってやろうと思っていますけど(笑

#C言語 #雑記
Icon of admin
 現場ですが、簡単なひな壇を組むだけの現地道具なので終演までヒマです。
 ArtNetPatch の ap_transmitter の構造を構造体配列を元に整理したのですが、割り切って構成したら案外軽い処理になりました。メモリの節約など考えず、条件分岐や計算を出来るだけ減らし繰り返しをヒトまとめにする方針だからでしょうか。
 実際に組んで実行時間を計測しなければなりませんが、パッチ処理の後にもディレイやプロファイルカーブの処理を入れられそうな予感がします。

 receive(受信)、bind(入力ユニバースを内部Bus(ユニバース)にパッチ、ここで数値をマージしてHTPミキサーとします)、pre-delay、pre-profile-curve、patch、post-delay、post-profile-curve、transmit といった流れで考えています。

 ついてはモジュール構成を少し変えます。
 ap_transmitter の中の数値操作と Art-net の出力を分割します。
 fps はともかく、すべてのユニバースを連続して送るのは避けた方がいいかなと思うので、ユニバース毎にインターバルを持たせるためです。

1)アプリの起動部とし、共有物を設定して以下のモジュールを呼ぶ「ap_main」
2)画面表示やユーザー操作を司る「ap_console」
3)Art-Netを受信する「ap_receiver」
4)受信値や設定値から出力値をまとめる「ap_effect」
5)Art-Net を出力する「ap_transmitter」
6)データのタイムアウト管理をする「ap_timeout」

 今後はタイムラグを減らす工夫を考えてみましょう。
 出力の目標は 30fps 以上、出来れば 36fps ですが、1/36秒以内に処理を一巡出来るなら遅れても1フレームとなります。無理ならスレッド的なアプローチで考えて出来るだけ遅れを少なくしましょう。
 何にしても、試作をして処理時間と処理負荷の計測が必要です。
 こんな複雑なシステムは10回くらい書き直すつもりで試すしかありません。素人の私が結果を完全に予測するなんで無理ですもん。

#[Art-Net] #器具の製作 #C言語
Icon of admin
 子プロセスを使う場合は親プロセスが落ちたらこれらも落とさないといけません。ゾンビとして残ってしまうからです。
 一般的にはメインから「終わりにしなさい」って指示を受けて落としますが、開発途中ではそうもいかないことがあります。
 ならば、「続けなさい」って指令が無ければタイムアウトすることにしましょう。親の存在確認をする方法もありますが、この方法なら管理が楽かも。

#C言語
Icon of admin
 Art-Netの扱いにアイデアを一つ。
 送信元を識別するにはIPv4アドレスがキーになると思います。運が悪くなければ送信元毎にユニークなハズです。これの扱い。

 IPv4アドレスは4バイト長(32bit)で構成され、文字列で表しても7~15文字です。
 これを一つの整数にまとめてしまうアイデアです。一般的にint型は4バイト長ですから、丸めてしまえばint型にしても情報は欠落しません。
 こうすれば、4つの数字や7~15文字のテキストよりも扱いが簡単で軽くなると思うのです。IPアドレスとしての情報は別途残すとして、識別IDにこれを使うのです。
 ユニバースも同じ考え方でいいでしょう。15bit長ですからshort型でも収まりますが、噂に聞くところではint型の方が処理が軽いらしいのでこの型にしておこうかなと。
 この両者を合わせた8バイトのlong型もしくはLongLong型を併記しておいてもいいかもしれません。なぜこの様なことを考えるかと言いますと、複数のユニバースを保管する配列から特定のものを探し出す処理を軽くしたいからです。処理の方針にもよりますが、受信したものを一旦スタックして一定の時間間隔で取り出そうというのが今の考え方ですので、クエリに相当するインデックスを作るのは当然としてもキーワードが簡素なことは重要だと思うのです。一定の時間間隔で作れらたLoop配列ならばその並びが時間情報となりますので、Delay を求めても受信日時と現在日時を比較する必要がありません。

 Art-Netのモニターも兼ねたいので、使う使わないはともかく、受信したデータを全て一時保管するつもりです。
 かといってモニターのための保管とパッチのための保管を別々にするのは気に入らないので、受信をすべて保管してそこから必要なモノを取り出したいのです。
 仮に、送信元8、それぞれ8ユニバースとするなら、最大44fpsとして1秒間に2,816件のスタックをしなければなりません。1パケット当たりのデータ長は540バイトくらいですから1.5MB/秒くらいです。2秒分のスタックをしても3MBです。動画の1フレーム当たりのデータ長は640x480の16bitカラーで1.8MBくらいですので55MB/秒です。動画に比べたらArt-Netの情報量は余裕っしょ。

#[Art-Net] #C言語
Icon of admin
 しばらく前に書いた Art-Net を扱うコードです。
 今だったらこうするのにってところは多いですが、これをたたき台にしようかなと。
 表示の都合で一部の文字を全角にしています。コピペしてお使いになる際はご注意ください。オレオレライブラリーやオレオレヘッダーが必要なのでこのソースだけでは動きませんけど、要点の参考になれば幸いかと。

/* --------------
  Art-Net Engine
  ae_main.c
  -------------- */

// 汎用ライブラリをインクルード
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <sys/shm.h>
//#include <netinet/in.h>
//#include <sys/ipc.h>

// 自作ライブラリをインクルード
#include "ae_main.h"
#include "std_inkey.h"
#include "std_dump.h"

// Main
int main( int argc, char *argv〔〕 ) {
  // 汎用変数
  // int i ;
  // int ret = 0 ;
  // 共有メモリ変数
  ae_sharemem_t *ae_sharemem ;
  int ae_sharememsize = sizeof( ae_sharemem_t ) ;
  int ae_sharememID ;
  //【開発用】共有メモリ領域の確保 上位モジュールが出来たらそちらで作る
  ae_sharememID = shmget( IPC_PRIVATE, ae_sharememsize, SHM_R|SHM_W ) ;
  // 共有メモリ定義
  ae_sharemem = shmat( ae_sharememID, 0, SHM_R|SHM_W ) ;
  // nsec時変数
  struct timespec now ;            // 現在POSIX時を得る構造体
  unsigned long long int start_nsec ;     // 処理開始nsec時を保存
  //【開発用】処理時間変数
  unsigned long long int lap_nsec ;      // 処理終了nsec時
  static unsigned long long int max_nsec = 0 ; // 経過したnsecの最大値
  // Art-Net 受信変数
  int an_rx ;                 // 受信socket番号
  struct sockaddr_in an_rx_addr ;       // 受信先指定構造体
  int an_rx_length ;             // recvfromで得たデータ長
  char an_rx_nic〔〕 = { "eth0" } ;       // 受信NICの名称
  char an_rx_buf〔 ADMX_BUF_SIZE 〕 ;      // 受信バッファ ArtDMXの最大バイト長で設定
  socklen_t an_rx_sin_size = ADMX_BUF_SIZE ; // recvfromに渡す受信バッファサイズ
  struct sockaddr_in an_rx_from_addr ;    // 送信元情報の構造体
  artdmx_t artdmx_rx ;            // 受信したArtDMXを保存する構造体
  // Art-Net 送信変数
  int an_tx ;                 // 送信socket番号
  struct sockaddr_in an_tx_addr ;       // 送信先指定構造体
  char an_tx_nic〔〕 = { "eth1" } ;       // 受信NICの名称
  int yes = 1 ;                // 送信をブロードキャストに設定する際のフラグ変数
  artdmx_t artdmx_tx〔 ADMX_TX_UNIVRESES 〕 ;  // 送信するArtDMXを保存する構造体配列
  char an_tx_buf〔 ADMX_BUF_SIZE 〕 ;      // 送信バッファ Art-DMXの最大バイト長で設定
  int an_tx_count = 0 ;            // 送信ユニバースカウンタ
  unsigned long long int an_tx_nsec = 0 ;   // 送信nsec時を保存
  float an_tx_fps = 29.97 ;          // 送信フレームレート
  unsigned int an_tx_interval =
      ( unsigned int )( 1e9 / ADMX_TX_UNIVRESES / an_tx_fps ) ;

  // Art-Netの受信ソケットを作成
  an_rx = socket( AF_INET, SOCK_DGRAM, 0 ) ;             // 受信ソケットのインスタンスを作成
  setsockopt( an_rx, SOL_SOCKET, SO_BINDTODEVICE,           // 受信NICを指定する
        &an_rx_nic, sizeof( an_rx_nic ) );
  an_rx_addr.sin_family = AF_INET ;                  // 受信をUDPに設定
  an_rx_addr.sin_port = htons( AN_PORT ) ;              // 受信ポート番号を設定
  an_rx_addr.sin_addr.s_addr = INADDR_ANY ;              // 送信元アドレスを無指定にする
  bind( an_rx, ( struct sockaddr * ) &an_rx_addr,           // 上記をインスタンスに設定する
          sizeof( an_rx_addr ) ) ;
  // Art-Netの送信ソケットを作成
  an_tx = socket( AF_INET, SOCK_DGRAM, 0 ) ;             // 送信ソケットのインスタンスを作成
  an_tx_addr.sin_family = AF_INET ;                  // 送信をUDPに設定
  an_tx_addr.sin_port = htons( AN_PORT ) ;              // 送信ポート番号を設定
  an_tx_addr.sin_addr.s_addr = inet_addr( "255.255.255.255" ) ;    // 送信先アドレスを無指定にする
  setsockopt( an_tx, SOL_SOCKET, SO_BINDTODEVICE,           // 送信NICを指定する
        &an_tx_nic, sizeof( an_tx_nic ) ) ;
  setsockopt( an_tx, SOL_SOCKET, SO_BROADCAST,            // 送信をブロードキャストに設定
        ( char * )&yes, sizeof( yes ) ) ;
  // std_inkey 用変数
  int key_code_length = 0 ;
  int key_code_length_st = 0 ;
  char key_code〔 32 〕 = { 0x00 } ;
  char key_code_st〔 32 〕 = { 0x00 } ;
  // std_inkey を開始
  set_inkey( SET ) ;

  // ***** Art-Netの受送信と表示 *****
  printf( "\e〔2J\e〔?25l" ) ;         // 画面を消去 カーソル非表示 \e〔?25l カーソル非表示
  while( strcmp( key_code, _KEY_F12 ) != 0 ) {
    // ------ 開始nsec時を取得 ------
    clock_gettime( CLOCK_REALTIME, &now ) ;           // 現在POSIX時間値取得
    start_nsec = ( unsigned long long int )now.tv_sec * 1e9   // unsigned long long int型(8バイト長int)変数に
           + now.tv_nsec ;                // 取得値をひとまとめにする(nsec)

    // --------- キー入力 ----------
    key_code_length = get_inkey( key_code ) ;          // キー入力を取得
    if( strcmp( key_code, "\x20" ) == 0 ) max_nsec = 0 ;    //【開発用】キー入力が〔SP〕なら実行時間計測の最大値をクリア
    if( key_code_length > 0 ) {                 // キー入力を別関数に渡すためのスタック
      key_code_length_st = key_code_length ;
      memcpy( key_code_st, key_code, sizeof( key_code ) ) ;
    }

    // --------- 受 信 -----------
    memset( an_rx_buf, 0, ADMX_BUF_SIZE ) ;                     // 受信バッファのクリア
    an_rx_length = recvfrom( an_rx, an_rx_buf, ADMX_BUF_SIZE, MSG_DONTWAIT,     // an_rx_from_addrには送信元情報が入る
        ( struct sockaddr * )&an_rx_from_addr, &an_rx_sin_size ) ;
    // 受信がある場合
    if( an_rx_length > 0 ) {                            // an_rx_lengthは受信値のバイト長
      // 送信元IPアドレスとポートを抽出し、構造体artdmx_rxに保存
      inet_ntop( AF_INET, &an_rx_from_addr.sin_addr,               // 受信データから送信元IPアドレスを抽出保存
            artdmx_rx.senderip, sizeof( artdmx_rx.senderip ) ) ;
      strcpy( artdmx_rx.senderip, inet_ntoa( an_rx_from_addr.sin_addr ) ) ;
      artdmx_rx.senderport = ntohs( an_rx_from_addr.sin_port ) ;         // 受信データから送信元ポートを抽出保存
      artdmx_rx.recv_nsec = start_nsec ;                     // 受信日時を保存
      // 受信したArtDMXをデコード
      if( artdmx_decode( an_rx_buf, &artdmx_rx ) >= 0 )              // ArtDMXなら送信スタックに保存
        memcpy( &artdmx_tx〔 artdmx_rx.universe 〕,                // ArtDMX以外なら読み飛ばし
            &artdmx_rx, sizeof( artdmx_t ) ) ;

      //【開発用】終了nsec時を取得
      clock_gettime( CLOCK_REALTIME, &now ) ;               // 現在POSIX時間値取得
      lap_nsec = ( unsigned long long int )now.tv_sec * 1e9        // unsigned long long int型(8バイト長int)変数に
            + now.tv_nsec - start_nsec ;              // 取得値をひとまとめにする(nsec)
      if( max_nsec < lap_nsec ) max_nsec = lap_nsec ;           // ここまでの最大処理時間を得る

    }

    // --------- 送 信 -----------
    else if( start_nsec - an_tx_nsec > ( unsigned int )( an_tx_interval ) ) {
      artdmx_encode( an_tx_buf, &artdmx_tx〔 an_tx_count 〕 ) ;       // 送信データをエンコード
      sendto( an_tx, an_tx_buf, ADMX_BUF_SIZE, MSG_DONTWAIT,       // 送信実行
          ( struct sockaddr * )&an_tx_addr, sizeof( an_tx_addr ) ) ;
/*
      //【開発用】終了nsec時を取得
      clock_gettime( CLOCK_REALTIME, &now ) ;               // 現在POSIX時間値取得
      lap_nsec = ( unsigned long long int )now.tv_sec * 1e9        // unsigned long long int型(8バイト長int)変数に
            + now.tv_nsec - start_nsec ;              // 取得値をひとまとめにする(nsec)
      if( max_nsec < lap_nsec ) max_nsec = lap_nsec ;           // ここまでの最大処理時間を得る
*/
      //【開発用】受信データの表示
      artdmx_check_disp( &artdmx_tx〔 an_tx_count 〕,            // 送信データの仮表示
          key_code_st, &key_code_length_st, &lap_nsec, &max_nsec ) ;
      // 送信事後処理
      an_tx_nsec = start_nsec ;                      // 送信インターバル・次回用のnsec取得
      if( ++an_tx_count > 7 ) an_tx_count = 0 ;              // 送信ルートカウンタインクリメント
    }
    // 待ち時間 無駄にループしても処理負荷が増えるだけなので少しお休み
    usleep( 1 ) ;
  }
  // --------- 終 了 ----------
  set_inkey( RESET ) ;      // std_inkeyを終了
  close( an_rx ) ;        // 受信ソケットをクローズ
  close( an_tx ) ;        // 送信ソケットをクローズ
  printf( "\e〔?25h\e〔0m\n" ) ;  // コンソール画面を戻す \e〔?25h カーソルの表示
  shmdt( ae_sharemem ) ;     // 共有メモリの切り離し
  //【開発用】上位モジュールが出来たらそちらで処理する
  shmctl( ae_sharememID, IPC_RMID, 0) ; // 共有メモリ領域開放
  return 0 ;
}

// ArtDMXのデコード関数
int artdmx_decode( char *an_rx_buf, artdmx_t *artdmx_rx ) {
  int i ;
  char int_st〔 sizeof( int ) 〕 ;
  // IDを抽出
  for( i = 0; i < 8; i++ ) {             // bufの先頭から8バイト
    artdmx_rx->id〔 i 〕 = an_rx_buf〔 i 〕 ;  
  }
  // OpCodeを抽出
  int_st〔 0 〕 = an_rx_buf〔 8 〕 ;
  int_st〔 1 〕 = an_rx_buf〔 9 〕 ;
  int_st〔 2 〕 = 0x00 ;
  int_st〔 3 〕 = 0x00 ;
  artdmx_rx->opcode = *( ( int * )&int_st〔 0 〕) ;
  if( ! (strcmp( artdmx_rx->id, "Art-Net" ) == 0     // ArtDMXかチェックし、そうでなければエラーを返して終了
        && artdmx_rx->opcode == 0x5000 ) ) {
    return -1 ;
  }
  // ProVerを抽出
  int_st〔 0 〕 = an_rx_buf〔 11 〕 ;
  int_st〔 1 〕 = an_rx_buf〔 10 〕 ;
  int_st〔 2 〕 = 0x00 ;
  int_st〔 3 〕 = 0x00 ;
  artdmx_rx->prover = *( ( int * )&int_st〔 0 〕) ;
  // Sequenceを抽出
  artdmx_rx->sequence = an_rx_buf〔 12 〕 ;
  // Physicalを抽出
  artdmx_rx->physical = an_rx_buf〔 13 〕 ;
  // Universeを抽出
  artdmx_rx->subnet = an_rx_buf〔 14 〕 / 0x10 ;
  artdmx_rx->universe = an_rx_buf〔 14 〕 % 0x10 ;
  artdmx_rx->net = an_rx_buf〔 15 〕 ;
  // Lengthを抽出
  int_st〔 0 〕 = an_rx_buf〔 17 〕 ;
  int_st〔 1 〕 = an_rx_buf〔 16 〕 ;
  int_st〔 2 〕 = 0x00 ;
  int_st〔 3 〕 = 0x00 ;
  artdmx_rx->length = *( ( int * )&int_st〔0〕) ;
  // Dataを抽出
  for( i = 0; i < artdmx_rx->length; i++ ) {
    artdmx_rx->data〔 i 〕 = an_rx_buf〔 i + 18 〕 ;
  }
  if( artdmx_rx->length < 512 ) {
    for( i = artdmx_rx->length; i < 512; i++ ) {
      artdmx_rx->data〔 i 〕 = 0x00 ;
    }
  }
  return 0 ;
}

// ArtDMXのエンコード関数
int artdmx_encode( char *an_tx_buf, artdmx_t *artdmx_tx ) {
  int i ;
  // IDを設定
  for( i = 0; i < 8; i++ ) {
    an_tx_buf〔 i 〕 = artdmx_tx->id〔 i 〕 ;
  }
  // OpCodeを設定
  an_tx_buf〔 8 〕 = (char)( artdmx_tx->opcode % 0x100 ) ;
  an_tx_buf〔 9 〕 = (char)( artdmx_tx->opcode / 0x100 ) ;
  // ProVerを設定
  an_tx_buf〔 10 〕 = (char)( artdmx_tx->prover / 0x100 ) ;
  an_tx_buf〔 11 〕 = (char)( artdmx_tx->prover % 0x100 ) ;
  // Sequenceを設定
  an_tx_buf〔 12 〕 = (char) artdmx_tx->sequence ;
  // Physicalを設定
  an_tx_buf〔 13 〕 = (char) artdmx_tx->physical ;
  // SubUniを設定
  an_tx_buf〔 14 〕 = (char)( artdmx_tx->subnet * 0x100 + artdmx_tx->universe ) ;
  // Netを設定
  an_tx_buf〔 15 〕 = (char)artdmx_tx->net ;
  // Lengthを設定
  an_tx_buf〔 16 〕 = (char)( artdmx_tx->length / 0x100 ) ;
  an_tx_buf〔 17 〕 = (char)( artdmx_tx->length % 0x100 ) ;
  // Dataを設定
  memcpy( an_tx_buf + 18, artdmx_tx->data, 512 ) ;
  return 0 ;
}

// ArtDMXのチェック用画面表示
int artdmx_check_disp( artdmx_t *artdmx, char *key_code_st, int *key_code_length_st,
          unsigned long long int *val1, unsigned long long int *val2 ) {
  // 汎用変数
  //int i ;
  // タイムカウント変数
  struct timespec now ;              // 現在POSIX時を得る構造体
  unsigned long long int now_nsec ;        // 現在nsecを保存
  static unsigned long long int prev_nsec = 0 ;  // 過去nsecを保存
  // データ管理用
  static artdmx_t artdmx_disp ;
  // std_dump 用変数
  static int first_line = 0 ;           // 表示開始行数
  // 表示制御変数
  struct winsize ws ;
  static int col = 20 ;
  static int row = 100 ;
  static int net = 0 ;
  static int subnet = 0 ;
  static int universe = 0 ;

  // カーソルキー入力
  if( *key_code_length_st > 0 ) {
    if( strcmp( key_code_st, _KEY_RIGHT ) == 0 ) {
      if( ++universe > 7 ) universe = 7 ;
    }
    if( strcmp( key_code_st, _KEY_LEFT ) == 0 ) {
      if( --universe < 0 ) universe = 0 ;
    }
    if( strcmp( key_code_st, _KEY_UP ) == 0 ) {
      first_line-- ;
    }
    if( strcmp( key_code_st, _KEY_DOWN ) == 0 ) {
      first_line++ ;
    }
    *key_code_length_st = 0 ;
  }
  // 対象ユニバースのデータをスタック
  if( ( artdmx->net == net ) && ( artdmx->subnet == subnet ) && ( artdmx->universe == universe ) ) {
    memcpy( &artdmx_disp, artdmx, sizeof( artdmx_t ) ) ;
  }

  // タイムカウント
  clock_gettime( CLOCK_REALTIME, &now ) ;             // 現在POSIX時間値取得
  now_nsec = ( unsigned long long int )now.tv_sec * 1e9      // unsigned long long int型(8バイト長int)変数に
        + now.tv_nsec ;                   // 取得値をひとまとめにする(nsec)
  if( now_nsec - prev_nsec < 5e7) return -1 ;           // 経過時間が不足なら何もせずリターン
  prev_nsec = now_nsec ;                     // 過去nsecを現在値としておく
  // 画面のテキスト表示サイズを取得
  ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) ;             // 画面サイズを取得 幅=ws.ws_col 高=ws.ws_row
  if( row + 4 > ws.ws_row ) row = ws.ws_row - 4 ;         // dumpの行数制限
  // artdmxを表示
  printf( "\e〔0;0H" ) ;                          // カーソルを左上に 
  printf( "Sender: %s:%d", artdmx_disp.senderip, artdmx_disp.senderport ) ;
  printf( "\e〔28GID: %s", artdmx_disp.id ) ;
  printf( "\e〔41GOpCode: 0x%04X", artdmx_disp.opcode ) ;
  printf( "\e〔57GProVer: 0x%04X", artdmx_disp.prover ) ;
  printf( "\e〔0K\nSequence: 0x%04X", artdmx_disp.sequence ) ;
  printf( "\e〔19GPhysical: 0x%04X", artdmx_disp.physical ) ;
  printf( "\e〔37GLength: %d", artdmx_disp.length ) ;
  printf( "\e〔50G< Net:%d SubNet:%d Universe:%d >\e〔0K\n", artdmx_disp.net, artdmx_disp.subnet, artdmx_disp.universe ) ;
  dump_print( artdmx_disp.data, artdmx_disp.length, col, row, &first_line, 0 ) ;
  printf( "%8lld /%8lld nsec", *val1, *val2 ) ;
  //fflush( stdout ) ;
  // 終了
  return 0 ;
}


#[Art-Net] #C言語
Icon of admin
 今年も師走です。
 現場は現地照明をもう1件やれば終わり。今期の年末年始は人並みにユックリ出来そうです。
 そんな時は机上の勉強を進めたいものです。物理的な加工や開発・設計ではなくお勉強。特にC言語の勉強です。
 一応書けるのですが、ポインタや構造体についてボンヤリしているところを整理したい。Art-Net関連には特に重要な基礎ですから抑えないといけません。

#C言語
Icon of admin
 セマフォの使い方が見えたので構造の重要な要素は獲得出来ました。
 構成ですが、起動し mmap や pipe を定義し子プロセスの状態を監視する親プロセス「ArtNetPatch」、ユーザーの操作の相手やデータを画面表示をする子プロセス「ArtNetConsole」、Art-Netを送受信しパッチ処理やプロファイルカーブ処理を行う子プロセス「ArtNetEngine」の3本柱にしようと思います。
 子プロセスの生成は fork を使います。fork ならば mmap、pipe、semaqhoe を子プロセスに渡すのが楽です。メモリの消費は多くなるようですが、実体の複製ですからポインタ祭りにならずに済みます。メモリの消費が多いと言っても、私が作るソフトウェアの規模などたかが知れていますので RaspberryPiCM4 の8GBモデルなら楽勝でしょう。
 こういった構造は面倒な気もしますが、プロセス構造をシッカリ作っておけば開発が楽になると思いますし、何よりも「ArtNetEngine」の処理にリソースを集中したいのです。状態監視やユーザー相手は100msec毎で十分ですが、Art-Net の処理は5msec毎が欲しいのです。分散処理というより、求めるラップが違う処理を明確に分けたいのです。
 処理時間を意識してしまうのは PIC マイコンからのクセですが、RaspberryPiCM4 で8ユニバースのパッチ処理をするならダイエットが大事かと。

#[Art-net] #C言語
Icon of admin
 ArtNet-Patch で共有メモリの mmap を使うために セマフォ を勉強しています。mmap へのアクセスを排他的にするためです。
 ただ、プロセス間のセマフォを使うような人は上級者が多いのか解説ページを見てもわかりにくい。
 POSIX系のセマフォは物凄くシンプルな感じがしますが、とどのつまりがわからない。
 man もわかっている人向けなのか引数の記述があるだけ。
 もっと勉強です。

追記
 なんて愚痴っていたらよい記事がありました。
 開始が sem_init だったり sem_open だったりするけど、同じことみたいですからヘッダファイルのプロトタイプ記述を読めば良さそう。
「sem_overview - POSIX セマフォの概要」
「man-pages-ja sem_init(3)」
 次の記事は、パーミッションにおける umask のことが主題ですが、セマフォの使い方がよくわかる。シンプルな記述を示してくれるこんな記事が嬉しい。
「sem_openで、Permission denied になる」
 わかった気になった。

#[Art-net] #C言語
Icon of admin
 少し時間が空いたので ArtNet-Patch を考えていました。
 部分はなんとなく出来ているので、全体の構造をどうするか。
 まずプロセスを分けます。シングルプロセスではたぶんダメ。画面やキーボード操作を親プロセスにし、Art-Net の受信、パッチ処理、送信などを子プロセスとしようかと。子プロセスはタイムアウトする様にし、親プロセスからのリフレッシュが一定時間無ければ自動的に閉じることにします。こうしておけば親プロセスが飛んでも子プロセスがゾンビにならずいいかなと。操作が飛んでも Art-Net は生きている方がいいとする考え方もありますが、タイムアウト処理をコメントにすればそうなります。少なくとも開発中はタイムアウトした方がいいでしょう。
 プロセス間は mmap や Pipe で繋ぎます。データには mmap を用い、コマンドには Pipe を用いるイメージです。
 子プロセスの Art-Net 処理をイメージしながら表示画面やキーボード操作を考えてみましょう。

#[Art-net] #C言語

■思ってみた

社屋を囲む田んぼの田植えが終わり季節を感じます。

編集

■全文検索:

複合検索窓に切り替える

■複合検索:

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

■日付検索:

■カレンダー:

2025年6月
1234567
891011121314
15161718192021
22232425262728
2930

■カテゴリ:

■最近の投稿:

最終更新日時:
2025年6月22日(日) 15時27分49秒