【Alexa】Alexaのスキルを作って自宅照明を点ける

角度を変えるサーボモータにirMagician(赤外線モジュール)をつけ、それをラズパイに接続します。
で、Alexaからラズパイ上に用意したBottleのサーバにHTTPを飛ばし、それをトリガにirMagicianとサーボモータを動かすことで、自宅照明のON/OFFをAlexa経由でやっちゃおう、というのが今回の趣旨です。
なのでAlexaスキルを作ります。
今回は自作スキルをベータテスト扱いで使うことになるので、使用期限は3ヶ月です。3ヶ月後に何をすればまた有効化できるのか(スキルを作り直す必要があるのか)はまた確認します。
 
irMagicianとサーボモータの操作については以下のとこらへんで書いているため、そちらを参考にどうぞ。一応、一番最後にBottleで動くとサーボモータ+irMagicianのソースを書いときます。ルータのポートを開けておくのは忘れずに。
engetu21.hatenablog.com
 
engetu21.hatenablog.com


1.スキル作成

Alexaの開発者ポータルサイトにアクセス。
developer.amazon.com

f:id:engetu21:20220217213603j:plain
左上のAlexaを押下。
ログインは自分が持っているAmazonアカウントを使用します。

f:id:engetu21:20211213225926j:plain
左上の「スキル開発」→「開発者コンソール」を押下。

f:id:engetu21:20211213230223j:plain
開発者登録をしていないとここで入力が必要になるようです。
サクッと情報を入れて、スキル開発に進みます。
 
f:id:engetu21:20211213230833j:plain
左側の「Alexa Skills Kit」を押下します。
 
f:id:engetu21:20211213231002j:plain
「スキルの作成」を押下することでスキル作成が始まります。
 
f:id:engetu21:20211213231349j:plain
スキル名を設定。「アレクサ、○○」の○○部分にもなるので、
呼びたい名称を設定します。
 
f:id:engetu21:20220217214122j:plain
モデルはデフォルトのカスタム。
 
f:id:engetu21:20211213234251j:plain
バッグエンドリソースのホスティングPythonを選択。
すべて選択したら右上の「スキルを作成」を押下。
 
f:id:engetu21:20220217214538j:plain
テンプレートはデフォルトのスクラッチで作成のままで「テンプレートで続ける」を押下。
 

2.ソースの変更

コードエディタタブを押下し、「lambda_function.py」のソースを変更します。
f:id:engetu21:20220217215904j:plain

import requests
url = 'http://XXXXXXXXXXXXXX'

class LaunchRequestHandler(AbstractRequestHandler):
    """Handler for Skill Launch."""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool

        return ask_utils.is_request_type("LaunchRequest")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        # speak_output = "Welcome, you can say Hello or Help. Which would you like to try?"

        response = requests.get(url)

        return (
            handler_input.response_builder
                #.speak(speak_output)
                .speak(response.text)
                #.ask(speak_output)
                .response
        )

・変更点
1.import requests を追加
2.url = ''でHTTPを飛ばしたいURLを変数に設定
3.speak_output変数の定義部分をコメントアウト
4.response = requests.get(url) を追加
5..speak(speak_output) をコメントアウト
6..speak(response.text) を追加
7..ask(speak_output) をコメントアウト
どうやら「.speak」に設定した文言をAlexaはしゃべってくれるようなので、
ここにHTTPレスポンスでもらったメッセージを指定することで、それをしゃべらせるようにします。
 
変更が完了したところで、右上の「デプロイ」を押下。
 
3.(ベータテストとして)開発スキルの公開
公開タブを押し、公開用の設定を行っていきます。
f:id:engetu21:20220217221723j:plain
誰にも公開しない、自分だけのスキルになるため、正直内容は適当でもいいです。
以下項目をそれぞれ埋めます。
公開名、説明、詳細な説明、サンプルフレーズ、小さなスキルアイコン、大きなスキルアイコン、カテゴリー
 
