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.

data_functions.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. """
  2. created by caliskan at 19.04.2023
  3. This file contains all functions, which handle the different cases.
  4. Every function should return json format with the wanted data from the database
  5. The functions are called, when data is received on the according channel
  6. """
  7. import paho.mqtt.client as mqtt
  8. from plantdatabase import PlantDataBase
  9. from defines import Topics, MAX_PLANT_COUNT
  10. import json
  11. import uuid
  12. from typing import Union
  13. from datetime import datetime
  14. import logging
  15. from robot import Robot
  16. # Robot Channel Reactions
  17. def data_sensordata(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase,
  18. robot: Robot):
  19. """
  20. This function is used to store received data from the robot inside the plant_database
  21. USAGE FOR DATA OF ONE PLANT
  22. :param client: mqtt client
  23. :param userdata:
  24. :param message: received data
  25. :param mydatabase: database information
  26. :param robot: robot object
  27. :return: none
  28. """
  29. # Load the message and convert to json
  30. str_in = str(message.payload.decode("UTF-8"))
  31. payload = json.loads(str_in)
  32. logging.info("ROBOT_DATA_SENSORDATA Received data: " + json.dumps(payload))
  33. drive_data = {
  34. "PlantID": [payload['PlantID']],
  35. "ActionID": payload['ActionID']
  36. }
  37. try:
  38. # Delete order from order list
  39. robot.delete_order(drive_data)
  40. # Save data in database
  41. mydatabase.insert_measurement_data(plant_id=payload['PlantID'],
  42. sensordata_temp=payload['AirTemperature'],
  43. sensordata_humidity=payload['AirHumidity'],
  44. sensordata_soil_moisture=payload['SoilMoisture'],
  45. sensordata_brightness=payload['Brightness'])
  46. logging.debug("Inserted to data base: " + json.dumps(payload))
  47. # Send received data to frontend
  48. action_getalldata(client, userdata, message, mydatabase)
  49. except Exception as e:
  50. logging.error("Could not delete order: " + str(e))
  51. def data_sensordataall(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase,
  52. robot: Robot):
  53. """
  54. This function is used to store received data from the robot inside the plant_database
  55. USAGE FOR DATA OF ALL PLANTS
  56. :param client:
  57. :param userdata:
  58. :param message:
  59. :param mydatabase:
  60. :param robot:
  61. :return: none
  62. """
  63. # Load the message and convert to json
  64. str_in = str(message.payload.decode("UTF-8"))
  65. payload = json.loads(str_in)
  66. logging.info("ROBOT_DATA_SENSORDATAALL Received data: " + json.dumps(payload))
  67. # Create list of plant_ids and create dataset
  68. plant_ids = []
  69. for i in payload['SensorData']:
  70. plant_ids.append(i["PlantID"])
  71. drive_data = {
  72. "PlantID": plant_ids,
  73. "ActionID": payload['ActionID']
  74. }
  75. try:
  76. # Delete order from order list
  77. robot.delete_order(drive_data)
  78. # Insert all received data files in plant_database
  79. for i in payload['SensorData']:
  80. mydatabase.insert_measurement_data(plant_id=i['PlantID'],
  81. sensordata_temp=i['AirTemperature'],
  82. sensordata_humidity=i['AirHumidity'],
  83. sensordata_soil_moisture=i['SoilMoisture'],
  84. sensordata_brightness=i['Brightness'])
  85. logging.debug("Inserted to data base: " + json.dumps(payload))
  86. # Send all the plant data to the frontend
  87. action_getalldata(client, userdata, message, mydatabase)
  88. except Exception as e:
  89. logging.error("Could not delete order: " + str(e))
  90. def data_position(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot):
  91. """
  92. This function is used to receive the robot position and insert it in the robot object
  93. :param client: mqtt client
  94. :param userdata:
  95. :param message: received data
  96. :param robot: robot object to store position
  97. :return: none
  98. """
  99. logging.info("ROBOT_DATA_POSITION Received data: " + json.dumps(message.payload.decode("UTF-8")))
  100. # Store received position data in robot object
  101. robot.store_position(json.loads(message.payload.decode("UTF-8"))["Position"])
  102. position_data = {
  103. "Position": robot.get_position(),
  104. "Timestamp": str(datetime.now())
  105. }
  106. # Send the position as a json object to the frontend channel
  107. client.publish(Topics['BACKEND_DATA_POSITION'], json.dumps(position_data))
  108. def data_battery(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot):
  109. """
  110. This function is used to receive the robot position and insert it in the robot object
  111. :param client: mqtt client object
  112. :param userdata:
  113. :param message: received data
  114. :param robot: robot object to store the received information
  115. :return: none
  116. """
  117. logging.info("ROBOT_DATA_BATTERY Received data: " + str(json.dumps(message.payload.decode("UTF-8"))))
  118. # Store battery status in robot object
  119. robot.store_battery(json.loads(message.payload.decode("UTF-8"))["Battery"])
  120. battery_data = {
  121. "Battery": robot.get_battery(),
  122. "Timestamp": str(datetime.now())
  123. }
  124. # Send Battery status and Robot-Ready Status as json objects to frontend
  125. client.publish(Topics['BACKEND_DATA_BATTERY'], json.dumps(battery_data))
  126. client.publish(Topics['BACKEND_DATA_ROBOTREADY'], str(robot.get_robot_status()))
  127. def data_error(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot):
  128. """
  129. This function is called when the robot sends an error message and forwards it to the frontend
  130. :param client: mqtt client
  131. :param userdata:
  132. :param message: received error message
  133. :param robot: robot objectg
  134. :return: none
  135. """
  136. # Store last error in robot object
  137. robot.store_last_error(message.payload.decode("UTF-8"))
  138. # Write error into server log
  139. logging.error("ROBOT_DATA_ERROR new error received from Robot: " + robot.get_last_error())
  140. # Send error data to FrontEnd Channel to display it to the user
  141. client.publish(Topics['BACKEND_DATA_ERROR'], message.payload.decode("UTF-8"))
  142. def data_robotready(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, robot: Robot):
  143. """
  144. This function is used to update the Robot-Ready Status of the Robot and inform the FrontEnd about it
  145. :param client: mqtt client
  146. :param userdata:
  147. :param message: received data
  148. :param robot: robot object
  149. :return: none
  150. """
  151. # Update the robot status
  152. robot.change_robot_status(message.payload.decode("UTF-8") == 'True')
  153. # If possible send new waiting order to the robot
  154. if robot.get_robot_status() is True and robot.get_order_number() >= 1:
  155. client.publish(Topics['ROBOT_ACTION_DRIVE'], json.dumps(robot.get_next_order()))
  156. logging.info("Waiting Order send to Robot")
  157. logging.info("ROBOT_DATA_ROBOTREADY status updated: " + str(robot.get_robot_status()))
  158. # Send new robot-ready status to frontend channel
  159. client.publish(Topics['BACKEND_DATA_ROBOTREADY'], str(robot.get_robot_status()))
  160. # FrontEnd Channel Reactions
  161. def action_drive(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase,
  162. robot: Robot):
  163. """
  164. This function is called when a drive command from the FrontEnd is received and forwards the order to the robot or
  165. stores it in the order list.
  166. :param client: mqtt client object
  167. :param userdata:
  168. :param message: information of plant to drive to
  169. :param mydatabase: plant_database
  170. :param robot: robot object
  171. :return: none
  172. """
  173. # Get PlantID from received PlantName
  174. plant_id = mydatabase.get_plant_id(plant_name=json.loads(str(message.payload.decode("UTF-8"))))
  175. # Generate a new ActionID
  176. action_id = str(uuid.uuid4())
  177. drive_data = {
  178. "PlantID": plant_id,
  179. "ActionID": action_id
  180. }
  181. # Store order in order list or discard if list already contains 5 orders
  182. if robot.get_order_number() < 6 and robot.get_robot_status() is True:
  183. robot.add_order({"PlantID": [plant_id], "ActionID": action_id})
  184. # Send order to robot, if robot is available
  185. client.publish(Topics['ROBOT_ACTION_DRIVE'], json.dumps(drive_data))
  186. logging.info("BACKEND_ACTION_DRIVE Drive Command published: " + json.dumps(drive_data))
  187. else:
  188. if robot.get_order_number() < 6:
  189. # Add to order list if robot not available
  190. robot.add_order(drive_data)
  191. logging.info("BACKEND_ACTION_DRIVE New data added to order list: " + str(drive_data))
  192. elif robot.get_order_number() >= 6:
  193. # Discard order if list full
  194. logging.error("Could not add Order to list. Order discarded")
  195. client.publish(Topics['BACKEND_DATA_ERROR'], "Could not add Order to list. Order discarded")
  196. def action_driveall(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase,
  197. robot: Robot):
  198. """
  199. This function is called when the frontend sends a drive_all command which lets the robot drive to all registered plants.
  200. Same as action_drive(), but for all plants.
  201. :param client:
  202. :param userdata:
  203. :param message:
  204. :param mydatabase:
  205. :param robot:
  206. :return: none
  207. """
  208. # Get all plantnames from the database and extract the id from them
  209. plant_names = mydatabase.get_plant_names()
  210. plant_ids = []
  211. for names in plant_names:
  212. _id = mydatabase.get_plant_id(names[0])
  213. plant_ids.append(_id)
  214. # Create a new order number
  215. action_id = str(uuid.uuid4())
  216. drive_data = {
  217. "PlantID": plant_ids,
  218. "ActionID": action_id
  219. }
  220. # Send drive command to Robot if possible (same as action_drive())
  221. if robot.get_order_number() < 6 and robot.get_robot_status() is True:
  222. robot.add_order(drive_data)
  223. client.publish(Topics['ROBOT_ACTION_DRIVEALL'], json.dumps(drive_data))
  224. logging.info("BACKEND_ACTION_DRIVEALL Drive Command published: " + json.dumps(drive_data))
  225. else:
  226. if robot.get_order_number() < 6:
  227. robot.add_order(drive_data)
  228. logging.info("BACKEND_ACTION_DRIVEALL New data added to order list: " + str(drive_data))
  229. elif robot.get_order_number() >= 6:
  230. client.publish(Topics['BACKEND_DATA_ERROR'], "Could not add Order to list. Order discarded")
  231. logging.error("Could not add Order to list. Order discarded")
  232. def action_getposition(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
  233. """
  234. This function is called when the frontend demands the robots position from the backend. It forwards the command to
  235. the robot.
  236. :param client: mqtt client object
  237. :param userdata:
  238. :param message:
  239. :param mydatabase:
  240. :return: none
  241. """
  242. # Send command to robot
  243. client.publish(Topics['ROBOT_ACTION_GETPOSITION'])
  244. logging.info("BACKEND_ACTION_GETPOSITION message forwarded to Robot")
  245. def action_getbattery(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
  246. """
  247. This function is called when the frontend demands the robots battery status from the backend. It forwards the
  248. command to the robot to get new information.
  249. :param client: mqtt client object
  250. :param userdata:
  251. :param message:
  252. :return: none
  253. """
  254. # Send command to robot
  255. client.publish(Topics['ROBOT_ACTION_GETBATTERY'])
  256. logging.info("BACKEND_ACTION_GETBATTERY message forwarded to Robot")
  257. def action_getalldata(client: mqtt.Client, userdata, message: Union[mqtt.MQTTMessage, list], mydatabase: PlantDataBase):
  258. """
  259. This function is called when the frontend demands the last data of the registered plants. It gets the last data from
  260. the local database and forwards it to the frontend
  261. :param client: mqtt client object
  262. :param userdata:
  263. :param message:
  264. :param mydatabase: database object, where the plant data is stored
  265. :return: none
  266. """
  267. # get the all PlantNames
  268. plant_names = mydatabase.get_plant_names()
  269. alldata = []
  270. # Get the latest data from all registered plant names by using the plant names
  271. for i in plant_names:
  272. alldata.append(mydatabase.get_latest_data(plant_name=i[0]))
  273. # Send the data as a list to the frontends channel
  274. client.publish(Topics['BACKEND_DATA_SENSORDATAALL'], json.dumps(alldata))
  275. logging.info("BACKEND_DATA_SENSORDATAALL got data from database:" + str(alldata))
  276. def action_newplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
  277. """
  278. This function is called when the frontend wants to register a new plant. The information of the new plant is
  279. delivered from the frontend and used to register the plant
  280. :param client: mqtt client object
  281. :param userdata:
  282. :param message: data from the frontend
  283. :param mydatabase: local database
  284. :return: none
  285. """
  286. # Load the plant data as json
  287. plant_data = json.loads(message.payload.decode("UTF-8"))
  288. # Insert the plant in the database
  289. mydatabase.insert_plant(plantname=plant_data["PlantName"], plant_id=plant_data["PlantID"])
  290. # Insert a first measurement value in the database
  291. mydatabase.insert_measurement_data(plant_id=plant_data["PlantID"],
  292. sensordata_temp=plant_data["AirTemperature"],
  293. sensordata_humidity=plant_data["AirHumidity"],
  294. sensordata_soil_moisture=plant_data["SoilMoisture"],
  295. sensordata_brightness=plant_data["Brightness"])
  296. logging.info("BACKEND_ACTION_NEWPLANT new plant data received and inserted: " + str(plant_data))
  297. # Send all new plant data to the frontend to update it
  298. action_getalldata(client, userdata, message, mydatabase)
  299. def action_configureplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
  300. """
  301. This function is called when a parameter of a plant is changed by the frontend. It updates the information in the
  302. database and sends the updated data to the frontend
  303. :param client: mqtt client object
  304. :param userdata:
  305. :param message: received data from frontend
  306. :param mydatabase: local database
  307. :return: none
  308. """
  309. # Load the received data as json
  310. plant_data = json.loads(message.payload.decode("UTF-8"))
  311. # Update the plant in the database
  312. mydatabase.configure_plant(plant_id=plant_data["PlantID"], plantname=plant_data["PlantName"])
  313. # Insert measurement_data into the database (from frontend)
  314. mydatabase.insert_measurement_data(plant_id=plant_data["PlantID"],
  315. sensordata_temp=plant_data["AirTemperature"],
  316. sensordata_humidity=plant_data["AirHumidity"],
  317. sensordata_soil_moisture=plant_data["SoilMoisture"],
  318. sensordata_brightness=plant_data["Brightness"])
  319. logging.info("BACKEND_ACTION_CONFIGUREPLANT configure plant data received and inserted: " + str(plant_data))
  320. # Update the frontend with the current data
  321. action_getalldata(client, userdata, message, mydatabase)
  322. def action_deleteplant(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
  323. """
  324. This function is called when the frontend wants to delete a registered plant from the database.
  325. :param client:
  326. :param userdata:
  327. :param message: Received data from the frontend
  328. :param mydatabase: local database object
  329. :return: none
  330. """
  331. # Load the Plant-Name from the message
  332. delete_plant = json.loads(message.payload.decode("UTF-8"))
  333. # Delete the plant
  334. mydatabase.delete_plant(plant_id=delete_plant)
  335. logging.info("BACKEND_ACTION_DELETEPLANT delete plant data received and deleted: " + str(delete_plant))
  336. # Send current data to frontend to update it
  337. action_getalldata(client, userdata, message, mydatabase)
  338. def action_countplants(client: mqtt.Client, userdata, message: mqtt.MQTTMessage, mydatabase: PlantDataBase):
  339. """
  340. This function is called when the frontend requires the count of the currently registerd plants. It sends the number
  341. and maximal possible plant number to the frontend as a json object
  342. :param client: mqtt client object
  343. :param userdata:
  344. :param message:
  345. :param mydatabase: local database
  346. :return: none
  347. """
  348. # Count plants
  349. count = mydatabase.plant_count()
  350. # Create Object and send to the FrontEnd
  351. count_payload = {
  352. "CurrentCount": count,
  353. "MaxCount": MAX_PLANT_COUNT
  354. }
  355. client.publish(Topics["BACKEND_DATA_PLANTCOUNT"], json.dumps(count_payload))
  356. logging.info("BACKEND_DATA_PLANTCOUNT forwarded plant count to FrontEnd: " + str(count_payload))