M1 Macbook AirでインストールしているArm版Ubuntu、アップデートが正常に終了しなくなった後で、起動しなくなった。新たにインストールし直ししか道はなさそう。
まあUTMの限界といえばそうなのかもしれないから、VMWaraのAppli silicon native版待ちかな。個人使用無償の継続期待が前提ですが。
admin

la vie libre
M1 Macbook AirでインストールしているArm版Ubuntu、アップデートが正常に終了しなくなった後で、起動しなくなった。新たにインストールし直ししか道はなさそう。
まあUTMの限界といえばそうなのかもしれないから、VMWaraのAppli silicon native版待ちかな。個人使用無償の継続期待が前提ですが。
admin
ラズパイでmyfareアプリを起動時に立ち上げしようと思ってrc.localに記述しても起動しません。実は最近のLinuxのバージョンではrc.localは単にipアドレスの表示をするだけになっていて、アプリの起動はsystemdを使えということのようです。しかしsystemdに設定したつもりでも起動するとエラーになります。実行ファイルと同じディレクトリに配置しているファイルが見つからないと言われます。
$ sudo systemctl status myfare.service
● myfare.service - myfare
Loaded: loaded (/etc/systemd/system/myfare.service; enabled; vendor preset>
Active: failed (Result: exit-code) since Wed 2023-04-19 11:57:00 BST; 9s a>
Process: 1805 ExecStart=/home/pi/myfare/main (code=exited, status=1/FAILURE)
Main PID: 1805 (code=exited, status=1/FAILURE)
CPU: 121ms
Apr 19 11:57:00 rasp-b systemd[1]: Started myfare.
Apr 19 11:57:00 rasp-b main[1805]: open uid.json: no such file or directory事例検索して行き着いたのが、WorkingDirectory設定。
[Unit]
Description=myfare
[Service]
WorkingDirectory=/home/pi/myfare/
Type=simple
ExecStart=/home/pi/myfare/main
[Install]
WantedBy = multi-user.targetつまりこれを指定しないと、実行ファイルからファイルを見つけられなくなります。
systemdの記述方法はネットにたくさんありますが、ここに行き着くのに一時間以上。大本のマニュアル見た方が早かったかと思いますが、ともかくも以下の一連のコマンドの手順で自動起動できました。
サービスファイルを記述して、daemonのリロード
$ sudo systemctl daemon-reload
起動確認
$ sudo systemctl start myfare.service
正常に起動していることを確認
$ sudo systemctl status myfare.service
起動時にサービスを有効化
$ sudo systemctl enable myfare.service
admin
Macで開発したアプリをラズパイで動かそうとしましたが、そのままでは動かない。なぜならgormもdbドライバーもcgoを使っている、つまりターゲットのgccを用意してそれを指定しないといけないから。
とりあえず動かすだけなら、すごく時間はかかりますがラズパイでビルド、2時間ぐらい放置してたらビルド完了してました。
実行ファイルを起動すると、Macよりは多少レスポンスは遅いのですがちゃんと動作しています。
<layout.html>
これだけはws://mbair.local:8080/wsをラズパイに変更が必要です。
window.onload = function () {
socket = new WebSocket("ws://mbair.local:8080/ws");
socket.onopen = function () {
append_message("system", "Socket Connected");
};
socket.onmessage = function (event) {
append_message("server", event.data);
};
const send = function (){
socket.send("")
}
setInterval(send, 500);
};
クロス環境をどうするかですが、Dockerがおすすめのようなので、それでやってみます。
メモリ使用状況は、こんな感じです、クライアント一台だけで、
すぐにもう一台増やすと、およそ200KBぐらい増えていますが、この程度では普通には十分です。
admin
以下の記事からのアップデートになります。
https://isehara-3lv.sakura.ne.jp/blog/2023/04/14/golangでwebsocketの実装/
カードリーダーにタッチで参加登録してブラウザで状況が見れる、追加で参加登録があるとwebsocketでその旨のメッセージを表示(画面更新はマニュアル)、発表者は登録のリンクをクリック(トグルになっています、Wi-Fi内の限定ユーザーなのでpwとかは要求しません)すると発表登録というアプリです。
コードの構成は、
uid.jsonはuidとnameのjson形式ファイルです。
全体のコードは、
https://github.com/chateight/golang/tree/master/myfare
になりますが、websocket経由メッセージのブロードキャストのためにコードは以下のようなっています(main.goの部分抜き出し)
① 新規参加者の登録あればその旨のメッセージをserial.goからチャネル経由でmain.goに通知する
② wsはブラウザからのリクエストの都度新規に作成されるから、通知内容が消滅しないようにmain.goの中で共通エリアに保存
③ 余計なwsを消すために、ブラウザから定期的にwebsocket経由でメッセージ送ってforループ内でタイムアウト(今は3秒に設定)すれば未使用wsと判断して終了させる、clients[ws] = trueで確認できるようにしてます
④ ②で保存された通知内容は各ws内で未送信(前回送付と同じかどうか判定、つまり同じ内容は2回以上送らない)であれば送信するが送信条件はタイマーで設定、チャネルから受信と並列条件としています
スマートではなさそうだからもっと別の方法がありそうです。
func msgHandler(ws *websocket.Conn) {
defer ws.Close()
clients[ws] = true
fmt.Println(clients)
premsg := msg // initialize websocket message
t := time.NewTicker(500 * time.Millisecond)
defer t.Stop()
label:
for {
msgr := ""
err := websocket.Message.Receive(ws, &msgr)
if err != nil {
//log.Println("receive error") // main pupose is to check timeout (to detect unused session)
break label
}
select {
// to send websocket message triggered by the timer
// the reason to separate receive and send is ws are running multi thread
case <- t.C:
if premsg != msg {
premsg = msg
err := websocket.Message.Send(ws, msg)
if err != nil {
log.Println("send err")
break label
}
}
case name := <- uidSerial.Notice: // wait for message from serial.go via channel
mu.Lock()
msg = name.(string)
mu.Unlock()
}
}
clients[ws] = false
}
admin
Myfare cardを追加してテーブル作成しようとしたら、特定のカードのテーブルの値がstring指定したはずなのに”Inf”になってしまった。Infとはおそらく無限大ということだろうからstring型を指定しても機能しないらしい。
で調べるとSQLiteで指定できる型にはstringは無い。
https://www.javadrive.jp/sqlite/type/index1.html
おそらくstringを指定しても整数扱いとなってオーバーフローしてしまったらしい。
string -> text
int -> integer
に変更して作り直してうまくいっているようです。GORMで指定する構造体ではこのように変更しようがない(おそらくGORMが自動で変換する)から、テーブル作成時のSQLコマンドだけでの対応。
admin
websocket自体はRFCで規定されているものですが、実装はそれぞれの言語ごとに存在します。
サンプルは、
https://zenn.dev/empenguin/articles/bcf95c19451020
元を辿ると、おそらくこちら。
https://echo.labstack.com/cookbook/websocket/
他の言語の例に漏れず、クライアント側のJavaScriptとサーバー側のGoのスクリプトが連携して動作します。ディレクトリ構成は以下の通りです。
<main.goのリクエスト処理部分>
func handleWebSocket(c echo.Context) error {
websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close()
// 初回のメッセージを送信
err := websocket.Message.Send(ws, "Server: Hello, Client!")
if err != nil {
c.Logger().Error(err)
}
for {
// Client からのメッセージを読み込む
msg := ""
err = websocket.Message.Receive(ws, &msg)
if err != nil {
//c.Logger().Error(err)
}
// Client からのメッセージを元に返すメッセージを作成し送信する
err := websocket.Message.Send(ws, fmt.Sprintf("Server: \"%s\" received!", msg))
if err != nil {
//c.Logger().Error(err)
}
}
}).ServeHTTP(c.Response(), c.Request())
return nil
}最初のページリクエスト後はwebsocketのループに入るので、ここでページのリロード(複数回ルートのリクエスト)をするとループ処理(for loop)でエラーが発生します。
一点疑問はwebsocketのポート番号指定をどこでやっているのかわからないこと。
P.S. 2023/4/15
ポートはHTTPと同じポートが使われています。WSはHTTPとは異なるプロトコルなので同じポートでも問題はないようですね。
admin
動的なページ作成のためにはテンプレートエンジンが必須で、Node.jsの場合にはexpress-generatorに同様の機能が存在しますが、golangでもライブラリhtml/templateを使うと実現できます。
https://isehara-3lv.sakura.ne.jp/blog/2023/03/29/golangとnode-jsでmifareカード使ってチェックイン表示/
ではビューはNode.jsを使っていますが、ラズパイで動かすと実行速度あるいは実行ファイルの扱いやすさなどを考えるとGolangに統一した方が良いだろうからhtml/templateを使います。myfareカードから読み取りデータを受信するGoのスクリプトはほぼそのままです。
https://www.twihike.dev/docs/golang-web/templates
上記を参考に作成してみました。
<web server起動のスクリプト>
myfareカードを読み取ってDBをアップデートする関数はgoオプションで並行処理として起動(go uidSerial.SerialMain())。
package main
import (
"fmt"
"html/template"
"myfare/uidSerial"
"net/http"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"strconv"
"time"
)
type Ninjya struct {
Uid string `gorm:"primaryKey"`
Name string
Time int
Stat int
}
var products []Ninjya
var ninjyaSlice []string
// implements TableName of the Tabler interface
func (Ninjya) TableName() string {
t := time.Now()
tableName := "tbl" + strconv.Itoa(t.Year()) + strconv.Itoa(t.YearDay())
return tableName
}
// to get active ninjya slice
func ninjya() {
db, err := gorm.Open(sqlite.Open("./myfare.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Migrate the schema
db.AutoMigrate(&Ninjya{}) // if you use Automigrate and change struct, it won't be reflected automatically
db.Debug().Order("Time desc").Where("Stat = ?", 1).Find(&products) // SELECT * FROM where Stat = tbl*****;
ninjyaSlice = nil
for i, p := range products {
ninjyaSlice = append(ninjyaSlice, p.Name)
fmt.Println(i, p)
}
}
func handler(w http.ResponseWriter, r *http.Request) {
t := template.Must(template.ParseFiles("layout.html", "pageData.html"))
// to update ninjya status
ninjya()
tm := time.Now().Format(time.RFC1123)
err := t.Execute(w, map[string]interface{}{
"Time": tm,
"Slice": ninjyaSlice,
})
if err != nil {
fmt.Fprintln(w, err)
}
}
func wevServer() {
mux := http.NewServeMux()
// to include static resoureces
mux.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir("resources/"))))
mux.HandleFunc("/", handler)
server := http.Server{
Addr: ":8080",
Handler: mux,
}
err := server.ListenAndServe()
if err != nil {
if err != http.ErrServerClosed {
panic(err)
}
}
}
func main() {
// to call card reader function()
go uidSerial.SerialMain()
// http server start
wevServer()
}t := template.Must(template.ParseFiles("layout.html", "pageData.html"))
ここで、以下の二個のtemplete処理対象ファイルを読み込みます。
<layout.html>
<pageData.html>
mux.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir("resources/"))))
スタイルシートを読み込むために検索パスを追加します。
実行すると、例えばこんな具合にブラウザ上に表示されます。
これでNode.jsと同等レベルになったので、Golang上で機能を追加していきます。
admin
いずれMyfareカードリーダーのアプリはラズパイで動かすので、つないでみた。まずはUSBがtype-Aで物理的に接続の簡単なラズパイ Model B+でつないでみます。最初I2Sのオーディオ拡張ボード挿入したままだと電源入らずラズパイも起動しないから、拡張ボードを外すと起動できました。
外部電源が不足か、ラズパイ本体から電力供給できないかのどちらかですが、USB type-Aにはmini-Bの5Vから直接供給しているので外部電源の容量不足のようです。
ラズパイからの見え方は、
$ lsusb
Bus 001 Device 005: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC
Bus 001 Device 003: ID 0424:ec00 Microchip Technology, Inc. (formerly SMSC) SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Microchip Technology, Inc. (formerly SMSC) SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hubDevice 005が該当で、当然ながらMacで読んだ値と同じで、
port.VID == “0403” && port.PID == “6001”
となります。
他の要因もあるから、Macでクロスコンパイルしたバイナリがそのまま動くかどうか?
admin
MARSというソフトがQNAPから昨年秋にリリースされていて、使い道はgoogle photoとwordpressのバックアップに使えそうです。
で、NASのディレクトリ再構築の機会にインストールしました。
に手順が記載されていますが、スケジュール設定すればほぼリアルタイムバックアップもできます。HBS3との違いはプロトコルが違うのと、バックアップ限定で同期処理はできないというのが機能面の違いでしょうか。
wordpressのバクアップ設定はこれから、
admin
他のGolangでSQL文を使って作成したテーブルにGORMを使ってアクセスします。ポイントはテーブル名がデフォルトでは使えないので、Tabler interfaceのメソッドであるTableName()を実装してテーブル名を与えているところになるかと思います。
https://isehara-3lv.sakura.ne.jp/blog/2023/04/01/goのormであるgormを見てみる/
の再構成に過ぎませんが、最低限やりたいことはGORMで実現できそうです。
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"time"
"strconv"
)
type Product struct {
Uid string `gorm:"primaryKey"`
Name string
Time int
Stat int
}
var products []Product
//
// implements TableName of the Tabler interface
//
func (Product) TableName() string {
t := time.Now()
tableName := "tbl" + strconv.Itoa(t.Year()) + strconv.Itoa(t.YearDay())
return tableName
}
func main() {
db, err := gorm.Open(sqlite.Open("~~~path to the db~~~ /myfare.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Migrate the schema
db.AutoMigrate(&Product{}) // if you use Automigrate and change struct, it won't be reflected automatically
db.Debug().Find(&products) // SELECT * FROM tbl*****;
for i, p := range products{
//db.Model(&p).Update("Qty", 20)
fmt.Println(i, p)
}
}
admin