XMPP チャットと bot でタイピング練習

スウちゃん (仮名、4年生) がパソコンでやりたいことのひとつが「字を打てるようになる」こと。

学校でローマ字も習ったし、パソコンには以前からちょっとは触っているので、人差し指打法でたどたどしくは打てます。せっかくだから「正しい」タイピング技法 (タッチタイピング) を習得させてやりたいと思っています。父である私はそれをやらずに我流のまま来てしまいました。ある程度までは速く打てるものの指が定まっていないため間違いが多く、打ち直しのため結局は遅いことになってしまっています。きれいにすらすらと打てる人は本当に間違いも少ないので、スウちゃんには最初からその方向でいってほしいと思っているのです。

子どもでもできそうな練習アプリやサイトを見て回りました。Windows 向けはまず却下。ウェブ版のほとんどは Flash ですが、いまどきこれで大丈夫なのでしょうか。Debian の Firefox に Flash を入れて動かすのが面倒なので、これもできるだけ避けたいところです。

いずれにしても、練習の順番に疑問を感じます。たいていのものはまずホームポジション fj からです。しかしこの2字とも日本語での出現頻度はかなり小さいはずです。jjj fff など日本語でもないから、つまらないことこの上なしです。もっと「日本語のローマ字入力のためのタイピング練習」というのがないものでしょうか。

ちょっと検索してみて、こんな意見を見つけました。「これぞ最速!ブラインドタッチ(タッチタイピング)の効率的な練習法」です。

  1. 指を基本となるホームポジションに置く
  2. パソコンのキーボードを見ながらローマ字のAIUEOを入力する
  3. キーボードを見ずに、パソコン画面を見ながらAIUEOを何度も入力する
  4. 子音を入れつつ練習する
  5. ……

そうだよなあ。この順番のほうがうんと納得がいきます。練習ソフトでこの順番になっているものは意外と見つかりません。

というわけで、既存の練習ソフトに頼らないことにしました。

前回、チャット (XMPP) のクライアント使えるようにしたので、チャットの相手側に私がいて「あお」「いおえ」……などと打ってやり、それをオウム返しに打つことにしました。日本語にない単語になってしまいますが、この際しばらく我慢してもらうことにして。

ついでに、日本語でのキーの出現頻度を検索して見つけた「ローマ字頻度表」によると、母音、n の後は t, k, s, … らしいので、この順に1字づつ (ひらがなでいえば5音づつ) 増やしていくことにしましょう。

さて、ずっと付き合うのは私もたいへんなので、Errbot が相手をするようにプラグインを書いてみました。超いい加減ですがメインはこんな感じ。

    @re_botcmd(pattern=r"^(.*)$", prefixed=False, flags=re.IGNORECASE)
    def question(self, msg, match):
        """文字列を表示する。それを入力してみてください。"""
        if not self.active:
            return None

        s = [[] for i in range(self.levelmax+1)]
        s[0] = 'あいうえおん' * 15
        s[1] = s[0] + 'かきくけこたちつてと' * 10
        s[2] = s[1] + 'さしすせそやゆよ' * 10
        s[3] = s[2] + 'なにぬねのはひふへほ' * 8
        s[4] = s[3] + 'らりるれろ' * 8
        s[5] = s[4] + 'まみむめもわを' * 6
        s[6] = s[5] + '、。ー' * 5
        s[7] = s[6] + 'がぎぐげござじずぜぞ' * 5
        s[8] = s[7] + 'だでどばびぶべぼ'  * 3
        s[9] = s[8] + 'ぢづ'
        s[10]= s[9] + 'ぱぴぷぺぽ' * 2

        reward1 = ['😀','😃','😉','🙂','❤','💗','💞','🎀','👌','✌','👏']
        reward2 = [' OK! ',' いいね! ',' ばっちり! ',' うまい! ',' じょうず! ']

        if match.group(0) == self.get('word'):
            yield(random.choice(reward1) + random.choice(reward2) + random.choice(reward1))

        self['word'] = ''.join(random.choices(s[self.level], k=self.length))
        yield(self['word'])

