シェルスクリプトマガジン

test

香川大学SLPからお届け!(Vol.73掲載)

投稿日:2021.07.25 | カテゴリー: コード

著者:岩本 和真

SLPでは最近、Webブラウザで動作するリズムゲームをチームで開発しました。さまざまな曲でプレーできるように、リズムゲーム本体と並行して、ゲームで使用する譜面を作成するツールも開発しました。このツールもWebブラ
ウザで動作します。今回は、この譜面作成ツールの実装について紹介します。

シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。

図3 最初に実行されるコード

window.onload = async function() {
  const {bpm, musicL} = getMusicInfoViaElement()
  await getWidth();
  await numberQLine(bpm, musicL);
  await setCanvas();
  await setQLine();
  await setXLine();
  await update();
  await draw();
  await scrollBottom();
}

図4 getMusicInfoViaElement()関数のコード

function getMusicInfoViaElement() {
  const bpm = document.getElementById('bpm').value;
  const musicL = document.getElementById('musicL').value;
  const musicBody = document.getElementById('musicBody').value;
  return {bpm, musicL, musicBody}
}

図5 getWidth()関数のコード

const can = document.getElementById("can2");
const ctx = can.getContext("2d");
can.setAttribute('style', 'background-color: #f6f7d7');
function getWidth() {
  return new Promise(function(resolve) {
    cst = window.getComputedStyle(can);
    canvasW = parseInt(cst.width);
    can.width = canvasW;
    resolve();
  })
}

図6 numberQLine()関数のコード

function numberQLine(bpm, musicL) {
  return new Promise(function(resolve) {
    qLineQty = Math.floor(musicL / (60 / bpm) + 1);
    resolve();
  })
}

図7 setCanvas()関数のコード

let qLineMargin = 80;
function setCanvas() {
  return new Promise(function(resolve) {
    canvasH = qLineQty * qLineMargin;
    can.height = canvasH;
    ctx.translate(0, can.height);
    ctx.scale(1, -1);
    can.style.height = canvasH * 2.5 +'px';
    resolve();
  })
}

図8 setQLine()関数のコード

function setQLine() {
  return new Promise(function(resolve) {
    for (let i = quarterLine.length; i < qLineQty; i++) {
      quarterLine[i] = new QuarterLine(i);
    }
    resolve();
  })
}

図9 QuarterLineクラスのコード

class QuarterLine {
  constructor(no) {
    this.no = no;
    this.y = qLineMargin * this.no + qLineMargin / 4;
  }
  update() {
    this.y = qLineMargin * this.no + qLineMargin / 4;
  }
  draw() {
    ctx.beginPath();
    ctx.strokeStyle = "#3ec1d3";
    ctx.lineWidth = Q_LINE_T;
    ctx.moveTo(0, Math.round(this.y));
    ctx.lineTo(canvasW, Math.round(this.y));
    ctx.stroke();
  }
}

図10 setXLine()関数のコード

function setXLine() {
  return new Promise(function(resolve) {
    for (let i = 0; i < 4; i++) {
      for (let j = xLine[i].length; j < qLineQty; j++) {
        xLine[i][j] = [];
        for (let k = 0; k < 14; k++) {
          xLine[i][j][k] = new XthLine(i, j, k);
        }
      }
    }
    resolve();
  })
}

図11 update()関数のコード

function update() {
  return new Promise(function (resolve) {
    // ノーツの縦の長さを更新
    noteH = (qLineMargin / 9 >= Q_LINE_T || divValue == 8)
             ? qLineMargin / 9 : Q_LINE_T
      // ノーツの位置と各拍、各連符の横線の位置を更新
    const laneMargin = canvasW / 5;
    laneSet = [laneMargin / 2, laneMargin * 1.5,
               laneMargin * 2.5, laneMargin * 3.5,
               laneMargin * 4.5];
    noteW = laneMargin / 3;
    quarterLine.forEach((val) => val.update() )
    editLane.forEach((val) => val.update() )
    for (let i = 0; i < 4; i++) {
      for (let j = 0; j < qLineQty; j++) {
        for (let k = 0; k < 14; k++) {
          xLine[i][j][k].update();
        }
      }
    }
    resolve();
  })
}

図12 draw()関数のコード

function draw() {
  return new Promise(function(resolve) {
    ctx.clearRect(0, 0, can.width, can.height);
    for (let i = 0; i < 4; i++) {
      for (let j = 0; j < qLineQty; j++) {
        for (let k = 0; k < 14; k++) {
          xLine[i][j][k].draw();
        }
      }
    }
    for (let i = 0; i < qLineQty; i++) {
      quarterLine[i].draw();
    }
    editLane.forEach((val) => val.draw())
    for (let i = 0; i < 4; i++) {
      for (let j = 0; j < qLineQty; j++) {
        for (let k = 0; k < 14; k++) {
          xLine[i][j][k].drawNote();
        }
      }
    }
    resolve();
  })
}

