kuroの覚え書き

96の個人的覚え書き

scRNA-seqやってみる(その3)

時系列や各種処理を与えたサンプル間で比較するなら、それらをマージして同じクラスターで重ね合わせて考えたい。
まずはumapチャートを出力するまで。4つの実験区の比較を行う例をやってみたい。Seuratのハンズオンでは2区の重ね合わせなんだけど、実際に10xの出力として保存されたデータからではなく、ライブラリからサンプルデータを読み出して処理しており、実際的ではない。なのでCellrangerの出力するfiltered_feature_bc_matrixフォルダに3つのファイル(barcode.tsv.gz, features.tsv.gz, matrix.mtx.gz)が保存されているところをスタートとした解析を試してみる。

if(!require(Seurat)){install.packages("Seurat")}
if(!require(patchwork)){install.packages("patchwork")}
if(!require(dplyr)){install.packages("dplyr")}

library(Seurat)
library(patchwork)

install.packages('BiocManager')
# Bioconductorのバージョンを設定
BiocManager::install(version = "3.19")
BiocManager::install('glmGamPoi')

#####Remove all objects before running#####
rm(list = ls())

input.dir <- "/path/to/sample/"

# サンプル1のデータセットを読み込む
sample1 <- Read10X(paste(input.dir, "sample1/filtered_feature_bc_matrix", sep=""))

# サンプル2のデータセットを読み込む
sample2 <- Read10X(paste(input.dir, "sample2/filtered_feature_bc_matrix", sep=""))

# サンプル3のデータセットを読み込む
sample3 <- Read10X(paste(input.dir, "sample3/filtered_feature_bc_matrix", sep=""))

# サンプル4のデータセットを読み込む
sample4 <- Read10X(paste(input.dir, "sample4/filtered_feature_bc_matrix", sep=""))

sample1 <- CreateSeuratObject(counts = sample1, project = "sample1")
sample2 <- CreateSeuratObject(counts = sample2, project = "sample2")
sample3 <- CreateSeuratObject(counts = sample3, project = "sample3")
sample4 <- CreateSeuratObject(counts = sample4, project = "sample4")

# 各データセットにユニークなセル識別子を追加
sample1 <- RenameCells(sample1, new.names = paste("sample1", Cells(sample1), sep = "_"))
sample2 <- RenameCells(sample2, new.names = paste("sample2", Cells(sample2), sep = "_"))
sample3 <- RenameCells(sample3, new.names = paste("sample3", Cells(sample3), sep = "_"))
sample4 <- RenameCells(sample4, new.names = paste("sample4", Cells(sample4), sep = "_"))

# サンプルデータセットのリストを作成
datasets <- list(sample1, sample2, sample3, sample4)

# データセットの前処理
datasets <- lapply(datasets, function(x) {
  x <- SCTransform(x, verbose = FALSE)
  x <- FindVariableFeatures(x, selection.method = "vst", nfeatures = 2000)
  x
})

# 統合のためのアンカーフィーチャーの選択
features <- SelectIntegrationFeatures(object.list = datasets, nfeatures = 2000)

# SCT正規化を使用した統合アンカーの発見
datasets <- PrepSCTIntegration(object.list = datasets, anchor.features = features)
anchors <- FindIntegrationAnchors(object.list = datasets, normalization.method = "SCT", anchor.features = features)

# データセットの統合
combined <- IntegrateData(anchorset = anchors, normalization.method = "SCT")

# 統合データの次の解析ステップ
combined <- ScaleData(combined, verbose = FALSE)
combined <- RunPCA(combined, npcs = 30, verbose = FALSE)
combined <- RunUMAP(combined, reduction = "pca", dims = 1:30)
combined <- FindNeighbors(combined, reduction = "pca", dims = 1:30)
combined <- FindClusters(combined, resolution = 0.5)

# ここでデータを一旦保存しておく
# 後で読み込むときはcombined <- readRDS(file =paste(input.dir, "combined.rds", sep=""))
saveRDS(combined, file = paste(input.dir, "combined.rds", sep=""))

# 結果の可視化
p1 <- DimPlot(combined, reduction = "umap", group.by = "orig.ident")
p2 <- DimPlot(combined, reduction = "umap", label = TRUE, repel = TRUE)
p1 + p2

DimPlot(combined, reduction = "umap", split.by = "orig.ident")

# 出来上がったクラスターに名前をつけることができる
combined <- RenameIdents(combined, `0` = "Cluster 0", `1` = "Cluster 1", `2` = "Cluster 2", `3` = "Cluster 3")
DimPlot(combined, label = TRUE)

# マーカー遺伝子の可視化
markers.to.plot <- c("GeneA", "GeneB", "GeneC")
DotPlot(combined, features = markers.to.plot, cols = c("blue", "red"), dot.scale = 8, split.by = "orig.ident") + RotatedAxis()

# Find Differentially Expressed Genes:
fold_change_threshold <- 0.25
pct_threshold <- 0.25
p_value_threshold <- 0.01

# クラスタごとのDEG
all_markers <- FindAllMarkers(
  object = combined,
  only.pos = FALSE,
  min.pct = pct_threshold,
  logfc.threshold = fold_change_threshold,
  test.use = "wilcox"
)

# p値でフィルタリング
significant_markers <- all_markers[all_markers$p_val_adj < p_value_threshold, ]

# 結果をCSVファイルとして保存
write.csv(significant_markers, file = "Differentially_Expressed_Genes.csv", row.names = FALSE)

# 上位10個のDEGを可視化
top10 <- significant_markers %>% group_by(cluster) %>% top_n(n = 10, wt = avg_log2FC)

pdf(file = "Top10_Differentially_Expressed_Genes.pdf", width = 8.27, height = 11.69)
DoHeatmap(combined, features = top10$gene) + NoLegend()
dev.off()

scRNA-seqやってみる(その2)

ちょっとごちゃごちゃしてきたので一旦整理すると

#Seuartパッケージのインストール
install.packages('Seurat')
library(Seurat)

#テストファイルの準備とdirectoryの移動
setwd("~/scrnaseqtest/immune_alignment_matrix/")

#data読み込み
ctrl.data <- read.table("immune_control_expression_matrix.txt", sep = '\t', row.names = 1)
stim.data <- read.table("immune_stimulated_expression_matrix.txt",  sep = "\t", row.names = 1)

#Seuratオブジェクトの作成
ctrl <- CreateSeuratObject(counts = ctrl.data, project = "IMMUNE_CTRL", min.cells = 5)
stim <- CreateSeuratObject(counts = stim.data, project = "IMMUNE_STIM", min.cells = 5)

#metadataの付与
ctrl@meta.data$ctrl <- "CTRL"
stim@meta.data$stim <- "STIM"

#フィルタリング
ctrl <- subset(ctrl, subset = nFeature_RNA > 500)
stim <- subset(stim, subset = nFeature_RNA > 500)

#正規化
ctrl <- NormalizeData(ctrl, scale.factor = 10000)
stim <- NormalizeData(stim, scale.factor = 10000)

#Z-Scoreの算出
ctrl <- ScaleData(ctrl)
stim <- ScaleData(stim)

#使用する遺伝子の選定
ctrl <- FindVariableFeatures(ctrl)
stim <- FindVariableFeatures(stim)

# 上位1000の変動遺伝子を取得
g.1 <- head(VariableFeatures(ctrl), 1000)
g.2 <- head(VariableFeatures(stim), 1000)

#g1、g2で共通して発現変動の大きい遺伝子をunique関数で抽出
genes.use <- unique(c(g.1, g.2))
scaled_genes <- rownames(GetAssayData(ctrl, slot = "scale.data"))
genes.use <- intersect(genes.use, scaled_genes)
scaled_genes <- rownames(GetAssayData(stim, slot = "scale.data"))
genes.use <- intersect(genes.use, scaled_genes)

とりあえずここまでは良さげ。

データを可視化していく。

CCA(Canonical Correlation Analysis)によるオブジェクトのマージ

# データセットをリストにまとめる
immune.list <- list(ctrl = ctrl, stim = stim)

# アンカーを見つける
anchors <- FindIntegrationAnchors(object.list = immune.list, anchor.features = genes.use)

# データセットの統合
immune.combined <- IntegrateData(anchorset = anchors, dims = 1:30)

# 統合データのスケーリングと主成分分析
immune.combined <- ScaleData(immune.combined)
immune.combined <- RunPCA(immune.combined)

PCAのプロットを出してみる

# 次元削減結果のプロット
p1 <- DimPlot(object = immune.combined, reduction = "pca", group.by = "stim", pt.size = 0.5)
p1

PC_1をy軸に取ったViolin plot

p2 <- VlnPlot(object = immune.combined, features = "PC_1", group.by = "stim")
p2

