Skip to content

Commit 46c946c

Browse files
committed
Add Uiflow2 functions
1 parent 45b5b92 commit 46c946c

28 files changed

Lines changed: 3293 additions & 0 deletions

uiflow2/README.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# stackchan-uiflow2
2+
## このリポジトリについて
3+
このリポジトリは、[アールティ版スタックチャン](https://rt-net.jp/products/rt-stackchan/)(Stach-chan)の機能をUIFlow2に実装したものです。
4+
5+
UIFlow2は、M5Stackが公式にサポートしている GUIベースのプログラミング環境です。
6+
内部の実装は、カスタム化されたMicroPythonになっており、GUIなしでもプログラム開発を行うことができます。
7+
8+
## 機能
9+
実装した機能は以下の通りです。
10+
11+
+ **顔のレンダリング**
12+
* 通常(瞬きあり)、笑い、怒り、悲しみ、ウィンク(左、右)、顔の向き(左右上下)、その他カスタマイズ可能
13+
14+
![Stack-chan-faces](html/images/face.png)
15+
16+
+ **モータ制御**
17+
* Dynamixel または SG90
18+
+ **音声合成**
19+
* Google Text-to-speech, Voicevox(準備中)
20+
+ **音声合成**
21+
* Google Speech-to-text, Vosk(独自サーバー、準備中)
22+
+ **Webサーバ**
23+
* 独自実装、REST API追加可能
24+
+ **カメラ操作**
25+
* Webサーバを介した画像取得
26+
27+
## Quick Start
28+
### ファームウェアのインストール
29+
ファームウェアは、公式サイトから提供されているUIFlow2のファームウェアをインストールしてください。
30+
31+
- [Core3のファームウェアインストール](https://docs.m5stack.com/ja/uiflow2/m5cores3/program)
32+
33+
### プログラムと設定ファイル等のアップロード
34+
#### プログラムのアップロード
35+
リポジトリの scripts/ 以下にあるPythonのプログラム(main.pyを除く)を /flash/libs の下にアップロードします。
36+
37+
scripts/main.pyを /flashの下にアップロードします。
38+
39+
#### 設定ファイルのカスタマイズとアップロード
40+
設定ファイルとして以下の3つが必要です。conf/の下にデフォルトの設定ファイルがありますので、お使いの環境にあわせて修正してください。
41+
42+
- stackchan.json
43+
- wlan.json
44+
- apikey.txt
45+
46+
###### stackchan.json
47+
スタックチャンのモータと音声認識、音声合成のサーバの設定を行います。
48+
conf/stackchan.jsonには、RT版スタックチャンのための設定を記載しています。
49+
50+
- **motor**: "Dynamixel" または "SG90"
51+
- **tts**: "google" または "voicevox"
52+
- **asr**: "google" または "vosk"
53+
54+
音声合成と音声認識にGoogleCloudを使わない場合には、それぞれのサーバが動作しているIPアドレスの設定が必要です。
55+
56+
###### wlan.json
57+
このファイルでは、WirelessLANの接続設定を行います。
58+
Home, Work, Mobileの3つの設定を行ってください。
59+
プログラムの起動時に、アクセスポイントをスキャンし自動接続を行います。
60+
61+
```
62+
{
63+
"Home": {"essid": "", "passwd": ""},
64+
"Work": {"essid": "", "passwd": ""},
65+
"Mobile": {"essid": "", "passwd": ""}
66+
}
67+
```
68+
###### apikey.txt
69+
このファイルは、GoogleCloudサービスへアクセスするためのAPIキーを設定します。
70+
音声認識および音声合成のAPIキーは、*OPENHRI_KEY*へ記載し、GeminiへアクセスするためのAPIキーは、*GEMINI_KEY*に記載してください。
71+
このパッケージでは、まだ、Geminiの動作検証を終えていませんが、後日、機能追加予定です。
72+
73+
```
74+
OPENHRI_KEY=
75+
GEMINI_KEY=
76+
```
77+
78+
##### 設定ファイルのアップロード
79+
stackchan.json, wlan.json, apikey.txtの3つの設定ファイルを SDカード(/sd) にアップロードしてください。
80+
81+
#### HTMLファイルのアップロード
82+
このパッケージでは、Webサーバを起動します。
83+
WebブラウザからStack-chanのIPアドレスへアクセスすると、SDカードの /sd/htmlの下にあるHTMLファイルを送信します。
84+
サンプルとして、index.htmlとtts_sample.html, favison.ico及び js/joy.min.js がありますので、 */sd/html* の下にコピーしてください。
85+
86+
87+
以上で必要なファイルのアップロードは完了です。

uiflow2/config/apikey.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
OPENHRI_KEY=
2+
GEMINI_KEY=

uiflow2/config/stackchan.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"motor": "Dynamixel",
3+
"sg90_pan": "2",
4+
"sg90_tilt": "9",
5+
"tts" : "google",
6+
"tts_ip": "192.168.0.100",
7+
"asr": "google",
8+
"asr_ip": "192.168.0.100",
9+
"web_server": "80"
10+
}

uiflow2/config/wlan.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"Home": {"essid": "", "passwd": ""},
3+
"Work": {"essid": "", "passwd": ""},
4+
"Mobile": {"essid": "", "passwd": ""}
5+
}

