cvl-robot's diary

研究ノート メモメモ https://github.com/dotchang/

Arduino MKR IoT CarrierとGrove SCD30搭載CO2センサを使って、長期間の環境測定をしてみる。

人間と共生するロボットは、24時間365日電源を切らないで稼働しなければならないと思うけれど、メンテナンスや省エネのために人間の睡眠に相当するような休息は必要で、ロボットはいつ寝るべきかを調べたいので、お手軽に環境測定と記録が可能なデバイスを用意してみました。
ハードウェアもクラウド連携のソフトウェアも、とても良くできているので、ほとんど買ってくるだけで完了しますが、備忘録のためのソースコードを残します。

購入したもの

KEEPPOWER 3500mAh P1835J 保護回路付き リチウムイオンバッテリー 日本製セル (2本組)
f:id:cvl-robot:20211116143419j:plain
Arduino MKR IoT CarrierとGrove SCD30搭載CO2センサ(表)
f:id:cvl-robot:20211116143431j:plain
Arduino MKR IoT CarrierとGrove SCD30搭載CO2センサ(裏)

用意したもの

WiFi接続のインターネット
Arduino IoT Cloudの有料サービス(1000円/月ぐらいのやつ)

制限

時間はUTCです。JSTに直すのは、面倒くさい処理のオーバーヘッドになるので止めました。
また、ミリ秒以下が取れていません。単純にその秒内での何回目を示すカウンタで代替しています。

ソースコード

// WiFiNINA - Version: 1.8.13
#include <WiFiNINA.h>
#include <RTCZero.h>
int day;
int month;
int year;
int hours;
int minutes;
int seconds;
int millisCount;

#include "thingProperties.h"
#include <Arduino_MKRIoTCarrier.h>
MKRIoTCarrier carrier;

#include "SCD30.h"

#if defined(ARDUINO_ARCH_AVR)
#pragma message("Defined architecture for ARDUINO_ARCH_AVR.")
#define SERIAL Serial
#elif defined(ARDUINO_ARCH_SAM)
#pragma message("Defined architecture for ARDUINO_ARCH_SAM.")
#define SERIAL SerialUSB
#elif defined(ARDUINO_ARCH_SAMD)
#pragma message("Defined architecture for ARDUINO_ARCH_SAMD.")
#define SERIAL SerialUSB
#elif defined(ARDUINO_ARCH_STM32F4)
#pragma message("Defined architecture for ARDUINO_ARCH_STM32F4.")
#define SERIAL SerialUSB
#else
#pragma message("Not found any architecture.")
#define SERIAL Serial
#endif

File myFile;
String filename;


/* Create an rtc object */
RTCZero rtcz;

void loopRTC()
{
  static int lastSeconds = -1;

  day = rtcz.getDay();
  month = rtcz.getMonth();
  year = rtcz.getYear();
  hours = rtcz.getHours();
  minutes = rtcz.getMinutes();
  seconds = rtcz.getSeconds();

  // Print date...
  print2digits(day);
  Serial.print("/");
  print2digits(month);
  Serial.print("/");
  print2digits(year);
  Serial.print(" ");

  // ...and time
  print2digits(hours);
  Serial.print(":");
  print2digits(minutes);
  Serial.print(":");
  print2digits(seconds);
  Serial.print(":");
  if (lastSeconds != seconds) {
    millisCount = 0;
  }
  else {
    millisCount += 1;
  }
  lastSeconds = seconds;
  print2digits(millisCount);

  Serial.println();
}

void print2digits(int number) {
  if (number < 10) {
    Serial.print("0"); // print a 0 before if the number is < than 10
  }
  Serial.print(number);
}

void loopSCD() {
  float result[3] = {0};

  if (scd30.isAvailable()) {
    scd30.getCarbonDioxideConcentration(result);
    CO2Grove = result[0];
    temperatureGrove = result[1];
    humidityGrove = result[2];
  }
}