図13 draw()関数のコード

function scrollBottom() {
  return new Promise(function(resolve) {
    let target = document.getElementById('scroll');
    target.scrollTop = target.scrollHeight;
    resolve();
  }
)

図14 レーン上のノーツをマウスクリックで制御するためのコード

let mouseDown;
can.addEventListener('mousedown', async function(e) {
  mouseDown = true;
  await pos(e);
  await update();
  await draw();
});
can.addEventListener('mouseup', function(e) {
  mouseDown = false;
});
function pos(e) {
  return new Promise(function(resolve) {
    mouseDownX = (e.clientX -
                  can.getBoundingClientRect().left);
    mouseDownY = -(e.clientY -
                   can.getBoundingClientRect().bottom) / 2.5;
    resolve();
  })
}

図15 スライダ機能のコード

let canScale = document.getElementById('canScale');
canScale.onchange = async function() {
  qLineMargin = this.value;
  await setCanvas();
  await update();
  await draw();
  await scrollBottom();
}

図16 apply()関数のコード

async function apply() {
  const {bpm, musicL} = getMusicInfoViaElement()
  await numberQLine(bpm, musicL);
  await setCanvas();
  await setQLine();
  await setXLine();
  await update();
  await draw();
  return Promise.resolve();
}

図17 convert()関数のコード

function calcNote(jnoteValue, calced, musicBody) {
  return Math.round((jnoteValue+calced+musicBody*1000)*100)/100;
}
async function convert() {
  await apply();
  const fileName = document.getElementById('fileName').value;
  const speed = document.getElementById("speed").value;
  const {bpm, musicL, musicBody} = getMusicInfoViaElement()
  const {noteValue, note32Value, note6Value} = calcNoteValue(bpm)
  const outInfo = Array()
  xLine.forEach((val1, i) => {
    val1.forEach((val2, j) => {
      val2.forEach((val3, k) => {
        if (val3.note) {
          const tmp = k < 8 ? calcNote(j*noteValue, k*note32Value, musicBody)
                      : calcNote(j*noteValue, k%8*note6Value, musicBody)
          outInfo.push(Array(1, i+1, speed, tmp))
        }
      })
   })
 })
 createAndDownloadCsv(fileName, outInfo, bpm, musicL, musicBody);
}

機械学習ことはじめ(Vol.73掲載)

投稿日:2021.07.25 | カテゴリー: コード

筆者:川嶋 宏彰

本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。今回は「確率モデル」による機械学習である、ガウス分布を用いた教師あり学習と教師なし学習の手法を紹介します。

シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。

図2 二つの特徴量を抽出して散布図をプロットするPythonコード

import seaborn as sns
import matplotlib.pyplot as plt

plt.rcParams['font.size'] = 14
penguins = sns.load_dataset('penguins')
# 取り出す特徴量
features = ['bill_depth_mm', 'body_mass_g'] # ★
# 対象とするペンギン種
target_species = ['Adelie', 'Gentoo'] # ★
# 今回用いる特徴量をクラスラベルと共に取り出す
df = penguins[['species'] + features].copy()
df.dropna(inplace=True)  # NaN が含まれる行は削除
# 今回用いるペンギン種のみとする
df2 = df[df['species'].isin(target_species)].copy()
print(df2.shape)  # (274, 3) と表示される
# 種(target_species)に合わせたパレットを作成
palette = {c: sns.color_palette()[k] 
           for k, c in enumerate(df['species'].unique())
           if c in target_species}
fig = plt.figure(figsize=(5, 5))
sns.scatterplot(data=df2, x=features[0], y=features[1],
                hue='species', palette=palette)
plt.show()

図4 各クラス(ペンギン種)の体重分布をプロットするPythonコード

import numpy as np
import scipy

X = df2[features].values  # 各個体の2次元特徴量(行数=個体数)
y = df2['species'].values  # 各個体のクラスラベル
prob_y = {c: np.sum(y==c)/y.size for c in target_species}  # 事前確率
print('Prior:', prob_y)
# 平均と分散は2次元にまとめてベクトルや行列として計算しておく
mu = {c: X[y==c, :].mean(axis=0) for c in target_species}  # 平均
sigma2 = {c: X[y==c, :].var(axis=0, ddof=1) for c in target_species}  # 不偏分散
f_idx = 1  # 用いる特徴量のindex(これは1次元の場合)
fig = plt.figure(figsize=(10, 5))
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()  # 右の縦軸
# ヒストグラムをプロット (体重のみ)
sns.histplot(ax=ax1, x=X[:, f_idx], hue=y, palette=palette, alpha=0.2)
xmin = np.min(X[:, f_idx])
xmax = np.max(X[:, f_idx])
xs = np.linspace(xmin-(xmax-xmin)/10, xmax+(xmax-xmin)/10, 100)  # 等間隔に100点用意
# 確率密度関数を準備 (体重のみ)
norm_dist1d = {c: scipy.stats.multivariate_normal(mu[c][f_idx], sigma2[c][f_idx])
               for c in target_species}
for c in target_species:
    # 各クラスの確率密度関数をプロット
    sns.lineplot(ax=ax2, x=xs, y=norm_dist1d[c].pdf(xs)*prob_y[c],
                 hue=[c]*len(xs), palette=palette)
# 各データを小さな縦線でプロット
sns.rugplot(x=X[:, f_idx], hue=y, palette=palette, height=0.05)
ax2.set_ylabel('Probability density')
ax1.set_xlabel(features[f_idx])
plt.show()

図6 1次元での決定境界を求めるPythonコード

# 曲線の上下が変化するおおよその点を求める
diff = norm_dist1d['Adelie'].pdf(xs)*prob_y['Adelie'] -
       norm_dist1d['Gentoo'].pdf(xs)*prob_y['Gentoo']
# 符号の変化点を見つけるために隣り合う要素の積を計算
ddiff = diff[1:] * diff[:-1]
print('boundary:', xs[np.where(ddiff < 0)[0][0]])

図8 2次元ガウス分布とその等高線を表示するPythonコード

from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm
# 共分散行列を求めておく
Sigma = {c: np.cov(X[y==c, :].T, ddof=1) for c in target_species}
def gen_2dgrid(X):
    """ 2次元メッシュグリッドの生成 """
    d = X.shape[1]
    xmin = X.min(axis=0)  # 各列の最小値
    xmax = X.max(axis=0)  # 各列の最大値
    xstep = [(xmax[j]-xmin[j]) / 100 for j in range(d)]  # グリッドのステップ幅
    xmin = [xmin[j] - 10*xstep[j] for j in range(d)]  # 少し広めに
    xmax = [xmax[j] + 10*xstep[j] for j in range(d)]
    aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)]
    return np.meshgrid(*aranges)  # d=2のときは (x0grid, x1grid) が返る
