cvl-robot's diary

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

Arduino Uno R3とCAN-BUS Shieldを使って、DDT M15モータをテストしたときのコード

DDT M15[1]は、Switch Scienceで輸入されている中国製の安いけど比較的よくできたモーターです。
いいところは置いておいて、問題点だけ列挙しておきます。
・マニュアルがなんか変
・電源電圧の範囲が狭い。24Vが規定電圧で、下限が22V以上で、動作する上限の記述がなし。瞬間最大定格は63V。18V-22Vに落ちても即停止とはならないけれど、アラームが付く。つまり、かなり安定した電源を用意しなければならない。
・初代からバージョンが進んでいるのに、速度制御が怪しいらしい。
・タイヤの交換できなさそう。タイヤの向きの変更方法も不明。

Switch Scienceの方がサンプルコードを公開してくださっているのですが、面倒くさいことにGo言語なので、Arduino用にちょっと書き直してみます。
高トルクモーターDDT-M15の使い方 #DirectDriveTech — スイッチサイエンス

使用機材は、Arduino Uno R3とSeed Studio CAN-BUS Shild v2です。
また電源は、OWON SPE6103(60V/10A)です。最大出力電流がモーターの最大負荷時の電流未満なので、最大負荷テストはできません。
テスト用の土台は、ダイソーで買ってきた木材とアルミのは材で製作しました。

DDT M15のテスト環境


www.youtube.com

// demo: CAN-BUS Shield, send data
// loovee@seeed.cc


#include <SPI.h>

#define CAN_2515
// #define CAN_2518FD

// Set SPI CS Pin according to your hardware

#if defined(SEEED_WIO_TERMINAL) && defined(CAN_2518FD)
// For Wio Terminal w/ MCP2518FD RPi Hat:
// Channel 0 SPI_CS Pin: BCM 8
// Channel 1 SPI_CS Pin: BCM 7
// Interupt Pin: BCM25
const int SPI_CS_PIN  = BCM8;
const int CAN_INT_PIN = BCM25;
#else

// For Arduino MCP2515 Hat:
// the cs pin of the version after v1.1 is default to D9
// v0.9b and v1.0 is default D10
const int SPI_CS_PIN = 9;
const int CAN_INT_PIN = 2;
#endif


#ifdef CAN_2518FD
#include "mcp2518fd_can.h"
mcp2518fd CAN(SPI_CS_PIN); // Set CS pin
#endif

#ifdef CAN_2515
#include "mcp2515_can.h"
mcp2515_can CAN(SPI_CS_PIN); // Set CS pin
#endif

byte canTx(int id, unsigned char stmp[8], int delay_ms = 10 )
{
    // send data:  id = 0x00, standrad frame, data len = 8, stmp: data buf
    byte ret = CAN.MCP_CAN::sendMsgBuf(id, 0, 8, stmp);
    delay(delay_ms);                       // send data per 10ms
    SERIAL_PORT_MONITOR.println("CAN BUS sendMsgBuf ok!");

    return ret;
}

byte canRx(byte& len,unsigned long &id, unsigned char *buf)
{
    byte ret = 0;
    if (CAN_MSGAVAIL == CAN.checkReceive()) {         // check if data coming
        ret = CAN.readMsgBuf(&len, buf);              // read data,  len: data length, buf: data buf

        unsigned long canId = CAN.getCanId();

        SERIAL_PORT_MONITOR.println("-----------------------------");
        SERIAL_PORT_MONITOR.print("get data from ID: 0x");
        SERIAL_PORT_MONITOR.print(canId, HEX);
        SERIAL_PORT_MONITOR.print("\t");

        for (int i = 0; i < len; i++) { // print the data
            SERIAL_PORT_MONITOR.print(buf[i]);
            SERIAL_PORT_MONITOR.print("\t");
        }
        SERIAL_PORT_MONITOR.println();
    }  
}

