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

test

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

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

著者:増田 嶺

 前回に引き続き、SLPで開発したリズムゲームを紹介します。前回はゲームで使用する譜面作成ツールについて解説しました。今回は、ゲーム本体について解説します。ゲーム本体は「Visual Studio Code」(VSCode)で開発します。

図5 「js/game/singleNote.js」ファイルに記述するコード

class SingleNote {
  constructor(speed, reachTime) {
    this.height = note.height;
    this.width = note.width;
    this.frameColor = 'rgb(' + note.frameColor + ')'; // 枠の色
    this.bodyColor = 'rgba(' + note.bodyColor + ')';  // 中の色
    this.this.centerY = -this.height;                 // ノーツ中心のY座標
    this.speed = speed * note.speedRatio;             // px/ms
    // ゲーム開始から判定ラインに到達するまでの時間
    this.reachTime = reachTime + note.delay; 
    // canvasに入る時間
    this.appearTime = this.reachTime -
                      (JUDGE_LINE.centerY + this.height / 2) / this.speed;           
    // canvasから出る時間
    this.hideTime = this.reachTime + 
                    (CANVAS_H - JUDGE_LINE.centerY + this.height / 2) / 
                    this.speed;
    this.act = true;   // 自身がヒット処理対象かどうか
    this.show = false; // 自身がcanvasに描画されるかどうか
  }
}

図6 「js/game/backLane.js」ファイルに追加するコード

(略)
  generateNote(data) {
    this.note = data.map((val) => new SingleNote(val[2], val[3]));
  }
(略)

図7 「js/game/singleNote.js」ファイルに追加するコード

class SingleNote {
  constructor(speed, reachTime) {
(略)
  }
  draw(x) {
    if (this.show) {
      this.centerY = JUDGE_LINE.centerY - 
                     (this.reachTime - time.elapsed) * this.speed;
      CTX.fillStyle = this.bodyColor;
      CTX.fillRect(x, this.centerY - this.height / 2, 
                   this.width, this.height);
      CTX.strokeStyle = this.frameColor;
      CTX.strokeRect(
        x + LINE_WIDTH / 2,
        this.centerY - this.height / 2 + LINE_WIDTH / 2,
        this.width - LINE_WIDTH,
        this.height - LINE_WIDTH
      );
    }
  }
}

図8 「js/game/backLane.js」ファイルに追加するコード

(略)
  drawNote() {
    this.note.forEach(val => val.draw(this.x));
  }
(略)

図9 「js/game/singleNote.js」ファイルに追加するコード

class SingleNote {
  constructor(speed, reachTime) {
(略)
  }
  draw(x) {
(略)
  }
  update() {
    this.updateShow();
  }
  updateShow() {
    if (this.act || this.show) {
      if (this.appearTime <= time.elapsed && 
          time.elapsed <= this.hideTime) {
        this.show = true;
      } else {
        this.show = false;
      }
    }
  }
}

図11 「js/game/backLane.js」ファイルに追加するコード

(略)
  update() {
    this.note.forEach(val => val.update());
  }
(略)

図13 「js/game/backLane.js」ファイルに追加するコード

(略)
  judge() {
    calcElapsedTime(); // 経過時間を更新
    // ヒットしているノーツを抽出
    const TARGET = this.note.filter(val => val.checkHit(note.hitRange[3])); 
    // TARGETの先頭から処理、先頭ノーツのグレードがBadだった場合のみ
    // 二つ目以降のノーツを処理し、それらのノーツがBadだった場合は中断
    const GRADE = [3]
    for (let i = 0; TARGET[i] && GRADE[0] === 3; i++) {
      GRADE[i] = TARGET[i].getGrade();
      // 二つ目以降のノーツがBadの場合はそこで中断
      if (i > 0 && GRADE[i] === 3) {  
        break;
      }
      JUDGE_LINE.setGrade(GRADE[i]); // ノーツヒットのグレードを描画
      TARGET[i].close();             // 判定済みのノーツ処理を停止
    }
  }
(略)

図15 「js/game/singleNote.js」ファイルに追加するコード

class SingleNote {
(略)
  updateShow() {
(略)
  }
  checkHit(range) {
    if (this.act && 
        Math.abs(time.elapsed - this.reachTime) <= range) {
      return true;
    } else {
      return false;
    }
  }
  getGrade() {
    let grade = 3;
    for (let i = 2; i >= 0; i--) {
      if (this.checkHit(note.hitRange[i])) {
        grade = i;
      }
    }
    return grade;
  }
}

図16 「js/game/singleNote.js」ファイルに追加するコード

class SingleNote {
(略)
  update() {
    this.updateShow();
    if (!this.act) {
      return;
    }
    // 判定ラインから自身の判定ゾーンが
    // 過ぎた時点で処理
    if (this.reachTime < time.elapsed &&
        !this.checkHit(note.hitRange[3])) {
      JUDGE_LINE.setGrade(3);
      this.act = false;
    }
  }
(略)
  close() {
    this.act = false;
    this.show = false;
  }
}