def gaussian_2dgrid(m, S, x0grid, x1grid):
    """ 2次元ガウス分布の密度関数の値を2次元メッシュで計算 """
    gaussian = scipy.stats.multivariate_normal(m, S)
    return gaussian.pdf(np.c_[x0grid.ravel(), x1grid.ravel()]).reshape(x0grid.shape)
c = 'Adelie'  # プロットするクラスを設定
xgrid_2d = gen_2dgrid(X)  # xgrid_2d: (x0grid, x1grid) のような二つ組
px_adelie = gaussian_2dgrid(mu[c], Sigma[c], *xgrid_2d)  # *xgrid_2d で2引数に展開
fig = plt.figure(figsize=(8, 5))  # 3次元プロット
ax = fig.add_subplot(111, projection='3d')
# 2次元ガウシアンの3次元的プロット
cmap = cm.coolwarm  # カラーマップを設定
ax.plot_surface(*xgrid_2d, px_adelie, cmap=cmap)
# 等高線のプロット
z_offset = -np.max(px_adelie)
ax.contour(*xgrid_2d, px_adelie, zdir='z', offset=z_offset, cmap=cmap)
ax.set_zlim(z_offset, ax.get_zlim()[1])
ax.view_init(elev=40, azim=-60)  # 3次元プロットの表示角度の設定
plt.show()

図10 2次元ガウス分布の等高線と元データの散布図を表示するPythonコード

def plot_ellipses(ms, Ss, pys, xgrids, xylabels=None, fig=None):
    """ 各クラスの確率だ円をプロット """
    if fig is None: fig = plt.figure(figsize=(5, 5))  # 新たに作成
    else: plt.figure(fig.number)  # 既存のfigureを利用
    levels = None
    # クラス名を辞書キーから取得
    for c in ms.keys() if type(ms) is dict else range(len(ms)):
        cs = plt.contour(*xgrids, gaussian_2dgrid(ms[c],
                         Ss[c], *xgrids)*pys[c], cmap=cmap,
                         levels=levels)
        levels = cs.levels  # 二つ目以降は一つ目のlevelsを利用
        # 密度(山の標高)をだ円に表示
        # plt.clabel(cs, inline=True, fontsize=8)
        if xylabels is not None:
        plt.xlabel(xylabels[0])
        plt.ylabel(xylabels[1])
    return fig
plot_ellipses(mu, Sigma, prob_y, xgrid_2d, xylabels=features)
sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette=palette)
plt.show()

図12 2次の識別による決定境界を表示するPythonコード

from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
# plot_decision_boundary()は
# 注釈のリンク先を参照して別途定義
clf_qda = QuadraticDiscriminantAnalysis()
clf_qda.fit(X, y)  # 学習
# 決定境界の表示
fig = plot_decision_boundary(X, y, clf_qda,
                             features, palette)