UI は XMPP クライアントだし応答は Errbot にお任せなので、書くのは実質ほんのわずかです。文字どおり子供だましで、そう遠からず飽きてしまいそうですが。

ノートパソコンに Debian をインストール

スウちゃん (仮名、4年生) が「ノートパソコンがほしい」と言い出しました。学校でも PC に少し触ることがあるし、家でも親がスマホやタブレットでなく大きな画面でカタカタやっているので興味はあるのでしょう。ほう、では何をやりたいか書き出してごらん、と言うと、「字を打てるようになる」「ゲーム」「メール」「プリント」とのこと。「プリント」って何と聞いてみると、宿題などのために調べ物をしたらそれをプリンターに出したいということでした。

ラップトップ型パソコン

興味を持ったときがチャンスだと思い、早速探してみました。はじめ中古は考えていなかったのですが本人に「中古でもいい?」と聞いてみたらまったく気にしないとのことで、1年落ちで14インチ、メモリ8GB、SSDが240GBという一段上のスペックのものが安い価格で見つかり早速購入。いまどき OS なしのパソコンを探すのは難しいので Windows10 も込みです。状態説明で「タッチパッドにテカリあり」となっていましたが、仮に新品でも使いはじめたらすぐにこのくらいになってしまうだろうという程度で、それに普段使う角度から見るとまったく気になりません。ほかに特に問題はなく、今のところいい買い物だったと思っています。

さて Windows10 が入っていても、私自身が普段使いしておらず面倒を見きれないので、さっそくおさらばすることにします。購入時に聞いたのですが Windows のプロダクトキーはどこかに記載したものは一切ないとのことでしたので、一度 Windows10 で起動して確認し (その方法はネットで探しました)、控えておきます。これで、そのうちどうしても他所と同じ Windows じゃないとと言い出した時には戻せます。

Debian stretch のインストール

現時点での安定版 Debian stretch をインストールしていきます。

まず親機で netinst イメージ (300MB ほど) を USB メモリにコピーします。これをラップトップ機に挿して起動し、あとは画面の指示に従っていくだけです。途中、ハードウェアを解析し必要なものを要求してくるので、それ (firmware-iwlwifi の deb パッケージ) を親機で 別の USB メモリに入れて、ラップトップ機の別の USB の口から与えました。ネットがつながればあとはあっさりインストール完了です。

task-xfce-desktop を選択したので、LibleOffice はじめ一通りのアプリケーションが揃っています。日本語変換は fcitx と mozc (キー設定はMS-IME互換) の組み合わせ。デスクトップのパネルを下に配置することで Windows にかなり似せました。「学校でさわるのとちょっと違うよ」と言い、スウちゃんも今の時点では難なく受け容れています。

スウちゃんの希望 (「ゲーム」は昨年のクリスマスに Nintendo Switch をもらったこともあって却下) に沿うため、thunderbird をインストールしましたが、実際にはまだ使っていません。XMPP (チャット) クライアントの Gajim を入れてやり、同じ家の中から私が話しかけてやります。スピーディだし、絵文字もすぐ選べるから楽しいし、なんとか返答しようとしてキー入力もやります。動機づけには十分です。

印刷のためには、cups のパッケージを一通りと、うちにあるのは Brother のプリンターなので、メーカーのサイトから入手したドライバーをインストール。そうか、そのドライバーは i386 用なので、その前にマルチアーキテクチャ対応にする必要がありました。

久しぶりにクリーンインストールを行いましたが、それにしても Debian のインストールからデスクトップ環境構築までがこんなに楽になっているとは驚きました。

あらためてチャットサポートを構築する

Converse.js という XMPP の Web クライアントを見つけて「チャットサポートを構築する」の記事を書いたのは2014年初頭でした。Converse.js が公開されて1年めほどでバージョンは 0.7.2 でした。

