【ホームセキュリティ】ラズパイとステッピングモーターで360°見守りカメラを作成し、Discordで操作する

2024/02/25

Discord IoT ステッピングモーター ラズパイ

  • B!
サムネ
サムネ

今回はラズパイで360°見守りカメラを自作していきます。

回転機構はステッピングモーター、操作パネルはDiscordBotで作成します。

今回の目標

今回使用する物

  • RaspberryPi 4
  • ステッピングモーター
  • モータードライバー(付属品)
  • WEBカメラ
  • 5V USB電源

ステッピングモーターとは

カメラを回転させるために今回はステッピングモーターを使います。

ステッピングモーターは回転子が磁石、固定子にコイルが取り付けられているモーターです。

コイルに電流を流したり流さなかったりする制御を行うことで磁界の変化が生じ、中の磁石が回転します。

1stepという一定の角度で動作するため、正確に回転角度を決めることができます。

今回用いるステッピングモーターは「2相ユニポーラ型」のため3パターンの制御方法をが使えます。

今回は1相励磁と2相励磁よりも細かい制御ができる「1-2相励磁」を使っていきます。

1-2相励磁

次のように1step当たり8回の制御を行います。

ラズパイにはIN1~IN4をGPIOに接続します。

モータードライバーの電源はUSBなどから5Vを供給します。

モータードライバー

順番にコイルに電流を流すことで軸を回します。

実現したい動き

  1. DiscordBotにコマンドを送信(操作パネル)
  2. WEBカメラの角度をボタンで変えられるようにする(360°)
  3. キャプチャーボタンを押すと写真を撮る
  4. 左右にスウィングする(180°)
  5.  
  6. 映像をリアルタイムで見れるようにする(ローカル)

ステッピングモーターを動かしてみる

とりあえずステッピングモーターの動作確認をします。

キャプチャー機能とリアルタイム機能は未実装です。

コントロールパネル

パネル

  1. [R]30 --- 右に30°回転
  2. [R]90 --- 右に90°回転
  3. [L]30 --- 左に30°回転
  4. [L]90 --- 左に90°回転
  5. Swing --- 左右に180°回転(2往復)
  6. Reset Position --- 初期位置まで回転

初期位置への戻し方

右方向の回転を+(プラス)左方向の回転を-(マイナス)として、どれだけ移動したかを変数(position)に記録します。

  1. (例)[R]30、[R]90、[L]30の操作
  2. position = +3 +9 -3 = +9
  3. position / 3 = +3
  4.  
  5. 左方向に30°の回転を3回行うと戻れる(左に90°)

回転角の制限

無限に回転するとケーブルがねじれるので制限する必要があります。

今回は右に180°、左に180°まで回転するようにしています。

今、何°回転しているかは変数(position)の値によって分かります。

サンプルコード

