nyabot’s diary

電気猫の夢を見るお話

格安デルタ型3Dプリンター「Kossel Linear Plus」の購入から動作確認まで

初めての3Dプリンターを購入してみたのでまとめ。

経緯・比較

購入を検討するに至った経緯を少し書いておきます。同じようなことしている方(いるのだろうか)には参考になるかも。
(プリンターに直接関係ない話なので興味ない方は読み飛ばしてOK.)

私は一年以上前からロボットを作っており、試作機ではアルミ板を切ったり折ったりしてボディを作成したのだけれど、以下のような課題がありました。

  • 重量
  • 加工の難度
  • 形状の自由度

まず、金属は重く、軽量なアルミでも使用する量によってはかなりの重さになること。重量があると、その分動作に必要なサーボモータのトルクも大きくなり、消費電力も多くなり、必要なコストもかさみます。
特にバッテリーは容量と出力電流を考慮する必要があるのだけど、高トルクなサーボを複数動かせるもの、という条件がかなり厳しい。高出力なバッテリーは扱いも難しく、爆発や発火の危険性もあるため、素人としてはできれば手を出したくない。

次に、加工の手間について。
金属板でパーツを作るとすると、大まかに次のような工程になります。

  1. 設計図を作る
  2. 金属板に線を引く
  3. 切る
  4. 穴を開ける
  5. 折り曲げる

特に、金属を切って穴を開けて折る、というのが思ったより大変で、とても時間がかかるし手も痛くなる。

そして、形状の自由度。金属板の場合、作れる形状にもかなり制限が出ます。
切ったり曲げたり、というのが紙くらい簡単にできれば形も自由に作れるのだろうけれど、滑らかなカーブとか細かなパーツはなかなかうまく作れない。曲げる時や穴を開けるときに、あるいはそれらを組み合わせたときに、どうしてもミリ単位で誤差が出てきてしまう。加工のしやすさと強度はトレードオフで仕方ない、とは思うのですが……。

見た目も動作もより良いロボットを作ろうと思うと、金属板の手加工では限界があることが目に見えていました。
かといって、金属を本格的に加工しようとすると、必要な技術も費用も趣味では済まないレベルになりそう。

そこで、3Dプリンターに目をつけました。これなら最低限の学習と費用で、もっと自由にロボットが作れるのでは、と思ったわけです。

3Dプリンターの種類 FDMと光造形

調べてみると、現在家庭用の3Dプリンターは出力方法で大きく二種類に分けられ、それぞれ「FDM(熱溶解方式)」と「光造形」と呼ばれるみたい。

二種類のプリンターの違いをまとめるとこんな感じ。 *各項目は主観かつ相対評価

FDM 光造形
造形精度 低い 高い
出力できる材質 PLA,ABSなど 光硬化レジン
出力サイズ 大きめ 小さめ
印刷の手間 あまりかからない 手間がかかる
印刷コスト(材料) 安い 高い

FDMでも使うフィラメントによっては高コストだったりするし、設定やなんやかんやを考えたら手間もあまり変わらないのかもしれません。

私の用途の場合、そこまで高精度の造形は求めないので、出力の大きさや強度と、扱いやすそうなことからFDMに決定。

さらに、FDMのプリンターはその構造によっていくつかの種類に分けられるそう。 今回購入したKossel Linear Plusはデルタ型と呼ばれるものです。

↓こんな感じのやつ

ANYCUBIC Predator 3Dプリンター 高精度 停電回復機能 DIYキット 組立易い 安定 印刷サイズ 370×370×455mm 大型/工業級 FDM デルタ 金属製 Delta 3D printer フィラメント付き (3D プリンター)

Kossel Pro DIY 高精度3Dプリンター デルタ型金属製3Dプリンター 組立キット 最大プリントサイズ250x250x380 3.5インチのタッチスクリーン スデッピングモータドランバ付き 超静かプリント 操作簡単 組立しやすい

昔のSF映画に出てきそう。かっこいい。 ざっと調べたデルタ型のメリット・デメリットは以下。

  • 精度が高い(?)
  • 動作音が比較的小さい
  • 設置面積が比較的小さい
  • 国内ネットショップでの取り扱いが少ない
  • 国内のレビューが少ない
  • 作るのがやや大変
  • かっこいい

他の形のプリンターと比べ、デルタ型は三つのモーターで動作を制御するパラレルリンクという機構になっており、精度が高い……らしい。 実際のところ、しっかりと調整されたプリンターはどれも違わないとか、デルタ型の方が精度を高めにくい、とかいう話もあったのでわかりません。

何かの商品とかツールを選ぶときは、基本的にはユーザーが多いものを選ぶべき、と私は考えています。トラブルが発生した時に、ネット上に同様の事例の情報があるとないとで苦労が全然違うためです。 その点、安価なKosselやKossel Linear Plusは日本のAmazonでは取り扱い終了していたり、製品について書かれているブログサイトも1,2件しかなかったり(しかも現在のバージョンと違ったり)と、かなり情報が少ない様子。自分の頭で解決できない問題が起きたら詰みそう。