f:id:engetu21:20220217222329j:plain
上記を埋めると、プライバシーとコンプライアンスの設定画面に飛ぶため、これも※印の部分をすべて回答します。
基本的にすべて「いいえ」で問題なし。
 
f:id:engetu21:20220217222437j:plain
これまでの設定に問題なければ、ベータテスト欄にて、Eメールの設定ができます。
ここで、管理者用メールアドレスとベータテスター向けのメールアドレスを設定します。
自分だけで使う場合は、一緒のメールアドレスでOK。
設定後に「ベータテスト有効化」のボタンを押下。
有効になると同時にテスターメールアドレスにメールが飛ぶため、そちらを確認します。
飛んできたメールの
JP customers: To get started, follow this link:
の文言の下にあるURLを押すことで、Alexa設定画面に飛びます。
※リンクは2つありますが、必ずJP customers: To get started, follow this link:の方を押下すること
 
f:id:engetu21:20220217223113j:plain
上記のようなメッセージが出てくるはずなので、「スキルテスト」を押下。
 
f:id:engetu21:20220217223359j:plain
「有効にする」を押下し、利用が可能となります。
 
なお、スキルが実際にAlexaで使えるようになるには、若干時間がかかる(数分~数十分?)ので、気長に待ちましょう。
 

4.Bottleで動くHTTPサーバのソースとirMagician+サーボモータのソース

書いておかないと自分が困りそうな気がしてきたので記載しておきます。
以下、Bottleで動作するHTTPサーバのソース。

from bottle import route, run, template
import subprocess

@route('/temae_on')
def temae_on():
    subprocess.call("/home/pi/kadensousa/shells/temae_light_on.sh", shell=True)
    response = f'手前の照明をつけました'
    return response

@route('/temae_off')
def temae_off():
    subprocess.call("/home/pi/kadensousa/shells/temae_light_off.sh", shell=True)
    response = f'手前の照明を消しました'
    return response

@route('/oku_on')
def oku_on():
    subprocess.call("/home/pi/kadensousa/shells/oku_light_on.sh", shell=True)
    response = f'奥の照明をつけました'
    return response

@route('/oku_off')
def oku_off():
    subprocess.call("/home/pi/kadensousa/shells/oku_light_off.sh", shell=True)
    response = f'奥の照明を消しました'
    return response

run(host='0.0.0.0', port=50001)

 
以下、HTTPサーバから呼び出されるShellの中身。

#!/bin/bash

echo 0=50 > /dev/servoblaster

python /home/pi/kadensousa/irmcli/irmcli.py -p -f /home/pi/kadensousa/irmcli/light_on.json

sleep 1

echo 0=70 > /dev/servoblaster

何でPythonとShellでそれぞれ分けてんの?って話ですが、最初にそういう風に作ったのでそれに倣ってるだけ、というのが理由で、基本的にPython内でsubprocessで書けばShellで用意する必要はないです。

【Jetson Nano 2GB】PyTorchのインストール

engetu21.hatenablog.com
で導入したjetson-inferenceにあるシェルスクリプトからPyTorchをインストールできる。

$ sudo ~/jetson-inference/tools/install-pytorch.sh


f:id:engetu21:20220101195520p:plain
スペースを押すことで「*」がつくため、この状態でEnterを押下。
必要となるパッケージを自動で取得(apt)してくれます。すごい楽・・・。
終了すると以下のメッセージが出力されます。

Using /usr/local/lib/python3.6/dist-packages
Finished processing dependencies for torchvision==0.7.0a0+78ed10c
[jetson-inference] restoring /usr/bin/ffmpeg from /usr/bin/ffmpeg_bak

[jetson-inference] installation complete, exiting with status code 0
[jetson-inference] to run this tool again, use the following commands:

$ cd /build
$ ./install-pytorch.sh

