doneパターン(Golang)

Goでの特徴的な機能の一つであるgoルーチンですが、その終了判定あるいはチャネルの選択時の一つのパターンがdoneパターンと言われるもの。

コードのベースは以下からですが、

https://github.com/mushahiroyuki/lgo/blob/main/example/ch10/ex1009.go

実はチャネルサイズが10なので、以下のgoルーチンのループでは11以降はチャネルへの書き込みができないので待ちになります。実質はmainルーチンが終了するとgoルーチンも終了されるので処理上の問題はないのですが、

ここでgoルーチン側で終了待ちをするためのdoneパターンを導入してみます。

close(done)がgoルーチンへの終了シグナルになりますが、goルーチン側ではselect/caseを使っています。チャネルの処理が継続できる条件が複数あるときに、継続可能となった処理をselect/caseで選択して実行するものです。select/case文の特徴的なところは、複数の条件が成立しているときにはランダムで継続可能処理を選ぶことでswitch文のように上から順番のような優先順位はつけないということです。

func main() {
	ch := make(chan int)
	var result []int

	done := make(chan struct{})

	go func() {  // 処理してもらう数値をchに入れる
		for i := 0; i < 100; i++ {
			select {
			case <- done:
				fmt.Print(". ")
				return
			case ch <- i:
			}
		}
	}()
	
	result = processChannel(ch)
	close(done)
	
	time.Sleep(1000)
	fmt.Printf("result: %d\n", result)
}

このサンプルでは確実にdone処理を実行するためにmainルーチンで1000nsの待ち時間を入れています。

実行すると、例えば以下のようになります。

ゴルーチン 起動完了
process: 0 0 0
process: 9 81 0
process: 5 25 2
process: 6 36 2
process: 4 16 0
process: 7 49 1
process: 1 1 2
process: 8 64 2
process: 2 4 1
process: 3 9 1
. result: [0 81 16 9 4 49 64 25 1 36]

 

admin

 

Fusion 360(Apple silicon)

https://knowledge.autodesk.com/support/fusion-360/troubleshooting/caas/sfdcarticles/sfdcarticles/Is-Fusion-360-supported-on-Apple-M1-System-Architecture.html

という情報を見たので、ダウンロードして立ち上げてみて、最初の二回は反応なくなったけど、3度目からは立ち上がるようになった。Intel版でも割と起きる現象だから、Apple silicon限定というわけでもない。

ただしFinderのアプリケーションからは見えず、ユーザディレクトリのApplications配下で見えるからDockに配置。

作成したパーツはクラウドに保存されているから、インポートとかは不要。

アプリ情報見てみるとrosetta2モードで動作しています、ネーティブ対応は今年の夏頃と言っていますから。

Intel Mac必須だった障壁また一つ乗り越えたから、そろそろApple silicon統一でも環境としては問題ないかもしれない。新規プラットホーム投入でも、二年半経過すると概ねそんな感じになるんだね。

P.S. 2023/3/14

アプリ立ち上げ時間比較(Dockのアイコンクリックから立ち上がり画面までストップウォッチ計測)

・2029 Intel i7 16GB 一回目:60秒、二回目:40秒

・M1 MacBook Air 16GB 一回目:33秒、二回目:36秒

ほぼ同じぐらいかと思っていたけど、M1の方がRosetta2にも関わらず高速立ち上がりだからApple siliconって優秀。

 

admin

依存性注入(Golang)

依存性注入(DI : Dependency Injection)は他の言語でもありますが、Goの場合にはより構造が単純化されると思います。

以下のコードは、

https://qiita.com/lostfind/items/cae2bca46d903bea167c

からですが、DIを使わないと電球の種類が変わる都度、部屋の工事が必要になるのですが、DI使って部屋にはソケットだけ用意して電球の種類はmain()ルーチンで簡単に変えられるようにしています。DIのメリットは変更発生時の変更範囲が分離されることと、テストが容易(関連するモジュールがなくてもできる)あたりがメリットになるでしょう。プログラムのライフサイクルで保守が大きな比重を占める訳なので、変更に強いコードと言えます。反面コードはその分冗長になります。

package main

import (
	"fmt"
)

func main() {
    lightOne := new(Incandescent) 	// only change these lines if new lighting is introduced
    lightTwo := new(LedLight)		//

    myRoom := NewRoom(lightOne, lightTwo) // DI or object Injection

    myRoom.SwitchOnOne()
    myRoom.SwitchOnTwo()
}

type LightSocket interface {
    LightUp() string
}

// room has two sokets
type Room struct {
    LightOne LightSocket		// prepare only socket, not bulb
    LightTwo LightSocket
}

// define switch on method of the two sockets
func (r *Room) SwitchOnOne() {
    fmt.Println("1番照明:", r.LightOne.LightUp())
}
func (r *Room) SwitchOnTwo() {
    fmt.Println("2番照明:", r.LightTwo.LightUp())
}

// Constructor Injection
func NewRoom(lightOne, lightTwo LightSocket) *Room {
    room := &Room{
        LightOne: lightOne,
        LightTwo: lightTwo,
    }
    return room
}

// Led light
type LedLight struct{}

