FC2ブログ
 最近の記事
スポンサーサイト
カテゴリ: スポンサー広告
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
編集 / --.--.-- / コメント: - / トラックバック: - / PageTop↑
活動報告 No.157 ニソコンに行ってきました
カテゴリ: 大会

こんにちは!ブログ担当代理人イトウです!

今回は本命のブログ担当マエダくんとの二部構成になります( ̄^ ̄)ゞ


先日6月17日に東京理科大Ⅰ部無線研究部さん主催のニソコンに参加してきました!

ニソコン  

参加した機体は、

3年生のmasao、caster、メテオ、釣式

OBのODA先輩の墨、イプシロンが出場!

墨は1年生が操縦が操縦したのですが、初めて大会に参加したとは思えないほど落ち着いていて

イトウは感心しておりました(´ω`人)


今回行われた競技は

障害物競走 

格闘競技大会 の二種目でした!

IMG_7653 圧縮


IMG_7667圧縮


イプシロン


メテオ





以下 結果になります。

障害物競走部門

1位 タイペリオン

2位  イプシロン

3位 で かーる


格闘競技部門

1位 タイペリオン

2位 ささペロリン

3位 クロムキッド


さらに会場に来た方々による人気投票も!

人気投票部門

1位 タイペリオン

2位 イプシロン

3位 墨


タイペリオンがすごい!まさかの全部門優勝でした.゚+.(・∀・)゚+.

そしてヒュー研はODA先輩の墨が3位、イプシロンが2位とちびっ子たちの熱い人気を獲得しました!


今回は残念ながらODA先輩のみの受賞となってしまいましたが

初めて大会を見学した1年生にとってはたくさんの機体を見れたいい経験になったと思います。


来月にはROBOT JAPANがあるので、3年生は頑張っていきましょう!


それじゃあイトウはここまで(=゚ω゚)ノ

マエダくんにバトンタッチ!





どうも,おはこんばんにちは.ニソコンに行かなかった3年のマエダです.
今年のニソコンは昨年と違い広めの会場でやりやすかったとのことなので,来年も更なる改善を期待しておりますっ.


さてさて,私がブログに名を出すのは結構久し振りですね.ここの所はずっと手書きの実験レポート作成以外は機体設計とヒュー研内新技術の開拓に勤しんでおりました.
なのでロボット屋さんに向けて更新するブログ記事が無く…めっきり更新しなくなってしまいました…


ま,新技術といってもそんな大層なことはしておらず,FPGAによる制御と,(物体検出用の)ディープラーニングを用いた機体の自動制御です.
上記に関しては心と時間と精神と心と精神に余裕があれば個人で持っているブログサイトにアップして,ここで報告という形になると思います.




雑談長くなりましたが,今回はFPGAともDNNとも関係ない(?)Electronについてです.


ElectronはNode.jsによりセキュリティ上できなかったWEBシステムからローカルPCへのアクセスが可能となることで,ブラウザを丸ごと梱包すればデスクトップアプリケーションをつくることが出来るものですが,jQueryを使う際にやり方によってはNode.jsが機能しなくなります.


結構簡単に対処できるのですが,ネット上にはこれを考慮しないjQueryの通し方をしている記事があるので,メモ感覚で書いていきます.


やってはいけない方法


Electronに欠かせないファイルとして「package.json」があるかと思います.

以下のようなファイルです.



{
    "name": "test",
    "version": "0.1.0",
    "description": "for HK",
    "main": "main.js",
    "scripts": {
        "start": "electron ./src/renderer/main.js",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "Shimizu_mizu",
    "license": "MIT",
    "devDependencies": {
        "electron": "^1.8.3",
        "fs": "0.0.1-security",
        "jquery": "^3.3.1",
        "require": "^2.4.20"
    }
}



この中で,「"main": "main.js"」がElectronのレンダラプロセスに相当しますが,この「main.js」内にjQueryを読み込ませる記述をする方法はやってはいけません.具体的にmain.jsの記述を抜粋すると,以下のような記述です.



function createMainWindow() {
    Menu.setApplicationMenu(menu);
    mainWindow = new BrowserWindow({
        width: 1280,
        height: 720,
        webPreferences: {  // jQueryの認識
            nodeIntegration: false
        }
    });
    mainWindow.loadURL('file://' + __dirname + '/../../index.html');
    mainWindow.webContents.openDevTools();
    mainWindow.on('closed', function() {
        mainWindow = null;
    });
}


上記の中でも,



        webPreferences: {  // jQueryの認識
            nodeIntegration: false
        }


ここの部分です.こうすると確かにElectronの実行時に「 $ is not undefined 」と出ることなくjQueryが正常に読み込まれ,実行もできます.しかし,ここで外部ファイルのJavaScriptに「 const fs = require('fs'); 」と記述すると,今度はrequireが認識されません.



正攻法(?)


1, npmコマンドでjQueryをインストールする
2, html内にとある記述を


この2作業で終わります.



1. npmコマンドでjQueryをインストールする

何はともあれ,とりあえずプロジェクトのディレクトリに移動して,下記のコマンドを打ち込みます.


:~ $ npm install jquery@3.3.1 --save-dev


以上です.複数プロジェクトの管理のために--save-devとした方が良いでしょう.なお,バージョンは下記のリンクより,一覧と最新の安定版,開発版がすぐにわかります.


https://www.npmjs.com/package/jquery



img-shim-01.png 
img-shim-02.png 


2, htmlにとある記述を


以下をhtml内に記述します.ここだけ画像なのは,<script>タグで囲むとFC2ブログがそれをブログエディタ上で認識してしまい,描画されないためです.

<pre>タグで囲っても認識してしまうなんて,もしかしたらFC2ブログのコメントや検索欄にjsやらphpのコード記述すればクラックできるかもですね!(やらないけど...)


img-shim03.png 



これで以上となります.結構簡単にNode.jsを殺さずにjQueryを認識させることができるかと思います.




さて,前段のブログ代理(本命)のイトウ氏に続いて趣味全開の記事となりましたが,いかがでしたでしょうか.


私はあまりヒュー研していないのでブログは中々コアな分野をつくことが多いですが,そんな記事を書く度に「すまん...ロボ屋に興味なさそうな内容ですまん...」と思いつつ今日も涙目にキーボードを打っています.


一応,活動上はROBO-ONE autoに出るのが責務なので,大会が近づくにつれてようやくロボ関係の記事もぼちぼち出てくると思います.それまでは主に大会報告屋さんのブログ本命(代理)のイトウ氏が書いて下さるかと.


ほんじゃ まったの〜〜 ♪(/・ω・)/ ♪





スポンサーサイト
編集 / 2018.06.22 / コメント: 0 / トラックバック: 0 / PageTop↑
活動報告 No.156 春休みの出来事②(2月編)
カテゴリ: 大会
こんにちは!ブログ代理人イトウです!
学校も始まって2ヶ月あっという間に4月、GWが終わってしまいました・・・
新入生歓迎会も終え、一年生たちの活動がそれぞれ始まってきました.みんな頑張っていきましょう!
それではだいぶ時間も空いてしまいましたが…
春休みの出来事その2 ROBOT JAPAN15thを振り返っていきますヾ(・∀・)ノ
2月12日にいつもの築地本願寺ブディストホールで行われました。

DSC_0861.jpg

出場機体は,
RJフライ級

イプシロン(KHR) 
まさお
釣式

RJバンタム級
2年生
caster
テオ

以下結果になります!
RJフライ級(2kg以下)
優勝 コビス
第2位 バンボー
第3位 メタリックファイター

RJバンタム級(3kg以下)
優勝 Spersnza
第2位 閃電
第3位 caster

一発芸コンテスト
優勝   逡巡
第二位 RND
第三位 TKU-X

ロボットダンスコンテスト
優勝 Juhwak Kim,Heewoong Shin
第2位 ピピ美ちゃん
第3位 Twin Ducks

一発芸ではロボットアームが習字をしたり、KXRがミサイルを飛ばしたり個性いっぱいで見ていてとても楽しかったです。また次の一発芸も楽しみだなぁ(* ´ ▽ ` *)