# 確率だ円の表示
plot_ellipses(mu, Sigma, prob_y, xgrid_2d, fig=fig)
plt.show()

図15 ガウス分布によるデータの生成をするPythonコード

fig = plt.figure(figsize=(5, 5))
Ngen_each = 120  # 各クラスで120個体生成する場合
# 各クラスの割合を変化させてもよい
X_gen = np.vstack(
            [np.random.multivariate_normal(mu[c], Sigma[c], Ngen_each)
            for c in target_species])
y_gen = np.hstack([[c] * Ngen_each for c in target_species])
sns.scatterplot(x=X_gen[:, 0], y=X_gen[:, 1], hue=y_gen, palette=palette)
plt.xlabel(features[0])
plt.ylabel(features[1])
plt.show()

図18 混合ガウス分布によるソフトクラスタリングをするPythonコード

from sklearn.mixture import GaussianMixture
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
k = 2  # クラスタ数(非階層的クラスタリングなので決める必要がある)
use_gmm = True  # Falseにするとk-meansになる
if use_gmm:
    gmm = GaussianMixture(n_components=k, covariance_type='full')
    # full、diagはスケーリング無しも可(sphericalでは必要)
    Xu = X
    # GMM推定による各データのクラスタID
    y_pred = gmm.fit_predict(Xu)
else:
    kmeans = KMeans(n_clusters=k)
    # 標準化によるスケーリング
    Xu = StandardScaler().fit_transform(X)
    # k-meansによる各データのクラスタID
    y_pred = kmeans.fit_predict(Xu)
fig = plt.figure(figsize=(11, 5))
fig.add_subplot(121)
sns.scatterplot(x=X[:, 0], y=X[:, 1], color='k')
plt.xlabel(features[0])
plt.ylabel(features[1])
plt.title('Unlabeled data')
fig.add_subplot(122)
sns.scatterplot(x=Xu[:, 0], y=Xu[:, 1],
                hue=y_pred, palette='bright')
method_str = 'GMM' if use_gmm else 'k-means'
plt.title(f'{method_str} clustering')
if use_gmm:
    plot_ellipses(gmm.means_, gmm.covariances_,
                  gmm.weights_, xgrid_2d, fig=fig)
plt.show()

Pythonあれこれ(Vol.73掲載)

投稿日:2021.07.25 | カテゴリー: コード

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第3回は、Pythonの言語機能である「ジェネレータ」に親しむための活用例を紹介します。

シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。

図2 「jugem.txt」の内容を行単位で反転して表示するPythonコード

#!/usr/bin/env python
  
def readJugemu():
  with open('jugemu.txt', 'r') as f:
    return f.readlines()

lines = readJugemu()
for l in lines:
  print(l.rstrip()[::-1])

図5 lines変数を使わないコード例

#!/usr/bin/env python
  
def readJugemu():
  with open('jugemu.txt', 'r') as f:
    return f.readlines()

for l in readJugemu():
  print(l.rstrip()[::-1])

図6 ジェネレータを使用したPythonコード「reverse2.py」

#!/usr/bin/env python
  
def readJugemu():
  with open('jugemu.txt', 'r') as f:
    for line in f:
      yield line

for l in readJugemu():
  print(l.rstrip()[::-1])

図7 関数readJugemu()が返すデータの種類を調べるPythonコード「test.py」

#!/usr/bin/env python
  
def readJugemu():
  with open('jugemu.txt', 'r') as f:
    return f.readlines()

lines = readJugemu()
print(type(lines))

図8 ジェネレータ関数readJugemu()が返すデータの種類を調べるPythonコード「test2.py」

#!/usr/bin/env python
  
def readJugemu():
  with open('jugemu.txt', 'r') as f:
    for line in f:
      yield line

lines = readJugemu()
print(type(lines))

図9 ジェネレータ関数readJugemu()をfor文で使用するPythonコード「reverse3.py」

#!/usr/bin/env python
  
def readJugemu():
  with open('jugemu.txt', 'r') as f:
    for line in f:
      print('readJugemu')
      yield line

for l in readJugemu():
  print('main: ', end='')
  print(l.rstrip()[::-1])

図11 ハノイの塔の解を求めるPythonコード「hanoi.py」

#!/usr/bin/env python
  
def hanoi(n, src, via, dst):
  if n <= 1:
    yield src, dst
  else:
    yield from hanoi(n-1, src, dst, via)
    yield src, dst
    yield from hanoi(n-1, via, src, dst)

for src, dst in hanoi(3, 'A', 'B', 'C'):
  print(f'{src}->{dst}')

図12 yield from構文を使わない場合のコード

#!/usr/bin/env python
  
def hanoi(n, src, via, dst):
  if n <= 1:
    yield src, dst
  else:
    for s, d in hanoi(n-1, src, dst, via): yield s, d
    yield src, dst
    for s, d in hanoi(n-1, via, src, dst): yield s, d

