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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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. ***Function is only neccessary for driving without color codes***
  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.error(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.error(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 = { # Sensordata for all plants
  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.error(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.error(f"{errorMessage}, Drive Plant aborted, Robot at plant {plant}")
  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. # End of drive and measure operation
  139. logging.info("All Plants measured, sending data")
  140. client.publish(Topics["ROBOT_DATA_ALL"], json.dumps(allPlantData, indent=4), qos=1)
  141. logging.info("Robot driving home")
  142. # Error handling for drive operation
  143. # Abort when drive operation at start when error occurs
  144. errorCode = os.system(f'sshpass -p maker ssh robot@ev3dev.local python3 /home/robot/Programme/drive_back_straight.py')
  145. if errorCode != 0:
  146. if errorCode == 65280:
  147. errorMessage = "EV3 not connected"
  148. else:
  149. errorMessage = "Motor or Sensor unplugged"
  150. logging.error(f"{errorMessage}, Drive Plant aborted, Robot at starting position")
  151. client.publish(Topics["ROBOT_DATA_ERROR"], f"{errorMessage}, Drive Plant aborted, Robot at plant {plant}", qos=1)
  152. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
  153. return
  154. logging.info("Robot home")
  155. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "True", qos=1)
  156. #endregion
  157. #region MQTT callbacks
  158. # Functions to be called by the MQTT callbacks
  159. # Starting the drive operations in threads or executing the small operations directly
  160. def drive_plant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
  161. """
  162. Function to drive to plant according to request
  163. Starting Drive in Thread to not block main programm
  164. ***Function is only neccessary for driving without color codes***
  165. Args:
  166. client (mqtt.Client): current mqtt client
  167. userdata (_type_): _description_
  168. message (mqtt.MQTTMessage): received DRIVE message with PlantID and ActionID
  169. """
  170. dictMessage = json.loads(str(message.payload.decode("UTF-8")))
  171. plantID = dictMessage["PlantID"]
  172. actionID = dictMessage["ActionID"]
  173. logging.info(f"Received Drive-request to plant {plantID}, ActionID: {actionID}")
  174. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "False", qos=1)
  175. thread = threading.Thread(target= drive_plant_thread, args=(plantID, actionID, client), daemon=True)
  176. thread.start()
  177. def drive_plant_all(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
  178. """
  179. Function to drive to plants according to request
  180. Starting Drive in Thread to not block main programm
  181. ***If color codes are working properly, only this function is needed for drive operations***
  182. Args:
  183. client (mqtt.Client): current MQTT client
  184. userdata (_type_): _description_
  185. message (mqtt.MQTTMessage): received DRIVEALL message with PlantIDs and ActionID
  186. """
  187. dictMessage = json.loads(str(message.payload.decode("UTF-8")))
  188. plantIDs = dictMessage["PlantID"] # List of numbers
  189. actionID = dictMessage["ActionID"]
  190. logging.info(f"Received Drive-request to plants {plantIDs}, ActionID: {actionID}")
  191. client.publish(Topics["ROBOT_DATA_ROBOTREADY"], "False", qos=1)
  192. thread = threading.Thread(target= drive_plant_all_thread, args=(plantIDs, actionID, client), daemon=True)
  193. thread.start()
  194. def get_position(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
  195. """
  196. Callback function for GPS position request
  197. Function to send actual GPS position via MQTT in form of POSITION
  198. ***Not implemented, available GPS-sensor was not working***
  199. Args:
  200. client (mqtt.Client): current mqtt client
  201. userdata (_type_): _description_
  202. message (mqtt.MQTTMessage): received message
  203. """
  204. logging.info("Received position request")
  205. position = {
  206. "Position": ""
  207. }
  208. client.publish(Topics["ROBOT_DATA_POSITION"], json.dumps(position, indent=4), qos=1)
  209. def get_batteryStatus(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
  210. """
  211. Callback function for battery status request
  212. Function to read battery status from EV3 and send via MQTT in form of BATTERY
  213. Current battery level is stored in "voltage_now" file
  214. Reading battery level via SSH script execution
  215. Battery level shown in Volts:
  216. 8,5V -> 100%
  217. 5V -> 0%
  218. Args:
  219. client (mqtt.Client): current mqtt client
  220. userdata (_type_): _description_
  221. message (mqtt.MQTTMessage): received message
  222. """
  223. logging.info("Received battery status request")
  224. try:
  225. batteryLevel = int(os.popen('sshpass -p maker ssh robot@ev3dev.local cat /sys/devices/platform/battery/power_supply/lego-ev3-battery/voltage_now').read())
  226. except:
  227. logging.error("EV3 not connected")
  228. client.publish(Topics["ROBOT_DATA_ERROR"], "EV3 not connected", qos=1)
  229. return
  230. batteryLevel = round(batteryLevel / 1000000, 2) # Conversion to Volt
  231. batteryLevel = batteryLevel - 5
  232. batteryLevel = round(batteryLevel / 3.5, 3) *100 # Conversion to Percentage, 8,5V -> 100%, 5V -> 0%
  233. battery = {
  234. "Battery": batteryLevel
  235. }
  236. client.publish(Topics["ROBOT_DATA_BATTERY"], json.dumps(battery, indent=4), qos=1)
  237. logging.info(f"Battery done {batteryLevel}")
  238. def sendPicture(client: mqtt.Client):
  239. """
  240. Takes picture and publish via MQTT
  241. ***Pictures can not be send until now -> assumption of reaching maximum payload limit***
  242. Args:
  243. client (mqtt.Client): current mqtt client
  244. """
  245. try:
  246. Sensors.takePicture()
  247. except Exception as e:
  248. logging.error(str(e))
  249. client.publish(Topics["ROBOT_DATA_ERROR"], str(e), qos=1)
  250. return
  251. with open("picture.png", "rb") as f:
  252. file = f.read()
  253. byteArr = bytearray(file)
  254. client.publish(Topics["ROBOT_DATA_PICTURE"], byteArr)
  255. logging.info("Picture Published")
  256. #endregion