色々調べたところ、低価格のFDM方式の3Dプリンターでは、Ender3というのが日本では人気みたいで良さそう。
でも結局、見た目のかっこよさに抗えず。デルタ型ってなんか近未来感あるしさ。

購入

Kossel Linear Plusは前述の通り、国内ネットショップでは販売終了していました(2019/11現在)。3Dプリンターは日本ではまだ多分ニッチでその割に競合が多いから、なかなか厳しいのかも。
そこで、海外通販サイトを利用して個人輸入することに。ハードルの低いいくつかの海外通販サイトの中で、今回は中国の通販サイトであるAliexpressを利用します。

なお、その他のデルタ型3Dプリンターは国内でもいくつかあったものの、値段が高かったり評価がイマイチだったり。
趣味で色々作る人なら少し奮発して高価なプリンターを買うといいかもしれません。
私はロボットのパーツを作りたいだけで、さらに言えばどの程度使うかもわからないものに5万も10万も出すのはちょっと…というプアなのでケチる。(できることとか商品の作りを考えたら5万10万が妥当な金額な気はする。)

購入方法と購入金額

AliExpressの11.11セールで購入。 セラーは「AnycubicBrand Sore」さん。 購入時の価格は送料込みで18500円ほど。そこからAliExpressの11.11の割引があったので実質17000円くらいでした。

購入したのは「Linear plus add 1KG」という商品と1kgのPLAをセットにしたもの。 フィラメントの追加なしのものはさらに1000円安かったのだけれど、フィラメント単品で買うと2000円はするので、色の指定はできないけどお得と思いこちらに。

ちなみに同セールではラズパイ用カメラモジュールが900円弱、USBマイクが6円(!?)で送料込み、とかだったのでたかが1000円などとはとても言えない。セールすごい。安く買えるものは安く買って、浮いたお金で他のモジュールを買ったり量産を目指したりするんだ。

懸念点

海外通販ということで、いくつかの懸念点もありました。

  • プラグ
  • 関税
  • 品質面、保証など

DIY製品ということで当然組み立ても心配だったけれど、組み立てについては後述。

プラグの種類

今まで考えたこともなく知らなかったのですが、国によって電化製品のプラグやコンセントの種類が違うのですね。
日本のプラグの形式は「USタイプ」らしい。製品によってはプラグのタイプが違うので、もしかしたら変換プラグを用意しないといけない。
事前にセラーに尋ねたら、「購入時に伝えてくれればUSプラグを送るよ」、とのこと。
メッセージのやり取りは全て英語。勉強した英語を思い出しつつ、最終的にはGoogle翻訳に頼る。
注文時にメッセージを添えられるので、そこに「USプラグ」プリーズって書く。簡潔に書けばとりあえずわかってくれます。多分。

変換プラグを購入

届いた商品には、きちんとUSプラグが同梱されていた。素晴らしいセラー。 でもプラグの先が3つ出ている、3ピンタイプのUSプラグでした。これは想定外。プラグも奥が深い。
わが家には対応するコンセントはなかったので、3ピン→2ピン変換プラグを追加で購入。Amazonで300円弱くらい。

エレコム 電源タップ 変換アダプタ 3ピン→2ピン アース付きコンセント T-H32

エレコム 電源タップ 変換アダプタ 3ピン→2ピン アース付きコンセント T-H32

関税

本体価格が免税範囲内に収まっていたので関税はかからず。
もう数千円以上高いプリンターだと輸入に関税もかかるはずなので、保証とか考えると国内のAmazon等で買ったほうがいいのかも。

品質、部品の状態など

  • 本体付属のPLAは1kg、工具も付属

付属のフィラメントはテスト用の数百グラム程度かと思ったら、なんと1kgもついててびっくり。色は白。
セットで購入したフィラメント(こちらは色は透明でした)は、当分開けずに保管しておくことに。
ニッパー、ドライバー、六角レンチとか工具も一通り入っていて、付属品は至れり尽くせりといった印象。

組み立て

おそらく最初にして最大の難所、組み立てと配線。
Youtubeで組み立てレビューを投稿されている素晴らしい方がいらっしゃったので、貼っておきます。


3Dプリンター 組み立て Kossel Plus Anycubic製 assemble デルタ型 Delta 3D printer

上記動画がほぼそのまま(電源ユニットの配線周りは私のと違っていた)なので、これから購入・組み立てする人はぜひ参考に。
動画ほどサクサクは作れないものの、これでできそうだな、と思えるなら多分できる。たぶん。