stepping_motor.py
  1. import discord
  2. from discord.ext import commands
  3. import datetime
  4. import RPi.GPIO as GPIO
  5. import time
  6.  
  7. #GPIOの設定
  8. IN1 = 2
  9. IN2 = 3
  10. IN3 = 4
  11. IN4 = 5
  12. GPIO.setmode(GPIO.BCM)
  13. GPIO.setup(IN1, GPIO.OUT)
  14. GPIO.setup(IN2, GPIO.OUT)
  15. GPIO.setup(IN3, GPIO.OUT)
  16. GPIO.setup(IN4, GPIO.OUT)
  17.  
  18. #変数の初期化
  19. rotate = 0
  20. p = 0
  21. count = 0
  22. step = 0
  23. angle = 0
  24. position = 0
  25.  
  26. #1-2相励磁パターン
  27. pt_half = [[1,0,0,0],[1,1,0,0],[0,1,0,0],[0,1,1,0],[0,0,1,0],[0,0,1,1],[0,0,0,1],[1,0,0,1]]
  28.  
  29. #回転速度
  30. interval = 0.001 #0.001
  31.  
  32. #[R]30 [R]90 [L]30 [L]90 の制御
  33. def motor(p, rotate, angle, count, step):
  34. while angle < p:
  35. if rotate == 0:
  36. GPIO.output(IN1, pt_half[count][0])
  37. GPIO.output(IN2, pt_half[count][1])
  38. GPIO.output(IN3, pt_half[count][2])
  39. GPIO.output(IN4, pt_half[count][3])
  40.  
  41. else:
  42. GPIO.output(IN1, pt_half[count][3])
  43. GPIO.output(IN2, pt_half[count][2])
  44. GPIO.output(IN3, pt_half[count][1])
  45. GPIO.output(IN4, pt_half[count][0])
  46. count += 1
  47. if count == 7:
  48. count = 0
  49. else:
  50. pass
  51. step += 1
  52. if step == 10:
  53. step = 0
  54. angle += 1
  55. else:
  56. pass
  57. time.sleep(interval)
  58.  
  59. #スウィングの制御
  60. def swing(p, rotate, angle, count, step, t):
  61. #スウィング中は回転速度を遅くする
  62. interval = 0.005
  63. #左右に合計4回ループする(2往復)
  64. while t < 4:
  65. if rotate == 0:
  66. GPIO.output(IN1, pt_half[count][0])
  67. GPIO.output(IN2, pt_half[count][1])
  68. GPIO.output(IN3, pt_half[count][2])
  69. GPIO.output(IN4, pt_half[count][3])
  70.  
  71. else:
  72. GPIO.output(IN1, pt_half[count][3])
  73. GPIO.output(IN2, pt_half[count][2])
  74. GPIO.output(IN3, pt_half[count][1])
  75. GPIO.output(IN4, pt_half[count][0])
  76. count += 1
  77. if count == 7:
  78. count = 0
  79. else:
  80. pass
  81. step += 1
  82. if step == 10:
  83. step = 0
  84. angle += 1
  85. if angle == 180:
  86. angle = 0
  87. #振り切った時に停止する時間
  88. time.sleep(2)
  89.  
  90. if rotate == 0:
  91. rotate = 1
  92. t += 1
  93. else:
  94. rotate = 0
  95. t += 1
  96. else:
  97. pass
  98. time.sleep(interval)
  99.  
  100. #初期位置に戻す
  101. def reset_p(reset, rotate, angle, count, step):
  102. while angle < reset:
  103. if rotate == 0:
  104. GPIO.output(IN1, pt_half[count][0])
  105. GPIO.output(IN2, pt_half[count][1])
  106. GPIO.output(IN3, pt_half[count][2])
  107. GPIO.output(IN4, pt_half[count][3])
  108.  
  109. else:
  110. GPIO.output(IN1, pt_half[count][3])
  111. GPIO.output(IN2, pt_half[count][2])
  112. GPIO.output(IN3, pt_half[count][1])
  113. GPIO.output(IN4, pt_half[count][0])
  114. count += 1
  115. if count == 7:
  116. count = 0
  117. else:
  118. pass
  119. step += 1
  120. if step == 10:
  121. step = 0
  122. angle += 1
  123. else:
  124. pass
  125. time.sleep(interval)
  126.  
  127. #DiscordBot
  128. intents = discord.Intents.default()
  129. intents.message_content = True
  130.  
  131. bot = commands.Bot(
  132. command_prefix=commands.when_mentioned_or("!"), debug_guilds=[サーバーID], intents=intents
  133. )
  134.  
  135. @bot.event
  136. async def on_ready():
  137. print('[INFO] <' + str(datetime.datetime.now().replace(microsecond=0)) + '> Bot is active')
  138.  
  139. #コントロールパネル作成(ボタンの配置)
  140. class TestView(discord.ui.View):
  141. def __init__(self, timeout=180):
  142. super().__init__(timeout=timeout)
  143.  
  144. @discord.ui.button(label='[R]30', row=0, style=discord.ButtonStyle.green)
  145. async def r_30(self, button: discord.ui.Button, interaction: discord.Interaction):
  146. print('[INFO] <' + str(datetime.datetime.now().replace(microsecond=0)) + '> [R]30')
  147. p = 30
  148. rotate = 0
  149. global position
  150. position += 3
  151. if abs(position) > 18:
  152. position -= 3
  153. else:
  154. motor(p, rotate, angle, count, step)
  155.  
  156. await interaction.response.edit_message(content="Controlled : [R]30", view=self)
  157. @discord.ui.button(label='[R]90', row=0, style=discord.ButtonStyle.green)
  158. async def r_90(self, button: discord.ui.Button, interaction: discord.Interaction):
  159. print('[INFO] <' + str(datetime.datetime.now().replace(microsecond=0)) + '> [R]90')
  160. p = 90
  161. rotate = 0
  162.  
  163. global position
  164. position += 9
  165. if abs(position) > 18:
  166. position -= 9
  167. else:
  168. motor(p, rotate, angle, count, step)
  169.  
  170. await interaction.response.edit_message(content="Controlled : [R]90", view=self)
  171. @discord.ui.button(label='[L]30', row=0, style=discord.ButtonStyle.primary)
  172. async def l_30(self, button: discord.ui.Button, interaction: discord.Interaction):
  173. print('[INFO] <' + str(datetime.datetime.now().replace(microsecond=0)) + '> [L]30')
  174. p = 30
  175. rotate = 1
  176. global position
  177. position -= 3
  178. if abs(position) > 18:
  179. position += 3
  180. else:
  181. motor(p, rotate, angle, count, step)
  182. await interaction.response.edit_message(content="Controlled : [L]30", view=self)
  183. @discord.ui.button(label='[L]90', row=0, style=discord.ButtonStyle.primary)
  184. async def l_90(self, button: discord.ui.Button, interaction: discord.Interaction):
  185. print('[INFO] <' + str(datetime.datetime.now().replace(microsecond=0)) + '> [L]90')
  186. p = 90
  187. rotate = 1
  188. global position
  189. position -= 9
  190. if abs(position) > 18:
  191. position += 9
  192. else:
  193. motor(p, rotate, angle, count, step)
  194. await interaction.response.edit_message(content="Controlled : [L]90", view=self)
  195.  
  196. #Capture 未実装
  197. @discord.ui.button(label='Capture', row=1, style=discord.ButtonStyle.gray)
  198. async def capture(self, button: discord.ui.Button, interaction: discord.Interaction):
  199. print('[INFO] <' + str(datetime.datetime.now().replace(microsecond=0)) + '> Capture')
  200. await interaction.response.edit_message(content="Controlled : Capture", view=self)
  201.  
  202. @discord.ui.button(label='Swing', row=1, style=discord.ButtonStyle.gray)
  203. async def swing(self, button: discord.ui.Button, interaction: discord.Interaction):
  204. print('[INFO] <' + str(datetime.datetime.now().replace(microsecond=0)) + '> Swing')
  205. await interaction.response.defer()
  206. #まず初期位置に戻す
  207. global position
  208. position = position / 3
  209. if position > 0:
  210. rotate = 1
  211. elif position < 0:
  212. rotate = 0
  213. else:
  214. rotate = 1
  215. reset = 0
  216. reset = abs(position * 30)
  217. reset_p(reset, rotate, angle, count, step)
  218. position = 0
  219. #次に右方向に90°回転させる
  220. p = 90
  221. rotate = 0
  222.  
  223. motor(p, rotate, angle, count, step)
  224. time.sleep(2)
  225. #スウィングさせる
  226. p = 180
  227. rotate = 1
  228. t = 0
  229. swing(p, rotate, angle, count, step, t)
  230. #左方向に90°回転させる(初期位置に戻す)
  231. p = 90
  232. rotate = 1
  233. motor(p, rotate, angle, count, step)
  234. await interaction.followup.send(content="Controlled : Swing", view=self)
  235.  
  236. @discord.ui.button(label='Reset Position', row=1, style=discord.ButtonStyle.gray)
  237. async def p_reset(self, button: discord.ui.Button, interaction: discord.Interaction):
  238. print('[INFO] <' + str(datetime.datetime.now().replace(microsecond=0)) + '> Reset')
  239. global position
  240. position = position / 3
  241. if position > 0:
  242. rotate = 1
  243. elif position < 0:
  244. rotate = 0
  245. else:
  246. rotate = 1
  247. reset = 0
  248. reset = abs(position * 30)
  249. reset_p(reset, rotate, angle, count, step)
  250. position = 0
  251.  
  252. await interaction.response.edit_message(content="Controlled : Reset", view=self)
  253.  
  254. @bot.slash_command(name="camera")
  255. async def view_test(ctx: discord.ApplicationContext):
  256. """[ Stepping Moter Camera ] Control Panel"""
  257. view = TestView()
  258. await ctx.interaction.response.send_message(content="Control Panel", view=view)
  259.  
  260.  
  261. bot.run("DiscordBot TOKEN")

回転角度の制御

今回使用したステッピングモーターには1/64のギアが内蔵されています。

正確に計算するとstepを少数単位で制御する必要があり非常に大変です。

そのため、今回は「10stepで1°回転する」と定義 し、分かりやすくしました。

よって実際の角度には少しずれが生じます。

Writer

アイコン
Python×Raspi IoTシステム・Bot・ラズパイの記録
  • プログラミング
  • IoT
  • Python
\FOLLOW ME/ 𝕏

Ranking

blogmura_pvcount

Community

Search