void setup() {
    SERIAL_PORT_MONITOR.begin(115200);
    while(!Serial){};

    while (CAN_OK != CAN.begin(CAN_1000KBPS)) {             // init can bus : baudrate = 500k
        SERIAL_PORT_MONITOR.println("CAN init fail, retry...");
        delay(100);
    }
    SERIAL_PORT_MONITOR.println("CAN init ok!");
    
    byte len = 0;
    unsigned long id = 0;
    unsigned char recv_buf[8];

    // send data:  id = 0x00, standrad frame, data len = 8, stmp: data buf
    unsigned char motor_can_terminal_resistor_switch_setting[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    canTx(0x109, motor_can_terminal_resistor_switch_setting);
    canRx(len, id, recv_buf);

    unsigned char set_status_report[8] = {0x80, 0, 0, 0, 0, 0, 0, 0};
    canTx(0x106, set_status_report);
    canRx(len, id, recv_buf);

    unsigned char setting_mode_and_feedback[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    canTx(0x105, setting_mode_and_feedback);
    canRx(len, id, recv_buf);
}

void loop() {
  byte len;
  unsigned long id;
  unsigned char recv_buf[8] = {0, 0, 0, 0, 0, 0, 0, 0};

  unsigned char query_operation[8] = {0x01, 0x01, 0x02, 0x04, 0x55, 0, 0, 0};
  canTx(0x107, query_operation);
  canRx(len, id, recv_buf);

  unsigned char sending_in_openloop[8] = {0x10, 0x01, 0, 0, 0, 0, 0, 0};
  canTx(0x32, sending_in_openloop);
  canRx(len, id, recv_buf);
}

// END FILE

ボリュームで回転速度を調整できるように修正。中央が0なことに注意。

void loop() {
  byte len;
  unsigned long id;
  unsigned char recv_buf[8] = {0, 0, 0, 0, 0, 0, 0, 0};

  int i = analogRead( PIN_ANALOG_INPUT );
  float RANGE_MAX = 32767.f;
  float RANGE_MIN = -32767.f;
  float f = (float)i / 1023.f * (RANGE_MAX-RANGE_MIN) + RANGE_MIN;
  Serial.println( f );

  unsigned char query_operation[8] = {0x01, 0x01, 0x02, 0x04, 0x55, 0, 0, 0};
  canTx(0x107, query_operation);
  canRx(len, id, recv_buf);

  unsigned char data0 = ((int)f >> 8) & 0xff;
  unsigned char data1 = ((int)f) & 0xff;
  unsigned char sending_in_openloop[8] = {data0, data1, 0, 0, 0, 0, 0, 0};
  canTx(0x32, sending_in_openloop);
  canRx(len, id, recv_buf);
}

[1] DDT M1502D_233 タイヤ付きダイレクトドライブモーター 24V/9.6Nm/115rpm — スイッチサイエンス

ChatGPT(GPT-4)で言葉の論理演算をしてみる

4種類の論理回路があれば、CPUのような複雑な論理回路でも作れるはずです。そこでGPT-4で言葉の論理演算ができるかどうかを試してみました。

NOT

AND

OR

XOR

ここまでは文句のつけようが無いですね。

T-FLIPFLOP

多分プロンプトが悪いのか、真面目過ぎるのか思ったような答えがもらえませんでした。上手いプロンプトを見つけたら教えてください。

2022/03/27追記

  • 気が向けば、つづく。

メモ:お手軽にWebRTCでVideoChatをしたいので、Janus-gatewayをAWS EC2やRaspberry Pi4上に立てたときのメモ

(まだ書きかけ)
 今、ネットワーク越しに動画データを送りたいと考えたときに、どうしてもWEBRTCがその候補に挙がってきます。WEBRTCのライブラリやサービスは山のようにあるのですが、進歩が速いことを理由に行き届かないことが多くて、どれもこれも中途半端で問題だらけです。

 まずWEBRTCが何なのかを把握するために、次の4つぐらいの区別が付くようにしとく必要が有ります。
・WEBRTC(P2P): 1対1で繋ぐとき。(もともとの設計。簡単。)
・WEBRTC SFUサーバー: サーバーを中心の媒介として複数間をつなぐとき
(・WEBRTC MCUサーバー): サーバーを中心の媒介として処理をさせつつ複数間をつなぐとき。使う機会はあまりなさそう。
・WEBRTCクライアント: ユーザーがWEBRTCを使うときのインターフェース

 インターネットに繋いでNAT越えをするときには、次も知っておく必要が有ります。WEBRTCが難しいとか言われるのは、むしろこっちな気がする。
・STUNサーバー: インターネットから自分クライアントのネットワークがどのように見えているかを教えてくれるもの
・TURNサーバー: NAT越しのデータをやり取りするときに中継してくれるもの。サービスをお金を払って買うのでなければ、基本的にcoturnとかでPublicIPのサーバーを自分で用意する必要がある。
逆に言うとインターネットに繋ぐ必要が無ければ、こんなの要らないんです。SoftetherVPNを繋げても構わないとかいう環境だったら、そうしちゃった方が早いし安い。

 今やりたいことは自分の管理下にあるローカルネットワークで動画を転送したいだけです。なので、GstreamerでもFFMpegでも良いっちゃ良いのですが、なんだかトラブルだらけであまり直に触りたくありません。OBSが遅延無く配信してくれればそれでも十分なのですが、遅延が大きいのが解決できません。遅延が少ないことを前提にするとWEBRTCに乗っかるのが早い、となります。

 お金がたっぷりあれば、時雨堂さんのSora Cloudが抜群に良いと思います。簡単に圧倒的な高画質を実現できるし、NAT越えに困ることも無いでしょう。HololensやUnity用でクライアントを作るためのSDKも用意されていて、他には類似の良いものがあまり見つけられません。(結局止めてたらしい。)
でも、固定費を年間60万円とか120万円とかそれ以上払える場合に限りますし、ビジネス展開することが前提のサービスです。また、海外との接続は対象外になっています。あとクライアントのためのSDKを作ってすぐ止めてしまう(ROSとか。。。Momoも音声デバイス選択とか放置されているし)ので、開発コストを掛けるリスクが取れません。

 ちょっとWEBRTCを試してみたいとか、ビジネスとしてサービスを提供するのでなければ、オープンソースで開発されているWEBRTCサーバーが選択肢に入ってきます。
・Mediasoup
・Janus-gateway
が有名どころです。たいてい有料のものも含めて、どのサービスもクライアントは自分で作れ、というスタンスなのですが、開発コストを掛けたくないので、良いサンプルがあるかどうかがとても重要になってきます。

 Githubに落ちているクライアントをつまみ食い程度に試した印象だと、Mediasoupは2020年以前のクライアントサンプルがことごとくまともに動かない感じがします。WEBインターフェースのgetUserMediaがhttps必須になった後のもので、使いやすい感じのものが見つけられませんでした。(Electronで動いて欲しかった、けど、分からなかった。)
Janus-Gatewayは、自身が用意しているWEBサンプルが充実しているのと、中国人の方が作りかけているQtとC++を使ったインターフェースが動くには動いてくれました。中途半端でもC++で書いてあってくれれば、あとはこっちで何とかなるので大変に有難い。ただ残念ながらGithubに上がっているJanus-Gateway向けの他のクライアントの例は多くは有りませんでした。

ということで、個人レベルでWEBRTC SFUを使うなら、Janus-gatewayの公式サンプルが良さそうです。

メモ:Janus-gatewayを立てて、推奨画質やビットレートの目標値を設定するのに編集が必要だったファイルはこちら。
nano /var/www/html/janus.js
sudo nano /opt/janus/etc/janus/janus.jcfg
sudo nano /opt/janus/etc/janus/janus.plugin.videoroom.jcfg

リンク
[0] https://github.com/meetecho/janus-gateway
[1] https://www.mikan-tech.net/entry/2020/05/02/173000
[2] https://www.mikan-tech.net/entry/2020/05/02/173000
[3] https://qiita.com/mksamba/items/601ae5b738b16b2b81d3
[4] https://github.com/meetecho/janus-gateway/issues/1354
[5] https://qiita.com/mksamba/items/601ae5b738b16b2b81d3
[6] https://towardsaws.com/setting-up-janus-webrtc-on-aws-a8aa8914b0c6
[7] https://qiita.com/satosisotas/items/d154b168772982a74b8d
[8] https://stackoverflow.com/questions/60261648/ice-failed-for-component-1-in-stream
[9] https://qiita.com/akase244/items/09d0898e51cf1b0faad7


AWS
[/] https://qiita.com/kanegoon/items/4bcdf5184cf1752eb44f

メモ:"qt.qpa.plugin: Could not find the Qt platform plugin "windows" in "" error" がWindows環境vcpkgでopenCVをインストールしたときに出てきてしまった場合の対処法

OpenCVをgstreamer付でvcpkgでインストールして、起動しようとしたらqt絡みの謎のエラーが出てきました。

vcpkg install opencv[gstreamer,cuda,world]:x64-windows

ググるPython絡みの解決方法ばかりだったので、MSVC(vcpkg)でc++で解決したいときのメモを残しておきたいと思います。

環境変数QT_QPA_PLATFORM_PLUGIN_PATHの定義

環境変数QT_QPA_PLATFORM_PLUGIN_PATHを新しく定義して、vcpkgの中に作られているplatformsフォルダを指定します。ユーザー変数で構いません。
D:ドライブ直下にvcpkgをインストールしているときの例はこんな感じ。
QT_QPA_PLATFORM_PLUGIN_PATH
D:\vcpkg\installed\x64-windows\plugins\platforms

QT_QPA_PLATFORM

ただし、起動が妙に重い。。。OpenCVにQt要らなくない?

オムロンの環境センサ2JCIE-BU01の公式サンプルにちょっと足して、シリアルナンバーを取得できるようにする

オムロンの環境センサ2JCIE-BU01は1万円ぐらいの価格で、USBドングルぐらいの大きさで、一杯データの取れる優秀な奴です。Bluetoothも内蔵していますので、有線でも無線でも使えます。
問題点があるとすれば、名前が覚えにくいことぐらいですね。ずっと使っていますが、いまだにうろ覚えです。

pythonの公式サンプルコードが公開されていて、自前のプログラムからすぐに呼び出して使うことができます[1]。
ただ複数台使おうとすると、このサンプルだとシリアル番号を取ることができないので、個体特定が面倒くさいです。ちょっと書き足してシリアルを取ればいいだけなので、やってしまいましょう。
最初からシリアルも取れるサンプルプログラムを公開されている方もいらっしゃる[2]ので、そちらを使われてもいいかもしれません。

データの書式は公式ユーザーマニュアルの70-73ページあたりのcommon frame formatとpayload frame formatに書かれています。(マニュアルの最初に書いてほしい・・・)
https://components.omron.com/jp-ja/ds_related_pdf/CDSC-016.pdf

前略

def now_utc_str():
    """
    Get now utc.
    """
    return datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")

def print_info(info):
    """
    print device information.
    """
    model_number = info[7:17].decode()
    serial_number = info[17:27].decode()
    firmware_revision = info[27:32].decode()
    hardware_revision = info[32:37].decode()
    manufacture_name = info[37:42].decode()
    print("")
    print("Model number:" + model_number)
    print("Serial Number:" + serial_number)
    print("Firmware Revision:" + firmware_revision)
    print("Hardware Revision:" + hardware_revision)
    print("Manufacturer Name:" + manufacture_name)

if __name__ == '__main__':

    # Serial.
    ser = serial.Serial("COM3", 115200, serial.EIGHTBITS, serial.PARITY_NONE)

    # Get Device Information 0x180A

    # common frame format is Header(0x52, 0x42: 2bytes) + Length(Payload-CRC-16: 2bytes) + Payload(n bytes) +CRC-16(2bytes) by little endian
    # payload frame format is Command(0x01:Read, 0x02:Write, 1byte) + Address(2bytes) + Data(n bytes)
    command = bytearray([0x52, 0x42, 0x05, 0x00, 0x01, 0x0a, 0x18])
    command = command + calc_crc(command, len(command))
    tmp = ser.write(command)
    time.sleep(0.1)
    info = ser.read(ser.inWaiting())
    # common frame format is Header(0x52, 0x42: 2bytes) + Length(Payload-CRC-16: 2bytes) + Payload(n bytes) +CRC-16(2bytes) by little endian
    # payload frame format is Command(0x01:Read, 0x02:Write, 1byte) + Address(2bytes) + Data(n bytes)
    print_info(info)
    time.sleep(1)

    try:
        # LED On. Color of Green.

後略

[1] omron-devhub/2jciebu-usb-raspberrypi
github.com
[2] nobrin/omron-2jcie-bu01
github.com

今日の本文

Whisperで僕の英語をどうしてもうまく認識してくれないので、良いマイクを買いました。でも結果は変わりませんでした。ソニーのマイクさんごめんなさい。

追記

3台つなげて、抜いてもさしても簡単には落ちないようにして、ZMQでOSC形式のMessageをひたすら送り続けるやつ。

掘り起こし手直し版 ofxZmqとofxTurboJpegで画像配信と受信

(編集中)
ofApp.h

#pragma once

#include "ofMain.h"

#include "ofxTurboJpeg.h"

class ofxZmqSubscriber;
class ofxZmqPublisher;

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();

		void keyPressed(int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y );
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void mouseEntered(int x, int y);
		void mouseExited(int x, int y);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);
		
		ofxZmqSubscriber* subscriber;
		ofxZmqPublisher* publisher;

		ofVideoGrabber cap;

		ofxTurboJpeg turbo;
		ofImage frame;
		ofBuffer send_buf;
		int quality;

		ofImage image;
		ofBuffer recv_buf;

protected:
	ofFbo fbo;
	ofPixels pix;
	bool flag_display;
	ofVec2f src_size, dst_size;
};