バトルではなんと我々2年のcasterがRJバンタム級に3位入賞しました!
このcasterを制作した二人は前回大会14thのRJフライ級3位メビウスKを制作した二人でもあります!
DSC_0854.jpg
左がcasterです(-∀-)

DSC_0986.jpg

光り輝く機体で力強い攻撃でダウンしていく様は圧巻でした(∩・∀・)∩ キャー

DSC_0993.jpg
casterおめでとう!
二人はまた新しい機体を考えているみたいなので次のロボジャパンも楽しみです。

DSC_1000.jpg

次の16thは7月にあるみたいなのでもうすぐですね。
casterだけでなく他の機体も勝てるよう準備していきます!!

次回もまた春休みの出来事の振り替えをしていきたいと思います。


次は早めに更新したいな…







編集 / 2018.05.09 / コメント: 0 / トラックバック: 0 / PageTop↑
活動報告 No.155 春休みの出来事①(2月編)
カテゴリ: 未分類

お久しぶりです!ブログ担当代理のイトウです(=゚ω゚)ノ


2ヶ月もあった春休みもあっという間に終わって、

もうすっかり暖かくなってもう4月になってしまいました…
ということで春休みの出来事を振り返っていきたいと思います(^∇^)


まず、2月10日にKOMNDO BATTLEに行ってきました 前回まで同様KOTOBUKIYA ホールで行われました。

我々 ヒューマノイド研究からは3年生のODA先輩が学生選抜クラス、KHRクラスの2機体で出場しました!残念ながら2年生はロボジャパンに向けて調整中でした。


出場機体

プロテウス

プロテウス 

イプシロン

IMG_6319.jpg


学生選抜の試合から一部紹介します。


プロテウス VS >(デクレッシェンド)

序盤お互い攻め合うがダウンはお互い取れず接戦に
プロテウス攻撃耐えていくが、残念ながら1ダウンに
しかし残り1分で>(デクレッシェンド)がタイムをとり1対1になりました。
その後>(デクレッシェンド)による猛攻を受けるが>(デクレッシェンド)も倒れてしまいスリップ判定に、そのままタイムアップとなり引き分けとなりました。

IMG_6321.jpg

 
プロテウス VS Typerion

開幕早々 ダウンを取られてしまいました。
まさかの再スタート直後に大技の前転攻撃をくらい
2ダウンにより 敗北…
戦いなれている…

タイペリオン


KONDO BATTLE出場者の方々は機体の性能はもちろんですか、バトルの経験値の差を感じた大会でした。我々ヒュー研も部内戦を通してもっとバトルの練習していくべきかなと思いました(・ω・)

春休みの出来事まだ続きます!