各次元の重要な遺伝子のヒートマップを表示

DimHeatmap(object = immune.combined, dims = 1:2, cells = 500, balanced = TRUE)

とりあえずできた気になっているが、自分のデータで意味のある解析ができるかというとかなり怪しい。

scRNA-seqやってみる(その1)

とりあえずSeuratというRのパッケージで解析すると良いらしい。
ちなみに10xのCell Rangerでマッピングまではやっている前提で。


こちらの情報を参考にやってみることにする。ちょっと古いけど大丈夫かな?
Seuratを駆使する会 ① - ばいばいバイオ


Rはほんとうに敬遠していて何もわからないのだけど。
まずはパッケージをインストールから
わからないなりに環境だけいっちょ前にLinux上に構築してRStudioサーバを建ててあり、ネット越しにブラウザでアクセスして解析ができるようにしていたりする。なにもわからないのに。
Rのバージョンは4.4.0である。

> install.packages('Seuart')

とするといつ終わるかもわからないくらいずらずらとメッセージが流れる。
幸い、致命的エラーは出ずにインストールできたらしい。

> library(Seuart)

ライブラリインポートも問題なくできたっぽい。
さて、チュートリアルを一通りやっておこう。
ConsoleタブからTerminalタブに切り替えて、

$ mkdir scrnaseqtest
(ngs) kuro@guilty6-x2110:~$ cd scrnaseqtest/
(ngs) kuro@guilty6-x2110:~/scrnaseqtest$ mkdir immune_alignment_matrix
(ngs) kuro@guilty6-x2110:~/scrnaseqtest$ cd immune_alignment_matrix/
(ngs) kuro@guilty6-x2110:~/scrnaseqtest/immune_alignment_matrix$ wget https://www.dropbox.com/s/79q6dttg8yl20zg/immune_alignment_expression_matrices.zip

`immune_alignment_expression_matrices.zip' へ保存完了 [21329741/21329741]

(ngs) kuro@guilty6-x2110:~/scrnaseqtest/immune_alignment_matrix$ unzip immune_alignment_expression_matrices.zip
Archive:  immune_alignment_expression_matrices.zip
  inflating: immune_control_expression_matrix.txt.gz  
   creating: __MACOSX/
  inflating: __MACOSX/._immune_control_expression_matrix.txt.gz  
  inflating: immune_stimulated_expression_matrix.txt.gz  
  inflating: __MACOSX/._immune_stimulated_expression_matrix.txt.gz  
(ngs) kuro@guilty6-x2110:~/scrnaseqtest/immune_alignment_matrix$ rm immune_alignment_expression_matrices.zip 
(ngs) kuro@guilty6-x2110:~/scrnaseqtest/immune_alignment_matrix$ gunzip immune_control_expression_matrix.txt.gz 
(ngs) kuro@guilty6-x2110:~/scrnaseqtest/immune_alignment_matrix$ gunzip immune_stimulated_expression_matrix.txt.gz 

と、これで下準備OK

さてConsoleに戻って

> ctrl.data <- read.table("immune_control_expression_matrix.txt", sep = '\t', row.names = 1)
file(file, "rt") でエラー: コネクションを開くことができません
追加情報: 警告メッセージ:
file(file, "rt") で:
  ファイル 'immune_control_expression_matrix.txt' を開くことができません: そのようなファイルやディレクトリはありません

ああ、そうかディレクトリを移動しないといけないのか。どうやって?(このレベルです)

> setwd("~/scrnaseqtest/immune_alignment_matrix/")
> ctrl.data <- read.table("immune_control_expression_matrix.txt", sep = '\t', row.names = 1)
> stim.data <- read.table("immune_stimulated_expression_matrix.txt",  sep = "\t", row.names = 1)
> ctrl <- CreateSeuratObject(raw.data = ctrl.data, project = "IMMUNE_CTRL", min.cells = 5)
CreateSeuratObject(raw.data = ctrl.data, project = "IMMUNE_CTRL",  でエラー: 
  引数 "counts" がありませんし、省略時既定値もありません

またひっかかる。どうもバージョン3のSeuratでなにか変わっているらしい。なのでチャットGPTの出番。

エラーの原因は、CreateSeuratObject 関数の引数の名前が変更されたためです。Seuratのバージョン3以降では、raw.data 引数が counts 引数に変更されました。

> ctrl <- CreateSeuratObject(counts = ctrl.data, project = "IMMUNE_CTRL", min.cells = 5)
警告: Feature names cannot have underscores ('_'), replacing with dashes ('-')
警告: Data is of class data.frame. Coercing to dgCMatrix.

なんか警告は出たけどいいらしい。
続いてmetadataの付与

> ctrl@meta.data$stim <- "CTRL"
> head(ctrl@meta.data$stim)
[1] "CTRL" "CTRL" "CTRL" "CTRL" "CTRL" "CTRL"

フィルタリング

> ctrl <- FilterCells(ctrl, subset.names = "nGene", low.thresholds = 500, high.thresholds = Inf)
FilterCells(ctrl, subset.names = "nGene", low.thresholds = 500,  でエラー: 
  関数 "FilterCells" を見つけることができませんでした

chatGPT

FilterCells 関数は、Seuratのバージョン3以降では廃止されました。代わりに、subset 関数を使用します。

らしい。

> ctrl <- subset(ctrl, subset = nFeature_RNA > 500)

正規化

> ctrl <- NormalizeData(ctrl, scale.factor = 10000)
Normalizing layer: counts
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|

z-scoreの算出

> ctrl <- ScaleData(ctrl)
Centering and scaling data matrix
  |=================================================================================================================| 100%

同様にstimulatedのほうも

> stim <- CreateSeuratObject(counts = stim.data, project = "IMMUNE_STIM", min.cells = 5)
警告: Feature names cannot have underscores ('_'), replacing with dashes ('-')
警告: Data is of class data.frame. Coercing to dgCMatrix.
> stim@meta.data$stim <- "STIM"
> stim <- subset(stim, subset = nFeature_RNA > 500)
> stim <- NormalizeData(stim)
Normalizing layer: counts
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
> stim <- ScaleData(stim)
Centering and scaling data matrix
  |=================================================================================================================| 100%

分散の大きい(発現変動の大きい)遺伝子を抽出

> ctrl <- FindVariableGenes(ctrl)
FindVariableGenes(ctrl) でエラー: 
  関数 "FindVariableGenes" を見つけることができませんでした
> ctrl <- FindVariableFeatures(ctrl)
Finding variable features for layer counts
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|

FindVariableGenes 関数も、Seuratのバージョン3以降では廃止されました。代わりに FindVariableFeatures 関数を使用します。

> g.1 <- head(rownames(ctrl@hvg.info), 1000)
dimnames(x)[[1L]] %||% if (do.NULL) NULL else { でエラー: 
  名前 "hvg.info" というスロットが、クラス "Seurat" のこのオブジェクトには存在しません
> # 上位1000の変動遺伝子を取得
> g.1 <- head(VariableFeatures(ctrl), 1000)
> g.2 <- head(VariableFeatures(stim), 1000)

hvg.info スロットも Seurat のバージョン3以降では変更されています。変動遺伝子の情報は VariableFeatures スロットに格納されています。

g1、g2で共通して発現変動の大きい遺伝子をunique関数で抽出

> genes.use <- unique(c(g.1, g.2))
> genes.use <- intersect(genes.use, rownames(ctrl@scale.data))
dimnames(x)[[1L]] %||% if (do.NULL) NULL else { でエラー: 
  名前 "scale.data" というスロットが、クラス "Seurat" のこのオブジェクトには存在しません

scale.data スロットはSeuratのバージョン3以降では存在しません。代わりに ScaleData 関数を使用してデータをスケーリングし、ctrl"RNA"@scale.data からスケールされたデータにアクセスできます。
だそうで

> genes.use <- intersect(genes.use, rownames(ctrl[["RNA"]]@scale.data))
dimnames(x)[[1L]] %||% if (do.NULL) NULL else { でエラー: 
  名前 "scale.data" というスロットが、クラス "Assay5" のこのオブジェクトには存在しません

まだだめ。
scale.data スロットが存在しないというエラーは、Seuratのバージョンによる変更の結果です。現在のSeuratバージョンでは、スケールされたデータは scale.data スロットではなく、ScaleData 関数が返すオブジェクト内に格納されます。

> scaled_genes <- rownames(ctrl[["RNA"]]@data)
dimnames(x)[[1L]] %||% if (do.NULL) NULL else { でエラー: 
  名前 "data" というスロットが、クラス "Assay5" のこのオブジェクトには存在しません

Seuratの最新バージョンでは、スケールされたデータは ScaleData 関数の出力として直接取得できないため、正しいスロットにアクセスする必要があります。スケーリングされたデータは通常、ctrl"RNA"@scale.data ではなく、ctrl"RNA"@scale.data に格納されます。

しかし、最新のバージョンではスケールデータが標準的なアクセス方法ではなく、GetAssayData 関数を使う必要があります。

> scaled_genes <- rownames(GetAssayData(ctrl, slot = "scale.data"))
警告メッセージ:
The `slot` argument of `GetAssayData()` is deprecated as of SeuratObject 5.0.0.
ℹ Please use the `layer` argument instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated. 
> genes.use <- intersect(genes.use, scaled_genes)


なんかあやしくなってきたぞ?
一旦停止して整理する。

MAiX II DOCKをいじってみる

どうもこれに入っているLinuxはTina-LinuxといってOpenWRTに由来する組み込みLinuxらしいとの情報を得た。
Allwinner Tina Linux · GitHub
https://d1.docs.aw-ol.com/en/study/study_1tina/

OpenWRT懐かしい。2008年頃、FONという無線LANをみんなでシェアしようと言う趣旨のプロジェクトがあって、そこで安価で配布された無線LANルータのFoneraという装置があった。このFoneraの基板には意味ありげなピンヘッダが立っていて、それがシリアルコンソールであることに気がついた人たちのあいだで、そのコンソールからファームウェアを流し込んでもとの無線LANルータ機能を上書きしてしまうハックが流行った。そこで使われていたのがOpenWRTであった。基本無線LANルータとしてのファームウェアなんだけど、実態としては組み込みLinuxだということで、無線LANルータとしてではなく、安価なLinuxboxとして使ってしまおうというプロジェクトも派生していった。基板上のパターンを解析してSDカードを乗せるSPI接続を実現したり、シリアル通信でArduinoと接続してIOを制御したり、まさに現在、ラズベリーパイがやっているようなことを先駆けてやっていたのよね。当然GUIが動くほどの能力がルータのSOCにあるはずもなく、すべてCUI。多くのコマンドが入っているBusyBoxを駆使しまくってた。WebサーバとかSDカードを使ったファイル共有サーバとか温湿度計を繋いでエアコンリモート制御とか。

閑話休題

で、MAiX ll DOCKですよ。

BusyBox v1.27.2 () built-in shell (ash)

------run profile file-----
 _   .-')      ('-.            ) (`-.