組み立てを終えたところ、部品に不備は一つもなく、組み立てに無理があるようなところもなし。
7ステップでできる、という説明書なのだけど、ステップごとに使用するネジ類も予め小分けされていてわかりやすい。
ただし気になる部分もあり。

  • 電源からのコードが二組一緒になっていて、一度分解しないとそれぞれどちらかわからない
  • ヒーターのコードが説明書だと赤と黒なのに赤と赤になっている(上記動画でも言及されている)
  • 説明書通りに作るだけだと上のリミットスイッチに全く当たらない

電子工作とか少しでもやっている人は配線等も抵抗ないはず。そうじゃない人には、少し(コードの色違いとかはかなり)不親切かも。個人的にはこういう不親切さもDIYらしくて嫌いじゃないです。

印刷

組み立てと配線が終わったら、いざ印刷! 印刷と呼ぶべきか出力とか造成とか呼ぶべきか迷う。プリンターだから印刷でいいのかな?

印刷するデータの準備

  • 自分で作る
  • 誰かが作ったものを使う

3Dプリント用データの共有サイトとかもあるので、自分でデータを作れなくてもそれなりに楽しめそう。 プリンターに付属のSDカードにもテストプリント用のSTLデータがいくつか入っている。

そのままのSTLデータではプリントできないので、curaでgcodeにして印刷します。

curaのインストールと設定

説明書にもインストール方法が書かれていたり、SDカードにインストーラーが入ってたりするけど、公式サイトからダウンロードしました。
バージョンが新しくなっていたので、最新のものを使用(Ver.4.4)

プリンターのプロファイルを設定

curaには「Kossel Linear Plus」のプロファイルが用意されていないので、以下のプロファイルを入れました。

www.thingiverse.com

とてもありがたい……!

テストプリント

印刷の手順は商品に付属している説明書通り。フィラメントをセットしておき、自動レベリングユニットを取り付けてまずはZ軸とレベリングの調整。 調整を終えたらいよいよ印刷。アームが動くたびにちょっとドキドキする。ヒートベッドとノズルの加熱が2分くらい(PLA)。

f:id:sizohu:20191126201723j:plain

f:id:sizohu:20191126201731j:plain

f:id:sizohu:20191126201742j:plain

有名な?ベンチマーク用のボート模型。ネット上でよく見かけるモデル。 全体的に直線も曲線も綺麗に出ていて、十分満足できる結果に。というよりむしろ想像以上に綺麗にできて感動。

  • Maker Fare Robotくん

f:id:sizohu:20191126202835j:plain

f:id:sizohu:20191126202844j:plain

日本でも盛り上がりつつあるMaker Fareのロボット君。よく知らないけどかわいくて可動するよう設計されていたので、気になって印刷してみた。すごい。

プリントの様子

初めてタイムラプス使った。

感想とか

組み立てたりするのが好きな人、機械が好きな人ならオススメ。半分できているとはいえ、自分で組んで配線した機械がガシャガシャ動くのはかなり楽しい。

説明書通りに組めばできるとはいえ、若干説明書と違うところがあったり、説明が省かれている箇所もあったりするため、工作とかが苦手な人には正直あまりおすすめできない気がします。Amazonの商品説明に、「DIY好き、根性がよい、経験がある人にオススメ!」って書かれていてすごい翻訳だなと笑ったけどあながち間違いじゃないかも。
配線込みで組み上げるまでになんだかんだで4,5時間かかりました。
作業量はそこまで多くないので、構造がわかっている人が組むなら2時間もかからなそう。

印刷物の品質には大満足。
もしかしたら、大きなサイズやより高精度が求められるモデルを作ったり、他のフィラメントを使ったりすると徐々に不満も出てくるのかもしれませんが、今のところ全く問題はありません。
買ってよかった!

3DCADを勉強中なので、次は自分の作成したモデルを印刷するのが楽しみ。
つづく。

近況と今後のこと。

8足歩行が無事動作する状態になったので、とりあえずの目標は達しました。
ここからは、ソフト面の作り込みと、機体の改良を目指します。

将来的には対話可能なロボットにしたい、という思いがあります。現状は知識が皆無です。
gaccoで機械学習やAI・自然言語処理に関する講座が開かれているので、ここ二ヶ月ほどそれを勉強しています。

学んでわかったことは、本来の意味での人工知能・AIの実装はまだ不可能、あるいは極めて困難だということ。 会話に特化したものにしても、膨大な学習データが必要になるなどの制約もあり、個人でオリジナルのAIを作るのは非現実的。 ある程度現実的な路線としては、学習済みのデータをどこかから拝借してきて使うか、twitterなどweb上の会話を教師として活用するか、より簡易的な、いわゆる人工無脳などとも呼ばれるような擬似的なAIにするか、といったところ。

どの場合でも、自分の今持っている知識や技術では全く太刀打ちできないので、当分の間はそれらを学ぶために時間を費やさなくてはなりません。

会話以外の機能については、作ったロボットを活用して以下を試していくつもりです。

  • 赤外線送受信
  • 音声操作
  • 顔検知、動体検知
  • カメラ、センサを用いた自動運転