void setup() {
  // Initialize serial and wait for port to open:
  Serial.begin(115200);
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  delay(1500);

  Serial.println("SCD30 Raw Data");
  scd30.initialize();

  //connect to WiFi
  Serial.print("Connecting to ");
  Serial.println(SSID);
  WiFi.begin(SSID, PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" CONNECTED");

  rtcz.begin(); //+
  unsigned long epoch;
  int numberOfTries = 0, maxTries = 16;
  do {
    epoch = WiFi.getTime();
    numberOfTries++;
  }
  while ((epoch == 0) && (numberOfTries < maxTries));
  Serial.println(epoch);
  Serial.println(numberOfTries);
  if (numberOfTries == maxTries) {
    Serial.print("NTP unreachable!!");
    //while (1);
  }
  else {
    Serial.print("Epoch received: ");
    rtcz.setEpoch(epoch);
    Serial.println();
  }

  //disconnect WiFi as it's no longer needed
  WiFi.end();

  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  //Get Cloud Info/errors , 0 (only errors) up to 4
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();

  //Wait to get cloud connection to init the carrier
  while (ArduinoCloud.connected() != 1) {
    ArduinoCloud.update();
    delay(500);
  }

  delay(500);
  CARRIER_CASE = false;
  carrier.begin();
  carrier.display.setRotation(0);
  delay(1500);

  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  SDwritable = false;
  loopRTC();
  String dates = String(month) + String(day);
  String times = String(hours) + String(minutes); // within 8 characters + 3 charcters
  filename = dates + times + String(".txt");
  myFile = SD.open(filename);
  if (myFile) {
    Serial.print("Opening ");
    Serial.println(filename.c_str());
    myFile.println("Year, Month, Day, Hours, Minutes, Seconds, MillisCount, temperature, humidity, pressure, light, r, g, b, Gx, Gy, Gz, Ax, Ay, Az, CO2Grove, temperatureGrove, humidityGrove, batteryVoltage");
    myFile.close();

    SDwritable = true;
  }
}