ofApp.cpp

#include "ofApp.h"

#include "ofxZmq.h"

#pragma comment(lib, "C:\\vcpkg\\installed\\x64-windows\\lib\\libzmq-mt-4_3_4.lib")

//--------------------------------------------------------------
void ofApp::setup(){
	ofSetFrameRate(60);

	cap.setVerbose(true);
	cap.setDeviceID(2);

	flag_display = true;
	src_size = ofVec2f(/*3840, 1920*/1920,1080);
	dst_size = src_size; // ofVec2f(2560, 1440);

	if (cap.initGrabber(src_size.x, src_size.y)) {
		//frame.allocate(cap.getWidth(), cap.getHeight(), OF_IMAGE_COLOR);
		quality = 95;

		// start server
		publisher = new ofxZmqPublisher();
		publisher->setHighWaterMark(1);
		publisher->bind("tcp://*:9999");
	}

	// start client
	subscriber = new ofxZmqSubscriber();
	subscriber->setHighWaterMark(1);
	// subscriber->connect("tcp://localhost:9999");

	fbo.allocate(dst_size.x, dst_size.y, GL_RGB, 4);
}

//--------------------------------------------------------------
void ofApp::update(){
	while (subscriber->hasWaitingMessage()) {
		subscriber->getNextMessage(recv_buf);
		turbo.load(image, recv_buf);

		cout << "received data: " << recv_buf.size() << endl;
	}

	cap.update();
	if (cap.isFrameNew()) {
		if (src_size != dst_size) {
			// TODO: using FBO for image size adjustment
			fbo.begin();
			cap.draw(0, 0, fbo.getWidth(), fbo.getHeight());
			fbo.end();
			fbo.readToPixels(pix);
			turbo.save(send_buf, pix, quality);
		}
		else {
			turbo.save(send_buf, cap.getPixelsRef(), quality);
		}
		publisher->send(send_buf);
	}
}