for src, dst in hanoi(3, 'A', 'B', 'C'):
  print(f'{src}->{dst}')

特集3  ラズパイでコロナ感染症対策(Vol.73記載)

投稿日:2021.07.25 | カテゴリー: コード

著者:麻生 二郎

新型コロナウイルス感染症対策として、日々の手洗いと検温は重要です。ただ、1人暮らしの人など、それらを習慣化するのは難しいかもしれません。本特集では、小型コンピュータボード「Raspberry Pi」(ラズパイ)と電子回路を用いて、新型コロナウイルス感染症対策を習慣化できるような支援システムを構築します。

シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。

Part2 感染症対策システムを完成する

図2 pirm_sensor.pyのソースコード

#!/usr/bin/env python3

import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(21,GPIO.IN)

pir = GPIO.input(21)
print(pir)

図3 temp_sensor.pyのソースコード

#!/usr/bin/env python3

from smbus2 import SMBus
from mlx90614 import MLX90614

bus = SMBus(1)
sensor = MLX90614(bus, address=0x5A)
print(sensor.get_ambient(),sensor.get_object_1())
bus.close()

図4 go_out.shのソースコード

#!/bin/bash

##外出確認の初期値と玄関にいる時間(秒)
GOOUT_CHECK=0
STAY_TIME=3

##表面体温の初期値と基準値
OUTSIDE_TEMP=0
OUTSIDE_TEMP_BASE=32

##周辺温度の初期値と基準値
AMBIENT_TEMP=35
AMBIENT_TEMP_BASE=28

##体内温度の基準値
INTSIDE_TEMP_BASE=36.7

##体内温度との差分
INSIDE_BODY_DIFF=4.2

##メッセージ集
MESSAGE1="おはようございます。出かける前に検温をしてください"
MESSAGE2="周辺温度が${AMBIENT_TEMP}であり、検温に適していません。エアコンをかけてください"
MESSAGE3="周辺温度が${AMBIENT_TEMP}です。検温を開始します。おでこをセンサーに向けてください"
MESSAGE4="今朝の体温は${INSIDE_TEMP}です。正常値なので外出可能です。マスクを着用し、予備のマスクも持って出かけてください"
MESSAGE5="今朝の体温は${INSIDE_TEMP}です。熱がありますので、正確に測れる体温計で再度測定してください"

##音声メッセージ関数
function voice_message(){
  echo $1 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /dev/stdout | aplay --quiet
}

##外出の確認
while [ ${GOOUT_CHECK} -lt ${STAY_TIME} ]
do
  if [ $(pirm_sensor.py) -eq 1 ];then
    GOOUT_CHECK=$(echo "${GOOUT_CHECK} + 1" | bc)
  else
    GOOUT_CHECK=0
  fi
  sleep 1
done
voice_message ${MESSAGE1}

##周辺温度の確認
while [ ${AMBIENT_TEMP} -ge ${AMBIENT_TEMP_BASE} ]
do
  AMBIENT_TEMP=$(/usr/bin/python3 temp_sensor.py | cut -d " " -f 1)
  voice_message ${MESSAGE2}
  sleep 5
done
voice_message ${MESSAGE3}

##検温
while [ ${OUTSIDE_TEMP} -gt ${OUTSIDE_TEMP_BASE} ]
do
  OUTSIDE_TEMP=$(/usr/bin/python3 temp_sensor.py | cut -d " " -f 2)
  sleep 1
done
echo "$OUTSIDE_TEMP,$(date +%Y%m%d-%H:%M)" >> ${HOME}/body_temp.csv

##測定結果
INSIDE_TEMP=$(echo "scale=1; ${OUTSIDE_TEMP} + ${INSIDE_BODY_DIFF}" | bc)

if [ ${INSIDE_TEMP} -lt ${OUTSIDE_TEMP_BASE} ]; then
  voice_message ${MESSAGE4}
else
  voice_message ${MESSAGE5}
fi

図5 handwash_mouthwash.pyのソースコード

#!/usr/bin/env python3

import board
import busio
i2c = busio.I2C(board.SCL, board.SDA)
ads = ADS.ADS1115(i2c)

chan1 = AnalogIn(ads, ADS.P0)
chan2 = AnalogIn(ads, ADS.P1)
print(chan1.voltage,chan2.voltage)

図6 go_home.shのソースコード

#!/bin/sh

##帰宅確認の初期値
GOHOME_CHECK=0

##手洗いとうがいの初期値
HAND_WASH=1
MOUTH_WASH=1

##ハンドソープのポンプ圧力基準値
POMP_PUSH_BASE=3

##うがい用コップの重量基準値
CUP_WEIGHT_BASE=3

