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 12KB

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