こちらはこちらで、早速配線を間違えてモジュールを壊したりと、まだまだ先が長そうな様子。

機体の改良に関しては、一度ゼロから作り直す予定。
手作業でアルミを加工して作った1号ですが、重量に対してサーボが弱かったり、構造的な脆さがあったり、ビジュアルがあまり美しくなかったりと問題が多数ありました。
そこで、設計を新たにして2号機を作ります。今回は、3DCADと3Dプリンターを使ってより高精度な、かつかっこよくかわいいロボットにしていきます。 3DCADは以前使用したTinkerCADより本格的で機能豊富なfusion360を学び、使います。使いこなせるようになったら3Dプリンターを買う予定。

こちらもとても時間がかかりそう。

そんなわけで、しばらくの間ブログの更新はなくなることでしょう。

つづく。

WebIOPiでRaspberry Piに繋いだフルカラーLEDを操作する

ロボットの目にRGBフルカラーLEDをつけたので、スマホからLEDを操作できるようにしたい。
ラズパイでWebIOPiが動作するように設定してあるので、操作画面とスクリプトを作る。

できあがったものはこんな感じ↓

作りかた

発光色に応じたボタンをクリックすることで、RGBの三色のうちの任意の1〜3色をONにし、他をOFFにする。
ON-OFFの切り替えだけで出せるカラーは、単色で発光させたときのred, green, blueに加えて、2色を同時に発光させたyellow, cyan, magenta、3色を発光させたwhite、計7色となる。
より細かな色合いを出すのは処理が複雑になるし、7色もあれば十分楽しいので、今回はこの7色+消灯で8つの状態を切り替えられるようにする。

まずはラズパイのGPIOを操作するスクリプトから。
これは、WebIOPi起動時に読み込むスクリプトファイルに記述する。

import webiopi
from time import sleep
from distutils.util import strtobool

# GPIOライブラリの取得
GPIO = webiopi.GPIO

# LEDのRGBに対応するGPIO。ここでは23,24,25を使う
LED_R = 23
LED_G = 24
LED_B = 25
LED_STATE = GPIO.LOW

# デバッグ出力を有効に
webiopi.setDebug()

# WebIOPiの起動時に呼ばれる関数
def setup():
    webiopi.debug("Script with macros - Setup")
    # GPIOのセットアップ
    GPIO.setFunction(LED_R, GPIO.OUT)
    GPIO.setFunction(LED_G, GPIO.OUT)
    GPIO.setFunction(LED_B, GPIO.OUT)
    GPIO.digitalWrite(LED_R, LED_STATE)
    GPIO.digitalWrite(LED_G, LED_STATE)
    GPIO.digitalWrite(LED_B, LED_STATE)

# WebIOPiにより繰り返される関数
def loop():
    webiopi.sleep(5)

# WebIOPi終了時に呼ばれる関数
def destroy():
    webiopi.debug("Script with macros - Destroy")
    # GPIO関数のリセット(入力にセットすることで行う)
    GPIO.setFunction(LED_R, GPIO.IN)
    GPIO.setFunction(LED_G, GPIO.IN)
    GPIO.setFunction(LED_B, GPIO.IN)

# Javascriptから呼ぶマクロ
@webiopi.macro
def setLED(gpio, led_state):
    global LED_STATE
    LED_STATE = strtobool(led_state)#渡された文字列をbool値に変換して使用する
    GPIO.digitalWrite(int(gpio), LED_STATE)
    return LED_STATE

次に、Javascript。WebIOPiの操作画面のHTMLから読み込むファイル。

function setLED(color){
  const gpio_r = 23;
  const gpio_g = 24;
  const gpio_b = 25;
  let state_r, state_g, state_b;

  // RGBのうちどれを点灯させるか色ごとに指定する
  if (color == 'white') {
    state_r = 'on';
    state_g = 'on';
    state_b = 'on';
  } else if (color == 'red') {
    state_r = 'on';
    state_g = 'off';
    state_b = 'off';
  } else if (color == 'green') {
    state_r = 'off';
    state_g = 'on';
    state_b = 'off';
  } else if (color == 'blue') {
    state_r = 'off';
    state_g = 'off';
    state_b = 'on';
  } else if (color == 'yellow') {
    state_r = 'on';
    state_g = 'on';
    state_b = 'off';
  } else if (color == 'cyan') {
    state_r = 'off';
    state_g = 'on';
    state_b = 'on';
  } else if (color == 'magenta') {
    state_r = 'on';
    state_g = 'off';
    state_b = 'on';
  } else if (color == 'black') {// 消灯
    state_r = 'off';
    state_g = 'off';
    state_b = 'off';
  }

  webiopi().callMacro('setLED', [gpio_r, state_r]);
  webiopi().callMacro('setLED', [gpio_g, state_g]);
  webiopi().callMacro('setLED', [gpio_b, state_b]);

  // ボタンのクラスを付け替える
  const led_btns = document.getElementsByClassName('btn_led');
  for (let led_btn of led_btns) {
    led_btn.classList.remove('btn_led--active');
  }
  document.getElementById('led_' + color).classList.add('btn_led--active');
}