2018年4月現在の Converse.js は 3.3.4 で、かなり進歩しています。情報もあちこちに出てきて、以前は苦労したことも現在では簡単にできるようになっていました。本家ページ以外に情報が散らばっていましたのでここにまとめてながら、あらためてチャットサポートを設置してみます。

PreBind

その前に、今回の構築には用いませんが、以前は私がよく理解できておらず適当に誤魔化していた PreBind について、簡潔に解説したページ PreBind: connettersi senza password がありましたので、紹介しておきます。

PHP のクラス XMPP Prebind for PHP をダウンロードしておき、次のようにインスタンス化します。

<?php 
include 'xmpp-prebind-php/lib/XmppPrebind.php';

$boshUrl = 'https://example.com:5280/bosh/';  // 環境に合わせて変更
$server = 'example.com';           // 環境に合わせて変更
$username = 'your_username';   // 環境に合わせて変更
$password = 'your_password';   // 環境に合わせて変更
$resource = rand(10000,99999); // ここでは5桁の数字。環境に合わせて変更

$xmppPrebind = new XmppPrebind($server, $boshUrl, $resource, false, false);
$xmppPrebind->connect($username, $password);
$xmppPrebind->auth();
$sessionInfo = $xmppPrebind->getSessionInfo(); // array containing sid, rid and jid

header('Content-Type: application/json');
echo json_encode($sessionInfo);

?>

これだけで動かしてみると分かりますが、sid, rid, jid の3つを JSON 形式で返すものです。

次に、Converse.js の初期化の際に、上のスクリプト名 (prebind.php だとします) を prebind_url で指定します。

<?php
  $boshUrl = 'https://example.com:5280/bosh/';  // 環境に合わせて変更
  $server = 'example.com';      // 環境に合わせて変更
  $username = 'your_username';  // 環境に合わせて変更
  $prebindUrl = 'prebind.php';  // 上のスクリプトの URL
?>
<html>
  <head>
    ...
    <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/css/inverse.min.css" />
    <script src="https://cdn.conversejs.org/dist/converse.min.js"></script>
    ...
  </head>
  <body>
     ...
    <script>
      converse.initialize({
        i18n: 'ja',
        locales_url: 'https://cdn.conversejs.org/locale/{{{locale}}}/LC_MESSAGES/converse.json',
        authentication: 'prebind',
        bosh_service_url: '<?php echo $boshUrl; ?>', 
        keepalive: true,
        jid: '<?php echo $username."@".$server; ?>',
        prebind_url: '<?php echo $prebindUrl; ?>',
        prebind: true,
        ...
      });
    </script>
  </body>
</html>

この HTML は誰にも見られることになりますが、パスワードは事前接続にのみ用いられているので、ここに書かずに済むというわけです。

チャットサポートの構築

XMPP サーバーで匿名接続を利用できる場合、Converse.js の初期化時に authentication: 'anonymous' とすることで、実は上述の PreBind はまったく使わずに済みます。

以下、この匿名接続を使ってチャットサポートを組み立てます。

  • 客側は Converse.js で匿名 (一時的な JID を使う) とする。
  • 窓口側の JID (たとえば support@example.com) は共有名簿として匿名接続時に与えられる。
  • 客側の Converse.js は自動的に窓口側 JID を呼び出しチャット窓を開く。

という流れです。

サーバー側設定

BOSH または Websocket

前述の PreBind でもそうでしたが、BOSH (または Websocket) を利用しますので、XMPP サーバーに付属のそれらの機能を有効にしておきます。Ejabberd の場合、ejabberd.yml は次のようになります。

listen:
  ...
  -
    port: 5280
    ip: "::"
    module: ejabberd_http
    request_handlers:
      "/ws": ejabberd_http_ws
      "/bosh": mod_bosh
    http_bind: true
    register: true
    tls: true
  ...
modules:
  ...
  mod_bosh: {}
  ...

匿名サーバー

通常クライアントがサーバーに接続する場合は、事前に作成しておいた user@example.com という形をした JID とパスワードが必要になります。匿名サーバーは、そのサーバー名だけを指定して接続を試み、@ より前のユーザー名を乱数のようにそのつど生成して接続します。