uiflow2/html/asr_tts.html

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!DOCTYPE html>
2+
<html lang="ja">
3+
<head>
4+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
5+
<meta charset="utf-8">
6+
<script type="text/javascript">
7+
async function request_tts(){
8+
var txt = document.getElementById("tts").value;
9+
var url ="/tts"
10+
const response = await fetch(url,{
11+
method: "POST",
12+
headers: {
13+
"Content-Type": "text/plain;charset=utf-8",
14+
},
15+
body: txt
16+
}
17+
);
18+
console.log(response);
19+
}
20+
21+
async function request_asr(){
22+
var max_seconds = Number(document.getElementById("max_seconds").value);
23+
var max_count = Number(document.getElementById("max_count").value);
24+
var threshold = Number(document.getElementById("threshold").value);
25+
var url ="/asr"
26+
27+
const response = await fetch(url,{
28+
method: "POST",
29+
headers: {
30+
"Content-Type": "text/plain;charset=utf-8",
31+
},
32+
body: JSON.stringify({'max_seconds': max_seconds, 'max_count': max_count, 'threshold': threshold})
33+
}
34+
);
35+
console.log(response);
36+
}
37+
</script>
38+
<title>音声認識&音声合成</title>
39+
</head>
40+
<body>
41+
<h1>音声合成</h1>
42+
発話する文字列: <input type="text" id="tts" value=""><button onclick="request_tts()">TTS</button>
43+
44+
<h1>音声認識音声認識</h1>
45+
最大録音時間(秒):<input type="text" id="max_seconds" size="2" value="5"/><br>
46+
発話区間推定 閾値:<input type="text" id="threshold" size="5" value="41"/><br>
47+
発話区間推定 連続検出回数<input type="text" id="max_count" size="2" value="1"/><br>
48+
<button onclick="request_asr()">スタート</button>
49+
50+
<hr>
51+
<a href="/">Back to Top</a>
52+
</body>
53+
</html>

uiflow2/html/favicon.ico

33.7 KB
Binary file not shown.

uiflow2/html/images/face.png

6.14 KB
Loading

uiflow2/html/images/stackchan.png

82.8 KB
Loading