//--------------------------------------------------------------
void ofApp::draw(){
	if (!flag_display) return;

	if (src_size != dst_size) {
		// resized fbo
		ofPushMatrix();
		ofScale(0.5);
		fbo.draw(0, 0, fbo.getWidth(), fbo.getHeight());
		ofPopMatrix();

		//image.draw(cap.getWidth(), 0);
	}
	else {
		cap.draw(0, 0);
		//image.draw(cap.getWidth(), 0);
	}
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key) {
	if (key == OF_KEY_RETURN) {
		flag_display = !flag_display;
		std::cout << "display " << flag_display << std::endl;
	}
	if (key == OF_KEY_UP) {
		quality = (quality < 100) ? quality + 1 : quality;
		std::cout << "quality " << quality << std::endl;
	}
	if (key == OF_KEY_DOWN) {
		quality = (quality > 0) ? quality - 1 : quality;
		std::cout << "quality " << quality << std::endl;
	}
}

//--------------------------------------------------------------
void ofApp::keyReleased(int key){

}

//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y ){

}

//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mouseEntered(int x, int y){

}

//--------------------------------------------------------------
void ofApp::mouseExited(int x, int y){

}

//--------------------------------------------------------------
void ofApp::windowResized(int w, int h){

}

//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg){

}

//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){ 

}

