tp-link HS105によるTV強制オフ

# Motivation
子供が無制限にTVを見てしまうので、見たいTV番組以外は自動でTVへの給電ON/OFFを行うため、tp-linkのHS105というのを買ってみた。iPhoneからスケジュール設定できる。
https://www.tp-link.com/jp/home-networking/smart-plug/hs105/

スケジュールした時間に給電OFFになっても横のボタンで物理的にONできるので、ボタン押したらTV点くことを子供が覚えてしまった。厚紙で蓋をしたけど、無理やり押す。。。
![IMG_0908.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117100/4e3f352a-b38e-53ba-cd4c-36b020f436b3.jpeg)
埃汚くてすいません。。。

# やったこと
HS105にはAPIがあって、その制御スクリプトが[公開](https://github.com/softScheck/tplink-smartplug)されているので、HS105のボタンを押そうがcronで30秒以内に給電OFFにするよう改変した(以下diff参照)。。。
(1年前にやったので下記版を改変)
https://github.com/softScheck/tplink-smartplug/blob/a094843c64bbfe62780f0e74412c090ad15a9f6d/tplink_smartplug.py

```
1c1
< #!/usr/bin/env python2
---
> #!/usr/bin/python
25a26,29
> import json
> import datetime
> import time
> 
72,86d75
< # Parse commandline arguments
< parser = argparse.ArgumentParser(description="TP-Link Wi-Fi Smart Plug Client v" + str(version))
< parser.add_argument("-t", "--target", metavar="<hostname>", required=True, help="Target hostname or IP address", type=validHostname)
< group = parser.add_mutually_exclusive_group(required=True)
< group.add_argument("-c", "--command", metavar="<command>", help="Preset command to send. Choices are: "+", ".join(commands), choices=commands)
< group.add_argument("-j", "--json", metavar="<JSON string>", help="Full JSON string of command to send")
< parser.add_argument('--influxdb', type=str, metavar=("URI", "database"), default=None,
< 		nargs=2, help='If command is "energy", push to influxdb. URI should point to influxdb, e.g. [http/https]://<ip>:<port>. Database: e.g. smarthome.')
< parser.add_argument('--influxdb_energy', type=str, metavar="query", default=None,
< 		help='query to store energy as Joule, e.g. energy,type=elec,device=hs110-1 this will be appended with <energy in joule> (as int)')
< parser.add_argument('--influxdb_power', type=str, metavar="query", default=None,
< 		help='query to store power as Watt, e.g. power,type=elec,device=hs110-1 this will be appended with <power in W> (as float)')
< args = parser.parse_args()
< 
< 
88c77
< ip = args.target
---
> ip = '192.168.0.7'
90,95c79,80
< if args.command is None:
< 	cmd = args.json
< else:
< 	cmd = commands[args.command]
< 
< 
---
> cmd = '{"system":{"get_sysinfo":{}}}'
> cmd2 = '{"system":{"set_relay_state":{"state":0}}}'
97a83
> while True:
98a85,88
>                 dt_now = datetime.datetime.now()
>                 print dt_now
>                 if dt_now.hour >= 21:
>                         break
105,112c95,105
< 	print "Sent:     ", cmd
< 	print "Received: ", decrypt(data[4:])
< except socket.error:
< 	quit("Cound not connect to host " + ip + ":" + str(port))
< 
< if (args.command == "energy") and (args.influxdb != None):
< 	import requests
< 	import json
---
>                 json_dict = json.loads(decrypt(data[4:]))
>                 relay_state = json_dict['system']['get_sysinfo']['relay_state']
>                 print "relay_state:     ", relay_state
>                 if relay_state == 1:
>                         sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>                         sock_tcp.connect((ip, port))
>                         sock_tcp.send(encrypt(cmd2))
>                         data = sock_tcp.recv(2048)
>                         sock_tcp.close()
>                 time.sleep(30)
114,129c107
< 	# Get total_wh and power_mw from json response
< 	energy_response = decrypt(data[4:])
< 	energy_wh = json.loads(energy_response)['emeter']['get_realtime']['total_wh']
< 	energy_joule = int(energy_wh)*3600
< 	power_mW = json.loads(energy_response)['emeter']['get_realtime']['power_mw']
< 	power_W = float(power_mW)/1000.0
< 
< 	# Build URI and query
< 	# Something like req_url = "http://localhost:8086/write?db=smarthometest&precision=s"
< 	# Something like post_data = "water,type=usage,device=devicename value=1"
< 	req_url = args.influxdb[0]+"/write?db="+args.influxdb[1]+"&precision=s"
< 	post_data = ""
< 	if (args.influxdb_energy != None):
< 		post_data = args.influxdb_energy+" value="+str(energy_joule)
< 	if (args.influxdb_power != None):
< 		post_data += "\n"+args.influxdb_power+" value="+str(power_W)
---
> #00 19 * * 1 /home/pi/my_tplink_smartplug.py >> /var/log/tplink.log 2>&1
131,137c109,110
< 	# Post data to influxdb, check for obvious errors
< 	try:
< 		httpresponse = requests.post(req_url, data=post_data, verify=False, timeout=5)
< 		if (httpresponse.status_code != 204):
< 			print "Push to influxdb failed: " + str(httpresponse.status_code) + " - " + str(httpresponse.text)
< 	except requests.exceptions.Timeout as e:
< 		print "Update failed due to timeout. Is influxdb running?"
---
>         except socket.error:
>                 quit("Cound not connect to host " + ip + ":" + str(port))
```

95-105行目あたりで `cmd = '{"system":{"get_sysinfo":{}}}'` で給電ON/OFFステータス`relay_state`を取得して、ON: `1`にされてたら `cmd2 = '{"system":{"set_relay_state":{"state":0}}}'` でOFFにするだけ。
87行目あたりで21時まで有無を言わさず無限ループ

## cron設定
常時動いているraspberry piにTV番組終了に合わせてcronでスクリプト起動。さて何のTV見てるでしょう?

```
32 18 * * 1 /home/pi/my_tplink_smartplug.py > /var/log/tplink.log 2>&1
17 19 * * 2 /home/pi/my_tplink_smartplug.py > /var/log/tplink.log 2>&1
32 18 * * 3 /home/pi/my_tplink_smartplug.py > /var/log/tplink.log 2>&1
17 19 * * 4 /home/pi/my_tplink_smartplug.py > /var/log/tplink.log 2>&1
57 19 * * 5 /home/pi/my_tplink_smartplug.py > /var/log/tplink.log 2>&1
```

仕事中にTVが消えたとメールで連絡が来るが、外からは家に入れないので、家に帰らないと無理と言って、諦めてもらう。。。

## 余談
本題とは関係ありませんが、下記を参照してDynamic DNSを使って外からSSHで家に入れるようにした。
送信元アドレス制限しないと、22番ポートにメッチャ不正アクセス来ます。

https://qiita.com/kazuki-ma/items/efa12b2f05506618adce

AWSのRoute 53でドメインホストしているので、下記のようなスクリプトをclonで定期的に動かして、内部サーバにポートフォワードかけてるHGWのグローバルアドレスのAレコードを更新
`ip.txt`に前回のアドレスを書いておいて、変わってたらaws-cliで更新

```updatedns.sh
#!/bin/sh
NEWIP=`/usr/bin/curl -s https://api.ipify.org`
IP=`cat /home/pi/ip.txt`
if [ -n $NEWIP ] && [ $IP != $NEWIP ]; then
    cp /home/pi/template.json /home/pi/current.json
    sed -i 's/<IP>/'"$NEWIP"'/' /home/pi/current.json
    echo $NEWIP > /home/pi/ip.txt
    /usr/bin/aws --debug --output=table route53 change-resource-record-sets --hosted-zone-id ドメインのzone id(ドメイン名ではない) --change-batch file:///home/pi/current.json
fi
```

更新のDNSレコード更新のtemplateはこんな感じ

```template.json
{
  "Comment": "Update by updatedns.sh",
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "ホスト名",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "<IP>"
          }
        ]
      }
    }
  ]
}
```

AWSのIAMはPoliciesからAmazonRoute53FullAccessをユーザに足しとく。
![Screen Shot 2021-04-18 at 19.27.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117100/cd383b40-6af4-fb2c-8f1f-702b0c248644.png)