サーバーアプリケーション Ejabberd の場合、ejabberd.yml に次のように設定して SASL 匿名サーバー (anonymous.example.com という名前だとします) を設定します。

hosts:
  - "example.com"
  - "anonymous.example.com"
  ...
host_config:
  "anonymous.example.com":
    auth_method: [anonymous]
    anonymous_protocol: sasl_anon

なお今回は特に関係ありませんが、匿名サーバーで接続するユーザーについては、悪用を防ぐため権限を絞っておくのが望ましいでしょう。たとえば、別のサーバーとの通信の禁止、MUC 談話室や PubSub ノードの作成の禁止、などです。

窓口側のJID

窓口側のJID (例: support@example.com) の作成は、通常どおり行います。

共有名簿 (shared roster)

匿名サーバーの場合、接続のたびにアカウントが新たに一時的に生成されるため、その JID には相手先名簿がありません。そこで、共有名簿 (shared roster) という機能を使うことにします。

まず Ejabberd の設定ファイル ejabberd.yml でこの機能を有効にしておきます。

modules:
  ...
  mod_shared_roster: {}
  ...

Ejabberd のウェブ管理画面の「バーチャルホスト -> anonymous.example.com -> 共有名簿グループ」の画面で、まずひとつのグループを作成します。たとえば

  • 名前: support
  • 説明: サポート窓口
  • メンバー: support@example.com
  • 表示グループ: (空欄)

のようにします。ここで support@example.com は、窓口側 (問い合わせを受ける側) の JID です。

さらにもうひとつグループを作成します。

  • 名前: all
  • 説明: 全ユーザー
  • メンバー: @all@
  • 表示グループ: support

ここで @all@ は、「全ユーザー」を意味します。

これで、「anonymous.example.com の全ユーザーの名簿に support グループのメンバー (ここでは support@example.com のみ) を自動的にセットする」ことができました。

Converse.js の設定

チャットウィンドウを設置する Web ページで、Converse.js を読み込むようにいくつかの行を書き加えます。Converse.js そのものを同じサイトに置いてもいいですし、CDN から読み込むようにもできます。

<head>
    ...
    <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/css/converse.min.css" />
    <script src="https://cdn.conversejs.org/dist/converse.min.js"></script>
    ...
</head>

それから、パラメータとともに Converse.js を初期化することで、そのページにチャットウィンドウを設置します。基本は

converse.initialize({
        websocket_url: 'wss://example.com:5280/ws/',
        authentication: 'anonymous',
        jid: 'anonymous.example.com',
        auto_login: true,
})

です。ここでは BOSH ではなく WebSocket を使う場合です。匿名接続の場合 jid はサーバー名のみにします。自動ログインでサーバーに接続するのですが、このままでは相手先とのチャットを開くところはできません。

自動で相手先とのチャットを開く

この情報は本家ではないところにありました。Converse opening a chat from API の回答で Converse.js の作者がプラグインを紹介しています。

converse.plugins.add('chatsopen', {
      initialize: function() {
        var _converse = this._converse;
        Promise.all([
            _converse.api.waitUntil('rosterContactsFetched'),
            _converse.api.waitUntil('chatBoxesFetched')
        ]).then(function() {
          _converse.api.chats.open('support@example.com');
        });
      }
    });

初期化時にこれを利用するようにします。

whitelisted_plugins: ['chatsopen'],

コントロールボックスは全部隠してしまうこともできます。

blacklisted_plugins: ['converse-controlbox'],

まとめ

以上をまとめて (ついでに他のパラメータも追加して)、チャットウィンドウを設置する Web ページまるごとの例です。

<html>
<head>
    <meta charset="utf-8"/>
    <title>チャットサポート</title>
    <link type="text/css" rel="stylesheet" media="screen" href="https://cdn.conversejs.org/css/converse.min.css" />
    <script src="https://cdn.conversejs.org/dist/converse.min.js"></script>