【Jetson Nano 2GB】Dockerコンテナの導入とカメラモジュールが認識されなかった話

実家に帰っても暇なので、Jetson Nano 2GBとモバイルモニターを持ってってセッティングの続きを実施。
Jetson Nano 2GBで物体認識や物体検出を行うにはDockerを入れるのが手っ取り早いらしい。

以下のページを参考に実施してみる。
arkouji.cocolog-nifty.com

1.Dockerの導入

github.com

上記のGitHubからコンテナを導入する。
どうやらJetPack-L4Tのバージョンは記載しているものに合っていないといけないらしい。
JetPack-L4Tってなんぞや?っと思って調べてみると、L4Tは「JetPack SDK includes the Jetson Linux Driver Package」のこと?らしい。
では、自分が入れているバージョンはどれなのか、というのは、以下の2つのコマンドでそれぞれ確認可能。

cat /etc/nv_tegra_release
# R32 (release), REVISION: 6.1, GCID: 27863751, BOARD: t210ref, EABI: aarch64, DATE: Mon Jul 26 19:20:30 UTC 2021

$ dpkg-query --show nvidia-l4t-core
nvidia-l4t-core 32.6.1-20210726122000

1つ目は、R32とREVISION: 6.1で「L4T R32 6.1」と判断できる。
2つ目は、32.6.1と出てくれるので、そのまま読み取れる。

GitHubに記載されているContainerTagはr32.6.1とあるが、該当するL4T versionはL4T R32 6.0とある。
先程のコマンドで使用しているバージョンはR32.6.1と出たが、これは問題なく動くのだろうか・・・?
まぁとりあえず、説明に沿って実行してみます。

$ git clone --recursive https://github.com/dusty-nv/jetson-inference
$ cd jetson-inference
$ docker/run.sh


※先に取り上げたL4Tのバージョンについては、上記GitHubで取得したjetson-inferenceのtool配下に、l4t-version.shを叩くことでも確認が可能。
$ ~/jetson-inference/tools/l4t-version.sh
reading L4T version from /etc/nv_tegra_release
L4T BSP Version: L4T R32.6.1


docker/run.shを実行すると以下の画面が出てきます。
f:id:engetu21:20211231232208p:plain

好みの?モデルを選択することができます。
選択後に自動でダウンロードされるため、あとは待つだけ。
f:id:engetu21:20211231233214p:plain

モデルインストール後に以下のようにメッセージが出るため、他のモデルもインストールしたいな、というときにはこの通りにするといいらしい。

[jetson-inference] to run this tool again, use the following commands:

$ cd /tools
$ ./download-models.sh

あとはカメラモジュールでテストすればいいのですが・・・

2.カメラモジュール(OV5647)が認識しない

ラズパイで認識されたので、てっきりJetson Nano 2GBも認識すると思いましたが、
どうやらOV5647と呼ばれるカメラモジュール(Raspberry Pi Camera Rev 1.3)は認識されないようです。
does Jetson Nano support CSI camera with sensor ov5647? - Jetson Nano - NVIDIA Developer Forums

どうやらEOLとのことで正式にはサポートしていない模様。
一応ドライバインストールを自力で実施する方法はいかに記載されてますが・・・これを頑張るくらいであれば、素直にUSBカメラをつけたほうが良さそう。
自宅だったらUSBカメラがあるのでリアルタイムでのカメラ確認ができるけど、あいにくと実家なのが悔やまれる。
Omnivision Linux Drivers | OmniVision OV5647 Linux Driver for Jetson Nano | RidgeRun

とりあえず、サンプル動画で色々試してみることとし、カメラによるリアルタイム分析を利用するのはまた今度。

【Windows10/WindowsServer2019】PowerShellで日付ログローテート

1.前置き

Windows10/WindowsServer2019でログローテートをやる場合は、PowerShellで組んでしまうのが便利。
ここに記載しているのは日付での対応なので、ファイルサイズによるものは別のサイトへどうぞ。