uiflow2/html/index.html

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<!DOCTYPE html>
2+
<html lang="ja">
3+
<head>
4+
<title>Hello Stack-chan</title>
5+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
6+
<meta charset="utf-8">
7+
8+
<script type="text/javascript" src="js/joy.min.js"></script>
9+
<script type="text/javascript">
10+
async function get_camera_image(){
11+
const res = await fetch('/get_camera_image', {
12+
method: "POST", body: ""
13+
});
14+
const { width, height, data } = await res.json();
15+
const canvas = document.getElementById('canvas');
16+
canvas.width = width;
17+
canvas.height = height;
18+
const ctx = canvas.getContext('2d');
19+
const imageData = ctx.createImageData(width, height);
20+
21+
const binary = atob(data); // Base64 → binary
22+
const buffer = new Uint8Array(binary.length);
23+
for (let i = 0; i < binary.length; i++) {
24+
buffer[i] = binary.charCodeAt(i);
25+
}
26+
27+
for (let i = 0, j = 0; i < buffer.length; i += 2, j += 4) {
28+
const value = (buffer[i] << 8) | buffer[i + 1];
29+
30+
// RGB565 → 8bit RGB変換
31+
const r = ((value >> 11) & 0x1F) << 3;
32+
const g = ((value >> 5) & 0x3F) << 2;
33+
const b = (value & 0x1F) << 3;
34+
35+
imageData.data[j] = r;
36+
imageData.data[j + 1] = g;
37+
imageData.data[j + 2] = b;
38+
imageData.data[j + 3] = 255; // Alpha
39+
}
40+
41+
ctx.putImageData(imageData, 0, 0);
42+
}
43+
44+
async function move(){
45+
var pn = document.getElementById('pan').value;
46+
var tlt = document.getElementById('tilt').value;
47+
48+
const res = await fetch('/move', {
49+
method: "POST",
50+
body: "[" + pn + "," + tlt +"]"
51+
});
52+
}
53+
54+
async function move_angle(x, y){
55+
var pn = Math.trunc(x * 0.6);
56+
var tlt = Math.trunc(Math.max(Math.min(-y*0.3, 5), -25));
57+
const res = await fetch('/move', {
58+
method: "POST",
59+
body: "[" + pn + "," + tlt +"]"
60+
});
61+
}
62+
63+
async function face(){
64+
var face_ = document.getElementById('face').value;
65+
const res = await fetch('/face', {
66+
method: "POST",
67+
body: face_
68+
});
69+
}
70+
71+
</script>
72+
</head>
73+
<body>
74+
<h1>アールティ版<br>スタックチャンの操作</h1>
75+
<h3>首振り操作</h3>
76+
<div >
77+
水平方向:<input id="joyX" type="text" value="0"/></br>
78+
垂直方向:<input id="joyY" type="text" value="0"/>
79+
</div>
80+
<div id="joyDiv" style="width:200px;height:200px;margin:10px;"></div>
81+
82+
<script type="text/javascript">
83+
var joyX = document.getElementById("joyX");
84+
var joyY = document.getElementById("joyY");
85+
86+
var joyParam = { "title": "joystick", "autoReturnToCenter": false };
87+
var Joy = new JoyStick('joyDiv', joyParam);
88+
89+
setInterval(function(){
90+
if (joyX.value != Joy.GetX() || joyY.value != Joy.GetY()){
91+
move_angle(Joy.GetX(), Joy.GetY());
92+
}
93+
joyX.value=Joy.GetX();
94+
joyY.value=Joy.GetY();
95+
}, 1000);
96+
</script>
97+
<h3>Face</h3>
98+
顔の表情:
99+
<select id="face" onchange="face()">
100+
<option value="normal">通常(瞬きあり)</option>
101+
<option value="smile">笑い</option>
102+
<option value="anger">怒り</option>
103+
<option value="unhappy">悲しみ</option>
104+
<option value="surprise">驚き</option>
105+
<option value="wink_r">ウィンク(右)</option>
106+
<option value="wink_l">ウィンク(左)</option>
107+
<option value="look_r">右を見る</option>
108+
<option value="look_l">左を見る</option>
109+
<option value="look_u">見上げる</option>
110+
<option value="look_d">見下ろす</option>
111+
</select><br>
112+
<!-- <img src="images/face.png" > -->
113+
<button onclick="get_camera_image()">カメラで撮影</button><br>
114+
<canvas id="canvas"></canvas>
115+
116+
<h3>リンク</h3>
117+
<ul>
118+
<li><a href="asr_tts.html">音声認識&音声合成</a></li>
119+
</ul>
120+
121+
122+
</body>
123+
</html>

uiflow2/html/js/joy.min.js

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)