そして、HTML。操作画面のボタンを作り、スクリプトと紐付けする。

<div class="btn_led_wrap">
  <span class="btn_led btn_led--active" id="led_black" onClick="setLED('black')"></span>
  <span class="btn_led" id="led_white" onClick="setLED('white')"></span>
  <span class="btn_led" id="led_red" onClick="setLED('red')"></span>
  <span class="btn_led" id="led_yellow" onClick="setLED('yellow')"></span>
  <span class="btn_led" id="led_green" onClick="setLED('green')"></span>
  <span class="btn_led" id="led_cyan" onClick="setLED('cyan')"></span>
  <span class="btn_led" id="led_blue" onClick="setLED('blue')"></span>
  <span class="btn_led" id="led_magenta" onClick="setLED('magenta')"></span>
</div>

ページを読み込んだタイミングでは、消灯状態(black)をアクティブにしておく。
本当はspanじゃなくてbuttonとかでマークアップした方がわかりやすいのかもだけど、フォーカスとか面倒だからspanとかdiv使ってしまう。
あと当然このHTMLファイルでjsとcssを読み込んでおく。

最後に、CSS。ボタンの配置と装飾をする。

.btn_led_wrap {
  position: fixed;// 配置方法や場所はよしなに
  bottom: 20px;
  width: 100%;
  display: flex;
  justify-content: center;
}

.btn_led {
  box-sizing: border-box;
  height: 36px;
  width : 36px;
  margin: 4px;
  border : 10px solid #fff;
  border-radius: 50%;
  transition: all 0.1s;
}

.btn_led--active {
  border-width: 0;
}

#led_white { background-color: rgb(230,230,230);}
#led_red { background-color: rgb(255,100,100);}
#led_yellow { background-color: rgb(255,255,100);}
#led_green { background-color: rgb(100,255,100);}
#led_cyan { background-color: rgb(100,255,255);}
#led_blue { background-color: rgb(100,100,255);}
#led_magenta { background-color: rgb(255,100,255);}
#led_black { background-color: rgb(60,60,60);}

各色のドットをクリックすると対応する色でLEDが点灯し、選択したボタンがいい感じに大きくなる。

今回はとてもスムーズに実現できたけど、ただLEDを操作するだけでこんなに色々書くことになるあたり、ロボットって大変だなぁと思いました。

つづく。

8速歩行ロボットの歩行プログラム

WebIOPiで動作させるpythonスクリプトについてメモ。

  • 使用サーボはSG90
  • 使用サーボドライバはPCA9685
  • 足は8本、各足2自由度(水平)

基本的なコード、特にPCA9685のセットとWebIOPiとの連携に関しては、以下の書籍の6足歩行ロボットを作る項のコードを参考にさせていただきました。
連携に関する設定等から初心者向けに詳しく書かれているので、毎度のことながら有難み極まる。

自分はプログラムには不慣れなため、感覚的にわかりやすいよう各足ごとに動作させるようにしました。
また、パルスではなく±90で動作角度を指定できるように関数を追加。
少し記述が冗長になってしまうけれど、後から調整もしやすいのでいいかな。

出来上がったコードは以下のような感じ。
これをWebIOPiが起動したときに動作するスクリプトとして設定する。
あとで自分が見返す時のためにコメントましましで。

import webiopi
from time import sleep
import smbus
import math
import threading

def resetPCA9685():
    bus.write_byte_data(address_pca9685, 0x00, 0x00)

# setPCA9685Freq、setPCA9685Duty関数は書籍(別記)より
def setPCA9685Freq(freq):
    ・・・

def setPCA9685Duty(channel, on, off):
    ・・・

# 角度指定でサーボを動かす関数
def setPos(channel, zeroOffset, pos):
    #角度をパルスに変換
    #+90〜-90度がパルス143〜410に相当するようなので、1度あたりのパルスは(410-143)/180
    #角度指定の±90に90を足して0〜180に直し、各サーボごとの補正値zeroOffsetを加えて、最終的な角度に相当するパルスを算出する
    pulse = int(round((410-143)/180*(pos+90+zeroOffset)))+143
    setPCA9685Duty(channel, 0, pulse)