##メッセージ集
MESSAGE1="おかえりなさい。帰ってきたら,手を洗って、うがいをしましょう"
MESSAGE2="手洗いを確認しました"
MESSAGE3="うがいを確認しました"		

##音声メッセージ関数
function voice_message(){
  echo $1 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /dev/stdout | aplay --quiet
}

##帰宅の確認
while [ ${GOHOME_CHECK} == 1 ]
do
  GOHOME_CHECK=$(pirm_sensor.py)
  sleep 1
done
voice_message ${MESSAGE1}

##手洗いとうがいの検出
while [ ${HANDWASH} == 1 -o ${MOUTHWASH} == 1 ]
do
  ##手洗い検出の処理
  HANDWASH_MOUTHWASH=$(handwash_mouthwash.py)
  if [ $(cut -d " " -f 1 ${HANDWASH_MOUTHWASH}) -gt ${POMP_PUSH_BASE} ]; then
    if [ $(HANDWASH) == 1 ]; then
      voice_message ${MESSAGE2}
      echo $(date +%Y%m%d-%H%M-%S) >> ${HOME}/handwash.csv
    fi
    HANDWASH=0
  fi
  ##うがい検出の処理
  if [ $(cut -d " " -f 2 ${HANDWASH_MOUTHWASH}) -lt ${CUP_WEIGHT_BASE} ]; then
    if [ $(MOUTHWASH) == 1 ]; then
      voice_message ${MESSAGE3}
      echo $(date +%Y%m%d-%H%M-%S) >> ${HOME}/mouthwash.csv
    fi
    MOUTHWASH=0
  fi
  sleep 0.5
done

図7 open_close.pyのソースコード

#!/usr/bin/env python3

import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(20,GPIO.IN)

window = GPIO.input(20)
print(window)

図8 ventilation.shのソースコード

#!/bin/bash

##メッセージ
MESSAGE="窓を開けて空気を入れ換えましょう"

##音声メッセージ関数
function voice_message(){
  echo $1 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /dev/stdout | aplay --quiet
}

##在宅の確認
STAY_HOME=$(pirm_sensor.py)

##開閉の確認
OPEN_CHECK=$(open_close.py)

##換気の通知
if [ ${STAY_HOME} == 1 -a ${OPEN_CHECK} == 0 ]; then
  voice_message ${MESSAGE}
  sleep 180
  if [ ${OPEN_CHECK} == 1 ]; then
    echo $(date +%Y%m%d-%H%M-%S) >> ${HOME}/ventilation.csv
  fi
fi

図9 spo2_sensor.pyのソースコード

#!/usr/bin/env python3

import max30102
import hrcalc

m = max30102.MAX30102()

red, ir = m.read_sequential()

hr_spo2 =hrcalc.calc_hr_and_spo2(ir, red)
print(hr_spo2[2], hr_spo2[3])

図10 spo2.shのソースコード

#!/bin/bash

##在宅状態の初期値
STAY_HOME=0

##メッセージ集
MESSAGE1="寝るときは血中酸素飽和度を測定しましょう"
MESSAGE2="酸素が不足しています。医療機器で正確な値を測定してください。同じ値を示せば、至急病院に連絡しましょう"

##音声メッセージ関数
function voice_message(){
  echo $1 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /dev/stdout | aplay --quiet
}

##在宅の確認
While [ ${STAY_HOME} == 0 ]
do
  STAY_HOME=$(pirm_sensor.py)
  sleep 180
done

##血中酸素飽和度測定の通知
voice_message ${MESSAGE1}
sleep 300

##血中酸素飽和度の測定開始
while [ 1 ]
do
  SPO2=$(/usr/bin/python3 spo2_sensor.py)
  SPO2_VALUE=$(echo ${SPO2} | cut -d " " -f 1)
  SPO2_JUDGE=$(echo ${SPO2} | cut -d " " -f 2)
  if [ ${SPO2_JUDGE} == "True" ]; then
    echo "${SPO2_VALUE} $(date +%Y%m%d-%H%M)" >> ${HOME}/spo2.csv
    if [ ${SPO2_VALUE} -le 93 ]; then
      voice_message ${MESSAGE2}
      exit 0
    fi
  fi
  sleep 300
done

特集2 JenkinsによるCI/CD(Vol.73記載)

投稿日:2021.07.25 | カテゴリー: コード

著者:長久保 篤、酒井 利治

ソフトウエアの開発・生産性を高めるには「CI(Continuous Integration)/CD(Continuous Delivery)」が不可欠となっています。本特集では、CI/CDとは何か、オープンソースソフトウエアの「Jenkins」を用いてCI/CD環境を構築する方法を分かりやすく解説します。

シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。

図9 Dockerfileの内容

FROM jenkins/jenkins:2.277.4-lts-jdk11
USER root
RUN apt update && apt install -y apt-transport-https \
      ca-certificates curl gnupg2 \
      software-properties-common
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
RUN apt-key fingerprint 0EBFCD88
RUN add-apt-repository \
      "deb [arch=amd64] https://download.docker.com/linux/debian \
      $(lsb_release -cs) stable"