2.ローテートプログラム

例えば1年に一回ログをローテーションするとした場合、
大まか流れとしては、ファイルを複製⇒複製したファイルのファイル名に昨年日付を付与⇒元のファイルは内容を削除、かつ作成日時も現在時刻に変更、という流れ。
元のファイルの内容を削除せずファイル丸ごと削除でもいいけど、それはログに書き込むプログラム側がログがないから作ろ!って作りになっていたらそれでも問題ない。
というわけでプログラム。
gist.github.com

一番最初の記述は、ログローテート自身のログ出力設定です(ややこしい)。
毎年ではなく毎月でやりたい、といった場合は、以下のように変数を作ってそれを使うようにします。

$sengetu = (Get-Date).AddMonths(-1).ToString("yyyyMM")


作成日が数年経っているログはもういらない、という場合、
例えば2年以上前の特定のフォルダ以下のファイルをまとめて削除する場合は、パスを指定する形でこのように記述。

Get-ChildItem -Path $Path_file_uploader -Recurse | Where-Object{$_.CreationTime -lt (Get-Date).AddYears(-2)} | Remove-Item -Force
G


このPowerShellファイルを先日取り上げたタスクスケジューラで毎年の1月1日や毎月1日に指定してあげれば、ログローテートの完成です。

【Windows10/WindowsServer2019】タスクスケジューラの設定

毎回忘れるのでメモ。

1.タスクスケジューラの設定

タスクスケジューラ画面の起動方法は二つ。
1つ目はデスクトップ左下の検索アイコンから「タスクスケジューラ」を検索する。
2つ目はナビゲーションウィンドウのPCアイコンを右クリックし、「管理」を押下。

タスクスケジューラライブラリを右クリックし、「タスクの作成」を押下。

1.1 [全般]の設定

f:id:engetu21:20211228153647j:plain

名前は適当でもいい。が、作成した後に変更はできない。
変更する場合は、作成後にエクスポートを行い、それをインポートするときに名前を変える。
その後元々作成していたものは削除する形になる。

セキュリティオプションについて、サーバ運用の場合は
「ユーザーがログオンしているかどうかにかかわらず実行する」
を選択する。

1.2 [トリガー]の設定

f:id:engetu21:20211228150615j:plain
スケジュール指定で起動させる場合は「スケジュールに従う」を選択。
該当日時に一回だけ動かすこともできるし、特定の曜日、月の特定の日だけとかなり自由に設定できる。

マシン起動と同時に動かすプログラムがある場合は、「スタートアップ時」を選択する。

1.3 [操作]の設定

f:id:engetu21:20211228151913j:plain
動かすプログラムを指定する。

PythonがC直下にインストールされている場合、プログラムがprogram配下にある場合は以下のように設定。

プログラム/スクリプト:C:\Python\Python310\python.exe
引数の追加:test.py
開始 :C:\program\test_py_program

プログラム/スクリプトPythonのEXEファイルを指定する。
引数の追加は動かすPythonプログラムファイルだが、ここにパスは記述しない。
パスの記述は開始欄に設定する必要がある。(パスの最後に\は不要)

また、PowerShellで作成したps1ファイルの場合は、以下のように設定する。

プログラム/スクリプト:%Systemroot%\System32\WindowsPowerShell\v1.0\powershell.exe
引数の追加:-ExecutionPolicy Bypass .\test.ps1
開始 :C:\program\test_ps_program

基本的にはpythonの時と同じだが、注意すべきは引数の追加の部分。
「-ExecutionPolicy Bypass」で実行ポリシーを設定している。
Bypassは一番強い権限となるため、基本的にはこれを設定すれば、どのps1も動く(はず)。
恒久的に変更する場合については以下のコマンドをPowerShellで実行する。
> PowerShell Set-ExecutionPolicy Bypass