# 動作させる足と動かし方を指定するための関数
def setLegServo(leg, order):
    if leg['Side'] == 'right':
        if order == 'f':
            setPos(leg['Channel_h'], leg['ZeroOffset_h'], -pos_f)
        elif order == 'b':
            setPos(leg['Channel_h'], leg['ZeroOffset_h'], pos_b)
        elif order == 'n':
            setPos(leg['Channel_h'], leg['ZeroOffset_h'], pos_n)
        elif order == 'u':
            setPos(leg['Channel_v'], leg['ZeroOffset_v'], -pos_u)
        elif order == 'd':
            setPos(leg['Channel_v'], leg['ZeroOffset_v'], pos_d)
    elif leg['Side'] == 'left':
        if order == 'f':
            setPos(leg['Channel_h'], leg['ZeroOffset_h'], pos_f)
        elif order == 'b':
            setPos(leg['Channel_h'], leg['ZeroOffset_h'], -pos_b)
        elif order == 'n':
            setPos(leg['Channel_h'], leg['ZeroOffset_h'], pos_n)
        elif order == 'u':
            setPos(leg['Channel_v'], leg['ZeroOffset_v'], pos_u)
        elif order == 'd':
            setPos(leg['Channel_v'], leg['ZeroOffset_v'], -pos_d)

# 足一本ごとに動作を指定するための関数
# 一本の足につき複数のサーボを動かす場合は、第二引数に配列で渡す。
def setLeg(leg, orderList):
    if type(orderList) is str:
        setLegServo(leg, orderList)
    elif type(orderList) is list:
        for order in orderList:
            setLegServo(leg, order)

# モーション設定
def forwardPhase1(delay):
    setLeg(leg_r1, ['u', 'f'])
    setLeg(leg_r3, ['u', 'f'])
    setLeg(leg_l1, ['u', 'f'])
    setLeg(leg_l3, ['u', 'f'])
    setLeg(leg_r2, ['b'])
    setLeg(leg_r4, ['b'])
    setLeg(leg_l2, ['b'])
    setLeg(leg_l4, ['b'])
    sleep(delay)
    setLeg(leg_r1, ['d'])
    setLeg(leg_r3, ['d'])
    setLeg(leg_l1, ['d'])
    setLeg(leg_l3, ['d'])
    sleep(delay)

def forwardPhase2(delay):
    setLeg(leg_r2, ['u', 'f'])
    setLeg(leg_r4, ['u', 'f'])
    setLeg(leg_l2, ['u', 'f'])
    setLeg(leg_l4, ['u', 'f'])
    setLeg(leg_r1, ['b'])
    setLeg(leg_r3, ['b'])
    setLeg(leg_l1, ['b'])
    setLeg(leg_l3, ['b'])
    sleep(delay)
    setLeg(leg_r2, ['d'])
    setLeg(leg_r4, ['d'])
    setLeg(leg_l2, ['d'])
    setLeg(leg_l4, ['d'])
    sleep(delay)

# 前進
def forwardMove(delay):
    forwardPhase1(delay)
    forwardPhase2(delay)

def backwardPhase1(delay):
    setLeg(leg_r1, ['u', 'b'])
    setLeg(leg_r3, ['u', 'b'])
    setLeg(leg_l1, ['u', 'b'])
    setLeg(leg_l3, ['u', 'b'])
    setLeg(leg_r2, ['f'])
    setLeg(leg_r4, ['f'])
    setLeg(leg_l2, ['f'])
    setLeg(leg_l4, ['f'])
    sleep(delay)
    setLeg(leg_r1, ['d'])
    setLeg(leg_r3, ['d'])
    setLeg(leg_l1, ['d'])
    setLeg(leg_l3, ['d'])
    sleep(delay)

def backwardPhase2(delay):
    setLeg(leg_r2, ['u', 'b'])
    setLeg(leg_r4, ['u', 'b'])
    setLeg(leg_l2, ['u', 'b'])
    setLeg(leg_l4, ['u', 'b'])
    setLeg(leg_r1, ['f'])
    setLeg(leg_r3, ['f'])
    setLeg(leg_l1, ['f'])
    setLeg(leg_l3, ['f'])
    sleep(delay)
    setLeg(leg_r2, ['d'])
    setLeg(leg_r4, ['d'])
    setLeg(leg_l2, ['d'])
    setLeg(leg_l4, ['d'])
    sleep(delay)

# 後退
def backwardMove(delay):
    backwardPhase1(delay)
    backwardPhase2(delay)

def rotateRightPhase1(delay):
    setLeg(leg_r1, ['u', 'b'])
    setLeg(leg_r3, ['u', 'b'])
    setLeg(leg_l1, ['u', 'f'])
    setLeg(leg_l3, ['u', 'f'])
    setLeg(leg_r2, ['f'])
    setLeg(leg_r4, ['f'])
    setLeg(leg_l2, ['b'])
    setLeg(leg_l4, ['b'])
    sleep(delay)
    setLeg(leg_r1, ['d'])
    setLeg(leg_r3, ['d'])
    setLeg(leg_l1, ['d'])
    setLeg(leg_l3, ['d'])
    sleep(delay)