( '.( OO )_   ( OO ).-.         ( OO ).
 ,--.   ,--.) / . --. /  ,-.-')(_/.  \_)-.
 |   `.'   |  | \-.  \   |  |OO)\  `.'  /
 |         |.-'-'  |  |  |  |  \ \     /\
 |  |'.'|  | \| |_.'  |  |  |(_/  \   \ |
 |  |   |  |  |  .-.  | ,|  |_.' .'    \_)
 |  |   |  |  |  | |  |(_|  |   /  .'.  \
 `--'   `--'  `--' `--'  `--'  '--'   '--'
   __   _
  / /  (_)__  __ ____ __ ------------------------
 / /__/ / _ \/ // /\ \ /  sipeed.com (Neptune)
/____/_/_//_/\_,_//_\_\  ------------------------

ほらBusyBoxじゃん。
ということでかつてのお約束のようにシリアルコンソール接続でログインを試みる。
その前にネットワークにつなぐ設定をしておく。SDカードをカードリーダーで覗くとトップディレクトリにWIFIの設定ファイル wpa_supplicant.conf があるので、SSIDとパスワードを入力しておく。
(追記)技適取れてないので無線接続はしてはいけません。日本語の情報が極端に少ないのはこのせいか。

USBでMacとつなぐと/dev/cu.usbserial-14330が現れるので

cu -l /dev/cu.usbserial-14330 -s 115200

としてやってMAiXをリセットすると

Last login: Fri May 17 13:58:12 on ttys002

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
------run rc.modules file-----
------run rc.final file-----
Load mpp modules
insmod: can't insert '/lib/modules/4.9.118/videobuf2-core.ko': No such file or directory
insmod: can't insert '/lib/modules/4.9.118/videobuf2-memops.ko': No such file or directory
insmod: can't insert '/lib/modules/4.9.118/videobuf2-v4l2.ko': No such file or directory
load /etc/asound.conf ...
alsactl: set_control:1461: Cannot write control '2:0:0:codec trigger playback time value:0' : Operation not permitted
alsactl: set_control:1461: Cannot write control '2:0:0:codec trigger capture time value:0' : Operation not permitted
exist /root/wpa_supplicant.conf
Starting app...
enable android usb
Initializing random number generator... done.
Starting network...
Start dropbear: OK
Starting ntpd: done


BusyBox v1.27.2 () built-in shell (ash)

------run profile file-----
 _   .-')      ('-.            ) (`-.
( '.( OO )_   ( OO ).-.         ( OO ).
 ,--.   ,--.) / . --. /  ,-.-')(_/.  \_)-.
 |   `.'   |  | \-.  \   |  |OO)\  `.'  /
 |         |.-'-'  |  |  |  |  \ \     /\
 |  |'.'|  | \| |_.'  |  |  |(_/  \   \ |
 |  |   |  |  |  .-.  | ,|  |_.' .'    \_)
 |  |   |  |  |  | |  |(_|  |   /  .'.  \
 `--'   `--'  `--' `--'  `--'  '--'   '--'
   __   _
  / /  (_)__  __ ____ __ ------------------------
 / /__/ / _ \/ // /\ \ /  sipeed.com (Neptune)
/____/_/_//_/\_,_//_\_\  ------------------------

root@sipeed:/# WARNING: Logging before InitGoogleLogging() is written to STDERR
I0522 03:02:09.853097   795 dup2SeldomUsedFd.c:20]          <dup2SeldomUsedFdInit> gFdLock init
Successfully initialized wpa_supplicant
udhcpc: started, v1.27.2
udhcpc: sending discover
I0522 03:02:10.799209   795 mpi_sys.c:766]                  <AW_MPI_SYS_SetConf> kfctmpdir is [/tmp]
I0522 03:02:10.800760   795 mpi_sys.c:1195]                 <AW_MPI_SYS_Init> ISP init
I0522 03:02:10.800925   795 mpi_sys.c:1197]                 <AW_MPI_SYS_Init> ISP init done
I0522 03:02:10.807874   795 hwdisplay.c:83]                 <hw_display_init> <(hwd_init 989)d_init:989> 95 hwdisplay.c:989] 
I0522 03:02:10.808255   795 hwdisplay.c:1044]               <hwd_init> <hwd_iniret[0][2,0]ch[2]lyl[0] init: enable[1], screenwin[0,0, 240x240], zorder[16], alpha[mode:0, value:255]
I0522 03:02:10.808458   795 alsa_interface.c:659]           <alsaOpenMixer> open mixer:hw:0
I0522 03:02:10.848054   795 alsa_interface.c:721]           <alsaOpenMixer> set player pa switch level 0
I0522 03:02:10.848312   795 alsa_interface.c:709]           <alsaOpenMixer> set playback vol_val to value: 27
E0522 03:02:10.969903   795 video_buffer_manager.c:211]     <VideoBufMgrCreate> Alloc 20 input frame buffers in list manager.
E0522 03:02:10.970302   795 VideoVirVi_Component.c:481]     <VideoViSetViDevAttr> fps 20 nbufs 3
E0522 03:02:11.001482   795 video_buffer_manager.c:211]     <VideoBufMgrCreate> Alloc 20 input frame buffers in list manager.
E0522 03:02:11.001873   795 VideoVirVi_Component.c:481]     <VideoViSetViDevAttr> fps 20 nbufs 3
I0522 03:02:11.002887   795 hwdisplay.c:1244]               <hwd_get_disp_type>Current the  disp_type:0x1  tv_mode:0x0
I0522 03:02:11.003194   795 hwdisplay.c:1117]               <hwd_layer_request_hlay:0, zorder=0, cnt:2t_hlay:1117> 
I0522 03:02:11.003396   795 mpi_vo.c:1030]                  <AW_MPI_VO_SetVideoLayerAttr> ch[0]lyl[0]:dispRect changed, [0, 0, 320x240]->[0, 0, 240x240]
I0522 03:02:11.003574   795 hwdisplay.c:408]                <hwd_layer_set_rectch[0]lyl[0]: screen_win[0,0, 240x240]
E0522 03:02:11.004062   795 vo.c:683]                       <vo_init> debuf create vo channel[0] success!
I0522 03:02:11.004436   896 Clock_Component.c:1109]         <Clock_ComponentThread> ClockComp state[0x1]->Idle!
E0522 03:02:11.004617   795 vo.c:718]                       <vo_init> debuf create clock channel[0] success!
I0522 03:02:11.004777   896 cedarx_avs_counter.c:148]       <avscounter_start> (f:avscounter_start, l:148) Avscounter status [pause]->[run], pauseDuration[0][0]ms
I0522 03:02:11.004956   795 hwdisplay.c:1117]               <hwd_layer_request_hlay:9, zorder=9, cnt:3t_hlay:1117> 
I0522 03:02:11.005098   795 mpi_vo.c:1030]                  <AW_MPI_VO_SetVideoLayerAttr> ch[2]lyl[1]:dispRect changed, [0, 0, 320x240]->[0, 0, 240x240]
I0522 03:02:11.005194   795 hwdisplay.c:408]                <hwd_layer_set_rectch[2]lyl[1]: screen_win[0,0, 240x240]
I0522 03:02:11.005291   795 mpi_vo.c:1130]                  <AW_MPI_VO_SetVideoLayerAlpha> video layer alpha changed, [0, 128]->[0, 25]
E0522 03:02:11.006791   795 vo.c:454]                       <CreateVoUiLayer> create vo channel[0] success!
udhcpc: sending discover
before paHostApiInitializers[0].
ALSA version (build): 1.1.4.1
ALSA version (runtime): 1.1.4.1
BuildDeviceList: Ignoring ALSA plugin device [cards] of type [unknown]
BuildDeviceList: Found plugin [default] of type [unknown]
BuildDeviceList: Found plugin [sysdefault] of type [unknown]
BuildDeviceList: Found plugin [front] of type [unknown]
BuildDeviceList: Found plugin [rear] of type [unknown]
BuildDeviceList: Found plugin [center_lfe] of type [unknown]
BuildDeviceList: Found plugin [side] of type [unknown]
BuildDeviceList: Found plugin [surround21] of type [unknown]
BuildDeviceList: Found plugin [surround40] of type [unknown]
BuildDeviceList: Found plugin [surround41] of type [unknown]
BuildDeviceList: Found plugin [surround50] of type [unknown]
BuildDeviceList: Found plugin [surround51] of type [unknown]
BuildDeviceList: Found plugin [surround71] of type [unknown]
BuildDeviceList: Found plugin [iec958] of type [unknown]
BuildDeviceList: Found plugin [spdif] of type [unknown]
BuildDeviceList: Found plugin [hdmi] of type [unknown]
BuildDeviceList: Found plugin [dmix] of type [unknown]
BuildDeviceList: Ignoring ALSA plugin device [dsnoop] of type [unknown]
BuildDeviceList: Found plugin [modem] of type [unknown]
BuildDeviceList: Found plugin [phoneline] of type [unknown]
BuildDeviceList: Ignoring ALSA plugin device [hw] of type [hw]
BuildDeviceList: Ignoring ALSA plugin device [plughw] of type [plug]
BuildDeviceList: Ignoring ALSA plugin device [plug] of type [plug]
BuildDeviceList: Ignoring ALSA plugin device [shm] of type [shm]
BuildDeviceList: Ignoring ALSA plugin device [tee] of type [file]
BuildDeviceList: Ignoring ALSA plugin device [file] of type [file]
BuildDeviceList: Ignoring ALSA plugin device [null] of type [null]
BuildDeviceList: Filling device info for 19 devices
FillInDevInfo: Filling device info for: sun8iw19-codec: - (hw:0,0)
GropeDevice: collecting info ..
GropeDevice: collecting info ..
Default input device: sun8iw19-codec: - (hw:0,0)
Default output device: sun8iw19-codec: - (hw:0,0)
FillInDevInfo: Adding device sun8iw19-codec: - (hw:0,0): 0
FillInDevInfo: Filling device info for: sysdefault
GropeDevice: collecting info ..
GropeDevice: Limiting number of plugin channels to 128
GropeDevice: collecting info ..
GropeDevice: Limiting number of plugin channels to 128
FillInDevInfo: Adding device sysdefault: 1
FillInDevInfo: Filling device info for: front
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.front
OpenPcm: Opened device 'front' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: front, all channels == 0
FillInDevInfo: Filling device info for: rear
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
OpenPcm: Opened device 'rear' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: rear, all channels == 0
FillInDevInfo: Filling device info for: center_lfe
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
OpenPcm: Opened device 'center_lfe' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: center_lfe, all channels == 0
FillInDevInfo: Filling device info for: side
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
OpenPcm: Opened device 'side' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: side, all channels == 0
FillInDevInfo: Filling device info for: surround21
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround21
OpenPcm: Opened device 'surround21' ptr[0] - result: [-2:No such file or directory]
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround21
OpenPcm: Opened device 'surround21' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: surround21, all channels == 0
FillInDevInfo: Filling device info for: surround40
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround40
OpenPcm: Opened device 'surround40' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: surround40, all channels == 0
FillInDevInfo: Filling device info for: surround41
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround41
OpenPcm: Opened device 'surround41' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: surround41, all channels == 0
FillInDevInfo: Filling device info for: surround50
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround50
OpenPcm: Opened device 'surround50' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: surround50, all channels == 0
FillInDevInfo: Filling device info for: surround51
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround51
OpenPcm: Opened device 'surround51' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: surround51, all channels == 0
FillInDevInfo: Filling device info for: surround71
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround71
OpenPcm: Opened device 'surround71' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: surround71, all channels == 0
FillInDevInfo: Filling device info for: iec958
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
OpenPcm: Opened device 'iec958' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: iec958, all channels == 0
FillInDevInfo: Filling device info for: spdif
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
OpenPcm: Opened device 'spdif' ptr[0] - result: [-2:No such file or directory]
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
OpenPcm: Opened device 'spdif' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: spdif, all channels == 0
FillInDevInfo: Filling device info for: hdmi
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
OpenPcm: Opened device 'hdmi' ptr[0] - result: [-2:No such file or directory]
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
OpenPcm: Opened device 'hdmi' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: hdmi, all channels == 0
FillInDevInfo: Filling device info for: modem
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
OpenPcm: Opened device 'modem' ptr[0] - result: [-2:No such file or directory]
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
OpenPcm: Opened device 'modem' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: modem, all channels == 0
FillInDevInfo: Filling device info for: phoneline
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
OpenPcm: Opened device 'phoneline' ptr[0] - result: [-2:No such file or directory]
ALSA lib pcm.c:2501:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
OpenPcm: Opened device 'phoneline' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: phoneline, all channels == 0
FillInDevInfo: Filling device info for: default
GropeDevice: collecting info ..
GropeDevice: Limiting number of plugin channels to 128
GropeDevice: collecting info ..
GropeDevice: Limiting number of plugin channels to 128
Default input device: default
Default output device: default
FillInDevInfo: Adding device default: 2
FillInDevInfo: Filling device info for: dmix
ALSA lib pcm_direct.c:1421:(snd1_pcm_direct_initialize_poll_fd) unable to open timer 'hw:CLASS=3,SCLASS=0,CARD=0,DEV=0,SUBDEV=0'
ALSA lib pcm_dmix.c:1183:(snd_pcm_dmix_open) unable to initialize poll_fd
OpenPcm: Opened device 'dmix' ptr[0] - result: [-2:No such file or directory]
FillInDevInfo: Skipped device: dmix, all channels == 0
BuildDeviceList: Building device list took 0.692696 seconds
after paHostApiInitializers[0].
before paHostApiInitializers[1].
PaOSS BuildDeviceList: Total number of devices found: 0
after paHostApiInitializers[1].
PaAlsaStreamComponent_Initialize: Host Chans P 2
AlsaOpen: Opening device default
PaAlsaStreamComponent_InitialConfigure: device MMAP SND_PCM_ACCESS_MMAP_INTERLEAVED: YES
PaAlsaStreamComponent_InitialConfigure: device MMAP SND_PCM_ACCESS_MMAP_NONINTERLEAVED: YES
PaAlsaStreamComponent_InitialConfigure: device can MMAP: YES
PaAlsaStreamComponent_DetermineFramesPerBuffer: user-buffer (frames)           = 1024
PaAlsaStreamComponent_DetermineFramesPerBuffer: user-buffer (sec)              = 0.046440
PaAlsaStreamComponent_DetermineFramesPerBuffer: suggested latency (sec)        = 0.008707
PaAlsaStreamComponent_DetermineFramesPerBuffer: suggested host buffer (frames) = 2048
PaAlsaStreamComponent_DetermineFramesPerBuffer: suggested host buffer (sec)    = 0.092880
PaAlsaStreamComponent_DetermineFramesPerBuffer: periods min = 2, max = 512, req = 4 
PaAlsaStreamComponent_DetermineFramesPerBuffer: suggested host buffer period   = 1024 
PaAlsaStreamComponent_DetermineFramesPerBuffer: device period minimum          = 64
PaAlsaStreamComponent_DetermineFramesPerBuffer: device period maximum          = 16384
PaAlsaStreamComponent_DetermineFramesPerBuffer: host buffer period             = 1024
PaAlsaStreamComponent_DetermineFramesPerBuffer: host buffer period latency     = 0.046440
PaAlsaStream_Configure: Playback period size: 1024, latency: 0.046440
OpenStream: Stream: framesPerBuffer = 1024, maxFramesPerHostBuffer = 1024, latency i=0.000000, o=0.046440
I0522 03:02:15.532507   891 VideoVirVi_Component.c:360]     <DoVideoViReturnAllValidFrames> release [1]validFrames
I0522 03:02:15.532899   891 VideoVirVi_Component.c:1077]    <Vi_ComponentThread> wait using frame return done
W0522 03:02:15.543506   890 videoInputHw.c:4128]            <VideoInputHw_CapThread> VIPP[0], No Virvi Component, drop this one yuv data.
E0522 03:02:15.548595   795 video_buffer_manager.c:211]     <VideoBufMgrCreate> Alloc 20 input frame buffers in list manager.
E0522 03:02:15.548939   795 VideoVirVi_Component.c:481]     <VideoViSetViDevAttr> fps 20 nbufs 3
I0522 03:02:16.274793   897 video_render_linux.cpp:224]     <vr4l_init> mDisplayFormat[0x200], new CedarXNativeRenderer
I0522 03:02:16.275141   897 CedarXNativeRenderer.cpp:174]   <CedarXNativeRenderer> hwc disp fmt[0x80], color space:260
I0522 03:02:16.275300   897 hwdisplay.c:239]                <hwd_layer_set_src>x: 0, y: 0, width: 0xf0, height: 0xf0
I0522 03:02:16.275398   897 hwdisplay.c:246]                <hwd_layer_set_src>width: 0xf000000000, height: 0xf000000000
I0522 03:02:16.275480   897 hwdisplay.c:366]                <hwd_layer_set_src>set fb.format 128 0, color_space 260 end, size0[240x240], size1[0x0]
E0522 03:02:16.275611   897 vo.c:281]                       <VoUiCallbackWrapper> debuf vo report video display size[240x240]
I0522 03:02:16.275724   897 VideoRender_Component.c:2316]   <VideoRender_ComponentThread> init video_render, param: displayRect[0,0][240x240], bufSize[240x240], vdecColorFormat[0xb]
I0522 03:02:16.275821   897 mpi_vo.c:508]                   <VideoRenderEventHandler> KeyFrameDecoded, pts[0]us
E0522 03:02:16.276000   897 vo.c:286]                       <VoUiCallbackWrapper> debuf vo report rendering start
udhcpc: sending discover
udhcpc: sending select for 10.0.1.201
udhcpc: sending select for 10.0.1.201
udhcpc: lease of 10.0.1.201 obtained, lease time 172800
udhcpc: ifconfig wlan0 10.0.1.201 netmask 255.255.255.0 broadcast +
udhcpc: setting default routers: 10.0.1.1

root@sipeed:/#  ls
bin          home         overlay      rom          squashfs     usr
data         lib          proc         root         swapfile     var
dev          lost+found   pseudo_init  run          sys
etc          mnt          rdinit       sbin         tmp

これは普通にLinux boxとして使えそうだ。エッジAIとして使うかどうかはさておき。

追記
とりあえずできること
viでテキスト編集

できないこと
すでに入っている以外のアプリケーションのopkgでのインストール

とりあえずファイルの転送手段としてネットワーク越しは一旦おいておくことにしてまずUSBでの転送を考える。
このデバイスにはUSB-Cが2つついていて、1つを電源&シリアルコンソールとして使っている。もう1つのUSBはOTGということだが、そのままではPCからUSBデバイスとして認識されるモードとなっているため、これをUSBホストに変えてやる。

echo "usb_host" > /sys/devices/platform/soc/usbc0/otg_role

これでOK。
ひとまずSDカードをリーダに入れて接続してみる。
exFATはだめっぽい
FAT32なら

mount -t vfat /dev/sda1 ./media

であらかじめ作っておいたマウントポイント./mediaにマウントすることができた。
これで、とりあえずMacpythonコードを書いてMAiX IIに移して実行。ということは可能になった。アプリのインストールもできるかな。

しかし本格的に活用するなら、やはり無線LANに繋がないとどうにもならないだろうな。
一応無線LANチップ自体は技適が取れているようなので、
総務省 電波利用ホームページ|その他|技適未取得機器を用いた実験等の特例制度
こちらで申請すれば短期間なら試験できるようだけど、180日経ったら廃止申請を出すとか面倒すぎるので、やっぱり使い物にはならない。せめてUSB無線子機を繋いで使えるようにできればいいのだが、それもなかなかハードルが高い。

MAiX II DOCK

Amazonでなんか面白いもの売ってないかなと思って彷徨っていたらこれを見つけた。

wiki.sipeed.com

えーっとこれが600円?

中身。
KIOXIAの32GB microSDだけで元値超えてるんじゃない?
Linuxの走るM2スタイルのCPUボードに64MB DDR2メモリ、2Mピクセルのカメラ、SDカードスロット、1.3インチIPSモニタ、USB-OTG、無線LAN、3軸ジャイロ、マイク、スピーカーまでついている。

とりあえずそのままUSBをMacと繋いでみたが、何も起こらない。一応USBーシリアルの変換の応答はある。SDカードにOS入っているのかな?ということで挿入し、改めてUSB接続。
キタキタ。

��3����	-'�-�m�����)��Ӵ��)R��+��------run rc.preboot file-----
[?25lsetup console
[H[Jfbv - The Framebuffer Viewer
/home/res/logo.png
140 x 140
[H[J[?25h------run rc.modules file-----
------run rc.final file-----
Load mpp modules
insmod: can't insert '/lib/modules/4.9.118/videobuf2-core.ko': No such file or directory
insmod: can't insert '/lib/modules/4.9.118/videobuf2-memops.ko': No such file or directory
insmod: can't insert '/lib/modules/4.9.118/videobuf2-v4l2.ko': No such file or directory
load /etc/asound.conf ...
alsactl: set_control:1461: Cannot write control '2:0:0:codec trigger playback time value:0' : Operation not permitted
alsactl: set_control:1461: Cannot write control '2:0:0:codec trigger capture time value:0' : Operation not permitted
Starting app...
enable android usb
Initializing random number generator... done.
Starting network...
generate key
WARNING: Logging before InitGoogleLogging() is written to STDERR
I0101 00:00:06.624842   803 dup2SeldomUsedFd.c:20] [60D[60C<dup2SeldomUsedFdInit> gFdLock init
Successfully initialized wpa_supplicant
udhcpc: started, v1.27.2
udhcpc: sending discover
I0101 00:00:07.530875   803 mpi_sys.c:766] [60D[60C<AW_MPI_SYS_SetConf> kfctmpdir is [/tmp]
I0101 00:00:07.532697   803 mpi_sys.c:1195] [60D[60C<AW_MPI_SYS_Init> ISP init
I0101 00:00:07.532854   803 mpi_sys.c:1197] [60D[60C<AW_MPI_SYS_Init> ISP init done
I0101 00:00:07.536176   803 hwdisplay.c:83] [60D[60C<hw_display_init> [1;34m<hw_display_init:83> [90D[90C[0m
I0101 00:00:07.536359   803 hwdisplay.c:989] [60D[60C<hwd_init> [1;34m<hwd_init:989> [90D[90C[0m(hwd_init 989)
I0101 00:00:07.536538   803 hwdisplay.c:1044] [60D[60C<hwd_init> [1;34m<hwd_init:1044> [90D[90C[0mret[0][2,0]ch[2]lyl[0] init: enable[1], screenwin[0,0, 240x240], zorder[16], alpha[mode:0, value:255]
I0101 00:00:07.536736   803 alsa_interface.c:659] [60D[60C<alsaOpenMixer> open mixer:hw:0
I0101 00:00:07.606276   803 alsa_interface.c:721] [60D[60C<alsaOpenMixer> set player pa switch level 0
I0101 00:00:07.606596   803 alsa_interface.c:709] [60D[60C<alsaOpenMixer> set playback vol_val to value: 27
E0101 00:00:07.744445   803 video_buffer_manager.c:211] [60D[60C<VideoBufMgrCreate> Alloc 20 input frame buffers in list manager.
E0101 00:00:07.745197   803 VideoVirVi_Component.c:481] [60D[60C<VideoViSetViDevAttr> fps 20 nbufs 3
E0101 00:00:07.796672   803 video_buffer_manager.c:211] [60D[60C<VideoBufMgrCreate> Alloc 20 input frame buffers in list manager.
E0101 00:00:07.797082   803 VideoVirVi_Component.c:481] [60D[60C<VideoViSetViDevAttr> fps 20 nbufs 3
I0101 00:00:07.804659   803 hwdisplay.c:1244] [60D[60C<hwd_get_disp_type> [1;32m<hwd_get_disp_type:1244> [90D[90C[0mCurrent the  disp_type:0x1  tv_mode:0x0
I0101 00:00:07.804977   803 hwdisplay.c:1117] [60D[60C<hwd_layer_request_hlay> [1;34m<hwd_layer_request_hlay:1117> [90D[90C[0mhlay:0, zorder=0, cnt:2
I0101 00:00:07.805199   803 mpi_vo.c:1030] [60D[60C<AW_MPI_VO_SetVideoLayerAttr> ch[0]lyl[0]:dispRect changed, [0, 0, 320x240]->[0, 0, 240x240]
I0101 00:00:07.805321   803 hwdisplay.c:408] [60D[60C<hwd_layer_set_rect> [1;34m<hwd_layer_set_rect:408> [90D[90C[0mch[0]lyl[0]: screen_win[0,0, 240x240]
E0101 00:00:07.805822   803 vo.c:683] [60D[60C<vo_init> debuf create vo channel[0] success!
I0101 00:00:07.806197   889 Clock_Component.c:1109] [60D[60C<Clock_ComponentThread> ClockComp state[0x1]->Idle!
E0101 00:00:07.806368   803 vo.c:718] [60D[60C<vo_init> debuf create clock channel[0] success!
I0101 00:00:07.806529   889 cedarx_avs_counter.c:148] [60D[60C<avscounter_start> (f:avscounter_start, l:148) Avscounter status [pause]->[run], pauseDuration[0][0]ms
I0101 00:00:07.806711   803 hwdisplay.c:1117] [60D[60C<hwd_layer_request_hlay> [1;34m<hwd_layer_request_hlay:1117> [90D[90C[0mhlay:9, zorder=9, cnt:3
I0101 00:00:07.806860   803 mpi_vo.c:1030] [60D[60C<AW_MPI_VO_SetVideoLayerAttr> ch[2]lyl[1]:dispRect changed, [0, 0, 320x240]->[0, 0, 240x240]
I0101 00:00:07.806959   803 hwdisplay.c:408] [60D[60C<hwd_layer_set_rect> [1;34m<hwd_layer_set_rect:408> [90D[90C[0mch[2]lyl[1]: screen_win[0,0, 240x240]
I0101 00:00:07.807058   803 mpi_vo.c:1130] [60D[60C<AW_MPI_VO_SetVideoLayerAlpha> video layer alpha changed, [0, 128]->[0, 25]
E0101 00:00:07.808746   803 vo.c:454] [60D[60C<CreateVoUiLayer> create vo channel[0] success!
udhcpc: sending discover

〜中略〜

I0101 00:00:16.854119   890 mpi_vo.c:508] [60D[60C<VideoRenderEventHandler> KeyFrameDecoded, pts[0]us
E0101 00:00:16.854372   890 vo.c:286] [60D[60C<VoUiCallbackWrapper> debuf vo report rendering start
done


BusyBox v1.27.2 () built-in shell (ash)

------run profile file-----
 _   .-')      ('-.            ) (`-.
( '.( OO )_   ( OO ).-.         ( OO ).
 ,--.   ,--.) / . --. /  ,-.-')(_/.  \_)-.
 |   `.'   |  | \-.  \   |  |OO)\  `.'  /
 |         |.-'-'  |  |  |  |  \ \     /\
 |  |'.'|  | \| |_.'  |  |  |(_/  \   \ |
 |  |   |  |  |  .-.  | ,|  |_.' .'    \_)
 |  |   |  |  |  | |  |(_|  |   /  .'.  \
 `--'   `--'  `--' `--'  `--'  '--'   '--'
   __   _
  / /  (_)__  __ ____ __ ------------------------
 / /__/ / _ \/ // /\ \ /  sipeed.com (Neptune)
/____/_/_//_/\_,_//_\_\  ------------------------

root@sipeed:/# PaUnixThread_New: Waited for 0.00035175 seconds for stream to start

〜後略〜

うんLinuxだね。

エッジAI?
どう使うかちょっと調べて遊んでみよう。
ちなみに在庫2個だったんで2個とも確保。いま見てみたら普通は9600円で売っているらしい。9を入力し損ねたんかも。

M5Stack ESP32CAM-PSRAM

さらに気をよくしてM5Stack ESP32CAM-PSRAMも試してみる。こちらは入手したもののArduinoで全然使い方が分からずに放置していたのだが。
まず、このボードはESP32-WROVER。メモリはPSRAM,FLASHとも4MBと半分しかない。カメラはOV2640なんだけど魚眼がついている。(このカメラが曲者だった。)

というわけでボードとしてはESP32 Wrover Kitを選択することになるのだが、camera_pin.hでどのカメラユニットとして設定すればいいのかがどこにも書かれていない。
結局
m5-docs
このページのピン情報をcamera_pin.hと1個ずつ見比べてやったところ、どうやらCAMERA_MODEL_M5STACK_V2_PSRAMかもしくはCAMERA_MODEL_M5STACK_WIDEということになりそうだLEDピンがないので前者の方が近いのかな。

で、これらの設定で他のカメラと基本同じプログラムを書き込んでみたのだが、
E (1118) camera: Camera probe failed with error 0x105(ESP_ERR_NOT_FOUND)
Camera init failed with error 0x105
という表示がシリアルに出て、全く認識してくれない。なのでほかのカメラ設定を念の為手当たり次第に試してみたのだがどれも同じ反応。
試しにカメラユニットをOV2640の標準画角のレンズのものに付け替えてみたら、なんかあっけなくCAMERA_MODEL_M5STACK_V2_PSRAMで認識して、普通に画像が撮れた。
つまりついていた広角レンズのカメラユニットが壊れているっぽい。以前逆にXIAOの方にこのカメラをつけてみたときには普通に写っていたのでどっかのタイミングで断線してしまったか何かなのだろう。というのもこのユニットはレンズ部分が不釣り合いにデカくて、フレキケーブルだけで支えていることに結構不安があったのだ。まさに不安的中。カバンに入れて持ち歩いているうちにどっか接触が悪くなってしまったに違いない。要注意だわ。
追記:よくよくみてみるとフレキケーブルが端から2ミリほど切れていた。こりゃあかんわな。ちなみにこのカメラユニットのレンズはいわゆるM12マウントなのでレンズは他に流用できそうだけど、ちぎれたフレキケーブルとCCDはもったいないけど捨てるしかないな。

ま、とりあえずカメラ(ESP基板)は使えることがわかったからよしとしよう。しかし、XIAOの方が普通に使えそう、ということがわかったので、こっちをわざわざ使う意味は完全になくなったわけだ。特に広角レンズが死んでしまった今となっては。

M5 Timer Camera X再び

XIAO ESP32S3 senseの運用がうまくいったのに気をよくして、M5 Timer Camera Xの方も上手く使えないかと思い、もう一度引っ張り出してきた。
そして以前作っていたファームウェアを書き込もうとしたらどういうわけかエラーが出てコンパイルができない。
以前使ってみた時と変わっているのはMacintelMacbook AirからM1のMacbook Proになっている点だけど、この環境構築がうまくいっていないんだろうかということで構築し直してone by oneで確認してみる。

まずM5 Timer Camera Xを使うためにArduino IDEのboard managerでM5のライブラリを入れる。
お作法に従って、
https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json


Preferenceにコピーしてやり、BOARDS MANAGERタブでM5と入れて検索し、出てくる”M5Stack by M5Stack”をインストールしてやる。

今回入ったバージョンは2.1.1であった。
次にTimer-CAMのライブラリをLIBRALY MANAGERタブでTimer-CAMで検索すると"Timer-CAM by M5Stack"が見つかる。

以前試してみていた時は最新バージョンが0.0.3だったけど最新バージョンのArduinoIDEでインストールできないというバグがあった。今回1.0.0とベータじゃなくなった感じのバージョンになっていて、インストールも問題がないようだった。

早速なんかプログラムを入れてみよう。
まずボードとしてM5Stack-Timer-CAMを選ぶ。

ここで気になったのだが、M5TimerCAMというハイフンの入っていない選択肢もあるということ。

こちらを選んでいいものかどうか、現時点では判断がつかない。M5Stackのオフィシャルページではそんなことは書かれていないのだが、これって以前にインストールしていた何かの残骸だったりするんだろうか。

とりあえずお約束のファイル>Examples>01.Basics>Blink

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);  // turn the LED on (HIGH is the voltage level)
  delay(1000);                      // wait for a second
  digitalWrite(LED_BUILTIN, LOW);   // turn the LED off by making the voltage LOW
  delay(1000);                      // wait for a second
}

このボードではLED_BUILTINはIO2とライブラリで定義されている。
無事LEDが1秒間隔で点滅した。とりあえずボードは認識できているね。
次にTimer-CAMライブラリで入ったexampleも試しておく。

#include "M5TimerCAM.h"

void setup() {
    TimerCAM.begin();
}

void loop() {
    for (int16_t i = 0; i < 255; i++) {
        TimerCAM.Power.setLed(i);
        vTaskDelay(pdMS_TO_TICKS(10));
    }

    for (int16_t i = 255; i >= 0; i--) {
        TimerCAM.Power.setLed(i);
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

#include "M5TimerCAM.h"
でライブラリを呼び出している。
こちらも特に問題なくLEDがpwmで明暗を繰り返している。
環境構築には問題なさそうだな。じゃあどうして以前作ったプログラムはコンパイルできないんだろうね。

どうも
# include battery.h
# include bm8563.h
が使えないらしい。どうなっているんだろうか。

M5TimerCAM.h

#ifndef _M5_TIMER_CAM_H_
#define _M5_TIMER_CAM_H_

#include "./utility/Power_Class.h"
#include "./utility/RTC8563_Class.h"
#include "./utility/Camera_Class.h"
#include "esp_camera.h"

namespace m5 {
class M5TimerCAM {
   private:
    /* data */
   public:
    void begin(bool enableRTC = false);
    Power_Class Power;
    RTC8563_Class Rtc;
    Camera_Class Camera;
};
}  // namespace m5
extern m5::M5TimerCAM TimerCAM;

#endif

なるほどbattery.h,bm8563.hともになくなって、Power_Class.h、RTC8563_Class.hに変わったというわけか。
とりあえずこの2つはincludeせず#include "M5TimerCAM.h"だけ入れてやればいけそう。

#include "M5TimerCAM.h"
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "Base64.h"

const char *ssid     = "your_SSID";
const char *password = "SSID_passward";

const char* myDomain = "script.google.com";
String myScript = "/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/exec"; //Replace with your own url
String myFilename = "filename=M5Camera.jpg";
String mimeType = "&mimetype=image/jpeg";
String myImage = "&data=";

int waitingTime = 10; //Wait 10 seconds to google response.

int shootingIntervalSec = 600;   // 撮影間隔 (秒)

void setup() {
  Serial.begin(115200);
  delay(10);

  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);

  WiFi.mode(WIFI_STA);

  Serial.println("");
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);  

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }

  Serial.println("");
  Serial.println("STAIP address: ");
  Serial.println(WiFi.localIP());

  Serial.println("");

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 10000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_UXGA;  // UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA
  config.jpeg_quality = 5;
  config.fb_count     = 1;
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; //CAMERA_GRAB_LATEST;

  esp_err_t err = esp_camera_init(&config);

  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    delay(1000);
    ESP.restart();
  }

  sensor_t * s = esp_camera_sensor_get();
  //initial sensors are flipped vertically and colors are a bit saturated
  s->set_vflip(s, 1);//flip it back
  s->set_brightness(s, -1);//up the blightness just a bit OV3660をOV2640に近づけるセッティング
  s->set_saturation(s, 2);//lower the saturation OV3660をOV2640に近づけるセッティング
  s->set_denoise(s, 1);//OV3660をOV2640に近づけるセッティング
  s->set_contrast(s, 1);//OV3660をOV2640に近づけるセッティング
  //drop down frame size for higher initial frame rate
  // s->set_framesize(s, FRAMESIZE_SXGA);
  delay(1000);
}

void loop() {
  saveCapturedImage();
  Serial.printf("Waiting for %u sec.\n", shootingIntervalSec);
//  delay(shootingIntervalSec * 1000);    // 次の撮影まで待つ。スリープしない時。  
  Serial.printf("esp_sleep_enable_timer_wakeup: %d\n", esp_sleep_enable_timer_wakeup((uint64_t)shootingIntervalSec * 1000ULL * 1000ULL)); //単位はマイクロ秒  スリープする時
  esp_deep_sleep_start();// スリープする時
}

void saveCapturedImage() {
  Serial.println("Connect to " + String(myDomain));
  WiFiClientSecure client;

  client.setInsecure();
  if (client.connect(myDomain, 443)) {
    Serial.println("Connection successful");

    camera_fb_t * fb = esp_camera_fb_get(); //最初の1枚は捨てる
    esp_camera_fb_return(fb); //最初の1枚は捨てる
    fb = esp_camera_fb_get();  //こちらを保存する
    if(!fb) {
      Serial.println("Camera capture failed");
      delay(1000);
      ESP.restart();
      return;
    }
    Serial.printf("frame buffer size: %u x %u\n", fb->width, fb->height);
    
    Serial.println("Step 1: calicurating data size...");    // Base64とurlencodeされたデータのサイズを数える。

    int index = 0;
    uint8_t *p = fb->buf;
    int rest = fb->len;
    int base64EncodedSize = 0;
    int urlencodedSize = 0;
    while (rest > 0)
    {
      char output[2048 +1];    // 一度に出力するBase64化されたデータを入れるバッファ (base64_encode()が末尾にヌルを入れるので、1バイト追加。)
      int srcLen = rest > 1536 ? 1536 : rest;   // このサイクルでエンコードする元データサイズ(最大はバッファの 3/4 のサイズ)
      int encLen = base64_encode(output, (char *)p + index, srcLen);   // Base64エンコードする。
      base64EncodedSize += encLen;
      if (encLen > 0) {
        String str = urlencode(String(output));   // URLエンコードする。
        urlencodedSize += str.length();
      }
      index += srcLen;
      rest -= srcLen;
    }
    Serial.printf("frame buffer size: %u\n", fb->len);
    Serial.printf("after Base64 encoding: %u\n", base64EncodedSize);
    Serial.printf("frame buffer size: %u\n", urlencodedSize);

    Serial.println("Step 2: Sending a captured image to Google Drive.");
    String Data = myFilename + mimeType + myImage;    // POSTで送られるデータの先頭部分。これの後に画像をBase64化したものが続く。
    client.println("POST " + myScript + " HTTP/1.1");
    client.println("Host: " + String(myDomain));
    client.println("Content-Length: " + String(Data.length() + urlencodedSize));    // ここでデータの長さを書く必要があるので、Step 1 が必要。
    client.println("Content-Type: application/x-www-form-urlencoded");
    client.println();
    client.print(Data);

    index = 0;
    p = fb->buf;
    rest = fb->len;
    Serial.printf("Estimated cycle: %u\n", rest / 1536);
    while (rest > 0 && client.connected())
    {
      char output[2048 +1];    // 一度に出力するBase64化されたデータを入れるバッファ (base64_encode()が末尾にヌルを入れるので、1バイト追加。)
      int srcLen = rest > 1536 ? 1536 : rest;   // このサイクルでエンコードする元データサイズ(最大はバッファの 3/4 のサイズ)
      int encLen = base64_encode(output, (char *)p + index, srcLen);    // Base64エンコードする。
      if (encLen > 0) {
        String str = urlencode(String(output));   // URLエンコードする。
        client.write((uint8_t *)(str.c_str()), str.length());   // データを送信する。
        index += srcLen;
        rest -= srcLen;
        Serial.print(".");
      }
    }
    Serial.println();
    client.flush();

    // char *input = (char *)fb->buf;
    // char output[base64_enc_len(3)];
    // String imageFile = "";
    // for (int i=0;i<fb->len;i++) {
    //   base64_encode(output, (input++), 3);
    //   if (i%3==0) imageFile += urlencode(String(output));
    // }
    // String Data = myFilename+mimeType+myImage;

    esp_camera_fb_return(fb);

    Serial.println("Send a captured image to Google Drive.");

    // client.println("POST " + myScript + " HTTP/1.1");
    // client.println("Host: " + String(myDomain));
    // client.println("Content-Length: " + String(Data.length()+imageFile.length()));
    // client.println("Content-Type: application/x-www-form-urlencoded");
    // client.println();

    // client.print(Data);
    // int Index;
    // for (Index = 0; Index < imageFile.length(); Index = Index+1000) {
    //   client.print(imageFile.substring(Index, Index+1000));
    // }

    Serial.println("Waiting for response.");
    long int StartTime=millis();
    while (!client.available()) {
      Serial.print(".");
      delay(100);
      if ((StartTime+waitingTime * 1000) < millis()) {
        Serial.println();
        Serial.println("No response.");
        //If you have no response, maybe need a greater value of waitingTime
        break;
      }
    }
    Serial.println();   
    while (client.available()) {
      Serial.print(char(client.read()));
    }  
  } else {         
    Serial.println("Connected to " + String(myDomain) + " failed.");
  }
  client.stop();
}

//https://github.com/zenmanenergy/ESP8266-Arduino-Examples/
String urlencode(String str)
{
    String encodedString="";
    char c;
    char code0;
    char code1;
    char code2;
    for (int i =0; i < str.length(); i++){
      c=str.charAt(i);
      if (c == ' '){
        encodedString+= '+';
      } else if (isalnum(c)){
        encodedString+=c;
      } else{
        code1=(c & 0xf)+'0';
        if ((c & 0xf) >9){
            code1=(c & 0xf) - 10 + 'A';
        }
        c=(c>>4)&0xf;
        code0=c+'0';
        if (c > 9){
            code0=c - 10 + 'A';
        }
        code2='\0';
        encodedString+='%';
        encodedString+=code0;
        encodedString+=code1;
        //encodedString+=code2;
      }
      yield();
    }
    return encodedString;
}

とりあえず超適当な画像だが取得してGoogleDriveに保存まで成功。

XIAO ESP32S3 senseがネットワークに繋がりにくい件



ESP32でwifi接続できてOV2640のカメラを搭載した小さい基板であるXIAO ESP32S3 senseをたくさん購入してタイムラプスを撮りまくるはずだったのだが、この基板、どういうわけか無線LANへの接続がうまくいかないことが多い。多いというのは合計8台買ってみたのだが、スッキリとつながるのは2台だけで、残りは全然繋がらなかったり、つながったり繋がらなかったり、ととても不安定だった。
ペラっとしたアンテナが付属しており、このアンテナをコネクタに接続しないと基板自体にはアンテナパターンがついていないため、まず電波をキャッチできそうにない。なので、このコネクタの接触が悪いのか、そもそも基板の品質がいまいちなのか判断できずお蔵入りしかけていたのだけれど、最近有力な情報を知った。
XIAO ESP32S3がWiFiに繋がらなかったので調べてみたら、ハードの問題である可能性が高いことがわかった - 知的好奇心 for IoT
こちらのブログエントリーによるとどうやら無線LANの出力をあえて下げてやる必要があるらしい。
そんなことよく気がついたな、と言う感じだ。普通繋がらなかったら電波が弱いからできるだけ上げようと思うだろうに。
ESP32のWiFiライブラリのうちのWiFiGeneric.hに

typedef enum {
    WIFI_POWER_19_5dBm = 78,// 19.5dBm
    WIFI_POWER_19dBm = 76,// 19dBm
    WIFI_POWER_18_5dBm = 74,// 18.5dBm
    WIFI_POWER_17dBm = 68,// 17dBm
    WIFI_POWER_15dBm = 60,// 15dBm
    WIFI_POWER_13dBm = 52,// 13dBm
    WIFI_POWER_11dBm = 44,// 11dBm
    WIFI_POWER_8_5dBm = 34,// 8.5dBm
    WIFI_POWER_7dBm = 28,// 7dBm
    WIFI_POWER_5dBm = 20,// 5dBm
    WIFI_POWER_2dBm = 8,// 2dBm
    WIFI_POWER_MINUS_1dBm = -4// -1dBm
} wifi_power_t;

このように定義されているらしいのだが、先のエントリーによると
tx power > 70以上にすると繋がらなくかるらしい。
で、デフォルトはmax powerになっているからtx power = 78 (19.5 dBm)

というわけで

void connectToWiFi() {
  // Wi-Fiに接続する
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  WiFi.setTxPower(WIFI_POWER_13dBm);
  // Wi-Fiに接続待ち
  Serial.println("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connected");
  Serial.print("STAIP address: ");
  Serial.println(WiFi.localIP());
  Serial.println("");
}

という感じに明示的にWIFI出力を13 dBmくらいに抑えてやるとなんということでしょう、8台全部普通につながるようになったとさ。

秋月電子で売っている小さいケース(タカチ PB3-2-3)にピッタリ。発泡ウレタンボードでレンズボードを作成してはめ込むととても安定する。

他にもこの方のブログは有益で、ESP32のカメラでよく起こる変な色かぶり(緑っぽかったり赤っぽかったり、起動して最初に撮影したフレームがだいたいおかしい)の原因と対策だとかOV3660がOV2640 よりハイスペックなはずなのにESP32につなぐとなんだかボンヤリした画像になる件の対策だったりが紹介されていた。とても役に立ったので拡散しておく。

Slurmでノードのstateがdrainedになってしまうとき

サブシステムをクラスタ化しようとしてちょっとハマった。
メインシステムはほぼ同じスペックで各ノード256GBメモリを積んでおり、特に問題なくSlurmがインストールできて

NODELIST       NODES PARTITION       STATE CPUS    S:C:T MEMORY TMP_DISK WEIGHT AVAIL_FE REASON              
node3-a6      1     work*        idle 48     1:24:2 256000        0      1   (null) none                
node4-a6      1     work*        idle 48     1:24:2 256000        0      1   (null) none                
node5-a6      1     work*        idle 48     1:24:2 256000        0      1   (null) none

こんな感じ。
ところが寄せ集めのサブシステムではなんだかうまくいかず、

NODELIST       NODES PARTITION       STATE CPUS    S:C:T MEMORY TMP_DISK WEIGHT AVAIL_FE REASON              
node6-x2110      1     work*     drained 8       1:4:2  32000        0      1   (null) Low RealMemory      
node7-r620        1     work*        idle 32      2:8:2  96000        0      1   (null) none                

このようにLowRealMemoryというエラーがついてstateがdrainedとなってしまう。
問題のノードの実メモリ

$ cat /proc/meminfo 
MemTotal:       32583320 kB
MemFree:        31144288 kB
MemAvailable:   31471072 kB
Buffers:            4664 kB
Cached:           686380 kB
SwapCached:            0 kB
Active:           177208 kB
Inactive:         918404 kB
Active(anon):       2884 kB
Inactive(anon):   425188 kB
Active(file):     174324 kB
Inactive(file):   493216 kB
Unevictable:       25072 kB
Mlocked:           21984 kB
SwapTotal:       8241148 kB
SwapFree:        8241148 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:        411780 kB
Mapped:           235504 kB
Shmem:             19008 kB
KReclaimable:      53572 kB
Slab:             124156 kB
SReclaimable:      53572 kB
SUnreclaim:        70584 kB
KernelStack:        7296 kB
PageTables:        23696 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:    24532808 kB
Committed_AS:    2863272 kB
VmallocTotal:   34359738367 kB
VmallocUsed:       84148 kB
VmallocChunk:          0 kB
Percpu:             5248 kB
HardwareCorrupted:     0 kB
AnonHugePages:    116736 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
FileHugePages:         0 kB
FilePmdMapped:         0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:      202340 kB
DirectMap2M:     6031360 kB
DirectMap1G:    27262976 kB

ということで32GBなので32000 (MB)として/etc/slurm/slurm.confで

# COMPUTE NODES
NodeName=node6-x2110 CPUs=8 Sockets=1 CoresPerSocket=4 ThreadsPerCore=2 RealMemory=32000 State=UNKNOWN
NodeName=node7-r620 CPUs=32 Sockets=2 CoresPerSocket=8 ThreadsPerCore=2 RealMemory=96000 State=UNKNOWN
PartitionName=work Nodes=ALL OverSubscribe=FORCE Default=YES MaxTime=INFINITE State=UP

というふうに設定したのだが。

slurmd -Cというコマンドでどういうふうに認識されているかを見る方法があるということなので

$ slurmd -C
NodeName=node6-x2110 CPUs=8 Boards=1 SocketsPerBoard=1 CoresPerSocket=4 ThreadsPerCore=2 RealMemory=31819
UpTime=0-00:53:14

おやおや?31819と認識されているよ?

ってことで実際に載せているメモリよりちょっと小さめに設定しておくのが吉らしい。
RealMemory=30000
としてやり、

$ sudo systemctl stop slurmctld
$ sudo systemctl stop slurmd
$ sudo systemctl start slurmd
$ sudo systemctl start slurmctld

さらに

$ sudo scontrol reconfigure
$ sudo scontrol update nodename=node6-x2110 state=resume

としてやってようやく

$ sinfo -N -l

NODELIST       NODES PARTITION       STATE CPUS    S:C:T MEMORY TMP_DISK WEIGHT AVAIL_FE REASON              
node6-x2110      1     work*     idle 8       1:4:2  30000        0      1   (null) none                
node7-r620       1     work*     idle 32      2:8:2  95000        0      1   (null) none 

無事使えるようになった。