</head>
<body>
<h1>チャットサポートのページ</h1>
<p>右下の窓で話しかけてください。</p>
</body>
<script>
    converse.plugins.add('chatsopen', {
      initialize: function() {
        var _converse = this._converse;
        Promise.all([
            _converse.api.waitUntil('rosterContactsFetched'),
            _converse.api.waitUntil('chatBoxesFetched')
        ]).then(function() {
          _converse.api.chats.open('support@example.com');
        });
      }
    });

    converse.initialize({
        i18n: 'ja',
        locales_url: 'https://cdn.conversejs.org/locale/{{{locale}}}/LC_MESSAGES/converse.json',
        authentication: 'anonymous',
        websocket_url: 'wss://example.com:5280/ws/',
        auto_away: 300,
        auto_reconnect: true,
        keep_alive: true,
        jid: 'anonymous.example.com',
        auto_login: true,
        allow_logout: false,
        allow_contact_removal: false,
        allow_contact_requests: false,
        allow_muc: false,
        allow_otr: false,
        allow_registration: false,
        blacklisted_plugins: [
            'converse-bookmarks',
            'converse-controlbox',
            'converse-mam',
            'converse-muc',
            'converse-notification',
            'converse-otr',
            'converse-register'
        ],
        whitelisted_plugins: ['chatsopen'],
        forward_messages: false,
        message_carbons: false,
        message_archiving: 'never',
        use_vcards: true,
        view_mode: 'overlayed',
        visible_toolbar_buttons: {
            call: false,
            clear: false,
            emoji: true
        }
    });
</script>
</html>

実際に設置してみたのが次です (サーバー名など変更)。

醤油差し

多くの人に経験があると思うのですが、醤油差しの液だれは地味にストレスです。これまでに100均のものをはじめいくつも使ってきましたが、どうもしっくりきません。何かいい知恵はないかとネットで検索してみると出るわ出るわ……。皆さん同じような思いをされているのでしょう。

そういうページで紹介されているものは案外種類が少なく、ベスト10となると多少順位は変わっても同じような顔ぶれがランクインしています。

最後に使っていた安物の醤油差しは差し口からの液だれなのか蓋と本体の継ぎ目からの漏れなのか判然としなかったのですが、周囲の汚れることおびただしかったのです。そこで、まず頭で考える私にはひとつこれだけはと思っている点がありました。注ぐ際に醤油が蓋と本体の継ぎ目をまたがなければ、後者の問題は起こり得ません。あとは注ぎ口の形状にのみ注意すればいいのです。

そうすると、あちこちで紹介されている白山陶器 G型しょうゆさし 大 白磁が候補に挙がりましたが、評判をじっくり見ていくと少数とはいえ液だれがあるというのが見つかります。陶製だと精度が出にくいのでしょうか、個体差があるのではないかと思われます。

結局、陶器より精度のよさそうなガラス製(残量もわかりやすいですし)の「iwaki クラフトライン しょうゆ差し 100ml K5023-SV」に決めました。しばらく使ってみましたが、注ぎ口からの液だれはまったくありません。もし蓋のパッキンが劣化して密閉性が多少悪くなっても醤油はそこを通過しないのでぜんぜん問題はありません。もう少し値段が安かったら言うことはありません。

さて、見た目が面白いのでスウちゃん (仮名・9歳) に
父「こっち (本体側) とこっち (注ぎ口側) の醤油の高さは同じになるんだよ」
などと話をしていました。
父「傾けても高さは同じになるよ」
スウ「違うよ」
父「えっ」
スウ「違うよ」
父「……」
よく見ると確かに。注ぎ口のほうは水平ですらありません。うーむ。「それは“表面張力”と言ってだな、」と言いかけましたが、残念ながらスウちゃんはそんなところまで興味はないのでした。もし入試にこんな問題があるとすれば、但し書きがないと「出題ミス」と揚げ足を取られることになりそうです。

Rhythmbox を DLNA クライアントとして使う

何を今さらというような内容かもしれませんが、ちょっと苦労したのでメモしておきます。