詳しくはこちら↓
qiita.com

1.4 [設定]の設定

※[条件]の設定は適宜設定で、特に触れることがないので飛ばします。

f:id:engetu21:20211228153432j:plain
[設定]も特に変更なくてもいけますが、サーバとして動かす場合は、「タスクを停止するまでの時間」のチェックは外しておいたほうがよいかと思われます。

【Ubuntu20.04】ConoHa VPSでSSHポートフォワーディング(リモートフォワード)を実現

1.前置き

IoTシステム技術検定中級の受験も終わり、前からやろうと思っていた、Alexa(Echo Flex)による自宅照明の操作を実現したくなったので、今回はその準備を行うことにしました。
 
Alexaから自宅照明をつけるには、まず自宅サーバを作る必要がある。サーバで用意しておいたAPIの受信をトリガにして、赤外線モジュールを利用するようにすればいい。そこらへんは過去にもまとめたので、それの通りにやれば問題ない。
で、Alexaの方は、自作スキルをPythonで作成し(これは今度まとめる)、その自作スキルから用意した自宅サーバAPIにHTTPを飛ばすようにしてあげればいい、となります。
 
で、自宅サーバの公開方法をあれこれと考え、結局採用しようと思ったのがVPSを経由したSSHポートフォワーディング(リモートフォワード)による接続。
今思うと、直接自宅サーバ公開したほうが手間なかったのでは?と思わなくないけど、ルータの設定が不要になる、ラズパイ3にIPTABLSの設定が必要ない、と言った部分のメリットはある。手軽にローカルサーバを簡単に展開するの手段として、ngrok(エングロック)がいいというのは調査して見つけたけど、どうも有料で利用しないとドメインころころ変わるみたいだし、だったらVPS借りてSSHポートフォワーディングできるようにしつつ、他の用途にも使えるようにした方がいいわ、となりました。
Alexaからのアクセス元IPアドレスが分かれば、それで接続元を絞り込むつもりだけど、そこは今後の調整。
 
SSHポートフォワーディングのイメージは以下の通り。
f:id:engetu21:20211226131512j:plain
暫定の形として、インターネットに接続できるブラウザからConoHa VPSで提供されているホストかIPアドレス+ポート番号50001にアクセスする。
ConoHa VPSとラズパイ3は事前にSSHポートフォワーディングによるSSHトンネルができており、アクセスされた通信がラズパイ3のポート8080に転送される仕組み。
Alexaの自作スキルの準備が整えば、このブラウザ部分がEcho Flex(より正確にはその先のAlexaのサーバ)になる。
 
肝となるのは、ConoHa VPSとラズパイ3で事前にSSHトンネルを作っておくというところで、これはConoHa VPSSSHサーバが入っている前提であり、かつファイアーウォール設定などを設けておく必要がある。というわけで設定内容を以下に記載。

2.ConoHa VPSでの設定

2.1 SSHサーバの設定変更

$ sudo vi /etc/ssh/sshd_config
#GatewayPorts no

GatewayPorts yes

PasswordAuthentication yes

PasswordAuthentication no

【追記:どうも時間が経つとSSHトンネリングが切れるので、以下の設定を追加しておく】
#ClientAliveInterval 0

ClientAliveInterval 120

#ClientAliveCountMax 3

ClientAliveCountMax 3

GatewayPortsは今回の対応でyesにするのが必須です。いくつかのサイトを参考にしましたが、これをちゃんと書いてるところが少なく、繋がらない原因がわからず時間がかかりました。
パスワードの認証は不要とします。そもそも公開鍵認証で接続するため、わざわざパスフレーズを入れなくていい。

2.2 ポートの開放

ConoHa VPSでは、ポータルでいくつかのポート設定ができますが、今回の対応にてこれは使用しないように(全て許可に変更)します。
また、OSはUbuntu20.04を使用しているため、マシン側でIPTABLESを使ってIPパケットフィルタリングによる通信制御をします。
 
