Pythonロボティクスコース レッスン 38
テーマ.10-1 バーコードリーダーの制作
コンピュータ上での数の表し方を学ぼう
チャプター1
このレッスンで学ぶこと
このレッスンでは、2進数や16進数などコンピュータにとって相性の良い数の表し方について学習します。また、学習したことを踏まえて、簡単なバーコードを読み取って購入する商品の精算を行えるレジシステムを制作します。
チャプター2
コンピュータに適した数の表し方
10という数字が表す数は「十」、100という数字がが表す数は「百」と、私たちが普段から使う数の表し方を「10進数」といいます。10進数は、0~9までの10種類の数字で数を表す方法です。人にとって10という区切りは、両手の指の数とも一致しているため、数を数えるときに分かりやすい表現になっていると言えます。
一方でコンピュータにとっては、10進数は非常に扱いにいく数の表し方です。その理由はコンピュータの内部でどのように計算が行われているのかを知ると理解できます。
2. 1 コンピュータの計算方法
コンピュータの計算は、中心部であるCPUで行われます。このCPUは簡単に例えると、スイッチのオン・オフを切り替えるシンプルな回路が、膨大な数集積された回路になっています。
この回路で例えると、スイッチがオフのときは発光ダイオード(LED)の両端にかかる電圧が低い状態に(LOW)にあり、スイッチがオンのときは電圧が高い状態(HIGH)にあります。コンピュータ上では、この電圧が低い状態(LOW)と高い状態(HIGH)を、デジタルの「0」と「1」に置き換えています。そして、「0」と「1」の論理演算を行えるように回路を組むことで、複雑な計算を処理できるようになっています。このように、論理演算用に組まれた回路を「論理回路」といいます。
2. 2 論理回路の種類
論理回路の基本は「AND」「OR」「NOT」の3種類です。それぞれの回路の働きは、Pythonの論理演算子(and
やor
、not
)と同じです。
■ AND回路
AND回路は、入力Aと入力Bの両方が「1(HIGH)」のときだけ、出力Yが「1(HIGH)」になる回路です。
入力A | 入力B | 出力Y |
---|---|---|
0(LOW) | 0(LOW) | 0(LOW) |
1(HIGH) | 0(LOW) | 0(LOW) |
0(LOW) | 1(HIGH) | 0(LOW) |
1(HIGH) | 1(HIGH) | 1(HIGH) |
AND回路をアナログな電気回路に例えると、下の図のように2つのスイッチを直列につないだ回路といえます。この回路では、両方のスイッチがオン(1)のときだけ、発行ダイオードが点灯(1)します。
■ OR回路
OR回路は、入力Aと入力Bのどちらか一方でも「1(HIGH)」であれば、出力Yが「1(HIGH)」になる回路です。
入力A | 入力B | 出力Y |
---|---|---|
0(LOW) | 0(LOW) | 0(LOW) |
1(HIGH) | 0(LOW) | 1(HIGH) |
0(LOW) | 1(HIGH) | 1(HIGH) |
1(HIGH) | 1(HIGH) | 1(HIGH) |
OR回路はアナログな電気回路に例えると、下の図のように2つのスイッチを並列につないだものになります。この回路では、どちらか一方でもスイッチがオン(1)のときに、発行ダイオードが点灯(1)します。
■ NOT回路
NOT回路は入力の逆を出力する回路です。
入力A | 出力Y |
---|---|
0(LOW) | 1(HIGH) |
1(HIGH) | 0(LOW) |
コンピュータは、ここまでで確認した3つの論理回路の組み合わせで数の計算を行います。しかし、「0(LOW)」と「1(HIGH)」の2つの状態しかない論理回路では、「0~9」の10種類の数字を使う10進数の計算を直接行うことはできません。そこで10進数の代わとして使われているのが「2進数」という数の表し方です。
2. 3 2進数とは
10進数の世界では、「10 + 10」を計算すると、もちろん「20」という結果になります。一方で、これから説明する2進数の世界では、「10 + 10」の計算結果は「100」です。この「100」という結果は、2進数の世界での「四」を表しています。このように表されるのは、2進数が、「0」と「1」の2つの数字だけを使って数を表現するためです。
さきほどの「10 + 10」の計算では、上位の桁どうしを足すと「2」になりますが、2進数では「2」という数字はないため、次の桁に繰り上がり、「100」になります。
■ 2進数と10進数の比較
2進数の各桁の位を10進数で表すと次のようになります。桁が1つ上がと、位の大きさは2倍になっています。
上の表を踏まえて、10進数の「0~9」を2進数に変換すると下の表のようになります。
この表と見比べて先程の2進数の計算式を、10進数の計算式に置き換えてみましょう。計算結果が正しいことがすぐに理解できます。
では、練習として、次の2進数で表した式を計算し、さらにその結果を10進数で表してみましょう。
【 問題 】
【 解答 】
※こちらも参照↓
■ Pythonでの2進数の表し方
Pythonで2進数を表すときは、先頭に0b
を付けます。b
は「2進」を英語で表した「binary」の頭文字です。
>>> print(0b1111) 15
上の実行結果からも分かるように、そのまま書くと10進数で表した数値として扱われるため、ここでは""
で囲み、文字列として定義します。
binary = "0b100"
■ 2進数から10進数への変換
文字列を2進数として解釈し、10進数の数値へ変換したいときは、int()
関数を使います。
int(文字列, 文字列が何進数で表されているのかを示す数字)
【 サンプルコード 2-3-1 】
追加【2・3行目】
binary = "0b100"
decimal = int(binary, 2)
print(decimal)
※ 10進数は英語で「decimal number」といいます。
(実行結果)
4
■ 10進数から2進数への変換
反対に、10進数の数値から2進数の文字列に変換するときは、bin()
関数を使います。
bin(10進数の数値)
【 サンプルコード 2-3-2 】
decimal = 10
binary = bin(decimal)
print(binary)
(実行結果)
0b1010
■ 論理回路での2進数の足し算
1桁の2進数の足し算は、上で説明した「AND」「OR」「NOT」の論理回路を組み合わせた下の回路で行うことができます。この論理回路は「半加算器」と呼ばれています。1桁の2進数「A」と「B」を入力すると、演算結果の1桁目の「S」と桁上がりの「C」が出力されます。
【 半加算器への入力値と出力値の関係 】
|A|B|C|S|
|:—|:—|:—|:—|
|0|0|0|0|
|0|1|0|1|
|1|0|0|1|
|1|1|1|0|
実際に、AとBにそれぞれ「1」を入力したときの、演算の過程は次のようになります。
このように、コンピュータにとって2進数は計算の都合上とても扱いやすい数の表し方です。しかし、数が大きくなると、どうしても数字の並びが長くなってしまうため、人にとっては扱いづらくなります。
そのため、人にもコンピュータにも扱いやすい数の表し方が必要です。そこで、使われているのが次に説明する「16進数」です。
2. 4 16進数とは
16進数では、数字の「0~9」に加えてアルファベットの「a~f」を使って合計16種類の文字で数を表します。4桁の「0」と「1」の数字の並びが「24 = 16」となり、全部で16通りあることから、16進数は4桁の2進数の代わりに使うことができます。
また、10進数と16進数との対応関係は次の表のようになります。
■ Pythonでの16進数の表し方
Pythonで16進数を表すときは、先頭に0x
を付けます。
>>> print(0x1f) 31
ここでも、16進数で表したことが分かるように、文字列として定義しておきましょう。
hexadecimal= "0x1f"
※ 16進数は英語で「hexadecimal number」といいます。
■ 16進数から10進数への変換
文字列を16進数として解釈し、10進数の数値へ変換したいときは、2進数のときと同じくint()
関数を使います。
【 サンプルコード 2-3-3 】
追加【2・3行目】
hexadecimal= "0x1f"
decimal = int(hexadecimal, 16) # 16進数からの変換なので第2引数は「16」
print(decimal)
(実行結果)
31
■ 10進数から16進数への変換
反対に10進数の数値から16進数の文字列へ変換するときは、hex()
関数を使います。
【 サンプルコード 2-3-4 】
decimal = 31
hexadecimal = hex(decimal)
print(hexadecimal)
(実行結果)
0x1f
16進数の他にも、「8進数」が2進数の代わりに使われることもあります。
2. 5 8進数とは
8進数では、「0~7」の8種類の数字で数を表します。「23 = 8」なので、3桁の2進数の代わりとして使えます。
また、10進数と8進数との対応関係は次の表のようになります。
■ Pythonでの8進数の表し方
Pythonで8進数を表すときは、先頭に0o
を付けます。
>>> print(0o17) 15
ここでも、8進数で表したことが分かるように、文字列として定義しておきましょう。
octal = "0o17"
※ 8進は英語で「octal」といいます。
■ 8進数から10進数への変換
文字列を8進数として解釈して、10進数の数値へ変換するときは、2進数や16進数のときと同じくint()
関数を使います。
【 サンプルコード 2-3-5 】
追加【2・3行目】
octal = "0o17"
decimal = int(octal, 8) # 8進数からの変換なので第2引数は「8」
print(decimal)
(実行結果)
15
■ 10進数から8進数への変換
反対に10進数の数値から8進数の文字列へ変換するときは、oct()
関数を使います。
【 サンプルコード 2-3-6 】
decimal = 15
octal = oct(decimal)
print(octal)
(実行結果)
0o17
2. 6 ゲームで進数間の相互変換を練習する
2進数や8進数、16進数から10進数への変換や、その逆の変換について理解を深めたいときは、以下のサンプルゲームで遊んでみましょう。
※ このゲームは次のチャプターの学習には関係ありません。時間がないときは飛ばしましょう。
【 2進数や8進数、16進数から10進数へ変換するゲームのサンプルコード 】
※start(“モード”) で開始。モードはb,o,h
import random
def start(mode="b"): # 引数modeでモードを選択してゲームを開始
if mode == "b": # 2進数モード
base = 2 # 2進
_max = 15 # 出題範囲(10進数)
elif mode == "o": # 8進数モード
base = 8 # 8進
_max = 63 # 出題範囲(10進数)
elif mode == "h": # 16進数モード
base = 16 # 16進
_max = 255 # 出題範囲(10進数)
else:
print("Please select the mode from 'b' ,'o' or 'h'.")
return
score = 0 # スコア
for _ in range(5): # 5問連続で出題
num = random.randint(0, _max) # 出題範囲から数(10進数)をランダムに選ぶ
if mode == "b":
print("binary:", bin(num)) # 選んだ数字を2進数表記に変換して表示
elif mode == "o":
print("octal:", oct(num)) # 選んだ数字を8進数表記に変換して表示
else:
print("hexadecimal:", hex(num)) # 選んだ数字を16進数表記に変換して表示
answer = input("decimal >>>") # 答えの入力を求める
if num == int(answer): # 正解の場合
print("Correct!")
score += 1
else: # 不正解の場合
print("Incorrect!")
print("A Correct answer is ", num)
print("Result:", str(score), "/5") # スコアの表示
【 10進数から2進数や8進数、16進数へ変換するゲームのサンプルコード 】
import random
def start(mode="b"): # 引数modeでモードを選択してゲームを開始
if mode == "b": # 2進数モード
base = 2 # 2進
message = "binary >>>" # 回答の入力を促すときに表示するメッセージ
_max = 15 # 出題範囲(10進数)
elif mode == "o": # 8進数モード
base = 8 # 8進
message = "octal >>>"
_max = 63
elif mode == "h": # 16進数モード
base = 16 # 16進
message = "hexadecimal >>>"
_max = 255
else:
print("Please select the mode from 'b' ,'o' or 'h'.")
return
score = 0 # スコア
for _ in range(5): # 5問連続で出題
num = random.randint(0, _max) # 出題範囲から数(10進数)をランダムに選ぶ
print("decimal:", num) # 問題を表示
answer = input(message) # 答えの入力を求める
answer = int(answer, base) # 答えを選択したモードに合わせて10進数に変換する
if num == answer: # 正解の場合
print("Correct!")
score += 1
else: # 不正解の場合
print("Incorrect!")
if mode == "b":
print("Correct ansewer:", bin(num))
elif mode == "o":
print("Correct ansewer:", oct(num))
else:
print("Correct ansewer:", hex(num))
print("Result:", str(score), "/5") # スコアの表示
チャプター3
バーコードリーダーの組み立て
レッスンの後半では、上で学習した2進数を応用して、簡単なバーコードを読み取るレジシステムの制作に取り組みます。レジシステムで使うバーコードリーダーをブロックで組み立てましょう。
【 組み立てるバーコードリーダー 】
3. 1 組み立てに必要なパーツ
【 パーツ一覧 】
- Studuino:bit×1
- ロボット拡張ユニット×1
- 電池ボックス×1
- サーボモーター×1
- 赤外線フォトリフレクタ×1
- センサー接続コード(3芯30cm)×1
- ブロック基本四角(グレー)×6
- ブロック基本四角(赤)×2
- ブロックハーフA(グレー)×2
- ブロックハーフB(グレー)×1
- ブロックハーフB(黒)×1
- ブロックハーフC(白)×7
- ブロックハーフD(白)×8
- ステー×4
- ギヤ大×1
- ラック×1
【 アーテックブロックの形状 】
3. 2 組立説明書
以下のリンク先から組立説明書を確認しましょう。
チャプター4
レジシステムのプログラムの作成
プログラムを作成する前に、組み立てたバーコードリーダーでバーコードを読み取る仕組みを確認しましょう。
4. 1 バーコードを読み取る仕組み
私たちが普段スーパーマーケットやコンビニエンスストアで買い物をする商品のパッケージには、必ずバーコードが印刷されています。
バーコードは、その下にある数字の並びを表しています。この数字の並びは、それぞれのメーカーが取得した、製品に対する固有の(世界で唯一の)番号です。お店は商品の価格や在庫数、販売数など様々なデータをこの番号にひもづけて管理しています。
バーコードが開発された当初は、センサーで数字を読み取ることが難しかったため、代わりにスペース(白)とバー(黒)の並びで表す方式が採用されました。組み立てたバーコードリーダーでは、実際の商品に使われているバーコードを読み取るのは難しいため、代わりに用意した単純なバーコードを使います。
【 バーコードの構造 】
レッスンで扱うバーコードは、4桁の2進数を表しています。4つに分かれたブロックが2進数の各桁に対応していて、色が黒の場合は0を、色が白の場合は1をそれぞれ表します。
このバーコードで読み取った4桁の2進数は、10進数へ変換して「0~15」までの番号の代わりに使います。
また、バーコードは紙や商品の包装紙などに印刷して使われますが、このレッスンでは印刷する代わりに、白と黒のブロックを使いましょう。
バーコード代わりの4つのブロックは、組み立てたバーコードリーダーの中央にセットします。サーボモーターで赤外線フォトリフレタを動かし、上位の桁から順番に読み取ります。
4. 2 制作するレジシステムで行う処理
一般的なレジシステムでは、商品に付けられた番号(EANコード)に対して、商品名や価格などの複数の情報がひもづけられて、データが管理されています。そして、機械に備わっているバーコードリーダーで商品のバーコードを読み取ると、自動的にこれらの情報が引き出され、モニターに表示する仕組みになっています。
そこで、これから制作するレジシステムでは、以下の商品データをあらかじめ辞書に登録しておき、バーコードから読み取った番号から商品を検索して、名前と価格を取得します。
【 商品番号と関連付けた商品名と価格のデータ 】
商品番号 | 商品名 | 価格(円) |
---|---|---|
1 | にんじん(carrot) | 300 |
2 | たまねぎ(onion) | 200 |
3 | 豆腐(tofu) | 100 |
4 | 卵(egg) | 230 |
5 | 牛乳(milk) | 240 |
6 | リンゴジュース(apple juice) | 320 |
7 | ソーセージ(sausage) | 430 |
8 | ベーコン(bacon) | 380 |
9 | ポテトチップス(potato chips) | 200 |
10 | アイスクリーム(ice cream) | 150 |
11 | チョコレート(chocolate) | 350 |
12 | 砂糖(sugar) | 250 |
13 | 醤油(soy sauce) | 500 |
14 | 米(rice) | 2000 |
15 | ティッシュペーパー(tissue paper) | 400 |
4. 3 プログラムの作成手順
プログラムでは、それぞれバーコードリーダーとレジシステムの役割をもつ2つのクラスを作成します。
クラス名 | 役割 |
---|---|
BarcodeReader | バーコードリーダーのクラス。赤外線フォトリフレクタを使ってバーコードを読み取り、レジシステムへ読み取った番号を渡します。 |
Register | レジシステムのクラス。バーコードリーダーから渡された番号で商品を検索して、商品名と価格を表示したり、これまでの合計金額を計算したりします。 |
そして、各クラスでは次のプロパティとメソッドを定義します。
【 BarcodeReaderクラスのプロパティとメソッド 】
プロパティ名/メソッド名 | 内容 |
---|---|
POSITIONS プロパティ | バーコードの各ブロックを読み取るときのサーボモーターの角度 |
THRESHOLD プロパティ | バーコードの白と黒を見分けるための赤外線フォトリフレクタのしきい値 |
irp プロパティ | 赤外線フォトリフレクタの制御用オブジェクト |
servo プロパティ | サーボモーターの制御用オブジェクト |
__init__() メソッド | インスタンス作成時に実行される初期化処理 |
read_barcode() メソッド | バーコードを読み取り、その番号を返す処理 |
【 Registerクラスのプロパティとメソッド 】
プロパティ名/メソッド名 | 内容 |
---|---|
PRODUCTS_DATA プロパティ | 商品データを格納した辞書 |
reader プロパティ | BarcodeReader クラスのインスタンス |
total プロパティ | バーコードを読み取った商品の合計金額 |
__init__() メソッド | インスタンス作成時に実行される初期化処理 |
read() メソッド | 読み取ったバーコードの商品名と価格を表示して、さらに合計金額(total )に加算する処理 |
show_total() メソッド | 合計金額(total )を表示する処理 |
また、レジシステムの操作は、Studuino:bitのボタンAとボタンBで次の通り行います。
- ボタンAを押す: バーコードを読み取り、商品名と価格を表示する
- ボタンBを押す: これまでにバーコードを読み取った商品の合計金額を表示する
そして、これらのボタン関連の処理やBarcodeReader
クラスとRegister
クラスのインスタンスを作成するコードをmain()
関数にまとめて実行します。
では、順番にクラスと関数を定義していきましょう。
4. 4 BarcodeReaderクラスの定義
以下の手順に沿ってBarcodeReader
クラスを定義します。
■ バーコード読み取り時のサーボモーターの角度の設定
以下のサンプルコードを実行して、バーコードの各桁を読み取るときの、サーボモーターの角度を調べましょう。
【 サンプルコード 4-4-1 】
from pystubit.board import button_a, button_b
from pyatcrobo2.parts import Servomotor
servo = Servomotor("P15")
angle = 90
servo.set_angle(angle)
while True:
# ボタンAを押すたびに少しずつ赤外線フォトリフレクタが左へ移動
if button_a.was_pressed() and angle > 0:
angle -= 1
servo.set_angle(angle)
print(angle)
# ボタンBを押すたびに少しずつ赤外線フォトリフレクタが右へ移動
elif button_b.was_pressed() and angle < 180:
angle += 1
servo.set_angle(angle)
print(angle)
上のサンプルコードの実行中は、ボタンAを押すと赤外線フォトリフレクタが左に移動し、ボタンBを押すと右に移動します。また、ターミナルに移動後のサーボモーターの角度が表示されるため、その数字を確認して、各桁を読み取るときのサーボモーターの角度を決めましょう。
新しいプログラムファイルを開き、BarcodeReader
クラスを宣言します。決めた角度を左(上位の桁)から順番にタプルに格納して、POSITIONS
プロパティに入れましょう。
class BarcodeReader:
POSITIONS = (30, 70, 110, 150) # 自分で決めた角度を入力します
■ 白か黒かを判定するための赤外線フォトリフレクタのしきい値の設定
別のプログラムファイルを新たに開き、以下のサンプルコードを実行します。
【 サンプルコード 4-3-2 】
import time
from pyatcrobo2.parts import IRPhotoReflector
irp = IRPhotoReflector("P2")
while True:
time.sleep_ms(500)
print(irp.get_value())
赤外線フォトリフレクタの直下に白と黒のブロックを置き、それぞれターミナルに表示された値を確認しましょう。確認した値から白か黒かを判定するためのしきい値を決定します。
そして、元のプログラムファイルに戻り、決めたしきい値をTHRESHOLD
プロパティとして定義します。
追加【3行目】
class BarcodeReader:
POSITIONS = (26, 65, 103, 143)
THRESHOLD = 2200 # 自分で決めたしきい値を入力します
■ __init__メソッドの作成
次に、__init__()
メソッドを定義します。このBarcodeReader
クラスでは、赤外線フォトリフレクタとサーボモーターを制御します。それぞれのパーツを接続した端子名を引数に受け取り、インスタンスを作成しましょう。
追加【2行目、7行目~9行目】
class BarcodeReader:
from pyatcrobo2.parts import IRPhotoReflector, Servomotor
POSITIONS = (26, 65, 103, 143)
THRESHOLD = 2200 # 自分で決めたしきい値を入力します
def __init__(self, pin_irp, pin_servo):
self.irp = self.IRPhotoReflector(pin_irp) # 赤外線フォトリフレクタ
self.servo = self.Servomotor(pin_servo) # サーボモーター
また、バーコードの読み取りは左(上位の桁)から行います。あらかじめ、サーボモーターを動かし、赤外線フォトリフレクタを左に移動させておきましょう。
追加【10行目】
class BarcodeReader:
from pyatcrobo2.parts import IRPhotoReflector, Servomotor
POSITIONS = (26, 65, 103, 143)
THRESHOLD = 2200 # 自分で決めたしきい値を入力します
def __init__(self, pin_irp, pin_servo):
self.irp = self.IRPhotoReflector(pin_irp)
self.servo = self.Servomotor(pin_servo)
self.servo.set_angle(self.POSITIONS[0]) # 左(上位の桁の位置)へ移動
■ read_barcodeメソッドの作成
今度は、バーコードの読み取りを行うread_barcode()
メソッドを定義します。
はじめに、上位の桁から順番にブロックの色を判定して、「"0"
(黒)」または「"1"
(白)」に変換し、4桁の2進数の文字列を作成します。
追加【1行目、14行目~21行目】
import time # sleep_ms()関数を使用するためにインポート
class BarcodeReader:
from pyatcrobo2.parts import IRPhotoReflector, Servomotor
POSITIONS = (26, 65, 103, 143)
THRESHOLD = 2200 # 自分で決めたしきい値を入力します
def __init__(self, pin_irp, pin_servo):
self.irp = self.IRPhotoReflector(pin_irp)
self.servo = self.Servomotor(pin_servo)
self.servo.set_angle(self.POSITIONS[0])
def read_barcode(self):
barcode_binary = "0b" # 2進数の文字列で表したバーコード
for pos in self.POSITIONS: # 上位の桁から順番にブロックの色を読み取る
self.servo.set_angle(pos)
time.sleep_ms(300) # サーボモーターが動き終えるまで少し待つ
val = self.irp.get_value()
# 黒の場合は"0"を、白の場合は"1"を末尾に追加
barcode_binary += "0" if val < self.THRESHOLD else "1"
これで、4桁の2進数の文字列へ変換できたので、さらにint()
関数を使い、10進数の数値へ変換します。そして、この結果を戻り値として呼び出し元に返します。また、次の読み取りを円滑に行うために、赤外線フォトリフレクタを左へ戻しておきましょう。
追加【21行目~23行目】
def read_barcode(self):
barcode_binary = "0b"
for pos in self.POSITIONS:
self.servo.set_angle(pos)
time.sleep_ms(300)
val = self.irp.get_value()
barcode_binary += "0" if val < self.THRESHOLD else "1"
barcode_decimal = int(barcode_binary, 2) # 2進数から10進数への変換
self.servo.set_angle(self.POSITIONS[0]) # 左へ戻す
return barcode_decimal # 読み取りの結果を返す
最後に、読み取り中であることが伝わるように、赤外線フォトリフレクタの値を取得した後に、ブザーから短く音を鳴らすコードを追加します。これでBarcodeReader
クラスの完成です。
追加【4行目、21行目】
import time
class BarcodeReader:
from pystubit.board import buzzer # ブザーオブジェクトをインポート
from pyatcrobo2.parts import IRPhotoReflector, Servomotor
def read_barcode(self):
barcode_binary = "0b"
for pos in self.POSITIONS:
self.servo.set_angle(pos)
time.sleep_ms(300)
val = self.irp.get_value()
self.buzzer.on("C5", duration=100) # ブザーから短く音を鳴らす
barcode_binary += "0" if val < self.THRESHOLD else "1"
barcode_decimal = int(barcode_binary, 2)
self.servo.set_angle(self.POSITIONS[0])
return barcode_decimal
4. 5 Registerクラスの定義
次は、Register
クラスを定義していきましょう。
■ 商品データの登録
はじめに、最初に確認した16種類の商品データを辞書にまとめ、PRODUCTS_DATA
プロパティとして定義します。商品番号をキーにし、商品名と価格を格納した辞書をひもづけましょう。
追加【28行目~45行目】
class Register:
PRODUCTS_DATA = {
1: {"name": "carrot", "price": 300},
2: {"name": "onion", "price": 200},
3: {"name": "tofu", "price": 100},
4: {"name": "egg", "price": 230},
5: {"name": "milk", "price": 240},
6: {"name": "apple juice", "price": 320},
7: {"name": "sausage", "price": 430},
8: {"name": "bacon", "price": 380},
9: {"name": "potato chips", "price": 200},
10: {"name": "ice cream", "price": 150},
11: {"name": "chocolate", "price": 350},
12: {"name": "sugar", "price": 250},
13: {"name": "soy sauce", "price": 500},
14: {"name": "rice", "price": 2000},
15: {"name": "tissue paper", "price": 400},
}
■ __init__メソッドの作成
Register
クラスでは、BarcodeReader
クラスの機能を利用してレジの会計の処理を行います。そのため、内部にBarcodeReader
クラスのインスタンスを保有するようにします。__init__()
メソッドの引数としてインスタンスを受け取り、reader
プロパティに格納しましょう。また、商品の合計金額を記録しておくための変数をtotal
プロパティとして定義しておきましょう。
追加【47行目~49行目】
class Register:
PRODUCTS_DATA = {
1: {"name": "carrot", "price": 300},
2: {"name": "onion", "price": 200},
3: {"name": "tofu", "price": 100},
4: {"name": "egg", "price": 230},
5: {"name": "milk", "price": 240},
6: {"name": "apple juice", "price": 320},
7: {"name": "sausage", "price": 430},
8: {"name": "bacon", "price": 380},
9: {"name": "potato chips", "price": 200},
10: {"name": "ice cream", "price": 150},
11: {"name": "chocolate", "price": 350},
12: {"name": "sugar", "price": 250},
13: {"name": "soy sauce", "price": 500},
14: {"name": "rice", "price": 2000},
15: {"name": "tissue paper", "price": 400},
}
def __init__(self, reader):
self.reader = reader # BarcodeReaderクラスのインスタンス
self.total = 0 # 商品の合計金額
■ readメソッドの作成
BarcodeReader
クラスの機能でバーコードを読み取り、その番号から商品データを検索する処理をread()
メソッドとしてまとめます。もしも誤った番号(例えば登録がない「0番」など)が読み取られたときは、PRODUCTS_DATA
に存在していないため、エラーになります。そこで、try-except
文を用いた例外処理でエラーを適切に処理します。
追加【51行目~57行目】
def __init__(self, reader):
self.reader = reader
self.total = 0
def read(self):
# バーコードの読み取り(BarcodeReaderクラスのメソッドを実行)
product_num = self.reader.read_barcode() # 番号を取得
try: # try-except文でエラーをチェック
product = self.PRODUCTS_DATA[product_num] # 番号から商品データを取得
except KeyError: # 番号が存在していない場合
print("Read error.") # エラー内容を知らせる
return # ここで処理を終える
そして、商品データが取得できた場合は、商品名と価格をターミナルに表示し、合計金額に加算します。
追加【58行目~61行目】
def read(self):
product_num = self.reader.read_barcode()
try:
product = self.PRODUCTS_DATA[product_num]
except KeyError:
print("Read error.")
return
name = product["name"] # 商品名
price = product["price"] # 商品の価格
print(product["name"], ":", product["price"]) # 商品名と価格の表示
self.total += price # 合計金額への加算
■ show_totalメソッドの作成
このメソッドが実行されると、これまで読み取ったバーコードの商品価格の合計金額(total
プロパティ)をターミナルに表示します。表示後は、合計金額を「0」にリセットしておきましょう。
追加【63行目~66行目】
def read(self):
product_num = self.reader.read_barcode()
try:
product = self.PRODUCTS_DATA[product_num]
except KeyError:
print("Read error.")
return
name = product["name"]
price = product["price"]
print(product["name"], ":", product["price"])
self.total += price
def show_total(self):
if self.total != 0: # 合計金額が0の場合は呼び出されても表示しない
print("total:" , self.total) # 合計金額の表示
self.total = 0 # 0にリセット
これで2つのクラスが定義できたました。
4. 6 main関数の定義
最後に、ボタンAやボタンBが押されると、Register
クラスのメソッドを呼び出す処理をつくり、main()
関数としてまとめましょう。
追加【2行目、70行目~79行目】
import time
from pystubit.board import button_a, button_b # 2つのボタンのオブジェクトをインポート
def main():
# 各クラスのインスタンスを作成
reader = BarcodeReader("P2", "P15")
register = Register(reader)
while True:
if button_a.is_pressed(): # ボタンAが押されるとバーコードを読み取って商品名と価格を表示
register.read()
if button_b.is_pressed(): # ボタンBが押されると合計金額を表示
register.show_total()
4. 7 動作の確認
完成したプログラムが以下になります。プログラムを実行して、ターミナルからmain()
関数を呼び出しましょう。ボタンAを押して、いくつかのバーコードを読み取り、正しく商品名と価格が表示されることを確認しましょう。また、ボタンBを押すと、商品の合計金額が表示されることも確かめましょう。
【 サンプルコード 4-6-1 】
import time
from pystubit.board import button_a, button_b
class BarcodeReader:
from pystubit.board import buzzer
from pyatcrobo2.parts import IRPhotoReflector, Servomotor
POSITIONS = (30, 70, 110, 150)
THRESHOLD = 1600
def __init__(self, pin_irp, pin_servo):
self.irp = self.IRPhotoReflector(pin_irp)
self.servo = self.Servomotor(pin_servo)
self.servo.set_angle(self.POSITIONS[0])
def read_barcode(self):
barcode_binary = "0b"
for pos in self.POSITIONS:
self.servo.set_angle(pos)
time.sleep_ms(300)
val = self.irp.get_value()
self.buzzer.on("C5", duration=100)
barcode_binary += "0" if val < self.THRESHOLD else "1"
barcode_decimal = int(barcode_binary, 2)
self.servo.set_angle(self.POSITIONS[0])
return barcode_decimal
class Register:
PRODUCTS_DATA = {
1: {"name": "carrot", "price": 300},
2: {"name": "onion", "price": 200},
3: {"name": "tofu", "price": 100},
4: {"name": "egg", "price": 230},
5: {"name": "milk", "price": 240},
6: {"name": "apple juice", "price": 320},
7: {"name": "sausage", "price": 430},
8: {"name": "bacon", "price": 380},
9: {"name": "potato chips", "price": 200},
10: {"name": "ice cream", "price": 150},
11: {"name": "chocolate", "price": 350},
12: {"name": "sugar", "price": 250},
13: {"name": "soy sauce", "price": 500},
14: {"name": "rice", "price": 2000},
15: {"name": "tissue paper", "price": 400},
}
def __init__(self, reader):
self.reader = reader
self.total = 0
def read(self):
product_num = self.reader.read_barcode()
try:
product = self.PRODUCTS_DATA[product_num]
except KeyError:
print("Read error.")
return
name = product["name"]
price = product["price"]
print(product["name"], ":", product["price"])
self.total += price
def show_total(self):
if self.total != 0:
print("total:" , self.total)
self.total = 0
def main():
reader = BarcodeReader("P2", "P15")
register = Register(reader)
while True:
if button_a.is_pressed():
register.read()
if button_b.is_pressed():
register.show_total()
チャプター5
課題:在庫数量の管理機能の追加
高機能なレジシステムには、会計や商品データの管理はもちろん、売上げの管理や会員専用のポイントサービスなど店舗の運営に必要な機能が豊富に備わっています。
この課題では、これらの機能の中から在庫数量に着目して、【 サンプルコード 4-6-1 】を改良します。商品データに新たに在庫数量の情報を追加して、バーコードが読み取られるたびに在庫数量を減らす処理と、残りの数量が少なくなると、在庫不足として商品名をLEDディスプレイに表示して知らせる処理を追加してみましょう。
下の表は、初期の在庫数量と在庫不足を知らせるときの残数量の設定例です。自分で考えたものを設定しても構いません。
【 所為の在庫数量と在庫不足を知らせる残数量の設定例 】
商品番号 | 商品名 | 初期の在庫数量 | 在庫不足を知らせる残数量 |
---|---|---|---|
1 | にんじん(carrot) | 10 | 2 |
2 | たまねぎ(onion) | 10 | 2 |
3 | 豆腐(tofu) | 5 | 1 |
4 | 卵(egg) | 10 | 2 |
5 | 牛乳(milk) | 10 | 2 |
6 | リンゴジュース(apple juice) | 5 | 1 |
7 | ソーセージ(sausage) | 5 | 1 |
8 | ベーコン(bacon) | 5 | 1 |
9 | ポテトチップス(potato chips) | 5 | 1 |
10 | アイスクリーム(ice cream) | 10 | 2 |
11 | チョコレート(chocolate) | 5 | 1 |
12 | 砂糖(sugar) | 5 | 1 |
13 | 醤油(soy sauce) | 5 | 1 |
14 | 米(rice) | 5 | 1 |
15 | ティッシュペーパー(tissue paper) | 5 | 1 |
5. 1 プログラムの作成手順
上の設定例でプログラムを作成する手順を紹介します。
■ 商品データへの初期の在庫数量と在庫不足を知らせる残数量の追加
商品データを管理しているRegister
クラスのPRODUCTS_DATA
プロパティへ新たに初期在庫数量(stock
)と在庫不足を知らせる残数量(shortage
)の情報を以下のように追加します。
変更【31行目~45行目】
class Register:
PRODUCTS_DATA = {
1: {"name": "carrot", "price": 300, "stock": 10, "shortage": 2},
2: {"name": "onion", "price": 200, "stock": 10, "shortage": 2},
3: {"name": "tofu", "price": 100, "stock": 5, "shortage": 1},
4: {"name": "egg", "price": 230, "stock": 10, "shortage": 2},
5: {"name": "milk", "price": 240, "stock": 10, "shortage": 2},
6: {"name": "apple juice", "price": 320, "stock": 5, "shortage": 1},
7: {"name": "sausage", "price": 430, "stock": 5, "shortage": 1},
8: {"name": "bacon", "price": 380, "stock": 5, "shortage": 1},
9: {"name": "potato chips", "price": 200, "stock": 5, "shortage": 1},
10: {"name": "ice cream", "price": 150, "stock": 10, "shortage": 2},
11: {"name": "chocolate", "price": 350, "stock": 5, "shortage": 1},
12: {"name": "sugar", "price": 250, "stock": 5, "shortage": 1},
13: {"name": "soy sauce", "price": 500, "stock": 5, "shortage": 1},
14: {"name": "rice", "price": 2000, "stock": 5, "shortage": 1},
15: {"name": "tissue paper", "price": 400, "stock": 5, "shortage": 1},
}
■ 在庫数量を減らして在庫不足になった場合は知らせる処理の追加
在庫数量は、Register
クラスのread()
メソッドでバーコードを読み取ったときに減らします。そして、在庫数量が商品データ上の在庫不足を知らせる残数量と一致する場合は、LEDディスプレイに商品名をスクロール表示して知らせます。
追加【30行目、65行目~67行目】
class Register:
from pystubit.board import display # LEDディスプレイを使用するためにインポート
def read(self):
product_num = self.reader.read_barcode()
try:
product = self.PRODUCTS_DATA[product_num]
except KeyError:
print("Read error.")
return
name = product["name"]
price = product["price"]
print(product["name"], ":", product["price"])
self.total += price
product["stock"] -= 1 # 在庫数量を1減らす
if product["stock"] == product["shortage"]: # 在庫不足の場合
self.display.scroll(name, delay=50) # 商品名をスクロール表示
5. 2 動作の確認
以上のように、簡単な変更を行うだけで在庫管理の機能ができました。(※ もちろん実際のレジシステムはこんなに簡単なものではなく、もっと高度な処理が行われています。)それでは、完成したプログラムを実行して動作を確認してみましょう。
【 サンプルコード 5-2-1 】
import time
from pystubit.board import button_a, button_b
class BarcodeReader:
from pystubit.board import buzzer
from pyatcrobo2.parts import IRPhotoReflector, Servomotor
POSITIONS = (30, 70, 110, 150)
THRESHOLD = 1600
def __init__(self, pin_irp, pin_servo):
self.irp = self.IRPhotoReflector(pin_irp)
self.servo = self.Servomotor(pin_servo)
self.servo.set_angle(self.POSITIONS[0])
def read_barcode(self):
barcode_binary = ""
for pos in self.POSITIONS:
self.servo.set_angle(pos)
time.sleep_ms(300)
val = self.irp.get_value()
self.buzzer.on("C5", duration=100)
barcode_binary += "0" if val < self.THRESHOLD else "1"
barcode_decimal = int(barcode_binary, 2)
self.servo.set_angle(self.POSITIONS[0])
return barcode_decimal
class Register:
from pystubit.board import display
PRODUCTS_DATA = {
1: {"name": "carrot", "price": 300, "stock": 10, "shortage": 2},
2: {"name": "onion", "price": 200, "stock": 10, "shortage": 2},
3: {"name": "tofu", "price": 100, "stock": 5, "shortage": 1},
4: {"name": "egg", "price": 230, "stock": 10, "shortage": 2},
5: {"name": "milk", "price": 240, "stock": 10, "shortage": 2},
6: {"name": "apple juice", "price": 320, "stock": 5, "shortage": 1},
7: {"name": "sausage", "price": 430, "stock": 5, "shortage": 1},
8: {"name": "bacon", "price": 380, "stock": 5, "shortage": 1},
9: {"name": "potato chips", "price": 200, "stock": 5, "shortage": 1},
10: {"name": "ice cream", "price": 150, "stock": 10, "shortage": 2},
11: {"name": "chocolate", "price": 350, "stock": 5, "shortage": 1},
12: {"name": "sugar", "price": 250, "stock": 5, "shortage": 1},
13: {"name": "soy sauce", "price": 500, "stock": 5, "shortage": 1},
14: {"name": "rice", "price": 2000, "stock": 5, "shortage": 1},
15: {"name": "tissue paper", "price": 400, "stock": 5, "shortage": 1},
}
def __init__(self, reader):
self.reader = reader
self.total = 0
def read(self):
product_num = self.reader.read_barcode()
try:
product = self.PRODUCTS_DATA[product_num]
except KeyError:
print("Read error.")
return
name = product["name"]
price = product["price"]
print(product["name"], ":", product["price"])
self.total += price
product["stock"] -= 1
if product["stock"] == product["shortage"]:
self.display.scroll(name,delay=50)
def show_total(self):
if self.total != 0:
print("total:" , self.total)
self.total = 0
def main():
reader = BarcodeReader("P2", "P15")
register = Register(reader)
while True:
if button_a.is_pressed():
register.read()
if button_b.is_pressed():
register.show_total()
チャプター6
おわりに
6. 1 このレッスンのまとめ
今回は、コンピュータに適した数の表し方として、新たに「2進数」「8進数」「16進数」を学習しました。また、高度で複雑な計算を行っているコンピュータも、細かく分解していくと、単純な論理回路で構成されていることを紹介しました。
レッスンの後半では、白と黒の2つの色から2進数へ、2進数から10進数へと変換できることを利用して、バーコードリーダーを備えた簡単なレジシステムを制作しました。
このレッスンで学習したことは、どれもコンピュータを理解する上で大切な基礎知識です。しっかりと復習をして、内容を理解してから次のレッスンに臨みましょう。
6. 2 次のレッスンについて
次回のレッスンでは、コンピュータ上でのデータ量の表し方と、文字データをコンピュータ上で扱うための仕組みについて学習します。