やりたいことは以前書いた「リモートに置いてある音楽ファイルをローカルPCのスピーカーで鳴らす」と同じです。あれから時間が経ってまたまた環境がずいぶん変わってしまいました。現在は宅内のリモートマシンで DLNAサーバー (Debian の minidlna パッケージ) を動かしており、それから手元の PC で何らかの手段で音楽を聴きたいのです。

手元の PC (OS は Debian testing です) にはインターネットラジオを聴くのに Rhythmbox がインストールされていて (Debian パッケージで言うと rhythmbox と rhythmbox-plugins)、これで DLNA を使えればいい訳で、検索してみると Ubuntu 情報ですが、パッケージ grilo-plugins-0.3 と libgrilo-0.3-0 を追加インストールすれば左欄に DLNA サーバーが現れるかその一番下の + でサーバーが見つかるようになる……はずなのですが、ぜんぜん反応しません。

なかなか手がかりがなく困り果てていたのですが、ふと

(rhythmbox:16045): Grilo-WARNING **: [dleyna] grl-dleyna-servers-manager.c:138: Unable to fetch the list of available servers: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name com.intel.dleyna-server was not provided by any .service files

の警告 (エラーではないのであまり気にしていなかった) の中のキーワードから、dleyna-server という Debian のパッケージを追加したら、無事に左欄の「共有」のところに DLNA サーバーが現れました。

結局、必要なのは

  • rhythmbox
  • rhythmbox-plugins

  • grilo-plugins-0.3

のほかに

  • dleyna-server

でした (依存関係でそのほかいくつか)。

しかし、dleyna-server に言及している情報にほとんど行き当たらなかったのはどうしてだろう……。

薪ストーブでブルーベリーパイ

スウちゃん (仮名、9歳) が突然「リンゴない?」と聞いてきました。数日前に買ってきた冷凍パイシートのパッケージの裏に書いてあるアップルパイの作り方を見ていたのです。「そんな急に言われてもないよ」と答えるしかありませんが、夏に採ったブルーベリーがまだ冷凍庫に残っていたことを思い出し、これでやってみることにしました。

材料

  • 冷凍パイシート 4枚入
  • ブルーベリー 適量 (100gくらい?)
  • 砂糖 適量 (30gくらい)

小鍋 (相変わらず鉄鍋でも気にしないことにします) にザラザラっと入れたので正確にはわかりませんが 100gくらいのブルーベリー、それを見て砂糖も適当に。薪ストーブの天板に載せるとすぐ水がどんどん出てきます。とろみが出るまで1時間ちかく煮詰めました。

1枚のパイシートにブルーベリーのジャムを載せ、その上に短冊状に切ったもう1枚分のパイシートを載せました。パッケージには4枚入だったので、これが2組できます。

焼くのも薪ストーブを使います。うちの薪ストーブの火室は幅18インチ、奥行き21½インチとやたら大きく、またガラス窓の前面以外は1¼インチ厚の耐火レンガで囲われていて、ちょうどピザ窯みたいなものなのです。朝から焚いているのでしっかり熱くなっています。火室内の温度を見極めるのが難しいのですが、暖房としては弱すぎるくらい温度が下がってきた頃がちょうどよくなります。炎が上がっているのではもちろんだめで、ジャムを煮詰めている後半からだんだん火を絞り、残った熾は奥の方に押しやって手前を灰だけにして (そうしないと底が熱くなりすぎ、上面が焼ける前に裏面が焦げてしまいます) 五徳を入れ、そこにパイ生地を載せたスキレットを置きます。

数分で焼き上がります。するとスウちゃんはさっさとそれを持ってお友達のところへ出かけていってしまいました。出来上がりの味はわかりませんが、鍋に残ったジャムを舐めてみたらちょっと甘すぎたかもしれません。今回は冷凍のパイシートを買ってきていたので、たいへん楽でした。

薪ストーブで八朔マーマレード

家の裏にある八朔がたくさん実をつけたので、マーマレードを作ることにしました。肥料どころか普段から何一つ手をかけない放置状態ですから、掛け値なしの無農薬です。