まずはIPTABLESの設定を永続化(再起動しても同一の設定を受け継ぎ)するように、iptables-persistentをインストールします。

$ sudo apt install iptables-persistent

 
インストール後、IPTABLESを構築するシェルスクリプトを作ります。
IPTABLESコマンドを逐次叩いてもいいけど、シェルスクリプトを作って一気に構築できるようにしておいた方がいいです。
昔からシェルスクリプトで作ってたので今回もそうしてますが、Pythonで作っても問題ない。

$ vi iptables.sh
#! /bin/bash
echo "### iptables_sh起動 ###"

# $IPTABLESパス
IPTABLES='/sbin/iptables'

#自宅のIPアドレス or あるんだったらドメイン
zitaku='hogehoge.com'

# 最初にすべてのルールをクリア
$IPTABLES -F # テーブル初期化
$IPTABLES -Z # チェーンを削除
$IPTABLES -X # パケットカウンタ・バイトカウンタをクリア
$IPTABLES -t nat -F #natテーブルを指定と初期化

# 内部から外に接続した通信に対する、外部からの応答アクセスを許可 #
$IPTABLES -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
#$IPTABLES -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

# 自宅からの22番ポート(SSH)へのアクセスを許可 #
$IPTABLES -A INPUT -p tcp --dport 22 -s $zitaku -j ACCEPT
$IPTABLES -A OUTPUT -p tcp --sport 22 -d $zitaku -j ACCEPT

# 外部から50001番ポート接続許可 #
$IPTABLES -A INPUT -p tcp --dport 50001 -j ACCEPT
$IPTABLES -A OUTPUT -p tcp --sport 50001 -j ACCEPT

# 設定ルール外のINPUT/FORWARDアクセスはログを記録して破棄。OUTPUTは基本的に許可 #
$IPTABLES -A INPUT -j LOG --log-prefix '[iptables INPUT DROP] '
$IPTABLES -A INPUT -j DROP
$IPTABLES -A FORWARD -j LOG --log-prefix '[iptables FORWARD DROP] '
$IPTABLES -A FORWARD -j DROP
#$IPTABLES -A OUTPUT -j LOG --log-prefix '[iptables OUTPUT DROP] '
$IPTABLES -A OUTPUT -j ACCEPT

# 設定の反映 #
sudo /sbin/iptables-save

# 設定の永続化 #
sudo /etc/init.d/netfilter-persistent save

echo "### すべての設定完了 ###"

 
作成後、パーミッションを変更し、実行

$ chmod 755 iptahles.sh
$ ./iptahles.sh

設定の反映を確認

$ iptales -nL

 
マシン側でパケットフィルタリングをしたため、ConoHa VPSの設定をポータルで変更します。
接続許可ポートを「全て許可」に変更。
f:id:engetu21:20211226135609j:plain
 

3.ラズパイ3での設定

設定というか以下のSSHコマンドを打ちます。

$ sudo ssh -l hoge xxxxxxxxxxx.budb.static.cnode.io -p 22 -i conoha_ssh_key.pem -R 50001:localhost:8080 -T -N

それぞれのオプションについては以下の通りです。

-l hoge:ConoHa VPSに接続するSSHのユーザ名
xxxxxxxxxxx.budb.static.cnode.io:ConoHa VPSのドメイン名。IPアドレスは固定らしいので、そちらを指定してもいい
-p 22:ConoHa VPSに接続するSSHのポート番号
-i conoha_ssh_key.pem:ConoHa VPSに接続するSSHの公開鍵(VPSを作る際に作ったものを指定)
-T:仮想端末の割り当てを禁止
-N:リモートコマンドを無効 (-T -NがないとConoHa VPSにSSHログインする感じになるので、これを抑止)
-R 50001:localhost:8080:リモートフォワードの設定。ConoHa VPSでポート50001に来たものをlocalhost(自分)の8080ポートに転送する設定。