func (*LedLight) LightUp() string {
    return "LEDが光るよ!"
}

// Filament bulb
type Incandescent struct{}

func (*Incandescent) LightUp() string {
    return "フィラメントが光るよ!"
}

DIはデザインパターンそのものです。

 

admin

 

Stringer interface(Golang)

今更ですが、fmtパッケージにはStringerインターフェースが定義されているので、StringerインターフェースのメソッドString()を独自に実装すると、実装したメソッドが有効となるのでその書式でPrintされます。

以下はGlangのAPIからのコードですが、String()を実装すればその書式になるし、未実装ならば構造体のままでプリントされています。

package main

import (
	"fmt"
)

// Animal has a Name and an Age to represent an animal.
type Animal struct {
	Name string
	Age  uint
}

// String makes Animal satisfy the Stringer interface.
func (a Animal) String() string {
	return fmt.Sprintf("%v (%d)", a.Name, a.Age)
}

func main() {
	a := Animal{
		Name: "Gopher",
		Age:  2,
	}
	fmt.Println(a)
}
<String() string実装>
Gopher (2)

<デフォルト>
{Gopher 2}

多少事情は異なりますがtime.Timeでは、time.Timeが独自のString()メソッドを実装しているから意図した通りにはならず独自のStringメソッドが補完してくれるようです。

https://tutuz-tech.hatenablog.com/entry/2019/11/28/091036

 

admin

 

M1 Macのミー文字

購入以来、いくら設定しても「龍安寺石庭」のような画像に戻ってしまっていましたが、おそらく最新のアップデートで設定がリセットされなくなりました。Apple siliconのソフトサポートもこんな感じで徐々にされて行くんでしょう。

 

admin

フィラメント吸湿対策

3Dプリンタのフィラメントは吸湿しやすく、特に夏場の高湿時には過酷です。吸湿すると造形品質劣化するのと、ノズルも痛むし、硬化して折れてしまって送りもうまくいかなくなります。

一度吸湿したフィラメントを乾燥できるというものがあったので購入してみました。

『3Dフィラメントストレージ用のSUNLUドライバックス』という製品です。

https://www.amazon.co.jp/gp/product/B08C9RF7R7/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1

 

熱中症指数把握のためのM5Stackセンサーが温湿度測定にピッタリなのでモニターしてみました。

湿度は今少し下げたほうがいいだろうから、中に吸湿剤も入れたほうが良さそうです。効果が明確にわかるのは夏場になるだろうから、それまでに環境を整備していきます。

 

admin

Mifare cardでの参加者表示アプリ作成

これを使って、USBシリアル経由でProcessingでカード情報を受け取り、UIDと参加者の対応表からカードをタッチした参加者は出席とSQLiteのテーブルに書き込み、デスクトップ画面に表示をするようなアプリです。

M5Stickc plusとProcessingのコードは以下のリンクから、

https://github.com/chateight/rfid

注意点はserialEvent()は割り込み処理なので、この中で共通リソースを変更すると、他で使用中のコードからは意図しない(不規則な)変更に見えるので、この中では共通リソースの書き込み処理を行ってはいけない。具体的にはdbTableUpdate()の呼び出しをserialEvent()中で行っていたら、draw()からの呼び出し中にString[] membersの配列を破壊してパニックが発生しました。dbTableUpdate()の実行中に配列を書き換えなければ問題ないのでそれで対応。

10人分の登録をしたらこんな画面になります。

 

admin

M5Stickc plus

https://isehara-3lv.sakura.ne.jp/blog/2023/02/17/mifareカードリーダー/

M5StackでRFカードリーダーのアプリ作ろうと思っていましたが、カードリーダー専用ならばM5Stackである必要性はないので、より小型のM5Stickc plusを使ってみました。

サイズの差、

動かしてみたところ、

M5StackとM5Stickc plusにはいくつか差分がありますが、以下はM5Stickc plus用のコードです。

主な違いを列挙すると、

    1. ライブラリは別です、PlatformIO使うときにM5Stack用のソースから書き換えにはplatforio.iniの書き換えが必要です。
    2. 画面はローテーションしないと幅が狭すぎて使いづらいので横方向で使います。
    3. 内蔵の赤LEDをカード読み取り信号として使っています。
    4. 内蔵のブザーも同じ目的で使っています。

5. 電源オンオフは、M5」と書かれたボタンを正面に見て左側にあるボタンを6秒長押しすると電源が切れます。電源ONは同じボタンを押します

    6. リセットは対応するボタンがないので、ネット情報を参考にBボタンに割り当てています。
    7. 本体の取り付けは2mmネジです(現物合わせ)、長すぎると本体を破壊しそうですが、係りはおよそ3mmぐらいです。ネジ穴センター間の距離は実測で15.8mmでした。
    1. -> P.S. たまたま目にした図面によると、ネジ穴センター間の距離は16mmのようです。