図18 「js/game/gameScoreManager.js」ファイルに追加するコード

(略)
  calcScore(grade) {
    switch (grade) {
      case 0:
        this.point += 100 + this.combo;
        this.perfect++;
        this.combo++;
        break;
      case 1:
        this.point += 80 + this.combo * 0.8;
        this.great++;
        this.combo++;
        break;
      case 2:
        this.point += 50 + this.combo * 0.5;
        this.good++;
        this.combo++;
        break;
      case 3:
        this.bad++;
        this.combo = 0;
        break;
    }
    if (this.maxCombo < this.combo) {
      this.maxCombo = this.combo;
    }
  }
(略)

図19 「js/game/backLane.js」ファイルに追加するコード

(略)
  judge() {
(略)
      if (GRADE[i] < 3) {
        // Bad以外の判定ならヒットSEを鳴らす
        sound.playSE(sound.seList[0]);   
      } else {
        sound.playSE(sound.seList[1]); // Bad判定ならバッドSEを鳴らす
      }
      gameScore.calcScore(GRADE[i]); // スコアを更新
      JUDGE_LINE.setGrade(GRADE[i]); // ノーツヒットのグレードを描画
      TARGET[i].close();             // 判定済みのノーツ処理を停止
    }
  }
(略)

図20 「js/game/singleNote.js」ファイルに追加するコード

class SingleNote {
(略)
  update() {
(略)
    if (this.reachTime < time.elapsed &&
        !this.checkHit(note.hitRange[3])) {
      gameScore.calcScore(3);
      JUDGE_LINE.setGrade(3);
      this.act = false;
    }
  }
(略)
}

図22 「js/game/backLane.js」ファイルに追加するコード

(略)
  draw() {
(略)
    if (inputKey.status[this.key]) {
      const GRAD = CTX.createLinearGradient(this.x, 
                     JUDGE_LINE.centerY, this.x, CANVAS_H / 3);
      GRAD.addColorStop(0.0, 'rgba(' + this.actColor + ', 0.6)');
      GRAD.addColorStop(1.0, 'rgba(' + this.actColor + ', 0)');
      CTX.fillStyle = GRAD;
      CTX.fillRect(this.x, 0, this.width, JUDGE_LINE.centerY);
    }
  }
(略)

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

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

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第4回は、フラクタル図形の一種である「ヒルベルト曲線」を描く方法を解説します。

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

図1 1次のヒルベルト曲線を描くPythonコード

#!/usr/bin/env python

from turtle import *
SIZE = 300
p = [[-0.5, 0.5], [-0.5, -0.5], [0.5, -0.5], [0.5, 0.5]]
def screen_pos(x):
  return (x[0]*SIZE, x[1]*SIZE)
setup(width = 2*SIZE, height = 2*SIZE)
color('red')
width(5)
hideturtle()
for x in p:
  goto(screen_pos(x))
mainloop()

図4 1 次のヒルベルト曲線を描くPythonコードの改良版

#!/usr/bin/env python

from turtle import *
SIZE = 300
penup_flag = True
p = [[-0.5, 0.5], [-0.5, -0.5], [0.5, -0.5], [0.5, 0.5]]
def screen_pos(x):
  return (x[0]*SIZE, x[1]*SIZE)
setup(width = 2*SIZE, height = 2*SIZE)
color('red')
width(5)
hideturtle()
penup()
for x in p:
  goto(screen_pos(x))
  if penup_flag:
    pendown()
    penup_flag = False
onkey(exit, 'q')
listen()
mainloop()

図7 2 次のヒルベルト曲線を描くPythonコード

#!/usr/bin/env python