def rotateRightPhase2(delay):
    setLeg(leg_r2, ['u', 'b'])
    setLeg(leg_r4, ['u', 'b'])
    setLeg(leg_l2, ['u', 'f'])
    setLeg(leg_l4, ['u', 'f'])
    setLeg(leg_r1, ['f'])
    setLeg(leg_r3, ['f'])
    setLeg(leg_l1, ['b'])
    setLeg(leg_l3, ['b'])
    sleep(delay)
    setLeg(leg_r2, ['d'])
    setLeg(leg_r4, ['d'])
    setLeg(leg_l2, ['d'])
    setLeg(leg_l4, ['d'])
    sleep(delay)

# 右旋回
def rotateRightMove(delay):
    rotateRightPhase1(delay)
    rotateRightPhase2(delay)

def rotateLeftPhase1(delay):
    setLeg(leg_r1, ['u', 'f'])
    setLeg(leg_r3, ['u', 'f'])
    setLeg(leg_l1, ['u', 'b'])
    setLeg(leg_l3, ['u', 'b'])
    setLeg(leg_r2, ['b'])
    setLeg(leg_r4, ['b'])
    setLeg(leg_l2, ['f'])
    setLeg(leg_l4, ['f'])
    sleep(delay)
    setLeg(leg_r1, ['d'])
    setLeg(leg_r3, ['d'])
    setLeg(leg_l1, ['d'])
    setLeg(leg_l3, ['d'])
    sleep(delay)

def rotateLeftPhase2(delay):
    setLeg(leg_r2, ['u', 'f'])
    setLeg(leg_r4, ['u', 'f'])
    setLeg(leg_l2, ['u', 'b'])
    setLeg(leg_l4, ['u', 'b'])
    setLeg(leg_r1, ['b'])
    setLeg(leg_r3, ['b'])
    setLeg(leg_l1, ['f'])
    setLeg(leg_l3, ['f'])
    sleep(delay)
    setLeg(leg_r2, ['d'])
    setLeg(leg_r4, ['d'])
    setLeg(leg_l2, ['d'])
    setLeg(leg_l4, ['d'])
    sleep(delay)

# 左旋回
def rotateLeftMove(delay):
    rotateLeftPhase1(delay)
    rotateLeftPhase2(delay)

# 停止
def stopMove(delay):
    setLeg(leg_r1, ['n', 'd'])
    setLeg(leg_r2, ['n', 'd'])
    setLeg(leg_r3, ['n', 'd'])
    setLeg(leg_r4, ['n', 'd'])
    setLeg(leg_l1, ['n', 'd'])
    setLeg(leg_l2, ['n', 'd'])
    setLeg(leg_l3, ['n', 'd'])
    setLeg(leg_l4, ['n', 'd'])
    sleep(delay)

def processCommands():
    global status

    while True:
        if status == 'i':
            sleep(delay)
        elif status == 'f':
            forwardMove(delay)
        elif status == 'b':
            backwardMove(delay)
        elif status == 'r':
            rotateRightMove(delay)
        elif status == 'l':
            rotateLeftMove(delay)
        elif status == 's':
            stopMove(delay)
            status = 'i'
        elif status == 'k':
            break

# 各足に対応する変数に、向き、サーボのチャンネル、角度補正値を連想配列で格納
# サーボは取り付けるときに0度位置に合わせるが、少しずれるのでここで補正する
# 水平方向に動かすサーボはCannel_h, ZeroIffset_h, 垂直方向に動かすサーボはChannel_v, ZeroOffset_vをそれぞれキーにする
leg_r1 = {'Side' : 'right', 'Channel_h' : 0, 'ZeroOffset_h' : -5, 'Channel_v' : 1, 'ZeroOffset_v' : 5}
leg_r2 = {'Side' : 'right', 'Channel_h' : 2, 'ZeroOffset_h' : 10, 'Channel_v' : 3, 'ZeroOffset_v' : -5}
leg_r3 = {'Side' : 'right', 'Channel_h' : 4, 'ZeroOffset_h' : 0, 'Channel_v' :5 , 'ZeroOffset_v' :0 }
leg_r4 = {'Side' : 'right', 'Channel_h' : 6, 'ZeroOffset_h' :0 , 'Channel_v' :7 , 'ZeroOffset_v' :-5 }
leg_l1 = {'Side' : 'left', 'Channel_h' : 8, 'ZeroOffset_h' :0, 'Channel_v' :9 , 'ZeroOffset_v' :-10 }
leg_l2 = {'Side' : 'left', 'Channel_h' : 10, 'ZeroOffset_h' : -5, 'Channel_v' :11 , 'ZeroOffset_v' :-5 }
leg_l3 = {'Side' : 'left', 'Channel_h' : 12, 'ZeroOffset_h' : -5, 'Channel_v' :13 , 'ZeroOffset_v' :5 }
leg_l4 = {'Side' : 'left', 'Channel_h' : 14, 'ZeroOffset_h' : 0, 'Channel_v' :15 , 'ZeroOffset_v' :5 }

