kuroの覚え書き

96の個人的覚え書き

水耕栽培用各種センサーをESP32で読み取る

測りたいもの
pH
EC
水温(pH,ECの温度補正用でもある)

購入した機材(メインとなるもの)
pHセンサー(https://akizukidenshi.com/catalog/g/gM-12547/)
ECセンサー(https://craft-studios.square.site/product/ec-/30)
ESP32WROOM32E基板(https://akizukidenshi.com/catalog/g/gK-16108/)

pHセンサーとESP32は秋月電子から、ECセンサーは浅草橋工房というところから。

ECセンサーは仕様は書いてあるけど配線の詳細やサーミスタの定数などは教えてくれないという不親切なので、色々と調査した結果、
https://ja.aliexpress.com/i/1005004211276916.html
www.hbbossen.com
これだろう、ということでCCT-3300seriesという製品のマニュアルを探したところ
https://ttkwater.com/wa_files/CCT-3300-new-series-1.pdf
これを見つけた。
これによると白ー黄色の配線がEC電極で、赤ー黒がNTC10Kのサーミスタであることが推測できる。ただし定数はやっぱりわからない。

ECは
【水耕栽培】Arduino,ESP32でECメーターを作成する①Arduino編 - Maker's Academy
こちらを参考に。このページ、ECセンサーを販売している会社が運営するセミナーの補足ページらしいが、肝心のセンサーの配線は教えてくれないって何なんでしょうね。教えてほしかったら有料のセミナーを受講しろってことなんでしょうか。それとも製品ロットごとに仕様が変わるから安易に書けないとか?(実際上記マニュアルでも配線の色はバリエーションが有るようなことが書いてあったしな)
このサイトでは付属サーミスタを使わず別の温度センサーをわざわざ使用しているあたり、この業者もサーミスタの仕様を把握できなかったんじゃないだろうか。

とりあえずサーミスタによる温度計測としては
スタインハート式を使ったサーミスタの温度校正のやり方|Arduino・ESP32
こちらを参考に3点校正方式で使うことにする。これなら定数が分からなくてもなんとかなると思われる。

温度ECはESP32の3.3v出入力で問題なさそう(ただしECは測定時のみパルスで電流を流す必要があるが、サーミスタはそこまでパルス電流でないほうが良さそうなので、回路には工夫が必要かもしれない。
pHはセンサー電極に付随する基板への電源供給が5Vであることから5V供給できるようにしなければならないが、ESP32はアナログ入力の側には抵抗分圧回路をつけて3.3Vに落としてやる必要があって若干回路が複雑になる。まあpH2以下を測らないならば3.3Vを超えないからいい、という話もあるが、若干不安なので分圧はしておいたほうがいいだろう。
https://interface.cqpub.co.jp/wp-content/uploads/if2111_016.pdf
基本的な使い方は
pHを測定する - IoTで水耕栽培研究
こちらを参考になせていただくとしよう。

fastp

fastp: an ultra-fast all-in-one FASTQ preprocessor
version 0.23.2
usage: fastp [options] ... 
options:
  -i, --in1                            read1 input file name (string [=])
  -o, --out1                           read1 output file name (string [=])
  -I, --in2                            read2 input file name (string [=])
  -O, --out2                           read2 output file name (string [=])
      --unpaired1                      for PE input, if read1 passed QC but read2 not, it will be written to unpaired1. Default is to discard it. (string [=])
      --unpaired2                      for PE input, if read2 passed QC but read1 not, it will be written to unpaired2. If --unpaired2 is same as --unpaired1 (default mode), both unpaired reads will be written to this same file. (string [=])
      --overlapped_out                 for each read pair, output the overlapped region if it has no any mismatched base. (string [=])
      --failed_out                     specify the file to store reads that cannot pass the filters. (string [=])
  -m, --merge                          for paired-end input, merge each pair of reads into a single read if they are overlapped. The merged reads will be written to the file given by --merged_out, the unmerged reads will be written to the files specified by --out1 and --out2. The merging mode is disabled by default.
      --merged_out                     in the merging mode, specify the file name to store merged output, or specify --stdout to stream the merged output (string [=])
      --include_unmerged               in the merging mode, write the unmerged or unpaired reads to the file specified by --merge. Disabled by default.
  -6, --phred64                        indicate the input is using phred64 scoring (it'll be converted to phred33, so the output will still be phred33)
  -z, --compression                    compression level for gzip output (1 ~ 9). 1 is fastest, 9 is smallest, default is 4. (int [=4])
      --stdin                          input from STDIN. If the STDIN is interleaved paired-end FASTQ, please also add --interleaved_in.
      --stdout                         stream passing-filters reads to STDOUT. This option will result in interleaved FASTQ output for paired-end output. Disabled by default.
      --interleaved_in                 indicate that <in1> is an interleaved FASTQ which contains both read1 and read2. Disabled by default.
      --reads_to_process               specify how many reads/pairs to be processed. Default 0 means process all reads. (int [=0])
      --dont_overwrite                 don't overwrite existing files. Overwritting is allowed by default.
      --fix_mgi_id                     the MGI FASTQ ID format is not compatible with many BAM operation tools, enable this option to fix it.
  -V, --verbose                        output verbose log information (i.e. when every 1M reads are processed).
  -A, --disable_adapter_trimming       adapter trimming is enabled by default. If this option is specified, adapter trimming is disabled
  -a, --adapter_sequence               the adapter for read1. For SE data, if not specified, the adapter will be auto-detected. For PE data, this is used if R1/R2 are found not overlapped. (string [=auto])
      --adapter_sequence_r2            the adapter for read2 (PE data only). This is used if R1/R2 are found not overlapped. If not specified, it will be the same as <adapter_sequence> (string [=auto])
      --adapter_fasta                  specify a FASTA file to trim both read1 and read2 (if PE) by all the sequences in this FASTA file (string [=])
      --detect_adapter_for_pe          by default, the auto-detection for adapter is for SE data input only, turn on this option to enable it for PE data.
  -f, --trim_front1                    trimming how many bases in front for read1, default is 0 (int [=0])
  -t, --trim_tail1                     trimming how many bases in tail for read1, default is 0 (int [=0])
  -b, --max_len1                       if read1 is longer than max_len1, then trim read1 at its tail to make it as long as max_len1. Default 0 means no limitation (int [=0])
  -F, --trim_front2                    trimming how many bases in front for read2. If it's not specified, it will follow read1's settings (int [=0])
  -T, --trim_tail2                     trimming how many bases in tail for read2. If it's not specified, it will follow read1's settings (int [=0])
  -B, --max_len2                       if read2 is longer than max_len2, then trim read2 at its tail to make it as long as max_len2. Default 0 means no limitation. If it's not specified, it will follow read1's settings (int [=0])
  -D, --dedup                          enable deduplication to drop the duplicated reads/pairs
      --dup_calc_accuracy              accuracy level to calculate duplication (1~6), higher level uses more memory (1G, 2G, 4G, 8G, 16G, 24G). Default 1 for no-dedup mode, and 3 for dedup mode. (int [=0])
      --dont_eval_duplication          don't evaluate duplication rate to save time and use less memory.
  -g, --trim_poly_g                    force polyG tail trimming, by default trimming is automatically enabled for Illumina NextSeq/NovaSeq data
      --poly_g_min_len                 the minimum length to detect polyG in the read tail. 10 by default. (int [=10])
  -G, --disable_trim_poly_g            disable polyG tail trimming, by default trimming is automatically enabled for Illumina NextSeq/NovaSeq data
  -x, --trim_poly_x                    enable polyX trimming in 3' ends.
      --poly_x_min_len                 the minimum length to detect polyX in the read tail. 10 by default. (int [=10])
  -5, --cut_front                      move a sliding window from front (5') to tail, drop the bases in the window if its mean quality < threshold, stop otherwise.
  -3, --cut_tail                       move a sliding window from tail (3') to front, drop the bases in the window if its mean quality < threshold, stop otherwise.
  -r, --cut_right                      move a sliding window from front to tail, if meet one window with mean quality < threshold, drop the bases in the window and the right part, and then stop.
  -W, --cut_window_size                the window size option shared by cut_front, cut_tail or cut_sliding. Range: 1~1000, default: 4 (int [=4])
  -M, --cut_mean_quality               the mean quality requirement option shared by cut_front, cut_tail or cut_sliding. Range: 1~36 default: 20 (Q20) (int [=20])
      --cut_front_window_size          the window size option of cut_front, default to cut_window_size if not specified (int [=4])
      --cut_front_mean_quality         the mean quality requirement option for cut_front, default to cut_mean_quality if not specified (int [=20])
      --cut_tail_window_size           the window size option of cut_tail, default to cut_window_size if not specified (int [=4])
      --cut_tail_mean_quality          the mean quality requirement option for cut_tail, default to cut_mean_quality if not specified (int [=20])
      --cut_right_window_size          the window size option of cut_right, default to cut_window_size if not specified (int [=4])
      --cut_right_mean_quality         the mean quality requirement option for cut_right, default to cut_mean_quality if not specified (int [=20])
  -Q, --disable_quality_filtering      quality filtering is enabled by default. If this option is specified, quality filtering is disabled
  -q, --qualified_quality_phred        the quality value that a base is qualified. Default 15 means phred quality >=Q15 is qualified. (int [=15])
  -u, --unqualified_percent_limit      how many percents of bases are allowed to be unqualified (0~100). Default 40 means 40% (int [=40])
  -n, --n_base_limit                   if one read's number of N base is >n_base_limit, then this read/pair is discarded. Default is 5 (int [=5])
  -e, --average_qual                   if one read's average quality score <avg_qual, then this read/pair is discarded. Default 0 means no requirement (int [=0])
  -L, --disable_length_filtering       length filtering is enabled by default. If this option is specified, length filtering is disabled
  -l, --length_required                reads shorter than length_required will be discarded, default is 15. (int [=15])
      --length_limit                   reads longer than length_limit will be discarded, default 0 means no limitation. (int [=0])
  -y, --low_complexity_filter          enable low complexity filter. The complexity is defined as the percentage of base that is different from its next base (base[i] != base[i+1]).
  -Y, --complexity_threshold           the threshold for low complexity filter (0~100). Default is 30, which means 30% complexity is required. (int [=30])
      --filter_by_index1               specify a file contains a list of barcodes of index1 to be filtered out, one barcode per line (string [=])
      --filter_by_index2               specify a file contains a list of barcodes of index2 to be filtered out, one barcode per line (string [=])
      --filter_by_index_threshold      the allowed difference of index barcode for index filtering, default 0 means completely identical. (int [=0])
  -c, --correction                     enable base correction in overlapped regions (only for PE data), default is disabled
      --overlap_len_require            the minimum length to detect overlapped region of PE reads. This will affect overlap analysis based PE merge, adapter trimming and correction. 30 by default. (int [=30])
      --overlap_diff_limit             the maximum number of mismatched bases to detect overlapped region of PE reads. This will affect overlap analysis based PE merge, adapter trimming and correction. 5 by default. (int [=5])
      --overlap_diff_percent_limit     the maximum percentage of mismatched bases to detect overlapped region of PE reads. This will affect overlap analysis based PE merge, adapter trimming and correction. Default 20 means 20%. (int [=20])
  -U, --umi                            enable unique molecular identifier (UMI) preprocessing
      --umi_loc                        specify the location of UMI, can be (index1/index2/read1/read2/per_index/per_read, default is none (string [=])
      --umi_len                        if the UMI is in read1/read2, its length should be provided (int [=0])
      --umi_prefix                     if specified, an underline will be used to connect prefix and UMI (i.e. prefix=UMI, UMI=AATTCG, final=UMI_AATTCG). No prefix by default (string [=])
      --umi_skip                       if the UMI is in read1/read2, fastp can skip several bases following UMI, default is 0 (int [=0])
  -p, --overrepresentation_analysis    enable overrepresented sequence analysis.
  -P, --overrepresentation_sampling    one in (--overrepresentation_sampling) reads will be computed for overrepresentation analysis (1~10000), smaller is slower, default is 20. (int [=20])
  -j, --json                           the json format report file name (string [=fastp.json])
  -h, --html                           the html format report file name (string [=fastp.html])
  -R, --report_title                   should be quoted with ' or ", default is "fastp report" (string [=fastp report])
  -w, --thread                         worker thread number, default is 3 (int [=3])
  -s, --split                          split output by limiting total split file number with this option (2~999), a sequential number prefix will be added to output name ( 0001.out.fq, 0002.out.fq...), disabled by default (int [=0])
  -S, --split_by_lines                 split output by limiting lines of each file with this option(>=1000), a sequential number prefix will be added to output name ( 0001.out.fq, 0002.out.fq...), disabled by default (long [=0])
  -d, --split_prefix_digits            the digits for the sequential number padding (1~10), default is 4, so the filename will be padded as 0001.xxx, 0 to disable padding (int [=4])
      --cut_by_quality5                DEPRECATED, use --cut_front instead.
      --cut_by_quality3                DEPRECATED, use --cut_tail instead.
      --cut_by_quality_aggressive      DEPRECATED, use --cut_right instead.
      --discard_unmerged               DEPRECATED, no effect now, see the introduction for merging.
  -?, --help                           print this message

ESP32をアクセスポイントにする

ESP32-WROOM-32EをアクセスポイントにしてWifiネットワークのとりあえずの中継地点とする

#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiAP.h>

// Set these to your desired credentials.
const char *ssid = "kuroesp"; //SSID
const char *pass = "kk********kk"; //password
const IPAddress ip(192,168,11,1); //IPアドレス
const IPAddress subnet(255,255,255,0); //サブネットマスク

WiFiClient client; //クライアント情報に関する変数
WiFiServer server(80); //ポート80(http)の接続を管理する変数

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("Configuring access point...");
  
  if (!WiFi.softAP(ssid, pass)) {
    log_e("Soft AP creation failed.");
    while(1);
  }

  // IPAddress myIP = WiFi.softAPIP();
  WiFi.softAPConfig(ip, ip, subnet); //IP及びサブネットマスクの設定
  Serial.print("AP IP address: ");

  // Serial.println(myIP);
  Serial.println(ip);

  server.begin();

  Serial.println("Server started");
}

void loop() {
  WiFiClient client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println("New Client.");           // print a message out the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        if (c == '\n') {                    // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();
            client.print(HtmlSet()); //Htmlを送信
            // break out of the while loop:
            break;
          } else {    // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // close the connection:
    client.stop();
    Serial.println("Client Disconnected.");
  }
}

/* クライアントに表示するhtml*/
String HtmlSet(void){
  String str = "";

  str += "<html lang=\"ja\">";
  str += "<head>";
  str += "<meta http-equiv=\"refresh\" content=\"5\">";
  str += "<meta charset=\"UTF-8\">";
  str += "<title>ESP AP</title>";
  str += "</head>";
  str += "<body>";
  str += "<h1>kuro ESP AccessPoint Works!!</h1>";
  str += "</body>";
  str += "</html>";

  return str;
}

M5STACK Timer Camera X

これまでタイムラプスカメラはラズパイベースで作ってきたが、もっと省電力で大量にばら撒くような使い方が必要となってきたのでESP32ベースに移行を考えた。
Amazonを見ていると1000円くらいで2MピクセルのカメラがついたESP32基板が見つかるが、どれもこれも技適がついておらず、日本国内で大っぴらに電波を飛ばせない代物ばかりであった。
そんな中、M5STACKシリーズの製品で3000円程度で技適もついて、さらにバッテリーも搭載しているという製品に目が止まり、まずは試してみることにした。
ESP32 PSRAM Timer Camera X (OV3660)www.switch-science.com

ESP32搭載なのでArduinoIDEでプログラミングできるだろうとおもいつつ、色々リサーチ。

まずは
lang-ship.com
この方、おそらくM5STACKで日本から情報を発信されている第一人者なんだろうか。

ArduinoIDEのセッティングなどわかりやすく紹介されていて、参考になった。
ライブラリとしてTimer-CAMというものを入れると。
ArduinoIDE2.2.2に最新の0.0.3を入れようとするとどうも何やらエラーが出て入らない。しかたがないのでとりあえず0.0.2を入れておく。

ちなみに今回
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
こちらをM5STACKのボード設定を取り込むために入れるためこれまで使っていたIDE1.8.19とは別に2.2.2を用意してみたんだが、これが仇となることが後で判明。

ここまでArduinoのセッティングをしてきたが、そもそも製品デモ的なファームウェアアプリがあるらしいことを知ったので、まずはそちらを試してみることにする。
3pysci.com

なるほど、M5Burnerというものでやるのね。
https://docs.m5stack.com/en/unit/timercam_xdocs.m5stack.com
こちらで該当するリンクを辿っていきアプリをダウンロードする。

しかし、ダウンロードしてきたM5Burnerは見た目がなんか違っていて、どうやらバージョンアップしているらしい。
で、ファームウェア書き込みまではうまく行くのだが、肝心の画像を見に行くtokenの発行がどうやってもうまくいかない。
なんだかバグがあってこのバージョンの M5Burnerではサーバとのやり取りがうまくいかず、tokenが取得できないというようなディスカッションが散見される。だめじゃん。

ということで次の作戦へ。

Arduinoでサンプルアプリを入れてみる。
sample.msr-r.net
こちらのかたの記述を参考に。

なるほどArduinoのexampleに入っているのか。

これね。

ArduinoでM5Stack-Timer-CAMをボードとして選び、書き込んでみる。

なるほど、カメラ自体がWEBサーバも立ち上げていて無線LAN経由でコントロールできるわけだ。

画質は・・・まあこんなもんなんでしょう。
ただ、これだと写真を撮ることはできるけどタイムラプスを自動で撮ることはできないので、目的には合致しない。

次に参照したのは
note.com
こちら。

さらにリンクされていた
twinklesmile.blog42.fc2.com
こちらを参考にGoogle Driveに撮った写真を転送することを考えてみる。

Google Driveスクリプトを仕込んでおき、アクセスがあったらファイルを格納するということなんだろうか。
言われるままにGoogleDriveにそれ用のフォルダ(timer_camera_x1)を作り、
左上の「+新規」ボタンから、その他ー>Google App Scriptとたどってスクリプトを入れる。

function doPost(e) {
var data = Utilities.base64Decode(e.parameters.data);
var nombreArchivo = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyyMMdd_HHmmss')+".jpg";
var blob = Utilities.newBlob(data, e.parameters.mimetype, nombreArchivo );

// Save the photo to Google Drive
var folder, folders = DriveApp.getFoldersByName("timer_camera_x1");
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder("timer_camera_x1");
}
var file = folder.createFile(blob);
return ContentService.createTextOutput('Complete')
}

という感じ。
ファイル名を無題.gsから適当な名前に変えて、右上の「デプロイ」から「新しいデプロイ」を選び、設定する
デプロイのタイプ>ウェブアプリ
説明>なにかてきとうに
ウェブアプリ>
次のユーザーとして実行: 自分
アクセスできるユーザー: 全員

ファームウェアは先人のものを基本そのままということで。


ところがここでまた躓く。

#include "fd_forward.h"

ができないとエラーを吐く。
どうやらTimer-CAM 0.0.2はダメで、0.0.3をインストールしようとすると
先ほどと同じように

Failed to install library: 'Timer-CAM:0.0.3'. No valid dependencies solution found: dependency 'Micro-RTSP' is not available

というエラーが出てインストールできない。

ところが、IDEの1.8.19を起動して0.0.3のライブラリをインストールしてみるとこれができた。
ファームウェアの書き込みも問題なく、ちゃんと5分間隔で写真を撮って、GoogleDriveにどんどん溜まっていく。

さらに一旦1.8.19でインストールしてしまえば2.2.2の方のアプリでも使えるという不思議さ。
なんなんだ?


ついでにdeep sleepによる省電力設定を加えて一応の完成とする。

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

#include "esp_camera.h"
#include "camera_pins.h"

#include "battery.h"
#include "bmm8563.h" //RTC
// #include "led.h"

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

int waitingTime = 30000; //Wait 30 seconds to google response.

unsigned long sleepTime = 10790; //sec

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

  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);

  // Init
  bat_init();
  bmm8563_init();
  // led_init(CAMERA_LED_GPIO);

  // disable bat output, will wake up after 5 sec, Sleep current is 1~2μA
  bat_disable_output();


  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 = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_UXGA;  // UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA
  config.jpeg_quality = 10;
  config.fb_count = 2;

  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
  s->set_saturation(s, -2);//lower the saturation

  //drop down frame size for higher initial frame rate
  s->set_framesize(s, FRAMESIZE_SVGA);

  saveCapturedImage(); 

 // if deep seep ここから
  esp_sleep_enable_timer_wakeup(sleepTime*1000000ULL);  
  Serial.println("Deep Sleep Start."); 
  Serial.println(" ");  
  esp_deep_sleep_start();  
 // if deep seep ここまで

}

void loop() {
  // if no deep sleep ここから
  // saveCapturedImage(); 
  // delay(sleepTime*1000ULL); 
  // if no deep sleep ここまで
}

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 = NULL;
    fb = esp_camera_fb_get();  
    if(!fb) {
      Serial.println("Camera capture failed");
      delay(1000);
      ESP.restart();
      return;
    }

    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) < 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;
}

おんどとりをwindows以外でどうにかするための調査

別途公開の通信プロトコルを使用し、お客様ご自身でソフトウェアを作成していただければシリアル通信が可能となります。その場合、オプションのシリアル通信ケーブル( T R - 0 7 C )が 必 要 で す 。
と取説には書かれているが、どこにも公開されている感じがない。問い合わせれば教えてくれるのか。

gwwiki.icrr.u-tokyo.ac.jp
Macでwineを使ってWindowsアプリを使う試みらしいのだが、USBで接続してコントロールができないとか言っている。そもそもTR-7Uiシリーズではないような。

github.com
普通にUSBで繋いでシリアル通信できるということなんだろうか。

mi2yo4.blog136.fc2.com
シリアルケーブル経由が一番簡単なんだろうな。別売りケーブルが4000円以上するのがいかがなものか。あとUSBーシリアル変換は色々と面倒な気もする。


www.silabs.com
とりあえずMac用のUSB-シリアルドライバはここからダウンロードしてインストールできるっぽい。と思ったが、どうも最近のVenturaとかの新しめのOSには対応してないとか。

Rのベンチマーク

#パッケージのインストール
install.packages("devtools")
devtools::install_github("csgillespie/benchmarkme")

注:色々とエラーが出て、それを一つずつ潰していく必要がある。
あまりに色々やったのでなにがクリティカルだったのかわからん。
Ubuntuの情報が豊富で、なんとかなったが、Alma Linux8.7とMacではそもそもインストールがまだ成功していない。
なのでこのマシンの速度が速いのかなんなのか比較できていない。

#パッケージの読み込み
library("benchmarkme")

> res<-benchmark_std()
# Programming benchmarks (5 tests):
	3,500,000 Fibonacci numbers calculation (vector calc): 0.231 (sec).
	Grand common divisors of 1,000,000 pairs (recursion): 0.485 (sec).
	Creation of a 3,500 x 3,500 Hilbert matrix (matrix calc): 0.37 (sec).
	Creation of a 3,000 x 3,000 Toeplitz matrix (loops): 1.17 (sec).
	Escoufier's method on a 60 x 60 matrix (mixed): 0.699 (sec).
# Matrix calculation benchmarks (5 tests):
	Creation, transp., deformation of a 5,000 x 5,000 matrix: 0.595 (sec).
	2,500 x 2,500 normal distributed random matrix^1,000: 0.18 (sec).
	Sorting of 7,000,000 random values: 0.536 (sec).
	2,500 x 2,500 cross-product matrix (b = a' * a): 1.05 (sec).
	Linear regr. over a 5,000 x 500 matrix (c = a \ b'): 0.11 (sec).
# Matrix function benchmarks (5 tests):
	Cholesky decomposition of a 3,000 x 3,000 matrix: 0.638 (sec).
	Determinant of a 2,500 x 2,500 random matrix: 0.761 (sec).
	Eigenvalues of a 640 x 640 random matrix: 0.369 (sec).
	FFT over 2,500,000 random values: 0.294 (sec).
	Inverse of a 1,600 x 1,600 random matrix: 0.635 (sec).

> plot(res)
You are ranked 43 out of 749 machines.
Press return to get next plot 
You are ranked 33 out of 747 machines.
Press return to get next plot 
You are ranked 48 out of 747 machines.

> get_cpu()
$vendor_id
[1] "GenuineIntel"

$model_name
[1] "Intel(R) Xeon(R) CPU E3-1281 v3 @ 3.70GHz"

$no_of_cores
[1] 8

> get_r_version()
$platform
[1] "x86_64-pc-linux-gnu"

$arch
[1] "x86_64"

$os
[1] "linux-gnu"

$system
[1] "x86_64, linux-gnu"

$status
[1] ""

$major
[1] "4"

$minor
[1] "3.1"

$year
[1] "2023"

$month
[1] "06"

$day
[1] "16"

$`svn rev`
[1] "84548"

$language
[1] "R"

$version.string
[1] "R version 4.3.1 (2023-06-16)"

$nickname
[1] "Beagle Scouts"


Alma Linux8.7でもできた。

> res <- benchmark_std()
# Programming benchmarks (5 tests):
	3,500,000 Fibonacci numbers calculation (vector calc): 0.39 (sec).
	Grand common divisors of 1,000,000 pairs (recursion): 0.416 (sec).
	Creation of a 3,500 x 3,500 Hilbert matrix (matrix calc): 0.226 (sec).
	Creation of a 3,000 x 3,000 Toeplitz matrix (loops): 1.05 (sec).
	Escoufier's method on a 60 x 60 matrix (mixed): 0.805 (sec).
# Matrix calculation benchmarks (5 tests):
	Creation, transp., deformation of a 5,000 x 5,000 matrix: 0.389 (sec).
	2,500 x 2,500 normal distributed random matrix^1,000: 0.35 (sec).
	Sorting of 7,000,000 random values: 0.758 (sec).
	2,500 x 2,500 cross-product matrix (b = a' * a): 0.0517 (sec).
	Linear regr. over a 5,000 x 500 matrix (c = a \ b'): 0.201 (sec).
# Matrix function benchmarks (5 tests):
	Cholesky decomposition of a 3,000 x 3,000 matrix: 1.09 (sec).
	Determinant of a 2,500 x 2,500 random matrix: 0.729 (sec).
	Eigenvalues of a 640 x 640 random matrix: 15.7 (sec).
	FFT over 2,500,000 random values: 0.102 (sec).
	Inverse of a 1,600 x 1,600 random matrix: 1.26 (sec).
> plot(res)
You are ranked 18 out of 93 machines.
Press return to get next plot 
You are ranked 24 out of 93 machines.
Press return to get next plot 
You are ranked 93 out of 93 machines.
> get_cpu()
$vendor_id
[1] "AuthenticAMD"

$model_name
[1] "AMD EPYC 7413 24-Core Processor"

$no_of_cores
[1] 48


Rは基本シングルコアで動作するから、24コアだけどクロックの遅いAMD EPYCより4コアでクロックの速いHaswellのXeonのほうが、ある部分では優っているかもしれない。

AlmaLinux8.7にRStudioをインストール

あんまりRは得意ではないんですが、入れてくれという希望があったためAlmaLinuxに入れてみる。

CentOS8にインストールした事例をいくつかみてみたところ

$ sudo dnf install epel-release
$ sudo dnf config-manager --set-enabled PowerTools

としろ、と書かれていたが、うまくいかない。

$ sudo dnf config-manager --set-enabled powertools

こちらに変わった模様。で、

$ sudo dnf install R-core-devel
$ R

R version 4.3.1 (2023-06-16) -- "Beagle Scouts"
Copyright (C) 2023 The R Foundation for Statistical Computing
Platform: x86_64-redhat-linux-gnu (64-bit)
以下略

$ which R
/usr/bin/R

これでいいようだ。

次にRStudio server
MacなどのデスクトップアプリではなくLinux用にはWebアプリとして作られているようで、サーバアプリをインストールして、Webブラウザ経由で操作するらしい。

$ sudo curl -O https://download2.rstudio.org/server/rhel8/x86_64/rstudio-server-rhel-2023.06.1-524-x86_64.rpm
$ sudo dnf install rstudio-server-rhel-2023.06.1-524-x86_64.rpm
$ sudo firewall-cmd --permanent --add-port=8787/tcp
$ sudo firewall-cmd --reload

これでサーバは自動で起動されているので
http://localhost:8787/
にアクセスすればRStudioが使えるはず。


このようにログインを促されるので一般ユーザでログインし

このような見慣れた画面にたどり着く。
ただ、一般ユーザではモジュールのインストールができないっぽいのでちょっと不便かな。
追記:一般ユーザでもインストールはできるが、場合によってはシステムの方に不足があってインストールがエラーになる場合がある。その時は管理者権限が必要になってくる

WSL2でwindows10にext4フォーマットのHDDをマウント

自宅サーバはほぼラボサーバのデータバックアップ用でしかなかったので、Windowsに置き換えることにした。データバックアップディスクをwindowsのフォーマットにしてもいいといえばいいが、ext4のままのほうが楽といえば楽なのでそのままマウントできる方法を模索した。
結果、結構面倒くさいことが判明。
手順を覚えていられる自信がないので書いておく。

まずext4のディスクをつなぐ。今回biosをいじってsataのホットプラグを有効にしたので、HDDをラックにガチャンとはめ込んでやる。その状態では全くマウントされないのでまずはWindowsに認識されるようにする。
powershellを管理者で開き、

> GET-CimInstance -query "SELECT * from Win32_DiskDrive"
DeviceID           Caption              Partitions Size          Model
--------           -------              ---------- ----          -----
\\.\PHYSICALDRIVE1 ST4000VN008-2DR166   1          4000784417280 ST4000VN008-2DR166
\\.\PHYSICALDRIVE0 WDC WD10EARX-32N0YB0 3          1000202273280 WDC WD10EARX-32N0YB0

\\.\PHYSICALDRIVE1がホットプラグしたHDDなので

>wsl --mount \\.\PHYSICALDRIVE1 --bare
この操作を正しく終了しました。

このようにマウントする。しかしこの状態ではext4フォーマットは読めるようにはなっておらず、続いてUbuntuでマウントしてやる。
Ubuntuを起動し、プロンプトに

$ sudo fdisk -l
[sudo] password for kkuro:
Disk /dev/ram0: 64 MiB, 67108864 bytes, 131072 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

中略

Disk /dev/sdc: 3.64 TiB, 4000787030016 bytes, 7814037168 sectors
Disk model: ST4000VN008-2DR1
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: 7ACA6F87-4951-4650-BD60-48A486258E88

Device     Start        End    Sectors  Size Type
/dev/sdc1   2048 7814035455 7814033408  3.6T Linux filesystem


Disk /dev/sdd: 1 TiB, 1099511627776 bytes, 2147483648 sectors
Disk model: Virtual Disk
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

このようになっている。ここで/dev/sddがWindowsの起動ディスクで、/dev/sdcがext4のHDD

$ sudo mkdir /media/hdd1
$ sudo mount /dev/sdc1 /media/hdd1

という風にマウントしてやる。
これでやっとファイルエクスプローラーのLinux>Ubuntu>media>hdd1にHDDの中身が出てくる。

なお/etc/fstabで自動マウントはできないっぽい。

HDDを取り外すときはまずUbuntu

$sudo umount /media/hdd1

とし、powershell

> wsl --unmount  \\.\PHYSICALDRIVE1

これで多分引っこ抜いても大丈夫なんじゃないかな。

INGOR.1.0.0

理研の玉田さんのSiGN-BNによるベイジアンネットワーク解析を利用させてもらっている。

データサンプルとしてRNA-seqデータを投入しているのだが、久々にやり直してみようと思ったらうまく動かない。
実行履歴をもとに同じコマンドを投げても動かないのだ。

どうやらこの間にスパコンのCPUがIntel XeonからAMD EPYCに置き換わり、AVX等の環境が変わったために以前のバイナリsignbnnnsr.0.16.7がエラーを吐いて止まってしまうのだ。


以下はとりあえずの覚書

qsub -o so -e se -pe mpi-fillup 2 ~tamada/sign/signmpi.sh ~tamada/sign/signbnnnsr.0.16.7 -o result all.edf.txt

qstatでモニターしているとすぐにランが終わってしまい、

$ cat result.log 
! ABSOLUTELY NO WARRANTY.  ALL RIGHTS RESERVED.
! THIS BINARY IS PERMITTED FOR HGC Supercomputer System Shirokane5 ONLY.

SiGN-BN NNSR Rel. 0.16.7
  rank=0, processes=2, threads=1
THIS IS THE ROOT PROCESS.
Initialization finished at Tue Jul 7 17:59:06 2023
HOSTNAME: gc275i
Random Seed:	1689065946
Random Walk Interval: 1280
Exchange Interval: 1280
1-to-1 Interval: --
Reading the input data matrix.
  file: all.edf.txt
  84526 nodes  x  36 samples read.
  Requires 24,343,488 [bytes] (23 [MiB])
  nodes = 84526, samples = 36
Broadcasting the input data matrix  at Tue Jul 7 17:59:26 2023
Sent the input matrix size.
  at Tue Jul 11 17:59:27 2023
Broadcasting input values.
  Done at Tue Jul 11 17:59:27 2023

というところでとまってしまっている。
そして

$ cat so
-catch_rsh /var/spool/ge/gc275i/active_jobs/78340293.1/pe_hostfile
gc275i
gc335i
-------------------------------------------------------
Primary job  terminated normally, but 1 process returned
a non-zero exit code. Per user-direction, the job has been aborted.
-------------------------------------------------------

となる。
なにも言わずに終わることもあれば、

  • pe mpi-fillup 8

とかにすると更にcoreを吐き始める。

あれ?
THIS BINARY IS PERMITTED FOR HGC Supercomputer System Shirokane5 ONLY.
ってもしかして今のシステムはShirokane6になってEPYCにCPUも替わっているよね。それで?

ということで玉田さんのディレクトリを見るとingor.1.0.0-MPIというバイナリがおいてある。これか?
ってことで

qsub -e se2 -o so2 -pe mpi-fillup 4 ~tamada/sign/ingor_mpi.sh ~tamada/sign/ingor.1.0.0-MPI -a nnsr all0.edf.txt

としてみるも

$ cat result.log 
INGOR release 1.0.0 (hash: f5e3722) (Thu Nov 17 20:10:45 2022)
  Copyright (C) 2018-2022 Yoshinori Tamada
MPI enabled: size=4, rank=0
Random seed:	1689069008
input file: nbn_all0.edf.txt
Max memory: 1,048,576,000 [Byte] (1,000 [MiB])
candidates: 10
maxParents: 10
Reading input data file: all0.edf.txt
  Done.  # of missing values=0.
[MPI] Broadcasting the read data...
[MPI] Finished.
WARNING: zero minimum value detected for ID=159 (s00160g00730)
ID=159; s00160g00730 (real); min=0.000, max=1153.841, mean=438.254, var=74813.518, sd=273.521, mv=0

・・・・

WARNING: zero minimum value detected for ID=52739 (s00060g00410)
ID=52739; s00060g00410 (real); min=0.000, max=0.001, mean=0.000, var=0.000, sd=0.000, mv=0
  variables = 52740,   samples = 36
  varialbe types:
    real: 52740
  Variable attributes: (Nothing)
Continuous variables: 52740
Discrete variables: 0
candidates: 10
candidatesCont: 0
candidatesDisc: 0
maxParents: 10
maxParentsCont: -1
maxParentsDisc: 2
Output file type: nodelist
Algorithm: nnsr
NNSR{0}: subNodes=13185, mrows=52737, subNodesMax=13185
NNSR{0}: sub graph 1: 2,781,349,380 [bytes]
FATAL ERROR (999999): NNSRWork_alloc: Memory allocation failed.
  algo/NNSR.c:558; 2,781,349,380 [bytes]

一筋縄でいかないな。
zero minimum value detectedてことは数値が0はだめなんだったっけ。
てことですべての項目に1を足すというこの手の処理でよくやる前処理をしてみる。

$ qsub -pe mpi-fillup 4 -e se3 -o so3 ~tamada/sign/ingor_mpi.sh ~tamada/sign/ingor.1.0.0-MPI -a nnsr -o result all1.edf

だめっぽい

$ cat result.log 
INGOR release 1.0.0 (hash: f5e3722) (Thu Nov 17 20:10:45 2022)
  Copyright (C) 2018-2022 Yoshinori Tamada
MPI enabled: size=4, rank=0
Random seed:	1689069627
input file: nbn_all1.edf
Max memory: 1,048,576,000 [Byte] (1,000 [MiB])
candidates: 10
maxParents: 10
Reading input data file: nbn_all1.edf
  Done.  # of missing values=0.
[MPI] Broadcasting the read data...
[MPI] Finished.
  variables = 52740,   samples = 36
  varialbe types:
    real: 52740
  Variable attributes: (Nothing)
Continuous variables: 52740
Discrete variables: 0
candidates: 10
candidatesCont: 0
candidatesDisc: 0
maxParents: 10
maxParentsCont: -1
maxParentsDisc: 2
Output file type: nodelist
Algorithm: nnsr
NNSR{0}: subNodes=13185, mrows=52737, subNodesMax=13185
NNSR{0}: sub graph 1: 2,781,349,380 [bytes]
FATAL ERROR (999999): NNSRWork_alloc: Memory allocation failed.
  algo/NNSR.c:558; 2,781,349,380 [bytes]

$ cat se3
--------------------------------------------------------------------------
Primary job  terminated normally, but 1 process returned
a non-zero exit code. Per user-direction, the job has been aborted.
--------------------------------------------------------------------------
--------------------------------------------------------------------------
mpirun detected that one or more processes exited with non-zero status, thus causing
the job to be terminated. The first process to do so was:

  Process name: [[64572,1],0]
  Exit code:    63
--------------------------------------------------------------------------

あかんね。

この件、結局未解決で、かわりといっちゃーなんだが、signbnnnsr.0.16.8のLinuxバイナリをオンプレミスにダウンロードしてやると見事MPI環境で3ノード72スロットで動作させることができたため、一旦は良しとすることにした。

UbuntuでSlurm

ローカルで運用しているUbuntu20.04にジョブスケジューラを入れよう、ということで最初Torqueを入れようとした。

sudo apt install torque-server torque-mom

しかしそんなものはないと言われる。aptのほうでももうメンテナンスされてないんだね。
ということでAlma Linuxに入れたSlurmを入れることにする。
まずは下ごしらえでユーザー設定など。

sudo groupadd -g 5000 slurm
sudo useradd -M -d /var/lib/slurm -s /sbin/nologin -u 5000 -g slurm slurm
sudo groupadd -g 5001 munge
sudo  useradd -M -d /var/lib/munge -s /bin/bash -u 5001 -g munge munge

sudo mkdir -p /var/lib/slurm/spool
sudo mkdir -p /var/log/slurm

sudo chown -R slurm:slurm /var/log/slurm
sudo chown -R slurm:slurm /var/lib/slurm

そしてmungeのインストール

sudo apt install -y munge libmunge-dev

Alma Linuxのほうではここでmunge-keyをゴニョゴニョしたと思うんだけど、
qiita.com
参考にさせていただいたこちらのページではそんなことはやっていないので、要らないんだろうか。
で、Slurmはソースを取ってきてビルド。

sudo apt install -y build-essential

wget https://download.schedmd.com/slurm/slurm-23.02.3.tar.bz2
tar -jxvf slurm-23.02.3.tar.bz2
cd slurm-23.02.3/

./configure
make
sudo make install

次にslurm.confを作る。
slurm.schedmd.com
こちらのページに必要事項を入力するといい感じにconfigファイルを作ってくれるらしい。
できたファイルを以下にコピーして、ちょっとだけ追加修正してやる。

sudo nano /usr/local/etc/slurm.conf

# slurm.conf file generated by configurator easy.html.
# Put this file on all nodes of your cluster.
# See the slurm.conf man page for more information.
#
ClusterName=guilty8
SlurmctldHost=guilty8-e5800t110fe
#
#MailProg=/bin/mail
MpiDefault=none
#MpiParams=ports=#-#
ProctrackType=proctrack/cgroup
ReturnToService=1
SlurmctldPidFile=/var/run/slurmctld.pid
#SlurmctldPort=6817
SlurmdPidFile=/var/run/slurmd.pid
#SlurmdPort=6818
SlurmdSpoolDir=/var/spool/slurmd
SlurmUser=slurm
#SlurmdUser=root
StateSaveLocation=/var/spool/slurmctld
SwitchType=switch/none
TaskPlugin=task/affinity,task/cgroup
#
#
# TIMERS
#KillWait=30
#MinJobAge=300
#SlurmctldTimeout=120
#SlurmdTimeout=300
#
#
# SCHEDULING
SchedulerType=sched/backfill
SelectType=select/cons_tres
#
#
# LOGGING AND ACCOUNTING
AccountingStorageType=accounting_storage/none
#JobAcctGatherFrequency=30
JobAcctGatherType=jobacct_gather/none
#SlurmctldDebug=info
SlurmctldLogFile=/var/log/slurmctld.log
#SlurmdDebug=info
SlurmdLogFile=/var/log/slurmd.log
#
#
# COMPUTE NODES
NodeName=guilty8-e5800t110fe CPUs=8 Sockets=1 CoresPerSocket=4 ThreadsPerCore=2 State=UNKNOWN
PartitionName=debug Nodes=ALL Default=YES MaxTime=INFINITE State=UP

これでいいはずなので

sudo cp etc/slurmctld.service /etc/systemd/system
sudo cp etc/slurmd.service /etc/systemd/system

sudo systemctl start slumctld slums

と起動してやったがどちらもActive: failed と言われる。
slumctldのほうは権限がなくて/var/spool/slurmctldが作れないとのことなので

sudo mkdir -p /var/spool/slurmctld
sudo chmod 777 /var/spool/slurmctld/

としてやることで無事起動した。
一方、slurmdは

sudo systemctl status slurmd
× slurmd.service - Slurm node daemon
     Loaded: loaded (/etc/systemd/system/slurmd.service; enabled; vendor preset: enabled)
     Active: failed (Result: exit-code) since Sun 2023-07-02 13:40:10 JST; 5min ago
    Process: 6779 ExecStart=/usr/local/sbin/slurmd -D -s $SLURMD_OPTIONS (code=exited, status=1/FAILURE)
   Main PID: 6779 (code=exited, status=1/FAILURE)
        CPU: 4ms

 7月 02 13:40:10 guilty8-e5800t110fe systemd[1]: Started Slurm node daemon.
 7月 02 13:40:10 guilty8-e5800t110fe slurmd[6779]: slurmd: error: Couldn't find the specified plugin name for cgroup/v2 looking at all files
 7月 02 13:40:10 guilty8-e5800t110fe slurmd[6779]: slurmd: error: cannot find cgroup plugin for cgroup/v2
 7月 02 13:40:10 guilty8-e5800t110fe slurmd[6779]: slurmd: error: cannot create cgroup context for cgroup/v2
 7月 02 13:40:10 guilty8-e5800t110fe slurmd[6779]: slurmd: error: Unable to initialize cgroup plugin
 7月 02 13:40:10 guilty8-e5800t110fe slurmd[6779]: slurmd: error: slurmd initialization failed
 7月 02 13:40:10 guilty8-e5800t110fe systemd[1]: slurmd.service: Main process exited, code=exited, status=1/FAILURE
 7月 02 13:40:10 guilty8-e5800t110fe systemd[1]: slurmd.service: Failed with result 'exit-code'.

で、Stackoverflowあたりをさまようとlibdbus-1-devを入れとけや、と言われるので

sudo apt install libdbus-1-dev

としたけど変わらない。
結局、libdbus-1-devをインストールしたあとmakeからやり直さないとだめなようで、

./configure
make
sudo make install

とすると

sudo systemctl status slurmd
● slurmd.service - Slurm node daemon
     Loaded: loaded (/etc/systemd/system/slurmd.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2023-07-02 13:45:56 JST; 30min ago
   Main PID: 26767 (slurmd)
      Tasks: 1
     Memory: 1.1M
        CPU: 15ms
     CGroup: /system.slice/slurmd.service
             └─26767 /usr/local/sbin/slurmd -D -s

 7月 02 13:45:56 guilty8-e5800t110fe systemd[1]: Started Slurm node daemon.
 7月 02 13:45:56 guilty8-e5800t110fe slurmd[26767]: slurmd: slurmd version 23.02.3 started
 7月 02 13:45:56 guilty8-e5800t110fe slurmd[26767]: slurmd: slurmd started on Sun, 02 Jul 2023 13:45:56 +0900
 7月 02 13:45:56 guilty8-e5800t110fe slurmd[26767]: slurmd: CPUs=8 Boards=1 Sockets=1 Cores=4 Threads=2 Memory=15931 TmpDisk=936080 Uptime=5890 CPUSpec>

と無事に起動した。めでたし。