from turtle import *
SIZE = 300
penup_flag = True
tm = [
  [ [0.0, -0.5, -0.5], [-0.5, 0.0,  0.5], [0.0, 0.0, 1.0] ],
  [ [0.5,  0.0, -0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ],
  [ [0.5,  0.0,  0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ],
  [ [0.0,  0.5,  0.5], [ 0.5, 0.0,  0.5], [0.0, 0.0, 1.0] ]
]
e = [ [ 1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0] ]
p = [ [-0.5, 0.5], [-0.5, -0.5], [0.5, -0.5], [0.5, 0.5] ]
def affine_transform(m, p):
  return [ m[0][0] * p[0] + m[0][1] * p[1] + m[0][2],
           m[1][0] * p[0] + m[1][1] * p[1] + m[1][2] ]
def mat_mul(m0, m1):
  return [ [m0[0][0]*m1[0][0]+m0[0][1]*m1[1][0]+m0[0][2]*m1[2][0],
            m0[0][0]*m1[0][1]+m0[0][1]*m1[1][1]+m0[0][2]*m1[2][1],
            m0[0][0]*m1[0][2]+m0[0][1]*m1[1][2]+m0[0][2]*m1[2][2]],
           [m0[1][0]*m1[0][0]+m0[1][1]*m1[1][0]+m0[1][2]*m1[2][0],
            m0[1][0]*m1[0][1]+m0[1][1]*m1[1][1]+m0[1][2]*m1[2][1],
            m0[1][0]*m1[0][2]+m0[1][1]*m1[1][2]+m0[1][2]*m1[2][2]],
           [m0[2][0]*m1[0][0]+m0[2][1]*m1[1][0]+m0[2][2]*m1[2][0],
            m0[2][0]*m1[0][1]+m0[2][1]*m1[1][1]+m0[2][2]*m1[2][1],
            m0[2][0]*m1[0][2]+m0[2][1]*m1[1][2]+m0[2][2]*m1[2][2]] ]
def screen_pos(x):
  return (x[0]*SIZE, x[1]*SIZE)
setup(width = 2*SIZE, height = 2*SIZE)
color('red')
width(5)
hideturtle()
penup()
for m in tm:
  p2 = [ affine_transform(mat_mul(e, m), x) for x in p ]
  for x in p2: 
    goto(screen_pos(x))
    if penup_flag:
      pendown()
      penup_flag = False
onkey(exit, 'q')
listen()
mainloop()

図9 書き換えた2 次のヒルベルト曲線を描くPythonコード

#!/usr/bin/env python

from turtle import *
import numpy as np
SIZE = 300
penup_flag = True
tm = [ np.matrix(x) for x in [
  [ [0.0, -0.5, -0.5], [-0.5, 0.0,  0.5], [0.0, 0.0, 1.0] ],
  [ [0.5,  0.0, -0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ],
  [ [0.5,  0.0,  0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ],
  [ [0.0,  0.5,  0.5], [ 0.5, 0.0,  0.5], [0.0, 0.0, 1.0] ] ]
]
e = np.eye(3) # 3次の単位行列
p = [ np.matrix(x).T for x in [
      [-0.5,  0.5, 1.0], [-0.5, -0.5, 1.0],
      [ 0.5, -0.5, 1.0], [ 0.5,  0.5, 1.0] ] ]
def screen_pos(x):
  return (x[0,0]*SIZE, x[1,0]*SIZE)
setup(width = 2*SIZE, height = 2*SIZE)
color('red', 'yellow')
width(5)
hideturtle()
penup()
for m in tm:
  for x in [ e * m * x for x in p ]:
    goto(screen_pos(x))
    if penup_flag:
      pendown()
      penup_flag = False
onkey(exit, 'q')
listen()
mainloop()

図10 図9 の赤枠部分を置き換えるPythonコード

def hilbert(n, m):
  if n > 1:
    for m_tm in tm: hilbert(n-1, m * m_tm)
  else:
    for x in [ m * x.T for x in p ]:
      goto(screen_pos(x))
      global penup_flag
      if penup_flag:
        pendown()
        penup_flag = False
hilbert(3, e)

図13 1 ~ 7 次のヒルベルト曲線を重ねて描画するPythonコード

#!/usr/bin/env python

from turtle import *
import numpy as np
SIZE = 300
MARGIN = 50
penup_flag = True
settings = [
  { 'color': 'forestgreen', 'width': 5 },
  { 'color': 'navy',        'width': 4 },
  { 'color': 'purple',      'width': 3 },
  { 'color': 'brown',       'width': 2 },
  { 'color': 'red',         'width': 2 },
  { 'color': 'orange',      'width': 1 },
  { 'color': 'yellowgreen', 'width': 1 }
]
tm = [ np.matrix(x) for x in [
  [ [0.0, -0.5, -0.5], [-0.5, 0.0,  0.5], [0.0, 0.0, 1.0] ],
  [ [0.5,  0.0, -0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ],
  [ [0.5,  0.0,  0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ],
  [ [0.0,  0.5,  0.5], [ 0.5, 0.0,  0.5], [0.0, 0.0, 1.0] ] ] ]
e = np.eye(3)
p = [ np.matrix(x).T for x in [
      [-0.5,  0.5, 1.0], [-0.5, -0.5, 1.0],
      [ 0.5, -0.5, 1.0], [ 0.5,  0.5, 1.0] ] ]
def draw_line_to(x):
  goto(x[0,0]*SIZE, x[1,0]*SIZE)
  global penup_flag
  if penup_flag:
    pendown()
    penup_flag = False
def reset():
  penup()
  global penup_flag
  penup_flag = True
def hilbert(n, m):
  if n > 1:
    for m_ in tm: hilbert(n-1, m * m_)
  else:
    for q in [ m * p_ for p_ in p ]: draw_line_to(q)
setup(width = 2*SIZE+MARGIN, height = 2*SIZE+MARGIN)
hideturtle()
for i in range(len(settings)):
  reset()
  color(settings[i]['color'], 'white')
  width(settings[i]['width'])
  hilbert(i+1, e)
onkey(exit, 'q')
listen()
mainloop()

機械学習ことはじめ

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

著者:川嶋 宏彰

本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。今回は、入力から予測値を出力する「回帰モデル」と、その教師あり学習について紹介します。

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

図5 気温に対するチョコレート菓子支出金額の散布図をプロットするPython コード

import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib  # pip install japanize-matplotlibが必要
import re
plt.rcParams['font.size'] = 14
# 家計調査データの読み込み
beg_col = 5  # 品目の始まる列
end_col = 6  # 品目の終わる列
usecols = [3] + list(range(beg_col, end_col + 1))  # 用いる列リスト
# 年月の列をインデックスとするデータフレームに
df_estat = pd.read_csv('estat.csv', header=0, encoding='cp932',
    usecols=usecols, index_col=0, parse_dates=True, 
    date_parser=lambda x: pd.to_datetime(x, format='%Y年%m月'))
# 各品目の支出金額を各月の日数で割る(★)
for col in df_estat:
    new_col = re.sub(r'.* (.*)【円】', r'\1 (円/日)', col)
    df_estat[new_col] = df_estat[col] / df_estat.index.days_in_month
# 気象庁の気温データの読み込み
df_jma = pd.read_csv('jma.csv', skiprows=5, header=None, 
    encoding='cp932',
    usecols=[0, 1], index_col=0, parse_dates=True)
hitemp_col = '平均最高気温 (℃)'  # 気温の列名を変える
df_jma.columns = [hitemp_col]
# データフレームの結合
df = pd.merge(df_estat, df_jma, left_index=True, right_index=True)
df.index.name = 'y/m'  # インデックスの名前を変更
# 年と月の列を追加(後で利用)
df['year'] = df_estat.index.year
df['month'] = df_estat.index.month
print('df (merged):\n', df)
# 散布図をプロット
target_col = 'チョコレート菓子 (円/日)'
df.plot(kind='scatter', x=hitemp_col, y=target_col, figsize=(5, 5))
plt.show()

図7 チョコレート菓子支出金額の単回帰の結果を示すPythonコード

from sklearn.linear_model import LinearRegression

reg = LinearRegression()  # モデルを準備
# データを準備
X = df[[hitemp_col]].values  # 平均最高気温(2次元配列)。「.values」は省略可
y = df[target_col].values  # 支出金額(1次元配列)。「.values」は省略可
reg.fit(X, y)  # 学習(データに直線を当てはめる)
print('intercept:', reg.intercept_)  # 切片
print('coef:', reg.coef_[0])  # 直線の傾き
print('R2:', reg.score(X, y))  # 決定係数
df.plot(kind='scatter', x=hitemp_col, y=target_col, figsize=(5, 5))
plt.plot(X, reg.predict(X), 'r')
plt.show()

図10 チョコレート菓子支出金額の単回帰における残差をプロットする
Pythonコード

fig = plt.figure(figsize=(5, 5))
plt.scatter(X, y - reg.predict(X))  # 残差をプロット
plt.axhline(y=0, c='r', linestyle='-')  # 水平線をプロット
plt.ylim([-3, 3])
plt.xlabel(hitemp_col)
plt.ylabel('残差')
plt.show()

図13 年を説明変数とした単回帰のためのPythonコード

from matplotlib import cm
from matplotlib.colors import ListedColormap

def ListedGradation(cmapname, num=10):
    """ LinearSegmentedColormap から ListedColormap を作成 """
    color_list = []
    cmap = cm.get_cmap(cmapname)
    for i in range(num):
        color = list(cmap(i/num))
        color_list.append(color)
    return ListedColormap(color_list)
# 年ごとに散布図の色を変える
df.plot(kind='scatter', x=hitemp_col, y=target_col, c=df['year'],
    cmap=ListedGradation('cividis', 11), sharex=False, figsize=(6, 5))
plt.show()
# 「年」を説明変数とする単回帰
reg_year = LinearRegression()
X = df[['year']].values  # 年
y = df[target_col].values  # 支出金額
reg_year.fit(X, y)  # 学習(データに直線を当てはめる)
print('intercept:', reg_year.intercept_)  # 切片
print('coef:', reg_year.coef_[0])  # 直線の傾き
print('R2:', reg_year.score(X, y))  # 決定係数
df.plot(kind='scatter', x='year', y=target_col, figsize=(5, 5))
plt.plot(X[:, 0], reg_year.predict(X), 'r')
plt.show()

図15 平均最高気温と年の二つを説明変数とした重回帰のためのPythonコード

from mpl_toolkits.mplot3d import Axes3D
import numpy as np

reg_multi = LinearRegression()
X = df[[hitemp_col, 'year']].values  # 二つの説明変数(2次元配列)
y = df[target_col].values  # 支出金額(1次元配列)
reg_multi.fit(X, y)  # 学習(データに平面を当てはめる)
print('intercept:', reg_multi.intercept_)  # 切片
print('coef:', reg_multi.coef_)  # 平面の傾き
print('R2:', reg_multi.score(X, y))  # 決定係数
# 3次元散布図のプロット
def scatter3d(X, y, xlabels, ylabel):
    fig = plt.figure(figsize=(9, 6))
    ax = fig.add_subplot(111, projection='3d')
    ax.scatter(X[:, 0], X[:, 1], y)
    ax.set_xlabel(xlabels[0])
    ax.set_ylabel(xlabels[1])
    ax.set_zlabel(ylabel)
    ax.view_init(elev=30, azim=-60)
    return ax
scatter3d(X, y, [hitemp_col, '年'], target_col)
plt.show()
# 平面(wireframe)を合わせてプロット
ax = scatter3d(X, y, [hitemp_col, '年'], target_col)
xlim = ax.get_xlim()
ylim = ax.get_ylim()
mesh_x = np.meshgrid(np.arange(*xlim, (xlim[1] - xlim[0]) / 20),
    np.arange(*ylim, (ylim[1] - ylim[0]) / 20))
mesh_y = reg_multi.intercept_ + reg_multi.coef_[0] * mesh_x[0] \ 
    + reg_multi.coef_[1] * mesh_x[1]  # 回帰式より予測
ax.plot_wireframe(*mesh_x, mesh_y, color='r', linewidth=0.5)
plt.show()

図18 平均最高気温と年の二つを説明変数とした重回帰の残差をプロットするPythonコード

fig = plt.figure(figsize=(5, 5))
plt.scatter(X[:, 0], y - reg_multi.predict(X)) # 残差をプロット
plt.axhline(y=0, c='r', linestyle='-') # 水平線をプロット
plt.ylim([-3, 3])plt.xlabel(hitemp_col)
plt.ylabel('残差')
plt.show()

図20 平均最高気温とアイスクリーム支出金額の散布図をプロットするPythonコード

target_col = 'アイスクリーム・シャーベット (円/日)'  # 目的変数を変える
df.plot(kind='scatter', x=hitemp_col, y=target_col, figsize=(5, 5))
plt.show()

図22 多項式回帰モデルを使ったPythonコード

from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import Ridge

def poly_fit(df, xlabel, ylabel, degree=2, ylim=None):
    """ 多項式回帰 """
    y = df[ylabel]
    pf = PolynomialFeatures(degree=degree, include_bias=False)
    Xpf = pf.fit_transform(df[[xlabel]])
    reg_pf = LinearRegression(normalize=True)
    # reg_pf = Ridge(normalize=True, alpha=0.01)  # (★)
    reg_pf.fit(Xpf, y)
    print('intercept:', reg_pf.intercept_)  # 切片
    print('coef:', reg_pf.coef_)  # 直線の傾き
    print('R2:', reg_pf.score(Xpf, y))  # 決定係数
    fig = plt.figure(figsize=(5, 5))
    plt.plot(X, y, '.', ms=8)
    plt.ylim(ylim)
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    # x軸に等間隔に点を配置して回帰曲線を描く
    xlim = plt.xlim()
    X_lin = np.arange(*xlim, (xlim[1] - xlim[0])/50).reshape(-1, 1)
    Xpf_lin = pf.fit_transform(X_lin)
    ypf_pred = reg_pf.predict(Xpf_lin)
    plt.plot(X_lin, ypf_pred, color='r')
X = df[[hitemp_col]].values  # 平均最高気温
y = df[target_col].values  # 支出金額
for p in [1, 2, 3, 20]:  # 多項式の次数
    poly_fit(df, hitemp_col, target_col, degree=p)
    plt.title(f'p = {p}')
    plt.show()

Raspberry Piを100%活用しよう(Vol.74掲載)

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

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第7回は、ボリュームスイッチのような操作を実現する拡張基板を扱います。

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

図4 サンプルプログラム(sample.py)

import smbus
import struct
import time

ADRSZRE_ADDR = 0x54
VALUE_HI     = 0x00
RESET        = 0x02

STEPS = 80  # 1回転=80カウント

bus = smbus.SMBus(1)
degree = 0.0

try:
    while True:
        temp = bus.read_word_data(ADRSZRE_ADDR, VALUE_HI)
        value = struct.unpack(">H", struct.pack("<H", temp))[0]
        if value == 0:
            bus.write_byte_data(ADRSZRE_ADDR, RESET, True) # Zero Reset
            
        degree =  (value % STEPS) *  360.0 / STEPS
        print("%.1f" % degree, end='\r', flush=True)
        time.sleep(0.1)

except KeyboardInterrupt:
    pass

特集1 本格的なホームサーバーを構築(Vol.74記載)

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

著者:麻生 二郎

小型コンピュータボードの最上位モデルである「Raspberry Pi 4 Model B」の4G/8Gバイト版と、人気のLinuxディストリビューション「Ubuntu」のサーバー版を組み合わせて、本格的なサーバーを構築しましょう。本特集では、テレワークに役立つサーバーアプリの導入方法を紹介します。

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

図11 Sambaをインストールして実行するシェルスクリプト(samba_install.sh)

#!/bin/sh

##初期設定
WORKGROUP_NAME="SHMAG"
SHARE_NAME="share"
SHARE_FOLDER="/var/share"

##Sambaのインストール
sudo apt update
sudo apt -y install samba

##旧設定バックアップ
mkdir -p ~/old_settings
sudo mv /etc/samba/smb.conf ~/old_settings/.

##Sambaの共有設定
cat << EOF | sudo tee /etc/samba/smb.conf > /dev/null
[global]
workgroup = workgroup_name
dos charset = CP932
unix charset = UTF8

[share_name]
comment = Raspberry Pi share
path = share_folder
browsable = yes
writable = yes
create mask = 0777
directory mask = 0777
EOF
sudo sed -i -e "s%workgroup_name%'$WORKGROUP_NAME'%" /etc/samba/smb.conf
sudo sed -i -e "s%share_name%'$SHARE_NAME'%" /etc/samba/smb.conf
sudo sed -i -e "s%share_folder%'$SHARE_FOLDER'%" /etc/samba/smb.conf

##共有フォルダ作成
sudo mkdir -p $SHARE_FOLDER
sudo chmod 777 $SHARE_FOLDER

##Sambaの設定反映
sudo systemctl restart smbd
sudo systemctl restart nmbd

図14 MediaWikiを導入するシェルスクリプト(mediawiki_install.sh)

#!/bin/sh

##初期設定
DB_PASSWORD="shmag"

##必要なパッケージのインストール
sudo apt update
sudo apt -y install mediawiki imagemagick

##データベースの作成
sudo mysql -e "create database my_wiki;"
sudo mysql -e "create user 'mediawiki'@'%' identified by '$DB_PASSWORD';"
sudo mysql -e "grant all privileges on my_wiki.* to 'mediawiki'@'%';"

図25 ownCloudをインストールするシェルスクリプト(owncloud_install.sh)

#!/bin/sh

##初期設定
DB_PASSWORD="shmag"
ADMIN_NAME="admin"
ADMIN_PASSWORD="admin"
OWNCLOUD_FILE="owncloud-10.8.0.tar.bz2"

##ヘルパースクリプト「occ」の作成
cat << EOM | sudo tee /usr/local/bin/occ
#! /bin/bash
cd /var/www/owncloud
sudo -E -u www-data /usr/bin/php /var/www/owncloud/occ "\$@"
EOM
sudo chmod +x /usr/local/bin/occ

##必要・推奨パッケージのインストール
sudo apt update
sudo apt -y install apache2 libapache2-mod-php mysql-server php-imagick php-common php-curl php-gd php-imap php-intl php-json php-mbstring php-mysql php-ssh2 php-xml php-zip php-apcu php-redis redis-server
sudo apt -y install jq inetutils-ping

##ownCloudの設定ファイル作成
sudo sed -i "s%html%owncloud%" /etc/apache2/sites-available/000-default.conf
cat << EOM | sudo tee /etc/apache2/sites-available/owncloud.conf
Alias /owncloud "/var/www/owncloud/"

<Directory /var/www/owncloud/>
  Options +FollowSymlinks
  AllowOverride All

 <IfModule mod_dav.c>
  Dav off
 </IfModule>

 SetEnv HOME /var/www/owncloud
 SetEnv HTTP_HOME /var/www/owncloud
</Directory>
EOM

##Apache HTTP Serverの設定
sudo a2ensite owncloud.conf
sudo a2enmod dir env headers mime rewrite setenvif
sudo systemctl restart apache2

##ownCloudの入手と展開
wget https://download.owncloud.org/community/$OWNCLOUD_FILE
tar -jxf $OWNCLOUD_FILE
sudo mv owncloud /var/www/.
sudo chown -R www-data /var/www/owncloud

##データベースの作成
sudo mysql -e "create database owncloud;"
sudo mysql -e "create user 'owncloud'@'%' identified by '$DB_PASSWORD';"
sudo mysql -e "grant all privileges on owncloud.* to 'owncloud'@'%';"

##ownCloudのインストール
echo "しばらくおまちください。"
occ maintenance:install --database "mysql" --database-name "owncloud" --database-user "owncloud" --database-pass $DB_PASSWORD --admin-user "$ADMIN_NAME" --admin-pass "$ADMIN_PASSWORD"
myip=$(hostname -I|cut -f1 -d ' ')
occ config:system:set trusted_domains 1 --value="$myip"

##バックグラウンド処理の設定
occ background:cron
sudo sh -c 'echo "*/15  *  *  *  * /var/www/owncloud/occ system:cron" > /var/spool/cron/crontabs/www-data'
sudo chown www-data.crontab /var/spool/cron/crontabs/www-data
sudo chmod 0600 /var/spool/cron/crontabs/www-data

##キャッシュとロックファイルの作成
occ config:system:set memcache.local --value '\OC\Memcache\APCu'
occ config:system:set memcache.locking --value '\OC\Memcache\Redis'
occ config:system:set redis --value '{"host": "127.0.0.1", "port": "6379"}' --type json

##ログローテーションの設定
cat << EOM | sudo tee /etc/logrotate.d/owncloud
/var/www/owncloud/data/owncloud.log {
  size 10M
  rotate 12
  copytruncate
  missingok
  compress
  compresscmd /bin/gzip
}
EOM

図29 RainLoopをインストールするシェルスクリプト(rainloop_install.sh)

#!/bin/sh

##初期設定
SIZE="100M"
DB_PASSWORD="admin"

##必要なパッケージの導入
sudo apt update
sudo apt -y install apache2 php php-curl php-xml php-mysql mysql-server unzip

##RainLoop Webmailの導入
wget https://www.rainloop.net/repository/webmail/rainloop-community-latest.zip
sudo mkdir -p /var/www/html/rainloop
sudo unzip rainloop-community-latest.zip -d /var/www/html/rainloop/.
sudo chown -R www-data /var/www/html/rainloop
cat << EOF | sudo tee /etc/apache2/sites-available/rainloop.conf >> /dev/null
<Directory /var/www/html/rainloop/data>
    Require all denied
</Directory>
EOF
sudo a2ensite rainloop

##連絡先データベースの作成とApacheに反映
sudo mysql -e "create database rainloop;"
sudo mysql -e "create user 'rainloop'@'%' identified by '$DB_PASSWORD';"
sudo mysql -e "grant all privileges on rainloop.* to 'rainloop'@'%';"

##添付ファイルサイズの拡大
sudo sed -i -e "s%upload_max_filesize = 2M%upload_max_filesize = '$SIZE'%" /etc/php/7.4/apache2/php.ini
sudo sed -i -e "s%post_max_size = 8M%post_max_size = '$SIZE'%" /etc/php/7.4/apache2/php.ini
sudo systemctl restart apache2

図36 Mattermostをインストールするシェルスクリプト(mattermost_install.sh)

#!/bin/sh

##初期設定
DB_PASSWORD="shmag"
MATTERMOST="v5.39.0/mattermost-v5.39.0-linux-arm64.tar.gz"
SITE_URL="http://192.168.11.100/"

##データベースの作成
sudo apt update
sudo apt -y install mysql-server
sudo mysql -uroot -e "create user 'mmuser'@'%' identified by '$DB_PASSWORD';"
sudo mysql -uroot -e "create database mattermost;"
sudo mysql -uroot -e "grant all privileges on mattermost.* to 'mmuser'@'%';"

##mattermostの入手と展開
wget https://github.com/SmartHoneybee/ubiquitous-memory/releases/download/$MATTERMOST
tar -xvzf mattermost*.gz
sudo mv mattermost /opt
sudo mkdir /opt/mattermost/data
sudo useradd --system --user-group mattermost
sudo chown -R mattermost:mattermost /opt/mattermost
sudo chmod -R g+w /opt/mattermost

##設定ファイルの書き換え
sudo sed -i -e 's%"postgres"%"mysql"%' /opt/mattermost/config/config.json
sudo sed -i -e 's%postgres://mmuser:mostest@localhost/mattermost_test?sslmode=disable\\u0026connect_timeout=10%mmuser:'$DB_PASSWORD'@tcp(localhost:3306)/mattermost?charset=utf8mb4,utf8\&writeTimeout=30s%' /opt/mattermost/config/config.json
sudo sed -i -e 's%"SiteURL": "",%"SiteURL": "'$SITE_URL'",%' /opt/mattermost/config/config.json

##起動・停止ファイルの作成
cat << EOF | sudo tee /lib/systemd/system/mattermost.service > /dev/null
[Unit]
Description=Mattermost
After=network.target
After=mysql.service
BindsTo=mysql.service

[Service]
Type=notify
ExecStart=/opt/mattermost/bin/mattermost
TimeoutStartSec=3600
KillMode=mixed
Restart=always
RestartSec=10
WorkingDirectory=/opt/mattermost
User=mattermost
Group=mattermost
LimitNOFILE=49152

[Install]
WantedBy=mysql.service
EOF

##mattermostの起動と自動起動設定
sudo systemctl daemon-reload
sudo systemctl start mattermost
sudo systemctl enable mattermost

図43 MosP勤怠管理をインストールするシェルスクリプト(mosp_install.sh)

#!/bin/sh

##初期設定
MOSP="time4.war"

##必要なパッケージのインストール
sudo apt update
sudo apt -y install tomcat9 tomcat9-admin postgresql
##Mosp勤怠管理の導入
sudo chown tomcat:tomcat $MOSP
sudo chmod 775 $MOSP
sudo mv $MOSP /var/lib/tomcat9/webapps/.

##データベース管理者に切り替え
sudo -i -u postgres

##初期設定
DBADMIN_PASSWORD="shmag"

##管理者パスワード設定
psql -c "alter role postgres with password '$DBADMIN_PASSWORD';"

図A3 ラズパイサーバーの初期設定(ubuntu_init1.sh)

#!/bin/sh

##日本のタイムゾーン設定
sudo timedatectl set-timezone Asia/Tokyo

##全ソフトウエア更新
sudo apt update
sudo apt -y upgrade
sudo apt -y autoremove
sudo apt clean

##ファームウエアアップデート
sudo apt -y install rpi-eeprom
sudo rpi-eeprom-update

##完了後の再起動
read -p "再起動しますか [y/N]:" YN
if [ " $YN" = " y" ] || [ " $YN" = " Y" ]; then
  sudo reboot
fi

図A4 ラズパイサーバーの初期設定(ubuntu_init2.sh)

#!/bin/sh

##固定IPアドレス
IP_ADDRESS="192.168.10.100"

##旧設定バックアップ
mkdir -p ~/old_settings
sudo mv /etc/netplan/50-cloud-init.yaml ~/old_settings/.

##新ネットワーク設定作成
cat << EOF | sudo tee /etc/netplan/50-cloud-init.yaml > /dev/null
network:
  ethernets:
    eth0:
      dhcp4: false
      addresses: [ip_address/24]
      gateway4: 192.168.10.1
      nameservers:
        addresses: [8.8.8.8]
  version: 2
EOF
sudo sed -i -e "s%ip_address%$IP_ADDRESS%" /etc/netplan/50-cloud-init.yaml

##ネットワーク設定反映
sudo netplan apply

図A3 データ領域を拡張するシェルスクリプト(storage_expand.sh)

#!/bin/sh

##パーティション作成とフォーマット
sudo parted -s /dev/sda rm 1
sudo parted -s /dev/sda mklabel msdos
sudo parted -s /dev/sda mkpart primary 0% 100%
sudo mke2fs -t ext4 -F /dev/sda1

##/varディレクトリに自動マウント
sudo e2label /dev/sda1 usbssd
sudo sh -c "echo 'LABEL=usbssd /var ext4 defaults 0 0' >> /etc/fstab"

##読み書き許可と/varディレクトリコピー
sudo mount /dev/sda1 /mnt
sudo chmod 777 /mnt
sudo cp -a /var/* /mnt

##完了後の再起動
read -p "再起動しますか [y/N]:" YN
if [ " $YN" = " y" ] || [ " $YN" = " Y" ]; then
  sudo reboot
fi

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

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

004 レポート UNIX基本コマンド集の新版
005 レポート IBMのフリー日本語ソフト
006 NEWS FLASH
008 特集1 本格的なホームサーバーを構築 アプリ編/麻生二郎 コード掲載
023 コラム「ユニケージは従来のやり方と何が違うのか(前編)」/シェル魔人
024 特集2 オープンソースのJitsiで構築しよう/橋本知里
034 特集3 キーボードを自作しよう/東峰沙希
044 特別企画 MDSを始めよう!(応用編)/生駒眞知子
054 Raspberry Piを100%活用しよう/米田聡 コード掲載
057 Hello Nogyo!
058 機械学習ことはじめ/川嶋宏彰 コード掲載
068 タイ語から分かる現地生活/つぎみき
072 MySQLのチューニング/稲垣大助
080 PPAP/桑原滝弥、イケヤシロウ
082 中小企業手作りIT化奮戦記/菅雄一
086 法林浩之のFIGHTING TALKS/法林浩之
088 Pythonあれこれ/飯尾淳  コード掲載
094 香川大学SLPからお届け!/増田嶺  コード掲載
102 Bash入門/大津真
110 Techパズル/gori.sh
111 コラム「ユニケージは従来のやり方と何が違うのか(後編)」/シェル魔人

Vol.74

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

 小型コンピュータボードの最上位モデル「Raspberry Pi 4 Model B」と、人気のLinuxディストリビューション「Ubuntu」を組み合わせれば、省スペースの本格的なホームサーバーを構築できます。しかし、無料でも利用できるさまざまなクラウドサービスが存在している今、ホームサーバーで何をすればよいのでしょうか。特集1では、テレワークをテーマに自宅での職場環境を豊かにするサーバーアプリの導入方法を解説します。
 特集2では、オープンソースのWeb会議システムソフト「Jitsi」(ジッツィ)を紹介しました。新型コロナ感染症拡大によりWeb会議が普及・一般化しています。これにより、Web会議の利便性が多くの人に伝わりました。ただし、Zeem MeetingsやCisco Webex Meetings、Microsoft TeamsのようなWeb会議サービスはとても便利ですが、無料で利用する場合は制限があります。有料版は、複数の会議を同時に行う場合はその数だけ契約したくてはいけません。この機会に無料で使えるJitsiの実力を体験してみてください。
 特集3では、自作キーボードを扱いました。自宅でのテレワークが増えてノートパソコンにもつなげられるキーボードの需要は高まっています。市販のキーボードでもよいでしょうが、この機会に自分だけのキーボードを自作してみましょう。オリジナルキーボードならきーの設定をカスタマイズできるので、複数のキーを使った特別な操作も一つのキーで実現できます。
 特別企画では、オープンソースのデータベース管理システムクラウドサービス「MySQL Database Service」(MDS)が備えるインメモリークエリーアクセラレータ「HeatWave」を紹介しました。HeatWaveならオンライン分析処理(OLAP)を高速に実行できます。MDSのトライアルアカウントでも利用できますので、ぜひ試してください。
 このほかに、タイ王国独特の文化が分かる連載「タイ語から分かる現地生活」を開始しました。タイ在住の日本人が経験した、タイの不思議な文化に触れられます。現在のタイ(チェンマイ)の写真も多数掲載しています。今回も読み応え十分のシェルスクリプトマガジン Vol.74。お見逃しなく!

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

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

Vol.74 補足情報

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

訂正・補足情報はありません。
情報は随時更新致します。

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

  • -->