Redis は key-value store として使えるデータベースです。いわゆる NoSQL データベースの一つです。
Koe では辞書やユーザーの声などの設定を保存するために使用しています。
コンテナ管理: Docker
Docker は言わずと知れたコンテナ管理ソフトウェアです。コンテナとは、超軽量な Linux の仮想マシンのようなものなのですが、簡潔かつ正確に語るのは難しいので詳細は割愛します。
Koe ではdocker-compose.ymlを配布しており、コマンド一つで簡単に Koe、Redis、VOICEVOX Engine のコンテナを起動・停止できるようにしています。
リリース自動化: GitHub Actions
GitHub Actions は、GitHub の管理する仮想マシン上で様々なイベントをトリガーとして任意の処理を実行できるサービスです。
Koe では以下のような処理を自動化しています。
さて、今回の記事では、今年の春に非プログラマーと協同で web ページの作成を担当し、リーダーとして動いていた経験をもとに、当時意識していたことを書きたいと思います。
目次
はじめに:背景
問題点
解決策
導入:開発裏話
結果
おわりに
リンク
1. はじめに:背景
私は別の団体と兼部をしているのですが(以下、G隊)、G隊では春の学祭にてクイズを使った出し物をしようということになりました。教室の中で完結するのですが、スタンプラリーのように順番に数問のクイズを解いていく形式で、最初の頃は設問を印刷して来場者に自分のペースで順番に解いてもらおうという議論でした。しかし、「様々な種類のクイズを用意したい」「答えは別用紙で用意する必要がある」「感染予防のため同じ紙を何枚もすらなければならない」などの理由から紙媒体ではなく電子媒体を用い、例えばQRコードを読み取ってもらってリンク先の web ページでクイズを読めるようにしようということになりました。結果的に印刷費用を抑えられるという利点もありました。
2. 問題点
非常にスマートで費用もかからないので現代の価値観に合った素晴らしい企画に思われましたが、ひとつだけ問題がありました。それは web ページを作ることができる人材の不足でした。
当時は私一人が G 隊の web 開発を担当していました。クイズ作成は複数人のチームで分担して行っていましたが、それを web ページにする仕事はひとりでやるには業務量が多すぎました。ぜひ web ページ作成も分担したいところですが、G 隊には web プログラマーは私一人しかいなかったので HTML & CSS で対応することは困難でした。
3. 解決策
HTML & CSS が使えないので Markdown 記法を用いて書けるシステムを採用すべきだと考えました。
Visual Studioで ASP.NET Core Web API のプロジェクトテンプレートを選択して作成します。
実行するとhttps://localhost:<port>/swagger/index.htmlが自動的に開き最初からある WeatherForecast API が表示されます。ここで<port>ランダムに選択されたポート番号です。よく見るSwaggerページだと思います。Try it out で試しに実行することもできます。
サンプルデータ用のサービスを作成する
まず次のようなTodoItemクラスを作成します。
public class TodoItem
{
public int Id { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public DateTime DueDate { get; set; }
}
ASP.NET Core は依存性注入(DI)を採用しているのでITodoItemContainerを API Controller で使うためにはDIコンテナに登録する必要があります。 Program.cs でbuilder.Build();が呼び出されている箇所より上でbuilder.Services.AddSingletonを呼び出せば登録することができます。
今回は次のコードをを追加しました。
API コントローラーはControllerBaseを継承して作ります。これを継承することで応答に便利な関数を利用できます。
[ApiController]
public class TodoItemsController : ControllerBase
ApiCotroller属性をつけると以下のようなAPIの動作を有効化できます。
属性ルーティング要件
自動的な HTTP 400 応答
バインディング ソース パラメーター推論
マルチパート/フォーム データ要求の推論
エラー状態コードに関する問題の詳細
以下のように API コントローラーにメソッドを定義することでアクションを設定できます。ApiController属性を付けているとHttpGet属性なしでもメソッド名にGETPOSTPUTDELETEが含まれていると自動的にHTTPメソッドとして登録されますが、Swaggerが認識できないのでHttpGet属性をつけるほうが良いです。
[HttpGet]
public ActionResult<TodoItem> GetTodo() { }
[Route("api/[controller]")]
[ApiController]
public class TodoItemController : ControllerBase
{
private ITodoItemContainer _todoItemContainer;
public TodoItemController(ITodoItemContainer todoItemContainer) {
_todoItemContainer = todoItemContainer;
}
[HttpGet("{id}")]
public ActionResult<TodoItem> GetTodo(int id) {
var todo = _todoItemContainer.GetTodoItems().FirstOrDefault(t => t.Id == id);
return todo is null ? NotFound() : Ok(todo);
}
[HttpPost]
public ActionResult<TodoItem> AddTodo([FromBody] TodoItem item) {
var todo = _todoItemContainer.AddTodoItem(item);
return todo is null ? BadRequest() : CreatedAtAction(nameof(GetTodo), new { Id = todo.Id }, todo);
}
}
完成
他にPUTDELETEメソッドを追加して最終的に次のようになりました。
TodoItemControllerの実装
[Route("api/[controller]")]
[ApiController]
public class TodoItemController : ControllerBase
{
private ITodoItemContainer _todoItemContainer;
public TodoItemController(ITodoItemContainer todoItemContainer) {
_todoItemContainer = todoItemContainer;
}
[HttpGet]
public ActionResult<TodoItem[]> GetTodoList() {
return Ok(_todoItemContainer.GetTodoItems().ToArray());
}
[HttpGet("{id}")]
public ActionResult<TodoItem> GetTodo(int id) {
var todo = _todoItemContainer.GetTodoItems().FirstOrDefault(t => t.Id == id);
return todo is null ? NotFound() : Ok(todo);
}
[HttpPost]
public ActionResult<TodoItem> AddTodo([FromBody] TodoItem item) {
var todo = _todoItemContainer.AddTodoItem(item);
return todo is null ? BadRequest() : CreatedAtAction(nameof(GetTodo), new { Id = todo.Id }, todo);
}
[HttpPut]
public ActionResult<TodoItem> UpdateTodo([FromBody] TodoItem item) {
var result = _todoItemContainer.UpdateTodoItem(item);
return result ? Ok(item) : BadRequest();
}
[HttpDelete("{id}")]
public ActionResult DeleteTodo(int id) {
var result = _todoItemContainer.RemoveTodoItem(id);
return result ? NoContent() : BadRequest();
}
}
ITodoItemContainerの実装
public class TodoItemContainer : ITodoItemContainer
{
private List<TodoItem> _items;
public TodoItemContainer() {
_items = Enumerable.Range(1, 10)
.Select(i => new TodoItem {
Id = i,
Title = $"todo item id:{i}",
DueDate = DateTime.Today.AddDays(1)
}).ToList();
}
public IEnumerable<TodoItem> GetTodoItems() => _items;
public TodoItem? AddTodoItem(TodoItem item) {
if (item.Id >= 1 && _items.Any(t => t.Id == item.Id))
return null;
if (item.Id < 1)
item.Id = _items.Max(t => t.Id) + 1;
_items.Add(item);
return item;
}
public bool RemoveTodoItem(int id) {
var todo = _items.FirstOrDefault(t => t.Id == id);
return todo is not null && _items.Remove(todo);
}
public bool UpdateTodoItem(TodoItem item) {
var todo = _items.FirstOrDefault(t => t.Id == item.Id);
if (todo is null)
return false;
_items[_items.IndexOf(todo)] = item;
return true;
}
}
あとがき
HTTP Status Code がどれを使えばいいのか全然わからなかったです。加えてうまくSwaggerに反映されてくれなくてよくわからなかったです...
ところがC#(.NET)では、一番簡単なコンソールアプリでも、専用にフォルダを用意し、dotnet new consoleしてプロジェクトを作らねばなりません。
しかも、プログラムが記述されたファイルと実行ファイル以外にいろいろファイルやフォルダが生成される始末で、「書き捨て」をするには余りに仰々しすぎるのです。
ではcscを叩く方法でC#にインテリセンスを効かすにはどうするか。 いくつか方法はあるのですが、最も単純なのは、*.csprojをワークスペースに配置することです。
これはdotnet new consoleすれば簡単に生成できます。
結局dotnet new consoleが必要なものの、1回で済むならだいぶ状況は良くなったと、初めはそう思いました。
これができればあとは好きにC#を書いていけます。
新規にスクリプトファイルを生成するのはdotnet-script new filenameでできます。
(普通にファイルを作っても問題ありませんが、たまにスクリプトファイルとして認識してくれずインテリセンス等が効かなくなることがあります。その場合、VSCodeを開きなおせば問題ありません。)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Text;
using System.Diagnostics;
using System.Dynamic;
using static System.Console;
Infinitode 2にはホットキー機能があり、キーボード入力一つでタワー設置やアップグレードからゲーム進行速度変更までできます。これをpyautoguiで操作しました。しかし、移動の矢印キーはホットキーで変更できず、pyautoguiが利きませんでした。調べた結果、自作キーボードなどに用いられるAtmega32U4というマイコンを積んだArduinoなら操作できるのではと考え、amazonでArduino Pro Microの互換機らしきものを購入しました。あとは
#csvdata.py抜粋
with open(f"csv/{stage}.csv","r") as file:
reader = csv.reader(file)
line = [row for row in reader]
for lineNo in range(line.__len__()):
l=line[lineNo]
if l == []:
continue
if not hotkeys.dic.__contains__(l[0]):
error(f"line {lineNo+1} : \"{l[0]}\" is not defined (csv/{stage}.csv)")
if l.__len__() > 2:
#posありのとき
pos=(int(l[1]),int(l[2]))
elif l.__len__()==2:
#posなしでoptionありのとき
l.__add__(["",l[1]])
if l.__len__() > 3:
#オプションがある時
if hotkeys.dic[l[0]][0]=="u":
for i in range(int(l[3]) if hotkeys.dic[l[0]][0]=="u" else int(l[3])):
algos.append(Algo(line=lineNo,name=l[0],pos=pos,cate=hotkeys.dic[l[0]][0],hotkey=hotkeys.dic[l[0]][1:],curLoop=i,maxLoop=int(l[3])))
elif hotkeys.dic[l[0]][0]=="t":
algos.append(Algo(line=lineNo,name=l[0],pos=pos,cate=hotkeys.dic[l[0]][0],hotkey=hotkeys.dic[l[0]][1:]))
algos.append(Algo(line=lineNo,name=f"ability{l[3]}",pos=pos,cate="a",hotkey=hotkeys.dic[f"ability{l[3]}"][1:]))
elif hotkeys.dic[l[0]][0]=="a":
algos.append(Algo(line=lineNo,name=f"ability{l[3]}",pos=pos,cate="a",hotkey=hotkeys.dic[f"ability{l[3]}"][1:]))
continue
algos.append(Algo(line=lineNo,name=l[0],pos=pos,cate=hotkeys.dic[l[0]][0],hotkey=hotkeys.dic[l[0]][1:]))
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Arduino Uno R3 を PC につないだままの状態で、シリアルモニタを開いて 19200 baud に設定する。もし外してしまっても再接続すればよい。
*s を送信してシグネチャを確かめる。Arduino IDE 2 では Ctrl + Enter が送信。
何も表示されない場合は Arduino Uno R3 を外して Arduino IDE を閉じ、再度 Arduino IDE を立ち上げて Arduino Uno R3 を接続してシリアルモニタを開き、コマンドを再送信してみる。もしそれでもうまくいかなければ、Japanino 用 IDE 等の古いバージョンのシリアルモニタを開いて試してみる。
Arduino Uno R3 に書き込んだスケッチはずっと動いているので、IDE の再起動やバージョン変更に伴うスケッチの再コンパイルは不要。
ちなみに、インストールは Windows に関しては .inf ファイルを右クリック (Windows 11 ならさらに「その他のオプションを表示」) で「インストール」を選択してもいいし、Japanino を接続すればデバイスマネージャーに警告表示になっているデバイスが現れるはずなのでそれを選択し、「ドライバの更新」からダウンロードしたドライバを選択してインストールしてもよい。前者は何も表示されないのでちょっと心配になるが、Japanino を接続して Arduino IDE が認識すればインストールできている (最新版 IDE なら不明なボード扱いになるが問題ない)。macOS は知らないので、自分で調べてほしい。多分普通のドライバインストールと同じ。
IDE
Japanino 用 IDE を使ってもいいのだが、Arduino IDE 2 が使えるようになった今あの太古の昔の IDE は使いたくないだろう。でも朗報だ。普通に接続して表示されたポートを選び、ボードとして「Arduino Pro or Pro Mini」を選択した上で「ツール -> Processor」から「ATmega168 (3.3V, 8 MHz)」を選べば普通にコンパイルと書き込みが行える。これは VSCode で行う場合も同じ。
なんというか、無理!!って言いたくなるような選択肢です。協賛では部室維持につながるような額は期待できず、広告料、企業との交渉、クラウドファンディングは余りに難易度が高く、「多さ課題が苦」の部員が十分にそれに貢献できるとも期待できません。有償 OB 会は KMC で実際に採用されているなど有力でしたが、加入メリットの捻出が課題です。しかし、部室を手放すと以下の懸念がありました。
怪しいかもしれないとは思いつつ、しかしこれはさすがに備え付けだろうと思って処分しなかった下駄箱が、なんと当日にこう言われた訳です。傘立ての壺があったのですが、これについても同様でした。契約当時を知る OB さんとは既に連絡が取れなくなっており、というか誰かも分からず、大家さんに確認を取るも返事がなく、当日にこの有様... 現場は大混乱でした。契約が切れた以上当日中に撤去する必要があり、しかし共用室に入るサイズではありません。先日の業者も、こんなタイミングでいきなり呼んで来てもらえるとは期待できないし、そもそもこの日は年末ということもあり条件は最悪です。