RUN apt update && apt install -y docker-ce-cli
USER jenkins
RUN jenkins-plugin-cli --plugins "blueocean:1.24.6 docker-workflow:1.26"

図21 Jenkinsfileの内容

pipeline {
  agent any
  stages {
    stage('ビルド') {
      steps {
        sh 'mvn -B -DskipTest clean package'
      }
    }
    stage('テスト') {
      steps {
        sh 'mvn -B test'
      }
    }
    stage('デプロイ') {
      steps {
        sh './jenkins/scripts/deliver.sh'
      }
    }
  }
}

図41 説明した内容を反映したJenkinsfile

pipeline {
  agent {
    label 'mvn3'
  }
  stages {
    stage('ビルド') {
      steps {
        sh 'mvn -B -DskipTest clean package'
        archiveArtifacts artifacts: 'target/*.jar'
      }
    }
    stage('テスト') {
      steps {
        sh 'mvn -B test'
      }
      post {
        always {
          junit 'target/surefire-reports/*.xml'
        }
      }
    }
    stage('デプロイ') {
      when {
        branch 'master'
        beforeInput true
      }
      input {
        message "デプロイしますか?"
      }
      steps {
        sh './jenkins/scripts/deliver.sh'
      }
    }
  }
  post {
    failure {
      emailext (
        subject: "失敗: プロジェクト '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
        body: """次のページでコンソールの出力を確認してください:
        ${env.BUILD_URL}""",
        recipientProviders: [developers()]
      )
    }
  }
}

特集1 PowerShellを学ぼう(Vol.73記載)

投稿日:2021.07.25 | カテゴリー: コード

著者:三沢 友治

PowerShellとは、米Microsoft社が開発したシェルおよびスクリプト言語です。Windows OSや、Microsoft Office製品に関連するサービス「Microsoft 365」を管理するのに活用されています。本特集では、これからPowerShellに取り組み始めたい人を対象に、PowerShellについて基本から分かりやすく解説します。

シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。

図47 時刻を取得するスクリプト(writeTime.ps1)

# 現在の時刻で時間オブジェクトを取得
# 時間オブジェクトは文字列ではなく時間の情報を保持している
# そのため一部の情報を容易に取得できる
$date = Get-Date
# 取得した時間から指定のフォーマットでファイルを上書き
# 時間を取得するhh:mmを指定し、その内容をfile.txtに上書きしている。追記したい場合は >> file.txtとする
$date.ToString("hh:mm") > file.txt

図48 ランダムな名前、ランダムな作成日でファイルを作成するスクリプト(CreateRandomFiles.ps1)

# 指定したディレクトリにランダムな内容のファイルを指定サイズで作成する
# 成功時はTrue、失敗時はFalseを返す
Function CreateRandomBiteArrayFile([string]$Path, [int]$sizeMB)
{
    try
    {
        # ランダムな10文字のファイル名を作成 
        $fileName = -join ((1..10) | %{(65..90) + (97..122) | Get-Random} | foreach-object {[char]$_})
        # フルパスのファイル名を作る
        $fullFileName = $Path + "\" + $fileName + ".tmp"
        # ファイルの存在を確認
        if(Test-Path $fullFileName)
        {
            # 同一ファイル名が失敗として終了する
            return $false
        } 
        # 指定サイズでバイト配列を作成する
        $file = new-object byte[] ($sizeMB * 1024 * 1024)
        # バイト配列にランダムデータを入れる
        (new-object Random).NextBytes($file)
        # IO APIを利用してファイルに書き込む
        [IO.File]::WriteAllBytes($fullFileName, $file)
        # 100日の間でランダムな日を決める
        $day = Get-Random -maximum 100
        # ファイルの作成日付を変更する(100日の間でランダム)
        Set-ItemProperty $fullFileName -Name LastWriteTime -Value (Get-Date).addDays($day * -1) 
        return $true
    }
    catch
    {
        # サンプルスクリプトのため例外を大きく割愛している
        # 実務的なコードでは例外発生個所が分かる範囲でCatchするのが理想となる(同じ例外が発生しない粒度で)
        return $false
    }
}


# ファイルを格納するディレクトリ(今回の指定はカレントディレクトリ)
$fileDirectoryPath = pwd
# ファイルサイズ(Mバイト指定)
$size = 1
# 作成ファイル数
$num = 10
# num個のファイルが作成される
# ただし、ランダムなファイル名が被る場合、作成されるファイルが減る
@(1..$num) | Foreach{
    CreateRandomBiteArrayFile $fileDirectoryPath $size
}

シェルスクリプトマガジンvol.73 Web掲載記事まとめ

投稿日:2021.07.25 | カテゴリー: コード