それでは、次回ロボットジャパン編でお会いしましょうヽ( ´_`)丿



編集 / 2018.04.04 / コメント: 0 / トラックバック: 0 / PageTop↑
活動報告 No.154 ElectronでD-WARS用のタイム・ポイントカウンタを作る
カテゴリ: 未分類

どうもおはこんばんにちは,もうすぐ無事3年になれる2年のマエダです.


最近は近藤バトルやロボジャパン,ロボワン,D-WARS8と色々ブログ記事になりそうなこといっぱいありましたが,大会報告に関しては他の人に執筆を頼んであるので,その方がアップするまでしばしお待ちを...(あれ,何週間前だろう?)


さて,今回も奇妙なタイトル発進ですが,要はオリジナルのタイムカウンターを作ってみた!てことです.


Electronて何

Electronを使うと,HTML・CSS・JavaScript・+αを使ったWEBサイト用のGUIアプリケーションを,macOS・Windows・Linux用デスクトップアプリケーションにパッケージングすることができます.


いやーデスクトップアプリケーションっていうとOSによってソースコード(というか言語そのもの)がかなり異なるので使用環境の多様性を考えると手をつけ難いもんでしたが,Electronを使えば大半のコードを共有して各OSに合わせたアプリが開発できちゃうんですねー.


1_wOcHbpZ25WbtWWsGI2b1Kw.png 

今回は超シンプルに,HTML・CSS・JavaScriptだけでタイム・ポイントカウンタを作っちゃいます!



諸注意

以下を前提としています.

・HTML,CSS,JS(JavaScript)に対する若干の文法とコードスタイル知識

・コマンドラインでの操作(パッケージインストール等)経験者



Electronをはじめるなら

この記事はコード紹介が主なので,作り方がイチから全部分かるってことはないです.それしたら何記事できてしまうのか...

私はドットインストールから始めました.大体この手の始めはドットインストールかプロゲートですね.


とりあえず手を動かして感覚掴んで,それから書籍をじっくり読み込むのが私の学習スタイルです.


ドットインストール

(・JavaScript入門:https://dotinstall.com/lessons/basic_javascript_v2)

・Electron入門:https://dotinstall.com/lessons/basic_electron


(あ,ステマじゃないですよ)



開発環境構築

※ macOSとUbuntu16.04LTSを想定


1,任意のディレクトリを作る

今回は「time_counter」というディレクトリ名でやっていきます.


2,Node.jsインストール

以下のサイトにアクセスして,Node.jsをインストールします.2つありますが,左側のLTS版を選ぶのが無難かと思われます.


https://nodejs.org/ja/


3,色々インストール

コマンドラインからtime_counterディレクトリまで移動します.

以下のコマンドを叩いていろんなヤツインストールします.


$ npm install electron1.6.1 --save-dev


これで同階層にnode_modulesディレクトリとpackage-lock.jsonファイルが生成されているかと思います.開発バージョンを柔軟にするために引数に「--save-dev」を付けてそのディレクトリのみにelectronを適用してあげます.



アプリつくろー

1,package.json

以下のコードを「package.json」として保存します.


{
"name": "time_counter",
"version": "0.1.0",
"description": "For D-WARS",
"main": "main.js",
"scripts": {
"start": "electron main.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Shimizu_mizu",
"license": "MIT",
"devDependencies": {
"electron": "^1.6.1"
}
}


パラメータ

"name":アプリケーション名

"version":まぁ何でも良いですが,開発段階は0.X台でいいかなと.

"description":アプリの簡易説明

"main":詳細は後述しますが,アプリ起動時最初に読み込まれるJSファイルです.アプリ全体のエントリーポイント(?)

"scripts" > "start":コマンドからより素早くElectronを起動するために必要です.

"author":作成者名

"license":個人でつくるんで,普段使っているMITでいきます


ちなみに,コマンドラインで


$ npm init


と叩けば対話形式でpackage.jsonをつくれます.


2,main.js

Electron製アプリケーションを起動する際,一番最初に読み込まれるJSファイルです.


// main process
'use stript';

const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const Menu = electron.Menu;
const dialog = electron.dialog;

let mainWindow;

let menuTemplate = [{
label: 'D-WARS',
submenu: [
{ label: 'About', accelerator: 'CmdOrCtrl+Shift+A', click: function() { showAboutDialog(); } },
{ type: 'separator' },
{ label: 'Quit', accelerator: 'CmdOrCtrl+Q', click: function() { app.quit(); } }
]
}];
let menu = Menu.buildFromTemplate(menuTemplate);

function showAboutDialog() {
dialog.showMessageBox({
type: 'info',
buttons: ['OK'],
message: 'このアプリケーションについて',
detail: '作成日 : 2018年3月5日\n作成者 : 東京電機大学理工学部学術文化部会ヒューマノイド研究部所属 16RT126 前田泰希\nバージョン : 0.1.0\n説明 : 本アプリケーションは,ヒュー研主催の二足歩行ロボット競技大会「D-WARS」向けに作られたタイム・ポイントカウンターです.'
});
}

function createMainWindow() {
Menu.setApplicationMenu(menu);
mainWindow = new BrowserWindow({
width: 600,
height: 400
});
mainWindow.loadURL('file://' + __dirname + '/index.html');
mainWindow.webContents.openDevTools();
mainWindow.on('closed', function() {
mainWindow = null;
});
}

app.on('ready', function() {
createMainWindow();
});

app.on('window-all-closed', function() {
if (process.platform !== 'darwin') {
app.quit();
}
});

app.on('activate', function() {
if (mainWindow === null) {
createMainWindow();
}
});


menuTemplate ではアプリ画面の左上によくあるメニューバーの表示名・ショートカット・クリック時の動作を記述しています.

{ type: 'separator' }, は区切りの横線です.

function showAboutDialog() では menuTemplate 'About'で表示する内容を記述しています.


mainWindow.loadURL('file://' + __dirname + '/index.html')でこのmain.jsとリンクするhtmlファイルを指定します.メインなので,WEBサーバで最初に読み込まれるindex.htmlという名前にするのがわかりやすくて良いと思います.__dirnameはこのmain.jsの同階層パスです.なので,このJSファイルを,例えば「time_counter/src/renderer/main.js」と「time_counter/index.html」というファイル構造なら,index.htmlはmain.jsから見て2階層上にあるので,記述すべきロードパスは'file://' + __dirname + '/../../index.html'となります.


mainWindow.webContents.openDevTools(); は開発時のみ使います.エラーや注意,デバッグが可能になります.アプリケーション配布時はこの行をコメントアウトするか,削除してからパッケージングします.


3,index.html

以下が中身です.


<!DOCTYPE html>
<html lang="ja">

<head>
<meta charset="UTF-8" />
<title>time_counter</title>

<link rel="stylesheet" type="text/css" href="./css/all.css">
<link rel="stylesheet" type="text/css" href="./css/index.css">
</head>

<body>
<h2 style="text-align: center;">タイムカウンタ</h2>
<ul id="access">
<li>
<a href="./contents/obstacle_race.html">> 障害物競争</a>
</li>
<li>
<a href="./contents/dice_battle.html">> さいころバトル</a>
</li>
<li>
<a href="./contents/robot_league.html">> ロボットリーグ</a>
</li>
<li>
<a href="./contents/battle.html">> バトル</a>
</li>
<li>
<a href="./contents/check.html">> 音量確認</a>
</li>
</ul>
</body>

</html>


複数競技があるので,トップでまず競技選択します.あとはHTMLが読めれば単純な話ですね.


4,カウンタコード(バトル用のみ)

タイム・ポイント両方をカウントしているバトルを例にとって解説です.


とりあえず「contents/check.html」と「js/all.js」を最初にお見せすると.


<!DOCTYPE html>
<html lang="ja">

<head>
<meta charset="UTF-8" />
<title>time_counter</title>

<link rel="stylesheet" type="text/css" href="../css/all.css">
<link rel="stylesheet" type="text/css" href="../css/battle.css">
</head>

<body>
<p id="displayArea">残り<span id="displayTimes"></span></p>
<p id="pointDisplayArea"><span id="displayBluePoints"></span> - <span id="displayRedPoints"></span></p>

<div class="buttonBlock">
<a href="../index.html" id="return">トップへ戻る</a>
<input type="button" value="初期設定" id="settingButton" onclick="setting(180, 0, 0);">
<input type="button" value="開始" id="startButton" class="timeButton" onclick="startCount();">
<input type="button" value="一時停止" id="stopButton" class="timeButton" onclick="stopCount();">
<input type="button" value="再開" id="restartButton" class="timeButton" onclick="restartCount();">
<input type="button" value="リセット" id="resetButton" class="timeButton" onclick="resetCount();">
</div>
<div class="buttonBlock">
<input type="button" value="blue-" id="dePointBlueButton" class="pointButton" onclick="addPoints('blue', -1);">
<input type="button" value="blue+" id="plusPointBlueButton" class="pointButton" onclick="addPoints('blue', 1);">
<input type="button" value="red+" id="plusPointRedButton" class="pointButton" onclick="addPoints('red', 1);">
<input type="button" value="red-" id="dePointRedButton" class="pointButton" onclick="addPoints('red', -1);">
</div>

<script type="text/javascript" src="../js/all.js"></script>
</body>

</html>



'use stript';


var passSec;
var firstTime = 0;
var bluePoints = 0;
var redPoints = 0;

var startButton = document.getElementById("startButton");
var stopButton = document.getElementById("stopButton");
var restartButton = document.getElementById("restartButton");
var resetButton = document.getElementById("resetButton");

var audio = new Audio('../audio/Opening_Buzzer02-1.mp3');


function setting(sec, bNum, rNum) {
firstTime = sec;
var msg = firstTime + "";
displayTimes.innerHTML = msg; // 表示更新

bluePoints = bNum;
redPoints = rNum;
msg = bluePoints + "";
displayBluePoints.innerHTML = msg;
msg = redPoints + "";
displayRedPoints.innerHTML = msg;
}


function blockButton(state) {
startButton.disabled = state[0];
stopButton.disabled = state[1];
restartButton.disabled = state[2];
resetButton.disabled = state[3];
(state[0]) ? setOpacity(startButton, 0.2) : setOpacity(startButton, 1.0);
(state[1]) ? setOpacity(stopButton, 0.2) : setOpacity(stopButton, 1.0);
(state[2]) ? setOpacity(restartButton, 0.2) : setOpacity(restartButton, 1.0);
(state[3]) ? setOpacity(resetButton, 0.2) : setOpacity(resetButton, 1.0);
}


// 要素の不透明度を操作
function setOpacity(elem, op) {
// IE6.0, IE7.0
elem.style.filter = 'alpha(opacity=' + (op * 100) + ')';
// Firefox, Netscape
elem.style.MozOpacity = op;
// Chrome, Safari, Opera
elem.style.opacity = op;
}


// 開始ボタン以外無効化
var buttonState = [false, true, true, true];
blockButton(buttonState);


// カウントが最後まで行ったら終了ブザーを鳴らす
function endCount() {
passSec = 0;
var msg = passSec + "";
displayTimes.innerHTML = msg; // 表示更新
audio.play();
}


// 表示内容
function showPassage() {
passSec--; // カウントダウン
if (passSec <= 0) {
stopCount();
endCount();
}
var msg = passSec + "";
displayTimes.innerHTML = msg; // 表示更新
}


// 開始
function startCount() {
passSec = firstTime; // カウンタのリセット
PassageID = setInterval('showPassage()', 1000); // タイマーをセット(1000ms間隔)
buttonState = [true, false, true, false];
blockButton(buttonState);
}


// 一時停止
function stopCount() {
clearInterval(PassageID); // タイマーの停止
buttonState = [true, true, false, false];
blockButton(buttonState);
}


// リスタート
function restartCount() {
PassageID = setInterval('showPassage()', 1000); // タイマーをセット(1000ms間隔)
buttonState = [true, false, true, false];
blockButton(buttonState);
}


// リセット
function resetCount() {
passSec = firstTime;
var msg = passSec + "";
displayTimes.innerHTML = msg; // 表示更新
clearInterval(PassageID); // タイマーの停止
buttonState = [false, true, true, true];
blockButton(buttonState);
}


// 秒数の加減
function addSec(num) {
passSec += num;
var msg = passSec + "";
displayTimes.innerHTML = msg; // 表示更新
}


// 得点の加減
function addPoints(type, num) {
if (type === 'blue') bluePoints += num;
if (type === 'red') redPoints += num;
msg = bluePoints + "";
displayBluePoints.innerHTML = msg;
msg = redPoints + "";
displayRedPoints.innerHTML = msg;
}


HTML内で記載されているinputタグは,

<input type="button" value="表示文字" onclick="起動するJSの関数名(引数)">

のルールにしたがって記述しています.


「js/all.js」はHTMLのボタンで呼び出す関数をつらつら書いているだけです.JSの暗黙の型変換やコメント,関数を細かく記述しているので,JS読めると多分わかるかと..


タイムカウントが最後までいったらvar audio = new Audio('../audio/Opening_Buzzer02-1.mp3');が呼び出されます.ちなみにブザー音です.


ここまでくれば何となく分かると思いますが,各ファイルは以下の役割を果たしています.

package.json:Electronの設定

main.js:ElectronのためのJSファイル

index.html:メイン

battle.html:タイム・ポイントカウント画面

all.js:動作関数群


WEBサイトで同様のアプリケーションを公開するなら,最低限

index.html

battle.html

all.js

が必要です.package.jsonとmain.jsが消えているだけです.

逆に,もしもHTML・CSS・JSで作ったWEBアプリケーションがあるなら,それをElectronを使ってデスクトップアプリケーションをつくるためには,Electronをインストールしてpackage.jsonとmain.jsを付け加えるだけでソースコードは出来上がってしまうのです.



パッケージング

macOS用の手順としては,まずアプリケーションのディレクトリ全てをasarというツールでアーカイブにします.んで,そのasarアーカイブをelectron-packagerというツールでアプリケーションにします.


↓実際のディレクトリ

sk.png 


1,asar

この子はグローバルにインストールして良いかと.


$ npm install -g asar


アーカイブ化するには,以下のルールに従います.


$ asar pack {アプリのディレクトリ} {出力名}.asar


例:

$ asar pack ./time_counter t_count.asar


2,electron-packager

こいつもグローバルでインストール.


$ npm i electron-packager -g


macOS向けパッケージング例

$ electron-packager ./time_counter time_counter --platform=darwin --arch=x64 -- electronVersion=1.4.13

Windows向けパッケージング例

$ electron-packager ./time_counter time_counter --platform=win32 --arch=x64 -- electronVersion=1.4.13



結果

time_counter.gif 

GIFにするとちょいとせっかちですが,ちゃんと動いています.状況によって触れられるボタンとそうでないボタンに分かれるので,誤操作を防ぐことができます.



しめ

最初にいった通り,WEB系の知識だけでいろんなOSに対応したデスクトップアプリが作れちゃう現代なので,誰でもお手軽にフリーソフト的なものを配布することもできちゃいます.

昔は「こういうフリーソフトないかなー」て思って窓の杜に行って見たりしてきましたが,今は比較的簡単に配布者側にまわれるのは感慨深いです.

Electronの元はWEBアプリなので,セキュリティにも注意を払わなくてはなりません.脆弱性をついて任意のコードを実行される恐れがありますから.責任を伴う配布は誰だって厳しいかもですね..






編集 / 2018.03.26 / コメント: 0 / トラックバック: 0 / PageTop↑
活動報告 No.153 公称ICS3.6未対応でもRCB-4HV経由で任意のサーボの現在角度を読み取ったり色々。。
カテゴリ: 未分類

明けましておめでとうございますぁあっす。さすがにヒュー研ブログをサボりすぎた2年のマエダです。
いや仕方ないんですよ…1月は期末期間で2月は大会ラッシュじゃないですか…。。

ま、新年の挨拶よりも言い訳が多くなりそうなのでさっさと本題に入ります。

近藤科学さんのサーボモータの通信規格に関して、ICS3.5以降はコマンドとPWMのどちらかで固定角度を決められますよね。そんなICS3.5ですが、ICS3.6は今まで主流だったICS3.5のコマンド群に加えて、任意のサーボの角度データを抽出できるコマンドが追加されました。
ICS3.5以下はどうやっていたのか明確な答えを見つけることができませんでしたが、サーボに目標値付コマンドを送った後の返り値を見たり、教示機能で読み取っていたんじゃないかと思います。

任意のサーボの角度データを抽出する技術ってのは、今まで皆さんが参加してきたであろうROBO-ONEとROBO-ONE lightから、最近新設されたROBO-ONE autoに機体を流用し易くなるものだと考えています。

あまりピンと来ないかと思いますが、ちょいと図解してみます。
まず、ROBO-ONEなど操縦者が人間の場合、機体とモーション、操作母体の関係は以下の通りです。

スライド1



そして、ROBO-ONE autoなど操縦権限がコンピュータの場合、機体とモーション、操作母体の関係は以下の通りです。


スライド2



 操作母体のやっていることは、「戦況によって限られたモーションデータのどれを再生するか判断している」です。条件分岐と言うことにすると、例えば、相手がちょいと遠ければ近づいて攻撃しよう、とか人間にも明示的ではないもののある程度の条件分岐を試合中にやっています。

ROBO-ONEからROBO-ONE autoに機体を流用しないとなったら、外界認識システムから基板、モーションの分岐、そもそもの機体に至るまでイチから機体を作り直すしかありません。しかし、もしもコンピュータからコントローラのコマンドを送れたらどうでしょうか。コンピュータは自律するコントローラと同等とみなせるので、ROBO-ONEの機体にCOM端子と電源だけ繋いで即ROBO-ONE auto仕様にすることにが出来てしまいます。

上記を理由に、コンピュータからRCB-4HVに適切なコントローラのボタンデータを送ったり、条件分岐するために任意のサーボの角度データを抽出したり指定したりできることは、auto用処理モジュールを製作するのに非常に有用なのです。



使用機器:

  • ラズパイ 3B
  • KRS-2542HV
  • RCB-4HV
  • Serial USB Adapter HS


です。USB経由でラズパイからRCB-4HVにコマンドを送信して、任意のサーボの角度データを持ってきてもらったり指定してあげたりします。



準備


ラズパイに近藤科学製のシリアルUSBを接続して、利用可能な状態にする必要があります。

近藤科学は以下の記事にてLinuxでシリアルUSBを利用する方法を紹介していますが、これではできません。注意です。

http://kondo-robot.com/faq/usb-adapter-for-linux


上記サイトは2010年に書かれたものです。今は以下の記事を参考にセットアップすれば一応使えます。

https://gpsnmeajp.blogspot.jp/2015/07/ftdi-ft232rlusbraspberry-pi.html


root権限じゃないと通らないです。スクリプト作って起動時に実行するようにすれば良いでしょう。



角度を指定する


コマンド方式で指定していきます。

コマンドのルールは以下の通りです。


[0x07, 0x0F, ICS番号, フレーム数, 角度データ(16進数で)下位, 角度データ(16進数で)上位, これ以前の総和]


1つめ:0x07

固定です。変える必要はありません。


2つめ:0x0F

ここも固定です。変える必要はありません。


3つめ:ICS番号

SIO端子が1〜4番ピンに刺さっていれば、「サーボID番号 × 2」がICS番号です。

SIO端子が5〜8番ピンに刺さっていれば、「サーボID番号 × 2 + 1」がICS番号です。


4つめ:フレーム数

まんまフレーム数です。1とか30とか60とか。。


5つめ・6つめ:角度データ(16進数で)下位・上位

近藤科学の定めるサーボの角度データは


35007500(ニュートラル)〜11500


です。これらを16進数で表すと、


0DAC1D4C(ニュートラル)〜2CEC


です。真ん中を例にとると、


角度データ(16進数で)下位 = 4C

角度データ(16進数で)上位 = 1D


です。言葉でいうと、近藤科学規格のサーボポジション数値の16進数版を前半と後半で2文字ずつ分けたとき、それぞれ上位と下位になります。


7つめ:これ以前の総和

1つめ〜6つめの数値を全て足し合わせたものです。


例:

例を示します。

・RCB-4HVの1〜4番ピンのどれかにサーボを接続してある

・サーボのIDは0番

・フレーム数は30

・サーボの目標位置は近藤規格で3500(-135度)

のとき、

[0x07, 0x0F, 0x00, 0x1E, 0xAC, 0x0D, 0xED]

というコマンドを送信すればサーボが3500の位置に動いてくれます。「0x」は16進数であることを示しています。ちなみに10進数で表すと、

[7, 15, 0, 30, 172, 13, 237]

です。

[0x07, 0x0F, ICS番号, フレーム数, 角度データ(16進数で)下位, 角度データ(16進数で)上位, これ以前の総和]

と比較してみてください。



ほんで、以下のプログラムをゴリゴリ書いていきます。Python3.5.3推奨です。ちなみにまだ未完成ですので、コピペは後でお願いします。


↓ 横スクロールできます ↓ ※ まだ未完成

import serial
from time import sleep


def set_pos(ser, sio_pin, servo_id, frames, deg):
# sioピンのチェック
if 1 <= sio_pin <= 4:
ics = servo_id * 2
elif 5 <= sio_pin <= 8:
ics = servo_id * 2 + 1
else:
return False

# 普通の角度か近藤規格の角度データかチェック
if -135 <= deg <= 135:
kondo_deg = 800.0 * deg / 27.0 + 7500.0
elif 3500 <= deg <= 11500:
kondo_deg = deg
else:
return False

deg_16 = hex(int(kondo_deg)) # 16進数に変換
deg_16 = deg_16[2:].zfill(4) # 0x以降を抽出して0パディング
deg_16_H, deg_16_L = int(deg_16[:2], 16), int(deg_16[2:], 16) # 上位と下位で分割
tail = 0x07 + 0x0F + ics + frames + deg_16_L + deg_16_H # 送信するコマンドの末尾に必要らしい(用途不明)
com_line = [0x07, 0x0F, ics, frames, deg_16_L, deg_16_H, tail] # 完成!!

ser.write(com_line)
sleep(2) # どう設定しようか。。

return True


def main():
ser = serial.Serial('/dev/ttyUSB0', 115200, parity=serial.PARITY_EVEN, timeout=1)
print('シリアルポートを開きました')

_ = set_pos(ser, 1, 0, 30, 0)

ser.close()
print('シリアルポート閉じました')


if __name__ == '__main__':
main()


set_pos関数にはPySerialでシリアル接続した際の返り値とsioピンの番号、サーボのID、フレーム、角度が引数として必要です。

上の例ではsioピン1番に接続されたID0のサーボをフレーム数30でニュートラルの位置まで動かしています。

-135 ~ 135の間で指定すれば自動的に単位が[度]として処理されますし、3500 ~ 11500の間であれば近藤規格のパラメータで処理されます。


set_pos関数の最後の方にあるsleepでは小数点使用可能で秒単位の待ち時間が指定できますが、どれくらい待てば良い

のやら。。例では適当にとりあえず2秒待っています。


動いてくれたら嬉しいっス。。


しかしバグ発生!!


先ほど送信コマンドのルールとして


[0x07, 0x0F, ICS番号, フレーム数, 角度データ(16進数で)下位, 角度データ(16進数で)上位, これ以前の総和]


と紹介しましたが、これの7要素目の「これ以前の総和」が256以上になると16進法で2バイトになってしまうので送信できませんでした。分割してもダメです。

RCB-4リファレンスを読み返してみると、


「チェックサム: 1byte⽬のサイズからチェックサムの1byte⼿前までのデタを加算した下位1byte」


と書いてありました。

というわけで、10進法で256以上の場合は下位1byteだけを抜き出してみます。


先ほどのset_pos関数を以下に書き換えます。


 
def set_pos(ser, sio_pin, servo_id, frames, deg):
""" 任意の1つのサーボをフレーム数と角度を指定して動かす """
# sioピンのチェック
if 1 <= sio_pin <= 4:
ics = servo_id * 2
elif 5 <= sio_pin <= 8:
ics = servo_id * 2 + 1
else:
return False

# 普通の角度か近藤規格の角度データかチェック
if -135 <= deg <= 135:
kondo_deg = 800.0 * deg / 27.0 + 7500.0
elif 3500 <= deg <= 11500:
kondo_deg = deg
else:
return False

deg_16 = hex(int(kondo_deg)) # 16進数に変換
deg_16 = deg_16[2:].zfill(4) # 0x以降を抽出して0パディング
deg_16_H, deg_16_L = int(deg_16[:2], 16), int(deg_16[2:], 16) # 上位と下位で分割
com_line = [0x07, 0x0F, ics, frames, deg_16_L, deg_16_H]

tail = 0x07 + 0x0F + ics + frames + deg_16_L + deg_16_H # 送信するコマンドの末尾に必要らしい(用途不明)
if tail >= 256:
# 10進数でいう256以上は16進数で2byteになるけど,下位を採用して切り抜ける
tail_16 = hex(tail)
com_line.append(int(tail_16[-2:], 16))
else:
com_line.append(tail)

ser.write(com_line)
_ = ser.readline()

return True




角度を読み取る


角度を読み取るときに使う特別なコマンドのルールはありません。


手順としては、まずHeartToHeart4のモーション作成パネルにて、「GetValue」を引っ張り出してきます。


キャプチャ



GetValueのブロックをダブルクリックしたら、以下の画面のように設定します。


キャプチャ2



現在値読み取りで返り値をCOMポートに出力します。ラズパイは4HVから返ってくる値を読み取れば良いだけです。


サーボの現在位置は人によって違うと思うので、ここは適宜対応お願いします。


設定したら、一番下にコマンドが出てくるので、メモしておきます。これが任意のサーボの角度を読み取るコマンドになります。


Python側のプログラムは関数だけ見せると以下になります。



↓ 横スクロールできます ↓

from time import sleep

def get_pos(ser, com_line, krs_deg=False):
""" コントローラに配置されたボタンを用いて任意のサーボの現在角度を読み取る """
for _ in range(100):
sleep(0.01)
ser.write(com_line)
data = ser.readline() # バイト列で受信
r_data = data.hex() # バイト列から16進数に変換
'''
r_data[0:2] = '05': 0x05はサーボの角度読み取り返り値を示す
r_data[2:4] = サーボモータのID番号 <- 使っていないけど一応
r_data[6:8] + r_data[4:6] = 角度データ(3500 ~ 7500:ニュートラル ~ 11500)
'''
if r_data[0:1] is '0' and r_data[1:2] is '5':
deg_16 = r_data[6:8] + r_data[4:6]
kondo_deg = int(deg_16, 16) # 16進数から10進数に変換
return kondo_deg if krs_deg is True else int(0.03375 * (kondo_deg - 7500))

return None



ざっとこんな感じです。0.01秒間隔で100回のループを行なっているのは、角度によって値が返ってくるまでの時間にばらつきがあるからです。



引用: http://kondo-robot.com/faq/ics3_6function

kondo.png 

上のグラフはICS3.6で角度を読み取るコマンドを発行したのち返ってくるまでの時間経過を表しています。

今回は4HV経由で指令を出していますが、上と同様なことが言えます。角度によって得たい数値が返ってくるまでにラグがほとんどなかったり0.5秒以上あったりです。なので、そこのばらつきを関数内にてfor文とsleepを使って吸収しています。


get_pos関数の引数にkrs_deg=Falseとありますが、これは返り値が近藤規格の角度データにするか否かを設定します。デフォルトのFalseであれば-135 ~ 135度の間で返ってきますし、Trueにすれば3500 ~ 11500の間で返ってきます。


get_pos関数は自律制御に使うカメラの角度に使おうとしているので、使用目的に合わなければ関数が宣言されている行の引数をkrs_deg=Trueとしてもらえればデフォルトで3500 ~ 11500の間で返ってきます。



今まで紹介してきた関数群の使用例は以下の通りです。


↓ 横スクロールできます ↓

import serial
from time import sleep


def set_pos(ser, sio_pin, servo_id, frames, deg):
""" 任意の1つのサーボをフレーム数と角度を指定して動かす """
# sioピンのチェック
if 1 <= sio_pin <= 4:
ics = servo_id * 2
elif 5 <= sio_pin <= 8:
ics = servo_id * 2 + 1
else:
return False

# 普通の角度か近藤規格の角度データかチェック
if -135 <= deg <= 135:
kondo_deg = 800.0 * deg / 27.0 + 7500.0
elif 3500 <= deg <= 11500:
kondo_deg = deg
else:
return False

deg_16 = hex(int(kondo_deg)) # 16進数に変換
deg_16 = deg_16[2:].zfill(4) # 0x以降を抽出して0パディング
deg_16_H, deg_16_L = int(deg_16[:2], 16), int(deg_16[2:], 16) # 上位と下位で分割
com_line = [0x07, 0x0F, ics, frames, deg_16_L, deg_16_H]

tail = 0x07 + 0x0F + ics + frames + deg_16_L + deg_16_H # 送信するコマンドの末尾に必要らしい(用途不明)
if tail >= 256:
# 10進数でいう256以上は16進数で2byteになるけど,下位を採用して切り抜ける
tail_16 = hex(tail)
com_line.append(int(tail_16[-2:], 16))
else:
com_line.append(tail)

ser.write(com_line)
_ = ser.readline()

return True


def get_pos(ser, com_line, krs_deg=False):
""" コントローラに配置されたボタンを用いて任意のサーボの現在角度を読み取る """
for _ in range(100):
sleep(0.01)
ser.write(com_line)
data = ser.readline() # バイト列で受信
r_data = data.hex() # バイト列から16進数に変換
'''
r_data[0:2] = '05': 0x05はサーボの角度読み取り返り値を示す
r_data[2:4] = サーボモータのID番号 <- 使っていないけど一応
r_data[6:8] + r_data[4:6] = 角度データ(3500 ~ 7500:ニュートラル ~ 11500)
'''
if r_data[0:1] is '0' and r_data[1:2] is '5':
deg_16 = r_data[6:8] + r_data[4:6]
kondo_deg = int(deg_16, 16) # 16進数から10進数に変換
return kondo_deg if krs_deg is True else int(0.03375 * (kondo_deg - 7500))

return None


def main():
ser = serial.Serial('/dev/ttyUSB0', 115200, parity=serial.PARITY_EVEN, timeout=1)
print('シリアルポートを開きました')

# 角度を読み取るコマンド(HTH4GetValueから要確認)
get_com_line = [0x0A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x96, 0x00, 0x02, 0xC2]

_ = set_pos(ser, 1, 0, 30, 135) # 指定角度に固定
sleep(1)
d = get_pos(ser, get_com_line, krs_deg=False) # 現在角度検出

print('検出角度: ' + str(d))

ser.close()
print('シリアルポート閉じました')


if __name__ == '__main__':
main()



set_pos関数とget_pos関数の間にsleepを入れています。今は適当ですが、sleepをいい感じで入れないと、サーボが目標値に達する前に読み取りが呼び出されます。


一応set_pos関数内にser.readline()を入れてみたんですが、非ブロッキングなのかな?返り値が来るまで止められないっぽいです。


set_pos関数の仮引数にタイムアウトを置きつつ、返り値が来るまでwhile文で回して、返ってきたらブレイクするのがいいのかなぁ、と思ったり。。



おわり


てなわけで今回は公称ICS3.6未対応でもRCB-4HV経由で任意のサーボの現在角度を読み取ったり角度を指定したりをPythonで制御する記事でした。







編集 / 2018.02.11 / コメント: 0 / トラックバック: 0 / PageTop↑
プロフィール

ヒュー研の中の人

Author:ヒュー研の人
このブログは東京電機大学理工学部ヒューマノイド研究部の公式ブログです。2012年から部に昇格しました!
その日の活動や大会の記録をできるだけ更新していきたいです!!

☆だいたい金曜日前後に更新します☆

FC2カウンター
カレンダー
07 | 2018/08 | 09
- - - 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 -
リンク
ブロとも申請フォーム
携帯でみるには↓
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。