OmniAICreator/Qiita-1.07M huggingface dataset:
MPU6050をROS2で動かす

気づいたらクリスマスイブだった(過ぎてた)。

遅れてすみません、ROS2 Advent Calender24日目の記事となります。

@youtalkさんのROS2本でROS2のお勉強をしていたのですが、せっかくだし何かしらROS2のパッケージを作りたいなと思い立って本記事を書きました。初心者なので「作ってみたよ」というだけの記事なってしまうかと思いますが、許してください...

## 使用する環境
ubuntu 18.04、ROS2 Dashing

## MPU6050のROS2対応
以下のレポジトリを元に、ROS2へポーティングを行いました。ボードはArduinoです。事前に書込しておく必要があります。
シリアル通信をするにあたり、serialというパッケージが必要ですが、[こちら](https://github.com/RoverRobotics-forks/serial-ros2)のレポジトリにてROS2版が公開されてたので使用させて頂きました。
[こちら](https://github.com/RyuYamamoto/mpu6050_serial_to_imu)にMPU6050のROS2コードを置いています。

## インストールと動作確認
まずserialパッケージをcloneします。

```
$ cd ~/ros2_ws/src && git clone https://github.com/RoverRobotics-forks/serial-ros2
```
mpu6050のパッケージをcloneし、ビルドします。

```
$ git clone https://github.com/RyuYamamoto/mpu6050_serial_to_imu
$ cd ../ && colcon build
```

実際に起動して確認してみます。

```terminal
$ ros2 run mpu6050_serial_to_imu mpu6050_imu_node
```

トピックが出てるか確認

```terminal
$ ros2 topic echo /imu/data
```

こんな感じで出てたらOK

```
---
header:
  stamp:
    sec: 1577263533
    nanosec: 749580149
  frame_id: imu_link
orientation:
  x: 0.0
  y: 0.0
  z: 0.0
  w: 1.0
orientation_covariance: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
angular_velocity:
  x: -0.7456851052221867
  y: 0.15978966540475428
  z: 0.07989483270237714
angular_velocity_covariance: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
linear_acceleration:
  x: 0.0622705078125
  y: 0.0383203125
  z: 9.60642333984375
linear_acceleration_covariance: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
---
```

## ちょっとだけハマったところ
parameterの設定方法どうやるのかでちょっとだけハマった。ROS1だと

```c++
ros::NodeHandle nh("~");
nh.param<std::string>("frame_id", _frame_id, "imu_link");
```
とかで設定できた気がするけどROS2だと

```c++
auto node_handle = rclcpp::Node::make_shared("hoge_node");
node_handle->declare_parameter("frame_id", "imu_link");
node_handle->get_parameter("frame_id", _frame_id);
```
になるらしい。ノードを実行した段階で

```
$ ros2 topic list
/mpu6050_imu_node:
  frame_id
  port
  tf_frame_id
  tf_parent_frame_id
  use_sim_tim
```
とするとパラメータの一覧をみることが出来る。ノードが持つパラメータの取得・設定は

```
$ ros2 param get /hoge_node foo
$ ros2 param set /hoge_node bar "hogehoge"
```
で行う。
(名前空間とパラメータ名くっつけるのかと思ったらそうじゃなくてここでもハマった)

ROS2ではroscoreが廃止され、パラメータサーバーをノードごとに持つようになった。上記のパラメータの設定はServer側だが、別ノードからClient経由してパラメータの取得・設定を行う場合はrclcpp::SyncParametersClientやrclcpp::AsyncParameterClientを用いて行うらしい([こちらを参照](https://qiita.com/NeK/items/c7710827c1676487473a))。パラメータ取得・設定を同期・非同期で行うかどうかの違い。　

(※今回コンポーネントで実装してみようとしたがコンパイル時にserial周りでこけてできなかった。主に共有ライブラリの生成部分でコケてる感じか)

## まとめ
一個パッケージを作るとやっぱり理解が深まる気がしました。結構ROS1とROS2で書き方が違うけど、慣れてきたらROS2の方が書きやすいのかも。早く慣れたいところです。
ちょっと時間なくて姿勢推定のコードのROS2ポーティング間に合わなかったのでそこらへんは年末までにやりたい。あとeloquent移行したい。xmlでlaunchかけるのやっぱいいな。

## 参考
[ROS2導入&レクチャー](https://qiita.com/NeK/items/7ac0f4ec10d51dbca084)
→ROS2についてめっちゃまとめられていて素晴らしかった。
Googleしごと検索のJSON-LDを作成

# 公式ドキュメント
https://developers.google.com/search/docs/data-types/job-posting
まずは公式ドキュメントでポリシーを確認しましょう。

# JSON-LDを作成
JSON-LD形式で求人ページにタグを埋め込めばbotがクロールしてくれます。
有効になった求人数はSearch Consoleで確認できます。

設置場所は基本的にheadタグ内ですが、bodyタグ内でも問題ないです。

# 用意する値

|プロパティ名|変数名|説明|
|:---|:---|:---|
|title|$title|職務の名称。Googleのポリシーには**求人のタイトルのような内容を入れない**ように記載されています。|
|datePosted|$date_posted|求人情報が掲載されたの日付(ISO8601形式)|
|description|$description|HTMLタグが一部使用可能(p, brなど)|
|hiringOrganization|$hiring_organization_name|企業名|
| |$logo_url|企業ロゴ画像|
|jobLocation|$address_region|都道府県|
| |$address_locality|市区町村|
| |$street_address|番地|
| |$postal_code|勤務地の郵便番号|
|validThrough|$valid_through|求人情報の掲載期限日付(ISO8601形式)|
|employmentType (任意)|$employment_type = 'PART_TIME';|アルバイト|
| |$employment_type = 'FULL_TIME';|正社員|
| |$employment_type = 'CONTRACTOR';|契約社員|
| |$employment_type = 'TEMPORARY';|派遣社員|
| |$employment_type = 'OTHER';|その他|
|baseSalary (任意)|$min_salary|最低給与額|
| |$max_salary|最高給与額|
| |$salary_span = 'HOUR';|時給|
| |$salary_span = 'DAY';|日給|
| |$salary_span = 'WEEK';|週給|
| |$salary_span = 'MONTH';|月給|
| |$salary_span = 'YEAR';|年収|
|identifier (任意)|$identifier_name|求人情報の一意の識別子名|
| |$identifier_value|求人情報の一意の値|

# JSON-LDの作成

上記の値を下記のようにまとめたら、タグを埋め込みます。

```php:create_jobpost.php
$json_ld =
<<<EOD
<script type="application/ld+json">
{
  "@context" : "http://schema.org/",
  "@type" : "JobPosting",
  "title" : "$title",
  "description" : "$description",
  "datePosted" : "$date_posted",
  "validThrough" : "$valid_through",
  "hiringOrganization" : {
    "@type" : "Organization",
    "name" : "$hiring_organization_name",
    "logo": "$logo_url"
  },
  "jobLocation" : {
    "@type" : "Place",
    "address" : {
      "@type" : "PostalAddress",
      "addressCountry": "JP",
      "addressRegion" : "$address_region",
      "addressLocality" : "$address_locality",
      "streetAddress" : "$street_address",
      "postalCode" : "$postal_code"
    }
  },
  "employmentType" : [$employment_type],
  "baseSalary": {
    "@type": "MonetaryAmount",
    "currency": "JPY",
    "value": {
      "@type": "QuantitativeValue",
      "minValue": $min_salary,
      "maxValue": $max_salary,
      "unitText": "$salary_span" 
    }
  },
  "identifier": {
    "@type": "PropertyValue",
    "name": "$identifier_name",
    "value": "$identifier_value" 
  }
}
</script>
EOD;
```

# 作成したJSON-LDデータのテスト方法
Googleの構造化データテストツールで確認
https://search.google.com/structured-data/testing-tool/u/0/?hl=ja
PHPUnit8系と7系で違うところメモ【with laravel 6 & laravel 5.8】

## 概要
同時に使っていたら少しずつ違って混乱したので、メモ

## 環境

- laravel 6.7.0 / PHPUnit 8.5.0
- laravel 5.8 / PHPUnit 7.5

をそれぞれ使い比べた

## 特定の環境下のみでテストは実行したい

setUpBeforeClassに定義する。
実行順からして、setUpBeforeClass内では一手間(後述)加えないとlaravelのヘルパーは使えない。

との話でしたが、 `laravel 6.7 / PHPUnit 8.5` では問題ありませんでした。
laravel6、またはPHPUnit8から仕様が変わっているのではと思います。


```php
    public static function setUpBeforeClass(): void
    {
        parent::setUpBeforeClass();
        if (config('app.env') === 'local') {
			// ごにょごにょ
        }
    }
```


## dataProviderでEloquentなどlaravelの機能使いたい

dataProviderはsetUpBeforeClassよりも先に実行されるためlaravelのクラス等が使用出来ないので一手間使う。

こちらは `laravel 6.7 / PHPUnit 8.5` でもエラーが発生した。

laravelの初期化を行うメソッドを実行するか、

```php
public function addProvider() 
{
	$this->refreshApplication();
}
```

クロージャ内ならば実行出来る。

```php
public function addProvider() 
{
    return [
        'test1' => function() {
			return Article::all();
		},
        'test2' => function() {
			return Article::all();
		}
    ]
}
```
EC2リザーブドインスタンスの購入制限について

# リザーブドインスタンスの購入制限とは
AWSのEC2リザーブドインスタンス（RI）には購入量に上限があり、それを超えて購入することはできません。
制限の仕様は[こちら](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-reserved-instances.html#ri-limits)で説明されています。

以下は制限の仕様について抜粋したものです。

>1 か月当たりに購入できる リザーブドインスタンス の数は制限されています。リージョンごとに 1 か月当たり 20 個のリージョン リザーブドインスタンス に追加して、アベイラビリティーゾーンごとに 1 か月当たり 20 個のゾーン リザーブドインスタンス を購入できます。

>たとえば、リージョンに 3 つのアベイラビリティーゾーンがある場合、リザーブドインスタンス の制限は 1 か月当たり 80 です。つまり、そのリージョンのリージョン リザーブドインスタンス 20 と、3 つのアベイラビリティーゾーンそれぞれで 20 のゾーン リザーブドインスタンス (20x3=60) です。

ここでは「リザーブドインスタンスの制限は1か月当たり80です」と説明されていますが、例えば

```
・東京リージョンのap-northeast-1aで40個購入
・リージョナルで40個購入
```
ということはできません。
上記の説明は

```
ap-northeast-1a　で最大20個まで購入可能
ap-northeast-1b　で最大20個まで購入可能
ap-northeast-1c　で最大20個まで購入可能
リージョナル　　     で最大20個まで購入可能
```
の全てを合わせた場合80個まで購入可能ということを表しています。
この点については注意が必要です。

また、この仕様はRIの購入形態やインスタンスタイプやインスタンスファミリーの違いに限らず適用されます。
例えば

```
・Standard RI(t2.micro)を50個
・Convertible RI(m5.large)を50個
```
のように購入する場合、50+50=100個で上記の80個を超えてしまうので購入はできません。


# 制限緩和の方法
リザーブドインスタンスの制限を超えて購入したい場合は、制限緩和申請を行う必要があります。
`EC2マネジメントコンソール` > `制限` > `リザーブドインスタンス`を選択して`制限緩和のリクエスト`の順で制限緩和申請できます。
<img width="1440" alt="スクリーンショット 2019-12-24 17.53.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212819/f8eac44f-7a4d-116e-53e2-724d8febc95f.png">

![スクリーンショット 2019-12-25 17.35.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212819/ae5c7d70-d3d6-8890-b014-e9e5ec6a1af1.png)



無事に制限緩和申請が受理され、制限が`20 → 40`に変更になった場合、

```
ap-northeast-1a　で最大40個まで購入可能
ap-northeast-1b　で最大40個まで購入可能
ap-northeast-1c　で最大40個まで購入可能
リージョナル　　     で最大40個まで購入可能
```
になり、最大160個のRIを購入できるようになります。

上限緩和は数日かかる場合があるようなので、余裕を持って緩和申請をしましょう。
YouTubeLiveのチャットを表示して遊んでみた

本記事は[VTuber Tech #1 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/vtuber)の25日目の記事です。
普段は、[珠響そうき](https://www.youtube.com/channel/UCRQ6fe53K3Qh-6FzPaGMUXw)という名前でVtuber活動をしています。

# チャットを取得して、グリーンバックで表示して遊んだ

右から左へ、チャットが流れていくアプリを作った。
文字色の変更や、音声を再生するコマンド、雨を降らせるコマンドなども実装した。

![でも.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358231/3606e3af-fc47-cad6-e986-d7bf84119883.png)


[機能テスト](https://youtu.be/n-W84i3QMSo)
[実際に使っている様子](https://youtu.be/TaZ6HkjYXfI?t=268) (直近の配信)

↓好きに使ってください。
[ソースコード](https://github.com/eteeeeeerminal/ChatPlayer)
[pyinstallerでexe化したやつ](https://github.com/eteeeeeerminal/ChatPlayer/releases)

# 使い方(READMEのコピペ)
起動して出てくるウィンドウのIDと書かれた横に、配信のID(URLの中にあるやつ)を入力して、okボタンを押してください。
何もなければ、緑色の画面にコメントが流れます。

- setting.json
    - "chat_id" ここに配信のIDを入力します。(起動時にソフト上で入力できます)
	- "win_size" の [縦の解像度, 横の解像度] でウィンドウの解像度を変更できます。
	- "back_color" の [R, G, B] で背景色を指定出来ます。

- chat_setting.json
	- "plain_font_path" でデフォルトで使用するフォントファイルを指定できます。
		
	- "sound_commands" で音声系のコマンドの設定が出来ます。  
	    新規に増やすことも出来ます。  
	    ※pygame(使用しているライブラリ)の仕様上、wavとoggのみロード出来ます。
		
	- "speed_commands" で文字の速度を変更するコマンドを設定できます。  
	    "speed" でマイナスを指定すると、逆から流れます。
		
	- "color_commands" で文字色を変更するコマンドを設定できます。  
		[R,G,B]で指定してください。  
		"outline_color" は1ドットだけついてる縁色の設定です。

	- "rain_commands" で文字がランダムに降ってくるコマンドを設定できます。  
		"time" の秒数だけ、ランダムに"drops" 内のいずれかの文字が上から降ります。  
                初速と加速度を設定できます。

# やったこと
1. [**pychat**](https://github.com/taizan-hokuto/pytchat) を使ってYouTubeLiveのチャットを取得
1. pygame で愚直に描画

~~はじめはSeleniumで無理やりチャットを取得してた~~

# チャットを取得
[pychat](https://github.com/taizan-hokuto/pytchat)を使えば余裕でできた(感謝)。
配信URLの中にあるIDを渡してやるだけで動いた。

```python:サンプルコード
from pytchat import LiveChat
import time

def main()
  chat = LiveChat("ここにidを入れる", callback = func)
  while chat.is_alive():
    time.sleep(3)
    # 好きな処理を書く
   
# callback 引数に関数を渡すと、定期的にチャットを受け取ってくれる。
def func(data):
  for c in data.items:
    print(f"{c.datetime} [{c.author.name}]-{c.message} {c.amountString}")
    data.tick()
```

ただ、アプリに組み込んだときに、python が終了しないバグ発生。

`chat.terminate()`を呼び出してやると、正常終了した。

# pygame で描画
[日本語訳されたリファレンス](http://westplain.sakuraweb.com/translate/pygame/)があるので、それを見つつやった。

- pygame は文字列も画像も`Surface`オブジェクトに乗っける
- インストールされているフォントの指定でつまづいたので、直接`.ttf`をロードした
- emoji が入ってくるとやられるので、[emojiを駆逐](https://qiita.com/yoshimo123/items/85331d881aed9ad41020)
- 文字の縁取りが標準で実装されていないので、[**ここ**](https://www.pygame.org/pcr/hollow_outline/index.php)のを使った

# 他の機能
- コマンド機能
  - 単純に `/red` 等の文字列が含まれているコメント用の処理を用意
  - json を書き換えれば、文字色や音声を鳴らすコマンドは好きに増やせる

- 音声を再生
    - もちろん pygame でできる
    - 効果音用の `mixer.Sound` クラスを使った

- ID入力
    - pygame には、お馴染みの入力フォームやボタンが無い
    - 起動時に、tkinter で IDを入力するウィンドウを表示

# 最後に
- チャット流れるの楽しい
- いくらでもコマンドを増やせて楽しい
- 配信画面が賑やかで楽しい
Airflowでredashのクエリ結果をGCSに送る

あまりない場面かと思いますが、「こんな事があった」がてらに…
Airflowでredashのクエリ結果を、APIを利用してGCSに送れるようにするために調査した個人メモ


# やりたいこと
- redashのクエリAPIを実行し、取得した結果をGCSにアップロードする

図に表すと下記のようになります。
![redashからgcs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262720/0b3ee571-fcb3-6995-5f27-9f331a96a728.png)
 


# 作業環境や利用サービス(verは作業当時のもの)
- GCP Cloud Composer 
    - Airflow(ver 1.10.2) 
- redash(var 5.0)
- GCS bucket(すでに用意済み)



# DAGの流れ

簡単な説明ですが、今回は下記2つの手順を踏んでデータを送りました。

## redashのクエリAPIをAirflow上で実行

- redashに関するoperatorは無いので、pythonの「requests」を利用してAPIを叩く
    - [Requests: HTTP for Humans](https://pypi.org/project/requests/)


## 取得したデータをGCSに送る
- 「google-cloud-storage」というライブラリを利用してAPIのデータをGCSに送る
    - [google-cloud-storage](https://pypi.org/project/google-cloud-storage/)

## サンプルコード

こちらも、とても大まかになりますが載せます。
redashのクエリAPIキーはcsvの方を利用しています。
クエリAPIキーの管理についてはAirflowの`Connections`等で管理するのが良いかと思います(ここではあえて直書きにしています)。

```python
import datetime
from airflow import DAG
from airflow.operators.python_operator import PythonOperator
from google.cloud import storage
import requests

client = storage.Client()

default_args = {
    'owner': 'task owner',
    'depends_on_past': False,
    'email': [''],
    'email_on_failure': False,
    'email_on_retry': False,
    'execution_timeout': datetime.timedelta(hours=1),
    'retries': 3,
    'retry_delay': datetime.timedelta(minutes=5),
    'start_date': datetime.datetime(2019, 08, 3),
}



def redash_to_gcs(ds, **kwargs):
    result = requests.get('{redashのクエリAPIキー}')
    print(result)
    bucket = client.get_bucket('{送り先bucket}')
    blob = bucket.blob("{出力ファイル名}.csv")
    blob.upload_from_string(result.text.encode('utf-8'))

to_gcs = PythonOperator(
    task_id='echo_results',
    python_callable=redash_to_gcs,
    provide_context=True
)


with airflow.DAG(
        'salesforce',
        'catchup=False',
        default_args=default_args,
        schedule_interval='0 23 * * *') as dag:
	
    start_task = DummyOperator(task_id='start')
    finish_task = DummyOperator(task_id='finish')


    to_gcs = PythonOperator(
        task_id='echo_results',
        python_callable=redash_to_gcs,
        provide_context=True)

    start_taslk >> to_gcs >> finish_task
```
Vuexで名前空間（Namespace）を使う

# Namespace とは

このオプションを付けることで、`state`, `getters`, `actions`, `mutations`には**自動的に自分が属しているmodule名(この場合helloModule)と同名の`namespace`が付与されます**。

```example.js

export default {
  namespaced: true, // 追加
  state: {
  },
  actions: {
    updateMessage(){
      // 処理
    }
  }

```

呼び出すときは「`mapState/mapActionsに引数で名前空間を渡す`」「`名前空間を明示的に指定する`」のどちらのパターンでもOK。

```example.vue

methods: {
  update() {
    this.$store.dispatch('example/updateMessage');
  },
}

```
scipyでspecialがない？？

## 概要
$_4C_2$ をpythonで求めたい。
[scipy](https://docs.scipy.org/doc/scipy/reference/index.html)というライブラリのscipy.special.combを使うと簡単にできそう。

## エラー
以下のように書くと

```python
import scipy
print(scipy.special.comb(4, 2))
```

エラー

```
AttributeError: module 'scipy' has no attribute 'special'
```

## 解決策
以下のように書くとOK

```python
from scipy.special import comb
print(comb(4, 2))
```

実行結果

```
6.0
```

以下も可

```python
from scipy import special
print(special.comb(4, 2))
```

## まとめ
`import scipy`
ではなく
`from scipy.special import comb` or 
`from scipy import special`
2段階認証死ぬほど面倒くさいときのやつ

Google Authenticatorのあれ


`oathtool`入れてるの前提
適当な名前をつけてシェルをつくる

```bash
#!/bin/sh

TOTP=`oathtool --totp --base32 XXXXXXXXXXXXXXX`

expect -c "
    set timeout 5
    spawn ssh AAAAAAAAAAAAA
    expect \"Verification code:\"
    send \"${TOTP}\n\"
    interact
"
```

これでaliasも登録すればいっぱつ。やったぜ。

ちなみにAWSの2段階認証はaws-mfa入れてなんやかんやできます。やったぜ。

```bash
pip3 install aws-mfa
```

設定とかは割愛
PHP の null 合体演算子から isset の真価を知る

他の言語は経験あるけど PHP は初心者って感じの人は `??` を使って

```php
$book = Book::where('name', "みんなのPHP")->first();

if (
    // そんな本はないかもしれない
    $book !== null &&
    // そんなフィールドは null かもしれない
    $book->metadata !== null &&
    // array にそんなキーはないかもしれない
    isset($book->metadata->customFields['PHP文庫特集']) &&
    $book->metadata->customFields['PHP文庫特集'] === "掲載"
) {
    // 松下幸之助特集も御覧ください
}
```

こう書けると考える。

```php
$book = Book::where('name', "みんなのPHP")->first();

if (
    // そんな本はないかもしれない
    $book !== null &&
    // そんなフィールドは null かもしれない
    $book->metadata !== null &&
    // array にそんなキーはないかもしれない
    // null 合体した: isset($book->metadata->customFields['PHP文庫特集']) &&
    $book->metadata->customFields['PHP文庫特集'] ?? null === "掲載"
) {
    // 松下幸之助特集も御覧ください
}
```

実はこう書ける。

```php
$book = Book::where('name', "みんなのPHP")->first();

if ($book->metadata->customFields['PHP文庫特集'] ?? null === "掲載") {
    // 松下幸之助特集も御覧ください
}
```

`isset()` は関数ではなく構文要素 (未定義または null 評価にならないかチェックする言語機能) なので、内部の式を評価しないからヌルポを起こさない。つまり `??` がなくても実は最初からこう書くことができた。

```php
$book = Book::where('name', "みんなのPHP")->first();

if (
    // そんな本はないかもしれないし、フィールドは null かもしれないし、array にそんなキーはないかもしれない
    isset($book->metadata->customFields['PHP文庫特集']) &&
    $book->metadata->customFields['PHP文庫特集'] == "掲載"
) {
    // 松下幸之助特集も御覧ください
}
```

知識は要るけど実用性つよい言語設計ですね。
NoMethodError undefined method `xxx' for nil:NilClass

# contoller側で定義した変数が全て`nil:NilClass`

routes.rbが間違っているわけではない。(というか開発中は表示できてた)

```
NoMethodError in Hoge#index
Showing /fuga/app/views/hoge/index.html.erb where line #14 raised:
undefined method `each' for nil:NilClass
```

# controllerのタイポが原因だった

```rb:hoge_controller.rb
class HogeController < ApplicationController
  before_action :set_user, only: [show] // 「：」が抜けている :show
```

`only: [:show]`としたら表示された。
ShellScript作るときによく使うやつ

# なんか便利そうなやつ
## なんか画面に入力してほしいときのやつ
```bash
read -p "なんか入力してちょ: " INPUT_VAL
```

## なんか確認するやつ

```bash
function confirmation_to_continue() {
  read -p "よろしいですか？ (y/N): " yn
  case "$yn" in [yY]*) ;; *) echo "abort." ; exit ;; esac
}
```

## 空判定するやつ

```bash
function error_if_empty() {
  while [ -z "$1" ]; do
    echo "入力値が空です。"
    exit 1
  done
}
```

## 数値判定するやつ

```bash
function error_if_not_numeric() {
  expr "$1" + 1 >/dev/null 2>&1
  while ! [ $? -lt 2 ]; do
    echo "数値で入力してください。"
    exit 1
  done
}
```

# DBでなんやかんややりたいやつ

## なんかDBから値とってきたいやつ
```bash
VAL=$(echo ${QUERY} | ${MYSQL} -N)
```

## transactionしたくね？的なやつ
```bash
${MYSQL} <<EOF
    BEGIN;
    $QUERY
    COMMIT;
EOF
```
[M5Stack] Hello Worldでハマった


# はじめに

**M5Stack**でHello Worldしてみたら、一瞬ハマったのでメモ。

# 環境
* Windows10
* M5Stack Gray(おそらくBasicも同じ)

ビルド環境はArduinoIDEを使いました。

# Hello Worldまでの手順

https://docs.m5stack.com/#/en/quick_start/m5core/m5stack_core_quick_start

クイックスタートの通り。
封入されていた説明書の手順と同じ。

他のサイトに書かれていた、ドライバのインストール云々とか一切いりませんでした。
これだけ。

# ここでハマる

完結に言うと、`5. Example`の`(1) Arduino port Configuration`をすっ飛ばしてビルドをしたら、エラーが出てしまいました。
要するに、使用するボードもポートも**初期設定のまま**ビルドした状態。

```console
Arduino\libraries\M5Stack\src\Fonts\Font16.c:3:10: fatal error: pgmspace.h: No such file or directory

「M5Stack.h」に対して複数のライブラリが見つかりました
 #include <pgmspace.h>

compilation terminated.

exit status 1
ボードArduino/Genuino Unoに対するコンパイル時にエラーが発生しました。
```

ボードの指定を忘れると、こんなエラーが出ちゃうんだ～というメモ。
単純なミスだけど、エラーはわかりにくい。。ちょっとハマった。

# できた

![20191225_162406.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171600/85711cd6-73a3-a94a-887a-fc1fb6081086.jpeg)

もう1度手順通りやったところ、Hello Worldできました。

…手順はちゃんと読もうねっていう自戒の念を込めて。

# ちなみに

![キャプチャd.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171600/8ad9dd66-8c37-d52f-316d-b3c5aa012ba2.jpeg)

使用ボードはM5stackで指定したものの、**ポート指定**を忘れたパターン。

1回目のビルド→書き込みでは、エラーは出ないけどLCDに文字が出ない。。。
2回目で、画像のダイアログが出ました。

昔使ってたときは、こんなの出てくれなかった、、賢くなってる。。
[実践 TJBot 受付ロボットを作ろう] Watson Assistant + Cloud Functions + Node-RED でconnpassイベント受付ロボットを作る 

この記事は TJBot Advent Calendar 2019の 11日目記事です。

※この記事を含む数回分はIBM Cloud Advent Calendarの方で切り出す予定だったのですが、
 記事が間に合わなかったので切り出した上でTJBot Advent Calendarの方で公開します。

イベント開催をしている間にいつも受付もっと楽にならないかなーと思うことがあり、
いつかTJBotに受付をしてもらおう！という野望を持ち続けて２年が立ってしまいました。

## やりたいこと
- connpassのCSVを読み込ませる
- connpass受付したデータを元に参加者を探す
- 探した参加者を名簿上で参加フラグを立てる

## 使うサービス
- Watson Assistant
- IBM Cloud Functions
- Node-RED
- Cloudant

## 各設定と手順
※それぞれ長くなってしまったので記事をわけて書き出していきます

[[実践 TJBot 受付ロボットを作ろう] Node-RED+Cloudantでconnpassイベント 申込者CSVインポートツールを作る ]()
IBMソフトウェアの情報のありか

IBMのソフトウェア情報をまとめます(某所に置いていたものを引っ越し)。

#### 技術情報
* [Knowledge Center](https://www.ibm.com/support/knowledgecenter/ja/)
製品マニュアル。
使い方: 製品名などを入力して検索。

* [IBM Software Lifecycle](https://www-01.ibm.com/software/support/lifecycle/)
End Of Supportや最新バージョンを確認できる。
使い方: 製品名を入力して検索。出てこなければ一部だけ入力してみる

* [Software Product Compatibility Reports(SPCR)](https://www.ibm.com/software/reports/compatibility/clarity/index.html)
ソフトウェアのシステム要件を確認できる。サポートしているOS、接続できるミドルウェア(DBなど)、前提となるミドルウェア(WASが必要など)、最低必要なHWスペック など。
使い方:
・ほとんどの用途には、「Detailed system requirements」の「create report」から確認できる
・製品名を入力して検索
・結果ページのURLを取得するのにコツがいる(うまいやり方があれば教えてください)

* [developerWorks](https://www.ibm.com/developerworks/jp/)
・製品により濃淡はあるが、日本語情報が充実している物もある
・英語になるが、製品スペシャリストに質問できるサイト[IBM Developer Answers](https://developer.ibm.com/answers/index.html)もある。ただしボランタリーなので、急ぎの質問への回答は期待できない(IBM IDが必要)

* [IBM Support](https://www.ibm.com/support/home/)
サポート情報のポータルサイト。FixCentralからFixのダウンロードが可能。

* [Redbook](http://www.redbooks.ibm.com/) または[Redbook日本語版](https://www.ibm.com/developerworks/jp/redbooks/)
技術書のRedbookやRedpaper(Redbookほどではない、短編的な内容)が出版されている。網羅的に製品を学びたいときに有効。日本語は一部製品のみ。

* [ProVISION](https://www.ibm.com/blogs/pro-vision/?lang=ja)
IBMの技術情報誌。PDFダウンロード可能。

* [IBMプロフェッショナル資格制度](https://www.ibm.com/jp-ja/partnerworld/resources/certify-jp)
日本語で受験可能な試験の確認。事前学習資料が存在するものもある。

#### パスポートアドバンテージ(PA)、ライセンス
* [Passport Advantage](https://www.ibm.com/software/jp/passportadvantage/index.html)
PAのポータルサイト。PAとは何か、PAオンラインとは何かを確認できる。

* [PVU licensing](https://www-01.ibm.com/software/passportadvantage/pvu_licensing_for_customers.html)
各プラットフォーム、プロセッサーのPVU(※)テーブルを確認できる。
※Processor Value Unit。CPUコア単位課金のこと

* [Passport Advantage Virtualization Capacity (Sub-capacity) Licensing](https://www-01.ibm.com/software/passportadvantage/subcaplicensing.html)
仮想化技術(PowerVM、vSphere、Xenなど)がサブキャパシティ対象かどうかを確認できる。ページ中程の[Virtualization Capacity License Counting Rules](https://www.ibm.com/software/passportadvantage/Counting_Software_licenses_using_specific_virtualization_technologies.html)から、各HWでのライセンスカウント例あり。

* [Software Subscription & Support (SS&S)](https://www.ibm.com/software/jp/passportadvantage/software_subscription_support_ov.html)
サブスクリプションサポート(ソフトウェアの保守契約)について確認できる。

* [IBM License Information](http://www-03.ibm.com/software/sla/sladb.nsf)
ライセンスで許諾されている範囲、許諾されていない事項、ライセンスにバンドルされている製品、最新バージョンの確認できる。
使い方: 
1)「Search LI documents」から検索。ライセンス名を入力して検索。出てこなければ一部だけ入力してみる。
2) プルダウンから「License Information documents」を選択する。日本語表示も可能。ドキュメントは複数ページにわたっていることもあるので注意

* [IBM License Information Cloud Service Terms](http://www-03.ibm.com/software/sla/sladb.nsf/sla/saas)
SaaSについてサービスの内容を確認できる。
使い方:
「Search Terms」で検索する、または「SDs」でSaaS一覧を表示させる。
「サービス記述書」は随時更新される。お客様の契約により、時期は異なるので注意

#### IBMのビジネスパートナー向け
* [Partner World](https://www.ibm.com/jp-ja/partnerworld/public)
パートナー向けの情報サイト。IBM IDによるログインが必要。
Promise (resolve) / (reject) について勉強


# Promise

`Promise`の引数に`resolve`と`reject`があり、**非同期処理が実行された場合の`コールバック関数`となります。**

`コールバック関数`： **関数の引数には、関数が渡せる。**これを利用して、「**ある特定の処理が終わったら、引数に渡した関数の処理を実行する**」といったように、処理のフローを制御することができる。
その際、**「終わったら実行したい処理」**を`コールバック関数`として渡します。
（非同期処理関数は処理が終わったらコールバック関数を呼び出す。）

**`resolve`**は**処理が正常に終了した時に呼ばれるコールバック関数**です。
また上記では記述されていませんが、rejectは処理が失敗した時に呼ばれるコールバック関数となります。


## new Promise(callback)

Promiseを利用するにはPromiseをnewでインスタンスを作ります。

作ったインスタンスはそのままリターンさせて
実際の処理はCallbackを渡して書いていきます。

そして成功すればresolveを失敗すればrejectを呼び出します。

```example.js

addCategories({ commit, rootgetters }, inputCategory){
      return new Promise((resolve) => { 
        axios(rootgetters['auth/token'])({
          method: 'POST',
          url: '/category',
        }).then(() => {
          commit('doneAddCategory');
        }).catch ((err) => {
          commit('failFetchCategory', { message: err.message });
        });
      });
    },
```

## 参考

[今更だけどPromise入門](https://qiita.com/koki_cheese/items/c559da338a3d307c9d88)
[JavaScript中級者への道【5. コールバック関数】](https://qiita.com/matsuby/items/3f635943f25e520b7c20)
バッチ処理するECSタスクが異常終了したとき通知する

Fargateでバッチ処理をしているが、それが異常終了したときどう検知するか検討しました。
バッチに使われているタスクはSTOPPEDになったとき**終了コード**で判断することにしました。

## 1. 概要

おおまかな作りは以下です

- CloudWatchEventでSTOPPEDになったECSタスクを検知
- Lambdaにわたす
- LambdaでメッセージからコンテナのExitCodeが"1"のときslackに通知する

## 2. CloudWatch Event

[タスク状態変更イベント](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs_cwe_events.html#ecs_task_events)を参考に
以下のようなイベントパターンを作成します

```json
{
  "source": [
    "aws.ecs"
  ],
  "detail-type": [
    "ECS Task State Change"
  ],
  "detail": {
    "clusterArn": [
      "arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName}"
    ],
    "lastStatus": [
      "STOPPED"
    ]
  }
}
```

本当は以下のようにexitCodeが1のときだけ検知したかったのですが
階層が深いためか、イベントパターンの構文エラーになってしまいうまく設定できませんでした

```json
"detail": {
  "clusterArn": [
    "arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterName}"
  ],
  "containers":[
    {
      "exitCode": 1
    }
  ],
  "lastStatus": [
    "STOPPED"
  ]
}
```

しょうがないので、STOPPEDのタスクはすべて検知して
Lambda側で`if extCode == 1`することにしました
ノウハウお持ちの方教えてほしいです
（会社でAWSサポートプラン契約してるんだからサポートに聞け）

## 3. Lambda

ここからは適当にLambdaを書くだけです
Pythonでは以下のようにしました

```python
def lambda_handler(event, context):
    taskExitCode = event['detail']['containers'][0]['exitCode']
    if taskExitCode == 1:
      # 以下slackに通知するコード
```

## 4. 所管

異常終了の通知からもう少し踏み込んで、タスク再実行の自動化までやれば幸せになりそう。
あと関係ないですがFargate Spot良さそうですよね。

## Appendix
AWS SAMにおけるテンプレートとLambdaのコードを参考に置いておきます
https://github.com/abe-ma/ecstask-error-notifier
まだ全角入力中に半角スペース打つとき、一度半角にしてスペース押してるの？

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">全角入力中にShift+Spaceで全角入力中に半角スペースが打てる。<br>これはノーベルすごいことに気づいたで賞狙える。</p>&mdash; ひよりん@h1y0r1n (@lovehilite) <a href="https://twitter.com/lovehilite/status/1209745291278082048?ref_src=twsrc%5Etfw">December 25, 2019</a></blockquote>

そういうものだと思って、調べてもなかったけど偶然発見して感動した。
Elixirで抽選するプログラムを作った

複数の人の中から抽選で1人を選ぶプログラムをElixirで作りました。

もっと具体的に言うと、数名でイクラを通販したんですが、おまけでカニが1杯ついてきたのでElixirの勉強がてら抽選プログラムを作成して決めてしまおうということで作りました。


#環境
- Mac OSX 10.15.2
- IEx 1.9.1 (compiled with Erlang/OTP 22)


#プログラム
参加者の名前でリストを作り、ランダムで1つ取り出すのを複数回実行し、取り出された名前が何回あったのかをカウントします。

名前をもっとも多く取り出された人がカニをゲットできることとしました。

```elixir
defmodule Kani do

  def lottery(list, count) do
    gets(list, count)
    |> Enum.reduce(%{}, fn (name, acc) -> Map.update(acc, name, 1, &(&1 + 1)) end)
    |> Enum.sort()
  end


  defp get_random(list) do
    list
    |> Enum.random()
  end

  defp gets(list, count) do
    lists = for _i <- count do
      get_random(list)
    end
    lists
  end

end
```



#実行結果（例）
```elixir:iex
iex> list = ["A", "B", "C", "D", "E"]
["A", "B", "C", "D", "E"]

iex> Kani.lottery(list, 1..10_000)
[
  {"A", 1979},
  {"B", 1928},
  {"C", 2033},
  {"D", 2038},　　←カニはDさんがゲット！
  {"E", 2022}
]
```


#まとめ
公平な抽選をした結果、ケンカすることなく @32hero さんがカニをゲットしました。

本当のところ、`Task`とか`Agent`を使ったりmapの値でソートしたかったんですが時間切れになったのでもう少しブラッシュアップしていきたいなぁと感じています。
テンプレ通りの生き方では満足できないあなたへ メリークリスマス🎄

みなさん、メリークリスマス！🍣
Pleasanter Advent Calendar 2019、25日目。いよいよ最終日です！🎄
今年1年Pleasanterをご愛顧いただきましてありがとうございます。
あるいは、Advent Calendarを通じてPleasanterを知ってくれた皆さん、これからもよろしくお願いします。

---
なんと、1週間後は1月1日なんですね。当たり前だけれど驚愕の事実です。
そんな残り僅かな令和元年。
今年はプリザンターにとっても、インプリムにとっても変化の多い1年となりました。
インプリムとしては、新オフィスへの移転。そして、メンバーの数もほぼ倍に増え、プリザンターの進化速度も格段に上がりました。
そんな、進化を続けるプリザンターとして、やはり、一番の大きいニュースと言えばLinux対応版(.NET Core版)のリリースと、東京都主催の世界発信コンペンティションにおいて、ベンチャー・技術部門 特別賞を授賞したことでしょうか。
大変栄誉ある賞をいただいたことで、プリザンターというプロダクトを多くの方に知ってもらうきっかけになりました。
本当にありがとうございます。

それでは、最終日も張り切ってまいりましょう！ 絶対的なJKなので今回もいいね！をお願いします！

## テンプレートまで自由にカスタマイズ
今回の記事の内容は「きっと使う機会は無いだろうけど、こんなことも出来ますよ」という「オリジナルテンプレートの作成」です。
或いは、テンプレ通りの型に嵌まった生き方なんて冗談じゃない、というあなたへ贈る応援歌です。
サイトを新規作成する時に表示されるテンプレート集ですね。↓これ
<img width="1267" alt="スクリーンショット 2019-12-25 14.14.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537390/3b5c8967-76d7-b1ac-8d89-761bff1965ff.png">



まずはテンプレートに追加したい構成のテーブルを作成します。
普通に新規作成で期限付きテーブルまたは記録テーブルを作成し、エディタで項目をカスタマイズしましょう。
もちろん、既存テンプレートからのカスタマイズでも構いません。

とりあえず、名前は「オリジナルテンプレ」としてみます。
項目は適当に、グルメマップでも作るような感じで。
でも、果たしてこんなテンプレートが必要なのでしょうか？ いや、不要だ！(断言)
<img width="704" alt="スクリーンショット 2019-12-25 14.55.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537390/8e9331d9-3b8f-3e08-030a-397d17ace764.png">
オリジナルテンプレの構成を作成したらサイトIDをメモしておいてください。

続いてSQLServer Management StudioまたはAzure Data StudioでImplem.Pleasanterのdbo.sitesテーブルを開きます。
SiteIdカラムが先程メモったサイトIDに該当するレコードを選択しSiteSettingsカラムの内容をコピーしてください。
それがオリジナルテンプレの構成になります。
<img width="1458" alt="スクリーンショット 2019-12-25 14.02.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537390/79dc4657-e0a4-3f65-acd6-c897e137d7e1.png">


続いて、プリザンターのフォルダ内\App_Data\Definitions\definition_Template.xlsmというファイルを開いて編集します。
<img width="883" alt="スクリーンショット 2019-12-25 16.19.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537390/c7f587e4-a48b-2a4e-acc9-3be474175337.png">


Idには他のテンプレートと重複しない値を入力します。
SiteSettingsTemplateには先程コピーしたSiteSettingsの内容を貼り付けましょう。
Languageにはja、Titleにはオリジナルテンプレと入れます。
Projectには、11と入れてください。これは「プロジェクト」タブの11番目に表示するという意味です。
以上の箇所を入力したらファイルを保存して、プリザンターを再起動してみましょう。

プリザンターにログインしたら、おもむろに「新規作成」をクリック！
そして、そこには……
<img width="1258" alt="スクリーンショット 2019-12-25 15.31.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537390/9ffdfe75-1c03-858c-77aa-c82abff70ed6.png">
やりました！
これでいつでもオリジナルテンプレを使った不要なグルメマップテーブルを量産できます！
かなしいことがあった日は不要なグルメマップテーブルを量産しましょう。あまりの不要さに、かなしいこともどうでもよくなります。

## おわりに
というわけでAdvent Calendar最終日にふさわしいネタ切れ感が存分に出た記事になったのではないかと思います。
使うことはきっとないかもしれませんが、プリザンターはこんなところまでカスタマイズできるんだよ、ということが少しでも伝われば良いなあと思っています。

以上、新しいテンプレを作って満足してしまうあなたへの鎮魂歌でした。

ちなみに、Definitionsにある他のファイルも見てみると楽しいですよ。


## オリンピックイヤーに向けて
すでにWebでもご案内しております通り、来年はPostgreSQL対応版のリリースを予定しております。
具体的な時期を出せないのが心苦しいところではありますが、できるだけ早くリリースできるようエンジニア一同頑張っておりますのでもう少しだけお待ち下さい。

プリザンターは来年も、今年以上の進化を遂げます。金メダル級です。
みなさん、どうぞ楽しみにしていてください。

それではまた来年のPleasanter Advent Calendarで！

メリークリスマス！ & エンジョイプリザンター！
![HAYATO飛翔.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537390/eb72843b-26ad-3f52-e0c9-1ba1e509e898.png)
ファイルのDeleteしたことだけをコミットしたいコマンド

普通に `git add .` とかしてしまうと modified も deleted も入ってしまう。

deletedのファイルだけをaddしたい。

```
git ls-files --deleted | xargs git rm
```
だそうです。

参考：
https://gist.github.com/alcidesqueiroz/3251468
強化学習43　ChainerRLの学習方法を読む　optimizerから追跡してみる

中学生から大学生のAI初学者を対象にしています。
習うより慣れろできましたが、少し習います。
まずは、ソースコードを読むことから始めます。
githubからダウンロードすると便利です。
https://github.com/chainer/chainerrl

```bash
git clone https://github.com/chainer/chainerrl.git
```
ダウンロードしたフォルダー　chainerrlを基準にソースコードの読みます。
ソースコードは、chainerrl/chainerrlの中にあります。
pythonでimportするときのフォルダー指定は、chainerrl/cainerrl/agentsの場合、

```python
from chinerrl.agent import DQN
```
のようになります。

さて、機械学習なる部分の学習(train)は、optimizerがやります。
本シリーズでは、optimizerは、agentのオブジェクト生成（初期化）に引数として渡されていました。

# 基本1　optimizerはagentに入る

※強調したいだけなので、見出しじゃないです。
これは、agentが自分の中にあるニューラルネットワークの訓練を、自分で行っているということです。
それでは、どのタイミングでagentは訓練をするのか調べてみます。
強化学習では、agentがobservationを受け取り、actionを返します。この部分を読むと、

```python
action = agent.act_and_train(observation)
```
となっています。

#　基本2　agent.act_and_trainで学習する。

そこで、agentの中身を見ます。
chainerrl/agents/dqn.py
optimizerで検索します。検索は、Edit-Findでやりましょう。
class DQNのdef__init__にあります。
159行目のself.optimizer=optimizerで定義され、
def updateの271行目にself.optimizer.update()とあるので、
agent.update()されたときに、optimizer.update()もされることがわかります。

#　基本3　agent.update()されたときに、optimizer.update()もされる

それでは、update()はいつされるかですが、
def__init__の173行目から176行目までに
update_func=self.update
と書いてあります。これは、その下に続く、self.replay_updaterに引数として渡されています。

#　基本4　agent.update()は、replay_updaterに引数で渡される。

そして、self.replay_updaterは、act_and_trainの405行目で実行されています。
これで、基本2につながりました。
以下の順番で呼び出されるみたいです。
1 agent.act_and_train
2 self.replay_updater.update_if_necessary(self.t)
3 agent.update
4 optimizer.update

一部、読んでいないところがあります。
replay_updaterのところです。

```python
from chainerrl.replay_buffer import ReplayUpdater
```
なので、chainerrl/replay_buffer.pyを読みます。
230行目にReplayUpdaterがあります。
262行目から続くdef update_if_necessaryの中に、277行目と280行目にself.update_funcがありました。
ついでにここを読んでみると、episodeごとにupdateするのか、そうでないのかわかります。また、batchsizeをここで指定していることもわかります。
PersistentVolumeClaim (pvc)削除できず、Terminating ステータスのままとの問題

### 問題の内容
NFS を使って PersistentVolumeClaim を作成しました。
NFS 修正があって、IP が変わりました。
この状態で、PersistentVolumeClaim を削除して、再作成するつもりでしたが、
削除できず、Terminating ステータスのままとなっていました。

```sh
# PersistentVolumeClaim リストする
kubectl get persistentVolumeClaims
> Output:
> NAME              CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                            STORAGECLASS   REASON   AGE
> nfs-data-volume   50Gi       RWX            Retain           Released   default/nfs-data-volume                           25d

# 対象の PersistentVolumeClaim を削除します
kubectl delete persistentVolumeClaims nfs-data-volume
# ずっと処理が完了できず、Ctrl+C でキャンセルした

# PersistentVolumeClaim を再確認して、Terminating ステータスのままとなっています
kubectl get persistentVolumeClaims
> Output:
> NAME              STATUS        VOLUME            CAPACITY   ACCESS MODES   STORAGECLASS   AGE
> nfs-data-volume   Terminating   nfs-data-volume   50Gi       RWX                           25d
```

### 解決方法
原因はPersistentVolumeClaimがProtected状態になってしまい、削除できなくなります。

kubectl patch コマンドでProtected状態をクリアしたら、解決できます。

```sh
# Protected状態の確認
kubectl describe pvc nfs-data-volume | grep Finalizers
> Output:
> Finalizers:    [kubernetes.io/pvc-protection]

# Protected状態をクリアする
kubectl patch pvc nfs-data-volume -p '{"metadata":{"finalizers": []}}' --type=merge

# 削除したい PersistentVolumeClaim を確認
kubectl get persistentVolumeClaims
> Output: なし（nfs-data-volumeが削除された）
```

<br> 
記事のご覧、どうもありがとうございます！
DSS 橋本
ReactでAdobeFontsを使いたい！

## はじめに
ReactでAdobeFontsを使うためにまとめました。
最終的にこんな感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502798/1fdf3c74-2e2b-fffd-2559-6df7ea7683b5.png)

###そもそもAdobeFontsって？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502798/d2e1f213-03ea-744f-04df-fc428bef52c7.png)
https://fonts.adobe.com/

豊富なフォントの中からお気に入りのフォントをサクッと探して、手軽に適用できる素晴らしいWebフォントサービスです。


## 開発環境
VsCode
npm 6.12.1

## セットアップ
###Adobeアカウント作成
https://fonts.adobe.com/
上記サイトへアクセスして、アカウントを作成しましょう。

###AdobeFontsからお気に入りのフォントを見つけて、プロジェクトに追加する
次に、お気に入りのフォントを見つけましょう。
見つけたら、下画像にマークしてあるコードアイコンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502798/71263cda-9431-cc81-d9b5-327e2e00a01c.png)

ウィンドウが出てくるので、プロジェクトを新規作成し、保存ボタンを押下しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502798/fff5b320-3e7a-93f5-f6dc-d8efd350de36.png)

すると、Webサイトからフォントを読み込むためのLinkタグとCssが表示されます。
これでAdobeFonts側の準備は完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502798/0402e801-589e-ba4b-6376-bba585914425.png)

###Reactプロジェクトからフォントを読み込み、適用する。
先ほど確認したLinkタグをコンポーネントの`return()`内に配置します。

```ShopPage.js
import React from "react";
import "./ShopPage.css";

export function ShopPage() {
  return (
    <div className="ShopPage">
      <link rel="stylesheet" href="https://use.typekit.net/wrq6syh.css"></link>
      <div className="top-title-background w-screen bg-red-800 text-white p-4">
        <div className="flex">
          <div className="w-1/2">
            <p className="-mb-8">Shop</p>
            <p className="-mb-8">Sample</p>
            <p className="-mb-8">Page</p>
          </div>
          <div className="discription w-1/2 h-auto">
            <p>some discription some discription some discription</p>
            <p>some discription some discription some discription</p>
            <p>some discription some discription some discription</p>
            <p>some discription some discription some discription</p>
            <p>some discription some discription some discription</p>
            <p>some discription some discription some discription</p>
            <p>some discription some discription some discription</p>
          </div>
        </div>
      </div>
      <div className="intro">
        <h1>Introduction</h1>
      </div>
    </div>
  );
}
```

次にCssを適用します。

```ShopPage.css
.top-title-background {
  height: 25rem;
  font-family: bungee, sans-serif;
  font-weight: 400;
  font-style: normal;
  font-size: 6rem;
}

.discription {
    font-family: fira-sans,sans-serif;
    font-weight: 400;
    font-style: normal;
    font-size: 1.2rem;
}

body{
    background-color: #e7e7b8;
}

h1{
  font-family: bungee, sans-serif;
  font-weight: 200;
  font-style: normal;
  font-size: 2.5rem;
}
```

## 動作確認
これで完了です。
サーバを立てて確認してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502798/1fdf3c74-2e2b-fffd-2559-6df7ea7683b5.png)
こんな感じになります。

## 終わりに
webフォントってめちゃめちゃ便利ですね。
ただローカルで開発する場合、ネット環境がないとフォントを読み込めないネックですけど。
指定したチャンネルIDをテキストに保存して次回起動時読み込む

##はじまり
Discordでbot作ろうと久々に作り始めたときにチャンネルIDを取得して指定しないとメッセージが送信できないと勘違いして作った。
~~偶然の産物的な（意図してるけど）~~

##これは何？
最初にファイルの存在を確認
あれば読み込みチャンネルIDを変数に格納
なければそのままスルー
/setup チャンネルID
でチャンネルIDが正しければ指定したチャンネルに成功とメッセージが送信される
/save
でチャンネルIDをテキストファイルに保存

##どういうときに使うの？
いろんなチャンネルでbotが動かないようにチャンネルを指定してる人はbotを動かすチャンネルを変えるときに便利かも？
~~特に気にしない人はmessage.channelを使おうね~~

##プログラム
<details><summary>ソースコード（折りたたんでいます）</summary><div>　
```python
import discord

client = discord.Client()


@client.event
async def on_ready():
    print("logged in as "+client.user.name)
    try:
        with open("test.txt","r")as f:
            channel=client.get_channel(int(f.read()))
            await channel.send("チャンネルIDをロードできました")
    except FileNotFoundError:
        print("チャンネルIDが取得できませんでした")

@client.event
async def on_message(message):
    global tmp
    if message.author != client.user:
        if message.content.startswith("/setup")==True:
            tmp=message.content[7:len(message.content)]
            try:
                channel=client.get_channel(int(tmp))
            except ValueError:
                print("不正な値です")
            try:
                await channel.send("成功")
            except AttributeError:
                print("チャンネルIDが間違っています")
            except UnboundLocalError:
                print("不正な型です")
        if message.content=="/save":
            f=open("test.txt","w")
            f.write(tmp)
            f.close()
            channel=client.get_channel(int(tmp))
            await channel.send("チャンネルIDを保存しました")
client.run("token")
```
</div></details>

##解説
###ファイルのロード
```python
    try:
        with open("test.txt","r")as f:
            channel=client.get_channel(int(f.read()))
            await channel.send("チャンネルIDをロードできました")
            load=1
    except FileNotFoundError:
        print("チャンネルIDが取得できませんでした")
```
ファイルがなかった場合などはtry exceptでエラー処理してます。
当然テキストファイルから読み込むため文字列から数値に変換も必要なのでint()も使ってます。
###チャンネルID取得
```python
        if message.content.startswith("/setup")==True:
            tmp=message.content[7:len(message.content)]
            try:
                channel=client.get_channel(int(tmp))
            except ValueError:
                print("不正な値です")
            try:
                await channel.send("成功")
            except AttributeError:
                print("チャンネルIDが間違っています")
            except UnboundLocalError:
                print("不正な型です")
```
/setup チャンネルID
でチャンネルID部分だけ取り出し```client.get_channel(int(tmp)) ```でチャンネルIDをchannelに格納
~~エラー処理はいるかわからないけど入れた~~
###チャンネルID保存

```python
        if message.content=="/save":
            f=open("test.txt","w")
            f.write(tmp)
            f.close()
            channel=client.get_channel(int(tmp))
            await channel.send("チャンネルIDを保存しました")
```

~~```open("保存ファイル","種類")```これ適当に覚えてるけどwriteのw　readのrぐらい覚えておけば何とかなる（）~~
ここらへんも主にファイルの書き込み読み込みだけだから難しくはないはず

##まとめ
#**基礎はしっかり学ぼう**
GitLab EEのユーザー数ライセンスで利用される True Up Modelとは？

毎度、ググっても出てこない小ネタを取り扱っております。
本記事は個人的な見解であり、筆者の所属するいかなる団体にも関係ございません。

# 0. はじめに

弊社スタイルズでは、GitLabのEnterpriseEditionを利用しております。
利用しておりますと言っても、使っているのはStarter Editionで月額 $4/人の低価格のものです。

Why Starter/Bronze? | GitLab
https://about.gitlab.com/pricing/starter/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/42481acc-e64e-1367-1962-922dcbe045cb.png)

このStaterEditionの100人分のライセンスを1年分購入しています。
このライセンスを使う時に、何人が使っているかを計算する方法について解説します。

# 1. True Up Model

ライセンスを100人分買っても、50人しか使ってなかったらもったいないですよね。
とすると実際の使用人数に合わせたライセンス数を買いたいと思うと思います。

しかし、50人分で買って充分だと思ってたのに予想外にユーザー数が増えて70人になってしまった。
50人のライセンス数を越えたら、51人目はすぐに使えなくなっちゃうの？
そうなると、あと20人分追加で買わないといけないの？
その時に最初に買った50人分の利用期限の1年とずれちゃうよね？
と、いろいろ問題が出てきます。

時々嘘をつく人がいて、50人分で買ってたのに70人で使っちゃえ！なんて人もいるかもしれません。
20人分ただで使えるじゃん！

そういう問題を解決するのが GitLabのtrue-up-modelです。

Pricing model | GitLab
https://about.gitlab.com/handbook/ceo/pricing/#true-up-pricing

# 1-1. ライセンス数を越えたら使えなくなるの？

まず、ライセンス数を越えた場合の挙動について解説します。
ライセンス数を越えても利用できます。
50人分のライセンスを買っておいて、70人で利用することも可能です。

# 1-2. じゃあ、少ない数で購入しておいて、70人で使えばいいじゃん？

残念ながらそうはいきません。

70人利用開始すると、Active Userが70になります。
以下のスクリーンショットを見てください。

赤枠で囲った88が現在ログインできるユーザー数です。
88人のユーザーが登録されていてログインできるということが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/b2291159-fb9b-c063-cb12-8b06d1da91c8.png)

で、このActive Usersは、真ん中の欄の一番上の紫の枠のMaximum Usersに反映されます。
このMaximum Usersはハイウォーターマークなので、一旦上がると下がることはありません。
次年度更新時には、リセットされるはずです。

つまり、上の図では、現在は利用者数が88人だが、過去 91人までActive Usersが上がったことがあった。ということを示しています。
(その後、利用者を無効化して88人になっています)
オレンジのUsersが122と91を越えているのは、登録はされているが利用していない無効になっているユーザーが34人いるということを示しています。

## LDAP接続している場合にはどうなるか？

LDAP接続していても、ログインしない限りは、Active Usersにカウントされません。

# 2. Maximum Usersは何にきくのか？

Maximum Usersは、次年度更新時の最低ユーザー数としてカウントされます。

つまり、50人分でライセンスを購入したけど、利用者数が70人になってしまった。
という場合、次年度の更新時の最低ユーザー数は、70人分～になります。
もし、それで100人分のライセンスを購入すれば問題ありませんが、50人分のみのユーザーライセンスを購入するすることはできません。

# 3. True Up Modelについて

なかなかよいライセンスモデルだと思います。
次年度更新しなければ、1回だけライセンス数をオーバーすることもできますが、基本的に次年度更新もする筈なので、次年度更新時に回収されます。

ライセンス数を厳密に一定のユーザー数で制限してしまうと、それはそれでユーザービリティの低下や請求の手間が発生するので手間の方が大きくなり、利用者とライセンス提供者の双方にデメリットが生じます。

その中間をうまく取った考え方だと思います。
Laravelで今度使ってみようと思っているComposerパッケージ(2020/03/04更新)

# はじめに
本記事は個人的に今度Laravelで利用してみたいと思っているパッケージをリストアップするだけの記事です。
こういったパッケージはネットで色々調べて見つける訳ですが、個人的に一番お勧めなリサーチ方法は[Laravel News](https://laravel-news.com/)です。
（どうやらLaravelオフィシャルのニュースサイトということらしい）

あと、たぶん随時更新します。

## パッケージ達

###▼Laravel View Model (2019/12/25追加)
* [GitHubのリンク](https://github.com/spatie/laravel-view-models)
* [Laravel Newsでの紹介記事](https://laravel-news.com/laravel-view-models)

Laravelのビュー用のモデルを作成するためのパッケージです。
以前「[Laravelで、view('target.view', ['id' => $id, 'name' => $name]) を少しでもスマートに書きたい](https://qiita.com/TF_Fujijun/items/d57c004132d1a4ad8d1d)」っていう記事を書いたときに最後に濁していた方法ですが、Laravelの`view()`ヘルパ関数は、ビューのパラメータ(第2引数)に連想配列を受け取ることになっています。

この`view()`ヘルパ、実はパラメータは連想配列だけでなく`Illuminate\Contracts\Support\Arrayable`の継承クラスを受けることも可能です。
その場限りのパラメータ用のオブジェクトを作る場合は`Arrayable`クラスを継承すれば解決できます…っという記事を書こうと思ってましたが既にComposerパッケージ化されていたものを発見。

コントローラ側の実装がよりシンプルになるため、今後はこちらを積極的に利用していこうと考えてます。

---

###▼Laravel Shift Blueprint（2019/12/25追加）
* [GitHubのリンク](https://github.com/laravel-shift/blueprint)
* [Laravel Newsでの紹介記事](https://laravel-news.com/laravel-shift-blueprint)

Laravelのコード生成ツールです。
"Blueprint"は"設計図"という意味を持っています。

YAMLファイルでモデルやコントローラの処理を記載し、`php artisan blueprint:build`コマンドで一挙にコードの足場を作成するためのパッケージです。
簡単なMVCであればYAMLファイルから一発で生成できてしまうようです。
Laravel Newsの紹介記事にLaravel Shift Blueprintを使っている動画が上がっているので、こちらを見ていただいた方がイメージしやすいかも。

いわゆるスカフォールドするためのパッケージですが、現状は公式のドキュメントがGitHub上のREADMEだけで、しかも記載内容がまだまだ少ない状況です。
今後このパッケージでできることが増えていきそうな予感がするので、新しい情報が確認できたら紹介記事を書いてみようと考えています。

---

###▼Laravel Option Framework（2020/03/04追加）
* [GitHubのリンク](https://github.com/haruncpi/laravel-option-framework)
* [Laravel Newsでの記事](https://laravel-news.com/laravel-option-framework)

ブラウザベースで利用可能な設定値管理ライブラリです。
主にWEBアプリケーションの管理画面を作ったりする場合にあると便利です。

設定された値は専用のヘルパ関数を呼び出して利用するため、Laravelの標準のconfigヘルパとは競合しない点が良いでしょう。
ただし値の管理はEloquentモデルを利用しているため、たくさん呼び出す場合に再利用できる仕組みを考えなくてはいけません。

設定値はHTMLのフォームを利用して管理しますが、このツールの優れている点は以下の種類のフォームが利用できることです。

* HTMLのinput要素のtype属性に指定できるすべての値
* switcher
* dropdown
* icon
* multicheck
* autocomplete
* tag
* datepicker
* timepicker
* editor
* textarea
* colorpicker

試しに使ってみましたが、オレオレ管理画面を作ったりする場合には最適なライブラリだと思います。
日々の株価を手軽にGoogleAppScriptsで取得する

# やること
毎日15時すぎにその日の株価を取得し、スプレッドシートに記録していくスクリプトを作成します。
自分の保有資産の毎日の推移をさくっと見ることができるようにします。

# スクリプト
サンプルとしてLIFULL(2120)の株価を取得することとします。


```javascript

function putRowAndPrice() {
  const ticker = '2120.T';
  const url = 'https://stocks.finance.yahoo.co.jp/stocks/detail/?code=' + ticker;

  const book = SpreadsheetApp.getActiveSpreadsheet();
  const sheet= book.getSheetByName("シート1");
  const currentDayPrice = fetchLatestPrice();
  
  const date = new Date();
  // arrayの最初の要素にarrayにしないとsetValuesはエラーになる
  const price = [ 
    [Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd'), currentDayPrice]
  ]
  sheet.insertRowAfter(1);
  sheet.getRange('A2:B2').setValues(price);

}

function fetchLatestPrice() {
  const response = UrlFetchApp.fetch(url);
  const html = response.getContentText('UTF-8');
  // 直前のクラス名
  const beforeTarget = html.split('realTimChangeMod');
  // 取得したい要素の文字列
  const target = beforeTarget[1].split('stoksPrice">');
  const price = target[1].split("</td>");
  
  return price[0];
}

```

上記でスクリプト編集画面で`putRowAndPrice` 関数を実行すれば、その日の株価を
A2:B2のセルに吐き出すことができました。

![スクリーンショット 2020-11-15 20.59.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328190/25703695-5c2b-a7c4-abde-65fdd002ff23.png)


## トリガーの設定

平日の15時10分にスクリプトが動くように設定します。
GASのGUI画面からでは特定の日の特定の時間には設定ができないのでスクリプト上で設定をします。

``` javascript
function setTrigger() {
  // 0 6 は 日曜、土曜
  const execDays = [1, 2, 3, 4, 5];
  const date = new Date();
  const dayOfTheWeek = date.getDay();
  
  if (execDays.includes(dayOfTheWeek)) {
    date.setDate(setTime.getDate() + 1)
    date.setHours(15);
    date.setMinutes(10); 
    ScriptApp.newTrigger('exec').timeBased().at(date).create();  
  }
}

function exec() {
  setTrigger();
  putRowAndPrice();
}
```

一度スクリプト画面からsetTriggerメソッドを実行する必要があります。
実行後に `スクリプト画面>編集>現在のプロジェクトのトリガー` で
設定されたトリガーを確認することができます。


![スクリーンショット 2020-11-15 21.12.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328190/7e0e8cbe-6c5c-098b-ea55-02e641405aed.png)

![スクリーンショット 2020-11-15 20.39.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328190/a1c2dc8b-fa95-b69a-df44-8d64a648f2ca.png)

## 終わりに
上記で指定した銘柄の日々の終値を記録できるようになりました。
この記事を8割方書き終わった後に[こんなライブラリ](https://pypi.org/project/yfinance/)を見つけたので、そちらで取得してみたほうがより便利、かつ面白いことができるかもしれません。
【Laravel】多対多のリレーション（紐づけの操作について）

多対多の紐づけ操作のメソッド、attach(),detach()についてのメモです。
前回は多対多の関係の構造と定義についてメモしています。
[多対多のリレーション(構造、定義)](https://qiita.com/rope19181/items/bcf7d6792f570082b920)

Laravelでは多対多の関係における紐づけはattach(),detach()などのヘルパメソッドが利用できる。



・**attach()**
役割の付与＝中間テーブルへのデータの追加（insertの操作）

```php:

public function addFavorite(Int $user_id)
{
    return $this->favorites()->attach($user_id);
}
```


・**detach()**
役割の削除＝中間テーブルへのデータの追加（deleteの操作）

```php:

public function rmFavorite(Int $user_id)
{
    return $this->favorites()->detach($user_id);
}
```
HTMLへgoogle map を組み込みたい

google mapをHTMLで作成したビューに組み込むべく、iframeタグを設置し、APIキーを設定したが、「your API key invalid」というエラーが出てしまう。数日前にrailsの勉強でアプリを作成した際に、google Platformに登録し、APIキーを取得した。そのAPIキーを使いまわしたのが、原因なのかもしれない。同じAPIキーを別にアプリには、使えないのか。

というわけで、APIキーでの方法は、諦めiframタグに直接google mapの埋め込みを利用して組み込むことにしました。
古いRe:dashで文字列パラメータがエスケープされてしまう問題

# はじめに
この記事は v6.0.0 より古いバージョンの Re:dash を使用している方向けに書いています。
v6.0.0 以降を使用している方は参考程度に読んでください。

# 経緯と事象
仕事でデータ分析基盤として Re:dash を使っている。
アクセスログを解析するのに文字列型のクエリパラメータを設定してURLの絞り込みを行いたかった。
ところがデータは存在するはずなのにURLを指定するとなぜか検索結果が0件。

# 原因
渡されるパラメータを調べたらなんとスラッシュ `/` が `&#x2F;` に変換されているではないか。
どうも特定の文字列を勝手に文字参照にエスケープしてくれちゃうらしい。
道理でマッチしないわけだ。

# 対処法
最初は REPLACE で置換していたんだけど、なんかスマートじゃないなぁと調べてみたらもっと良い方法を見つけた。
`{{{param}}}` とするだけ。ふつうのパラメータ指定のブラケットを1つ増やせばOK。
こうすると文字がエスケープされずにスラッシュをそのまま渡せる。
https://github.com/getredash/redash/issues/1211

# 確認した環境
- Re:dash v5.0.2
- macOS Mojave v10.14.6
- Chrome v79.0.3945.88

# 現行バージョンではエスケープされないらしい (未確認)
v6.0.0 でエスケープされないように修正されたもよう
https://github.com/getredash/redash/pull/3058

# おまけ
Re:dash は Mustache というテンプレートエンジンを使っているので参考までに貼っておく。
https://mustache.github.io/
firebaseを初めて使う

[未来電子テクノロジー](https://www.miraidenshi-tech.jp/intern-content/program/)でインターンをしている[箕牧和廣](https://qiita.com/mimaki_kazuhiro)です。
今回はfirebaseについて書いていこうと思います。

プログラミング初心者であるため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するのでどんどん指摘してください。

#Firebaseとは
Firebaseはデータストレージ、ユーザー管理などアプリケーションのバックエンドとして必要となる機能をサービスを提供することで、アプリケーション開発者がクライアントサイドの開発に集中できるようにしてくれるBaaS（Backend as a Service）の一種。

#Install

以下のコマンドで、firebase-adminをインストール。

```terminal
＄ pip3 install firebase-admin
```

ルールを以下のように変更

```
{
  "rules": {
      ".read": true,
      ".write": true
  }
}
```

#Firebaseにアクセス

以下のコードでアクセスし、データを追加・参照できる。

```firebase.py
import firebase_admin
from firebase_admin import credentials
from firebase_admin import db

cred = credentials.Certificate('./<your service account json>')

firebase_admin.initialize_app(cred, {
    'databaseURL': 'https://<your database url>',
    'databaseAuthVariableOverride': {
        'uid': 'my-service-worker'
    }
})

##databaseに初期データを追加する
users_ref = db.reference('/users')

users_ref.set({
    'user001': {
        'date_of_birth': 'June 23, 1984',
        'full_name': 'Sazae Isono'
        },
    'user002': {
        'date_of_birth': 'December 9, 1995',
        'full_name': 'Tama Isono'
        }
    })

# databaseにデータを追加する
users_ref.child('user003').set({
    'date_of_birth': 'Aug 23, 1980',
    'full_name': 'Masuo Isono'
    })

##データを取得する
print(users_ref.get())
```

これを実行するとデータが保管される。

#一部データのみ更新

データを保存後に一部のデータのみ更新したい場合、UPDATE関数を使って更新できる。

```firebase.py
##データを更新する
updates = {}
updates['/user001/full_name'] = 'Sazae Fuguta'
users_ref.update(updates)
```
iOS13のダークモードを対応したくない皆さまへ

``` AppDelegate.swift
if #available(iOS 13.0, *) {
   window?.overrideUserInterfaceStyle = .light
}
```

これを書きましょう！

全体適用は ↑
`application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool`

画面ごとに切り替えたい場合は ↓ 
`viewDidLoad()`

``` UIViewController.swift
if #available(iOS 13.0, *) {
    overrideUserInterfaceStyle = .light
}
```

参考: https://stackoverflow.com/questions/56537855/is-it-possible-to-opt-out-of-dark-mode-on-ios-13