/*
to change for the M5stickc-plus 2023/2/23
*******************************************************************************
* Copyright (c) 2022 by M5Stack
*                  Equipped with M5Core sample source code
*                          配套  M5Core 示例源代码
* Visit for more information: https://docs.m5stack.com/en/core/gray
* 获取更多资料请访问: https://docs.m5stack.com/zh_CN/core/gray
*
* Describe: RFID.
* Date: 2021/8/19
*******************************************************************************
  Please connect to Port A(22、21),Use the RFID Unit to read the Fudan card ID
and display the ID on the screen. 请连接端口A(22、21),使用RFID Unit
读取ID卡并在屏幕上显示。
*/

#include <m5stickcplus.h>

#include "MFRC522_I2C.h"

MFRC522 mfrc522(0x28);  // Create MFRC522 instance.  创建MFRC522实例

void setup() {
    M5.begin();             // Init M5Stack.  初始化M5Stack
    //M5.Power.begin();       // Init power  初始化电源模块
    M5.Lcd.setRotation(3);
    M5.lcd.setTextSize(2);  // Set the text size to 2.  设置文字大小为2
    M5.Lcd.println("MFRC522 RFC reader");
    Wire.begin();  // Wire init, adding the I2C bus.  Wire初始化, 加入i2c总线
    Serial.begin(115200);

    mfrc522.PCD_Init();  // Init MFRC522.  初始化 MFRC522
    M5.Lcd.println("Please put the card\n\nUID:");
    // LED port as output & set to "OFF"
    pinMode(10, OUTPUT);
    digitalWrite(10, HIGH);
}

void loop() {
    // reset by B button push
    if(M5.BtnB.wasPressed()){
        esp_restart();
    }
    M5.update();

    M5.Lcd.setCursor(40, 47);
    if (!mfrc522.PICC_IsNewCardPresent() ||
        !mfrc522.PICC_ReadCardSerial()) {  //如果没有读取到新的卡片
        delay(200);
        return;
    }
    M5.Lcd.fillRect(42, 47, 240, 20, BLACK);
    for (byte i = 0; i < mfrc522.uid.size;
         i++) {  // Output the stored UID data.  将存储的UID数据输出
        M5.Lcd.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
        M5.Lcd.print(mfrc522.uid.uidByte[i], HEX);
        Serial.print(mfrc522.uid.uidByte[i], HEX);
        // beep & LED on for 50ms    
        M5.Beep.tone(880);
        digitalWrite(10, LOW);
        delay(50);
        M5.Beep.mute();
        digitalWrite(10, HIGH);
    }
    M5.Lcd.println("");
    Serial.println("");
}

M5Stackファミリーも後から出てくるハードの方が進化しています。

 

admin

Processingで使えるシリアルポートをスキャンする

M5StackからPCへの接続方法にはいろいろありますが、無線でなくても良い、つまりUSBで電源供給しているならばUSB経由のシリアルで接続するのが妥当そうです。

このときにシリアルポートを指定しないといけないですが、ポートをスキャンするサンプルがあったので使ってみました。

参考は、

https://garchiving.com/processing-auto-serial-port/

になります。

PC側はProcessingを使っていますが、12行目から21行目までがポート検出の該当スクリプトです。ポート指定方法としてnew Serialの第二引数に”/dev/tty.usbserial-54260147311″のように直接指定する方法もありますが、環境が変われば改めて設定しなければならないでしょうから、自動検出にはメリットがあります。

import processing.serial.*;

Serial myPort;

int x;
String[] comPort = myPort.list();

void setup(){
  // 画面サイズ
  size(256, 256);

  int num = comPort.length;
  for (int i=0; i<num; i++) {
    try {
      myPort = new Serial(this, comPort[i], 115200);
      println(comPort[i]);
    }
    catch(Exception e) {
      continue;
    }
  }
}

void draw(){
  // 背景色を白に設定
  background(255);

  // XY座標を(x,100)に設定し、
  // 幅50、高さ50の円を描画
  ellipse(x,100,50,50);
}

void serialEvent(Serial p){
  //変数xにシリアル通信で読み込んだ値を代入
  x = p.read();
  println(x);
}

M5Stack側は極めてシンプルなスクリプトで1秒ごとに異なる値を送るだけ。Processing側ではcircleの座標として扱っています。

#include

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

void loop(){
  Serial.write(100);
  delay(1000);
  Serial.write(200);
  delay(1000);
}

 

admin

関数に可変長引数をスライスで渡すとき(Golang)

Goで可変引数を渡すときにの記述方法についてです。

可変長引数は関数で内部的にはスライスに変換されるのでスライスで渡すこともできますが、関数側は”v1(names ...string)“のように受け取りますが、呼び出し側は”v1(names...)“のように…が関数とは逆の位置に来ますよということです。

/*
Variadic functions in Golang
*/ package main import ( "fmt" ) func v1(names ...string) { // pack operator used before type in argument fmt.Println(names) } func main() { names := []string{"Albert", "Issac"} v1("John", "Jane", "Dexter", "Bruce") // [John Jane Dexter Bruce] // Here is unpack operator in action v1(names...) // [Albert Issac] name1 := []string{"John", "F.", "Kennedy"} v1(name1...) }

決まり事ではありますが、論理的に考えれば受け取り側は型を指定していて、呼び出し側は変数名(スライス名)を指定しているから合理的とは言えます。

 

admin