# 各動作に応じたサーボの動作角度設定
# 水平方向 n: neutral, f: forward, b: backward
# 垂直方向 u: up, d: down
pos_n = 0
pos_f = 18
pos_b = 18
pos_u = 30
pos_d = 0

delay = 0.15

# i:idle, f: forward, b: backward, r: right, l: left, s: stop, k: kill
status = 'i'

bus = smbus.SMBus(1)
address_pca9685 = 0x40

resetPCA9685()
setPCA9685Freq(50)

stopMove(delay)

t = threading.Thread(target=processCommands)
t.start()

# デバッグ出力を有効に
webiopi.setDebug()

# WebIOPiの起動時に呼ばれる関数
def setup():
    webiopi.debug("Script with macros - Setup")

# WebIOPiにより繰り返される関数
def loop():
    webiopi.sleep(5)

# WebIOPi終了時に呼ばれる関数
def destroy():
    webiopi.debug("Script with macros - Destroy")
    global status
    status = 'k'
    t.join()

@webiopi.macro
def setLegsAction(action, commandID):
    global status
    status = action

足を運ぶ順番や角度の指定を少し変えると、また違った動きになって楽しい。 単なる前進でも足運びの方法が多数あるのも、多足ロボットならではの面白さかも。

なお、他の一般的なサーボは動作角度が+-逆らしい。Mk-Ⅱを作るときは気をつける。

つづく。

ラズパイに入れたGoogle Assistantのその後と今後のこととか

一年前くらいに始めて、間が空いたりして半年前くらいにようやく動くようになった、Google AssistantをSnowboyのホットワードで起動するやつ↓

nyabot.hatenablog.com

nyabot.hatenablog.com

nyabot.hatenablog.com

nyabot.hatenablog.com

2019年8月現在、動かなくなってる…かなり苦労したのに;;

Google Assistantのアップデートの関係かな。調べてみたらGoogle Assistant Libralyは今年6月に廃止されてるみたいだし、、結局サービスとの違いがよくわからなかったなぁ。

2022年6月 音声認識にJuliusを使用することにしました↓

nyabot.hatenablog.com

Webサービスに依存することについて

もともとはロボットに対話機能を付けたくて、そのためにGoogle Assistantを入れたのでした。
企業の提供してくれているサービスを利用する以上、それがいつ変更や廃止されても文句は言えない(Googleは割とよくやる)。
特に無料で利用できるWebサービスなんて、使い続けられる保証はどこにもない。

そういうのを踏まえると、Webサービスに機能の一部を依存するのは、リスクが高い。
もっとも、個人で実装可能な機能は高が知れているし、どこかで折り合いをつけて活用させてもらうのが良いとは思う。
問題は、リスクが許容できる範囲内か否か、ということ。

ロボットの寿命のこと

僕がロボットに求めているのは一種の永続性だ。例えばソニーのアイボを購入する人の中には、通常のペットとの別れが嫌で、という人が一定数いると思う。それと同じ。
企業の商品として作られたロボットは、保証やサポートという形でそれを実現しようとしている。最近は月額で料金を取るものも一般的になってきたし、ビジネスモデルとしてそれがうまくいけば、また、数十年くらいのスパンで言えば、実現も可能なのかもしれない。

一方で、自作のロボットであればサポートという概念はないけれど、その代わり壊れても部品があれば自分で直すことができる。データもバックアップを取っておけば、最悪一から作り直すことだって可能だ。
しかし、外部サービスに依存する機能だけは、自分ではどうすることもできない。

ロボットに命や思考はなくても、外から見たときにそれを感じることはある。
表現する機能があれば、笑っているように見えたり、喜んでいるように見えたりすることはある。
外部サービスを用いてそうした擬似的な生命を感じさせる機能を実現したとしたら、それらのサービスが停止したときがロボットが死ぬときになってしまう。

さすがに寿命だの死だのというのは大げさすぎる話で、実際はただの一機能の問題に過ぎず、失われたところで少し悲しい程度のことかもしれない。
でも、できることなら僕は、これから作るロボットとずっと一緒にいたい。ずっと一緒にいられるようなロボットを作りたい。

よって、対話機能というコアな部分がいつ動かなくなるかわからない、というのは少し問題だと思う。リスクが許容範囲外というわけ。

今後の方針

Google Assistantは便利だし、機能も素晴らしいから使いたかったけれど、使うとなるとやはりなくなったときがいたい。
対話機能については他に使えそうなものがあるので、そちらで挑戦してみる。会話の精度とかは敵わないだろうけど、それが愛されるロボットに必ずしも必要なわけではない。R2D2の人気がその証左だ。なんなら日本語じゃなくて機械音でもいいんだ。よくないけど。

またしてもハードルが上がった。完成は遠退くばかり。。
機体の方も課題がわかってきたから設計からやり直したいけど、手をつけられるのは一体いつになることやら。
まぁ、終わりが見えないくらいの方が楽しいけどね:-)

つづく。