「焦げつかない程度に火を強くして短時間で」らしいですが、量が多いと結局は時間もかかるし何かと大変なので、うちでは薪ストーブで煮込みます。それに金属でなくホーロー鍋のほうがいいらしいですが、それも気にしないことにします。

材料

  • 八朔 2000g
  • 白砂糖 800g

作業1日め

今年一番の雪の後にソリを引いて収穫に行きました。積もった雪のおかげで実に手が届きました。でもこの雪で大枝が折れてしまいました。来年以降はどうなることやら。

八朔をきれいに洗い、4分の1にカットして、外皮と果肉に分けます。

外皮は、千切り (できるだけ薄くスライス) します。今回はスウちゃん (仮名、9歳) が外皮をぜんぶ包丁でスライスしました。けっこう固いし大丈夫なのかと思いましたが割と上手に薄く切り、いつの間にかこんなことも難なくこなせるようになったんだなあと感心しきり。

果肉は、スウちゃんが外皮を切っている間に父がひたすら薄皮剥き。本当の果肉と、薄皮・種を別にして保存しておきます。

2人とも手が忙しいだけなので、たくさん色々な話ができました。普段は宿題などやらなければならないことや楽しいことに気持ちがいってしまうので、じっくり話す機会が実は減っていたのだなと逆に気づかされました。

外皮は水を換えながら揉み洗いを数回、その後たっぷりの水に浸しておきます。そんな感じで1時間ほどで作業終了。

作業2日め

都合で1日あけたので、丸2日近くも水に浸しておいたことになります。途中で何度か水を換えています。

小鍋で、別にしておいた薄皮・種を少量の水で煮はじめます。2時間ほどでどろどろになるのでザルでこし、その汁を後で使います。

水に浸していた外皮を、水切りして鍋に入れ、500ccくらいの水[1]で1時間ほど煮ます。はじめは全部が水に浸らないのでときどきかき混ぜます。

果肉ぜんぶと砂糖の半量、小鍋でできた汁 (増粘剤となるペクチン) を加え、さらに1時間ほど煮ます。瓶の煮沸に湯をたくさん使うので、八朔を煮ている鍋の脇でヤカンなどで沸かしておきます。

八朔を煮ている間に瓶の用意をします。きれいに洗って大きな鍋で煮沸します。大鍋は薪ストーブの上に載せにくいので、これはガスコンロで行います。

さて八朔に戻って、残りの砂糖を加え、水分が少なくなり粘りが出るまで煮ます。焦げつかないように何度もかき混ぜます。ヤカンの湯は煮沸に使ってしまいましたが最後の瓶詰めにもたくさんの湯を使うので、またヤカンで沸かしておきます。

最後は、長期保存のための瓶詰めです。きれいになった瓶にマーマレードを入れていきます。瓶の口とネジの部分をきれいに拭いてから蓋を載せ、ネジ蓋を軽く締めます (蓋の丸い平らな部分と、周囲のネジの部分が分離しているタイプです)。このときはまだ強く締めません。カゴに入れて煮立ったお湯に瓶の下半分を浸し、10分ほど瓶ごと茹でます。これは大鍋であることと瞬発的な火力がほしいのでガスコンロで行います。

充分熱くなったらそこから瓶を引き上げながらネジ蓋を強く締めます。だんだん冷めてくると中の圧力が下がり、「ペコッ」と音がして蓋の中央が凹みます。密封できている証拠です。これで軽く1年は保存できます。半パイント瓶に11本のマーマレードが完成しました。

今回はちょっと手を抜いたこともあって、苦味がやや強くなりました。それはそれで美味しいですけどね。外皮の内側の白い部分を包丁を入れてできるだけ取り除く、外皮を煮る際のはじめの煮汁を捨てる、薄皮・種は使わない、そして砂糖を多めになどすれば、もうちょっと苦味の少ない甘いマーマレードができるかもしれません。

  1. 結局、最後は煮詰めるので水の量は適当です。