void loop() {
  ArduinoCloud.update();
  carrier.Buttons.update();

  loopRTC();
  loopSCD();

  temperature = carrier.Env.readTemperature();
  humidity = carrier.Env.readHumidity();
  pressure = carrier.Pressure.readPressure();
  carrier.IMUmodule.readGyroscope(Gx, Gy, Gz);
  carrier.IMUmodule.readAcceleration(Ax, Ay, Az);

  batteryVoltage = analogRead(ADC_BATTERY) * 3.3f / 1023.0f / 1.2f * (1.2f + 0.33f); // https://mag.switch-science.com/2021/10/07/arduino-mkr-wifi-1010/
  battery = (batteryVoltage - 2.8f) / (4.208f - 2.8f) * 100; //for battery available in %

  while (!carrier.Light.colorAvailable()) {
    delay(5);
  }
  carrier.Light.readColor(r, g, b, light);

  if (carrier.Buttons.onTouchDown(TOUCH0)) {
    carrier.display.fillScreen(ST77XX_WHITE);
    carrier.display.setTextColor(ST77XX_RED);
    carrier.display.setTextSize(2);

    carrier.display.setCursor(20, 80);
    carrier.display.print("Temp: ");
    carrier.display.print(temperature);
    carrier.display.print(" C");

    carrier.display.setCursor(20, 120);
    carrier.display.print("TempGrove: ");
    carrier.display.print(temperatureGrove);
    carrier.display.print(" C");
  }

  if (carrier.Buttons.onTouchDown(TOUCH1)) {
    carrier.display.fillScreen(ST77XX_WHITE);
    carrier.display.setTextColor(ST77XX_RED);
    carrier.display.setTextSize(2);

    carrier.display.setCursor(20, 80);
    carrier.display.print("Humi: ");
    carrier.display.print(humidity);
    carrier.display.print(" %");

    carrier.display.setCursor(20, 120);
    carrier.display.print("HumiGrove: ");
    carrier.display.print(humidityGrove);
    carrier.display.print(" %");
  }

  if (carrier.Buttons.onTouchDown(TOUCH2)) {
    carrier.display.fillScreen(ST77XX_WHITE);
    carrier.display.setTextColor(ST77XX_RED);
    carrier.display.setTextSize(2);

    carrier.display.setCursor(30, 80);
    carrier.display.print("Light: ");
    carrier.display.print(light);

    carrier.display.setCursor(30, 110);
    carrier.display.print("RGB: ");
    carrier.display.setCursor(30, 125);
    carrier.display.print(r);
    carrier.display.setCursor(30, 140);
    carrier.display.print(g);
    carrier.display.setCursor(30, 155);
    carrier.display.print(b);
  }

  if (carrier.Buttons.onTouchDown(TOUCH3)) {
    carrier.display.fillScreen(ST77XX_WHITE);
    carrier.display.setTextColor(ST77XX_RED);
    carrier.display.setTextSize(2);

    carrier.display.setCursor(30, 80);
    carrier.display.print("Pressure: ");
    carrier.display.print(pressure);

    carrier.display.setCursor(30, 110);
    carrier.display.print("CO2Grove: ");
    carrier.display.print(CO2Grove);
  }

  if (carrier.Buttons.onTouchDown(TOUCH4)) {
    carrier.display.fillScreen(ST77XX_WHITE);
    carrier.display.setTextColor(ST77XX_RED);
    carrier.display.setCursor(70, 30);
    carrier.display.print("Gyroscope: ");
    carrier.display.setCursor(50, 45);
    carrier.display.print(Gx);
    carrier.display.setCursor(50, 60);
    carrier.display.print(Gy);
    carrier.display.setCursor(50, 75);
    carrier.display.print(Gz);

    carrier.display.setCursor(50, 120);
    carrier.display.print("Accelerometer: ");
    carrier.display.setCursor(50, 135);
    carrier.display.print(Ax);
    carrier.display.setCursor(50, 150);
    carrier.display.print(Ay);
    carrier.display.setCursor(50, 165);
    carrier.display.print(Az);
  }

  // if the file opened okay, write to it:
  myFile = SD.open(filename, FILE_WRITE);
  if (myFile) {
    myFile.print(year);
    myFile.print(",");
    myFile.print(month);
    myFile.print(",");
    myFile.print(day);
    myFile.print(",");
    myFile.print(hours);
    myFile.print(",");
    myFile.print(minutes);
    myFile.print(",");
    myFile.print(seconds);
    myFile.print(",");
    myFile.print(millisCount);
    myFile.print(", ");

    myFile.print(temperature);
    myFile.print(",");
    myFile.print(humidity);
    myFile.print(",");
    myFile.print(pressure);
    myFile.print(",");
    myFile.print(light);
    myFile.print(",");
    myFile.print(r);
    myFile.print(",");
    myFile.print(g);
    myFile.print(",");
    myFile.print(b);
    myFile.print(",");
    myFile.print(Gx);
    myFile.print(",");
    myFile.print(Gy);
    myFile.print(",");
    myFile.print(Gz);
    myFile.print(",");
    myFile.print(Ax);
    myFile.print(",");
    myFile.print(Ay);
    myFile.print(",");
    myFile.print(Az);
    myFile.print(",");
    myFile.print(CO2Grove);
    myFile.print(",");
    myFile.print(temperatureGrove);
    myFile.print(",");
    myFile.print(humidityGrove);
    myFile.print(",");
    myFile.println(batteryVoltage);

    // close the file:
    myFile.close();

    SDwritable = true;
  } else {
    // if the file didn't open, print an error:
    Serial.print("error opening ");
    Serial.println(filename.c_str());

    SDwritable = false;
  }

  if (humidity >= 60 && temperature >= 15) {
    weather_report = "It is very humid outside";

  } else if (temperature >= 15 && light >= 700) {
    weather_report = "Warm and sunny outside";

  } else if (temperature <= 16 && light >= 700) {
    weather_report = "A little cold, but sunny outside";
  }

  else {
    weather_report = "Weather is normal";
  }

}
/*
  Since TemperatureGrove is READ_WRITE variable, onTemperatureGroveChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onTemperatureGroveChange()  {
  // Add your code here to act upon TemperatureGrove change
}

Arduino IoT Cloudのダッシュボード画面

出来上がるとこんな感じ。メーター類は適当に配置。

f:id:cvl-robot:20211116143152p:plain
Arduino MKR IoT Carrierのサンプル画面

12月2日追記

Groveセンサーを付けていると、2時間ぐらいでハングアップしてしまいます。まだ、原因不明。

JINS MEME LoggerのデータをWebSocketServerを立てて取得してみる

新型JINS MEMEが安くておしゃれになっていたので、買ってみました。


ということを、事前に見かけていたので簡単にできるかと思ってやってみたら、本当に簡単にリアルタイムでデータが拾えたので、そのご報告です。

準備

1.新型JINS MEMEを買います。

フレーム形状は4種類、色は2種類、レンズはいろいろから選べます。

2.スマホを用意して、JINS MEME Loggerアプリ(¥120 iphone版、Androidは知らず)を購入します。
‎「JINS MEME Logger」をApp Storeで
JINS MEME Logger - Apps on Google Play

3.スマホとWebSocketServerを立てるPCがつながるローカルのWIFIルータを立てて、それぞれ繋ぎます。

4.WebSocketServerを立てるPCのIPアドレスを調べます。Windowsならcmd.exeを立ち上げてipconfigなど。IPアドレスはメモしておきます。また、データを送るのに使うポートがファイアウォールなどでふさがっていないか確認しておきます。

5.スマホアプリJINS MEME Loggerを起動して、JINS MEMEを接続します。接続後、アプリ中の「ロガー」や「グラフ」上でデータが取れているかどうかを確認して、うまくつながっていたら「設定」を開きます。
「設定」から「WebSocketクライアント」を「追加する」して、適当な「設定名」、「ip address」、「port」、「data type」を埋めたり選択します。
例.
設定名: MEME01
ip address: 192.168.10.17 (WebSocketServerのPCのIPアドレス)
port: 5001 (適度な範囲で適当に)
data_type: currentData (など。ロガーに詳細が出ているので、ほしいものを選ぶ。)

6.PCにPythonを入れて、websocket-serverのライブラリもpipで入れて、コマンドプロンプトからサーバーを実行。

7.スマホアプリの「設定」→「WebSocketクライアント」から登録してある設定を有効にする。うまくつながれば✓のマークが出てデータ送信が始まる。失敗すると×が出て止まる。

サーバー実行

#!/usr/bin/env python3
# reference: https://xp-cloud.jp/blog/2020/09/15/7564/
# pip install git+https://github.com/Pithikos/python-websocket-server

from websocket_server import WebsocketServer
import time
import json
import pprint

def start():
    # Connection event
    def new_client(client, server):
        print('New client {}:{} has joined.'.format(client['address'][0], client['address'][1]))
 
    # Disconnection event
    def client_left(client, server):
        print('Client {}:{} has left.'.format(client['address'][0], client['address'][1]))
 
    # Message receive event
    def message_received(client, server, message):
        # print(message)
        d = json.loads(message)
        # pprint.pprint(d, width=40)
	# currentData
        accX = d.get('accX')
        accY = d.get('accY')
        accZ = d.get('accZ')
        blinkSpeed = d.get('blinkSpeed')
        blinkStrength = d.get('blinkStrength')
        eyeMoveDown = d.get('eyeMoveDown')
        eyeMoveLeft = d.get('eyeMoveLeft')
        eyeMoveRight = d.get('eyeMoveRight')
        eyeMoveUp = d.get('eyeMoveUp')
        fitError = d.get('fitError')
        noiseStatus = d.get('noiseStatus')
        pitch = d.get('pitch')
        powerLeft = d.get('powerLeft')
        roll = d.get('roll')
        sequenceNumber = d.get('sequenceNumber')
        walking = d.get('walking')
        yaw = d.get('yaw')
        
        # debug
        print('accX:', accX, 'accY:', accY, 'accZ:', accZ)
        print('blinkSpeed:', blinkSpeed, 'blinkStrength:', blinkStrength)
        print('eyeMoveDown:', eyeMoveDown, 'eyeMoveLeft:', eyeMoveLeft, 'eyeMoveRight:', eyeMoveRight, 'eyeMoveLeft:', eyeMoveLeft, 'eyeMoveUp', eyeMoveUp)
        print('fitError:', fitError, 'noiseStatus:', noiseStatus)
        print('pitch:', pitch, 'roll:', roll, 'yaw:', yaw) 
        print('powerLeft:', powerLeft, 'sequenceNumber:', sequenceNumber, 'walking:', walking)

    # 5001番ポートでサーバーを立ち上げる
    server = WebsocketServer(port=5001, host='192.168.10.17')
    # イベントで使うメソッドの設定
    server.set_fn_new_client(new_client)
    server.set_fn_client_left(client_left)
    server.set_fn_message_received(message_received)
    # 実行
    server.run_forever()
 
if __name__ == "__main__":
    start()

実行すると、こんな感じ。

f:id:cvl-robot:20211015183450p:plain
JINS MEME LOGGERのWebSocketServerでデータ受信の実行結果

開発されたい方向け。このコードが良くまとまっていて使いやすそうです。

Google Mapで日本の「熊のオブジェ」を探しています。

街中の熊のオブジェをご存じでしたら、教えてください。
※絵にかかれただけの看板や、野生ではない生の熊は不要です。

1. 丸の内オアゾ
goo.gl

2. 展望レストランとかち亭
goo.gl

3. 国道274号シートベルトするベアー
goo.gl

4. 上原仲通り商店街
goo.gl

5. 熊のオブジェ
goo.gl

6. TED HYBER(テッド・イベール) a.k.a みどりのくまさん
goo.gl

7. 網元感動市場かに御殿
goo.gl

8. 大雄山駅
goo.gl

9. 宮城県美術館
goo.gl

10. 熊トイレ
goo.gl

11. 富山県美術館
goo.gl

12. 嵐山 りらっくま茶房
goo.gl

13. 苫前町郷土資料館
goo.gl

14. とままえだベアー
goo.gl

15. 金町公園
goo.gl

16. ゼビオ熊アリオ鷺宮
goo.gl

17. 奇岩親子熊岩
goo.gl

18. パンくま
goo.gl

19. 焼鳥くまちゃん
goo.gl

20. 多摩動物公園ツキノワグマ
goo.gl

21. くまぱんの屋根看板
goo.gl

22. 森のくまさん 米子店
goo.gl

23. 石窯パン工房森のくまさん 松江店
goo.gl

24. パンウルス 店内の壁に二頭
goo.gl

25. お食事とご宴会の店 くまげら
goo.gl

26. くまのがっこうジャッキーのゆめ
goo.gl

27. シュタイフ
goo.gl

28. モンベル熊 恵比寿店
goo.gl

29. cafe bear
goo.gl

30. ベーグルカフェクマナカ
goo.gl

31. 黒沼公園
goo.gl

32.
goo.gl

33.
goo.gl

34.
goo.gl

35. のぼりべつクマ牧場
goo.gl

36. 熊の穴
goo.gl

37. ルサフィールドハウス
goo.gl

38. 大矢相互不動産
goo.gl

39. 練馬区立美術の森緑地
goo.gl

40. 鳥取砂丘 砂の美術館
goo.gl

41. 光来福
goo.gl

以下、教えてもらったやつ

101. ぢっと手を見る
maps.app.goo.gl

102. FUJIMI CAFE
goo.gl

103. 余市駅前公園
goo.gl

104. テディベアキングダム
goo.gl

105. 房前公園
goo.gl

106. 北海道博物館
goo.gl

107. ウポポイ (民族共生象徴空間)
goo.gl

108. くまモンポート八代
goo.gl

109. 足柄山聖天堂
goo.gl

110. くまモンスクエア
goo.gl

111. くまくま園
maps.app.goo.gl

112. LINE FRIENDS FLAGSHIP STORE HARAJUKU
maps.app.goo.gl

113. わずん
maps.app.goo.gl

114. 三毛別ヒグマ事件復元地
maps.app.goo.gl

115. 小樽運河食堂
maps.app.goo.gl

116. 昭和新山熊牧場
maps.app.goo.gl

117. 北海道大学総合博物館
goo.gl

118. 北海道大学総合博物館 2Fへもどうぞ
goo.gl

119. 洲本市役所
goo.gl

120.
maps.app.goo.gl

121. クマカフェ
goo.gl

122. 上野動物園
maps.app.goo.gl

123. 越中國一之宮雄山神社 芦峅中宮祈願殿
maps.app.goo.gl

124. サクラマチクマモト
maps.app.goo.gl

125. 東京中央郵便局
maps.app.goo.gl

126. オリミネベーカーズ勝どき店
goo.gl

127. 阿寒湖アイヌコタン
maps.app.goo.gl

128. 質蔵屋
goo.gl

129. 群馬県立自然史博物館
goo.gl

130. 北海道大学総合博物館
goo.gl



201. サホロリゾート ベア・マウンテン
goo.gl

今日の本題

ハリオの新型ドリッパーが、一気にお湯を注いでも勝手に上手い具合に調整してくれて、とても良いらしい。
普通に単体で頼んじゃったけど、限定の「お試しキャンペーン」セットにしておけばよかった。

ROS用の小型PCが欲しいので、Intel NUC11PAKi5にUbuntuとROSを入れてみる

無駄に各種センサーを買い集めるのが趣味なので、ベロダインやらLavoxやらHokuyoやらの未使用のLidarが部屋に転がっていて、もったいないお化けが出そうな状態が続いています。
この問題を解決するために、せめて移動ロボットに搭載可能な小型PCを組んで、ROSでこれらのセンサーをすぐに動かせるようにしておきたいと思います。
結論としては、この記事では、Intel NUC11PAKi5でUbuntu20.10とROS Noeticが上手く動いたよー、ということを紹介します。

f:id:cvl-robot:20210428182131j:plain
Intel NUC11PAKi5でUbuntu 20.10とROS Noeticが何とか動いたの図

Intel NUC11を購入

世の中の流れを考えれば、Ryzen 4650Gあたりを積んだベアボーンキットを買ってくるのが、きっと正解なのですが、運悪くAKIBAWatchのこの提灯記事を読んで適当にぽちってしまいました。
akiba-pc.watch.impress.co.jp

Intel NUC11PAKi5

ark.intel.com
買ってから気が付いたのですが、本当に欲しかったのは、「NUC11TNKi5」でした。
Intel NUCは、Intelの製品紹介のページに写真は無いし、似た名前の製品が多すぎるのよなー。
note.com
現在の半導体危機につき、買ってしまったものは仕方ないので、「NUC11PAKi5」を使っていくことにします。

IntelAMDどっちを買うべきか?

計算性能の差は気にしなくても大丈夫そうですが、GPUや消費電力を見れば、AMDRyzenを選択するべきです。
www.4gamer.net

メモリとM2SSDを購入

SODIMM DDR4-3200 32GByte×2
Amazon | Crucial 32GBシングルDDR4 3200 MT/s CL22 SODIMMメモリモデルCT32G4SFD832A | Crucial(クルーシャル) | メモリ 通販
NVMe M2 2280 Gen4 1TB
Amazon | CFD販売 SSD 1TB 内蔵 M.2-2280(MVMe) 接続 PCIe Gen4x4 CFD PG3VNDシリーズ CSSD-M2B1TPG3VND | CFD販売 | 内蔵SSD 通販
とりあえず多けりゃ良いだろ精神で、対応規格のもので、入手可能なメモリ類を手配しました。

f:id:cvl-robot:20210427180720j:plain
間違えて買ったPAKi5に、メモリを一杯積んで過剰投資を行うの図

Ubuntuをインストール

Ubuntuに入れるROSのバージョンは、Ubuntuのバージョンに強く拘束されていて、基本的にLTSパッケージにしかapt-getで入れられるような便利なインストール方法は対応していません。
そこで、希望のROSバージョンに合わせてUbuntuを入れていくという試行錯誤をしました。

Ubuntu 18.04LTS

まず、本当に使いたいのはROS Melodicなので、対応しているUbuntu18.04LTSをインストールしました。
結果は、Ubuntu本体のインストールは可能ですが、Intel Iris Xe GPUに対応したドライバがどうしても見つけられませんでした。
仕方ないので、ここからapt-get dist-upgradeしようとしたら、クラッシュしてOSがつぶれました。

f:id:cvl-robot:20210427182450j:plain
Ubuntu自体は入ってくれるが、なにやらエラーを一杯吐いているっぽいの図

Ubuntu 20.04LTS

Melodicが良いのは、参考にしたい論文が使っていたから、とかその程度の理由なので、新しいバージョンのROS noeticでも動いてくれれば問題ありません。
そこで、Ubuntu20.04LTSをインストールしてみましたが、18.04LTSと同様に、インストールは可能だが、グラフィックチップのドライバが見つからない、という問題が出ました。
そもそもインストール画面から解像度が合っていないので、画面が見切れてしまい、インストール作業にも難儀します。多少もがいてみましたが、解決方法が見つからないので諦めました。

Ubuntu 20.10

長期サポート版のLTSではないものの、リリースからそれなりに時間が経っていて、安定して動くことが期待できるだろう20.10を次にインストールしてみました。
すると、インストール画面から正しい解像度で動いてくれて、すんなりUbuntuのインストールは完了します。
有線ネットワークも無線WiFiも問題なく動いてくれます。
画面が表示されて、ネットワークにさえつながってくれれば、こっちの勝ちのはずなので喜んでいたら、新しい問題が見つかりました。ROS Noeticが入らないのです。

ROS Noeticをインストール

昔のバージョンのROSだと、ちょっとリポジトリの登録をごにょごにょするだけで、LTSじゃなくても簡単にインストールできた記憶が有ったので、今回もそのつもりでいたのですが、どうにもこうにもお手軽な方法では入ってくれそうもありません。
失敗報告を書いている人もいますね。
qiita.com

ただ、幸いなことにROSはオープンソースなので、自分でコンパイルすることができます。ひたすら面倒くさいですが、動くはずです。ということで、やってみました。結論から言うと、入るし動きます。
まず、参考にするべきページはこちらです。
wiki.ros.org
指示に従って、

$ sudo apt update
$ sudo apt remove rosdep rospkg rosinstall vcstools
$ sudo apt autoremove

$ sudo apt-get install python3-rosdep python3-rosinstall-generator python3-vcstool build-essential
$ sudo rosdep init
$ rosdep update
$ mkdir ~/ros_catkin_ws
$ cd ~/ros_catkin_ws

ここまでは、すんなりいくかと思います。(ただ、自分は途中で失敗して試行錯誤して、消したり入れなおしたりしているので、本当にすんなりいくかの自信はないです・・・)
つぎに、マニュアルではdesktopパッケージを入れるように指示していますが、これだとpclやらgazeboやらが入ってくれません。そこで、desktopパッケージの代わりにdesktop_fullパッケージを入れるように書き換えます。

$ rosinstall_generator desktop_full --rosdistro noetic --deps --tar > noetic-desktop_full.rosinstall
$ mkdir ./src
$ vcs import --input noetic-desktop_full.rosinstall ./src

次に、依存関係のあるライブラリをインストールするのですが、ubuntuのバージョンをfocal(20.04LTS)に偽装するために、--os=ubuntu:focalを付けます。

$ rosdep install --from-paths ./src --ignore-packages-from-source --rosdistro noetic --os=ubuntu:focal -y

ここで、上手く入ってくれれば良いのですが、先に入っているライブラリやらなんやらで上手く動かないことが有るようです。少なくとも、最初に試した時はダメでした。
そんな時は、apt removeでros関係のライブラリを片っ端からアンインストールして、最初から再インストールすると上手く行くようです、知らんけど。
historyをとってあるので、あとで時間が有ったら整理して確認します。

適当に入れたライブラリ群。多分、間違いや無駄が多いです。あくまで、参考まで。

409 sudo apt install gazebo
412 sudo apt reinstall python3-rospkg
417 sudo apt reinstall python3-ros
419 sudo apt reinstall python3-catkin-pkg
422 sudo apt reinstall python3-pkg-resources
425 sudo apt install libgazebo-dev
436 sudo apt reinstall libgazebo11
439 sudo apt reinstall python3-catkin
441 sudo apt reinstall python3-rospkg
447 sudo apt install libdart-all-dev
450 sudo apt install libdart-external-odelcpsolver-dev

ROSをビルドします。ros_catkin_ws/install_isolatedがROSのインストール先になります。

$ ./src/catkin/bin/catkin_make_isolated --install -DCMAKE_BUILD_TYPE=Release

次のコマンドを実行するか、~/.bashrcの一番最後の行に追加しておきます。

$ source ~/ros_catkin_ws/install_isolated/setup.bash

rosdepで見かけ上、依存ライブラリのインストールが上手く行っていても実際に進めてみると、いくつか引っかかるライブラリが出てくるかもしれません。
ただ、そのほとんどは、sudo apt install [library-name]-devをその都度してやれば、そのうち先に進めます。

普通のcatkin_wsを作って、velodyneのドライバーをインストール

471 mkdir catkin_ws
473 cd catkin_ws/
487 mkdir src
489 cd src
490 git clone https://github.com/ros-drivers/velodyne
491 ls
492 cd ..
493 catkin_make
494 sudo apt search pcap
495 sudo apt install libpcap-dev
496 catkin_make

次のコマンドを実行するか、~/.bashrcの一番最後の行に追加しておきます。

source ~/catkin_ws/devel/setup.bash

今日の本題

本格クロカン4駆ラジコンのロッククローラが欲しい。
アキシャルSCX10IIIが最強とのことなのですが、コロナのせいでラジコンが人気過ぎて在庫が見つかりません。

駆動モータはESC内蔵のこれがよさそう。1200KVと1800KVでどのくらい実質的にトルクが違うのか、気になるところ。
amzn.to

サーボモータはピンキリだけど、DCブラシレスにこだわってみよう。
Amazon | Savox Sb-2270sg High Voltage Brushless Digital Servo (.12s/13130ml-in) Sb2270sg | ラジコン・ドローン 通販


(続く)

Ubuntu18.04LTSで、Logitech G29 Steering Wheel Controllerを動かそうとして、はまった点2点のメモ

www.codetd.com
すでにROS melodicのインストールは済んでいたので、こちらのサイトを参考に超簡単じゃーんとか思いながら

sudo apt-get install ros-melodic-pacmod*
sudo apt-get install ros-melodic-joy*

をして、必要なドライバ類をインストールしました。

AXISが反応しない問題

jstestやjstest-gtkで見てみても、上記サイトのlaunchファイルを作ってTopicを観察したりしても、

ボタンは反応するけど、ステアリングやアクセルなどの軸が反応しない。

という問題に遭遇しました。

Ubuntuカーネルをhweの最新のものにしてみたり、xboxdrvを試してみたり、xserverのjoystickドライバを入れてみたり、
あれこれ試行錯誤しようとしてみたのの上手く行かないので、半日ぐらい途方に暮れていたら、
G29ステアリングの上に知らない変なボタンが有ることに気が付きました。

左右の選択式スイッチになっているコレ

f:id:cvl-robot:20210414094950p:plain
Logitech G29のPS3-PS4切り替えスイッチ

を試しに、逆にしてみると動きました!後から調べてみると、これはPS3モードで動かすかPS4モードで動かすかの切り替えスイッチだそうです。
マニュアルを読めば書いてあったのかも知らんので、ちゃんと読まないとだめですね。

f:id:cvl-robot:20210414085813j:plain
g29がUbuntu18.04LTSで動いた

ステアリングやクラッチペダルでマウスカーソルが動いてしまう問題

マウスカーソルは、マウスで動かすことができるのですが、ほっておくとカーソルが右や左に流れて言ってしまいます。
調べてみると、ステアリングホイールに反応しているようです。
これも原因が分からずに途方に暮れていたら、xserverのjoystickドライバをインストールしていたことを思い出しました。
多分、いらん子なので

sudo apt-get remove xserver-xorg-input-joystick

で排除してから、再起動してG29をjstestで確認すると、無事に動くことが確認できました!ちゃんと、ステアリングやペダルの軸やボタンだけでなく、シフトレバーもボタンとして認識してくれます。

今日の本題 THX-AAAが欲しい

フィードフォワードで謎補正を掛けることによって脅威の低歪み率を実現するアンプ技術THX-AAAを使ったデバイスが各社から出ています。

Benchmark社

一番良さそうなのは、アメリカのBenchmark社が出している製品、ヘッドホンアンプHPA4やパワーアンプAHB2です。
ただし難点があって、各40万円以上と価格が異様に高いことです。エミライが代理店になので修理保証は安心できるのは良いですが、いかんせん税率高すぎませんかね。。。
www.benchmarkmedia.jp

日本製

日本製が良いのですが、残念ながら該当製品は見つかりませんでした。

中華製 Fiio, S.M.S.L, Drop

中華アンプメーカーから良さげな製品がいくつか出ています。3社見つかり、FiioとS.M.S.LとDropです。
Fiioはもう完全にHiFiヘッドホン界でも市民権を得た処か、AKやソニーに比肩するレベルのいい製品を作っていますね。
Amazon | 【国内正規品1年保証】FiiO Q3 DSD512 | 768K/32ビット AK4462DAC THX AAAアンプテクノロジー 携帯電話&PC用 (2.5/3.5/4.4mm) 出力付き | Fiio | ヘッドホンアンプ
このポタアンとか値段も安いしとても気になります。工場を燃やされてしまったAKMのDACを使っているのでいつまで製品供給が続くかは気がかりです。
安いアンプに興味は無かったのでS.M.S.Lは今回調べるまで知らないメーカーだったのですが、Toppingのように安くて使い勝手の良さそうなアンプ製品群を一杯ラインナップしているようです。
ヘッドホンアンプですが、プリアンプにもなるコレとか良さそうです。回路はメーカーの評価用デザインをそのまま使っているように見えますので、余計なことをしてなさそうなのも魅力です。
Amazon | S.M.S.L SP400 ヘッドホンアンプ フルバランス THX AAA-888アンプ回路 XLRバランス出力・入力端子付き プリ HIFI アンプ | S.M.S.L | ヘッドホンアンプ
Dropはアマゾンでの取り扱いが無いようなので、入手難なのが問題ですが、良さげなヘッドホンアンプを出しています。
Drop + THX AAA™ 789 Linear Amplifier | Price & Reviews | Drop

本家THX

THX-AAAのチップにもいくつかのランクがあり、S/N, THDに-10db単位の違いが有るようです。
www.thx.com
どれも興味あるけどピンとこないなー、とぼやぼやしてたら、本家のTHXからTHX-AAA搭載ポータブルヘッドホンDACアンプが2万円ぐらいで販売されるとのニュースがありました。
しばらく、これかな!発売待ち!
www.phileweb.com

Adobe CC学割ライセンス自動更新の罠の回避方法

Amazon Creative Cloudの学割ライセンス、1年目はとてもお安いのですが、自動更新にしておくと2年目以降は3000円/月ぐらい(39,336 円 / 年)に値上がりしてしまいます。
この出費をできるだけ抑えるための方法として、パッケージ販売されているライセンスを購入して、自分のアカウントに追加しておく、という方法が有ります。

具体的には、

これを買ってください。
1年版でも、2年版でも、3年版でも、単価は変わらないようです。

2021年4月2日現在の情報として、

【アドビ製品がお買い得】セール開催中:4/9(金) 23:59まで

とのことで、5%ほどお安いようです。

ライセンス更新時期以前なら、先に購入して登録しておくことで、ライセンス適用期間が積み増しされるようなので安心です。
ご参考まで。

本日の買い物メモ

カラーチャート
https://amzn.to/2PjlSu5

自作系四足歩行ロボットが最近熱いようなので情報収集。2

ちゃんと整理する時間も余裕もないから、差分を並べてみて楽しむだけ。

見かけた自作系四足歩行ロボット

fabcross.jp

EZH ANYmal


Boston Dynamics Spotと自動走行のAutoware

YUKI. 見るたび進化していて楽しい。


A1とは違うの?


2足だけど4足っぽい


好き

機構・センサなど、要素部品

磁石を使った変速機。効率とか、ノイズとか、ちゃんと研究してみたい。


すごく魅力的な足裏センサ。二足。

ぷらぎあ on Twitter: "イグスのこれすごい
https://t.co/eUXaaXsrWB
https://t.co/tAKegs1xEq… "

Open Dynamic Robot Initiative


2足だけどカッコイイ

ソフトウェア・シミュレータ等


Fusion360からURDF


2足の綺麗な歩行シミュレーション。まだ軌道だけかな?


強化学習でラフテライン

その他


アリは歩数で移動距離を把握している


別件