repository to manage all files related to the makeathon farm bot project (Software + Documentation).
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

functions.py 11KB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. """
  2. created by waldhauser
  3. This file contains the functions for the mainProg.py.
  4. The functions are part of three groups, MQTT-callbacks, functions for external threads and one normal function.
  5. """
  6. import paho.mqtt.client as mqtt
  7. import json
  8. import threading
  9. import os
  10. import logging
  11. import raspy_sensors as Sensors
  12. from defines import Topics, SENSORDATA, ALLSENSORDATA
  13. def measure_send_data(plantID, actionID, client: mqtt.Client):
  14. """
  15. Reads all sensors and publishes data via MQTT in form of SENSORDATA
  16. Args:
  17. plantID (_type_): plant to measure
  18. actionID (_type_): current ID of driving action
  19. client (mqtt.Client): current mqtt client for publishing
  20. """
  21. sensorData = {}
  22. sensorData |= SENSORDATA
  23. try:
  24. Sensors.readSensors(sensorData)
  25. except Exception as e:
  26. logging.error(str(e))
  27. client.publish(Topics["ROBOT_DATA_ERROR"], str(e), qos=1)
  28. sensorData["PlantID"] = plantID
  29. sensorData["ActionID"] = actionID
  30. client.publish(Topics["ROBOT_DATA_SENSORDATA"], json.dumps(sensorData, indent=4), qos=1)
  31. #region Thread
  32. # Functions to be executed in external Thread to not block the mainProg
  33. # Functions contain the drive and sensor measurement operations
  34. def drive_plant_thread(plantID, actionID, client: mqtt.Client):
  35. """
  36. Function to drive to plant according to number from MQTT message in thread
  37. Drive programm based on hardcoded drive programms specific for each plant
  38. Only possible to drive to one plant per execution
  39. Meassure and publish data via MQTT
  40. Drive home to starting point
  41. ***If color codes are working properly this function is not needed anymore***
  42. Args:
  43. plantID (_type_): plant to measure
  44. actionID (_type_): current ID of driving action
  45. client (mqtt.Client): current mqtt client for publishing
  46. """
  47. errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/plant_{plantID}.py')
  48. if errorCode != 0:
  49. if errorCode == 65280:
  50. errorMessage = "EV3 not connected"
  51. else:
  52. errorMessage = "Motor or Sensor unplugged"
  53. logging.info(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
  54. client.publish(Topics["ROBOT_DATA_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at starting position", qos=1)
  55. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
  56. return
  57. logging.info("Measuring Sensors")
  58. measure_send_data(plantID, actionID, client)
  59. # TODO How to Handle Pictures and PlantID
  60. logging.info("Taking Picture")
  61. sendPicture(client)
  62. logging.info("Robot driving home")
  63. errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/drive_back.py {plantID}')
  64. if errorCode != 0:
  65. if errorCode == 65280:
  66. errorMessage = "EV3 not connected"
  67. else:
  68. errorMessage = "Motor or Sensor unplugged"
  69. logging.info(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
  70. client.publish(Topics["ROBOT_DATA_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at plant {plantID}", qos=1)
  71. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
  72. return
  73. logging.info("Robot home")
  74. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
  75. def drive_plant_all_thread(plantIDs: list, actionID, client: mqtt.Client):
  76. """
  77. Function to drive to plants according to numbers from MQTT message in thread, one or more plants possible
  78. PlantIDs have to be in ascending order
  79. Drive programm based on color codes in the flowerbed
  80. Meassure and publish data for all plants via MQTT
  81. Drive home to starting point
  82. ***If color codes are working properly, only this function is needed for drive operations***
  83. Args:
  84. plantIDs (list): Plants to drive to, plants have to be in ascending order
  85. actionID (_type_): ActionID from Backend for whole drive action
  86. client (mqtt.Client): current MQTT client
  87. """
  88. allPlantData = {
  89. "SensorData": [],
  90. "ActionID": ""
  91. }
  92. allPlantData["ActionID"] = actionID
  93. for plant in plantIDs:
  94. # FIXME Only possible with color codes
  95. logging.info(f"Robot Driving to plant {plant}")
  96. errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/drive_plant.py {plant}')
  97. if errorCode != 0:
  98. if errorCode == 65280:
  99. errorMessage = "EV3 not connected"
  100. else:
  101. errorMessage = "Motor or Sensor unplugged"
  102. logging.info(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
  103. client.publish(Topics["ROBOT_DATA_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at starting position", qos=1)
  104. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
  105. return
  106. # Read Sensors and store data, take picture
  107. logging.info("Measuring Sensors")
  108. try:
  109. sensordata = {}
  110. sensordata |= SENSORDATA
  111. Sensors.readSensors(sensordata)
  112. except Exception as e:
  113. logging.error(str(e))
  114. client.publish(Topics["ROBOT_DATA_ERROR"], str(e), qos=1)
  115. sensordata["PlantID"] = plant
  116. allPlantData["SensorData"].append(sensordata)
  117. # TODO How to Handle Pictures and PlantID
  118. logging.info("Taking Picture")
  119. sendPicture(client)
  120. errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/drive_arm.py {plant}')
  121. if errorCode != 0:
  122. if errorCode == 65280:
  123. errorMessage = "EV3 not connected"
  124. else:
  125. errorMessage = "Motor or Sensor unplugged"
  126. logging.info(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
  127. client.publish(Topics["ROBOT_DATA_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at plant {plant}", qos=1)
  128. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
  129. return
  130. logging.info("All Plants measured, sending data")
  131. client.publish(Topics["ROBOT_DATA_ALL"], json.dumps(allPlantData, indent=4), qos=1)
  132. logging.info("Robot driving home")
  133. errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/drive_back_straight.py')
  134. if errorCode != 0:
  135. if errorCode == 65280:
  136. errorMessage = "EV3 not connected"
  137. else:
  138. errorMessage = "Motor or Sensor unplugged"
  139. logging.info(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
  140. client.publish(Topics["ROBOT_DATA_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at plant {plant}", qos=1)
  141. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
  142. return
  143. logging.info("Robot home")
  144. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
  145. #endregion
  146. #region MQTT callbacks
  147. # Functions to be called by the MQTT callbacks
  148. # Starting the drive operations in threads or executing the small operations directly
  149. def drive_plant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
  150. """
  151. Function to drive to plant according to request
  152. Starting Drive in Thread to not block main programm
  153. ***If color codes are working properly this function is not needed anymore***
  154. Args:
  155. client (mqtt.Client): current mqtt client
  156. userdata (_type_): _description_
  157. message (mqtt.MQTTMessage): received DRIVE message with PlantID and ActionID
  158. """
  159. dictMessage = json.loads(str(message.payload.decode("UTF-8")))
  160. plantID = dictMessage["PlantID"]
  161. actionID = dictMessage["ActionID"]
  162. logging.info(f"Received Drive-request to plant {plantID}, ActionID: {actionID}")
  163. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "False", qos=1)
  164. thread = threading.Thread(target= drive_plant_thread, args=(plantID, actionID, client), daemon=True)
  165. thread.start()
  166. # FIXME Only possible with color codes
  167. def drive_plant_all(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
  168. """
  169. Function to drive to plants according to request
  170. Starting Drive in Thread to not block main programm
  171. ***If color codes are working properly, only this function is needed for drive operations***
  172. Args:
  173. client (mqtt.Client): current MQTT client
  174. userdata (_type_): _description_
  175. message (mqtt.MQTTMessage): received DRIVEALL message with PlantIDs and ActionID
  176. """
  177. dictMessage = json.loads(str(message.payload.decode("UTF-8")))
  178. plantIDs = dictMessage["PlantID"] # List of numbers
  179. actionID = dictMessage["ActionID"]
  180. logging.info(f"Received Drive-request to plants {plantIDs}, ActionID: {actionID}")
  181. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "False", qos=1)
  182. thread = threading.Thread(target= drive_plant_all_thread, args=(plantIDs, actionID, client), daemon=True)
  183. thread.start()
  184. def get_position(clients: mqtt.Client, userdata, message: mqtt.MQTTMessage):
  185. """
  186. Callback function for GPS position request
  187. Function to send actual GPS position via MQTT in form of POSITION
  188. ***Not implemented, available GPS-sensor was not working***
  189. Args:
  190. client (mqtt.Client): current mqtt client
  191. userdata (_type_): _description_
  192. message (mqtt.MQTTMessage): received message
  193. """
  194. logging.info("Received position request")
  195. # TODO Write GPS Sensor Function
  196. position = {
  197. "Position": ""
  198. }
  199. clients.publish(Topics["ROBOT_DATA_POSITION"], json.dumps(position, indent=4), qos=1)
  200. def get_batteryStatus(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
  201. """
  202. Callback function for battery status request
  203. Function to read battery status from EV3 and send via MQTT in form of BATTERY
  204. Reading battery level via SSH script execution
  205. Battery level shown in Volts:
  206. 8,5V -> 100%
  207. 5V -> 0%
  208. Args:
  209. client (mqtt.Client): current mqtt client
  210. userdata (_type_): _description_
  211. message (mqtt.MQTTMessage): received message
  212. """
  213. logging.info("Received battery status request")
  214. try:
  215. batteryLevel = int(os.popen('sshpass -p maker ssh robot@ev3dev.local cat /sys/devices/platform/battery/power_supply/lego-ev3-battery/voltage_now').read())
  216. except:
  217. logging.error("EV3 not connected")
  218. client.publish(Topics["ROBOT_DATA_ERROR"], "Robot not connected", qos=1)
  219. return
  220. batteryLevel = round(batteryLevel / 1000000, 2) # Conversion to Volt
  221. batteryLevel = batteryLevel - 5
  222. batteryLevel = round(batteryLevel / 3.5, 3) *100 # Conversion to Percentage, 8,5V -> 100%, 5V -> 0%
  223. battery = {
  224. "Battery": batteryLevel
  225. }
  226. client.publish(Topics["ROBOT_DATA_BATTERY"], json.dumps(battery, indent=4), qos=1)
  227. logging.info(f"Battery done {batteryLevel}")
  228. def sendPicture(client: mqtt.Client):
  229. """
  230. Takes picture and publish via MQTT
  231. ***Pictures can not be send until now -> assumption of reaching maximum payload limit***
  232. Args:
  233. client (mqtt.Client): current mqtt client
  234. """
  235. try:
  236. Sensors.takePicture()
  237. except Exception as e:
  238. logging.error(str(e))
  239. client.publish(Topics["ROBOT_DATA_ERROR"], str(e), qos=1)
  240. return
  241. with open("picture.png", "rb") as f:
  242. file = f.read()
  243. byteArr = bytearray(file)
  244. client.publish(Topics["ROBOT_DATA_PICTURE"], byteArr)
  245. logging.info("Picture Published")
  246. #endregion