買う予定の本2022年7月版

諸事忙しすぎて、本を積むことすらできていません。すでに世間からおいて行かれている気がするので、本を積むことで功徳も積んで、少しでも普通に近づきたいと思います。
でも、何冊かもう山に埋もれているような気も。。。

ロボット関係

人に優しいロボットのデザイン 「なんもしない」の心の科学

人に優しいロボットのデザイン 「なんもしない」の心の科学 | 高橋 英之 |本 | 通販 | Amazon

ロボット工学者が考える「嫌なロボット」の作り方―ヒューマンエージェントインタラクションの思想

ロボット工学者が考える「嫌なロボット」の作り方──ヒューマンエージェントインタラクションの思想 | 松井 哲也 | Kindle本 | Kindleストア | Amazon

LiDARを用いた高度自己位置推定システム - 移動ロボットのための自己位置推定の高性能化とその実装例

LiDARを用いた高度自己位置推定システム - 移動ロボットのための自己位置推定の高性能化とその実装例 - | 赤井 直紀 |本 | 通販 | Amazon

機械学習

The Mathematics of Artificial Intelligence

arxiv.org/abs/2203.08890

オーディオ関係

主にFinalのブログから。もう、洋書とか探すの大変なのでFinalで売って欲しい。。

Audio Processing – Learning from experience(邦訳「オーディオ信号処理 – 経験からの学習」)

AES E-Library » Audio Processing—Learning From Experience

JEITA RC-8140C ヘッドホン及びイヤホン

https://www.jeita.or.jp/japanese/public_standard/

Headphone technology - Hear-through, bone conduction, and noise canceling

AES E-Library » Headphone Technology: Hear-Through, Bone Conduction, and Noise Canceling