004 レポート Windows 11発表
005 レポート HE C++ Transpiler
006 NEWS FLASH
008 特集1 PowerShellを学ぼう/三沢友治 コード掲載
024 特集2 JenkinsによるCI/CD/長久保篤、酒井利治 コード掲載
042 特集3 ラズパイでコロナ感染症対策/麻生二郎 コード掲載
066 特別企画 新・地球シミュレータ(ES4)への招待/今任嘉幸、上原均
074 Raspberry Piを100%活用しよう/米田聡
076 機械学習ことはじめ/川嶋宏彰 コード掲載
085 Hello Nogyo!
086 CRM/桑原滝弥、イケヤシロウ
088 レッドハットのプロダクト/宇都宮卓也
096 MySQLのチューニング/稲垣大助
104 法林浩之のFIGHTING TALKS/法林浩之
106 中小企業手作りIT化奮戦記/菅雄一
110 Pythonあれこれ/飯尾淳 コード掲載
114 香川大学SLPからお届け!/岩本和真 コード掲載
120 Bash入門/大津真
130 Techパズル/gori.sh
131 コラム「ユニケージは従来のやり方と何が違うのか(前編)」/シェル魔人

Vol.73

投稿日:2021.07.25 | カテゴリー: バックナンバー

 UNIXやLinuxなどのOSでは、コマンドや、コマンドをプログラムのように記述したシェルスクリプトを利用してさまざまな操作が可能です。実は、Windowsでもそのようなコマンドやシェルスクリプトに相当する仕組みが「PowerShell」として用意されています。特集1では、このPowerShellを使い始めたい人向けに、PowerShellの基本操作や構文、使いこなしなどを分かりやすく解説しています。
 特集2では、「Jenkins」というオープンソースソフトウエアを紹介しています。Jenkinsは、CI(継続的インテクレーション/CD(継続的デリバリ)を実現するツールであり、アプリやシステムの開発におけるコード管理、ビルド、テスト、デプロイ(配備)などを自動化します。開発者の負担を減らし、開発効率を上げるのにはとても最適です。
 特集3では、小型コンピュータボード「Raspberry Pi」(ラズパイ)と電子回路を組み合わせて新型コロナ感染症対策を支援する四つのシステムを構築しています。電子回路を初めて触る人でも簡単に作成できますので、ぜひチャレンジしてみてください。
 特別企画では、2021年3月に稼働を開始した、海洋研究開発機構が運用するスーパーコンピュータ「地球シミュレータ」の第4世代を紹介しています。地球シミュレータがどのようなもので、何に利用されていて、どのような仕組みで動作しているのかを知るにはよい記事です。
 そのほか、2021年内の提供されるWindows 11をラズパイで試すレポート、入門者や初心者向けの機械学習連載など、面白い記事満載です。今回も読み応え十分のシェルスクリプトマガジン Vol.73。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

Vol.73 補足情報

投稿日:2021.07.25 | カテゴリー: コード

特集1 PowerShellを学ぼう

pp.18-21の図33~図34のキャプション内容に誤りがありました。キャプションの内容が一つずつずれています。正しくは、以下の通りです。お詫びして訂正いたします。

図33 テーマ「powerlevel10k_rainbow」を適用した画面
図34 フォントのインストール
図35 インストールしたフォントをPowerShellに適用する
図36 プロファイルの配置を確認する
図37 Windows PowerShell ISEの起動
図38 ISEの初期画面
図39 インテリセンスにより候補を表示する
図40 コマンド アドオンからコマンドレットを調べる
図41 コマンド アドオンからコマンドレットを選択したら、パラメータを付けてそのまま実行できる
図42 ISEのデバックメニュー
図43 ブレークポイントは茶色で表示される
図44 スニペットを表示して挿入できる
図45 ISEが意図しない状態で停止した場合に表示されるダイアログ
図46 復元されたスクリプトはタブに「回復済み」と表示される
図47 時刻を取得するスクリプト(writeTime.ps1)

p.22の「図48 ランダムな名前、ランダムな作成日でファイルを作成するスクリプト(CreateRandomFiles.ps1)」の最終行として「}」が抜けていました。 お詫びして訂正いたします。

特集3  ラズパイでコロナ感染症対策

p.59の「図2 pirm_sensor.pyのソースコード」の先頭にある「1 !/usr/bin/env python3」は「2 #!/usr/bin/env python3」の誤りです。お詫びして訂正します。

コラム「ユニケージは従来のやり方と何が違うのか(前編)」

2021年6月号(Vol.72)と同じ内容でした。2021年8月号(Vol.73)のコラムはこちらから閲覧できます(Kindle版やPDF版は反映済みです)。次号となる2021年10月号(Vol.74)にも掲載する予定です。お詫びして訂正します。

情報は随時更新致します。

  • shell-mag ブログの 2021年7月 のアーカイブを表示しています。

  • -->