このコマンドによって、常にSSHトンネルがラズパイ3とConoHa VPS間で構築されていることになります。
動かしっぱなしにする場合は、screenを利用したり、Pythonプログラムでコマンドを実行するように作り、Pythonプログラムは自動起動にしておく必要があります。

【Python】CSVのデータをPostgreSQLに格納

CSVのデータをそのままポスグレに設定する場合は、以下のようにPythonファイルを作ります。
PythonファイルからSQLファイルを実行させて、SQLファイル側でCSVファイル内をコピー→INSERTする形になる。
一度tmpテーブルを作成し、それにコピーする形になるのが肝。
これによって、例えばCSVファイルのデータだけでは足りない情報(テーブルへの登録日時や更新日時)を後から追加することができる。後述のSQLファイルではINSERTにて末尾に登録日時と更新日時を追加しています。
 
Pythonで一度取り込んでINSERTする形をとってもいいけど、psqlコマンドでSQLを実行する形にしておいたほうが、呼び出す側がどんな言語を使っていてもSQLの内容に影響を与えない(疎結合)の形になる、というメリットはあるかと。
まぁそこらへんは好みの問題。あと性能的な側面では考慮していないので、悪しからず。
 
ちなみに、Windowsで実行する場合、psqlを実行するのにパスワード入力を省略させるために以下の場所にpasswordファイルを作って格納しておく必要がある。ファイル名は「pgpass.conf」で固定です。
C:\Users\<ユーザ名>\AppData\Roaming\postgresql\pgpass.conf

<host名 or IPアドレス>:<ポート番号>:<DB名>:<ユーザ名>:<パスワード>
例:yyy.yyy.yyy.yyy:5432:postgres:postgres:password

Linuxの場合は、psqlを実行できるユーザでpythonを実行すればパスワードを要求されることはない。
 
Pythonファイル】
gist.github.com
psqlの -vで変数を設定できます。文中の「file_name=」はSQLファイルに渡す変数です。SQLファイルのほうでは「:file_name」と記述することで渡された変数の中身を参照できます。
 
SQLファイル】
gist.github.com
変数の指定は「:XXX」の形で書けば勝手に参照してくれます。シングルクォーテーションはいちいちエスケープ文字書くのが面倒なので変数で用意し、それを結合で利用してます。結合に特に+とかは不要。続けて書くだけ。

なんでcopyコマンドを変数に入れてから実行する形にしてるの?というと、
まず前提として、copyコマンドには「copy」コマンドと「\copy」コマンドがある。(SQLファイル上、\\copyとなっているが、これはエスケープ処理の都合上こうなる)
前者の「copy」は、ポスグレが入っているマシンのローカルディスクのファイル(今回はCSVファイル)を指定することになる。
なのでポスグレが入っているマシンでSQLファイルを実行する場合は「copy」を使えばいい。
問題はアプリ層とデータベース層が分かれているシステムの場合で(まぁ普通はこの構成なんだけど)、
特にAWS Auroraを利用していた場合などは、Aurora内にSQLファイルやCSVファイルを置いておく、ということはできないので「copy」は使えない。
なのでその場合は後者の「\copy」を使う。これはSQLファイルを実行したマシンのディスクにあるファイル(CSVファイル)を指定することになる。
ただ厄介なのが、「copy」コマンドはSQLファイルに直書きしても動いてくれるが、「\copy」のほうはエスケープ文字の関係上、書けば簡単に動いてくれるわけではない。で、そこら辺を調べてると、海外の掲示板でコマンドをあらかじめ変数に入れて用意しとけばいいんだよ、との書き込みがあったのでその通りにしている、という感じになります。
 
SQLファイルのパスの指定はどちらかのプログラムファイルに寄せたほうがいいんだろうけど、まぁとりあえずこれで。